backend: Refactor authentication controller to improve login flow and add user profile retrieval

This commit is contained in:
juancarmore 2025-03-21 00:57:16 +01:00
parent d1af9637a6
commit fbbef9eedf
2 changed files with 47 additions and 56 deletions

View File

@ -6,81 +6,56 @@ import { LoggerService } from '../services/logger.service.js';
import { import {
ACCESS_TOKEN_COOKIE_NAME, ACCESS_TOKEN_COOKIE_NAME,
MEET_ACCESS_TOKEN_EXPIRATION, MEET_ACCESS_TOKEN_EXPIRATION,
MEET_ADMIN_USER,
MEET_API_BASE_PATH_V1, MEET_API_BASE_PATH_V1,
MEET_REFRESH_TOKEN_EXPIRATION, MEET_REFRESH_TOKEN_EXPIRATION,
REFRESH_TOKEN_COOKIE_NAME REFRESH_TOKEN_COOKIE_NAME
} from '../environment.js'; } from '../environment.js';
import { ClaimGrants } from 'livekit-server-sdk'; import { ClaimGrants } from 'livekit-server-sdk';
import { getCookieOptions } from '../utils/cookie-utils.js'; import { getCookieOptions } from '../utils/cookie-utils.js';
import { UserService } from '../services/user.service.js';
export const login = (req: Request, res: Response) => { export const login = async (req: Request, res: Response) => {
const logger = container.get(LoggerService); const logger = container.get(LoggerService);
logger.verbose('Login request received'); logger.verbose('Login request received');
const { username, password } = req.body; const { username, password } = req.body as { username: string; password: string };
if (!username || !password) {
logger.warn('Missing username or password');
return res.status(400).json({ message: 'Missing username or password' });
}
const authService = container.get(AuthService); const authService = container.get(AuthService);
const authenticated = authService.authenticateUser(username, password); const user = authService.authenticate(username, password);
if (!authenticated) { if (!user) {
logger.warn('Login failed'); logger.warn('Login failed');
return res.status(401).json({ message: 'Login failed' }); return res.status(404).json({ message: 'Login failed. Invalid username or password' });
}
return res.status(200).json({ message: 'Login succeeded' });
};
export const logout = (req: Request, res: Response) => {
return res.status(200).json({ message: 'Logout successful' });
};
export const adminLogin = async (req: Request, res: Response) => {
const logger = container.get(LoggerService);
logger.verbose('Admin login request received');
const { username, password } = req.body;
const authService = container.get(AuthService);
const authenticated = authService.authenticateAdmin(username, password);
if (!authenticated) {
logger.warn(`Admin login failed for username: ${username}`);
return res.status(404).json({ message: 'Admin login failed. Invalid username or password' });
} }
try { try {
const tokenService = container.get(TokenService); const tokenService = container.get(TokenService);
const accessToken = await tokenService.generateAccessToken(username); const accessToken = await tokenService.generateAccessToken(user);
const refreshToken = await tokenService.generateRefreshToken(username); const refreshToken = await tokenService.generateRefreshToken(user);
res.cookie(ACCESS_TOKEN_COOKIE_NAME, accessToken, getCookieOptions('/', MEET_ACCESS_TOKEN_EXPIRATION)); res.cookie(ACCESS_TOKEN_COOKIE_NAME, accessToken, getCookieOptions('/', MEET_ACCESS_TOKEN_EXPIRATION));
res.cookie( res.cookie(
REFRESH_TOKEN_COOKIE_NAME, REFRESH_TOKEN_COOKIE_NAME,
refreshToken, refreshToken,
getCookieOptions(`${MEET_API_BASE_PATH_V1}/auth/admin`, MEET_REFRESH_TOKEN_EXPIRATION) getCookieOptions(`${MEET_API_BASE_PATH_V1}/auth`, MEET_REFRESH_TOKEN_EXPIRATION)
); );
logger.info(`Admin login succeeded for username: ${username}`); logger.info(`Login succeeded for user ${username}`);
return res.status(200).json({ message: 'Admin login succeeded' }); return res.status(200).json({ message: 'Login succeeded' });
} catch (error) { } catch (error) {
logger.error('Error generating admin token' + error); logger.error('Error generating token' + error);
return res.status(500).json({ message: 'Internal server error' }); return res.status(500).json({ message: 'Internal server error' });
} }
}; };
export const adminLogout = (req: Request, res: Response) => { export const logout = (_req: Request, res: Response) => {
res.clearCookie(ACCESS_TOKEN_COOKIE_NAME); res.clearCookie(ACCESS_TOKEN_COOKIE_NAME);
res.clearCookie(REFRESH_TOKEN_COOKIE_NAME, { res.clearCookie(REFRESH_TOKEN_COOKIE_NAME, {
path: `${MEET_API_BASE_PATH_V1}/auth/admin` path: `${MEET_API_BASE_PATH_V1}/auth`
}); });
return res.status(200).json({ message: 'Logout successful' }); return res.status(200).json({ message: 'Logout successful' });
}; };
export const adminRefresh = async (req: Request, res: Response) => { export const refreshToken = async (req: Request, res: Response) => {
const logger = container.get(LoggerService); const logger = container.get(LoggerService);
logger.verbose('Admin refresh request received'); logger.verbose('Refresh token request received');
const refreshToken = req.cookies[REFRESH_TOKEN_COOKIE_NAME]; const refreshToken = req.cookies[REFRESH_TOKEN_COOKIE_NAME];
if (!refreshToken) { if (!refreshToken) {
@ -98,18 +73,32 @@ export const adminRefresh = async (req: Request, res: Response) => {
return res.status(400).json({ message: 'Invalid refresh token' }); return res.status(400).json({ message: 'Invalid refresh token' });
} }
if (payload.sub !== MEET_ADMIN_USER) { const username = payload.sub;
const userService = container.get(UserService);
const user = username ? userService.getUser(username) : null;
if (!user) {
logger.warn('Invalid refresh token subject'); logger.warn('Invalid refresh token subject');
return res.status(403).json({ message: 'Invalid refresh token subject' }); return res.status(403).json({ message: 'Invalid refresh token subject' });
} }
try { try {
const accessToken = await tokenService.generateAccessToken(MEET_ADMIN_USER); const accessToken = await tokenService.generateAccessToken(user);
res.cookie(ACCESS_TOKEN_COOKIE_NAME, accessToken, getCookieOptions('/', MEET_ACCESS_TOKEN_EXPIRATION)); res.cookie(ACCESS_TOKEN_COOKIE_NAME, accessToken, getCookieOptions('/', MEET_ACCESS_TOKEN_EXPIRATION));
logger.info(`Admin refresh succeeded for username: ${MEET_ADMIN_USER}`); logger.info(`Token refreshed for user ${username}`);
return res.status(200).json({ message: 'Admin refresh succeeded' }); return res.status(200).json({ message: 'Token refreshed' });
} catch (error) { } catch (error) {
logger.error('Error refreshing admin token' + error); logger.error('Error refreshing token' + error);
return res.status(500).json({ message: 'Internal server error' }); return res.status(500).json({ message: 'Internal server error' });
} }
}; };
export const getProfile = (req: Request, res: Response) => {
const user = req.session?.user;
if (!user) {
return res.status(401).json({ message: 'Unauthorized' });
}
return res.status(200).json(user);
};

View File

@ -1,27 +1,29 @@
import { Router, Request, Response } from 'express'; import { Router } from 'express';
import bodyParser from 'body-parser'; import bodyParser from 'body-parser';
import * as authCtrl from '../controllers/auth.controller.js'; import * as authCtrl from '../controllers/auth.controller.js';
import rateLimit from 'express-rate-limit'; import rateLimit from 'express-rate-limit';
import { withAdminValidToken } from '../middlewares/auth.middleware.js'; import { tokenAndRoleValidator, withAuth } from '../middlewares/auth.middleware.js';
import { Role } from '@typings-ce';
import { validateLoginRequest } from '../middlewares/request-validators/auth-validator.middleware.js';
export const authRouter = Router(); export const authRouter = Router();
// Limit login attempts for avoiding brute force attacks // Limit login attempts for avoiding brute force attacks
const loginLimiter = rateLimit({ const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 min windowMs: 15 * 60 * 1000, // 15 min
max: 5, limit: 5,
message: 'Too many login attempts, please try again later.' message: 'Too many login attempts, please try again later'
}); });
authRouter.use(bodyParser.urlencoded({ extended: true })); authRouter.use(bodyParser.urlencoded({ extended: true }));
authRouter.use(bodyParser.json()); authRouter.use(bodyParser.json());
// Auth Routes // Auth Routes
authRouter.post('/login', authCtrl.login); authRouter.post('/login', validateLoginRequest, loginLimiter, authCtrl.login);
authRouter.post('/logout', authCtrl.logout); authRouter.post('/logout', authCtrl.logout);
authRouter.post('/admin/login', loginLimiter, authCtrl.adminLogin); authRouter.post('/refresh', authCtrl.refreshToken);
authRouter.post('/admin/logout', authCtrl.adminLogout); authRouter.get(
authRouter.post('/admin/refresh', authCtrl.adminRefresh); '/profile',
authRouter.get('/admin/verify', withAdminValidToken, (_req: Request, res: Response) => withAuth(tokenAndRoleValidator(Role.ADMIN), tokenAndRoleValidator(Role.USER)),
res.status(200).json({ message: 'Valid token' }) authCtrl.getProfile
); );