Skip to content

feat(journey-client): webauthn conditional mediation autofill passkey support …#581

Merged
vatsalparikh merged 4 commits intomainfrom
sdks-4612
Apr 30, 2026
Merged

feat(journey-client): webauthn conditional mediation autofill passkey support …#581
vatsalparikh merged 4 commits intomainfrom
sdks-4612

Conversation

@vatsalparikh
Copy link
Copy Markdown
Contributor

@vatsalparikh vatsalparikh commented Apr 23, 2026

JIRA Ticket

pingidentity.atlassian.net/browse/SDKS-4612

Description

Add WebAuthn conditional mediation (passkey autofill) support to @forgerock/journey-client.
This enables consumers to opt-in to conditional UI by passing mediation: 'conditional' and an AbortSignal through to navigator.credentials.get(...) with a helper to feature-detect browser support.

What changed

  • WebAuthn authenticate now supports conditional mediation: WebAuthn.authenticate(step, mediation?, signal?)
  • Forwards mediation and signal to navigator.credentials.get({ publicKey, mediation, signal })
  • Requires an AbortSignal when mediation === 'conditional' If conditional mediation is requested but unsupported, throws NotSupportedError and existing error handling sets the hidden outcome to unsupported
  • Added WebAuthn.isConditionalMediationSupported() helper for feature detection
  • Added unit tests
  • Updated journey app to show autofill passkey option

How to test

Recording

Screen.Recording.2026-04-23.at.11.53.07.AM.mov

Did you add a changeset?

Yes

Summary by CodeRabbit

  • New Features

    • Adds passkey conditional mediation support with optional cancellation control and a runtime helper to detect browser support.
  • Bug Fixes / UX

    • Improved autocomplete hints on credential and username fields to enhance passkey autofill.
    • Authentication now reports unsupported platforms and cancellation consistently and coordinates passkey UI to avoid unintended submissions.
  • Tests

    • Adds unit and end-to-end tests for conditional mediation (e2e currently skipped).

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 23, 2026

🦋 Changeset detected

Latest commit: 8d314c6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
Name Type
@forgerock/journey-client Minor
@forgerock/davinci-client Minor
@forgerock/device-client Minor
@forgerock/oidc-client Minor
@forgerock/protect Minor
@forgerock/sdk-types Minor
@forgerock/sdk-utilities Minor
@forgerock/iframe-manager Minor
@forgerock/sdk-logger Minor
@forgerock/sdk-oidc Minor
@forgerock/sdk-request-middleware Minor
@forgerock/storage Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds conditional WebAuthn mediation support: API gains optional AbortSignal and mediation fields; runtime chooses conditional flow when step metadata requests it and the browser supports it (with cancellation and error semantics). E2E inputs get autocomplete="webauthn", a new handler orchestrates WebAuthn flows, tests and a changeset added.

Changes

Cohort / File(s) Summary
WebAuthn Core Library
packages/journey-client/src/lib/webauthn/webauthn.ts, packages/journey-client/src/lib/webauthn/interfaces.ts, packages/journey-client/src/lib/webauthn/webauthn.test.ts, packages/journey-client/api-report/journey-client.webauthn.api.md
Added conditional-mediation support: authenticate(step, signal?), getAuthenticationCredential(..., mediation?, signal?), isConditionalMediationSupported(), and WebAuthnAuthenticationMetadata.mediation?. Updated abort/error handling and API report.
E2E App Input Components
e2e/journey-app/components/text-input.ts, e2e/journey-app/components/validated-username.ts
Set autocomplete="webauthn" on NameCallback/validated username inputs to enable conditional passkey autofill.
E2E App WebAuthn Orchestration
e2e/journey-app/components/webauthn-step.ts, e2e/journey-app/main.ts
Introduced handleWebAuthnStep() to branch auth vs registration, focus the webauthn input, check conditional support, call WebAuthn.authenticate(step, signal) for conditional flows, and return { callbacksRendered, didSubmit } to gate callback rendering and submission.
E2E Tests / Suites
e2e/journey-suites/src/webauthn-device.test.ts
Refactored virtual authenticator setup/cleanup and added a Playwright suite (currently skipped) exercising conditional passkey autofill (resident-key virtual authenticator + registration then conditional authentication).
Changeset / Docs
.changeset/ready-snakes-sell.md
New changeset documenting API signature changes, conditional mediation behavior, AbortSignal usage, helper isConditionalMediationSupported(), and test/docs updates.

