diff --git a/src/auth.ts b/src/auth.ts index beb345d..5937288 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -46,6 +46,11 @@ const HAS_PROVIDER_GH = !!process.env.BETTER_AUTH_GITHUB_ID && !!process.env.BET const HAS_PROVIDER_MSFT = !!process.env.MSAL_CLIENT_ID && !!process.env.MSAL_CLIENT_SECRET; export const auth = betterAuth({ + // baseUrl: set over BETTER_AUTH_URL, + rateLimit: { + window: 10, // time window in seconds + max: 1000 // max requests in the window + }, user: { additionalFields: { firstName: { type: 'string', required: true, input: true }, diff --git a/src/controllers/documentRoots.ts b/src/controllers/documentRoots.ts index 55b403d..9cbff51 100644 --- a/src/controllers/documentRoots.ts +++ b/src/controllers/documentRoots.ts @@ -45,6 +45,29 @@ export const findManyFor: RequestHandler< res.json(documents); }; +export const findMultipleFor: RequestHandler< + { id: string /** userId */ }, + any, + { documentRootIds: string[]; ignoreMissingRoots?: boolean; type?: string } +> = async (req, res, next) => { + if (!req.params.id) { + throw new HTTP400Error('Missing user id'); + } + const canLoad = req.user!.id === req.params.id || hasElevatedAccess(req.user?.role); + if (!canLoad) { + throw new HTTP403Error('Not Authorized'); + } + const ids = req.body.documentRootIds; + if (ids.length === 0) { + return res.json([]); + } + const documents = await DocumentRoot.findManyModels(req.params.id, ids, { + ignoreMissingRoots: !!req.body.ignoreMissingRoots, + documentType: req.body.type && (req.body.type as string | undefined) + }); + res.json(documents); +}; + export const allDocuments: RequestHandler = async (req, res, next) => { if (!hasElevatedAccess((req as any).user!.role)) { throw new HTTP403Error('Not Authorized'); @@ -57,6 +80,26 @@ export const allDocuments: RequestHandler = a res.json(documents); }; +export const multipleDocuments: RequestHandler< + any, + any, + { documentRootIds: string[]; userId?: string } +> = async (req, res, next) => { + const user = req.user; + if (!hasElevatedAccess(user.role)) { + throw new HTTP403Error('Not Authorized'); + } + const ids = req.body.documentRootIds; + if (ids.length === 0) { + return res.json([]); + } + const documents = await Document.allOfDocumentRoots( + { role: user.role, id: req.body.userId ?? user.id }, + ids + ); + res.json(documents); +}; + export const create: RequestHandler<{ id: string }, any, CreateConfig | undefined> = async ( req, res, diff --git a/src/models/Document.ts b/src/models/Document.ts index ac3c808..a3580d3 100644 --- a/src/models/Document.ts +++ b/src/models/Document.ts @@ -266,7 +266,10 @@ function Document(db: PrismaClient['document']) { return model; }, - async allOfDocumentRoots(actor: User, documentRootIds: string[]): Promise { + async allOfDocumentRoots( + actor: User | { role: Role | string; id: string }, + documentRootIds: string[] + ): Promise { if (!hasElevatedAccess(actor.role)) { throw new HTTP403Error('Not authorized'); } diff --git a/src/routes/authConfig.ts b/src/routes/authConfig.ts index 4b4684c..38d43a3 100644 --- a/src/routes/authConfig.ts +++ b/src/routes/authConfig.ts @@ -23,7 +23,7 @@ const authConfig: Config = { userFind: { path: '/users/:id', access: [{ methods: ['GET', 'PUT'], minRole: Role.STUDENT }] }, usersDocumentRoots: { path: '/users/:id/documentRoots', - access: [{ methods: ['GET'], minRole: Role.STUDENT }] + access: [{ methods: ['GET', 'POST'], minRole: Role.STUDENT }] }, studentGroups: { path: '/studentGroups', @@ -40,6 +40,10 @@ const authConfig: Config = { path: '/documents', access: [{ methods: ['GET', 'PUT', 'POST', 'DELETE'], minRole: Role.STUDENT }] }, + documentsMultiple: { + path: '/documents/multiple', + access: [{ methods: ['POST'], minRole: Role.TEACHER }] + }, documentRoots: { path: '/documentRoots', access: [ diff --git a/src/routes/router.ts b/src/routes/router.ts index 22a0030..bb2e68f 100644 --- a/src/routes/router.ts +++ b/src/routes/router.ts @@ -34,8 +34,10 @@ import { update as updateDocumentRoot, permissions as allPermissions, findManyFor as findManyDocumentRootsFor, + findMultipleFor as findMultipleDocumentRootsFor, allDocuments, - destroy as deleteDocumentRoot + destroy as deleteDocumentRoot, + multipleDocuments } from '../controllers/documentRoots.js'; import { allowedActions, @@ -60,11 +62,18 @@ router.get('/users', allUsers); router.get('/users/:id', findUser); router.put('/users/:id', updateUser); /** + * TODO: remove once [post] /users/:id/documentRoots is established and clients are updated + * * @optional ?ignoreMissingRoots: boolean * @optional ?type: string -> filter included documents by provided type * @requires ?ids: string[] */ router.get('/users/:id/documentRoots', findManyDocumentRootsFor); +/** + * a post endpoint to prevent issues with long query strings when requesting + * many document roots for a user + */ +router.post('/users/:id/documentRoots', findMultipleDocumentRootsFor); router.get('/studentGroups', allStudentGroups); router.post('/studentGroups', createStudentGroup); @@ -93,18 +102,23 @@ router.post('/documentRoots/:id', createDocumentRoot); router.put('/documentRoots/:id', updateDocumentRoot); router.delete('/documentRoots/:id', deleteDocumentRoot); router.get('/documentRoots/:id/permissions', allPermissions); -/** - * TODO: Reactivate once the controller's permissions are updated. - * router.get('/documents', allDocuments); - */ router.post('/documents', createDocument); /** + * TODO: remove once /documents/multiple is established and clients are updated + * * @adminOnly --> handle in controller * Returns all documents which are linked to the **document roots**. * @requires ?rids: string[] -> the document root ids */ router.get('/documents', allDocuments); +/** + * a post endpoint to prevent issues with long query strings when requesting + * many document roots for a user + * @adminOnly --> handle in controller + * Returns all documents which are linked to the **document roots**. + */ +router.post('/documents/multiple', multipleDocuments); router.get('/documents/:id', findDocument); router.put('/documents/:id', updateDocument); router.put('/documents/:id/linkTo/:parentId', linkDocument); diff --git a/src/server.ts b/src/server.ts index 9481a5e..d959592 100644 --- a/src/server.ts +++ b/src/server.ts @@ -3,6 +3,8 @@ import { initialize as initializeSocketIo } from './socketIoServer.js'; import http from 'http'; import * as Sentry from '@sentry/node'; import Logger from './utils/logger.js'; +import dotenv from 'dotenv'; +dotenv.config(); const PORT = process.env.PORT || 3002;