Tree:
fcef37bfc7
cached-config-operations
main
revert-7033-patch-1
test_dylint
0.10.0
0.11.0
0.12.0
0.13.0
0.9.0
1.0.0
1.1.0
1.10.0
1.11.0
1.12.0
1.13.0
1.13.1
1.14
1.14.1
1.14.2
1.15.0
1.15.1
1.16.0
1.16.1
1.16.2
1.16.3
1.17.0
1.18.0
1.19.0
1.2.0
1.20.0
1.21.0
1.22.0
1.22.1
1.22.2
1.23.0
1.23.1
1.24.0
1.25.0
1.25.1
1.25.2
1.26.0
1.27.0
1.28.0
1.28.1
1.29.0
1.29.1
1.29.2
1.3.0
1.30.0
1.30.1
1.30.2
1.30.3
1.30.4
1.30.5
1.31.0
1.32.0
1.32.1
1.32.2
1.32.3
1.32.4
1.32.5
1.32.6
1.32.7
1.33.0
1.33.1
1.33.2
1.34.0
1.34.1
1.34.2
1.34.3
1.35.0
1.35.1
1.35.2
1.35.3
1.35.4
1.35.5
1.35.6
1.35.7
1.35.8
1.36.0
1.4.0
1.5.0
1.6.0
1.6.1
1.7.0
1.8.0
1.9.0
1.9.1
${ noResults }
3 Commits (fcef37bfc735cedc4b03073f6a92901d7295dfee)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
247a8905d8 |
Fix correctness, test reliability, and observability issues
Server hardening:
- accounts.rs: reject duplicate ids in `passkey_unlock_data` before they
collapse through the HashSet superset check and silently double-apply
with the second write winning.
- accounts.rs: re-check the PRF credential set immediately before
`set_password` commits the new account key. A concurrent enrol between
the validation snapshot and the rewrap loop would otherwise leave the
new credential's `encrypted_user_key` wrapped under the OLD akey,
permanently broken once the rotation commits.
- mod.rs: drop the legacy bare-`PasskeyRegistration` fallback in
`passkey_registration_challenge_state` — no writer produces that
shape, and the fallback permitted token-less finishes against a
hypothetical un-tokened row.
- mod.rs: switch token compares in both challenge-state extractors to
`crypto::ct_eq` (matches the `cred_id` pattern already used at
mod.rs:1424).
- mod.rs: normalise `passkey_assertion_challenge_state`'s deserialise
error to the generic "Invalid assertion challenge" rather than leaking
the underlying serde shape.
- mod.rs: document why `post_api_webauthn_delete` is intentionally
exempt from the SSO_ONLY gate (delete narrows capability, never
grants it; session is still SSO-authed).
- mod.rs: document that the post-`start_passkey_registration` mutation
of `authenticator_selection.require_resident_key` is a client-side
hint only (webauthn-rs destructures the field as unused), so readers
don't mistake it for a server-enforced policy.
Observability:
- web_authn_credential.rs / two_factor.rs: log Diesel errors from the
SELECT+DELETE transactions in `WebAuthnLoginChallenge::take` and
`TwoFactor::take_by_user_and_type` before collapsing to `None`. The
prior `.unwrap_or(None)` made degraded DB behaviour (deadlock, lock
timeout, conn drop) indistinguishable from a normal stale-token
rejection.
- identity.rs: log the failure path of `passkey_transports`'s
`serde_json::to_value(passkey)` instead of silently emitting
`Transports: []`. Clients can't otherwise distinguish "authenticator
reported no transports" from "we failed to serialise the passkey".
UX:
- vaultwarden.scss.hbs: re-add the `.vw-passkey-login` hide under
`sso_enabled && sso_only`. Without it the login page renders the
"Log in with passkey" button under SSO_ONLY, but the server gate at
`identity.rs:1250` rejects the click — UX dead end.
Test reliability:
- passkey.spec.ts: the "Non-PRF passkey" test now logs out + logs back
in via passkey between enrol and the lock-screen check. Without
this refresh the lock-screen reads the pre-enrol cached `/api/sync`
and the assertion passes for the wrong reason (cached
webAuthnPrfOptions was empty before enrol regardless of what the
server stored).
- passkey.spec.ts: guard the three describe blocks that restart the
vault container (`Passkey grant is rejected when SSO_ONLY is on`,
`Passkey enrolment is rejected when SSO_ONLY is on`, `Passkey login
rejects forged unverified-email handles`) on `useExternalVault` so
`PW_USE_EXTERNAL_VAULT=1` (host-cargo iteration) cleanly skips them
instead of colliding with the host process on port 8003.
- passkey.spec.ts: the SSO_ONLY-enrolment `afterAll` now also blanks
`sso_authority`, `sso_client_id`, `sso_client_secret` — the previous
reset left those placeholders in `config.json` and contaminated any
later test that toggled `sso_enabled=true`.
- passkey.spec.ts: the bearer-sniffer in the SSO_ONLY-enrolment
beforeAll now filters on `url.includes('/api/')` instead of capturing
whatever token happened to fly last during createAccount; mirrors
the disciplined sniffer in `account_lifecycle.spec.ts:178` and
insulates the test from web-vault request-order churn.
- 2fa.ts: export `resetTotpTimeStep()`; call it from the
`test.beforeEach` in `account_lifecycle.spec.ts` and `passkey.spec.ts`
so the module-scoped `lastSubmittedTotpTimeStep` doesn't leak across
tests / projects under `workers: 1` (would otherwise force a 30s
wait before the first TOTP of project N+1 based on project N's
history).
- global-utils.ts: document the `resetDB=false` contract honestly —
docker recreates the Vaultwarden container on any env-var change in
the compose `environment:` block, dropping the tmpfs sqlite DB along
with the in-process RSA key.
- login.smtp.spec.ts: drop the redundant "Dismiss extension prompts"
step from the 2fa test. `logUser` already calls
`utils.ignoreExtension` and lands on /vault, so the explicit "Add
it later" click timed out on a button the prior step had already
dismissed.
Test coverage additions:
- accounts.rs rotation validator: add a regression test for the
duplicate-id rejection in `passkey_unlock_data`. With a HashSet-only
superset check the rewrap loop in `post_rotatekey` could silently
apply two updates to the same credential id (second blob wins); the
test pins the new len-equality guard.
- mod.rs registration-challenge state: replace
`legacy_registration_challenge_rejects_finish_token` (which asserted
a "legacy bare-`PasskeyRegistration` accepted without token" fallback
no longer present) with
`registration_challenge_rejects_unwrapped_legacy_state`, matching
the assertion-side test's stricter contract: any blob that's not the
`{token, state}` wrapper is rejected regardless of whether a token
is sent. Pins the token-binding bypass closure.
|
2 weeks ago |
|
|
6522923cdb |
playwright: account lifecycle test + host-iteration config
Add a 23-step single-session lifecycle test covering every code path a real PRF-passkey user exercises: register → enrol passkey #1 → enrol passkey #2 on a second virtual authenticator → log in with passkey → lock + unlock with passkey → register a second-device context + "Log in with device" approval flow → enrol WebAuthn-2FA + TOTP-2FA → log in with passkey (server skips 2FA on webauthn grant) → log in with MP + WebAuthn-2FA → lock + unlock → remove passkey #1 → bump KDF iterations (auto-logout) → re-login with WebAuthn-2FA → rotate account encryption keys (auto-logout) → re-login with MP + TOTP-2FA → lock + unlock with passkey #2 → remove passkey #2 → log in with WebAuthn-2FA (post-login sync refreshes the client cache so the lock-screen assertion sees the credential-free state) → unlock with MP → disable both 2FA providers → log in with MP alone. A sibling `account-lifecycle-sso` project runs the same 23-step lifecycle under `SSO_ENABLED=true`. Login flows that previously typed MP at the prompt go through Keycloak + the bundled "Unlock vault" MP-decrypt step instead, exercising the SSO + WebAuthn-2FA + PRF passkey composition the original MP-only project couldn't cover. The body of the lifecycle is shared between both projects via mode- dispatch (`modeOps(sso)`); CDP virtual-authenticator wrangling, passkey enrol/remove, lock/unlock, KDF + MP rotation, and the second-device auth-request flow are extracted into `tests/setups/account_lifecycle_helpers.ts`. Notable wire-shape coverage: * `userDecryption.webAuthnPrfOptions` is populated only after PRF enrolment and emptied after passkey removal. * Rotation re-wraps each PRF credential's stored encryptedUserKey/encryptedPrivateKey; passkey #2 still unlocks the rotated user key. * KDF change auto-logs out via security-stamp rotation. * "Log in with device" is gated on `isKnownDevice` in the bundled web vault — the test asserts the affordance is absent on a fresh second-device context and surfaces after that context's first MP login. Reuses `logUser`/`submitTwoFactor` from `setups/`; the only spec-local helpers are CDP-specific (virtual-authenticator creation, `withAuthenticatorDisabled` callback wrapper) or test-local expectations (`expectLockScreenButtons`, `expectPostEmailPageNoPasskey`, etc.). Supporting changes for SSO mode: - `enterEmailOnLoginPage`: SSO branch fills `.vw-email-sso` and clicks "Other" to reveal the MP-continue flow — the standard email-label selector matches the SCSS-hidden `.vw-email-continue` input under `SSO_ENABLED=true`. - `sso.ts#logUser`: accepts a separate `kcPassword` for cases where vault MP and the IdP credential diverge (post-MP-rotation); accepts either /#/lock or /#/vault after 2FA so PRF auto-unlock via the lock screen's `promptBiometric=true` redirect is tolerated; uses `name: 'Unlock', exact: true` to disambiguate from the "Unlock with passkey" affordance when PRF is enrolled. The "Join organi[sz]ation" heading match is locale-tolerant (en vs en_GB). - `2fa.ts#submitTwoFactor`: post-2FA URL waiter accepts /#/lock too; TOTP submission tracks its own `last_used` time-step and waits for the next period boundary when a repeat would land on a consumed step, so consecutive TOTP submissions in the same period don't trip vw's `last_used > current` rejection. - `2fa.ts#ensure2FAProvider`: 5s probe (was 1s) for the default provider's input before falling through to the picker — under SSO mode the extra Keycloak round-trip can delay the connector iframe mount enough to race the switcher. - `global-utils.ts#cleanLanding`: swallow "navigation interrupted" / `net::ERR_ABORTED` from `page.goto('/')` — the bundled web vault's `/` → `/#/login` hash-route redirect occasionally fires while the initial nav is still resolving under docker's slower I/O. - `global-utils.ts#startVault`/`dbConfig`: register `account-lifecycle-sso` as a sqlite-backed project. - `user.ts#logUser`: accepts `kcPassword` for option-shape parity with `sso.ts#logUser`. Ignored in MP-only mode. - `global-setup.ts`: short-circuits the docker-compose build when `PW_USE_EXTERNAL_VAULT=1` — host-iteration runs against a cargo-run vw don't need the multi-minute release rebuild. In SSO mode, login-with-passkey is left as MP-mode-only coverage (the SSO project skips the two wire-shape probes for the same reason); the two smaller tests check the same server endpoint shape and are mode- invariant. The SSO lifecycle uses TOTP-2FA with `withAuthenticatorDisabled` at the MP-fresh-required logins so the lock screen waits for manual MP entry instead of auto-firing PRF unlock. Plumbing changes that come with this spec: * `playwright.config.ts` threads `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` into the `account-lifecycle` project's `launchOptions.executablePath` so the spec can run locally against a host-running Vaultwarden on systems where `npx playwright install chromium` is unsupported. * `compose/playwright/Dockerfile` installs Chromium alongside Firefox so the docker harness can run the `account-lifecycle` project too. * `account_lifecycle.spec.ts` honors `PW_USE_EXTERNAL_VAULT=1` to skip the docker startVault/stopVault hooks (host-mode iteration only; CI leaves it unset). Run requires `LOGIN_RATELIMIT_MAX_BURST` raised above the 10/60s default — multiple `connect/token` POSTs during the auth-request approval + back-to-back 2FA cycles exhaust the limit otherwise. Runtime: ~1.5min MP-mode, ~2min SSO-mode in docker; both well under the 180s test budget. |
3 weeks ago |
|
|
a75273d40f |
playwright: cover PRF login-passkey enrolment via Chromium virtual authenticator
Adds an end-to-end check that registering a PRF-enabled login passkey populates `userDecryption.webAuthnPrfOptions` in /api/sync — the wire-level prerequisite for the web vault's lock-screen "Unlock with passkey" option. Two tests, complementary: - PRF enrolment (`useForEncryption` checked) yields a non-empty array in /sync, with the wrapped-key blobs the client uses to derive the user key after the PRF assertion. - Enrolment without PRF (`useForEncryption` unchecked) leaves the array empty, pinning the emission filter's other branch. Drives the real "Turn on Log in with passkey" UI flow under Settings → Security → Master password against the bundled web vault, satisfying the WebAuthn credential creation step with a Chromium CDP virtual authenticator. The post-enrolment /sync call sniffs the bearer token from a live SPA request rather than reaching into IndexedDB, because the vault aggressively caches sync state and won't re-fetch on demand. Runs as a dedicated `account-lifecycle` project in `playwright.config.ts` (Chromium, `en` locale, SQLite-volatile via `utils.startVault`). The four DB projects exclude the spec via `testIgnore`, since the rest of the suite runs Firefox and the CDP virtual-authenticator with the `hmac-secret` PRF extension is Chromium-only. Why this file isn't in `passkey.spec.ts`: - The "Log in with passkey" assertion ceremony itself runs inside a same-origin `/webauthn-connector.html` iframe; current Chromium does not satisfy navigator.credentials calls inside that iframe via CDP-injected virtual authenticators. The enrolment step (which runs WebAuthn in the main frame via a bit-dialog) IS reachable, and that's exactly the step that populates webAuthnPrfOptions. Run: npx playwright test --project=account-lifecycle Verified against bundled web-vault v2026.4.1: 2/2 passed end-to-end via the docker harness. |
3 weeks ago |