swaf/src/auth/AuthController.ts

140 lines
5.5 KiB
TypeScript

import {NextFunction, Request, Response} from "express";
import Controller from "../Controller.js";
import ModelFactory from "../db/ModelFactory.js";
import {UnknownRelationValidationError} from "../db/Validator.js";
import {BadRequestError} from "../HttpError.js";
import AuthComponent, {AuthMiddleware, RequireAuthMiddleware, RequireGuestMiddleware} from "./AuthComponent.js";
import AuthMethod from "./AuthMethod.js";
import AuthProof from "./AuthProof.js";
import User from "./models/User.js";
import UserNameComponent from "./models/UserNameComponent.js";
import UserPasswordComponent from "./password/UserPasswordComponent.js";
export default class AuthController extends Controller {
public static flashSuccessfulAuthenticationWelcomeMessage(
user: User,
req: Request,
messagePrefix: string,
): void {
const name = user.asOptional(UserNameComponent)?.getName();
req.flash('success', `${messagePrefix} Welcome${name ? `, ${name}` : ''}.`);
}
public getRoutesPrefix(): string {
return '/auth';
}
public routes(): void {
this.post('/logout', this.postLogout, 'logout', RequireAuthMiddleware);
this.use(async (req, res, next) => {
const authGuard = this.getApp().as(AuthComponent).getAuthGuard();
if (await authGuard.interruptAuth(req, res)) return;
next();
});
this.get('/', this.getAuth, 'auth', RequireGuestMiddleware);
this.post('/login', this.postLogin, 'login', RequireGuestMiddleware);
this.post('/register', this.postRegister, 'register', RequireGuestMiddleware);
}
protected async getAuth(req: Request, res: Response, _next: NextFunction): Promise<void> {
const authGuard = this.getApp().as(AuthComponent).getAuthGuard();
const userModelFactory = ModelFactory.get(User);
const hasUsername = userModelFactory.hasComponent(UserNameComponent);
res.formatViewData('auth/auth', {
auth_methods: authGuard.getAuthMethodNames(),
has_username: hasUsername,
register_with_password: hasUsername && userModelFactory.hasComponent(UserPasswordComponent),
});
}
protected async postLogin(req: Request, res: Response): Promise<void> {
return await this.handleAuth(req, res, false);
}
protected async postRegister(req: Request, res: Response): Promise<void> {
return await this.handleAuth(req, res, true);
}
protected async handleAuth(req: Request, res: Response, isRegistration: boolean): Promise<void> {
if (isRegistration && !req.body.auth_method) {
throw new BadRequestError('Cannot register without specifying desired auth_method.',
'Please specify auth_method.', req.url);
}
const authGuard = this.getApp().as(AuthComponent).getAuthGuard();
const identifier = req.body.identifier;
if (!identifier) throw new BadRequestError('Identifier not specified.', 'Please try again.', req.originalUrl);
// Get requested auth method
if (req.body.auth_method) {
const method = await authGuard.getAuthMethodByName(req.body.auth_method);
if (!method) {
throw new BadRequestError('Invalid auth method: ' + req.body.auth_method,
'Available methods are: ' + authGuard.getAuthMethodNames(), req.url);
}
// Register
if (isRegistration) return await method.attemptRegister(req, res, identifier);
const user = await method.findUserByIdentifier(identifier);
// Redirect to registration if user not found
if (!user) return await this.redirectToRegistration(req, res, identifier);
// Login
return await method.attemptLogin(req, res, user);
}
const methods = await authGuard.getAuthMethodsByIdentifier(identifier);
// Redirect to registration if user not found
if (methods.length === 0) return await this.redirectToRegistration(req, res, identifier);
// Choose best matching method
let user: User | null = null;
let method: AuthMethod<AuthProof<User>> | null = null;
let weight = -1;
for (const entry of methods) {
const methodWeight = entry.method.getWeightForRequest(req);
if (methodWeight > weight) {
user = entry.user;
method = entry.method;
weight = methodWeight;
}
}
if (!method || !user) ({method, user} = methods[0]); // Default to first method
// Login
return await method.attemptLogin(req, res, user);
}
protected async postLogout(req: Request, res: Response, _next: NextFunction): Promise<void> {
const userId = typeof req.body.user_id === 'string' ? parseInt(req.body.user_id) : null;
const proofs = await req.as(AuthMiddleware).getAuthGuard().getProofs(req);
for (const proof of proofs) {
if (userId === null || (await proof.getResource())?.id === userId) {
await proof.revoke();
}
}
req.flash('success', 'Successfully logged out.');
res.redirect(req.getIntendedUrl() || '/');
}
protected async redirectToRegistration(req: Request, res: Response, identifier: string): Promise<void> {
const error = new UnknownRelationValidationError(User.table, 'identifier');
error.thingName = 'identifier';
error.value = identifier;
throw error;
}
}