Sequence Diagram

sequenceDiagram
    participant App as Journey App
    participant Handler as WebAuthn Handler
    participant Browser as Browser Capability\n(PublicKeyCredential.isConditionalMediationAvailable)
    participant Creds as navigator.credentials
    participant Server as Backend

    App->>Handler: handleWebAuthnStep(step)
    Handler->>App: render callbacks (input[autocomplete="webauthn"])
    Handler->>Browser: isConditionalMediationSupported()
    alt unsupported
        Browser-->>Handler: false
        Handler->>App: fall back to prompted flow (webauthnComponent) -> submitForm()
    else supported
        Browser-->>Handler: true
        Handler->>Creds: get({mediation:'conditional', signal, ...})
        alt credential returned
            Creds-->>Handler: PublicKeyCredential
            Handler->>Server: submitForm()
            Server-->>Handler: response
            Handler-->>App: didSubmit: true
        else abort or error
            Creds-->>Handler: AbortError / Error
            Handler-->>App: setError(...), didSubmit: false
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • ancheetah
  • ryanbas21
  • cerebrl
  • SteinGabriel

Poem

🐰 A tiny hop on keyboard keys,
Signals whisper with the breeze,
Passkeys peek and softly call,
Autofill hums — no hands at all,
The rabbit twitches, "Nice work, please!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main feature: WebAuthn conditional mediation support for passkey autofill in the journey-client package.
Description check ✅ Passed The PR description covers the JIRA ticket, describes changes comprehensively, and documents testing procedures including a recording.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sdks-4612

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud Bot commented Apr 23, 2026

View your CI Pipeline Execution ↗ for commit 72550da

Command Status Duration Result
nx affected -t build lint test typecheck e2e-ci ❌ Failed 1m 49s View ↗

☁️ Nx Cloud last updated this comment at 2026-04-30 23:58:41 UTC

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
e2e/journey-app/components/webauthn-step.ts (1)

68-69: Redundant conditional-mediation support check.

