import {LoginProvider, LoginProviderConfig} from '../LoginProvider' import {ResponseObject, Route} from '../../../http/routing/Route' import {Inject, Injectable} from '../../../di' import {Routing} from '../../../service/Routing' import {GuestRequiredMiddleware} from '../../middleware/GuestRequiredMiddleware' import {redirect} from '../../../http/response/RedirectResponseFactory' import {view} from '../../../http/response/ViewResponseFactory' import {Request} from '../../../http/lifecycle/Request' import {Awaitable} from '../../../util' import {Authenticatable} from '../../types' export interface OAuth2LoginProviderConfig extends LoginProviderConfig { displayName: string, clientId: string|number clientSecret: string loginUrl: string loginMessage?: string logoutUrl?: string tokenUrl: string, userUrl: string, } /** * LoginProvider implementation for OAuth2-based logins. */ @Injectable() export abstract class OAuth2LoginProvider extends LoginProvider { @Inject() protected readonly routing!: Routing public routes(): void { super.routes() Route.any('redirect') .alias(`@auth:${this.name}:redirect`) .pre(GuestRequiredMiddleware) .handledBy(() => redirect(this.getLoginUrl())) Route.any('callback') .alias(`@auth:${this.name}:callback`) .pre(GuestRequiredMiddleware) .passingRequest() .handledBy(this.handleCallback.bind(this)) } protected async handleCallback(request: Request): Promise { const user = await this.callback(request) if ( user ) { await this.security.authenticate(user) return this.redirectToIntendedRoute() } return redirect(this.routing.getNamedPath(`@auth:${this.name}:login`).toRemote) } /** * After redirecting back from the OAuth2 server, look up the user information. * @param request * @protected */ protected abstract callback(request: Request): Awaitable public login(): ResponseObject { const buttonUrl = this.routing .getNamedPath(`@auth:${this.name}:redirect`) .toRemote return view('@extollo:auth:message', { message: this.config.loginMessage ?? `Sign-in with ${this.config.displayName} to continue`, buttonText: 'Sign-in', buttonUrl, }) } public async logout(): Promise { await this.security.flush() if ( this.config.logoutUrl ) { return redirect(this.config.logoutUrl) } return view('@extollo:auth:message', { message: 'You have been signed-out', }) } /** * Get the URL where the user should be redirected to sign-in. * @protected */ protected getLoginUrl(): string { const callbackRoute = this.routing.getNamedPath(`@auth:${this.name}:callback`) return this.config.loginUrl .replace(/%c/g, String(this.config.clientId)) .replace(/%r/g, callbackRoute.toRemote) } }