backend: implement password change functionality and move user profile retrieval to users endpoints
This commit is contained in:
parent
927035c1ea
commit
a64f48bc5b
@ -8,7 +8,6 @@ import {
|
||||
errorInvalidRefreshToken,
|
||||
errorInvalidTokenSubject,
|
||||
errorRefreshTokenNotPresent,
|
||||
errorUnauthorized,
|
||||
handleError,
|
||||
rejectRequestFromMeetError
|
||||
} from '../models/error.model.js';
|
||||
@ -103,16 +102,3 @@ export const refreshToken = async (req: Request, res: Response) => {
|
||||
handleError(res, error, 'refreshing token');
|
||||
}
|
||||
};
|
||||
|
||||
export const getProfile = (req: Request, res: Response) => {
|
||||
const user = req.session?.user;
|
||||
|
||||
if (!user) {
|
||||
const error = errorUnauthorized();
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
}
|
||||
|
||||
const userService = container.get(UserService);
|
||||
const userDTO = userService.convertToDTO(user);
|
||||
return res.status(200).json(userDTO);
|
||||
};
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
export * from './auth.controller.js';
|
||||
export * from './user.controller.js';
|
||||
export * from './room.controller.js';
|
||||
export * from './meeting.controller.js';
|
||||
export * from './participant.controller.js';
|
||||
|
||||
36
backend/src/controllers/user.controller.ts
Normal file
36
backend/src/controllers/user.controller.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { container } from '../config/index.js';
|
||||
import { errorUnauthorized, handleError, rejectRequestFromMeetError } from '../models/error.model.js';
|
||||
import { UserService } from '../services/index.js';
|
||||
|
||||
export const getProfile = (req: Request, res: Response) => {
|
||||
const user = req.session?.user;
|
||||
|
||||
if (!user) {
|
||||
const error = errorUnauthorized();
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
}
|
||||
|
||||
const userService = container.get(UserService);
|
||||
const userDTO = userService.convertToDTO(user);
|
||||
return res.status(200).json(userDTO);
|
||||
};
|
||||
|
||||
export const changePassword = async (req: Request, res: Response) => {
|
||||
const user = req.session?.user;
|
||||
|
||||
if (!user) {
|
||||
const error = errorUnauthorized();
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
}
|
||||
|
||||
const { newPassword } = req.body as { newPassword: string };
|
||||
|
||||
try {
|
||||
const userService = container.get(UserService);
|
||||
await userService.changePassword(user.username, newPassword);
|
||||
return res.status(200).json({ message: 'Password changed successfully.' });
|
||||
} catch (error) {
|
||||
handleError(res, error, 'changing password');
|
||||
}
|
||||
};
|
||||
@ -5,6 +5,7 @@ export * from './participant.middleware.js';
|
||||
export * from './recording.middleware.js';
|
||||
|
||||
export * from './request-validators/auth-validator.middleware.js';
|
||||
export * from './request-validators/user-validator.middleware.js';
|
||||
export * from './request-validators/room-validator.middleware.js';
|
||||
export * from './request-validators/participant-validator.middleware.js';
|
||||
export * from './request-validators/recording-validator.middleware.js';
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import { z } from 'zod';
|
||||
import { rejectUnprocessableRequest } from '../../models/error.model.js';
|
||||
|
||||
const ChangePasswordRequestSchema = z.object({
|
||||
newPassword: z.string().min(4, 'New password must be at least 4 characters long')
|
||||
});
|
||||
|
||||
export const validateChangePasswordRequest = (req: Request, res: Response, next: NextFunction) => {
|
||||
const { success, error, data } = ChangePasswordRequestSchema.safeParse(req.body);
|
||||
|
||||
if (!success) {
|
||||
return rejectUnprocessableRequest(res, error);
|
||||
}
|
||||
|
||||
req.body = data;
|
||||
next();
|
||||
};
|
||||
@ -1,8 +1,7 @@
|
||||
import { UserRole } from '@typings-ce';
|
||||
import bodyParser from 'body-parser';
|
||||
import { Router } from 'express';
|
||||
import * as authCtrl from '../controllers/auth.controller.js';
|
||||
import { tokenAndRoleValidator, validateLoginRequest, withAuth, withLoginLimiter } from '../middlewares/index.js';
|
||||
import { validateLoginRequest, withLoginLimiter } from '../middlewares/index.js';
|
||||
|
||||
export const authRouter = Router();
|
||||
authRouter.use(bodyParser.urlencoded({ extended: true }));
|
||||
@ -12,8 +11,3 @@ authRouter.use(bodyParser.json());
|
||||
authRouter.post('/login', validateLoginRequest, withLoginLimiter, authCtrl.login);
|
||||
authRouter.post('/logout', authCtrl.logout);
|
||||
authRouter.post('/refresh', authCtrl.refreshToken);
|
||||
authRouter.get(
|
||||
'/profile',
|
||||
withAuth(tokenAndRoleValidator(UserRole.ADMIN), tokenAndRoleValidator(UserRole.USER)),
|
||||
authCtrl.getProfile
|
||||
);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
export * from './global-preferences.routes.js';
|
||||
export * from './auth.routes.js';
|
||||
export * from './user.routes.js';
|
||||
export * from './room.routes.js';
|
||||
export * from './meeting.routes.js';
|
||||
export * from './participant.routes.js';
|
||||
|
||||
22
backend/src/routes/user.routes.ts
Normal file
22
backend/src/routes/user.routes.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { UserRole } from '@typings-ce';
|
||||
import bodyParser from 'body-parser';
|
||||
import { Router } from 'express';
|
||||
import * as userCtrl from '../controllers/user.controller.js';
|
||||
import { tokenAndRoleValidator, validateChangePasswordRequest, withAuth } from '../middlewares/index.js';
|
||||
|
||||
export const userRouter = Router();
|
||||
userRouter.use(bodyParser.urlencoded({ extended: true }));
|
||||
userRouter.use(bodyParser.json());
|
||||
|
||||
// Users Routes
|
||||
userRouter.get(
|
||||
'/profile',
|
||||
withAuth(tokenAndRoleValidator(UserRole.ADMIN), tokenAndRoleValidator(UserRole.USER)),
|
||||
userCtrl.getProfile
|
||||
);
|
||||
userRouter.post(
|
||||
'/change-password',
|
||||
withAuth(tokenAndRoleValidator(UserRole.ADMIN), tokenAndRoleValidator(UserRole.USER)),
|
||||
validateChangePasswordRequest,
|
||||
userCtrl.changePassword
|
||||
);
|
||||
@ -15,7 +15,8 @@ import {
|
||||
livekitWebhookRouter,
|
||||
preferencesRouter,
|
||||
recordingRouter,
|
||||
roomRouter
|
||||
roomRouter,
|
||||
userRouter
|
||||
} from './routes/index.js';
|
||||
import {
|
||||
frontendDirectoryPath,
|
||||
@ -56,6 +57,7 @@ const createApp = () => {
|
||||
res.sendFile(internalApiHtmlFilePath)
|
||||
);
|
||||
app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/auth`, authRouter);
|
||||
app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/users`, userRouter);
|
||||
app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/rooms`, internalRoomRouter);
|
||||
app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/meetings`, internalMeetingRouter);
|
||||
app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/participants`, internalParticipantRouter);
|
||||
|
||||
@ -1,15 +1,11 @@
|
||||
import { User } from '@typings-ce';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { PasswordHelper } from '../helpers/index.js';
|
||||
import { LoggerService, MeetStorageService, UserService } from './index.js';
|
||||
import { UserService } from './index.js';
|
||||
|
||||
@injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
@inject(LoggerService) protected logger: LoggerService,
|
||||
@inject(UserService) protected userService: UserService,
|
||||
@inject(MeetStorageService) protected globalPrefService: MeetStorageService
|
||||
) {}
|
||||
constructor(@inject(UserService) protected userService: UserService) {}
|
||||
|
||||
async authenticate(username: string, password: string): Promise<User | null> {
|
||||
const user = await this.userService.getUser(username);
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { User, UserDTO, UserRole } from '@typings-ce';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import INTERNAL_CONFIG from '../config/internal-config.js';
|
||||
import { PasswordHelper } from '../helpers/password.helper.js';
|
||||
import { internalError } from '../models/error.model.js';
|
||||
import { MeetStorageService } from './index.js';
|
||||
|
||||
@injectable()
|
||||
@ -27,6 +29,17 @@ export class UserService {
|
||||
};
|
||||
}
|
||||
|
||||
async changePassword(username: string, newPassword: string) {
|
||||
const user = await this.storageService.getUser(username);
|
||||
|
||||
if (!user) {
|
||||
throw internalError(`getting user ${username} for password change`);
|
||||
}
|
||||
|
||||
user.passwordHash = await PasswordHelper.hashPassword(newPassword);
|
||||
await this.storageService.saveUser(user);
|
||||
}
|
||||
|
||||
// Convert user to UserDTO to remove sensitive information
|
||||
convertToDTO(user: User): UserDTO {
|
||||
const { passwordHash, ...userDTO } = user;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user