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