Skip to content

Commit ad9317b

Browse files
jmjfNikitaFedorov1NikitaIT
authored
fix: Async key provider and errors should be resolved internally -- dynamic JWTs in tests (#338)
* test: Async key provider should be resolved internaly * test: Async key provider errors should be resolved internaly * test: Async key provider errors should be resolved internally * feat: Async key provider * test: generate JWTs dynamically --------- Co-authored-by: NikitaFedorov1 <nikitaf@piazzaro.io> Co-authored-by: NikitaIT <NIKITA2008-101@yandex.ru>
1 parent 54be35b commit ad9317b

File tree

2 files changed

+164
-21
lines changed

2 files changed

+164
-21
lines changed

jwt.js

+29-21
Original file line numberDiff line numberDiff line change
@@ -486,34 +486,21 @@ function fastifyJwt (fastify, options, next) {
486486
},
487487
function verify (secretOrPublicKey, callback) {
488488
try {
489+
let verifyResult
489490
if (useLocalVerifier) {
490491
const verifierOptions = mergeOptionsWithKey(options.verify || options, secretOrPublicKey)
491492
const localVerifier = createVerifier(verifierOptions)
492-
const verifyResult = localVerifier(token)
493-
callback(null, verifyResult)
493+
verifyResult = localVerifier(token)
494+
} else {
495+
verifyResult = verifier(token)
496+
}
497+
if (verifyResult && typeof verifyResult.then === 'function') {
498+
verifyResult.then(result => callback(null, result), error => wrapError(error, callback))
494499
} else {
495-
const verifyResult = verifier(token)
496500
callback(null, verifyResult)
497501
}
498502
} catch (error) {
499-
if (error.code === TokenError.codes.expired) {
500-
return callback(new AuthorizationTokenExpiredError())
501-
}
502-
503-
if (error.code === TokenError.codes.invalidKey ||
504-
error.code === TokenError.codes.invalidSignature ||
505-
error.code === TokenError.codes.invalidClaimValue
506-
) {
507-
return callback(typeof messagesOptions.authorizationTokenInvalid === 'function'
508-
? new AuthorizationTokenInvalidError(error.message)
509-
: new AuthorizationTokenInvalidError())
510-
}
511-
512-
if (error.code === TokenError.codes.missingSignature) {
513-
return callback(new AuthorizationTokenUnsignedError())
514-
}
515-
516-
return callback(error)
503+
return wrapError(error, callback)
517504
}
518505
},
519506
function checkIfIsTrusted (result, callback) {
@@ -542,6 +529,27 @@ function fastifyJwt (fastify, options, next) {
542529
}
543530
})
544531
}
532+
533+
function wrapError (error, callback) {
534+
if (error.code === TokenError.codes.expired) {
535+
return callback(new AuthorizationTokenExpiredError())
536+
}
537+
538+
if (error.code === TokenError.codes.invalidKey ||
539+
error.code === TokenError.codes.invalidSignature ||
540+
error.code === TokenError.codes.invalidClaimValue
541+
) {
542+
return callback(typeof messagesOptions.authorizationTokenInvalid === 'function'
543+
? new AuthorizationTokenInvalidError(error.message)
544+
: new AuthorizationTokenInvalidError())
545+
}
546+
547+
if (error.code === TokenError.codes.missingSignature) {
548+
return callback(new AuthorizationTokenUnsignedError())
549+
}
550+
551+
return callback(error)
552+
}
545553
}
546554

