You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lib/src/auth/provider/oauth/OAuth2LoginProvider.ts

99 lines
3.1 KiB

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)
}
}