WebAuthn.authenticate(..., 'conditional', signal) already calls isConditionalMediationSupported() internally and throws NotSupportedError if unsupported. Calling it here too means two async feature-detection round-trips per auth step. Consider caching the result, or relying on authenticate()'s internal check and falling back on the NotSupportedError rejection.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/journey-app/components/webauthn-step.ts` around lines 68 - 69, The extra
await WebAuthn.isConditionalMediationSupported() + if (isConditionalSupported &&
conditionalInput) is redundant and causes double feature-detection; remove that
pre-check and always call WebAuthn.authenticate(..., 'conditional', signal) when
conditionalInput is available, then catch the rejection and detect a
NotSupportedError (or exception.name === 'NotSupportedError') to fall back to
the non-conditional path. If you care about avoiding repeated detection calls,
add a small cache (e.g., a module-level cachedConditionalSupported flag
checked/updated when WebAuthn.isConditionalMediationSupported() is first called)
and use it instead of awaiting every time. Ensure references to
WebAuthn.authenticate, WebAuthn.isConditionalMediationSupported, and the
conditionalInput handling are updated accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/journey-app/components/webauthn-step.ts`:
- Around line 70-78: handleWebAuthnStep currently launches
WebAuthn.authenticate(step, 'conditional', controller.signal) with a local
AbortController that is never aborted, so the in-flight promise can later call
submitForm() or setError() against a stale step; fix by exposing and using an
abort mechanism (e.g., return or register the controller via a module-level
hook) so callers (main.ts and the submit handler) can call controller.abort()
whenever renderForm()/step changes, the form is submitted, or the component is
torn down; specifically ensure the AbortController created in handleWebAuthnStep
(and used in WebAuthn.authenticate) is aborted from main.ts before calling
journeyClient.next(...) and when swapping steps to prevent stale
submitForm()/setError() invocations.
- Around line 71-75: The catch block on WebAuthn.authenticate currently treats
every rejection as a user-facing error; change it so AbortError is ignored: in
the promise rejection handler for WebAuthn.authenticate(step, 'conditional',
controller.signal) check the thrown error (e.g., if (err?.name === 'AbortError')
return;), and only call setError('WebAuthn failed or was cancelled. Please try
again or use a different method.') for non-AbortError failures so cancellations
from the conditional flow (controller.signal/step changes) are not surfaced to
the user; keep successful flow calling submitForm() unchanged.

---

Nitpick comments:
In `@e2e/journey-app/components/webauthn-step.ts`:
- Around line 68-69: The extra await WebAuthn.isConditionalMediationSupported()
+ if (isConditionalSupported && conditionalInput) is redundant and causes double
feature-detection; remove that pre-check and always call
WebAuthn.authenticate(..., 'conditional', signal) when conditionalInput is
available, then catch the rejection and detect a NotSupportedError (or
exception.name === 'NotSupportedError') to fall back to the non-conditional
path. If you care about avoiding repeated detection calls, add a small cache
(e.g., a module-level cachedConditionalSupported flag checked/updated when
WebAuthn.isConditionalMediationSupported() is first called) and use it instead
of awaiting every time. Ensure references to WebAuthn.authenticate,
WebAuthn.isConditionalMediationSupported, and the conditionalInput handling are
updated accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8f68f8e4-60dd-4731-ad4c-0ede19ab8556

📥 Commits

Reviewing files that changed from the base of the PR and between 8e77da3 and 64195ab.

📒 Files selected for processing (8)
  • .changeset/ready-snakes-sell.md
  • e2e/journey-app/components/text-input.ts
  • e2e/journey-app/components/validated-username.ts
  • e2e/journey-app/components/webauthn-step.ts
  • e2e/journey-app/main.ts
  • packages/journey-client/api-report/journey-client.webauthn.api.md
  • packages/journey-client/src/lib/webauthn/webauthn.test.ts
  • packages/journey-client/src/lib/webauthn/webauthn.ts

Comment on lines +70 to +78
const controller = new AbortController();
void WebAuthn.authenticate(step, 'conditional', controller.signal)
.then(() => submitForm())
.catch(() => {
setError('WebAuthn failed or was cancelled. Please try again or use a different method.');
});

return { callbacksRendered: true, didSubmit: false };
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Stale in-flight conditional authenticate can submit or error against a newer step.

handleWebAuthnStep returns { callbacksRendered: true, didSubmit: false } while the WebAuthn.authenticate(step, 'conditional', controller.signal) promise keeps running in the background. The AbortController is created but never aborted, so if the user submits via a different method (e.g., username/password on the same step) or renderForm() is invoked again for a new step, the in-flight conditional request can still resolve later and call submitForm() on a now-stale step, or call setError() over the new UI. You should abort the controller when the step changes / form is submitted / component is torn down.

Suggested direction

Expose an abort hook (or module-level controller) so main.ts can cancel the in-flight conditional request before navigating to the next step, and abort it inside the submit handler in main.ts before calling journeyClient.next(...).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/journey-app/components/webauthn-step.ts` around lines 70 - 78,
handleWebAuthnStep currently launches WebAuthn.authenticate(step, 'conditional',
controller.signal) with a local AbortController that is never aborted, so the
in-flight promise can later call submitForm() or setError() against a stale
step; fix by exposing and using an abort mechanism (e.g., return or register the
controller via a module-level hook) so callers (main.ts and the submit handler)
can call controller.abort() whenever renderForm()/step changes, the form is
submitted, or the component is torn down; specifically ensure the
AbortController created in handleWebAuthnStep (and used in
WebAuthn.authenticate) is aborted from main.ts before calling
journeyClient.next(...) and when swapping steps to prevent stale
submitForm()/setError() invocations.

