swaf/src/auth/AuthGuard.ts

137 lines
4.6 KiB
TypeScript
Raw Normal View History

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";
import {Request} from "express";
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
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;
return await User.getById<User>(session.auth_id);
2020-04-24 12:12:27 +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));
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();
}
if (user) {
if (!user!.isApproved()) {
await new Mail(PENDING_ACCOUNT_REVIEW_MAIL_TEMPLATE, {
username: user!.name,
link: config.get<string>('base_url') + 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.');
}
} 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
}
if (!user.isApproved()) {
throw new PendingApprovalAuthError();
}
2020-06-16 11:12:58 +02:00
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;
}
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.`);
}
}