@atlasauth/js 0.1.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.
Files changed (46) hide show
  1. package/dist/attempt.d.ts +71 -0
  2. package/dist/attempt.js +89 -0
  3. package/dist/attempt.js.map +1 -0
  4. package/dist/check-session.d.ts +56 -0
  5. package/dist/check-session.js +106 -0
  6. package/dist/check-session.js.map +1 -0
  7. package/dist/connect.d.ts +50 -0
  8. package/dist/connect.js +72 -0
  9. package/dist/connect.js.map +1 -0
  10. package/dist/fapi.d.ts +123 -0
  11. package/dist/fapi.js +314 -0
  12. package/dist/fapi.js.map +1 -0
  13. package/dist/index.d.ts +15 -0
  14. package/dist/index.js +43 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/native.d.ts +112 -0
  17. package/dist/native.js +201 -0
  18. package/dist/native.js.map +1 -0
  19. package/dist/passkey.d.ts +70 -0
  20. package/dist/passkey.js +126 -0
  21. package/dist/passkey.js.map +1 -0
  22. package/dist/password-reset.d.ts +41 -0
  23. package/dist/password-reset.js +80 -0
  24. package/dist/password-reset.js.map +1 -0
  25. package/dist/reauth.d.ts +29 -0
  26. package/dist/reauth.js +45 -0
  27. package/dist/reauth.js.map +1 -0
  28. package/dist/redirect.d.ts +38 -0
  29. package/dist/redirect.js +56 -0
  30. package/dist/redirect.js.map +1 -0
  31. package/dist/siwe.d.ts +23 -0
  32. package/dist/siwe.js +28 -0
  33. package/dist/siwe.js.map +1 -0
  34. package/dist/tab-election.d.ts +68 -0
  35. package/dist/tab-election.js +111 -0
  36. package/dist/tab-election.js.map +1 -0
  37. package/dist/telegram.d.ts +25 -0
  38. package/dist/telegram.js +18 -0
  39. package/dist/telegram.js.map +1 -0
  40. package/dist/telemetry.d.ts +125 -0
  41. package/dist/telemetry.js +357 -0
  42. package/dist/telemetry.js.map +1 -0
  43. package/dist/token-cache.d.ts +74 -0
  44. package/dist/token-cache.js +104 -0
  45. package/dist/token-cache.js.map +1 -0
  46. package/package.json +26 -0