Comment on lines +71 to +75
void WebAuthn.authenticate(step, 'conditional', controller.signal)
.then(() => submitForm())
.catch(() => {
setError('WebAuthn failed or was cancelled. Please try again or use a different method.');
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Don't surface an error for AbortError in the conditional flow.

With conditional mediation, authenticate() intentionally rethrows AbortError without mutating the hidden outcome because cancellations are expected (user picked a different method, step changed, controller aborted). The blanket .catch(() => setError(...)) here turns every cancellation into a user-visible "WebAuthn failed or was cancelled" message, which will be noisy during normal autofill UX.

-      void WebAuthn.authenticate(step, 'conditional', controller.signal)
-        .then(() => submitForm())
-        .catch(() => {
-          setError('WebAuthn failed or was cancelled. Please try again or use a different method.');
-        });
+      void WebAuthn.authenticate(step, 'conditional', controller.signal)
+        .then(() => submitForm())
+        .catch((err: unknown) => {
+          if (err instanceof Error && err.name === 'AbortError') return;
+          setError('WebAuthn failed or was cancelled. Please try again or use a different method.');
+        });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/journey-app/components/webauthn-step.ts` around lines 71 - 75, The catch
block on WebAuthn.authenticate currently treats every rejection as a user-facing
error; change it so AbortError is ignored: in the promise rejection handler for
WebAuthn.authenticate(step, 'conditional', controller.signal) check the thrown
error (e.g., if (err?.name === 'AbortError') return;), and only call
setError('WebAuthn failed or was cancelled. Please try again or use a different
method.') for non-AbortError failures so cancellations from the conditional flow
(controller.signal/step changes) are not surfaced to the user; keep successful
flow calling submitForm() unchanged.

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 23, 2026

Codecov Report

❌ Patch coverage is 5.88235% with 48 lines in your changes missing coverage. Please review.
✅ Project coverage is 17.48%. Comparing base (5d6747a) to head (8c34384).
⚠️ Report is 83 commits behind head on main.

Files with missing lines Patch % Lines
...ckages/journey-client/src/lib/webauthn/webauthn.ts 5.88% 48 Missing ⚠️

❌ Your patch status has failed because the patch coverage (5.88%) is below the target coverage (40.00%). You can increase the patch coverage or adjust the target coverage.
❌ Your project status has failed because the head coverage (17.48%) is below the target coverage (40.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff             @@
##             main     #581       +/-   ##
===========================================
- Coverage   70.90%   17.48%   -53.42%     
===========================================
  Files          53      154      +101     
  Lines        2021    24195    +22174     
  Branches      377     1146      +769     
===========================================
+ Hits         1433     4231     +2798     
- Misses        588    19964    +19376     
Files with missing lines Coverage Δ
...ages/journey-client/src/lib/webauthn/interfaces.ts 5.00% <ø> (ø)
...ckages/journey-client/src/lib/webauthn/webauthn.ts 15.69% <5.88%> (-1.64%) ⬇️

... and 102 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 23, 2026

Open in StackBlitz

@forgerock/davinci-client

pnpm add https://pkg.pr.new/@forgerock/davinci-client@581

@forgerock/device-client

pnpm add https://pkg.pr.new/@forgerock/device-client@581

@forgerock/journey-client

pnpm add https://pkg.pr.new/@forgerock/journey-client@581

@forgerock/oidc-client

pnpm add https://pkg.pr.new/@forgerock/oidc-client@581

@forgerock/protect

pnpm add https://pkg.pr.new/@forgerock/protect@581

@forgerock/sdk-types

pnpm add https://pkg.pr.new/@forgerock/sdk-types@581

@forgerock/sdk-utilities

pnpm add https://pkg.pr.new/@forgerock/sdk-utilities@581

@forgerock/iframe-manager

pnpm add https://pkg.pr.new/@forgerock/iframe-manager@581

@forgerock/sdk-logger

pnpm add https://pkg.pr.new/@forgerock/sdk-logger@581

@forgerock/sdk-oidc

pnpm add https://pkg.pr.new/@forgerock/sdk-oidc@581

@forgerock/sdk-request-middleware

pnpm add https://pkg.pr.new/@forgerock/sdk-request-middleware@581

@forgerock/storage

pnpm add https://pkg.pr.new/@forgerock/storage@581

commit: 8c34384

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 23, 2026

Deployed 4cdb392 to https://ForgeRock.github.io/ping-javascript-sdk/pr-581/4cdb39250bff1828e3c5c84449aa3255763af1b8 branch gh-pages in ForgeRock/ping-javascript-sdk

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 23, 2026

📦 Bundle Size Analysis

📦 Bundle Size Analysis

🚨 Significant Changes

🔻 @forgerock/device-client - 0.0 KB (-10.0 KB, -100.0%)
🔻 @forgerock/journey-client - 0.0 KB (-90.3 KB, -100.0%)
🔺 @forgerock/journey-client - 91.9 KB (+1.6 KB, +1.8%)

➖ No Changes

@forgerock/device-client - 10.0 KB
@forgerock/davinci-client - 48.3 KB
@forgerock/oidc-client - 25.2 KB
@forgerock/sdk-utilities - 11.2 KB
@forgerock/sdk-types - 7.9 KB
@forgerock/protect - 144.6 KB
@forgerock/storage - 1.5 KB
@forgerock/sdk-oidc - 4.8 KB
@forgerock/sdk-request-middleware - 4.5 KB
@forgerock/sdk-logger - 1.6 KB
@forgerock/iframe-manager - 2.4 KB


14 packages analyzed • Baseline from latest main build

Legend

🆕 New package
🔺 Size increased
🔻 Size decreased
➖ No change

ℹ️ How bundle sizes are calculated
  • Current Size: Total gzipped size of all files in the package's dist directory
  • Baseline: Comparison against the latest build from the main branch
  • Files included: All build outputs except source maps and TypeScript build cache
  • Exclusions: .map, .tsbuildinfo, and .d.ts.map files

🔄 Updated automatically on each push to this PR

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/journey-client/src/lib/webauthn/webauthn.ts (1)

355-371: Optional: mirror the signal guard inside getAuthenticationCredential for defense-in-depth.

authenticate() enforces that mediation === 'conditional' requires an AbortSignal, but getAuthenticationCredential is a public static method on an exported class (surfaced in journey-client.webauthn.api.md). A caller invoking it directly with mediation: 'conditional' and no signal will get a browser-level rejection, which is acceptable, but a thin guard here would make the public API self-consistent and keep error messages uniform. Not blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/journey-client/src/lib/webauthn/webauthn.ts` around lines 355 - 371,
Add a defensive guard in getAuthenticationCredential: before calling
navigator.credentials.get, check if mediation === 'conditional' and signal is
falsy, and if so throw an Error with a clear message like "AbortSignal required
when mediation === 'conditional'" and set the error name to
WebAuthnOutcomeType.InvalidStateError (mirroring the enforcement in
authenticate); place this check in the getAuthenticationCredential method just
after the PublicKeyCredential feature check and before the call to
navigator.credentials.get so callers who invoke getAuthenticationCredential
directly get a consistent, descriptive error.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/journey-client/src/lib/webauthn/webauthn.ts`:
- Around line 355-371: Add a defensive guard in getAuthenticationCredential:
before calling navigator.credentials.get, check if mediation === 'conditional'
and signal is falsy, and if so throw an Error with a clear message like
"AbortSignal required when mediation === 'conditional'" and set the error name
to WebAuthnOutcomeType.InvalidStateError (mirroring the enforcement in
authenticate); place this check in the getAuthenticationCredential method just
after the PublicKeyCredential feature check and before the call to
navigator.credentials.get so callers who invoke getAuthenticationCredential
directly get a consistent, descriptive error.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9f1390bc-a634-4fc6-9b70-94c33f98dafb

📥 Commits

Reviewing files that changed from the base of the PR and between 64195ab and 31044cf.

📒 Files selected for processing (8)
  • .changeset/ready-snakes-sell.md
  • e2e/journey-app/components/text-input.ts
  • e2e/journey-app/components/validated-username.ts
  • e2e/journey-app/components/webauthn-step.ts
  • e2e/journey-app/main.ts
  • packages/journey-client/api-report/journey-client.webauthn.api.md
  • packages/journey-client/src/lib/webauthn/webauthn.test.ts
  • packages/journey-client/src/lib/webauthn/webauthn.ts
✅ Files skipped from review due to trivial changes (2)
  • e2e/journey-app/components/text-input.ts
  • e2e/journey-app/components/validated-username.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • .changeset/ready-snakes-sell.md
  • e2e/journey-app/main.ts

Copy link
Copy Markdown
Collaborator

@cerebrl cerebrl left a comment

Choose a reason for hiding this comment

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

Before further review, I needed to ask a question.

Comment thread packages/journey-client/src/lib/webauthn/webauthn.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/journey-client/api-report/journey-client.webauthn.api.md (1)

95-95: ⚡ Quick win

Add JSDoc documentation for new APIs.

The modified and new APIs are marked // (undocumented), indicating missing JSDoc comments in the source. Since these are public APIs involving a new feature (conditional mediation), documentation would help consumers understand:

  • When signal is required (conditional mediation) vs. optional
  • What conditional mediation is and when to use it
  • Browser compatibility considerations
  • The relationship between metadata.mediation and the signal parameter

Consider adding JSDoc comments in the source TypeScript files to explain these aspects.

Also applies to: 98-98, 107-107, 120-120

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/journey-client/api-report/journey-client.webauthn.api.md` at line
95, Add JSDoc comments to the public WebAuthn APIs — specifically the static
method authenticate(step: JourneyStep, signal?: AbortSignal) and the related
methods at the other marked locations — to explain when the AbortSignal is
required versus optional (i.e., conditional mediation), define what “conditional
mediation” means and when callers should use it, call out browser compatibility
caveats, and describe how the optional signal parameter interacts with
metadata.mediation on JourneyStep; update the TypeScript source (where
authenticate and the other undocumented APIs are declared) to include these
JSDoc blocks so the generated api-report no longer shows “(undocumented)”.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/journey-client/api-report/journey-client.webauthn.api.md`:
- Line 95: Add JSDoc comments to the public WebAuthn APIs — specifically the
static method authenticate(step: JourneyStep, signal?: AbortSignal) and the
related methods at the other marked locations — to explain when the AbortSignal
is required versus optional (i.e., conditional mediation), define what
“conditional mediation” means and when callers should use it, call out browser
compatibility caveats, and describe how the optional signal parameter interacts
with metadata.mediation on JourneyStep; update the TypeScript source (where
authenticate and the other undocumented APIs are declared) to include these
JSDoc blocks so the generated api-report no longer shows “(undocumented)”.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c382bd37-9d7b-4d0c-a238-d24ea34559a3

📥 Commits

Reviewing files that changed from the base of the PR and between 31044cf and a935fc4.

📒 Files selected for processing (6)
  • .changeset/ready-snakes-sell.md
  • e2e/journey-app/components/webauthn-step.ts
  • packages/journey-client/api-report/journey-client.webauthn.api.md
  • packages/journey-client/src/lib/webauthn/interfaces.ts
  • packages/journey-client/src/lib/webauthn/webauthn.test.ts
  • packages/journey-client/src/lib/webauthn/webauthn.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/journey-client/src/lib/webauthn/interfaces.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • .changeset/ready-snakes-sell.md
  • packages/journey-client/src/lib/webauthn/webauthn.test.ts
  • e2e/journey-app/components/webauthn-step.ts
  • packages/journey-client/src/lib/webauthn/webauthn.ts

@vatsalparikh vatsalparikh force-pushed the sdks-4612 branch 2 times, most recently from 14892f6 to 1028da7 Compare April 30, 2026 01:25
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
e2e/journey-app/components/webauthn-step.ts (2)

81-83: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Ignore AbortError in conditional mediation catch path.

At Line 81, cancellation is expected behavior in conditional flows; surfacing it as an error message will create false-negative UX noise.

Suggested fix
-      void WebAuthn.authenticate(step, controller.signal)
-        .then(() => submitForm())
-        .catch(() => {
-          setError('WebAuthn failed or was cancelled. Please try again or use a different method.');
-        });
+      void WebAuthn.authenticate(step, controller.signal)
+        .then(() => submitForm())
+        .catch((err: unknown) => {
+          if (err instanceof Error && err.name === 'AbortError') return;
+          setError('WebAuthn failed or was cancelled. Please try again or use a different method.');
+        });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/journey-app/components/webauthn-step.ts` around lines 81 - 83, The catch
handler currently always sets an error message on any rejection; change the
catch to accept the error (e.g., catch((err) => ...)) and if err.name ===
'AbortError' simply return/ignore (no call to setError) to avoid surfacing
expected cancellation in the conditional mediation flow, otherwise call
setError('WebAuthn failed or was cancelled. Please try again or use a different
method.') as before; update the catch associated with the conditional mediation
/ navigator.credentials flow so it references the caught error and checks its
name before calling setError.

77-85: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Conditional flow needs lifecycle cancellation to prevent stale submissions.

At Line 78, the AbortController is local and never aborted when the step changes or another submit path wins, so the in-flight conditional request can later call submitForm()/setError() on stale UI state.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/journey-suites/src/webauthn-device.test.ts`:
- Around line 139-176: The PR is blocked by missing unit tests for the changed
branches in packages/journey-client/src/lib/webauthn/webauthn.ts — add focused
unit tests that exercise the module's conditional-mediation path, the
AbortSignal/abort handling, and the error paths: mock
navigator.credentials.get/create to return a conditional mediation response,
simulate an AbortController abort to hit the abort branch, and force navigator
failures to hit error propagation; write tests that import the exported
functions from webauthn.ts and assert expected results/throws and any cleanup
behavior so the conditional/abort/error branches are covered.

---

Duplicate comments:
In `@e2e/journey-app/components/webauthn-step.ts`:
- Around line 81-83: The catch handler currently always sets an error message on
any rejection; change the catch to accept the error (e.g., catch((err) => ...))
and if err.name === 'AbortError' simply return/ignore (no call to setError) to
avoid surfacing expected cancellation in the conditional mediation flow,
otherwise call setError('WebAuthn failed or was cancelled. Please try again or
use a different method.') as before; update the catch associated with the
conditional mediation / navigator.credentials flow so it references the caught
error and checks its name before calling setError.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e43e7be9-8da2-4b20-b9e0-14ae70d36356

📥 Commits

Reviewing files that changed from the base of the PR and between a935fc4 and 1028da7.

📒 Files selected for processing (7)
  • .changeset/ready-snakes-sell.md
  • e2e/journey-app/components/webauthn-step.ts
  • e2e/journey-suites/src/webauthn-device.test.ts
  • packages/journey-client/api-report/journey-client.webauthn.api.md
  • packages/journey-client/src/lib/webauthn/interfaces.ts
  • packages/journey-client/src/lib/webauthn/webauthn.test.ts
  • packages/journey-client/src/lib/webauthn/webauthn.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/journey-client/src/lib/webauthn/webauthn.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/journey-client/src/lib/webauthn/interfaces.ts
  • .changeset/ready-snakes-sell.md
  • packages/journey-client/src/lib/webauthn/webauthn.ts

Comment on lines +139 to +176
test('registers a passkey then authenticates via conditional autofill', async ({ page }) => {
const { clickButton, navigate } = asyncEvents(page);

await test.step('Register a WebAuthn credential', async () => {
// Start with an empty virtual authenticator.
const { credentials: initialCredentials } = await cdp.send('WebAuthn.getCredentials', {
authenticatorId,
});
expect(initialCredentials).toHaveLength(0);

// Run a registration journey that creates a credential in the authenticator.
await navigate('/?clientId=tenant&journey=TEST_WebAuthn-Registration');
await expect(page.getByLabel('User Name')).toBeVisible();
await page.getByLabel('User Name').fill(username);
await page.getByLabel('Password').fill(password);
await clickButton('Submit', '/authenticate');
await expect(page.getByRole('button', { name: 'Logout' })).toBeVisible();

const { credentials } = await cdp.send('WebAuthn.getCredentials', { authenticatorId });
expect(credentials.length).toBeGreaterThan(0);
});

await test.step('Authenticate using conditional UI / passkey autofill', async () => {
// Ensure we are not reusing an existing AM session.
// This makes the test exercise passkey auth, not cookie auth.
await page.context().clearCookies();

// This journey emits conditional mediation metadata and should complete via background
// WebAuthn (journey-app triggers the request and submits when a credential is returned).
await navigate('/?clientId=tenant&journey=TEST_AutofillPasskeyWebAuthn');

// With a virtual authenticator configured for automatic presence simulation, this should
// complete without any manual click.
await expect(page.getByRole('button', { name: 'Logout' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Complete' })).toBeVisible();
});
});
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Coverage gate is still failing for changed webauthn.ts logic.

This E2E test helps, but the PR remains blocked by low patch coverage on packages/journey-client/src/lib/webauthn/webauthn.ts (notably conditional/abort/error branches). Please add targeted unit tests for those branches to satisfy the required threshold.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/journey-suites/src/webauthn-device.test.ts` around lines 139 - 176, The
PR is blocked by missing unit tests for the changed branches in
packages/journey-client/src/lib/webauthn/webauthn.ts — add focused unit tests
that exercise the module's conditional-mediation path, the AbortSignal/abort
handling, and the error paths: mock navigator.credentials.get/create to return a
conditional mediation response, simulate an AbortController abort to hit the
abort branch, and force navigator failures to hit error propagation; write tests
that import the exported functions from webauthn.ts and assert expected
results/throws and any cleanup behavior so the conditional/abort/error branches
are covered.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/journey-client/src/lib/webauthn/webauthn.ts (1)

195-209: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't persist AbortError into webAuthnOutcome for caller-cancelled requests.

authenticate() now accepts a caller-supplied AbortSignal for every auth request, but this catch only treats AbortError as benign when mediation === 'conditional'. If a consumer aborts a prompted authenticate(step, signal), Line 208 writes ERROR::AbortError... into the hidden callback even though the cancellation was intentional, which can leak stale error state into a later submit.

Suggested fix
-        if (mediation === 'conditional' && error.name === 'AbortError') {
+        if (
+          error.name === 'AbortError' &&
+          (mediation === 'conditional' || signal?.aborted)
+        ) {
           throw error;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/journey-client/src/lib/webauthn/webauthn.ts` around lines 195 - 209,
The catch block in authenticate() currently only skips persisting AbortError
when mediation === 'conditional', causing caller-triggered AbortErrors to be
written into hiddenCallback (webAuthnOutcome); change the logic so any
AbortError is treated as a benign cancellation: if error.name === 'AbortError'
then rethrow immediately without calling hiddenCallback.setInputValue,
regardless of mediation. Keep the existing NotSupportedError handling intact and
continue to set hiddenCallback for other errors.
♻️ Duplicate comments (1)
e2e/journey-app/components/webauthn-step.ts (1)

77-85: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Expose and abort the conditional-flow controller on step changes / alternate submits.

This branch returns immediately while WebAuthn.authenticate(step, controller.signal) keeps running, but the controller never leaves this scope. That means a later step render or different submit path cannot cancel the in-flight conditional request, so the stale promise can still call submitForm() or hit this catch against newer UI. Since packages/journey-client/src/lib/webauthn/webauthn.ts already treats conditional AbortError as expected cancellation, the rejection handler here should also stop surfacing AbortError once the controller is wired up.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/journey-app/components/webauthn-step.ts` around lines 77 - 85, The
in-flight WebAuthn conditional controller is scoped locally and cannot be
aborted by later renders or alternate submits, allowing a stale promise to call
submitForm() or setError(); fix by exposing the AbortController (e.g., attach it
to the step object or component-level ref) so other code paths and the step
cleanup can abort it, and update the rejection handler of
WebAuthn.authenticate(step, controller.signal) to ignore AbortError (check
error.name === 'AbortError' or similar) so aborted attempts do not trigger
submitForm() or setError; ensure any previous controller is aborted before
creating a new one and clear the attached controller after resolution.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/journey-client/src/lib/webauthn/webauthn.ts`:
- Around line 195-209: The catch block in authenticate() currently only skips
persisting AbortError when mediation === 'conditional', causing caller-triggered
AbortErrors to be written into hiddenCallback (webAuthnOutcome); change the
logic so any AbortError is treated as a benign cancellation: if error.name ===
'AbortError' then rethrow immediately without calling
hiddenCallback.setInputValue, regardless of mediation. Keep the existing
NotSupportedError handling intact and continue to set hiddenCallback for other
errors.

---

Duplicate comments:
In `@e2e/journey-app/components/webauthn-step.ts`:
- Around line 77-85: The in-flight WebAuthn conditional controller is scoped
locally and cannot be aborted by later renders or alternate submits, allowing a
stale promise to call submitForm() or setError(); fix by exposing the
AbortController (e.g., attach it to the step object or component-level ref) so
other code paths and the step cleanup can abort it, and update the rejection
handler of WebAuthn.authenticate(step, controller.signal) to ignore AbortError
(check error.name === 'AbortError' or similar) so aborted attempts do not
trigger submitForm() or setError; ensure any previous controller is aborted
before creating a new one and clear the attached controller after resolution.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2a6794a1-e797-4c94-9419-3dc1d428e06d

📥 Commits

Reviewing files that changed from the base of the PR and between 1028da7 and 75c47f4.

📒 Files selected for processing (7)
  • .changeset/ready-snakes-sell.md
  • e2e/journey-app/components/webauthn-step.ts
  • e2e/journey-suites/src/webauthn-device.test.ts
  • packages/journey-client/api-report/journey-client.webauthn.api.md
  • packages/journey-client/src/lib/webauthn/interfaces.ts
  • packages/journey-client/src/lib/webauthn/webauthn.test.ts
  • packages/journey-client/src/lib/webauthn/webauthn.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/journey-client/src/lib/webauthn/webauthn.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • .changeset/ready-snakes-sell.md

nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

@vatsalparikh vatsalparikh merged commit 07015a2 into main Apr 30, 2026
6 of 7 checks passed
@vatsalparikh vatsalparikh deleted the sdks-4612 branch May 1, 2026 00:00
Copy link
Copy Markdown
Contributor

@nx-cloud nx-cloud Bot left a comment

Choose a reason for hiding this comment

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

Nx Cloud has identified a flaky task in your failed CI:

🔂 Since the failure was identified as flaky, we triggered a CI rerun by adding an empty commit to this branch.

Nx Cloud View detailed reasoning in Nx Cloud ↗


🎓 Learn more about Self-Healing CI on nx.dev

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants