99 lines
3.1 KiB
TypeScript
99 lines
3.1 KiB
TypeScript
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<TConfig extends OAuth2LoginProviderConfig> extends LoginProvider<TConfig> {
|
|
@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<ResponseObject> {
|
|
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<Authenticatable>
|
|
|
|
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<ResponseObject> {
|
|
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)
|
|
}
|
|
}
|