Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion packages/demo/src/States.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type StateName =
| "awaiting_input_full"
| "awaiting_input_mfa"
| "awaiting_external_action"
| "awaiting_external_action_multi"
| "submitting"
| "success"
| "expired"
Expand All @@ -83,6 +84,7 @@ const allStates: StateName[] = [
"awaiting_input_full",
"awaiting_input_mfa",
"awaiting_external_action",
"awaiting_external_action_multi",
"submitting",
"success",
"expired",
Expand Down Expand Up @@ -169,7 +171,23 @@ export function States() {
);
case "awaiting_external_action":
return (
<ExternalActionWaiting message="Check your phone for a push notification" />
<ExternalActionWaiting
message="Check your phone for a push notification"
mfaOptions={[{ type: "switch", label: "Try another way" }]}
onMFASelect={() => {}}
/>
);
case "awaiting_external_action_multi":
return (
<ExternalActionWaiting
message="Open the Gmail app on Apple iPhone 14 Pro Max and tap Yes on the prompt to verify it's you"
mfaOptions={[
{ type: "totp", label: "Use authenticator app" },
{ type: "sms", label: "Get a code via SMS" },
{ type: "switch", label: "Try another way" },
]}
onMFASelect={() => {}}
/>
);
case "submitting":
return (
Expand Down
3 changes: 3 additions & 0 deletions packages/managed-auth-react/src/KernelManagedAuth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ function KernelManagedAuthInner({
return (
<ExternalActionWaiting
message={state?.external_action_message ?? undefined}
mfaOptions={state?.mfa_options ?? []}
onMFASelect={submitMFA}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MFA submit error recovery navigates to wrong screen

Medium Severity

When submitMFA is called from the ExternalActionWaiting screen and the API call fails, the submit helper's catch block unconditionally falls back to "awaiting_input" UI state. This causes users on the external-action-waiting screen to be unexpectedly redirected to the input form (which may have no relevant fields) instead of returning to the external action screen. Polling will eventually correct the state, but there's a visible flash of the wrong screen.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f38988a. Configure here.

isLoading={isSubmitting}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,55 @@
import { useSlot } from "../appearance/context";
import { useLocalization } from "../localization/context";
import { FingerprintIcon, KeyIcon, SmartphoneIcon } from "./icons";
import type { MFAOption, MFAType } from "../lib/types";
import { Button } from "./primitives/Button";
import {
FingerprintIcon,
KeyIcon,
MailIcon,
PhoneIcon,
RepeatIcon,
ShieldCheckIcon,
SmartphoneIcon,
} from "./icons";

function getMFAIcon(type: MFAType) {
switch (type) {
case "sms":
return <SmartphoneIcon />;
case "call":
return <PhoneIcon />;
case "email":
return <MailIcon />;
case "totp":
return <KeyIcon />;
case "push":
return <ShieldCheckIcon />;
case "password":
return <FingerprintIcon />;
case "switch":
return <RepeatIcon />;
default:
return <KeyIcon />;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated getMFAIcon function across two components

Low Severity

The getMFAIcon function in ExternalActionWaiting.tsx is an exact copy of the one in UnifiedAuthForm.tsx. This duplication means any future change to icon mappings (e.g. adding a passkey case) needs to be applied in two places, risking inconsistency.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f38988a. Configure here.


interface ExternalActionWaitingProps {
message?: string | null;
mfaOptions?: MFAOption[];
onMFASelect?: (mfaType: MFAType) => void;
isLoading?: boolean;
}

export function ExternalActionWaiting({ message }: ExternalActionWaitingProps) {
export function ExternalActionWaiting({
message,
mfaOptions = [],
onMFASelect,
isLoading,
}: ExternalActionWaitingProps) {
const slot = useSlot();
const l = useLocalization();
const hasMfaOptions = mfaOptions.length > 0 && onMFASelect;

return (
<div className="kma-step kma-step--center kma-external-action">
<div className="kma-step__icon-wrap">
Expand Down Expand Up @@ -45,6 +86,33 @@ export function ExternalActionWaiting({ message }: ExternalActionWaitingProps) {
</div>

<p className="kma-loading-hint">{l.externalActionWaiting}</p>

{hasMfaOptions && (
<div className="kma-external-action__alternatives">
{mfaOptions.map((option, idx) => (
<Button
key={idx}
variant="secondary"
slotKey="mfaOption"
className="kma-option"
onClick={() => onMFASelect(option.type)}
disabled={isLoading}
>
<span
{...slot("mfaOptionIcon", "kma-option__icon")}
aria-hidden="true"
>
{getMFAIcon(option.type)}
</span>
<div className="kma-option__text">
<div {...slot("mfaOptionLabel", "kma-option__label")}>
{option.label || l.mfaTypeLabels[option.type] || option.type}
</div>
</div>
</Button>
))}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MFA options missing "switch-last" sort from UnifiedAuthForm

Low Severity

UnifiedAuthForm explicitly sorts MFA options so that "switch" types ("Try another way") always render last, with a code comment explaining this intentional design. ExternalActionWaiting renders mfaOptions in the order received from the backend without this sort. If the backend sends the "switch" option before more specific methods like TOTP or SMS, the generic fallback will appear above them, contradicting the established UX pattern.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f38988a. Configure here.

</div>
)}
</div>
);
}
5 changes: 5 additions & 0 deletions packages/managed-auth-react/src/styles/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,11 @@
}
}

.kma-external-action__alternatives {
width: 100%;
padding: 0 1.5rem;
}

.kma-bouncing-dots {
display: flex;
justify-content: center;
Expand Down
Loading