From 930541c725ccfecdcee1cf34d87f8827c39f881b Mon Sep 17 00:00:00 2001 From: juancarmore Date: Wed, 26 Mar 2025 12:43:24 +0100 Subject: [PATCH] backend: Enhance authentication middleware to always include user that is performing the action in a resource --- backend/src/environment.ts | 10 +++ backend/src/middlewares/auth.middleware.ts | 82 +++++++++++++--------- backend/src/routes/auth.routes.ts | 9 ++- 3 files changed, 67 insertions(+), 34 deletions(-) diff --git a/backend/src/environment.ts b/backend/src/environment.ts index f24bd1a..b220f26 100644 --- a/backend/src/environment.ts +++ b/backend/src/environment.ts @@ -61,14 +61,24 @@ export const { ENABLED_MODULES = '' } = process.env; +// Base paths for the API export const MEET_API_BASE_PATH = '/meet/api'; export const MEET_INTERNAL_API_BASE_PATH_V1 = '/meet/internal-api/v1'; export const MEET_API_BASE_PATH_V1 = MEET_API_BASE_PATH + '/v1'; + +// Cookie names export const PARTICIPANT_TOKEN_COOKIE_NAME = 'OvMeetParticipantToken'; export const ACCESS_TOKEN_COOKIE_NAME = 'OvMeetAccessToken'; export const REFRESH_TOKEN_COOKIE_NAME = 'OvMeetRefreshToken'; + +// Fixed usernames +export const MEET_ANONYMOUS_USER = 'anonymous'; +export const MEET_API_USER = 'api-user'; + +// S3 prefixes export const MEET_S3_ROOMS_PREFIX = 'rooms'; export const MEET_S3_RECORDINGS_PREFIX = 'recordings'; + // Time to live for the active recording lock in Redis export const MEET_RECORDING_LOCK_TTL = '6h'; diff --git a/backend/src/middlewares/auth.middleware.ts b/backend/src/middlewares/auth.middleware.ts index db1575d..dadce54 100644 --- a/backend/src/middlewares/auth.middleware.ts +++ b/backend/src/middlewares/auth.middleware.ts @@ -1,9 +1,15 @@ import { NextFunction, Request, RequestHandler, Response } from 'express'; -import { GlobalPreferencesService, LoggerService, TokenService, UserService } from '../services/index.js'; -import { ACCESS_TOKEN_COOKIE_NAME, MEET_API_KEY, PARTICIPANT_TOKEN_COOKIE_NAME } from '../environment.js'; +import { LoggerService, TokenService, UserService } from '../services/index.js'; +import { + ACCESS_TOKEN_COOKIE_NAME, + MEET_ANONYMOUS_USER, + MEET_API_KEY, + MEET_API_USER, + PARTICIPANT_TOKEN_COOKIE_NAME +} from '../environment.js'; import { container } from '../config/dependency-injector.config.js'; import { ClaimGrants } from 'livekit-server-sdk'; -import { AuthMode, UserRole } from '@typings-ce'; +import { User, UserRole } from '@typings-ce'; import { errorUnauthorized, errorInvalidToken, @@ -13,6 +19,14 @@ import { OpenViduMeetError } from '../models/index.js'; +/** + * This middleware allows to chain multiple validators to check if the request is authorized. + * If any of the validators grants access, the request is allowed to continue, skipping the rest of the validators. + * If none of the validators grants access, the request is rejected with an unauthorized error. + * + * @param validators List of validators to check if the request is authorized + * @returns RequestHandler middleware + */ export const withAuth = (...validators: ((req: Request) => Promise)[]): RequestHandler => { return async (req: Request, res: Response, next: NextFunction) => { let lastError: OpenViduMeetError | null = null; @@ -103,39 +117,43 @@ export const apiKeyValidator = async (req: Request) => { if (apiKey !== MEET_API_KEY) { throw errorInvalidApiKey(); } + + const apiUser = { + username: MEET_API_USER, + role: UserRole.APP + }; + + req.session = req.session || {}; + req.session.user = apiUser; }; // Allow anonymous access export const allowAnonymous = async (req: Request) => { - const anonymousUser = { - username: 'anonymous', - role: UserRole.USER - }; + let user: User | null = null; + + // Check if there is a user already authenticated + const token = req.cookies[ACCESS_TOKEN_COOKIE_NAME]; + + if (token) { + try { + const tokenService = container.get(TokenService); + const payload = await tokenService.verifyToken(token); + const username = payload.sub; + const userService = container.get(UserService); + user = username ? await userService.getUser(username) : null; + } catch (error) { + const logger = container.get(LoggerService); + logger.debug('Token found but invalid:' + error); + } + } + + if (!user) { + user = { + username: MEET_ANONYMOUS_USER, + role: UserRole.USER + }; + } req.session = req.session || {}; - req.session.user = anonymousUser; -}; - -export const configureProfileAuth = async (req: Request, res: Response, next: NextFunction) => { - const logger = container.get(LoggerService); - const globalPrefService = container.get(GlobalPreferencesService); - let requireAuthForRoomCreation: boolean; - let authMode: AuthMode; - - try { - const { securityPreferences } = await globalPrefService.getGlobalPreferences(); - requireAuthForRoomCreation = securityPreferences.roomCreationPolicy.requireAuthentication; - authMode = securityPreferences.authentication.authMode; - } catch (error) { - logger.error('Error checking authentication preferences:' + error); - return res.status(500).json({ message: 'Internal server error' }); - } - - const authValidators = [tokenAndRoleValidator(UserRole.ADMIN)]; - - if (requireAuthForRoomCreation || authMode !== AuthMode.NONE) { - authValidators.push(tokenAndRoleValidator(UserRole.USER)); - } - - return withAuth(...authValidators)(req, res, next); + req.session.user = user; }; diff --git a/backend/src/routes/auth.routes.ts b/backend/src/routes/auth.routes.ts index fff234c..dbd457b 100644 --- a/backend/src/routes/auth.routes.ts +++ b/backend/src/routes/auth.routes.ts @@ -3,8 +3,9 @@ import { Router } from 'express'; import bodyParser from 'body-parser'; import * as authCtrl from '../controllers/auth.controller.js'; import rateLimit from 'express-rate-limit'; -import { configureProfileAuth } from '../middlewares/auth.middleware.js'; +import { tokenAndRoleValidator, withAuth } from '../middlewares/auth.middleware.js'; import { validateLoginRequest } from '../middlewares/request-validators/auth-validator.middleware.js'; +import { UserRole } from '@typings-ce'; export const authRouter = Router(); @@ -22,4 +23,8 @@ authRouter.use(bodyParser.json()); authRouter.post('/login', validateLoginRequest, loginLimiter, authCtrl.login); authRouter.post('/logout', authCtrl.logout); authRouter.post('/refresh', authCtrl.refreshToken); -authRouter.get('/profile', configureProfileAuth, authCtrl.getProfile); +authRouter.get( + '/profile', + withAuth(tokenAndRoleValidator(UserRole.ADMIN), tokenAndRoleValidator(UserRole.USER)), + authCtrl.getProfile +);