From a118b5cf92d8383951acd60b9695d882e2d57f78 Mon Sep 17 00:00:00 2001 From: juancarmore Date: Sat, 31 May 2025 00:10:03 +0200 Subject: [PATCH] backend: remove default user, update user interface to include password hash and a list of roles, and refactor associated code --- backend/src/controllers/auth.controller.ts | 4 +- backend/src/environment.ts | 2 - backend/src/middlewares/auth.middleware.ts | 17 +++---- backend/src/services/auth.service.ts | 25 ++-------- backend/src/services/token.service.ts | 4 +- backend/src/services/user.service.ts | 58 ++++++++++------------ typings/src/user.ts | 5 +- 7 files changed, 44 insertions(+), 71 deletions(-) diff --git a/backend/src/controllers/auth.controller.ts b/backend/src/controllers/auth.controller.ts index 5f4bcda..28cf415 100644 --- a/backend/src/controllers/auth.controller.ts +++ b/backend/src/controllers/auth.controller.ts @@ -112,5 +112,7 @@ export const getProfile = (req: Request, res: Response) => { return rejectRequestFromMeetError(res, error); } - return res.status(200).json(user); + const userService = container.get(UserService); + const userDTO = userService.convertToDTO(user); + return res.status(200).json(userDTO); }; diff --git a/backend/src/environment.ts b/backend/src/environment.ts index 7dfa658..8e2676a 100644 --- a/backend/src/environment.ts +++ b/backend/src/environment.ts @@ -23,8 +23,6 @@ export const { // Authentication configuration MEET_API_KEY = 'meet-api-key', - MEET_USER = 'user', - MEET_SECRET = 'user', MEET_ADMIN_USER = 'admin', MEET_ADMIN_SECRET = 'admin', diff --git a/backend/src/middlewares/auth.middleware.ts b/backend/src/middlewares/auth.middleware.ts index b20b2df..c394e1e 100644 --- a/backend/src/middlewares/auth.middleware.ts +++ b/backend/src/middlewares/auth.middleware.ts @@ -82,7 +82,7 @@ export const tokenAndRoleValidator = (role: UserRole) => { throw errorWithControl(errorInvalidTokenSubject(), true); } - if (user.role !== role) { + if (!user.roles.includes(role)) { throw errorWithControl(errorInsufficientPermissions(), false); } @@ -134,10 +134,8 @@ export const apiKeyValidator = async (req: Request) => { throw errorWithControl(errorInvalidApiKey(), true); } - const apiUser = { - username: INTERNAL_CONFIG.API_USER, - role: UserRole.APP - }; + const userService = container.get(UserService); + const apiUser = userService.getApiUser(); req.session = req.session || {}; req.session.user = apiUser; @@ -152,7 +150,8 @@ export const allowAnonymous = async (req: Request) => { }; // Return the authenticated user if available, otherwise return an anonymous user -const getAuthenticatedUserOrAnonymous = async (req: Request) => { +const getAuthenticatedUserOrAnonymous = async (req: Request): Promise => { + const userService = container.get(UserService); let user: User | null = null; // Check if there is a user already authenticated @@ -163,7 +162,6 @@ const getAuthenticatedUserOrAnonymous = async (req: Request) => { 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); @@ -172,10 +170,7 @@ const getAuthenticatedUserOrAnonymous = async (req: Request) => { } if (!user) { - user = { - username: INTERNAL_CONFIG.ANONYMOUS_USER, - role: UserRole.USER - }; + user = userService.getAnonymousUser(); } return user; diff --git a/backend/src/services/auth.service.ts b/backend/src/services/auth.service.ts index 9679d54..d5be7a7 100644 --- a/backend/src/services/auth.service.ts +++ b/backend/src/services/auth.service.ts @@ -1,6 +1,5 @@ import { User } from '@typings-ce'; import { inject, injectable } from 'inversify'; -import { MEET_ADMIN_SECRET, MEET_ADMIN_USER } from '../environment.js'; import { PasswordHelper } from '../helpers/index.js'; import { LoggerService, MeetStorageService, UserService } from './index.js'; @@ -13,28 +12,12 @@ export class AuthService { ) {} async authenticate(username: string, password: string): Promise { - const isAdmin = this.authenticateAdmin(username, password); - const isUser = await this.authenticateUser(username, password); + const user = await this.userService.getUser(username); - if (isAdmin || isUser) { - return this.userService.getUser(username); + if (!user || !(await PasswordHelper.verifyPassword(password, user.passwordHash))) { + return null; } - return null; - } - - private authenticateAdmin(username: string, password: string): boolean { - return username === MEET_ADMIN_USER && password === MEET_ADMIN_SECRET; - } - - private async authenticateUser(username: string, password: string): Promise { - const userCredentials = await this.userService.getStoredUserCredentials(); - - if (!userCredentials) { - return false; - } - - const isPasswordValid = await PasswordHelper.verifyPassword(password, userCredentials.passwordHash); - return username === userCredentials.username && isPasswordValid; + return user; } } diff --git a/backend/src/services/token.service.ts b/backend/src/services/token.service.ts index 7778b46..b851c8f 100644 --- a/backend/src/services/token.service.ts +++ b/backend/src/services/token.service.ts @@ -21,7 +21,7 @@ export class TokenService { identity: user.username, ttl: MEET_ACCESS_TOKEN_EXPIRATION, metadata: JSON.stringify({ - role: user.role + roles: user.roles }) }; return await this.generateJwtToken(tokenOptions); @@ -32,7 +32,7 @@ export class TokenService { identity: user.username, ttl: MEET_REFRESH_TOKEN_EXPIRATION, metadata: JSON.stringify({ - role: user.role + roles: user.roles }) }; return await this.generateJwtToken(tokenOptions); diff --git a/backend/src/services/user.service.ts b/backend/src/services/user.service.ts index ca430c0..9225687 100644 --- a/backend/src/services/user.service.ts +++ b/backend/src/services/user.service.ts @@ -1,43 +1,35 @@ -import { SingleUserAuth, SingleUserCredentials, User, UserRole } from '@typings-ce'; +import { User, UserDTO, UserRole } from '@typings-ce'; import { inject, injectable } from 'inversify'; -import { MEET_ADMIN_USER } from '../environment.js'; -import { LoggerService, MeetStorageService } from './index.js'; +import INTERNAL_CONFIG from '../config/internal-config.js'; +import { MeetStorageService } from './index.js'; @injectable() export class UserService { - constructor( - @inject(LoggerService) protected logger: LoggerService, - @inject(MeetStorageService) protected globalPrefService: MeetStorageService - ) {} + constructor(@inject(MeetStorageService) protected storageService: MeetStorageService) {} async getUser(username: string): Promise { - if (username === MEET_ADMIN_USER) { - return { - username: MEET_ADMIN_USER, - role: UserRole.ADMIN - }; - } - - const userCredentials = await this.getStoredUserCredentials(); - - if (userCredentials && username === userCredentials.username) { - return { - username, - role: UserRole.USER - }; - } - - return null; + return this.storageService.getUser(username); } - async getStoredUserCredentials(): Promise { - try { - const { securityPreferences } = await this.globalPrefService.getGlobalPreferences(); - const { method: authMethod } = securityPreferences.authentication; - return (authMethod as SingleUserAuth).credentials; - } catch (error) { - this.logger.error('Error getting stored user credentials:' + error); - return null; - } + getAnonymousUser(): User { + return { + username: INTERNAL_CONFIG.ANONYMOUS_USER, + passwordHash: '', + roles: [UserRole.USER] + }; + } + + getApiUser(): User { + return { + username: INTERNAL_CONFIG.API_USER, + passwordHash: '', + roles: [UserRole.APP] + }; + } + + // Convert user to UserDTO to remove sensitive information + convertToDTO(user: User): UserDTO { + const { passwordHash, ...userDTO } = user; + return userDTO; } } diff --git a/typings/src/user.ts b/typings/src/user.ts index 79cce05..c9486d5 100644 --- a/typings/src/user.ts +++ b/typings/src/user.ts @@ -1,6 +1,7 @@ export interface User { username: string; - role: UserRole; + passwordHash: string; + roles: UserRole[]; } export const enum UserRole { @@ -8,3 +9,5 @@ export const enum UserRole { USER = 'user', APP = 'app' } + +export type UserDTO = Omit;