backend: Refactor authentication logic to use new global auth configuration

This commit is contained in:
juancarmore 2025-03-25 13:06:52 +01:00
parent 499e36786b
commit 042f7f2fd4
4 changed files with 72 additions and 36 deletions

View File

@ -20,7 +20,7 @@ export const login = async (req: Request, res: Response) => {
const { username, password } = req.body as { username: string; password: string }; const { username, password } = req.body as { username: string; password: string };
const authService = container.get(AuthService); const authService = container.get(AuthService);
const user = authService.authenticate(username, password); const user = await authService.authenticate(username, password);
if (!user) { if (!user) {
logger.warn('Login failed'); logger.warn('Login failed');
@ -75,7 +75,7 @@ export const refreshToken = async (req: Request, res: Response) => {
const username = payload.sub; const username = payload.sub;
const userService = container.get(UserService); const userService = container.get(UserService);
const user = username ? userService.getUser(username) : null; const user = username ? await userService.getUser(username) : null;
if (!user) { if (!user) {
logger.warn('Invalid refresh token subject'); logger.warn('Invalid refresh token subject');

View File

@ -1,14 +1,9 @@
import { NextFunction, Request, RequestHandler, Response } from 'express'; import { NextFunction, Request, RequestHandler, Response } from 'express';
import { TokenService, UserService } from '../services/index.js'; import { GlobalPreferencesService, LoggerService, TokenService, UserService } from '../services/index.js';
import { import { ACCESS_TOKEN_COOKIE_NAME, MEET_API_KEY, PARTICIPANT_TOKEN_COOKIE_NAME } from '../environment.js';
ACCESS_TOKEN_COOKIE_NAME,
MEET_API_KEY,
MEET_PRIVATE_ACCESS,
PARTICIPANT_TOKEN_COOKIE_NAME
} from '../environment.js';
import { container } from '../config/dependency-injector.config.js'; import { container } from '../config/dependency-injector.config.js';
import { ClaimGrants } from 'livekit-server-sdk'; import { ClaimGrants } from 'livekit-server-sdk';
import { Role } from '@typings-ce'; import { AuthMode, UserRole } from '@typings-ce';
import { import {
errorUnauthorized, errorUnauthorized,
errorInvalidToken, errorInvalidToken,
@ -22,9 +17,9 @@ export const withAuth = (...validators: ((req: Request) => Promise<void>)[]): Re
return async (req: Request, res: Response, next: NextFunction) => { return async (req: Request, res: Response, next: NextFunction) => {
let lastError: OpenViduMeetError | null = null; let lastError: OpenViduMeetError | null = null;
for (const middleware of validators) { for (const validator of validators) {
try { try {
await middleware(req); await validator(req);
// If any middleware granted access, it is not necessary to continue checking the rest // If any middleware granted access, it is not necessary to continue checking the rest
return next(); return next();
} catch (error) { } catch (error) {
@ -44,13 +39,8 @@ export const withAuth = (...validators: ((req: Request) => Promise<void>)[]): Re
}; };
// Configure token validatior for role-based access // Configure token validatior for role-based access
export const tokenAndRoleValidator = (role: Role) => { export const tokenAndRoleValidator = (role: UserRole) => {
return async (req: Request) => { return async (req: Request) => {
// Skip token validation if role is USER and access is public
if (role == Role.USER && MEET_PRIVATE_ACCESS === 'false') {
return;
}
const token = req.cookies[ACCESS_TOKEN_COOKIE_NAME]; const token = req.cookies[ACCESS_TOKEN_COOKIE_NAME];
if (!token) { if (!token) {
@ -68,7 +58,7 @@ export const tokenAndRoleValidator = (role: Role) => {
const username = payload.sub; const username = payload.sub;
const userService = container.get(UserService); const userService = container.get(UserService);
const user = username ? userService.getUser(username) : null; const user = username ? await userService.getUser(username) : null;
if (!user) { if (!user) {
throw errorInvalidTokenSubject(); throw errorInvalidTokenSubject();

View File

@ -1,15 +1,21 @@
import { MEET_ADMIN_SECRET, MEET_ADMIN_USER, MEET_PRIVATE_ACCESS, MEET_SECRET, MEET_USER } from '../environment.js'; import { MEET_ADMIN_SECRET, MEET_ADMIN_USER } from '../environment.js';
import { inject, injectable } from '../config/dependency-injector.config.js'; import { inject, injectable } from '../config/dependency-injector.config.js';
import { User } from '@typings-ce'; import { AuthMode, AuthType, SingleUserAuth, User, ValidAuthMethod } from '@typings-ce';
import { UserService } from './user.service.js'; import { UserService } from './user.service.js';
import { GlobalPreferencesService } from './preferences/global-preferences.service.js';
import { LoggerService } from './logger.service.js';
@injectable() @injectable()
export class AuthService { export class AuthService {
constructor(@inject(UserService) protected userService: UserService) {} constructor(
@inject(LoggerService) protected logger: LoggerService,
@inject(UserService) protected userService: UserService,
@inject(GlobalPreferencesService) protected globalPrefService: GlobalPreferencesService
) {}
authenticate(username: string, password: string): User | null { async authenticate(username: string, password: string): Promise<User | null> {
const isAdmin = this.authenticateAdmin(username, password); const isAdmin = this.authenticateAdmin(username, password);
const isUser = this.authenticateUser(username, password); const isUser = await this.authenticateUser(username, password);
if (isAdmin || isUser) { if (isAdmin || isUser) {
return this.userService.getUser(username); return this.userService.getUser(username);
@ -22,9 +28,28 @@ export class AuthService {
return username === MEET_ADMIN_USER && password === MEET_ADMIN_SECRET; return username === MEET_ADMIN_USER && password === MEET_ADMIN_SECRET;
} }
private authenticateUser(username: string, password: string): boolean { private async authenticateUser(username: string, password: string): Promise<boolean> {
if (MEET_PRIVATE_ACCESS === 'true') { let requireAuthForRoomCreation: boolean;
return username === MEET_USER && password === MEET_SECRET; let authMode: AuthMode;
let authMethod: ValidAuthMethod;
try {
const { securityPreferences } = await this.globalPrefService.getGlobalPreferences();
requireAuthForRoomCreation = securityPreferences.roomCreationPolicy.requireAuthentication;
({ authMode, method: authMethod } = securityPreferences.authentication);
} catch (error) {
this.logger.error('Error checking authentication preferences:' + error);
return false;
}
if (requireAuthForRoomCreation || authMode !== AuthMode.NONE) {
if (authMethod.type !== AuthType.SINGLE_USER) {
return false;
}
const { username: configuredUsername, passwordHash: configurePassword } = (authMethod as SingleUserAuth)
.credentials;
return username === configuredUsername && password === configurePassword;
} }
return false; return false;

View File

@ -1,21 +1,42 @@
import { MEET_ADMIN_USER, MEET_USER } from '../environment.js'; import { MEET_ADMIN_USER } from '../environment.js';
import { injectable } from '../config/dependency-injector.config.js'; import { inject, injectable } from '../config/dependency-injector.config.js';
import { Role, User } from '@typings-ce'; import { AuthType, UserRole, SingleUserAuth, User } from '@typings-ce';
import { LoggerService } from './logger.service.js';
import { GlobalPreferencesService } from './preferences/global-preferences.service.js';
@injectable() @injectable()
export class UserService { export class UserService {
getUser(username: string): User | null { constructor(
@inject(LoggerService) protected logger: LoggerService,
@inject(GlobalPreferencesService) protected globalPrefService: GlobalPreferencesService
) {}
async getUser(username: string): Promise<User | null> {
if (username === MEET_ADMIN_USER) { if (username === MEET_ADMIN_USER) {
return { return {
username: MEET_ADMIN_USER, username: MEET_ADMIN_USER,
role: Role.ADMIN role: UserRole.ADMIN
}; };
} }
if (username === MEET_USER) { let configuredUsername: string | undefined;
try {
const { securityPreferences } = await this.globalPrefService.getGlobalPreferences();
const method = securityPreferences.authentication.method;
if (method.type === AuthType.SINGLE_USER) {
configuredUsername = (method as SingleUserAuth).credentials.username;
}
} catch (error) {
this.logger.error('Error checking room creation policy:' + error);
return null;
}
if (username === configuredUsername) {
return { return {
username: MEET_USER, username: configuredUsername,
role: Role.USER role: UserRole.USER
}; };
} }