@atlasauth/js 0.5.0 → 0.6.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/CHANGELOG.md ADDED
@@ -0,0 +1,152 @@
1
+ # @atlasauth/js changelog
2
+
3
+ ## 0.6.0
4
+ - **Per-flow captcha widget rendering**, shared by the prebuilt sign-in / sign-up UIs:
5
+ `renderCaptchaWidget(flow, container)` → `{ token: Promise<string>, remove() }`,
6
+ `getCaptchaToken(flow, container)`, `loadCaptchaProvider(provider, siteKey, win)`,
7
+ `isRenderableCaptcha(provider)`, `flowNeedsCaptcha(flow)`, `isCaptchaError(error)` and
8
+ `CAPTCHA_ERROR_CODES`. Renders Turnstile, hCaptcha, reCAPTCHA v2, v3 and Enterprise, honouring the
9
+ `invisible` / `managed` / `visible` widget mode. A failed script load is retried on the next render.
10
+ - **Added types** `FlowCaptcha` (`{ enabled, provider?, siteKey?, widget? }` — one entry of
11
+ `/v1/appearance` `captchaFlows`), `CaptchaFlowKey`, `CaptchaWidgetMode`, `CaptchaWidgetHandle`.
12
+ - **Framework-agnostic captcha lifecycle** for the prebuilt forms of every framework SDK:
13
+ `createCaptchaController(options?)` → `CaptchaController` (`needed`, `configure(options)`,
14
+ `attach(el)`, `getToken()`, `reset()`, `destroy()`), with the new `CaptchaControllerOptions` type
15
+ (`{ flow, active, eager?, onSolved? }`); `localizeCaptchaErrors(errors, t)`.
16
+ - **Sign-in / sign-up submit helpers:** `signInCaptchaOptions(flow, state, onChallengeSolved)`,
17
+ `advanceWithCaptcha(client, state, values, { captcha?, identifier?, t })` → `{ state, identifier }`
18
+ (adds the captcha token on the identifier / `needs_captcha` step, reports a rejected token as
19
+ `CAPTCHA_FAILED` and draws a fresh widget), and `createSignUp(client, values, { consent, captcha?, t })`
20
+ (posts `/v1/client/sign_ups` with the sign-up captcha's token).
21
+ - **Instance settings:** `fetchInstanceSettings(client)` (`GET /v1/appearance`, null on failure),
22
+ `instanceSettingsFrom(body)`, `toSignUpInput(values, consent, captchaToken?)`, and the types
23
+ `InstanceSettings` (`{ captchaFlows, signUpFields, legal }`) and `PublicSignUpField` (moved here from
24
+ `@atlasauth/react`, which re-exports them unchanged).
25
+
26
+ - **New: `resolveFrontendApi(value, { component?, allowSameOrigin? })`** — validates a provider's
27
+ `frontendApi` and returns the request base URL. Throws an **`AtlasConfigError`** (new, exported;
28
+ `code: AtlasConfigErrorCode` = `'FRONTEND_API_REQUIRED' | 'FRONTEND_API_INVALID'`) when the value is
29
+ missing, non-string, blank, not an absolute `https:` URL (`http:` only for localhost / local development hosts), or
30
+ carries credentials, a query or a fragment. `"same-origin"` (new constant `SAME_ORIGIN_FRONTEND_API`)
31
+ resolves to `""` and a root-relative path such as `"/atlas"` is kept, for reverse proxies; both are refused
32
+ with `allowSameOrigin: false` (native apps). An explicit `""` is the **deprecated** same-origin spelling: it
33
+ still resolves to `""` (one-time console warning pointing at `"same-origin"`), and is refused with
34
+ `allowSameOrigin: false`. Every framework SDK now uses it instead of `frontendApi ?? ''`.
35
+ - **New: `revokeSession(request, sessionId, { retries = 1 })`** → `RevokeSessionOutcome`
36
+ (`{ revoked, status, response?, activeSessionId? }`). Retries once on a network failure, 5xx or 429; a 404
37
+ counts as revoked; `activeSessionId` is the parked session the server promoted.
38
+ - **New: `SessionTombstones`** — remembers (memory + `sessionStorage`, bounded to 20) the session ids a tab
39
+ signed out of, so a re-boot from a still-valid refresh cookie never re-adopts them.
40
+ - **New types:** `SignOutResult` (`{ revoked: boolean; activeSessionId?: string }`), `SignOutError`
41
+ (`code: 'SIGN_OUT_REVOKE_FAILED'`, `sessionId`, `status`), `ResolveFrontendApiOptions`, `RevokeSessionOutcome`.
42
+ - **`SignOutOptions.strict?: boolean`** added (reject with `SignOutError` when the revoke fails; honoured by
43
+ `@atlasauth/react`).
44
+ - **Popup OAuth:** when the popup closes without posting a result, `startOauthPopup()` checks
45
+ `GET /v1/client` once; if a session now exists it resolves `{ error: 'popup_closed', sessionDetected: true }`
46
+ (new `PopupStartResult.sessionDetected?: boolean`) so the caller re-boots instead of treating it as a cancel.
47
+ - **End-user API key self-service.** New `listApiKeys(client, { organizationId? })`,
48
+ `createApiKey(client, input)`, `updateApiKey(client, id, { name })` and
49
+ `revokeApiKey(client, id)` drive `/v1/client/me/api_keys` through a `FapiClient`.
50
+ Added types: `UserApiKey` (`id`, `subject_type`, `subject_id`, `name`, `prefix`, `claims`,
51
+ `scopes`, optional `limits`, `last_used_at`, `expires_at`, `revoked_at`, `created_at`),
52
+ `CreatedUserApiKey` (adds the one-time `secret`, `note`), `UserApiKeyList` (`data`,
53
+ `allowedScopes`, `allowedClaims`, `maxActiveKeys`, `activeCount`) and `CreateUserApiKeyInput`
54
+ (`name`, `expiresAt`, `expiresInSeconds`, `scopes`, `claims`, `organizationId`). A refused call
55
+ throws the new `ApiKeyRequestError` (`code`, `status`, `param`, `needsReverification`).
56
+
57
+ ## 0.5.0
58
+ - **OIDC Front-Channel Logout helpers.** `runFrontChannelLogout(urls, { timeoutMs })` loads each
59
+ front-channel logout URL in a hidden, sandboxed iframe and resolves once they have all loaded or
60
+ after the timeout (default 5 s); it never throws, only loads `https:` URLs (and `http://localhost`
61
+ for development), and is a no-op without a DOM (SSR). `runFrontChannelLogoutFromResponse(res)`
62
+ reads `frontchannel_logout_urls` from a Frontend API sign-out response. The framework SDKs'
63
+ `signOut()` uses them; the new `SignOutOptions` type (`{ frontchannel?: boolean }`) is the option
64
+ they accept.
65
+
66
+
67
+ ## 0.4.0
68
+ - `AttemptView` now carries `id` (`string | null`; `null` only on the pre-attempt captcha
69
+ challenge), plus optional `object`, `identifier` and `expires_at`, matching what the server
70
+ returns, so consumers can read `attempt.id` without a cast.
71
+
72
+ ## 0.3.0
73
+ - **Captcha for any action.** New `captcha(client)` returns `config(action?)` (fetch the
74
+ centrally managed public provider + site key), `verify(token, action?)` (server-side check)
75
+ and `protect(element, action?)` (load the provider's widget script, render it, resolve the
76
+ solved token) — so an app can protect any action without hardcoding provider keys.
77
+ - New `CaptchaWidgetConfig` and `CaptchaVerification` types, plus `loadCaptchaScript()` /
78
+ `resetCaptchaScripts()`.
79
+
80
+ ## 0.2.0
81
+ - **Native-session helpers** for apps without a cookie jar (React Native, Electron, extension
82
+ service workers): `exchangeForSession()` trades a first-party OAuth access token for an Atlas
83
+ session, and `refreshNativeSession()` rotates it without cookies. Both return `null` instead
84
+ of throwing on failure.
85
+ - `NativeSessionManager` holds the current session, hands out a fresh session JWT and fires
86
+ `onChange` on every rotation so the new refresh token can be persisted.
87
+ `serializeNativeSession()` / `parseNativeSession()` help store it. New `NativeSession` type.
88
+
89
+ ## 0.1.9
90
+ - Fixed: the invisible Google overlay now uses a near-zero opacity instead of exactly `0`.
91
+ Google's button treats an element at `opacity: 0` as not visible and silently ignores the
92
+ click; the overlay stays invisible to users but now receives the click.
93
+
94
+ ## 0.1.8
95
+ - Fixed: the native Google overlay now stretches Google's button iframe, which Google injects
96
+ asynchronously, to cover the whole wrapper (re-applied via a `MutationObserver`), so clicks
97
+ near the edges no longer miss.
98
+
99
+ ## 0.1.7
100
+ - Added: `renderGoogleOverlayButton` accepts `visible` — `false` (default) keeps Google's button
101
+ as an invisible click target over your own styled button; `true` shows Google's own button.
102
+ - Added: `diagnoseProvider()` reports a provider's resolved mode, client id, overlay geometry and
103
+ whether a credential was minted this session, for debugging a dead Google button. New `Rect`,
104
+ `OverlayGeometry` and `ProviderDiagnosis` types; `resetGoogleCredentialFlag()`.
105
+ - Changed: the invisible Google overlay is stretched to fill its wrapper, and in development it
106
+ warns in the console when it is misaligned or undersized.
107
+
108
+ ## 0.1.6
109
+ - Added: `reportProviderUnavailable(client, provider, reason)` sends a best-effort beacon when a
110
+ native provider (Google) can't be used, so the failure shows up in the dashboard. New
111
+ `ProviderUnavailableReason` type (`'script_blocked' | 'not_rendered'`).
112
+ - Changed: device telemetry honours Do-Not-Track — when it is on, no persistent device id is
113
+ stored. New `doNotTrackEnabled()`.
114
+
115
+ ## 0.1.5
116
+ - Added: popup-mode social / OIDC sign-in. `startOauthPopup(client, { provider, redirectUrl })`
117
+ opens the provider in a popup, accepts the result only from the Frontend API origin, and falls
118
+ back to a full-page redirect when the popup is blocked. New `parseOAuthPopupMessage()`,
119
+ `PopupOutcome`, `PopupStartResult` and `OAuthPopupHandlers`.
120
+ - Added: `FapiClient.frontendApiOrigin`.
121
+
122
+ ## 0.1.4
123
+ - Added: `startOauthRedirect(client, { provider, redirectUrl })` starts a redirect OAuth sign-in
124
+ and returns the provider's authorization URL.
125
+
126
+ ## 0.1.3
127
+ - Added: `renderGoogleOverlayButton` accepts `oneTap` to also show Google One Tap (off by
128
+ default).
129
+
130
+ ## 0.1.2
131
+ - Added: native Google sign-in with replay protection. `mintNativeNonce(client, provider)` gets a
132
+ single-use nonce from Atlas and `nativeSignIn(client, { provider, idToken, nonce })` signs in
133
+ with a provider id token, returning the same `FlowState` as the other sign-in flows.
134
+
135
+ ## 0.1.1
136
+ - First published release.
137
+ - Sign-in / sign-up flow driver over the Frontend API (`FapiClient`, `initialFlow`, `advance`,
138
+ `startSignUp`, `prepareFactor`, `pollAttempt`) with attempt helpers (`nextStep`, `fieldErrors`,
139
+ `formErrors`).
140
+ - Session token cache with refresh scheduling (`TokenCache`) and cross-tab refresh election
141
+ (`winsElection`, `bestChannel`).
142
+ - Google Identity Services helpers (`loadGoogleIdentityServices`, `renderGoogleButton`,
143
+ `renderGoogleOverlayButton`), passkeys (`createPasskey`, `getPasskeyAssertion`,
144
+ `passkeysSupported`), Telegram and Sign-In with Ethereum request builders.
145
+ - Redirect handling (`readRedirectResult`, `stripRedirectParams`), password reset
146
+ (`startPasswordReset`, `advancePasswordReset`), step-up re-authentication
147
+ (`reauthenticate`), connecting an extra provider (`connectExternalAccount`) and provider
148
+ tokens (`getProviderToken`).
149
+ - Silent cross-site session check (`checkSession`, `handleSilentCallback`) and device
150
+ telemetry (`sendTelemetry`).
151
+ - Authorization helpers re-exported from `@atlasauth/authz` (`hasFromClaims`,
152
+ `protectFromClaims`, `evaluate`, `ForbiddenError`, …).
@@ -0,0 +1,84 @@
1
+ import type { FapiClient } from './fapi';
2
+ /**
3
+ * End-user API key self-service — a signed-in user manages their OWN `ak_` keys
4
+ * (or, when the application allows it, their active organization's) over
5
+ * `/v1/client/me/api_keys`. Cookie-authenticated through the {@link FapiClient};
6
+ * the feature is off until the application enables it.
7
+ *
8
+ * Creating a key needs a RECENT re-authentication: a stale session gets an
9
+ * {@link ApiKeyRequestError} whose `code` is `STEP_UP_REQUIRED`. Re-authenticate
10
+ * (`reauthenticate()`), then call `createApiKey` again.
11
+ */
12
+ /** An end-user API key as its owner sees it. The secret is never part of it. */
13
+ export interface UserApiKey {
14
+ object: 'api_key';
15
+ id: string;
16
+ subject_type: 'user' | 'organization';
17
+ subject_id: string;
18
+ name: string | null;
19
+ /** The public `ak_…` head — recognises a key, cannot be used as one. */
20
+ prefix: string;
21
+ claims: Record<string, unknown>;
22
+ scopes: string[] | null;
23
+ /** Usage limits on the key, when the application sets them. */
24
+ limits?: Record<string, unknown> | null;
25
+ last_used_at: number | null;
26
+ expires_at: number | null;
27
+ revoked_at: number | null;
28
+ created_at: number;
29
+ }
30
+ /** The create response: the key plus its one-time `secret`. */
31
+ export interface CreatedUserApiKey extends UserApiKey {
32
+ /** The plaintext `ak_…` key. Shown exactly once — store it now. */
33
+ secret: string;
34
+ note?: string;
35
+ }
36
+ export interface UserApiKeyList {
37
+ data: UserApiKey[];
38
+ /** The scopes a new key may carry. */
39
+ allowedScopes: string[];
40
+ /** The claim names a new key may set. */
41
+ allowedClaims: string[];
42
+ /** The most active keys this user (or organization) may hold. */
43
+ maxActiveKeys: number;
44
+ /** Active (unrevoked, unexpired) keys held now. */
45
+ activeCount: number;
46
+ }
47
+ export interface CreateUserApiKeyInput {
48
+ name?: string;
49
+ /** Absolute expiry, epoch milliseconds. Not with `expiresInSeconds`. */
50
+ expiresAt?: number;
51
+ /** Relative lifetime in seconds. Not with `expiresAt`. */
52
+ expiresInSeconds?: number;
53
+ /** Only scopes the application allows (`allowedScopes`). */
54
+ scopes?: string[];
55
+ /** Only claim names the application allows (`allowedClaims`). */
56
+ claims?: Record<string, unknown>;
57
+ /** Create the key for this (active) organization instead of the user. */
58
+ organizationId?: string;
59
+ }
60
+ /** A refused request. `code` is the server's stable error code. */
61
+ export declare class ApiKeyRequestError extends Error {
62
+ readonly code: string;
63
+ readonly status: number;
64
+ readonly param?: string;
65
+ constructor(status: number, code: string, message: string, param?: string);
66
+ /** True when the action needs a fresh re-authentication first. */
67
+ get needsReverification(): boolean;
68
+ }
69
+ /** List the user's keys (or the active organization's, with `organizationId`). */
70
+ export declare function listApiKeys(client: FapiClient, options?: {
71
+ organizationId?: string;
72
+ }): Promise<UserApiKeyList>;
73
+ /** Create a key. The returned `secret` is shown exactly once. */
74
+ export declare function createApiKey(client: FapiClient, input?: CreateUserApiKeyInput): Promise<CreatedUserApiKey>;
75
+ /** Rename a key (`null` clears the name). */
76
+ export declare function updateApiKey(client: FapiClient, id: string, input: {
77
+ name: string | null;
78
+ }): Promise<UserApiKey>;
79
+ /** Revoke a key. Immediate and permanent. */
80
+ export declare function revokeApiKey(client: FapiClient, id: string): Promise<{
81
+ object: 'api_key';
82
+ id: string;
83
+ revoked: true;
84
+ }>;
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApiKeyRequestError = void 0;
4
+ exports.listApiKeys = listApiKeys;
5
+ exports.createApiKey = createApiKey;
6
+ exports.updateApiKey = updateApiKey;
7
+ exports.revokeApiKey = revokeApiKey;
8
+ /** A refused request. `code` is the server's stable error code. */
9
+ class ApiKeyRequestError extends Error {
10
+ code;
11
+ status;
12
+ param;
13
+ constructor(status, code, message, param) {
14
+ super(message);
15
+ this.name = 'ApiKeyRequestError';
16
+ this.status = status;
17
+ this.code = code;
18
+ this.param = param;
19
+ }
20
+ /** True when the action needs a fresh re-authentication first. */
21
+ get needsReverification() {
22
+ return this.code === 'STEP_UP_REQUIRED';
23
+ }
24
+ }
25
+ exports.ApiKeyRequestError = ApiKeyRequestError;
26
+ const BASE = '/v1/client/me/api_keys';
27
+ async function call(client, path, init = {}) {
28
+ const res = await client.request(path, init);
29
+ if (!res.ok || !res.data) {
30
+ const first = res.errors[0];
31
+ throw new ApiKeyRequestError(res.status, first?.code ?? 'UNKNOWN', first?.message ?? 'The request failed.', first?.param);
32
+ }
33
+ return res.data;
34
+ }
35
+ /** List the user's keys (or the active organization's, with `organizationId`). */
36
+ async function listApiKeys(client, options = {}) {
37
+ const query = options.organizationId
38
+ ? `?organization_id=${encodeURIComponent(options.organizationId)}`
39
+ : '';
40
+ const body = await call(client, `${BASE}${query}`, { method: 'GET' });
41
+ return {
42
+ data: body.data ?? [],
43
+ allowedScopes: body.allowed_scopes ?? [],
44
+ allowedClaims: body.allowed_claims ?? [],
45
+ maxActiveKeys: body.max_active_keys ?? 0,
46
+ activeCount: body.active_count ?? 0,
47
+ };
48
+ }
49
+ /** Create a key. The returned `secret` is shown exactly once. */
50
+ async function createApiKey(client, input = {}) {
51
+ const body = {};
52
+ if (input.name !== undefined)
53
+ body.name = input.name;
54
+ if (input.expiresAt !== undefined)
55
+ body.expires_at = input.expiresAt;
56
+ if (input.expiresInSeconds !== undefined)
57
+ body.expires_in_seconds = input.expiresInSeconds;
58
+ if (input.scopes !== undefined)
59
+ body.scopes = input.scopes;
60
+ if (input.claims !== undefined)
61
+ body.claims = input.claims;
62
+ if (input.organizationId !== undefined)
63
+ body.organization_id = input.organizationId;
64
+ return call(client, BASE, { method: 'POST', body: JSON.stringify(body) });
65
+ }
66
+ /** Rename a key (`null` clears the name). */
67
+ async function updateApiKey(client, id, input) {
68
+ return call(client, `${BASE}/${encodeURIComponent(id)}`, {
69
+ method: 'PATCH',
70
+ body: JSON.stringify({ name: input.name }),
71
+ });
72
+ }
73
+ /** Revoke a key. Immediate and permanent. */
74
+ async function revokeApiKey(client, id) {
75
+ return call(client, `${BASE}/${encodeURIComponent(id)}/revoke`, { method: 'POST' });
76
+ }
77
+ //# sourceMappingURL=api-keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-keys.js","sourceRoot":"","sources":["../src/api-keys.ts"],"names":[],"mappings":";;;AAsGA,kCAqBC;AAGD,oCAYC;AAGD,oCASC;AAGD,oCASC;AAjGD,mEAAmE;AACnE,MAAa,kBAAmB,SAAQ,KAAK;IAClC,IAAI,CAAS;IACb,MAAM,CAAS;IACf,KAAK,CAAU;IAExB,YAAY,MAAc,EAAE,IAAY,EAAE,OAAe,EAAE,KAAc;QACvE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,kEAAkE;IAClE,IAAI,mBAAmB;QACrB,OAAO,IAAI,CAAC,IAAI,KAAK,kBAAkB,CAAC;IAC1C,CAAC;CACF;AAjBD,gDAiBC;AAED,MAAM,IAAI,GAAG,wBAAwB,CAAC;AAEtC,KAAK,UAAU,IAAI,CAAI,MAAkB,EAAE,IAAY,EAAE,OAAoB,EAAE;IAC7E,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,OAAO,CAAI,IAAI,EAAE,IAAI,CAAC,CAAC;IAChD,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC5B,MAAM,IAAI,kBAAkB,CAC1B,GAAG,CAAC,MAAM,EACV,KAAK,EAAE,IAAI,IAAI,SAAS,EACxB,KAAK,EAAE,OAAO,IAAI,qBAAqB,EACvC,KAAK,EAAE,KAAK,CACb,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,CAAC;AAClB,CAAC;AAED,kFAAkF;AAC3E,KAAK,UAAU,WAAW,CAC/B,MAAkB,EAClB,UAAuC,EAAE;IAEzC,MAAM,KAAK,GAAG,OAAO,CAAC,cAAc;QAClC,CAAC,CAAC,oBAAoB,kBAAkB,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE;QAClE,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,IAAI,GAAG,MAAM,IAAI,CAMpB,MAAM,EAAE,GAAG,IAAI,GAAG,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACjD,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE;QACrB,aAAa,EAAE,IAAI,CAAC,cAAc,IAAI,EAAE;QACxC,aAAa,EAAE,IAAI,CAAC,cAAc,IAAI,EAAE;QACxC,aAAa,EAAE,IAAI,CAAC,eAAe,IAAI,CAAC;QACxC,WAAW,EAAE,IAAI,CAAC,YAAY,IAAI,CAAC;KACpC,CAAC;AACJ,CAAC;AAED,iEAAiE;AAC1D,KAAK,UAAU,YAAY,CAChC,MAAkB,EAClB,QAA+B,EAAE;IAEjC,MAAM,IAAI,GAA4B,EAAE,CAAC;IACzC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;QAAE,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACrD,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS;QAAE,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC;IACrE,IAAI,KAAK,CAAC,gBAAgB,KAAK,SAAS;QAAE,IAAI,CAAC,kBAAkB,GAAG,KAAK,CAAC,gBAAgB,CAAC;IAC3F,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;QAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC3D,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;QAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC3D,IAAI,KAAK,CAAC,cAAc,KAAK,SAAS;QAAE,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC,cAAc,CAAC;IACpF,OAAO,IAAI,CAAoB,MAAM,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC/F,CAAC;AAED,6CAA6C;AACtC,KAAK,UAAU,YAAY,CAChC,MAAkB,EAClB,EAAU,EACV,KAA8B;IAE9B,OAAO,IAAI,CAAa,MAAM,EAAE,GAAG,IAAI,IAAI,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE;QACnE,MAAM,EAAE,OAAO;QACf,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;KAC3C,CAAC,CAAC;AACL,CAAC;AAED,6CAA6C;AACtC,KAAK,UAAU,YAAY,CAChC,MAAkB,EAClB,EAAU;IAEV,OAAO,IAAI,CACT,MAAM,EACN,GAAG,IAAI,IAAI,kBAAkB,CAAC,EAAE,CAAC,SAAS,EAC1C,EAAE,MAAM,EAAE,MAAM,EAAE,CACnB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,95 @@
1
+ import { type FieldError } from './attempt';
2
+ import { type FapiClient, type FlowState } from './fapi';
3
+ import { type FlowCaptcha } from './captcha-widget';
4
+ /**
5
+ * The captcha half of a prebuilt sign-in / sign-up form, without a framework.
6
+ *
7
+ * The Vue, Svelte, Angular, Solid and Elements bindings each hold one of these
8
+ * per form; React's `useCaptcha` hook is the same lifecycle. It is driven by the
9
+ * instance's settings (`captchaFlows` from `/v1/appearance`), so switching
10
+ * captcha on in the dashboard makes the forms render it with no code change.
11
+ *
12
+ * Lifecycle: a visible/managed widget is drawn as soon as its slot is attached,
13
+ * so the user can solve it while filling in the form; an invisible one is
14
+ * executed only when the form is submitted ({@link CaptchaController.getToken}).
15
+ * Tokens are single-use, so after a submit spent one the caller
16
+ * {@link CaptchaController.reset}s, which draws a fresh widget.
17
+ */
18
+ export interface CaptchaControllerOptions {
19
+ /** The flow's captcha settings (`captchaFlows.signUp`, …). */
20
+ flow: FlowCaptcha | null | undefined;
21
+ /** Render at all — the caller's "this flow requires a token here" decision. */
22
+ active: boolean;
23
+ /** Draw the widget as soon as the slot is attached. Default: unless invisible. */
24
+ eager?: boolean;
25
+ /** Called with the token when an eagerly drawn widget is solved. */
26
+ onSolved?: (token: string) => void;
27
+ }
28
+ export interface CaptchaController {
29
+ /** Whether the current settings render a widget at all. */
30
+ readonly needed: boolean;
31
+ /** Replace the settings; redraws an attached widget only when they changed. */
32
+ configure(options: CaptchaControllerOptions): void;
33
+ /** Attach the element the widget draws into (null detaches and removes it). */
34
+ attach(el: HTMLElement | null): void;
35
+ /**
36
+ * The solved token, rendering/executing the widget first if needed. Null when
37
+ * no captcha is needed, or the provider failed (the server then refuses with
38
+ * CAPTCHA_REQUIRED, which the caller turns into a message).
39
+ */
40
+ getToken(): Promise<string | null>;
41
+ /** Drop the (spent) widget and, for an eager one, draw a fresh one. */
42
+ reset(): void;
43
+ /** Remove the widget for good (component teardown). */
44
+ destroy(): void;
45
+ }
46
+ export declare function createCaptchaController(initial?: CaptchaControllerOptions): CaptchaController;
47
+ /**
48
+ * Swap the server's captcha refusals (CAPTCHA_REQUIRED / CAPTCHA_FAILED, which
49
+ * the server ties to the `captcha_token` param) for a localized, form-level
50
+ * message — there is no `captcha_token` input to hang a field error on. `t`
51
+ * resolves the `captcha.required` / `captcha.failed` catalog keys.
52
+ */
53
+ export declare function localizeCaptchaErrors(errors: FieldError[], t: (key: string) => string): {
54
+ errors: FieldError[];
55
+ captchaRejected: boolean;
56
+ };
57
+ /**
58
+ * The sign-in captcha settings for the flow's current step: drawn on the
59
+ * identifier step when the instance ALWAYS requires one for sign-in (so the
60
+ * first POST already carries the token), and on the `needs_captcha` challenge
61
+ * step the server answers with when it withheld the attempt (always-on, or
62
+ * risk-triggered) — where it is drawn at once (even an invisible one) and
63
+ * solving it calls `onChallengeSolved`, which should resubmit.
64
+ */
65
+ export declare function signInCaptchaOptions(flow: FlowCaptcha | null | undefined, state: FlowState, onChallengeSolved: () => void): CaptchaControllerOptions;
66
+ /**
67
+ * {@link advance} with the sign-in captcha folded in — what a prebuilt sign-in
68
+ * form calls on submit. On the identifier / `needs_captcha` step it adds the
69
+ * widget's token (re-issuing the identifier POST the challenge marker withheld);
70
+ * a token the provider rejected comes back as the same challenge, so it reports
71
+ * CAPTCHA_FAILED and draws a fresh widget (tokens are single-use). Captcha
72
+ * refusals are localized through `t`.
73
+ *
74
+ * `identifier` is the last identifier the user typed (the challenge marker
75
+ * carries none to re-send); the returned one is what to keep for next time.
76
+ */
77
+ export declare function advanceWithCaptcha(client: FapiClient, state: FlowState, values: Record<string, string>, opts: {
78
+ captcha?: CaptchaController | null;
79
+ identifier?: string;
80
+ t: (key: string) => string;
81
+ }): Promise<{
82
+ state: FlowState;
83
+ identifier: string;
84
+ }>;
85
+ /**
86
+ * Create the account from a prebuilt create-account form: takes the sign-up
87
+ * captcha's token (when one is needed), POSTs `/v1/client/sign_ups` via
88
+ * {@link startSignUp}, localizes captcha refusals and, when the create was
89
+ * refused, draws a fresh widget (the token was spent either way).
90
+ */
91
+ export declare function createSignUp(client: FapiClient, values: Record<string, string>, opts: {
92
+ consent: boolean;
93
+ captcha?: CaptchaController | null;
94
+ t: (key: string) => string;
95
+ }): Promise<FlowState>;
@@ -0,0 +1,205 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createCaptchaController = createCaptchaController;
4
+ exports.localizeCaptchaErrors = localizeCaptchaErrors;
5
+ exports.signInCaptchaOptions = signInCaptchaOptions;
6
+ exports.advanceWithCaptcha = advanceWithCaptcha;
7
+ exports.createSignUp = createSignUp;
8
+ const attempt_1 = require("./attempt");
9
+ const fapi_1 = require("./fapi");
10
+ const instance_settings_1 = require("./instance-settings");
11
+ const captcha_widget_1 = require("./captcha-widget");
12
+ function createCaptchaController(initial) {
13
+ let options = initial ?? { flow: undefined, active: false };
14
+ let container = null;
15
+ let handle = null;
16
+ // The handle whose token a submit already took: the next getToken() draws a
17
+ // fresh widget instead of replaying it.
18
+ let spent = null;
19
+ // The settings the current widget was drawn with, so a redraw happens only
20
+ // when they actually change.
21
+ let drawnKey = null;
22
+ const needed = () => options.active && !!options.flow && (0, captcha_widget_1.isRenderableCaptcha)(options.flow.provider);
23
+ const eager = () => options.eager ?? options.flow?.widget !== 'invisible';
24
+ const key = () => {
25
+ const f = options.flow;
26
+ return `${needed()}|${f?.provider ?? ''}|${f?.siteKey ?? ''}|${f?.widget ?? ''}|${eager()}`;
27
+ };
28
+ const remove = () => {
29
+ handle?.remove();
30
+ handle = null;
31
+ drawnKey = null;
32
+ };
33
+ const mount = () => {
34
+ remove();
35
+ const flow = options.flow;
36
+ if (!needed() || !flow || !container)
37
+ return null;
38
+ const h = (0, captcha_widget_1.renderCaptchaWidget)(flow, container);
39
+ handle = h;
40
+ drawnKey = key();
41
+ // Swallow here so a removed / failed widget is never an unhandled
42
+ // rejection; getToken() reads the same promise and reports it.
43
+ h.token.then((token) => {
44
+ if (handle === h)
45
+ options.onSolved?.(token);
46
+ }, () => undefined);
47
+ return h;
48
+ };
49
+ const reset = () => {
50
+ remove();
51
+ if (needed() && eager())
52
+ mount();
53
+ };
54
+ return {
55
+ get needed() {
56
+ return needed();
57
+ },
58
+ configure(next) {
59
+ options = next;
60
+ if (!container)
61
+ return;
62
+ if (!needed() || !eager())
63
+ remove();
64
+ else if (drawnKey !== key())
65
+ mount();
66
+ },
67
+ attach(el) {
68
+ if (el === container)
69
+ return;
70
+ remove();
71
+ container = el;
72
+ if (el && needed() && eager())
73
+ mount();
74
+ },
75
+ async getToken() {
76
+ if (!needed())
77
+ return null;
78
+ let h = handle;
79
+ if (!h || spent === h)
80
+ h = mount();
81
+ if (!h)
82
+ return null;
83
+ try {
84
+ const token = await h.token;
85
+ spent = h;
86
+ return token;
87
+ }
88
+ catch {
89
+ // The provider errored / timed out: start over with a fresh widget.
90
+ if (handle === h)
91
+ reset();
92
+ return null;
93
+ }
94
+ },
95
+ reset,
96
+ destroy() {
97
+ remove();
98
+ container = null;
99
+ },
100
+ };
101
+ }
102
+ /**
103
+ * Swap the server's captcha refusals (CAPTCHA_REQUIRED / CAPTCHA_FAILED, which
104
+ * the server ties to the `captcha_token` param) for a localized, form-level
105
+ * message — there is no `captcha_token` input to hang a field error on. `t`
106
+ * resolves the `captcha.required` / `captcha.failed` catalog keys.
107
+ */
108
+ function localizeCaptchaErrors(errors, t) {
109
+ let captchaRejected = false;
110
+ const out = errors.map((error) => {
111
+ if (!(0, captcha_widget_1.isCaptchaError)(error))
112
+ return error;
113
+ captchaRejected = true;
114
+ return {
115
+ code: error.code,
116
+ message: t(error.code === 'CAPTCHA_REQUIRED' ? 'captcha.required' : 'captcha.failed'),
117
+ };
118
+ });
119
+ return { errors: out, captchaRejected };
120
+ }
121
+ /**
122
+ * The sign-in captcha settings for the flow's current step: drawn on the
123
+ * identifier step when the instance ALWAYS requires one for sign-in (so the
124
+ * first POST already carries the token), and on the `needs_captcha` challenge
125
+ * step the server answers with when it withheld the attempt (always-on, or
126
+ * risk-triggered) — where it is drawn at once (even an invisible one) and
127
+ * solving it calls `onChallengeSolved`, which should resubmit.
128
+ */
129
+ function signInCaptchaOptions(flow, state, onChallengeSolved) {
130
+ const kind = state.attempt ? (0, attempt_1.nextStep)(state.attempt).kind : 'collect_identifier';
131
+ const challenged = kind === 'collect_captcha';
132
+ return {
133
+ flow,
134
+ active: challenged || (kind === 'collect_identifier' && (0, captcha_widget_1.flowNeedsCaptcha)(flow)),
135
+ ...(challenged ? { eager: true, onSolved: () => onChallengeSolved() } : {}),
136
+ };
137
+ }
138
+ /**
139
+ * {@link advance} with the sign-in captcha folded in — what a prebuilt sign-in
140
+ * form calls on submit. On the identifier / `needs_captcha` step it adds the
141
+ * widget's token (re-issuing the identifier POST the challenge marker withheld);
142
+ * a token the provider rejected comes back as the same challenge, so it reports
143
+ * CAPTCHA_FAILED and draws a fresh widget (tokens are single-use). Captcha
144
+ * refusals are localized through `t`.
145
+ *
146
+ * `identifier` is the last identifier the user typed (the challenge marker
147
+ * carries none to re-send); the returned one is what to keep for next time.
148
+ */
149
+ async function advanceWithCaptcha(client, state, values, opts) {
150
+ const { captcha, t } = opts;
151
+ const kind = state.attempt ? (0, attempt_1.nextStep)(state.attempt).kind : 'collect_identifier';
152
+ let identifier = opts.identifier ?? '';
153
+ let input = values;
154
+ let sentToken = false;
155
+ if (kind === 'collect_identifier' || kind === 'collect_captcha') {
156
+ if (values.identifier)
157
+ identifier = values.identifier;
158
+ if (captcha?.needed) {
159
+ const token = await captcha.getToken();
160
+ if (token) {
161
+ input = {
162
+ ...values,
163
+ identifier: identifier || (state.attempt?.identifier ?? ''),
164
+ captcha_token: token,
165
+ };
166
+ sentToken = true;
167
+ }
168
+ else if (kind === 'collect_captcha') {
169
+ // Nothing to send until the widget yields a token.
170
+ return {
171
+ state: {
172
+ ...state,
173
+ busy: false,
174
+ errors: [{ code: 'CAPTCHA_REQUIRED', message: t('captcha.required') }],
175
+ },
176
+ identifier,
177
+ };
178
+ }
179
+ }
180
+ }
181
+ let next = await (0, fapi_1.advance)(client, state, input);
182
+ const rechallenged = !!next.attempt && (0, attempt_1.nextStep)(next.attempt).kind === 'collect_captcha';
183
+ if (sentToken && rechallenged) {
184
+ next = { ...next, errors: [{ code: 'CAPTCHA_FAILED', message: t('captcha.failed') }] };
185
+ }
186
+ next = { ...next, errors: localizeCaptchaErrors(next.errors, t).errors };
187
+ if (sentToken && (rechallenged || next.errors.length > 0))
188
+ captcha?.reset();
189
+ return { state: next, identifier };
190
+ }
191
+ /**
192
+ * Create the account from a prebuilt create-account form: takes the sign-up
193
+ * captcha's token (when one is needed), POSTs `/v1/client/sign_ups` via
194
+ * {@link startSignUp}, localizes captcha refusals and, when the create was
195
+ * refused, draws a fresh widget (the token was spent either way).
196
+ */
197
+ async function createSignUp(client, values, opts) {
198
+ const token = opts.captcha ? await opts.captcha.getToken() : null;
199
+ let next = await (0, fapi_1.startSignUp)(client, (0, instance_settings_1.toSignUpInput)(values, opts.consent, token));
200
+ next = { ...next, errors: localizeCaptchaErrors(next.errors, opts.t).errors };
201
+ if (next.errors.length > 0 && opts.captcha?.needed)
202
+ opts.captcha.reset();
203
+ return next;
204
+ }
205
+ //# sourceMappingURL=captcha-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"captcha-controller.js","sourceRoot":"","sources":["../src/captcha-controller.ts"],"names":[],"mappings":"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"}