Tree:
019a1adf98
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 }
23 Commits (019a1adf98decd4f0a7d5e1010ac76cdf099a6a0)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
019a1adf98 |
webauthn: passkey-management module + login hardening + Result-typed 2FA lookups
- src/api/core/passkeys.rs: extract passkey-management endpoints into
their own module from api/core/mod.rs
- src/api/core/two_factor/webauthn.rs: add extensions field to the
PublicKeyCredentialCopy / RegisterPublicKeyCredentialCopy serde
wrappers with serde(default, alias = clientExtensionResults), and
propagate through the From impls
- src/api/core/two_factor/{authenticator,duo,email,protected_actions,yubikey}.rs:
propagate ? cascade for find_by_user_and_type's new
Result<Option<TwoFactor>, Error> signature
- src/api/core/{accounts,ciphers,mod}.rs: rotate-key PRF rewrap,
/sync webauthn_prf_options gating
- src/api/identity.rs: webauthn_login grant hardening + defensive
documentation justifying unauth-grant 503-vs-AUTH_FAILED asymmetries
- src/db/models/two_factor.rs: try_find_by_user, find_by_user_and_type
Result-ification, take_by_user_and_type, is_policy_provider*
predicates, POLICY_PROVIDER_TYPES const
- src/db/models/user.rs: try_find_by_uuid sibling, WebAuthnCredential
delete cascade in User::delete
- src/db/models/web_authn_credential.rs: model hardening
- migrations/*: schema additions for webauthn passkey credentials
- playwright/tests/passkey.spec.ts: passkey feature tests
- README.md, scss.hbs, config.rs: docs, UI hides, feature flag
|
2 weeks ago |
|
|
b5678daf8e |
webauthn: harden passkey management flows
Tightens the passkey-management endpoints against concurrent-mutation races, stale challenges, and token-replay; routes the unauthenticated- login path through atomic challenge consumption; and pins the behaviour with new test coverage. Atomic challenge consumption: - `WebAuthnLoginChallenge::take` performs a transactional SELECT+DELETE on the challenge row, replacing the previous "find then delete" pattern that could leave a stale challenge consumable across tabs. - `TwoFactor::take_by_user_and_type` does the same for the passkey-management registration / assertion challenges, replacing the prior `find_by_user_and_type` + `tf.delete` pattern at every passkey-management call site. Challenge state binding: - `passkey_management_challenge_is_fresh` enforces a TTL + clock-skew window on persisted challenge rows. - `passkey_registration_challenge_state` / `passkey_assertion_challenge_state` enforce per-request token equality, freshness, and user `security_stamp` equality before unwrapping the saved webauthn-rs state. A password change mid-ceremony invalidates the in-flight challenge; a stale tab cannot consume a fresh one. Identity.rs (`webauthn_login`): - Consume the challenge via `WebAuthnLoginChallenge::take` before any cryptographic verification, so a malformed `device_response` cannot poison subsequent grant attempts that share the same token. - AUTH_FAILED-uniformity: every pre-bind error path returns the same generic "Passkey authentication failed" rather than leaking the underlying cause (DB transient vs missing user vs malformed assertion vs verification failure). - Drop the local `AssertionResponseCopy` in favour of the shared `PublicKeyCredentialCopy` from `core::two_factor::webauthn`, so the passkey-login and 2FA-webauthn paths agree on the serde wire shape for the assertion response. Other: - `ciphers.rs` / `accounts.rs`: `/sync` gates `webAuthnPrfOptions` on `account_passkeys_allowed`; PRF rotation path tolerates a credential that was removed mid-rotation. - `vaultwarden.scss.hbs`: hide the lock-screen "Unlock with passkey" button until PRF enrolment completes so the affordance only appears when the server will actually accept it. - `playwright/tests/passkey.spec.ts`: additional coverage for the hardened flows. |
2 weeks ago |
|
|
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 |
|
|
58bbc2d533 |
playwright: passkey UI flow tests + SSO_ONLY denial coverage
UI-driven Playwright coverage for the passkey login + management flows: enrolment via the Account page, PRF unlock toggle, login via the "Log in with passkey" affordance, removal, and post-removal fall-through to the master-password unlock path. The UI suite complements the request-level coverage in `passkey.spec.ts` by exercising the bundled web vault's connector iframe and the account-page CDP virtual-authenticator interaction. SSO_ONLY denial coverage extends the suite with two tests next to the existing webauthn-grant denial: - 'GET assertion-options (login challenge) denied with an SSO-mentioning message' — sibling to the existing webauthn-grant test, covers the unauthenticated entry point of the discoverable-login flow at `src/api/identity.rs:1250`. The SPA fetches this BEFORE invoking the WebAuthn ceremony, so the server-side gate here is what prevents an attacker from attempting passkey login even with a credential a victim has previously enrolled. - 'POST /api/webauthn/attestation-options denied with an SSO-mentioning message' — covers the deny-by-default gate on the enrolment endpoint at `src/api/core/mod.rs:308`. The handler is authenticated, so the test provisions a user via the UI under default config, sniffs the Bearer header from the SPA's post-login /api/sync, then flips `sso_enabled`/`sso_only` at runtime via `POST /admin/config` instead of restarting the vault container. The runtime toggle avoids the tmpfs wipe an env-change-driven container recreate would trigger, keeping the user, the RSA signing key, and the pre-issued token all valid for the test. |
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 |
|
|
2d59e7e631 |
playwright: implement fido2 + auto-pick 2FA provider in submitTwoFactor
`submitTwoFactor` previously threw `Not Implemented` for `kind: 'fido2'`. Implement it: the bundled web vault's connector iframe on /#/2fa auto-fires `navigator.credentials.get()` as soon as it mounts and the page transitions to /vault on its own, so the helper just waits for that URL transition (caller is responsible for keeping a virtual authenticator attached with auto-presence enabled). Add `ensure2FAProvider`: when the user has multiple 2FA providers enrolled and the current page is showing a different provider's UI than the requested one, click "Select another method" → the labeled row for the requested kind. The label table is fixed per `TwoFactor.kind` (`/Authenticator app/i` for `totp`, `/Email/i` for `mail2fa`, `/Passkey|FIDO2/i` for `fido2`), so callers don't have to know about it. No-op when the requested kind's input is already visible (single- provider case, or it was already the default). Pre-MP setup required to keep the page-default provider from auto-firing (e.g. CDP `setAutomaticPresenceSimulation(false)` for the WebAuthn-2FA default) remains the caller's responsibility — the CDP authenticator handle lives in the test spec, not in the shared 2FA helper. Also moves the post-submit `expect(page).toHaveURL(/vault/)` assertion inside `submitTwoFactor` so callers don't have to repeat it. |
3 weeks ago |
|
|
524194bbf4 |
config: advertise pm-2035-passkey-unlock feature flag
The bundled web vault gates its lock-screen "Unlock with passkey" affordance on the `pm-2035-passkey-unlock` feature flag in `/api/config`'s `featureStates`. Without it, `WebAuthnPrfUnlockService.isPrfUnlockAvailable` short-circuits to `false` and the button never renders even for users with a PRF-enabled passkey enrolled. Vaultwarden supports PRF passkey unlock end-to-end (the `userDecryption.webAuthnPrfOptions` blob in `/api/sync` feeds the client-side unwrap), so the flag must be advertised as enabled. Extracts `build_feature_states` from the `/api/config` handler so the feature-state assembly is unit-testable without `CONFIG` initialisation, and pins both the new flag and the existing `pm-19148-innovation-archive` companion via tests in `src/api/core/mod.rs` + a `/api/config` Playwright probe in `passkey.spec.ts`. |
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 |
|
|
6f2751ee79 |
Align UserDecryption response shapes with upstream Bitwarden
Upstream's `IdentityTokenResponse.UserDecryptionOptions` model carries a **singular** `WebAuthnPrfOption`, populated solely from `UserDecryptionOptionsBuilder.WithWebAuthnLoginCredential` after a successful passkey assertion. Upstream's `SyncResponseModel.UserDecryption` model carries the **plural** `WebAuthnPrfOptions` array, populated for every PRF-enabled credential the user owns. The Bitwarden client reads each at a different point in the flow: - Singular drives the immediate post-passkey-login vault decryption (the client combines it with the PRF secret from the assertion it just performed). - Plural drives the lock-screen "Unlock with passkey" option (read from disk state populated at sync time). Vaultwarden previously emitted only the singular on the webauthn-grant response and nothing on /sync, so the lock-screen option never appeared even when the user had a PRF-enabled credential. This commit: - Adds `webAuthnPrfOptions` (plural array) to `/sync` via a new `build_webauthn_prf_options(&[WebAuthnCredential])` helper. - Extracts the singular emission into `build_webauthn_login_prf_option` and a wrapper `build_webauthn_login_response` that applies it to the output of `authenticated_response`. The wrapper makes the call site unit-testable: a regression that removes the call site trips the dead-code lint at build time (the helper's only caller). - Pins both helpers and the response-augmentation wrapper with unit tests covering shape (field names + values) and behaviour (idempotency, no-op for non-Enabled credentials, untouched payload for unsupported credentials). - Adds a playwright integration test that pins the wire-level response shapes for password-grant /connect/token and /sync against the upstream contract. The login responses for `password` and `client_credentials` grants no longer emit any PRF field (matching upstream — the singular is only populated on webauthn grant, and the plural doesn't exist on that response model at all). |
2 weeks ago |
|
|
c489186e4f |
Add playwright tests for passkey login
`passkey.spec.ts` exercises the unauthenticated and authorization-required
surface that doesn't need a virtual authenticator:
- `GET /identity/accounts/webauthn/assertion-options` returns the documented
shape (`Content-Type: application/json`, `options` + `userVerification`
+ a non-empty `token`). The token format is intentionally not pinned:
Vaultwarden mints a UUID, upstream Bitwarden mints a
`DataProtectorTokenable`; both are opaque from the client's view.
- Five back-to-back calls return five distinct tokens AND five distinct
challenges — a refactor that re-used either would let an attacker replay.
- The `grant_type=webauthn` token endpoint returns a generic auth-failed
message for an unknown token, a malformed deviceresponse, and a
structurally-valid but unsignable assertion. The regex accepts both
Vaultwarden's "Passkey authentication failed." and upstream Bitwarden's
"Invalid credential." — the security contract is the byte-equality
between failure branches (oracle defense), not the surface text.
- Every webauthn-management endpoint (`GET /api/webauthn`, attestation /
assertion options, finish, update, delete) rejects anonymous callers
AND callers with a garbage Bearer with 401.
- Missing-required-field requests to the webauthn grant are rejected
before the handler body runs (token / deviceresponse / client_id /
scope). The specific rejection text differs between projects so we
only assert that the response is an error.
- The web vault renders the "Log in with passkey" entry point.
Security-gate coverage covers forged user-handle attempts against
disabled and unverified accounts plus the SSO_ONLY webauthn grant gate.
The forged-handle cases create real target users but submit
intentionally unsignable assertions, asserting the response stays
byte-equal to the unknown-user baseline before WebAuthn verification
succeeds. Adds the docker-compose environment passthrough needed for
per-describe vault restarts with SIGNUPS_VERIFY and SSO_ONLY test
configs.
README: document the Playwright image's bake-in behavior — the
Dockerfile copies `tests/` in at build time, so local edits to
`*.spec.ts` are not picked up by `docker compose run Playwright`
until the image is rebuilt. Verified empirically: an in-place rename
of a `test('…')` title is invisible to `run` until `build Playwright`
is invoked, and absolute paths through the mounted `..:/project`
volume don't override Playwright's config-derived `testDir`. Add a
short note next to the existing "force a rebuild" command.
The spec is Firefox-compatible and runs unmodified under the existing
playwright project matrix.
|
3 weeks ago |
|
|
88ab51443a |
playwright: centralize 2FA challenge handling
2FA challenge submission was inlined across the suite: `login.spec.ts`,
`login.smtp.spec.ts` and `setups/sso.ts:logUser` each asserted the
"Verify your Identity" heading, generated/retrieved the factor-specific
verification code, then clicked Continue. The only piece that varied was
the source of the code (TOTP generator vs. email-OTP retrieved from
maildev). When the web vault changes the heading copy, the code-input
label, or the Continue-button name, every duplicate has to be hunted
down separately.
Centralises the challenge flow in `setups/2fa.ts` behind a `TwoFactor`
discriminated union and a `submitTwoFactor` dispatcher:
type TwoFactor =
| { kind: 'totp', totp: OTPAuth.TOTP }
| { kind: 'mail2fa', mailBuffer: MailBuffer }
| { kind: 'fido2' };
Each variant carries exactly the state it needs. `submitTwoFactor`
asserts the heading then `switch`es on `kind`: TOTP fills the
next-period-boundary code (avoiding period-boundary expiry races near a
30-second tick) and mail2fa retrieves from the buffer; both then click
Continue. The `fido2` variant is declared so the union covers every
2FA provider the bundled web vault exposes (the provider row labelled
"FIDO2 WebAuthn" in en_GB / "Passkey" in en); no test currently drives
the webauthn-connector iframe / CDP virtual-authenticator handshake, so
the case throws `Not Implemented` rather than silently no-op'ing.
`setups/user.ts:logUser` and `setups/sso.ts:logUser` now share an
options bag `{ mailBuffer?, twoFactor? }` (the SSO variant's existing
positional `totp` parameter is replaced) and delegate to
`submitTwoFactor` when `twoFactor` is set, keeping the two login
helpers in lock-step.
Refactored consumers:
- `login.spec.ts:Authenticator 2fa` -> the inline 2FA block collapses to
`await logUser(test, page, user, { twoFactor: { kind: 'totp', totp } })`.
- `login.smtp.spec.ts:2fa` -> ditto with `{ kind: 'mail2fa', mailBuffer }`.
- `sso_login.spec.ts:SSO login with TOTP 2fa` -> same `totp` variant.
- `sso_login.smtp.spec.ts:Log and disable` -> same `mail2fa` variant.
- Positional-mailBuffer callers (`login.smtp.spec.ts:Login`,
`organization.smtp.spec.ts:Confirm invited user` and
`Organization is visible`) switch to options-bag
`logUser(..., { mailBuffer })`.
login.spec.ts loses no-longer-needed `expect` / `OTPAuth` imports; the
factor-specific timestamp logic moves into `submitTwoFactor`.
No behaviour change in any test; only structure.
|
3 weeks ago |
|
|
ad582b460a |
playwright: centralize web-vault selectors into shared setup helpers
The suite had several locator patterns scattered across helpers and specs;
changes to the bundled web vault would require touching N call sites for
each. This commit funnels them into shared helpers so the next web-vault
update only touches one place per pattern.
`setups/user.ts` additions:
- `openAvatarMenu(page, userName)` — header avatar menu open, anchored on
the user's display name (`{ exact: true }` to avoid cipher-name
substring matches).
- `fillNewMasterPassword(page, password)` — registration / MP-change form's
`newPassword` + `newPasswordConfirm` `formcontrolname` inputs (the three
labels containing "Master password" make label-based locators ambiguous).
- `submitMasterPasswordVerification(page, mp)` — the in-dialog
`app-user-verification` master-password gate (the `<input id="masterPassword">`
inside any sensitive-operation dialog: 2FA enrol/disable, passkey
enrol/remove, key rotation, KDF change). Presses Enter on the input to
avoid the multi-`Continue`-button ambiguity that the current bundled
vault renders.
- `createAccount` switched to `fillNewMasterPassword`.
`setups/2fa.ts` additions + refactor:
- `gotoTwoStepLogin(page, userName)` — Settings → Security → Two-step login
navigation, used by every 2FA enrol/disable function.
- `clickTwoFactorProviderManage(page, providerLabel)` — `bit-item` provider
row → Manage button. Accepts string or RegExp for the row's hasText.
- `activateTOTP` / `disableTOTP` / `activateEmail` / `disableEmail` all
rewritten to use the new helpers, removing the inline duplication.
`setups/sso.ts:logNewUser` — uses `fillNewMasterPassword`.
`organization.smtp.spec.ts` and `sso_organization.smtp.spec.ts` invited-with-
new-account flows — use `fillNewMasterPassword`.
Incidental fixes spotted while refactoring:
- `disableTOTP` / `disableEmail` previously had `getByRole('button',
{ name: 'Test' })` hardcoded for the avatar menu — broke for any user
not named "Test". Now `openAvatarMenu(page, user.name)`, parameterised.
- `activateTOTP` declared its return type as `: OTPAuth.TOTP` (an async
function actually returns `Promise<OTPAuth.TOTP>`); `retrieveEmailCode`
similarly declared `: string` instead of `: Promise<string>`. Both
fixed.
Post-refactor scatter check (rg-confirmed):
- `formcontrolname="newPassword"` outside setups/user.ts: 0
- `input#masterPassword` outside setups/user.ts: 0
- `bit-item` provider-row pattern outside setups/2fa.ts: 0
- Avatar-menu via `name: user.name` outside setups/user.ts: 0
|
3 weeks ago |
|
|
0c009d679d |
playwright: fix invitation-accepted toast text + SSO existing-account redirect
The bundled web vault renames the org-invitation-acceptance toast from "Invitation accepted" to "Successfully accepted your invitation" in the post-MP-unlock flow used by both invited-with-new-account and invited-with-existing-account tests. Update both specs to match the new toast text. The SSO-side logNewUser flow still emits "Invitation accepted" for the SSO account-creation toast, so setups/sso.ts is left untouched. The SSO invited-with-existing-account flow also no longer auto-redirects to Keycloak — existing emails land on the email-prefilled login form and require an explicit "Use single sign-on" click. Add it before the Keycloak heading assertion. |
3 weeks ago |
|
|
9186ec245b |
playwright: fix stale org-nav selectors against bundled web vault
The product-switch links in the side nav ("Password Manager", "Admin
Console", "Members") are icon-only on the current bundled web vault —
the link element carries the accessible name but no visible text
content. `locator('a').filter({ hasText: '…' })` therefore matches
nothing, and every spec that calls into `setups/orgs.ts` (org create,
member invite, policy edit, …) times out before doing anything.
Switch to `getByRole('link', { name: '…' })` for the three navs.
"Admin Console" appears twice once an org exists (in both
`bit-nav-logo` and `navigation-product-switcher`); `.first()` picks the
visible one. The "Members" entry inside an org also moved from a
`<div>` to a `<link>`, so the `locator('div').filter(...).nth(2)`
selector is replaced with the same role-based selector. The
org-switcher row has a hover tooltip that intercepts the click on the
bundled vault, so the click is forced past the overlay.
Verified empirically: with these changes, `organization.spec.ts` (1/1)
and `sso_organization.spec.ts` (4/5; the remaining failure is an
unrelated server-side master-password-policy enforcement issue) run
green where they previously failed before any helper step.
|
3 weeks ago |
|
|
f30847d15e |
playwright: serve the test Vaultwarden over HTTPS
The bundled web vault refuses to submit registration and login requests over plain HTTP, surfacing "Insecure URL not allowed. All URLs must use HTTPS." in the UI. The Continue button is left `bit-aria-disable=true` and click handlers are no-ops, which manifests in tests as `locator.fill: timeout exceeded` deep into createAccount — diagnosed via DOM dump showing the error banner. Make the test Rocket server actually serve HTTPS: - Generate a self-signed cert in the Vaultwarden runtime image (separate RUN layer from the apt install so cert tweaks don't bust the deps layer cache). - Point `ROCKET_TLS` at the cert + key in test.env and the dev .env.template. - Switch DOMAIN to `https://localhost:${ROCKET_PORT}`. - Tell Playwright to ignore HTTPS errors on the self-signed cert (in both `playwright.config.ts` for test contexts and `global-utils.ts` for the manual context startVault uses to poll for vault readiness). Self-signed + `ignoreHTTPSErrors` is the idiomatic Playwright pattern for a local-only test target; importing a custom CA into each browser's profile would be substantially more invasive (Firefox uses NSS, Chromium has its own store) for no real-world fidelity gain. |
3 weeks ago |
|
|
c02ced432d |
playwright: fix TOTP setup flow against bundled web vault
The `activateTOTP` helper had three issues against the current
bundled web vault (v2026.4.1):
1. The master-password reprompt's Continue button click was racing
form validation: the form uses Angular's `updateOn: 'blur'` so
clicking immediately after `fill()` saw an "invalid" form and the
click was silently no-op. Submitting via `mpInput.press('Enter')`
triggers the form's `ngSubmit` directly, which validates and
submits in one go.
2. `getByLabel('Key').innerText()` was ambiguous: the same page has
a `<bit-svg aria-label="Yubico OTP security key">` providers entry
whose accessible name contains "Key" as a substring. Strict-mode
violation on Firefox. Anchor with `{ exact: true }` to pick only
the `<code aria-label="Key">` element holding the base32 secret.
3. After clicking "Turn on", the original code was effectively a
no-op (`await page.getByRole('heading', { name: 'Turned on' })`
creates a Locator without awaiting visibility), then clicked the
"Close" button which is `bit-aria-disable=true` until the dialog
finishes its transition. Chromium happens to tolerate the early
click; Firefox doesn't. Wait for `networkidle` after Turn on so
the activation request completes; drop the Close click because
the dialog auto-closes on success on this web vault.
`disableTOTP` got the same reprompt-via-Enter fix for parity.
Verified empirically: with these changes, `login.spec.ts`'s
`Authenticator 2fa` test passes on Firefox against bundled
web-vault v2026.4.1.
|
3 weeks ago |
|
|
c0589bbd74 |
playwright: fix stale Master password selectors against bundled web vault
The bundled web vault dropped the "(required)" suffix from the
master-password input's label text (it likely became a separate visual
indicator). Every test that drives a master-password reprompt — account
creation, TOTP setup, email-2FA setup, SSO master-password flows,
organization-policy management — has been failing on the current
bundled vault because `getByLabel('Master password (required)',
{ exact: true })` no longer matches anything.
Two-line change in most files: switch to `getByLabel('Master password')`,
matching the pattern that `logUser` (setups/user.ts:46) and
`login.spec.ts:40` already use successfully. Substring matching is
case-sensitive in Playwright, so the selector is unambiguous against
the confirm-field ("Confirm master password" has a lowercase 'm').
`createAccount` step 2 (setups/user.ts) and `logNewUser` step 3
(setups/sso.ts) are special: the registration / "Join organisation"
form has three labels matching "Master password" as a case-insensitive
substring ("Master password\n(required)", "Confirm master password\n
(required)" which matches because Playwright's substring match is
case-insensitive in practice, and "Master password hint" which also
matches). Anchor those two fields by their stable `formcontrolname`
attribute (`newPassword` / `newPasswordConfirm`) instead of label text.
Verified empirically: with these changes, both `login.spec.ts` (3/3:
Account creation, Master password login, Authenticator 2fa) and
`sso_login.spec.ts` (8/8 including SSO Account creation, SSO login,
SSO login with TOTP 2fa, Non-SSO login fallback, SSO_ONLY, no SSO)
run green against bundled web-vault v2026.4.1.
|
3 weeks ago |
|
|
0182567a62
|
Playwright against abitrary web-vault (#6380)
* Playwright improvements * Playwright fix for the extension setup --------- Co-authored-by: Timshel <timshel@users.noreply.github.com> |
7 months ago |
|
|
7c597e88f9
|
[Playwright] Improvements around node (#6321)
* Playwright node improvements * Upgrade Keycloak compose to trixie |
8 months ago |
|
|
de808c5ad9
|
Fix Playwright docker (#6206)
|
10 months ago |
|
|
7f386d38ae
|
Fix Playwright test conf and update deps (#6176)
|
10 months ago |
|
|
5ea0779d6b
|
a little cleanup after SSO merge (#6153)
* fix some typos * rename scss variable to sso_enabled * refactor is_mobile to device * also mask sensitive sso config options |
10 months ago |
|
|
cff6c2b3af
|
SSO using OpenID Connect (#3899)
* Add SSO functionality using OpenID Connect Co-authored-by: Pablo Ovelleiro Corral <mail@pablo.tools> Co-authored-by: Stuart Heap <sheap13@gmail.com> Co-authored-by: Alex Moore <skiepp@my-dockerfarm.cloud> Co-authored-by: Brian Munro <brian.alexander.munro@gmail.com> Co-authored-by: Jacques B. <timshel@github.com> * Improvements and error handling * Stop rolling device token * Add playwright tests * Activate PKCE by default * Ensure result order when searching for sso_user * add SSO_ALLOW_UNKNOWN_EMAIL_VERIFICATION * Toggle SSO button in scss * Base64 encode state before sending it to providers * Prevent disabled User from SSO login * Review fixes * Remove unused UserOrganization.invited_by_email * Split SsoUser::find_by_identifier_or_email * api::Accounts::verify_password add the policy even if it's ignored * Disable signups if SSO_ONLY is activated * Add verifiedDate to organizations::get_org_domain_sso_details * Review fixes * Remove OrganizationId guard from get_master_password_policy * Add wrapper type OIDCCode OIDCState OIDCIdentifier * Membership::confirm_user_invitations fix and tests * Allow set-password only if account is unitialized * Review fixes * Prevent accepting another user invitation * Log password change event on SSO account creation * Unify master password policy resolution * Upgrade openidconnect to 4.0.0 * Revert "Remove unused UserOrganization.invited_by_email" This reverts commit 548e19995e141314af98a10d170ea7371f02fab4. * Process org enrollment in accounts::post_set_password * Improve tests * Pass the claim invited_by_email in case it was not in db * Add Slack configuration hints * Fix playwright tests * Skip broken tests * Add sso identifier in admin user panel * Remove duplicate expiration check, add a log * Augment mobile refresh_token validity * Rauthy configuration hints * Fix playwright tests * Playwright upgrade and conf improvement * Playwright tests improvements * 2FA email and device creation change * Fix and improve Playwright tests * Minor improvements * Fix enforceOnLogin org policies * Run playwright sso tests against correct db * PKCE should now work with Zitadel * Playwright upgrade maildev to use MailBuffer.expect * Upgrades playwright tests deps * Check email_verified in id_token and user_info * Add sso verified endpoint for v2025.6.0 * Fix playwright tests * Create a separate sso_client * Upgrade openidconnect to 4.0.1 * Server settings for login fields toggle * Use only css for login fields * Fix playwright test * Review fix * More review fix * Perform same checks when setting kdf --------- Co-authored-by: Felix Eckhofer <felix@eckhofer.com> Co-authored-by: Pablo Ovelleiro Corral <mail@pablo.tools> Co-authored-by: Stuart Heap <sheap13@gmail.com> Co-authored-by: Alex Moore <skiepp@my-dockerfarm.cloud> Co-authored-by: Brian Munro <brian.alexander.munro@gmail.com> Co-authored-by: Jacques B. <timshel@github.com> Co-authored-by: Timshel <timshel@480s> |
10 months ago |