import AuthProof from "./AuthProof"; import MysqlConnectionManager from "../db/MysqlConnectionManager"; import User from "./models/User"; import {Connection} from "mysql"; import {Request} from "express"; import {PENDING_ACCOUNT_REVIEW_MAIL_TEMPLATE} from "../Mails"; import Mail from "../Mail"; import Controller from "../Controller"; import config from "config"; import ModelFactory from "../db/ModelFactory"; export default abstract class AuthGuard

> { public abstract async getProofForSession(session: Express.Session): Promise

; public async getProofForRequest(req: Request): Promise

{ return null; } public async authenticateOrRegister( session: Express.Session, proof: P, onLogin?: (user: User) => Promise, onRegister?: (connection: Connection, user: User) => Promise ): Promise { if (!await proof.isValid()) throw new InvalidAuthProofError(); if (!await proof.isAuthorized()) throw new UnauthorizedAuthProofError(); let user = await proof.getResource(); // Register if user doesn't exist if (!user) { const callbacks: RegisterCallback[] = []; await MysqlConnectionManager.wrapTransaction(async connection => { user = User.create({}); if (onRegister) { (await onRegister(connection, user)).forEach(c => callbacks.push(c)); } await user.save(connection, c => callbacks.push(c)); }); for (const callback of callbacks) { await callback(); } if (user) { if (!user!.isApproved()) { await new Mail(PENDING_ACCOUNT_REVIEW_MAIL_TEMPLATE, { username: user!.name, link: config.get('base_url') + Controller.route('accounts-approval'), }).send(config.get('app.contact_email')); } } else { throw new Error('Unable to register user.'); } } // Don't login if user isn't approved if (!user.isApproved()) { throw new PendingApprovalAuthError(); } // Login session.auth_id = user.id; if (onLogin) await onLogin(user); } public async isAuthenticated(session: Express.Session): Promise { if (typeof session.auth_id !== 'number') return false; const proof = await this.getProofForSession(session); if (!proof || !await proof.isValid() || !await proof.isAuthorized()) { await proof?.revoke(); return false; } return true; } public async isAuthenticatedViaRequest(req: Request): Promise { const proof = await this.getProofForRequest(req); return Boolean(proof && await proof.isValid() && await proof.isAuthorized()); } } export class AuthError extends Error { constructor(message: string) { super(message); } } export class AuthProofError extends AuthError { } export class InvalidAuthProofError extends AuthProofError { constructor() { super('Invalid auth proof.'); } } export class UnauthorizedAuthProofError extends AuthProofError { constructor() { super('Unauthorized auth proof.'); } } export class PendingApprovalAuthError extends AuthError { constructor() { super(`User is not approved.`); } } export type RegisterCallback = () => Promise;