From c834b5f6a65f73204180268b8ee43e6172ae7ea8 Mon Sep 17 00:00:00 2001 From: masnwilliams <43387599+masnwilliams@users.noreply.github.com> Date: Sat, 29 Aug 2026 08:33:06 +0000 Subject: [PATCH 1/3] Render typed managed auth errors --- .changeset/calm-codes-render.md | 5 +++ .../src/components/StepError.test.tsx | 27 ++++++++++++ .../src/components/StepError.tsx | 41 ++++++++++++++++++- 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 .changeset/calm-codes-render.md create mode 100644 packages/managed-auth-react/src/components/StepError.test.tsx diff --git a/.changeset/calm-codes-render.md b/.changeset/calm-codes-render.md new file mode 100644 index 0000000..6c09fa1 --- /dev/null +++ b/.changeset/calm-codes-render.md @@ -0,0 +1,5 @@ +--- +"@onkernel/managed-auth-react": patch +--- + +Render typed reauthentication blockers and rejected authenticator codes with specific recovery guidance. diff --git a/packages/managed-auth-react/src/components/StepError.test.tsx b/packages/managed-auth-react/src/components/StepError.test.tsx new file mode 100644 index 0000000..81870a9 --- /dev/null +++ b/packages/managed-auth-react/src/components/StepError.test.tsx @@ -0,0 +1,27 @@ +import { describe, expect, test } from "bun:test"; +import { errorDisplayForCode } from "./StepError"; + +describe("errorDisplayForCode", () => { + test("renders typed reauth blockers", () => { + const cases = [ + ["totp_required", "Authenticator code required"], + ["sms_code_required", "SMS code required"], + ["email_code_required", "Email code required"], + ["account_choice_required", "Account selection required"], + ["customer_input_required", "Additional input required"], + ["external_action_required", "External action required"], + ]; + + for (const [code, title] of cases) { + expect(errorDisplayForCode(code)?.title).toBe(title); + } + }); + + test("distinguishes a rejected authenticator code from invalid credentials", () => { + const display = errorDisplayForCode("totp_code_rejected"); + + expect(display?.title).toBe("Authenticator code rejected"); + expect(display?.description).toContain("Try a fresh code"); + expect(display).not.toEqual(errorDisplayForCode("credentials_invalid")); + }); +}); diff --git a/packages/managed-auth-react/src/components/StepError.tsx b/packages/managed-auth-react/src/components/StepError.tsx index 75ee41b..493bae9 100644 --- a/packages/managed-auth-react/src/components/StepError.tsx +++ b/packages/managed-auth-react/src/components/StepError.tsx @@ -26,6 +26,41 @@ const ERROR_DISPLAY: Record = { title: "Invalid credentials", description: "The username or password was not accepted by the website.", }, + totp_required: { + title: "Authenticator code required", + description: + "An authenticator code is required, but no TOTP secret is available for automatic re-authentication.", + }, + sms_code_required: { + title: "SMS code required", + description: + "A code sent by SMS is required. Complete an interactive login to continue.", + }, + email_code_required: { + title: "Email code required", + description: + "A code sent by email is required. Complete an interactive login to continue.", + }, + account_choice_required: { + title: "Account selection required", + description: + "An account or identity must be selected before login can continue.", + }, + customer_input_required: { + title: "Additional input required", + description: + "Additional customer input is required before login can continue.", + }, + external_action_required: { + title: "External action required", + description: + "An external action is required to continue. Check your authenticator app, email, or phone for a verification request.", + }, + totp_code_rejected: { + title: "Authenticator code rejected", + description: + "The website rejected the authenticator code. Try a fresh code, or reconnect the account if generated codes keep failing across new code windows.", + }, bot_detected: { title: "Verification required", description: @@ -66,6 +101,10 @@ const ERROR_DISPLAY: Record = { }, }; +export function errorDisplayForCode(errorCode?: string) { + return errorCode ? ERROR_DISPLAY[errorCode] : undefined; +} + function extractErrorText(raw: string): string { try { const parsed = JSON.parse(raw); @@ -92,7 +131,7 @@ export function StepError({ const siteName = extractDomainName(targetDomain); const [showDetails, setShowDetails] = useState(false); - const display = errorCode ? ERROR_DISPLAY[errorCode] : undefined; + const display = errorDisplayForCode(errorCode); const title = display?.title ?? l.errorTitle; const description = display?.description ?? l.errorGenericMessage; From 850d2028989a68dd43c7ad1810849e1e095012fc Mon Sep 17 00:00:00 2001 From: masnwilliams <43387599+masnwilliams@users.noreply.github.com> Date: Sat, 29 Aug 2026 09:00:33 +0000 Subject: [PATCH 2/3] Keep challenge outcome copy neutral --- .../managed-auth-react/src/components/StepError.test.tsx | 6 ++++-- packages/managed-auth-react/src/components/StepError.tsx | 8 +++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/managed-auth-react/src/components/StepError.test.tsx b/packages/managed-auth-react/src/components/StepError.test.tsx index 81870a9..272a741 100644 --- a/packages/managed-auth-react/src/components/StepError.test.tsx +++ b/packages/managed-auth-react/src/components/StepError.test.tsx @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"; import { errorDisplayForCode } from "./StepError"; describe("errorDisplayForCode", () => { - test("renders typed reauth blockers", () => { + test("renders typed challenge outcomes with context-neutral copy", () => { const cases = [ ["totp_required", "Authenticator code required"], ["sms_code_required", "SMS code required"], @@ -13,7 +13,9 @@ describe("errorDisplayForCode", () => { ]; for (const [code, title] of cases) { - expect(errorDisplayForCode(code)?.title).toBe(title); + const display = errorDisplayForCode(code); + expect(display?.title).toBe(title); + expect(display?.description.toLowerCase()).not.toContain("reauth"); } }); diff --git a/packages/managed-auth-react/src/components/StepError.tsx b/packages/managed-auth-react/src/components/StepError.tsx index 493bae9..8f8e041 100644 --- a/packages/managed-auth-react/src/components/StepError.tsx +++ b/packages/managed-auth-react/src/components/StepError.tsx @@ -29,17 +29,15 @@ const ERROR_DISPLAY: Record = { totp_required: { title: "Authenticator code required", description: - "An authenticator code is required, but no TOTP secret is available for automatic re-authentication.", + "An authenticator code is required, but no saved TOTP secret is available.", }, sms_code_required: { title: "SMS code required", - description: - "A code sent by SMS is required. Complete an interactive login to continue.", + description: "A code sent by SMS is required.", }, email_code_required: { title: "Email code required", - description: - "A code sent by email is required. Complete an interactive login to continue.", + description: "A code sent by email is required.", }, account_choice_required: { title: "Account selection required", From 02be4ec077690ba1508244cc28a847f8b555bd88 Mon Sep 17 00:00:00 2001 From: masnwilliams <43387599+masnwilliams@users.noreply.github.com> Date: Mon, 31 Aug 2026 01:33:48 +0000 Subject: [PATCH 3/3] Keep TOTP requirement copy provider-agnostic --- .../managed-auth-react/src/components/StepError.test.tsx | 6 ++++++ packages/managed-auth-react/src/components/StepError.tsx | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/managed-auth-react/src/components/StepError.test.tsx b/packages/managed-auth-react/src/components/StepError.test.tsx index 272a741..cfde11e 100644 --- a/packages/managed-auth-react/src/components/StepError.test.tsx +++ b/packages/managed-auth-react/src/components/StepError.test.tsx @@ -19,6 +19,12 @@ describe("errorDisplayForCode", () => { } }); + test("does not infer TOTP secret availability", () => { + expect(errorDisplayForCode("totp_required")?.description).toBe( + "An authenticator code is required.", + ); + }); + test("distinguishes a rejected authenticator code from invalid credentials", () => { const display = errorDisplayForCode("totp_code_rejected"); diff --git a/packages/managed-auth-react/src/components/StepError.tsx b/packages/managed-auth-react/src/components/StepError.tsx index 8f8e041..f515b7e 100644 --- a/packages/managed-auth-react/src/components/StepError.tsx +++ b/packages/managed-auth-react/src/components/StepError.tsx @@ -28,8 +28,7 @@ const ERROR_DISPLAY: Record = { }, totp_required: { title: "Authenticator code required", - description: - "An authenticator code is required, but no saved TOTP secret is available.", + description: "An authenticator code is required.", }, sms_code_required: { title: "SMS code required",