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"; export default abstract class AuthGuard

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

; protected async getProofForRequest(_req: Request): Promise

{ return null; } public async getProof(req: Request): Promise

{ let proof = await this.isAuthenticatedViaRequest(req); if (!proof && req.session) { proof = await this.isAuthenticated(req.session); } return proof; } public async isAuthenticated(session: Express.Session): Promise

{ if (!session.is_authenticated) return null; const proof = await this.getProofForSession(session); if (!proof || !await proof.isValid() || !await proof.isAuthorized()) { await proof?.revoke(); session.is_authenticated = false; return null; } return proof; } public async isAuthenticatedViaRequest(req: Request): Promise

{ const proof = await this.getProofForRequest(req); if (!proof || !await proof.isValid() || !await proof.isAuthorized()) { await proof?.revoke(); return null; } return proof; } 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[] = []; user = await MysqlConnectionManager.wrapTransaction(async connection => { const user = User.create({}); await user.save(connection, c => callbacks.push(c)); if (onRegister) { (await onRegister(connection, user)).forEach(c => callbacks.push(c)); } return user; }); for (const callback of callbacks) { await callback(); } if (!user.isApproved()) { await new Mail(PENDING_ACCOUNT_REVIEW_MAIL_TEMPLATE, { username: (await user.mainEmail.get())?.getOrFail('email'), link: config.get('base_url') + Controller.route('accounts-approval'), }).send(config.get('app.contact_email')); } } // Don't login if user isn't approved if (!user.isApproved()) { throw new PendingApprovalAuthError(); } // Login session.is_authenticated = true; if (onLogin) await onLogin(user); return user; } } export class AuthError extends Error { } export class AuthProofError extends AuthError { } export class InvalidAuthProofError extends AuthProofError { public constructor() { super('Invalid auth proof.'); } } export class UnauthorizedAuthProofError extends AuthProofError { public constructor() { super('Unauthorized auth proof.'); } } export class PendingApprovalAuthError extends AuthError { public constructor() { super(`User is not approved.`); } } export type RegisterCallback = () => Promise;