From 38b978ca6ebac1ceb289ffcc70f334b2da240892 Mon Sep 17 00:00:00 2001 From: qaz741wsd856 Date: Sat, 6 Dec 2025 17:52:31 +0000 Subject: [PATCH] chore: format code --- src/auth.rs | 16 +++--- src/crypto.rs | 64 ++++++++++++------------ src/error.rs | 4 +- src/handlers/accounts.rs | 33 +++++++++---- src/handlers/ciphers.rs | 13 +++-- src/handlers/config.rs | 2 +- src/handlers/identity.rs | 93 +++++++++++++++++++++-------------- src/handlers/import.rs | 9 ++-- src/handlers/twofactor.rs | 100 ++++++++++++++++++++------------------ src/models/cipher.rs | 13 +++-- src/models/mod.rs | 4 +- src/models/twofactor.rs | 15 +++--- src/models/user.rs | 4 +- src/router.rs | 20 ++++---- 14 files changed, 219 insertions(+), 171 deletions(-) diff --git a/src/auth.rs b/src/auth.rs index 00d274c..df46d85 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -25,11 +25,13 @@ pub struct Claims { /// AuthUser extractor - provides (user_id, email) tuple pub struct AuthUser(pub String, pub String); -impl FromRequestParts> for Claims -{ +impl FromRequestParts> for Claims { type Rejection = AppError; - async fn from_request_parts(parts: &mut Parts, state: &Arc) -> Result { + async fn from_request_parts( + parts: &mut Parts, + state: &Arc, + ) -> Result { // Extract the token from the authorization header let token = parts .headers @@ -55,11 +57,13 @@ impl FromRequestParts> for Claims } } -impl FromRequestParts> for AuthUser -{ +impl FromRequestParts> for AuthUser { type Rejection = AppError; - async fn from_request_parts(parts: &mut Parts, state: &Arc) -> Result { + async fn from_request_parts( + parts: &mut Parts, + state: &Arc, + ) -> Result { let claims = Claims::from_request_parts(parts, state).await?; Ok(AuthUser(claims.sub, claims.email)) } diff --git a/src/crypto.rs b/src/crypto.rs index 9731e1e..e2e7103 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -154,7 +154,7 @@ pub fn generate_totp_secret() -> Result { crypto .get_random_values_with_array_buffer_view(&secret) .map_err(|e| AppError::Crypto(format!("Failed to generate TOTP secret: {:?}", e)))?; - + Ok(base32_encode(&secret.to_vec())) } @@ -164,24 +164,26 @@ async fn hmac_sha1(key: &[u8], data: &[u8]) -> Result, AppError> { // Create algorithm object for HMAC with SHA-1 let algorithm = js_sys::Object::new(); - js_sys::Reflect::set(&algorithm, &JsValue::from_str("name"), &JsValue::from_str("HMAC")) - .map_err(|e| AppError::Crypto(format!("Failed to set algorithm name: {:?}", e)))?; - js_sys::Reflect::set(&algorithm, &JsValue::from_str("hash"), &JsValue::from_str("SHA-1")) - .map_err(|e| AppError::Crypto(format!("Failed to set hash: {:?}", e)))?; + js_sys::Reflect::set( + &algorithm, + &JsValue::from_str("name"), + &JsValue::from_str("HMAC"), + ) + .map_err(|e| AppError::Crypto(format!("Failed to set algorithm name: {:?}", e)))?; + js_sys::Reflect::set( + &algorithm, + &JsValue::from_str("hash"), + &JsValue::from_str("SHA-1"), + ) + .map_err(|e| AppError::Crypto(format!("Failed to set hash: {:?}", e)))?; // Import the key let key_array = Uint8Array::new_from_slice(key); let key_usages = js_sys::Array::of1(&JsValue::from_str("sign")); - + let crypto_key = JsFuture::from( subtle - .import_key_with_object( - "raw", - &key_array, - &algorithm, - false, - &key_usages, - ) + .import_key_with_object("raw", &key_array, &algorithm, false, &key_usages) .map_err(|e| AppError::Crypto(format!("HMAC import_key failed: {:?}", e)))?, ) .await @@ -191,11 +193,7 @@ async fn hmac_sha1(key: &[u8], data: &[u8]) -> Result, AppError> { let data_array = Uint8Array::new_from_slice(data); let signature = JsFuture::from( subtle - .sign_with_str_and_buffer_source( - "HMAC", - &CryptoKey::from(crypto_key), - &data_array, - ) + .sign_with_str_and_buffer_source("HMAC", &CryptoKey::from(crypto_key), &data_array) .map_err(|e| AppError::Crypto(format!("HMAC sign failed: {:?}", e)))?, ) .await @@ -205,42 +203,42 @@ async fn hmac_sha1(key: &[u8], data: &[u8]) -> Result, AppError> { } /// Generates a TOTP code for the given secret and time. -/// +/// /// # Arguments /// * `secret` - Base32 encoded secret key /// * `time_step` - Unix timestamp divided by 30 (or custom period) -/// +/// /// # Returns /// * 6-digit TOTP code as a string pub async fn generate_totp(secret: &str, time_step: u64) -> Result { let decoded_secret = base32_decode(secret)?; - + // Convert time_step to big-endian bytes let counter = time_step.to_be_bytes(); - + // Compute HMAC-SHA1 let hmac = hmac_sha1(&decoded_secret, &counter).await?; - + // Dynamic truncation (RFC 4226) let offset = (hmac[19] & 0x0f) as usize; let code = ((hmac[offset] & 0x7f) as u32) << 24 | (hmac[offset + 1] as u32) << 16 | (hmac[offset + 2] as u32) << 8 | (hmac[offset + 3] as u32); - + // Get 6-digit code let otp = code % 1_000_000; Ok(format!("{:06}", otp)) } /// Validates a TOTP code against a secret. -/// +/// /// # Arguments /// * `code` - The TOTP code to validate /// * `secret` - Base32 encoded secret key /// * `last_used` - The last used time step (for replay protection) /// * `allow_drift` - Whether to allow 1 time step drift (±30 seconds) -/// +/// /// # Returns /// * `Ok(time_step)` if valid, with the time step that matched /// * `Err` if invalid @@ -257,25 +255,25 @@ pub async fn validate_totp( let current_time = chrono::Utc::now().timestamp(); let current_step = current_time / 30; - + // Check drift range let steps: i64 = if allow_drift { 1 } else { 0 }; - + for step_offset in -steps..=steps { let time_step = current_step + step_offset; - + // Skip if this time step was already used (replay protection) if time_step <= last_used { continue; } - + let expected = generate_totp(secret, time_step as u64).await?; - + if constant_time_eq(code.as_bytes(), expected.as_bytes()) { return Ok(time_step); } } - + Err(AppError::Unauthorized(format!( "Invalid TOTP code. Server time: {}", chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC") @@ -289,7 +287,7 @@ pub fn generate_recovery_code() -> Result { crypto .get_random_values_with_array_buffer_view(&bytes) .map_err(|e| AppError::Crypto(format!("Failed to generate recovery code: {:?}", e)))?; - + Ok(base32_encode(&bytes.to_vec())) } diff --git a/src/error.rs b/src/error.rs index aa41958..5b61af8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -62,7 +62,9 @@ impl IntoResponse for AppError { StatusCode::INTERNAL_SERVER_ERROR, format!("Crypto error: {}", msg), ), - AppError::JsonWebToken(_) => (StatusCode::UNAUTHORIZED, "Invalid token".to_string()), + AppError::JsonWebToken(_) => { + (StatusCode::UNAUTHORIZED, "Invalid token".to_string()) + } AppError::Internal => ( StatusCode::INTERNAL_SERVER_ERROR, "Internal server error".to_string(), diff --git a/src/handlers/accounts.rs b/src/handlers/accounts.rs index 2e2bd2e..b55b9b8 100644 --- a/src/handlers/accounts.rs +++ b/src/handlers/accounts.rs @@ -1,8 +1,4 @@ -use axum::{ - extract::State, - http::HeaderMap, - Json, -}; +use axum::{extract::State, http::HeaderMap, Json}; use chrono::Utc; use glob_match::glob_match; use serde_json::{json, Value}; @@ -21,7 +17,8 @@ use crate::{ sync::Profile, user::{ AvatarData, ChangeKdfRequest, ChangePasswordRequest, MasterPasswordUnlockData, - PasswordOrOtpData, PreloginResponse, ProfileData, RegisterRequest, RotateKeyRequest, User, + PasswordOrOtpData, PreloginResponse, ProfileData, RegisterRequest, RotateKeyRequest, + User, }, }, }; @@ -148,7 +145,9 @@ pub async fn prelogin( let db = db::get_db(&env)?; - let stmt = db.prepare("SELECT kdf_type, kdf_iterations, kdf_memory, kdf_parallelism FROM users WHERE email = ?1"); + let stmt = db.prepare( + "SELECT kdf_type, kdf_iterations, kdf_memory, kdf_parallelism FROM users WHERE email = ?1", + ); let query = stmt.bind(&[email.into()])?; let row: Option = query.first(None).await.map_err(|_| AppError::Database)?; @@ -616,7 +615,11 @@ pub async fn post_rotatekey( // All personal ciphers must have an id for key rotation if personal_ciphers.len() != request_cipher_ids.len() { - log::error!("All ciphers must have an id for key rotation: {:?} != {:?}", personal_ciphers.len(), request_cipher_ids.len()); + log::error!( + "All ciphers must have an id for key rotation: {:?} != {:?}", + personal_ciphers.len(), + request_cipher_ids.len() + ); return Err(AppError::BadRequest( "All ciphers must have an id for key rotation".to_string(), )); @@ -676,7 +679,13 @@ pub async fn post_rotatekey( .unwrap_or(0) as usize; if db_cipher_count != request_cipher_ids.len() || db_folder_count != request_folder_ids.len() { - log::error!("Cipher or folder count mismatch in rotation request: {:?} != {:?} or {:?} != {:?}", db_cipher_count, request_cipher_ids.len(), db_folder_count, request_folder_ids.len()); + log::error!( + "Cipher or folder count mismatch in rotation request: {:?} != {:?} or {:?} != {:?}", + db_cipher_count, + request_cipher_ids.len(), + db_folder_count, + request_folder_ids.len() + ); return Err(AppError::BadRequest( "All existing ciphers and folders must be included in the rotation".to_string(), )); @@ -687,7 +696,11 @@ pub async fn post_rotatekey( let has_missing_folders = !validation_results[3].results::()?.is_empty(); if has_missing_ciphers || has_missing_folders { - log::error!("Missing ciphers or folders in rotation request: {:?} or {:?}", has_missing_ciphers, has_missing_folders); + log::error!( + "Missing ciphers or folders in rotation request: {:?} or {:?}", + has_missing_ciphers, + has_missing_folders + ); return Err(AppError::BadRequest( "All existing ciphers and folders must be included in the rotation".to_string(), )); diff --git a/src/handlers/ciphers.rs b/src/handlers/ciphers.rs index cb87499..e5a3a81 100644 --- a/src/handlers/ciphers.rs +++ b/src/handlers/ciphers.rs @@ -2,7 +2,7 @@ use super::get_batch_size; use axum::{extract::State, Json}; use chrono::{DateTime, Utc}; use log; // Used for warning logs on parse failures -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use serde_json::Value; use std::sync::Arc; use uuid::Uuid; @@ -11,7 +11,10 @@ use worker::{query, D1PreparedStatement, Env}; use crate::auth::Claims; use crate::db; use crate::error::AppError; -use crate::models::cipher::{Cipher, CipherData, CipherDBModel, CipherListResponse, CipherRequestData, CreateCipherRequest, MoveCipherData, PartialCipherData}; +use crate::models::cipher::{ + Cipher, CipherDBModel, CipherData, CipherListResponse, CipherRequestData, CreateCipherRequest, + MoveCipherData, PartialCipherData, +}; use crate::models::user::{PasswordOrOtpData, User}; use axum::extract::Path; @@ -153,7 +156,7 @@ pub async fn update_cipher( Err(err) => log::warn!("Error parsing lastKnownRevisionDate '{}': {}", dt, err), } } - + let cipher_data_req = payload; let cipher_data = CipherData { @@ -515,7 +518,9 @@ pub async fn restore_ciphers_bulk( let ids_json = serde_json::to_string(&ids).map_err(|_| AppError::Internal)?; let restored_ciphers: Vec = db - .prepare("SELECT * FROM ciphers WHERE user_id = ?1 AND id IN (SELECT value FROM json_each(?2))") + .prepare( + "SELECT * FROM ciphers WHERE user_id = ?1 AND id IN (SELECT value FROM json_each(?2))", + ) .bind(&[claims.sub.clone().into(), ids_json.into()])? .all() .await? diff --git a/src/handlers/config.rs b/src/handlers/config.rs index 635828a..643bdd0 100644 --- a/src/handlers/config.rs +++ b/src/handlers/config.rs @@ -31,7 +31,7 @@ pub async fn config( // feature_states.insert("unauth-ui-refresh".to_string(), true); // feature_states.insert("enable-pm-flight-recorder".to_string(), true); // feature_states.insert("mobile-error-reporting".to_string(), true); - + let disable_user_registration = get_disable_user_registration(&env); Json(json!({ diff --git a/src/handlers/identity.rs b/src/handlers/identity.rs index 1a33b69..a5cfa88 100644 --- a/src/handlers/identity.rs +++ b/src/handlers/identity.rs @@ -9,9 +9,9 @@ use worker::{query, Env}; use crate::{ auth::Claims, crypto::{ct_eq, generate_salt, hash_password_for_storage, validate_totp}, - handlers::allow_totp_drift, db, error::AppError, + handlers::allow_totp_drift, models::twofactor::{RememberTokenData, TwoFactor, TwoFactorType}, models::user::User, }; @@ -49,9 +49,17 @@ pub struct TokenRequest { // 2FA fields #[serde(rename = "twoFactorToken")] two_factor_token: Option, - #[serde(rename = "twoFactorProvider", default, deserialize_with = "deserialize_trimmed_i32")] + #[serde( + rename = "twoFactorProvider", + default, + deserialize_with = "deserialize_trimmed_i32" + )] two_factor_provider: Option, - #[serde(rename = "twoFactorRemember", default, deserialize_with = "deserialize_trimmed_i32")] + #[serde( + rename = "twoFactorRemember", + default, + deserialize_with = "deserialize_trimmed_i32" + )] two_factor_remember: Option, #[serde(rename = "deviceIdentifier")] device_identifier: Option, @@ -235,7 +243,9 @@ pub async fn token( Some(code) => code, None => { // Return 2FA required error - return Err(AppError::TwoFactorRequired(json_err_twofactor(&twofactor_ids))); + return Err(AppError::TwoFactorRequired(json_err_twofactor( + &twofactor_ids, + ))); } }; @@ -250,12 +260,13 @@ pub async fn token( let tf = selected_twofactor.ok_or_else(|| { AppError::BadRequest("TOTP not configured".to_string()) })?; - + // Validate TOTP code let allow_drift = allow_totp_drift(&env); let new_last_used = - validate_totp(twofactor_code, &tf.data, tf.last_used, allow_drift).await?; - + validate_totp(twofactor_code, &tf.data, tf.last_used, allow_drift) + .await?; + // Update last_used query!( &db, @@ -276,19 +287,21 @@ pub async fn token( let remember_tf = twofactors .iter() .find(|tf| tf.atype == TwoFactorType::Remember as i32); - + if let Some(tf) = remember_tf { // Parse stored remember tokens as JSON let mut token_data = RememberTokenData::from_json(&tf.data); - + // Remove expired tokens first token_data.remove_expired(); - + // Validate the provided token if !token_data.validate(device_id, twofactor_code) { - return Err(AppError::TwoFactorRequired(json_err_twofactor(&twofactor_ids))); + return Err(AppError::TwoFactorRequired(json_err_twofactor( + &twofactor_ids, + ))); } - + // Update database with cleaned tokens (remove expired) let updated_data = token_data.to_json(); query!( @@ -301,33 +314,35 @@ pub async fn token( .run() .await .map_err(|_| AppError::Database)?; - + // Remember token valid, proceed with login } else { - return Err(AppError::TwoFactorRequired(json_err_twofactor(&twofactor_ids))); + return Err(AppError::TwoFactorRequired(json_err_twofactor( + &twofactor_ids, + ))); } } else { - return Err(AppError::TwoFactorRequired(json_err_twofactor(&twofactor_ids))); + return Err(AppError::TwoFactorRequired(json_err_twofactor( + &twofactor_ids, + ))); } } Some(TwoFactorType::RecoveryCode) => { // Check recovery code if let Some(ref stored_code) = user.totp_recover { if !ct_eq(&stored_code.to_uppercase(), &twofactor_code.to_uppercase()) { - return Err(AppError::BadRequest("Recovery code is incorrect".to_string())); + return Err(AppError::BadRequest( + "Recovery code is incorrect".to_string(), + )); } - + // Delete all 2FA and clear recovery code - query!( - &db, - "DELETE FROM twofactor WHERE user_uuid = ?1", - &user.id - ) - .map_err(|_| AppError::Database)? - .run() - .await - .map_err(|_| AppError::Database)?; - + query!(&db, "DELETE FROM twofactor WHERE user_uuid = ?1", &user.id) + .map_err(|_| AppError::Database)? + .run() + .await + .map_err(|_| AppError::Database)?; + query!( &db, "UPDATE users SET totp_recover = NULL WHERE id = ?1", @@ -338,11 +353,15 @@ pub async fn token( .await .map_err(|_| AppError::Database)?; } else { - return Err(AppError::BadRequest("Recovery code is incorrect".to_string())); + return Err(AppError::BadRequest( + "Recovery code is incorrect".to_string(), + )); } } _ => { - return Err(AppError::BadRequest("Invalid two factor provider".to_string())); + return Err(AppError::BadRequest( + "Invalid two factor provider".to_string(), + )); } } @@ -350,24 +369,24 @@ pub async fn token( if payload.two_factor_remember == Some(1) { if let Some(ref device_id) = payload.device_identifier { let remember_token = uuid::Uuid::new_v4().to_string(); - + // Load existing remember tokens or create new let remember_tf = twofactors .iter() .find(|tf| tf.atype == TwoFactorType::Remember as i32); - + let mut token_data = remember_tf .map(|tf| RememberTokenData::from_json(&tf.data)) .unwrap_or_default(); - + // Remove expired tokens first token_data.remove_expired(); - + // Add/update token for this device token_data.upsert(device_id.clone(), remember_token.clone()); - + let json_data = token_data.to_json(); - + // Store or update remember token query!( &db, @@ -383,7 +402,7 @@ pub async fn token( .run() .await .map_err(|_| AppError::Database)?; - + two_factor_remember_token = Some(remember_token); } } @@ -467,7 +486,7 @@ fn json_err_twofactor(providers: &[i32]) -> Value { // Add provider-specific info for provider in providers { result["TwoFactorProviders2"][provider.to_string()] = Value::Null; - + // TOTP doesn't need any additional info // Other providers like Email, WebAuthn etc. would add their info here } diff --git a/src/handlers/import.rs b/src/handlers/import.rs index f21c100..af85194 100644 --- a/src/handlers/import.rs +++ b/src/handlers/import.rs @@ -38,10 +38,8 @@ pub async fn import_data( .await? .results::()?; - let existing_folders: HashSet = existing_folder_rows - .into_iter() - .map(|row| row.id) - .collect(); + let existing_folders: HashSet = + existing_folder_rows.into_iter().map(|row| row.id).collect(); // Process folders and build the folder_id list let mut folder_statements: Vec = Vec::new(); @@ -112,7 +110,8 @@ pub async fn import_data( // Build the relations map: cipher_index -> folder_index // Each cipher can only be in one folder at a time - let mut relations_map: HashMap = HashMap::with_capacity(data.folder_relationships.len()); + let mut relations_map: HashMap = + HashMap::with_capacity(data.folder_relationships.len()); for relation in data.folder_relationships { relations_map.insert(relation.key, relation.value); } diff --git a/src/handlers/twofactor.rs b/src/handlers/twofactor.rs index f568f50..3394142 100644 --- a/src/handlers/twofactor.rs +++ b/src/handlers/twofactor.rs @@ -21,8 +21,7 @@ use crate::{ pub async fn get_twofactor( State(env): State>, AuthUser(user_id, _): AuthUser, -) --> Result, AppError> { +) -> Result, AppError> { let db = db::get_db(&env)?; let twofactors: Vec = db @@ -50,8 +49,7 @@ pub async fn get_authenticator( State(env): State>, AuthUser(user_id, _): AuthUser, Json(data): Json, -) --> Result, AppError> { +) -> Result, AppError> { let db = db::get_db(&env)?; // Verify master password @@ -69,7 +67,10 @@ pub async fn get_authenticator( // Check if TOTP is already configured let existing: Option = db .prepare("SELECT * FROM twofactor WHERE user_uuid = ?1 AND atype = ?2") - .bind(&[user_id.clone().into(), (TwoFactorType::Authenticator as i32).into()])? + .bind(&[ + user_id.clone().into(), + (TwoFactorType::Authenticator as i32).into(), + ])? .first(None) .await .map_err(|_| AppError::Database)?; @@ -95,8 +96,7 @@ pub async fn activate_authenticator( State(env): State>, AuthUser(user_id, _): AuthUser, Json(data): Json, -) --> Result, AppError> { +) -> Result, AppError> { let db = db::get_db(&env)?; // Verify master password @@ -129,7 +129,10 @@ pub async fn activate_authenticator( // Check if TOTP is already configured - reuse existing record for replay protection let existing: Option = db .prepare("SELECT * FROM twofactor WHERE user_uuid = ?1 AND atype = ?2") - .bind(&[user_id.clone().into(), (TwoFactorType::Authenticator as i32).into()])? + .bind(&[ + user_id.clone().into(), + (TwoFactorType::Authenticator as i32).into(), + ])? .first(None) .await .map_err(|_| AppError::Database)? @@ -191,8 +194,7 @@ pub async fn activate_authenticator_put( state: State>, auth_user: AuthUser, json: Json, -) --> Result, AppError> { +) -> Result, AppError> { activate_authenticator(state, auth_user, json).await } @@ -202,8 +204,7 @@ pub async fn disable_twofactor( State(env): State>, AuthUser(user_id, _): AuthUser, Json(data): Json, -) --> Result, AppError> { +) -> Result, AppError> { let db = db::get_db(&env)?; // Verify master password @@ -250,7 +251,6 @@ pub async fn disable_twofactor( }))) } - /// DELETE /api/two-factor/authenticator - Disable TOTP with key verification #[worker::send] pub async fn disable_authenticator( @@ -304,17 +304,17 @@ pub async fn disable_authenticator( )); } - query!( - &db, - "DELETE FROM twofactor WHERE uuid = ?1", - &tf.uuid - ) - .map_err(|_| AppError::Database)? - .run() - .await - .map_err(|_| AppError::Database)?; + query!(&db, "DELETE FROM twofactor WHERE uuid = ?1", &tf.uuid) + .map_err(|_| AppError::Database)? + .run() + .await + .map_err(|_| AppError::Database)?; - log::info!("User {} disabled authenticator (2FA type {})", user_id, data.r#type); + log::info!( + "User {} disabled authenticator (2FA type {})", + user_id, + data.r#type + ); clear_recovery_if_no_twofactor(&db, &user_id).await?; @@ -331,8 +331,7 @@ pub async fn disable_twofactor_put( state: State>, auth_user: AuthUser, json: Json, -) --> Result, AppError> { +) -> Result, AppError> { disable_twofactor(state, auth_user, json).await } @@ -342,8 +341,7 @@ pub async fn get_recover( State(env): State>, AuthUser(user_id, _): AuthUser, Json(data): Json, -) --> Result, AppError> { +) -> Result, AppError> { let db = db::get_db(&env)?; // Verify master password @@ -369,8 +367,7 @@ pub async fn get_recover( pub async fn recover( State(env): State>, Json(data): Json, -) --> Result, AppError> { +) -> Result, AppError> { let db = db::get_db(&env)?; // Get user by email @@ -384,7 +381,9 @@ pub async fn recover( let user: User = serde_json::from_value(user_value).map_err(|_| AppError::Internal)?; // Verify master password - let verification = user.verify_master_password(&data.master_password_hash).await?; + let verification = user + .verify_master_password(&data.master_password_hash) + .await?; if !verification.is_valid() { return Err(AppError::Unauthorized( "Username or password is incorrect".to_string(), @@ -393,7 +392,10 @@ pub async fn recover( // Check recovery code (case-insensitive) let is_valid = user.totp_recover.as_ref().map_or(false, |stored_code| { - ct_eq(&stored_code.to_uppercase(), &data.recovery_code.to_uppercase()) + ct_eq( + &stored_code.to_uppercase(), + &data.recovery_code.to_uppercase(), + ) }); if !is_valid { @@ -403,15 +405,11 @@ pub async fn recover( } // Delete all 2FA methods - query!( - &db, - "DELETE FROM twofactor WHERE user_uuid = ?1", - &user.id - ) - .map_err(|_| AppError::Database)? - .run() - .await - .map_err(|_| AppError::Database)?; + query!(&db, "DELETE FROM twofactor WHERE user_uuid = ?1", &user.id) + .map_err(|_| AppError::Database)? + .run() + .await + .map_err(|_| AppError::Database)?; // Clear recovery code query!( @@ -448,8 +446,7 @@ async fn validate_password_or_otp(user: &User, data: &PasswordOrOtpData) -> Resu async fn generate_recovery_code_for_user( db: &worker::D1Database, user_id: &str, -) --> Result<(), AppError> { +) -> Result<(), AppError> { // Check if recovery code already exists let user_value: Value = db .prepare("SELECT totp_recover FROM users WHERE id = ?1") @@ -482,7 +479,10 @@ async fn generate_recovery_code_for_user( } /// Clear recovery code when no real 2FA providers remain. -async fn clear_recovery_if_no_twofactor(db: &worker::D1Database, user_id: &str) -> Result<(), AppError> { +async fn clear_recovery_if_no_twofactor( + db: &worker::D1Database, + user_id: &str, +) -> Result<(), AppError> { let remaining: Vec = db .prepare("SELECT * FROM twofactor WHERE user_uuid = ?1 AND atype < 1000 AND atype != ?2") .bind(&[ @@ -496,11 +496,15 @@ async fn clear_recovery_if_no_twofactor(db: &worker::D1Database, user_id: &str) .map_err(|_| AppError::Database)?; if remaining.is_empty() { - query!(db, "UPDATE users SET totp_recover = NULL WHERE id = ?1", user_id) - .map_err(|_| AppError::Database)? - .run() - .await - .map_err(|_| AppError::Database)?; + query!( + db, + "UPDATE users SET totp_recover = NULL WHERE id = ?1", + user_id + ) + .map_err(|_| AppError::Database)? + .run() + .await + .map_err(|_| AppError::Database)?; } Ok(()) diff --git a/src/models/cipher.rs b/src/models/cipher.rs index 328ea5e..fd2b980 100644 --- a/src/models/cipher.rs +++ b/src/models/cipher.rs @@ -183,10 +183,13 @@ impl Serialize for Cipher { response_map.insert("edit".to_string(), json!(self.edit)); response_map.insert("viewPassword".to_string(), json!(self.view_password)); // new key "permissions" used by clients since v2025.6.0 - response_map.insert("permissions". to_string(), json! ({ - "delete": self.edit, // if edit is true, allow delete - "restore": self.edit, // if edit is true, allow restore - })); + response_map.insert( + "permissions".to_string(), + json! ({ + "delete": self.edit, // if edit is true, allow delete + "restore": self.edit, // if edit is true, allow restore + }), + ); response_map.insert( "organizationUseTotp".to_string(), json!(self.organization_use_totp), @@ -336,4 +339,4 @@ pub struct CipherListResponse { pub struct PartialCipherData { pub folder_id: Option, pub favorite: bool, -} \ No newline at end of file +} diff --git a/src/models/mod.rs b/src/models/mod.rs index a58b386..cdf4cfb 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,6 +1,6 @@ -pub mod user; -pub mod sync; pub mod cipher; pub mod folder; pub mod import; +pub mod sync; pub mod twofactor; +pub mod user; diff --git a/src/models/twofactor.rs b/src/models/twofactor.rs index 519593a..04c7d42 100644 --- a/src/models/twofactor.rs +++ b/src/models/twofactor.rs @@ -169,7 +169,8 @@ impl RememberTokenData { pub fn remove_expired(&mut self) { let now = Utc::now().timestamp(); let expiration_seconds = Duration::days(REMEMBER_TOKEN_EXPIRATION_DAYS).num_seconds(); - self.tokens.retain(|t| now - t.created_at < expiration_seconds); + self.tokens + .retain(|t| now - t.created_at < expiration_seconds); } /// Validate a token for a specific device @@ -177,11 +178,9 @@ impl RememberTokenData { pub fn validate(&self, device_id: &str, token: &str) -> bool { let now = Utc::now().timestamp(); let expiration_seconds = Duration::days(REMEMBER_TOKEN_EXPIRATION_DAYS).num_seconds(); - + self.tokens.iter().any(|t| { - t.device_id == device_id - && t.token == token - && now - t.created_at < expiration_seconds + t.device_id == device_id && t.token == token && now - t.created_at < expiration_seconds }) } @@ -190,7 +189,7 @@ impl RememberTokenData { pub fn upsert(&mut self, device_id: String, token: String) { // Remove existing token for this device self.tokens.retain(|t| t.device_id != device_id); - + // Add new token self.tokens.push(RememberTokenEntry { device_id, @@ -203,6 +202,8 @@ impl RememberTokenData { pub fn has_valid_tokens(&self) -> bool { let now = Utc::now().timestamp(); let expiration_seconds = Duration::days(REMEMBER_TOKEN_EXPIRATION_DAYS).num_seconds(); - self.tokens.iter().any(|t| now - t.created_at < expiration_seconds) + self.tokens + .iter() + .any(|t| now - t.created_at < expiration_seconds) } } diff --git a/src/models/user.rs b/src/models/user.rs index b053b26..dec592e 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -19,7 +19,7 @@ pub struct User { pub public_key: String, pub kdf_type: i32, pub kdf_iterations: i32, - pub kdf_memory: Option, // Argon2 memory parameter (15-1024 MB) + pub kdf_memory: Option, // Argon2 memory parameter (15-1024 MB) pub kdf_parallelism: Option, // Argon2 parallelism parameter (1-16) pub security_stamp: String, pub totp_recover: Option, // Recovery code for 2FA @@ -125,7 +125,7 @@ pub struct RegisterRequest { pub user_asymmetric_keys: KeyData, pub kdf: i32, pub kdf_iterations: i32, - pub kdf_memory: Option, // Argon2 memory parameter (15-1024 MB) + pub kdf_memory: Option, // Argon2 memory parameter (15-1024 MB) pub kdf_parallelism: Option, // Argon2 parallelism parameter (1-16) } diff --git a/src/router.rs b/src/router.rs index 485506b..5a87ee2 100644 --- a/src/router.rs +++ b/src/router.rs @@ -5,7 +5,10 @@ use axum::{ use std::sync::Arc; use worker::Env; -use crate::handlers::{accounts, ciphers, config, devices, emergency_access, folders, identity, import, sync, twofactor, webauth}; +use crate::handlers::{ + accounts, ciphers, config, devices, emergency_access, folders, identity, import, sync, + twofactor, webauth, +}; pub fn api_router(env: Env) -> Router { let app_state = Arc::new(env); @@ -48,7 +51,10 @@ pub fn api_router(env: Env) -> Router { .route("/api/ciphers/create", post(ciphers::create_cipher)) .route("/api/ciphers/import", post(import::import_data)) .route("/api/ciphers/{id}", get(ciphers::get_cipher)) - .route("/api/ciphers/{id}/details", get(ciphers::get_cipher_details)) + .route( + "/api/ciphers/{id}/details", + get(ciphers::get_cipher_details), + ) .route("/api/ciphers/{id}", put(ciphers::update_cipher)) .route("/api/ciphers/{id}", post(ciphers::update_cipher)) // Cipher soft delete (PUT sets deleted_at timestamp) @@ -126,10 +132,7 @@ pub fn api_router(env: Env) -> Router { post(devices::post_clear_device_token), ) // WebAuthn (stub - prevents 404 errors, passkeys not supported) - .route( - "/api/webauthn", - get(webauth::get_webauthn_credentials), - ) + .route("/api/webauthn", get(webauth::get_webauthn_credentials)) // Two-factor authentication .route("/api/two-factor", get(twofactor::get_twofactor)) .route( @@ -156,10 +159,7 @@ pub fn api_router(env: Env) -> Router { "/api/two-factor/disable", put(twofactor::disable_twofactor_put), ) - .route( - "/api/two-factor/get-recover", - post(twofactor::get_recover), - ) + .route("/api/two-factor/get-recover", post(twofactor::get_recover)) .route("/api/two-factor/recover", post(twofactor::recover)) .with_state(app_state) }