dani-garcia/vaultwarden · error
Error rotating organization API Key
Error message
Error rotating organization API Key
What it means
A Rust .expect() panic raised when OrganizationApiKey::save returns Err during key rotation: the UPDATE write to the organization_api_key table failed at the database level. It sits in the api_key handler (organizations.rs:3198) behind POST /organizations/<org_id>/rotate-api-key, after AdminHeaders auth and PasswordOrOtpData validation (data.validate at organizations.rs:3212) have already passed. Because Rocket catches the panic per-request, the client typically gets a 500 instead of a structured error, and the in-memory key has already been regenerated while the DB row may still hold the old key.
Source
Thrown at src/api/core/organizations.rs:3218
data: Json<PasswordOrOtpData>,
rotate: bool,
headers: AdminHeaders,
conn: DbConn,
) -> JsonResult {
if org_id != &headers.org_id {
err!("Organization not found", "Organization id's do not match");
}
let data: PasswordOrOtpData = data.into_inner();
let user = headers.user;
// Validate the admin users password/otp
data.validate(&user, true, &conn).await?;
let org_api_key = if let Some(mut org_api_key) = OrganizationApiKey::find_by_org_uuid(org_id, &conn).await {
if rotate {
org_api_key.api_key = crate::crypto::generate_api_key();
org_api_key.revision_date = chrono::Utc::now().naive_utc();
org_api_key.save(&conn).await.expect("Error rotating organization API Key");
}
org_api_key
} else {
let api_key = crate::crypto::generate_api_key();
let new_org_api_key = OrganizationApiKey::new(org_id.clone(), api_key);
new_org_api_key.save(&conn).await.expect("Error creating organization API Key");
new_org_api_key
};
Ok(Json(json!({
"apiKey": org_api_key.api_key,
"revisionDate": crate::util::format_date(&org_api_key.revision_date),
"object": "apiKey",
})))
}
#[post("/organizations/<org_id>/api-key", data = "<data>")]
async fn post_api_key(View on GitHub (pinned to 0cefa4cca7)
Solutions
- Check the server logs for the underlying diesel QueryResult error (connection refused, constraint name, 'attempt to write a readonly database') — that message names the real cause.
- Verify DB health and permissions: for SQLite check write permission on the db file and its directory; for PostgreSQL/MySQL check connectivity, disk space, and that the DB user can UPDATE organization_api_key.
- Ensure database migrations have been run for the current version so the organization_api_key schema matches the model.
- Replace .expect() with proper error propagation (return a 500 JsonResult via the project's error handling) so clients get a response and the server keeps serving other requests.
Example fix
// before
org_api_key.save(&conn).await.expect("Error rotating organization API Key");
// after (propagate as an API error instead of panicking)
org_api_key.save(&conn).await.map_err(|e| {
error!("Error rotating organization API key for {}: {:#?}", org_id, e);
Error::new("Error rotating organization API key", "Failed to save the rotated API key")
})?; Defensive patterns
Strategy: validation
Validate before calling
-- Confirm the row is writable and the schema is current before rotating SELECT org_uuid, revision_date FROM organization_api_key WHERE org_uuid = '<org_id>'; -- then verify the app user can UPDATE it (PostgreSQL example) SELECT has_table_privilege(current_user, 'organization_api_key', 'UPDATE');
Try / catch
// Recommended: propagate the error as a handled API error instead of expect()
org_api_key
.save(&conn)
.await
.map_err(|e| {
error!("Error rotating organization API key for {}: {:#?}", org_id, e);
Error::new("Error rotating organization API key", "Database write failed")
})?; Prevention
- Run database migrations as part of every deployment so schema always matches the binary.
- For SQLite, mount the data directory read-write and verify file ownership before starting the container.
- Take a DB backup before bulk key-rotation operations so a failed UPDATE is recoverable.
- Watch server logs for diesel errors after each rotation call instead of relying on the client 500.
When it happens
Trigger: An org admin calls POST /organizations/<org_id>/rotate-api-key with valid password/OTP, an organization_api_key row already exists for that org (the find_by_org_uuid Some branch), rotate=true — and the subsequent UPDATE fails: database down/connection dropped, table locked, constraint violation, read-only DB file, or schema drift after a skipped migration.
Common situations: SQLite file permissions or a read-only mount (common in Docker when the data dir is mis-mounted); PostgreSQL/MySQL restarted mid-request; migrations not applied so column types/constraints mismatch the model; unique-constraint collisions on org_uuid; disk full.
Related errors
- Error saving API key
- Error creating organization API Key
- Error saving attachment
- Error updating attachment
- Grantee user should exist but does not!
AI-assisted analysis of dani-garcia/vaultwarden@0cefa4cca7 (2026-08-16).
Data as JSON: /api/errors/f32b31a7fda6a77f.
Report an issue: GitHub.