this repo has no description
1use crate::state::AppState; 2use axum::{ 3 Json, 4 extract::{Query, State}, 5 http::StatusCode, 6 response::{IntoResponse, Response}, 7}; 8use bcrypt::{hash, verify, DEFAULT_COST}; 9use chrono::{Duration, Utc}; 10use rand::Rng; 11use serde::{Deserialize, Serialize}; 12use serde_json::json; 13use tracing::{error, info, warn}; 14use uuid::Uuid; 15 16#[derive(Deserialize)] 17pub struct GetServiceAuthParams { 18 pub aud: String, 19 pub lxm: Option<String>, 20 pub exp: Option<i64>, 21} 22 23#[derive(Serialize)] 24pub struct GetServiceAuthOutput { 25 pub token: String, 26} 27 28pub async fn get_service_auth( 29 State(state): State<AppState>, 30 headers: axum::http::HeaderMap, 31 Query(params): Query<GetServiceAuthParams>, 32) -> Response { 33 let auth_header = headers.get("Authorization"); 34 if auth_header.is_none() { 35 return ( 36 StatusCode::UNAUTHORIZED, 37 Json(json!({"error": "AuthenticationRequired"})), 38 ) 39 .into_response(); 40 } 41 42 let token = auth_header 43 .unwrap() 44 .to_str() 45 .unwrap_or("") 46 .replace("Bearer ", ""); 47 48 let session = sqlx::query!( 49 r#" 50 SELECT s.did, k.key_bytes 51 FROM sessions s 52 JOIN users u ON s.did = u.did 53 JOIN user_keys k ON u.id = k.user_id 54 WHERE s.access_jwt = $1 55 "#, 56 token 57 ) 58 .fetch_optional(&state.db) 59 .await; 60 61 let (did, key_bytes) = match session { 62 Ok(Some(row)) => (row.did, row.key_bytes), 63 Ok(None) => { 64 return ( 65 StatusCode::UNAUTHORIZED, 66 Json(json!({"error": "AuthenticationFailed"})), 67 ) 68 .into_response(); 69 } 70 Err(e) => { 71 error!("DB error in get_service_auth: {:?}", e); 72 return ( 73 StatusCode::INTERNAL_SERVER_ERROR, 74 Json(json!({"error": "InternalError"})), 75 ) 76 .into_response(); 77 } 78 }; 79 80 if let Err(_) = crate::auth::verify_token(&token, &key_bytes) { 81 return ( 82 StatusCode::UNAUTHORIZED, 83 Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})), 84 ) 85 .into_response(); 86 } 87 88 let lxm = params.lxm.as_deref().unwrap_or("*"); 89 90 let service_token = match crate::auth::create_service_token(&did, &params.aud, lxm, &key_bytes) 91 { 92 Ok(t) => t, 93 Err(e) => { 94 error!("Failed to create service token: {:?}", e); 95 return ( 96 StatusCode::INTERNAL_SERVER_ERROR, 97 Json(json!({"error": "InternalError"})), 98 ) 99 .into_response(); 100 } 101 }; 102 103 (StatusCode::OK, Json(GetServiceAuthOutput { token: service_token })).into_response() 104} 105 106#[derive(Deserialize)] 107pub struct CreateSessionInput { 108 pub identifier: String, 109 pub password: String, 110} 111 112#[derive(Serialize)] 113#[serde(rename_all = "camelCase")] 114pub struct CreateSessionOutput { 115 pub access_jwt: String, 116 pub refresh_jwt: String, 117 pub handle: String, 118 pub did: String, 119} 120 121pub async fn create_session( 122 State(state): State<AppState>, 123 Json(input): Json<CreateSessionInput>, 124) -> Response { 125 info!("create_session: identifier='{}'", input.identifier); 126 127 let user_row = sqlx::query!( 128 "SELECT u.id, u.did, u.handle, u.password_hash, k.key_bytes FROM users u JOIN user_keys k ON u.id = k.user_id WHERE u.handle = $1 OR u.email = $1", 129 input.identifier 130 ) 131 .fetch_optional(&state.db) 132 .await; 133 134 match user_row { 135 Ok(Some(row)) => { 136 let user_id = row.id; 137 let stored_hash = &row.password_hash; 138 let did = &row.did; 139 let handle = &row.handle; 140 let key_bytes = &row.key_bytes; 141 142 let password_valid = if verify(&input.password, stored_hash).unwrap_or(false) { 143 true 144 } else { 145 let app_pass_rows = sqlx::query!("SELECT password_hash FROM app_passwords WHERE user_id = $1", user_id) 146 .fetch_all(&state.db) 147 .await 148 .unwrap_or_default(); 149 150 app_pass_rows.iter().any(|row| { 151 verify(&input.password, &row.password_hash).unwrap_or(false) 152 }) 153 }; 154 155 if password_valid { 156 let access_jwt = match crate::auth::create_access_token(&did, &key_bytes) { 157 Ok(t) => t, 158 Err(e) => { 159 error!("Failed to create access token: {:?}", e); 160 return ( 161 StatusCode::INTERNAL_SERVER_ERROR, 162 Json(json!({"error": "InternalError"})), 163 ) 164 .into_response(); 165 } 166 }; 167 168 let refresh_jwt = match crate::auth::create_refresh_token(&did, &key_bytes) { 169 Ok(t) => t, 170 Err(e) => { 171 error!("Failed to create refresh token: {:?}", e); 172 return ( 173 StatusCode::INTERNAL_SERVER_ERROR, 174 Json(json!({"error": "InternalError"})), 175 ) 176 .into_response(); 177 } 178 }; 179 180 let session_insert = sqlx::query!( 181 "INSERT INTO sessions (access_jwt, refresh_jwt, did) VALUES ($1, $2, $3)", 182 access_jwt, 183 refresh_jwt, 184 did 185 ) 186 .execute(&state.db) 187 .await; 188 189 match session_insert { 190 Ok(_) => { 191 return ( 192 StatusCode::OK, 193 Json(CreateSessionOutput { 194 access_jwt, 195 refresh_jwt, 196 handle: handle.clone(), 197 did: did.clone(), 198 }), 199 ) 200 .into_response(); 201 } 202 Err(e) => { 203 error!("Failed to insert session: {:?}", e); 204 return ( 205 StatusCode::INTERNAL_SERVER_ERROR, 206 Json(json!({"error": "InternalError"})), 207 ) 208 .into_response(); 209 } 210 } 211 } else { 212 warn!( 213 "Password verification failed for identifier: {}", 214 input.identifier 215 ); 216 } 217 } 218 Ok(None) => { 219 warn!("User not found for identifier: {}", input.identifier); 220 } 221 Err(e) => { 222 error!("Database error fetching user: {:?}", e); 223 return ( 224 StatusCode::INTERNAL_SERVER_ERROR, 225 Json(json!({"error": "InternalError"})), 226 ) 227 .into_response(); 228 } 229 } 230 231 ( 232 StatusCode::UNAUTHORIZED, 233 Json(json!({"error": "AuthenticationFailed", "message": "Invalid identifier or password"})), 234 ) 235 .into_response() 236} 237 238pub async fn get_session( 239 State(state): State<AppState>, 240 headers: axum::http::HeaderMap, 241) -> Response { 242 let auth_header = headers.get("Authorization"); 243 if auth_header.is_none() { 244 return ( 245 StatusCode::UNAUTHORIZED, 246 Json(json!({"error": "AuthenticationRequired"})), 247 ) 248 .into_response(); 249 } 250 251 let token = auth_header 252 .unwrap() 253 .to_str() 254 .unwrap_or("") 255 .replace("Bearer ", ""); 256 257 let result = sqlx::query!( 258 r#" 259 SELECT u.handle, u.did, u.email, k.key_bytes 260 FROM sessions s 261 JOIN users u ON s.did = u.did 262 JOIN user_keys k ON u.id = k.user_id 263 WHERE s.access_jwt = $1 264 "#, 265 token 266 ) 267 .fetch_optional(&state.db) 268 .await; 269 270 match result { 271 Ok(Some(row)) => { 272 if let Err(_) = crate::auth::verify_token(&token, &row.key_bytes) { 273 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"}))).into_response(); 274 } 275 276 return ( 277 StatusCode::OK, 278 Json(json!({ 279 "handle": row.handle, 280 "did": row.did, 281 "email": row.email, 282 "didDoc": {} 283 })), 284 ) 285 .into_response(); 286 } 287 Ok(None) => { 288 return ( 289 StatusCode::UNAUTHORIZED, 290 Json(json!({"error": "AuthenticationFailed"})), 291 ) 292 .into_response(); 293 } 294 Err(e) => { 295 error!("Database error in get_session: {:?}", e); 296 return ( 297 StatusCode::INTERNAL_SERVER_ERROR, 298 Json(json!({"error": "InternalError"})), 299 ) 300 .into_response(); 301 } 302 } 303} 304 305pub async fn delete_session( 306 State(state): State<AppState>, 307 headers: axum::http::HeaderMap, 308) -> Response { 309 let auth_header = headers.get("Authorization"); 310 if auth_header.is_none() { 311 return ( 312 StatusCode::UNAUTHORIZED, 313 Json(json!({"error": "AuthenticationRequired"})), 314 ) 315 .into_response(); 316 } 317 318 let token = auth_header 319 .unwrap() 320 .to_str() 321 .unwrap_or("") 322 .replace("Bearer ", ""); 323 324 let result = sqlx::query!("DELETE FROM sessions WHERE access_jwt = $1", token) 325 .execute(&state.db) 326 .await; 327 328 match result { 329 Ok(res) => { 330 if res.rows_affected() > 0 { 331 return (StatusCode::OK, Json(json!({}))).into_response(); 332 } 333 } 334 Err(e) => { 335 error!("Database error in delete_session: {:?}", e); 336 } 337 } 338 339 ( 340 StatusCode::UNAUTHORIZED, 341 Json(json!({"error": "AuthenticationFailed"})), 342 ) 343 .into_response() 344} 345 346pub async fn request_account_delete( 347 State(state): State<AppState>, 348 headers: axum::http::HeaderMap, 349) -> Response { 350 let auth_header = headers.get("Authorization"); 351 if auth_header.is_none() { 352 return ( 353 StatusCode::UNAUTHORIZED, 354 Json(json!({"error": "AuthenticationRequired"})), 355 ) 356 .into_response(); 357 } 358 359 let token = auth_header 360 .unwrap() 361 .to_str() 362 .unwrap_or("") 363 .replace("Bearer ", ""); 364 365 let session = sqlx::query!( 366 r#" 367 SELECT s.did, u.id as user_id, u.email, u.handle, k.key_bytes 368 FROM sessions s 369 JOIN users u ON s.did = u.did 370 JOIN user_keys k ON u.id = k.user_id 371 WHERE s.access_jwt = $1 372 "#, 373 token 374 ) 375 .fetch_optional(&state.db) 376 .await; 377 378 let (did, user_id, email, handle, key_bytes) = match session { 379 Ok(Some(row)) => (row.did, row.user_id, row.email, row.handle, row.key_bytes), 380 Ok(None) => { 381 return ( 382 StatusCode::UNAUTHORIZED, 383 Json(json!({"error": "AuthenticationFailed"})), 384 ) 385 .into_response(); 386 } 387 Err(e) => { 388 error!("DB error in request_account_delete: {:?}", e); 389 return ( 390 StatusCode::INTERNAL_SERVER_ERROR, 391 Json(json!({"error": "InternalError"})), 392 ) 393 .into_response(); 394 } 395 }; 396 397 if let Err(_) = crate::auth::verify_token(&token, &key_bytes) { 398 return ( 399 StatusCode::UNAUTHORIZED, 400 Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})), 401 ) 402 .into_response(); 403 } 404 405 let confirmation_token = Uuid::new_v4().to_string(); 406 let expires_at = Utc::now() + Duration::minutes(15); 407 408 let insert = sqlx::query!( 409 "INSERT INTO account_deletion_requests (token, did, expires_at) VALUES ($1, $2, $3)", 410 confirmation_token, 411 did, 412 expires_at 413 ) 414 .execute(&state.db) 415 .await; 416 417 if let Err(e) = insert { 418 error!("DB error creating deletion token: {:?}", e); 419 return ( 420 StatusCode::INTERNAL_SERVER_ERROR, 421 Json(json!({"error": "InternalError"})), 422 ) 423 .into_response(); 424 } 425 426 let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 427 if let Err(e) = crate::notifications::enqueue_account_deletion( 428 &state.db, 429 user_id, 430 &email, 431 &handle, 432 &confirmation_token, 433 &hostname, 434 ) 435 .await 436 { 437 warn!("Failed to enqueue account deletion notification: {:?}", e); 438 } 439 440 info!("Account deletion requested for user {}", did); 441 442 (StatusCode::OK, Json(json!({}))).into_response() 443} 444 445pub async fn refresh_session( 446 State(state): State<AppState>, 447 headers: axum::http::HeaderMap, 448) -> Response { 449 let auth_header = headers.get("Authorization"); 450 if auth_header.is_none() { 451 return ( 452 StatusCode::UNAUTHORIZED, 453 Json(json!({"error": "AuthenticationRequired"})), 454 ) 455 .into_response(); 456 } 457 458 let refresh_token = auth_header 459 .unwrap() 460 .to_str() 461 .unwrap_or("") 462 .replace("Bearer ", ""); 463 464 let session = sqlx::query!( 465 "SELECT s.did, k.key_bytes FROM sessions s JOIN users u ON s.did = u.did JOIN user_keys k ON u.id = k.user_id WHERE s.refresh_jwt = $1", 466 refresh_token 467 ) 468 .fetch_optional(&state.db) 469 .await; 470 471 match session { 472 Ok(Some(session_row)) => { 473 let did = &session_row.did; 474 let key_bytes = &session_row.key_bytes; 475 476 if let Err(_) = crate::auth::verify_token(&refresh_token, &key_bytes) { 477 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid refresh token signature"}))).into_response(); 478 } 479 480 let new_access_jwt = match crate::auth::create_access_token(&did, &key_bytes) { 481 Ok(t) => t, 482 Err(e) => { 483 error!("Failed to create access token: {:?}", e); 484 return ( 485 StatusCode::INTERNAL_SERVER_ERROR, 486 Json(json!({"error": "InternalError"})), 487 ) 488 .into_response(); 489 } 490 }; 491 let new_refresh_jwt = match crate::auth::create_refresh_token(&did, &key_bytes) { 492 Ok(t) => t, 493 Err(e) => { 494 error!("Failed to create refresh token: {:?}", e); 495 return ( 496 StatusCode::INTERNAL_SERVER_ERROR, 497 Json(json!({"error": "InternalError"})), 498 ) 499 .into_response(); 500 } 501 }; 502 503 let update = sqlx::query!( 504 "UPDATE sessions SET access_jwt = $1, refresh_jwt = $2 WHERE refresh_jwt = $3", 505 new_access_jwt, 506 new_refresh_jwt, 507 refresh_token 508 ) 509 .execute(&state.db) 510 .await; 511 512 match update { 513 Ok(_) => { 514 let user = sqlx::query!("SELECT handle FROM users WHERE did = $1", did) 515 .fetch_optional(&state.db) 516 .await; 517 518 match user { 519 Ok(Some(u)) => { 520 return ( 521 StatusCode::OK, 522 Json(json!({ 523 "accessJwt": new_access_jwt, 524 "refreshJwt": new_refresh_jwt, 525 "handle": u.handle, 526 "did": did 527 })), 528 ) 529 .into_response(); 530 } 531 Ok(None) => { 532 error!("User not found for existing session: {}", did); 533 return ( 534 StatusCode::INTERNAL_SERVER_ERROR, 535 Json(json!({"error": "InternalError"})), 536 ) 537 .into_response(); 538 } 539 Err(e) => { 540 error!("Database error fetching user: {:?}", e); 541 return ( 542 StatusCode::INTERNAL_SERVER_ERROR, 543 Json(json!({"error": "InternalError"})), 544 ) 545 .into_response(); 546 } 547 } 548 } 549 Err(e) => { 550 error!("Database error updating session: {:?}", e); 551 return ( 552 StatusCode::INTERNAL_SERVER_ERROR, 553 Json(json!({"error": "InternalError"})), 554 ) 555 .into_response(); 556 } 557 } 558 } 559 Ok(None) => { 560 return ( 561 StatusCode::UNAUTHORIZED, 562 Json(json!({"error": "AuthenticationFailed", "message": "Invalid refresh token"})), 563 ) 564 .into_response(); 565 } 566 Err(e) => { 567 error!("Database error fetching session: {:?}", e); 568 return ( 569 StatusCode::INTERNAL_SERVER_ERROR, 570 Json(json!({"error": "InternalError"})), 571 ) 572 .into_response(); 573 } 574 } 575} 576 577#[derive(Serialize)] 578#[serde(rename_all = "camelCase")] 579pub struct CheckAccountStatusOutput { 580 pub activated: bool, 581 pub valid_did: bool, 582 pub repo_commit: String, 583 pub repo_rev: String, 584 pub repo_blocks: i64, 585 pub indexed_records: i64, 586 pub private_state_values: i64, 587 pub expected_blobs: i64, 588 pub imported_blobs: i64, 589} 590 591pub async fn check_account_status( 592 State(state): State<AppState>, 593 headers: axum::http::HeaderMap, 594) -> Response { 595 let auth_header = headers.get("Authorization"); 596 if auth_header.is_none() { 597 return ( 598 StatusCode::UNAUTHORIZED, 599 Json(json!({"error": "AuthenticationRequired"})), 600 ) 601 .into_response(); 602 } 603 604 let token = auth_header 605 .unwrap() 606 .to_str() 607 .unwrap_or("") 608 .replace("Bearer ", ""); 609 610 let session = sqlx::query!( 611 r#" 612 SELECT s.did, k.key_bytes, u.id as user_id 613 FROM sessions s 614 JOIN users u ON s.did = u.did 615 JOIN user_keys k ON u.id = k.user_id 616 WHERE s.access_jwt = $1 617 "#, 618 token 619 ) 620 .fetch_optional(&state.db) 621 .await; 622 623 let (did, key_bytes, user_id) = match session { 624 Ok(Some(row)) => (row.did, row.key_bytes, row.user_id), 625 Ok(None) => { 626 return ( 627 StatusCode::UNAUTHORIZED, 628 Json(json!({"error": "AuthenticationFailed"})), 629 ) 630 .into_response(); 631 } 632 Err(e) => { 633 error!("DB error in check_account_status: {:?}", e); 634 return ( 635 StatusCode::INTERNAL_SERVER_ERROR, 636 Json(json!({"error": "InternalError"})), 637 ) 638 .into_response(); 639 } 640 }; 641 642 if let Err(_) = crate::auth::verify_token(&token, &key_bytes) { 643 return ( 644 StatusCode::UNAUTHORIZED, 645 Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})), 646 ) 647 .into_response(); 648 } 649 650 let user_status = sqlx::query!("SELECT deactivated_at FROM users WHERE did = $1", did) 651 .fetch_optional(&state.db) 652 .await; 653 654 let deactivated_at = match user_status { 655 Ok(Some(row)) => row.deactivated_at, 656 _ => None, 657 }; 658 659 let repo_result = sqlx::query!("SELECT repo_root_cid FROM repos WHERE user_id = $1", user_id) 660 .fetch_optional(&state.db) 661 .await; 662 663 let repo_commit = match repo_result { 664 Ok(Some(row)) => row.repo_root_cid, 665 _ => String::new(), 666 }; 667 668 let record_count: i64 = sqlx::query_scalar!("SELECT COUNT(*) FROM records WHERE repo_id = $1", user_id) 669 .fetch_one(&state.db) 670 .await 671 .unwrap_or(Some(0)) 672 .unwrap_or(0); 673 674 let blob_count: i64 = 675 sqlx::query_scalar!("SELECT COUNT(*) FROM blobs WHERE created_by_user = $1", user_id) 676 .fetch_one(&state.db) 677 .await 678 .unwrap_or(Some(0)) 679 .unwrap_or(0); 680 681 let valid_did = did.starts_with("did:"); 682 683 ( 684 StatusCode::OK, 685 Json(CheckAccountStatusOutput { 686 activated: deactivated_at.is_none(), 687 valid_did, 688 repo_commit: repo_commit.clone(), 689 repo_rev: chrono::Utc::now().timestamp_millis().to_string(), 690 repo_blocks: 0, 691 indexed_records: record_count, 692 private_state_values: 0, 693 expected_blobs: blob_count, 694 imported_blobs: blob_count, 695 }), 696 ) 697 .into_response() 698} 699 700pub async fn activate_account( 701 State(state): State<AppState>, 702 headers: axum::http::HeaderMap, 703) -> Response { 704 let auth_header = headers.get("Authorization"); 705 if auth_header.is_none() { 706 return ( 707 StatusCode::UNAUTHORIZED, 708 Json(json!({"error": "AuthenticationRequired"})), 709 ) 710 .into_response(); 711 } 712 713 let token = auth_header 714 .unwrap() 715 .to_str() 716 .unwrap_or("") 717 .replace("Bearer ", ""); 718 719 let session = sqlx::query!( 720 r#" 721 SELECT s.did, k.key_bytes 722 FROM sessions s 723 JOIN users u ON s.did = u.did 724 JOIN user_keys k ON u.id = k.user_id 725 WHERE s.access_jwt = $1 726 "#, 727 token 728 ) 729 .fetch_optional(&state.db) 730 .await; 731 732 let (did, key_bytes) = match session { 733 Ok(Some(row)) => (row.did, row.key_bytes), 734 Ok(None) => { 735 return ( 736 StatusCode::UNAUTHORIZED, 737 Json(json!({"error": "AuthenticationFailed"})), 738 ) 739 .into_response(); 740 } 741 Err(e) => { 742 error!("DB error in activate_account: {:?}", e); 743 return ( 744 StatusCode::INTERNAL_SERVER_ERROR, 745 Json(json!({"error": "InternalError"})), 746 ) 747 .into_response(); 748 } 749 }; 750 751 if let Err(_) = crate::auth::verify_token(&token, &key_bytes) { 752 return ( 753 StatusCode::UNAUTHORIZED, 754 Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})), 755 ) 756 .into_response(); 757 } 758 759 let result = sqlx::query!("UPDATE users SET deactivated_at = NULL WHERE did = $1", did) 760 .execute(&state.db) 761 .await; 762 763 match result { 764 Ok(_) => (StatusCode::OK, Json(json!({}))).into_response(), 765 Err(e) => { 766 error!("DB error activating account: {:?}", e); 767 ( 768 StatusCode::INTERNAL_SERVER_ERROR, 769 Json(json!({"error": "InternalError"})), 770 ) 771 .into_response() 772 } 773 } 774} 775 776#[derive(Deserialize)] 777#[serde(rename_all = "camelCase")] 778pub struct DeactivateAccountInput { 779 pub delete_after: Option<String>, 780} 781 782pub async fn deactivate_account( 783 State(state): State<AppState>, 784 headers: axum::http::HeaderMap, 785 Json(_input): Json<DeactivateAccountInput>, 786) -> Response { 787 let auth_header = headers.get("Authorization"); 788 if auth_header.is_none() { 789 return ( 790 StatusCode::UNAUTHORIZED, 791 Json(json!({"error": "AuthenticationRequired"})), 792 ) 793 .into_response(); 794 } 795 796 let token = auth_header 797 .unwrap() 798 .to_str() 799 .unwrap_or("") 800 .replace("Bearer ", ""); 801 802 let session = sqlx::query!( 803 r#" 804 SELECT s.did, k.key_bytes 805 FROM sessions s 806 JOIN users u ON s.did = u.did 807 JOIN user_keys k ON u.id = k.user_id 808 WHERE s.access_jwt = $1 809 "#, 810 token 811 ) 812 .fetch_optional(&state.db) 813 .await; 814 815 let (did, key_bytes) = match session { 816 Ok(Some(row)) => (row.did, row.key_bytes), 817 Ok(None) => { 818 return ( 819 StatusCode::UNAUTHORIZED, 820 Json(json!({"error": "AuthenticationFailed"})), 821 ) 822 .into_response(); 823 } 824 Err(e) => { 825 error!("DB error in deactivate_account: {:?}", e); 826 return ( 827 StatusCode::INTERNAL_SERVER_ERROR, 828 Json(json!({"error": "InternalError"})), 829 ) 830 .into_response(); 831 } 832 }; 833 834 if let Err(_) = crate::auth::verify_token(&token, &key_bytes) { 835 return ( 836 StatusCode::UNAUTHORIZED, 837 Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})), 838 ) 839 .into_response(); 840 } 841 842 let result = sqlx::query!("UPDATE users SET deactivated_at = NOW() WHERE did = $1", did) 843 .execute(&state.db) 844 .await; 845 846 match result { 847 Ok(_) => (StatusCode::OK, Json(json!({}))).into_response(), 848 Err(e) => { 849 error!("DB error deactivating account: {:?}", e); 850 ( 851 StatusCode::INTERNAL_SERVER_ERROR, 852 Json(json!({"error": "InternalError"})), 853 ) 854 .into_response() 855 } 856 } 857} 858 859#[derive(Serialize)] 860#[serde(rename_all = "camelCase")] 861pub struct AppPassword { 862 pub name: String, 863 pub created_at: String, 864 pub privileged: bool, 865} 866 867#[derive(Serialize)] 868pub struct ListAppPasswordsOutput { 869 pub passwords: Vec<AppPassword>, 870} 871 872pub async fn list_app_passwords( 873 State(state): State<AppState>, 874 headers: axum::http::HeaderMap, 875) -> Response { 876 let auth_header = headers.get("Authorization"); 877 if auth_header.is_none() { 878 return ( 879 StatusCode::UNAUTHORIZED, 880 Json(json!({"error": "AuthenticationRequired"})), 881 ) 882 .into_response(); 883 } 884 885 let token = auth_header 886 .unwrap() 887 .to_str() 888 .unwrap_or("") 889 .replace("Bearer ", ""); 890 891 let session = sqlx::query!( 892 r#" 893 SELECT s.did, k.key_bytes, u.id as user_id 894 FROM sessions s 895 JOIN users u ON s.did = u.did 896 JOIN user_keys k ON u.id = k.user_id 897 WHERE s.access_jwt = $1 898 "#, 899 token 900 ) 901 .fetch_optional(&state.db) 902 .await; 903 904 let (_did, key_bytes, user_id) = match session { 905 Ok(Some(row)) => (row.did, row.key_bytes, row.user_id), 906 Ok(None) => { 907 return ( 908 StatusCode::UNAUTHORIZED, 909 Json(json!({"error": "AuthenticationFailed"})), 910 ) 911 .into_response(); 912 } 913 Err(e) => { 914 error!("DB error in list_app_passwords: {:?}", e); 915 return ( 916 StatusCode::INTERNAL_SERVER_ERROR, 917 Json(json!({"error": "InternalError"})), 918 ) 919 .into_response(); 920 } 921 }; 922 923 if let Err(_) = crate::auth::verify_token(&token, &key_bytes) { 924 return ( 925 StatusCode::UNAUTHORIZED, 926 Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})), 927 ) 928 .into_response(); 929 } 930 931 let result = sqlx::query!("SELECT name, created_at, privileged FROM app_passwords WHERE user_id = $1 ORDER BY created_at DESC", user_id) 932 .fetch_all(&state.db) 933 .await; 934 935 match result { 936 Ok(rows) => { 937 let passwords: Vec<AppPassword> = rows 938 .iter() 939 .map(|row| { 940 AppPassword { 941 name: row.name.clone(), 942 created_at: row.created_at.to_rfc3339(), 943 privileged: row.privileged, 944 } 945 }) 946 .collect(); 947 948 (StatusCode::OK, Json(ListAppPasswordsOutput { passwords })).into_response() 949 } 950 Err(e) => { 951 error!("DB error listing app passwords: {:?}", e); 952 ( 953 StatusCode::INTERNAL_SERVER_ERROR, 954 Json(json!({"error": "InternalError"})), 955 ) 956 .into_response() 957 } 958 } 959} 960 961#[derive(Deserialize)] 962pub struct CreateAppPasswordInput { 963 pub name: String, 964 pub privileged: Option<bool>, 965} 966 967#[derive(Serialize)] 968#[serde(rename_all = "camelCase")] 969pub struct CreateAppPasswordOutput { 970 pub name: String, 971 pub password: String, 972 pub created_at: String, 973 pub privileged: bool, 974} 975 976pub async fn create_app_password( 977 State(state): State<AppState>, 978 headers: axum::http::HeaderMap, 979 Json(input): Json<CreateAppPasswordInput>, 980) -> Response { 981 let auth_header = headers.get("Authorization"); 982 if auth_header.is_none() { 983 return ( 984 StatusCode::UNAUTHORIZED, 985 Json(json!({"error": "AuthenticationRequired"})), 986 ) 987 .into_response(); 988 } 989 990 let token = auth_header 991 .unwrap() 992 .to_str() 993 .unwrap_or("") 994 .replace("Bearer ", ""); 995 996 let session = sqlx::query!( 997 r#" 998 SELECT s.did, k.key_bytes, u.id as user_id 999 FROM sessions s 1000 JOIN users u ON s.did = u.did 1001 JOIN user_keys k ON u.id = k.user_id 1002 WHERE s.access_jwt = $1 1003 "#, 1004 token 1005 ) 1006 .fetch_optional(&state.db) 1007 .await; 1008 1009 let (_did, key_bytes, user_id) = match session { 1010 Ok(Some(row)) => (row.did, row.key_bytes, row.user_id), 1011 Ok(None) => { 1012 return ( 1013 StatusCode::UNAUTHORIZED, 1014 Json(json!({"error": "AuthenticationFailed"})), 1015 ) 1016 .into_response(); 1017 } 1018 Err(e) => { 1019 error!("DB error in create_app_password: {:?}", e); 1020 return ( 1021 StatusCode::INTERNAL_SERVER_ERROR, 1022 Json(json!({"error": "InternalError"})), 1023 ) 1024 .into_response(); 1025 } 1026 }; 1027 1028 if let Err(_) = crate::auth::verify_token(&token, &key_bytes) { 1029 return ( 1030 StatusCode::UNAUTHORIZED, 1031 Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})), 1032 ) 1033 .into_response(); 1034 } 1035 1036 let name = input.name.trim(); 1037 if name.is_empty() { 1038 return ( 1039 StatusCode::BAD_REQUEST, 1040 Json(json!({"error": "InvalidRequest", "message": "name is required"})), 1041 ) 1042 .into_response(); 1043 } 1044 1045 let existing = sqlx::query!("SELECT id FROM app_passwords WHERE user_id = $1 AND name = $2", user_id, name) 1046 .fetch_optional(&state.db) 1047 .await; 1048 1049 if let Ok(Some(_)) = existing { 1050 return ( 1051 StatusCode::BAD_REQUEST, 1052 Json(json!({"error": "DuplicateAppPassword", "message": "App password with this name already exists"})), 1053 ) 1054 .into_response(); 1055 } 1056 1057 let password: String = (0..4) 1058 .map(|_| { 1059 use rand::Rng; 1060 let mut rng = rand::thread_rng(); 1061 let chars: Vec<char> = "abcdefghijklmnopqrstuvwxyz234567".chars().collect(); 1062 (0..4).map(|_| chars[rng.gen_range(0..chars.len())]).collect::<String>() 1063 }) 1064 .collect::<Vec<String>>() 1065 .join("-"); 1066 1067 let password_hash = match bcrypt::hash(&password, bcrypt::DEFAULT_COST) { 1068 Ok(h) => h, 1069 Err(e) => { 1070 error!("Failed to hash password: {:?}", e); 1071 return ( 1072 StatusCode::INTERNAL_SERVER_ERROR, 1073 Json(json!({"error": "InternalError"})), 1074 ) 1075 .into_response(); 1076 } 1077 }; 1078 1079 let privileged = input.privileged.unwrap_or(false); 1080 let created_at = chrono::Utc::now(); 1081 1082 let result = sqlx::query!( 1083 "INSERT INTO app_passwords (user_id, name, password_hash, created_at, privileged) VALUES ($1, $2, $3, $4, $5)", 1084 user_id, 1085 name, 1086 password_hash, 1087 created_at, 1088 privileged 1089 ) 1090 .execute(&state.db) 1091 .await; 1092 1093 match result { 1094 Ok(_) => ( 1095 StatusCode::OK, 1096 Json(CreateAppPasswordOutput { 1097 name: name.to_string(), 1098 password, 1099 created_at: created_at.to_rfc3339(), 1100 privileged, 1101 }), 1102 ) 1103 .into_response(), 1104 Err(e) => { 1105 error!("DB error creating app password: {:?}", e); 1106 ( 1107 StatusCode::INTERNAL_SERVER_ERROR, 1108 Json(json!({"error": "InternalError"})), 1109 ) 1110 .into_response() 1111 } 1112 } 1113} 1114 1115#[derive(Deserialize)] 1116pub struct RevokeAppPasswordInput { 1117 pub name: String, 1118} 1119 1120pub async fn revoke_app_password( 1121 State(state): State<AppState>, 1122 headers: axum::http::HeaderMap, 1123 Json(input): Json<RevokeAppPasswordInput>, 1124) -> Response { 1125 let auth_header = headers.get("Authorization"); 1126 if auth_header.is_none() { 1127 return ( 1128 StatusCode::UNAUTHORIZED, 1129 Json(json!({"error": "AuthenticationRequired"})), 1130 ) 1131 .into_response(); 1132 } 1133 1134 let token = auth_header 1135 .unwrap() 1136 .to_str() 1137 .unwrap_or("") 1138 .replace("Bearer ", ""); 1139 1140 let session = sqlx::query!( 1141 r#" 1142 SELECT s.did, k.key_bytes, u.id as user_id 1143 FROM sessions s 1144 JOIN users u ON s.did = u.did 1145 JOIN user_keys k ON u.id = k.user_id 1146 WHERE s.access_jwt = $1 1147 "#, 1148 token 1149 ) 1150 .fetch_optional(&state.db) 1151 .await; 1152 1153 let (_did, key_bytes, user_id) = match session { 1154 Ok(Some(row)) => (row.did, row.key_bytes, row.user_id), 1155 Ok(None) => { 1156 return ( 1157 StatusCode::UNAUTHORIZED, 1158 Json(json!({"error": "AuthenticationFailed"})), 1159 ) 1160 .into_response(); 1161 } 1162 Err(e) => { 1163 error!("DB error in revoke_app_password: {:?}", e); 1164 return ( 1165 StatusCode::INTERNAL_SERVER_ERROR, 1166 Json(json!({"error": "InternalError"})), 1167 ) 1168 .into_response(); 1169 } 1170 }; 1171 1172 if let Err(_) = crate::auth::verify_token(&token, &key_bytes) { 1173 return ( 1174 StatusCode::UNAUTHORIZED, 1175 Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})), 1176 ) 1177 .into_response(); 1178 } 1179 1180 let name = input.name.trim(); 1181 if name.is_empty() { 1182 return ( 1183 StatusCode::BAD_REQUEST, 1184 Json(json!({"error": "InvalidRequest", "message": "name is required"})), 1185 ) 1186 .into_response(); 1187 } 1188 1189 let result = sqlx::query!("DELETE FROM app_passwords WHERE user_id = $1 AND name = $2", user_id, name) 1190 .execute(&state.db) 1191 .await; 1192 1193 match result { 1194 Ok(r) => { 1195 if r.rows_affected() == 0 { 1196 return ( 1197 StatusCode::NOT_FOUND, 1198 Json(json!({"error": "AppPasswordNotFound", "message": "App password not found"})), 1199 ) 1200 .into_response(); 1201 } 1202 (StatusCode::OK, Json(json!({}))).into_response() 1203 } 1204 Err(e) => { 1205 error!("DB error revoking app password: {:?}", e); 1206 ( 1207 StatusCode::INTERNAL_SERVER_ERROR, 1208 Json(json!({"error": "InternalError"})), 1209 ) 1210 .into_response() 1211 } 1212 } 1213} 1214 1215fn generate_reset_code() -> String { 1216 let mut rng = rand::thread_rng(); 1217 let chars: Vec<char> = "abcdefghijklmnopqrstuvwxyz234567".chars().collect(); 1218 let part1: String = (0..5).map(|_| chars[rng.gen_range(0..chars.len())]).collect(); 1219 let part2: String = (0..5).map(|_| chars[rng.gen_range(0..chars.len())]).collect(); 1220 format!("{}-{}", part1, part2) 1221} 1222 1223#[derive(Deserialize)] 1224pub struct RequestPasswordResetInput { 1225 pub email: String, 1226} 1227 1228pub async fn request_password_reset( 1229 State(state): State<AppState>, 1230 Json(input): Json<RequestPasswordResetInput>, 1231) -> Response { 1232 let email = input.email.trim().to_lowercase(); 1233 if email.is_empty() { 1234 return ( 1235 StatusCode::BAD_REQUEST, 1236 Json(json!({"error": "InvalidRequest", "message": "email is required"})), 1237 ) 1238 .into_response(); 1239 } 1240 1241 let user = sqlx::query!( 1242 "SELECT id, handle FROM users WHERE LOWER(email) = $1", 1243 email 1244 ) 1245 .fetch_optional(&state.db) 1246 .await; 1247 1248 let (user_id, handle) = match user { 1249 Ok(Some(row)) => (row.id, row.handle), 1250 Ok(None) => { 1251 info!("Password reset requested for unknown email: {}", email); 1252 return (StatusCode::OK, Json(json!({}))).into_response(); 1253 } 1254 Err(e) => { 1255 error!("DB error in request_password_reset: {:?}", e); 1256 return ( 1257 StatusCode::INTERNAL_SERVER_ERROR, 1258 Json(json!({"error": "InternalError"})), 1259 ) 1260 .into_response(); 1261 } 1262 }; 1263 1264 let code = generate_reset_code(); 1265 let expires_at = Utc::now() + Duration::minutes(10); 1266 1267 let update = sqlx::query!( 1268 "UPDATE users SET password_reset_code = $1, password_reset_code_expires_at = $2 WHERE id = $3", 1269 code, 1270 expires_at, 1271 user_id 1272 ) 1273 .execute(&state.db) 1274 .await; 1275 1276 if let Err(e) = update { 1277 error!("DB error setting reset code: {:?}", e); 1278 return ( 1279 StatusCode::INTERNAL_SERVER_ERROR, 1280 Json(json!({"error": "InternalError"})), 1281 ) 1282 .into_response(); 1283 } 1284 1285 let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 1286 if let Err(e) = crate::notifications::enqueue_password_reset( 1287 &state.db, 1288 user_id, 1289 &email, 1290 &handle, 1291 &code, 1292 &hostname, 1293 ) 1294 .await 1295 { 1296 warn!("Failed to enqueue password reset notification: {:?}", e); 1297 } 1298 1299 info!("Password reset requested for user {}", user_id); 1300 1301 (StatusCode::OK, Json(json!({}))).into_response() 1302} 1303 1304#[derive(Deserialize)] 1305pub struct ResetPasswordInput { 1306 pub token: String, 1307 pub password: String, 1308} 1309 1310pub async fn reset_password( 1311 State(state): State<AppState>, 1312 Json(input): Json<ResetPasswordInput>, 1313) -> Response { 1314 let token = input.token.trim(); 1315 let password = &input.password; 1316 1317 if token.is_empty() { 1318 return ( 1319 StatusCode::BAD_REQUEST, 1320 Json(json!({"error": "InvalidToken", "message": "token is required"})), 1321 ) 1322 .into_response(); 1323 } 1324 1325 if password.is_empty() { 1326 return ( 1327 StatusCode::BAD_REQUEST, 1328 Json(json!({"error": "InvalidRequest", "message": "password is required"})), 1329 ) 1330 .into_response(); 1331 } 1332 1333 let user = sqlx::query!( 1334 "SELECT id, password_reset_code, password_reset_code_expires_at FROM users WHERE password_reset_code = $1", 1335 token 1336 ) 1337 .fetch_optional(&state.db) 1338 .await; 1339 1340 let (user_id, expires_at) = match user { 1341 Ok(Some(row)) => { 1342 let expires = row.password_reset_code_expires_at; 1343 (row.id, expires) 1344 } 1345 Ok(None) => { 1346 return ( 1347 StatusCode::BAD_REQUEST, 1348 Json(json!({"error": "InvalidToken", "message": "Invalid or expired token"})), 1349 ) 1350 .into_response(); 1351 } 1352 Err(e) => { 1353 error!("DB error in reset_password: {:?}", e); 1354 return ( 1355 StatusCode::INTERNAL_SERVER_ERROR, 1356 Json(json!({"error": "InternalError"})), 1357 ) 1358 .into_response(); 1359 } 1360 }; 1361 1362 if let Some(exp) = expires_at { 1363 if Utc::now() > exp { 1364 let _ = sqlx::query!( 1365 "UPDATE users SET password_reset_code = NULL, password_reset_code_expires_at = NULL WHERE id = $1", 1366 user_id 1367 ) 1368 .execute(&state.db) 1369 .await; 1370 1371 return ( 1372 StatusCode::BAD_REQUEST, 1373 Json(json!({"error": "ExpiredToken", "message": "Token has expired"})), 1374 ) 1375 .into_response(); 1376 } 1377 } else { 1378 return ( 1379 StatusCode::BAD_REQUEST, 1380 Json(json!({"error": "InvalidToken", "message": "Invalid or expired token"})), 1381 ) 1382 .into_response(); 1383 } 1384 1385 let password_hash = match hash(password, DEFAULT_COST) { 1386 Ok(h) => h, 1387 Err(e) => { 1388 error!("Failed to hash password: {:?}", e); 1389 return ( 1390 StatusCode::INTERNAL_SERVER_ERROR, 1391 Json(json!({"error": "InternalError"})), 1392 ) 1393 .into_response(); 1394 } 1395 }; 1396 1397 let update = sqlx::query!( 1398 "UPDATE users SET password_hash = $1, password_reset_code = NULL, password_reset_code_expires_at = NULL WHERE id = $2", 1399 password_hash, 1400 user_id 1401 ) 1402 .execute(&state.db) 1403 .await; 1404 1405 if let Err(e) = update { 1406 error!("DB error updating password: {:?}", e); 1407 return ( 1408 StatusCode::INTERNAL_SERVER_ERROR, 1409 Json(json!({"error": "InternalError"})), 1410 ) 1411 .into_response(); 1412 } 1413 1414 let _ = sqlx::query!("DELETE FROM sessions WHERE did = (SELECT did FROM users WHERE id = $1)", user_id) 1415 .execute(&state.db) 1416 .await; 1417 1418 info!("Password reset completed for user {}", user_id); 1419 1420 (StatusCode::OK, Json(json!({}))).into_response() 1421}