swaf/src/auth/AuthGuard.ts

95 lines
3.3 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";
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 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) {
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
}
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;
}
}
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-04-24 12:12:27 +02:00
}