openvidu/backend/src/middlewares/auth.middleware.ts

142 lines
4.0 KiB
TypeScript

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 { container } from '../config/dependency-injector.config.js';
import { ClaimGrants } from 'livekit-server-sdk';
import { AuthMode, UserRole } from '@typings-ce';
import {
errorUnauthorized,
errorInvalidToken,
errorInvalidTokenSubject,
errorInsufficientPermissions,
errorInvalidApiKey,
OpenViduMeetError
} from '../models/index.js';
export const withAuth = (...validators: ((req: Request) => Promise<void>)[]): RequestHandler => {
return async (req: Request, res: Response, next: NextFunction) => {
let lastError: OpenViduMeetError | null = null;
for (const validator of validators) {
try {
await validator(req);
// If any middleware granted access, it is not necessary to continue checking the rest
return next();
} catch (error) {
// If no middleware granted access, return unauthorized
if (error instanceof OpenViduMeetError) {
lastError = error;
}
}
}
if (lastError) {
return res.status(lastError.statusCode).json({ message: lastError.message });
}
return res.status(500).json({ message: 'Internal server error' });
};
};
// Configure token validatior for role-based access
export const tokenAndRoleValidator = (role: UserRole) => {
return async (req: Request) => {
const token = req.cookies[ACCESS_TOKEN_COOKIE_NAME];
if (!token) {
throw errorUnauthorized();
}
const tokenService = container.get(TokenService);
let payload: ClaimGrants;
try {
payload = await tokenService.verifyToken(token);
} catch (error) {
throw errorInvalidToken();
}
const username = payload.sub;
const userService = container.get(UserService);
const user = username ? await userService.getUser(username) : null;
if (!user) {
throw errorInvalidTokenSubject();
}
if (user.role !== role) {
throw errorInsufficientPermissions();
}
req.session = req.session || {};
req.session.user = user;
};
};
// Configure token validatior for participant access
export const participantTokenValidator = async (req: Request) => {
const token = req.cookies[PARTICIPANT_TOKEN_COOKIE_NAME];
if (!token) {
throw errorUnauthorized();
}
const tokenService = container.get(TokenService);
try {
const payload = await tokenService.verifyToken(token);
req.session = req.session || {};
req.session.tokenClaims = payload;
} catch (error) {
throw errorInvalidToken();
}
};
// Configure API key validatior
export const apiKeyValidator = async (req: Request) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey) {
throw errorUnauthorized();
}
if (apiKey !== MEET_API_KEY) {
throw errorInvalidApiKey();
}
};
// Allow anonymous access
export const allowAnonymous = async (req: Request) => {
const anonymousUser = {
username: 'anonymous',
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);
};