From 7d462a8f08b1830e860c5220f10043fbdc7ac60d Mon Sep 17 00:00:00 2001 From: juancarmore Date: Wed, 14 Jan 2026 17:02:21 +0100 Subject: [PATCH] backend: add priority mechanism to authentication validators for improved request handling --- .../src/middlewares/auth.middleware.ts | 55 ++++++++++++++----- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/meet-ce/backend/src/middlewares/auth.middleware.ts b/meet-ce/backend/src/middlewares/auth.middleware.ts index d0af280d..ef6748ce 100644 --- a/meet-ce/backend/src/middlewares/auth.middleware.ts +++ b/meet-ce/backend/src/middlewares/auth.middleware.ts @@ -30,6 +30,12 @@ import { getAccessToken, getRoomMemberToken } from '../utils/token.utils.js'; * Each validator must implement methods to check if credentials are present and to validate them. */ export interface AuthValidator { + /** + * Returns the priority of this validator (higher number = higher priority). + * Priority order: apiKeyValidator (4) > roomMemberTokenValidator (3) > tokenAndRoleValidator (2) > allowAnonymous (1) + */ + getPriority(): number; + /** * Checks if the authentication credentials for this validator are present in the request. * This allows the middleware to skip validation for methods that are not being used. @@ -44,33 +50,40 @@ export interface AuthValidator { /** * This middleware allows to chain multiple validators to check if the request is authorized. - * First checks which authentication methods are present in the request, then validates only those methods. + * Validators are automatically sorted by priority: apiKeyValidator > roomMemberTokenValidator > tokenAndRoleValidator > allowAnonymous. + * Only validates the authentication methods that are present in the request. * If any of the validators grants access, the request is allowed to continue. - * If none of the validators grants access, the request is rejected with the most recent error. + * If a validator is present but fails validation, the error is returned immediately. + * If none are present, the request is rejected with unauthorized error. * * @param validators List of validators to check if the request is authorized * @returns RequestHandler middleware */ export const withAuth = (...validators: AuthValidator[]): RequestHandler => { return async (req: Request, res: Response, next: NextFunction) => { - let lastError: OpenViduMeetError | null = null; + // Sort validators by priority (descending) + const sortedValidators = [...validators].sort((a, b) => { + return b.getPriority() - a.getPriority(); + }); - for (const validator of validators) { - try { - if (await validator.isPresent(req)) { + for (const validator of sortedValidators) { + const isPresent = await validator.isPresent(req); + + if (isPresent) { + try { await validator.validate(req); - // If any validator grants access, allow the request to continue + // If validator grants access, allow the request to continue return next(); - } - } catch (error) { - if (error instanceof OpenViduMeetError) { - lastError = error; + } catch (error) { + // If a present validator fails, return the error immediately + const meetError = error instanceof OpenViduMeetError ? error : errorUnauthorized(); + return rejectRequestFromMeetError(res, meetError); } } } - lastError = lastError || errorUnauthorized(); - return rejectRequestFromMeetError(res, lastError); + // No validator was present, return unauthorized + return rejectRequestFromMeetError(res, errorUnauthorized()); }; }; @@ -82,6 +95,10 @@ export const withAuth = (...validators: AuthValidator[]): RequestHandler => { */ export const tokenAndRoleValidator = (...roles: MeetUserRole[]): AuthValidator => { return { + getPriority(): number { + return 2; + }, + async isPresent(req: Request): Promise { const token = getAccessToken(req); return !!token; @@ -138,6 +155,10 @@ export const tokenAndRoleValidator = (...roles: MeetUserRole[]): AuthValidator = * Validates room member tokens and sets the room member metadata in the session. */ export const roomMemberTokenValidator: AuthValidator = { + getPriority(): number { + return 3; + }, + async isPresent(req: Request): Promise { const token = getRoomMemberToken(req); return !!token; @@ -190,6 +211,10 @@ export const roomMemberTokenValidator: AuthValidator = { * Validates API keys from request headers. */ export const apiKeyValidator: AuthValidator = { + getPriority(): number { + return 4; + }, + async isPresent(req: Request): Promise { const apiKey = req.headers[INTERNAL_CONFIG.API_KEY_HEADER]; return !!apiKey; @@ -226,6 +251,10 @@ export const apiKeyValidator: AuthValidator = { * Allows unauthenticated access with an anonymous user. */ export const allowAnonymous: AuthValidator = { + getPriority(): number { + return 1; + }, + async isPresent(): Promise { // Anonymous access is always available return true;