@@ -108,6 +108,53 @@ describe('JWT', () => {
108108 expect ( authorized ) . toBeUndefined ( )
109109 } )
110110
111+ describe ( 'JwtTokenNotBefore with malformed nbf claim' , ( ) => {
112+ it ( 'rejects token with nbf as a non-numeric string' , async ( ) => {
113+ const secret = 'a-secret'
114+ const tok = await JWT . sign (
115+ // @ts -expect-error - testing malformed payload (nbf must be number)
116+ { message : 'hello' , nbf : 'tomorrow' } ,
117+ secret ,
118+ AlgorithmTypes . HS256
119+ )
120+
121+ let err
122+ let authorized
123+ try {
124+ authorized = await JWT . verify ( tok , secret , AlgorithmTypes . HS256 )
125+ } catch ( e ) {
126+ err = e
127+ }
128+ expect ( err ) . toEqual ( new JwtTokenNotBefore ( tok ) )
129+ expect ( authorized ) . toBeUndefined ( )
130+ } )
131+
132+ it ( 'rejects token with nbf = Infinity (parsed from 1e400 in JSON)' , async ( ) => {
133+ // JSON.stringify converts Infinity to null, so hand-craft the payload.
134+ const secret = 'a-secret'
135+ const encode = ( s : string ) => encodeBase64Url ( utf8Encoder . encode ( s ) . buffer ) . replace ( / = / g, '' )
136+ const encodedHeader = encode ( '{"alg":"HS256","typ":"JWT"}' )
137+ const encodedPayload = encode ( '{"message":"hello","nbf":1e400}' )
138+ const signingInput = `${ encodedHeader } .${ encodedPayload } `
139+ const signatureBuffer = await signing (
140+ secret ,
141+ AlgorithmTypes . HS256 ,
142+ utf8Encoder . encode ( signingInput )
143+ )
144+ const tok = `${ signingInput } .${ encodeBase64Url ( signatureBuffer ) . replace ( / = / g, '' ) } `
145+
146+ let err
147+ let authorized
148+ try {
149+ authorized = await JWT . verify ( tok , secret , AlgorithmTypes . HS256 )
150+ } catch ( e ) {
151+ err = e
152+ }
153+ expect ( err ) . toEqual ( new JwtTokenNotBefore ( tok ) )
154+ expect ( authorized ) . toBeUndefined ( )
155+ } )
156+ } )
157+
111158 it ( 'JwtTokenExpired' , async ( ) => {
112159 const tok =
113160 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MzMwNDYxMDAsImV4cCI6MTYzMzA0NjQwMH0.H-OI1TWAbmK8RonvcpPaQcNvOKS9sxinEOsgKwjoiVo'
@@ -145,6 +192,68 @@ describe('JWT', () => {
145192 vi . useRealTimers ( )
146193 } )
147194
195+ describe ( 'JwtTokenExpired with malformed exp claim' , ( ) => {
196+ it ( 'rejects token with exp = 0 (epoch zero)' , async ( ) => {
197+ const secret = 'a-secret'
198+ const tok = await JWT . sign ( { message : 'hello' , exp : 0 } , secret , AlgorithmTypes . HS256 )
199+
200+ let err
201+ let authorized
202+ try {
203+ authorized = await JWT . verify ( tok , secret , AlgorithmTypes . HS256 )
204+ } catch ( e ) {
205+ err = e
206+ }
207+ expect ( err ) . toEqual ( new JwtTokenExpired ( tok ) )
208+ expect ( authorized ) . toBeUndefined ( )
209+ } )
210+
211+ it ( 'rejects token with exp as a non-numeric string' , async ( ) => {
212+ const secret = 'a-secret'
213+ const tok = await JWT . sign (
214+ // @ts -expect-error - testing malformed payload (exp must be number)
215+ { message : 'hello' , exp : 'tomorrow' } ,
216+ secret ,
217+ AlgorithmTypes . HS256
218+ )
219+
220+ let err
221+ let authorized
222+ try {
223+ authorized = await JWT . verify ( tok , secret , AlgorithmTypes . HS256 )
224+ } catch ( e ) {
225+ err = e
226+ }
227+ expect ( err ) . toEqual ( new JwtTokenExpired ( tok ) )
228+ expect ( authorized ) . toBeUndefined ( )
229+ } )
230+
231+ it ( 'rejects token with exp = Infinity (parsed from 1e400 in JSON)' , async ( ) => {
232+ // JSON.stringify converts Infinity to null, so hand-craft the payload.
233+ const secret = 'a-secret'
234+ const encode = ( s : string ) => encodeBase64Url ( utf8Encoder . encode ( s ) . buffer ) . replace ( / = / g, '' )
235+ const encodedHeader = encode ( '{"alg":"HS256","typ":"JWT"}' )
236+ const encodedPayload = encode ( '{"message":"hello","exp":1e400}' )
237+ const signingInput = `${ encodedHeader } .${ encodedPayload } `
238+ const signatureBuffer = await signing (
239+ secret ,
240+ AlgorithmTypes . HS256 ,
241+ utf8Encoder . encode ( signingInput )
242+ )
243+ const tok = `${ signingInput } .${ encodeBase64Url ( signatureBuffer ) . replace ( / = / g, '' ) } `
244+
245+ let err
246+ let authorized
247+ try {
248+ authorized = await JWT . verify ( tok , secret , AlgorithmTypes . HS256 )
249+ } catch ( e ) {
250+ err = e
251+ }
252+ expect ( err ) . toEqual ( new JwtTokenExpired ( tok ) )
253+ expect ( authorized ) . toBeUndefined ( )
254+ } )
255+ } )
256+
148257 it ( 'JwtTokenIssuedAt' , async ( ) => {
149258 const now = 1633046400
150259 vi . useFakeTimers ( ) . setSystemTime ( new Date ( ) . setTime ( now * 1000 ) )
@@ -165,6 +274,63 @@ describe('JWT', () => {
165274 expect ( authorized ) . toBeUndefined ( )
166275 } )
167276
277+ describe ( 'JwtTokenIssuedAt with malformed iat claim' , ( ) => {
278+ it ( 'rejects token with iat as a non-numeric string' , async ( ) => {
279+ const now = 1633046400
280+ vi . useFakeTimers ( ) . setSystemTime ( new Date ( now * 1000 ) )
281+
282+ const secret = 'a-secret'
283+ const tok = await JWT . sign (
284+ // @ts -expect-error - testing malformed payload (iat must be number)
285+ { message : 'hello' , iat : 'tomorrow' } ,
286+ secret ,
287+ AlgorithmTypes . HS256
288+ )
289+
290+ let err
291+ let authorized
292+ try {
293+ authorized = await JWT . verify ( tok , secret , AlgorithmTypes . HS256 )
294+ } catch ( e ) {
295+ err = e
296+ }
297+ expect ( err ) . toEqual ( new JwtTokenIssuedAt ( now , 'tomorrow' as unknown as number ) )
298+ expect ( authorized ) . toBeUndefined ( )
299+
300+ vi . useRealTimers ( )
301+ } )
302+
303+ it ( 'rejects token with iat = Infinity (parsed from 1e400 in JSON)' , async ( ) => {
304+ // JSON.stringify converts Infinity to null, so hand-craft the payload.
305+ const now = 1633046400
306+ vi . useFakeTimers ( ) . setSystemTime ( new Date ( now * 1000 ) )
307+
308+ const secret = 'a-secret'
309+ const encode = ( s : string ) => encodeBase64Url ( utf8Encoder . encode ( s ) . buffer ) . replace ( / = / g, '' )
310+ const encodedHeader = encode ( '{"alg":"HS256","typ":"JWT"}' )
311+ const encodedPayload = encode ( '{"message":"hello","iat":1e400}' )
312+ const signingInput = `${ encodedHeader } .${ encodedPayload } `
313+ const signatureBuffer = await signing (
314+ secret ,
315+ AlgorithmTypes . HS256 ,
316+ utf8Encoder . encode ( signingInput )
317+ )
318+ const tok = `${ signingInput } .${ encodeBase64Url ( signatureBuffer ) . replace ( / = / g, '' ) } `
319+
320+ let err
321+ let authorized
322+ try {
323+ authorized = await JWT . verify ( tok , secret , AlgorithmTypes . HS256 )
324+ } catch ( e ) {
325+ err = e
326+ }
327+ expect ( err ) . toEqual ( new JwtTokenIssuedAt ( now , Infinity ) )
328+ expect ( authorized ) . toBeUndefined ( )
329+
330+ vi . useRealTimers ( )
331+ } )
332+ } )
333+
168334 it ( 'JwtTokenIssuer (none)' , async ( ) => {
169335 const tok =
170336 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MzMwNDY0MDB9.Ha3tPZzmnLGyFfZYd7GSV0iCn2F9kbZffFVZcTe5kJo'
0 commit comments