From 117cca61902ce9ca9c37e0609f976b993430f25f Mon Sep 17 00:00:00 2001 From: bh0fer Date: Wed, 4 Feb 2026 09:23:42 +0100 Subject: [PATCH 1/3] add rate limit --- src/auth.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/auth.ts b/src/auth.ts index beb345d..35aad93 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -46,6 +46,10 @@ 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({ + rateLimit: { + window: 10, // time window in seconds + max: 1000 // max requests in the window + }, user: { additionalFields: { firstName: { type: 'string', required: true, input: true }, From 61e9c4791ac9bdeb7852e7b98d75014d97e52ec3 Mon Sep 17 00:00:00 2001 From: bh0fer Date: Fri, 6 Feb 2026 13:56:05 +0000 Subject: [PATCH 2/3] chore: add post endpoints for /documents --- src/auth.ts | 1 + src/controllers/documentRoots.ts | 43 ++++++++++++++++++++++++++++++++ src/models/Document.ts | 5 +++- src/routes/authConfig.ts | 6 ++++- src/routes/router.ts | 18 +++++++++---- src/server.ts | 2 ++ 6 files changed, 68 insertions(+), 7 deletions(-) diff --git a/src/auth.ts b/src/auth.ts index 35aad93..5937288 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -46,6 +46,7 @@ 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 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..b49e849 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, @@ -65,6 +67,11 @@ router.put('/users/:id', updateUser); * @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,10 +100,6 @@ 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); /** @@ -105,6 +108,11 @@ router.post('/documents', createDocument); * @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 + */ +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; From 60f50c9043c4ffba1ec76a72630976281f59101a Mon Sep 17 00:00:00 2001 From: bh0fer Date: Fri, 6 Feb 2026 17:06:29 +0000 Subject: [PATCH 3/3] add todo --- src/routes/router.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/routes/router.ts b/src/routes/router.ts index b49e849..bb2e68f 100644 --- a/src/routes/router.ts +++ b/src/routes/router.ts @@ -62,6 +62,8 @@ 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[] @@ -103,6 +105,8 @@ router.get('/documentRoots/:id/permissions', allPermissions); 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 @@ -111,6 +115,8 @@ 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);