213 lines
6.9 KiB
TypeScript
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' });
|
|
}
|
|
}
|
|
} |