import { BoosterConfig , UserEnvelope , TokenVerifierConfig } from '@boostercloud/framework-types' ; import * as jwksRSA from 'jwks-rsa' ; import * as jwt from 'jsonwebtoken' ; class TokenVerifierClient { private client ? : jwksRSA . JwksClient ; private options ? : jwt . VerifyOptions ; public constructor ( private tokenVerifierConfig ) { if ( this . tokenVerifierConfig . jwksUri ) { this . client = jwksRSA ( { jwksUri : this . tokenVerifierConfig . jwksUri , cache : true , cacheMaxAge : 15 * 60 * 1000 , } ) ; } this . options = { algorithms : [ 'RS256' ] , issuer : this . tokenVerifierConfig . issuer , } ; } public async verify ( token ) { return new Promise ( ( resolve , reject ) => { const getKey = ( header , callback ) : void => { if ( ! header . kid ) { callback ( new Error ( 'JWT kid not found' ) ) ; return ; } this . client ?. getSigningKey ( header . kid , function ( err , key ) { if ( err ) { callback ( err , '' ) ; return ; } const signingKey = key . getPublicKey ( ) ; callback ( null , signingKey ) ; } ) ; } ; let key = getKey ; if ( ! this . client ) { if ( this . tokenVerifierConfig . publicKey ) { key = this . tokenVerifierConfig . publicKey ; } else { throw new Error ( 'Token verifier not well configured' ) ; } } token = this . sanitizeToken ( token ) ; jwt . verify ( token , key , this . options , ( err ? , decoded ? ) => { if ( err ) { return reject ( err ) ; } return resolve ( this . tokenToUserEnvelope ( decoded ) ) ; } ) ; } ) ; } private tokenToUserEnvelope ( decodedToken ) { const username = decodedToken ?. email || decodedToken ?. phone_number || decodedToken . sub ; const id = decodedToken . sub ; const rolesClaim = this . tokenVerifierConfig . rolesClaim || 'custom:role' ; const role = decodedToken [ rolesClaim ] ; const roleValue = Array . isArray ( role ) ? role [ 0 ] : role ; return { id , username , role : roleValue ?. trim ( ) ?? '' , claims : decodedToken , } ; } private sanitizeToken ( token ) { return token . replace ( 'Bearer ' , '' ) ; } } export class BoosterTokenVerifier { private tokenVerifierClients : Array < TokenVerifierClient > ; public constructor ( config ) { this . tokenVerifierClients = config . tokenVerifiers . map ( ( tokenVerifierConfig ) => new TokenVerifierClient ( tokenVerifierConfig ) ) ; } public async verify ( token ) { const results = await Promise . allSettled ( this . tokenVerifierClients . map ( ( tokenVerifierClient ) => tokenVerifierClient . verify ( token ) ) ) ; const winner = results . find ( ( result ) => result . status === 'fulfilled' ) ; if ( winner ) return Promise . resolve ( ( winner as PromiseFulfilledResult < UserEnvelope > ) . value ) ; return Promise . reject ( new Error ( results . map ( ( result ) => ( result as PromiseRejectedResult ) . reason ) . join ( '\n' ) ) ) ; } }