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

  1. 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.
  2. 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.
  3. Ensure database migrations have been run for the current version so the organization_api_key schema matches the model.
  4. 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

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


AI-assisted analysis of dani-garcia/vaultwarden@0cefa4cca7 (2026-08-16). Data as JSON: /api/errors/f32b31a7fda6a77f. Report an issue: GitHub.