@hyper.authio/server 0.7.0 → 0.8.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 +43 -7
- package/dist/index.cjs +69 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -7
- package/dist/index.d.ts +35 -7
- package/dist/index.js +70 -15
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -301,13 +301,26 @@ declare class AuthioBearerError extends AuthioError {
|
|
|
301
301
|
get wwwAuthenticate(): string;
|
|
302
302
|
}
|
|
303
303
|
/**
|
|
304
|
-
*
|
|
304
|
+
* A rule refused a caller whose token is valid. A refusal asking for a stronger or more recent sign-in
|
|
305
|
+
* (`insufficient_user_authentication`) answers `401`, as RFC 9470 §3 has it, naming the protected resource metadata
|
|
306
|
+
* (`resource_metadata`, RFC 9728 §5.1) when the API publishes it; every other refusal answers `403`, with
|
|
307
|
+
* `wwwAuthenticate` when it is about scopes (`insufficient_scope`, RFC 6750 §3.1).
|
|
305
308
|
*/
|
|
306
309
|
declare class AuthioForbiddenError extends AuthioError {
|
|
307
|
-
|
|
310
|
+
/** `401` for a step-up (`insufficient_user_authentication`), `403` otherwise. */
|
|
311
|
+
readonly status: 401 | 403;
|
|
308
312
|
readonly failure: AuthioRuleFailure;
|
|
309
|
-
|
|
310
|
-
|
|
313
|
+
/** The API's protected resource metadata URL (RFC 9728), sent as `resource_metadata` with a step-up `401`. */
|
|
314
|
+
readonly resourceMetadata: string | undefined;
|
|
315
|
+
constructor(failure: AuthioRuleFailure, options?: {
|
|
316
|
+
resourceMetadata?: string;
|
|
317
|
+
});
|
|
318
|
+
/** The same refusal, pointing at the API's protected resource metadata. */
|
|
319
|
+
withResourceMetadata(resourceMetadata: string | undefined): AuthioForbiddenError;
|
|
320
|
+
/**
|
|
321
|
+
* The `WWW-Authenticate` value, or `undefined` when the refusal is not about scopes or the sign-in. A step-up `401`
|
|
322
|
+
* carries `resource_metadata` when `resourceMetadata` is set.
|
|
323
|
+
*/
|
|
311
324
|
get wwwAuthenticate(): string | undefined;
|
|
312
325
|
}
|
|
313
326
|
/**
|
|
@@ -316,7 +329,8 @@ declare class AuthioForbiddenError extends AuthioError {
|
|
|
316
329
|
*/
|
|
317
330
|
declare function toForbiddenChallenge(failure: AuthioRuleFailure): string | undefined;
|
|
318
331
|
/**
|
|
319
|
-
* Problem details (RFC 9457) for a `401` or `403
|
|
332
|
+
* Problem details (RFC 9457) for a `401` or `403`, with the refusal's `status`: a step-up refusal is `401`
|
|
333
|
+
* Unauthorized and carries `error`, `acr_values` and `max_age`.
|
|
320
334
|
*/
|
|
321
335
|
declare function toProblemDetails(error: AuthioBearerError | AuthioForbiddenError): Record<string, unknown>;
|
|
322
336
|
|
|
@@ -359,8 +373,14 @@ interface AuthioVerifierOptions extends AuthioConnectionOptions, AuthioUserClaim
|
|
|
359
373
|
introspectionCacheDuration?: number;
|
|
360
374
|
/** Seconds of clock skew tolerated on `exp` and `nbf`. Defaults to 30. */
|
|
361
375
|
clockSkew?: number;
|
|
362
|
-
/**
|
|
363
|
-
|
|
376
|
+
/**
|
|
377
|
+
* The algorithm (or algorithms) access tokens are signed with, as the client registered in Authio signs them:
|
|
378
|
+
* RS/PS/ES 256/384/512, or HS256/384/512 verified with the `clientSecret` of `client` (its UTF-8 octets, at least
|
|
379
|
+
* 32, 48 or 64 bytes as RFC 7518 §3.2 asks). Only the client's own API holds that secret, so only it validates an HS*
|
|
380
|
+
* token locally; any other API validates such tokens with introspection. Left out, the asymmetric algorithms of the
|
|
381
|
+
* realm's keys are accepted, and never HS*.
|
|
382
|
+
*/
|
|
383
|
+
accessTokenSignedResponseAlg?: string | readonly string[];
|
|
364
384
|
/** Refuses a token whose `typ` claim is present and not `Bearer` (an ID token, for one). Defaults to `true`. */
|
|
365
385
|
rejectNonAccessTokens?: boolean;
|
|
366
386
|
/**
|
|
@@ -370,6 +390,13 @@ interface AuthioVerifierOptions extends AuthioConnectionOptions, AuthioUserClaim
|
|
|
370
390
|
clientEncryptionKey?: AuthioEncryptionKeySource;
|
|
371
391
|
/** A PEM or JWK file holding `clientEncryptionKey` (Node only). */
|
|
372
392
|
clientEncryptionKeyPath?: string;
|
|
393
|
+
/**
|
|
394
|
+
* The key management algorithms (`alg`) decrypted. Defaults to RSA-OAEP and RSA-OAEP-256; RSA1_5 cannot be listed,
|
|
395
|
+
* because Node cannot decrypt it.
|
|
396
|
+
*/
|
|
397
|
+
encryptionAlgorithms?: readonly string[];
|
|
398
|
+
/** The content encryption algorithms (`enc`) decrypted. Defaults to A128CBC-HS256, A192CBC-HS384, A256CBC-HS512. */
|
|
399
|
+
encryptionMethods?: readonly string[];
|
|
373
400
|
/** Accepted for parity with the other Authio SDKs; RSA1_5 stays refused, because Node cannot decrypt it. */
|
|
374
401
|
allowLegacyEncryptionAlgorithms?: boolean;
|
|
375
402
|
}
|
|
@@ -416,6 +443,7 @@ declare const AuthioBearerErrorDescriptions: {
|
|
|
416
443
|
readonly Empty: "The token is empty.";
|
|
417
444
|
readonly Encrypted: "The token is encrypted; set clientEncryptionKey to decrypt it, or validate it with introspection ('introspectionOnly').";
|
|
418
445
|
readonly SigningKeysUnavailable: "Authio could not be reached to read its signing keys.";
|
|
446
|
+
readonly Symmetric: "The token is signed with the client's secret (HS*): set accessTokenSignedResponseAlg and the client's clientSecret to validate it locally, or validate it with introspection ('introspectionOnly').";
|
|
419
447
|
};
|
|
420
448
|
/**
|
|
421
449
|
* Creates a token verifier.
|
package/dist/index.d.ts
CHANGED
|
@@ -301,13 +301,26 @@ declare class AuthioBearerError extends AuthioError {
|
|
|
301
301
|
get wwwAuthenticate(): string;
|
|
302
302
|
}
|
|
303
303
|
/**
|
|
304
|
-
*
|
|
304
|
+
* A rule refused a caller whose token is valid. A refusal asking for a stronger or more recent sign-in
|
|
305
|
+
* (`insufficient_user_authentication`) answers `401`, as RFC 9470 §3 has it, naming the protected resource metadata
|
|
306
|
+
* (`resource_metadata`, RFC 9728 §5.1) when the API publishes it; every other refusal answers `403`, with
|
|
307
|
+
* `wwwAuthenticate` when it is about scopes (`insufficient_scope`, RFC 6750 §3.1).
|
|
305
308
|
*/
|
|
306
309
|
declare class AuthioForbiddenError extends AuthioError {
|
|
307
|
-
|
|
310
|
+
/** `401` for a step-up (`insufficient_user_authentication`), `403` otherwise. */
|
|
311
|
+
readonly status: 401 | 403;
|
|
308
312
|
readonly failure: AuthioRuleFailure;
|
|
309
|
-
|
|
310
|
-
|
|
313
|
+
/** The API's protected resource metadata URL (RFC 9728), sent as `resource_metadata` with a step-up `401`. */
|
|
314
|
+
readonly resourceMetadata: string | undefined;
|
|
315
|
+
constructor(failure: AuthioRuleFailure, options?: {
|
|
316
|
+
resourceMetadata?: string;
|
|
317
|
+
});
|
|
318
|
+
/** The same refusal, pointing at the API's protected resource metadata. */
|
|
319
|
+
withResourceMetadata(resourceMetadata: string | undefined): AuthioForbiddenError;
|
|
320
|
+
/**
|
|
321
|
+
* The `WWW-Authenticate` value, or `undefined` when the refusal is not about scopes or the sign-in. A step-up `401`
|
|
322
|
+
* carries `resource_metadata` when `resourceMetadata` is set.
|
|
323
|
+
*/
|
|
311
324
|
get wwwAuthenticate(): string | undefined;
|
|
312
325
|
}
|
|
313
326
|
/**
|
|
@@ -316,7 +329,8 @@ declare class AuthioForbiddenError extends AuthioError {
|
|
|
316
329
|
*/
|
|
317
330
|
declare function toForbiddenChallenge(failure: AuthioRuleFailure): string | undefined;
|
|
318
331
|
/**
|
|
319
|
-
* Problem details (RFC 9457) for a `401` or `403
|
|
332
|
+
* Problem details (RFC 9457) for a `401` or `403`, with the refusal's `status`: a step-up refusal is `401`
|
|
333
|
+
* Unauthorized and carries `error`, `acr_values` and `max_age`.
|
|
320
334
|
*/
|
|
321
335
|
declare function toProblemDetails(error: AuthioBearerError | AuthioForbiddenError): Record<string, unknown>;
|
|
322
336
|
|
|
@@ -359,8 +373,14 @@ interface AuthioVerifierOptions extends AuthioConnectionOptions, AuthioUserClaim
|
|
|
359
373
|
introspectionCacheDuration?: number;
|
|
360
374
|
/** Seconds of clock skew tolerated on `exp` and `nbf`. Defaults to 30. */
|
|
361
375
|
clockSkew?: number;
|
|
362
|
-
/**
|
|
363
|
-
|
|
376
|
+
/**
|
|
377
|
+
* The algorithm (or algorithms) access tokens are signed with, as the client registered in Authio signs them:
|
|
378
|
+
* RS/PS/ES 256/384/512, or HS256/384/512 verified with the `clientSecret` of `client` (its UTF-8 octets, at least
|
|
379
|
+
* 32, 48 or 64 bytes as RFC 7518 §3.2 asks). Only the client's own API holds that secret, so only it validates an HS*
|
|
380
|
+
* token locally; any other API validates such tokens with introspection. Left out, the asymmetric algorithms of the
|
|
381
|
+
* realm's keys are accepted, and never HS*.
|
|
382
|
+
*/
|
|
383
|
+
accessTokenSignedResponseAlg?: string | readonly string[];
|
|
364
384
|
/** Refuses a token whose `typ` claim is present and not `Bearer` (an ID token, for one). Defaults to `true`. */
|
|
365
385
|
rejectNonAccessTokens?: boolean;
|
|
366
386
|
/**
|
|
@@ -370,6 +390,13 @@ interface AuthioVerifierOptions extends AuthioConnectionOptions, AuthioUserClaim
|
|
|
370
390
|
clientEncryptionKey?: AuthioEncryptionKeySource;
|
|
371
391
|
/** A PEM or JWK file holding `clientEncryptionKey` (Node only). */
|
|
372
392
|
clientEncryptionKeyPath?: string;
|
|
393
|
+
/**
|
|
394
|
+
* The key management algorithms (`alg`) decrypted. Defaults to RSA-OAEP and RSA-OAEP-256; RSA1_5 cannot be listed,
|
|
395
|
+
* because Node cannot decrypt it.
|
|
396
|
+
*/
|
|
397
|
+
encryptionAlgorithms?: readonly string[];
|
|
398
|
+
/** The content encryption algorithms (`enc`) decrypted. Defaults to A128CBC-HS256, A192CBC-HS384, A256CBC-HS512. */
|
|
399
|
+
encryptionMethods?: readonly string[];
|
|
373
400
|
/** Accepted for parity with the other Authio SDKs; RSA1_5 stays refused, because Node cannot decrypt it. */
|
|
374
401
|
allowLegacyEncryptionAlgorithms?: boolean;
|
|
375
402
|
}
|
|
@@ -416,6 +443,7 @@ declare const AuthioBearerErrorDescriptions: {
|
|
|
416
443
|
readonly Empty: "The token is empty.";
|
|
417
444
|
readonly Encrypted: "The token is encrypted; set clientEncryptionKey to decrypt it, or validate it with introspection ('introspectionOnly').";
|
|
418
445
|
readonly SigningKeysUnavailable: "Authio could not be reached to read its signing keys.";
|
|
446
|
+
readonly Symmetric: "The token is signed with the client's secret (HS*): set accessTokenSignedResponseAlg and the client's clientSecret to validate it locally, or validate it with introspection ('introspectionOnly').";
|
|
419
447
|
};
|
|
420
448
|
/**
|
|
421
449
|
* Creates a token verifier.
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { systemClock, AuthioClaimTypes, parseResourceAccess, AuthioError, AuthioDefaults, resolveIssuer, AuthioConfigurationError, resolveConnection, createAuthioDiscoveryClient, seconds, createAuthioSigningKeys, encryptionKeyFromFile, createAuthioTokenDecrypter, createAuthioClient, toScopeString, AuthioWebhookHeaders, verifyWebhookSignature, tryParseWebhookEvent, toClientCredentialsToken, isAuthioTokenEncrypted, AuthioTokenValidationError, verifyAuthioJwt, AuthioTokenTypes } from '@hyper.authio/client';
|
|
1
|
+
import { systemClock, AuthioClaimTypes, parseResourceAccess, AuthioError, AuthioDefaults, resolveIssuer, AuthioConfigurationError, resolveConnection, createAuthioDiscoveryClient, seconds, AuthioHmacAlgorithms, createAuthioSigningKeys, encryptionKeyFromFile, createAuthioTokenDecrypter, AuthioAsymmetricAlgorithms, requireHmacSecretLength, createAuthioClient, toScopeString, parseAuthioBearerChallenge, AuthioOAuthErrors, AuthioWebhookHeaders, verifyWebhookSignature, tryParseWebhookEvent, toClientCredentialsToken, isAuthioTokenEncrypted, AuthioTokenValidationError, withClientSecret, verifyAuthioJwt, AuthioTokenTypes } from '@hyper.authio/client';
|
|
2
|
+
import { decodeProtectedHeader } from 'jose';
|
|
2
3
|
|
|
3
4
|
// src/user.ts
|
|
4
5
|
var accessTokenTypes = /* @__PURE__ */ new Set(["bearer", "api_key"]);
|
|
@@ -468,16 +469,30 @@ var AuthioBearerError = class _AuthioBearerError extends AuthioError {
|
|
|
468
469
|
return parameters.length === 0 ? "Bearer" : `Bearer ${parameters.join(", ")}`;
|
|
469
470
|
}
|
|
470
471
|
};
|
|
471
|
-
var AuthioForbiddenError = class extends AuthioError {
|
|
472
|
-
|
|
472
|
+
var AuthioForbiddenError = class _AuthioForbiddenError extends AuthioError {
|
|
473
|
+
/** `401` for a step-up (`insufficient_user_authentication`), `403` otherwise. */
|
|
474
|
+
status;
|
|
473
475
|
failure;
|
|
474
|
-
|
|
476
|
+
/** The API's protected resource metadata URL (RFC 9728), sent as `resource_metadata` with a step-up `401`. */
|
|
477
|
+
resourceMetadata;
|
|
478
|
+
constructor(failure, options = {}) {
|
|
475
479
|
super(failure.message);
|
|
476
480
|
this.failure = failure;
|
|
481
|
+
this.status = failure.error === "insufficient_user_authentication" ? 401 : 403;
|
|
482
|
+
this.resourceMetadata = options.resourceMetadata;
|
|
477
483
|
}
|
|
478
|
-
/** The
|
|
484
|
+
/** The same refusal, pointing at the API's protected resource metadata. */
|
|
485
|
+
withResourceMetadata(resourceMetadata) {
|
|
486
|
+
if (!resourceMetadata || resourceMetadata === this.resourceMetadata) return this;
|
|
487
|
+
return new _AuthioForbiddenError(this.failure, { resourceMetadata });
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* The `WWW-Authenticate` value, or `undefined` when the refusal is not about scopes or the sign-in. A step-up `401`
|
|
491
|
+
* carries `resource_metadata` when `resourceMetadata` is set.
|
|
492
|
+
*/
|
|
479
493
|
get wwwAuthenticate() {
|
|
480
|
-
|
|
494
|
+
const challenge = toForbiddenChallenge(this.failure);
|
|
495
|
+
return challenge && this.status === 401 && this.resourceMetadata ? `${challenge}, resource_metadata="${escapeQuoted(this.resourceMetadata)}"` : challenge;
|
|
481
496
|
}
|
|
482
497
|
};
|
|
483
498
|
function toForbiddenChallenge(failure) {
|
|
@@ -491,15 +506,15 @@ function toForbiddenChallenge(failure) {
|
|
|
491
506
|
function toProblemDetails(error) {
|
|
492
507
|
if (error instanceof AuthioBearerError)
|
|
493
508
|
return {
|
|
494
|
-
type:
|
|
509
|
+
type: unauthorizedType,
|
|
495
510
|
title: "Unauthorized",
|
|
496
511
|
status: 401,
|
|
497
512
|
detail: "A valid Authio access token is required."
|
|
498
513
|
};
|
|
499
514
|
return {
|
|
500
|
-
type: "https://tools.ietf.org/html/rfc9110#section-15.5.4",
|
|
501
|
-
title: "Forbidden",
|
|
502
|
-
status:
|
|
515
|
+
type: error.status === 401 ? unauthorizedType : "https://tools.ietf.org/html/rfc9110#section-15.5.4",
|
|
516
|
+
title: error.status === 401 ? "Unauthorized" : "Forbidden",
|
|
517
|
+
status: error.status,
|
|
503
518
|
detail: error.failure.message || "The caller is not allowed to access this resource.",
|
|
504
519
|
...error.failure.error ? { error: error.failure.error } : {},
|
|
505
520
|
...error.failure.scope ? { scope: error.failure.scope } : {},
|
|
@@ -507,6 +522,7 @@ function toProblemDetails(error) {
|
|
|
507
522
|
...error.failure.maxAge !== void 0 ? { max_age: error.failure.maxAge } : {}
|
|
508
523
|
};
|
|
509
524
|
}
|
|
525
|
+
var unauthorizedType = "https://tools.ietf.org/html/rfc9110#section-15.5.2";
|
|
510
526
|
function toHeaderText(text) {
|
|
511
527
|
return [...text].map((character) => character >= " " && character <= "~" && character !== '"' && character !== "\\" ? character : "?").join("");
|
|
512
528
|
}
|
|
@@ -551,7 +567,8 @@ var AuthioBearerErrorDescriptions = {
|
|
|
551
567
|
Unconfirmed: "Authio could not be reached to confirm that the token is active.",
|
|
552
568
|
Empty: "The token is empty.",
|
|
553
569
|
Encrypted: "The token is encrypted; set clientEncryptionKey to decrypt it, or validate it with introspection ('introspectionOnly').",
|
|
554
|
-
SigningKeysUnavailable: "Authio could not be reached to read its signing keys."
|
|
570
|
+
SigningKeysUnavailable: "Authio could not be reached to read its signing keys.",
|
|
571
|
+
Symmetric: "The token is signed with the client's secret (HS*): set accessTokenSignedResponseAlg and the client's clientSecret to validate it locally, or validate it with introspection ('introspectionOnly')."
|
|
555
572
|
};
|
|
556
573
|
var maxCachedAnswers = 1e4;
|
|
557
574
|
function createAuthioVerifier(options) {
|
|
@@ -571,7 +588,9 @@ function createAuthioVerifier(options) {
|
|
|
571
588
|
const resourceMetadataUrl = resolveResourceMetadata(options.resourceMetadata, resource);
|
|
572
589
|
const cacheDurationMs = seconds(options.introspectionCacheDuration, 30, "introspectionCacheDuration");
|
|
573
590
|
const clockSkew = seconds(options.clockSkew, 30, "clockSkew") / 1e3;
|
|
574
|
-
const algorithms = options.
|
|
591
|
+
const algorithms = resolveAccessTokenAlgorithms(options.accessTokenSignedResponseAlg);
|
|
592
|
+
const symmetric = algorithms?.filter((algorithm) => AuthioHmacAlgorithms.includes(algorithm)) ?? [];
|
|
593
|
+
const clientSecret = symmetric.length > 0 ? resolveClientSecret(options, symmetric) : void 0;
|
|
575
594
|
const rejectNonAccessTokens = options.rejectNonAccessTokens ?? true;
|
|
576
595
|
const claimSettings = { roleClaims: options.roleClaims, groupClaims: options.groupClaims };
|
|
577
596
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -579,7 +598,9 @@ function createAuthioVerifier(options) {
|
|
|
579
598
|
const encryptionKey = options.clientEncryptionKey ?? (options.clientEncryptionKeyPath ? encryptionKeyFromFile(options.clientEncryptionKeyPath) : void 0);
|
|
580
599
|
const decrypt = encryptionKey === void 0 ? void 0 : createAuthioTokenDecrypter(encryptionKey, {
|
|
581
600
|
setting: options.clientEncryptionKey === void 0 ? "clientEncryptionKeyPath" : "clientEncryptionKey",
|
|
582
|
-
allowLegacyEncryptionAlgorithms: options.allowLegacyEncryptionAlgorithms
|
|
601
|
+
allowLegacyEncryptionAlgorithms: options.allowLegacyEncryptionAlgorithms,
|
|
602
|
+
encryptionAlgorithms: options.encryptionAlgorithms,
|
|
603
|
+
encryptionMethods: options.encryptionMethods
|
|
583
604
|
});
|
|
584
605
|
const requiredScopes = Object.freeze([...new Set((options.requiredScopes ?? []).filter((scope) => scope?.trim()))]);
|
|
585
606
|
const introspect = async (token, key, signal) => {
|
|
@@ -609,7 +630,7 @@ function createAuthioVerifier(options) {
|
|
|
609
630
|
}
|
|
610
631
|
let keySet;
|
|
611
632
|
try {
|
|
612
|
-
keySet = await signingKeys(signal);
|
|
633
|
+
keySet = withClientSecret(await signingKeys(signal), clientSecret);
|
|
613
634
|
} catch (error) {
|
|
614
635
|
throw new AuthioBearerError(AuthioBearerErrorDescriptions.SigningKeysUnavailable, { cause: error, reason: "signing_keys_unavailable" });
|
|
615
636
|
}
|
|
@@ -623,6 +644,8 @@ function createAuthioVerifier(options) {
|
|
|
623
644
|
now: connection.clock.now()
|
|
624
645
|
}));
|
|
625
646
|
} catch (error) {
|
|
647
|
+
if (error instanceof AuthioTokenValidationError && error.reason === "invalid_algorithm" && symmetric.length === 0 && isSymmetricToken(signed))
|
|
648
|
+
throw new AuthioBearerError(AuthioBearerErrorDescriptions.Symmetric, { cause: error, reason: error.reason });
|
|
626
649
|
if (error instanceof AuthioTokenValidationError) throw new AuthioBearerError(error.message, { cause: error, reason: error.reason });
|
|
627
650
|
throw error;
|
|
628
651
|
}
|
|
@@ -704,6 +727,35 @@ function resolveResourceMetadata(setting, resource) {
|
|
|
704
727
|
throw new AuthioConfigurationError("resourceMetadata: true needs the API's resource identifier: set options.resource, or pass the metadata URL.");
|
|
705
728
|
return protectedResourceMetadataUrl(resource);
|
|
706
729
|
}
|
|
730
|
+
function resolveAccessTokenAlgorithms(setting) {
|
|
731
|
+
const algorithms = [...new Set((typeof setting === "string" ? [setting] : [...setting ?? []]).map((algorithm) => algorithm.trim()).filter(Boolean))];
|
|
732
|
+
const known = [...AuthioAsymmetricAlgorithms, ...AuthioHmacAlgorithms];
|
|
733
|
+
for (const algorithm of algorithms)
|
|
734
|
+
if (!known.includes(algorithm))
|
|
735
|
+
throw new AuthioConfigurationError(`The Authio accessTokenSignedResponseAlg '${algorithm}' is not one of ${known.join(", ")}.`);
|
|
736
|
+
return algorithms.length > 0 ? algorithms : void 0;
|
|
737
|
+
}
|
|
738
|
+
function resolveClientSecret(options, symmetric) {
|
|
739
|
+
const given = options.client;
|
|
740
|
+
const source = given && !isClient(given) ? given.clientSecret : void 0;
|
|
741
|
+
if (!source)
|
|
742
|
+
throw new AuthioConfigurationError(
|
|
743
|
+
`accessTokenSignedResponseAlg ${symmetric.join(", ")} is verified with the client secret: pass options.client as { clientId, clientSecret } (an AuthioClient does not expose its secret).`
|
|
744
|
+
);
|
|
745
|
+
if (typeof source === "string") for (const algorithm of symmetric) requireHmacSecretLength(algorithm, new TextEncoder().encode(source).length);
|
|
746
|
+
return async () => {
|
|
747
|
+
const value = typeof source === "function" ? await source() : source;
|
|
748
|
+
if (typeof value !== "string" || !value) throw new AuthioConfigurationError("The Authio clientSecret function returned no secret.");
|
|
749
|
+
return value;
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
function isSymmetricToken(token) {
|
|
753
|
+
try {
|
|
754
|
+
return AuthioHmacAlgorithms.includes(String(decodeProtectedHeader(token).alg));
|
|
755
|
+
} catch {
|
|
756
|
+
return false;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
707
759
|
function resolveClient(options) {
|
|
708
760
|
const given = options.client;
|
|
709
761
|
if (!given) return void 0;
|
|
@@ -866,13 +918,16 @@ function createOnBehalfOf(options) {
|
|
|
866
918
|
};
|
|
867
919
|
const token = await getToken(subject, request.signal);
|
|
868
920
|
const response = await attempt(token);
|
|
869
|
-
if (response.status !== 401) return response;
|
|
921
|
+
if (response.status !== 401 || isStepUpChallenge(response)) return response;
|
|
870
922
|
await invalidate(subject, token.accessToken);
|
|
871
923
|
return attempt(await getToken(subject, request.signal));
|
|
872
924
|
};
|
|
873
925
|
}
|
|
874
926
|
};
|
|
875
927
|
}
|
|
928
|
+
function isStepUpChallenge(response) {
|
|
929
|
+
return parseAuthioBearerChallenge(response.headers.get("www-authenticate"))?.error === AuthioOAuthErrors.InsufficientUserAuthentication;
|
|
930
|
+
}
|
|
876
931
|
function tokenOf(subject) {
|
|
877
932
|
if (!subject) return void 0;
|
|
878
933
|
return typeof subject === "string" ? subject : subject.token;
|