backend: remove default user, update user interface to include password hash and a list of roles, and refactor associated code

This commit is contained in:
juancarmore 2025-05-31 00:10:03 +02:00
parent 7d128ed699
commit a118b5cf92
7 changed files with 44 additions and 71 deletions

View File

@ -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);
};

View File

@ -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',

View File

@ -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<User> => {
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;

View File

@ -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<User | null> {
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<boolean> {
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;
}
}

View File

@ -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);

View File

@ -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<User | null> {
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<SingleUserCredentials | null> {
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;
}
}

View File

@ -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<User, 'passwordHash'>;