2020-04-24 12:12:27 +02:00
|
|
|
import AuthProof from "./AuthProof";
|
|
|
|
import MysqlConnectionManager from "../db/MysqlConnectionManager";
|
|
|
|
import User from "./models/User";
|
|
|
|
import UserEmail from "./models/UserEmail";
|
2020-04-25 09:35:49 +02:00
|
|
|
import {Connection} from "mysql";
|
2020-06-14 11:59:02 +02:00
|
|
|
import {Request} from "express";
|
2020-07-20 17:32:32 +02:00
|
|
|
import {PENDING_ACCOUNT_REVIEW_MAIL_TEMPLATE} from "../Mails";
|
|
|
|
import Mail from "../Mail";
|
|
|
|
import Controller from "../Controller";
|
|
|
|
import config from "config";
|
2020-04-24 12:12:27 +02:00
|
|
|
|
|
|
|
export default abstract class AuthGuard<P extends AuthProof> {
|
2020-04-25 09:35:49 +02:00
|
|
|
public abstract async getProofForSession(session: Express.Session): Promise<P | null>;
|
2020-04-24 12:12:27 +02:00
|
|
|
|
2020-06-14 11:59:02 +02:00
|
|
|
public async getProofForRequest(req: Request): Promise<P | null> {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-04-24 12:12:27 +02:00
|
|
|
public async getUserForSession(session: Express.Session): Promise<User | null> {
|
|
|
|
if (!await this.isAuthenticated(session)) return null;
|
2020-06-27 14:36:50 +02:00
|
|
|
return await User.getById<User>(session.auth_id);
|
2020-04-24 12:12:27 +02:00
|
|
|
}
|
|
|
|
|
2020-04-25 09:36:20 +02:00
|
|
|
public async authenticateOrRegister(session: Express.Session, proof: P, registerCallback?: (connection: Connection, userID: number) => Promise<(() => Promise<void>)[]>): Promise<void> {
|
2020-04-24 12:12:27 +02:00
|
|
|
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<void>)[] = [];
|
|
|
|
|
|
|
|
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));
|
2020-04-25 09:36:20 +02:00
|
|
|
if (registerCallback) {
|
|
|
|
(await registerCallback(connection, user.id!)).forEach(c => callbacks.push(c));
|
|
|
|
}
|
2020-04-24 12:12:27 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
for (const callback of callbacks) {
|
|
|
|
await callback();
|
|
|
|
}
|
|
|
|
|
2020-07-20 17:32:32 +02:00
|
|
|
if (user) {
|
|
|
|
if (User.isApprovalMode()) {
|
|
|
|
await new Mail(PENDING_ACCOUNT_REVIEW_MAIL_TEMPLATE, {
|
|
|
|
username: user!.name,
|
|
|
|
link: Controller.route('accounts-approval'),
|
|
|
|
}).send(config.get<string>('app.contact_email'));
|
|
|
|
}
|
|
|
|
} else {
|
2020-04-24 12:12:27 +02:00
|
|
|
throw new Error('Unable to register user.');
|
|
|
|
}
|
2020-04-25 09:36:20 +02:00
|
|
|
} else if (registerCallback) {
|
2020-04-25 16:08:20 +02:00
|
|
|
throw new UserAlreadyExistsAuthError(await proof.getEmail());
|
2020-04-24 12:12:27 +02:00
|
|
|
}
|
|
|
|
|
2020-06-16 11:12:58 +02:00
|
|
|
if (!user.isApproved()) throw new PendingApprovalAuthError();
|
|
|
|
|
2020-04-24 12:12:27 +02:00
|
|
|
session.auth_id = user.id;
|
|
|
|
}
|
|
|
|
|
|
|
|
public async isAuthenticated(session: Express.Session): Promise<boolean> {
|
|
|
|
return await this.checkCurrentSessionProofValidity(session);
|
|
|
|
}
|
|
|
|
|
|
|
|
public async logout(session: Express.Session): Promise<void> {
|
2020-04-25 09:35:49 +02:00
|
|
|
const proof = await this.getProofForSession(session);
|
2020-04-24 12:12:27 +02:00
|
|
|
if (proof) {
|
2020-04-25 09:35:49 +02:00
|
|
|
await proof.revoke(session);
|
2020-04-24 12:12:27 +02:00
|
|
|
}
|
|
|
|
session.auth_id = undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
private async checkCurrentSessionProofValidity(session: Express.Session): Promise<boolean> {
|
|
|
|
if (typeof session.auth_id !== 'number') return false;
|
|
|
|
|
2020-04-25 09:35:49 +02:00
|
|
|
const proof = await this.getProofForSession(session);
|
2020-04-24 12:12:27 +02:00
|
|
|
|
|
|
|
if (!proof || !await proof.isValid() || !await proof.isAuthorized() || !await proof.isOwnedBy(session.auth_id)) {
|
|
|
|
await this.logout(session);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2020-06-14 11:59:02 +02:00
|
|
|
|
|
|
|
public async isAuthenticatedViaRequest(req: Request): Promise<boolean> {
|
|
|
|
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<User | null> {
|
|
|
|
const proof = await this.getProofForRequest(req);
|
|
|
|
return proof ? await proof.getUser() : null;
|
|
|
|
}
|
|
|
|
|
2020-04-24 12:12:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export class AuthError extends Error {
|
|
|
|
constructor(message: string) {
|
|
|
|
super(message);
|
|
|
|
}
|
2020-04-25 16:08:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export class UserAlreadyExistsAuthError extends AuthError {
|
|
|
|
public readonly email: string;
|
|
|
|
|
|
|
|
constructor(userEmail: string) {
|
|
|
|
super(`User with email ${userEmail} already exists.`);
|
|
|
|
this.email = userEmail;
|
|
|
|
}
|
2020-06-16 11:12:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export class PendingApprovalAuthError extends AuthError {
|
|
|
|
constructor() {
|
|
|
|
super(`User is not approved.`);
|
|
|
|
}
|
2020-04-24 12:12:27 +02:00
|
|
|
}
|