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,
|
errorInvalidRefreshToken,
|
||||||
errorInvalidTokenSubject,
|
errorInvalidTokenSubject,
|
||||||
errorRefreshTokenNotPresent,
|
errorRefreshTokenNotPresent,
|
||||||
errorUnauthorized,
|
|
||||||
handleError,
|
handleError,
|
||||||
rejectRequestFromMeetError
|
rejectRequestFromMeetError
|
||||||
} from '../models/error.model.js';
|
} from '../models/error.model.js';
|
||||||
@ -103,16 +102,3 @@ export const refreshToken = async (req: Request, res: Response) => {
|
|||||||
handleError(res, error, 'refreshing token');
|
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 './auth.controller.js';
|
||||||
|
export * from './user.controller.js';
|
||||||
export * from './room.controller.js';
|
export * from './room.controller.js';
|
||||||
export * from './meeting.controller.js';
|
export * from './meeting.controller.js';
|
||||||
export * from './participant.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 './recording.middleware.js';
|
||||||
|
|
||||||
export * from './request-validators/auth-validator.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/room-validator.middleware.js';
|
||||||
export * from './request-validators/participant-validator.middleware.js';
|
export * from './request-validators/participant-validator.middleware.js';
|
||||||
export * from './request-validators/recording-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 bodyParser from 'body-parser';
|
||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import * as authCtrl from '../controllers/auth.controller.js';
|
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();
|
export const authRouter = Router();
|
||||||
authRouter.use(bodyParser.urlencoded({ extended: true }));
|
authRouter.use(bodyParser.urlencoded({ extended: true }));
|
||||||
@ -12,8 +11,3 @@ authRouter.use(bodyParser.json());
|
|||||||
authRouter.post('/login', validateLoginRequest, withLoginLimiter, authCtrl.login);
|
authRouter.post('/login', validateLoginRequest, withLoginLimiter, authCtrl.login);
|
||||||
authRouter.post('/logout', authCtrl.logout);
|
authRouter.post('/logout', authCtrl.logout);
|
||||||
authRouter.post('/refresh', authCtrl.refreshToken);
|
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 './global-preferences.routes.js';
|
||||||
export * from './auth.routes.js';
|
export * from './auth.routes.js';
|
||||||
|
export * from './user.routes.js';
|
||||||
export * from './room.routes.js';
|
export * from './room.routes.js';
|
||||||
export * from './meeting.routes.js';
|
export * from './meeting.routes.js';
|
||||||
export * from './participant.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,
|
livekitWebhookRouter,
|
||||||
preferencesRouter,
|
preferencesRouter,
|
||||||
recordingRouter,
|
recordingRouter,
|
||||||
roomRouter
|
roomRouter,
|
||||||
|
userRouter
|
||||||
} from './routes/index.js';
|
} from './routes/index.js';
|
||||||
import {
|
import {
|
||||||
frontendDirectoryPath,
|
frontendDirectoryPath,
|
||||||
@ -56,6 +57,7 @@ const createApp = () => {
|
|||||||
res.sendFile(internalApiHtmlFilePath)
|
res.sendFile(internalApiHtmlFilePath)
|
||||||
);
|
);
|
||||||
app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/auth`, authRouter);
|
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}/rooms`, internalRoomRouter);
|
||||||
app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/meetings`, internalMeetingRouter);
|
app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/meetings`, internalMeetingRouter);
|
||||||
app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/participants`, internalParticipantRouter);
|
app.use(`${INTERNAL_CONFIG.INTERNAL_API_BASE_PATH_V1}/participants`, internalParticipantRouter);
|
||||||
|
|||||||
@ -1,15 +1,11 @@
|
|||||||
import { User } from '@typings-ce';
|
import { User } from '@typings-ce';
|
||||||
import { inject, injectable } from 'inversify';
|
import { inject, injectable } from 'inversify';
|
||||||
import { PasswordHelper } from '../helpers/index.js';
|
import { PasswordHelper } from '../helpers/index.js';
|
||||||
import { LoggerService, MeetStorageService, UserService } from './index.js';
|
import { UserService } from './index.js';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
constructor(
|
constructor(@inject(UserService) protected userService: UserService) {}
|
||||||
@inject(LoggerService) protected logger: LoggerService,
|
|
||||||
@inject(UserService) protected userService: UserService,
|
|
||||||
@inject(MeetStorageService) protected globalPrefService: MeetStorageService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async authenticate(username: string, password: string): Promise<User | null> {
|
async authenticate(username: string, password: string): Promise<User | null> {
|
||||||
const user = await this.userService.getUser(username);
|
const user = await this.userService.getUser(username);
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import { User, UserDTO, UserRole } from '@typings-ce';
|
import { User, UserDTO, UserRole } from '@typings-ce';
|
||||||
import { inject, injectable } from 'inversify';
|
import { inject, injectable } from 'inversify';
|
||||||
import INTERNAL_CONFIG from '../config/internal-config.js';
|
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';
|
import { MeetStorageService } from './index.js';
|
||||||
|
|
||||||
@injectable()
|
@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
|
// Convert user to UserDTO to remove sensitive information
|
||||||
convertToDTO(user: User): UserDTO {
|
convertToDTO(user: User): UserDTO {
|
||||||
const { passwordHash, ...userDTO } = user;
|
const { passwordHash, ...userDTO } = user;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user