Our Personal Data Server from scratch!
0

Configure Feed

Select the types of activity you want to include in your feed.

1use super::*; 2 3pub async fn authorize_get( 4 State(state): State<AppState>, 5 headers: HeaderMap, 6 Query(query): Query<AuthorizeQuery>, 7) -> Response { 8 let request_uri = match query.request_uri { 9 Some(uri) => uri, 10 None => { 11 if wants_json(&headers) { 12 return ( 13 StatusCode::BAD_REQUEST, 14 Json(serde_json::json!({ 15 "error": "invalid_request", 16 "error_description": "Missing request_uri parameter. Use PAR to initiate authorization." 17 })), 18 ).into_response(); 19 } 20 return redirect_to_frontend_error( 21 "invalid_request", 22 "Missing request_uri parameter. Use PAR to initiate authorization.", 23 ); 24 } 25 }; 26 let request_id = RequestId::from(request_uri.clone()); 27 let request_data = match state 28 .repos 29 .oauth 30 .get_authorization_request(&request_id) 31 .await 32 { 33 Ok(Some(data)) => data, 34 Ok(None) => { 35 if wants_json(&headers) { 36 return ( 37 StatusCode::BAD_REQUEST, 38 Json(serde_json::json!({ 39 "error": "invalid_request", 40 "error_description": "Invalid or expired request_uri. Please start a new authorization request." 41 })), 42 ).into_response(); 43 } 44 return redirect_to_frontend_error( 45 "invalid_request", 46 "Invalid or expired request_uri. Please start a new authorization request.", 47 ); 48 } 49 Err(e) => { 50 if wants_json(&headers) { 51 return ( 52 StatusCode::INTERNAL_SERVER_ERROR, 53 Json(serde_json::json!({ 54 "error": "server_error", 55 "error_description": format!("Database error: {:?}", e) 56 })), 57 ) 58 .into_response(); 59 } 60 return redirect_to_frontend_error("server_error", "A database error occurred."); 61 } 62 }; 63 if request_data.expires_at < Utc::now() { 64 let _ = state 65 .repos 66 .oauth 67 .delete_authorization_request(&request_id) 68 .await; 69 if wants_json(&headers) { 70 return ( 71 StatusCode::BAD_REQUEST, 72 Json(serde_json::json!({ 73 "error": "invalid_request", 74 "error_description": "Authorization request has expired. Please start a new request." 75 })), 76 ).into_response(); 77 } 78 return redirect_to_frontend_error( 79 "invalid_request", 80 "Authorization request has expired. Please start a new request.", 81 ); 82 } 83 let client_cache = ClientMetadataCache::new(3600); 84 let client_name = client_cache 85 .get(&request_data.parameters.client_id) 86 .await 87 .ok() 88 .and_then(|m| m.client_name); 89 if wants_json(&headers) { 90 return Json(AuthorizeResponse { 91 client_id: request_data.parameters.client_id.clone(), 92 client_name: client_name.clone(), 93 scope: request_data.parameters.scope.clone(), 94 redirect_uri: request_data.parameters.redirect_uri.clone(), 95 state: request_data.parameters.state.clone(), 96 login_hint: request_data.parameters.login_hint.clone(), 97 }) 98 .into_response(); 99 } 100 let force_new_account = query.new_account.unwrap_or(false); 101 102 if let Some(ref login_hint) = request_data.parameters.login_hint { 103 tracing::info!(login_hint = %login_hint, "Checking login_hint for delegation"); 104 let hostname_for_handles = tranquil_config::get().server.hostname_without_port(); 105 let normalized = NormalizedLoginIdentifier::normalize(login_hint, hostname_for_handles); 106 tracing::info!(normalized = %normalized, "Normalized login_hint"); 107 108 let hint_identifier = tranquil_types::AtIdentifier::new(normalized.as_str()).ok(); 109 let hint_lookup = match hint_identifier { 110 Some(ref id) => state.repos.user.get_login_check_by_identifier(id).await, 111 None => Ok(None), 112 }; 113 match hint_lookup { 114 Ok(Some(user)) => { 115 tracing::info!(did = %user.did, has_password = user.password_hash.is_some(), "Found user for login_hint"); 116 let is_delegated = state 117 .repos 118 .delegation 119 .is_delegated_account(&user.did) 120 .await 121 .unwrap_or(false); 122 let has_password = user.password_hash.is_some(); 123 tracing::info!(is_delegated = %is_delegated, has_password = %has_password, "Delegation check"); 124 125 if is_delegated { 126 tracing::info!("Redirecting to delegation auth"); 127 if let Err(e) = state 128 .repos 129 .oauth 130 .set_request_did(&request_id, &user.did) 131 .await 132 { 133 tracing::error!(error = %e, "Failed to set delegated DID on authorization request"); 134 return redirect_to_frontend_error( 135 "server_error", 136 "Failed to initialize delegation flow", 137 ); 138 } 139 return redirect_see_other(&format!( 140 "/app/oauth/delegation?request_uri={}&delegated_did={}", 141 url_encode(&request_uri), 142 url_encode(&user.did) 143 )); 144 } 145 } 146 Ok(None) => { 147 tracing::info!(normalized = %normalized, "No user found for login_hint"); 148 } 149 Err(e) => { 150 tracing::error!(error = %e, "Error looking up user for login_hint"); 151 } 152 } 153 } else { 154 tracing::info!("No login_hint in request"); 155 } 156 157 if request_data.parameters.prompt == Some(Prompt::Create) { 158 return redirect_see_other(&format!( 159 "/app/oauth/register?request_uri={}", 160 url_encode(&request_uri) 161 )); 162 } 163 164 if !force_new_account 165 && let Some(device_id) = extract_device_cookie(&headers) 166 && let Ok(accounts) = state 167 .repos 168 .oauth 169 .get_device_accounts(&device_id.clone()) 170 .await 171 && !accounts.is_empty() 172 { 173 let login_hint_param = request_data 174 .parameters 175 .login_hint 176 .as_ref() 177 .map(|h| format!("&login_hint={}", url_encode(h))) 178 .unwrap_or_default(); 179 return redirect_see_other(&format!( 180 "/app/oauth/accounts?request_uri={}{}", 181 url_encode(&request_uri), 182 login_hint_param 183 )); 184 } 185 redirect_see_other(&format!( 186 "/app/oauth/login?request_uri={}", 187 url_encode(&request_uri) 188 )) 189} 190 191pub async fn authorize_get_json( 192 State(state): State<AppState>, 193 Query(query): Query<AuthorizeQuery>, 194) -> Result<Json<AuthorizeResponse>, OAuthError> { 195 let request_uri = query 196 .request_uri 197 .ok_or_else(|| OAuthError::InvalidRequest("request_uri is required".to_string()))?; 198 let request_id_json = RequestId::from(request_uri.clone()); 199 let request_data = state 200 .repos 201 .oauth 202 .get_authorization_request(&request_id_json) 203 .await 204 .map_err(tranquil_pds::oauth::db_err_to_oauth)? 205 .ok_or_else(|| OAuthError::InvalidRequest("Invalid or expired request_uri".to_string()))?; 206 if request_data.expires_at < Utc::now() { 207 let _ = state 208 .repos 209 .oauth 210 .delete_authorization_request(&request_id_json) 211 .await; 212 return Err(OAuthError::InvalidRequest( 213 "request_uri has expired".to_string(), 214 )); 215 } 216 Ok(Json(AuthorizeResponse { 217 client_id: request_data.parameters.client_id.clone(), 218 client_name: None, 219 scope: request_data.parameters.scope.clone(), 220 redirect_uri: request_data.parameters.redirect_uri.clone(), 221 state: request_data.parameters.state.clone(), 222 login_hint: request_data.parameters.login_hint.clone(), 223 })) 224} 225 226#[derive(Debug, Serialize)] 227pub struct AccountInfo { 228 pub did: String, 229 pub handle: Handle, 230 #[serde(skip_serializing_if = "Option::is_none")] 231 pub email: Option<String>, 232} 233 234#[derive(Debug, Serialize)] 235pub struct AccountsResponse { 236 pub accounts: Vec<AccountInfo>, 237 pub request_uri: String, 238} 239 240fn mask_email(email: &str) -> String { 241 if let Some(at_pos) = email.find('@') { 242 let local = &email[..at_pos]; 243 let domain = &email[at_pos..]; 244 if local.len() <= 2 { 245 format!("{}***{}", local.chars().next().unwrap_or('*'), domain) 246 } else { 247 let first = local.chars().next().unwrap_or('*'); 248 let last = local.chars().last().unwrap_or('*'); 249 format!("{}***{}{}", first, last, domain) 250 } 251 } else { 252 "***".to_string() 253 } 254} 255 256pub async fn authorize_accounts( 257 State(state): State<AppState>, 258 headers: HeaderMap, 259 Query(query): Query<AuthorizeQuery>, 260) -> Response { 261 let request_uri = match query.request_uri { 262 Some(uri) => uri, 263 None => { 264 return ( 265 StatusCode::BAD_REQUEST, 266 Json(serde_json::json!({ 267 "error": "invalid_request", 268 "error_description": "Missing request_uri parameter" 269 })), 270 ) 271 .into_response(); 272 } 273 }; 274 let device_id = match extract_device_cookie(&headers) { 275 Some(id) => id, 276 None => { 277 return Json(AccountsResponse { 278 accounts: vec![], 279 request_uri, 280 }) 281 .into_response(); 282 } 283 }; 284 let accounts = match state.repos.oauth.get_device_accounts(&device_id).await { 285 Ok(accts) => accts, 286 Err(_) => { 287 return Json(AccountsResponse { 288 accounts: vec![], 289 request_uri, 290 }) 291 .into_response(); 292 } 293 }; 294 let account_infos: Vec<AccountInfo> = accounts 295 .into_iter() 296 .map(|row| AccountInfo { 297 did: row.did.to_string(), 298 handle: row.handle, 299 email: row.email.map(|e| mask_email(&e)), 300 }) 301 .collect(); 302 Json(AccountsResponse { 303 accounts: account_infos, 304 request_uri, 305 }) 306 .into_response() 307} 308 309pub async fn authorize_post( 310 State(state): State<AppState>, 311 _rate_limit: OAuthRateLimited<OAuthAuthorizeLimit>, 312 headers: HeaderMap, 313 client_ip: ClientIp, 314 Json(form): Json<AuthorizeSubmit>, 315) -> Response { 316 let json_response = wants_json(&headers); 317 let form_request_id = RequestId::from(form.request_uri.clone()); 318 let request_data = match state 319 .repos 320 .oauth 321 .get_authorization_request(&form_request_id) 322 .await 323 { 324 Ok(Some(data)) => data, 325 Ok(None) => { 326 if json_response { 327 return ( 328 axum::http::StatusCode::BAD_REQUEST, 329 Json(serde_json::json!({ 330 "error": "invalid_request", 331 "error_description": "Invalid or expired request_uri." 332 })), 333 ) 334 .into_response(); 335 } 336 return redirect_to_frontend_error( 337 "invalid_request", 338 "Invalid or expired request_uri. Please start a new authorization request.", 339 ); 340 } 341 Err(e) => { 342 if json_response { 343 return ( 344 axum::http::StatusCode::INTERNAL_SERVER_ERROR, 345 Json(serde_json::json!({ 346 "error": "server_error", 347 "error_description": format!("Database error: {:?}", e) 348 })), 349 ) 350 .into_response(); 351 } 352 return redirect_to_frontend_error("server_error", &format!("Database error: {:?}", e)); 353 } 354 }; 355 if request_data.expires_at < Utc::now() { 356 let _ = state 357 .repos 358 .oauth 359 .delete_authorization_request(&form_request_id) 360 .await; 361 if json_response { 362 return ( 363 axum::http::StatusCode::BAD_REQUEST, 364 Json(serde_json::json!({ 365 "error": "invalid_request", 366 "error_description": "Authorization request has expired." 367 })), 368 ) 369 .into_response(); 370 } 371 return redirect_to_frontend_error( 372 "invalid_request", 373 "Authorization request has expired. Please start a new request.", 374 ); 375 } 376 let show_login_error = |error_msg: &str, json: bool| -> Response { 377 if json { 378 return ( 379 axum::http::StatusCode::FORBIDDEN, 380 Json(serde_json::json!({ 381 "error": "access_denied", 382 "error_description": error_msg 383 })), 384 ) 385 .into_response(); 386 } 387 redirect_see_other(&format!( 388 "/app/oauth/login?request_uri={}&error={}", 389 url_encode(&form.request_uri), 390 url_encode(error_msg) 391 )) 392 }; 393 let hostname_for_handles = tranquil_config::get().server.hostname_without_port(); 394 let normalized_username = 395 NormalizedLoginIdentifier::normalize(&form.username, hostname_for_handles); 396 tracing::debug!( 397 original_username = %form.username, 398 normalized_username = %normalized_username, 399 pds_hostname = %tranquil_config::get().server.hostname, 400 "Normalized username for lookup" 401 ); 402 let login_identifier = tranquil_types::AtIdentifier::new(normalized_username.as_str()).ok(); 403 let login_lookup = match login_identifier { 404 Some(ref id) => state.repos.user.get_login_info_by_identifier(id).await, 405 None => Ok(None), 406 }; 407 let user = match login_lookup { 408 Ok(Some(u)) => u, 409 Ok(None) => { 410 let _ = bcrypt::verify( 411 &form.password, 412 "$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.VTtYw1ZzQKZqmK", 413 ); 414 return show_login_error("Invalid identifier or password.", json_response); 415 } 416 Err(_) => return show_login_error("An error occurred. Please try again.", json_response), 417 }; 418 if user.deactivated_at.is_some() { 419 return show_login_error("This account has been deactivated.", json_response); 420 } 421 if user.takedown_ref.is_some() { 422 return show_login_error("This account has been taken down.", json_response); 423 } 424 if user.account_type.is_delegated() { 425 if state 426 .repos 427 .oauth 428 .set_authorization_did(&form_request_id, &user.did, None) 429 .await 430 .is_err() 431 { 432 return show_login_error("An error occurred. Please try again.", json_response); 433 } 434 let redirect_url = format!( 435 "/app/oauth/delegation?request_uri={}&delegated_did={}", 436 url_encode(&form.request_uri), 437 url_encode(&user.did) 438 ); 439 if json_response { 440 return ( 441 StatusCode::OK, 442 Json(serde_json::json!({ 443 "next": "delegation", 444 "delegated_did": user.did, 445 "redirect": redirect_url 446 })), 447 ) 448 .into_response(); 449 } 450 return redirect_see_other(&redirect_url); 451 } 452 453 if !user.password_required { 454 if state 455 .repos 456 .oauth 457 .set_authorization_did(&form_request_id, &user.did, None) 458 .await 459 .is_err() 460 { 461 return show_login_error("An error occurred. Please try again.", json_response); 462 } 463 let redirect_url = format!( 464 "/app/oauth/passkey?request_uri={}", 465 url_encode(&form.request_uri) 466 ); 467 if json_response { 468 return ( 469 StatusCode::OK, 470 Json(serde_json::json!({ 471 "next": "passkey", 472 "redirect": redirect_url 473 })), 474 ) 475 .into_response(); 476 } 477 return redirect_see_other(&redirect_url); 478 } 479 480 let password_valid = match &user.password_hash { 481 Some(hash) => match bcrypt::verify(&form.password, hash.as_str()) { 482 Ok(valid) => valid, 483 Err(_) => { 484 return show_login_error("An error occurred. Please try again.", json_response); 485 } 486 }, 487 None => false, 488 }; 489 if !password_valid { 490 return show_login_error("Invalid identifier or password.", json_response); 491 } 492 if tranquil_api::server::verification_blocks_login(&user.channel_verification) { 493 let resend_info = tranquil_api::server::auto_resend_verification(&state, &user.did).await; 494 let handle = resend_info 495 .as_ref() 496 .map(|r| r.handle.to_string()) 497 .unwrap_or_else(|| form.username.clone()); 498 let channel = resend_info 499 .map(|r| r.channel.as_str().to_owned()) 500 .unwrap_or_else(|| user.preferred_comms_channel.as_str().to_owned()); 501 if json_response { 502 return ( 503 axum::http::StatusCode::FORBIDDEN, 504 Json(serde_json::json!({ 505 "error": "account_not_verified", 506 "error_description": "Please verify your account before logging in.", 507 "did": user.did, 508 "handle": handle, 509 "channel": channel 510 })), 511 ) 512 .into_response(); 513 } 514 return redirect_see_other(&format!( 515 "/app/oauth/login?request_uri={}&error={}", 516 url_encode(&form.request_uri), 517 url_encode("account_not_verified") 518 )); 519 } 520 let has_totp = tranquil_api::server::has_totp_enabled(&state, &user.did).await; 521 if has_totp { 522 let device_cookie = extract_device_cookie(&headers); 523 let device_is_trusted = if let Some(ref dev_id) = device_cookie { 524 tranquil_api::server::is_device_trusted(state.repos.oauth.as_ref(), dev_id, &user.did) 525 .await 526 } else { 527 false 528 }; 529 530 if device_is_trusted { 531 if let Some(ref dev_id) = device_cookie { 532 let _ = tranquil_api::server::extend_device_trust( 533 state.repos.oauth.as_ref(), 534 dev_id, 535 &user.did, 536 ) 537 .await; 538 } 539 } else { 540 if state 541 .repos 542 .oauth 543 .set_authorization_did(&form_request_id, &user.did, None) 544 .await 545 .is_err() 546 { 547 return show_login_error("An error occurred. Please try again.", json_response); 548 } 549 if json_response { 550 return Json(serde_json::json!({ 551 "needs_totp": true 552 })) 553 .into_response(); 554 } 555 return redirect_see_other(&format!( 556 "/app/oauth/totp?request_uri={}", 557 url_encode(&form.request_uri) 558 )); 559 } 560 } 561 if user.two_factor_enabled { 562 let _ = state 563 .repos 564 .oauth 565 .delete_2fa_challenge_by_request_uri(&form_request_id) 566 .await; 567 match state 568 .repos 569 .oauth 570 .create_2fa_challenge(&user.did, &form_request_id) 571 .await 572 { 573 Ok(challenge) => { 574 let hostname = &tranquil_config::get().server.hostname; 575 if let Err(e) = enqueue_2fa_code( 576 state.repos.user.as_ref(), 577 state.repos.infra.as_ref(), 578 user.id, 579 &challenge.code, 580 hostname, 581 ) 582 .await 583 { 584 tracing::warn!( 585 did = %user.did, 586 error = %e, 587 "Failed to enqueue 2FA notification" 588 ); 589 } 590 let channel_name = user.preferred_comms_channel.display_name(); 591 if json_response { 592 return Json(serde_json::json!({ 593 "needs_2fa": true, 594 "channel": channel_name 595 })) 596 .into_response(); 597 } 598 return redirect_see_other(&format!( 599 "/app/oauth/2fa?request_uri={}&channel={}", 600 url_encode(&form.request_uri), 601 url_encode(channel_name) 602 )); 603 } 604 Err(_) => { 605 return show_login_error("An error occurred. Please try again.", json_response); 606 } 607 } 608 } 609 let mut device_id: Option<DeviceId> = extract_device_cookie(&headers); 610 let mut new_cookie: Option<String> = None; 611 if form.remember_device { 612 let final_device_id = if let Some(existing_id) = &device_id { 613 existing_id.clone() 614 } else { 615 let new_id = DeviceId::generate(); 616 let device_data = DeviceData { 617 session_id: SessionId::generate(), 618 user_agent: extract_user_agent(&headers), 619 ip_address: client_ip.into_string(), 620 last_seen_at: Utc::now(), 621 }; 622 if state 623 .repos 624 .oauth 625 .create_device(&new_id, &device_data) 626 .await 627 .is_ok() 628 { 629 new_cookie = Some(make_device_cookie(&new_id)); 630 device_id = Some(new_id.clone()); 631 } 632 new_id 633 }; 634 let _ = state 635 .repos 636 .oauth 637 .upsert_account_device(&user.did, &final_device_id) 638 .await; 639 } 640 let set_auth_device_id = device_id.clone(); 641 if state 642 .repos 643 .oauth 644 .set_authorization_did(&form_request_id, &user.did, set_auth_device_id.as_ref()) 645 .await 646 .is_err() 647 { 648 return show_login_error("An error occurred. Please try again.", json_response); 649 } 650 let requested_scope_str = request_data 651 .parameters 652 .scope 653 .as_deref() 654 .unwrap_or("atproto"); 655 let requested_scopes: Vec<String> = requested_scope_str 656 .split_whitespace() 657 .map(|s| s.to_string()) 658 .collect(); 659 let needs_consent = should_show_consent( 660 state.repos.oauth.as_ref(), 661 &user.did, 662 &request_data.parameters.client_id, 663 &requested_scopes, 664 ) 665 .await 666 .unwrap_or(true); 667 if needs_consent { 668 let consent_url = format!( 669 "/app/oauth/consent?request_uri={}", 670 url_encode(&form.request_uri) 671 ); 672 if json_response { 673 if let Some(cookie) = new_cookie { 674 return ( 675 StatusCode::OK, 676 [(SET_COOKIE, cookie)], 677 Json(serde_json::json!({"redirect_uri": consent_url})), 678 ) 679 .into_response(); 680 } 681 return Json(serde_json::json!({"redirect_uri": consent_url})).into_response(); 682 } 683 if let Some(cookie) = new_cookie { 684 return ( 685 StatusCode::SEE_OTHER, 686 [(SET_COOKIE, cookie), (LOCATION, consent_url)], 687 ) 688 .into_response(); 689 } 690 return redirect_see_other(&consent_url); 691 } 692 let code = AuthorizationCode::generate(); 693 let auth_post_device_id = device_id.clone(); 694 if state 695 .repos 696 .oauth 697 .update_authorization_request( 698 &form_request_id, 699 &user.did, 700 auth_post_device_id.as_ref(), 701 &code, 702 ) 703 .await 704 .is_err() 705 { 706 return show_login_error("An error occurred. Please try again.", json_response); 707 } 708 if json_response { 709 let redirect_url = build_intermediate_redirect_url( 710 &request_data.parameters.redirect_uri, 711 code.as_str(), 712 request_data.parameters.state.as_deref(), 713 request_data.parameters.response_mode.map(|m| m.as_str()), 714 ); 715 if let Some(cookie) = new_cookie { 716 ( 717 StatusCode::OK, 718 [(SET_COOKIE, cookie)], 719 Json(serde_json::json!({"redirect_uri": redirect_url})), 720 ) 721 .into_response() 722 } else { 723 Json(serde_json::json!({"redirect_uri": redirect_url})).into_response() 724 } 725 } else { 726 let redirect_url = build_success_redirect( 727 &request_data.parameters.redirect_uri, 728 code.as_str(), 729 request_data.parameters.state.as_deref(), 730 request_data.parameters.response_mode.map(|m| m.as_str()), 731 ); 732 if let Some(cookie) = new_cookie { 733 ( 734 StatusCode::SEE_OTHER, 735 [(SET_COOKIE, cookie), (LOCATION, redirect_url)], 736 ) 737 .into_response() 738 } else { 739 redirect_see_other(&redirect_url) 740 } 741 } 742} 743 744pub async fn authorize_select( 745 State(state): State<AppState>, 746 headers: HeaderMap, 747 Json(form): Json<AuthorizeSelectSubmit>, 748) -> Response { 749 let json_error = |status: StatusCode, error: &str, description: &str| -> Response { 750 ( 751 status, 752 Json(serde_json::json!({ 753 "error": error, 754 "error_description": description 755 })), 756 ) 757 .into_response() 758 }; 759 let select_request_id = RequestId::from(form.request_uri.clone()); 760 let request_data = match state 761 .repos 762 .oauth 763 .get_authorization_request(&select_request_id) 764 .await 765 { 766 Ok(Some(data)) => data, 767 Ok(None) => { 768 return json_error( 769 StatusCode::BAD_REQUEST, 770 "invalid_request", 771 "Invalid or expired request_uri. Please start a new authorization request.", 772 ); 773 } 774 Err(_) => { 775 return json_error( 776 StatusCode::INTERNAL_SERVER_ERROR, 777 "server_error", 778 "An error occurred. Please try again.", 779 ); 780 } 781 }; 782 if request_data.expires_at < Utc::now() { 783 let _ = state 784 .repos 785 .oauth 786 .delete_authorization_request(&select_request_id) 787 .await; 788 return json_error( 789 StatusCode::BAD_REQUEST, 790 "invalid_request", 791 "Authorization request has expired. Please start a new request.", 792 ); 793 } 794 let device_id = match extract_device_cookie(&headers) { 795 Some(id) => id, 796 None => { 797 return json_error( 798 StatusCode::BAD_REQUEST, 799 "invalid_request", 800 "No device session found. Please sign in.", 801 ); 802 } 803 }; 804 let did: Did = match form.did.parse() { 805 Ok(d) => d, 806 Err(_) => { 807 return json_error( 808 StatusCode::BAD_REQUEST, 809 "invalid_request", 810 "Invalid DID format.", 811 ); 812 } 813 }; 814 let verify_device_id = device_id.clone(); 815 let account_valid = match state 816 .repos 817 .oauth 818 .verify_account_on_device(&verify_device_id, &did) 819 .await 820 { 821 Ok(valid) => valid, 822 Err(_) => { 823 return json_error( 824 StatusCode::INTERNAL_SERVER_ERROR, 825 "server_error", 826 "An error occurred. Please try again.", 827 ); 828 } 829 }; 830 if !account_valid { 831 return json_error( 832 StatusCode::FORBIDDEN, 833 "access_denied", 834 "This account is not available on this device. Please sign in.", 835 ); 836 } 837 let user = match state.repos.user.get_2fa_status_by_did(&did).await { 838 Ok(Some(u)) => u, 839 Ok(None) => { 840 return json_error( 841 StatusCode::FORBIDDEN, 842 "access_denied", 843 "Account not found. Please sign in.", 844 ); 845 } 846 Err(_) => { 847 return json_error( 848 StatusCode::INTERNAL_SERVER_ERROR, 849 "server_error", 850 "An error occurred. Please try again.", 851 ); 852 } 853 }; 854 if tranquil_api::server::verification_blocks_login(&user.channel_verification) { 855 let resend_info = tranquil_api::server::auto_resend_verification(&state, &did).await; 856 return ( 857 StatusCode::FORBIDDEN, 858 Json(serde_json::json!({ 859 "error": "account_not_verified", 860 "error_description": "Please verify your account before logging in.", 861 "did": did, 862 "handle": resend_info.as_ref().map(|r| r.handle.to_string()), 863 "channel": resend_info.as_ref().map(|r| r.channel.as_str()) 864 })), 865 ) 866 .into_response(); 867 } 868 let has_totp = tranquil_api::server::has_totp_enabled(&state, &did).await; 869 if has_totp { 870 let device_is_trusted = 871 tranquil_api::server::is_device_trusted(state.repos.oauth.as_ref(), &device_id, &did) 872 .await; 873 if !device_is_trusted { 874 if state 875 .repos 876 .oauth 877 .set_authorization_did(&select_request_id, &did, Some(&device_id)) 878 .await 879 .is_err() 880 { 881 return json_error( 882 StatusCode::INTERNAL_SERVER_ERROR, 883 "server_error", 884 "An error occurred. Please try again.", 885 ); 886 } 887 return Json(serde_json::json!({ 888 "needs_totp": true 889 })) 890 .into_response(); 891 } 892 let _ = 893 tranquil_api::server::extend_device_trust(state.repos.oauth.as_ref(), &device_id, &did) 894 .await; 895 } 896 if user.two_factor_enabled { 897 let _ = state 898 .repos 899 .oauth 900 .delete_2fa_challenge_by_request_uri(&select_request_id) 901 .await; 902 match state 903 .repos 904 .oauth 905 .create_2fa_challenge(&did, &select_request_id) 906 .await 907 { 908 Ok(challenge) => { 909 let hostname = &tranquil_config::get().server.hostname; 910 if let Err(e) = enqueue_2fa_code( 911 state.repos.user.as_ref(), 912 state.repos.infra.as_ref(), 913 user.id, 914 &challenge.code, 915 hostname, 916 ) 917 .await 918 { 919 tracing::warn!( 920 did = %form.did, 921 error = %e, 922 "Failed to enqueue 2FA notification" 923 ); 924 } 925 let channel_name = user.preferred_comms_channel.display_name(); 926 return Json(serde_json::json!({ 927 "needs_2fa": true, 928 "channel": channel_name 929 })) 930 .into_response(); 931 } 932 Err(_) => { 933 return json_error( 934 StatusCode::INTERNAL_SERVER_ERROR, 935 "server_error", 936 "An error occurred. Please try again.", 937 ); 938 } 939 } 940 } 941 let _ = state 942 .repos 943 .oauth 944 .upsert_account_device(&did, &device_id) 945 .await; 946 947 if state 948 .repos 949 .oauth 950 .set_authorization_did(&select_request_id, &did, Some(&device_id)) 951 .await 952 .is_err() 953 { 954 return json_error( 955 StatusCode::INTERNAL_SERVER_ERROR, 956 "server_error", 957 "An error occurred. Please try again.", 958 ); 959 } 960 let consent_url = format!( 961 "/app/oauth/consent?request_uri={}", 962 url_encode(&form.request_uri) 963 ); 964 Json(serde_json::json!({"redirect_uri": consent_url})).into_response() 965}