@hyper.authio/client 0.1.1 → 0.7.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 +102 -9
- package/dist/index.cjs +748 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +742 -110
- package/dist/index.d.ts +742 -110
- package/dist/index.js +725 -49
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,8 @@ var AuthioClaimTypes = {
|
|
|
20
20
|
AuthenticationMethods: "amr",
|
|
21
21
|
IdentityProvider: "idp",
|
|
22
22
|
Nonce: "nonce",
|
|
23
|
+
AccessTokenHash: "at_hash",
|
|
24
|
+
Events: "events",
|
|
23
25
|
OrganizationId: "org_id",
|
|
24
26
|
OrganizationAlias: "org_alias",
|
|
25
27
|
AccountId: "account_id",
|
|
@@ -39,6 +41,12 @@ var AuthioClaimTypes = {
|
|
|
39
41
|
ConsumerName: "consumer_name",
|
|
40
42
|
CredentialId: "credential_id",
|
|
41
43
|
CredentialType: "credential_type",
|
|
44
|
+
CredentialName: "credential_name",
|
|
45
|
+
CredentialRealm: "credential_realm",
|
|
46
|
+
CredentialClient: "credential_client",
|
|
47
|
+
ActorType: "actor_type",
|
|
48
|
+
OwnerUserId: "owner_user_id",
|
|
49
|
+
OwnerUsername: "owner_username",
|
|
42
50
|
UserResourceAccess: "user_resource_access",
|
|
43
51
|
RealmResourceAccess: "realm_resource_access",
|
|
44
52
|
ClientResourceAccess: "client_resource_access",
|
|
@@ -96,7 +104,13 @@ var AuthioOAuthErrors = {
|
|
|
96
104
|
ConnectionSelectionRequired: "connection_selection_required",
|
|
97
105
|
ConnectionPolicyViolation: "connection_policy_violation",
|
|
98
106
|
ServerError: "server_error",
|
|
99
|
-
TemporarilyUnavailable: "temporarily_unavailable"
|
|
107
|
+
TemporarilyUnavailable: "temporarily_unavailable",
|
|
108
|
+
LoginRequired: "login_required",
|
|
109
|
+
InteractionRequired: "interaction_required",
|
|
110
|
+
ConsentRequired: "consent_required",
|
|
111
|
+
AccountSelectionRequired: "account_selection_required",
|
|
112
|
+
InsufficientScope: "insufficient_scope",
|
|
113
|
+
InsufficientUserAuthentication: "insufficient_user_authentication"
|
|
100
114
|
};
|
|
101
115
|
var AuthioWebhookEventTypes = {
|
|
102
116
|
UserCreated: "user_created",
|
|
@@ -113,6 +127,8 @@ var AuthioWebhookEventTypes = {
|
|
|
113
127
|
AdminResourceDeleted: "admin_resource_deleted",
|
|
114
128
|
UserSuspended: "user_suspended",
|
|
115
129
|
UserSuspensionEnded: "user_suspension_ended",
|
|
130
|
+
AttackDetected: "attack_detected",
|
|
131
|
+
CredentialCompromised: "credential_compromised",
|
|
116
132
|
Ping: "ping"
|
|
117
133
|
};
|
|
118
134
|
var AuthioWebhookHeaders = {
|
|
@@ -120,6 +136,8 @@ var AuthioWebhookHeaders = {
|
|
|
120
136
|
Event: "X-Authio-Event",
|
|
121
137
|
EventId: "X-Authio-Event-Id"
|
|
122
138
|
};
|
|
139
|
+
var AuthioCallerKinds = ["user", "client", "consumer", "account_credential"];
|
|
140
|
+
var AuthioPrompts = ["none", "login", "select_account", "consent"];
|
|
123
141
|
var AuthioAsymmetricAlgorithms = [
|
|
124
142
|
"RS256",
|
|
125
143
|
"RS384",
|
|
@@ -133,12 +151,23 @@ var AuthioAsymmetricAlgorithms = [
|
|
|
133
151
|
];
|
|
134
152
|
var AuthioDefaults = {
|
|
135
153
|
DiscoveryPath: "/.well-known/openid-configuration",
|
|
154
|
+
ProtectedResourceMetadataPath: "/.well-known/oauth-protected-resource",
|
|
136
155
|
RedactedValue: "[redacted]",
|
|
137
156
|
PlatformRealm: "authio",
|
|
138
157
|
PlatformClientId: "authio",
|
|
139
158
|
ApiKeyPrefix: "ak_",
|
|
140
159
|
ClientSecretPrefix: "cs_",
|
|
141
160
|
PersonalAccessTokenPrefix: "pat_",
|
|
161
|
+
/** The `client_id` an account credential's client secret is exchanged with: `cred_` and the credential id (format N). */
|
|
162
|
+
CredentialClientIdPrefix: "cred_",
|
|
163
|
+
ApiKeyHeader: "x-api-key",
|
|
164
|
+
RequestIdHeader: "X-Request-Id",
|
|
165
|
+
/** The header `typ` of a back-channel logout token. */
|
|
166
|
+
LogoutTokenType: "logout+jwt",
|
|
167
|
+
/** The event a back-channel logout token carries. */
|
|
168
|
+
BackchannelLogoutEvent: "http://schemas.openid.net/event/backchannel-logout",
|
|
169
|
+
/** Seconds. */
|
|
170
|
+
ClockSkew: 30,
|
|
142
171
|
/** Seconds. */
|
|
143
172
|
ClientAssertionLifetime: 120,
|
|
144
173
|
/** Seconds. */
|
|
@@ -190,6 +219,47 @@ var AuthioDiscoveryError = class extends AuthioError {
|
|
|
190
219
|
};
|
|
191
220
|
var AuthioConfigurationError = class extends AuthioError {
|
|
192
221
|
};
|
|
222
|
+
var AuthioTokenValidationError = class extends AuthioError {
|
|
223
|
+
/** Why, as a stable code. */
|
|
224
|
+
reason;
|
|
225
|
+
constructor(reason, message, options) {
|
|
226
|
+
super(message, options);
|
|
227
|
+
this.reason = reason;
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
var AuthioCallbackError = class extends AuthioError {
|
|
231
|
+
/** Why, as a stable code. */
|
|
232
|
+
reason;
|
|
233
|
+
constructor(reason, message) {
|
|
234
|
+
super(message);
|
|
235
|
+
this.reason = reason;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
var AuthioAdminError = class extends AuthioError {
|
|
239
|
+
/** The HTTP status. */
|
|
240
|
+
status;
|
|
241
|
+
/** Authio's error code, such as `ValidationFailed` or `RealmNotFound`. */
|
|
242
|
+
errorCode;
|
|
243
|
+
/** The messages, with the field each one is about. */
|
|
244
|
+
notifications;
|
|
245
|
+
/** The request id, from the envelope or the `X-Request-Id` header. */
|
|
246
|
+
requestId;
|
|
247
|
+
/** Seconds to wait before retrying, from `Retry-After` (`429` and `503`). */
|
|
248
|
+
retryAfterSeconds;
|
|
249
|
+
constructor(details) {
|
|
250
|
+
const notifications = details.notifications ?? [];
|
|
251
|
+
const summary = notifications.map((notification) => notification.message).filter(Boolean).join(" ");
|
|
252
|
+
super(
|
|
253
|
+
`The Authio Admin API answered HTTP ${details.status}${details.errorCode ? ` (${details.errorCode})` : ""}${summary ? `: ${summary}` : "."}`,
|
|
254
|
+
{ cause: details.cause }
|
|
255
|
+
);
|
|
256
|
+
this.status = details.status;
|
|
257
|
+
this.errorCode = details.errorCode;
|
|
258
|
+
this.notifications = Object.freeze([...notifications]);
|
|
259
|
+
this.requestId = details.requestId;
|
|
260
|
+
this.retryAfterSeconds = details.retryAfterSeconds;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
193
263
|
|
|
194
264
|
// src/options.ts
|
|
195
265
|
var systemClock = {
|
|
@@ -536,17 +606,22 @@ function createClientAuthenticator(settings, connection) {
|
|
|
536
606
|
`The Authio clientAssertionLifetime must be more than 0 and at most ${AuthioDefaults.MaxClientAssertionLifetime} seconds.`
|
|
537
607
|
);
|
|
538
608
|
const loadKey = method === "private_key_jwt" ? createKeyLoader(settings) : void 0;
|
|
609
|
+
const secret = async () => {
|
|
610
|
+
const value = typeof settings.clientSecret === "function" ? await settings.clientSecret() : settings.clientSecret;
|
|
611
|
+
if (typeof value !== "string" || !value) throw new AuthioConfigurationError("The Authio clientSecret function returned no secret.");
|
|
612
|
+
return value;
|
|
613
|
+
};
|
|
539
614
|
return {
|
|
540
615
|
clientId,
|
|
541
616
|
method,
|
|
542
617
|
async apply(form, headers) {
|
|
543
618
|
switch (method) {
|
|
544
619
|
case "client_secret_basic":
|
|
545
|
-
headers.set("authorization", toBasicAuthorization(clientId,
|
|
620
|
+
headers.set("authorization", toBasicAuthorization(clientId, await secret()));
|
|
546
621
|
return;
|
|
547
622
|
case "client_secret_post":
|
|
548
623
|
form.set("client_id", clientId);
|
|
549
|
-
form.set("client_secret",
|
|
624
|
+
form.set("client_secret", await secret());
|
|
550
625
|
return;
|
|
551
626
|
case "private_key_jwt":
|
|
552
627
|
form.set("client_id", clientId);
|
|
@@ -600,17 +675,20 @@ function createKeyLoader(settings) {
|
|
|
600
675
|
};
|
|
601
676
|
}
|
|
602
677
|
var AuthioKeyManagementAlgorithms = ["RSA-OAEP", "RSA-OAEP-256"];
|
|
678
|
+
var AuthioLegacyKeyManagementAlgorithms = ["RSA1_5"];
|
|
603
679
|
var AuthioContentEncryptionAlgorithms = ["A128CBC-HS256", "A192CBC-HS384", "A256CBC-HS512"];
|
|
604
680
|
var rsaAlgorithmIdentifier = [48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0];
|
|
605
681
|
function isAuthioTokenEncrypted(token) {
|
|
606
682
|
return typeof token === "string" && token.split(".").length === 5;
|
|
607
683
|
}
|
|
608
|
-
function createAuthioTokenDecrypter(key,
|
|
684
|
+
function createAuthioTokenDecrypter(key, options = {}) {
|
|
685
|
+
const settings = typeof options === "string" ? { setting: options } : options;
|
|
686
|
+
const setting = settings.setting ?? "clientEncryptionKey";
|
|
609
687
|
const imported = /* @__PURE__ */ new Map();
|
|
610
688
|
const importFor = (algorithm) => {
|
|
611
689
|
let pending = imported.get(algorithm);
|
|
612
690
|
if (!pending) {
|
|
613
|
-
pending = importDecryptionKey(
|
|
691
|
+
pending = Promise.resolve(typeof key === "function" ? key() : key).then((value) => importDecryptionKey(value, algorithm, setting));
|
|
614
692
|
imported.set(algorithm, pending);
|
|
615
693
|
pending.catch(() => imported.delete(algorithm));
|
|
616
694
|
}
|
|
@@ -618,17 +696,59 @@ function createAuthioTokenDecrypter(key, setting = "clientEncryptionKey") {
|
|
|
618
696
|
};
|
|
619
697
|
return async (token) => {
|
|
620
698
|
if (!isAuthioTokenEncrypted(token)) return token;
|
|
621
|
-
|
|
622
|
-
|
|
699
|
+
const algorithm = algorithmOf(token);
|
|
700
|
+
if (AuthioLegacyKeyManagementAlgorithms.includes(algorithm))
|
|
701
|
+
throw new AuthioTokenValidationError(
|
|
702
|
+
"legacy_algorithm",
|
|
703
|
+
settings.allowLegacyEncryptionAlgorithms ? "The token is encrypted with RSA1_5 (PKCS#1 v1.5), which Node and WebCrypto cannot decrypt even with allowLegacyEncryptionAlgorithms; register RSA-OAEP-256 as the client's encryption algorithm in Authio." : "The token is encrypted with the legacy RSA1_5 algorithm, which is refused; register RSA-OAEP-256 as the client's encryption algorithm in Authio."
|
|
704
|
+
);
|
|
705
|
+
if (!AuthioKeyManagementAlgorithms.includes(algorithm))
|
|
706
|
+
throw new AuthioTokenValidationError(
|
|
707
|
+
"decryption_failed",
|
|
708
|
+
`The token is encrypted with '${algorithm}'; Authio encrypts with ${AuthioKeyManagementAlgorithms.join(" or ")}.`
|
|
709
|
+
);
|
|
710
|
+
if (key === void 0 || key === null || key === "")
|
|
711
|
+
throw new AuthioTokenValidationError(
|
|
712
|
+
"decryption_failed",
|
|
623
713
|
`Authio encrypted this token to the client's own key; set ${setting} to the private key Authio showed when it generated the pair.`
|
|
624
714
|
);
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
715
|
+
try {
|
|
716
|
+
const { plaintext } = await jose.compactDecrypt(token, (header) => importFor(header.alg), {
|
|
717
|
+
keyManagementAlgorithms: [...AuthioKeyManagementAlgorithms],
|
|
718
|
+
contentEncryptionAlgorithms: [...AuthioContentEncryptionAlgorithms]
|
|
719
|
+
});
|
|
720
|
+
return new TextDecoder().decode(plaintext);
|
|
721
|
+
} catch (error) {
|
|
722
|
+
if (error instanceof AuthioConfigurationError) throw error;
|
|
723
|
+
if (error instanceof jose.errors.JOSEError)
|
|
724
|
+
throw new AuthioTokenValidationError("decryption_failed", `The token could not be decrypted with ${setting}: ${error.message}`, { cause: error });
|
|
725
|
+
throw error;
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
function encryptionKeyFromFile(path, setting = "clientEncryptionKeyPath") {
|
|
730
|
+
let current;
|
|
731
|
+
return async () => {
|
|
732
|
+
const nodeCrypto = await loadNodeCrypto();
|
|
733
|
+
if (!nodeCrypto) throw new AuthioConfigurationError(`${setting} needs Node; pass clientEncryptionKey instead.`);
|
|
734
|
+
const { readFile, stat } = await import('fs/promises');
|
|
735
|
+
let written;
|
|
736
|
+
try {
|
|
737
|
+
written = (await stat(path)).mtimeMs;
|
|
738
|
+
} catch (error) {
|
|
739
|
+
throw new AuthioConfigurationError(`${setting} '${path}' could not be read.`, { cause: error });
|
|
740
|
+
}
|
|
741
|
+
if (!current || current.written !== written) current = { written, key: readFile(path, "utf8") };
|
|
742
|
+
return current.key;
|
|
630
743
|
};
|
|
631
744
|
}
|
|
745
|
+
function algorithmOf(token) {
|
|
746
|
+
try {
|
|
747
|
+
return String(jose.decodeProtectedHeader(token).alg ?? "");
|
|
748
|
+
} catch (error) {
|
|
749
|
+
throw new AuthioTokenValidationError("decryption_failed", "The encrypted token's header could not be read.", { cause: error });
|
|
750
|
+
}
|
|
751
|
+
}
|
|
632
752
|
async function importDecryptionKey(key, algorithm, setting) {
|
|
633
753
|
try {
|
|
634
754
|
if (typeof key !== "string") return await importPrivateJwk(key, algorithm, setting);
|
|
@@ -668,14 +788,295 @@ function toBase64(bytes) {
|
|
|
668
788
|
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
669
789
|
return btoa(binary);
|
|
670
790
|
}
|
|
791
|
+
function createAuthioSigningKeys(connection, discovery) {
|
|
792
|
+
let keys;
|
|
793
|
+
return async (signal) => {
|
|
794
|
+
const document = await discovery.get(signal);
|
|
795
|
+
const uri = document.jwks_uri;
|
|
796
|
+
if (!uri) throw new AuthioError("The Authio discovery document has no jwks_uri.");
|
|
797
|
+
if (!keys || keys.uri !== uri)
|
|
798
|
+
keys = { uri, set: jose.createRemoteJWKSet(new URL(uri), { [jose.customFetch]: connection.fetch, timeoutDuration: connection.httpTimeoutMs }) };
|
|
799
|
+
return keys.set;
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
async function verifyAuthioJwt(token, keys, expected) {
|
|
803
|
+
if (!looksLikeJws(token))
|
|
804
|
+
throw new AuthioTokenValidationError("reference_token", "The token is not a JWT; only introspection can resolve it ('introspectionOnly').");
|
|
805
|
+
try {
|
|
806
|
+
const { payload, protectedHeader } = await jose.jwtVerify(token, keys, {
|
|
807
|
+
issuer: expected.issuer,
|
|
808
|
+
audience: [...expected.audiences],
|
|
809
|
+
algorithms: [...expected.algorithms && expected.algorithms.length > 0 ? expected.algorithms : AuthioAsymmetricAlgorithms],
|
|
810
|
+
clockTolerance: expected.clockSkew ?? AuthioDefaults.ClockSkew,
|
|
811
|
+
requiredClaims: [.../* @__PURE__ */ new Set(["exp", ...expected.requiredClaims ?? []])],
|
|
812
|
+
currentDate: new Date(expected.now)
|
|
813
|
+
});
|
|
814
|
+
return { payload, header: protectedHeader };
|
|
815
|
+
} catch (error) {
|
|
816
|
+
throw toTokenValidationError(error, expected.audiences, token);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
function toTokenValidationError(error, audiences, token) {
|
|
820
|
+
if (error instanceof AuthioTokenValidationError) return error;
|
|
821
|
+
const refuse = (reason, message) => new AuthioTokenValidationError(reason, message, { cause: error });
|
|
822
|
+
if (error instanceof jose.errors.JWTExpired) {
|
|
823
|
+
const exp = error.payload.exp;
|
|
824
|
+
return refuse("expired", typeof exp === "number" ? `The token expired at '${new Date(exp * 1e3).toISOString()}'` : "The token has expired");
|
|
825
|
+
}
|
|
826
|
+
if (error instanceof jose.errors.JWTClaimValidationFailed) {
|
|
827
|
+
switch (error.claim) {
|
|
828
|
+
case "aud":
|
|
829
|
+
return refuse("invalid_audience", describeAudience([error.payload.aud ?? []].flat(), audiences));
|
|
830
|
+
case "iss":
|
|
831
|
+
return refuse("invalid_issuer", `The issuer '${error.payload.iss ?? ""}' is invalid`);
|
|
832
|
+
case "nbf":
|
|
833
|
+
return refuse(
|
|
834
|
+
"not_yet_valid",
|
|
835
|
+
typeof error.payload.nbf === "number" ? `The token is not valid before '${new Date(error.payload.nbf * 1e3).toISOString()}'` : "The token is not valid yet"
|
|
836
|
+
);
|
|
837
|
+
case "exp":
|
|
838
|
+
return refuse("expired", "The token has no expiration");
|
|
839
|
+
default:
|
|
840
|
+
return refuse("malformed", `The token's ${error.claim} claim is invalid`);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
if (error instanceof jose.errors.JWSSignatureVerificationFailed) return refuse("invalid_signature", "The signature is invalid");
|
|
844
|
+
if (error instanceof jose.errors.JWKSNoMatchingKey || error instanceof jose.errors.JWKSMultipleMatchingKeys)
|
|
845
|
+
return refuse("invalid_signature", "The signature key was not found");
|
|
846
|
+
if (error instanceof jose.errors.JOSEAlgNotAllowed) return refuse("invalid_algorithm", `The algorithm '${algorithmOf2(token)}' is not available`);
|
|
847
|
+
if (error instanceof jose.errors.JWKSTimeout || error instanceof jose.errors.JWKSInvalid)
|
|
848
|
+
return refuse("signing_keys_unavailable", "Authio could not be reached to read its signing keys.");
|
|
849
|
+
if (error instanceof jose.errors.JWTInvalid || error instanceof jose.errors.JWSInvalid) return refuse("malformed", "The token is malformed");
|
|
850
|
+
if (error instanceof jose.errors.JOSEError) return refuse("malformed", error.message);
|
|
851
|
+
return refuse("signing_keys_unavailable", "Authio could not be reached to read its signing keys.");
|
|
852
|
+
}
|
|
853
|
+
async function computeAuthioTokenHash(value, algorithm) {
|
|
854
|
+
const size = /(\d{3})$/.exec(algorithm)?.[1];
|
|
855
|
+
const hash = size === "384" ? "SHA-384" : size === "512" ? "SHA-512" : "SHA-256";
|
|
856
|
+
const digest = new Uint8Array(await crypto.subtle.digest(hash, new TextEncoder().encode(value)));
|
|
857
|
+
return toBase64Url(digest.subarray(0, digest.length / 2));
|
|
858
|
+
}
|
|
859
|
+
async function validateAuthioIdToken(token, context, expectations = {}) {
|
|
860
|
+
const signed = isAuthioTokenEncrypted(token) ? await context.decrypt(token) : token;
|
|
861
|
+
const { payload, header } = await verifyAuthioJwt(signed, context.keys, {
|
|
862
|
+
issuer: context.issuer,
|
|
863
|
+
audiences: [context.clientId],
|
|
864
|
+
algorithms: context.algorithms,
|
|
865
|
+
clockSkew: context.clockSkew,
|
|
866
|
+
now: context.now,
|
|
867
|
+
requiredClaims: ["sub", "iat"]
|
|
868
|
+
});
|
|
869
|
+
const type = typeof payload.typ === "string" ? payload.typ : void 0;
|
|
870
|
+
if (type !== void 0 && type.toLowerCase() !== "id")
|
|
871
|
+
throw new AuthioTokenValidationError("not_id_token", `Only ID tokens are accepted; this token's typ is '${type}'.`);
|
|
872
|
+
const audiences = [payload.aud ?? []].flat();
|
|
873
|
+
if (audiences.length > 1 && payload.azp !== context.clientId)
|
|
874
|
+
throw new AuthioTokenValidationError("invalid_audience", `The ID token has several audiences and its azp is not '${context.clientId}'.`);
|
|
875
|
+
if (expectations.nonce !== void 0 && payload[AuthioClaimTypes.Nonce] !== expectations.nonce)
|
|
876
|
+
throw new AuthioTokenValidationError(
|
|
877
|
+
"invalid_nonce",
|
|
878
|
+
payload[AuthioClaimTypes.Nonce] === void 0 ? "The ID token carries no nonce, although one was sent." : "The ID token's nonce is not the one sent."
|
|
879
|
+
);
|
|
880
|
+
const atHash = payload[AuthioClaimTypes.AccessTokenHash];
|
|
881
|
+
if (expectations.accessToken !== void 0 && typeof atHash === "string") {
|
|
882
|
+
const expected = await computeAuthioTokenHash(expectations.accessToken, String(header.alg ?? ""));
|
|
883
|
+
if (expected !== atHash) throw new AuthioTokenValidationError("invalid_at_hash", "The ID token's at_hash does not match the access token.");
|
|
884
|
+
}
|
|
885
|
+
if (expectations.maxAge !== void 0) {
|
|
886
|
+
const authTime = payload[AuthioClaimTypes.AuthTime];
|
|
887
|
+
const skew = context.clockSkew ?? AuthioDefaults.ClockSkew;
|
|
888
|
+
if (typeof authTime !== "number")
|
|
889
|
+
throw new AuthioTokenValidationError("authentication_too_old", "max_age was requested, and the ID token carries no auth_time.");
|
|
890
|
+
if ((authTime + expectations.maxAge + skew) * 1e3 < context.now)
|
|
891
|
+
throw new AuthioTokenValidationError(
|
|
892
|
+
"authentication_too_old",
|
|
893
|
+
`The person signed in at '${new Date(authTime * 1e3).toISOString()}', more than ${expectations.maxAge} seconds ago.`
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
return payload;
|
|
897
|
+
}
|
|
898
|
+
async function validateAuthioLogoutToken(token, context) {
|
|
899
|
+
const signed = isAuthioTokenEncrypted(token) ? await context.decrypt(token) : token;
|
|
900
|
+
const invalid = (message) => new AuthioTokenValidationError("invalid_logout_token", message);
|
|
901
|
+
let headerType;
|
|
902
|
+
try {
|
|
903
|
+
const header = jose.decodeProtectedHeader(signed);
|
|
904
|
+
headerType = typeof header.typ === "string" ? header.typ.toLowerCase() : void 0;
|
|
905
|
+
} catch (error) {
|
|
906
|
+
throw new AuthioTokenValidationError("malformed", "The logout token is malformed", { cause: error });
|
|
907
|
+
}
|
|
908
|
+
const { payload } = await verifyAuthioJwt(signed, context.keys, {
|
|
909
|
+
issuer: context.issuer,
|
|
910
|
+
audiences: [context.clientId],
|
|
911
|
+
algorithms: context.algorithms,
|
|
912
|
+
clockSkew: context.clockSkew,
|
|
913
|
+
now: context.now,
|
|
914
|
+
requiredClaims: ["iat"]
|
|
915
|
+
});
|
|
916
|
+
if (headerType !== AuthioDefaults.LogoutTokenType && headerType !== `application/${AuthioDefaults.LogoutTokenType}`)
|
|
917
|
+
throw invalid(`The token's header typ is '${headerType ?? ""}', not '${AuthioDefaults.LogoutTokenType}'.`);
|
|
918
|
+
const events = payload[AuthioClaimTypes.Events];
|
|
919
|
+
if (!events || typeof events !== "object" || Array.isArray(events) || typeof events[AuthioDefaults.BackchannelLogoutEvent] !== "object")
|
|
920
|
+
throw invalid("The token carries no back-channel logout event.");
|
|
921
|
+
if (payload[AuthioClaimTypes.Nonce] !== void 0) throw invalid("A logout token must not carry a nonce.");
|
|
922
|
+
const subject = typeof payload.sub === "string" && payload.sub ? payload.sub : void 0;
|
|
923
|
+
const sessionId = typeof payload.sid === "string" && payload.sid ? payload.sid : void 0;
|
|
924
|
+
if (!subject && !sessionId) throw invalid("The logout token names neither a subject nor a session.");
|
|
925
|
+
return Object.freeze({ subject, sessionId, tokenId: typeof payload.jti === "string" ? payload.jti : void 0, claims: Object.freeze({ ...payload }) });
|
|
926
|
+
}
|
|
927
|
+
function describeAudience(tokenAudiences, audiences) {
|
|
928
|
+
const known = tokenAudiences.length === 0 ? "" : ` ${tokenAudiences.map((audience) => `'${audience}'`).join(" and ")}`;
|
|
929
|
+
const accepted = audiences.map((audience) => `'${audience}'`).join(" or ");
|
|
930
|
+
return audiences.length === 1 ? `The token is not addressed to this API: its audience${known} is not ${accepted}. Request the token for that resource (RFC 8707).` : `The token is not addressed to this API: its audience${known} is not ${accepted}. Request the token for one of those resources (RFC 8707).`;
|
|
931
|
+
}
|
|
932
|
+
function looksLikeJws(token) {
|
|
933
|
+
const parts = token.split(".");
|
|
934
|
+
return parts.length === 3 && parts[0].length > 0;
|
|
935
|
+
}
|
|
936
|
+
function algorithmOf2(token) {
|
|
937
|
+
try {
|
|
938
|
+
return String(jose.decodeProtectedHeader(token).alg ?? "");
|
|
939
|
+
} catch {
|
|
940
|
+
return "";
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
function toBase64Url(bytes) {
|
|
944
|
+
let binary = "";
|
|
945
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
946
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// src/authorization.ts
|
|
950
|
+
async function createAuthioPkce(method = "S256") {
|
|
951
|
+
if (method !== "S256" && method !== "plain") throw new TypeError(`The PKCE method '${String(method)}' is not S256 or plain.`);
|
|
952
|
+
const codeVerifier = randomValue(32);
|
|
953
|
+
const codeChallenge = method === "plain" ? codeVerifier : toBase64Url2(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(codeVerifier))));
|
|
954
|
+
return { codeVerifier, codeChallenge, codeChallengeMethod: method };
|
|
955
|
+
}
|
|
956
|
+
function randomValue(bytes = 32) {
|
|
957
|
+
return toBase64Url2(crypto.getRandomValues(new Uint8Array(bytes)));
|
|
958
|
+
}
|
|
959
|
+
async function buildAuthorizationUrl(endpoint, clientId, request) {
|
|
960
|
+
const pkce = await createAuthioPkce(request.codeChallengeMethod ?? "S256");
|
|
961
|
+
const state = request.state?.trim() || randomValue();
|
|
962
|
+
const nonce = request.nonce?.trim() || randomValue();
|
|
963
|
+
if (request.maxAge !== void 0 && (!Number.isInteger(request.maxAge) || request.maxAge < 0))
|
|
964
|
+
throw new RangeError("maxAge must be a non-negative whole number of seconds.");
|
|
965
|
+
const display = request.popup ? "popup" : request.display;
|
|
966
|
+
const responseMode = request.popup ? "web_message" : request.responseMode;
|
|
967
|
+
const url = new URL(endpoint);
|
|
968
|
+
const parameters = {
|
|
969
|
+
...request.extraParameters,
|
|
970
|
+
response_type: "code",
|
|
971
|
+
client_id: clientId,
|
|
972
|
+
redirect_uri: request.redirectUri,
|
|
973
|
+
scope: toScopeString(request.scopes) ?? "openid",
|
|
974
|
+
state,
|
|
975
|
+
nonce,
|
|
976
|
+
code_challenge: pkce.codeChallenge,
|
|
977
|
+
code_challenge_method: pkce.codeChallengeMethod,
|
|
978
|
+
resource: request.resource,
|
|
979
|
+
organization: request.organization,
|
|
980
|
+
prompt: joined(request.prompt),
|
|
981
|
+
max_age: request.maxAge?.toString(),
|
|
982
|
+
acr_values: joined(request.acrValues),
|
|
983
|
+
login_hint: request.loginHint,
|
|
984
|
+
id_token_hint: request.idTokenHint,
|
|
985
|
+
ui_locales: joined(request.uiLocales),
|
|
986
|
+
display,
|
|
987
|
+
response_mode: responseMode,
|
|
988
|
+
idp_hint: request.idpHint
|
|
989
|
+
};
|
|
990
|
+
for (const [name, value] of Object.entries(parameters)) if (value !== void 0 && value !== "") url.searchParams.set(name, value);
|
|
991
|
+
return {
|
|
992
|
+
url: url.toString(),
|
|
993
|
+
transaction: Object.freeze({
|
|
994
|
+
state,
|
|
995
|
+
nonce,
|
|
996
|
+
codeVerifier: pkce.codeVerifier,
|
|
997
|
+
codeChallengeMethod: pkce.codeChallengeMethod,
|
|
998
|
+
redirectUri: request.redirectUri,
|
|
999
|
+
...request.resource ? { resource: request.resource } : {},
|
|
1000
|
+
...request.maxAge !== void 0 ? { maxAge: request.maxAge } : {}
|
|
1001
|
+
})
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
function readCallbackParameters(callback) {
|
|
1005
|
+
if (callback instanceof URLSearchParams) return Object.fromEntries(callback);
|
|
1006
|
+
if (callback instanceof URL) return Object.fromEntries(callback.searchParams);
|
|
1007
|
+
if (typeof callback === "string") {
|
|
1008
|
+
const text = callback.trim();
|
|
1009
|
+
const query = /^[a-z][a-z0-9+.-]*:/i.test(text) ? new URL(text).search : text.slice(text.indexOf("?") + 1);
|
|
1010
|
+
return Object.fromEntries(new URLSearchParams(query));
|
|
1011
|
+
}
|
|
1012
|
+
const parameters = {};
|
|
1013
|
+
for (const [name, value] of Object.entries(callback ?? {})) {
|
|
1014
|
+
const first = Array.isArray(value) ? value[0] : value;
|
|
1015
|
+
if (typeof first === "string") parameters[name] = first;
|
|
1016
|
+
}
|
|
1017
|
+
return parameters;
|
|
1018
|
+
}
|
|
1019
|
+
function parseAuthioBearerChallenge(header) {
|
|
1020
|
+
if (!header || !/^\s*bearer(\s|$)/i.test(header)) return void 0;
|
|
1021
|
+
const values = {};
|
|
1022
|
+
for (const match of header.matchAll(/([a-zA-Z_]+)\s*=\s*"((?:[^"\\]|\\.)*)"|([a-zA-Z_]+)\s*=\s*([^,\s]+)/g)) {
|
|
1023
|
+
const name = (match[1] ?? match[3] ?? "").toLowerCase();
|
|
1024
|
+
values[name] ??= (match[2] ?? match[4] ?? "").replace(/\\(.)/g, "$1");
|
|
1025
|
+
}
|
|
1026
|
+
const maxAge = values.max_age !== void 0 && /^\d+$/.test(values.max_age) ? Number(values.max_age) : void 0;
|
|
1027
|
+
return {
|
|
1028
|
+
error: values.error,
|
|
1029
|
+
errorDescription: values.error_description,
|
|
1030
|
+
scope: values.scope,
|
|
1031
|
+
acrValues: values.acr_values,
|
|
1032
|
+
maxAge,
|
|
1033
|
+
resourceMetadata: values.resource_metadata
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
function joined(value) {
|
|
1037
|
+
if (value === void 0) return void 0;
|
|
1038
|
+
const text = (typeof value === "string" ? [value] : [...value]).map((item) => item.trim()).filter(Boolean).join(" ");
|
|
1039
|
+
return text || void 0;
|
|
1040
|
+
}
|
|
1041
|
+
function toBase64Url2(bytes) {
|
|
1042
|
+
let binary = "";
|
|
1043
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
1044
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// src/client.ts
|
|
671
1048
|
function createAuthioClient(options) {
|
|
672
1049
|
const connection = resolveConnection(options);
|
|
673
1050
|
const discovery = createAuthioDiscoveryClient(connection);
|
|
674
1051
|
const authenticator = createClientAuthenticator(options, connection);
|
|
675
1052
|
const refreshSkewMs = seconds(options.tokenRefreshSkew, AuthioDefaults.TokenRefreshSkew, "tokenRefreshSkew");
|
|
676
|
-
const
|
|
1053
|
+
const clockSkew = seconds(options.clockSkew, AuthioDefaults.ClockSkew, "clockSkew") / 1e3;
|
|
1054
|
+
const decrypt = createAuthioTokenDecrypter(resolveEncryptionKey(options), {
|
|
1055
|
+
setting: options.clientEncryptionKey === void 0 && options.clientEncryptionKeyPath ? "clientEncryptionKeyPath" : "clientEncryptionKey",
|
|
1056
|
+
allowLegacyEncryptionAlgorithms: options.allowLegacyEncryptionAlgorithms
|
|
1057
|
+
});
|
|
1058
|
+
const signingKeys = createAuthioSigningKeys(connection, discovery);
|
|
677
1059
|
const tokens = createTokenClient(connection, discovery, authenticator);
|
|
678
1060
|
const clientCredentials = createClientCredentialsCache(connection, authenticator.clientId, tokens, refreshSkewMs);
|
|
1061
|
+
const validationContext = async (signal) => ({
|
|
1062
|
+
keys: await signingKeys(signal),
|
|
1063
|
+
decrypt,
|
|
1064
|
+
issuer: connection.issuer,
|
|
1065
|
+
clientId: authenticator.clientId,
|
|
1066
|
+
algorithms: options.validAlgorithms,
|
|
1067
|
+
clockSkew,
|
|
1068
|
+
now: connection.clock.now()
|
|
1069
|
+
});
|
|
1070
|
+
const validateIdToken = async (idToken, expectations = {}) => {
|
|
1071
|
+
requireValue(idToken, "idToken");
|
|
1072
|
+
return validateAuthioIdToken(idToken, await validationContext(expectations.signal), expectations);
|
|
1073
|
+
};
|
|
1074
|
+
const createAuthorizationUrl = async (request = {}) => {
|
|
1075
|
+
const redirectUri = request.redirectUri ?? options.redirectUri;
|
|
1076
|
+
requireValue(redirectUri, "redirectUri");
|
|
1077
|
+
const document = await discovery.get(request.signal);
|
|
1078
|
+
return buildAuthorizationUrl(requireEndpoint(document, "authorization_endpoint"), authenticator.clientId, { ...request, redirectUri });
|
|
1079
|
+
};
|
|
679
1080
|
return {
|
|
680
1081
|
issuer: connection.issuer,
|
|
681
1082
|
clientId: authenticator.clientId,
|
|
@@ -718,7 +1119,12 @@ function createAuthioClient(options) {
|
|
|
718
1119
|
);
|
|
719
1120
|
const mediaType = response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase();
|
|
720
1121
|
if (response.ok && mediaType === "application/jwt")
|
|
721
|
-
return readUserInfoJwt((await response.text()).trim(), document, connection, authenticator.clientId, decrypt,
|
|
1122
|
+
return readUserInfoJwt((await response.text()).trim(), document, connection, authenticator.clientId, decrypt, signingKeys, {
|
|
1123
|
+
algorithms: options.validAlgorithms,
|
|
1124
|
+
clockSkew,
|
|
1125
|
+
status: response.status,
|
|
1126
|
+
signal: callOptions.signal
|
|
1127
|
+
});
|
|
722
1128
|
if (!response.ok) {
|
|
723
1129
|
const body = await response.text();
|
|
724
1130
|
const challenge = toErrorResponse(body) ? void 0 : readBearerChallenge(response);
|
|
@@ -726,6 +1132,66 @@ function createAuthioClient(options) {
|
|
|
726
1132
|
}
|
|
727
1133
|
return readProtocolJson(response);
|
|
728
1134
|
},
|
|
1135
|
+
createAuthorizationUrl,
|
|
1136
|
+
async handleAuthorizationCallback(callback, transaction, callOptions = {}) {
|
|
1137
|
+
if (!transaction?.state || !transaction.redirectUri) throw new TypeError("transaction is required: keep what createAuthorizationUrl returned.");
|
|
1138
|
+
const parameters = readCallbackParameters(callback);
|
|
1139
|
+
if (parameters.state !== transaction.state)
|
|
1140
|
+
throw new AuthioCallbackError("state_mismatch", "The callback's state is not the one this sign-in was started with.");
|
|
1141
|
+
const document = await discovery.get(callOptions.signal);
|
|
1142
|
+
if (parameters.iss !== void 0) {
|
|
1143
|
+
if (parameters.iss.replace(/\/+$/, "") !== connection.issuer)
|
|
1144
|
+
throw new AuthioCallbackError("issuer_mismatch", `The callback comes from the issuer '${parameters.iss}', not '${connection.issuer}' (RFC 9207).`);
|
|
1145
|
+
} else if (document.authorization_response_iss_parameter_supported === true) {
|
|
1146
|
+
throw new AuthioCallbackError("missing_issuer", "The callback carries no iss parameter, although Authio sends one (RFC 9207).");
|
|
1147
|
+
}
|
|
1148
|
+
if (parameters.error) throw new AuthioProtocolError(parameters.error, parameters.error_description, parameters.error_code);
|
|
1149
|
+
if (!parameters.code) throw new AuthioCallbackError("missing_code", "The callback carries neither a code nor an error.");
|
|
1150
|
+
const answer = await tokens.authorizationCode({
|
|
1151
|
+
code: parameters.code,
|
|
1152
|
+
redirectUri: transaction.redirectUri,
|
|
1153
|
+
codeVerifier: transaction.codeVerifier,
|
|
1154
|
+
resource: transaction.resource,
|
|
1155
|
+
signal: callOptions.signal
|
|
1156
|
+
});
|
|
1157
|
+
const idToken = answer.id_token ? await validateIdToken(answer.id_token, {
|
|
1158
|
+
nonce: transaction.nonce,
|
|
1159
|
+
accessToken: answer.access_token,
|
|
1160
|
+
maxAge: transaction.maxAge,
|
|
1161
|
+
signal: callOptions.signal
|
|
1162
|
+
}) : void 0;
|
|
1163
|
+
return { tokens: answer, idToken };
|
|
1164
|
+
},
|
|
1165
|
+
validateIdToken,
|
|
1166
|
+
async validateLogoutToken(logoutToken, callOptions = {}) {
|
|
1167
|
+
requireValue(logoutToken, "logoutToken");
|
|
1168
|
+
return validateAuthioLogoutToken(logoutToken, await validationContext(callOptions.signal));
|
|
1169
|
+
},
|
|
1170
|
+
async createEndSessionUrl(request = {}) {
|
|
1171
|
+
const document = await discovery.get(request.signal);
|
|
1172
|
+
const url = new URL(requireEndpoint(document, "end_session_endpoint"));
|
|
1173
|
+
const locales = request.uiLocales === void 0 ? void 0 : (typeof request.uiLocales === "string" ? [request.uiLocales] : [...request.uiLocales]).join(" ");
|
|
1174
|
+
const parameters = {
|
|
1175
|
+
id_token_hint: request.idTokenHint,
|
|
1176
|
+
post_logout_redirect_uri: request.postLogoutRedirectUri ?? options.postLogoutRedirectUri,
|
|
1177
|
+
state: request.state,
|
|
1178
|
+
ui_locales: locales,
|
|
1179
|
+
client_id: request.includeClientId === false ? void 0 : authenticator.clientId
|
|
1180
|
+
};
|
|
1181
|
+
for (const [name, value] of Object.entries(parameters)) if (value) url.searchParams.set(name, value);
|
|
1182
|
+
return url.toString();
|
|
1183
|
+
},
|
|
1184
|
+
async createStepUpAuthorizationUrl(challenge, request = {}) {
|
|
1185
|
+
const parsed = typeof challenge === "string" ? parseAuthioBearerChallenge(challenge) : challenge;
|
|
1186
|
+
if (!parsed || parsed.error !== AuthioOAuthErrors.InsufficientUserAuthentication)
|
|
1187
|
+
throw new TypeError("The challenge is not an insufficient_user_authentication bearer challenge.");
|
|
1188
|
+
return createAuthorizationUrl({
|
|
1189
|
+
...request,
|
|
1190
|
+
acrValues: request.acrValues ?? parsed.acrValues,
|
|
1191
|
+
maxAge: request.maxAge ?? parsed.maxAge,
|
|
1192
|
+
prompt: request.prompt ?? (parsed.maxAge === 0 ? "login" : void 0)
|
|
1193
|
+
});
|
|
1194
|
+
},
|
|
729
1195
|
clientCredentialsFetch(fetchOptions = {}) {
|
|
730
1196
|
const inner = fetchOptions.fetch ?? connection.fetch;
|
|
731
1197
|
const key = { scopes: fetchOptions.scopes, resource: fetchOptions.resource };
|
|
@@ -747,8 +1213,8 @@ function createAuthioClient(options) {
|
|
|
747
1213
|
};
|
|
748
1214
|
}
|
|
749
1215
|
function createTokenClient(connection, discovery, authenticator) {
|
|
750
|
-
const requestToken = async (parameters, signal) => {
|
|
751
|
-
const response = await postForm(connection, discovery, authenticator, "token_endpoint", parameters, signal);
|
|
1216
|
+
const requestToken = async (parameters, signal, authenticate = true) => {
|
|
1217
|
+
const response = await postForm(connection, discovery, authenticate ? authenticator : void 0, "token_endpoint", parameters, signal);
|
|
752
1218
|
const token = await readProtocolJson(response);
|
|
753
1219
|
return redact(token, ["access_token", "refresh_token", "id_token"]);
|
|
754
1220
|
};
|
|
@@ -764,12 +1230,9 @@ function createTokenClient(connection, discovery, authenticator) {
|
|
|
764
1230
|
return await requestToken({ grant_type: AuthioGrantTypes.DeviceCode, device_code: authorization.device_code }, options.signal);
|
|
765
1231
|
} catch (error) {
|
|
766
1232
|
if (!(error instanceof AuthioProtocolError)) throw error;
|
|
767
|
-
|
|
768
|
-
if (
|
|
769
|
-
|
|
770
|
-
continue;
|
|
771
|
-
}
|
|
772
|
-
throw error;
|
|
1233
|
+
const outcome = toDevicePollOutcome(error.error);
|
|
1234
|
+
if (outcome.outcome === "failed") throw error;
|
|
1235
|
+
wait += outcome.intervalIncrease * 1e3;
|
|
773
1236
|
}
|
|
774
1237
|
}
|
|
775
1238
|
};
|
|
@@ -845,6 +1308,20 @@ function createTokenClient(connection, discovery, authenticator) {
|
|
|
845
1308
|
return redact(await readProtocolJson(response), ["device_code"]);
|
|
846
1309
|
},
|
|
847
1310
|
pollDevice,
|
|
1311
|
+
personalAccessToken: (options) => {
|
|
1312
|
+
requireValue(options?.token, "token");
|
|
1313
|
+
return requestToken(
|
|
1314
|
+
{
|
|
1315
|
+
grant_type: AuthioGrantTypes.PersonalAccessToken,
|
|
1316
|
+
client_secret: options.token,
|
|
1317
|
+
client_id: options.clientId,
|
|
1318
|
+
scope: toScopeString(options.scopes),
|
|
1319
|
+
resource: options.resource
|
|
1320
|
+
},
|
|
1321
|
+
options.signal,
|
|
1322
|
+
false
|
|
1323
|
+
);
|
|
1324
|
+
},
|
|
848
1325
|
password: (options) => {
|
|
849
1326
|
requireValue(options?.username, "username");
|
|
850
1327
|
if (typeof options.password !== "string") throw new TypeError("password is required.");
|
|
@@ -902,48 +1379,90 @@ function toConnectionExchangeResult(body, status, provider) {
|
|
|
902
1379
|
const token = parseProtocolJson(body, status);
|
|
903
1380
|
if (typeof token.access_token === "string" && token.access_token)
|
|
904
1381
|
return redact(
|
|
905
|
-
{
|
|
1382
|
+
withoutUndefined({
|
|
906
1383
|
status: "granted",
|
|
907
1384
|
accessToken: token.access_token,
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
},
|
|
1385
|
+
issuedTokenType: textOf(token.issued_token_type),
|
|
1386
|
+
tokenType: textOf(token.token_type),
|
|
1387
|
+
expiresIn: typeof token.expires_in === "number" ? token.expires_in : void 0,
|
|
1388
|
+
scope: textOf(token.scope),
|
|
1389
|
+
connectionId: textOf(token.connection_id),
|
|
1390
|
+
provider: textOf(token.provider) ?? provider,
|
|
1391
|
+
providerType: textOf(token.provider_type),
|
|
1392
|
+
externalAccount: token.external_account && typeof token.external_account === "object" ? token.external_account : void 0
|
|
1393
|
+
}),
|
|
917
1394
|
["accessToken"]
|
|
918
1395
|
);
|
|
919
1396
|
}
|
|
920
1397
|
const error = toErrorResponse(body);
|
|
921
|
-
const
|
|
1398
|
+
const errorDescription = error?.error_description?.trim() ? error.error_description : `Authio answered HTTP ${status} to the connected-account token exchange.`;
|
|
1399
|
+
const errorCode = textOf(error?.error_code);
|
|
922
1400
|
switch (error?.error) {
|
|
923
1401
|
case AuthioOAuthErrors.ConnectionRequired:
|
|
924
|
-
return {
|
|
1402
|
+
return withoutUndefined({
|
|
1403
|
+
status: "required",
|
|
1404
|
+
connectUri: textOf(error.connect_uri),
|
|
1405
|
+
provider: textOf(error.connection_provider) ?? provider,
|
|
1406
|
+
errorCode,
|
|
1407
|
+
errorDescription
|
|
1408
|
+
});
|
|
925
1409
|
case AuthioOAuthErrors.ConnectionSelectionRequired:
|
|
926
|
-
return {
|
|
1410
|
+
return withoutUndefined({
|
|
1411
|
+
status: "selection_required",
|
|
1412
|
+
provider: textOf(error.connection_provider) ?? provider,
|
|
1413
|
+
connections: (Array.isArray(error.connections) ? error.connections : []).map(toConnectionChoice),
|
|
1414
|
+
errorCode,
|
|
1415
|
+
errorDescription
|
|
1416
|
+
});
|
|
927
1417
|
default:
|
|
928
|
-
return {
|
|
1418
|
+
return withoutUndefined({
|
|
1419
|
+
status: "refused",
|
|
1420
|
+
error: error?.error ?? `http_${status}`,
|
|
1421
|
+
errorCode,
|
|
1422
|
+
errorDescription,
|
|
1423
|
+
connectUri: textOf(error?.connect_uri)
|
|
1424
|
+
});
|
|
929
1425
|
}
|
|
930
1426
|
}
|
|
1427
|
+
function toDevicePollOutcome(error) {
|
|
1428
|
+
if (error === AuthioOAuthErrors.AuthorizationPending) return { outcome: "keep_polling", intervalIncrease: 0 };
|
|
1429
|
+
if (error === AuthioOAuthErrors.SlowDown) return { outcome: "keep_polling", intervalIncrease: AuthioDefaults.DeviceSlowDownIncrement };
|
|
1430
|
+
return { outcome: "failed", error };
|
|
1431
|
+
}
|
|
1432
|
+
function toConnectionChoice(value) {
|
|
1433
|
+
const record = value && typeof value === "object" ? value : {};
|
|
1434
|
+
return withoutUndefined({ id: textOf(record.id) ?? "", label: textOf(record.label), externalLogin: textOf(record.external_login) });
|
|
1435
|
+
}
|
|
1436
|
+
function textOf(value) {
|
|
1437
|
+
return typeof value === "string" && value ? value : void 0;
|
|
1438
|
+
}
|
|
1439
|
+
function withoutUndefined(value) {
|
|
1440
|
+
for (const key of Object.keys(value)) if (value[key] === void 0) delete value[key];
|
|
1441
|
+
return value;
|
|
1442
|
+
}
|
|
1443
|
+
function resolveEncryptionKey(options) {
|
|
1444
|
+
if (options.clientEncryptionKey !== void 0) return options.clientEncryptionKey;
|
|
1445
|
+
if (options.clientEncryptionKeyPath) return encryptionKeyFromFile(options.clientEncryptionKeyPath);
|
|
1446
|
+
return void 0;
|
|
1447
|
+
}
|
|
931
1448
|
async function postForm(connection, discovery, authenticator, endpoint, parameters, signal) {
|
|
932
1449
|
const document = await discovery.get(signal);
|
|
933
1450
|
const url = requireEndpoint(document, endpoint);
|
|
934
1451
|
const form = formBody(parameters);
|
|
935
1452
|
const headers = new Headers({ accept: "application/json", "content-type": "application/x-www-form-urlencoded" });
|
|
936
|
-
await authenticator
|
|
1453
|
+
await authenticator?.apply(form, headers);
|
|
937
1454
|
return send(connection, url, { method: "POST", headers, body: form.toString() }, signal);
|
|
938
1455
|
}
|
|
939
|
-
async function readUserInfoJwt(jwt, document, connection, clientId, decrypt,
|
|
1456
|
+
async function readUserInfoJwt(jwt, document, connection, clientId, decrypt, signingKeys, settings) {
|
|
940
1457
|
try {
|
|
941
1458
|
const signed = isAuthioTokenEncrypted(jwt) ? await decrypt(jwt) : jwt;
|
|
942
|
-
const
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1459
|
+
const { payload } = await verifyAuthioJwt(signed, await signingKeys(settings.signal), {
|
|
1460
|
+
issuer: document.issuer.replace(/\/+$/, ""),
|
|
1461
|
+
audiences: [clientId],
|
|
1462
|
+
algorithms: settings.algorithms,
|
|
1463
|
+
clockSkew: settings.clockSkew,
|
|
1464
|
+
now: connection.clock.now(),
|
|
1465
|
+
requiredClaims: ["sub"]
|
|
947
1466
|
});
|
|
948
1467
|
return payload;
|
|
949
1468
|
} catch (error) {
|
|
@@ -951,7 +1470,7 @@ async function readUserInfoJwt(jwt, document, connection, clientId, decrypt, sta
|
|
|
951
1470
|
"invalid_userinfo",
|
|
952
1471
|
`The userinfo JWT could not be verified: ${error?.message ?? "unknown reason."}`,
|
|
953
1472
|
void 0,
|
|
954
|
-
status,
|
|
1473
|
+
settings.status,
|
|
955
1474
|
{ cause: error }
|
|
956
1475
|
);
|
|
957
1476
|
}
|
|
@@ -973,6 +1492,149 @@ function requireValue(value, name) {
|
|
|
973
1492
|
if (typeof value !== "string" || !value.trim()) throw new TypeError(`${name} is required.`);
|
|
974
1493
|
}
|
|
975
1494
|
|
|
1495
|
+
// src/admin.ts
|
|
1496
|
+
function parseAuthioAdminEnvelope(status, body, headers) {
|
|
1497
|
+
const parsed = parseJson(body);
|
|
1498
|
+
if (status >= 200 && status < 300) {
|
|
1499
|
+
if (!parsed) return { ok: true };
|
|
1500
|
+
return withoutUndefined2({ ok: true, data: parsed.data });
|
|
1501
|
+
}
|
|
1502
|
+
const notifications = Array.isArray(parsed?.notifications) ? parsed.notifications.map(toNotification).filter((item) => item.message) : [];
|
|
1503
|
+
return {
|
|
1504
|
+
ok: false,
|
|
1505
|
+
error: new AuthioAdminError({
|
|
1506
|
+
status,
|
|
1507
|
+
errorCode: textOf2(parsed?.errorCode),
|
|
1508
|
+
notifications,
|
|
1509
|
+
requestId: textOf2(parsed?.requestId) ?? headerOf(headers, AuthioDefaults.RequestIdHeader),
|
|
1510
|
+
retryAfterSeconds: retryAfterOf(headerOf(headers, "Retry-After"))
|
|
1511
|
+
})
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
async function readAuthioAdminResponse(response) {
|
|
1515
|
+
const envelope = parseAuthioAdminEnvelope(response.status, response.status === 204 ? null : await response.text(), response.headers);
|
|
1516
|
+
if (!envelope.ok) throw envelope.error;
|
|
1517
|
+
return envelope;
|
|
1518
|
+
}
|
|
1519
|
+
function createAuthioAccountCredential(options) {
|
|
1520
|
+
const value = options.credential?.trim();
|
|
1521
|
+
if (!value) throw new AuthioConfigurationError("The account credential is required.");
|
|
1522
|
+
const kind = value.startsWith(AuthioDefaults.ApiKeyPrefix) ? "api_key" : value.startsWith(AuthioDefaults.ClientSecretPrefix) ? "client_secret" : value.startsWith(AuthioDefaults.PersonalAccessTokenPrefix) ? "personal_access_token" : void 0;
|
|
1523
|
+
if (!kind)
|
|
1524
|
+
throw new AuthioConfigurationError(
|
|
1525
|
+
`The account credential must start with ${AuthioDefaults.ApiKeyPrefix}, ${AuthioDefaults.ClientSecretPrefix} or ${AuthioDefaults.PersonalAccessTokenPrefix}.`
|
|
1526
|
+
);
|
|
1527
|
+
const exchanged = kind === "client_secret" || kind === "personal_access_token" && options.exchangePersonalAccessToken === true;
|
|
1528
|
+
const clock = options.clock ?? systemClock;
|
|
1529
|
+
const inner = options.fetch ?? globalThis.fetch;
|
|
1530
|
+
const scope = toScopeString(options.scopes);
|
|
1531
|
+
const refreshSkewMs = seconds(options.tokenRefreshSkew, AuthioDefaults.TokenRefreshSkew, "tokenRefreshSkew");
|
|
1532
|
+
let client;
|
|
1533
|
+
let cached;
|
|
1534
|
+
let pending;
|
|
1535
|
+
if (exchanged) {
|
|
1536
|
+
if (kind === "client_secret" && !options.credentialId?.trim())
|
|
1537
|
+
throw new AuthioConfigurationError("A cs_ client secret is exchanged with its credential id: set credentialId.");
|
|
1538
|
+
client = createAuthioClient({
|
|
1539
|
+
...options,
|
|
1540
|
+
realm: AuthioDefaults.PlatformRealm,
|
|
1541
|
+
clientId: kind === "client_secret" ? toCredentialClientId(options.credentialId) : AuthioDefaults.PlatformClientId,
|
|
1542
|
+
clientSecret: kind === "client_secret" ? value : void 0,
|
|
1543
|
+
clientAuthentication: kind === "client_secret" ? "client_secret_post" : "none"
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
const exchange = async (signal) => {
|
|
1547
|
+
if (cached && clock.now() < cached.refreshAt) return cached;
|
|
1548
|
+
pending ??= (async () => {
|
|
1549
|
+
const issuedAt = clock.now();
|
|
1550
|
+
const answer = kind === "client_secret" ? await client.tokens.clientCredentials({ scopes: scope, signal }) : await client.tokens.personalAccessToken({ token: value, scopes: scope, signal });
|
|
1551
|
+
cached = toClientCredentialsToken(answer, issuedAt, refreshSkewMs);
|
|
1552
|
+
return cached;
|
|
1553
|
+
})().finally(() => {
|
|
1554
|
+
pending = void 0;
|
|
1555
|
+
});
|
|
1556
|
+
return pending;
|
|
1557
|
+
};
|
|
1558
|
+
const headers = async (signal) => {
|
|
1559
|
+
if (exchanged) {
|
|
1560
|
+
const token = await exchange(signal);
|
|
1561
|
+
return { authorization: `${token.tokenType} ${token.accessToken}` };
|
|
1562
|
+
}
|
|
1563
|
+
return options.presentAs === "x-api-key" ? { [AuthioDefaults.ApiKeyHeader]: value } : { authorization: `Bearer ${value}` };
|
|
1564
|
+
};
|
|
1565
|
+
const authenticatedFetch = async (input, init) => {
|
|
1566
|
+
const request = new Request(input, init);
|
|
1567
|
+
const body = request.body ? await request.arrayBuffer() : void 0;
|
|
1568
|
+
const idempotent = ["GET", "HEAD", "PUT", "DELETE"].includes(request.method.toUpperCase());
|
|
1569
|
+
const attempt = async () => {
|
|
1570
|
+
const merged = new Headers(request.headers);
|
|
1571
|
+
for (const [name, header] of Object.entries(await headers(request.signal))) merged.set(name, header);
|
|
1572
|
+
return inner(request.url, { method: request.method, headers: merged, body, signal: request.signal, redirect: request.redirect });
|
|
1573
|
+
};
|
|
1574
|
+
let response = await attempt();
|
|
1575
|
+
if (response.status === 401 && exchanged) {
|
|
1576
|
+
cached = void 0;
|
|
1577
|
+
response = await attempt();
|
|
1578
|
+
}
|
|
1579
|
+
if ((response.status === 429 || response.status === 503) && idempotent) {
|
|
1580
|
+
const wait = retryAfterOf(response.headers.get("retry-after") ?? void 0);
|
|
1581
|
+
if (wait !== void 0) {
|
|
1582
|
+
await clock.sleep(wait * 1e3, request.signal);
|
|
1583
|
+
response = await attempt();
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
return response;
|
|
1587
|
+
};
|
|
1588
|
+
return {
|
|
1589
|
+
kind,
|
|
1590
|
+
exchanged,
|
|
1591
|
+
headers,
|
|
1592
|
+
invalidate() {
|
|
1593
|
+
cached = void 0;
|
|
1594
|
+
},
|
|
1595
|
+
fetch: authenticatedFetch
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
function toCredentialClientId(credentialId) {
|
|
1599
|
+
const id = credentialId.trim();
|
|
1600
|
+
return id.startsWith(AuthioDefaults.CredentialClientIdPrefix) ? id : `${AuthioDefaults.CredentialClientIdPrefix}${id.replace(/[{}-]/g, "").toLowerCase()}`;
|
|
1601
|
+
}
|
|
1602
|
+
function parseJson(body) {
|
|
1603
|
+
if (!body?.trim()) return void 0;
|
|
1604
|
+
try {
|
|
1605
|
+
return asRecord(JSON.parse(body));
|
|
1606
|
+
} catch {
|
|
1607
|
+
return void 0;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
function asRecord(value) {
|
|
1611
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1612
|
+
}
|
|
1613
|
+
function toNotification(value) {
|
|
1614
|
+
const record = asRecord(value) ?? {};
|
|
1615
|
+
return withoutUndefined2({ message: textOf2(record.message) ?? "", field: textOf2(record.field), code: textOf2(record.code) });
|
|
1616
|
+
}
|
|
1617
|
+
function headerOf(headers, name) {
|
|
1618
|
+
if (!headers) return void 0;
|
|
1619
|
+
if (typeof headers.get === "function") return headers.get(name) ?? void 0;
|
|
1620
|
+
const wanted = name.toLowerCase();
|
|
1621
|
+
for (const [key, value] of Object.entries(headers)) if (key.toLowerCase() === wanted) return value;
|
|
1622
|
+
return void 0;
|
|
1623
|
+
}
|
|
1624
|
+
function retryAfterOf(value) {
|
|
1625
|
+
if (!value?.trim()) return void 0;
|
|
1626
|
+
if (/^\d+$/.test(value.trim())) return Number(value.trim());
|
|
1627
|
+
const date = Date.parse(value);
|
|
1628
|
+
return Number.isNaN(date) ? void 0 : Math.max(0, Math.ceil((date - Date.now()) / 1e3));
|
|
1629
|
+
}
|
|
1630
|
+
function textOf2(value) {
|
|
1631
|
+
return typeof value === "string" && value ? value : void 0;
|
|
1632
|
+
}
|
|
1633
|
+
function withoutUndefined2(value) {
|
|
1634
|
+
for (const key of Object.keys(value)) if (value[key] === void 0) delete value[key];
|
|
1635
|
+
return value;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
976
1638
|
// src/resource-access.ts
|
|
977
1639
|
var claimSources = new Map(Object.entries(AuthioResourceAccessClaims).map(([name, source]) => [name.toLowerCase(), source]));
|
|
978
1640
|
var AuthioResourceAccess = class _AuthioResourceAccess {
|
|
@@ -1052,7 +1714,7 @@ function parseResourceAccess(claims) {
|
|
|
1052
1714
|
return roles.length === 0 ? AuthioResourceAccess.empty : new AuthioResourceAccess(roles);
|
|
1053
1715
|
}
|
|
1054
1716
|
function parseResourceAccessClaim(source, value) {
|
|
1055
|
-
const root = typeof value === "string" ?
|
|
1717
|
+
const root = typeof value === "string" ? parseJson2(value) : value;
|
|
1056
1718
|
if (!isPlainObject(root)) return [];
|
|
1057
1719
|
const roles = [];
|
|
1058
1720
|
for (const [name, rolePermissions] of Object.entries(root)) {
|
|
@@ -1109,7 +1771,7 @@ function distinctIgnoringCase(values) {
|
|
|
1109
1771
|
return true;
|
|
1110
1772
|
});
|
|
1111
1773
|
}
|
|
1112
|
-
function
|
|
1774
|
+
function parseJson2(text) {
|
|
1113
1775
|
if (!text.trim()) return void 0;
|
|
1114
1776
|
try {
|
|
1115
1777
|
return JSON.parse(text);
|
|
@@ -1196,9 +1858,23 @@ var asAdminResourceEvent = (event) => dataWhen(
|
|
|
1196
1858
|
AuthioWebhookEventTypes.AdminResourceDeleted
|
|
1197
1859
|
);
|
|
1198
1860
|
var asUserSuspensionEvent = (event) => dataWhen(event, AuthioWebhookEventTypes.UserSuspended, AuthioWebhookEventTypes.UserSuspensionEnded);
|
|
1861
|
+
var asAttackDetectedEvent = (event) => dataWhen(event, AuthioWebhookEventTypes.AttackDetected);
|
|
1862
|
+
var asCredentialCompromisedEvent = (event) => dataWhen(event, AuthioWebhookEventTypes.CredentialCompromised);
|
|
1199
1863
|
var asPingEvent = (event) => dataWhen(event, AuthioWebhookEventTypes.Ping);
|
|
1864
|
+
var knownEventTypes = new Set(Object.values(AuthioWebhookEventTypes));
|
|
1865
|
+
function isKnownWebhookEventType(type) {
|
|
1866
|
+
return knownEventTypes.has(type);
|
|
1867
|
+
}
|
|
1868
|
+
function readWebhookEventData(event) {
|
|
1869
|
+
return isKnownWebhookEventType(event.type) ? dataWhen(event, event.type) : void 0;
|
|
1870
|
+
}
|
|
1871
|
+
function toCamelCasedMembers(value) {
|
|
1872
|
+
const result = {};
|
|
1873
|
+
for (const [name, member] of Object.entries(value)) result[name.replace(/_([a-z0-9])/g, (_, letter) => letter.toUpperCase())] = member;
|
|
1874
|
+
return result;
|
|
1875
|
+
}
|
|
1200
1876
|
function dataWhen(event, ...types) {
|
|
1201
|
-
return types.includes(event.type) && event.data !== null && event.data
|
|
1877
|
+
return types.includes(event.type) && event.data !== null && typeof event.data === "object" && !Array.isArray(event.data) ? toCamelCasedMembers(event.data) : void 0;
|
|
1202
1878
|
}
|
|
1203
1879
|
function parseSignatureHeader(header) {
|
|
1204
1880
|
let timestamp = 0;
|
|
@@ -1237,7 +1913,10 @@ function constantTimeEquals(left, right) {
|
|
|
1237
1913
|
return difference === 0;
|
|
1238
1914
|
}
|
|
1239
1915
|
|
|
1916
|
+
exports.AuthioAdminError = AuthioAdminError;
|
|
1240
1917
|
exports.AuthioAsymmetricAlgorithms = AuthioAsymmetricAlgorithms;
|
|
1918
|
+
exports.AuthioCallbackError = AuthioCallbackError;
|
|
1919
|
+
exports.AuthioCallerKinds = AuthioCallerKinds;
|
|
1241
1920
|
exports.AuthioClaimTypes = AuthioClaimTypes;
|
|
1242
1921
|
exports.AuthioClientAssertionAlgorithms = AuthioClientAssertionAlgorithms;
|
|
1243
1922
|
exports.AuthioConfigurationError = AuthioConfigurationError;
|
|
@@ -1247,15 +1926,20 @@ exports.AuthioDiscoveryError = AuthioDiscoveryError;
|
|
|
1247
1926
|
exports.AuthioError = AuthioError;
|
|
1248
1927
|
exports.AuthioGrantTypes = AuthioGrantTypes;
|
|
1249
1928
|
exports.AuthioKeyManagementAlgorithms = AuthioKeyManagementAlgorithms;
|
|
1929
|
+
exports.AuthioLegacyKeyManagementAlgorithms = AuthioLegacyKeyManagementAlgorithms;
|
|
1250
1930
|
exports.AuthioOAuthErrors = AuthioOAuthErrors;
|
|
1931
|
+
exports.AuthioPrompts = AuthioPrompts;
|
|
1251
1932
|
exports.AuthioProtocolError = AuthioProtocolError;
|
|
1252
1933
|
exports.AuthioResourceAccess = AuthioResourceAccess;
|
|
1253
1934
|
exports.AuthioResourceAccessClaims = AuthioResourceAccessClaims;
|
|
1254
1935
|
exports.AuthioScopes = AuthioScopes;
|
|
1255
1936
|
exports.AuthioTokenTypes = AuthioTokenTypes;
|
|
1937
|
+
exports.AuthioTokenValidationError = AuthioTokenValidationError;
|
|
1256
1938
|
exports.AuthioWebhookEventTypes = AuthioWebhookEventTypes;
|
|
1257
1939
|
exports.AuthioWebhookHeaders = AuthioWebhookHeaders;
|
|
1258
1940
|
exports.asAdminResourceEvent = asAdminResourceEvent;
|
|
1941
|
+
exports.asAttackDetectedEvent = asAttackDetectedEvent;
|
|
1942
|
+
exports.asCredentialCompromisedEvent = asCredentialCompromisedEvent;
|
|
1259
1943
|
exports.asKeysRotatedEvent = asKeysRotatedEvent;
|
|
1260
1944
|
exports.asLoginEvent = asLoginEvent;
|
|
1261
1945
|
exports.asPingEvent = asPingEvent;
|
|
@@ -1264,26 +1948,43 @@ exports.asTokenRevokedEvent = asTokenRevokedEvent;
|
|
|
1264
1948
|
exports.asUserEvent = asUserEvent;
|
|
1265
1949
|
exports.asUserLockedOutEvent = asUserLockedOutEvent;
|
|
1266
1950
|
exports.asUserSuspensionEvent = asUserSuspensionEvent;
|
|
1951
|
+
exports.computeAuthioTokenHash = computeAuthioTokenHash;
|
|
1267
1952
|
exports.computeWebhookSignatureHeader = computeWebhookSignatureHeader;
|
|
1953
|
+
exports.createAuthioAccountCredential = createAuthioAccountCredential;
|
|
1268
1954
|
exports.createAuthioClient = createAuthioClient;
|
|
1269
1955
|
exports.createAuthioDiscoveryClient = createAuthioDiscoveryClient;
|
|
1956
|
+
exports.createAuthioPkce = createAuthioPkce;
|
|
1957
|
+
exports.createAuthioSigningKeys = createAuthioSigningKeys;
|
|
1270
1958
|
exports.createAuthioTokenDecrypter = createAuthioTokenDecrypter;
|
|
1959
|
+
exports.encryptionKeyFromFile = encryptionKeyFromFile;
|
|
1271
1960
|
exports.escapeDataString = escapeDataString;
|
|
1272
1961
|
exports.isAllowedUrl = isAllowedUrl;
|
|
1273
1962
|
exports.isAuthioTokenEncrypted = isAuthioTokenEncrypted;
|
|
1963
|
+
exports.isKnownWebhookEventType = isKnownWebhookEventType;
|
|
1274
1964
|
exports.isWebhookEventOfType = isWebhookEventOfType;
|
|
1965
|
+
exports.parseAuthioAdminEnvelope = parseAuthioAdminEnvelope;
|
|
1966
|
+
exports.parseAuthioBearerChallenge = parseAuthioBearerChallenge;
|
|
1275
1967
|
exports.parseResourceAccess = parseResourceAccess;
|
|
1276
1968
|
exports.parseResourceAccessClaim = parseResourceAccessClaim;
|
|
1277
1969
|
exports.parseWebhookEvent = parseWebhookEvent;
|
|
1970
|
+
exports.randomValue = randomValue;
|
|
1971
|
+
exports.readAuthioAdminResponse = readAuthioAdminResponse;
|
|
1972
|
+
exports.readWebhookEventData = readWebhookEventData;
|
|
1278
1973
|
exports.resolveConnection = resolveConnection;
|
|
1279
1974
|
exports.resolveIssuer = resolveIssuer;
|
|
1280
1975
|
exports.resourceAccessSourceOf = resourceAccessSourceOf;
|
|
1281
1976
|
exports.seconds = seconds;
|
|
1282
1977
|
exports.systemClock = systemClock;
|
|
1283
1978
|
exports.toBasicAuthorization = toBasicAuthorization;
|
|
1979
|
+
exports.toCamelCasedMembers = toCamelCasedMembers;
|
|
1284
1980
|
exports.toClientCredentialsToken = toClientCredentialsToken;
|
|
1981
|
+
exports.toConnectionExchangeResult = toConnectionExchangeResult;
|
|
1982
|
+
exports.toCredentialClientId = toCredentialClientId;
|
|
1983
|
+
exports.toDevicePollOutcome = toDevicePollOutcome;
|
|
1285
1984
|
exports.toScopeString = toScopeString;
|
|
1985
|
+
exports.toTokenValidationError = toTokenValidationError;
|
|
1286
1986
|
exports.tryParseWebhookEvent = tryParseWebhookEvent;
|
|
1987
|
+
exports.verifyAuthioJwt = verifyAuthioJwt;
|
|
1287
1988
|
exports.verifyWebhookSignature = verifyWebhookSignature;
|
|
1288
1989
|
//# sourceMappingURL=index.cjs.map
|
|
1289
1990
|
//# sourceMappingURL=index.cjs.map
|