import bcrypt from 'bcryptjs'; import { UsersMongoManager } from "../db/mongo/UsersMongoManager"; import { SecurityManager } from '../managers/SecurityManager'; import { ApiTokenMongoManager } from '../db/mongo/ApiTokenMongoManager'; import { TemporalTokenMongoManager } from '../db/mongo/TemporalTokenMongoManager'; import { BaseController } from './BaseController'; import { NextFunction, Request, Response } from 'express'; import { AuthenticationOption, Token, User } from '../db/interfaces'; export class AuthController extends BaseController { security = new SecurityManager(); usersManager = new UsersMongoManager(); temporalTokenManager = new TemporalTokenMongoManager(); async login(req: Request, res: Response): Promise { const { log } = req; try { let token = null const { username, password } = req.body; this.logger.debug('login', username, password); const { valid: isValidPassword, user } = await this._checkPassword(username, password); this.logger.debug('isValidPassword', isValidPassword); if (!isValidPassword) { res.status(401).json({ error: 'Unauthorized' }).end(); log.error('Unauthorized login attempt for user: ', username); return; } this._jwtSignUser(user, res) } catch (error) { this.handleError(res, error); } } _jwtSignUser(user: User | null, res: Response) { if (user === null) { res.status(401).json({ error: 'Unauthorized' }).end(); return; } delete user.hash; const token = this.security.signJwt(user); if (token === null) { res.status(401).json({ error: 'Unauthorized' }).end(); } else { res.status(200).json({ token }).end(); } return; } async twoFactorCodeAuthentication(req: Request, res: Response) { const { code, username } = req.body; const { valid: isValid, user } = await this._isValidTemporalCode(username, code); if (!isValid) { res.status(406).json({ error: 'Unauthorized' }).end(); return; } res.status(200).end(); } async _isValidTemporalCode(username: string, code: string) { const user = await this.usersManager.getByUsername(username); if (user === null || user._id === undefined) { return { valid: false, user: null }; } const temporalToken = await this.temporalTokenManager.getByUserAndType(user._id.toString(), TemporalTokenMongoManager.Types.PASSWORD_RECOVERY); if (temporalToken === null) { return { valid: false, user: null }; } const { token } = temporalToken; const valid = bcrypt.compareSync(code, token); return { valid, user: valid ? user : null}; } async changePasswordWithCode(req: Request, res: Response) { try { const { username, newPassword, code } = req.body; const { valid: isValid, user } = await this._isValidTemporalCode(username, code); if (isValid) { await this._setNewPassword(username, newPassword); this._jwtSignUser(user, res); } else { res.status(400).json({ error: 'Code not valid.' }).end(); } } catch (error) { this.handleError(res, error); } } async changePassword(req: Request, res: Response) { try { const { username, oldPassword, newPassword } = req.body; const { valid: isValidPassword } = await this._checkPassword(username, oldPassword); if (isValidPassword) { await this._setNewPassword(username, newPassword); res.status(200).end(); } res.status(400).json({ error: 'Password not valid.' }).end(); } catch (error) { this.handleError(res, error); } } async _setNewPassword(username: string, newPassword: string) { const hash = this.security.getHashedPassword(newPassword); await this.usersManager.updatePassword(username, hash); } async _checkPassword(username: string, password: string) { let valid = false; const user = await this.usersManager.getByUsername(username); if (user && user.hash) { const { hash } = user; valid = bcrypt.compareSync(password, hash); } return { valid, user }; } static async checkRolesToken(token: Token, rolesToCheck: string[]) { if (rolesToCheck.length === 0) { return true; } if (!token._id) { return false; } const tokenFromDb: Token = await new ApiTokenMongoManager().getById(token._id.toString()) as Token; if (!tokenFromDb) { return false; } const { roles } = tokenFromDb; const validRoles = rolesToCheck.filter((r: string) => roles?.includes(r) || false); return validRoles.length === rolesToCheck.length; } static async checkRoles(user: User, rolesToCheck: string[]) { if (rolesToCheck.length === 0) { return true; } if (!user._id) { return false; } const usersManager = new UsersMongoManager(); const userFromDb = await usersManager.getById(user._id.toString()); if (!userFromDb) { return false; } const { roles } = userFromDb; const validRoles = rolesToCheck.filter((r: string) => roles.includes(r)); return validRoles.length === rolesToCheck.length; } static authenticate(options: AuthenticationOption = {}) { return async function(req: Request, res: Response, next: NextFunction) { const security = new SecurityManager(); const token = req.headers.authorization; const { roles = [] } = options; if (!token) { return res.status(401).json({ error: 'Unauthorized' }); } try { const user: User = await security.verifyJwt(token); const validRoles = await AuthController.checkRoles(user, roles); if (!validRoles) { return res.status(403).json({ error: 'Forbidden' }); } req.user = user; next(); } catch (error) { return res.status(403).json({ error: 'Forbidden' }); } } } static tokenAuthenticate(options: AuthenticationOption = {}) { return async function(req: Request, res: Response, next: NextFunction) { const { log } = req; // log.info('tokenAuthenticate') try { const token: string = req.headers['x-api-key'] as string; const dm = new ApiTokenMongoManager(); const apiToken = await dm.getByToken(token); const { roles = [] } = options; const valid = !!apiToken && await AuthController.checkRolesToken(apiToken, roles); if (!valid) { return res.status(401).json({ error: 'Unauthorized' }); } req.token = apiToken; next(); } catch (error) { return res.status(403).json({ error: 'Forbidden' }); } } } static async withUser(req: Request, res: Response, next: NextFunction) { try { const token = req.token; const dm = new UsersMongoManager(); const user = await dm.getById(token.userId); req.user = user; next(); } catch (error) { return res.status(403).json({ error: 'Forbidden' }); } } }