dani-garcia/vaultwarden · error

Error creating organization API Key

Error message

Error creating organization API Key

What it means

A Rust .expect() panic raised when inserting a brand-new OrganizationApiKey row fails: the else-branch at organizations.rs:3221-3225 runs when no organization_api_key row exists yet for the org (first-time API key retrieval), generates a key, and .save() returns Err. Reached via POST /organizations/<org_id>/api-key (rotate=false) after admin auth and password/OTP validation succeed. The panic surfaces to the client as an HTTP 500 with no useful body.

Source

Thrown at src/api/core/organizations.rs:3224

        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(
    org_id: OrganizationId,
    data: Json<PasswordOrOtpData>,
    headers: AdminHeaders,
    conn: DbConn,
) -> JsonResult {
    api_key(&org_id, data, false, headers, conn).await

View on GitHub (pinned to 0cefa4cca7)

Solutions

  1. Read the server log for the underlying diesel error — 'no such table organization_api_key' means migrations are missing; 'readonly database' means file/mount permissions; a constraint error means a race or duplicate row.
  2. Run/verify database migrations for the deployed version so the organization_api_key table and its constraints exist and match the model.
  3. Fix storage access: ensure the data directory and SQLite file are writable by the service user (chmod/chown, correct Docker volume mount), or confirm the external DB grants INSERT.
  4. Convert the .expect() into error propagation so first-time key creation failures return a proper error response instead of a panic, and consider handling the unique-race by re-fetching the row on conflict.

Example fix

// before
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

// after (propagate as an API error instead of panicking)
let new_org_api_key = OrganizationApiKey::new(org_id.clone(), api_key);
new_org_api_key.save(&conn).await.map_err(|e| {
    error!("Error creating organization API key for {}: {:#?}", org_id, e);
    Error::new("Error creating organization API key", "Failed to save the new API key")
})?;
new_org_api_key
Defensive patterns

Strategy: validation

Validate before calling

-- Pre-check that first-time key creation can succeed
-- 1) table exists and is writable
SELECT count(*) FROM organization_api_key;
-- 2) no pre-existing row for this org (else the rotate path applies)
SELECT * FROM organization_api_key WHERE org_uuid = '<org_id>';
-- 3) (SQLite) file and directory writable by the service user
--    ls -l <data_dir>/db.sqlite3 && test -w <data_dir>

Try / catch

// Recommended: convert the panic into a returned error; on a unique-constraint
// race (two first-time requests), re-fetch the existing row instead of failing.
if let Err(e) = new_org_api_key.save(&conn).await {
    if let Some(existing) = OrganizationApiKey::find_by_org_uuid(org_id, &conn).await {
        return Ok(Json(json!({ "apiKey": existing.api_key, "revisionDate": crate::util::format_date(&existing.revision_date), "object": "apiKey" })));
    }
    return Err(Error::new("Error creating organization API key", "Database write failed"));
}

Prevention

When it happens

Trigger: An org admin calls POST /organizations/<org_id>/api-key with valid credentials for an organization that has never generated an API key (find_by_org_uuid returns None), and the INSERT fails: missing organization_api_key table (migrations never run), unique-constraint violation on org_uuid from a concurrent first-time request racing to insert, DB connection loss, or a read-only/full disk.

Common situations: Upgraded instance where migrations were skipped so the table/columns do not exist; SQLite data directory mounted read-only or with wrong ownership in Docker; two clients requesting the key simultaneously on first generation; Postgres/MySQL out of disk or with revoked INSERT privileges; schema drift between the running binary and the database.

Related errors


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