Files
domino-server/src/server/controllers/AuthController.ts
Jose Conde 5f117667a4 reworked
2024-07-12 16:27:52 +02:00

213 lines
6.9 KiB
TypeScript

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<void> {
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' });
}
}
}