import AuthProof from "./AuthProof"; import MysqlConnectionManager from "../db/MysqlConnectionManager"; import User from "./models/User"; import UserEmail from "./models/UserEmail"; 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

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

; public async getProofForRequest(req: Request): Promise

{ return null; } public async getUserForSession(session: Express.Session): Promise { if (!await this.isAuthenticated(session)) return null; return await User.getById(session.auth_id); } public async authenticateOrRegister(session: Express.Session, proof: P, registerCallback?: (connection: Connection, userID: number) => Promise<(() => Promise)[]>): Promise { if (!await proof.isAuthorized()) { throw new AuthError('Invalid argument: cannot authenticate with an unauthorized proof.'); } let user = await proof.getUser(); if (!user) { // Register const callbacks: (() => Promise)[] = []; await MysqlConnectionManager.wrapTransaction(async connection => { const email = await proof.getEmail(); user = new User({ name: email.split('@')[0], }); await user.save(connection, c => callbacks.push(c)); const userEmail = new UserEmail({ user_id: user.id, email: email, main: true, }); await userEmail.save(connection, c => callbacks.push(c)); if (registerCallback) { (await registerCallback(connection, user.id!)).forEach(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.'); } } else if (registerCallback) { throw new UserAlreadyExistsAuthError(await proof.getEmail()); } if (!user.isApproved()) { throw new PendingApprovalAuthError(); } session.auth_id = user.id; } public async isAuthenticated(session: Express.Session): Promise { return await this.checkCurrentSessionProofValidity(session); } public async logout(session: Express.Session): Promise { const proof = await this.getProofForSession(session); if (proof) { await proof.revoke(session); } session.auth_id = undefined; } private async checkCurrentSessionProofValidity(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.isOwnedBy(session.auth_id)) { await this.logout(session); return false; } return true; } public async isAuthenticatedViaRequest(req: Request): Promise { const proof = await this.getProofForRequest(req); if (proof && await proof.isValid() && await proof.isAuthorized()) { return true; } else { return false; } } public async getUserForRequest(req: Request): Promise { const proof = await this.getProofForRequest(req); return proof ? await proof.getUser() : null; } } export class AuthError extends Error { constructor(message: string) { super(message); } } export class UserAlreadyExistsAuthError extends AuthError { public readonly email: string; constructor(userEmail: string) { super(`User with email ${userEmail} already exists.`); this.email = userEmail; } } export class PendingApprovalAuthError extends AuthError { constructor() { super(`User is not approved.`); } }