Skip to content

Commit

Permalink
feat: default to an empty payload in JWT producing constructors
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Oct 23, 2023
1 parent bd830a4 commit 98d6ca1
Show file tree
Hide file tree
Showing 6 changed files with 23 additions and 35 deletions.
4 changes: 2 additions & 2 deletions src/jwt/produce.ts
Expand Up @@ -15,8 +15,8 @@ function validateInput(label: string, input: number) {
export class ProduceJWT {
protected _payload!: JWTPayload

/** @param payload The JWT Claims Set object. */
constructor(payload: JWTPayload) {
/** @param payload The JWT Claims Set object. Defaults to an empty object. */
constructor(payload: JWTPayload = {}) {
if (!isObject(payload)) {
throw new TypeError('JWT Claims Set MUST be an object')
}
Expand Down
2 changes: 1 addition & 1 deletion tap/jwks.ts
Expand Up @@ -105,7 +105,7 @@ export default (QUnit: QUnit, lib: typeof jose) => {
{
const [jwk] = keys
const key = await lib.importJWK({ ...jwk, alg: 'PS256' })
const jwt = await new lib.SignJWT({})
const jwt = await new lib.SignJWT()
.setProtectedHeader({ alg: 'PS256', kid: jwk.kid })
.sign(key)
const { key: resolvedKey } = await lib.jwtVerify(jwt, JWKS)
Expand Down
16 changes: 8 additions & 8 deletions test/jwks/remote.test.mjs
Expand Up @@ -126,7 +126,7 @@ test.serial('RemoteJWKSet', async (t) => {
{
const [jwk] = keys
const key = await importJWK({ ...jwk, alg: 'PS256' })
const jwt = await new SignJWT({}).setProtectedHeader({ alg: 'PS256', kid: jwk.kid }).sign(key)
const jwt = await new SignJWT().setProtectedHeader({ alg: 'PS256', kid: jwk.kid }).sign(key)
await t.notThrowsAsync(async () => {
const { key: resolvedKey } = await jwtVerify(jwt, JWKS)
t.truthy(resolvedKey)
Expand Down Expand Up @@ -178,7 +178,7 @@ test.serial('RemoteJWKSet', async (t) => {
{
const [jwk] = keys
const key = await importJWK({ ...jwk, alg: 'RS256' })
const jwt = await new SignJWT({}).setProtectedHeader({ alg: 'RS256' }).sign(key)
const jwt = await new SignJWT().setProtectedHeader({ alg: 'RS256' }).sign(key)
let error = await t.throwsAsync(jwtVerify(jwt, JWKS), {
code: 'ERR_JWKS_MULTIPLE_MATCHING_KEYS',
message: 'multiple matching keys found in the JSON Web Key Set',
Expand Down Expand Up @@ -206,7 +206,7 @@ test.serial('RemoteJWKSet', async (t) => {
{
const [, jwk] = keys
const key = await importJWK({ ...jwk, alg: 'PS256' })
const jwt = await new SignJWT({}).setProtectedHeader({ alg: 'PS256', kid: jwk.kid }).sign(key)
const jwt = await new SignJWT().setProtectedHeader({ alg: 'PS256', kid: jwk.kid }).sign(key)
await t.throwsAsync(jwtVerify(jwt, JWKS), {
code: 'ERR_JWKS_NO_MATCHING_KEY',
message: 'no applicable key found in the JSON Web Key Set',
Expand All @@ -215,7 +215,7 @@ test.serial('RemoteJWKSet', async (t) => {
{
const [, , jwk] = keys
const key = await importJWK({ ...jwk, alg: 'ES256' })
const jwt = await new SignJWT({}).setProtectedHeader({ alg: 'ES256' }).sign(key)
const jwt = await new SignJWT().setProtectedHeader({ alg: 'ES256' }).sign(key)
await t.notThrowsAsync(jwtVerify(jwt, JWKS))
}
})
Expand Down Expand Up @@ -247,12 +247,12 @@ test.serial('refreshes the JWKS once off cooldown', async (t) => {
const JWKS = createRemoteJWKSet(url)
const key = await importJWK({ ...jwk, alg: 'ES256' })
{
const jwt = await new SignJWT({}).setProtectedHeader({ alg: 'ES256', kid: 'one' }).sign(key)
const jwt = await new SignJWT().setProtectedHeader({ alg: 'ES256', kid: 'one' }).sign(key)
await t.notThrowsAsync(jwtVerify(jwt, JWKS))
await t.notThrowsAsync(jwtVerify(jwt, JWKS))
}
{
const jwt = await new SignJWT({}).setProtectedHeader({ alg: 'ES256', kid: 'two' }).sign(key)
const jwt = await new SignJWT().setProtectedHeader({ alg: 'ES256', kid: 'two' }).sign(key)
await t.throwsAsync(jwtVerify(jwt, JWKS), {
code: 'ERR_JWKS_NO_MATCHING_KEY',
message: 'no applicable key found in the JSON Web Key Set',
Expand Down Expand Up @@ -291,7 +291,7 @@ test.serial('refreshes the JWKS once stale', async (t) => {
const JWKS = createRemoteJWKSet(url, { cacheMaxAge: 60 * 10 * 1000 })
const key = await importJWK({ ...jwk, alg: 'ES256' })
{
const jwt = await new SignJWT({}).setProtectedHeader({ alg: 'ES256', kid: 'one' }).sign(key)
const jwt = await new SignJWT().setProtectedHeader({ alg: 'ES256', kid: 'one' }).sign(key)
await t.notThrowsAsync(jwtVerify(jwt, JWKS))
await t.notThrowsAsync(jwtVerify(jwt, JWKS))
timekeeper.travel((now + 60 * 10) * 1000)
Expand Down Expand Up @@ -326,7 +326,7 @@ test.serial('can be configured to never be stale', async (t) => {
const JWKS = createRemoteJWKSet(url, { cacheMaxAge: Infinity })
const key = await importJWK({ ...jwk, alg: 'ES256' })
{
const jwt = await new SignJWT({}).setProtectedHeader({ alg: 'ES256', kid: 'one' }).sign(key)
const jwt = await new SignJWT().setProtectedHeader({ alg: 'ES256', kid: 'one' }).sign(key)
await t.notThrowsAsync(jwtVerify(jwt, JWKS))
await t.notThrowsAsync(jwtVerify(jwt, JWKS))
timekeeper.travel((now + 60 * 10) * 1000)
Expand Down
9 changes: 1 addition & 8 deletions test/jwt/encrypt.test.mjs
Expand Up @@ -67,15 +67,8 @@ test('EncryptJWT w/crit', async (t) => {
)
})

test('new EncryptJWT', (t) => {
t.throws(() => new EncryptJWT(), {
instanceOf: TypeError,
message: 'JWT Claims Set MUST be an object',
})
})

async function testJWTsetFunction(t, method, claim, value, duplicate = false, expected = value) {
let enc = new EncryptJWT({}).setProtectedHeader({ alg: 'dir', enc: 'A128GCM' })[method](value)
let enc = new EncryptJWT().setProtectedHeader({ alg: 'dir', enc: 'A128GCM' })[method](value)

if (duplicate) {
enc = enc[`replicate${method.slice(3)}AsHeader`]()
Expand Down
18 changes: 8 additions & 10 deletions test/jwt/sign.test.mjs
Expand Up @@ -24,6 +24,11 @@ test('SignJWT', async (t) => {
)
})

test('SignJWT with default (empty) payload', async (t) => {
const jwt = await new SignJWT().setProtectedHeader({ alg: 'HS256' }).sign(t.context.secret)
t.is(jwt, 'eyJhbGciOiJIUzI1NiJ9.e30.4E_Bsx-pJi3kOW9wVXN8CgbATwP09D9V5gxh9-9zSZ0')
})

test('SignJWT w/crit', async (t) => {
const expected =
'eyJhbGciOiJIUzI1NiIsImNyaXQiOlsiaHR0cDovL29wZW5iYW5raW5nLm9yZy51ay9pYXQiXSwiaHR0cDovL29wZW5iYW5raW5nLm9yZy51ay9pYXQiOjB9.eyJ1cm46ZXhhbXBsZTpjbGFpbSI6dHJ1ZX0.YzOrPZaNql7PpCo43HAJdj-LASP8lOmtb-Bzj9OrNAk'
Expand Down Expand Up @@ -61,29 +66,22 @@ test('SignJWT w/crit', async (t) => {
)
})

test('new SignJWT', (t) => {
t.throws(() => new SignJWT(), {
instanceOf: TypeError,
message: 'JWT Claims Set MUST be an object',
})
})

test('Signed JWTs cannot use unencoded payload', async (t) => {
await t.throwsAsync(
() =>
new SignJWT({})
new SignJWT()
.setProtectedHeader({ alg: 'HS256', crit: ['b64'], b64: false })
.sign(t.context.secret),
{ code: 'ERR_JWT_INVALID', message: 'JWTs MUST NOT use unencoded payload' },
)
await t.throwsAsync(() => new SignJWT({}).sign(t.context.secret), {
await t.throwsAsync(() => new SignJWT().sign(t.context.secret), {
code: 'ERR_JWS_INVALID',
message: 'either setProtectedHeader or setUnprotectedHeader must be called before #sign()',
})
})

async function testJWTsetFunction(t, method, claim, value, expected = value) {
const jwt = await new SignJWT({})
const jwt = await new SignJWT()
.setProtectedHeader({ alg: 'HS256' })
[method](value)
.sign(t.context.secret)
Expand Down
9 changes: 3 additions & 6 deletions test/jwt/unsecured.test.mjs
Expand Up @@ -41,15 +41,12 @@ test('UnsecuredJWT validations', (t) => {
})
})

test('new UnsecuredJWT', (t) => {
t.throws(() => new UnsecuredJWT(), {
instanceOf: TypeError,
message: 'JWT Claims Set MUST be an object',
})
test('new UnsecuredJWT()', (t) => {
t.is(new UnsecuredJWT().encode(), 'eyJhbGciOiJub25lIn0.e30.')
})

async function testJWTsetFunction(t, method, claim, value, expected = value) {
const jwt = new UnsecuredJWT({})[method](value).encode()
const jwt = new UnsecuredJWT()[method](value).encode()
const { payload: claims } = UnsecuredJWT.decode(jwt)
t.true(claim in claims)
t.is(claims[claim], expected)
Expand Down

0 comments on commit 98d6ca1

Please sign in to comment.