swaf/src/auth/AuthGuard.ts

116 lines
3.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";
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-07-26 11:37:01 +02:00
import ModelFactory from "../db/ModelFactory";
2020-04-24 12:12:27 +02:00
2020-07-25 10:28:50 +02:00
export default abstract class AuthGuard<P extends AuthProof<User>> {
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-07-25 10:28:50 +02:00
public async authenticateOrRegister(
session: Express.Session,
proof: P,
onLogin?: (user: User) => Promise<void>,
onRegister?: (connection: Connection, user: User) => Promise<RegisterCallback[]>
): Promise<void> {
if (!await proof.isValid()) throw new InvalidAuthProofError();
if (!await proof.isAuthorized()) throw new UnauthorizedAuthProofError();
2020-04-24 12:12:27 +02:00
2020-07-25 10:28:50 +02:00
let user = await proof.getResource();
2020-04-24 12:12:27 +02:00
2020-07-25 10:28:50 +02:00
// Register if user doesn't exist
if (!user) {
const callbacks: RegisterCallback[] = [];
2020-04-24 12:12:27 +02:00
await MysqlConnectionManager.wrapTransaction(async connection => {
2020-07-26 11:37:01 +02:00
user = ModelFactory.get(User).make({});
2020-07-25 10:28:50 +02:00
if (onRegister) {
(await onRegister(connection, user)).forEach(c => callbacks.push(c));
}
2020-07-26 11:37:01 +02:00
await user.save(connection, 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.');
}
}
2020-07-25 10:28:50 +02:00
// Don't login if user isn't approved
if (!user.isApproved()) {
throw new PendingApprovalAuthError();
}
2020-06-16 11:12:58 +02:00
2020-07-25 10:28:50 +02:00
// Login
2020-04-24 12:12:27 +02:00
session.auth_id = user.id;
2020-07-25 10:28:50 +02:00
if (onLogin) await onLogin(user);
2020-04-24 12:12:27 +02:00
}
public async isAuthenticated(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
2020-07-25 10:28:50 +02:00
if (!proof || !await proof.isValid() || !await proof.isAuthorized()) {
await proof?.revoke();
2020-04-24 12:12:27 +02:00
return false;
}
return true;
}
public async isAuthenticatedViaRequest(req: Request): Promise<boolean> {
const proof = await this.getProofForRequest(req);
2020-07-25 10:28:50 +02:00
return Boolean(proof && await proof.isValid() && await proof.isAuthorized());
}
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
}
2020-07-25 10:28:50 +02:00
export class AuthProofError extends AuthError {
}
export class InvalidAuthProofError extends AuthProofError {
constructor() {
super('Invalid auth proof.');
}
}
2020-04-25 16:08:20 +02:00
2020-07-25 10:28:50 +02:00
export class UnauthorizedAuthProofError extends AuthProofError {
constructor() {
super('Unauthorized auth proof.');
2020-04-25 16:08:20 +02:00
}
2020-06-16 11:12:58 +02:00
}
export class PendingApprovalAuthError extends AuthError {
constructor() {
super(`User is not approved.`);
}
}
2020-07-25 10:28:50 +02:00
export type RegisterCallback = () => Promise<void>;