fix: add support for updating attachment keys and filenames during key rotation
This commit is contained in:
parent
84067f41f0
commit
58847e9788
3 changed files with 34 additions and 1 deletions
|
|
@ -754,6 +754,7 @@ pub async fn post_rotatekey(
|
||||||
// Only update personal ciphers (organization_id is None)
|
// Only update personal ciphers (organization_id is None)
|
||||||
let mut cipher_statements: Vec<D1PreparedStatement> =
|
let mut cipher_statements: Vec<D1PreparedStatement> =
|
||||||
Vec::with_capacity(personal_ciphers.len());
|
Vec::with_capacity(personal_ciphers.len());
|
||||||
|
let mut attachment_statements: Vec<D1PreparedStatement> = Vec::new();
|
||||||
for cipher in personal_ciphers {
|
for cipher in personal_ciphers {
|
||||||
// id is guaranteed to exist (validated above)
|
// id is guaranteed to exist (validated above)
|
||||||
let cipher_id = cipher.id.as_ref().unwrap();
|
let cipher_id = cipher.id.as_ref().unwrap();
|
||||||
|
|
@ -778,8 +779,27 @@ pub async fn post_rotatekey(
|
||||||
)
|
)
|
||||||
.map_err(|_| AppError::Database)?;
|
.map_err(|_| AppError::Database)?;
|
||||||
cipher_statements.push(stmt);
|
cipher_statements.push(stmt);
|
||||||
|
|
||||||
|
// Update attachments key and encrypted filename when rotating.
|
||||||
|
// The Bitwarden clients send `attachments2` only during key rotation.
|
||||||
|
if let Some(attachments2) = &cipher.attachments2 {
|
||||||
|
for (attachment_id, attachment) in attachments2 {
|
||||||
|
let stmt = query!(
|
||||||
|
&db,
|
||||||
|
"UPDATE attachments SET file_name = ?1, akey = ?2, updated_at = ?3 WHERE id = ?4 AND cipher_id = ?5",
|
||||||
|
attachment.file_name,
|
||||||
|
attachment.key,
|
||||||
|
now,
|
||||||
|
attachment_id,
|
||||||
|
cipher_id
|
||||||
|
)
|
||||||
|
.map_err(|_| AppError::Database)?;
|
||||||
|
attachment_statements.push(stmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
db::execute_in_batches(&db, cipher_statements, batch_size).await?;
|
db::execute_in_batches(&db, cipher_statements, batch_size).await?;
|
||||||
|
db::execute_in_batches(&db, attachment_statements, batch_size).await?;
|
||||||
|
|
||||||
// Generate new salt and hash the new password
|
// Generate new salt and hash the new password
|
||||||
let new_salt = generate_salt()?;
|
let new_salt = generate_salt()?;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use serde_json::{json, Map, Value};
|
use serde_json::{json, Map, Value};
|
||||||
|
|
||||||
|
|
@ -317,12 +319,23 @@ pub struct CipherRequestData {
|
||||||
pub favorite: Option<bool>,
|
pub favorite: Option<bool>,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub type_fields: CipherTypeFields,
|
pub type_fields: CipherTypeFields,
|
||||||
|
/// Used during key rotation to update attachment keys and encrypted filenames.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub attachments2: Option<HashMap<String, Attachments2Data>>,
|
||||||
// The revision datetime (in ISO 8601 format) of the client's local copy
|
// The revision datetime (in ISO 8601 format) of the client's local copy
|
||||||
// Used to prevent updating a cipher when client doesn't have the latest version
|
// Used to prevent updating a cipher when client doesn't have the latest version
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub last_known_revision_date: Option<String>,
|
pub last_known_revision_date: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attachment metadata sent by clients during key rotation.
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Attachments2Data {
|
||||||
|
pub file_name: String,
|
||||||
|
pub key: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents the full request payload for creating a cipher with collections.
|
/// Represents the full request payload for creating a cipher with collections.
|
||||||
/// Supports both camelCase and PascalCase for compatibility with different clients.
|
/// Supports both camelCase and PascalCase for compatibility with different clients.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ pub struct User {
|
||||||
pub master_password_hash: String,
|
pub master_password_hash: String,
|
||||||
pub master_password_hint: Option<String>,
|
pub master_password_hint: Option<String>,
|
||||||
pub password_salt: Option<String>, // Salt for server-side PBKDF2 (NULL for legacy users)
|
pub password_salt: Option<String>, // Salt for server-side PBKDF2 (NULL for legacy users)
|
||||||
pub password_iterations: i32, // Server-side PBKDF2 iterations used for master_password_hash
|
pub password_iterations: i32, // Server-side PBKDF2 iterations used for master_password_hash
|
||||||
pub key: String,
|
pub key: String,
|
||||||
pub private_key: String,
|
pub private_key: String,
|
||||||
pub public_key: String,
|
pub public_key: String,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue