oauth4webapi 2.4.4 → 2.5.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/README.md +1 -2
- package/build/index.d.ts +178 -15
- package/build/index.js +70 -60
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ import * as oauth2 from 'oauth4webapi'
|
|
|
43
43
|
**`example`** Deno import
|
|
44
44
|
|
|
45
45
|
```js
|
|
46
|
-
import * as oauth2 from 'https://deno.land/x/oauth4webapi@v2.
|
|
46
|
+
import * as oauth2 from 'https://deno.land/x/oauth4webapi@v2.5.0/mod.ts'
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
- Authorization Code Flow - OpenID Connect [source](examples/code.ts), or plain OAuth 2 [source](examples/oauth.ts)
|
|
@@ -74,6 +74,5 @@ The following features are currently out of scope:
|
|
|
74
74
|
|
|
75
75
|
- CommonJS
|
|
76
76
|
- Implicit, Hybrid, and Resource Owner Password Credentials Flows
|
|
77
|
-
- Mutual-TLS Client Authentication and Certificate-Bound Access Tokens
|
|
78
77
|
- JSON Web Encryption (JWE)
|
|
79
78
|
- Automatic polyfills of any kind
|
package/build/index.d.ts
CHANGED
|
@@ -127,10 +127,160 @@ export type ClientAuthenticationMethod = 'client_secret_basic' | 'client_secret_
|
|
|
127
127
|
* ```
|
|
128
128
|
*/
|
|
129
129
|
export type JWSAlgorithm = 'PS256' | 'ES256' | 'RS256' | 'EdDSA' | 'ES384' | 'PS384' | 'RS384' | 'ES512' | 'PS512' | 'RS512';
|
|
130
|
-
/** @ignore during Documentation generation but part of the public API */
|
|
131
130
|
export declare const clockSkew: unique symbol;
|
|
132
|
-
/** @ignore during Documentation generation but part of the public API */
|
|
133
131
|
export declare const clockTolerance: unique symbol;
|
|
132
|
+
/**
|
|
133
|
+
* This is an experimental feature, it is not subject to semantic versioning rules. Non-backward
|
|
134
|
+
* compatible changes or removal may occur in any future release.
|
|
135
|
+
*
|
|
136
|
+
* When configured on an interface that extends {@link HttpRequestOptions}, that's every `options`
|
|
137
|
+
* parameter for functions that trigger HTTP Requests, this replaces the use of global fetch. As a
|
|
138
|
+
* fetch replacement the arguments and expected return are the same as fetch.
|
|
139
|
+
*
|
|
140
|
+
* In theory any module that claims to be compatible with the Fetch API can be used but your mileage
|
|
141
|
+
* may vary. No workarounds to allow use of non-conform {@link Response}s will be considered.
|
|
142
|
+
*
|
|
143
|
+
* If you only need to update the {@link Request} properties you do not need to use a Fetch API
|
|
144
|
+
* module, just change what you need and pass it to globalThis.fetch just like this module would
|
|
145
|
+
* normally do.
|
|
146
|
+
*
|
|
147
|
+
* Its intended use cases are:
|
|
148
|
+
*
|
|
149
|
+
* - {@link Request}/{@link Response} tracing and logging
|
|
150
|
+
* - Custom caching strategies for responses of Authorization Server Metadata and JSON Web Key Set
|
|
151
|
+
* (JWKS) endpoints
|
|
152
|
+
* - Changing the {@link Request} properties like headers, body, credentials, mode before it is passed
|
|
153
|
+
* to fetch
|
|
154
|
+
*
|
|
155
|
+
* Known caveats:
|
|
156
|
+
*
|
|
157
|
+
* - Expect Type-related issues when passing the inputs through to fetch-like modules, they hardly
|
|
158
|
+
* ever get their typings inline with actual fetch, you should `@ts-expect-error` them.
|
|
159
|
+
* - Returning self-constructed {@link Response} instances prohibits AS-signalled DPoP Nonce caching.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
*
|
|
163
|
+
* Using [sindresorhus/ky](https://github.com/sindresorhus/ky) hooks feature for logging outgoing
|
|
164
|
+
* requests and their responses.
|
|
165
|
+
*
|
|
166
|
+
* ```js
|
|
167
|
+
* import ky from 'ky'
|
|
168
|
+
* import * as oauth from 'oauth4webapi'
|
|
169
|
+
*
|
|
170
|
+
* // example use
|
|
171
|
+
* await oauth.discoveryRequest(new URL('https://as.example.com'), {
|
|
172
|
+
* [oauth.experimentalCustomFetch]: (...args) =>
|
|
173
|
+
* ky(args[0], {
|
|
174
|
+
* ...args[1],
|
|
175
|
+
* hooks: {
|
|
176
|
+
* beforeRequest: [
|
|
177
|
+
* (request) => {
|
|
178
|
+
* logRequest(request)
|
|
179
|
+
* },
|
|
180
|
+
* ],
|
|
181
|
+
* beforeRetry: [
|
|
182
|
+
* ({ request, error, retryCount }) => {
|
|
183
|
+
* logRetry(request, error, retryCount)
|
|
184
|
+
* },
|
|
185
|
+
* ],
|
|
186
|
+
* afterResponse: [
|
|
187
|
+
* (request, _, response) => {
|
|
188
|
+
* logResponse(request, response)
|
|
189
|
+
* },
|
|
190
|
+
* ],
|
|
191
|
+
* },
|
|
192
|
+
* }),
|
|
193
|
+
* })
|
|
194
|
+
* ```
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
*
|
|
198
|
+
* Using [nodejs/undici](https://github.com/nodejs/undici) for mocking.
|
|
199
|
+
*
|
|
200
|
+
* ```js
|
|
201
|
+
* import * as undici from 'undici'
|
|
202
|
+
* import * as oauth from 'oauth4webapi'
|
|
203
|
+
*
|
|
204
|
+
* const mockAgent = new undici.MockAgent()
|
|
205
|
+
* mockAgent.disableNetConnect()
|
|
206
|
+
* undici.setGlobalDispatcher(mockAgent)
|
|
207
|
+
*
|
|
208
|
+
* // continue as per undici documentation
|
|
209
|
+
* // https://github.com/nodejs/undici/blob/v6.2.1/docs/api/MockAgent.md#example---basic-mocked-request
|
|
210
|
+
*
|
|
211
|
+
* // example use
|
|
212
|
+
* await oauth.discoveryRequest(new URL('https://as.example.com'), {
|
|
213
|
+
* [oauth.experimentalCustomFetch]: undici.fetch,
|
|
214
|
+
* })
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
export declare const experimentalCustomFetch: unique symbol;
|
|
218
|
+
/**
|
|
219
|
+
* This is an experimental feature, it is not subject to semantic versioning rules. Non-backward
|
|
220
|
+
* compatible changes or removal may occur in any future release.
|
|
221
|
+
*
|
|
222
|
+
* When combined with {@link experimentalCustomFetch} (to use a Fetch API implementation that
|
|
223
|
+
* supports client certificates) this can be used to target FAPI 2.0 profiles that utilize
|
|
224
|
+
* Mutual-TLS for either client authentication or sender constraining. FAPI 1.0 Advanced profiles
|
|
225
|
+
* that use PAR and JARM can also be targetted.
|
|
226
|
+
*
|
|
227
|
+
* When configured on an interface that extends {@link ExperimentalUseMTLSAliasOptions} this makes
|
|
228
|
+
* the client prioritize an endpoint URL present in
|
|
229
|
+
* {@link AuthorizationServer.mtls_endpoint_aliases `as.mtls_endpoint_aliases`}.
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
*
|
|
233
|
+
* (Node.js) Using [nodejs/undici](https://github.com/nodejs/undici) for Mutual-TLS Client
|
|
234
|
+
* Authentication and Certificate-Bound Access Tokens support.
|
|
235
|
+
*
|
|
236
|
+
* ```js
|
|
237
|
+
* import * as undici from 'undici'
|
|
238
|
+
* import * as oauth from 'oauth4webapi'
|
|
239
|
+
*
|
|
240
|
+
* const response = await oauth.pushedAuthorizationRequest(as, client, params, {
|
|
241
|
+
* [oauth.experimentalUseMtlsAlias]: true,
|
|
242
|
+
* [oauth.experimentalCustomFetch]: (...args) => {
|
|
243
|
+
* return undici.fetch(args[0], {
|
|
244
|
+
* ...args[1],
|
|
245
|
+
* dispatcher: new undici.Agent({
|
|
246
|
+
* connect: {
|
|
247
|
+
* key: clientKey,
|
|
248
|
+
* cert: clientCertificate,
|
|
249
|
+
* },
|
|
250
|
+
* }),
|
|
251
|
+
* })
|
|
252
|
+
* },
|
|
253
|
+
* })
|
|
254
|
+
* ```
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
*
|
|
258
|
+
* (Deno) Using Deno.createHttpClient API for Mutual-TLS Client Authentication and Certificate-Bound
|
|
259
|
+
* Access Tokens support. This is currently (Jan 2023) locked behind the --unstable command line
|
|
260
|
+
* flag.
|
|
261
|
+
*
|
|
262
|
+
* ```js
|
|
263
|
+
* import * as oauth from 'oauth4webapi'
|
|
264
|
+
*
|
|
265
|
+
* const agent = Deno.createHttpClient({
|
|
266
|
+
* certChain: clientCertificate,
|
|
267
|
+
* privateKey: clientKey,
|
|
268
|
+
* })
|
|
269
|
+
*
|
|
270
|
+
* const response = await oauth.pushedAuthorizationRequest(as, client, params, {
|
|
271
|
+
* [oauth.experimentalUseMtlsAlias]: true,
|
|
272
|
+
* [oauth.experimentalCustomFetch]: (...args) => {
|
|
273
|
+
* return fetch(args[0], {
|
|
274
|
+
* ...args[1],
|
|
275
|
+
* client: agent,
|
|
276
|
+
* })
|
|
277
|
+
* },
|
|
278
|
+
* })
|
|
279
|
+
* ```
|
|
280
|
+
*
|
|
281
|
+
* @see [RFC 8705 - OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens](https://www.rfc-editor.org/rfc/rfc8705.html)
|
|
282
|
+
*/
|
|
283
|
+
export declare const experimentalUseMtlsAlias: unique symbol;
|
|
134
284
|
/**
|
|
135
285
|
* Authorization Server Metadata
|
|
136
286
|
*
|
|
@@ -456,9 +606,9 @@ export interface Client {
|
|
|
456
606
|
* Use to adjust the client's assumed current time. Positive and negative finite values
|
|
457
607
|
* representing seconds are allowed. Default is `0` (Date.now() + 0 seconds is used).
|
|
458
608
|
*
|
|
459
|
-
* @
|
|
609
|
+
* @example
|
|
460
610
|
*
|
|
461
|
-
*
|
|
611
|
+
* When the client's local clock is mistakenly 1 hour in the past
|
|
462
612
|
*
|
|
463
613
|
* ```ts
|
|
464
614
|
* const client: oauth.Client = {
|
|
@@ -468,7 +618,9 @@ export interface Client {
|
|
|
468
618
|
* }
|
|
469
619
|
* ```
|
|
470
620
|
*
|
|
471
|
-
* @example
|
|
621
|
+
* @example
|
|
622
|
+
*
|
|
623
|
+
* When the client's local clock is mistakenly 1 hour in the future
|
|
472
624
|
*
|
|
473
625
|
* ```ts
|
|
474
626
|
* const client: oauth.Client = {
|
|
@@ -483,9 +635,9 @@ export interface Client {
|
|
|
483
635
|
* Use to set allowed client's clock tolerance when checking DateTime JWT Claims. Only positive
|
|
484
636
|
* finite values representing seconds are allowed. Default is `30` (30 seconds).
|
|
485
637
|
*
|
|
486
|
-
* @
|
|
638
|
+
* @example
|
|
487
639
|
*
|
|
488
|
-
*
|
|
640
|
+
* Tolerate 30 seconds clock skew when validating JWT claims like exp or nbf.
|
|
489
641
|
*
|
|
490
642
|
* ```ts
|
|
491
643
|
* const client: oauth.Client = {
|
|
@@ -522,11 +674,15 @@ export interface HttpRequestOptions {
|
|
|
522
674
|
* ```
|
|
523
675
|
*/
|
|
524
676
|
signal?: (() => AbortSignal) | AbortSignal;
|
|
677
|
+
/** Headers to additionally send with the HTTP Request(s) triggered by this function's invocation. */
|
|
678
|
+
headers?: [string, string][] | Record<string, string> | Headers;
|
|
525
679
|
/**
|
|
526
|
-
*
|
|
527
|
-
*
|
|
680
|
+
* This is an experimental feature, it is not subject to semantic versioning rules. Non-backward
|
|
681
|
+
* compatible changes or removal may occur in any future release.
|
|
682
|
+
*
|
|
683
|
+
* See {@link experimentalCustomFetch} for its documentation.
|
|
528
684
|
*/
|
|
529
|
-
|
|
685
|
+
[experimentalCustomFetch]?: typeof fetch;
|
|
530
686
|
}
|
|
531
687
|
export interface DiscoveryRequestOptions extends HttpRequestOptions {
|
|
532
688
|
/** The issuer transformation algorithm to use. */
|
|
@@ -625,7 +781,16 @@ export interface DPoPRequestOptions {
|
|
|
625
781
|
/** DPoP-related options. */
|
|
626
782
|
DPoP?: DPoPOptions;
|
|
627
783
|
}
|
|
628
|
-
export interface
|
|
784
|
+
export interface ExperimentalUseMTLSAliasOptions {
|
|
785
|
+
/**
|
|
786
|
+
* This is an experimental feature, it is not subject to semantic versioning rules. Non-backward
|
|
787
|
+
* compatible changes or removal may occur in any future release.
|
|
788
|
+
*
|
|
789
|
+
* See {@link experimentalUseMtlsAlias} for its documentation.
|
|
790
|
+
*/
|
|
791
|
+
[experimentalUseMtlsAlias]?: boolean;
|
|
792
|
+
}
|
|
793
|
+
export interface AuthenticatedRequestOptions extends ExperimentalUseMTLSAliasOptions {
|
|
629
794
|
/**
|
|
630
795
|
* Private key to use for `private_key_jwt`
|
|
631
796
|
* {@link ClientAuthenticationMethod client authentication}. Its algorithm must be compatible with
|
|
@@ -747,10 +912,8 @@ export interface ProtectedResourceRequestOptions extends Omit<HttpRequestOptions
|
|
|
747
912
|
*
|
|
748
913
|
* This option only affects the request if the {@link ProtectedResourceRequestOptions.DPoP DPoP}
|
|
749
914
|
* option is also used.
|
|
750
|
-
*
|
|
751
|
-
* @ignore during Documentation generation but part of the public API
|
|
752
915
|
*/
|
|
753
|
-
clockSkew?: number;
|
|
916
|
+
[clockSkew]?: number;
|
|
754
917
|
}
|
|
755
918
|
/**
|
|
756
919
|
* Performs a protected resource request at an arbitrary URL.
|
|
@@ -769,7 +932,7 @@ export interface ProtectedResourceRequestOptions extends Omit<HttpRequestOptions
|
|
|
769
932
|
* @see [RFC 9449 - OAuth 2.0 Demonstrating Proof-of-Possession at the Application Layer (DPoP)](https://www.rfc-editor.org/rfc/rfc9449.html#name-protected-resource-access)
|
|
770
933
|
*/
|
|
771
934
|
export declare function protectedResourceRequest(accessToken: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | string, url: URL, headers: Headers, body?: ReadableStream | Blob | ArrayBufferView | ArrayBuffer | FormData | URLSearchParams | string | null, options?: ProtectedResourceRequestOptions): Promise<Response>;
|
|
772
|
-
export interface UserInfoRequestOptions extends HttpRequestOptions, DPoPRequestOptions {
|
|
935
|
+
export interface UserInfoRequestOptions extends HttpRequestOptions, DPoPRequestOptions, ExperimentalUseMTLSAliasOptions {
|
|
773
936
|
}
|
|
774
937
|
/**
|
|
775
938
|
* Performs a UserInfo Request at the
|
package/build/index.js
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
let USER_AGENT;
|
|
2
2
|
if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) {
|
|
3
3
|
const NAME = 'oauth4webapi';
|
|
4
|
-
const VERSION = 'v2.
|
|
4
|
+
const VERSION = 'v2.5.0';
|
|
5
5
|
USER_AGENT = `${NAME}/${VERSION}`;
|
|
6
6
|
}
|
|
7
|
+
function looseInstanceOf(input, expected) {
|
|
8
|
+
if (input == null) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
return (input instanceof expected ||
|
|
13
|
+
Object.getPrototypeOf(input)[Symbol.toStringTag] === expected.prototype[Symbol.toStringTag]);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
7
19
|
export const clockSkew = Symbol();
|
|
8
20
|
export const clockTolerance = Symbol();
|
|
21
|
+
export const experimentalCustomFetch = Symbol();
|
|
22
|
+
export const experimentalUseMtlsAlias = Symbol();
|
|
9
23
|
const encoder = new TextEncoder();
|
|
10
24
|
const decoder = new TextDecoder();
|
|
11
25
|
function buf(input) {
|
|
@@ -148,8 +162,8 @@ function isJsonObject(input) {
|
|
|
148
162
|
return true;
|
|
149
163
|
}
|
|
150
164
|
function prepareHeaders(input) {
|
|
151
|
-
if (
|
|
152
|
-
|
|
165
|
+
if (looseInstanceOf(input, Headers)) {
|
|
166
|
+
input = Object.fromEntries(input.entries());
|
|
153
167
|
}
|
|
154
168
|
const headers = new Headers(input);
|
|
155
169
|
if (USER_AGENT && !headers.has('user-agent')) {
|
|
@@ -198,13 +212,12 @@ export async function discoveryRequest(issuerIdentifier, options) {
|
|
|
198
212
|
}
|
|
199
213
|
const headers = prepareHeaders(options?.headers);
|
|
200
214
|
headers.set('accept', 'application/json');
|
|
201
|
-
|
|
202
|
-
headers,
|
|
215
|
+
return (options?.[experimentalCustomFetch] || fetch)(url.href, {
|
|
216
|
+
headers: Object.fromEntries(headers.entries()),
|
|
203
217
|
method: 'GET',
|
|
204
218
|
redirect: 'manual',
|
|
205
219
|
signal: options?.signal ? signal(options.signal) : null,
|
|
206
|
-
});
|
|
207
|
-
return fetch(request).then(processDpopNonce);
|
|
220
|
+
}).then(processDpopNonce);
|
|
208
221
|
}
|
|
209
222
|
function validateString(input) {
|
|
210
223
|
return typeof input === 'string' && input.length !== 0;
|
|
@@ -213,7 +226,7 @@ export async function processDiscoveryResponse(expectedIssuerIdentifier, respons
|
|
|
213
226
|
if (!(expectedIssuerIdentifier instanceof URL)) {
|
|
214
227
|
throw new TypeError('"expectedIssuer" must be an instance of URL');
|
|
215
228
|
}
|
|
216
|
-
if (!(response
|
|
229
|
+
if (!looseInstanceOf(response, Response)) {
|
|
217
230
|
throw new TypeError('"response" must be an instance of Response');
|
|
218
231
|
}
|
|
219
232
|
if (response.status !== 200) {
|
|
@@ -427,9 +440,11 @@ async function clientAuthentication(as, client, body, headers, clientPrivateKey)
|
|
|
427
440
|
body.set('client_assertion', await privateKeyJwt(as, client, key, kid));
|
|
428
441
|
break;
|
|
429
442
|
}
|
|
443
|
+
case 'tls_client_auth':
|
|
444
|
+
case 'self_signed_tls_client_auth':
|
|
430
445
|
case 'none': {
|
|
431
|
-
assertNoClientSecret(
|
|
432
|
-
assertNoClientPrivateKey(
|
|
446
|
+
assertNoClientSecret(client.token_endpoint_auth_method, client.client_secret);
|
|
447
|
+
assertNoClientPrivateKey(client.token_endpoint_auth_method, clientPrivateKey);
|
|
433
448
|
body.set('client_id', client.client_id);
|
|
434
449
|
break;
|
|
435
450
|
}
|
|
@@ -531,13 +546,29 @@ async function publicJwk(key) {
|
|
|
531
546
|
jwkCache.set(key, jwk);
|
|
532
547
|
return jwk;
|
|
533
548
|
}
|
|
549
|
+
function validateEndpoint(value, endpoint, options) {
|
|
550
|
+
if (typeof value !== 'string') {
|
|
551
|
+
if (options?.[experimentalUseMtlsAlias]) {
|
|
552
|
+
throw new TypeError(`"as.mtls_endpoint_aliases.${endpoint}" must be a string`);
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
throw new TypeError(`"as.${endpoint}" must be a string`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return new URL(value);
|
|
559
|
+
}
|
|
560
|
+
function resolveEndpoint(as, endpoint, options) {
|
|
561
|
+
if (options?.[experimentalUseMtlsAlias] &&
|
|
562
|
+
as.mtls_endpoint_aliases &&
|
|
563
|
+
endpoint in as.mtls_endpoint_aliases) {
|
|
564
|
+
return validateEndpoint(as.mtls_endpoint_aliases[endpoint], endpoint, options);
|
|
565
|
+
}
|
|
566
|
+
return validateEndpoint(as[endpoint], endpoint);
|
|
567
|
+
}
|
|
534
568
|
export async function pushedAuthorizationRequest(as, client, parameters, options) {
|
|
535
569
|
assertAs(as);
|
|
536
570
|
assertClient(client);
|
|
537
|
-
|
|
538
|
-
throw new TypeError('"as.pushed_authorization_request_endpoint" must be a string');
|
|
539
|
-
}
|
|
540
|
-
const url = new URL(as.pushed_authorization_request_endpoint);
|
|
571
|
+
const url = resolveEndpoint(as, 'pushed_authorization_request_endpoint', options);
|
|
541
572
|
const body = new URLSearchParams(parameters);
|
|
542
573
|
body.set('client_id', client.client_id);
|
|
543
574
|
const headers = prepareHeaders(options?.headers);
|
|
@@ -585,7 +616,7 @@ function wwwAuth(scheme, params) {
|
|
|
585
616
|
};
|
|
586
617
|
}
|
|
587
618
|
export function parseWwwAuthenticateChallenges(response) {
|
|
588
|
-
if (!(response
|
|
619
|
+
if (!looseInstanceOf(response, Response)) {
|
|
589
620
|
throw new TypeError('"response" must be an instance of Response');
|
|
590
621
|
}
|
|
591
622
|
if (!response.headers.has('www-authenticate')) {
|
|
@@ -615,7 +646,7 @@ export function parseWwwAuthenticateChallenges(response) {
|
|
|
615
646
|
export async function processPushedAuthorizationResponse(as, client, response) {
|
|
616
647
|
assertAs(as);
|
|
617
648
|
assertClient(client);
|
|
618
|
-
if (!(response
|
|
649
|
+
if (!looseInstanceOf(response, Response)) {
|
|
619
650
|
throw new TypeError('"response" must be an instance of Response');
|
|
620
651
|
}
|
|
621
652
|
if (response.status !== 201) {
|
|
@@ -656,25 +687,21 @@ export async function protectedResourceRequest(accessToken, method, url, headers
|
|
|
656
687
|
headers.set('authorization', `Bearer ${accessToken}`);
|
|
657
688
|
}
|
|
658
689
|
else {
|
|
659
|
-
await dpopProofJwt(headers, options.DPoP, url, 'GET', getClockSkew({ [clockSkew]: options?.clockSkew }), accessToken);
|
|
690
|
+
await dpopProofJwt(headers, options.DPoP, url, 'GET', getClockSkew({ [clockSkew]: options?.[clockSkew] }), accessToken);
|
|
660
691
|
headers.set('authorization', `DPoP ${accessToken}`);
|
|
661
692
|
}
|
|
662
|
-
|
|
693
|
+
return (options?.[experimentalCustomFetch] || fetch)(url.href, {
|
|
663
694
|
body,
|
|
664
|
-
headers,
|
|
695
|
+
headers: Object.fromEntries(headers.entries()),
|
|
665
696
|
method,
|
|
666
697
|
redirect: 'manual',
|
|
667
698
|
signal: options?.signal ? signal(options.signal) : null,
|
|
668
|
-
});
|
|
669
|
-
return fetch(request).then(processDpopNonce);
|
|
699
|
+
}).then(processDpopNonce);
|
|
670
700
|
}
|
|
671
701
|
export async function userInfoRequest(as, client, accessToken, options) {
|
|
672
702
|
assertAs(as);
|
|
673
703
|
assertClient(client);
|
|
674
|
-
|
|
675
|
-
throw new TypeError('"as.userinfo_endpoint" must be a string');
|
|
676
|
-
}
|
|
677
|
-
const url = new URL(as.userinfo_endpoint);
|
|
704
|
+
const url = resolveEndpoint(as, 'userinfo_endpoint', options);
|
|
678
705
|
const headers = prepareHeaders(options?.headers);
|
|
679
706
|
if (client.userinfo_signed_response_alg) {
|
|
680
707
|
headers.set('accept', 'application/jwt');
|
|
@@ -685,7 +712,7 @@ export async function userInfoRequest(as, client, accessToken, options) {
|
|
|
685
712
|
}
|
|
686
713
|
return protectedResourceRequest(accessToken, 'GET', url, headers, null, {
|
|
687
714
|
...options,
|
|
688
|
-
clockSkew: getClockSkew(client),
|
|
715
|
+
[clockSkew]: getClockSkew(client),
|
|
689
716
|
});
|
|
690
717
|
}
|
|
691
718
|
let jwksCache;
|
|
@@ -778,7 +805,7 @@ function getContentType(response) {
|
|
|
778
805
|
export async function processUserInfoResponse(as, client, expectedSubject, response) {
|
|
779
806
|
assertAs(as);
|
|
780
807
|
assertClient(client);
|
|
781
|
-
if (!(response
|
|
808
|
+
if (!looseInstanceOf(response, Response)) {
|
|
782
809
|
throw new TypeError('"response" must be an instance of Response');
|
|
783
810
|
}
|
|
784
811
|
if (response.status !== 200) {
|
|
@@ -826,20 +853,16 @@ export async function processUserInfoResponse(as, client, expectedSubject, respo
|
|
|
826
853
|
async function authenticatedRequest(as, client, method, url, body, headers, options) {
|
|
827
854
|
await clientAuthentication(as, client, body, headers, options?.clientPrivateKey);
|
|
828
855
|
headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
|
|
829
|
-
|
|
856
|
+
return (options?.[experimentalCustomFetch] || fetch)(url.href, {
|
|
830
857
|
body,
|
|
831
|
-
headers,
|
|
858
|
+
headers: Object.fromEntries(headers.entries()),
|
|
832
859
|
method,
|
|
833
860
|
redirect: 'manual',
|
|
834
861
|
signal: options?.signal ? signal(options.signal) : null,
|
|
835
|
-
});
|
|
836
|
-
return fetch(request).then(processDpopNonce);
|
|
862
|
+
}).then(processDpopNonce);
|
|
837
863
|
}
|
|
838
864
|
async function tokenEndpointRequest(as, client, grantType, parameters, options) {
|
|
839
|
-
|
|
840
|
-
throw new TypeError('"as.token_endpoint" must be a string');
|
|
841
|
-
}
|
|
842
|
-
const url = new URL(as.token_endpoint);
|
|
865
|
+
const url = resolveEndpoint(as, 'token_endpoint', options);
|
|
843
866
|
parameters.set('grant_type', grantType);
|
|
844
867
|
const headers = prepareHeaders(options?.headers);
|
|
845
868
|
headers.set('accept', 'application/json');
|
|
@@ -872,7 +895,7 @@ export function getValidatedIdTokenClaims(ref) {
|
|
|
872
895
|
async function processGenericAccessTokenResponse(as, client, response, ignoreIdToken = false, ignoreRefreshToken = false) {
|
|
873
896
|
assertAs(as);
|
|
874
897
|
assertClient(client);
|
|
875
|
-
if (!(response
|
|
898
|
+
if (!looseInstanceOf(response, Response)) {
|
|
876
899
|
throw new TypeError('"response" must be an instance of Response');
|
|
877
900
|
}
|
|
878
901
|
if (response.status !== 200) {
|
|
@@ -1092,10 +1115,7 @@ export async function revocationRequest(as, client, token, options) {
|
|
|
1092
1115
|
if (!validateString(token)) {
|
|
1093
1116
|
throw new TypeError('"token" must be a non-empty string');
|
|
1094
1117
|
}
|
|
1095
|
-
|
|
1096
|
-
throw new TypeError('"as.revocation_endpoint" must be a string');
|
|
1097
|
-
}
|
|
1098
|
-
const url = new URL(as.revocation_endpoint);
|
|
1118
|
+
const url = resolveEndpoint(as, 'revocation_endpoint', options);
|
|
1099
1119
|
const body = new URLSearchParams(options?.additionalParameters);
|
|
1100
1120
|
body.set('token', token);
|
|
1101
1121
|
const headers = prepareHeaders(options?.headers);
|
|
@@ -1103,7 +1123,7 @@ export async function revocationRequest(as, client, token, options) {
|
|
|
1103
1123
|
return authenticatedRequest(as, client, 'POST', url, body, headers, options);
|
|
1104
1124
|
}
|
|
1105
1125
|
export async function processRevocationResponse(response) {
|
|
1106
|
-
if (!(response
|
|
1126
|
+
if (!looseInstanceOf(response, Response)) {
|
|
1107
1127
|
throw new TypeError('"response" must be an instance of Response');
|
|
1108
1128
|
}
|
|
1109
1129
|
if (response.status !== 200) {
|
|
@@ -1126,10 +1146,7 @@ export async function introspectionRequest(as, client, token, options) {
|
|
|
1126
1146
|
if (!validateString(token)) {
|
|
1127
1147
|
throw new TypeError('"token" must be a non-empty string');
|
|
1128
1148
|
}
|
|
1129
|
-
|
|
1130
|
-
throw new TypeError('"as.introspection_endpoint" must be a string');
|
|
1131
|
-
}
|
|
1132
|
-
const url = new URL(as.introspection_endpoint);
|
|
1149
|
+
const url = resolveEndpoint(as, 'introspection_endpoint', options);
|
|
1133
1150
|
const body = new URLSearchParams(options?.additionalParameters);
|
|
1134
1151
|
body.set('token', token);
|
|
1135
1152
|
const headers = prepareHeaders(options?.headers);
|
|
@@ -1144,7 +1161,7 @@ export async function introspectionRequest(as, client, token, options) {
|
|
|
1144
1161
|
export async function processIntrospectionResponse(as, client, response) {
|
|
1145
1162
|
assertAs(as);
|
|
1146
1163
|
assertClient(client);
|
|
1147
|
-
if (!(response
|
|
1164
|
+
if (!looseInstanceOf(response, Response)) {
|
|
1148
1165
|
throw new TypeError('"response" must be an instance of Response');
|
|
1149
1166
|
}
|
|
1150
1167
|
if (response.status !== 200) {
|
|
@@ -1186,23 +1203,19 @@ export async function processIntrospectionResponse(as, client, response) {
|
|
|
1186
1203
|
}
|
|
1187
1204
|
async function jwksRequest(as, options) {
|
|
1188
1205
|
assertAs(as);
|
|
1189
|
-
|
|
1190
|
-
throw new TypeError('"as.jwks_uri" must be a string');
|
|
1191
|
-
}
|
|
1192
|
-
const url = new URL(as.jwks_uri);
|
|
1206
|
+
const url = resolveEndpoint(as, 'jwks_uri');
|
|
1193
1207
|
const headers = prepareHeaders(options?.headers);
|
|
1194
1208
|
headers.set('accept', 'application/json');
|
|
1195
1209
|
headers.append('accept', 'application/jwk-set+json');
|
|
1196
|
-
|
|
1197
|
-
headers,
|
|
1210
|
+
return (options?.[experimentalCustomFetch] || fetch)(url.href, {
|
|
1211
|
+
headers: Object.fromEntries(headers.entries()),
|
|
1198
1212
|
method: 'GET',
|
|
1199
1213
|
redirect: 'manual',
|
|
1200
1214
|
signal: options?.signal ? signal(options.signal) : null,
|
|
1201
|
-
});
|
|
1202
|
-
return fetch(request).then(processDpopNonce);
|
|
1215
|
+
}).then(processDpopNonce);
|
|
1203
1216
|
}
|
|
1204
1217
|
async function processJwksResponse(response) {
|
|
1205
|
-
if (!(response
|
|
1218
|
+
if (!looseInstanceOf(response, Response)) {
|
|
1206
1219
|
throw new TypeError('"response" must be an instance of Response');
|
|
1207
1220
|
}
|
|
1208
1221
|
if (response.status !== 200) {
|
|
@@ -1525,10 +1538,7 @@ async function importJwk(alg, jwk) {
|
|
|
1525
1538
|
export async function deviceAuthorizationRequest(as, client, parameters, options) {
|
|
1526
1539
|
assertAs(as);
|
|
1527
1540
|
assertClient(client);
|
|
1528
|
-
|
|
1529
|
-
throw new TypeError('"as.device_authorization_endpoint" must be a string');
|
|
1530
|
-
}
|
|
1531
|
-
const url = new URL(as.device_authorization_endpoint);
|
|
1541
|
+
const url = resolveEndpoint(as, 'device_authorization_endpoint', options);
|
|
1532
1542
|
const body = new URLSearchParams(parameters);
|
|
1533
1543
|
body.set('client_id', client.client_id);
|
|
1534
1544
|
const headers = prepareHeaders(options?.headers);
|
|
@@ -1538,7 +1548,7 @@ export async function deviceAuthorizationRequest(as, client, parameters, options
|
|
|
1538
1548
|
export async function processDeviceAuthorizationResponse(as, client, response) {
|
|
1539
1549
|
assertAs(as);
|
|
1540
1550
|
assertClient(client);
|
|
1541
|
-
if (!(response
|
|
1551
|
+
if (!looseInstanceOf(response, Response)) {
|
|
1542
1552
|
throw new TypeError('"response" must be an instance of Response');
|
|
1543
1553
|
}
|
|
1544
1554
|
if (response.status !== 200) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oauth4webapi",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "OAuth 2 / OpenID Connect for JavaScript Runtimes",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"auth",
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
"puppeteer-core": "^21.7.0",
|
|
81
81
|
"qunit": "^2.20.0",
|
|
82
82
|
"raw-body": "^2.5.2",
|
|
83
|
+
"selfsigned": "^2.4.1",
|
|
83
84
|
"timekeeper": "^2.3.1",
|
|
84
85
|
"tsx": "^4.7.0",
|
|
85
86
|
"typedoc": "^0.25.7",
|