anything-llm/server/endpoints/api/admin/index.js
Timothy Carambat 9a237db3d1
Implement total permission overhaul (#629)
* Implement total permission overhaul
Add explicit permissions on each flex and strict route
Patch issues with role escalation and CRUD of users
Patch permissions on all routes for coverage
Improve middleware to accept role array for clarity

* update comments

* remove permissions to API-keys for manager. Manager could generate API-key and using high-privelege api-key give themselves admin

* update sidebar permissions for multi-user and single user

* update options for mobile sidebar
2024-01-22 14:14:01 -08:00

653 lines
18 KiB
JavaScript

const { Invite } = require("../../../models/invite");
const { SystemSettings } = require("../../../models/systemSettings");
const { User } = require("../../../models/user");
const { Workspace } = require("../../../models/workspace");
const { WorkspaceChats } = require("../../../models/workspaceChats");
const { canModifyAdmin } = require("../../../utils/helpers/admin");
const { multiUserMode, reqBody } = require("../../../utils/http");
const { validApiKey } = require("../../../utils/middleware/validApiKey");
function apiAdminEndpoints(app) {
if (!app) return;
app.get("/v1/admin/is-multi-user-mode", [validApiKey], (_, response) => {
/*
#swagger.tags = ['Admin']
#swagger.description = 'Check to see if the instance is in multi-user-mode first. Methods are disabled until multi user mode is enabled via the UI.'
#swagger.responses[200] = {
content: {
"application/json": {
schema: {
type: 'object',
example: {
"isMultiUser": true
}
}
}
}
}
#swagger.responses[403] = {
schema: {
"$ref": "#/definitions/InvalidAPIKey"
}
}
*/
const isMultiUser = multiUserMode(response);
response.status(200).json({ isMultiUser });
});
app.get("/v1/admin/users", [validApiKey], async (request, response) => {
/*
#swagger.tags = ['Admin']
#swagger.description = 'Check to see if the instance is in multi-user-mode first. Methods are disabled until multi user mode is enabled via the UI.'
#swagger.responses[200] = {
content: {
"application/json": {
schema: {
type: 'object',
example: {
"users": [
{
username: "sample-sam",
role: 'default',
}
]
}
}
}
}
}
#swagger.responses[403] = {
schema: {
"$ref": "#/definitions/InvalidAPIKey"
}
}
#swagger.responses[401] = {
description: "Instance is not in Multi-User mode. Method denied",
}
*/
try {
if (!multiUserMode(response)) {
response.sendStatus(401).end();
return;
}
const users = (await User.where()).map((user) => {
const { password, ...rest } = user;
return rest;
});
response.status(200).json({ users });
} catch (e) {
console.error(e);
response.sendStatus(500).end();
}
});
app.post("/v1/admin/users/new", [validApiKey], async (request, response) => {
/*
#swagger.tags = ['Admin']
#swagger.description = 'Create a new user with username and password. Methods are disabled until multi user mode is enabled via the UI.'
#swagger.requestBody = {
description: 'Key pair object that will define the new user to add to the system.',
required: true,
type: 'object',
content: {
"application/json": {
example: {
username: "sample-sam",
password: 'hunter2',
role: 'default | admin'
}
}
}
}
#swagger.responses[200] = {
content: {
"application/json": {
schema: {
type: 'object',
example: {
user: {
id: 1,
username: 'sample-sam',
role: 'default',
},
error: null,
}
}
}
}
}
#swagger.responses[403] = {
schema: {
"$ref": "#/definitions/InvalidAPIKey"
}
}
#swagger.responses[401] = {
description: "Instance is not in Multi-User mode. Method denied",
}
*/
try {
if (!multiUserMode(response)) {
response.sendStatus(401).end();
return;
}
const newUserParams = reqBody(request);
const { user: newUser, error } = await User.create(newUserParams);
response.status(200).json({ user: newUser, error });
} catch (e) {
console.error(e);
response.sendStatus(500).end();
}
});
app.post("/v1/admin/users/:id", [validApiKey], async (request, response) => {
/*
#swagger.tags = ['Admin']
#swagger.path = '/v1/admin/users/{id}'
#swagger.parameters['id'] = {
in: 'path',
description: 'id of the user in the database.',
required: true,
type: 'string'
}
#swagger.description = 'Update existing user settings. Methods are disabled until multi user mode is enabled via the UI.'
#swagger.requestBody = {
description: 'Key pair object that will update the found user. All fields are optional and will not update unless specified.',
required: true,
type: 'object',
content: {
"application/json": {
example: {
username: "sample-sam",
password: 'hunter2',
role: 'default | admin',
suspended: 0,
}
}
}
}
#swagger.responses[200] = {
content: {
"application/json": {
schema: {
type: 'object',
example: {
success: true,
error: null,
}
}
}
}
}
#swagger.responses[403] = {
schema: {
"$ref": "#/definitions/InvalidAPIKey"
}
}
#swagger.responses[401] = {
description: "Instance is not in Multi-User mode. Method denied",
}
*/
try {
if (!multiUserMode(response)) {
response.sendStatus(401).end();
return;
}
const { id } = request.params;
const updates = reqBody(request);
const user = await User.get({ id: Number(id) });
const validAdminRoleModification = await canModifyAdmin(user, updates);
if (!validAdminRoleModification.valid) {
response
.status(200)
.json({ success: false, error: validAdminRoleModification.error });
return;
}
const { success, error } = await User.update(id, updates);
response.status(200).json({ success, error });
} catch (e) {
console.error(e);
response.sendStatus(500).end();
}
});
app.delete(
"/v1/admin/users/:id",
[validApiKey],
async (request, response) => {
/*
#swagger.tags = ['Admin']
#swagger.description = 'Delete existing user by id. Methods are disabled until multi user mode is enabled via the UI.'
#swagger.path = '/v1/admin/users/{id}'
#swagger.parameters['id'] = {
in: 'path',
description: 'id of the user in the database.',
required: true,
type: 'string'
}
#swagger.responses[200] = {
content: {
"application/json": {
schema: {
type: 'object',
example: {
success: true,
error: null,
}
}
}
}
}
#swagger.responses[403] = {
schema: {
"$ref": "#/definitions/InvalidAPIKey"
}
}
#swagger.responses[401] = {
description: "Instance is not in Multi-User mode. Method denied",
}
*/
try {
if (!multiUserMode(response)) {
response.sendStatus(401).end();
return;
}
const { id } = request.params;
await User.delete({ id });
response.status(200).json({ success: true, error: null });
} catch (e) {
console.error(e);
response.sendStatus(500).end();
}
}
);
app.get("/v1/admin/invites", [validApiKey], async (request, response) => {
/*
#swagger.tags = ['Admin']
#swagger.description = 'List all existing invitations to instance regardless of status. Methods are disabled until multi user mode is enabled via the UI.'
#swagger.responses[200] = {
content: {
"application/json": {
schema: {
type: 'object',
example: {
"invites": [
{
id: 1,
status: "pending",
code: 'abc-123',
claimedBy: null
}
]
}
}
}
}
}
#swagger.responses[403] = {
schema: {
"$ref": "#/definitions/InvalidAPIKey"
}
}
#swagger.responses[401] = {
description: "Instance is not in Multi-User mode. Method denied",
}
*/
try {
if (!multiUserMode(response)) {
response.sendStatus(401).end();
return;
}
const invites = await Invite.whereWithUsers();
response.status(200).json({ invites });
} catch (e) {
console.error(e);
response.sendStatus(500).end();
}
});
app.post("/v1/admin/invite/new", [validApiKey], async (request, response) => {
/*
#swagger.tags = ['Admin']
#swagger.description = 'Create a new invite code for someone to use to register with instance. Methods are disabled until multi user mode is enabled via the UI.'
#swagger.responses[200] = {
content: {
"application/json": {
schema: {
type: 'object',
example: {
invite: {
id: 1,
status: "pending",
code: 'abc-123',
},
error: null,
}
}
}
}
}
#swagger.responses[403] = {
schema: {
"$ref": "#/definitions/InvalidAPIKey"
}
}
#swagger.responses[401] = {
description: "Instance is not in Multi-User mode. Method denied",
}
*/
try {
if (!multiUserMode(response)) {
response.sendStatus(401).end();
return;
}
const { invite, error } = await Invite.create();
response.status(200).json({ invite, error });
} catch (e) {
console.error(e);
response.sendStatus(500).end();
}
});
app.delete(
"/v1/admin/invite/:id",
[validApiKey],
async (request, response) => {
/*
#swagger.tags = ['Admin']
#swagger.description = 'Deactivates (soft-delete) invite by id. Methods are disabled until multi user mode is enabled via the UI.'
#swagger.path = '/v1/admin/invite/{id}'
#swagger.parameters['id'] = {
in: 'path',
description: 'id of the invite in the database.',
required: true,
type: 'string'
}
#swagger.responses[200] = {
content: {
"application/json": {
schema: {
type: 'object',
example: {
success: true,
error: null,
}
}
}
}
}
#swagger.responses[403] = {
schema: {
"$ref": "#/definitions/InvalidAPIKey"
}
}
#swagger.responses[401] = {
description: "Instance is not in Multi-User mode. Method denied",
}
*/
try {
if (!multiUserMode(response)) {
response.sendStatus(401).end();
return;
}
const { id } = request.params;
const { success, error } = await Invite.deactivate(id);
response.status(200).json({ success, error });
} catch (e) {
console.error(e);
response.sendStatus(500).end();
}
}
);
app.post(
"/v1/admin/workspaces/:workspaceId/update-users",
[validApiKey],
async (request, response) => {
/*
#swagger.tags = ['Admin']
#swagger.path = '/v1/admin/workspaces/{workspaceId}/update-users'
#swagger.parameters['workspaceId'] = {
in: 'path',
description: 'id of the workspace in the database.',
required: true,
type: 'string'
}
#swagger.description = 'Overwrite workspace permissions to only be accessible by the given user ids and admins. Methods are disabled until multi user mode is enabled via the UI.'
#swagger.requestBody = {
description: 'Entire array of user ids who can access the workspace. All fields are optional and will not update unless specified.',
required: true,
type: 'object',
content: {
"application/json": {
example: {
userIds: [1,2,4,12],
}
}
}
}
#swagger.responses[200] = {
content: {
"application/json": {
schema: {
type: 'object',
example: {
success: true,
error: null,
}
}
}
}
}
#swagger.responses[403] = {
schema: {
"$ref": "#/definitions/InvalidAPIKey"
}
}
#swagger.responses[401] = {
description: "Instance is not in Multi-User mode. Method denied",
}
*/
try {
if (!multiUserMode(response)) {
response.sendStatus(401).end();
return;
}
const { workspaceId } = request.params;
const { userIds } = reqBody(request);
const { success, error } = await Workspace.updateUsers(
workspaceId,
userIds
);
response.status(200).json({ success, error });
} catch (e) {
console.error(e);
response.sendStatus(500).end();
}
}
);
app.post(
"/v1/admin/workspace-chats",
[validApiKey],
async (request, response) => {
/*
#swagger.tags = ['Admin']
#swagger.description = 'All chats in the system ordered by most recent. Methods are disabled until multi user mode is enabled via the UI.'
#swagger.requestBody = {
description: 'Page offset to show of workspace chats. All fields are optional and will not update unless specified.',
required: false,
type: 'integer',
content: {
"application/json": {
example: {
offset: 2,
}
}
}
}
#swagger.responses[200] = {
content: {
"application/json": {
schema: {
type: 'object',
example: {
success: true,
error: null,
}
}
}
}
}
#swagger.responses[403] = {
schema: {
"$ref": "#/definitions/InvalidAPIKey"
}
}
*/
try {
const pgSize = 20;
const { offset = 0 } = reqBody(request);
const chats = await WorkspaceChats.whereWithData(
{},
pgSize,
offset * pgSize,
{ id: "desc" }
);
const hasPages = (await WorkspaceChats.count()) > (offset + 1) * pgSize;
response.status(200).json({ chats: chats, hasPages });
} catch (e) {
console.error(e);
response.sendStatus(500).end();
}
}
);
app.get("/v1/admin/preferences", [validApiKey], async (request, response) => {
/*
#swagger.tags = ['Admin']
#swagger.description = 'Show all multi-user preferences for instance. Methods are disabled until multi user mode is enabled via the UI.'
#swagger.responses[200] = {
content: {
"application/json": {
schema: {
type: 'object',
example: {
settings: {
users_can_delete_workspaces: true,
limit_user_messages: false,
message_limit: 10,
}
}
}
}
}
}
#swagger.responses[403] = {
schema: {
"$ref": "#/definitions/InvalidAPIKey"
}
}
#swagger.responses[401] = {
description: "Instance is not in Multi-User mode. Method denied",
}
*/
try {
if (!multiUserMode(response)) {
response.sendStatus(401).end();
return;
}
const settings = {
users_can_delete_workspaces:
(await SystemSettings.get({ label: "users_can_delete_workspaces" }))
?.value === "true",
limit_user_messages:
(await SystemSettings.get({ label: "limit_user_messages" }))
?.value === "true",
message_limit:
Number(
(await SystemSettings.get({ label: "message_limit" }))?.value
) || 10,
};
response.status(200).json({ settings });
} catch (e) {
console.error(e);
response.sendStatus(500).end();
}
});
app.post(
"/v1/admin/preferences",
[validApiKey],
async (request, response) => {
/*
#swagger.tags = ['Admin']
#swagger.description = 'Update multi-user preferences for instance. Methods are disabled until multi user mode is enabled via the UI.'
#swagger.requestBody = {
description: 'Object with setting key and new value to set. All keys are optional and will not update unless specified.',
required: true,
type: 'object',
content: {
"application/json": {
example: {
users_can_delete_workspaces: false,
limit_user_messages: true,
message_limit: 5,
}
}
}
}
#swagger.responses[200] = {
content: {
"application/json": {
schema: {
type: 'object',
example: {
success: true,
error: null,
}
}
}
}
}
#swagger.responses[403] = {
schema: {
"$ref": "#/definitions/InvalidAPIKey"
}
}
#swagger.responses[401] = {
description: "Instance is not in Multi-User mode. Method denied",
}
*/
try {
if (!multiUserMode(response)) {
response.sendStatus(401).end();
return;
}
const updates = reqBody(request);
await SystemSettings.updateSettings(updates);
response.status(200).json({ success: true, error: null });
} catch (e) {
console.error(e);
response.sendStatus(500).end();
}
}
);
}
module.exports = { apiAdminEndpoints };