oauth4webapi 2.9.0 → 2.10.1
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 -1
- package/build/index.d.ts +22 -5
- package/build/index.js +61 -49
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ import * as oauth2 from 'oauth4webapi'
|
|
|
44
44
|
**`example`** Deno import
|
|
45
45
|
|
|
46
46
|
```js
|
|
47
|
-
import * as oauth2 from 'https://deno.land/x/oauth4webapi@v2.
|
|
47
|
+
import * as oauth2 from 'https://deno.land/x/oauth4webapi@v2.10.1/mod.ts'
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
- Authorization Code Flow - OpenID Connect [source](examples/code.ts), or plain OAuth 2 [source](examples/oauth.ts)
|
package/build/index.d.ts
CHANGED
|
@@ -161,12 +161,13 @@ export declare const clockTolerance: unique symbol;
|
|
|
161
161
|
*
|
|
162
162
|
* - Expect Type-related issues when passing the inputs through to fetch-like modules, they hardly
|
|
163
163
|
* ever get their typings inline with actual fetch, you should `@ts-expect-error` them.
|
|
164
|
-
* - Returning self-constructed {@link Response} instances prohibits AS-signalled DPoP Nonce
|
|
164
|
+
* - Returning self-constructed {@link Response} instances prohibits AS/RS-signalled DPoP Nonce
|
|
165
|
+
* caching.
|
|
165
166
|
*
|
|
166
167
|
* @example
|
|
167
168
|
*
|
|
168
|
-
* Using [sindresorhus/ky](https://github.com/sindresorhus/ky) hooks feature for
|
|
169
|
-
* requests and their responses.
|
|
169
|
+
* Using [sindresorhus/ky](https://github.com/sindresorhus/ky) for retries and its hooks feature for
|
|
170
|
+
* logging outgoing requests and their responses.
|
|
170
171
|
*
|
|
171
172
|
* ```js
|
|
172
173
|
* import ky from 'ky'
|
|
@@ -200,7 +201,7 @@ export declare const clockTolerance: unique symbol;
|
|
|
200
201
|
*
|
|
201
202
|
* @example
|
|
202
203
|
*
|
|
203
|
-
* Using [nodejs/undici](https://github.com/nodejs/undici)
|
|
204
|
+
* Using [nodejs/undici](https://github.com/nodejs/undici) to mock responses in tests.
|
|
204
205
|
*
|
|
205
206
|
* ```js
|
|
206
207
|
* import * as undici from 'undici'
|
|
@@ -1183,12 +1184,22 @@ export interface IDToken extends JWTPayload {
|
|
|
1183
1184
|
readonly azp?: string;
|
|
1184
1185
|
readonly [claim: string]: JsonValue | undefined;
|
|
1185
1186
|
}
|
|
1187
|
+
export interface AuthorizationDetails {
|
|
1188
|
+
readonly type: string;
|
|
1189
|
+
readonly locations?: string[];
|
|
1190
|
+
readonly actions?: string[];
|
|
1191
|
+
readonly datatypes?: string[];
|
|
1192
|
+
readonly privileges?: string[];
|
|
1193
|
+
readonly identifier?: string;
|
|
1194
|
+
readonly [parameter: string]: JsonValue | undefined;
|
|
1195
|
+
}
|
|
1186
1196
|
export interface TokenEndpointResponse {
|
|
1187
1197
|
readonly access_token: string;
|
|
1188
1198
|
readonly expires_in?: number;
|
|
1189
1199
|
readonly id_token?: string;
|
|
1190
1200
|
readonly refresh_token?: string;
|
|
1191
1201
|
readonly scope?: string;
|
|
1202
|
+
readonly authorization_details?: AuthorizationDetails[];
|
|
1192
1203
|
/**
|
|
1193
1204
|
* NOTE: because the value is case insensitive it is always returned lowercased
|
|
1194
1205
|
*/
|
|
@@ -1201,6 +1212,7 @@ export interface OpenIDTokenEndpointResponse {
|
|
|
1201
1212
|
readonly id_token: string;
|
|
1202
1213
|
readonly refresh_token?: string;
|
|
1203
1214
|
readonly scope?: string;
|
|
1215
|
+
readonly authorization_details?: AuthorizationDetails[];
|
|
1204
1216
|
/**
|
|
1205
1217
|
* NOTE: because the value is case insensitive it is always returned lowercased
|
|
1206
1218
|
*/
|
|
@@ -1213,6 +1225,7 @@ export interface OAuth2TokenEndpointResponse {
|
|
|
1213
1225
|
readonly id_token?: undefined;
|
|
1214
1226
|
readonly refresh_token?: string;
|
|
1215
1227
|
readonly scope?: string;
|
|
1228
|
+
readonly authorization_details?: AuthorizationDetails[];
|
|
1216
1229
|
/**
|
|
1217
1230
|
* NOTE: because the value is case insensitive it is always returned lowercased
|
|
1218
1231
|
*/
|
|
@@ -1223,6 +1236,7 @@ export interface ClientCredentialsGrantResponse {
|
|
|
1223
1236
|
readonly access_token: string;
|
|
1224
1237
|
readonly expires_in?: number;
|
|
1225
1238
|
readonly scope?: string;
|
|
1239
|
+
readonly authorization_details?: AuthorizationDetails[];
|
|
1226
1240
|
/**
|
|
1227
1241
|
* NOTE: because the value is case insensitive it is always returned lowercased
|
|
1228
1242
|
*/
|
|
@@ -1393,11 +1407,12 @@ export interface IntrospectionResponse {
|
|
|
1393
1407
|
readonly jti?: string;
|
|
1394
1408
|
readonly username?: string;
|
|
1395
1409
|
readonly aud?: string | string[];
|
|
1396
|
-
readonly scope
|
|
1410
|
+
readonly scope?: string;
|
|
1397
1411
|
readonly sub?: string;
|
|
1398
1412
|
readonly nbf?: number;
|
|
1399
1413
|
readonly token_type?: string;
|
|
1400
1414
|
readonly cnf?: ConfirmationClaims;
|
|
1415
|
+
readonly authorization_details?: AuthorizationDetails[];
|
|
1401
1416
|
readonly [claim: string]: JsonValue | undefined;
|
|
1402
1417
|
}
|
|
1403
1418
|
/**
|
|
@@ -1596,6 +1611,8 @@ export interface JWTAccessTokenClaims extends JWTPayload {
|
|
|
1596
1611
|
readonly iat: number;
|
|
1597
1612
|
readonly jti: string;
|
|
1598
1613
|
readonly client_id: string;
|
|
1614
|
+
readonly authorization_details?: AuthorizationDetails[];
|
|
1615
|
+
readonly scope?: string;
|
|
1599
1616
|
readonly [claim: string]: JsonValue | undefined;
|
|
1600
1617
|
}
|
|
1601
1618
|
export interface ValidateJWTAccessTokenOptions extends HttpRequestOptions {
|
package/build/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.10.1';
|
|
5
5
|
USER_AGENT = `${NAME}/${VERSION}`;
|
|
6
6
|
}
|
|
7
7
|
function looseInstanceOf(input, expected) {
|
|
@@ -143,14 +143,13 @@ const SUPPORTED_JWS_ALGS = [
|
|
|
143
143
|
];
|
|
144
144
|
function processDpopNonce(response) {
|
|
145
145
|
try {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
dpopNonces.set(url.origin,
|
|
146
|
+
const nonce = response.headers.get('dpop-nonce');
|
|
147
|
+
if (nonce) {
|
|
148
|
+
dpopNonces.set(new URL(response.url).origin, nonce);
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
151
|
+
catch { }
|
|
152
|
+
return response;
|
|
154
153
|
}
|
|
155
154
|
function normalizeTyp(value) {
|
|
156
155
|
return value.toLowerCase().replace(/^application\//, '');
|
|
@@ -201,7 +200,7 @@ export async function discoveryRequest(issuerIdentifier, options) {
|
|
|
201
200
|
break;
|
|
202
201
|
case 'oauth2':
|
|
203
202
|
if (url.pathname === '/') {
|
|
204
|
-
url.pathname =
|
|
203
|
+
url.pathname = '.well-known/oauth-authorization-server';
|
|
205
204
|
}
|
|
206
205
|
else {
|
|
207
206
|
url.pathname = `.well-known/oauth-authorization-server/${url.pathname}`.replace('//', '/');
|
|
@@ -342,21 +341,14 @@ function keyToJws(key) {
|
|
|
342
341
|
}
|
|
343
342
|
}
|
|
344
343
|
function getClockSkew(client) {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
return client[clockSkew];
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
return 0;
|
|
344
|
+
const skew = client?.[clockSkew];
|
|
345
|
+
return typeof skew === 'number' && Number.isFinite(skew) ? skew : 0;
|
|
351
346
|
}
|
|
352
347
|
function getClockTolerance(client) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
return 30;
|
|
348
|
+
const tolerance = client?.[clockTolerance];
|
|
349
|
+
return typeof tolerance === 'number' && Number.isFinite(tolerance) && Math.sign(tolerance) !== -1
|
|
350
|
+
? tolerance
|
|
351
|
+
: 30;
|
|
360
352
|
}
|
|
361
353
|
function epochTime() {
|
|
362
354
|
return Math.floor(Date.now() / 1000);
|
|
@@ -489,19 +481,41 @@ export async function issueRequestObject(as, client, parameters, privateKey) {
|
|
|
489
481
|
resource.length > 1) {
|
|
490
482
|
claims.resource = resource;
|
|
491
483
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
if (value
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
484
|
+
{
|
|
485
|
+
let value = parameters.get('max_age');
|
|
486
|
+
if (value !== null) {
|
|
487
|
+
claims.max_age = parseInt(value, 10);
|
|
488
|
+
if (!Number.isFinite(claims.max_age)) {
|
|
489
|
+
throw new OPE('"max_age" parameter must be a number');
|
|
490
|
+
}
|
|
499
491
|
}
|
|
500
|
-
|
|
501
|
-
|
|
492
|
+
}
|
|
493
|
+
{
|
|
494
|
+
let value = parameters.get('claims');
|
|
495
|
+
if (value !== null) {
|
|
496
|
+
try {
|
|
497
|
+
claims.claims = JSON.parse(value);
|
|
498
|
+
}
|
|
499
|
+
catch (cause) {
|
|
500
|
+
throw new OPE('failed to parse the "claims" parameter as JSON', { cause });
|
|
501
|
+
}
|
|
502
|
+
if (!isJsonObject(claims.claims)) {
|
|
503
|
+
throw new OPE('"claims" parameter must be a JSON with a top level object');
|
|
504
|
+
}
|
|
502
505
|
}
|
|
503
|
-
|
|
504
|
-
|
|
506
|
+
}
|
|
507
|
+
{
|
|
508
|
+
let value = parameters.get('authorization_details');
|
|
509
|
+
if (value !== null) {
|
|
510
|
+
try {
|
|
511
|
+
claims.authorization_details = JSON.parse(value);
|
|
512
|
+
}
|
|
513
|
+
catch (cause) {
|
|
514
|
+
throw new OPE('failed to parse the "authorization_details" parameter as JSON', { cause });
|
|
515
|
+
}
|
|
516
|
+
if (!Array.isArray(claims.authorization_details)) {
|
|
517
|
+
throw new OPE('"authorization_details" parameter must be a JSON with a top level array');
|
|
518
|
+
}
|
|
505
519
|
}
|
|
506
520
|
}
|
|
507
521
|
return jwt({
|
|
@@ -540,24 +554,22 @@ async function dpopProofJwt(headers, options, url, htm, clockSkew, accessToken)
|
|
|
540
554
|
headers.set('dpop', proof);
|
|
541
555
|
}
|
|
542
556
|
let jwkCache;
|
|
543
|
-
async function
|
|
544
|
-
jwkCache || (jwkCache = new WeakMap());
|
|
545
|
-
if (jwkCache.has(key)) {
|
|
546
|
-
return jwkCache.get(key);
|
|
547
|
-
}
|
|
557
|
+
async function getSetPublicJwkCache(key) {
|
|
548
558
|
const { kty, e, n, x, y, crv } = await crypto.subtle.exportKey('jwk', key);
|
|
549
559
|
const jwk = { kty, e, n, x, y, crv };
|
|
550
560
|
jwkCache.set(key, jwk);
|
|
551
561
|
return jwk;
|
|
552
562
|
}
|
|
563
|
+
async function publicJwk(key) {
|
|
564
|
+
jwkCache || (jwkCache = new WeakMap());
|
|
565
|
+
return jwkCache.get(key) || getSetPublicJwkCache(key);
|
|
566
|
+
}
|
|
553
567
|
function validateEndpoint(value, endpoint, options) {
|
|
554
568
|
if (typeof value !== 'string') {
|
|
555
569
|
if (options?.[useMtlsAlias]) {
|
|
556
570
|
throw new TypeError(`"as.mtls_endpoint_aliases.${endpoint}" must be a string`);
|
|
557
571
|
}
|
|
558
|
-
|
|
559
|
-
throw new TypeError(`"as.${endpoint}" must be a string`);
|
|
560
|
-
}
|
|
572
|
+
throw new TypeError(`"as.${endpoint}" must be a string`);
|
|
561
573
|
}
|
|
562
574
|
return new URL(value);
|
|
563
575
|
}
|
|
@@ -621,10 +633,10 @@ export function parseWwwAuthenticateChallenges(response) {
|
|
|
621
633
|
if (!looseInstanceOf(response, Response)) {
|
|
622
634
|
throw new TypeError('"response" must be an instance of Response');
|
|
623
635
|
}
|
|
624
|
-
|
|
636
|
+
const header = response.headers.get('www-authenticate');
|
|
637
|
+
if (header === null) {
|
|
625
638
|
return undefined;
|
|
626
639
|
}
|
|
627
|
-
const header = response.headers.get('www-authenticate');
|
|
628
640
|
const result = [];
|
|
629
641
|
for (const { 1: scheme, index } of header.matchAll(SCHEMES_REGEXP)) {
|
|
630
642
|
result.push([scheme, index]);
|
|
@@ -791,7 +803,7 @@ async function getPublicSigKeyFromIssuerJwksUri(as, options, header) {
|
|
|
791
803
|
}
|
|
792
804
|
throw new OPE('error when selecting a JWT verification key, no applicable keys found');
|
|
793
805
|
}
|
|
794
|
-
|
|
806
|
+
if (length !== 1) {
|
|
795
807
|
throw new OPE('error when selecting a JWT verification key, multiple applicable keys found, a "kid" JWT Header Parameter is required');
|
|
796
808
|
}
|
|
797
809
|
const key = await importJwk(alg, jwk);
|
|
@@ -1771,8 +1783,9 @@ function normalizeHtu(htu) {
|
|
|
1771
1783
|
url.hash = '';
|
|
1772
1784
|
return url.href;
|
|
1773
1785
|
}
|
|
1774
|
-
async function validateDPoP(as, request, accessTokenClaims, options) {
|
|
1775
|
-
|
|
1786
|
+
async function validateDPoP(as, request, accessToken, accessTokenClaims, options) {
|
|
1787
|
+
const header = request.headers.get('dpop');
|
|
1788
|
+
if (header === null) {
|
|
1776
1789
|
throw new OPE('operation indicated DPoP use but the request has no DPoP HTTP Header');
|
|
1777
1790
|
}
|
|
1778
1791
|
if (request.headers.get('authorization')?.toLowerCase().startsWith('dpop ') === false) {
|
|
@@ -1782,7 +1795,7 @@ async function validateDPoP(as, request, accessTokenClaims, options) {
|
|
|
1782
1795
|
throw new OPE('operation indicated DPoP use but the JWT Access Token has no jkt confirmation claim');
|
|
1783
1796
|
}
|
|
1784
1797
|
const clockSkew = getClockSkew(options);
|
|
1785
|
-
const proof = await validateJwt(
|
|
1798
|
+
const proof = await validateJwt(header, checkSigningAlgorithm.bind(undefined, undefined, as?.dpop_signing_alg_values_supported || SUPPORTED_JWS_ALGS), async ({ jwk, alg }) => {
|
|
1786
1799
|
if (!jwk) {
|
|
1787
1800
|
throw new OPE('DPoP Proof is missing the jwk header parameter');
|
|
1788
1801
|
}
|
|
@@ -1807,7 +1820,6 @@ async function validateDPoP(as, request, accessTokenClaims, options) {
|
|
|
1807
1820
|
throw new OPE('DPoP Proof htu mismatch');
|
|
1808
1821
|
}
|
|
1809
1822
|
{
|
|
1810
|
-
const accessToken = request.headers.get('authorization').split(' ')[1];
|
|
1811
1823
|
const expected = b64u(await crypto.subtle.digest('SHA-256', encoder.encode(accessToken)));
|
|
1812
1824
|
if (proof.claims.ath !== expected) {
|
|
1813
1825
|
throw new OPE('DPoP Proof ath mismatch');
|
|
@@ -1856,7 +1868,7 @@ export async function validateJwtAccessToken(as, request, expectedAudience, opti
|
|
|
1856
1868
|
throw new OPE('"expectedAudience" must be a non-empty string');
|
|
1857
1869
|
}
|
|
1858
1870
|
const authorization = request.headers.get('authorization');
|
|
1859
|
-
if (
|
|
1871
|
+
if (authorization === null) {
|
|
1860
1872
|
throw new OPE('"request" is missing an Authorization HTTP Header');
|
|
1861
1873
|
}
|
|
1862
1874
|
let { 0: scheme, 1: accessToken, length } = authorization.split(' ');
|
|
@@ -1911,7 +1923,7 @@ export async function validateJwtAccessToken(as, request, expectedAudience, opti
|
|
|
1911
1923
|
scheme === 'dpop' ||
|
|
1912
1924
|
claims.cnf?.jkt !== undefined ||
|
|
1913
1925
|
request.headers.has('dpop')) {
|
|
1914
|
-
await validateDPoP(as, request, claims, options);
|
|
1926
|
+
await validateDPoP(as, request, accessToken, claims, options);
|
|
1915
1927
|
}
|
|
1916
1928
|
return claims;
|
|
1917
1929
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oauth4webapi",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.1",
|
|
4
4
|
"description": "OAuth 2 / OpenID Connect for JavaScript Runtimes",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"access token",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@koa/cors": "^5.0.0",
|
|
69
69
|
"@types/koa__cors": "^5.0.0",
|
|
70
|
-
"@types/node": "^20.11.
|
|
70
|
+
"@types/node": "^20.11.16",
|
|
71
71
|
"@types/oidc-provider": "^8.4.3",
|
|
72
72
|
"@types/qunit": "^2.19.10",
|
|
73
73
|
"archiver": "^6.0.1",
|
|
@@ -75,12 +75,12 @@
|
|
|
75
75
|
"chrome-launcher": "^1.1.0",
|
|
76
76
|
"edge-runtime": "^2.5.8",
|
|
77
77
|
"esbuild": "^0.20.0",
|
|
78
|
-
"jose": "^5.2.
|
|
78
|
+
"jose": "^5.2.1",
|
|
79
79
|
"oidc-provider": "^8.4.5",
|
|
80
80
|
"patch-package": "^8.0.0",
|
|
81
|
-
"prettier": "^3.2.
|
|
81
|
+
"prettier": "^3.2.5",
|
|
82
82
|
"prettier-plugin-jsdoc": "^1.3.0",
|
|
83
|
-
"puppeteer-core": "^21.
|
|
83
|
+
"puppeteer-core": "^21.11.0",
|
|
84
84
|
"qunit": "^2.20.0",
|
|
85
85
|
"raw-body": "^2.5.2",
|
|
86
86
|
"selfsigned": "^2.4.1",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"tsx": "^4.7.0",
|
|
89
89
|
"typedoc": "^0.25.7",
|
|
90
90
|
"typedoc-plugin-markdown": "^3.17.1",
|
|
91
|
-
"typedoc-plugin-mdn-links": "^3.1.
|
|
91
|
+
"typedoc-plugin-mdn-links": "^3.1.15",
|
|
92
92
|
"typescript": "^5.3.3",
|
|
93
93
|
"undici": "^5.28.2"
|
|
94
94
|
}
|