Skip to content

GH#1357: feat(auth): add passwordless login with passkeys#1358

Merged
superdav42 merged 5 commits into
mainfrom
feature/auto-20260608-193530
Jun 10, 2026
Merged

GH#1357: feat(auth): add passwordless login with passkeys#1358
superdav42 merged 5 commits into
mainfrom
feature/auto-20260608-193530

Conversation

@superdav42

@superdav42 superdav42 commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds native email-first passwordless authentication with passkey/WebAuthn support, email OTP fallback, auth tables, login/checkout UI integration, docs, and focused unit/E2E coverage.

Files Changed

README.md,assets/js/passwordless-auth.js,inc/auth/class-email-otp-service.php,inc/auth/class-passkey-credential-store.php,inc/auth/class-passkey-service.php,inc/auth/class-passwordless-auth-manager.php,inc/auth/class-webauthn-challenge-store.php,inc/auth/class-webauthn-helper.php,inc/checkout/class-checkout.php,inc/class-wp-ultimo.php,inc/database/email-otp-attempts/class-email-otp-attempts-schema.php,inc/database/email-otp-attempts/class-email-otp-attempts-table.php,inc/database/passkey-credentials/class-passkey-credentials-schema.php,inc/database/passkey-credentials/class-passkey-credentials-table.php,inc/database/webauthn-challenges/class-webauthn-challenges-schema.php,inc/database/webauthn-challenges/class-webauthn-challenges-table.php,inc/functions/helper.php,inc/loaders/class-table-loader.php,inc/ui/class-login-form-element.php,readme.txt,tests/WP_Ultimo/Auth/Passwordless_Auth_Test.php,tests/e2e/cypress/integration/login.spec.js,tests/e2e/cypress/support/commands/login.js,views/checkout/partials/inline-login-prompt.php

Runtime Testing

  • Risk level: Low (agent prompts / infrastructure scripts)
  • Verification: vendor/bin/phpcs on changed PHP files; vendor/bin/phpstan targeted passwordless analysis; ./node_modules/.bin/eslint assets/js/passwordless-auth.js --max-warnings=0; vendor/bin/phpunit --filter Passwordless_Auth_Test; browser-agent runtime checks for wp-login.php, custom login page, and register checkout inline prompt.

Resolves #1357


aidevops.sh v3.20.41 plugin for OpenCode v1.16.2 with gpt-5.5 spent 4h 16m and 1,842,331 tokens on this with the user in an interactive session.

Summary by CodeRabbit

  • New Features
    • Passwordless login: passkey (WebAuthn) sign-in with short-lived email one-time codes fallback, available on wp-login, checkout, and inline prompts; client-side and server flows with auto-init and public JS API.
  • Documentation
    • README/FAQ updated to describe Passwordless Login behavior.
  • Tests
    • New unit and E2E tests for OTPs, passkeys, challenges, and the passwordless UI.

@superdav42 superdav42 added the origin:interactive Created by interactive user session label Jun 9, 2026
@superdav42

Copy link
Copy Markdown
Collaborator Author

Completion Summary

  • What: Adds native email-first passwordless authentication with passkey/WebAuthn support, email OTP fallback, auth tables, login/checkout UI integration, docs, and focused unit/E2E coverage.
  • Issue: Add native passkey login with email OTP fallback #1357
  • Files changed: README.md,assets/js/passwordless-auth.js,inc/auth/class-email-otp-service.php,inc/auth/class-passkey-credential-store.php,inc/auth/class-passkey-service.php,inc/auth/class-passwordless-auth-manager.php,inc/auth/class-webauthn-challenge-store.php,inc/auth/class-webauthn-helper.php,inc/checkout/class-checkout.php,inc/class-wp-ultimo.php,inc/database/email-otp-attempts/class-email-otp-attempts-schema.php,inc/database/email-otp-attempts/class-email-otp-attempts-table.php,inc/database/passkey-credentials/class-passkey-credentials-schema.php,inc/database/passkey-credentials/class-passkey-credentials-table.php,inc/database/webauthn-challenges/class-webauthn-challenges-schema.php,inc/database/webauthn-challenges/class-webauthn-challenges-table.php,inc/functions/helper.php,inc/loaders/class-table-loader.php,inc/ui/class-login-form-element.php,readme.txt,tests/WP_Ultimo/Auth/Passwordless_Auth_Test.php,tests/e2e/cypress/integration/login.spec.js,tests/e2e/cypress/support/commands/login.js,views/checkout/partials/inline-login-prompt.php
  • Testing: vendor/bin/phpcs on changed PHP files; vendor/bin/phpstan targeted passwordless analysis; ./node_modules/.bin/eslint assets/js/passwordless-auth.js --max-warnings=0; vendor/bin/phpunit --filter Passwordless_Auth_Test; browser-agent runtime checks for wp-login.php, custom login page, and register checkout inline prompt.
  • Key decisions: none

