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 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.9.0/mod.ts'
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 caching.
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 logging outgoing
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) for mocking.
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: string;
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.9.0';
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
- if (response.headers.has('dpop-nonce')) {
147
- const url = new URL(response.url);
148
- dpopNonces.set(url.origin, response.headers.get('dpop-nonce'));
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
- finally {
152
- return response;
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 = `.well-known/oauth-authorization-server`;
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
- if (client && clockSkew in client) {
346
- if (Number.isFinite(client[clockSkew])) {
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
- if (client && clockTolerance in client) {
354
- const tolerance = client[clockTolerance];
355
- if (Number.isFinite(tolerance) && Math.sign(tolerance) !== -1) {
356
- return tolerance;
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
- if (parameters.has('claims')) {
493
- const value = parameters.get('claims');
494
- if (value === '[object Object]') {
495
- throw new OPE('"claims" parameter must be passed as a UTF-8 encoded JSON');
496
- }
497
- try {
498
- claims.claims = JSON.parse(value);
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
- catch (cause) {
501
- throw new OPE('failed to parse the "claims" parameter as JSON', { cause });
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
- if (!isJsonObject(claims.claims)) {
504
- throw new OPE('"claims" parameter must be a top level object');
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 publicJwk(key) {
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
- else {
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
- if (!response.headers.has('www-authenticate')) {
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
- else if (length !== 1) {
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
- if (!request.headers.has('dpop')) {
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(request.headers.get('dpop'), checkSigningAlgorithm.bind(undefined, undefined, as?.dpop_signing_alg_values_supported || SUPPORTED_JWS_ALGS), async ({ jwk, alg }) => {
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 (!authorization) {
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.9.0",
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.15",
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.0",
78
+ "jose": "^5.2.1",
79
79
  "oidc-provider": "^8.4.5",
80
80
  "patch-package": "^8.0.0",
81
- "prettier": "^3.2.4",
81
+ "prettier": "^3.2.5",
82
82
  "prettier-plugin-jsdoc": "^1.3.0",
83
- "puppeteer-core": "^21.10.0",
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.14",
91
+ "typedoc-plugin-mdn-links": "^3.1.15",
92
92
  "typescript": "^5.3.3",
93
93
  "undici": "^5.28.2"
94
94
  }