@atlasauth/js 0.33.1 → 0.34.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 +4 -0
- package/dist/attempt.d.ts +2 -0
- package/dist/attempt.js.map +1 -1
- package/dist/email-link.d.ts +78 -0
- package/dist/email-link.js +153 -0
- package/dist/email-link.js.map +1 -0
- package/dist/environment.d.ts +2 -0
- package/dist/environment.js +1 -0
- package/dist/environment.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -0
- package/dist/errors.js.map +1 -1
- package/dist/fapi.d.ts +9 -26
- package/dist/fapi.js +55 -33
- package/dist/fapi.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mfa-enrollment.d.ts +31 -10
- package/dist/mfa-enrollment.js +90 -12
- package/dist/mfa-enrollment.js.map +1 -1
- package/dist/passkey-sign-up.d.ts +0 -17
- package/dist/passkey-sign-up.js +3 -0
- package/dist/passkey-sign-up.js.map +1 -1
- package/dist/password-reset.d.ts +2 -0
- package/dist/password-reset.js +8 -1
- package/dist/password-reset.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @atlasauth/js changelog
|
|
2
2
|
|
|
3
|
+
## 0.34.0 — 2026-11-06
|
|
4
|
+
|
|
5
|
+
- Breaking: `previewCrossDeviceLink` no longer returns a match code. Flow state carries `matchCode`, the tab stores its secret for the link page, and enrolment supports HOTP, YubiKey, Duo and passkey. A sign-up resend rotates the secret; `finished_elsewhere` ends the wait.
|
|
6
|
+
|
|
3
7
|
## 0.33.1 — 2026-11-05
|
|
4
8
|
|
|
5
9
|
- A sign-up polls only while it waits for a proof, so a poll can no longer collect a ticket past the recovery codes.
|
package/dist/attempt.d.ts
CHANGED
|
@@ -114,6 +114,8 @@ export interface AttemptIdentityVerification {
|
|
|
114
114
|
export interface AttemptView {
|
|
115
115
|
/** A sign-in link proved the address of an account whose password was on hold. No session was made: the password signs in now. */
|
|
116
116
|
password_hold_released?: boolean;
|
|
117
|
+
/** A repeat sign-up whose earlier attempt finished: no ticket comes here; the person signs in. */
|
|
118
|
+
finished_elsewhere?: boolean;
|
|
117
119
|
/** The code just proved an address on an existing account: the password typed at sign-up was not saved. Only on that response; `FlowState.existingAccount` keeps it. */
|
|
118
120
|
existing_account?: boolean;
|
|
119
121
|
/**
|
package/dist/attempt.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attempt.js","sourceRoot":"","sources":["../src/attempt.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;
|
|
1
|
+
{"version":3,"file":"attempt.js","sourceRoot":"","sources":["../src/attempt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emailed links, both ends. A mailed link completes nothing by being opened: the
|
|
3
|
+
* opener must prove a tie to the tab that started the flow, or anyone who starts
|
|
4
|
+
* a flow with someone else's address would collect the session when they click.
|
|
5
|
+
*
|
|
6
|
+
* - Starting tab: the sign-up / sign-in response carries `poll_secret` and
|
|
7
|
+
* `match_code`. {@link rememberLinkProof} stores the secret for this browser;
|
|
8
|
+
* the flow shows the code (`FlowState.matchCode`) and polls as usual. An
|
|
9
|
+
* answer that mailed nothing has a secret and no code: nothing is stored or shown.
|
|
10
|
+
* - Link page: {@link createEmailLinkConfirmation} checks the link, tries the
|
|
11
|
+
* stored secret (same browser AND same origin: one click, no typing), and
|
|
12
|
+
* otherwise asks the person to type the code from the starting tab. Storage is
|
|
13
|
+
* per origin, so a flow started on another origin always types. The server never sends the
|
|
14
|
+
* code to this page. A success gives the opener nothing to sign in with: the
|
|
15
|
+
* starting tab collects the ticket by polling.
|
|
16
|
+
*/
|
|
17
|
+
import type { FieldError } from './attempt';
|
|
18
|
+
import type { FapiClient } from './fapi';
|
|
19
|
+
/** Keep a mailed link's poll secret for this browser. Returns the code to show, or null when no link was mailed. */
|
|
20
|
+
export declare function rememberLinkProof(attemptId: string, response: {
|
|
21
|
+
poll_secret?: unknown;
|
|
22
|
+
match_code?: unknown;
|
|
23
|
+
}): string | null;
|
|
24
|
+
/** The poll secret this browser holds for an attempt, if it started it. */
|
|
25
|
+
export declare function readLinkSecret(attemptId: string): string | null;
|
|
26
|
+
/** Forget an attempt's poll secret (it completed, or the link was confirmed). */
|
|
27
|
+
export declare function forgetLinkSecret(attemptId: string): void;
|
|
28
|
+
/** Which route a mailed link belongs to: attempt ids don't say. */
|
|
29
|
+
export type EmailLinkFlow = 'sign_in' | 'sign_up' | 'password_reset';
|
|
30
|
+
/** A mailed link, as its page reads it. */
|
|
31
|
+
export interface EmailLink {
|
|
32
|
+
attempt: string;
|
|
33
|
+
token: string;
|
|
34
|
+
/** Default `sign_in`. */
|
|
35
|
+
flow?: EmailLinkFlow;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The mailed link in a URL (`?attempt=sia_…&token=…`), or null. The id's prefix
|
|
39
|
+
* keeps an app's own `?attempt=&token=` params from being taken for a link.
|
|
40
|
+
*/
|
|
41
|
+
export declare function emailLinkFromUrl(href?: string, flow?: EmailLinkFlow): EmailLink | null;
|
|
42
|
+
/** Whether a mailed link is still live. Spends and completes nothing, so a mail scanner's visit is harmless. Null when refused. */
|
|
43
|
+
export declare function previewCrossDeviceLink(client: FapiClient, link: EmailLink): Promise<{
|
|
44
|
+
requiresConfirmation: boolean;
|
|
45
|
+
} | null>;
|
|
46
|
+
/** Exactly one proof: the secret this browser stored, or the code the person typed. */
|
|
47
|
+
export type EmailLinkProof = {
|
|
48
|
+
pollSecret: string;
|
|
49
|
+
matchCode?: never;
|
|
50
|
+
} | {
|
|
51
|
+
matchCode: string;
|
|
52
|
+
pollSecret?: never;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Confirm a mailed link. The starting tab's `pollAttempt` then returns the ticket.
|
|
56
|
+
* A wrong code burns the link; a wrong secret does not, so the page can ask for the code.
|
|
57
|
+
*/
|
|
58
|
+
export declare function confirmCrossDeviceLink(client: FapiClient, link: EmailLink & EmailLinkProof): Promise<boolean>;
|
|
59
|
+
export interface EmailLinkConfirmationState {
|
|
60
|
+
/** `checking`, then `confirmed`, `needs_code` (draw the typed input) or `invalid` (the single refusal, for every failure). */
|
|
61
|
+
status: 'checking' | 'needs_code' | 'confirmed' | 'invalid';
|
|
62
|
+
busy: boolean;
|
|
63
|
+
/** Why a typed code could not be checked (rate limit, network). The link is still live. */
|
|
64
|
+
errors: FieldError[];
|
|
65
|
+
/** `confirmed`, a sign-up on an address with an account: where to reset its password. */
|
|
66
|
+
resetUrl?: string;
|
|
67
|
+
}
|
|
68
|
+
export interface EmailLinkConfirmation {
|
|
69
|
+
readonly state: EmailLinkConfirmationState;
|
|
70
|
+
/** Called after every state change; returns the unsubscribe. */
|
|
71
|
+
subscribe(listener: (state: EmailLinkConfirmationState) => void): () => void;
|
|
72
|
+
/** Check the link and try this browser's secret. Runs once, however often it is called. */
|
|
73
|
+
start(): Promise<void>;
|
|
74
|
+
/** Confirm with the code typed at `needs_code`. */
|
|
75
|
+
submitCode(code: string): Promise<void>;
|
|
76
|
+
}
|
|
77
|
+
/** The link page's state and requests, independent of any framework. */
|
|
78
|
+
export declare function createEmailLinkConfirmation(client: FapiClient, link: EmailLink): EmailLinkConfirmation;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rememberLinkProof = rememberLinkProof;
|
|
4
|
+
exports.readLinkSecret = readLinkSecret;
|
|
5
|
+
exports.forgetLinkSecret = forgetLinkSecret;
|
|
6
|
+
exports.emailLinkFromUrl = emailLinkFromUrl;
|
|
7
|
+
exports.previewCrossDeviceLink = previewCrossDeviceLink;
|
|
8
|
+
exports.confirmCrossDeviceLink = confirmCrossDeviceLink;
|
|
9
|
+
exports.createEmailLinkConfirmation = createEmailLinkConfirmation;
|
|
10
|
+
const storageKey = (attemptId) => `atlas_link_${attemptId}`;
|
|
11
|
+
function storage() {
|
|
12
|
+
try {
|
|
13
|
+
return globalThis.localStorage ?? null;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Keep a mailed link's poll secret for this browser. Returns the code to show, or null when no link was mailed. */
|
|
20
|
+
function rememberLinkProof(attemptId, response) {
|
|
21
|
+
const code = typeof response.match_code === 'string' && response.match_code ? response.match_code : null;
|
|
22
|
+
if (code && typeof response.poll_secret === 'string' && response.poll_secret) {
|
|
23
|
+
try {
|
|
24
|
+
storage()?.setItem(storageKey(attemptId), response.poll_secret);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Storage full or blocked: the link page falls back to the typed code.
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return code;
|
|
31
|
+
}
|
|
32
|
+
/** The poll secret this browser holds for an attempt, if it started it. */
|
|
33
|
+
function readLinkSecret(attemptId) {
|
|
34
|
+
try {
|
|
35
|
+
return storage()?.getItem(storageKey(attemptId)) ?? null;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Forget an attempt's poll secret (it completed, or the link was confirmed). */
|
|
42
|
+
function forgetLinkSecret(attemptId) {
|
|
43
|
+
try {
|
|
44
|
+
storage()?.removeItem(storageKey(attemptId));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Nothing to clean up where storage is blocked.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The mailed link in a URL (`?attempt=sia_…&token=…`), or null. The id's prefix
|
|
52
|
+
* keeps an app's own `?attempt=&token=` params from being taken for a link.
|
|
53
|
+
*/
|
|
54
|
+
function emailLinkFromUrl(href, flow = 'sign_in') {
|
|
55
|
+
const url = href ?? globalThis.location?.href;
|
|
56
|
+
if (!url)
|
|
57
|
+
return null;
|
|
58
|
+
try {
|
|
59
|
+
const params = new URL(url).searchParams;
|
|
60
|
+
const attempt = params.get('attempt');
|
|
61
|
+
const token = params.get('token');
|
|
62
|
+
return attempt?.startsWith('sia_') && token ? { attempt, token, flow } : null;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const verifyPath = (link) => `/v1/client/${link.flow === 'sign_up' ? 'sign_ups' : link.flow === 'password_reset' ? 'password_resets' : 'sign_ins'}/verify`;
|
|
69
|
+
/** Whether a mailed link is still live. Spends and completes nothing, so a mail scanner's visit is harmless. Null when refused. */
|
|
70
|
+
async function previewCrossDeviceLink(client, link) {
|
|
71
|
+
const response = await client.get(`${verifyPath(link)}?attempt=${encodeURIComponent(link.attempt)}&token=${encodeURIComponent(link.token)}`);
|
|
72
|
+
if (!response.ok || !response.data)
|
|
73
|
+
return null;
|
|
74
|
+
return { requiresConfirmation: response.data.requires_confirmation === true };
|
|
75
|
+
}
|
|
76
|
+
function postConfirmation(client, link) {
|
|
77
|
+
return client.post(`${verifyPath(link)}/confirm`, {
|
|
78
|
+
attempt: link.attempt,
|
|
79
|
+
token: link.token,
|
|
80
|
+
...(link.pollSecret ? { poll_secret: link.pollSecret } : { match_code: link.matchCode }),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Confirm a mailed link. The starting tab's `pollAttempt` then returns the ticket.
|
|
85
|
+
* A wrong code burns the link; a wrong secret does not, so the page can ask for the code.
|
|
86
|
+
*/
|
|
87
|
+
async function confirmCrossDeviceLink(client, link) {
|
|
88
|
+
return (await postConfirmation(client, link)).ok;
|
|
89
|
+
}
|
|
90
|
+
/** The link page's state and requests, independent of any framework. */
|
|
91
|
+
function createEmailLinkConfirmation(client, link) {
|
|
92
|
+
let state = { status: 'checking', busy: false, errors: [] };
|
|
93
|
+
let started = false;
|
|
94
|
+
const listeners = new Set();
|
|
95
|
+
const set = (patch) => {
|
|
96
|
+
state = { ...state, ...patch };
|
|
97
|
+
for (const listener of listeners)
|
|
98
|
+
listener(state);
|
|
99
|
+
};
|
|
100
|
+
const confirmed = (response) => {
|
|
101
|
+
forgetLinkSecret(link.attempt);
|
|
102
|
+
const resetUrl = response.data?.reset_url;
|
|
103
|
+
// Only a page a link can safely go to.
|
|
104
|
+
set({ status: 'confirmed', busy: false, errors: [], ...(typeof resetUrl === 'string' && /^https:\/\//i.test(resetUrl) ? { resetUrl } : {}) });
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
get state() {
|
|
108
|
+
return state;
|
|
109
|
+
},
|
|
110
|
+
subscribe(listener) {
|
|
111
|
+
listeners.add(listener);
|
|
112
|
+
return () => listeners.delete(listener);
|
|
113
|
+
},
|
|
114
|
+
async start() {
|
|
115
|
+
if (started)
|
|
116
|
+
return;
|
|
117
|
+
started = true;
|
|
118
|
+
const preview = await previewCrossDeviceLink(client, link);
|
|
119
|
+
if (!preview?.requiresConfirmation) {
|
|
120
|
+
set({ status: 'invalid' });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const pollSecret = readLinkSecret(link.attempt);
|
|
124
|
+
if (pollSecret) {
|
|
125
|
+
const response = await postConfirmation(client, { ...link, pollSecret });
|
|
126
|
+
if (response.ok) {
|
|
127
|
+
confirmed(response);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
set({ status: 'needs_code' });
|
|
132
|
+
},
|
|
133
|
+
async submitCode(code) {
|
|
134
|
+
const matchCode = code.replace(/\s+/g, '');
|
|
135
|
+
// An empty submit must not reach the server: a wrong code burns the link.
|
|
136
|
+
if (state.status !== 'needs_code' || state.busy || !matchCode)
|
|
137
|
+
return;
|
|
138
|
+
set({ busy: true, errors: [] });
|
|
139
|
+
const response = await postConfirmation(client, { ...link, matchCode });
|
|
140
|
+
if (response.ok) {
|
|
141
|
+
confirmed(response);
|
|
142
|
+
}
|
|
143
|
+
else if (response.status === 0 || response.status === 429) {
|
|
144
|
+
// Never reached the check, so the link wasn't burned: let them try again.
|
|
145
|
+
set({ busy: false, errors: response.errors });
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
set({ status: 'invalid', busy: false, errors: [] });
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=email-link.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-link.js","sourceRoot":"","sources":["../src/email-link.ts"],"names":[],"mappings":"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"}
|
package/dist/environment.d.ts
CHANGED
package/dist/environment.js
CHANGED
|
@@ -91,6 +91,7 @@ function accountUxFrom(body) {
|
|
|
91
91
|
...(raw.mfa.hotp === true ? { hotp: true } : {}),
|
|
92
92
|
...(raw.mfa.yubikey === true ? { yubikey: true } : {}),
|
|
93
93
|
...(raw.mfa.duo === true ? { duo: true } : {}),
|
|
94
|
+
...(raw.mfa.passkey === true ? { passkey: true } : {}),
|
|
94
95
|
...(raw.mfa.security_questions === true ? { securityQuestions: true } : {}),
|
|
95
96
|
...(raw.mfa.smart_card === true ? { smartCard: true } : {}),
|
|
96
97
|
},
|
package/dist/environment.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"environment.js","sourceRoot":"","sources":["../src/environment.ts"],"names":[],"mappings":";;;AAiDA,8CAWC;AAQD,kDAMC;
|
|
1
|
+
{"version":3,"file":"environment.js","sourceRoot":"","sources":["../src/environment.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* `error.<reason>` keys. Listed for docs and coverage checks; a reason missing
|
|
14
14
|
* from this list still renders (as the server's `message`).
|
|
15
15
|
*/
|
|
16
|
-
export declare const ERROR_REASONS: readonly ["form_password_incorrect", "form_code_incorrect", "form_code_already_used", "form_code_expired", "form_code_max_attempts", "form_link_invalid", "form_identifier_not_found", "form_identifier_exists", "form_password_pwned", "form_password_too_short", "form_password_too_long", "form_password_requires_uppercase", "form_password_requires_lowercase", "form_password_requires_number", "form_password_requires_symbol", "form_password_not_allowed", "form_password_contains_identifier", "form_password_same_as_current", "form_password_reused", "password_expired", "email_not_verified", "sign_in_token_invalid", "too_many_attempts", "too_many_requests", "captcha_required", "captcha_failed", "captcha_unavailable", "email_delivery_unavailable", "strategy_disabled", "sign_up_restricted", "sign_up_denied", "password_not_yet_usable", "account_deleted", "waitlisted", "identifier_not_allowed", "disposable_email_blocked", "sso_required", "sign_ins_disabled", "sign_ups_disabled", "high_risk_blocked", "sms_region_blocked", "github_team_required"];
|
|
16
|
+
export declare const ERROR_REASONS: readonly ["form_password_incorrect", "form_code_incorrect", "form_code_already_used", "form_code_expired", "form_code_max_attempts", "form_link_invalid", "form_identifier_not_found", "form_identifier_exists", "form_password_pwned", "form_password_too_short", "form_password_too_long", "form_password_requires_uppercase", "form_password_requires_lowercase", "form_password_requires_number", "form_password_requires_symbol", "form_password_not_allowed", "form_password_contains_identifier", "form_password_same_as_current", "form_password_reused", "password_expired", "email_not_verified", "mfa_setup_unavailable", "sign_in_token_invalid", "too_many_attempts", "too_many_requests", "captcha_required", "captcha_failed", "captcha_unavailable", "email_delivery_unavailable", "strategy_disabled", "sign_up_restricted", "sign_up_denied", "password_not_yet_usable", "account_deleted", "waitlisted", "identifier_not_allowed", "disposable_email_blocked", "sso_required", "sign_ins_disabled", "sign_ups_disabled", "high_risk_blocked", "sms_region_blocked", "github_team_required"];
|
|
17
17
|
/** A reason with a string in every shipped catalog. */
|
|
18
18
|
export type ErrorReason = (typeof ERROR_REASONS)[number];
|
|
19
19
|
/** The catalog key for a reason: `error.<reason>`. */
|
package/dist/errors.js
CHANGED
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/fapi.d.ts
CHANGED
|
@@ -253,7 +253,7 @@ export interface FlowState {
|
|
|
253
253
|
* server returns a new attempt.
|
|
254
254
|
*/
|
|
255
255
|
prepared?: 'email_code' | 'email_link' | null;
|
|
256
|
-
/**
|
|
256
|
+
/** A mailed link: the code to show on this device, which a person opening the link on another device must type there. */
|
|
257
257
|
matchCode?: string | null;
|
|
258
258
|
/**
|
|
259
259
|
* After a sign-in completed here: true when this browser now holds the
|
|
@@ -342,7 +342,9 @@ export declare function startSignUp(client: FapiClient, input: {
|
|
|
342
342
|
* Resend a sign-up's emailed proof (a code, a link, or both — whatever the
|
|
343
343
|
* instance sends): `POST /v1/client/sign_ups/:id/prepare_verification`. The
|
|
344
344
|
* earlier code / link stops working. A refusal (a 429 with `retry_after`)
|
|
345
|
-
* comes back as `errors` with the attempt kept.
|
|
345
|
+
* comes back as `errors` with the attempt kept. A new link rotates the poll
|
|
346
|
+
* secret (the old one stops working) and comes with a new code: both replace the
|
|
347
|
+
* old ones, here and in this browser's storage.
|
|
346
348
|
*/
|
|
347
349
|
export declare function resendSignUpVerification(client: FapiClient, state: FlowState): Promise<FlowState>;
|
|
348
350
|
/**
|
|
@@ -360,31 +362,11 @@ export declare function attemptPasswordHoldProof(client: FapiClient, state: Flow
|
|
|
360
362
|
*/
|
|
361
363
|
export declare function awaitingSignUpLink(attempt: AttemptView | null | undefined, emailVerification: string | null | undefined): boolean;
|
|
362
364
|
/** §5.3: ask the server to send a code or a magic link. */
|
|
363
|
-
export declare function prepareFactor(client: FapiClient, state: FlowState, strategy: 'email_code' | 'email_link',
|
|
365
|
+
export declare function prepareFactor(client: FapiClient, state: FlowState, strategy: 'email_code' | 'email_link',
|
|
366
|
+
/** @deprecated Every mailed link now needs a tie to this tab; this changes nothing. */
|
|
367
|
+
_opts?: {
|
|
364
368
|
crossDevice?: boolean;
|
|
365
369
|
}): Promise<FlowState>;
|
|
366
|
-
/** What the page the cross-device link opens needs: whether to ask for confirmation, and the code to compare. */
|
|
367
|
-
export interface CrossDeviceLinkPreview {
|
|
368
|
-
requiresConfirmation: boolean;
|
|
369
|
-
matchCode: string | null;
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Cross-device magic link, second device: read the code from the opened link (`?attempt=&token=&cross_device=1`)
|
|
373
|
-
* so the page can show it next to the prompt "does this match the code on your other device?". Completes nothing.
|
|
374
|
-
*/
|
|
375
|
-
export declare function previewCrossDeviceLink(client: FapiClient, link: {
|
|
376
|
-
attempt: string;
|
|
377
|
-
token: string;
|
|
378
|
-
}): Promise<CrossDeviceLinkPreview | null>;
|
|
379
|
-
/**
|
|
380
|
-
* Cross-device magic link, second device: confirm with the code both devices show. The first device's
|
|
381
|
-
* `pollAttempt` then returns the ticket. One use; a wrong code invalidates the link.
|
|
382
|
-
*/
|
|
383
|
-
export declare function confirmCrossDeviceLink(client: FapiClient, link: {
|
|
384
|
-
attempt: string;
|
|
385
|
-
token: string;
|
|
386
|
-
matchCode: string;
|
|
387
|
-
}): Promise<boolean>;
|
|
388
370
|
/**
|
|
389
371
|
* §5.3 polling. Returns the updated flow, and a ticket once the link has been
|
|
390
372
|
* opened somewhere.
|
|
@@ -407,7 +389,8 @@ export declare function acknowledgeExistingAccount(state: FlowState): FlowState;
|
|
|
407
389
|
/**
|
|
408
390
|
* A sign-up's emailed link proved the address and made no session, so this tab
|
|
409
391
|
* can only go and sign in. The tab's poll secret proves nothing about the mailbox:
|
|
410
|
-
* say the same thing whichever account the address belongs to.
|
|
392
|
+
* say the same thing whichever account the address belongs to. So too a repeat
|
|
393
|
+
* sign-up whose earlier attempt finished (`finished_elsewhere`).
|
|
411
394
|
*/
|
|
412
395
|
export declare function signUpProvedByLink(attempt: AttemptView | null | undefined): boolean;
|
|
413
396
|
/**
|
package/dist/fapi.js
CHANGED
|
@@ -17,8 +17,6 @@ exports.preparePasswordHoldProof = preparePasswordHoldProof;
|
|
|
17
17
|
exports.attemptPasswordHoldProof = attemptPasswordHoldProof;
|
|
18
18
|
exports.awaitingSignUpLink = awaitingSignUpLink;
|
|
19
19
|
exports.prepareFactor = prepareFactor;
|
|
20
|
-
exports.previewCrossDeviceLink = previewCrossDeviceLink;
|
|
21
|
-
exports.confirmCrossDeviceLink = confirmCrossDeviceLink;
|
|
22
20
|
exports.pollAttempt = pollAttempt;
|
|
23
21
|
exports.shouldKeepPolling = shouldKeepPolling;
|
|
24
22
|
exports.pendingExistingAccountNotice = pendingExistingAccountNotice;
|
|
@@ -31,6 +29,7 @@ const policies_1 = require("./policies");
|
|
|
31
29
|
const attempt_1 = require("./attempt");
|
|
32
30
|
const sdk_advisory_1 = require("./sdk-advisory");
|
|
33
31
|
const dev_browser_1 = require("./dev-browser");
|
|
32
|
+
const email_link_1 = require("./email-link");
|
|
34
33
|
const mfa_enrollment_1 = require("./mfa-enrollment");
|
|
35
34
|
const referral_1 = require("./referral");
|
|
36
35
|
const telemetry_1 = require("./telemetry");
|
|
@@ -432,6 +431,19 @@ function requestForStep(attempt, values) {
|
|
|
432
431
|
body: { factor_id: values.factorId ?? '', code: values.code ?? '' },
|
|
433
432
|
};
|
|
434
433
|
}
|
|
434
|
+
if (values.enrollStrategy === 'hotp' || values.enrollStrategy === 'duo' || values.enrollStrategy === 'yubikey' || values.enrollStrategy === 'passkey') {
|
|
435
|
+
const strategy = values.enrollStrategy;
|
|
436
|
+
return {
|
|
437
|
+
path: `/v1/client/sign_ins/${id}/attempt_mfa_enrollment`,
|
|
438
|
+
body: strategy === 'hotp'
|
|
439
|
+
? { strategy, factor_id: values.factorId ?? '', code: values.code ?? '' }
|
|
440
|
+
: strategy === 'duo'
|
|
441
|
+
? { strategy, factor_id: values.factorId ?? '' }
|
|
442
|
+
: strategy === 'yubikey'
|
|
443
|
+
? { strategy, otp: values.otp ?? '' }
|
|
444
|
+
: { strategy, challenge: values.challenge ?? '', attestation_object: values.attestation_object ?? '', client_data_json: values.client_data_json ?? '' },
|
|
445
|
+
};
|
|
446
|
+
}
|
|
435
447
|
return {
|
|
436
448
|
path: `/v1/client/sign_ins/${id}/attempt_mfa_enrollment`,
|
|
437
449
|
body: {
|
|
@@ -568,6 +580,8 @@ async function advance(client, state, values) {
|
|
|
568
580
|
const { backup_codes: backupCodes, ...view } = response.data;
|
|
569
581
|
const recoveryCodes = (0, mfa_enrollment_1.recoveryCodesFrom)(backupCodes);
|
|
570
582
|
const sameAttempt = view.id === state.attempt?.id && view.status === state.attempt?.status;
|
|
583
|
+
if (state.attempt?.id && (0, attempt_1.isTerminal)(view))
|
|
584
|
+
(0, email_link_1.forgetLinkSecret)(state.attempt.id);
|
|
571
585
|
return {
|
|
572
586
|
attempt: view,
|
|
573
587
|
errors: [],
|
|
@@ -576,6 +590,7 @@ async function advance(client, state, values) {
|
|
|
576
590
|
// A completion response carries the ticket to exchange for cookies.
|
|
577
591
|
ticket: view.ticket ?? null,
|
|
578
592
|
prepared: sameAttempt ? (state.prepared ?? null) : null,
|
|
593
|
+
...(sameAttempt && state.matchCode ? { matchCode: state.matchCode } : {}),
|
|
579
594
|
...(view.existing_account || state.existingAccount ? { existingAccount: true } : {}),
|
|
580
595
|
...(recoveryCodes ? { recoveryCodes } : {}),
|
|
581
596
|
...(view.policy_acceptance_token || view.required_action_token
|
|
@@ -610,6 +625,7 @@ async function startSignUp(client, input) {
|
|
|
610
625
|
if (!response.ok || !response.data) {
|
|
611
626
|
return { ...exports.initialFlow, busy: false, errors: response.errors };
|
|
612
627
|
}
|
|
628
|
+
const matchCode = (0, email_link_1.rememberLinkProof)(response.data.id ?? '', response.data);
|
|
613
629
|
return {
|
|
614
630
|
attempt: response.data,
|
|
615
631
|
errors: [],
|
|
@@ -618,6 +634,7 @@ async function startSignUp(client, input) {
|
|
|
618
634
|
// tab collects the session by polling with the secret the create returned.
|
|
619
635
|
pollSecret: response.data.poll_secret ?? exports.initialFlow.pollSecret,
|
|
620
636
|
ticket: response.data.ticket ?? null,
|
|
637
|
+
...(matchCode ? { matchCode } : {}),
|
|
621
638
|
...(response.data.policy_acceptance_token || response.data.required_action_token ? { policyToken: response.data.policy_acceptance_token ?? response.data.required_action_token } : {}),
|
|
622
639
|
};
|
|
623
640
|
}
|
|
@@ -625,14 +642,26 @@ async function startSignUp(client, input) {
|
|
|
625
642
|
* Resend a sign-up's emailed proof (a code, a link, or both — whatever the
|
|
626
643
|
* instance sends): `POST /v1/client/sign_ups/:id/prepare_verification`. The
|
|
627
644
|
* earlier code / link stops working. A refusal (a 429 with `retry_after`)
|
|
628
|
-
* comes back as `errors` with the attempt kept.
|
|
645
|
+
* comes back as `errors` with the attempt kept. A new link rotates the poll
|
|
646
|
+
* secret (the old one stops working) and comes with a new code: both replace the
|
|
647
|
+
* old ones, here and in this browser's storage.
|
|
629
648
|
*/
|
|
630
649
|
async function resendSignUpVerification(client, state) {
|
|
631
650
|
const id = state.attempt?.id;
|
|
632
651
|
if (!id)
|
|
633
652
|
return state;
|
|
634
653
|
const response = await client.post(`/v1/client/sign_ups/${id}/prepare_verification`, {});
|
|
635
|
-
|
|
654
|
+
if (!response.ok)
|
|
655
|
+
return { ...state, busy: false, errors: response.errors };
|
|
656
|
+
const pollSecret = response.data?.poll_secret;
|
|
657
|
+
// No new secret: only a code went out, and the tab's wait is unchanged.
|
|
658
|
+
if (!pollSecret)
|
|
659
|
+
return { ...state, busy: false, errors: [] };
|
|
660
|
+
const matchCode = (0, email_link_1.rememberLinkProof)(id, response.data ?? {});
|
|
661
|
+
// Rotated with nothing mailed: the stored secret is dead, and there's no code to show.
|
|
662
|
+
if (!matchCode)
|
|
663
|
+
(0, email_link_1.forgetLinkSecret)(id);
|
|
664
|
+
return { ...state, busy: false, errors: [], pollSecret, matchCode };
|
|
636
665
|
}
|
|
637
666
|
/**
|
|
638
667
|
* Email the proof that lifts a password hold (`FlowState.passwordHold`), through
|
|
@@ -646,12 +675,14 @@ async function preparePasswordHoldProof(client, state, via = 'email_code') {
|
|
|
646
675
|
const response = await client.post(`/v1/client/sign_ups/${hold.attemptId}/prepare_verification`, {});
|
|
647
676
|
if (!response.ok)
|
|
648
677
|
return { ...state, busy: false, errors: response.errors };
|
|
678
|
+
const matchCode = (0, email_link_1.rememberLinkProof)(hold.attemptId, response.data ?? {});
|
|
649
679
|
return {
|
|
650
680
|
...state,
|
|
651
681
|
busy: false,
|
|
652
682
|
errors: [],
|
|
653
683
|
pollSecret: null,
|
|
654
684
|
prepared: via,
|
|
685
|
+
...(matchCode ? { matchCode } : {}),
|
|
655
686
|
passwordHold: { ...hold, email: response.data?.identifier ?? null },
|
|
656
687
|
};
|
|
657
688
|
}
|
|
@@ -677,11 +708,13 @@ function awaitingSignUpLink(attempt, emailVerification) {
|
|
|
677
708
|
emailVerification === 'link');
|
|
678
709
|
}
|
|
679
710
|
/** §5.3: ask the server to send a code or a magic link. */
|
|
680
|
-
async function prepareFactor(client, state, strategy,
|
|
711
|
+
async function prepareFactor(client, state, strategy,
|
|
712
|
+
/** @deprecated Every mailed link now needs a tie to this tab; this changes nothing. */
|
|
713
|
+
_opts = {}) {
|
|
681
714
|
const id = state.attempt?.id;
|
|
682
715
|
if (!id)
|
|
683
716
|
return state;
|
|
684
|
-
const response = await client.post(`/v1/client/sign_ins/${id}/prepare_first_factor`, { strategy
|
|
717
|
+
const response = await client.post(`/v1/client/sign_ins/${id}/prepare_first_factor`, { strategy });
|
|
685
718
|
if (!response.ok)
|
|
686
719
|
return { ...state, busy: false, errors: response.errors };
|
|
687
720
|
return {
|
|
@@ -693,27 +726,9 @@ async function prepareFactor(client, state, strategy, opts = {}) {
|
|
|
693
726
|
// there is nothing to poll for.
|
|
694
727
|
pollSecret: strategy === 'email_link' ? (response.data?.poll_secret ?? null) : null,
|
|
695
728
|
prepared: strategy,
|
|
696
|
-
matchCode: response.data
|
|
729
|
+
matchCode: strategy === 'email_link' ? (0, email_link_1.rememberLinkProof)(id, response.data ?? {}) : null,
|
|
697
730
|
};
|
|
698
731
|
}
|
|
699
|
-
/**
|
|
700
|
-
* Cross-device magic link, second device: read the code from the opened link (`?attempt=&token=&cross_device=1`)
|
|
701
|
-
* so the page can show it next to the prompt "does this match the code on your other device?". Completes nothing.
|
|
702
|
-
*/
|
|
703
|
-
async function previewCrossDeviceLink(client, link) {
|
|
704
|
-
const response = await client.get(`/v1/client/sign_ins/verify?attempt=${encodeURIComponent(link.attempt)}&token=${encodeURIComponent(link.token)}&cross_device=1`);
|
|
705
|
-
if (!response.ok || !response.data)
|
|
706
|
-
return null;
|
|
707
|
-
return { requiresConfirmation: response.data.requires_confirmation === true, matchCode: response.data.match_code ?? null };
|
|
708
|
-
}
|
|
709
|
-
/**
|
|
710
|
-
* Cross-device magic link, second device: confirm with the code both devices show. The first device's
|
|
711
|
-
* `pollAttempt` then returns the ticket. One use; a wrong code invalidates the link.
|
|
712
|
-
*/
|
|
713
|
-
async function confirmCrossDeviceLink(client, link) {
|
|
714
|
-
const response = await client.post('/v1/client/sign_ins/verify/confirm', { attempt: link.attempt, token: link.token, match_code: link.matchCode });
|
|
715
|
-
return response.ok;
|
|
716
|
-
}
|
|
717
732
|
/**
|
|
718
733
|
* §5.3 polling. Returns the updated flow, and a ticket once the link has been
|
|
719
734
|
* opened somewhere.
|
|
@@ -731,15 +746,18 @@ async function pollAttempt(client, state) {
|
|
|
731
746
|
// no session: the password signs in now, so stop polling and ask for it.
|
|
732
747
|
if (response.data.password_hold_released) {
|
|
733
748
|
const email = response.data.identifier ?? null;
|
|
749
|
+
(0, email_link_1.forgetLinkSecret)(id);
|
|
734
750
|
return {
|
|
735
|
-
state: { ...state, attempt: response.data, pollSecret: null, prepared: null, passwordHold: { attemptId: id, email, released: true } },
|
|
751
|
+
state: { ...state, attempt: response.data, pollSecret: null, prepared: null, matchCode: null, passwordHold: { attemptId: id, email, released: true } },
|
|
736
752
|
ticket: null,
|
|
737
753
|
};
|
|
738
754
|
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
ticket: response.data.ticket ?? null
|
|
742
|
-
|
|
755
|
+
const next = { ...state, attempt: response.data };
|
|
756
|
+
if (shouldKeepPolling(next))
|
|
757
|
+
return { state: next, ticket: response.data.ticket ?? null };
|
|
758
|
+
// The wait is over, one way or another: the code and the stored secret are done with.
|
|
759
|
+
(0, email_link_1.forgetLinkSecret)(id);
|
|
760
|
+
return { state: { ...next, matchCode: null }, ticket: response.data.ticket ?? null };
|
|
743
761
|
}
|
|
744
762
|
/** Whether polling should continue, so a tab does not poll a dead attempt forever. */
|
|
745
763
|
function shouldKeepPolling(state) {
|
|
@@ -748,6 +766,9 @@ function shouldKeepPolling(state) {
|
|
|
748
766
|
// A sign-up polls for its link only. Past that this tab drives every step (the passkey,
|
|
749
767
|
// MFA set-up, policies), and a poll that collected a ticket would skip the recovery codes.
|
|
750
768
|
const a = state.attempt;
|
|
769
|
+
// An earlier sign-up for this address finished: no ticket will ever come here.
|
|
770
|
+
if (a.finished_elsewhere)
|
|
771
|
+
return false;
|
|
751
772
|
return !(a.object === 'sign_up_attempt' && a.status !== 'needs_email_verification');
|
|
752
773
|
}
|
|
753
774
|
/**
|
|
@@ -766,13 +787,14 @@ function acknowledgeExistingAccount(state) {
|
|
|
766
787
|
/**
|
|
767
788
|
* A sign-up's emailed link proved the address and made no session, so this tab
|
|
768
789
|
* can only go and sign in. The tab's poll secret proves nothing about the mailbox:
|
|
769
|
-
* say the same thing whichever account the address belongs to.
|
|
790
|
+
* say the same thing whichever account the address belongs to. So too a repeat
|
|
791
|
+
* sign-up whose earlier attempt finished (`finished_elsewhere`).
|
|
770
792
|
*/
|
|
771
793
|
function signUpProvedByLink(attempt) {
|
|
772
794
|
return (!!attempt &&
|
|
773
795
|
attempt.object === 'sign_up_attempt' &&
|
|
774
|
-
attempt.
|
|
775
|
-
|
|
796
|
+
(attempt.finished_elsewhere === true ||
|
|
797
|
+
(attempt.status === 'needs_first_factor' && !(attempt.supported_first_factors ?? []).includes('passkey'))));
|
|
776
798
|
}
|
|
777
799
|
/**
|
|
778
800
|
* Where `signUpProvedByLink`'s "Sign in" goes: `url` when the app has its own page,
|