import { Injectable } from '@nestjs/common' ; import { AccountBlockedException , AccountService } from '@fc/account' ; import { ConfigService } from '@fc/config' ; import { LoggerLevelNames , LoggerService } from '@fc/logger' ; import { Acr , OidcSession } from '@fc/oidc' ; import { OidcCtx , OidcProviderAuthorizationEvent , OidcProviderConfig , OidcProviderMiddlewareStep , OidcProviderRoutes , OidcProviderService , OidcProviderTokenEvent , OidcProviderUserinfoEvent , } from '@fc/oidc-provider' ; import { OidcProviderErrorService } from '@fc/oidc-provider/services' ; import { ServiceProviderAdapterMongoService } from '@fc/service-provider-adapter-mongo' ; import { ISessionBoundContext , SessionService } from '@fc/session' ; import { IEventContext , TrackingService } from '@fc/tracking' ; import { CoreFailedPersistenceException , CoreInvalidAcrException , CoreLowAcrException , } from '../exceptions' ; import { AcrValues , pickAcr } from '../transforms' ; import { ComputeIdp , ComputeSp } from '../types' ; @ Injectable ( ) export class CoreService { constructor ( private readonly logger , private readonly config , private readonly oidcProvider , private readonly oidcErrorService , private readonly account , private readonly serviceProvider , private readonly sessionService , private readonly tracking ) { this . logger . setContext ( this . constructor . name ) ; } /**
     * Configure hooks on oidc-provider
     */ onModuleInit ( ) { this . registerMiddlewares ( ) ; } private getEventContext ( ctx ) { const interactionId = this . oidcProvider . getInteractionIdFromCtx ( ctx ) ; const ip = this . getIpFromCtx ( ctx ) ; const eventContext = { fc : { interactionId } , headers : { 'x-forwarded-for' : ip } , } ; this . logger . trace ( eventContext ) ; return eventContext ; } private registerMiddlewares ( ) { const { defaultAcrValue , forcedPrompt , knownAcrValues } = this . config . get < OidcProviderConfig > ( 'OidcProvider' ) ; this . oidcProvider . registerMiddleware ( OidcProviderMiddlewareStep . BEFORE , OidcProviderRoutes . AUTHORIZATION , this . resetCookies . bind ( this ) ) ; /** Force prompt @see overrideAuthorizePrompt header */ this . oidcProvider . registerMiddleware ( OidcProviderMiddlewareStep . BEFORE , OidcProviderRoutes . AUTHORIZATION , this . overrideAuthorizePrompt . bind ( this , forcedPrompt . join ( ' ' ) ) ) ; /** Force Acr values @see overrideAuthorizeAcrValues header */ this . oidcProvider . registerMiddleware ( OidcProviderMiddlewareStep . BEFORE , OidcProviderRoutes . AUTHORIZATION , this . overrideAuthorizeAcrValues . bind ( this , knownAcrValues , defaultAcrValue ) ) ; this . oidcProvider . registerMiddleware ( OidcProviderMiddlewareStep . AFTER , OidcProviderRoutes . AUTHORIZATION , this . authorizationMiddleware . bind ( this ) ) ; this . oidcProvider . registerMiddleware ( OidcProviderMiddlewareStep . AFTER , OidcProviderRoutes . TOKEN , this . tokenMiddleware . bind ( this ) ) ; this . oidcProvider . registerMiddleware ( OidcProviderMiddlewareStep . AFTER , OidcProviderRoutes . USERINFO , this . userinfoMiddleware . bind ( this ) ) ; } private async authorizationMiddleware ( ctx ) { /**
         * Abort middleware if authorize is in error
         *
         * We do not want to start a session
         * nor trigger authorization event for invalid requests
         */ if ( ctx . oidc [ 'isError' ] === true ) { return ; } const eventContext = this . getEventContext ( ctx ) ; const { interactionId } = eventContext . fc ; const { req , res } = ctx ; const sessionId = await this . sessionService . reset ( req , res ) ; const { acr_values : spAcr , client_id : spId } = ctx . oidc . params ; const { name : spName } = await this . serviceProvider . getById ( spId ) ; const sessionProperties = { interactionId , spAcr , spId , spName , } ; const sessionContext = { moduleName : 'OidcClient' , sessionId , } ; await this . sessionService . set ( sessionContext , sessionProperties ) ; const authEventContext = { ... eventContext , spAcr , spId , spName , } ; this . tracking . track ( OidcProviderAuthorizationEvent , authEventContext ) ; } private tokenMiddleware ( ctx ) { try { const eventContext = this . getEventContext ( ctx ) ; this . tracking . track ( OidcProviderTokenEvent , eventContext ) ; } catch ( exception ) { this . oidcErrorService . throwError ( ctx , exception ) ; } } private userinfoMiddleware ( ctx ) { try { const eventContext = this . getEventContext ( ctx ) ; this . tracking . track ( OidcProviderUserinfoEvent , eventContext ) ; } catch ( exception ) { this . oidcErrorService . throwError ( ctx , exception ) ; } } param ctx private resetCookies ( ctx ) { ctx . req . headers . cookie = '' ; } param ctx param overrideValue private overrideAuthorizePrompt ( overrideValue , ctx ) { this . logger . trace ( { ctx , overrideValue } ) ; TODO see https switch ( ctx . method ) { case 'GET' : ctx . query . prompt = overrideValue ; break ; case 'POST' : ctx . req . body . prompt = overrideValue ; break ; default : this . logger . warn ( `Unsupported method "${ ctx . method } on /authorize endpoint". This should not happen` ) ; } } getIpFromCtx ( ctx ) { return ctx . req . headers [ 'x-forwarded-for' ] ; } private overrideAuthorizeAcrValues ( knownAcrValues , defaultAcrValue , ctx ) { this . logger . trace ( { ctx , knownAcrValues } ) ; if ( [ 'POST' , 'GET' ] . includes ( ctx . method ) ) { const isPostMethod = ctx . method === 'POST' ; const data = isPostMethod ? ctx . req . body : ctx . query ; const { acr_values : dataAcrValues } = data as AcrValues ; const acrValues = dataAcrValues . split ( /[ ]+/ ) ; data . acr_values = pickAcr ( knownAcrValues , acrValues , defaultAcrValue ) ; } else { this . logger . warn ( `Unsupported method "${ ctx . method } on /authorize endpoint". This should not happen` ) ; } } param identity async checkIfAccountIsBlocked ( identityHash ) { const accountIsBlocked = await this . account . isBlocked ( identityHash ) ; if ( accountIsBlocked ) { throw new AccountBlockedException ( ) ; } } param providerId param sub param entityId private getFederation ( providerId , sub , entityId ? ) { const key = entityId || providerId ; return { [ key ] : { sub } } ; } param { string } spId param { string } entityId param { string } subSp param { string } hashSp param { string } idpId param { string } subIdp async computeInteraction ( { entityId , hashSp , spId , subSp } , { idpId , subIdp } ) { const spFederation = this . getFederation ( spId , subSp , entityId ) ; const idpFederation = this . getFederation ( idpId , subIdp ) ; const interaction = { identityHash : hashSp , idpFederation : idpFederation , lastConnection : new Date ( ) , spFederation : spFederation , } ; this . logger . trace ( interaction ) ; try { const accountId = await this . account . storeInteraction ( interaction ) ; return accountId ; } catch ( error ) { throw new CoreFailedPersistenceException ( error ) ; } } checkIfAcrIsValid ( received , requested ) { todo see https if ( ! received || ! requested ) { this . logger . trace ( { error : 'received or requested ACR missing' } , LoggerLevelNames . WARN ) ; throw new CoreInvalidAcrException ( ) ; } if ( ! this . isAcrValid ( received , requested ) ) { this . logger . trace ( { error : 'received ACR lower than requested' } , LoggerLevelNames . WARN ) ; throw new CoreLowAcrException ( ) ; } } param source param target returns isAcrValid ( source , target ) { const sourceAcr = Acr [ source ] ; const targetAcr = Acr [ target ] ; return sourceAcr >= targetAcr ; } async rejectInvalidAcr ( currentAcrValue , allowedAcrValues , { req , res } ) { const isAllowed = allowedAcrValues . includes ( currentAcrValue ) ; if ( isAllowed ) { this . logger . trace ( { isAllowed , currentAcrValue , allowedAcrValues } ) ; return false ; } const error = 'invalid_acr' ; const allowedAcrValuesString = allowedAcrValues . join ( ',' ) ; const errorDescription = `acr_value is not valid, should be equal one of these values, expected ${ allowedAcrValuesString }, got ${ currentAcrValue }` ; await this . oidcProvider . abortInteraction ( req , res , error , errorDescription ) ; this . logger . trace ( { isAllowed , currentAcrValue , allowedAcrValues } , LoggerLevelNames . WARN ) ; return true ; } }