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 { const authGuard = this.getApp().as(AuthComponent).getAuthGuard(); const userModelFactory = ModelFactory.get(User); const hasUsername = userModelFactory.hasComponent(UserNameComponent); const hasPassword = userModelFactory.hasComponent(UserPasswordComponent); res.formatViewData('auth/auth', { auth_methods: authGuard.getAuthMethodNames(), hasUsername: hasUsername, hasPassword: hasPassword, canRegisterWithPassword: hasUsername && hasPassword, }); } protected async postLogin(req: Request, res: Response): Promise { return await this.handleAuth(req, res, false); } protected async postRegister(req: Request, res: Response): Promise { return await this.handleAuth(req, res, true); } protected async handleAuth(req: Request, res: Response, isRegistration: boolean): Promise { 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> | 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 { 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 { const error = new UnknownRelationValidationError(User.table, 'identifier'); error.thingName = 'identifier'; error.value = identifier; throw error; } }