{"record":{"id":"76d302446224539c","repo":"dani-garcia/vaultwarden","slug":"error-saving-api-key","errorCode":null,"errorMessage":"Error saving API key","messagePattern":"Error saving API key","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/api/core/accounts.rs","lineNumber":1389,"sourceCode":"\n    if !user.check_valid_password(&data.master_password_hash) {\n        err!(\"Invalid password\")\n    }\n\n    kdf_upgrade(&mut user, &data.master_password_hash, &conn).await?;\n\n    Ok(Json(master_password_policy(&user, &conn).await))\n}\n\nasync fn update_api_key(data: Json<PasswordOrOtpData>, rotate: bool, headers: Headers, conn: DbConn) -> JsonResult {\n    let data: PasswordOrOtpData = data.into_inner();\n    let mut user = headers.user;\n\n    data.validate(&user, true, &conn).await?;\n\n    if rotate || user.api_key.is_none() {\n        user.api_key = Some(crypto::generate_api_key());\n        user.save(&conn).await.expect(\"Error saving API key\");\n    }\n\n    Ok(Json(json!({\n      \"apiKey\": user.api_key,\n      \"revisionDate\": format_date(&user.updated_at),\n      \"object\": \"apiKey\",\n    })))\n}\n\n#[post(\"/accounts/api-key\", data = \"<data>\")]\nasync fn post_api_key(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> JsonResult {\n    update_api_key(data, false, headers, conn).await\n}\n\n#[post(\"/accounts/rotate-api-key\", data = \"<data>\")]\nasync fn rotate_api_key(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn) -> JsonResult {\n    update_api_key(data, true, headers, conn).await\n}","sourceCodeStart":1371,"sourceCodeEnd":1407,"githubUrl":"https://github.com/dani-garcia/vaultwarden/blob/0cefa4cca7c9f2a5579dd290f78193b543818c51/src/api/core/accounts.rs#L1371-L1407","documentation":"update_api_key backs POST /accounts/api-key and POST /accounts/rotate-api-key. After PasswordOrOtpData validation succeeds, a new API key is generated and the user row saved with user.save(&conn).await.expect(\"Error saving API key\") — a database failure at this point panics the handler instead of returning a proper error response, so the client typically sees a 500/connection reset, and the key was generated in memory but not persisted.","triggerScenarios":"Calling POST /api/accounts/api-key or /api/accounts/rotate-api-key when the user-row update fails: lost DB connection, SQLite 'database is locked' under concurrency, disk full, or missing migrations.","commonSituations":"SQLite deployments with concurrent writers and no WAL; DB restarted mid-request; disk pressure; the user row concurrently modified by another session (e.g. sync).","solutions":["Check DB health and disk space, then retry the request","For SQLite: enable WAL and ensure only one instance writes the file","Run pending migrations so the schema matches the binary","Code fix: propagate the save error with ? instead of expect"],"exampleFix":"// before\nuser.save(&conn).await.expect(\"Error saving API key\");\n// after\nuser.save(&conn).await.map_err(|e| Error::new(\"Failed to save API key\", e.to_string()))?;","handlingStrategy":"try-catch","validationCode":"# Pre-flight the DB before rotating keys (sqlite example)\nsqlite3 /data/db.sqlite3 'PRAGMA quick_check;' && echo db-ok","typeGuard":null,"tryCatchPattern":"// Replace panic-prone expects with propagated errors in handlers\nmatch user.save(&conn).await {\n    Ok(_) => {}\n    Err(e) => {\n        error!(\"Failed to save API key for user {}: {e}\", user.uuid);\n        return Err(Error::new(\"Failed to save API key\", e.to_string()));\n    }\n}","preventionTips":["Monitor DB connectivity and disk space","Enable WAL for SQLite and keep write workloads single-instance","Run migrations after upgrades before serving traffic","Avoid rotating API keys during maintenance windows"],"tags":["rust","database","api-key","panic","accounts"],"backgroundTag":null,"analyzedSha":"0cefa4cca7c9f2a5579dd290f78193b543818c51","analyzedAt":"2026-08-16T07:44:56.102Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}