547555
module.exports = fp(fastifyJwt, {

test/jwt-async.test.js

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
'use strict'
2+
3+
const test = require('tap').test
4+
const Fastify = require('fastify')
5+
const jwt = require('../jwt')
6+
const { createSigner } = require('fast-jwt')
7+
8+
test('Async key provider should be resolved internally', async function (t) {
9+
const fastify = Fastify()
10+
fastify.register(jwt, {
11+
secret: {
12+
private: 'supersecret',
13+
public: async () => Promise.resolve('supersecret')
14+
},
15+
verify: {
16+
extractToken: (request) => request.headers.jwt,
17+
key: () => Promise.resolve('supersecret')
18+
}
19+
})
20+
fastify.get('/', async function (request, reply) {
21+
const token = await reply.jwtSign({ user: 'test' })
22+
request.headers.jwt = token
23+
await request.jwtVerify()
24+
return reply.send(request.user)
25+
})
26+
const response = await fastify.inject({
27+
method: 'get',
28+
url: '/',
29+
headers: {
30+
jwt: 'supersecret'
31+
}
32+
})
33+
t.ok(response)
34+
t.comment("Should be 'undefined'")
35+
t.match(response.json(), { user: 'test' })
36+
})
37+
38+
test('Async key provider errors should be resolved internally', async function (t) {
39+
const fastify = Fastify()
40+
fastify.register(jwt, {
41+
secret: {
42+
public: async () => Promise.resolve('key used per request, false not allowed')
43+
},
44+
verify: {
45+
extractToken: (request) => request.headers.jwt,
46+
key: () => Promise.resolve('key not used')
47+
}
48+
})
49+
fastify.get('/', async function (request, reply) {
50+
const signSync = createSigner({ key: 'invalid signature error' })
51+
request.headers.jwt = signSync({ sub: '1234567890', name: 'John Doe', iat: 1516239022 })
52+
// call to local verifier without cache
53+
await request.jwtVerify()
54+
return reply.send(typeof request.user.then)
55+
})
56+
const response = await fastify.inject({
57+
method: 'get',
58+
url: '/'
59+
})
60+
61+
t.equal(response.statusCode, 401)
62+
})
63+
64+
test('Async key provider should be resolved internally with cache', async function (t) {
65+
const fastify = Fastify()
66+
fastify.register(jwt, {
67+
secret: {
68+
private: 'this secret reused from cache',
69+
public: async () => false
70+
},
71+
verify: {
72+
extractToken: (request) => request.headers.jwt,
73+
key: () => Promise.resolve('this secret reused from cache')
74+
}
75+
})
76+
fastify.get('/', async function (request, reply) {
77+
const signSync = createSigner({ key: 'this secret reused from cache' })
78+
request.headers.jwt = signSync({ sub: '1234567890', name: 'John Doe', iat: 1516239022 })
79+
await new Promise((resolve, reject) => request.jwtVerify((err, payload) => {
80+
if (err) {
81+
reject(err)
82+
return
83+
}
84+
resolve(payload)
85+
}))
86+
await new Promise((resolve, reject) => request.jwtVerify((err, payload) => {
87+
if (err) {
88+
reject(err)
89+
return
90+
}
91+
resolve(payload)
92+
}))
93+
return reply.send(request.user)
94+
})
95+
const response = await fastify.inject({
96+
method: 'get',
97+
url: '/'
98+
})
99+
t.equal(response.statusCode, 200)
100+
t.match(response.json(), { name: 'John Doe' })
101+
})
102+
103+
test('Async key provider errors should be resolved internally with cache', async function (t) {
104+
const fastify = Fastify()
105+
fastify.register(jwt, {
106+
secret: {
107+
public: async () => false
108+
},
109+
verify: {
110+
extractToken: (request) => request.headers.jwt,
111+
key: () => Promise.resolve('this secret reused from cache')
112+
}
113+
})
114+
fastify.get('/', async function (request, reply) {
115+
const signSync = createSigner({ key: 'invalid signature error' })
116+
request.headers.jwt = signSync({ sub: '1234567890', name: 'John Doe', iat: 1516239022 })
117+
// request.headers.jwt =
118+
// 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
119+
// call to plugin root level verifier
120+
await new Promise((resolve, reject) => request.jwtVerify((err, payload) => {
121+
if (err) {
122+
reject(err)
123+
return
124+
}
125+
resolve(payload)
126+
}))
127+
return reply.send(typeof request.user.then)
128+
})
129+
const response = await fastify.inject({
130+
method: 'get',
131+
url: '/'
132+
})
133+
134+
t.equal(response.statusCode, 401)
135+
})

0 commit comments

Comments
 (0)