import { get } from 'lodash' ; import { InteractionResults , KoaContextWithOIDC , Provider , } from 'oidc-provider' ; import { HttpOptions } from 'openid-client' ; import { Global , Inject , Injectable } from '@nestjs/common' ; import { HttpAdapterHost } from '@nestjs/core' ; import { LoggerService } from '@fc/logger' ; import { OidcSession } from '@fc/oidc' ; import { OidcClientSession } from '@fc/oidc-client' ; import { Redis , REDIS_CONNECTION_TOKEN } from '@fc/redis' ; import { OidcProviderMiddlewarePattern , OidcProviderMiddlewareStep , OidcProviderRoutes , } from './enums' ; import { OidcProviderBindingException , OidcProviderInitialisationException , OidcProviderInteractionNotFoundException , OidcProviderRuntimeException , } from './exceptions' ; import { OidcProviderConfigService , OidcProviderErrorService , } from './services' ; import { OidcProviderGrantService } from './services/oidc-provider-grant.service' ; const PATHS = { [ OidcProviderRoutes . TOKEN ] : 'oidc.entities.Grant.accountId' , [ OidcProviderRoutes . USERINFO ] : 'oidc.entities.Account.accountId' , } ; const DEFAULT_PATH = 'oidc.entities.Interaction.uid' ; /**
 * Make service global in order to ease retrieval and override from other libraries.
 * It is unlikely we will need multiple instances of this module any time.
 */ @ Global ( ) @ Injectable ( ) export class OidcProviderService { private ProviderProxy = Provider ; private provider : Provider ; private configuration ; constructor ( private httpAdapterHost , readonly logger , @ Inject ( REDIS_CONNECTION_TOKEN ) readonly redis , private readonly errorService , private readonly configService , private readonly grantService ) { this . logger . setContext ( this . constructor . name ) ; } see https see https async onModuleInit ( ) { const { prefix , issuer , configuration } = await this . configService . getConfig ( this ) ; this . configuration = configuration ; this . logger . debug ( 'Initializing oidc-provider' ) ; try { this . provider = new this . ProviderProxy ( issuer , { ... configuration , httpOptions : this . getHttpOptions . bind ( this ) , } ) ; this . provider . proxy = true ; } catch ( error ) { throw new OidcProviderInitialisationException ( ) ; } this . logger . debug ( 'Mouting oidc-provider middleware' ) ; try { see https this . httpAdapterHost . httpAdapter . use ( prefix , this . provider . callback ( ) ) ; } catch ( error ) { throw new OidcProviderBindingException ( ) ; } this . errorService . catchErrorEvents ( this . provider ) ; } getInteractionIdFromCtx ( ctx ) { const path = PATHS [ ctx . req . _parsedUrl . pathname ] || DEFAULT_PATH ; const interactionId = get ( ctx , path ) ; if ( ! interactionId ) { throw new OidcProviderInteractionNotFoundException ( ) ; } this . logger . trace ( { interactionId } ) ; return interactionId ; } warning getProvider ( ) { return this . provider ; } see https param { HttpOptions } options private getHttpOptions ( options ) { options . timeout = this . configuration . timeout ; return options ; } param req param res todo async getInteraction ( req , res ) { try { const interactionDetail = await this . provider . interactionDetails ( req , res ) ; this . logger . trace ( { text : 'Interaction Type' , interactionType : interactionDetail . prompt . name , interactionReason : interactionDetail . prompt . reasons , } ) ; return interactionDetail ; } catch ( error ) { throw new OidcProviderRuntimeException ( error ) ; } } todo see async abortInteraction ( req , res , error , errorDescription ) { try { const result = { error , error_description : errorDescription } ; const finished = await this . provider . interactionFinished ( req , res , result ) ; return finished ; } catch ( error ) { throw new OidcProviderRuntimeException ( error ) ; } } param { any } req param { any } res param { OidcSession } session async finishInteraction ( req , res , session ) { const { spAcr : acr , amr , spIdentity } = session ; see https const grant = await this . grantService . generateGrant ( this . provider , req , res , spIdentity . sub ) ; const grantId = await this . grantService . saveGrant ( grant ) ; const result = { login : { amr , acr , accountId : spIdentity . sub , ts : Math . floor ( Date . now ( ) / 1000 ) , remember : false , } , /**
             * We need to return this information, it will always be empty arrays
             * since franceConnect does not allow for partial authorizations yet,
             * it's an "all or nothing" consent.
             */ consent : { grantId , } , } as InteractionResults ; try { return await this . provider . interactionFinished ( req , res , result ) ; } catch ( error ) { throw new OidcProviderRuntimeException ( error ) ; } } private async runMiddlewareBeforePattern ( { step , path , pattern , ctx } , middleware ) { if ( step === OidcProviderMiddlewareStep . BEFORE && path === pattern ) { await middleware ( ctx ) ; } } private async runMiddlewareAfterPattern ( { step , route , path , pattern , ctx } , middleware ) { if ( step === OidcProviderMiddlewareStep . AFTER && see https ( route === pattern || path === pattern ) ) { await middleware ( ctx ) ; } } see https registerMiddleware ( step , pattern , middleware ) { this . provider . use ( async ( ctx , next ) => { const { path , oidc : { route = '' } = { } } = ctx ; await this . runMiddlewareBeforePattern ( { step , path , pattern , ctx } , middleware ) ; await next ( ) ; await this . runMiddlewareAfterPattern ( { step , route , path , pattern , ctx } , middleware ) ; } ) ; } }