deno.land / x / jose@v5.2.4 / jwks / remote.ts

نووسراو ببینە
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
import fetchJwks from '../runtime/fetch_jwks.ts'
import type { KeyLike, JWSHeaderParameters, FlattenedJWSInput, JSONWebKeySet } from '../types.d.ts'import { JWKSNoMatchingKey } from '../util/errors.ts'
import { createLocalJWKSet } from './local.ts'
function isCloudflareWorkers() { return ( // @ts-ignore typeof WebSocketPair !== 'undefined' || // @ts-ignore (typeof navigator !== 'undefined' && navigator.userAgent === 'Cloudflare-Workers') || // @ts-ignore (typeof EdgeRuntime !== 'undefined' && EdgeRuntime === 'vercel') )}
// An explicit user-agent in browser environment is a trigger for CORS preflight requests which// are not needed for our request, so we're omitting setting a default user-agent in browser// environments.let USER_AGENT: string// @ts-ignoreif (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) { const NAME = 'jose' const VERSION = 'v5.2.4' USER_AGENT = `${NAME}/${VERSION}`}
/** Options for the remote JSON Web Key Set. */export interface RemoteJWKSetOptions { /** * Timeout (in milliseconds) for the HTTP request. When reached the request will be aborted and * the verification will fail. Default is 5000 (5 seconds). */ timeoutDuration?: number
/** * Duration (in milliseconds) for which no more HTTP requests will be triggered after a previous * successful fetch. Default is 30000 (30 seconds). */ cooldownDuration?: number
/** * Maximum time (in milliseconds) between successful HTTP requests. Default is 600000 (10 * minutes). */ cacheMaxAge?: number | typeof Infinity
/** * An instance of {@link https://nodejs.org/api/http.html#class-httpagent http.Agent} or * {@link https://nodejs.org/api/https.html#class-httpsagent https.Agent} to pass to the * {@link https://nodejs.org/api/http.html#httpgetoptions-callback http.get} or * {@link https://nodejs.org/api/https.html#httpsgetoptions-callback https.get} method's options. * Use when behind an http(s) proxy. This is a Node.js runtime specific option, it is ignored when * used outside of Node.js runtime. */ agent?: any
/** * Headers to be sent with the HTTP request. Default is that `User-Agent: jose/v${version}` header * is added unless the runtime is a browser in which adding an explicit headers fetch * configuration would cause an unnecessary CORS preflight request. */ headers?: Record<string, string>}
class RemoteJWKSet<KeyLikeType extends KeyLike = KeyLike> { private _url: URL
private _timeoutDuration: number
private _cooldownDuration: number
private _cacheMaxAge: number
private _jwksTimestamp?: number
private _pendingFetch?: Promise<unknown>
private _options: Pick<RemoteJWKSetOptions, 'agent' | 'headers'>
private _local!: ReturnType<typeof createLocalJWKSet<KeyLikeType>>
constructor(url: unknown, options?: RemoteJWKSetOptions) { if (!(url instanceof URL)) { throw new TypeError('url must be an instance of URL') } this._url = new URL(url.href) this._options = { agent: options?.agent, headers: options?.headers } this._timeoutDuration = typeof options?.timeoutDuration === 'number' ? options?.timeoutDuration : 5000 this._cooldownDuration = typeof options?.cooldownDuration === 'number' ? options?.cooldownDuration : 30000 this._cacheMaxAge = typeof options?.cacheMaxAge === 'number' ? options?.cacheMaxAge : 600000 }
coolingDown() { return typeof this._jwksTimestamp === 'number' ? Date.now() < this._jwksTimestamp + this._cooldownDuration : false }
fresh() { return typeof this._jwksTimestamp === 'number' ? Date.now() < this._jwksTimestamp + this._cacheMaxAge : false }
async getKey( protectedHeader?: JWSHeaderParameters, token?: FlattenedJWSInput, ): Promise<KeyLikeType> { if (!this._local || !this.fresh()) { await this.reload() }
try { return await this._local(protectedHeader, token) } catch (err) { if (err instanceof JWKSNoMatchingKey) { if (this.coolingDown() === false) { await this.reload() return this._local(protectedHeader, token) } } throw err } }
async reload() { // Do not assume a fetch created in another request reliably resolves // see https://github.com/panva/jose/issues/355 and https://github.com/panva/jose/issues/509 if (this._pendingFetch && isCloudflareWorkers()) { this._pendingFetch = undefined }
const headers = new Headers(this._options.headers) if (USER_AGENT && !headers.has('User-Agent')) { headers.set('User-Agent', USER_AGENT) this._options.headers = Object.fromEntries(headers.entries()) }
this._pendingFetch ||= fetchJwks(this._url, this._timeoutDuration, this._options) .then((json) => { this._local = createLocalJWKSet(<JSONWebKeySet>(<unknown>json)) this._jwksTimestamp = Date.now() this._pendingFetch = undefined }) .catch((err: Error) => { this._pendingFetch = undefined throw err })
await this._pendingFetch }}
/** * Returns a function that resolves a JWS JOSE Header to a public key object downloaded from a * remote endpoint returning a JSON Web Key Set, that is, for example, an OAuth 2.0 or OIDC * jwks_uri. The JSON Web Key Set is fetched when no key matches the selection process but only as * frequently as the `cooldownDuration` option allows to prevent abuse. * * It uses the "alg" (JWS Algorithm) Header Parameter to determine the right JWK "kty" (Key Type), * then proceeds to match the JWK "kid" (Key ID) with one found in the JWS Header Parameters (if * there is one) while also respecting the JWK "use" (Public Key Use) and JWK "key_ops" (Key * Operations) Parameters (if they are present on the JWK). * * Only a single public key must match the selection process. As shown in the example below when * multiple keys get matched it is possible to opt-in to iterate over the matched keys and attempt * verification in an iterative manner. * * Note: The function's purpose is to resolve public keys used for verifying signatures and will not * work for public encryption keys. * * @param url URL to fetch the JSON Web Key Set from. * @param options Options for the remote JSON Web Key Set. */export function createRemoteJWKSet<KeyLikeType extends KeyLike = KeyLike>( url: URL, options?: RemoteJWKSetOptions,) { const set = new RemoteJWKSet<KeyLikeType>(url, options) return async ( protectedHeader?: JWSHeaderParameters, token?: FlattenedJWSInput, ): Promise<KeyLikeType> => set.getKey(protectedHeader, token)}
jose

Version Info

Tagged at
a month ago