Skip to content

Commit 1f12d88

Browse files
committed
refactor: reorganize jwt claim set utils
1 parent 0848798 commit 1f12d88

9 files changed

+123
-150
lines changed

src/jwt/decrypt.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import type * as types from '../types.d.ts'
88
import { compactDecrypt } from '../jwe/compact/decrypt.js'
9-
import jwtPayload from '../lib/jwt_claims_set.js'
9+
import { validateClaimsSet } from '../lib/jwt_claims_set.js'
1010
import { JWTClaimValidationFailed } from '../util/errors.js'
1111

1212
/** Combination of JWE Decryption options and JWT Claims Set verification options. */
@@ -71,7 +71,7 @@ export async function jwtDecrypt(
7171
options?: JWTDecryptOptions,
7272
) {
7373
const decrypted = await compactDecrypt(jwt, key as Parameters<typeof compactDecrypt>[1], options)
74-
const payload = jwtPayload(decrypted.protectedHeader, decrypted.plaintext, options)
74+
const payload = validateClaimsSet(decrypted.protectedHeader, decrypted.plaintext, options)
7575

7676
const { protectedHeader } = decrypted
7777

src/jwt/encrypt.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import type * as types from '../types.d.ts'
88
import { CompactEncrypt } from '../jwe/compact/encrypt.js'
9-
import { ProduceJWT } from './produce.js'
9+
import { JWTClaimsBuilder } from '../lib/jwt_claims_set.js'
1010

1111
/**
1212
* The EncryptJWT class is used to build and encrypt Compact JWE formatted JSON Web Tokens.
@@ -44,15 +44,15 @@ export class EncryptJWT implements types.ProduceJWT {
4444

4545
#replicateAudienceAsHeader!: boolean
4646

47-
#jwt: ProduceJWT
47+
#jwt: JWTClaimsBuilder
4848

4949
/**
5050
* {@link EncryptJWT} constructor
5151
*
5252
* @param payload The JWT Claims Set object. Defaults to an empty object.
5353
*/
5454
constructor(payload: types.JWTPayload = {}) {
55-
this.#jwt = new ProduceJWT(payload)
55+
this.#jwt = new JWTClaimsBuilder(payload)
5656
}
5757

5858
setIssuer(issuer: string): this {

src/jwt/produce.ts

-104
This file was deleted.

src/jwt/sign.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import { CompactSign } from '../jws/compact/sign.js'
88
import { JWTInvalid } from '../util/errors.js'
99
import type * as types from '../types.d.ts'
10-
import { ProduceJWT } from './produce.js'
10+
import { JWTClaimsBuilder } from '../lib/jwt_claims_set.js'
1111

1212
/**
1313
* The SignJWT class is used to build and sign Compact JWS formatted JSON Web Tokens.
@@ -116,15 +116,15 @@ import { ProduceJWT } from './produce.js'
116116
export class SignJWT implements types.ProduceJWT {
117117
#protectedHeader!: types.JWTHeaderParameters
118118

119-
#jwt: ProduceJWT
119+
#jwt: JWTClaimsBuilder
120120

121121
/**
122122
* {@link SignJWT} constructor
123123
*
124124
* @param payload The JWT Claims Set object. Defaults to an empty object.
125125
*/
126126
constructor(payload: types.JWTPayload = {}) {
127-
this.#jwt = new ProduceJWT(payload)
127+
this.#jwt = new JWTClaimsBuilder(payload)
128128
}
129129

130130
setIssuer(issuer: string): this {

src/jwt/unsecured.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import * as b64u from '../util/base64url.js'
99
import type * as types from '../types.d.ts'
1010
import { decoder } from '../lib/buffer_utils.js'
1111
import { JWTInvalid } from '../util/errors.js'
12-
import jwtPayload from '../lib/jwt_claims_set.js'
13-
import { ProduceJWT } from './produce.js'
12+
import { validateClaimsSet, JWTClaimsBuilder } from '../lib/jwt_claims_set.js'
1413

1514
/** Result of decoding an Unsecured JWT. */
1615
export interface UnsecuredResult<PayloadType = types.JWTPayload> {
@@ -53,15 +52,15 @@ export interface UnsecuredResult<PayloadType = types.JWTPayload> {
5352
* ```
5453
*/
5554
export class UnsecuredJWT implements types.ProduceJWT {
56-
#jwt: ProduceJWT
55+
#jwt: JWTClaimsBuilder
5756

5857
/**
5958
* {@link UnsecuredJWT} constructor
6059
*
6160
* @param payload The JWT Claims Set object. Defaults to an empty object.
6261
*/
6362
constructor(payload: types.JWTPayload = {}) {
64-
this.#jwt = new ProduceJWT(payload)
63+
this.#jwt = new JWTClaimsBuilder(payload)
6564
}
6665

6766
/** Encodes the Unsecured JWT. */
@@ -134,7 +133,7 @@ export class UnsecuredJWT implements types.ProduceJWT {
134133
throw new JWTInvalid('Invalid Unsecured JWT')
135134
}
136135

137-
const payload = jwtPayload(
136+
const payload = validateClaimsSet(
138137
header,
139138
b64u.decode(encodedPayload),
140139
options,

src/jwt/verify.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import { compactVerify } from '../jws/compact/verify.js'
88
import type * as types from '../types.d.ts'
9-
import jwtPayload from '../lib/jwt_claims_set.js'
9+
import { validateClaimsSet } from '../lib/jwt_claims_set.js'
1010
import { JWTInvalid } from '../util/errors.js'
1111

1212
/** Combination of JWS Verification options and JWT Claims Set verification options. */
@@ -151,7 +151,7 @@ export async function jwtVerify(
151151
if (verified.protectedHeader.crit?.includes('b64') && verified.protectedHeader.b64 === false) {
152152
throw new JWTInvalid('JWTs MUST NOT use unencoded payload')
153153
}
154-
const payload = jwtPayload(verified.protectedHeader, verified.payload, options)
154+
const payload = validateClaimsSet(verified.protectedHeader, verified.payload, options)
155155
const result = { payload, protectedHeader: verified.protectedHeader }
156156
if (typeof key === 'function') {
157157
return { ...result, key: verified.key }

src/lib/jwt_claims_set.ts

+102-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ import { decoder } from './buffer_utils.js'
44
import epoch from './epoch.js'
55
import secs from './secs.js'
66
import isObject from './is_object.js'
7+
import { encoder } from './buffer_utils.js'
8+
9+
function validateInput(label: string, input: number) {
10+
if (!Number.isFinite(input)) {
11+
throw new TypeError(`Invalid ${label} input`)
12+
}
13+
14+
return input
15+
}
716

817
const normalizeTyp = (value: string) => value.toLowerCase().replace(/^application\//, '')
918

@@ -21,11 +30,11 @@ const checkAudiencePresence = (audPayload: unknown, audOption: unknown[]) => {
2130
return false
2231
}
2332

24-
export default (
33+
export function validateClaimsSet(
2534
protectedHeader: types.JWEHeaderParameters | types.JWSHeaderParameters,
2635
encodedPayload: Uint8Array,
2736
options: types.JWTClaimVerificationOptions = {},
28-
) => {
37+
) {
2938
let payload!: { [propName: string]: unknown }
3039
try {
3140
payload = JSON.parse(decoder.decode(encodedPayload))
@@ -174,3 +183,94 @@ export default (
174183

175184
return payload as types.JWTPayload
176185
}
186+
187+
export class JWTClaimsBuilder {
188+
#payload!: types.JWTPayload
189+
190+
constructor(payload: types.JWTPayload) {
191+
if (!isObject(payload)) {
192+
throw new TypeError('JWT Claims Set MUST be an object')
193+
}
194+
this.#payload = structuredClone(payload)
195+
}
196+
197+
data(): Uint8Array {
198+
return encoder.encode(JSON.stringify(this.#payload))
199+
}
200+
201+
get iss(): string | undefined {
202+
return this.#payload.iss
203+
}
204+
205+
set iss(value: string) {
206+
this.#payload.iss = value
207+
}
208+
209+
get sub(): string | undefined {
210+
return this.#payload.sub
211+
}
212+
213+
set sub(value: string) {
214+
this.#payload.sub = value
215+
}
216+
217+
get aud(): string | string[] | undefined {
218+
return this.#payload.aud
219+
}
220+
221+
set aud(value: string | string[]) {
222+
this.#payload.aud = value
223+
}
224+
225+
get jti(): string | undefined {
226+
return this.#payload.jti
227+
}
228+
229+
set jti(value: string) {
230+
this.#payload.jti = value
231+
}
232+
233+
get nbf(): number | undefined {
234+
return this.#payload.nbf
235+
}
236+
237+
set nbf(value: number | string | Date) {
238+
if (typeof value === 'number') {
239+
this.#payload.nbf = validateInput('setNotBefore', value)
240+
} else if (value instanceof Date) {
241+
this.#payload.nbf = validateInput('setNotBefore', epoch(value))
242+
} else {
243+
this.#payload.nbf = epoch(new Date()) + secs(value)
244+
}
245+
}
246+
247+
get exp(): number | undefined {
248+
return this.#payload.exp
249+
}
250+
251+
set exp(value: number | string | Date) {
252+
if (typeof value === 'number') {
253+
this.#payload.exp = validateInput('setExpirationTime', value)
254+
} else if (value instanceof Date) {
255+
this.#payload.exp = validateInput('setExpirationTime', epoch(value))
256+
} else {
257+
this.#payload.exp = epoch(new Date()) + secs(value)
258+
}
259+
}
260+
261+
get iat(): number | undefined {
262+
return this.#payload.iat
263+
}
264+
265+
set iat(value: number | string | Date | undefined) {
266+
if (typeof value === 'undefined') {
267+
this.#payload.iat = epoch(new Date())
268+
} else if (value instanceof Date) {
269+
this.#payload.iat = validateInput('setIssuedAt', epoch(value))
270+
} else if (typeof value === 'string') {
271+
this.#payload.iat = validateInput('setIssuedAt', epoch(new Date()) + secs(value))
272+
} else {
273+
this.#payload.iat = validateInput('setIssuedAt', value)
274+
}
275+
}
276+
}

src/lib/produce_jwt.ts

Whitespace-only changes.

typedoc.json

+7-29
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,13 @@
44
"hideBreadcrumbs": true,
55
"entryPoints": [
66
"src/types.d.ts",
7-
"src/jwt/decrypt.ts",
8-
"src/jwt/encrypt.ts",
9-
"src/jwt/sign.ts",
10-
"src/jwt/unsecured.ts",
11-
"src/jwt/verify.ts",
12-
"src/jwe/compact/encrypt.ts",
13-
"src/jwe/compact/decrypt.ts",
14-
"src/jwe/flattened/encrypt.ts",
15-
"src/jwe/flattened/decrypt.ts",
16-
"src/jwe/general/encrypt.ts",
17-
"src/jwe/general/decrypt.ts",
18-
"src/jws/compact/sign.ts",
19-
"src/jws/compact/verify.ts",
20-
"src/jws/flattened/sign.ts",
21-
"src/jws/flattened/verify.ts",
22-
"src/jws/general/sign.ts",
23-
"src/jws/general/verify.ts",
24-
"src/jwk/embedded.ts",
25-
"src/jwk/thumbprint.ts",
26-
"src/jwks/local.ts",
27-
"src/jwks/remote.ts",
28-
"src/util/base64url.ts",
29-
"src/util/decode_jwt.ts",
30-
"src/util/decode_protected_header.ts",
31-
"src/util/errors.ts",
32-
"src/key/export.ts",
33-
"src/key/generate_key_pair.ts",
34-
"src/key/generate_secret.ts",
35-
"src/key/import.ts"
7+
"src/jwe/**/*.ts",
8+
"src/jwk/*.ts",
9+
"src/jwks/*.ts",
10+
"src/jws/**/*.ts",
11+
"src/jwt/*.ts",
12+
"src/key/*.ts",
13+
"src/util/*.ts",
3614
],
3715
"excludeExternals": true,
3816
"excludePrivate": true,

0 commit comments

Comments
 (0)