aidevops.sh v3.20.41 plugin for OpenCode v1.16.2 with gpt-5.5 spent 4h 16m and 1,842,331 tokens on this with the user in an interactive session.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds native passwordless authentication: WebAuthn passkeys with short-lived email OTP fallback across DB schemas, WebAuthn helpers/challenges, OTP/passkey services, a manager with AJAX endpoints, client-side runtime JS, login/checkout UI integration, tests, and docs updates.

Changes

Passwordless authentication feature

Layer / File(s) Summary
Auth storage contracts and table wiring
inc/database/email-otp-attempts/*, inc/database/passkey-credentials/*, inc/database/webauthn-challenges/*, inc/loaders/class-table-loader.php, inc/auth/class-passkey-credential-store.php
Defines and registers DB schemas/tables for OTP attempts, passkey credentials, and WebAuthn challenges; wires them into the Table_Loader and adds a passkey credential store with create/find/update operations.
WebAuthn helper and challenge store
inc/auth/class-webauthn-helper.php, inc/auth/class-webauthn-challenge-store.php
Implements WebAuthn primitives: base64url helpers, challenge generation, origin/RP validation, CBOR/authenticator parsing, COSE→PEM conversion, signature verification; provides challenge create/get_valid/mark_used with TTL and single-use enforcement.
OTP and Passkey domain services
inc/auth/class-email-otp-service.php, inc/auth/class-passkey-service.php
Adds Email OTP service (rate limits, hashed storage, send, verify/consume) and Passkey service (build registration/auth options, verify registration/authentication, update sign counters), integrating with the stores and helper.
Passwordless manager, AJAX endpoints, and client asset
inc/auth/class-passwordless-auth-manager.php, assets/js/passwordless-auth.js
Adds Passwordless_Auth_Manager (init, assets/localization, markup, AJAX handlers for start/verify/register flows, table install) and the browser runtime handling WebAuthn/OTP flows, AJAX wrapper, helpers, MutationObserver initialization, and public API window.wuPasswordlessAuth.
Integration into login surfaces and sanitization rules
inc/ui/class-login-form-element.php, views/checkout/partials/inline-login-prompt.php, inc/checkout/class-checkout.php, inc/class-wp-ultimo.php, inc/functions/helper.php
Replaces password fields with server-generated passwordless markup in login and inline prompt, enqueues assets for checkout/login, initializes manager from core loader, hardens WP-CLI helper call, and expands KSES allowlist (ARIA, input attributes, data-* and button attributes).
Automated validation and docs
tests/WP_Ultimo/Auth/Passwordless_Auth_Test.php, tests/e2e/cypress/integration/login.spec.js, tests/e2e/cypress/support/commands/login.js, README.md, readme.txt
Adds unit tests for OTP/challenge/passkey storage and behavior, an E2E test and Cypress helper updates for passwordless UI, and updates README/readme.txt feature and FAQ entries.

Sequence Diagram

sequenceDiagram
  participant Browser
  participant PasswordlessAuthJS as wuPasswordlessAuth
  participant AuthManager as Passwordless_Auth_Manager
  participant OTPService as Email_OTP_Service
  participant PasskeyService as Passkey_Service

  Browser->>PasswordlessAuthJS: submit identifier
  PasswordlessAuthJS->>AuthManager: ajax_start(identifier)
  AuthManager->>PasskeyService: get_authentication_options(user)
  alt user has passkeys
    AuthManager-->>PasswordlessAuthJS: mode=passkey + publicKey options
    PasswordlessAuthJS->>Browser: navigator.credentials.get()
    PasswordlessAuthJS->>AuthManager: ajax_verify_passkey(payload)
    AuthManager->>PasskeyService: verify_authentication(payload)
    AuthManager-->>PasswordlessAuthJS: success + redirect
  else user has no passkeys
    AuthManager->>OTPService: create_and_send(user, email)
    AuthManager-->>PasswordlessAuthJS: mode=otp + token
    PasswordlessAuthJS->>AuthManager: ajax_verify_otp(token, code)
    AuthManager->>OTPService: verify(token, code)
    AuthManager-->>PasswordlessAuthJS: success + redirect + enrollment options
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

status:available

Poem

🐰 Hopped to the login, no password to keep,

A passkey or code makes the journey quick,
Challenges hashed, and secrets sleep deep,
Inbox a number, or touch—click click!
A rabbit cheers for secure login tricks.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'GH#1357: feat(auth): add passwordless login with passkeys' accurately and directly summarizes the primary change: adding passwordless login with passkey support.
Linked Issues check ✅ Passed The pull request comprehensively implements all primary coding requirements from issue #1357: email-first passwordless flow [#1357], WebAuthn/passkey support with OTP fallback [#1357], native implementation without plugin dependency [#1357], UI integration across wp-login.php and checkout [#1357], passwordless subsystem architecture [#1357], database tables with hashing [#1357], security controls [#1357], and comprehensive tests [#1357].
Out of Scope Changes check ✅ Passed All code changes are directly scoped to passwordless authentication: auth service classes, WebAuthn helpers, credential/challenge stores, email OTP service, passwordless manager, database schemas/tables, UI integration (login form, checkout), and supporting tests. Helper function updates (kses, CLI check) are appropriately scoped to support passwordless/security requirements.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/auto-20260608-193530

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.

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

@github-actions

Copy link
Copy Markdown

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

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

⚠️ Outside diff range comments (1)
views/checkout/partials/inline-login-prompt.php (1)

24-37: ⚠️ Potential issue | 🟠 Major

Hook wu_inline_login_prompt_before_submit executes after all passwordless login UI is output, contradicting its name and intended use.

The hook fires at line 36, after get_inline_login_markup() has already output the complete wrapper containing email input, "Continue", "Use passkey", "Verify code", and "Create passkey" buttons. Callbacks (e.g., captcha fields) will render after all these controls, not before submit as the hook name implies.

Either move the hook call before line 24, or rename/document it as wu_inline_login_prompt_after_auth_controls.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@views/checkout/partials/inline-login-prompt.php` around lines 24 - 37, The
hook wu_inline_login_prompt_before_submit is fired after
\WP_Ultimo\Auth\Passwordless_Auth_Manager::get_instance()->get_inline_login_markup($field_type)
outputs the full passwordless controls, so move the
do_action('wu_inline_login_prompt_before_submit', $field_type) call to run
before the get_inline_login_markup() echo (so callbacks render inside the form
before the submit controls), and update the surrounding docblock/comment to
reflect the hook now fires prior to the submit/auth controls; alternatively, if
you prefer not to move it, rename the hook to
wu_inline_login_prompt_after_auth_controls and update usages and docs to match.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@assets/js/passwordless-auth.js`:
- Around line 297-304: Change the redirect precedence in the finish flow so the
server-issued redirect (data.redirect_url) is used before any client-seeded
value (state.redirectUrl from container.dataset.redirect-to) to avoid open
redirects; in the block that currently calls
window.location.assign(state.redirectUrl || data.redirect_url ||
window.location.href), flip the order to
window.location.assign(data.redirect_url || state.redirectUrl ||
window.location.href) and keep the existing reload check
(container.dataset.success === 'reload') intact.

In `@inc/auth/class-email-otp-service.php`:
- Around line 164-196: The code currently validates OTPs then calls
$this->consume((int) $attempt->id) after returning the user, allowing races to
accept the same token concurrently; fix by enforcing atomic single-use in the
verification path: change the consume() implementation (and/or use a new
atomicConsume method) to perform an atomic UPDATE that sets consumed_at WHERE id
= ? AND consumed_at IS NULL and return whether rows_affected === 1, and in the
verify method (class Email_OTP_Service / current verify function) call that
atomic consume before returning the $user and only return success when
atomicConsume returns true; also ensure increment_attempts() uses an atomic
UPDATE on attempts to avoid lost updates.
- Around line 130-140: create_and_send() currently ignores the result of
send_email() and returns a success payload even if email delivery failed;
capture the boolean return from $this->send_email($user, $code) and if it
returns false, undo the created OTP (remove the row identified by $token) and
return a payload indicating sent => false (and avoid advertising a usable
token), e.g. call your existing delete/revoke method with $token (or add one if
missing) before returning so a failed SMTP send does not leave a live OTP
behind.

In `@inc/auth/class-passwordless-auth-manager.php`:
- Around line 411-437: The current branch in class-passwordless-auth-manager.php
leaks account existence by returning 'mode: passkey' when
passkeys->credentials()->user_has_credentials($user->ID) is true; instead, keep
the initial response generic and do not reveal passkey availability. Change the
logic around passkeys->get_authentication_options(...) and the
wp_send_json_success call so the endpoint always returns a non-enumerable
generic success payload/message (same shape for all identifiers) and only expose
passkey-specific options after the caller proves control (e.g., after OTP
verification or authenticated challenge), ensuring otp->create_and_send($user,
$email) and send_error(is_wp_error($otp)) behavior remains intact. Use the
unique symbols passkeys->credentials()->user_has_credentials,
passkeys->get_authentication_options, otp->create_and_send, send_error, and
wp_send_json_success to locate and update the code.
- Around line 406-408: The boolean request values are being cast incorrectly
with (bool) wu_request(...) causing string '0' to become true; update the
parsing of supports_webauthn and force_otp in the PasswordlessAuthManager code
to explicitly sanitize/convert the incoming values (e.g., use
rest_sanitize_boolean(wu_request('...')) or filter_var(wu_request('...'),
FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) and then coerce to bool) before
any branching that uses $supports_webauthn or $force_otp so passkey vs OTP logic
behaves correctly.

In `@inc/auth/class-webauthn-challenge-store.php`:
- Around line 94-139: The current flow (WebAuthn_Challenge_Store::get_valid() +
mark_used()) has a TOCTOU race allowing challenge replay; modify the logic to
atomically reserve a challenge before verification by performing a single
conditional update or transactional SELECT+UPDATE: change mark_used() (or add a
new reserve_challenge()) to run an UPDATE on wu_webauthn_challenges that sets
used_at = current_time(...) WHERE id = absint($id) AND used_at IS NULL and
return false if affected_rows === 0, then have
Passkey_Service::verify_authentication() and verify_registration() call this
reserve method before doing signature/origin/counter checks and abort if
reservation failed; alternatively implement SELECT ... FOR UPDATE inside a
transaction in get_valid()/mark_used() to lock the row until verification
completes. Ensure callers check the boolean result and stop verification when
reservation fails; keep sanitize_key($type) and current_time usage as before.

In `@inc/auth/class-webauthn-helper.php`:
- Around line 78-82: The RP ID extraction currently strips ports only when no
']' is present, causing bracketed IPv6 with ports (e.g. "[::1]:8080") to remain
malformed; update the logic around $host so the port is removed prior to
trimming brackets: ensure the preg_replace that strips ':\d+$' is applied for
bracketed addresses too (or use a regex that removes ']:\d+$' before calling
trim($host, '[]')), and keep use of strpos($host, ':')/strpos($host, ']') and
functions preg_replace and trim to locate and update the code path that returns
trim($host, '[]').

In `@inc/ui/class-login-form-element.php`:
- Around line 789-801: The passwordless login render call to
\WP_Ultimo\Auth\Passwordless_Auth_Manager::get_instance()->get_login_form_markup(...)
drops this element's existing settings (label_*, remember toggle, submit label,
fallback_url), removing the "Use password instead" escape hatch and custom
labels; fix by passing the element's current settings into get_login_form_markup
from this class when building the 'passwordless_login' field (i.e., enrich the
array currently containing 'context', 'redirect_to', and 'redirect_type' with
the element's label_* values, remember toggle flag, submit label, and
fallback_url derived from the element/atts/state so get_login_form_markup
receives those options and preserves prior behavior).

In `@tests/e2e/cypress/support/commands/login.js`:
- Around line 46-53: The assertion in the passwordless branch of the support
command using cy.get("`#user_pass`").should("not.be.visible") can hang if the
`#user_pass` element is not in the DOM; update the passwordless branch in
tests/e2e/cypress/support/commands/login.js (the block that checks
.wu-passwordless-auth and calls cy.loginByApi and cy.visit) to assert that
`#user_pass` does not exist instead of not visible (use the Cypress not.exist
assertion) so the test will pass when the password field is omitted from the
DOM.

---

Outside diff comments:
In `@views/checkout/partials/inline-login-prompt.php`:
- Around line 24-37: The hook wu_inline_login_prompt_before_submit is fired
after
\WP_Ultimo\Auth\Passwordless_Auth_Manager::get_instance()->get_inline_login_markup($field_type)
outputs the full passwordless controls, so move the
do_action('wu_inline_login_prompt_before_submit', $field_type) call to run
before the get_inline_login_markup() echo (so callbacks render inside the form
before the submit controls), and update the surrounding docblock/comment to
reflect the hook now fires prior to the submit/auth controls; alternatively, if
you prefer not to move it, rename the hook to
wu_inline_login_prompt_after_auth_controls and update usages and docs to match.
🪄 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: 1fc05124-237e-4c2d-978a-deb09ee4b471

📥 Commits

Reviewing files that changed from the base of the PR and between dda1a0a and 552274e.

📒 Files selected for processing (24)
  • README.md
  • assets/js/passwordless-auth.js
  • inc/auth/class-email-otp-service.php
  • inc/auth/class-passkey-credential-store.php
  • inc/auth/class-passkey-service.php
  • inc/auth/class-passwordless-auth-manager.php
  • inc/auth/class-webauthn-challenge-store.php
  • inc/auth/class-webauthn-helper.php
  • inc/checkout/class-checkout.php
  • inc/class-wp-ultimo.php
  • inc/database/email-otp-attempts/class-email-otp-attempts-schema.php
  • inc/database/email-otp-attempts/class-email-otp-attempts-table.php
  • inc/database/passkey-credentials/class-passkey-credentials-schema.php
  • inc/database/passkey-credentials/class-passkey-credentials-table.php
  • inc/database/webauthn-challenges/class-webauthn-challenges-schema.php
  • inc/database/webauthn-challenges/class-webauthn-challenges-table.php
  • inc/functions/helper.php
  • inc/loaders/class-table-loader.php
  • inc/ui/class-login-form-element.php
  • readme.txt
  • tests/WP_Ultimo/Auth/Passwordless_Auth_Test.php
  • tests/e2e/cypress/integration/login.spec.js
  • tests/e2e/cypress/support/commands/login.js
  • views/checkout/partials/inline-login-prompt.php

Comment thread assets/js/passwordless-auth.js Outdated
Comment thread inc/auth/class-email-otp-service.php
Comment thread inc/auth/class-email-otp-service.php
Comment thread inc/auth/class-passwordless-auth-manager.php Outdated
Comment thread inc/auth/class-passwordless-auth-manager.php Outdated
Comment thread inc/auth/class-webauthn-challenge-store.php
Comment thread inc/auth/class-webauthn-helper.php Outdated
Comment thread inc/ui/class-login-form-element.php
Comment thread tests/e2e/cypress/support/commands/login.js Outdated
@github-actions

Copy link
Copy Markdown

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

@superdav42

Copy link
Copy Markdown
Collaborator Author

CLAIM_RELEASED reason=worker_complete runner=superdav42 ts=2026-06-10T01:57:08Z aidevops_version=3.20.43 opencode_version=1.16.2

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@inc/auth/class-passkey-service.php`:
- Around line 281-291: After consuming the challenge you must ensure persisting
the new sign counter succeeded; check the boolean return of
Passkey_Credential_Store::update_usage (called via
$this->credentials->update_usage with $credential->id and $new_count) and if it
returns false, fail closed by returning a WP_Error (e.g. 'storage_error')
instead of continuing; locate the block around $this->challenges->mark_used(...)
and the subsequent $this->credentials->update_usage(...) call and add the
conditional error return when update_usage fails so authentication does not
proceed if the write fails.
🪄 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: 739cfabc-9b5e-49d3-86c1-bd46d39be2f1

📥 Commits

Reviewing files that changed from the base of the PR and between 552274e and 84c35cc.

📒 Files selected for processing (9)
  • assets/js/passwordless-auth.js
  • inc/auth/class-email-otp-service.php
  • inc/auth/class-passkey-service.php
  • inc/auth/class-passwordless-auth-manager.php
  • inc/auth/class-webauthn-challenge-store.php
  • inc/auth/class-webauthn-helper.php
  • inc/ui/class-login-form-element.php
  • tests/WP_Ultimo/Auth/Passwordless_Auth_Test.php
  • tests/e2e/cypress/support/commands/login.js
🚧 Files skipped from review as they are similar to previous changes (6)
  • tests/e2e/cypress/support/commands/login.js
  • inc/auth/class-webauthn-challenge-store.php
  • inc/ui/class-login-form-element.php
  • inc/auth/class-email-otp-service.php
  • inc/auth/class-passwordless-auth-manager.php
  • inc/auth/class-webauthn-helper.php

Comment thread inc/auth/class-passkey-service.php Outdated
@github-actions

Copy link
Copy Markdown

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

@superdav42

Copy link
Copy Markdown
Collaborator Author

CLAIM_RELEASED reason=worker_complete runner=superdav42 ts=2026-06-10T03:41:46Z aidevops_version=3.20.46 opencode_version=1.16.2

@github-actions

Copy link
Copy Markdown

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

@superdav42

Copy link
Copy Markdown
Collaborator Author

CLAIM_RELEASED reason=worker_complete runner=superdav42 ts=2026-06-10T04:08:25Z aidevops_version=3.20.46 opencode_version=1.16.2

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
inc/auth/class-passwordless-auth-manager.php (1)

406-420: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Passkey sign-in no longer has a way to start.

Line 418 now hard-codes the public start flow to OTP, but this class no longer exposes any unauthenticated endpoint that issues a WebAuthn authentication challenge/options for logged-out users. ajax_verify_passkey() only verifies an assertion after the browser has already completed navigator.credentials.get(), so existing passkey users no longer have a viable path to perform a passkey login.

This regresses the passkey-primary flow described for the feature; please restore a non-enumerable way to mint authentication options before keeping ajax_start() OTP-only.

Also applies to: 466-487

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@inc/auth/class-passwordless-auth-manager.php` around lines 406 - 420, The
AJAX start flow was locked to OTP only, breaking passkey logins; restore a
non-enumerable path that mints WebAuthn assertion/options for logged-out users
by updating ajax_start() to detect a passkey start request (or client
capability) and call the WebAuthn options factory (e.g. the class/method used
elsewhere for assertions—refer to ajax_verify_passkey() and the WebAuthn helper
on this class) to create assertion options and a token, then return
mode='webauthn' with the token and the generated options (and keep the public
message generic/non-enumerable). Apply the same change to the second start-like
block referenced around lines 466-487 so both entry points can return either OTP
or webauthn responses depending on client request/capability.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@inc/auth/class-passwordless-auth-manager.php`:
- Around line 406-420: The AJAX start flow was locked to OTP only, breaking
passkey logins; restore a non-enumerable path that mints WebAuthn
assertion/options for logged-out users by updating ajax_start() to detect a
passkey start request (or client capability) and call the WebAuthn options
factory (e.g. the class/method used elsewhere for assertions—refer to
ajax_verify_passkey() and the WebAuthn helper on this class) to create assertion
options and a token, then return mode='webauthn' with the token and the
generated options (and keep the public message generic/non-enumerable). Apply
the same change to the second start-like block referenced around lines 466-487
so both entry points can return either OTP or webauthn responses depending on
client request/capability.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6b5d266b-dd65-4304-ba47-fdad546f9753

📥 Commits

Reviewing files that changed from the base of the PR and between e9d2154 and 887e82d.

📒 Files selected for processing (1)
  • inc/auth/class-passwordless-auth-manager.php

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

Labels

origin:interactive Created by interactive user session review-feedback-scanned Merged PR already scanned for quality feedback

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add native passkey login with email OTP fallback

1 participant