TODO import { JWK } from 'jose-openid-client' ; import { CallbackExtras , Client , TokenSet } from 'openid-client' ; import { Inject , Injectable } from '@nestjs/common' ; import { CryptographyService } from '@fc/cryptography' ; import { LoggerLevelNames , LoggerService } from '@fc/logger' ; import { IOidcIdentity , IServiceProviderAdapter , SERVICE_PROVIDER_SERVICE_TOKEN , } from '@fc/oidc' ; import { OidcClientFailedToFetchBlacklist , OidcClientGetEndSessionUrlException , OidcClientIdpBlacklistedException , OidcClientInvalidStateException , OidcClientMissingCodeException , OidcClientMissingStateException , OidcClientTokenFailedException , } from '../exceptions' ; import { ExtraTokenParams , IGetAuthorizeUrlParams , TokenParams , } from '../interfaces' ; import { OidcClientConfigService } from './oidc-client-config.service' ; import { OidcClientIssuerService } from './oidc-client-issuer.service' ; @ Injectable ( ) export class OidcClientUtilsService { constructor ( private readonly logger , private readonly issuer , private readonly oidcClientConfig , private readonly crypto , @ Inject ( SERVICE_PROVIDER_SERVICE_TOKEN ) private readonly serviceProvider ) { this . logger . setContext ( this . constructor . name ) ; } async buildAuthorizeParameters ( ) { const { stateLength } = await this . oidcClientConfig . get ( ) ; const state = this . crypto . genRandomString ( stateLength ) ; TODO see https const nonce = this . crypto . genRandomString ( stateLength ) ; const params = { state , nonce , } ; this . logger . trace ( { params } ) ; return params ; } async getAuthorizeUrl ( { state , scope , idpId , acr_values , nonce , claims , } ) { const client = await this . issuer . getClient ( idpId ) ; const params = { scope , state , nonce , claims , acr_values , prompt : 'login' , } ; this . logger . trace ( { params } ) ; return client . authorizationUrl ( params ) ; } async wellKnownKeys ( ) { const { jwks : { keys } , } = await this . oidcClientConfig . get ( ) ; TODO see https const publicKeys = keys . map ( ( key ) => JWK . asKey ( key as any ) . toJWK ( ) ) ; return { keys : publicKeys } ; } private async extractParams ( req , client , stateFromSession ) { /**
         * Although it is not noted as async
         * openidClient.callbackParams is and should be awaited
         */ const receivedParams = await client . callbackParams ( req ) ; if ( ! receivedParams . code ) { throw new OidcClientMissingCodeException ( ) ; } if ( ! receivedParams . state ) { throw new OidcClientMissingStateException ( ) ; } if ( receivedParams . state !== stateFromSession ) { throw new OidcClientInvalidStateException ( ) ; } return receivedParams ; } private buildExtraParameters ( extraParams ) { if ( extraParams ) { return { exchangeBody : extraParams } ; } return { } ; } async getTokenSet ( req , ipdId , params , extraParams ? ) { this . logger . debug ( 'getTokenSet' ) ; const client = await this . issuer . getClient ( ipdId ) ; const { state } = params ; const receivedParams = await this . extractParams ( req , client , state ) ; let tokenSet ; try { tokenSet = await client . callback ( client . metadata . redirect_uris . join ( ',' ) , receivedParams , { ... params , response_type : client . metadata . response_types . join ( ',' ) , } , this . buildExtraParameters ( extraParams ) ) ; } catch ( error ) { this . logger . trace ( { error } , LoggerLevelNames . WARN ) ; throw new OidcClientTokenFailedException ( ) ; } this . logger . trace ( { tokenSet } ) ; return tokenSet ; } async revokeToken ( accessToken , idpId ) { this . logger . debug ( 'revokeToken' ) ; const client = await this . issuer . getClient ( idpId ) ; this . logger . trace ( { accessToken , idpId } ) ; await client . revoke ( accessToken ) ; } async getUserInfo ( accessToken , idpId ) { this . logger . debug ( 'getUserInfo' ) ; const client = await this . issuer . getClient ( idpId ) ; const userInfo = ( await client . userinfo ( accessToken ) ) as IOidcIdentity ; this . logger . trace ( { accessToken , idpId , userInfo } ) ; return userInfo ; } param { string } idpId param { string } stateFromSession param { TokenSet | string } idTokenHint param { string } postLogoutRedirectUri returns { Promise < string > } async getEndSessionUrl ( idpId , stateFromSession , idTokenHint ? , postLogoutRedirectUri ? ) { this . logger . debug ( 'getEndSessionUrl' ) ; const client = await this . issuer . getClient ( idpId ) ; let endSessionUrl ; try { endSessionUrl = client . endSessionUrl ( { id_token_hint : idTokenHint , post_logout_redirect_uri : postLogoutRedirectUri , state : stateFromSession , } ) ; } catch ( error ) { this . logger . trace ( { error } , LoggerLevelNames . WARN ) ; throw new OidcClientGetEndSessionUrlException ( ) ; } this . logger . trace ( { idpId , endSessionUrl } ) ; return endSessionUrl ; } param { string } spId param { string } idpId returns { Promise < boolean > } async checkIdpBlacklisted ( spId , idpId ) { let isIdpExcluded = false ; try { isIdpExcluded = await this . serviceProvider . shouldExcludeIdp ( spId , idpId ) ; } catch ( error ) { this . logger . trace ( { error } , LoggerLevelNames . WARN ) ; throw new OidcClientFailedToFetchBlacklist ( ) ; } if ( isIdpExcluded ) { this . logger . trace ( { isIdpExcluded } , LoggerLevelNames . WARN ) ; throw new OidcClientIdpBlacklistedException ( ) ; } this . logger . trace ( { check : { spId , idpId , isIdpExcluded } } ) ; return false ; } }