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