Skip to content

Commit

Permalink
feat: support promise/async secret function (#142)
Browse files Browse the repository at this point in the history
* feat: support promise/async secret function

* chore: types

* test: use async/await for secret tests
  • Loading branch information
simoneb committed Mar 19, 2021
1 parent 4c34812 commit 1fa52fc
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 65 deletions.
19 changes: 15 additions & 4 deletions README.md
Expand Up @@ -121,13 +121,25 @@ const fastify = require('fastify')()
const jwt = require('fastify-jwt')
// secret as a string
fastify.register(jwt, { secret: 'supersecret' })
// secret as a function
// secret as a function with callback
fastify.register(jwt, {
secret: function (request, token, callback) {
// do something
callback(null, 'supersecret')
}
})
// secret as a function returning a promise
fastify.register(jwt, {
secret: function (request, token) {
return Promise.resolve('supersecret')
}
})
// secret as an async function
fastify.register(jwt, {
secret: async function (request, token) {
return 'supersecret'
}
})
// secret as an object of RSA keys (without passphrase)
// the files are loaded as strings
fastify.register(jwt, {
Expand Down Expand Up @@ -558,10 +570,9 @@ const getJwks = buildGetJwks()

fastify.register(fjwt, {
decode: { complete: true },
secret: (request, token, callback) => {
secret: (request, token) => {
const { header: { kid, alg }, payload: { iss } } = token
getJwks.getPublicKey({ kid, domain: iss, alg })
.then(publicKey => callback(null, publicKey), callback)
return getJwks.getPublicKey({ kid, domain: iss, alg })
}
})

Expand Down
6 changes: 5 additions & 1 deletion jwt.d.ts
Expand Up @@ -37,7 +37,11 @@ export type UserType = FastifyJWT extends { user: infer T }
? T
: SignPayloadType

export type Secret = jwt.Secret | ((request: fastify.FastifyRequest, reply: fastify.FastifyReply, cb: (e: Error | null, secret: string | undefined) => void) => void)
export type TokenOrHeader = jwt.JwtHeader | { header: jwt.JwtHeader; payload: any }

export type Secret = jwt.Secret
| ((request: fastify.FastifyRequest, tokenOrHeader: TokenOrHeader, cb: (e: Error | null, secret: string | undefined) => void) => void)
| ((request: fastify.FastifyRequest, tokenOrHeader: TokenOrHeader) => Promise<string>)

export type VerifyPayloadType = object | string

Expand Down
20 changes: 16 additions & 4 deletions jwt.js
Expand Up @@ -51,8 +51,12 @@ function fastifyJwt (fastify, options, next) {

let secretCallbackSign = secretOrPrivateKey
let secretCallbackVerify = secretOrPublicKey
if (typeof secretCallbackSign !== 'function') { secretCallbackSign = wrapStaticSecretInCallback(secretCallbackSign) }
if (typeof secretCallbackVerify !== 'function') { secretCallbackVerify = wrapStaticSecretInCallback(secretCallbackVerify) }
if (typeof secretCallbackSign !== 'function') {
secretCallbackSign = wrapStaticSecretInCallback(secretCallbackSign)
}
if (typeof secretCallbackVerify !== 'function') {
secretCallbackVerify = wrapStaticSecretInCallback(secretCallbackVerify)
}

const cookie = options.cookie
const formatUser = options.formatUser
Expand Down Expand Up @@ -175,7 +179,11 @@ function fastifyJwt (fastify, options, next) {

steed.waterfall([
function getSecret (callback) {
secretCallbackSign(reply.request, payload, callback)
const signResult = secretCallbackSign(reply.request, payload, callback)

if (signResult && typeof signResult.then === 'function') {
signResult.then(result => callback(null, result), callback)
}
},
function sign (secretOrPrivateKey, callback) {
jwt.sign(payload, secretOrPrivateKey, options, callback)
Expand Down Expand Up @@ -240,7 +248,11 @@ function fastifyJwt (fastify, options, next) {

steed.waterfall([
function getSecret (callback) {
secretCallbackVerify(request, decodedToken, callback)
const verifyResult = secretCallbackVerify(request, decodedToken, callback)

if (verifyResult && typeof verifyResult.then === 'function') {
verifyResult.then(result => callback(null, result), callback)
}
},
function verify (secretOrPublicKey, callback) {
jwt.verify(token, secretOrPublicKey, options, (err, result) => {
Expand Down
40 changes: 24 additions & 16 deletions jwt.test-d.ts
Expand Up @@ -4,23 +4,27 @@ import { expectAssignable } from 'tsd'

const app = fastify();

const secretOptions = {
secret: 'supersecret',
publicPrivateKey: {
public: 'publicKey',
private: 'privateKey'
},
secretFnCallback: (_req, _token, cb) => { cb(null, 'supersecret') },
secretFnPromise: (_req, _token) => Promise.resolve('supersecret'),
secretFnAsync: async (_req, _token) => 'supersecret',
publicPrivateKeyFn: {
public: (_req, _rep, cb) => { cb(null, 'publicKey') },
private: 'privateKey'
},
publicPrivateKeyFn2: {
public: 'publicKey',
private: (_req, _rep, cb) => { cb(null, 'privateKey') },
}
}

const jwtOptions: FastifyJWTOptions = {
secret: {
secret: 'supersecret',
publicPrivateKey: {
public: 'publicKey',
private: 'privateKey'
},
secretFn: (_req, _rep, cb) => { cb(null, 'supersecret') },
publicPrivateKeyFn: {
public: (_req, _rep, cb) => { cb(null, 'publicKey') },
private: 'privateKey'
},
publicPrivateKeyFn2: {
public: 'publicKey',
private: (_req, _rep, cb) => { cb(null, 'privateKey') },
}
}[process.env.secretOption!],
secret: 'supersecret',
sign: {
expiresIn: '1h'
},
Expand Down Expand Up @@ -54,6 +58,10 @@ const jwtOptions: FastifyJWTOptions = {

app.register(fastifyJwt, jwtOptions);

Object.values(secretOptions).forEach((value) => {
app.register(fastifyJwt, {...jwtOptions, secret: value });
})

app.register(fastifyJwt, {...jwtOptions, trusted: () => Promise.resolve(false || '' || Buffer.from('foo')) })

// expect jwt and its subsequent methods have merged with the fastify instance
Expand Down
83 changes: 43 additions & 40 deletions test.js
Expand Up @@ -23,7 +23,7 @@ const privateKeyProtectedECDSA = readFileSync(`${path.join(__dirname, 'certs')}/
const publicKeyProtectedECDSA = readFileSync(`${path.join(__dirname, 'certs')}/publicECDSA.pem`)

test('register', function (t) {
t.plan(11)
t.plan(13)

t.test('Expose jwt methods', function (t) {
t.plan(7)
Expand Down Expand Up @@ -279,54 +279,57 @@ test('register', function (t) {
})
})

t.test('secret as a function', function (t) {
t.plan(2)

async function runWithSecret (t, secret) {
const fastify = Fastify()
fastify.register(jwt, {
secret: function (request, reply, callback) {
callback(null, 'some-secret')
}
})
fastify.register(jwt, { secret })

fastify.post('/sign', function (request, reply) {
reply.jwtSign(request.body)
.then(function (token) {
return reply.send({ token })
})
fastify.post('/sign', async function (request, reply) {
const token = await reply.jwtSign(request.body)
return reply.send({ token })
})

fastify.get('/verify', function (request, reply) {
return request.jwtVerify()
})

fastify
.ready()
.then(function () {
fastify.inject({
method: 'post',
url: '/sign',
payload: { foo: 'bar' }
}).then(function (signResponse) {
const token = JSON.parse(signResponse.payload).token
t.ok(token)
await fastify.ready()

fastify.inject({
method: 'get',
url: '/verify',
headers: {
authorization: `Bearer ${token}`
}
}).then(function (verifyResponse) {
const decodedToken = JSON.parse(verifyResponse.payload)
t.is(decodedToken.foo, 'bar')
}).catch(function (error) {
t.fail(error)
})
}).catch(function (error) {
t.fail(error)
})
})
const signResponse = await fastify.inject({
method: 'post',
url: '/sign',
payload: { foo: 'bar' }
})

const token = JSON.parse(signResponse.payload).token
t.ok(token)

const verifyResponse = await fastify.inject({
method: 'get',
url: '/verify',
headers: {
authorization: `Bearer ${token}`
}
})
const decodedToken = JSON.parse(verifyResponse.payload)
t.is(decodedToken.foo, 'bar')
}

t.test('secret as a function with callback', t => {
return runWithSecret(t, function (request, token, callback) {
callback(null, 'some-secret')
})
})

t.test('secret as a function returning a promise', t => {
return runWithSecret(t, function (request, token) {
return Promise.resolve('some-secret')
})
})

t.test('secret as an async function', t => {
return runWithSecret(t, async function (request, token) {
return 'some-secret'
})
})

t.test('fail without secret', function (t) {
Expand Down

0 comments on commit 1fa52fc

Please sign in to comment.