diff --git a/server/endpoints/api/index.js b/server/endpoints/api/index.js index e5149ad7..c5a2b8a8 100644 --- a/server/endpoints/api/index.js +++ b/server/endpoints/api/index.js @@ -4,6 +4,7 @@ const { apiAuthEndpoints } = require("./auth"); const { apiDocumentEndpoints } = require("./document"); const { apiSystemEndpoints } = require("./system"); const { apiWorkspaceEndpoints } = require("./workspace"); +const { apiUserManagementEndpoints } = require("./userManagement"); // All endpoints must be documented and pass through the validApiKey Middleware. // How to JSDoc an endpoint @@ -16,6 +17,7 @@ function developerEndpoints(app, router) { apiSystemEndpoints(router); apiWorkspaceEndpoints(router); apiDocumentEndpoints(router); + apiUserManagementEndpoints(router); } module.exports = { developerEndpoints }; diff --git a/server/endpoints/api/userManagement/index.js b/server/endpoints/api/userManagement/index.js new file mode 100644 index 00000000..656fc580 --- /dev/null +++ b/server/endpoints/api/userManagement/index.js @@ -0,0 +1,64 @@ +const { User } = require("../../../models/user"); +const { multiUserMode } = require("../../../utils/http"); +const { validApiKey } = require("../../../utils/middleware/validApiKey"); + +function apiUserManagementEndpoints(app) { + if (!app) return; + + app.get("/v1/users", [validApiKey], async (request, response) => { + /* + #swagger.tags = ['User Management'] + #swagger.description = 'List all users' + #swagger.responses[200] = { + content: { + "application/json": { + schema: { + type: 'object', + example: { + users: [ + { + "id": 1, + "username": "john_doe", + "role": "admin" + }, + { + "id": 2, + "username": "jane_smith", + "role": "default" + } + ] + } + } + } + } + } + #swagger.responses[403] = { + schema: { + "$ref": "#/definitions/InvalidAPIKey" + } + } + #swagger.responses[401] = { + description: "Instance is not in Multi-User mode. Permission denied.", + } + */ + try { + if (!multiUserMode(response)) + return response + .status(401) + .send("Instance is not in Multi-User mode. Permission denied."); + + const users = await User.where(); + const filteredUsers = users.map((user) => ({ + id: user.id, + username: user.username, + role: user.role, + })); + response.status(200).json({ users: filteredUsers }); + } catch (e) { + console.log(e.message, e); + response.sendStatus(500).end(); + } + }); +} + +module.exports = { apiUserManagementEndpoints }; diff --git a/server/swagger/init.js b/server/swagger/init.js index 4707b582..06481456 100644 --- a/server/swagger/init.js +++ b/server/swagger/init.js @@ -1,73 +1,76 @@ -const swaggerAutogen = require('swagger-autogen')({ openapi: '3.0.0' }); -const fs = require('fs') -const path = require('path') +const swaggerAutogen = require("swagger-autogen")({ openapi: "3.0.0" }); +const fs = require("fs"); +const path = require("path"); const doc = { info: { - version: '1.0.0', - title: 'AnythingLLM Developer API', - description: 'API endpoints that enable programmatic reading, writing, and updating of your AnythingLLM instance. UI supplied by Swagger.io.', + version: "1.0.0", + title: "AnythingLLM Developer API", + description: + "API endpoints that enable programmatic reading, writing, and updating of your AnythingLLM instance. UI supplied by Swagger.io.", }, // Swagger-autogen does not allow us to use relative paths as these will resolve to // http:///api in the openapi.json file, so we need to monkey-patch this post-generation. - host: '/api', - schemes: ['http'], + host: "/api", + schemes: ["http"], securityDefinitions: { BearerAuth: { - type: 'http', - scheme: 'bearer', - bearerFormat: 'JWT' - } + type: "http", + scheme: "bearer", + bearerFormat: "JWT", + }, }, - security: [ - { BearerAuth: [] } - ], + security: [{ BearerAuth: [] }], definitions: { InvalidAPIKey: { - message: 'Invalid API Key', + message: "Invalid API Key", }, - } + }, }; -const outputFile = path.resolve(__dirname, './openapi.json'); +const outputFile = path.resolve(__dirname, "./openapi.json"); const endpointsFiles = [ - '../endpoints/api/auth/index.js', - '../endpoints/api/admin/index.js', - '../endpoints/api/document/index.js', - '../endpoints/api/workspace/index.js', - '../endpoints/api/system/index.js', + "../endpoints/api/auth/index.js", + "../endpoints/api/admin/index.js", + "../endpoints/api/document/index.js", + "../endpoints/api/workspace/index.js", + "../endpoints/api/system/index.js", + "../endpoints/api/userManagement/index.js", ]; -swaggerAutogen(outputFile, endpointsFiles, doc) - .then(({ data }) => { - - // Remove Authorization parameters from arguments. - for (const path of Object.keys(data.paths)) { - if (data.paths[path].hasOwnProperty('get')) { - let parameters = data.paths[path].get?.parameters || []; - parameters = parameters.filter((arg) => arg.name !== 'Authorization'); - data.paths[path].get.parameters = parameters; - } - - if (data.paths[path].hasOwnProperty('post')) { - let parameters = data.paths[path].post?.parameters || []; - parameters = parameters.filter((arg) => arg.name !== 'Authorization'); - data.paths[path].post.parameters = parameters; - } - - if (data.paths[path].hasOwnProperty('delete')) { - let parameters = data.paths[path].delete?.parameters || []; - parameters = parameters.filter((arg) => arg.name !== 'Authorization'); - data.paths[path].delete.parameters = parameters; - } +swaggerAutogen(outputFile, endpointsFiles, doc).then(({ data }) => { + // Remove Authorization parameters from arguments. + for (const path of Object.keys(data.paths)) { + if (data.paths[path].hasOwnProperty("get")) { + let parameters = data.paths[path].get?.parameters || []; + parameters = parameters.filter((arg) => arg.name !== "Authorization"); + data.paths[path].get.parameters = parameters; } - const openApiSpec = { - ...data, - servers: [{ - url: "/api" - }] + if (data.paths[path].hasOwnProperty("post")) { + let parameters = data.paths[path].post?.parameters || []; + parameters = parameters.filter((arg) => arg.name !== "Authorization"); + data.paths[path].post.parameters = parameters; } - fs.writeFileSync(outputFile, JSON.stringify(openApiSpec, null, 2), { encoding: 'utf-8', flag: 'w' }); - console.log(`Swagger-autogen: \x1b[32mPatched servers.url ✔\x1b[0m`) - }) \ No newline at end of file + + if (data.paths[path].hasOwnProperty("delete")) { + let parameters = data.paths[path].delete?.parameters || []; + parameters = parameters.filter((arg) => arg.name !== "Authorization"); + data.paths[path].delete.parameters = parameters; + } + } + + const openApiSpec = { + ...data, + servers: [ + { + url: "/api", + }, + ], + }; + fs.writeFileSync(outputFile, JSON.stringify(openApiSpec, null, 2), { + encoding: "utf-8", + flag: "w", + }); + console.log(`Swagger-autogen: \x1b[32mPatched servers.url ✔\x1b[0m`); +}); diff --git a/server/swagger/openapi.json b/server/swagger/openapi.json index 2154c561..2a1b5543 100644 --- a/server/swagger/openapi.json +++ b/server/swagger/openapi.json @@ -2370,6 +2370,62 @@ } } } + }, + "/v1/users": { + "get": { + "tags": [ + "User Management" + ], + "description": "List all users", + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "example": { + "users": [ + { + "id": 1, + "username": "john_doe", + "role": "admin" + }, + { + "id": 2, + "username": "jane_smith", + "role": "default" + } + ] + } + } + } + } + }, + "401": { + "description": "Instance is not in Multi-User mode. Permission denied." + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidAPIKey" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/InvalidAPIKey" + } + } + } + }, + "500": { + "description": "Internal Server Error" + } + } + } } }, "components": {