import { assertStringArrayContainsString , assertStringArraysOverlap , } from "./assert.js" ; import { JwtHeader , JwtPayload } from "./jwt-model.js" ; import { safeJsonParse , isJsonObject } from "./safe-json-parse.js" ; import { JwtExpiredError , JwtNotBeforeError , JwtInvalidIssuerError , JwtInvalidAudienceError , JwtInvalidScopeError , JwtParseError , ParameterValidationError , } from "./error.js" ; param header returns function assertJwtHeader ( header ) { if ( ! isJsonObject ( header ) ) { throw new JwtParseError ( "JWT header is not an object" ) ; } if ( header . alg !== undefined && typeof header . alg !== "string" ) { throw new JwtParseError ( "JWT header alg claim is not a string" ) ; } if ( header . kid !== undefined && typeof header . kid !== "string" ) { throw new JwtParseError ( "JWT header kid claim is not a string" ) ; } } param payload returns function assertJwtPayload ( payload ) { if ( ! isJsonObject ( payload ) ) { throw new JwtParseError ( "JWT payload is not an object" ) ; } if ( payload . exp !== undefined && ! Number . isFinite ( payload . exp ) ) { throw new JwtParseError ( "JWT payload exp claim is not a number" ) ; } if ( payload . iss !== undefined && typeof payload . iss !== "string" ) { throw new JwtParseError ( "JWT payload iss claim is not a string" ) ; } if ( payload . aud !== undefined && typeof payload . aud !== "string" && ( ! Array . isArray ( payload . aud ) || payload . aud . some ( ( aud ) => typeof aud !== "string" ) ) ) { throw new JwtParseError ( "JWT payload aud claim is not a string or array of strings" ) ; } if ( payload . nbf !== undefined && ! Number . isFinite ( payload . nbf ) ) { throw new JwtParseError ( "JWT payload nbf claim is not a number" ) ; } if ( payload . iat !== undefined && ! Number . isFinite ( payload . iat ) ) { throw new JwtParseError ( "JWT payload iat claim is not a number" ) ; } if ( payload . scope !== undefined && typeof payload . scope !== "string" ) { throw new JwtParseError ( "JWT payload scope claim is not a string" ) ; } if ( payload . jti !== undefined && typeof payload . jti !== "string" ) { throw new JwtParseError ( "JWT payload jti claim is not a string" ) ; } } param jwt returns export function decomposeJwt ( jwt ) { if ( ! jwt ) { throw new JwtParseError ( "Empty JWT" ) ; } if ( typeof jwt !== "string" ) { throw new JwtParseError ( "JWT is not a string" ) ; } if ( ! jwt . match ( /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/ ) ) { throw new JwtParseError ( "JWT string does not consist of exactly 3 parts (header, payload, signature)" ) ; } const [ headerB64 , payloadB64 , signatureB64 ] = jwt . split ( "." ) ; const [ headerString , payloadString ] = [ headerB64 , payloadB64 ] . map ( ( b64 ) => Buffer . from ( b64 , "base64" ) . toString ( "utf8" ) ) ; let header ; try { header = safeJsonParse ( headerString ) ; } catch ( err ) { throw new JwtParseError ( "Invalid JWT. Header is not a valid JSON object" , err ) ; } assertJwtHeader ( header ) ; let payload ; try { payload = safeJsonParse ( payloadString ) ; } catch ( err ) { throw new JwtParseError ( "Invalid JWT. Payload is not a valid JSON object" , err ) ; } assertJwtPayload ( payload ) ; return { header , headerB64 , payload , payloadB64 , signatureB64 , } ; } export type DecomposedJwt = ReturnType < typeof decomposeJwt > ; param payload param options returns export function validateJwtFields ( payload , options ) { if ( payload . exp !== undefined ) { if ( payload . exp + ( options . graceSeconds ?? 0 ) < Date . now ( ) / 1000 ) { throw new JwtExpiredError ( `Token expired at ${ new Date ( payload . exp * 1000 ) . toISOString ( ) }` , payload . exp ) ; } } if ( payload . nbf !== undefined ) { if ( payload . nbf - ( options . graceSeconds ?? 0 ) > Date . now ( ) / 1000 ) { throw new JwtNotBeforeError ( `Token can't be used before ${ new Date ( payload . nbf * 1000 ) . toISOString ( ) }` , payload . nbf ) ; } } if ( options . issuer !== null ) { if ( options . issuer === undefined ) { throw new ParameterValidationError ( "issuer must be provided or set to null explicitly" ) ; } assertStringArrayContainsString ( "Issuer" , payload . iss , options . issuer , JwtInvalidIssuerError ) ; } if ( options . audience !== null ) { if ( options . audience === undefined ) { throw new ParameterValidationError ( "audience must be provided or set to null explicitly" ) ; } assertStringArraysOverlap ( "Audience" , payload . aud , options . audience , JwtInvalidAudienceError ) ; } if ( options . scope != null ) { assertStringArraysOverlap ( "Scope" , payload . scope ?. split ( " " ) , options . scope , JwtInvalidScopeError ) ; } }