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 = match sqlx::query!("SELECT id, email, handle FROM users WHERE did = $1", did) 251 .fetch_optional(&state.db) 252 .await 253 { 254 Ok(Some(row)) => row, 255 _ => { 256 return ( 257 StatusCode::INTERNAL_SERVER_ERROR, 258 Json(json!({"error": "InternalError"})), 259 ) 260 .into_response(); 261 } 262 }; 263 let user_id = user.id; 264 let email = user.email; 265 let handle = user.handle; 266 267 let confirmation_token = Uuid::new_v4().to_string(); 268 let expires_at = Utc::now() + Duration::minutes(15); 269 270 let insert = sqlx::query!( 271 "INSERT INTO account_deletion_requests (token, did, expires_at) VALUES ($1, $2, $3)", 272 confirmation_token, 273 did, 274 expires_at 275 ) 276 .execute(&state.db) 277 .await; 278 279 if let Err(e) = insert { 280 error!("DB error creating deletion token: {:?}", e); 281 return ( 282 StatusCode::INTERNAL_SERVER_ERROR, 283 Json(json!({"error": "InternalError"})), 284 ) 285 .into_response(); 286 } 287 288 let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 289 if let Err(e) = crate::notifications::enqueue_account_deletion( 290 &state.db, 291 user_id, 292 &email, 293 &handle, 294 &confirmation_token, 295 &hostname, 296 ) 297 .await 298 { 299 warn!("Failed to enqueue account deletion notification: {:?}", e); 300 } 301 302 info!("Account deletion requested for user {}", did); 303 304 (StatusCode::OK, Json(json!({}))).into_response() 305} 306 307#[derive(Deserialize)] 308pub struct DeleteAccountInput { 309 pub did: String, 310 pub password: String, 311 pub token: String, 312} 313 314pub async fn delete_account( 315 State(state): State<AppState>, 316 Json(input): Json<DeleteAccountInput>, 317) -> Response { 318 let did = input.did.trim(); 319 let password = &input.password; 320 let token = input.token.trim(); 321 322 if did.is_empty() { 323 return ( 324 StatusCode::BAD_REQUEST, 325 Json(json!({"error": "InvalidRequest", "message": "did is required"})), 326 ) 327 .into_response(); 328 } 329 330 if password.is_empty() { 331 return ( 332 StatusCode::BAD_REQUEST, 333 Json(json!({"error": "InvalidRequest", "message": "password is required"})), 334 ) 335 .into_response(); 336 } 337 338 if token.is_empty() { 339 return ( 340 StatusCode::BAD_REQUEST, 341 Json(json!({"error": "InvalidToken", "message": "token is required"})), 342 ) 343 .into_response(); 344 } 345 346 let user = sqlx::query!( 347 "SELECT id, password_hash FROM users WHERE did = $1", 348 did 349 ) 350 .fetch_optional(&state.db) 351 .await; 352 353 let (user_id, password_hash) = match user { 354 Ok(Some(row)) => (row.id, row.password_hash), 355 Ok(None) => { 356 return ( 357 StatusCode::BAD_REQUEST, 358 Json(json!({"error": "AccountNotFound", "message": "Account not found"})), 359 ) 360 .into_response(); 361 } 362 Err(e) => { 363 error!("DB error in delete_account: {:?}", e); 364 return ( 365 StatusCode::INTERNAL_SERVER_ERROR, 366 Json(json!({"error": "InternalError"})), 367 ) 368 .into_response(); 369 } 370 }; 371 372 let password_valid = if verify(password, &password_hash).unwrap_or(false) { 373 true 374 } else { 375 let app_pass_rows = sqlx::query!( 376 "SELECT password_hash FROM app_passwords WHERE user_id = $1", 377 user_id 378 ) 379 .fetch_all(&state.db) 380 .await 381 .unwrap_or_default(); 382 383 app_pass_rows 384 .iter() 385 .any(|row| verify(password, &row.password_hash).unwrap_or(false)) 386 }; 387 388 if !password_valid { 389 return ( 390 StatusCode::UNAUTHORIZED, 391 Json(json!({"error": "AuthenticationFailed", "message": "Invalid password"})), 392 ) 393 .into_response(); 394 } 395 396 let deletion_request = sqlx::query!( 397 "SELECT did, expires_at FROM account_deletion_requests WHERE token = $1", 398 token 399 ) 400 .fetch_optional(&state.db) 401 .await; 402 403 let (token_did, expires_at) = match deletion_request { 404 Ok(Some(row)) => (row.did, row.expires_at), 405 Ok(None) => { 406 return ( 407 StatusCode::BAD_REQUEST, 408 Json(json!({"error": "InvalidToken", "message": "Invalid or expired token"})), 409 ) 410 .into_response(); 411 } 412 Err(e) => { 413 error!("DB error fetching deletion token: {:?}", e); 414 return ( 415 StatusCode::INTERNAL_SERVER_ERROR, 416 Json(json!({"error": "InternalError"})), 417 ) 418 .into_response(); 419 } 420 }; 421 422 if token_did != did { 423 return ( 424 StatusCode::BAD_REQUEST, 425 Json(json!({"error": "InvalidToken", "message": "Token does not match account"})), 426 ) 427 .into_response(); 428 } 429 430 if Utc::now() > expires_at { 431 let _ = sqlx::query!("DELETE FROM account_deletion_requests WHERE token = $1", token) 432 .execute(&state.db) 433 .await; 434 435 return ( 436 StatusCode::BAD_REQUEST, 437 Json(json!({"error": "ExpiredToken", "message": "Token has expired"})), 438 ) 439 .into_response(); 440 } 441 442 let mut tx = match state.db.begin().await { 443 Ok(tx) => tx, 444 Err(e) => { 445 error!("Failed to begin transaction: {:?}", e); 446 return ( 447 StatusCode::INTERNAL_SERVER_ERROR, 448 Json(json!({"error": "InternalError"})), 449 ) 450 .into_response(); 451 } 452 }; 453 454 let deletion_result: Result<(), sqlx::Error> = async { 455 sqlx::query!("DELETE FROM session_tokens WHERE did = $1", did) 456 .execute(&mut *tx) 457 .await?; 458 459 sqlx::query!("DELETE FROM records WHERE repo_id = $1", user_id) 460 .execute(&mut *tx) 461 .await?; 462 463 sqlx::query!("DELETE FROM repos WHERE user_id = $1", user_id) 464 .execute(&mut *tx) 465 .await?; 466 467 sqlx::query!("DELETE FROM blobs WHERE created_by_user = $1", user_id) 468 .execute(&mut *tx) 469 .await?; 470 471 sqlx::query!("DELETE FROM user_keys WHERE user_id = $1", user_id) 472 .execute(&mut *tx) 473 .await?; 474 475 sqlx::query!("DELETE FROM app_passwords WHERE user_id = $1", user_id) 476 .execute(&mut *tx) 477 .await?; 478 479 sqlx::query!("DELETE FROM account_deletion_requests WHERE did = $1", did) 480 .execute(&mut *tx) 481 .await?; 482 483 sqlx::query!("DELETE FROM users WHERE id = $1", user_id) 484 .execute(&mut *tx) 485 .await?; 486 487 Ok(()) 488 } 489 .await; 490 491 match deletion_result { 492 Ok(()) => { 493 if let Err(e) = tx.commit().await { 494 error!("Failed to commit account deletion transaction: {:?}", e); 495 return ( 496 StatusCode::INTERNAL_SERVER_ERROR, 497 Json(json!({"error": "InternalError"})), 498 ) 499 .into_response(); 500 } 501 info!("Account {} deleted successfully", did); 502 (StatusCode::OK, Json(json!({}))).into_response() 503 } 504 Err(e) => { 505 error!("DB error deleting account, rolling back: {:?}", e); 506 ( 507 StatusCode::INTERNAL_SERVER_ERROR, 508 Json(json!({"error": "InternalError"})), 509 ) 510 .into_response() 511 } 512 } 513}