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