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.isApprovalMode()) {
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.`);
}
}