Skip to content

Commit

Permalink
support notBefore and expiresIn as strings
Browse files Browse the repository at this point in the history
  • Loading branch information
Tiago Ramos committed Aug 10, 2023
1 parent e696a4b commit c77055d
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 12 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ Create a signer function by calling `createSigner` and providing one or more of

- `mutatePayload`: If set to `true`, the original payload will be modified in place (via `Object.assign`) by the signing function. This is useful if you need a raw reference to the payload after claims have been applied to it but before it has been encoded into a token. Default is `false`.

- `expiresIn`: Time span (in milliseconds) after which the token expires, added as the `exp` claim in the payload as defined by the [section 4.1.4 of RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4). This will override any existing value in the claim.
- `expiresIn`: Time span (in milliseconds or text describing time) after which the token expires, added as the `exp` claim in the payload as defined by the [section 4.1.4 of RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4). This will override any existing value in the claim.
> Eg: `60`, `"2 days"`, `"10h"`, `"7d"`. A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (days, hours, etc), otherwise milliseconds unit is used by default (`"120"` is equal to `"120ms"`).
- `notBefore`: Time span (in milliseconds) before the token is active, added as the `nbf` claim in the payload as defined by the [section 4.1.5 of RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5). This will override any existing value in the claim.
- `notBefore`: Time span (in milliseconds or text describing time) before the token is active, added as the `nbf` claim in the payload as defined by the [section 4.1.5 of RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5). This will override any existing value in the claim.
> Eg: `60`, `"2 days"`, `"10h"`, `"7d"`. A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (days, hours, etc), otherwise milliseconds unit is used by default (`"120"` is equal to `"120ms"`).
- `jti`: The token unique identifier, added as the `jti` claim in the payload as defined by the [section 4.1.7 of RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.7). This will override any existing value in the claim.

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"benchmark:auth0": "node benchmarks/auth0.mjs"
},
"dependencies": {
"@lukeed/ms": "^2.0.1",
"asn1.js": "^5.4.1",
"ecdsa-sig-formatter": "^1.0.11",
"mnemonist": "^0.39.5"
Expand Down
19 changes: 15 additions & 4 deletions src/signer.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const {
const { TokenError } = require('./error')
const { getAsyncKey, ensurePromiseCallback } = require('./utils')
const { createPrivateKey, createSecretKey } = require('crypto')
const { parse:parseMs } = require("@lukeed/ms")

const supportedAlgorithms = new Set([...hsAlgorithms, ...esAlgorithms, ...rsaAlgorithms, ...edAlgorithms, 'none'])

Expand Down Expand Up @@ -240,12 +241,22 @@ module.exports = function createSigner(options) {
key = prepareKeyOrSecret(key, algorithm)
}

if (expiresIn && (typeof expiresIn !== 'number' || expiresIn < 0)) {
throw new TokenError(TokenError.codes.invalidOption, 'The expiresIn option must be a positive number.')
if (expiresIn) {
if (typeof expiresIn === 'string') {
expiresIn = parseMs(expiresIn)
}
if (typeof expiresIn !== 'number' || expiresIn < 0) {
throw new TokenError(TokenError.codes.invalidOption, 'The expiresIn option must be a positive number or a valid string.')
}
}

if (notBefore && (typeof notBefore !== 'number' || notBefore < 0)) {
throw new TokenError(TokenError.codes.invalidOption, 'The notBefore option must be a positive number.')
if (notBefore) {
if (typeof notBefore === 'string') {
notBefore = parseMs(notBefore)
}
if (typeof notBefore !== 'number' || notBefore < 0) {
throw new TokenError(TokenError.codes.invalidOption, 'The notBefore option must be a positive number or a valid string.')
}
}

if (clockTimestamp && (typeof clockTimestamp !== 'number' || clockTimestamp < 0)) {
Expand Down
48 changes: 42 additions & 6 deletions test/signer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,20 @@ test('it respects payload exp claim (if supplied), overriding the default expire
t.end()
})

test('it supports expiresIn as a string', t => {
t.equal(
sign({ a: 1, iat: 100 }, { expiresIn: '1000' }),
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJpYXQiOjEwMCwiZXhwIjoxMDF9.ULKqTsvUYm7iNOKA6bP5NXsa1A8vofgPIGiC182Vf_Q'
)

t.equal(
sign({ a: 1, iat: 100 }, { expiresIn: '1s' }),
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJpYXQiOjEwMCwiZXhwIjoxMDF9.ULKqTsvUYm7iNOKA6bP5NXsa1A8vofgPIGiC182Vf_Q'
)

t.end()
})

test('it adds the payload exp claim', t => {
t.equal(
sign({ a: 1, iat: 100, exp: 200 }, {}),
Expand Down Expand Up @@ -397,6 +411,20 @@ test('it adds a nbf claim, overriding the payload one, only if the payload is a
t.end()
})

test('it supports notBefore as a string', t => {
t.equal(
sign({ a: 1, iat: 100 }, { notBefore: '1000' }),
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJpYXQiOjEwMCwibmJmIjoxMDF9.WhZeNowse7q1s5FSlcMcs_4KcxXpSdQ4yqv0xrGB3sU'
)

t.equal(
sign({ a: 1, iat: 100 }, { notBefore: '1s' }),
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJpYXQiOjEwMCwibmJmIjoxMDF9.WhZeNowse7q1s5FSlcMcs_4KcxXpSdQ4yqv0xrGB3sU'
)

t.end()
})

test('it adds a jti claim, overriding the payload one, only if the payload is a object', t => {
t.equal(
sign({ a: 1 }, { jti: 'JTI', noTimestamp: true }),
Expand Down Expand Up @@ -672,24 +700,32 @@ test('options validation - clockTimestamp', t => {
})

test('options validation - expiresIn', t => {
t.throws(() => createSigner({ key: 'secret', expiresIn: '123' }), {
message: 'The expiresIn option must be a positive number.'
t.throws(() => createSigner({ key: 'secret', expiresIn: true }), {
message: 'The expiresIn option must be a positive number or a valid string.'
})

t.throws(() => createSigner({ key: 'secret', expiresIn: 'invalid string' }), {
message: 'The expiresIn option must be a positive number or a valid string'
})

t.throws(() => createSigner({ key: 'secret', expiresIn: -1 }), {
message: 'The expiresIn option must be a positive number.'
message: 'The expiresIn option must be a positive number or a valid string'
})

t.end()
})

test('options validation - notBefore', t => {
t.throws(() => createSigner({ key: 'secret', notBefore: '123' }), {
message: 'The notBefore option must be a positive number.'
t.throws(() => createSigner({ key: 'secret', notBefore: true }), {
message: 'The notBefore option must be a positive number or a valid string.'
})

t.throws(() => createSigner({ key: 'secret', notBefore: 'invalid string' }), {
message: 'The notBefore option must be a positive number or a valid string.'
})

t.throws(() => createSigner({ key: 'secret', notBefore: -1 }), {
message: 'The notBefore option must be a positive number.'
message: 'The notBefore option must be a positive number or a valid string.'
})

t.end()
Expand Down

0 comments on commit c77055d

Please sign in to comment.