import {Request, Response} from "express"; import {Session} from "express-session"; import Application from "../../Application.js"; import {route} from "../../common/Routing.js"; import ModelFactory from "../../db/ModelFactory.js"; import Validator, {InvalidFormatValidationError} from "../../db/Validator.js"; import {ServerError} from "../../HttpError.js"; import Throttler from "../../Throttler.js"; import AuthComponent from "../AuthComponent.js"; import AuthController from "../AuthController.js"; import {AuthError, PendingApprovalAuthError, RegisterCallback} from "../AuthGuard.js"; import AuthMethod from "../AuthMethod.js"; import User from "../models/User.js"; import UserEmail from "../models/UserEmail.js"; import UserNameComponent, {USERNAME_REGEXP} from "../models/UserNameComponent.js"; import PasswordAuthProof from "./PasswordAuthProof.js"; import UserPasswordComponent from "./UserPasswordComponent.js"; export default class PasswordAuthMethod implements AuthMethod { public constructor( protected readonly app: Application, ) { } public getName(): string { return 'password'; } public getWeightForRequest(req: Request): number { return !req.body.identifier || !req.body.password || req.body.password.length === 0 ? 0 : 2; } public async findUserByIdentifier(identifier: string): Promise { const query = UserEmail.select() .with('user') .where('email', identifier); const user = (await query .first())?.user.getOrFail(); if (user) return user; if (ModelFactory.get(User).hasComponent(UserNameComponent)) { return await User.select().where('name', identifier).first(); } return null; } public async getProofsForSession(session: Session): Promise { const proof = PasswordAuthProof.getProofForSession(session); return proof ? [proof] : []; } public async attemptLogin(req: Request, res: Response, user: User): Promise { const passwordAuthProof = PasswordAuthProof.createProofForLogin(req.getSession()); passwordAuthProof.setResource(user); await Validator.validate({ password: new Validator().defined(), }, req.body); await passwordAuthProof.authorize(req.body.password); try { await this.app.as(AuthComponent).getAuthGuard().authenticateOrRegister( req.getSession(), passwordAuthProof, !!req.body.persist_session, ); } catch (e) { if (e instanceof AuthError) { Throttler.throttle('login_failed_attempts_user', 3, 3 * 60 * 1000, // 3min user.getOrFail('id').toString(), 1000, 60 * 1000); // 1min Throttler.throttle('login_failed_attempts_ip', 50, 60 * 1000, // 1min req.ip, 1000, 3600 * 1000); // 1h if (e instanceof PendingApprovalAuthError) { req.flash('error', 'Your account is still being reviewed.'); res.redirect(route('auth')); return; } else { const err = new InvalidFormatValidationError('Invalid password.'); err.thingName = 'password'; throw err; } } else { throw e; } } AuthController.flashSuccessfulAuthenticationWelcomeMessage(user, req, 'Authentication success.'); res.redirect(req.getIntendedUrl() || route('home')); } public async attemptRegister(req: Request, res: Response, identifier: string): Promise { if (!ModelFactory.get(User).hasComponent(UserNameComponent)) throw new ServerError('Cannot register with password without UserNameComponent.'); Throttler.throttle('register_password', 10, 30000, req.ip); req.body.identifier = identifier; await Validator.validate({ identifier: new Validator().defined().between(3, 64).regexp(USERNAME_REGEXP).unique(User, 'name'), password: new Validator().defined().minLength(UserPasswordComponent.PASSWORD_MIN_LENGTH), password_confirmation: new Validator().defined().sameAs('password', req.body.password), terms: new Validator().defined(), }, req.body); const passwordAuthProof = PasswordAuthProof.createAuthorizedProofForRegistration(req.getSession()); try { await this.app.as(AuthComponent).getAuthGuard().authenticateOrRegister(req.getSession(), passwordAuthProof, true, undefined, async (connection, user) => { const callbacks: RegisterCallback[] = []; // Password await user.as(UserPasswordComponent).setPassword(req.body.password); // Username user.as(UserNameComponent).setName(req.body.identifier); return callbacks; }, async (connection, user) => { passwordAuthProof.setResource(user); return []; }); } catch (e) { if (e instanceof PendingApprovalAuthError) { req.flash('info', `Your account was successfully created and is pending review from an administrator.`); res.redirect(route('auth')); return; } else { throw e; } } const user = await passwordAuthProof.getResource(); if (!user) throw new Error('Password auth proof has no user.'); AuthController.flashSuccessfulAuthenticationWelcomeMessage(user, req, 'Your account was successfully created!'); res.redirect(req.getIntendedUrl() || route('home')); } }