@atproto/jwk 0.5.0 → 0.7.0-next.0
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/CHANGELOG.md +30 -0
- package/LICENSE.txt +1 -1
- package/dist/alg.d.ts +2 -2
- package/dist/alg.d.ts.map +1 -1
- package/dist/alg.js +19 -16
- package/dist/alg.js.map +1 -1
- package/dist/errors.js +15 -36
- package/dist/errors.js.map +1 -1
- package/dist/index.js +10 -28
- package/dist/index.js.map +1 -1
- package/dist/jwk.d.ts +3725 -1143
- package/dist/jwk.d.ts.map +1 -1
- package/dist/jwk.js +178 -96
- package/dist/jwk.js.map +1 -1
- package/dist/jwks.d.ts +212 -1523
- package/dist/jwks.d.ts.map +1 -1
- package/dist/jwks.js +25 -11
- package/dist/jwks.js.map +1 -1
- package/dist/jwt-decode.js +8 -11
- package/dist/jwt-decode.js.map +1 -1
- package/dist/jwt-verify.js +1 -2
- package/dist/jwt-verify.js.map +1 -1
- package/dist/jwt.d.ts +3937 -1186
- package/dist/jwt.d.ts.map +1 -1
- package/dist/jwt.js +97 -102
- package/dist/jwt.js.map +1 -1
- package/dist/key.d.ts +22 -9
- package/dist/key.d.ts.map +1 -1
- package/dist/key.js +159 -88
- package/dist/key.js.map +1 -1
- package/dist/keyset.d.ts +382 -15
- package/dist/keyset.d.ts.map +1 -1
- package/dist/keyset.js +153 -183
- package/dist/keyset.js.map +1 -1
- package/dist/util.d.ts +1 -6
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +21 -26
- package/dist/util.js.map +1 -1
- package/package.json +8 -7
- package/src/alg.ts +22 -10
- package/src/jwk.ts +163 -51
- package/src/jwks.ts +23 -6
- package/src/key.ts +137 -27
- package/src/keyset.ts +60 -60
- package/src/util.ts +8 -19
- package/tsconfig.build.tsbuildinfo +1 -1
package/src/keyset.ts
CHANGED
|
@@ -6,37 +6,35 @@ import {
|
|
|
6
6
|
JwtCreateError,
|
|
7
7
|
JwtVerifyError,
|
|
8
8
|
} from './errors.js'
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { PrivateKeyUsage } from './jwk.js'
|
|
10
|
+
import { JwksPub } from './jwks.js'
|
|
11
11
|
import { unsafeDecodeJwt } from './jwt-decode.js'
|
|
12
12
|
import { VerifyOptions, VerifyResult } from './jwt-verify.js'
|
|
13
13
|
import { JwtHeader, JwtPayload, SignedJwt } from './jwt.js'
|
|
14
|
-
import { Key } from './key.js'
|
|
14
|
+
import { ActivityCheckOptions, Key, KeyMatchOptions } from './key.js'
|
|
15
15
|
import {
|
|
16
|
-
DeepReadonly,
|
|
17
16
|
Override,
|
|
18
|
-
UnReadonly,
|
|
19
17
|
cachedGetter,
|
|
20
18
|
isDefined,
|
|
21
19
|
matchesAny,
|
|
22
20
|
preferredOrderCmp,
|
|
23
21
|
} from './util.js'
|
|
24
22
|
|
|
25
|
-
export type
|
|
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
|
+
>
|
|
26
30
|
|
|
27
31
|
export type JwtPayloadGetter<P = JwtPayload> = (
|
|
28
32
|
header: JwtHeader,
|
|
29
33
|
key: Key,
|
|
30
34
|
) => P | PromiseLike<P>
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
kid?: string | string[]
|
|
35
|
-
alg?: string | string[]
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const extractPrivateJwk = (key: Key): Jwk | undefined => key.privateJwk
|
|
39
|
-
const extractPublicJwk = (key: Key): Jwk | undefined => key.publicJwk
|
|
36
|
+
const extractPrivateJwk = (key: Key) => key.privateJwk
|
|
37
|
+
const extractPublicJwk = (key: Key) => key.publicJwk
|
|
40
38
|
|
|
41
39
|
export class Keyset<K extends Key = Key> implements Iterable<K> {
|
|
42
40
|
private readonly keys: readonly K[]
|
|
@@ -67,15 +65,15 @@ export class Keyset<K extends Key = Key> implements Iterable<K> {
|
|
|
67
65
|
) {
|
|
68
66
|
const keys: K[] = []
|
|
69
67
|
|
|
70
|
-
const
|
|
68
|
+
const keyIds = new Set<string>()
|
|
71
69
|
for (const key of iterable) {
|
|
72
70
|
if (!key) continue
|
|
73
71
|
|
|
74
72
|
keys.push(key)
|
|
75
73
|
|
|
76
74
|
if (key.kid) {
|
|
77
|
-
if (
|
|
78
|
-
else
|
|
75
|
+
if (keyIds.has(key.kid)) throw new JwkError(`Duplicate key: ${key.kid}`)
|
|
76
|
+
else keyIds.add(key.kid)
|
|
79
77
|
}
|
|
80
78
|
}
|
|
81
79
|
|
|
@@ -101,66 +99,67 @@ export class Keyset<K extends Key = Key> implements Iterable<K> {
|
|
|
101
99
|
}
|
|
102
100
|
|
|
103
101
|
@cachedGetter
|
|
104
|
-
get publicJwks()
|
|
105
|
-
return {
|
|
106
|
-
keys: Array.from(this, extractPublicJwk).filter(isDefined),
|
|
107
|
-
}
|
|
102
|
+
get publicJwks() {
|
|
103
|
+
return Object.freeze({
|
|
104
|
+
keys: Object.freeze(Array.from(this, extractPublicJwk).filter(isDefined)),
|
|
105
|
+
})
|
|
108
106
|
}
|
|
109
107
|
|
|
110
108
|
@cachedGetter
|
|
111
|
-
get privateJwks()
|
|
112
|
-
return {
|
|
113
|
-
keys:
|
|
114
|
-
|
|
109
|
+
get privateJwks() {
|
|
110
|
+
return Object.freeze({
|
|
111
|
+
keys: Object.freeze(
|
|
112
|
+
Array.from(this, extractPrivateJwk).filter(isDefined),
|
|
113
|
+
),
|
|
114
|
+
})
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
has(kid: string): boolean {
|
|
118
118
|
return this.keys.some((key) => key.kid === kid)
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
get(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
121
|
+
get(options: FindKeyOptions): K {
|
|
122
|
+
const key = this.find(options)
|
|
123
|
+
if (key) return key
|
|
125
124
|
|
|
126
125
|
throw new JwkError(
|
|
127
|
-
`Key not found ${
|
|
126
|
+
`Key not found ${options.kid ?? options.alg ?? options.usage ?? '<unknown>'}`,
|
|
128
127
|
ERR_JWK_NOT_FOUND,
|
|
129
128
|
)
|
|
130
129
|
}
|
|
131
130
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
for (const key of this) {
|
|
138
|
-
if (search.use && key.use !== search.use) continue
|
|
131
|
+
find(options: FindKeyOptions): K | undefined {
|
|
132
|
+
for (const key of this.list(options)) {
|
|
133
|
+
return key
|
|
134
|
+
}
|
|
139
135
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
} else if (search.kid) {
|
|
143
|
-
if (key.kid !== search.kid) continue
|
|
144
|
-
}
|
|
136
|
+
return undefined
|
|
137
|
+
}
|
|
145
138
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
139
|
+
*list<O extends FindKeyOptions>(options: O) {
|
|
140
|
+
for (const key of this) {
|
|
141
|
+
if (key.isActive(options) && key.matches(options)) {
|
|
142
|
+
yield key
|
|
150
143
|
}
|
|
151
|
-
|
|
152
|
-
yield key
|
|
153
144
|
}
|
|
154
145
|
}
|
|
155
146
|
|
|
156
|
-
findPrivateKey({
|
|
147
|
+
findPrivateKey({
|
|
148
|
+
kid,
|
|
149
|
+
alg,
|
|
150
|
+
usage,
|
|
151
|
+
...options
|
|
152
|
+
}: FindKeyOptions & { usage: PrivateKeyUsage }): {
|
|
153
|
+
key: Key
|
|
154
|
+
alg: string
|
|
155
|
+
} {
|
|
157
156
|
const matchingKeys: Key[] = []
|
|
158
157
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (!key.isPrivate) continue
|
|
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]
|
|
162
160
|
|
|
163
|
-
|
|
161
|
+
for (const key of this.list({ ...options, kid, alg, usage })) {
|
|
162
|
+
// Skip negotiation if a single "alg" was provided
|
|
164
163
|
if (typeof alg === 'string') return { key, alg }
|
|
165
164
|
|
|
166
165
|
matchingKeys.push(key)
|
|
@@ -188,7 +187,7 @@ export class Keyset<K extends Key = Key> implements Iterable<K> {
|
|
|
188
187
|
}
|
|
189
188
|
|
|
190
189
|
throw new JwkError(
|
|
191
|
-
`No private key found for ${kid || alg ||
|
|
190
|
+
`No private key found for ${kid || alg || usage}`,
|
|
192
191
|
ERR_JWK_NOT_FOUND,
|
|
193
192
|
)
|
|
194
193
|
}
|
|
@@ -205,7 +204,8 @@ export class Keyset<K extends Key = Key> implements Iterable<K> {
|
|
|
205
204
|
const { key, alg } = this.findPrivateKey({
|
|
206
205
|
alg: sAlg,
|
|
207
206
|
kid: sKid,
|
|
208
|
-
|
|
207
|
+
usage: 'sign',
|
|
208
|
+
allowRevoked: false, // For explicitness (default value is false)
|
|
209
209
|
})
|
|
210
210
|
const protectedHeader = { ...header, alg, kid: key.kid }
|
|
211
211
|
|
|
@@ -221,14 +221,14 @@ export class Keyset<K extends Key = Key> implements Iterable<K> {
|
|
|
221
221
|
|
|
222
222
|
async verifyJwt<C extends string = never>(
|
|
223
223
|
token: SignedJwt,
|
|
224
|
-
options?: VerifyOptions<C>,
|
|
224
|
+
options?: ActivityCheckOptions & VerifyOptions<C>,
|
|
225
225
|
): Promise<VerifyResult<C> & { key: K }> {
|
|
226
226
|
const { header } = unsafeDecodeJwt(token)
|
|
227
227
|
const { kid, alg } = header
|
|
228
228
|
|
|
229
229
|
const errors: unknown[] = []
|
|
230
230
|
|
|
231
|
-
for (const key of this.list({ kid, alg })) {
|
|
231
|
+
for (const key of this.list({ ...options, kid, alg, usage: 'verify' })) {
|
|
232
232
|
try {
|
|
233
233
|
const result = await key.verifyJwt<C>(token, options)
|
|
234
234
|
return { ...result, key }
|
|
@@ -247,8 +247,8 @@ export class Keyset<K extends Key = Key> implements Iterable<K> {
|
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
toJSON()
|
|
251
|
-
// Make a copy to
|
|
252
|
-
return structuredClone(this.publicJwks) as
|
|
250
|
+
toJSON() {
|
|
251
|
+
// Make a copy to allow mutation of the result
|
|
252
|
+
return structuredClone(this.publicJwks) as JwksPub
|
|
253
253
|
}
|
|
254
254
|
}
|
package/src/util.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { base64url } from 'multiformats/bases/base64'
|
|
2
2
|
import { RefinementCtx, ZodIssueCode } from 'zod'
|
|
3
3
|
|
|
4
|
-
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
5
4
|
export type Simplify<T> = { [K in keyof T]: T[K] } & {}
|
|
6
5
|
export type Override<T, V> = Simplify<V & Omit<T, keyof V>>
|
|
7
6
|
|
|
@@ -13,24 +12,6 @@ export type RequiredKey<T, K extends keyof T = never> = Simplify<
|
|
|
13
12
|
}
|
|
14
13
|
>
|
|
15
14
|
|
|
16
|
-
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
17
|
-
export type DeepReadonly<T> = T extends Function
|
|
18
|
-
? T
|
|
19
|
-
: T extends object
|
|
20
|
-
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
|
|
21
|
-
: T extends readonly (infer U)[]
|
|
22
|
-
? readonly DeepReadonly<U>[]
|
|
23
|
-
: T
|
|
24
|
-
|
|
25
|
-
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
26
|
-
export type UnReadonly<T> = T extends Function
|
|
27
|
-
? T
|
|
28
|
-
: T extends object
|
|
29
|
-
? { -readonly [K in keyof T]: UnReadonly<T[K]> }
|
|
30
|
-
: T extends readonly (infer U)[]
|
|
31
|
-
? UnReadonly<U>[]
|
|
32
|
-
: T
|
|
33
|
-
|
|
34
15
|
export const isDefined = <T>(i: T | undefined): i is T => i !== undefined
|
|
35
16
|
|
|
36
17
|
export const preferredOrderCmp =
|
|
@@ -44,6 +25,7 @@ export const preferredOrderCmp =
|
|
|
44
25
|
return aIdx - bIdx
|
|
45
26
|
}
|
|
46
27
|
|
|
28
|
+
/* eslint-disable @typescript-eslint/no-unused-vars -- `v` is used at runtime in the returned type guards; v8 false-positive */
|
|
47
29
|
export function matchesAny<T extends string | number | symbol | boolean>(
|
|
48
30
|
value: null | undefined | T | readonly T[],
|
|
49
31
|
): (v: unknown) => v is T {
|
|
@@ -53,6 +35,7 @@ export function matchesAny<T extends string | number | symbol | boolean>(
|
|
|
53
35
|
? (v): v is T => value.includes(v)
|
|
54
36
|
: (v): v is T => v === value
|
|
55
37
|
}
|
|
38
|
+
/* eslint-enable @typescript-eslint/no-unused-vars */
|
|
56
39
|
|
|
57
40
|
/**
|
|
58
41
|
* Decorator to cache the result of a getter on a class instance.
|
|
@@ -197,3 +180,9 @@ export const segmentedStringRefinementFactory = <C extends number>(
|
|
|
197
180
|
return true
|
|
198
181
|
}
|
|
199
182
|
}
|
|
183
|
+
|
|
184
|
+
export function isLastOccurrence<
|
|
185
|
+
T extends number | boolean | string | null | undefined | symbol | bigint,
|
|
186
|
+
>(v: T, i: number, arr: readonly T[]): boolean {
|
|
187
|
+
return arr.indexOf(v, i + 1) === -1
|
|
188
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/alg.ts","./src/errors.ts","./src/index.ts","./src/jwk.ts","./src/jwks.ts","./src/jwt-decode.ts","./src/jwt-verify.ts","./src/jwt.ts","./src/key.ts","./src/keyset.ts","./src/util.ts"],"version":"
|
|
1
|
+
{"root":["./src/alg.ts","./src/errors.ts","./src/index.ts","./src/jwk.ts","./src/jwks.ts","./src/jwt-decode.ts","./src/jwt-verify.ts","./src/jwt.ts","./src/key.ts","./src/keyset.ts","./src/util.ts"],"version":"6.0.3"}
|