this repo has no description
1use crate::state::AppState; 2use axum::{ 3 Json, 4 extract::State, 5 http::StatusCode, 6 response::{IntoResponse, Response}, 7}; 8use bcrypt::verify; 9use chrono::{Duration, Utc}; 10use serde::{Deserialize, Serialize}; 11use serde_json::json; 12use tracing::{error, info, warn}; 13use uuid::Uuid; 14 15#[derive(Serialize)] 16#[serde(rename_all = "camelCase")] 17pub struct CheckAccountStatusOutput { 18 pub activated: bool, 19 pub valid_did: bool, 20 pub repo_commit: String, 21 pub repo_rev: String, 22 pub repo_blocks: i64, 23 pub indexed_records: i64, 24 pub private_state_values: i64, 25 pub expected_blobs: i64, 26 pub imported_blobs: i64, 27} 28 29pub async fn check_account_status( 30 State(state): State<AppState>, 31 headers: axum::http::HeaderMap, 32) -> Response { 33 let token = match crate::auth::extract_bearer_token_from_header( 34 headers.get("Authorization").and_then(|h| h.to_str().ok()) 35 ) { 36 Some(t) => t, 37 None => { 38 return ( 39 StatusCode::UNAUTHORIZED, 40 Json(json!({"error": "AuthenticationRequired"})), 41 ) 42 .into_response(); 43 } 44 }; 45 46 let auth_result = crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await; 47 let did = match auth_result { 48 Ok(user) => user.did, 49 Err(e) => { 50 return ( 51 StatusCode::UNAUTHORIZED, 52 Json(json!({"error": e})), 53 ) 54 .into_response(); 55 } 56 }; 57 58 let user_id = match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did) 59 .fetch_optional(&state.db) 60 .await 61 { 62 Ok(Some(id)) => id, 63 _ => { 64 return ( 65 StatusCode::INTERNAL_SERVER_ERROR, 66 Json(json!({"error": "InternalError"})), 67 ) 68 .into_response(); 69 } 70 }; 71 72 let user_status = sqlx::query!("SELECT deactivated_at FROM users WHERE did = $1", did) 73 .fetch_optional(&state.db) 74 .await; 75 76 let deactivated_at = match user_status { 77 Ok(Some(row)) => row.deactivated_at, 78 _ => None, 79 }; 80 81 let repo_result = sqlx::query!("SELECT repo_root_cid FROM repos WHERE user_id = $1", user_id) 82 .fetch_optional(&state.db) 83 .await; 84 85 let repo_commit = match repo_result { 86 Ok(Some(row)) => row.repo_root_cid, 87 _ => String::new(), 88 }; 89 90 let record_count: i64 = sqlx::query_scalar!("SELECT COUNT(*) FROM records WHERE repo_id = $1", user_id) 91 .fetch_one(&state.db) 92 .await 93 .unwrap_or(Some(0)) 94 .unwrap_or(0); 95 96 let blob_count: i64 = 97 sqlx::query_scalar!("SELECT COUNT(*) FROM blobs WHERE created_by_user = $1", user_id) 98 .fetch_one(&state.db) 99 .await 100 .unwrap_or(Some(0)) 101 .unwrap_or(0); 102 103 let valid_did = did.starts_with("did:"); 104 105 ( 106 StatusCode::OK, 107 Json(CheckAccountStatusOutput { 108 activated: deactivated_at.is_none(), 109 valid_did, 110 repo_commit: repo_commit.clone(), 111 repo_rev: chrono::Utc::now().timestamp_millis().to_string(), 112 repo_blocks: 0, 113 indexed_records: record_count, 114 private_state_values: 0, 115 expected_blobs: blob_count, 116 imported_blobs: blob_count, 117 }), 118 ) 119 .into_response() 120} 121 122pub async fn activate_account( 123 State(state): State<AppState>, 124 headers: axum::http::HeaderMap, 125) -> Response { 126 let token = match crate::auth::extract_bearer_token_from_header( 127 headers.get("Authorization").and_then(|h| h.to_str().ok()) 128 ) { 129 Some(t) => t, 130 None => { 131 return ( 132 StatusCode::UNAUTHORIZED, 133 Json(json!({"error": "AuthenticationRequired"})), 134 ) 135 .into_response(); 136 } 137 }; 138 139 let auth_result = crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await; 140 let did = match auth_result { 141 Ok(user) => user.did, 142 Err(e) => { 143 return ( 144 StatusCode::UNAUTHORIZED, 145 Json(json!({"error": e})), 146 ) 147 .into_response(); 148 } 149 }; 150 151 let result = sqlx::query!("UPDATE users SET deactivated_at = NULL WHERE did = $1", did) 152 .execute(&state.db) 153 .await; 154 155 match result { 156 Ok(_) => (StatusCode::OK, Json(json!({}))).into_response(), 157 Err(e) => { 158 error!("DB error activating account: {:?}", e); 159 ( 160 StatusCode::INTERNAL_SERVER_ERROR, 161 Json(json!({"error": "InternalError"})), 162 ) 163 .into_response() 164 } 165 } 166} 167 168#[derive(Deserialize)] 169#[serde(rename_all = "camelCase")] 170pub struct DeactivateAccountInput { 171 pub delete_after: Option<String>, 172} 173 174pub async fn deactivate_account( 175 State(state): State<AppState>, 176 headers: axum::http::HeaderMap, 177 Json(_input): Json<DeactivateAccountInput>, 178) -> Response { 179 let token = match crate::auth::extract_bearer_token_from_header( 180 headers.get("Authorization").and_then(|h| h.to_str().ok()) 181 ) { 182 Some(t) => t, 183 None => { 184 return ( 185 StatusCode::UNAUTHORIZED, 186 Json(json!({"error": "AuthenticationRequired"})), 187 ) 188 .into_response(); 189 } 190 }; 191 192 let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await; 193 let did = match auth_result { 194 Ok(user) => user.did, 195 Err(e) => { 196 return ( 197 StatusCode::UNAUTHORIZED, 198 Json(json!({"error": e})), 199 ) 200 .into_response(); 201 } 202 }; 203 204 let result = sqlx::query!("UPDATE users SET deactivated_at = NOW() WHERE did = $1", did) 205 .execute(&state.db) 206 .await; 207 208 match result { 209 Ok(_) => (StatusCode::OK, Json(json!({}))).into_response(), 210 Err(e) => { 211 error!("DB error deactivating account: {:?}", e); 212 ( 213 StatusCode::INTERNAL_SERVER_ERROR, 214 Json(json!({"error": "InternalError"})), 215 ) 216 .into_response() 217 } 218 } 219} 220 221pub async fn request_account_delete( 222 State(state): State<AppState>, 223 headers: axum::http::HeaderMap, 224) -> Response { 225 let token = match crate::auth::extract_bearer_token_from_header( 226 headers.get("Authorization").and_then(|h| h.to_str().ok()) 227 ) { 228 Some(t) => t, 229 None => { 230 return ( 231 StatusCode::UNAUTHORIZED, 232 Json(json!({"error": "AuthenticationRequired"})), 233 ) 234 .into_response(); 235 } 236 }; 237 238 let auth_result = crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await; 239 let did = match auth_result { 240 Ok(user) => user.did, 241 Err(e) => { 242 return ( 243 StatusCode::UNAUTHORIZED, 244 Json(json!({"error": e})), 245 ) 246 .into_response(); 247 } 248 }; 249 250 let user_id = match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did) 251 .fetch_optional(&state.db) 252 .await 253 { 254 Ok(Some(id)) => id, 255 _ => { 256 return ( 257 StatusCode::INTERNAL_SERVER_ERROR, 258 Json(json!({"error": "InternalError"})), 259 ) 260 .into_response(); 261 } 262 }; 263 264 let confirmation_token = Uuid::new_v4().to_string(); 265 let expires_at = Utc::now() + Duration::minutes(15); 266 267 let insert = sqlx::query!( 268 "INSERT INTO account_deletion_requests (token, did, expires_at) VALUES ($1, $2, $3)", 269 confirmation_token, 270 did, 271 expires_at 272 ) 273 .execute(&state.db) 274 .await; 275 276 if let Err(e) = insert { 277 error!("DB error creating deletion token: {:?}", e); 278 return ( 279 StatusCode::INTERNAL_SERVER_ERROR, 280 Json(json!({"error": "InternalError"})), 281 ) 282 .into_response(); 283 } 284 285 let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 286 if let Err(e) = 287 crate::notifications::enqueue_account_deletion(&state.db, user_id, &confirmation_token, &hostname).await 288 { 289 warn!("Failed to enqueue account deletion notification: {:?}", e); 290 } 291 292 info!("Account deletion requested for user {}", did); 293 294 (StatusCode::OK, Json(json!({}))).into_response() 295} 296 297#[derive(Deserialize)] 298pub struct DeleteAccountInput { 299 pub did: String, 300 pub password: String, 301 pub token: String, 302} 303 304pub async fn delete_account( 305 State(state): State<AppState>, 306 Json(input): Json<DeleteAccountInput>, 307) -> Response { 308 let did = input.did.trim(); 309 let password = &input.password; 310 let token = input.token.trim(); 311 312 if did.is_empty() { 313 return ( 314 StatusCode::BAD_REQUEST, 315 Json(json!({"error": "InvalidRequest", "message": "did is required"})), 316 ) 317 .into_response(); 318 } 319 320 if password.is_empty() { 321 return ( 322 StatusCode::BAD_REQUEST, 323 Json(json!({"error": "InvalidRequest", "message": "password is required"})), 324 ) 325 .into_response(); 326 } 327 328 if token.is_empty() { 329 return ( 330 StatusCode::BAD_REQUEST, 331 Json(json!({"error": "InvalidToken", "message": "token is required"})), 332 ) 333 .into_response(); 334 } 335 336 let user = sqlx::query!( 337 "SELECT id, password_hash FROM users WHERE did = $1", 338 did 339 ) 340 .fetch_optional(&state.db) 341 .await; 342 343 let (user_id, password_hash) = match user { 344 Ok(Some(row)) => (row.id, row.password_hash), 345 Ok(None) => { 346 return ( 347 StatusCode::BAD_REQUEST, 348 Json(json!({"error": "AccountNotFound", "message": "Account not found"})), 349 ) 350 .into_response(); 351 } 352 Err(e) => { 353 error!("DB error in delete_account: {:?}", e); 354 return ( 355 StatusCode::INTERNAL_SERVER_ERROR, 356 Json(json!({"error": "InternalError"})), 357 ) 358 .into_response(); 359 } 360 }; 361 362 let password_valid = if verify(password, &password_hash).unwrap_or(false) { 363 true 364 } else { 365 let app_pass_rows = sqlx::query!( 366 "SELECT password_hash FROM app_passwords WHERE user_id = $1", 367 user_id 368 ) 369 .fetch_all(&state.db) 370 .await 371 .unwrap_or_default(); 372 373 app_pass_rows 374 .iter() 375 .any(|row| verify(password, &row.password_hash).unwrap_or(false)) 376 }; 377 378 if !password_valid { 379 return ( 380 StatusCode::UNAUTHORIZED, 381 Json(json!({"error": "AuthenticationFailed", "message": "Invalid password"})), 382 ) 383 .into_response(); 384 } 385 386 let deletion_request = sqlx::query!( 387 "SELECT did, expires_at FROM account_deletion_requests WHERE token = $1", 388 token 389 ) 390 .fetch_optional(&state.db) 391 .await; 392 393 let (token_did, expires_at) = match deletion_request { 394 Ok(Some(row)) => (row.did, row.expires_at), 395 Ok(None) => { 396 return ( 397 StatusCode::BAD_REQUEST, 398 Json(json!({"error": "InvalidToken", "message": "Invalid or expired token"})), 399 ) 400 .into_response(); 401 } 402 Err(e) => { 403 error!("DB error fetching deletion token: {:?}", e); 404 return ( 405 StatusCode::INTERNAL_SERVER_ERROR, 406 Json(json!({"error": "InternalError"})), 407 ) 408 .into_response(); 409 } 410 }; 411 412 if token_did != did { 413 return ( 414 StatusCode::BAD_REQUEST, 415 Json(json!({"error": "InvalidToken", "message": "Token does not match account"})), 416 ) 417 .into_response(); 418 } 419 420 if Utc::now() > expires_at { 421 let _ = sqlx::query!("DELETE FROM account_deletion_requests WHERE token = $1", token) 422 .execute(&state.db) 423 .await; 424 425 return ( 426 StatusCode::BAD_REQUEST, 427 Json(json!({"error": "ExpiredToken", "message": "Token has expired"})), 428 ) 429 .into_response(); 430 } 431 432 let mut tx = match state.db.begin().await { 433 Ok(tx) => tx, 434 Err(e) => { 435 error!("Failed to begin transaction: {:?}", e); 436 return ( 437 StatusCode::INTERNAL_SERVER_ERROR, 438 Json(json!({"error": "InternalError"})), 439 ) 440 .into_response(); 441 } 442 }; 443 444 let deletion_result: Result<(), sqlx::Error> = async { 445 sqlx::query!("DELETE FROM session_tokens WHERE did = $1", did) 446 .execute(&mut *tx) 447 .await?; 448 449 sqlx::query!("DELETE FROM records WHERE repo_id = $1", user_id) 450 .execute(&mut *tx) 451 .await?; 452 453 sqlx::query!("DELETE FROM repos WHERE user_id = $1", user_id) 454 .execute(&mut *tx) 455 .await?; 456 457 sqlx::query!("DELETE FROM blobs WHERE created_by_user = $1", user_id) 458 .execute(&mut *tx) 459 .await?; 460 461 sqlx::query!("DELETE FROM user_keys WHERE user_id = $1", user_id) 462 .execute(&mut *tx) 463 .await?; 464 465 sqlx::query!("DELETE FROM app_passwords WHERE user_id = $1", user_id) 466 .execute(&mut *tx) 467 .await?; 468 469 sqlx::query!("DELETE FROM account_deletion_requests WHERE did = $1", did) 470 .execute(&mut *tx) 471 .await?; 472 473 sqlx::query!("DELETE FROM users WHERE id = $1", user_id) 474 .execute(&mut *tx) 475 .await?; 476 477 Ok(()) 478 } 479 .await; 480 481 match deletion_result { 482 Ok(()) => { 483 if let Err(e) = tx.commit().await { 484 error!("Failed to commit account deletion transaction: {:?}", e); 485 return ( 486 StatusCode::INTERNAL_SERVER_ERROR, 487 Json(json!({"error": "InternalError"})), 488 ) 489 .into_response(); 490 } 491 info!("Account {} deleted successfully", did); 492 (StatusCode::OK, Json(json!({}))).into_response() 493 } 494 Err(e) => { 495 error!("DB error deleting account, rolling back: {:?}", e); 496 ( 497 StatusCode::INTERNAL_SERVER_ERROR, 498 Json(json!({"error": "InternalError"})), 499 ) 500 .into_response() 501 } 502 } 503}