diff --git a/src/handlers/accounts.rs b/src/handlers/accounts.rs index 65a2378..c3fcfb2 100644 --- a/src/handlers/accounts.rs +++ b/src/handlers/accounts.rs @@ -20,7 +20,7 @@ use crate::{ cipher::CipherData, sync::Profile, user::{ - ChangeKdfRequest, ChangePasswordRequest, DeleteAccountRequest, PreloginResponse, + ChangeKdfRequest, ChangePasswordRequest, PasswordOrOtpData, PreloginResponse, RegisterRequest, RotateKeyRequest, User, }, }, @@ -314,7 +314,7 @@ pub async fn get_profile( pub async fn delete_account( claims: Claims, State(env): State>, - Json(payload): Json, + Json(payload): Json, ) -> Result, AppError> { let db = db::get_db(&env)?; let user_id = &claims.sub; diff --git a/src/handlers/ciphers.rs b/src/handlers/ciphers.rs index e1c5717..9eaa07f 100644 --- a/src/handlers/ciphers.rs +++ b/src/handlers/ciphers.rs @@ -2,6 +2,7 @@ use super::get_batch_size; use axum::{extract::State, Json}; use chrono::Utc; use serde::Deserialize; +use serde_json::Value; use std::sync::Arc; use uuid::Uuid; use worker::{query, D1PreparedStatement, Env}; @@ -10,6 +11,7 @@ use crate::auth::Claims; use crate::db; use crate::error::AppError; use crate::models::cipher::{Cipher, CipherData, CipherRequestData, CreateCipherRequest}; +use crate::models::user::{PasswordOrOtpData, User}; use axum::extract::Path; #[worker::send] @@ -440,3 +442,57 @@ pub async fn create_cipher_simple( Ok(Json(cipher)) } + +/// Purge the user's vault - delete all ciphers and folders +/// POST /api/ciphers/purge +/// +/// This is a destructive operation that requires password verification. +/// In vaultwarden, this endpoint also supports purging organization vaults, +/// but this simplified version only supports personal vault purge. +#[worker::send] +pub async fn purge_vault( + claims: Claims, + State(env): State>, + Json(payload): Json, +) -> Result, AppError> { + let db = db::get_db(&env)?; + let user_id = &claims.sub; + + // Get the user from the database + let user: Value = db + .prepare("SELECT * FROM users WHERE id = ?1") + .bind(&[user_id.clone().into()])? + .first(None) + .await + .map_err(|_| AppError::Database)? + .ok_or_else(|| AppError::NotFound("User not found".to_string()))?; + let user: User = serde_json::from_value(user).map_err(|_| AppError::Internal)?; + + // Validate password (OTP not supported in this simplified version) + let provided_hash = payload + .master_password_hash + .ok_or_else(|| AppError::BadRequest("Missing master password hash".to_string()))?; + + let verification = user.verify_master_password(&provided_hash).await?; + + if !verification.is_valid() { + return Err(AppError::Unauthorized("Invalid password".to_string())); + } + + // Delete all user's ciphers (both active and soft-deleted) + query!(&db, "DELETE FROM ciphers WHERE user_id = ?1", user_id) + .map_err(|_| AppError::Database)? + .run() + .await?; + + // Delete all user's folders + query!(&db, "DELETE FROM folders WHERE user_id = ?1", user_id) + .map_err(|_| AppError::Database)? + .run() + .await?; + + // Update user's revision date to trigger client sync + db::touch_user_updated_at(&db, user_id).await?; + + Ok(Json(())) +} diff --git a/src/models/user.rs b/src/models/user.rs index e082366..02563cc 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -134,10 +134,12 @@ pub struct KeyData { pub encrypted_private_key: String, } -// For DELETE /accounts request +/// Request body for password-protected operations (delete account, purge vault, etc.) +/// Supports both master password hash and OTP verification. +/// Note: OTP verification is not implemented in this simplified version. #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct DeleteAccountRequest { +pub struct PasswordOrOtpData { #[serde(alias = "MasterPasswordHash")] pub master_password_hash: Option, pub otp: Option, diff --git a/src/router.rs b/src/router.rs index 8cc5759..fedcfb9 100644 --- a/src/router.rs +++ b/src/router.rs @@ -67,6 +67,8 @@ pub fn api_router(env: Env) -> Router { .route("/api/ciphers/{id}/restore", put(ciphers::restore_cipher)) // Cipher bulk restore .route("/api/ciphers/restore", put(ciphers::restore_ciphers_bulk)) + // Purge vault - delete all ciphers and folders (requires password verification) + .route("/api/ciphers/purge", post(ciphers::purge_vault)) // Folders CRUD .route("/api/folders", post(folders::create_folder)) .route("/api/folders/{id}", put(folders::update_folder)) diff --git a/vaultwarden b/vaultwarden new file mode 160000 index 0000000..cb2f574 --- /dev/null +++ b/vaultwarden @@ -0,0 +1 @@ +Subproject commit cb2f5741accd5bf510d03233ed8457cef8a6a35c