eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1// Friendly copy for WebAuthn ceremony failures — pure and DOM-free so it's
2// unit-testable in Node (home/test/ceremony-errors.test.mjs) and shared by the
3// home pages (home.js, enroll.js, restore.js).
4//
5// The browser's ceremony errors are famously opaque (NotAllowedError is the
6// catch-all for "cancelled", "timed out", "blocked", and "no credential"), so
7// members read a specific, human sentence instead of an exception name. The
8// technical detail (error name + the phase tag backup-core.js attaches) is
9// deliberately kept — parenthesised after the sentence — because it's the only
10// signal we have when debugging a member's report.
11
12// friendlyCeremonyError maps a ceremony failure to member-facing copy.
13// `ceremony` is the intent the user pressed: "signin" (credentials.get) or
14// "create" (credentials.create). Returns { message, detail }: message is the
15// friendly sentence, detail the technical crumb (e.g. "NotAllowedError [create]").
16export function friendlyCeremonyError(err, ceremony) {
17 const name = (err && err.name) || "";
18 const phase = err && err.phase ? " [" + err.phase + "]" : "";
19 const detail = (name || "Error") + phase;
20 const creating = ceremony === "create";
21 switch (name) {
22 case "NotAllowedError":
23 // Cancelled, timed out, or the browser refused — indistinguishable by
24 // design, so the copy covers both the deliberate and the accidental read.
25 return {
26 message: creating
27 ? "Creating your sign-in was cancelled. If you didn't cancel, try again."
28 : "Sign in was cancelled. If you didn't cancel, try again.",
29 detail,
30 };
31 case "InvalidStateError":
32 // Duplicate registration: the device already holds a matching sign-in.
33 if (creating) return { message: "That's already set up. Try signing in instead.", detail };
34 break;
35 case "ConstraintError":
36 case "NotSupportedError":
37 // In practice: user verification is required but the device has nothing
38 // to verify with — no screen lock set up.
39 return {
40 message: "This device needs a screen lock, PIN, fingerprint, or face unlock before it can sign in this way.",
41 detail,
42 };
43 case "SecurityError":
44 // RP id / origin mismatch — a deployment problem, not the member's.
45 return { message: "Sign-in isn't configured for this website address.", detail };
46 case "AbortError":
47 return { message: "Sign-in was interrupted. Try again.", detail };
48 }
49 // Anything else (network errors, our own thrown Errors) already carries a
50 // human message — pass it through under the same shape.
51 return { message: (err && err.message) || String(err), detail };
52}
53
54// formatCeremonyError composes the one-line status string every caller renders:
55// the friendly sentence plus the kept-on-purpose technical detail.
56export function formatCeremonyError(err, ceremony) {
57 const { message, detail } = friendlyCeremonyError(err, ceremony);
58 return message + " (" + detail + ")";
59}
60