import {Controller} from '../../../http/Controller' import {Inject, Injectable} from '../../../di' import {SecurityContext} from '../../context/SecurityContext' import {Session} from '../../../http/session/Session' import {ResponseFactory} from '../../../http/response/ResponseFactory' import {redirectToGet, view} from '../../../http/response/ViewResponseFactory' import {Routing} from '../../../service/Routing' import {ResponseObject, Route} from '../../../http/routing/Route' import {BasicLoginFormData, BasicLoginFormRequest} from './BasicLoginFormRequest' import {Valid, ValidationError} from '../../../forms' import {BasicRegisterFormData, BasicRegisterFormRequest} from './BasicRegisterFormRequest' import {AuthenticatableAlreadyExistsError} from '../../AuthenticatableAlreadyExistsError' import {Request} from '../../../http/lifecycle/Request' @Injectable() export class BasicLoginController extends Controller { public static routes({ enableRegistration = true } = {}): void { const controller = (request: Request) => { return request.make(BasicLoginController) } Route.group('auth', () => { Route.get('login', (request: Request) => controller(request).getLogin()) .pre('@auth:guest') .alias('@auth.login') Route.post('login', (request: Request) => controller(request).attemptLogin()) .pre('@auth:guest') .alias('@auth.login.attempt') Route.any('logout', (request: Request) => controller(request).attemptLogout()) .pre('@auth:required') .alias('@auth.logout') if ( enableRegistration ) { Route.get('register', (request: Request) => controller(request).getRegistration()) .pre('@auth:guest') .alias('@auth.register') Route.post('register', (request: Request) => controller(request).attemptRegister()) .pre('@auth:guest') .alias('@auth.register.attempt') } }).pre('@auth:web') } @Inject() protected readonly security!: SecurityContext @Inject() protected readonly session!: Session @Inject() protected readonly routing!: Routing public getLogin(): ResponseFactory { return this.getLoginView() } public getRegistration(): ResponseFactory { if ( !this.security.repository.supportsRegistration() ) { return redirectToGet('/') } return this.getRegistrationView() } public async attemptLogin(): Promise { const form = this.request.make(BasicLoginFormRequest) try { const data: Valid = await form.get() const user = await this.security.repository.getByIdentifier(data.username) if ( user && (await user.validateCredential(data.password)) ) { await this.security.authenticate(user) const intention = this.session.get('@extollo:auth.intention', '/') this.session.forget('@extollo:auth.intention') return redirectToGet(intention) } return this.getLoginView(['Invalid username/password.']) } catch (e: unknown) { if ( e instanceof ValidationError ) { return this.getLoginView(e.errors.all()) } throw e } } public async attemptRegister(): Promise { const form = this.request.make(BasicRegisterFormRequest) try { const data: Valid = await form.get() const user = await this.security.repository.createFromCredentials(data.username, data.password) await this.security.authenticate(user) const intention = this.session.get('@extollo:auth.intention', '/') this.session.forget('@extollo:auth.intention') return redirectToGet(intention) } catch (e: unknown) { if ( e instanceof ValidationError ) { return this.getRegistrationView(e.errors.all()) } else if ( e instanceof AuthenticatableAlreadyExistsError ) { return this.getRegistrationView(['A user with that username already exists.']) } throw e } } public async attemptLogout(): Promise { await this.security.flush() return this.getMessageView('You have been logged out.') } protected getRegistrationView(errors?: string[]): ResponseFactory { return view('@extollo:auth:register', { formAction: this.routing.getNamedPath('@auth.register.attempt').toRemote, errors, }) } protected getLoginView(errors?: string[]): ResponseFactory { return view('@extollo:auth:login', { formAction: this.routing.getNamedPath('@auth.login.attempt').toRemote, errors, }) } protected getMessageView(message: string): ResponseFactory { return view('@extollo:auth:message', { message, }) } }