@hyper.authio/server 0.7.0 → 0.8.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 +24 -3
- package/dist/index.cjs +41 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +42 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -359,8 +359,14 @@ interface AuthioVerifierOptions extends AuthioConnectionOptions, AuthioUserClaim
|
|
|
359
359
|
introspectionCacheDuration?: number;
|
|
360
360
|
/** Seconds of clock skew tolerated on `exp` and `nbf`. Defaults to 30. */
|
|
361
361
|
clockSkew?: number;
|
|
362
|
-
/**
|
|
363
|
-
|
|
362
|
+
/**
|
|
363
|
+
* The algorithm (or algorithms) access tokens are signed with, as the client registered in Authio signs them:
|
|
364
|
+
* RS/PS/ES 256/384/512, or HS256/384/512 verified with the `clientSecret` of `client` (its UTF-8 octets, at least
|
|
365
|
+
* 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*
|
|
366
|
+
* token locally; any other API validates such tokens with introspection. Left out, the asymmetric algorithms of the
|
|
367
|
+
* realm's keys are accepted, and never HS*.
|
|
368
|
+
*/
|
|
369
|
+
accessTokenSignedResponseAlg?: string | readonly string[];
|
|
364
370
|
/** Refuses a token whose `typ` claim is present and not `Bearer` (an ID token, for one). Defaults to `true`. */
|
|
365
371
|
rejectNonAccessTokens?: boolean;
|
|
366
372
|
/**
|
|
@@ -370,6 +376,13 @@ interface AuthioVerifierOptions extends AuthioConnectionOptions, AuthioUserClaim
|
|
|
370
376
|
clientEncryptionKey?: AuthioEncryptionKeySource;
|
|
371
377
|
/** A PEM or JWK file holding `clientEncryptionKey` (Node only). */
|
|
372
378
|
clientEncryptionKeyPath?: string;
|
|
379
|
+
/**
|
|
380
|
+
* The key management algorithms (`alg`) decrypted. Defaults to RSA-OAEP and RSA-OAEP-256; RSA1_5 cannot be listed,
|
|
381
|
+
* because Node cannot decrypt it.
|
|
382
|
+
*/
|
|
383
|
+
encryptionAlgorithms?: readonly string[];
|
|
384
|
+
/** The content encryption algorithms (`enc`) decrypted. Defaults to A128CBC-HS256, A192CBC-HS384, A256CBC-HS512. */
|
|
385
|
+
encryptionMethods?: readonly string[];
|
|
373
386
|
/** Accepted for parity with the other Authio SDKs; RSA1_5 stays refused, because Node cannot decrypt it. */
|
|
374
387
|
allowLegacyEncryptionAlgorithms?: boolean;
|
|
375
388
|
}
|
|
@@ -416,6 +429,7 @@ declare const AuthioBearerErrorDescriptions: {
|
|
|
416
429
|
readonly Empty: "The token is empty.";
|
|
417
430
|
readonly Encrypted: "The token is encrypted; set clientEncryptionKey to decrypt it, or validate it with introspection ('introspectionOnly').";
|
|
418
431
|
readonly SigningKeysUnavailable: "Authio could not be reached to read its signing keys.";
|
|
432
|
+
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
433
|
};
|
|
420
434
|
/**
|
|
421
435
|
* 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, 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"]);
|
|
@@ -551,7 +552,8 @@ var AuthioBearerErrorDescriptions = {
|
|
|
551
552
|
Unconfirmed: "Authio could not be reached to confirm that the token is active.",
|
|
552
553
|
Empty: "The token is empty.",
|
|
553
554
|
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."
|
|
555
|
+
SigningKeysUnavailable: "Authio could not be reached to read its signing keys.",
|
|
556
|
+
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
557
|
};
|
|
556
558
|
var maxCachedAnswers = 1e4;
|
|
557
559
|
function createAuthioVerifier(options) {
|
|
@@ -571,7 +573,9 @@ function createAuthioVerifier(options) {
|
|
|
571
573
|
const resourceMetadataUrl = resolveResourceMetadata(options.resourceMetadata, resource);
|
|
572
574
|
const cacheDurationMs = seconds(options.introspectionCacheDuration, 30, "introspectionCacheDuration");
|
|
573
575
|
const clockSkew = seconds(options.clockSkew, 30, "clockSkew") / 1e3;
|
|
574
|
-
const algorithms = options.
|
|
576
|
+
const algorithms = resolveAccessTokenAlgorithms(options.accessTokenSignedResponseAlg);
|
|
577
|
+
const symmetric = algorithms?.filter((algorithm) => AuthioHmacAlgorithms.includes(algorithm)) ?? [];
|
|
578
|
+
const clientSecret = symmetric.length > 0 ? resolveClientSecret(options, symmetric) : void 0;
|
|
575
579
|
const rejectNonAccessTokens = options.rejectNonAccessTokens ?? true;
|
|
576
580
|
const claimSettings = { roleClaims: options.roleClaims, groupClaims: options.groupClaims };
|
|
577
581
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -579,7 +583,9 @@ function createAuthioVerifier(options) {
|
|
|
579
583
|
const encryptionKey = options.clientEncryptionKey ?? (options.clientEncryptionKeyPath ? encryptionKeyFromFile(options.clientEncryptionKeyPath) : void 0);
|
|
580
584
|
const decrypt = encryptionKey === void 0 ? void 0 : createAuthioTokenDecrypter(encryptionKey, {
|
|
581
585
|
setting: options.clientEncryptionKey === void 0 ? "clientEncryptionKeyPath" : "clientEncryptionKey",
|
|
582
|
-
allowLegacyEncryptionAlgorithms: options.allowLegacyEncryptionAlgorithms
|
|
586
|
+
allowLegacyEncryptionAlgorithms: options.allowLegacyEncryptionAlgorithms,
|
|
587
|
+
encryptionAlgorithms: options.encryptionAlgorithms,
|
|
588
|
+
encryptionMethods: options.encryptionMethods
|
|
583
589
|
});
|
|
584
590
|
const requiredScopes = Object.freeze([...new Set((options.requiredScopes ?? []).filter((scope) => scope?.trim()))]);
|
|
585
591
|
const introspect = async (token, key, signal) => {
|
|
@@ -609,7 +615,7 @@ function createAuthioVerifier(options) {
|
|
|
609
615
|
}
|
|
610
616
|
let keySet;
|
|
611
617
|
try {
|
|
612
|
-
keySet = await signingKeys(signal);
|
|
618
|
+
keySet = withClientSecret(await signingKeys(signal), clientSecret);
|
|
613
619
|
} catch (error) {
|
|
614
620
|
throw new AuthioBearerError(AuthioBearerErrorDescriptions.SigningKeysUnavailable, { cause: error, reason: "signing_keys_unavailable" });
|
|
615
621
|
}
|
|
@@ -623,6 +629,8 @@ function createAuthioVerifier(options) {
|
|
|
623
629
|
now: connection.clock.now()
|
|
624
630
|
}));
|
|
625
631
|
} catch (error) {
|
|
632
|
+
if (error instanceof AuthioTokenValidationError && error.reason === "invalid_algorithm" && symmetric.length === 0 && isSymmetricToken(signed))
|
|
633
|
+
throw new AuthioBearerError(AuthioBearerErrorDescriptions.Symmetric, { cause: error, reason: error.reason });
|
|
626
634
|
if (error instanceof AuthioTokenValidationError) throw new AuthioBearerError(error.message, { cause: error, reason: error.reason });
|
|
627
635
|
throw error;
|
|
628
636
|
}
|
|
@@ -704,6 +712,35 @@ function resolveResourceMetadata(setting, resource) {
|
|
|
704
712
|
throw new AuthioConfigurationError("resourceMetadata: true needs the API's resource identifier: set options.resource, or pass the metadata URL.");
|
|
705
713
|
return protectedResourceMetadataUrl(resource);
|
|
706
714
|
}
|
|
715
|
+
function resolveAccessTokenAlgorithms(setting) {
|
|
716
|
+
const algorithms = [...new Set((typeof setting === "string" ? [setting] : [...setting ?? []]).map((algorithm) => algorithm.trim()).filter(Boolean))];
|
|
717
|
+
const known = [...AuthioAsymmetricAlgorithms, ...AuthioHmacAlgorithms];
|
|
718
|
+
for (const algorithm of algorithms)
|
|
719
|
+
if (!known.includes(algorithm))
|
|
720
|
+
throw new AuthioConfigurationError(`The Authio accessTokenSignedResponseAlg '${algorithm}' is not one of ${known.join(", ")}.`);
|
|
721
|
+
return algorithms.length > 0 ? algorithms : void 0;
|
|
722
|
+
}
|
|
723
|
+
function resolveClientSecret(options, symmetric) {
|
|
724
|
+
const given = options.client;
|
|
725
|
+
const source = given && !isClient(given) ? given.clientSecret : void 0;
|
|
726
|
+
if (!source)
|
|
727
|
+
throw new AuthioConfigurationError(
|
|
728
|
+
`accessTokenSignedResponseAlg ${symmetric.join(", ")} is verified with the client secret: pass options.client as { clientId, clientSecret } (an AuthioClient does not expose its secret).`
|
|
729
|
+
);
|
|
730
|
+
if (typeof source === "string") for (const algorithm of symmetric) requireHmacSecretLength(algorithm, new TextEncoder().encode(source).length);
|
|
731
|
+
return async () => {
|
|
732
|
+
const value = typeof source === "function" ? await source() : source;
|
|
733
|
+
if (typeof value !== "string" || !value) throw new AuthioConfigurationError("The Authio clientSecret function returned no secret.");
|
|
734
|
+
return value;
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
function isSymmetricToken(token) {
|
|
738
|
+
try {
|
|
739
|
+
return AuthioHmacAlgorithms.includes(String(decodeProtectedHeader(token).alg));
|
|
740
|
+
} catch {
|
|
741
|
+
return false;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
707
744
|
function resolveClient(options) {
|
|
708
745
|
const given = options.client;
|
|
709
746
|
if (!given) return void 0;
|