{"record":{"id":"f32b31a7fda6a77f","repo":"dani-garcia/vaultwarden","slug":"error-rotating-organization-api-key","errorCode":null,"errorMessage":"Error rotating organization API Key","messagePattern":"Error rotating organization API Key","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/api/core/organizations.rs","lineNumber":3218,"sourceCode":"    data: Json<PasswordOrOtpData>,\n    rotate: bool,\n    headers: AdminHeaders,\n    conn: DbConn,\n) -> JsonResult {\n    if org_id != &headers.org_id {\n        err!(\"Organization not found\", \"Organization id's do not match\");\n    }\n    let data: PasswordOrOtpData = data.into_inner();\n    let user = headers.user;\n\n    // Validate the admin users password/otp\n    data.validate(&user, true, &conn).await?;\n\n    let org_api_key = if let Some(mut org_api_key) = OrganizationApiKey::find_by_org_uuid(org_id, &conn).await {\n        if rotate {\n            org_api_key.api_key = crate::crypto::generate_api_key();\n            org_api_key.revision_date = chrono::Utc::now().naive_utc();\n            org_api_key.save(&conn).await.expect(\"Error rotating organization API Key\");\n        }\n        org_api_key\n    } else {\n        let api_key = crate::crypto::generate_api_key();\n        let new_org_api_key = OrganizationApiKey::new(org_id.clone(), api_key);\n        new_org_api_key.save(&conn).await.expect(\"Error creating organization API Key\");\n        new_org_api_key\n    };\n\n    Ok(Json(json!({\n      \"apiKey\": org_api_key.api_key,\n      \"revisionDate\": crate::util::format_date(&org_api_key.revision_date),\n      \"object\": \"apiKey\",\n    })))\n}\n\n#[post(\"/organizations/<org_id>/api-key\", data = \"<data>\")]\nasync fn post_api_key(","sourceCodeStart":3200,"sourceCodeEnd":3236,"githubUrl":"https://github.com/dani-garcia/vaultwarden/blob/0cefa4cca7c9f2a5579dd290f78193b543818c51/src/api/core/organizations.rs#L3200-L3236","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\norg_api_key.save(&conn).await.expect(\"Error rotating organization API Key\");\n\n// after (propagate as an API error instead of panicking)\norg_api_key.save(&conn).await.map_err(|e| {\n    error!(\"Error rotating organization API key for {}: {:#?}\", org_id, e);\n    Error::new(\"Error rotating organization API key\", \"Failed to save the rotated API key\")\n})?;","handlingStrategy":"validation","validationCode":"-- Confirm the row is writable and the schema is current before rotating\nSELECT org_uuid, revision_date FROM organization_api_key WHERE org_uuid = '<org_id>';\n-- then verify the app user can UPDATE it (PostgreSQL example)\nSELECT has_table_privilege(current_user, 'organization_api_key', 'UPDATE');","typeGuard":null,"tryCatchPattern":"// Recommended: propagate the error as a handled API error instead of expect()\norg_api_key\n    .save(&conn)\n    .await\n    .map_err(|e| {\n        error!(\"Error rotating organization API key for {}: {:#?}\", org_id, e);\n        Error::new(\"Error rotating organization API key\", \"Database write failed\")\n    })?;","preventionTips":["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."],"tags":["rust","panic","database","api-key","rocket","write-failure"],"backgroundTag":null,"analyzedSha":"0cefa4cca7c9f2a5579dd290f78193b543818c51","analyzedAt":"2026-08-16T07:44:56.102Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}