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 authService = container.get(AuthService);
const user = authService.authenticate(username, password);
const user = await authService.authenticate(username, password);
if (!user) {
logger.warn('Login failed');
@ -75,7 +75,7 @@ export const refreshToken = async (req: Request, res: Response) => {
const username = payload.sub;
const userService = container.get(UserService);
const user = username ? userService.getUser(username) : null;
const user = username ? await userService.getUser(username) : null;
if (!user) {
logger.warn('Invalid refresh token subject');
@ -95,7 +95,7 @@ export const refreshToken = async (req: Request, res: Response) => {
export const getProfile = (req: Request, res: Response) => {
const user = req.session?.user;
if (!user) {
return res.status(401).json({ message: 'Unauthorized' });
}

View File

@ -1,14 +1,9 @@
import { NextFunction, Request, RequestHandler, Response } from 'express';
import { TokenService, UserService } from '../services/index.js';
import {
ACCESS_TOKEN_COOKIE_NAME,
MEET_API_KEY,
MEET_PRIVATE_ACCESS,
PARTICIPANT_TOKEN_COOKIE_NAME
} from '../environment.js';
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 { Role } from '@typings-ce';
import { AuthMode, UserRole } from '@typings-ce';
import {
errorUnauthorized,
errorInvalidToken,
@ -22,9 +17,9 @@ export const withAuth = (...validators: ((req: Request) => Promise<void>)[]): Re
return async (req: Request, res: Response, next: NextFunction) => {
let lastError: OpenViduMeetError | null = null;
for (const middleware of validators) {
for (const validator of validators) {
try {
await middleware(req);
await validator(req);
// If any middleware granted access, it is not necessary to continue checking the rest
return next();
} catch (error) {
@ -44,13 +39,8 @@ export const withAuth = (...validators: ((req: Request) => Promise<void>)[]): Re
};
// Configure token validatior for role-based access
export const tokenAndRoleValidator = (role: Role) => {
export const tokenAndRoleValidator = (role: UserRole) => {
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];
if (!token) {
@ -68,7 +58,7 @@ export const tokenAndRoleValidator = (role: Role) => {
const username = payload.sub;
const userService = container.get(UserService);
const user = username ? userService.getUser(username) : null;
const user = username ? await userService.getUser(username) : null;
if (!user) {
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 { User } from '@typings-ce';
import { AuthMode, AuthType, SingleUserAuth, User, ValidAuthMethod } from '@typings-ce';
import { UserService } from './user.service.js';
import { GlobalPreferencesService } from './preferences/global-preferences.service.js';
import { LoggerService } from './logger.service.js';
@injectable()
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 isUser = this.authenticateUser(username, password);
const isUser = await this.authenticateUser(username, password);
if (isAdmin || isUser) {
return this.userService.getUser(username);
@ -22,9 +28,28 @@ export class AuthService {
return username === MEET_ADMIN_USER && password === MEET_ADMIN_SECRET;
}
private authenticateUser(username: string, password: string): boolean {
if (MEET_PRIVATE_ACCESS === 'true') {
return username === MEET_USER && password === MEET_SECRET;
private async authenticateUser(username: string, password: string): Promise<boolean> {
let requireAuthForRoomCreation: boolean;
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;

View File

@ -1,21 +1,42 @@
import { MEET_ADMIN_USER, MEET_USER } from '../environment.js';
import { injectable } from '../config/dependency-injector.config.js';
import { Role, User } from '@typings-ce';
import { MEET_ADMIN_USER } from '../environment.js';
import { inject, injectable } from '../config/dependency-injector.config.js';
import { AuthType, UserRole, SingleUserAuth, User } from '@typings-ce';
import { LoggerService } from './logger.service.js';
import { GlobalPreferencesService } from './preferences/global-preferences.service.js';
@injectable()
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) {
return {
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 {
username: MEET_USER,
role: Role.USER
username: configuredUsername,
role: UserRole.USER
};
}