package/dist/native.js ADDED
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ /**
3
+ * §6 native / One-Tap sign-in — the client half.
4
+ *
5
+ * The SDK does not make HTTP calls (the app owns fetch + cookies), so this
6
+ * module is two pure helpers for the request/response shape plus a
7
+ * framework-agnostic Google Identity Services loader that resolves a credential
8
+ * id_token. The app posts that to `/v1/client/sign_ins/id_token` and then reads
9
+ * the result exactly like any other attempt (nextStep / a returned ticket).
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.nativeSignInBody = nativeSignInBody;
13
+ exports.parseNativeSignInResponse = parseNativeSignInResponse;
14
+ exports.loadGoogleIdentityServices = loadGoogleIdentityServices;
15
+ exports.resetGoogleIdentityServices = resetGoogleIdentityServices;
16
+ exports.requestGoogleCredential = requestGoogleCredential;
17
+ exports.renderGoogleButton = renderGoogleButton;
18
+ exports.renderGoogleOverlayButton = renderGoogleOverlayButton;
19
+ /** Build the POST body for `/v1/client/sign_ins/id_token`. */
20
+ function nativeSignInBody(input) {
21
+ return {
22
+ provider: input.provider,
23
+ id_token: input.idToken,
24
+ ...(input.nonce ? { nonce: input.nonce } : {}),
25
+ };
26
+ }
27
+ /**
28
+ * Parse the native sign-in response. A `ticket` means complete; a status
29
+ * without a ticket (e.g. `needs_second_factor`) means a factor is still owed —
30
+ * the same contract the redirect flow uses, so callers must not treat it as
31
+ * signed-in.
32
+ */
33
+ function parseNativeSignInResponse(body) {
34
+ if (!body || typeof body !== 'object')
35
+ return null;
36
+ const b = body;
37
+ if (typeof b.id !== 'string' || typeof b.status !== 'string')
38
+ return null;
39
+ return {
40
+ attemptId: b.id,
41
+ status: b.status,
42
+ ...(typeof b.ticket === 'string' ? { ticket: b.ticket } : {}),
43
+ };
44
+ }
45
+ /* ---- Google Identity Services (One-Tap / GSI) ---------------------------- */
46
+ const GSI_SRC = 'https://accounts.google.com/gsi/client';
47
+ let gsiLoad = null;
48
+ /** Inject the GSI script once; resolves when it is ready. */
49
+ function loadGoogleIdentityServices(doc) {
50
+ if (gsiLoad)
51
+ return gsiLoad;
52
+ gsiLoad = new Promise((resolve, reject) => {
53
+ if (doc.querySelector(`script[src="${GSI_SRC}"]`)) {
54
+ resolve();
55
+ return;
56
+ }
57
+ const script = doc.createElement('script');
58
+ script.src = GSI_SRC;
59
+ script.async = true;
60
+ script.defer = true;
61
+ script.addEventListener('load', () => resolve());
62
+ script.addEventListener('error', () => reject(new Error('Failed to load Google Identity Services.')));
63
+ doc.head.appendChild(script);
64
+ });
65
+ return gsiLoad;
66
+ }
67
+ /** Reset the one-shot loader (tests). */
68
+ function resetGoogleIdentityServices() {
69
+ gsiLoad = null;
70
+ }
71
+ /**
72
+ * Load GSI and resolve a Google credential (an OIDC id_token) from One-Tap.
73
+ * The nonce, when supplied, is embedded in the credential and MUST be echoed to
74
+ * the server so it can bind the token to this request.
75
+ */
76
+ async function requestGoogleCredential(opts) {
77
+ const host = opts.host ?? window;
78
+ await loadGoogleIdentityServices(host.document);
79
+ const id = host.google?.accounts?.id;
80
+ if (!id)
81
+ throw new Error('Google Identity Services unavailable.');
82
+ return new Promise((resolve, reject) => {
83
+ id.initialize({
84
+ client_id: opts.clientId,
85
+ nonce: opts.nonce,
86
+ auto_select: opts.autoSelect ?? false,
87
+ callback: (response) => {
88
+ if (response.credential)
89
+ resolve(response.credential);
90
+ else
91
+ reject(new Error('Google returned no credential.'));
92
+ },
93
+ });
94
+ id.prompt();
95
+ });
96
+ }
97
+ /**
98
+ * Render the official "Sign in with Google" BUTTON into `parent` (and, when
99
+ * `oneTap` is set, also show the One-Tap prompt). Each resolved credential
100
+ * id_token is handed to `onCredential`, which the app posts to
101
+ * /v1/client/sign_ins/id_token. Unlike {@link requestGoogleCredential} this does
102
+ * not resolve once — a button can be clicked repeatedly — so it takes a
103
+ * callback. Framework-agnostic: the caller owns the DOM node and the fetch.
104
+ */
105
+ async function renderGoogleButton(opts) {
106
+ const host = opts.host ?? window;
107
+ await loadGoogleIdentityServices(host.document);
108
+ const id = host.google?.accounts?.id;
109
+ if (!id)
110
+ throw new Error('Google Identity Services unavailable.');
111
+ id.initialize({
112
+ client_id: opts.clientId,
113
+ nonce: opts.nonce,
114
+ callback: (response) => {
115
+ if (response.credential)
116
+ opts.onCredential(response.credential);
117
+ else
118
+ opts.onError?.(new Error('Google returned no credential.'));
119
+ },
120
+ });
121
+ id.renderButton(opts.parent, opts.buttonOptions ?? { theme: 'outline', size: 'large', text: 'continue_with', width: 320 });
122
+ if (opts.oneTap)
123
+ id.prompt();
124
+ }
125
+ /**
126
+ * Render YOUR uniform button, backed by Google's real (but invisible) GSI button.
127
+ *
128
+ * Google refuses to let you style its button, and One-Tap is FedCM-flaky — so to
129
+ * show your OWN button while still using the secretless id_token flow, you overlay
130
+ * Google's real button, at opacity 0, exactly on top of your styled one. The user
131
+ * sees your button; the click lands on Google's, which mints the id_token. No
132
+ * secret, no redirect.
133
+ *
134
+ * The failure mode people hit is misalignment — the invisible button not covering
135
+ * the visible one, so clicks miss. This guards against it: the overlay fills the
136
+ * wrapper, and a ResizeObserver keeps Google's button width matched to yours so
137
+ * the whole surface stays clickable as the layout changes.
138
+ *
139
+ * `wrapper` is YOUR styled button (or its container); it is made position:relative
140
+ * if it is not already positioned. Returns `{ destroy }` to tear the overlay down.
141
+ */
142
+ async function renderGoogleOverlayButton(opts) {
143
+ const host = opts.host ?? window;
144
+ const doc = host.document;
145
+ await loadGoogleIdentityServices(doc);
146
+ const id = host.google?.accounts?.id;
147
+ if (!id)
148
+ throw new Error('Google Identity Services unavailable.');
149
+ id.initialize({
150
+ client_id: opts.clientId,
151
+ nonce: opts.nonce,
152
+ callback: (response) => {
153
+ if (response.credential)
154
+ opts.onCredential(response.credential);
155
+ else
156
+ opts.onError?.(new Error('Google returned no credential.'));
157
+ },
158
+ });
159
+ // Ensure the wrapper can anchor an absolutely-positioned overlay.
160
+ const hadPosition = opts.wrapper.style.position !== '';
161
+ if (!hadPosition)
162
+ opts.wrapper.style.position = 'relative';
163
+ const overlay = doc.createElement('div');
164
+ overlay.setAttribute('aria-hidden', 'true');
165
+ // On top, transparent, click-catching, and it clips Google's fixed-size button
166
+ // to the wrapper so nothing pokes out even at opacity 0.
167
+ overlay.style.cssText =
168
+ 'position:absolute;inset:0;z-index:2;opacity:0;overflow:hidden;' +
169
+ 'display:flex;align-items:center;justify-content:center;';
170
+ opts.wrapper.appendChild(overlay);
171
+ const draw = () => {
172
+ // Google's button width must track the wrapper so every click lands on it;
173
+ // width is fixed at render time, so re-render on resize. Clamp to GSI's range.
174
+ const measured = Math.round(opts.wrapper.getBoundingClientRect().width) || 320;
175
+ const width = Math.max(200, Math.min(400, measured));
176
+ overlay.replaceChildren();
177
+ id.renderButton(overlay, {
178
+ type: 'standard',
179
+ theme: 'outline',
180
+ size: opts.size ?? 'large',
181
+ text: 'continue_with',
182
+ width,
183
+ });
184
+ };
185
+ draw();
186
+ let observer;
187
+ const RO = host.ResizeObserver ?? (typeof ResizeObserver !== 'undefined' ? ResizeObserver : undefined);
188
+ if (RO) {
189
+ observer = new RO(() => draw());
190
+ observer.observe(opts.wrapper);
191
+ }
192
+ return {
193
+ destroy: () => {
194
+ observer?.disconnect();
195
+ overlay.remove();
196
+ if (!hadPosition)
197
+ opts.wrapper.style.position = '';
198
+ },
199
+ };
200
+ }
201
+ //# sourceMappingURL=native.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native.js","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * §5.5 passkeys / WebAuthn — the client half.
3
+ *
4
+ * The browser ceremony (navigator.credentials.create / .get) can only run on the
5
+ * client, so these helpers turn a server `begin` response into the arguments for
6
+ * that ceremony, run it, and produce the `finish` POST body. The caller owns the
7
+ * two HTTP calls (begin → helper → finish), matching the SDK's "the app owns
8
+ * fetch + cookies" model (see native.ts). Everything is injectable via `host`,
9
+ * so it is unit-testable without a real authenticator or DOM.
10
+ *
11
+ * Server routes these pair with:
12
+ * register: POST /v1/client/me/passkeys/begin → create → /finish
13
+ * sign in : POST /v1/client/sign_ins/passkey/begin → get → /finish
14
+ */
15
+ /** The slice of `navigator` these helpers need — injectable for tests. */
16
+ export interface WebAuthnHost {
17
+ credentials: {
18
+ create(options: {
19
+ publicKey: PublicKeyCredentialCreationOptions;
20
+ signal?: AbortSignal;
21
+ }): Promise<Credential | null>;
22
+ get(options: {
23
+ publicKey: PublicKeyCredentialRequestOptions;
24
+ mediation?: 'silent' | 'optional' | 'conditional' | 'required';
25
+ signal?: AbortSignal;
26
+ }): Promise<Credential | null>;
27
+ };
28
+ }
29
+ /** ArrayBuffer/Uint8Array → base64url (no padding). */
30
+ export declare function bufferToBase64Url(buf: ArrayBuffer | Uint8Array): string;
31
+ /** base64url (padded or not) → ArrayBuffer. */
32
+ export declare function base64UrlToBuffer(value: string): ArrayBuffer;
33
+ /** True when the runtime supports the WebAuthn API. */
34
+ export declare function passkeysSupported(host?: unknown): boolean;
35
+ /** Whether conditional-UI (autofill) discovery is available (best-effort). */
36
+ export declare function conditionalUiAvailable(): Promise<boolean>;
37
+ /** The POST body for /v1/client/me/passkeys/finish. */
38
+ export interface PasskeyRegistrationBody {
39
+ challenge: string;
40
+ attestation_object: string;
41
+ client_data_json: string;
42
+ name?: string;
43
+ }
44
+ /**
45
+ * Run navigator.credentials.create from a `/passkeys/begin` response and return
46
+ * the `/finish` body. `begin` is the JSON the server sent (challenge, rp, user,
47
+ * pubKeyCredParams, excludeCredentials, … — all base64url where it is bytes).
48
+ */
49
+ export declare function createPasskey(begin: Record<string, unknown>, opts?: {
50
+ name?: string;
51
+ host?: WebAuthnHost;
52
+ }): Promise<PasskeyRegistrationBody>;
53
+ /** The POST body for /v1/client/sign_ins/passkey/finish. */
54
+ export interface PasskeyAssertionBody {
55
+ handle: string;
56
+ challenge: string;
57
+ credential_id: string;
58
+ authenticator_data: string;
59
+ client_data_json: string;
60
+ signature: string;
61
+ }
62
+ /**
63
+ * Run navigator.credentials.get from a `/sign_ins/passkey/begin` response and
64
+ * return the `/finish` body. Pass `mediation: 'conditional'` for autofill UI.
65
+ */
66
+ export declare function getPasskeyAssertion(begin: Record<string, unknown>, opts?: {
67
+ host?: WebAuthnHost;
68
+ mediation?: 'optional' | 'conditional';
69
+ signal?: AbortSignal;
70
+ }): Promise<PasskeyAssertionBody>;
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ /**
3
+ * §5.5 passkeys / WebAuthn — the client half.
4
+ *
5
+ * The browser ceremony (navigator.credentials.create / .get) can only run on the
6
+ * client, so these helpers turn a server `begin` response into the arguments for
7
+ * that ceremony, run it, and produce the `finish` POST body. The caller owns the
8
+ * two HTTP calls (begin → helper → finish), matching the SDK's "the app owns
9
+ * fetch + cookies" model (see native.ts). Everything is injectable via `host`,
10
+ * so it is unit-testable without a real authenticator or DOM.
11
+ *
12
+ * Server routes these pair with:
13
+ * register: POST /v1/client/me/passkeys/begin → create → /finish
14
+ * sign in : POST /v1/client/sign_ins/passkey/begin → get → /finish
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.bufferToBase64Url = bufferToBase64Url;
18
+ exports.base64UrlToBuffer = base64UrlToBuffer;
19
+ exports.passkeysSupported = passkeysSupported;
20
+ exports.conditionalUiAvailable = conditionalUiAvailable;
21
+ exports.createPasskey = createPasskey;
22
+ exports.getPasskeyAssertion = getPasskeyAssertion;
23
+ const defaultHost = () => {
24
+ const nav = globalThis.navigator;
25
+ if (!nav || !nav.credentials) {
26
+ throw new Error('WebAuthn is not available in this environment.');
27
+ }
28
+ return nav;
29
+ };
30
+ /** ArrayBuffer/Uint8Array → base64url (no padding). */
31
+ function bufferToBase64Url(buf) {
32
+ const bytes = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
33
+ let bin = '';
34
+ for (let i = 0; i < bytes.length; i += 1)
35
+ bin += String.fromCharCode(bytes[i]);
36
+ return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
37
+ }
38
+ /** base64url (padded or not) → ArrayBuffer. */
39
+ function base64UrlToBuffer(value) {
40
+ const norm = value.replace(/-/g, '+').replace(/_/g, '/');
41
+ const pad = norm.length % 4 === 0 ? '' : '='.repeat(4 - (norm.length % 4));
42
+ const bin = atob(norm + pad);
43
+ const bytes = new Uint8Array(bin.length);
44
+ for (let i = 0; i < bin.length; i += 1)
45
+ bytes[i] = bin.charCodeAt(i);
46
+ return bytes.buffer;
47
+ }
48
+ /** True when the runtime supports the WebAuthn API. */
49
+ function passkeysSupported(host) {
50
+ const h = host ?? globalThis.PublicKeyCredential;
51
+ return typeof h !== 'undefined';
52
+ }
53
+ /** Whether conditional-UI (autofill) discovery is available (best-effort). */
54
+ async function conditionalUiAvailable() {
55
+ const PK = globalThis
56
+ .PublicKeyCredential;
57
+ try {
58
+ return (await PK?.isConditionalMediationAvailable?.()) === true;
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ }
64
+ /**
65
+ * Run navigator.credentials.create from a `/passkeys/begin` response and return
66
+ * the `/finish` body. `begin` is the JSON the server sent (challenge, rp, user,
67
+ * pubKeyCredParams, excludeCredentials, … — all base64url where it is bytes).
68
+ */
69
+ async function createPasskey(begin, opts = {}) {
70
+ const host = opts.host ?? defaultHost();
71
+ const user = begin.user;
72
+ const exclude = begin.excludeCredentials ?? [];
73
+ const publicKey = {
74
+ challenge: base64UrlToBuffer(begin.challenge),
75
+ rp: begin.rp,
76
+ user: { ...user, id: base64UrlToBuffer(user.id) },
77
+ pubKeyCredParams: begin.pubKeyCredParams,
78
+ excludeCredentials: exclude.map((c) => ({ type: c.type, id: base64UrlToBuffer(c.id) })),
79
+ authenticatorSelection: begin.authenticatorSelection,
80
+ timeout: begin.timeout,
81
+ attestation: begin.attestation,
82
+ };
83
+ const credential = (await host.credentials.create({ publicKey }));
84
+ if (!credential)
85
+ throw new Error('Passkey registration was cancelled.');
86
+ const response = credential.response;
87
+ return {
88
+ // Echo the server's challenge string — the server matched/stored it.
89
+ challenge: begin.challenge,
90
+ attestation_object: bufferToBase64Url(response.attestationObject),
91
+ client_data_json: bufferToBase64Url(response.clientDataJSON),
92
+ ...(opts.name ? { name: opts.name } : {}),
93
+ };
94
+ }
95
+ /**
96
+ * Run navigator.credentials.get from a `/sign_ins/passkey/begin` response and
97
+ * return the `/finish` body. Pass `mediation: 'conditional'` for autofill UI.
98
+ */
99
+ async function getPasskeyAssertion(begin, opts = {}) {
100
+ const host = opts.host ?? defaultHost();
101
+ const allow = begin.allowCredentials ?? [];
102
+ const publicKey = {
103
+ challenge: base64UrlToBuffer(begin.challenge),
104
+ rpId: begin.rpId,
105
+ allowCredentials: allow.map((c) => ({ type: c.type, id: base64UrlToBuffer(c.id) })),
106
+ userVerification: begin.userVerification,
107
+ timeout: begin.timeout,
108
+ };
109
+ const credential = (await host.credentials.get({
110
+ publicKey,
111
+ ...(opts.mediation ? { mediation: opts.mediation } : {}),
112
+ ...(opts.signal ? { signal: opts.signal } : {}),
113
+ }));
114
+ if (!credential)
115
+ throw new Error('Passkey sign-in was cancelled.');
116
+ const response = credential.response;
117
+ return {
118
+ handle: begin.handle,
119
+ challenge: begin.challenge,
120
+ credential_id: bufferToBase64Url(credential.rawId),
121
+ authenticator_data: bufferToBase64Url(response.authenticatorData),
122
+ client_data_json: bufferToBase64Url(response.clientDataJSON),
123
+ signature: bufferToBase64Url(response.signature),
124
+ };
125
+ }
126
+ //# sourceMappingURL=passkey.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passkey.js","sourceRoot":"","sources":["../src/passkey.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;AAuBH,8CAKC;AAGD,8CAOC;AAGD,8CAGC;AAGD,wDAQC;AAeD,sCA8BC;AAgBD,kDA+BC;AArID,MAAM,WAAW,GAAG,GAAiB,EAAE;IACrC,MAAM,GAAG,GAAI,UAAsC,CAAC,SAAS,CAAC;IAC9D,IAAI,CAAC,GAAG,IAAI,CAAE,GAAiC,CAAC,WAAW,EAAE,CAAC;QAC5D,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,GAA8B,CAAC;AACxC,CAAC,CAAC;AAEF,uDAAuD;AACvD,SAAgB,iBAAiB,CAAC,GAA6B;IAC7D,MAAM,KAAK,GAAG,GAAG,YAAY,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC;IACpE,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC;QAAE,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;IAChF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC9E,CAAC;AAED,+CAA+C;AAC/C,SAAgB,iBAAiB,CAAC,KAAa;IAC7C,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACzD,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IAC3E,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC;IAC7B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC;QAAE,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACrE,OAAO,KAAK,CAAC,MAAM,CAAC;AACtB,CAAC;AAED,uDAAuD;AACvD,SAAgB,iBAAiB,CAAC,IAAc;IAC9C,MAAM,CAAC,GAAG,IAAI,IAAK,UAAgD,CAAC,mBAAmB,CAAC;IACxF,OAAO,OAAO,CAAC,KAAK,WAAW,CAAC;AAClC,CAAC;AAED,8EAA8E;AACvE,KAAK,UAAU,sBAAsB;IAC1C,MAAM,EAAE,GAAI,UAAqG;SAC9G,mBAAmB,CAAC;IACvB,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,EAAE,EAAE,+BAA+B,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAUD;;;;GAIG;AACI,KAAK,UAAU,aAAa,CACjC,KAA8B,EAC9B,OAA+C,EAAE;IAEjD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,WAAW,EAAE,CAAC;IACxC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAyD,CAAC;IAC7E,MAAM,OAAO,GAAI,KAAK,CAAC,kBAAiE,IAAI,EAAE,CAAC;IAE/F,MAAM,SAAS,GAAG;QAChB,SAAS,EAAE,iBAAiB,CAAC,KAAK,CAAC,SAAmB,CAAC;QACvD,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,IAAI,EAAE,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;QACjD,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,kBAAkB,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACvF,sBAAsB,EAAE,KAAK,CAAC,sBAAsB;QACpD,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,WAAW,EAAE,KAAK,CAAC,WAAW;KACkB,CAAC;IAEnD,MAAM,UAAU,GAAG,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,CAA+B,CAAC;IAChG,IAAI,CAAC,UAAU;QAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACxE,MAAM,QAAQ,GAAG,UAAU,CAAC,QAA4C,CAAC;IAEzE,OAAO;QACL,qEAAqE;QACrE,SAAS,EAAE,KAAK,CAAC,SAAmB;QACpC,kBAAkB,EAAE,iBAAiB,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QACjE,gBAAgB,EAAE,iBAAiB,CAAC,QAAQ,CAAC,cAAc,CAAC;QAC5D,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1C,CAAC;AACJ,CAAC;AAYD;;;GAGG;AACI,KAAK,UAAU,mBAAmB,CACvC,KAA8B,EAC9B,OAA8F,EAAE;IAEhG,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,WAAW,EAAE,CAAC;IACxC,MAAM,KAAK,GAAI,KAAK,CAAC,gBAA+D,IAAI,EAAE,CAAC;IAE3F,MAAM,SAAS,GAAG;QAChB,SAAS,EAAE,iBAAiB,CAAC,KAAK,CAAC,SAAmB,CAAC;QACvD,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,gBAAgB,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACnF,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,OAAO,EAAE,KAAK,CAAC,OAAO;KACyB,CAAC;IAElD,MAAM,UAAU,GAAG,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC;QAC7C,SAAS;QACT,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChD,CAAC,CAA+B,CAAC;IAClC,IAAI,CAAC,UAAU;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACnE,MAAM,QAAQ,GAAG,UAAU,CAAC,QAA0C,CAAC;IAEvE,OAAO;QACL,MAAM,EAAE,KAAK,CAAC,MAAgB;QAC9B,SAAS,EAAE,KAAK,CAAC,SAAmB;QACpC,aAAa,EAAE,iBAAiB,CAAC,UAAU,CAAC,KAAK,CAAC;QAClD,kBAAkB,EAAE,iBAAiB,CAAC,QAAQ,CAAC,iBAAiB,CAAC;QACjE,gBAAgB,EAAE,iBAAiB,CAAC,QAAQ,CAAC,cAAc,CAAC;QAC5D,SAAS,EAAE,iBAAiB,CAAC,QAAQ,CAAC,SAAS,CAAC;KACjD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * §5.4 Password reset ("forgot password").
3
+ *
4
+ * A standalone flow, separate from sign-in: the user proves control of their
5
+ * inbox with an emailed code, clears any second factor (a reset must NOT bypass
6
+ * MFA), then sets a new password and ends up signed in. It drives the
7
+ * `/v1/client/password_resets/*` endpoints, which mirror the sign-in steps but on
8
+ * their own attempt, so it cannot be expressed through the sign-in `advance`.
9
+ *
10
+ * Statuses, in order: `needs_email_verification` → (`needs_second_factor`) →
11
+ * `needs_new_password` → `complete` (carrying a session ticket, so a completed
12
+ * reset signs the user in through the normal cookie exchange).
13
+ */
14
+ import type { FieldError } from './attempt';
15
+ import type { FapiClient } from './fapi';
16
+ export interface ResetAttempt {
17
+ id: string;
18
+ status: string;
19
+ }
20
+ export interface ResetFlowState {
21
+ attempt: ResetAttempt | null;
22
+ errors: FieldError[];
23
+ busy: boolean;
24
+ /** Present once the reset completes — exchange it for session cookies. */
25
+ ticket: string | null;
26
+ }
27
+ export declare const initialResetFlow: ResetFlowState;
28
+ /** Begin a reset: email the code. Returns a flow parked at `needs_email_verification`. */
29
+ export declare function startPasswordReset(client: FapiClient, email: string, captchaToken?: string): Promise<ResetFlowState>;
30
+ /**
31
+ * Advance the reset by one step, routing on the current status: the emailed code
32
+ * → verification, a 2FA code → second factor, a new password → set-password. On
33
+ * completion the returned state carries a ticket to exchange for cookies.
34
+ */
35
+ export declare function advancePasswordReset(client: FapiClient, state: ResetFlowState, values: {
36
+ code?: string;
37
+ password?: string;
38
+ }): Promise<ResetFlowState>;
39
+ /** The step a reset flow is on — what the UI should collect next. */
40
+ export type ResetStep = 'request' | 'collect_email_code' | 'collect_second_factor' | 'collect_new_password' | 'done' | 'unknown';
41
+ export declare function resetStep(state: ResetFlowState): ResetStep;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.initialResetFlow = void 0;
4
+ exports.startPasswordReset = startPasswordReset;
5
+ exports.advancePasswordReset = advancePasswordReset;
6
+ exports.resetStep = resetStep;
7
+ exports.initialResetFlow = {
8
+ attempt: null,
9
+ errors: [],
10
+ busy: false,
11
+ ticket: null,
12
+ };
13
+ /** Begin a reset: email the code. Returns a flow parked at `needs_email_verification`. */
14
+ async function startPasswordReset(client, email, captchaToken) {
15
+ const res = await client.post('/v1/client/password_resets', {
16
+ email_address: email,
17
+ captcha_token: captchaToken,
18
+ });
19
+ if (!res.ok || !res.data)
20
+ return { ...exports.initialResetFlow, errors: res.errors };
21
+ return { attempt: res.data, errors: [], busy: false, ticket: null };
22
+ }
23
+ /**
24
+ * Advance the reset by one step, routing on the current status: the emailed code
25
+ * → verification, a 2FA code → second factor, a new password → set-password. On
26
+ * completion the returned state carries a ticket to exchange for cookies.
27
+ */
28
+ async function advancePasswordReset(client, state, values) {
29
+ const attempt = state.attempt;
30
+ if (!attempt)
31
+ return state;
32
+ let path = null;
33
+ let body = {};
34
+ switch (attempt.status) {
35
+ case 'needs_email_verification':
36
+ path = `/v1/client/password_resets/${attempt.id}/attempt_verification`;
37
+ body = { code: values.code ?? '' };
38
+ break;
39
+ case 'needs_second_factor':
40
+ path = `/v1/client/password_resets/${attempt.id}/attempt_second_factor`;
41
+ body = { code: values.code ?? '' };
42
+ break;
43
+ case 'needs_new_password':
44
+ path = `/v1/client/password_resets/${attempt.id}/set_new_password`;
45
+ body = { password: values.password ?? '' };
46
+ break;
47
+ default:
48
+ return state;
49
+ }
50
+ const res = await client.post(path, body);
51
+ if (!res.ok || !res.data) {
52
+ // Keep the attempt on failure (a wrong code must not drop the whole flow),
53
+ // exactly like the sign-in advance.
54
+ return { ...state, busy: false, errors: res.errors };
55
+ }
56
+ return {
57
+ attempt: { id: res.data.id, status: res.data.status },
58
+ errors: [],
59
+ busy: false,
60
+ ticket: res.data.ticket ?? null,
61
+ };
62
+ }
63
+ function resetStep(state) {
64
+ const status = state.attempt?.status;
65
+ if (!status)
66
+ return 'request';
67
+ switch (status) {
68
+ case 'needs_email_verification':
69
+ return 'collect_email_code';
70
+ case 'needs_second_factor':
71
+ return 'collect_second_factor';
72
+ case 'needs_new_password':
73
+ return 'collect_new_password';
74
+ case 'complete':
75
+ return 'done';
76
+ default:
77
+ return 'unknown';
78
+ }
79
+ }
80
+ //# sourceMappingURL=password-reset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password-reset.js","sourceRoot":"","sources":["../src/password-reset.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * §5.6 Step-up re-authentication.
3
+ *
4
+ * A signed-in user re-proves a credential to refresh the "recent sign-in"
5
+ * window IN PLACE — no sign-out, no new session — so the app can immediately
6
+ * perform a security-sensitive action (changing 2FA) that requires it. This is
7
+ * the SDK call a frontend makes when a step-up-gated request returns
8
+ * `STEP_UP_REQUIRED`; retry the original action after it resolves.
9
+ */
10
+ export type ReauthInput = {
11
+ strategy: 'password';
12
+ password: string;
13
+ } | {
14
+ strategy: 'id_token';
15
+ provider: string;
16
+ idToken: string;
17
+ };
18
+ export interface ReauthOptions {
19
+ /** FAPI base, e.g. 'https://accounts.acme.com' or '' for same-origin. */
20
+ api: string;
21
+ publishableKey: string;
22
+ fetchImpl?: typeof fetch;
23
+ }
24
+ /**
25
+ * Re-authenticate the current session. Resolves on success; throws with the
26
+ * server's message (and a `.code`) on failure — `STEP_UP_REQUIRED` cannot recur
27
+ * here, but a wrong password surfaces as a 401.
28
+ */
29
+ export declare function reauthenticate(input: ReauthInput, opts: ReauthOptions): Promise<void>;
package/dist/reauth.js ADDED
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ /**
3
+ * §5.6 Step-up re-authentication.
4
+ *
5
+ * A signed-in user re-proves a credential to refresh the "recent sign-in"
6
+ * window IN PLACE — no sign-out, no new session — so the app can immediately
7
+ * perform a security-sensitive action (changing 2FA) that requires it. This is
8
+ * the SDK call a frontend makes when a step-up-gated request returns
9
+ * `STEP_UP_REQUIRED`; retry the original action after it resolves.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.reauthenticate = reauthenticate;
13
+ /**
14
+ * Re-authenticate the current session. Resolves on success; throws with the
15
+ * server's message (and a `.code`) on failure — `STEP_UP_REQUIRED` cannot recur
16
+ * here, but a wrong password surfaces as a 401.
17
+ */
18
+ async function reauthenticate(input, opts) {
19
+ const body = input.strategy === 'password'
20
+ ? { strategy: 'password', password: input.password }
21
+ : { strategy: 'id_token', provider: input.provider, id_token: input.idToken };
22
+ const doFetch = opts.fetchImpl ?? fetch;
23
+ const res = await doFetch(`${opts.api}/v1/client/me/reauthenticate`, {
24
+ method: 'POST',
25
+ headers: { 'content-type': 'application/json', 'x-publishable-key': opts.publishableKey },
26
+ credentials: 'include',
27
+ body: JSON.stringify(body),
28
+ });
29
+ if (!res.ok) {
30
+ let message = 'Re-authentication failed.';
31
+ let code;
32
+ try {
33
+ const parsed = (await res.json());
34
+ message = parsed.errors?.[0]?.message ?? message;
35
+ code = parsed.errors?.[0]?.code;
36
+ }
37
+ catch {
38
+ /* keep the default */
39
+ }
40
+ const err = new Error(message);
41
+ err.code = code;
42
+ throw err;
43
+ }
44
+ }
45
+ //# sourceMappingURL=reauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reauth.js","sourceRoot":"","sources":["../src/reauth.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAkBH,wCA2BC;AAhCD;;;;GAIG;AACI,KAAK,UAAU,cAAc,CAAC,KAAkB,EAAE,IAAmB;IAC1E,MAAM,IAAI,GACR,KAAK,CAAC,QAAQ,KAAK,UAAU;QAC3B,CAAC,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE;QACpD,CAAC,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;IAElF,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC;IACxC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,8BAA8B,EAAE;QACnE,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,IAAI,CAAC,cAAc,EAAE;QACzF,WAAW,EAAE,SAAS;QACtB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;KAC3B,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,IAAI,OAAO,GAAG,2BAA2B,CAAC;QAC1C,IAAI,IAAwB,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAuD,CAAC;YACxF,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,OAAO,CAAC;YACjD,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;QAClC,CAAC;QAAC,MAAM,CAAC;YACP,sBAAsB;QACxB,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,GAAyB,CAAC,IAAI,GAAG,IAAI,CAAC;QACvC,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}