11import { createLogger , pickValueIgnoreCase } from '@sap-cloud-sdk/util' ;
2- import { decode } from 'jsonwebtoken ' ;
2+ import { jwtDecode } from 'jwt-decode ' ;
33import { Cache } from '../cache' ;
44import { getIssuerSubdomain } from '../subdomain-replacer' ;
55import type {
6- Jwt ,
6+ JwtHeader ,
77 JwtPayload ,
88 JwtWithPayloadObject
99} from '../jsonwebtoken-type' ;
@@ -136,7 +136,7 @@ function audiencesFromAud({ aud }: JwtPayload): string[] {
136136}
137137
138138function audiencesFromScope ( { scope } : JwtPayload ) : string [ ] {
139- return makeArray ( scope ) . reduce (
139+ return makeArray ( scope ) . reduce < string [ ] > (
140140 ( aud , s ) => ( s . includes ( '.' ) ? [ ...aud , s . split ( '.' ) [ 0 ] ] : aud ) ,
141141 [ ]
142142 ) ;
@@ -148,7 +148,20 @@ function audiencesFromScope({ scope }: JwtPayload): string[] {
148148 * @returns Decoded payload.
149149 */
150150export function decodeJwt ( token : string | JwtPayload ) : JwtPayload {
151- return typeof token === 'string' ? decodeJwtComplete ( token ) . payload : token ;
151+ if ( typeof token !== 'string' ) {
152+ return token ;
153+ }
154+ try {
155+ validateJwtFormat ( token ) ;
156+ return decodeJwtPart < JwtPayload > ( token ) ;
157+ } catch ( error ) {
158+ throw new Error (
159+ 'JwtError: The given jwt payload does not encode valid JSON.' ,
160+ {
161+ cause : error
162+ }
163+ ) ;
164+ }
152165}
153166
154167/**
@@ -158,13 +171,21 @@ export function decodeJwt(token: string | JwtPayload): JwtPayload {
158171 * @internal
159172 */
160173export function decodeJwtComplete ( token : string ) : JwtWithPayloadObject {
161- const decodedToken = decode ( token , { complete : true , json : true } ) ;
162- if ( decodedToken !== null && isJwtWithPayloadObject ( decodedToken ) ) {
163- return decodedToken ;
174+ try {
175+ const signature = validateJwtFormat ( token ) ;
176+ return {
177+ header : decodeJwtPart < JwtHeader > ( token , { header : true } ) ,
178+ payload : decodeJwtPart < JwtPayload > ( token ) ,
179+ signature
180+ } ;
181+ } catch ( error ) {
182+ throw new Error (
183+ 'JwtError: The given jwt payload does not encode valid JSON.' ,
184+ {
185+ cause : error
186+ }
187+ ) ;
164188 }
165- throw new Error (
166- 'JwtError: The given jwt payload does not encode valid JSON.'
167- ) ;
168189}
169190
170191/**
@@ -274,6 +295,40 @@ export function isUserToken(token: JwtPair | undefined): token is JwtPair {
274295 return ! ( keys . length === 1 && keys [ 0 ] === 'iss' ) ;
275296}
276297
277- function isJwtWithPayloadObject ( decoded : Jwt ) : decoded is JwtWithPayloadObject {
278- return typeof decoded . payload !== 'string' ;
298+ /**
299+ * Validate the format of the given JWT and return the signature part if valid.
300+ * @returns The signature part of the JWT if the format is valid.
301+ * @throws An error if the JWT format is invalid.
302+ * @internal
303+ */
304+ function validateJwtFormat ( token : string ) : string {
305+ const [ encodedHeader , encodedPayload , signature , ...rest ] = token . split ( '.' ) ;
306+
307+ if ( ! encodedHeader || ! encodedPayload || rest . length > 0 ) {
308+ throw new Error ( 'Invalid JWT format.' ) ;
309+ }
310+
311+ return signature ;
312+ }
313+
314+ /**
315+ * Decodes part of a JWT (header or payload) and ensures that the decoded value is an object.
316+ * @param token - The JWT to decode.
317+ * @param options - Options for decoding, e.g. whether to decode the header or payload.
318+ * @param options.header - If true, decodes the header; otherwise, decodes the payload.
319+ * @returns The decoded JWT part as an object.
320+ * @throws An error if the decoded value is not an object.
321+ * @internal
322+ */
323+ function decodeJwtPart < T extends object > (
324+ token : string ,
325+ options ?: { header ?: boolean }
326+ ) : T {
327+ const decoded = jwtDecode < T > ( token , options ) ;
328+
329+ if ( typeof decoded !== 'object' || decoded === null ) {
330+ throw new Error ( 'Invalid JWT content.' ) ;
331+ }
332+
333+ return decoded ;
279334}
0 commit comments