diff --git a/src/handlers/accounts.rs b/src/handlers/accounts.rs index 2fa462c..c6d27ec 100644 --- a/src/handlers/accounts.rs +++ b/src/handlers/accounts.rs @@ -1,5 +1,6 @@ use axum::{extract::State, Json}; use chrono::Utc; +use constant_time_eq::constant_time_eq; use serde_json::{json, Value}; use std::sync::Arc; use uuid::Uuid; @@ -8,7 +9,7 @@ use worker::{query, Env}; use crate::{ db, error::AppError, - models::user::{PreloginResponse, RegisterRequest, User}, + models::user::{DeleteAccountRequest, PreloginResponse, RegisterRequest, User}, auth::Claims, }; @@ -127,3 +128,63 @@ pub async fn revision_date( Ok(Json(revision_date)) } + +#[worker::send] +pub async fn delete_account( + 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)?; + + // Verify the master password hash + if !constant_time_eq( + user.master_password_hash.as_bytes(), + payload.master_password_hash.as_bytes(), + ) { + return Err(AppError::Unauthorized("Invalid password".to_string())); + } + + // Delete all user's ciphers + 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?; + + // Delete the user + query!( + &db, + "DELETE FROM users WHERE id = ?1", + user_id + ) + .map_err(|_| AppError::Database)? + .run() + .await?; + + Ok(Json(json!({}))) +} diff --git a/src/models/user.rs b/src/models/user.rs index 35ce6fc..fc612b4 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -74,3 +74,10 @@ pub struct KeyData { pub public_key: String, pub encrypted_private_key: String, } + +// For DELETE /accounts request +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DeleteAccountRequest { + pub master_password_hash: String, +} \ No newline at end of file diff --git a/src/router.rs b/src/router.rs index 4bfcf80..c7c2fd1 100644 --- a/src/router.rs +++ b/src/router.rs @@ -26,6 +26,9 @@ pub fn api_router(env: Env) -> Router { .route("/api/sync", get(sync::get_sync_data)) // For on-demand sync checks .route("/api/accounts/revision-date", get(accounts::revision_date)) + // Delete account + .route("/api/accounts", delete(accounts::delete_account)) + .route("/api/accounts/delete", post(accounts::delete_account)) // Ciphers CRUD .route("/api/ciphers", post(ciphers::create_cipher_simple)) .route("/api/ciphers/create", post(ciphers::create_cipher))