Skip to content

Commit

Permalink
feat: Async key provider
Browse files Browse the repository at this point in the history
  • Loading branch information
NikitaIT committed Apr 2, 2024
1 parent 2046ff5 commit 04288be
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 29 deletions.
50 changes: 29 additions & 21 deletions jwt.js
Original file line number Diff line number Diff line change
Expand Up @@ -487,34 +487,21 @@ function fastifyJwt (fastify, options, next) {
},
function verify (secretOrPublicKey, callback) {
try {
let verifyResult
if (useLocalVerifier) {
const verifierOptions = mergeOptionsWithKey(options.verify || options, secretOrPublicKey)
const localVerifier = createVerifier(verifierOptions)
const verifyResult = localVerifier(token)
callback(null, verifyResult)
verifyResult = localVerifier(token)
} else {
verifyResult = verifier(token)
}
if (verifyResult && typeof verifyResult.then === 'function') {
verifyResult.then(result => callback(null, result), error => wrapError(error, callback))
} else {
const verifyResult = verifier(token)
callback(null, verifyResult)
}
} catch (error) {
if (error.code === TokenError.codes.expired) {
return callback(new AuthorizationTokenExpiredError())
}

if (error.code === TokenError.codes.invalidKey ||
error.code === TokenError.codes.invalidSignature ||
error.code === TokenError.codes.invalidClaimValue
) {
return callback(typeof messagesOptions.authorizationTokenInvalid === 'function'
? new AuthorizationTokenInvalidError(error.message)
: new AuthorizationTokenInvalidError())
}

if (error.code === TokenError.codes.missingSignature) {
return callback(new AuthorizationTokenUnsignedError())
}

return callback(error)
return wrapError(error, callback)
}
},
function checkIfIsTrusted (result, callback) {
Expand Down Expand Up @@ -543,6 +530,27 @@ function fastifyJwt (fastify, options, next) {
}
})
}

function wrapError (error, callback) {
if (error.code === TokenError.codes.expired) {
return callback(new AuthorizationTokenExpiredError())
}

if (error.code === TokenError.codes.invalidKey ||
error.code === TokenError.codes.invalidSignature ||
error.code === TokenError.codes.invalidClaimValue
) {
return callback(typeof messagesOptions.authorizationTokenInvalid === 'function'
? new AuthorizationTokenInvalidError(error.message)
: new AuthorizationTokenInvalidError())
}

if (error.code === TokenError.codes.missingSignature) {
return callback(new AuthorizationTokenUnsignedError())
}

return callback(error)
}
}

module.exports = fp(fastifyJwt, {
Expand Down
84 changes: 76 additions & 8 deletions test/jwt-async.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ test('Async key provider should be resolved internally', async function (t) {
fastify.register(jwt, {
secret: {
private: 'supersecret',
public: async () => false
public: async () => Promise.resolve('supersecret')
},
verify: {
extractToken: (request) => request.headers.jwt,
Expand All @@ -20,7 +20,7 @@ test('Async key provider should be resolved internally', async function (t) {
const token = await reply.jwtSign({ user: 'test' })
request.headers.jwt = token
await request.jwtVerify()
return reply.send(typeof request.user.then)
return reply.send(request.user)
})
const response = await fastify.inject({
method: 'get',
Expand All @@ -31,24 +31,24 @@ test('Async key provider should be resolved internally', async function (t) {
})
t.ok(response)
t.comment("Should be 'undefined'")
t.equal(response.payload, 'function')
t.match(response.json(), { user: 'test' })
})

test('Async key provider errors should be resolved internally', async function (t) {
const fastify = Fastify()
fastify.register(jwt, {
secret: {
private: 'supersecret',
public: async () => false
public: async () => Promise.resolve('key used per request, false not allowed')
},
verify: {
extractToken: (request) => request.headers.jwt,
key: () => Promise.resolve('supersecret')
key: () => Promise.resolve('key not used')
}
})
fastify.get('/', async function (request, reply) {
request.headers.jwt =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
// call to local verifier without cache
await request.jwtVerify()
return reply.send(typeof request.user.then)
})
Expand All @@ -57,6 +57,74 @@ test('Async key provider errors should be resolved internally', async function (
url: '/'
})

t.comment('Should be 401')
t.equal(response.statusCode, 500)
t.equal(response.statusCode, 401)
})

test('Async key provider should be resolved internally with cache', async function (t) {
const fastify = Fastify()
fastify.register(jwt, {
secret: {
public: async () => false
},
verify: {
extractToken: (request) => request.headers.jwt,
key: () => Promise.resolve('this secret reused from cache')
}
})
fastify.get('/', async function (request, reply) {
request.headers.jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.hQOxra1Zo9z61vCqe6_86kVfLqKI0WxDnkiJ_upW0sM'
await new Promise((resolve, reject) => request.jwtVerify((err, payload) => {
if (err) {
reject(err)
return
}
resolve(payload)
}))
await new Promise((resolve, reject) => request.jwtVerify((err, payload) => {
if (err) {
reject(err)
return
}
resolve(payload)
}))
return reply.send(request.user)
})
const response = await fastify.inject({
method: 'get',
url: '/'
})
t.equal(response.statusCode, 200)
t.match(response.json(), { name: 'John Doe' })
})

test('Async key provider errors should be resolved internally with cache', async function (t) {
const fastify = Fastify()
fastify.register(jwt, {
secret: {
public: async () => false
},
verify: {
extractToken: (request) => request.headers.jwt,
key: () => Promise.resolve('this secret reused from cache')
}
})
fastify.get('/', async function (request, reply) {
request.headers.jwt =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
// call to plugin root level verifier
await new Promise((resolve, reject) => request.jwtVerify((err, payload) => {
if (err) {
reject(err)
return
}
resolve(payload)
}))
return reply.send(typeof request.user.then)
})
const response = await fastify.inject({
method: 'get',
url: '/'
})

t.equal(response.statusCode, 401)
})

0 comments on commit 04288be

Please sign in to comment.