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:
parent
7d128ed699
commit
a118b5cf92
@ -112,5 +112,7 @@ export const getProfile = (req: Request, res: Response) => {
|
|||||||
return rejectRequestFromMeetError(res, error);
|
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);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -23,8 +23,6 @@ export const {
|
|||||||
|
|
||||||
// Authentication configuration
|
// Authentication configuration
|
||||||
MEET_API_KEY = 'meet-api-key',
|
MEET_API_KEY = 'meet-api-key',
|
||||||
MEET_USER = 'user',
|
|
||||||
MEET_SECRET = 'user',
|
|
||||||
MEET_ADMIN_USER = 'admin',
|
MEET_ADMIN_USER = 'admin',
|
||||||
MEET_ADMIN_SECRET = 'admin',
|
MEET_ADMIN_SECRET = 'admin',
|
||||||
|
|
||||||
|
|||||||
@ -82,7 +82,7 @@ export const tokenAndRoleValidator = (role: UserRole) => {
|
|||||||
throw errorWithControl(errorInvalidTokenSubject(), true);
|
throw errorWithControl(errorInvalidTokenSubject(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.role !== role) {
|
if (!user.roles.includes(role)) {
|
||||||
throw errorWithControl(errorInsufficientPermissions(), false);
|
throw errorWithControl(errorInsufficientPermissions(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,10 +134,8 @@ export const apiKeyValidator = async (req: Request) => {
|
|||||||
throw errorWithControl(errorInvalidApiKey(), true);
|
throw errorWithControl(errorInvalidApiKey(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiUser = {
|
const userService = container.get(UserService);
|
||||||
username: INTERNAL_CONFIG.API_USER,
|
const apiUser = userService.getApiUser();
|
||||||
role: UserRole.APP
|
|
||||||
};
|
|
||||||
|
|
||||||
req.session = req.session || {};
|
req.session = req.session || {};
|
||||||
req.session.user = apiUser;
|
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
|
// 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;
|
let user: User | null = null;
|
||||||
|
|
||||||
// Check if there is a user already authenticated
|
// Check if there is a user already authenticated
|
||||||
@ -163,7 +162,6 @@ const getAuthenticatedUserOrAnonymous = async (req: Request) => {
|
|||||||
const tokenService = container.get(TokenService);
|
const tokenService = container.get(TokenService);
|
||||||
const payload = await tokenService.verifyToken(token);
|
const payload = await tokenService.verifyToken(token);
|
||||||
const username = payload.sub;
|
const username = payload.sub;
|
||||||
const userService = container.get(UserService);
|
|
||||||
user = username ? await userService.getUser(username) : null;
|
user = username ? await userService.getUser(username) : null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const logger = container.get(LoggerService);
|
const logger = container.get(LoggerService);
|
||||||
@ -172,10 +170,7 @@ const getAuthenticatedUserOrAnonymous = async (req: Request) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
user = {
|
user = userService.getAnonymousUser();
|
||||||
username: INTERNAL_CONFIG.ANONYMOUS_USER,
|
|
||||||
role: UserRole.USER
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { User } from '@typings-ce';
|
import { User } from '@typings-ce';
|
||||||
import { inject, injectable } from 'inversify';
|
import { inject, injectable } from 'inversify';
|
||||||
import { MEET_ADMIN_SECRET, MEET_ADMIN_USER } from '../environment.js';
|
|
||||||
import { PasswordHelper } from '../helpers/index.js';
|
import { PasswordHelper } from '../helpers/index.js';
|
||||||
import { LoggerService, MeetStorageService, UserService } from './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> {
|
async authenticate(username: string, password: string): Promise<User | null> {
|
||||||
const isAdmin = this.authenticateAdmin(username, password);
|
const user = await this.userService.getUser(username);
|
||||||
const isUser = await this.authenticateUser(username, password);
|
|
||||||
|
|
||||||
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 user;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export class TokenService {
|
|||||||
identity: user.username,
|
identity: user.username,
|
||||||
ttl: MEET_ACCESS_TOKEN_EXPIRATION,
|
ttl: MEET_ACCESS_TOKEN_EXPIRATION,
|
||||||
metadata: JSON.stringify({
|
metadata: JSON.stringify({
|
||||||
role: user.role
|
roles: user.roles
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
return await this.generateJwtToken(tokenOptions);
|
return await this.generateJwtToken(tokenOptions);
|
||||||
@ -32,7 +32,7 @@ export class TokenService {
|
|||||||
identity: user.username,
|
identity: user.username,
|
||||||
ttl: MEET_REFRESH_TOKEN_EXPIRATION,
|
ttl: MEET_REFRESH_TOKEN_EXPIRATION,
|
||||||
metadata: JSON.stringify({
|
metadata: JSON.stringify({
|
||||||
role: user.role
|
roles: user.roles
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
return await this.generateJwtToken(tokenOptions);
|
return await this.generateJwtToken(tokenOptions);
|
||||||
|
|||||||
@ -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 { inject, injectable } from 'inversify';
|
||||||
import { MEET_ADMIN_USER } from '../environment.js';
|
import INTERNAL_CONFIG from '../config/internal-config.js';
|
||||||
import { LoggerService, MeetStorageService } from './index.js';
|
import { MeetStorageService } from './index.js';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
constructor(
|
constructor(@inject(MeetStorageService) protected storageService: MeetStorageService) {}
|
||||||
@inject(LoggerService) protected logger: LoggerService,
|
|
||||||
@inject(MeetStorageService) protected globalPrefService: MeetStorageService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async getUser(username: string): Promise<User | null> {
|
async getUser(username: string): Promise<User | null> {
|
||||||
if (username === MEET_ADMIN_USER) {
|
return this.storageService.getUser(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnonymousUser(): User {
|
||||||
return {
|
return {
|
||||||
username: MEET_ADMIN_USER,
|
username: INTERNAL_CONFIG.ANONYMOUS_USER,
|
||||||
role: UserRole.ADMIN
|
passwordHash: '',
|
||||||
|
roles: [UserRole.USER]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const userCredentials = await this.getStoredUserCredentials();
|
getApiUser(): User {
|
||||||
|
|
||||||
if (userCredentials && username === userCredentials.username) {
|
|
||||||
return {
|
return {
|
||||||
username,
|
username: INTERNAL_CONFIG.API_USER,
|
||||||
role: UserRole.USER
|
passwordHash: '',
|
||||||
|
roles: [UserRole.APP]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
// Convert user to UserDTO to remove sensitive information
|
||||||
}
|
convertToDTO(user: User): UserDTO {
|
||||||
|
const { passwordHash, ...userDTO } = user;
|
||||||
async getStoredUserCredentials(): Promise<SingleUserCredentials | null> {
|
return userDTO;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
export interface User {
|
export interface User {
|
||||||
username: string;
|
username: string;
|
||||||
role: UserRole;
|
passwordHash: string;
|
||||||
|
roles: UserRole[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum UserRole {
|
export const enum UserRole {
|
||||||
@ -8,3 +9,5 @@ export const enum UserRole {
|
|||||||
USER = 'user',
|
USER = 'user',
|
||||||
APP = 'app'
|
APP = 'app'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UserDTO = Omit<User, 'passwordHash'>;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user