Upstream #7656 changed log_event to take an EventType instead of a raw
i32. Do the same for its Public API sibling, so the callers pass the enum
and the cast lives in one place.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When `archiveDate` is set to `null` it should unarchive it for that
specific user, which is what Bitwarden does.
This should fix this by checking and validating if it is `null`
Fixes#7581
Signed-off-by: BlackDex <black.dex@gmail.com>
* storage: route OpenDAL through HTTP client
OpenDAL 0.58 requires applications to provide an HTTP transport. Its
default installer creates a standalone client, bypassing Vaultwarden
DNS, redirect, proxy, timeout, and request configuration.
Build the client through the internal HTTP interface and inject it into
OpenDAL's public reqwest transport.
* http: honor block setting on redirects
Clients can disable host blocking for administrator-configured private
services. DNS resolution honors this setting, but the redirect policy
still performs config-backed host checks.
Capture the setting in the redirect policy and skip those checks when
blocking is disabled. This also avoids re-entering CONFIG when a remote
configuration request is redirected during startup.
* http: make DNS setup bootstrap-safe
Remote configuration can require an HTTP client while CONFIG is still
initializing. Building the DNS resolver currently reads
CONFIG.dns_prefer_ipv6(), so loading an S3-backed config can deadlock.
Build one resolver without consulting CONFIG. Order addresses for each
lookup using the merged setting when available, falling back to the
environment and then IPv4-first during bootstrap.
* aws: use internal HTTP client
The AWS SDK connector builds a raw reqwest client, bypassing
Vaultwarden TLS, DNS, redirect, proxy, timeout, and request setup.
Construct it through the internal HTTP client interface and retain the
standard ten-second request deadline. Permit private AWS metadata and
service endpoints by disabling non-global IP blocking.
Preserve timeout errors when adapting reqwest failures to the AWS SDK so
the runtime receives the correct connector error category.
* Added comment for the prefer IPv function
Signed-off-by: BlackDex <black.dex@gmail.com>
---------
Signed-off-by: BlackDex <black.dex@gmail.com>
Co-authored-by: BlackDex <black.dex@gmail.com>
The three "Username or password is incorrect" errors in
send_email_login() (email.rs) don't log the client IP or submitted
identifier, unlike the equivalent wrong-password error in
password_login() (identity.rs), which logs both via
format!("IP: {}. Username: {username}.", ip.ip).
This makes the two code paths inconsistent for the same underlying
error, and means log-based tooling that keys on the identity.rs
error's "IP: x.x.x.x" pattern can't do the same for this endpoint.
Bring email.rs's three call sites in line with identity.rs's existing
format. The two email-present branches log IP+Username (the email
submitted); the device-identifier-only branch (SSO path, no email in
scope) logs IP+Device instead of fabricating a username.
Verified: cargo build/test/clippy/fmt all pass with the sqlite feature
(matching one leg of this repo's own CI matrix), including the two
existing unit tests in this file.
Account recovery requires SMTP. When mail is off, treat the organization
reset-password auto-enroll policy as inactive so invite/accept flows are
not forced to supply a reset-password key.
Fixes#7459
`src/api/core/ciphers.rs:170` comment said "similar to the the
userDecryptionOptions" -> "similar to the userDecryptionOptions".
Comment-only.
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
The member permissions object could not be written back. The read shape emits
permissions null for anyone who is not a manage-all member, but the write models
declared a plain map with serde(default), which only covers a missing field: an
explicit null failed to deserialize and the request was rejected before the
handler ran. Reading a member and sending it straight back therefore failed for
every member except the manage-all case the previous test covered. The field is
optional now, and the smoke test round-trips a plain member as well.
Reinvite was the one member write that could still reach an owner, and it can
change an owner from invited to accepted, so it takes the same guard as the rest.
Group updates log before mutating, so a failure part way through the collection
associations cannot leave an unaudited change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The write endpoints derive the access_all flag from the raw type plus the three
collection permissions, but the member serializer emitted neither the custom
role nor the permissions object. A client that read a manage-all member and
wrote it back unchanged therefore dropped that member from every collection,
because the information needed to round-trip was not in the response.
The member object now reports the custom role and its permissions the same way
the internal serializers do, which is also the shape upstream uses: Permissions
is part of the shared member model and is returned on reads as well as accepted
on writes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adversarial review of the new endpoints turned up several defects.
Ownership is now out of reach of a Public API client. The internal endpoints
only let an Owner grant, change or remove Owner, and that check cannot be
applied here because there is no user behind the request. Since the
organization API key can be created by an Admin, a client could previously
promote itself to Owner and take over the organization. Granting the Owner role
and acting on an existing owner are both refused now, which subsumes the
narrower last confirmed owner guard.
An omitted groups list on a member update no longer unassigns every group.
Upstream leaves group access untouched when the field is absent and only
collections reset on omission, so groups is optional now.
An omitted accessAll on a group update no longer clears the flag, and an
omitted externalId no longer clears the directory matching key that
/public/organization/import relies on to match members and groups.
Revoked members are reported with the upstream status of -1 instead of the
internal offset encoding, matching every other serializer in the codebase.
Restore refuses a member who is not revoked, rather than saving nothing and
logging a restore that did not happen, and group creation is logged before its
collection associations so a failure cannot leave an unaudited group.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds the write side of the organization Public API for members and groups, so
an organization API client can make incremental changes instead of pushing a
full directory snapshot through /public/organization/import, which revokes any
member missing from the payload when overwriteExisting is set.
Members: create (invite), update, delete, replace group ids, reinvite, revoke
and restore. Groups: create, update, delete and replace member ids.
Routes and request models follow the upstream Bitwarden Public API controllers.
The handlers mirror the equivalent internal endpoints in organizations.rs but
are guarded by PublicToken instead of AdminHeaders. A PublicToken carries only
an organization, so the per-actor permission checks do not apply, while the
guards protecting organization integrity are kept: the last confirmed owner
cannot be demoted, revoked or deleted, org policies are still enforced on
member changes, and group endpoints still require group support to be enabled.
Public API writes have no acting user or device, so log_event_impl now takes
both as optional and a new log_public_event records these events with a null
actingUserId rather than leaving them out of the organization event log.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The public organization API previously exposed only the write-side
"/public/organization/import" endpoint. This adds the corresponding
read endpoints so an organization-scoped API client can read back the
members, groups, and collections it manages, along with their access
associations:
- GET /public/members and /public/members/<id> (with collection grants)
- GET /public/members/<id>/group-ids
- GET /public/groups and /public/groups/<id> (with collection grants)
- GET /public/groups/<id>/member-ids
- GET /public/collections and /public/collections/<id> (with group grants)
All handlers reuse the existing PublicToken guard, so they are
authorized by the same organization API key as the import endpoint, and
every handler is scoped to the token's organization: a resource id that
belongs to another organization returns 404 rather than leaking data.
Collection responses intentionally omit the end-to-end encrypted name
and key collections by id and externalId only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Include user email in successful login logs
* Modified disable account log to display Email instead of Display Name
---------
Co-authored-by: Alejandro Olmos <aolmos@trevenque.es>
* Update GHA and pre-commit
Signed-off-by: BlackDex <black.dex@gmail.com>
* Update admin diagnostics
Added a check if the templates are overridden and return which specific folder, `admin`, `email` or `scss`.
This way we could more quickly point users to possible outdated templates which they are using.
Also updated the Support String to use some emojis so we should be able to quicker see if there is something wrong.
Just checking `true` or `false` could be difficult sometimes, and sometimes what we had as `false` wasn't bad either.
Also adjusted the eslint comments so it will work with the latest version of eslint.
Signed-off-by: BlackDex <black.dex@gmail.com>
* Fix updating collections for a cipher
The newer clients expect a `cipherDetails` response on the `collections-admin` endpoints.
Without it, the client will cause an error and stops handling the update correctly.
This will fix this by returning the cipher json.
Fixes#7545Fixes#7546
Signed-off-by: BlackDex <black.dex@gmail.com>
* Cache CSS file in a different way
Currently we set a cache ttl of 24 hours, and users need to do a force refresh if there is anything changed to the CSS file.
In the past we have had several issue reported which were related to a still cached CSS file.
This commit will change the caching and also cache the generated CSS file in memory.
Instead of letting the browser cache it for 24 hours we generate an ETag, this is just a hash of the contents.
This ETag is returned by the browser during a request, and we can match this, and if so, just return a `304` `Not Modified`.
If the ETag is not known, we return the new content.
This should make simple refreshes by clients get updated settings or a new version of Vaultwarden which has other CSS entries get updated instantly.
If a user does a hard refresh, we will not receive the ETag and the content will be served.
The same goes if someone has the `reload_templates` feature enabled, since then we should not cache anyway.
If someone adjust settings via the `/admin` interface, the cache will be invalidated and a new CSS will be generated.
Signed-off-by: BlackDex <black.dex@gmail.com>
* Fix showing events for a specific user
Signed-off-by: BlackDex <black.dex@gmail.com>
* Update crates and adjust code.
- Updated opendal and adjusted code where needed.
- Updated yubico_ng and adjusted code where needed.
This version now supports using an own HttpClient and it pulls in no reqwest dependency anymore.
Now it will use our own client which uses custom hickory DNS and other features.
Signed-off-by: BlackDex <black.dex@gmail.com>
* Update web-vault to v2026.7.0
Signed-off-by: BlackDex <black.dex@gmail.com>
* Fix hadolint warnings
Signed-off-by: BlackDex <black.dex@gmail.com>
---------
Signed-off-by: BlackDex <black.dex@gmail.com>
The bundled web vault (2026.6.4) requires seven query parameters in the
accept-organization URL and rejects the invite client-side when any of them is
null, showing only "Unable to accept invitation" without sending a request to
the server.
send_invite() never appended initOrganization, and appended
orgUserHasExistingUser only for users who already had an account, so every
organization invitation e-mail produced a link that could not be accepted.
Web vault 2026.4.1 (shipped with 1.36.0) read these parameters null-safely,
which is why this only appeared in 1.37.0.
Fixes#7481
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
- Updated API response to more closely match v2026.6.0+ server versions.
- Updated all the crates
- Updated Rust to v1.97.1
- Updated the web-vault to v2026.6.4
- Updated GitHub Actions
Signed-off-by: BlackDex <black.dex@gmail.com>
* Misc updates and fixes
- Updated Rust to v1.96.1
- Updated all the crates
- Updated GitHub Actions
- Updated the web-vault to v2026.6.2
- Updated Alpine to v3.24
- Fixed several clippy lints
- The `send` UUID wrappers didn't need the special namespace anymore since an updated crate, so removed this extra mod.
Signed-off-by: BlackDex <black.dex@gmail.com>
* Update MSRV to v1.94.1
Signed-off-by: BlackDex <black.dex@gmail.com>
---------
Signed-off-by: BlackDex <black.dex@gmail.com>
The /api/sync response serialized a Send hide_email field directly from
Option<bool>, so a NULL value in the sends table (the column is
Nullable<Bool> with no default) produced "hideEmail": null.
The Bitwarden Android client deserializes SyncResponseJson.Send.hideEmail
as a non-null Kotlin Boolean and aborts the entire sync with a
JsonDecodingException when it encounters null. Web, desktop and CLI
clients coerce null to false, so only accounts with at least one Send
are affected and only on Android.
Default None to false at the serialization boundary, matching the
official Bitwarden server where hideEmail is non-nullable. This needs no
database migration and fixes both legacy NULL rows and any future NULLs.
The hide_email field stays Option<bool> internally.
- Updated Rust to v1.96.0
- Updated all the crates, and adjusted code where needed
- Fixed some nightly reported clippy lints
- Updated all the GitHub actions
Signed-off-by: BlackDex <black.dex@gmail.com>
* Update to Rust 2024 Edition
Updated to the Rust 2024 Edition and added and fixed several lint checks.
This is a large change which, because of the extra lints, added some possible fixes for issues.
Signed-off-by: BlackDex <black.dex@gmail.com>
* Reorder and merge imports
Signed-off-by: BlackDex <black.dex@gmail.com>
* Remove "db_run!" macro calls where possible
Signed-off-by: BlackDex <black.dex@gmail.com>
---------
Signed-off-by: BlackDex <black.dex@gmail.com>
* Update crates and gha
Updated all the crates
Updated GitHub Actions
Signed-off-by: BlackDex <black.dex@gmail.com>
* Fix restoring revoked user
A new endpoint is used to restore a revoked user.
This commit fixes that.
Fixes#7224
Signed-off-by: BlackDex <black.dex@gmail.com>
* Update datatables
Signed-off-by: BlackDex <black.dex@gmail.com>
---------
Signed-off-by: BlackDex <black.dex@gmail.com>
* Panic on unrecognised DATABASE_URL instead of silent SQLite fallback
Previously, any DATABASE_URL that did not match the mysql: or postgresql:
prefix was silently treated as a SQLite file path. This caused data loss
in containerised environments when the URL was misconfigured (typos,
quoting issues), as vaultwarden would create an ephemeral SQLite database
that was wiped on restart.
Now, an explicit sqlite:// prefix is supported and used as the default.
Bare paths without a recognised scheme are still accepted for backwards
compatibility, but only if the database file already exists. If not, the
process panics with a clear error message.
Relates to #2835, #1910, #860.
* Use err!() instead of panic!() for unrecognised DATABASE_URL
Follow the established codebase convention where configuration
validation errors use err!() to propagate gracefully, rather than
panic!(). The error propagates through from_config() and is caught
by create_db_pool() which logs and calls exit(1).
* Use 'scheme' instead of 'prefix' in DATABASE_URL messages
Per review feedback, 'scheme' is the more accurate term for the
sqlite:// portion of the URL.