@atproto/jwk 0.7.2 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/jwt.ts DELETED
@@ -1,220 +0,0 @@
1
- import { z } from 'zod'
2
- import { jwkPubSchema } from './jwk.js'
3
- import { jwtCharsRefinement, segmentedStringRefinementFactory } from './util.js'
4
-
5
- export const signedJwtSchema = z
6
- .string()
7
- .superRefine(jwtCharsRefinement)
8
- .superRefine(segmentedStringRefinementFactory(3))
9
-
10
- export type SignedJwt = z.infer<typeof signedJwtSchema>
11
- export const isSignedJwt = (data: unknown): data is SignedJwt =>
12
- signedJwtSchema.safeParse(data).success
13
-
14
- export const unsignedJwtSchema = z
15
- .string()
16
- .superRefine(jwtCharsRefinement)
17
- .superRefine(segmentedStringRefinementFactory(2))
18
-
19
- export type UnsignedJwt = z.infer<typeof unsignedJwtSchema>
20
- export const isUnsignedJwt = (data: unknown): data is UnsignedJwt =>
21
- unsignedJwtSchema.safeParse(data).success
22
-
23
- /**
24
- * @see {@link https://www.rfc-editor.org/rfc/rfc7515.html#section-4}
25
- */
26
- export const jwtHeaderSchema = z
27
- .object({
28
- /** "alg" (Algorithm) Header Parameter */
29
- alg: z.string(),
30
- /** "jku" (JWK Set URL) Header Parameter */
31
- jku: z.string().url().optional(),
32
- /** "jwk" (JSON Web Key) Header Parameter */
33
- jwk: z
34
- .object({
35
- kty: z.string(),
36
- crv: z.string().optional(),
37
- x: z.string().optional(),
38
- y: z.string().optional(),
39
- e: z.string().optional(),
40
- n: z.string().optional(),
41
- })
42
- .optional(),
43
- /** "kid" (Key ID) Header Parameter */
44
- kid: z.string().optional(),
45
- /** "x5u" (X.509 URL) Header Parameter */
46
- x5u: z.string().optional(),
47
- /** "x5c" (X.509 Certificate Chain) Header Parameter */
48
- x5c: z.array(z.string()).optional(),
49
- /** "x5t" (X.509 Certificate SHA-1 Thumbprint) Header Parameter */
50
- x5t: z.string().optional(),
51
- /** "x5t#S256" (X.509 Certificate SHA-256 Thumbprint) Header Parameter */
52
- 'x5t#S256': z.string().optional(),
53
- /** "typ" (Type) Header Parameter */
54
- typ: z.string().optional(),
55
- /** "cty" (Content Type) Header Parameter */
56
- cty: z.string().optional(),
57
- /** "crit" (Critical) Header Parameter */
58
- crit: z.array(z.string()).optional(),
59
- })
60
- .passthrough()
61
-
62
- export type JwtHeader = z.infer<typeof jwtHeaderSchema>
63
-
64
- /**
65
- * @see {@link https://www.rfc-editor.org/rfc/rfc9449.html#section-4.2-4.6}
66
- * @see {@link https://www.rfc-editor.org/rfc/rfc9110#section-7.1}
67
- */
68
- export const htuSchema = z.string().superRefine((value, ctx) => {
69
- try {
70
- const url = new URL(value)
71
- if (url.protocol !== 'http:' && url.protocol !== 'https:') {
72
- ctx.addIssue({
73
- code: z.ZodIssueCode.custom,
74
- message: 'Only http: and https: protocols are allowed',
75
- })
76
- }
77
-
78
- if (url.username || url.password) {
79
- ctx.addIssue({
80
- code: z.ZodIssueCode.custom,
81
- message: 'Credentials not allowed',
82
- })
83
- }
84
-
85
- if (url.search) {
86
- ctx.addIssue({
87
- code: z.ZodIssueCode.custom,
88
- message: 'Query string not allowed',
89
- })
90
- }
91
-
92
- if (url.hash) {
93
- ctx.addIssue({
94
- code: z.ZodIssueCode.custom,
95
- message: 'Fragment not allowed',
96
- })
97
- }
98
- } catch (err) {
99
- ctx.addIssue({
100
- code: z.ZodIssueCode.invalid_string,
101
- validation: 'url',
102
- })
103
- }
104
-
105
- return value
106
- })
107
-
108
- // https://www.iana.org/assignments/jwt/jwt.xhtml
109
- export const jwtPayloadSchema = z
110
- .object({
111
- iss: z.string().optional(),
112
- aud: z.union([z.string(), z.array(z.string()).nonempty()]).optional(),
113
- sub: z.string().optional(),
114
- exp: z.number().int().optional(),
115
- nbf: z.number().int().optional(),
116
- iat: z.number().int().optional(),
117
- jti: z.string().optional(),
118
- htm: z.string().optional(),
119
- htu: htuSchema.optional(),
120
- ath: z.string().optional(),
121
- acr: z.string().optional(),
122
- azp: z.string().optional(),
123
- amr: z.array(z.string()).optional(),
124
- // https://datatracker.ietf.org/doc/html/rfc7800
125
- cnf: z
126
- .object({
127
- kid: z.string().optional(), // Key ID
128
- jwk: jwkPubSchema.optional(), // JWK
129
- jwe: z.string().optional(), // Encrypted key
130
- jku: z.string().url().optional(), // JWK Set URI ("kid" should also be provided)
131
-
132
- // https://datatracker.ietf.org/doc/html/rfc9449#section-6.1
133
- jkt: z.string().optional(),
134
-
135
- // https://datatracker.ietf.org/doc/html/rfc8705
136
- 'x5t#S256': z.string().optional(), // X.509 Certificate SHA-256 Thumbprint
137
-
138
- // https://datatracker.ietf.org/doc/html/rfc9203
139
- osc: z.string().optional(), // OSCORE_Input_Material carrying the parameters for using OSCORE per-message security with implicit key confirmation
140
- })
141
- .optional(),
142
-
143
- client_id: z.string().optional(),
144
-
145
- scope: z.string().optional(),
146
- nonce: z.string().optional(),
147
-
148
- at_hash: z.string().optional(),
149
- c_hash: z.string().optional(),
150
- s_hash: z.string().optional(),
151
- auth_time: z.number().int().optional(),
152
-
153
- // https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
154
-
155
- // OpenID: "profile" scope
156
- name: z.string().optional(),
157
- family_name: z.string().optional(),
158
- given_name: z.string().optional(),
159
- middle_name: z.string().optional(),
160
- nickname: z.string().optional(),
161
- preferred_username: z.string().optional(),
162
- gender: z.string().optional(), // OpenID only defines "male" and "female" without forbidding other values
163
- picture: z.string().url().optional(),
164
- profile: z.string().url().optional(),
165
- website: z.string().url().optional(),
166
- birthdate: z
167
- .string()
168
- .regex(/\d{4}-\d{2}-\d{2}/) // YYYY-MM-DD
169
- .optional(),
170
- zoneinfo: z
171
- .string()
172
- .regex(/^[A-Za-z0-9_/]+$/)
173
- .optional(),
174
- locale: z
175
- .string()
176
- .regex(/^[a-z]{2,3}(-[A-Z]{2})?$/)
177
- .optional(),
178
- updated_at: z.number().int().optional(),
179
-
180
- // OpenID: "email" scope
181
- email: z.string().optional(),
182
- email_verified: z.boolean().optional(),
183
-
184
- // OpenID: "phone" scope
185
- phone_number: z.string().optional(),
186
- phone_number_verified: z.boolean().optional(),
187
-
188
- // OpenID: "address" scope
189
- // https://openid.net/specs/openid-connect-core-1_0.html#AddressClaim
190
- address: z
191
- .object({
192
- formatted: z.string().optional(),
193
- street_address: z.string().optional(),
194
- locality: z.string().optional(),
195
- region: z.string().optional(),
196
- postal_code: z.string().optional(),
197
- country: z.string().optional(),
198
- })
199
- .optional(),
200
-
201
- // https://datatracker.ietf.org/doc/html/rfc9396#section-14.2
202
- authorization_details: z
203
- .array(
204
- z
205
- .object({
206
- type: z.string(),
207
- // https://datatracker.ietf.org/doc/html/rfc9396#section-2.2
208
- locations: z.array(z.string()).optional(),
209
- actions: z.array(z.string()).optional(),
210
- datatypes: z.array(z.string()).optional(),
211
- identifier: z.string().optional(),
212
- privileges: z.array(z.string()).optional(),
213
- })
214
- .passthrough(),
215
- )
216
- .optional(),
217
- })
218
- .passthrough()
219
-
220
- export type JwtPayload = z.infer<typeof jwtPayloadSchema>
package/src/key.ts DELETED
@@ -1,211 +0,0 @@
1
- import { jwkAlgorithms } from './alg.js'
2
- import {
3
- Jwk,
4
- KeyUsage,
5
- PUBLIC_KEY_USAGE,
6
- PrivateJwk,
7
- PublicJwk,
8
- PublicKeyUsage,
9
- hasSharedSecretJwk,
10
- isEncKeyUsage,
11
- isPrivateJwk,
12
- isPublicKeyUsage,
13
- isSigKeyUsage,
14
- jwkPubSchema,
15
- jwkSchema,
16
- } from './jwk.js'
17
- import { VerifyOptions, VerifyResult } from './jwt-verify.js'
18
- import { JwtHeader, JwtPayload, SignedJwt } from './jwt.js'
19
- import { cachedGetter } from './util.js'
20
-
21
- export type KeyMatchOptions = {
22
- usage?: KeyUsage
23
- kid?: string | string[]
24
- alg?: string | string[]
25
- }
26
-
27
- export type ActivityCheckOptions = {
28
- allowRevoked?: boolean
29
- clockTolerance?: number
30
- currentDate?: Date
31
- }
32
-
33
- export abstract class Key<J extends Jwk = Jwk> {
34
- constructor(readonly jwk: Readonly<J>) {}
35
-
36
- @cachedGetter
37
- get isPrivate(): boolean {
38
- return isPrivateJwk(this.jwk)
39
- }
40
-
41
- @cachedGetter
42
- get isSymetric(): boolean {
43
- return hasSharedSecretJwk(this.jwk)
44
- }
45
-
46
- get privateJwk(): Readonly<PrivateJwk> | undefined {
47
- if (!this.isPrivate) return undefined
48
-
49
- return this.jwk as Readonly<PrivateJwk>
50
- }
51
-
52
- @cachedGetter
53
- get publicJwk(): Readonly<PublicJwk> | undefined {
54
- if (this.isSymetric) return undefined
55
- if (!this.isPrivate) return this.jwk as Readonly<PublicJwk>
56
-
57
- const validated = jwkPubSchema.safeParse({
58
- ...this.jwk,
59
- d: undefined,
60
- k: undefined,
61
- use: undefined,
62
- key_ops: buildPublicKeyOps(this.keyOps) ?? PUBLIC_KEY_USAGE,
63
- })
64
-
65
- // One reason why the parsing might fail is if key_ops is empty. This check
66
- // also allows to future proof the code (e.g if another type of private key
67
- // is added that uses a different property than "d" or "k" to store its
68
- // private value).
69
- if (!validated.success) return undefined
70
-
71
- return Object.freeze(validated.data)
72
- }
73
-
74
- @cachedGetter
75
- get bareJwk(): Readonly<Jwk> | undefined {
76
- if (this.isSymetric) return undefined
77
- const { kty, crv, e, n, x, y } = this.jwk as any
78
- return Object.freeze(jwkSchema.parse({ crv, e, kty, n, x, y }))
79
- }
80
-
81
- /**
82
- * @note Only defined on public keys
83
- */
84
- get use(): 'sig' | 'enc' | undefined {
85
- return this.jwk.use
86
- }
87
-
88
- get keyOps(): readonly KeyUsage[] | undefined {
89
- return this.jwk.key_ops
90
- }
91
-
92
- /**
93
- * The (forced) algorithm to use. If not provided, the key will be usable with
94
- * any of the algorithms in {@link algorithms}.
95
- *
96
- * @see {@link https://datatracker.ietf.org/doc/html/rfc7518#section-3.1 | "alg" (Algorithm) Header Parameter Values for JWS}
97
- */
98
- get alg() {
99
- return this.jwk.alg
100
- }
101
-
102
- get kid() {
103
- return this.jwk.kid
104
- }
105
-
106
- get crv() {
107
- return (this.jwk as { crv: undefined } | Extract<J, { crv: unknown }>).crv
108
- }
109
-
110
- /**
111
- * All the algorithms that this key can be used with. If `alg` is provided,
112
- * this set will only contain that algorithm.
113
- */
114
- @cachedGetter
115
- get algorithms(): readonly string[] {
116
- return Object.freeze(Array.from(jwkAlgorithms(this.jwk)))
117
- }
118
-
119
- get isRevoked() {
120
- return this.jwk.revoked != null
121
- }
122
-
123
- isActive(options?: ActivityCheckOptions) {
124
- if (!options?.allowRevoked && this.isRevoked) return false
125
-
126
- const tolerance = options?.clockTolerance ?? 0
127
- if (tolerance !== Infinity) {
128
- const now = options?.currentDate?.getTime() ?? Date.now()
129
- const { exp, nbf } = this.jwk
130
-
131
- if (nbf != null && !(now >= nbf * 1e3 - tolerance)) return false
132
- if (exp != null && !(now < exp * 1e3 + tolerance)) return false
133
- }
134
-
135
- return true
136
- }
137
-
138
- matches(opts: KeyMatchOptions): boolean {
139
- if (opts.kid != null) {
140
- const matchesKid = Array.isArray(opts.kid)
141
- ? this.kid != null && opts.kid.includes(this.kid)
142
- : this.kid === opts.kid
143
- if (!matchesKid) return false
144
- }
145
-
146
- if (opts.alg != null) {
147
- const matchesAlg = Array.isArray(opts.alg)
148
- ? opts.alg.some((a) => this.algorithms.includes(a))
149
- : this.algorithms.includes(opts.alg)
150
- if (!matchesAlg) return false
151
- }
152
-
153
- if (opts.usage != null) {
154
- const matchesOps =
155
- this.keyOps == null ||
156
- this.keyOps.includes(opts.usage) ||
157
- // @NOTE Because this.jwk represents the private key (typically used for
158
- // private operations), the public counterpart operations are allowed.
159
- (opts.usage === 'verify' && this.keyOps.includes('sign')) ||
160
- (opts.usage === 'encrypt' && this.keyOps.includes('decrypt')) ||
161
- (opts.usage === 'wrapKey' && this.keyOps.includes('unwrapKey'))
162
- if (!matchesOps) return false
163
-
164
- const matchesUse =
165
- this.use == null ||
166
- (this.use === 'sig' && isSigKeyUsage(opts.usage)) ||
167
- (this.use === 'enc' && isEncKeyUsage(opts.usage))
168
- if (!matchesUse) return false
169
-
170
- // @NOTE This is only relevant when "key_ops" and "use" are undefined.
171
- // This line also ensures that when "opts.usage" is a private key usage
172
- // (e.g. "sign"), the key is indeed a private key.
173
- const matchesKeyType = this.isPrivate || isPublicKeyUsage(opts.usage)
174
- if (!matchesKeyType) return false
175
- }
176
-
177
- return true
178
- }
179
-
180
- /**
181
- * Create a signed JWT
182
- */
183
- abstract createJwt(header: JwtHeader, payload: JwtPayload): Promise<SignedJwt>
184
-
185
- /**
186
- * Verify the signature, headers and payload of a JWT
187
- *
188
- * @throws {JwtVerifyError} if the JWT is invalid
189
- */
190
- abstract verifyJwt<C extends string = never>(
191
- token: SignedJwt,
192
- options?: VerifyOptions<C>,
193
- ): Promise<VerifyResult<C>>
194
- }
195
-
196
- function buildPublicKeyOps(
197
- keyUsages?: readonly KeyUsage[],
198
- ): PublicKeyUsage[] | undefined {
199
- if (keyUsages == null) return undefined
200
-
201
- // https://datatracker.ietf.org/doc/html/rfc7517#section-4.3
202
- // > Duplicate key operation values MUST NOT be present in the array.
203
- const publicOps = new Set(keyUsages.filter(isPublicKeyUsage))
204
-
205
- // @NOTE Translating private key usage into public key usage
206
- if (keyUsages.includes('sign')) publicOps.add('verify')
207
- if (keyUsages.includes('decrypt')) publicOps.add('encrypt')
208
- if (keyUsages.includes('unwrapKey')) publicOps.add('wrapKey')
209
-
210
- return Array.from(publicOps)
211
- }
package/src/keyset.ts DELETED
@@ -1,254 +0,0 @@
1
- import {
2
- ERR_JWKS_NO_MATCHING_KEY,
3
- ERR_JWK_NOT_FOUND,
4
- ERR_JWT_INVALID,
5
- JwkError,
6
- JwtCreateError,
7
- JwtVerifyError,
8
- } from './errors.js'
9
- import { PrivateKeyUsage } from './jwk.js'
10
- import { JwksPub } from './jwks.js'
11
- import { unsafeDecodeJwt } from './jwt-decode.js'
12
- import { VerifyOptions, VerifyResult } from './jwt-verify.js'
13
- import { JwtHeader, JwtPayload, SignedJwt } from './jwt.js'
14
- import { ActivityCheckOptions, Key, KeyMatchOptions } from './key.js'
15
- import {
16
- Override,
17
- cachedGetter,
18
- isDefined,
19
- matchesAny,
20
- preferredOrderCmp,
21
- } from './util.js'
22
-
23
- export type { ActivityCheckOptions, KeyMatchOptions }
24
- export type FindKeyOptions = KeyMatchOptions & ActivityCheckOptions
25
-
26
- export type JwtSignHeader = Override<
27
- JwtHeader,
28
- Pick<FindKeyOptions, 'alg' | 'kid'>
29
- >
30
-
31
- export type JwtPayloadGetter<P = JwtPayload> = (
32
- header: JwtHeader,
33
- key: Key,
34
- ) => P | PromiseLike<P>
35
-
36
- const extractPrivateJwk = (key: Key) => key.privateJwk
37
- const extractPublicJwk = (key: Key) => key.publicJwk
38
-
39
- export class Keyset<K extends Key = Key> implements Iterable<K> {
40
- private readonly keys: readonly K[]
41
-
42
- constructor(
43
- iterable: Iterable<K | null | undefined | false>,
44
- /**
45
- * The preferred algorithms to use when signing a JWT using this keyset.
46
- *
47
- * @see {@link https://datatracker.ietf.org/doc/html/rfc7518#section-3.1}
48
- */
49
- public readonly preferredSigningAlgorithms: readonly string[] = iterable instanceof
50
- Keyset
51
- ? [...iterable.preferredSigningAlgorithms]
52
- : [
53
- // Prefer elliptic curve algorithms
54
- 'EdDSA',
55
- 'ES256K',
56
- 'ES256',
57
- // https://datatracker.ietf.org/doc/html/rfc7518#section-3.5
58
- 'PS256',
59
- 'PS384',
60
- 'PS512',
61
- 'HS256',
62
- 'HS384',
63
- 'HS512',
64
- ],
65
- ) {
66
- const keys: K[] = []
67
-
68
- const keyIds = new Set<string>()
69
- for (const key of iterable) {
70
- if (!key) continue
71
-
72
- keys.push(key)
73
-
74
- if (key.kid) {
75
- if (keyIds.has(key.kid)) throw new JwkError(`Duplicate key: ${key.kid}`)
76
- else keyIds.add(key.kid)
77
- }
78
- }
79
-
80
- this.keys = Object.freeze(keys)
81
- }
82
-
83
- get size(): number {
84
- return this.keys.length
85
- }
86
-
87
- @cachedGetter
88
- get signAlgorithms(): readonly string[] {
89
- const algorithms = new Set<string>()
90
- for (const key of this) {
91
- if (key.use !== 'sig') continue
92
- for (const alg of key.algorithms) {
93
- algorithms.add(alg)
94
- }
95
- }
96
- return Object.freeze(
97
- [...algorithms].sort(preferredOrderCmp(this.preferredSigningAlgorithms)),
98
- )
99
- }
100
-
101
- @cachedGetter
102
- get publicJwks() {
103
- return Object.freeze({
104
- keys: Object.freeze(Array.from(this, extractPublicJwk).filter(isDefined)),
105
- })
106
- }
107
-
108
- @cachedGetter
109
- get privateJwks() {
110
- return Object.freeze({
111
- keys: Object.freeze(
112
- Array.from(this, extractPrivateJwk).filter(isDefined),
113
- ),
114
- })
115
- }
116
-
117
- has(kid: string): boolean {
118
- return this.keys.some((key) => key.kid === kid)
119
- }
120
-
121
- get(options: FindKeyOptions): K {
122
- const key = this.find(options)
123
- if (key) return key
124
-
125
- throw new JwkError(
126
- `Key not found ${options.kid ?? options.alg ?? options.usage ?? '<unknown>'}`,
127
- ERR_JWK_NOT_FOUND,
128
- )
129
- }
130
-
131
- find(options: FindKeyOptions): K | undefined {
132
- for (const key of this.list(options)) {
133
- return key
134
- }
135
-
136
- return undefined
137
- }
138
-
139
- *list<O extends FindKeyOptions>(options: O) {
140
- for (const key of this) {
141
- if (key.isActive(options) && key.matches(options)) {
142
- yield key
143
- }
144
- }
145
- }
146
-
147
- findPrivateKey({
148
- kid,
149
- alg,
150
- usage,
151
- ...options
152
- }: FindKeyOptions & { usage: PrivateKeyUsage }): {
153
- key: Key
154
- alg: string
155
- } {
156
- const matchingKeys: Key[] = []
157
-
158
- // Allow the loop bellow to return early when a single "alg" is provided
159
- if (Array.isArray(alg) && alg.length === 1) alg = alg[0]
160
-
161
- for (const key of this.list({ ...options, kid, alg, usage })) {
162
- // Skip negotiation if a single "alg" was provided
163
- if (typeof alg === 'string') return { key, alg }
164
-
165
- matchingKeys.push(key)
166
- }
167
-
168
- const isAllowedAlg = matchesAny(alg)
169
- const candidates = matchingKeys.map(
170
- (key) => [key, key.algorithms.filter(isAllowedAlg)] as const,
171
- )
172
-
173
- // Return the first candidates that matches the preferred algorithms
174
- for (const prefAlg of this.preferredSigningAlgorithms) {
175
- for (const [matchingKey, matchingAlgs] of candidates) {
176
- if (matchingAlgs.includes(prefAlg)) {
177
- return { key: matchingKey, alg: prefAlg }
178
- }
179
- }
180
- }
181
-
182
- // Return any candidate
183
- for (const [matchingKey, matchingAlgs] of candidates) {
184
- for (const alg of matchingAlgs) {
185
- return { key: matchingKey, alg }
186
- }
187
- }
188
-
189
- throw new JwkError(
190
- `No private key found for ${kid || alg || usage}`,
191
- ERR_JWK_NOT_FOUND,
192
- )
193
- }
194
-
195
- [Symbol.iterator](): IterableIterator<K> {
196
- return this.keys.values()
197
- }
198
-
199
- async createJwt(
200
- { alg: sAlg, kid: sKid, ...header }: JwtSignHeader,
201
- payload: JwtPayload | JwtPayloadGetter,
202
- ): Promise<SignedJwt> {
203
- try {
204
- const { key, alg } = this.findPrivateKey({
205
- alg: sAlg,
206
- kid: sKid,
207
- usage: 'sign',
208
- allowRevoked: false, // For explicitness (default value is false)
209
- })
210
- const protectedHeader = { ...header, alg, kid: key.kid }
211
-
212
- if (typeof payload === 'function') {
213
- payload = await payload(protectedHeader, key)
214
- }
215
-
216
- return await key.createJwt(protectedHeader, payload)
217
- } catch (err) {
218
- throw JwtCreateError.from(err)
219
- }
220
- }
221
-
222
- async verifyJwt<C extends string = never>(
223
- token: SignedJwt,
224
- options?: ActivityCheckOptions & VerifyOptions<C>,
225
- ): Promise<VerifyResult<C> & { key: K }> {
226
- const { header } = unsafeDecodeJwt(token)
227
- const { kid, alg } = header
228
-
229
- const errors: unknown[] = []
230
-
231
- for (const key of this.list({ ...options, kid, alg, usage: 'verify' })) {
232
- try {
233
- const result = await key.verifyJwt<C>(token, options)
234
- return { ...result, key }
235
- } catch (err) {
236
- errors.push(err)
237
- }
238
- }
239
-
240
- switch (errors.length) {
241
- case 0:
242
- throw new JwtVerifyError('No key matched', ERR_JWKS_NO_MATCHING_KEY)
243
- case 1:
244
- throw JwtVerifyError.from(errors[0], ERR_JWT_INVALID)
245
- default:
246
- throw JwtVerifyError.from(errors, ERR_JWT_INVALID)
247
- }
248
- }
249
-
250
- toJSON() {
251
- // Make a copy to allow mutation of the result
252
- return structuredClone(this.publicJwks) as JwksPub
253
- }
254
- }