@authsome/ui-core 1.7.1 → 1.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/dist/index.d.ts +153 -68
- package/dist/index.js +380 -152
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -805,7 +805,7 @@ var AuthClient = class {
|
|
|
805
805
|
* List SSO connections for an app (admin)
|
|
806
806
|
* GET /v1/admin/sso/connections
|
|
807
807
|
*/
|
|
808
|
-
async ssoAdminListConnections(
|
|
808
|
+
async ssoAdminListConnections(token, app_id) {
|
|
809
809
|
const params = new URLSearchParams();
|
|
810
810
|
if (app_id !== void 0) params.set("app_id", String(app_id));
|
|
811
811
|
const qs = params.toString();
|
|
@@ -995,6 +995,19 @@ var AuthClient = class {
|
|
|
995
995
|
token
|
|
996
996
|
);
|
|
997
997
|
}
|
|
998
|
+
/**
|
|
999
|
+
* Unlock user (admin)
|
|
1000
|
+
* POST /v1/admin/users/{userId}/unlock
|
|
1001
|
+
*/
|
|
1002
|
+
async adminUnlockUser(userId, token) {
|
|
1003
|
+
const path = `/v1/admin/users/${userId}/unlock`;
|
|
1004
|
+
return this.request(
|
|
1005
|
+
"POST",
|
|
1006
|
+
path,
|
|
1007
|
+
void 0,
|
|
1008
|
+
token
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
998
1011
|
/**
|
|
999
1012
|
* List coupons
|
|
1000
1013
|
* GET /v1/billing/coupons
|
|
@@ -1717,6 +1730,32 @@ var AuthClient = class {
|
|
|
1717
1730
|
token
|
|
1718
1731
|
);
|
|
1719
1732
|
}
|
|
1733
|
+
/**
|
|
1734
|
+
* List my OAuth2 grants
|
|
1735
|
+
* GET /v1/me/oauth/grants
|
|
1736
|
+
*/
|
|
1737
|
+
async oauth2ListMyGrants(token) {
|
|
1738
|
+
const path = "/v1/me/oauth/grants";
|
|
1739
|
+
return this.request(
|
|
1740
|
+
"GET",
|
|
1741
|
+
path,
|
|
1742
|
+
void 0,
|
|
1743
|
+
token
|
|
1744
|
+
);
|
|
1745
|
+
}
|
|
1746
|
+
/**
|
|
1747
|
+
* Revoke an OAuth2 grant
|
|
1748
|
+
* DELETE /v1/me/oauth/grants/{clientId}
|
|
1749
|
+
*/
|
|
1750
|
+
async oauth2RevokeMyGrant(clientId, token) {
|
|
1751
|
+
const path = `/v1/me/oauth/grants/${clientId}`;
|
|
1752
|
+
return this.request(
|
|
1753
|
+
"DELETE",
|
|
1754
|
+
path,
|
|
1755
|
+
void 0,
|
|
1756
|
+
token
|
|
1757
|
+
);
|
|
1758
|
+
}
|
|
1720
1759
|
/**
|
|
1721
1760
|
* Switch active organization
|
|
1722
1761
|
* POST /v1/me/switch-org
|
|
@@ -1835,7 +1874,7 @@ var AuthClient = class {
|
|
|
1835
1874
|
* OAuth2 Authorization
|
|
1836
1875
|
* GET /v1/oauth/authorize
|
|
1837
1876
|
*/
|
|
1838
|
-
async oauth2Authorize(response_type, client_id, redirect_uri, scope, state, code_challenge, code_challenge_method, resource) {
|
|
1877
|
+
async oauth2Authorize(response_type, client_id, redirect_uri, scope, state, code_challenge, code_challenge_method, resource, prompt) {
|
|
1839
1878
|
const params = new URLSearchParams();
|
|
1840
1879
|
if (response_type !== void 0) params.set("response_type", String(response_type));
|
|
1841
1880
|
if (client_id !== void 0) params.set("client_id", String(client_id));
|
|
@@ -1845,6 +1884,7 @@ var AuthClient = class {
|
|
|
1845
1884
|
if (code_challenge !== void 0) params.set("code_challenge", String(code_challenge));
|
|
1846
1885
|
if (code_challenge_method !== void 0) params.set("code_challenge_method", String(code_challenge_method));
|
|
1847
1886
|
if (resource !== void 0) for (const element of resource) params.append("resource", String(element));
|
|
1887
|
+
if (prompt !== void 0) params.set("prompt", String(prompt));
|
|
1848
1888
|
const qs = params.toString();
|
|
1849
1889
|
const path = "/v1/oauth/authorize" + (qs ? `?${qs}` : "");
|
|
1850
1890
|
return this.request(
|
|
@@ -1853,6 +1893,32 @@ var AuthClient = class {
|
|
|
1853
1893
|
void 0
|
|
1854
1894
|
);
|
|
1855
1895
|
}
|
|
1896
|
+
/**
|
|
1897
|
+
* OAuth2 consent
|
|
1898
|
+
* GET /v1/oauth/consent
|
|
1899
|
+
*/
|
|
1900
|
+
async oauth2ConsentPage(token) {
|
|
1901
|
+
const path = "/v1/oauth/consent";
|
|
1902
|
+
return this.request(
|
|
1903
|
+
"GET",
|
|
1904
|
+
path,
|
|
1905
|
+
void 0,
|
|
1906
|
+
token
|
|
1907
|
+
);
|
|
1908
|
+
}
|
|
1909
|
+
/**
|
|
1910
|
+
* OAuth2 consent decision
|
|
1911
|
+
* POST /v1/oauth/consent
|
|
1912
|
+
*/
|
|
1913
|
+
async oauth2ConsentDecision(token) {
|
|
1914
|
+
const path = "/v1/oauth/consent";
|
|
1915
|
+
return this.request(
|
|
1916
|
+
"POST",
|
|
1917
|
+
path,
|
|
1918
|
+
void 0,
|
|
1919
|
+
token
|
|
1920
|
+
);
|
|
1921
|
+
}
|
|
1856
1922
|
/**
|
|
1857
1923
|
* Device Authorization
|
|
1858
1924
|
* POST /v1/oauth/device/authorize
|
|
@@ -1978,8 +2044,12 @@ var AuthClient = class {
|
|
|
1978
2044
|
* List organizations
|
|
1979
2045
|
* GET /v1/orgs
|
|
1980
2046
|
*/
|
|
1981
|
-
async listOrganizations(token) {
|
|
1982
|
-
const
|
|
2047
|
+
async listOrganizations(token, limit, cursor) {
|
|
2048
|
+
const params = new URLSearchParams();
|
|
2049
|
+
if (limit !== void 0) params.set("limit", String(limit));
|
|
2050
|
+
if (cursor !== void 0) params.set("cursor", String(cursor));
|
|
2051
|
+
const qs = params.toString();
|
|
2052
|
+
const path = "/v1/orgs" + (qs ? `?${qs}` : "");
|
|
1983
2053
|
return this.request(
|
|
1984
2054
|
"GET",
|
|
1985
2055
|
path,
|
|
@@ -2086,8 +2156,12 @@ var AuthClient = class {
|
|
|
2086
2156
|
* List invitations
|
|
2087
2157
|
* GET /v1/orgs/{orgId}/invitations
|
|
2088
2158
|
*/
|
|
2089
|
-
async listInvitations(orgId, token) {
|
|
2090
|
-
const
|
|
2159
|
+
async listInvitations(orgId, token, limit, cursor) {
|
|
2160
|
+
const params = new URLSearchParams();
|
|
2161
|
+
if (limit !== void 0) params.set("limit", String(limit));
|
|
2162
|
+
if (cursor !== void 0) params.set("cursor", String(cursor));
|
|
2163
|
+
const qs = params.toString();
|
|
2164
|
+
const path = `/v1/orgs/${orgId}/invitations` + (qs ? `?${qs}` : "");
|
|
2091
2165
|
return this.request(
|
|
2092
2166
|
"GET",
|
|
2093
2167
|
path,
|
|
@@ -2112,8 +2186,12 @@ var AuthClient = class {
|
|
|
2112
2186
|
* List members
|
|
2113
2187
|
* GET /v1/orgs/{orgId}/members
|
|
2114
2188
|
*/
|
|
2115
|
-
async listMembers(orgId, token) {
|
|
2116
|
-
const
|
|
2189
|
+
async listMembers(orgId, token, limit, cursor) {
|
|
2190
|
+
const params = new URLSearchParams();
|
|
2191
|
+
if (limit !== void 0) params.set("limit", String(limit));
|
|
2192
|
+
if (cursor !== void 0) params.set("cursor", String(cursor));
|
|
2193
|
+
const qs = params.toString();
|
|
2194
|
+
const path = `/v1/orgs/${orgId}/members` + (qs ? `?${qs}` : "");
|
|
2117
2195
|
return this.request(
|
|
2118
2196
|
"GET",
|
|
2119
2197
|
path,
|
|
@@ -2164,8 +2242,12 @@ var AuthClient = class {
|
|
|
2164
2242
|
* List teams
|
|
2165
2243
|
* GET /v1/orgs/{orgId}/teams
|
|
2166
2244
|
*/
|
|
2167
|
-
async listTeams(orgId, token) {
|
|
2168
|
-
const
|
|
2245
|
+
async listTeams(orgId, token, limit, cursor) {
|
|
2246
|
+
const params = new URLSearchParams();
|
|
2247
|
+
if (limit !== void 0) params.set("limit", String(limit));
|
|
2248
|
+
if (cursor !== void 0) params.set("cursor", String(cursor));
|
|
2249
|
+
const qs = params.toString();
|
|
2250
|
+
const path = `/v1/orgs/${orgId}/teams` + (qs ? `?${qs}` : "");
|
|
2169
2251
|
return this.request(
|
|
2170
2252
|
"GET",
|
|
2171
2253
|
path,
|
|
@@ -2512,8 +2594,12 @@ var AuthClient = class {
|
|
|
2512
2594
|
* List sessions
|
|
2513
2595
|
* GET /v1/sessions
|
|
2514
2596
|
*/
|
|
2515
|
-
async listSessions(token) {
|
|
2516
|
-
const
|
|
2597
|
+
async listSessions(token, limit, cursor) {
|
|
2598
|
+
const params = new URLSearchParams();
|
|
2599
|
+
if (limit !== void 0) params.set("limit", String(limit));
|
|
2600
|
+
if (cursor !== void 0) params.set("cursor", String(cursor));
|
|
2601
|
+
const qs = params.toString();
|
|
2602
|
+
const path = "/v1/sessions" + (qs ? `?${qs}` : "");
|
|
2517
2603
|
return this.request(
|
|
2518
2604
|
"GET",
|
|
2519
2605
|
path,
|
|
@@ -2521,6 +2607,19 @@ var AuthClient = class {
|
|
|
2521
2607
|
token
|
|
2522
2608
|
);
|
|
2523
2609
|
}
|
|
2610
|
+
/**
|
|
2611
|
+
* Revoke other sessions
|
|
2612
|
+
* DELETE /v1/sessions
|
|
2613
|
+
*/
|
|
2614
|
+
async revokeOtherSessions(token) {
|
|
2615
|
+
const path = "/v1/sessions";
|
|
2616
|
+
return this.request(
|
|
2617
|
+
"DELETE",
|
|
2618
|
+
path,
|
|
2619
|
+
void 0,
|
|
2620
|
+
token
|
|
2621
|
+
);
|
|
2622
|
+
}
|
|
2524
2623
|
/**
|
|
2525
2624
|
* Revoke session
|
|
2526
2625
|
* DELETE /v1/sessions/{sessionId}
|
|
@@ -2821,6 +2920,19 @@ var AuthClient = class {
|
|
|
2821
2920
|
token
|
|
2822
2921
|
);
|
|
2823
2922
|
}
|
|
2923
|
+
/**
|
|
2924
|
+
* Rotate webhook secret
|
|
2925
|
+
* POST /v1/webhooks/{webhookId}/rotate-secret
|
|
2926
|
+
*/
|
|
2927
|
+
async rotateWebhookSecret(webhookId, token) {
|
|
2928
|
+
const path = `/v1/webhooks/${webhookId}/rotate-secret`;
|
|
2929
|
+
return this.request(
|
|
2930
|
+
"POST",
|
|
2931
|
+
path,
|
|
2932
|
+
void 0,
|
|
2933
|
+
token
|
|
2934
|
+
);
|
|
2935
|
+
}
|
|
2824
2936
|
// ──────────────────────────────────────────────────
|
|
2825
2937
|
// Internal request helper
|
|
2826
2938
|
// ──────────────────────────────────────────────────
|
|
@@ -2883,7 +2995,65 @@ var AuthClientError = class extends Error {
|
|
|
2883
2995
|
};
|
|
2884
2996
|
|
|
2885
2997
|
// src/client.ts
|
|
2998
|
+
function readOrganizationList(res) {
|
|
2999
|
+
const body = res ?? {};
|
|
3000
|
+
const organizations = body.organizations ?? body.items ?? [];
|
|
3001
|
+
return {
|
|
3002
|
+
organizations,
|
|
3003
|
+
nextCursor: body.next_cursor || void 0,
|
|
3004
|
+
total: typeof body.total === "number" ? body.total : organizations.length
|
|
3005
|
+
};
|
|
3006
|
+
}
|
|
2886
3007
|
var AuthClient2 = class extends AuthClient {
|
|
3008
|
+
waitlistURL;
|
|
3009
|
+
waitlistFetch;
|
|
3010
|
+
constructor(config) {
|
|
3011
|
+
const fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
3012
|
+
let baseURL = config.baseURL;
|
|
3013
|
+
while (baseURL.endsWith("/")) baseURL = baseURL.slice(0, -1);
|
|
3014
|
+
const deviceCompletionURL = `${baseURL}/v1/oauth/device/complete`;
|
|
3015
|
+
super({
|
|
3016
|
+
...config,
|
|
3017
|
+
fetch: (input, init) => {
|
|
3018
|
+
const isDeviceCompletion = input === deviceCompletionURL && init?.method === "POST";
|
|
3019
|
+
return fetchFn(
|
|
3020
|
+
input,
|
|
3021
|
+
isDeviceCompletion ? { ...init, credentials: "include" } : init
|
|
3022
|
+
);
|
|
3023
|
+
}
|
|
3024
|
+
});
|
|
3025
|
+
this.waitlistURL = `${baseURL}/v1/waitlist/join`;
|
|
3026
|
+
this.waitlistFetch = fetchFn;
|
|
3027
|
+
}
|
|
3028
|
+
/** Join the waitlist, or return the existing entry and its approval status. */
|
|
3029
|
+
async joinWaitlist(body) {
|
|
3030
|
+
const headers = {
|
|
3031
|
+
"Content-Type": "application/json",
|
|
3032
|
+
Accept: "application/json"
|
|
3033
|
+
};
|
|
3034
|
+
const key = this.getPublishableKey();
|
|
3035
|
+
if (key) headers["X-Publishable-Key"] = key;
|
|
3036
|
+
const response = await this.waitlistFetch(this.waitlistURL, {
|
|
3037
|
+
method: "POST",
|
|
3038
|
+
headers,
|
|
3039
|
+
body: JSON.stringify({ ...body, email: body.email.trim().toLowerCase() })
|
|
3040
|
+
});
|
|
3041
|
+
const data = await response.json().catch(() => null);
|
|
3042
|
+
if (!response.ok) {
|
|
3043
|
+
throw new AuthClientError(
|
|
3044
|
+
data?.error ?? `Request failed with status ${response.status}`,
|
|
3045
|
+
response.status,
|
|
3046
|
+
data?.type,
|
|
3047
|
+
data ?? void 0
|
|
3048
|
+
);
|
|
3049
|
+
}
|
|
3050
|
+
if (typeof data?.email !== "string" || !["pending", "approved", "rejected"].includes(data?.status)) {
|
|
3051
|
+
throw new AuthClientError(
|
|
3052
|
+
"Unable to check waitlist status. Please try again."
|
|
3053
|
+
);
|
|
3054
|
+
}
|
|
3055
|
+
return data;
|
|
3056
|
+
}
|
|
2887
3057
|
/**
|
|
2888
3058
|
* Refresh session tokens.
|
|
2889
3059
|
*
|
|
@@ -2908,97 +3078,44 @@ var AuthClient2 = class extends AuthClient {
|
|
|
2908
3078
|
}
|
|
2909
3079
|
return super.signOut(token);
|
|
2910
3080
|
}
|
|
2911
|
-
/**
|
|
2912
|
-
* MFA challenge — bridge for auth.ts.
|
|
2913
|
-
*
|
|
2914
|
-
* auth.ts calls `mfaChallenge({ enrollment_id, code })` and expects
|
|
2915
|
-
* an AuthResponse back. The generated client uses `challengeMFA`.
|
|
2916
|
-
*/
|
|
3081
|
+
/** @deprecated Use challengeMFA with the ticket from sign-in. */
|
|
2917
3082
|
async mfaChallenge(body) {
|
|
3083
|
+
if (!("mfa_ticket" in body) || !body.mfa_ticket) {
|
|
3084
|
+
throw new Error(
|
|
3085
|
+
"MFA challenge requires an authentication ticket, not an enrollment ID"
|
|
3086
|
+
);
|
|
3087
|
+
}
|
|
2918
3088
|
return super.challengeMFA(body);
|
|
2919
3089
|
}
|
|
2920
|
-
/**
|
|
2921
|
-
* Verify an MFA recovery code.
|
|
2922
|
-
*
|
|
2923
|
-
* Accepts a raw code string (used by auth.ts) or the full request body.
|
|
2924
|
-
*/
|
|
3090
|
+
/** Verify a recovery code in an existing cookie-authenticated session. */
|
|
2925
3091
|
async verifyRecoveryCode(body) {
|
|
2926
|
-
|
|
2927
|
-
|
|
3092
|
+
return super.verifyRecoveryCode(
|
|
3093
|
+
typeof body === "string" ? { code: body } : body
|
|
3094
|
+
);
|
|
2928
3095
|
}
|
|
2929
|
-
/**
|
|
2930
|
-
* Send an SMS code for MFA — bridge for auth.ts.
|
|
2931
|
-
*
|
|
2932
|
-
* Simplifies the call-site by accepting just the session token.
|
|
2933
|
-
*/
|
|
3096
|
+
/** Send an SMS code for the authenticated session. */
|
|
2934
3097
|
async sendSMSCodeForMFA(token) {
|
|
2935
3098
|
return super.sendSMSCode({}, token);
|
|
2936
3099
|
}
|
|
2937
|
-
/**
|
|
2938
|
-
* Verify an SMS code for MFA — bridge for auth.ts.
|
|
2939
|
-
*
|
|
2940
|
-
* Simplifies the call-site by accepting code + token directly.
|
|
2941
|
-
*/
|
|
3100
|
+
/** Verify an SMS code without issuing a session. */
|
|
2942
3101
|
async verifySMSCodeForMFA(code, token) {
|
|
2943
3102
|
return super.verifySMSCode({ code }, token);
|
|
2944
3103
|
}
|
|
2945
|
-
/**
|
|
2946
|
-
* Complete a device authorization (RFC 8628).
|
|
2947
|
-
*
|
|
2948
|
-
* Approves or denies a device code. Requires an authenticated session —
|
|
2949
|
-
* pass the session token so the backend can identify the user.
|
|
2950
|
-
*/
|
|
3104
|
+
/** Approve or deny a device using a bearer token or the existing session cookie. */
|
|
2951
3105
|
async completeDeviceAuthorization(userCode, action, token) {
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
};
|
|
2957
|
-
if (token) {
|
|
2958
|
-
headers.Authorization = `Bearer ${token}`;
|
|
2959
|
-
}
|
|
2960
|
-
const res = await fetchFn(`${base}/v1/oauth/device/complete`, {
|
|
2961
|
-
method: "POST",
|
|
2962
|
-
headers,
|
|
2963
|
-
credentials: "include",
|
|
2964
|
-
body: JSON.stringify({ user_code: userCode, action })
|
|
2965
|
-
});
|
|
2966
|
-
if (!res.ok) {
|
|
2967
|
-
const body = await res.json().catch(() => ({}));
|
|
2968
|
-
throw new AuthClientError(
|
|
2969
|
-
body.error || "Failed to authorize device",
|
|
2970
|
-
res.status
|
|
2971
|
-
);
|
|
2972
|
-
}
|
|
2973
|
-
return res.json();
|
|
3106
|
+
return super.oauth2DeviceComplete(
|
|
3107
|
+
{ user_code: userCode, action },
|
|
3108
|
+
token ?? ""
|
|
3109
|
+
);
|
|
2974
3110
|
}
|
|
2975
3111
|
/**
|
|
2976
3112
|
* Fetch the client configuration from the backend.
|
|
2977
3113
|
*
|
|
2978
3114
|
* The config describes which auth methods are enabled so SDK
|
|
2979
3115
|
* components can auto-configure without manual props.
|
|
2980
|
-
*
|
|
2981
|
-
* Reads `baseURL` and `fetchFn` directly off the generated parent
|
|
2982
|
-
* class — earlier generator templates kept a `this.config` ref but
|
|
2983
|
-
* the current embedded template stores fields individually, so the
|
|
2984
|
-
* old `(this as any).config.baseURL` shape produced `undefined` and
|
|
2985
|
-
* the URL constructor threw.
|
|
2986
3116
|
*/
|
|
2987
3117
|
async fetchClientConfig(publishableKey) {
|
|
2988
|
-
|
|
2989
|
-
const fetchFn = this.fetchFn ?? globalThis.fetch;
|
|
2990
|
-
const url = new URL("/v1/client-config", baseURL);
|
|
2991
|
-
if (publishableKey) {
|
|
2992
|
-
url.searchParams.set("key", publishableKey);
|
|
2993
|
-
}
|
|
2994
|
-
const res = await fetchFn(url.toString(), {
|
|
2995
|
-
method: "GET",
|
|
2996
|
-
headers: { "Content-Type": "application/json" }
|
|
2997
|
-
});
|
|
2998
|
-
if (!res.ok) {
|
|
2999
|
-
throw new AuthClientError("Failed to fetch client config", res.status);
|
|
3000
|
-
}
|
|
3001
|
-
return res.json();
|
|
3118
|
+
return super.getClientConfig("", publishableKey);
|
|
3002
3119
|
}
|
|
3003
3120
|
};
|
|
3004
3121
|
|
|
@@ -3007,6 +3124,7 @@ var SESSION_STORAGE_KEY = "authsome:session";
|
|
|
3007
3124
|
var SESSION_KEY = SESSION_STORAGE_KEY;
|
|
3008
3125
|
var CONFIG_KEY = "authsome:client_config";
|
|
3009
3126
|
var REFRESH_BEFORE_MS = 6e4;
|
|
3127
|
+
var MAX_TIMEOUT_MS = 2147483647;
|
|
3010
3128
|
var CONFIG_TTL_MS = 5 * 6e4;
|
|
3011
3129
|
var DEFAULT_SESSION_TTL_MS = 36e5;
|
|
3012
3130
|
var MAX_REFRESH_ATTEMPTS = 5;
|
|
@@ -3045,19 +3163,45 @@ function isAuthRejection(err) {
|
|
|
3045
3163
|
}
|
|
3046
3164
|
return false;
|
|
3047
3165
|
}
|
|
3166
|
+
var refreshQueues = /* @__PURE__ */ new Map();
|
|
3167
|
+
async function withRefreshLock(key, operation) {
|
|
3168
|
+
if (typeof navigator !== "undefined" && navigator.locks) {
|
|
3169
|
+
await navigator.locks.request(key, operation);
|
|
3170
|
+
return;
|
|
3171
|
+
}
|
|
3172
|
+
const previous = refreshQueues.get(key) ?? Promise.resolve();
|
|
3173
|
+
const next = previous.catch(() => {
|
|
3174
|
+
}).then(operation);
|
|
3175
|
+
refreshQueues.set(key, next);
|
|
3176
|
+
try {
|
|
3177
|
+
await next;
|
|
3178
|
+
} finally {
|
|
3179
|
+
if (refreshQueues.get(key) === next) refreshQueues.delete(key);
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3048
3182
|
var AuthManager = class {
|
|
3049
3183
|
client;
|
|
3050
3184
|
storage;
|
|
3051
3185
|
state = { status: "idle" };
|
|
3052
3186
|
listeners = /* @__PURE__ */ new Set();
|
|
3187
|
+
initializePromise = null;
|
|
3188
|
+
refreshPromise = null;
|
|
3189
|
+
refreshLockKey;
|
|
3190
|
+
active = true;
|
|
3191
|
+
smsCeremonyVersion = 0;
|
|
3053
3192
|
refreshTimer = null;
|
|
3054
3193
|
onError;
|
|
3055
3194
|
publishableKey;
|
|
3195
|
+
configCacheKey;
|
|
3056
3196
|
clientConfig = null;
|
|
3057
3197
|
configListeners = /* @__PURE__ */ new Set();
|
|
3058
3198
|
configFetchPromise = null;
|
|
3059
3199
|
constructor(config) {
|
|
3060
3200
|
this.client = new AuthClient2(config);
|
|
3201
|
+
let baseEnd = config.baseURL.length;
|
|
3202
|
+
while (baseEnd > 0 && config.baseURL[baseEnd - 1] === "/") baseEnd--;
|
|
3203
|
+
this.configCacheKey = `${CONFIG_KEY}:${JSON.stringify([config.baseURL.slice(0, baseEnd), config.publishableKey ?? ""])}`;
|
|
3204
|
+
this.refreshLockKey = `authsome:refresh:${config.baseURL}:${config.publishableKey ?? ""}`;
|
|
3061
3205
|
this.storage = config.storage ?? createMemoryStorage();
|
|
3062
3206
|
this.onError = config.onError;
|
|
3063
3207
|
this.publishableKey = config.publishableKey;
|
|
@@ -3089,9 +3233,20 @@ var AuthManager = class {
|
|
|
3089
3233
|
* When a publishableKey is set, also fetches client config in parallel.
|
|
3090
3234
|
* Call this once on app start.
|
|
3091
3235
|
*/
|
|
3092
|
-
|
|
3236
|
+
initialize() {
|
|
3237
|
+
this.active = true;
|
|
3238
|
+
if (!this.initializePromise) {
|
|
3239
|
+
this.initializePromise = this.restoreSession().finally(() => {
|
|
3240
|
+
this.initializePromise = null;
|
|
3241
|
+
});
|
|
3242
|
+
}
|
|
3243
|
+
return this.initializePromise;
|
|
3244
|
+
}
|
|
3245
|
+
async restoreSession() {
|
|
3246
|
+
let session;
|
|
3093
3247
|
if (this.publishableKey && !this.clientConfig) {
|
|
3094
|
-
void this.fetchClientConfig()
|
|
3248
|
+
void this.fetchClientConfig().catch(() => {
|
|
3249
|
+
});
|
|
3095
3250
|
}
|
|
3096
3251
|
try {
|
|
3097
3252
|
const raw = await this.storage.getItem(SESSION_KEY);
|
|
@@ -3099,7 +3254,7 @@ var AuthManager = class {
|
|
|
3099
3254
|
this.setState({ status: "unauthenticated" });
|
|
3100
3255
|
return;
|
|
3101
3256
|
}
|
|
3102
|
-
|
|
3257
|
+
session = JSON.parse(raw);
|
|
3103
3258
|
const expiresAt = new Date(session.expires_at).getTime();
|
|
3104
3259
|
if (Date.now() >= expiresAt) {
|
|
3105
3260
|
await this.refreshSession(session.refresh_token);
|
|
@@ -3110,7 +3265,11 @@ var AuthManager = class {
|
|
|
3110
3265
|
this.setState({ status: "authenticated", user, session });
|
|
3111
3266
|
this.scheduleRefresh(session);
|
|
3112
3267
|
} catch (err) {
|
|
3113
|
-
|
|
3268
|
+
if (session?.refresh_token && isAuthRejection(err)) {
|
|
3269
|
+
await this.refreshSession(session.refresh_token);
|
|
3270
|
+
} else {
|
|
3271
|
+
await this.handleInitFailure(err);
|
|
3272
|
+
}
|
|
3114
3273
|
}
|
|
3115
3274
|
}
|
|
3116
3275
|
/**
|
|
@@ -3146,7 +3305,12 @@ var AuthManager = class {
|
|
|
3146
3305
|
return;
|
|
3147
3306
|
}
|
|
3148
3307
|
this.setState({ status: "unknown", session });
|
|
3149
|
-
this.
|
|
3308
|
+
this.clearRefreshTimer();
|
|
3309
|
+
if (this.active) {
|
|
3310
|
+
this.refreshTimer = setTimeout(() => {
|
|
3311
|
+
void this.initialize();
|
|
3312
|
+
}, REFRESH_RETRY_BASE_MS);
|
|
3313
|
+
}
|
|
3150
3314
|
}
|
|
3151
3315
|
/** Sign in with email & password. */
|
|
3152
3316
|
async signIn(credentials) {
|
|
@@ -3179,10 +3343,51 @@ var AuthManager = class {
|
|
|
3179
3343
|
throw err;
|
|
3180
3344
|
}
|
|
3181
3345
|
}
|
|
3346
|
+
/**
|
|
3347
|
+
* Begin an SSO login against one of the app's configured connections.
|
|
3348
|
+
*
|
|
3349
|
+
* Resolves to the IdP `login_url`; the caller navigates the browser there.
|
|
3350
|
+
* `returnUrl` is where the backend lands the browser after the IdP round
|
|
3351
|
+
* trip, with `?code=` on success or `?sso_error=` on failure. It must be an
|
|
3352
|
+
* allowlisted https origin (or localhost). Omit it to use the server's
|
|
3353
|
+
* default `/sso/callback` landing.
|
|
3354
|
+
*/
|
|
3355
|
+
async startSSOLogin(connectionId, returnUrl) {
|
|
3356
|
+
const res = await this.client.startSSOLogin(connectionId, returnUrl);
|
|
3357
|
+
return res.login_url;
|
|
3358
|
+
}
|
|
3359
|
+
/**
|
|
3360
|
+
* Finish an SSO login by exchanging the one-time `code` from the callback
|
|
3361
|
+
* URL for a session. The exchange is publishable-key authed, so no bearer
|
|
3362
|
+
* token is sent.
|
|
3363
|
+
*/
|
|
3364
|
+
async completeSSOLogin(code) {
|
|
3365
|
+
try {
|
|
3366
|
+
const res = await this.client.ssoExchange({ code }, "");
|
|
3367
|
+
const session = toSession(res);
|
|
3368
|
+
await this.handleAuthResponse(res.user, session);
|
|
3369
|
+
} catch (err) {
|
|
3370
|
+
this.handleError(err);
|
|
3371
|
+
throw err;
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3182
3374
|
/** Sign up with email & password. */
|
|
3183
3375
|
async signUp(data) {
|
|
3184
3376
|
try {
|
|
3185
|
-
await this.
|
|
3377
|
+
const config = this.clientConfig ?? (this.publishableKey ? await this.fetchClientConfig() : null);
|
|
3378
|
+
if (config?.signup_enabled === false)
|
|
3379
|
+
throw new Error("Signup isn't available for this app.");
|
|
3380
|
+
const res = await this.client.signUp(data);
|
|
3381
|
+
const verification = config?.email_verification;
|
|
3382
|
+
if (verification && (!verification.enabled || !verification.required)) {
|
|
3383
|
+
try {
|
|
3384
|
+
const user = await this.client.getMe(res.session_token);
|
|
3385
|
+
await this.handleAuthResponse(user, toSession(res));
|
|
3386
|
+
return;
|
|
3387
|
+
} catch (err) {
|
|
3388
|
+
if (!isAuthRejection(err)) throw err;
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3186
3391
|
this.setState({
|
|
3187
3392
|
status: "verification_pending",
|
|
3188
3393
|
email: data.email
|
|
@@ -3213,6 +3418,8 @@ var AuthManager = class {
|
|
|
3213
3418
|
}
|
|
3214
3419
|
/** Sign out and clear the session. */
|
|
3215
3420
|
async signOut() {
|
|
3421
|
+
this.smsCeremonyVersion++;
|
|
3422
|
+
this.clearRefreshTimer();
|
|
3216
3423
|
const token = this.getSessionToken();
|
|
3217
3424
|
if (token) {
|
|
3218
3425
|
try {
|
|
@@ -3237,7 +3444,9 @@ var AuthManager = class {
|
|
|
3237
3444
|
async submitMFAChallenge(code) {
|
|
3238
3445
|
const state = this.state;
|
|
3239
3446
|
if (state.status !== "mfa_required") {
|
|
3240
|
-
throw new Error(
|
|
3447
|
+
throw new Error(
|
|
3448
|
+
"auth: submitMFAChallenge called outside the mfa_required state"
|
|
3449
|
+
);
|
|
3241
3450
|
}
|
|
3242
3451
|
try {
|
|
3243
3452
|
const res = await this.client.challengeMFA({
|
|
@@ -3262,41 +3471,51 @@ var AuthManager = class {
|
|
|
3262
3471
|
async submitMFACode(_enrollmentId, code) {
|
|
3263
3472
|
return this.submitMFAChallenge(code);
|
|
3264
3473
|
}
|
|
3265
|
-
/** Submit
|
|
3474
|
+
/** Submit a recovery code against the active login ticket. */
|
|
3266
3475
|
async submitRecoveryCode(code) {
|
|
3267
|
-
this.
|
|
3268
|
-
try {
|
|
3269
|
-
const res = await this.client.verifyRecoveryCode(code);
|
|
3270
|
-
const session = toSession(res);
|
|
3271
|
-
await this.handleAuthResponse(res.user, session);
|
|
3272
|
-
} catch (err) {
|
|
3273
|
-
this.handleError(err);
|
|
3274
|
-
}
|
|
3476
|
+
return this.submitMFAChallenge(code);
|
|
3275
3477
|
}
|
|
3276
|
-
/** Send an SMS code for
|
|
3478
|
+
/** Send an SMS code for an already authenticated session. */
|
|
3277
3479
|
async sendSMSCode() {
|
|
3278
|
-
const
|
|
3279
|
-
|
|
3280
|
-
|
|
3480
|
+
const state = this.requireSMSAuthState();
|
|
3481
|
+
const version = this.smsCeremonyVersion;
|
|
3482
|
+
const result = await this.client.sendSMSCodeForMFA(
|
|
3483
|
+
state.session.session_token
|
|
3484
|
+
);
|
|
3485
|
+
this.checkSMSAuthState(state, version);
|
|
3486
|
+
if (!result.sent) throw new Error("SMS code was not sent");
|
|
3487
|
+
return result;
|
|
3281
3488
|
}
|
|
3282
|
-
/**
|
|
3489
|
+
/** Verify an SMS ceremony without replacing the authenticated session. */
|
|
3283
3490
|
async submitSMSCode(code) {
|
|
3284
|
-
this.
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3491
|
+
const state = this.requireSMSAuthState();
|
|
3492
|
+
const version = this.smsCeremonyVersion;
|
|
3493
|
+
const result = await this.client.verifySMSCodeForMFA(
|
|
3494
|
+
code,
|
|
3495
|
+
state.session.session_token
|
|
3496
|
+
);
|
|
3497
|
+
this.checkSMSAuthState(state, version);
|
|
3498
|
+
if (!result.verified || result.method !== "sms") {
|
|
3499
|
+
throw new Error("SMS verification failed");
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
requireSMSAuthState() {
|
|
3503
|
+
if (!this.active || this.state.status !== "authenticated" || !this.state.session.session_token) {
|
|
3504
|
+
throw new Error("SMS verification requires an authenticated session");
|
|
3505
|
+
}
|
|
3506
|
+
return this.state;
|
|
3507
|
+
}
|
|
3508
|
+
checkSMSAuthState(state, version) {
|
|
3509
|
+
if (!this.active || this.state !== state || this.smsCeremonyVersion !== version) {
|
|
3510
|
+
throw new Error("Session changed during SMS verification");
|
|
3293
3511
|
}
|
|
3294
3512
|
}
|
|
3295
3513
|
/** Refresh the current session manually. */
|
|
3296
3514
|
async refreshNow() {
|
|
3297
|
-
const
|
|
3298
|
-
if (
|
|
3299
|
-
|
|
3515
|
+
const raw = await this.storage.getItem(SESSION_KEY);
|
|
3516
|
+
if (!raw) return;
|
|
3517
|
+
const session = JSON.parse(raw);
|
|
3518
|
+
if (session.refresh_token) await this.refreshSession(session.refresh_token);
|
|
3300
3519
|
}
|
|
3301
3520
|
/**
|
|
3302
3521
|
* Get the current session token (if authenticated). Returns null
|
|
@@ -3346,7 +3565,7 @@ var AuthManager = class {
|
|
|
3346
3565
|
return this.configFetchPromise;
|
|
3347
3566
|
}
|
|
3348
3567
|
try {
|
|
3349
|
-
const cached = await this.storage.getItem(
|
|
3568
|
+
const cached = await this.storage.getItem(this.configCacheKey);
|
|
3350
3569
|
if (cached) {
|
|
3351
3570
|
const { config, fetchedAt } = JSON.parse(cached);
|
|
3352
3571
|
if (Date.now() - fetchedAt < CONFIG_TTL_MS) {
|
|
@@ -3360,7 +3579,7 @@ var AuthManager = class {
|
|
|
3360
3579
|
this.setClientConfig(config);
|
|
3361
3580
|
try {
|
|
3362
3581
|
await this.storage.setItem(
|
|
3363
|
-
|
|
3582
|
+
this.configCacheKey,
|
|
3364
3583
|
JSON.stringify({ config, fetchedAt: Date.now() })
|
|
3365
3584
|
);
|
|
3366
3585
|
} catch {
|
|
@@ -3373,6 +3592,7 @@ var AuthManager = class {
|
|
|
3373
3592
|
}
|
|
3374
3593
|
/** Tear down: clear timers and listeners. */
|
|
3375
3594
|
destroy() {
|
|
3595
|
+
this.active = false;
|
|
3376
3596
|
this.clearRefreshTimer();
|
|
3377
3597
|
this.listeners.clear();
|
|
3378
3598
|
this.configListeners.clear();
|
|
@@ -3383,43 +3603,46 @@ var AuthManager = class {
|
|
|
3383
3603
|
this.setState({ status: "authenticated", user, session });
|
|
3384
3604
|
this.scheduleRefresh(session);
|
|
3385
3605
|
}
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3606
|
+
// Keep rotation and persistence in the same lock. Once the server rotates a
|
|
3607
|
+
// token, every later operation must read its replacement from storage.
|
|
3608
|
+
refreshSession(refreshToken, attempt = 0) {
|
|
3609
|
+
if (!this.refreshPromise) {
|
|
3610
|
+
this.refreshPromise = withRefreshLock(
|
|
3611
|
+
this.refreshLockKey,
|
|
3612
|
+
() => this.exchangeSession(refreshToken, attempt)
|
|
3613
|
+
).finally(() => {
|
|
3614
|
+
this.refreshPromise = null;
|
|
3615
|
+
});
|
|
3616
|
+
}
|
|
3617
|
+
return this.refreshPromise;
|
|
3618
|
+
}
|
|
3619
|
+
async exchangeSession(refreshToken, attempt) {
|
|
3620
|
+
let current;
|
|
3621
|
+
let exchanged = false;
|
|
3399
3622
|
try {
|
|
3400
|
-
const
|
|
3401
|
-
|
|
3402
|
-
await this.persistSession(newSession);
|
|
3403
|
-
this.setState({ status: "authenticated", user, session: newSession });
|
|
3404
|
-
this.scheduleRefresh(newSession);
|
|
3405
|
-
} catch (err) {
|
|
3406
|
-
if (isAuthRejection(err)) {
|
|
3623
|
+
const raw = await this.storage.getItem(SESSION_KEY);
|
|
3624
|
+
if (!raw) {
|
|
3407
3625
|
this.setState({ status: "unauthenticated" });
|
|
3408
3626
|
return;
|
|
3409
3627
|
}
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3628
|
+
current = JSON.parse(raw);
|
|
3629
|
+
if (current.refresh_token === refreshToken || Date.now() >= Date.parse(current.expires_at)) {
|
|
3630
|
+
current = await this.client.refresh(current.refresh_token);
|
|
3631
|
+
exchanged = true;
|
|
3632
|
+
await this.persistSession(current);
|
|
3633
|
+
}
|
|
3634
|
+
const user = await this.client.getMe(current.session_token);
|
|
3635
|
+
this.setState({ status: "authenticated", user, session: current });
|
|
3636
|
+
this.scheduleRefresh(current);
|
|
3637
|
+
} catch (err) {
|
|
3638
|
+
this.clearRefreshTimer();
|
|
3639
|
+
if (!exchanged && isAuthRejection(err) && current?.refresh_token === refreshToken) {
|
|
3640
|
+
await this.clearSession();
|
|
3419
3641
|
this.setState({ status: "unauthenticated" });
|
|
3420
3642
|
return;
|
|
3421
3643
|
}
|
|
3422
|
-
this.
|
|
3644
|
+
if (current) this.setState({ status: "unknown", session: current });
|
|
3645
|
+
if (attempt >= MAX_REFRESH_ATTEMPTS || !this.active) return;
|
|
3423
3646
|
this.refreshTimer = setTimeout(
|
|
3424
3647
|
() => {
|
|
3425
3648
|
void this.refreshSession(refreshToken, attempt + 1);
|
|
@@ -3430,15 +3653,19 @@ var AuthManager = class {
|
|
|
3430
3653
|
}
|
|
3431
3654
|
scheduleRefresh(session) {
|
|
3432
3655
|
this.clearRefreshTimer();
|
|
3656
|
+
if (!this.active) return;
|
|
3433
3657
|
const expiresAt = new Date(session.expires_at).getTime();
|
|
3434
3658
|
const delay = expiresAt - Date.now() - REFRESH_BEFORE_MS;
|
|
3435
|
-
if (delay <= 0) {
|
|
3659
|
+
if (!Number.isFinite(expiresAt) || delay <= 0) {
|
|
3436
3660
|
void this.refreshSession(session.refresh_token);
|
|
3437
3661
|
return;
|
|
3438
3662
|
}
|
|
3439
|
-
this.refreshTimer = setTimeout(
|
|
3440
|
-
|
|
3441
|
-
|
|
3663
|
+
this.refreshTimer = setTimeout(
|
|
3664
|
+
() => {
|
|
3665
|
+
this.scheduleRefresh(session);
|
|
3666
|
+
},
|
|
3667
|
+
Math.min(delay, MAX_TIMEOUT_MS)
|
|
3668
|
+
);
|
|
3442
3669
|
}
|
|
3443
3670
|
clearRefreshTimer() {
|
|
3444
3671
|
if (this.refreshTimer !== null) {
|
|
@@ -3616,6 +3843,7 @@ export {
|
|
|
3616
3843
|
isSafeRedirect,
|
|
3617
3844
|
prepareCreationOptions,
|
|
3618
3845
|
prepareRequestOptions,
|
|
3846
|
+
readOrganizationList,
|
|
3619
3847
|
safeRedirectTarget,
|
|
3620
3848
|
serializeCredential
|
|
3621
3849
|
};
|