From 8a83879a3a43f904042fc45a37f1572b1ebf6fb0 Mon Sep 17 00:00:00 2001 From: NikitaFedorov1 Date: Tue, 2 Apr 2024 22:47:12 +0200 Subject: [PATCH 1/5] test: Async key provider should be resolved internaly --- test/jwt-async.test.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 test/jwt-async.test.js diff --git a/test/jwt-async.test.js b/test/jwt-async.test.js new file mode 100644 index 0000000..a445bf8 --- /dev/null +++ b/test/jwt-async.test.js @@ -0,0 +1,35 @@ +'use strict' + +const test = require('tap').test +const Fastify = require('fastify') +const jwt = require('../jwt') + +test('Async key provider should be resolved internaly', async function (t) { + const fastify = Fastify() + fastify.register(jwt, { + secret: { + private: 'supersecret', + public: async () => false + }, + 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(typeof request.user.then) + }) + const response = await fastify.inject({ + method: 'get', + url: '/', + headers: { + jwt: 'supersecret' + } + }) + t.ok(response) + t.comment("Should be 'undefined'") + t.equal(response.payload, 'function') +}) From 89a496482a44cfbfb233b5dd755188413985b15f Mon Sep 17 00:00:00 2001 From: NikitaFedorov1 Date: Tue, 2 Apr 2024 22:57:45 +0200 Subject: [PATCH 2/5] test: Async key provider errors should be resolved internaly --- test/jwt-async.test.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/jwt-async.test.js b/test/jwt-async.test.js index a445bf8..53e6167 100644 --- a/test/jwt-async.test.js +++ b/test/jwt-async.test.js @@ -33,3 +33,30 @@ test('Async key provider should be resolved internaly', async function (t) { t.comment("Should be 'undefined'") t.equal(response.payload, 'function') }) + +test('Async key provider errors should be resolved internaly', async function (t) { + const fastify = Fastify() + fastify.register(jwt, { + secret: { + private: 'supersecret', + public: async () => false + }, + verify: { + extractToken: (request) => request.headers.jwt, + key: () => Promise.resolve('supersecret') + } + }) + fastify.get('/', async function (request, reply) { + request.headers.jwt = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' + await request.jwtVerify() + return reply.send(typeof request.user.then) + }) + const response = await fastify.inject({ + method: 'get', + url: '/' + }) + + t.comment('Should be 401') + t.equal(response.statusCode, 500) +}) From 2046ff5b435f16ced5d9fa3350b374041003d792 Mon Sep 17 00:00:00 2001 From: NikitaFedorov1 Date: Tue, 2 Apr 2024 23:03:54 +0200 Subject: [PATCH 3/5] test: Async key provider errors should be resolved internally --- test/jwt-async.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/jwt-async.test.js b/test/jwt-async.test.js index 53e6167..0b5651e 100644 --- a/test/jwt-async.test.js +++ b/test/jwt-async.test.js @@ -4,7 +4,7 @@ const test = require('tap').test const Fastify = require('fastify') const jwt = require('../jwt') -test('Async key provider should be resolved internaly', async function (t) { +test('Async key provider should be resolved internally', async function (t) { const fastify = Fastify() fastify.register(jwt, { secret: { @@ -34,7 +34,7 @@ test('Async key provider should be resolved internaly', async function (t) { t.equal(response.payload, 'function') }) -test('Async key provider errors should be resolved internaly', async function (t) { +test('Async key provider errors should be resolved internally', async function (t) { const fastify = Fastify() fastify.register(jwt, { secret: { From 04288be3d7702e341de8becb0abfb4374a7388e9 Mon Sep 17 00:00:00 2001 From: NikitaIT Date: Wed, 3 Apr 2024 01:12:23 +0200 Subject: [PATCH 4/5] feat: Async key provider --- jwt.js | 50 ++++++++++++++----------- test/jwt-async.test.js | 84 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 105 insertions(+), 29 deletions(-) diff --git a/jwt.js b/jwt.js index 4e8779e..9b05234 100644 --- a/jwt.js +++ b/jwt.js @@ -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) { @@ -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, { diff --git a/test/jwt-async.test.js b/test/jwt-async.test.js index 0b5651e..6aec7f4 100644 --- a/test/jwt-async.test.js +++ b/test/jwt-async.test.js @@ -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, @@ -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', @@ -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) }) @@ -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) }) From 92feb846ea673f3cbde401034d6d47df73aea585 Mon Sep 17 00:00:00 2001 From: jmjf Date: Sat, 29 Jun 2024 12:47:24 -0400 Subject: [PATCH 5/5] test: generate JWTs dynamically --- test/jwt-async.test.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test/jwt-async.test.js b/test/jwt-async.test.js index 6aec7f4..2c35bba 100644 --- a/test/jwt-async.test.js +++ b/test/jwt-async.test.js @@ -3,6 +3,7 @@ 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() @@ -46,8 +47,8 @@ test('Async key provider errors should be resolved internally', async function ( } }) fastify.get('/', async function (request, reply) { - request.headers.jwt = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' + 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) @@ -64,6 +65,7 @@ test('Async key provider should be resolved internally with cache', async functi const fastify = Fastify() fastify.register(jwt, { secret: { + private: 'this secret reused from cache', public: async () => false }, verify: { @@ -72,7 +74,8 @@ test('Async key provider should be resolved internally with cache', async functi } }) fastify.get('/', async function (request, reply) { - request.headers.jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.hQOxra1Zo9z61vCqe6_86kVfLqKI0WxDnkiJ_upW0sM' + 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) @@ -109,8 +112,10 @@ test('Async key provider errors should be resolved internally with cache', async } }) fastify.get('/', async function (request, reply) { - request.headers.jwt = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' + 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) {