Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Async key provider and errors should be resolved internally -- dynamic JWTs in tests #338

Merged
merged 5 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
135 changes: 135 additions & 0 deletions test/jwt-async.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
'use strict'

const test = require('tap').test
const Fastify = require('fastify')
const jwt = require('../jwt')
const { createSigner } = require('fast-jwt')

test('Async key provider should be resolved internally', async function (t) {
const fastify = Fastify()
fastify.register(jwt, {
secret: {
private: 'supersecret',
public: async () => Promise.resolve('supersecret')
},
verify: {
extractToken: (request) => request.headers.jwt,
key: () => Promise.resolve('supersecret')
}
})
fastify.get('/', async function (request, reply) {
const token = await reply.jwtSign({ user: 'test' })
request.headers.jwt = token
await request.jwtVerify()
return reply.send(request.user)
})
const response = await fastify.inject({
method: 'get',
url: '/',
headers: {
jwt: 'supersecret'
}
})
t.ok(response)
t.comment("Should be 'undefined'")
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: {
public: async () => Promise.resolve('key used per request, false not allowed')
},
verify: {
extractToken: (request) => request.headers.jwt,
key: () => Promise.resolve('key not used')
}
})
fastify.get('/', async function (request, reply) {
const signSync = createSigner({ key: 'invalid signature error' })
request.headers.jwt = signSync({ sub: '1234567890', name: 'John Doe', iat: 1516239022 })
// call to local verifier without cache
await request.jwtVerify()
return reply.send(typeof request.user.then)
})
const response = await fastify.inject({
method: 'get',
url: '/'
})

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: {
private: 'this secret reused from cache',
public: async () => false
},
verify: {
extractToken: (request) => request.headers.jwt,
key: () => Promise.resolve('this secret reused from cache')
}
})
fastify.get('/', async function (request, reply) {
const signSync = createSigner({ key: 'this secret reused from cache' })
request.headers.jwt = signSync({ sub: '1234567890', name: 'John Doe', iat: 1516239022 })
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) {
const signSync = createSigner({ key: 'invalid signature error' })
request.headers.jwt = signSync({ sub: '1234567890', name: 'John Doe', iat: 1516239022 })
// 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)
})