Our Personal Data Server from scratch!
0

Configure Feed

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

1use axum::{ 2 Json, 3 extract::{Query, State}, 4 http::{ 5 HeaderMap, StatusCode, 6 header::{LOCATION, SET_COOKIE}, 7 }, 8 response::{IntoResponse, Response}, 9}; 10use chrono::Utc; 11use serde::{Deserialize, Serialize}; 12use subtle::ConstantTimeEq; 13use tranquil_db_traits::{ScopePreference, WebauthnChallengeType}; 14use tranquil_pds::auth::{BareLoginIdentifier, NormalizedLoginIdentifier}; 15use tranquil_pds::comms::comms_repo::enqueue_2fa_code; 16use tranquil_pds::oauth::{ 17 AuthFlow, ClientMetadataCache, Code, DeviceData, DeviceId, OAuthError, Prompt, SessionId, 18 db::should_show_consent, scopes::expand_include_scopes, 19}; 20use tranquil_pds::rate_limit::{ 21 OAuthAuthorizeLimit, OAuthRateLimited, OAuthRegisterCompleteLimit, TotpVerifyLimit, 22 check_user_rate_limit, 23}; 24use tranquil_pds::state::AppState; 25use tranquil_pds::types::{Did, Handle, PlainPassword}; 26use tranquil_pds::util::extract_client_ip; 27use tranquil_types::{AuthorizationCode, ClientId, DeviceId as DeviceIdType, RequestId}; 28use urlencoding::encode as url_encode; 29 30const DEVICE_COOKIE_NAME: &str = "oauth_device_id"; 31const RENEW_EXPIRY_SECONDS: i64 = 600; 32const MAX_RENEWAL_STALENESS_SECONDS: i64 = 3600; 33 34fn redirect_see_other(uri: &str) -> Response { 35 ( 36 StatusCode::SEE_OTHER, 37 [ 38 (LOCATION, uri.to_string()), 39 (axum::http::header::CACHE_CONTROL, "no-store".to_string()), 40 ( 41 SET_COOKIE, 42 "bfCacheBypass=foo; max-age=1; SameSite=Lax".to_string(), 43 ), 44 ], 45 ) 46 .into_response() 47} 48 49fn redirect_to_frontend_error(error: &str, description: &str) -> Response { 50 redirect_see_other(&format!( 51 "/app/oauth/error?error={}&error_description={}", 52 url_encode(error), 53 url_encode(description) 54 )) 55} 56 57fn json_error(status: StatusCode, error: &str, description: &str) -> Response { 58 ( 59 status, 60 Json(serde_json::json!({ 61 "error": error, 62 "error_description": description 63 })), 64 ) 65 .into_response() 66} 67 68fn is_granular_scope(s: &str) -> bool { 69 s.starts_with("repo:") 70 || s.starts_with("repo?") 71 || s == "repo" 72 || s.starts_with("blob:") 73 || s.starts_with("blob?") 74 || s == "blob" 75 || s.starts_with("rpc:") 76 || s.starts_with("rpc?") 77 || s.starts_with("account:") 78 || s.starts_with("identity:") 79} 80 81fn is_valid_scope(s: &str) -> bool { 82 s == "atproto" 83 || s == "transition:generic" 84 || s == "transition:chat.bsky" 85 || s == "transition:email" 86 || is_granular_scope(s) 87 || s.starts_with("include:") 88} 89 90fn extract_device_cookie(headers: &HeaderMap) -> Option<tranquil_types::DeviceId> { 91 headers 92 .get("cookie") 93 .and_then(|v| v.to_str().ok()) 94 .and_then(|cookie_str| { 95 cookie_str.split(';').map(|c| c.trim()).find_map(|cookie| { 96 cookie 97 .strip_prefix(&format!("{}=", DEVICE_COOKIE_NAME)) 98 .and_then(|value| { 99 tranquil_pds::config::AuthConfig::get().verify_device_cookie(value) 100 }) 101 .map(tranquil_types::DeviceId::new) 102 }) 103 }) 104} 105 106fn extract_user_agent(headers: &HeaderMap) -> Option<String> { 107 headers 108 .get("user-agent") 109 .and_then(|v| v.to_str().ok()) 110 .map(|s| s.to_string()) 111} 112 113fn make_device_cookie(device_id: &tranquil_types::DeviceId) -> String { 114 let signed_value = 115 tranquil_pds::config::AuthConfig::get().sign_device_cookie(device_id.as_str()); 116 format!( 117 "{}={}; Path=/oauth; HttpOnly; Secure; SameSite=Lax; Max-Age=31536000", 118 DEVICE_COOKIE_NAME, signed_value 119 ) 120} 121 122#[derive(Debug, Deserialize)] 123pub struct AuthorizeQuery { 124 pub request_uri: Option<String>, 125 pub client_id: Option<String>, 126 pub new_account: Option<bool>, 127} 128 129#[derive(Debug, Serialize)] 130pub struct AuthorizeResponse { 131 pub client_id: String, 132 pub client_name: Option<String>, 133 pub scope: Option<String>, 134 pub redirect_uri: String, 135 pub state: Option<String>, 136 pub login_hint: Option<String>, 137} 138 139#[derive(Debug, Deserialize)] 140pub struct AuthorizeSubmit { 141 pub request_uri: String, 142 pub username: String, 143 pub password: PlainPassword, 144 #[serde(default)] 145 pub remember_device: bool, 146} 147 148#[derive(Debug, Deserialize)] 149pub struct AuthorizeSelectSubmit { 150 pub request_uri: String, 151 pub did: String, 152} 153 154fn wants_json(headers: &HeaderMap) -> bool { 155 headers 156 .get("accept") 157 .and_then(|v| v.to_str().ok()) 158 .map(|accept| accept.contains("application/json")) 159 .unwrap_or(false) 160} 161 162pub async fn authorize_get( 163 State(state): State<AppState>, 164 headers: HeaderMap, 165 Query(query): Query<AuthorizeQuery>, 166) -> Response { 167 let request_uri = match query.request_uri { 168 Some(uri) => uri, 169 None => { 170 if wants_json(&headers) { 171 return ( 172 StatusCode::BAD_REQUEST, 173 Json(serde_json::json!({ 174 "error": "invalid_request", 175 "error_description": "Missing request_uri parameter. Use PAR to initiate authorization." 176 })), 177 ).into_response(); 178 } 179 return redirect_to_frontend_error( 180 "invalid_request", 181 "Missing request_uri parameter. Use PAR to initiate authorization.", 182 ); 183 } 184 }; 185 let request_id = RequestId::from(request_uri.clone()); 186 let request_data = match state 187 .oauth_repo 188 .get_authorization_request(&request_id) 189 .await 190 { 191 Ok(Some(data)) => data, 192 Ok(None) => { 193 if wants_json(&headers) { 194 return ( 195 StatusCode::BAD_REQUEST, 196 Json(serde_json::json!({ 197 "error": "invalid_request", 198 "error_description": "Invalid or expired request_uri. Please start a new authorization request." 199 })), 200 ).into_response(); 201 } 202 return redirect_to_frontend_error( 203 "invalid_request", 204 "Invalid or expired request_uri. Please start a new authorization request.", 205 ); 206 } 207 Err(e) => { 208 if wants_json(&headers) { 209 return ( 210 StatusCode::INTERNAL_SERVER_ERROR, 211 Json(serde_json::json!({ 212 "error": "server_error", 213 "error_description": format!("Database error: {:?}", e) 214 })), 215 ) 216 .into_response(); 217 } 218 return redirect_to_frontend_error("server_error", "A database error occurred."); 219 } 220 }; 221 if request_data.expires_at < Utc::now() { 222 let _ = state 223 .oauth_repo 224 .delete_authorization_request(&request_id) 225 .await; 226 if wants_json(&headers) { 227 return ( 228 StatusCode::BAD_REQUEST, 229 Json(serde_json::json!({ 230 "error": "invalid_request", 231 "error_description": "Authorization request has expired. Please start a new request." 232 })), 233 ).into_response(); 234 } 235 return redirect_to_frontend_error( 236 "invalid_request", 237 "Authorization request has expired. Please start a new request.", 238 ); 239 } 240 let client_cache = ClientMetadataCache::new(3600); 241 let client_name = client_cache 242 .get(&request_data.parameters.client_id) 243 .await 244 .ok() 245 .and_then(|m| m.client_name); 246 if wants_json(&headers) { 247 return Json(AuthorizeResponse { 248 client_id: request_data.parameters.client_id.clone(), 249 client_name: client_name.clone(), 250 scope: request_data.parameters.scope.clone(), 251 redirect_uri: request_data.parameters.redirect_uri.clone(), 252 state: request_data.parameters.state.clone(), 253 login_hint: request_data.parameters.login_hint.clone(), 254 }) 255 .into_response(); 256 } 257 let force_new_account = query.new_account.unwrap_or(false); 258 259 if let Some(ref login_hint) = request_data.parameters.login_hint { 260 tracing::info!(login_hint = %login_hint, "Checking login_hint for delegation"); 261 let hostname_for_handles = tranquil_config::get().server.hostname_without_port(); 262 let normalized = NormalizedLoginIdentifier::normalize(login_hint, hostname_for_handles); 263 tracing::info!(normalized = %normalized, "Normalized login_hint"); 264 265 match state 266 .user_repo 267 .get_login_check_by_handle_or_email(normalized.as_str()) 268 .await 269 { 270 Ok(Some(user)) => { 271 tracing::info!(did = %user.did, has_password = user.password_hash.is_some(), "Found user for login_hint"); 272 let is_delegated = state 273 .delegation_repo 274 .is_delegated_account(&user.did) 275 .await 276 .unwrap_or(false); 277 let has_password = user.password_hash.is_some(); 278 tracing::info!(is_delegated = %is_delegated, has_password = %has_password, "Delegation check"); 279 280 if is_delegated { 281 tracing::info!("Redirecting to delegation auth"); 282 if let Err(e) = state 283 .oauth_repo 284 .set_request_did(&request_id, &user.did) 285 .await 286 { 287 tracing::error!(error = %e, "Failed to set delegated DID on authorization request"); 288 return redirect_to_frontend_error( 289 "server_error", 290 "Failed to initialize delegation flow", 291 ); 292 } 293 return redirect_see_other(&format!( 294 "/app/oauth/delegation?request_uri={}&delegated_did={}", 295 url_encode(&request_uri), 296 url_encode(&user.did) 297 )); 298 } 299 } 300 Ok(None) => { 301 tracing::info!(normalized = %normalized, "No user found for login_hint"); 302 } 303 Err(e) => { 304 tracing::error!(error = %e, "Error looking up user for login_hint"); 305 } 306 } 307 } else { 308 tracing::info!("No login_hint in request"); 309 } 310 311 if request_data.parameters.prompt == Some(Prompt::Create) { 312 return redirect_see_other(&format!( 313 "/app/oauth/register?request_uri={}", 314 url_encode(&request_uri) 315 )); 316 } 317 318 if !force_new_account 319 && let Some(device_id) = extract_device_cookie(&headers) 320 && let Ok(accounts) = state 321 .oauth_repo 322 .get_device_accounts(&device_id.clone()) 323 .await 324 && !accounts.is_empty() 325 { 326 return redirect_see_other(&format!( 327 "/app/oauth/accounts?request_uri={}", 328 url_encode(&request_uri) 329 )); 330 } 331 redirect_see_other(&format!( 332 "/app/oauth/login?request_uri={}", 333 url_encode(&request_uri) 334 )) 335} 336 337pub async fn authorize_get_json( 338 State(state): State<AppState>, 339 Query(query): Query<AuthorizeQuery>, 340) -> Result<Json<AuthorizeResponse>, OAuthError> { 341 let request_uri = query 342 .request_uri 343 .ok_or_else(|| OAuthError::InvalidRequest("request_uri is required".to_string()))?; 344 let request_id_json = RequestId::from(request_uri.clone()); 345 let request_data = state 346 .oauth_repo 347 .get_authorization_request(&request_id_json) 348 .await 349 .map_err(tranquil_pds::oauth::db_err_to_oauth)? 350 .ok_or_else(|| OAuthError::InvalidRequest("Invalid or expired request_uri".to_string()))?; 351 if request_data.expires_at < Utc::now() { 352 let _ = state 353 .oauth_repo 354 .delete_authorization_request(&request_id_json) 355 .await; 356 return Err(OAuthError::InvalidRequest( 357 "request_uri has expired".to_string(), 358 )); 359 } 360 Ok(Json(AuthorizeResponse { 361 client_id: request_data.parameters.client_id.clone(), 362 client_name: None, 363 scope: request_data.parameters.scope.clone(), 364 redirect_uri: request_data.parameters.redirect_uri.clone(), 365 state: request_data.parameters.state.clone(), 366 login_hint: request_data.parameters.login_hint.clone(), 367 })) 368} 369 370#[derive(Debug, Serialize)] 371pub struct AccountInfo { 372 pub did: String, 373 pub handle: Handle, 374 #[serde(skip_serializing_if = "Option::is_none")] 375 pub email: Option<String>, 376} 377 378#[derive(Debug, Serialize)] 379pub struct AccountsResponse { 380 pub accounts: Vec<AccountInfo>, 381 pub request_uri: String, 382} 383 384fn mask_email(email: &str) -> String { 385 if let Some(at_pos) = email.find('@') { 386 let local = &email[..at_pos]; 387 let domain = &email[at_pos..]; 388 if local.len() <= 2 { 389 format!("{}***{}", local.chars().next().unwrap_or('*'), domain) 390 } else { 391 let first = local.chars().next().unwrap_or('*'); 392 let last = local.chars().last().unwrap_or('*'); 393 format!("{}***{}{}", first, last, domain) 394 } 395 } else { 396 "***".to_string() 397 } 398} 399 400pub async fn authorize_accounts( 401 State(state): State<AppState>, 402 headers: HeaderMap, 403 Query(query): Query<AuthorizeQuery>, 404) -> Response { 405 let request_uri = match query.request_uri { 406 Some(uri) => uri, 407 None => { 408 return ( 409 StatusCode::BAD_REQUEST, 410 Json(serde_json::json!({ 411 "error": "invalid_request", 412 "error_description": "Missing request_uri parameter" 413 })), 414 ) 415 .into_response(); 416 } 417 }; 418 let device_id = match extract_device_cookie(&headers) { 419 Some(id) => id, 420 None => { 421 return Json(AccountsResponse { 422 accounts: vec![], 423 request_uri, 424 }) 425 .into_response(); 426 } 427 }; 428 let accounts = match state.oauth_repo.get_device_accounts(&device_id).await { 429 Ok(accts) => accts, 430 Err(_) => { 431 return Json(AccountsResponse { 432 accounts: vec![], 433 request_uri, 434 }) 435 .into_response(); 436 } 437 }; 438 let account_infos: Vec<AccountInfo> = accounts 439 .into_iter() 440 .map(|row| AccountInfo { 441 did: row.did.to_string(), 442 handle: row.handle, 443 email: row.email.map(|e| mask_email(&e)), 444 }) 445 .collect(); 446 Json(AccountsResponse { 447 accounts: account_infos, 448 request_uri, 449 }) 450 .into_response() 451} 452 453pub async fn authorize_post( 454 State(state): State<AppState>, 455 _rate_limit: OAuthRateLimited<OAuthAuthorizeLimit>, 456 headers: HeaderMap, 457 Json(form): Json<AuthorizeSubmit>, 458) -> Response { 459 let json_response = wants_json(&headers); 460 let form_request_id = RequestId::from(form.request_uri.clone()); 461 let request_data = match state 462 .oauth_repo 463 .get_authorization_request(&form_request_id) 464 .await 465 { 466 Ok(Some(data)) => data, 467 Ok(None) => { 468 if json_response { 469 return ( 470 axum::http::StatusCode::BAD_REQUEST, 471 Json(serde_json::json!({ 472 "error": "invalid_request", 473 "error_description": "Invalid or expired request_uri." 474 })), 475 ) 476 .into_response(); 477 } 478 return redirect_to_frontend_error( 479 "invalid_request", 480 "Invalid or expired request_uri. Please start a new authorization request.", 481 ); 482 } 483 Err(e) => { 484 if json_response { 485 return ( 486 axum::http::StatusCode::INTERNAL_SERVER_ERROR, 487 Json(serde_json::json!({ 488 "error": "server_error", 489 "error_description": format!("Database error: {:?}", e) 490 })), 491 ) 492 .into_response(); 493 } 494 return redirect_to_frontend_error("server_error", &format!("Database error: {:?}", e)); 495 } 496 }; 497 if request_data.expires_at < Utc::now() { 498 let _ = state 499 .oauth_repo 500 .delete_authorization_request(&form_request_id) 501 .await; 502 if json_response { 503 return ( 504 axum::http::StatusCode::BAD_REQUEST, 505 Json(serde_json::json!({ 506 "error": "invalid_request", 507 "error_description": "Authorization request has expired." 508 })), 509 ) 510 .into_response(); 511 } 512 return redirect_to_frontend_error( 513 "invalid_request", 514 "Authorization request has expired. Please start a new request.", 515 ); 516 } 517 let show_login_error = |error_msg: &str, json: bool| -> Response { 518 if json { 519 return ( 520 axum::http::StatusCode::FORBIDDEN, 521 Json(serde_json::json!({ 522 "error": "access_denied", 523 "error_description": error_msg 524 })), 525 ) 526 .into_response(); 527 } 528 redirect_see_other(&format!( 529 "/app/oauth/login?request_uri={}&error={}", 530 url_encode(&form.request_uri), 531 url_encode(error_msg) 532 )) 533 }; 534 let hostname_for_handles = tranquil_config::get().server.hostname_without_port(); 535 let normalized_username = 536 NormalizedLoginIdentifier::normalize(&form.username, hostname_for_handles); 537 tracing::debug!( 538 original_username = %form.username, 539 normalized_username = %normalized_username, 540 pds_hostname = %tranquil_config::get().server.hostname, 541 "Normalized username for lookup" 542 ); 543 let user = match state 544 .user_repo 545 .get_login_info_by_handle_or_email(normalized_username.as_str()) 546 .await 547 { 548 Ok(Some(u)) => u, 549 Ok(None) => { 550 let _ = bcrypt::verify( 551 &form.password, 552 "$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.VTtYw1ZzQKZqmK", 553 ); 554 return show_login_error("Invalid handle/email or password.", json_response); 555 } 556 Err(_) => return show_login_error("An error occurred. Please try again.", json_response), 557 }; 558 if user.deactivated_at.is_some() { 559 return show_login_error("This account has been deactivated.", json_response); 560 } 561 if user.takedown_ref.is_some() { 562 return show_login_error("This account has been taken down.", json_response); 563 } 564 if user.account_type.is_delegated() { 565 if state 566 .oauth_repo 567 .set_authorization_did(&form_request_id, &user.did, None) 568 .await 569 .is_err() 570 { 571 return show_login_error("An error occurred. Please try again.", json_response); 572 } 573 let redirect_url = format!( 574 "/app/oauth/delegation?request_uri={}&delegated_did={}", 575 url_encode(&form.request_uri), 576 url_encode(&user.did) 577 ); 578 if json_response { 579 return ( 580 StatusCode::OK, 581 Json(serde_json::json!({ 582 "next": "delegation", 583 "delegated_did": user.did, 584 "redirect": redirect_url 585 })), 586 ) 587 .into_response(); 588 } 589 return redirect_see_other(&redirect_url); 590 } 591 592 if !user.password_required { 593 if state 594 .oauth_repo 595 .set_authorization_did(&form_request_id, &user.did, None) 596 .await 597 .is_err() 598 { 599 return show_login_error("An error occurred. Please try again.", json_response); 600 } 601 let redirect_url = format!( 602 "/app/oauth/passkey?request_uri={}", 603 url_encode(&form.request_uri) 604 ); 605 if json_response { 606 return ( 607 StatusCode::OK, 608 Json(serde_json::json!({ 609 "next": "passkey", 610 "redirect": redirect_url 611 })), 612 ) 613 .into_response(); 614 } 615 return redirect_see_other(&redirect_url); 616 } 617 618 let password_valid = match &user.password_hash { 619 Some(hash) => match bcrypt::verify(&form.password, hash) { 620 Ok(valid) => valid, 621 Err(_) => { 622 return show_login_error("An error occurred. Please try again.", json_response); 623 } 624 }, 625 None => false, 626 }; 627 if !password_valid { 628 return show_login_error("Invalid handle/email or password.", json_response); 629 } 630 let is_verified = user.channel_verification.has_any_verified(); 631 if !is_verified { 632 let resend_info = tranquil_api::server::auto_resend_verification(&state, &user.did).await; 633 let handle = resend_info 634 .as_ref() 635 .map(|r| r.handle.to_string()) 636 .unwrap_or_else(|| form.username.clone()); 637 let channel = resend_info 638 .map(|r| r.channel.as_str().to_owned()) 639 .unwrap_or_else(|| user.preferred_comms_channel.as_str().to_owned()); 640 if json_response { 641 return ( 642 axum::http::StatusCode::FORBIDDEN, 643 Json(serde_json::json!({ 644 "error": "account_not_verified", 645 "error_description": "Please verify your account before logging in.", 646 "did": user.did, 647 "handle": handle, 648 "channel": channel 649 })), 650 ) 651 .into_response(); 652 } 653 return redirect_see_other(&format!( 654 "/app/oauth/login?request_uri={}&error={}", 655 url_encode(&form.request_uri), 656 url_encode("account_not_verified") 657 )); 658 } 659 let has_totp = tranquil_api::server::has_totp_enabled(&state, &user.did).await; 660 if has_totp { 661 let device_cookie = extract_device_cookie(&headers); 662 let device_is_trusted = if let Some(ref dev_id) = device_cookie { 663 tranquil_api::server::is_device_trusted(state.oauth_repo.as_ref(), dev_id, &user.did) 664 .await 665 } else { 666 false 667 }; 668 669 if device_is_trusted { 670 if let Some(ref dev_id) = device_cookie { 671 let _ = 672 tranquil_api::server::extend_device_trust(state.oauth_repo.as_ref(), dev_id) 673 .await; 674 } 675 } else { 676 if state 677 .oauth_repo 678 .set_authorization_did(&form_request_id, &user.did, None) 679 .await 680 .is_err() 681 { 682 return show_login_error("An error occurred. Please try again.", json_response); 683 } 684 if json_response { 685 return Json(serde_json::json!({ 686 "needs_totp": true 687 })) 688 .into_response(); 689 } 690 return redirect_see_other(&format!( 691 "/app/oauth/totp?request_uri={}", 692 url_encode(&form.request_uri) 693 )); 694 } 695 } 696 if user.two_factor_enabled { 697 let _ = state 698 .oauth_repo 699 .delete_2fa_challenge_by_request_uri(&form_request_id) 700 .await; 701 match state 702 .oauth_repo 703 .create_2fa_challenge(&user.did, &form_request_id) 704 .await 705 { 706 Ok(challenge) => { 707 let hostname = &tranquil_config::get().server.hostname; 708 if let Err(e) = enqueue_2fa_code( 709 state.user_repo.as_ref(), 710 state.infra_repo.as_ref(), 711 user.id, 712 &challenge.code, 713 hostname, 714 ) 715 .await 716 { 717 tracing::warn!( 718 did = %user.did, 719 error = %e, 720 "Failed to enqueue 2FA notification" 721 ); 722 } 723 let channel_name = user.preferred_comms_channel.display_name(); 724 if json_response { 725 return Json(serde_json::json!({ 726 "needs_2fa": true, 727 "channel": channel_name 728 })) 729 .into_response(); 730 } 731 return redirect_see_other(&format!( 732 "/app/oauth/2fa?request_uri={}&channel={}", 733 url_encode(&form.request_uri), 734 url_encode(channel_name) 735 )); 736 } 737 Err(_) => { 738 return show_login_error("An error occurred. Please try again.", json_response); 739 } 740 } 741 } 742 let mut device_id: Option<DeviceIdType> = extract_device_cookie(&headers); 743 let mut new_cookie: Option<String> = None; 744 if form.remember_device { 745 let final_device_id = if let Some(existing_id) = &device_id { 746 existing_id.clone() 747 } else { 748 let new_id = DeviceId::generate(); 749 let new_device_id_typed = DeviceIdType::new(new_id.0.clone()); 750 let device_data = DeviceData { 751 session_id: SessionId::generate(), 752 user_agent: extract_user_agent(&headers), 753 ip_address: extract_client_ip(&headers, None), 754 last_seen_at: Utc::now(), 755 }; 756 if state 757 .oauth_repo 758 .create_device(&new_device_id_typed, &device_data) 759 .await 760 .is_ok() 761 { 762 new_cookie = Some(make_device_cookie(&new_device_id_typed)); 763 device_id = Some(new_device_id_typed.clone()); 764 } 765 new_device_id_typed 766 }; 767 let _ = state 768 .oauth_repo 769 .upsert_account_device(&user.did, &final_device_id) 770 .await; 771 } 772 let set_auth_device_id = device_id.clone(); 773 if state 774 .oauth_repo 775 .set_authorization_did(&form_request_id, &user.did, set_auth_device_id.as_ref()) 776 .await 777 .is_err() 778 { 779 return show_login_error("An error occurred. Please try again.", json_response); 780 } 781 let requested_scope_str = request_data 782 .parameters 783 .scope 784 .as_deref() 785 .unwrap_or("atproto"); 786 let requested_scopes: Vec<String> = requested_scope_str 787 .split_whitespace() 788 .map(|s| s.to_string()) 789 .collect(); 790 let client_id_typed = ClientId::from(request_data.parameters.client_id.clone()); 791 let needs_consent = should_show_consent( 792 state.oauth_repo.as_ref(), 793 &user.did, 794 &client_id_typed, 795 &requested_scopes, 796 ) 797 .await 798 .unwrap_or(true); 799 if needs_consent { 800 let consent_url = format!( 801 "/app/oauth/consent?request_uri={}", 802 url_encode(&form.request_uri) 803 ); 804 if json_response { 805 if let Some(cookie) = new_cookie { 806 return ( 807 StatusCode::OK, 808 [(SET_COOKIE, cookie)], 809 Json(serde_json::json!({"redirect_uri": consent_url})), 810 ) 811 .into_response(); 812 } 813 return Json(serde_json::json!({"redirect_uri": consent_url})).into_response(); 814 } 815 if let Some(cookie) = new_cookie { 816 return ( 817 StatusCode::SEE_OTHER, 818 [(SET_COOKIE, cookie), (LOCATION, consent_url)], 819 ) 820 .into_response(); 821 } 822 return redirect_see_other(&consent_url); 823 } 824 let code = Code::generate(); 825 let auth_post_device_id = device_id.clone(); 826 let auth_post_code = AuthorizationCode::from(code.0.clone()); 827 if state 828 .oauth_repo 829 .update_authorization_request( 830 &form_request_id, 831 &user.did, 832 auth_post_device_id.as_ref(), 833 &auth_post_code, 834 ) 835 .await 836 .is_err() 837 { 838 return show_login_error("An error occurred. Please try again.", json_response); 839 } 840 if json_response { 841 let redirect_url = build_intermediate_redirect_url( 842 &request_data.parameters.redirect_uri, 843 &code.0, 844 request_data.parameters.state.as_deref(), 845 request_data.parameters.response_mode.map(|m| m.as_str()), 846 ); 847 if let Some(cookie) = new_cookie { 848 ( 849 StatusCode::OK, 850 [(SET_COOKIE, cookie)], 851 Json(serde_json::json!({"redirect_uri": redirect_url})), 852 ) 853 .into_response() 854 } else { 855 Json(serde_json::json!({"redirect_uri": redirect_url})).into_response() 856 } 857 } else { 858 let redirect_url = build_success_redirect( 859 &request_data.parameters.redirect_uri, 860 &code.0, 861 request_data.parameters.state.as_deref(), 862 request_data.parameters.response_mode.map(|m| m.as_str()), 863 ); 864 if let Some(cookie) = new_cookie { 865 ( 866 StatusCode::SEE_OTHER, 867 [(SET_COOKIE, cookie), (LOCATION, redirect_url)], 868 ) 869 .into_response() 870 } else { 871 redirect_see_other(&redirect_url) 872 } 873 } 874} 875 876pub async fn authorize_select( 877 State(state): State<AppState>, 878 headers: HeaderMap, 879 Json(form): Json<AuthorizeSelectSubmit>, 880) -> Response { 881 let json_error = |status: StatusCode, error: &str, description: &str| -> Response { 882 ( 883 status, 884 Json(serde_json::json!({ 885 "error": error, 886 "error_description": description 887 })), 888 ) 889 .into_response() 890 }; 891 let select_request_id = RequestId::from(form.request_uri.clone()); 892 let request_data = match state 893 .oauth_repo 894 .get_authorization_request(&select_request_id) 895 .await 896 { 897 Ok(Some(data)) => data, 898 Ok(None) => { 899 return json_error( 900 StatusCode::BAD_REQUEST, 901 "invalid_request", 902 "Invalid or expired request_uri. Please start a new authorization request.", 903 ); 904 } 905 Err(_) => { 906 return json_error( 907 StatusCode::INTERNAL_SERVER_ERROR, 908 "server_error", 909 "An error occurred. Please try again.", 910 ); 911 } 912 }; 913 if request_data.expires_at < Utc::now() { 914 let _ = state 915 .oauth_repo 916 .delete_authorization_request(&select_request_id) 917 .await; 918 return json_error( 919 StatusCode::BAD_REQUEST, 920 "invalid_request", 921 "Authorization request has expired. Please start a new request.", 922 ); 923 } 924 let device_id = match extract_device_cookie(&headers) { 925 Some(id) => id, 926 None => { 927 return json_error( 928 StatusCode::BAD_REQUEST, 929 "invalid_request", 930 "No device session found. Please sign in.", 931 ); 932 } 933 }; 934 let did: Did = match form.did.parse() { 935 Ok(d) => d, 936 Err(_) => { 937 return json_error( 938 StatusCode::BAD_REQUEST, 939 "invalid_request", 940 "Invalid DID format.", 941 ); 942 } 943 }; 944 let verify_device_id = device_id.clone(); 945 let account_valid = match state 946 .oauth_repo 947 .verify_account_on_device(&verify_device_id, &did) 948 .await 949 { 950 Ok(valid) => valid, 951 Err(_) => { 952 return json_error( 953 StatusCode::INTERNAL_SERVER_ERROR, 954 "server_error", 955 "An error occurred. Please try again.", 956 ); 957 } 958 }; 959 if !account_valid { 960 return json_error( 961 StatusCode::FORBIDDEN, 962 "access_denied", 963 "This account is not available on this device. Please sign in.", 964 ); 965 } 966 let user = match state.user_repo.get_2fa_status_by_did(&did).await { 967 Ok(Some(u)) => u, 968 Ok(None) => { 969 return json_error( 970 StatusCode::FORBIDDEN, 971 "access_denied", 972 "Account not found. Please sign in.", 973 ); 974 } 975 Err(_) => { 976 return json_error( 977 StatusCode::INTERNAL_SERVER_ERROR, 978 "server_error", 979 "An error occurred. Please try again.", 980 ); 981 } 982 }; 983 let is_verified = user.channel_verification.has_any_verified(); 984 if !is_verified { 985 let resend_info = tranquil_api::server::auto_resend_verification(&state, &did).await; 986 return ( 987 StatusCode::FORBIDDEN, 988 Json(serde_json::json!({ 989 "error": "account_not_verified", 990 "error_description": "Please verify your account before logging in.", 991 "did": did, 992 "handle": resend_info.as_ref().map(|r| r.handle.to_string()), 993 "channel": resend_info.as_ref().map(|r| r.channel.as_str()) 994 })), 995 ) 996 .into_response(); 997 } 998 let has_totp = tranquil_api::server::has_totp_enabled(&state, &did).await; 999 let select_early_device_typed = device_id.clone(); 1000 if has_totp { 1001 let device_is_trusted = 1002 tranquil_api::server::is_device_trusted(state.oauth_repo.as_ref(), &device_id, &did) 1003 .await; 1004 if !device_is_trusted { 1005 if state 1006 .oauth_repo 1007 .set_authorization_did(&select_request_id, &did, Some(&select_early_device_typed)) 1008 .await 1009 .is_err() 1010 { 1011 return json_error( 1012 StatusCode::INTERNAL_SERVER_ERROR, 1013 "server_error", 1014 "An error occurred. Please try again.", 1015 ); 1016 } 1017 return Json(serde_json::json!({ 1018 "needs_totp": true 1019 })) 1020 .into_response(); 1021 } 1022 let _ = 1023 tranquil_api::server::extend_device_trust(state.oauth_repo.as_ref(), &device_id).await; 1024 } 1025 if user.two_factor_enabled { 1026 let _ = state 1027 .oauth_repo 1028 .delete_2fa_challenge_by_request_uri(&select_request_id) 1029 .await; 1030 match state 1031 .oauth_repo 1032 .create_2fa_challenge(&did, &select_request_id) 1033 .await 1034 { 1035 Ok(challenge) => { 1036 let hostname = &tranquil_config::get().server.hostname; 1037 if let Err(e) = enqueue_2fa_code( 1038 state.user_repo.as_ref(), 1039 state.infra_repo.as_ref(), 1040 user.id, 1041 &challenge.code, 1042 hostname, 1043 ) 1044 .await 1045 { 1046 tracing::warn!( 1047 did = %form.did, 1048 error = %e, 1049 "Failed to enqueue 2FA notification" 1050 ); 1051 } 1052 let channel_name = user.preferred_comms_channel.display_name(); 1053 return Json(serde_json::json!({ 1054 "needs_2fa": true, 1055 "channel": channel_name 1056 })) 1057 .into_response(); 1058 } 1059 Err(_) => { 1060 return json_error( 1061 StatusCode::INTERNAL_SERVER_ERROR, 1062 "server_error", 1063 "An error occurred. Please try again.", 1064 ); 1065 } 1066 } 1067 } 1068 let select_device_typed = device_id.clone(); 1069 let _ = state 1070 .oauth_repo 1071 .upsert_account_device(&did, &select_device_typed) 1072 .await; 1073 1074 if state 1075 .oauth_repo 1076 .set_authorization_did(&select_request_id, &did, Some(&select_device_typed)) 1077 .await 1078 .is_err() 1079 { 1080 return json_error( 1081 StatusCode::INTERNAL_SERVER_ERROR, 1082 "server_error", 1083 "An error occurred. Please try again.", 1084 ); 1085 } 1086 let consent_url = format!( 1087 "/app/oauth/consent?request_uri={}", 1088 url_encode(&form.request_uri) 1089 ); 1090 Json(serde_json::json!({"redirect_uri": consent_url})).into_response() 1091} 1092 1093fn build_success_redirect( 1094 redirect_uri: &str, 1095 code: &str, 1096 state: Option<&str>, 1097 response_mode: Option<&str>, 1098) -> String { 1099 let mut redirect_url = redirect_uri.to_string(); 1100 let use_fragment = response_mode == Some("fragment"); 1101 let separator = if use_fragment { 1102 '#' 1103 } else if redirect_url.contains('?') { 1104 '&' 1105 } else { 1106 '?' 1107 }; 1108 redirect_url.push(separator); 1109 let pds_host = &tranquil_config::get().server.hostname; 1110 redirect_url.push_str(&format!( 1111 "iss={}", 1112 url_encode(&format!("https://{}", pds_host)) 1113 )); 1114 if let Some(req_state) = state { 1115 redirect_url.push_str(&format!("&state={}", url_encode(req_state))); 1116 } 1117 redirect_url.push_str(&format!("&code={}", url_encode(code))); 1118 redirect_url 1119} 1120 1121fn build_intermediate_redirect_url( 1122 redirect_uri: &str, 1123 code: &str, 1124 state: Option<&str>, 1125 response_mode: Option<&str>, 1126) -> String { 1127 let pds_host = &tranquil_config::get().server.hostname; 1128 let mut url = format!( 1129 "https://{}/oauth/authorize/redirect?redirect_uri={}&code={}", 1130 pds_host, 1131 url_encode(redirect_uri), 1132 url_encode(code) 1133 ); 1134 if let Some(s) = state { 1135 url.push_str(&format!("&state={}", url_encode(s))); 1136 } 1137 if let Some(rm) = response_mode { 1138 url.push_str(&format!("&response_mode={}", url_encode(rm))); 1139 } 1140 url 1141} 1142 1143#[derive(Debug, Deserialize)] 1144pub struct AuthorizeRedirectParams { 1145 redirect_uri: String, 1146 code: String, 1147 state: Option<String>, 1148 response_mode: Option<String>, 1149} 1150 1151pub async fn authorize_redirect(Query(params): Query<AuthorizeRedirectParams>) -> Response { 1152 let final_url = build_success_redirect( 1153 &params.redirect_uri, 1154 &params.code, 1155 params.state.as_deref(), 1156 params.response_mode.as_deref(), 1157 ); 1158 tracing::info!( 1159 final_url = %final_url, 1160 client_redirect = %params.redirect_uri, 1161 "authorize_redirect performing 303 redirect" 1162 ); 1163 ( 1164 StatusCode::SEE_OTHER, 1165 [ 1166 (axum::http::header::LOCATION, final_url), 1167 (axum::http::header::CACHE_CONTROL, "no-store".to_string()), 1168 ], 1169 ) 1170 .into_response() 1171} 1172 1173#[derive(Debug, Serialize)] 1174pub struct AuthorizeDenyResponse { 1175 pub error: String, 1176 pub error_description: String, 1177} 1178 1179pub async fn authorize_deny( 1180 State(state): State<AppState>, 1181 Json(form): Json<AuthorizeDenyForm>, 1182) -> Response { 1183 let deny_request_id = RequestId::from(form.request_uri.clone()); 1184 let request_data = match state 1185 .oauth_repo 1186 .get_authorization_request(&deny_request_id) 1187 .await 1188 { 1189 Ok(Some(data)) => data, 1190 Ok(None) => { 1191 return ( 1192 StatusCode::BAD_REQUEST, 1193 Json(serde_json::json!({ 1194 "error": "invalid_request", 1195 "error_description": "Invalid request_uri" 1196 })), 1197 ) 1198 .into_response(); 1199 } 1200 Err(_) => { 1201 return ( 1202 StatusCode::INTERNAL_SERVER_ERROR, 1203 Json(serde_json::json!({ 1204 "error": "server_error", 1205 "error_description": "An error occurred" 1206 })), 1207 ) 1208 .into_response(); 1209 } 1210 }; 1211 let _ = state 1212 .oauth_repo 1213 .delete_authorization_request(&deny_request_id) 1214 .await; 1215 let redirect_uri = &request_data.parameters.redirect_uri; 1216 let mut redirect_url = redirect_uri.to_string(); 1217 let separator = if redirect_url.contains('?') { '&' } else { '?' }; 1218 redirect_url.push(separator); 1219 redirect_url.push_str("error=access_denied"); 1220 redirect_url.push_str("&error_description=User%20denied%20the%20request"); 1221 if let Some(state) = &request_data.parameters.state { 1222 redirect_url.push_str(&format!("&state={}", url_encode(state))); 1223 } 1224 Json(serde_json::json!({ 1225 "redirect_uri": redirect_url 1226 })) 1227 .into_response() 1228} 1229 1230#[derive(Debug, Deserialize)] 1231pub struct AuthorizeDenyForm { 1232 pub request_uri: String, 1233} 1234 1235#[derive(Debug, Deserialize)] 1236pub struct Authorize2faQuery { 1237 pub request_uri: String, 1238 pub channel: Option<String>, 1239} 1240 1241#[derive(Debug, Deserialize)] 1242pub struct Authorize2faSubmit { 1243 pub request_uri: String, 1244 pub code: String, 1245 #[serde(default)] 1246 pub trust_device: bool, 1247} 1248 1249const MAX_2FA_ATTEMPTS: i32 = 5; 1250 1251pub async fn authorize_2fa_get( 1252 State(state): State<AppState>, 1253 Query(query): Query<Authorize2faQuery>, 1254) -> Response { 1255 let twofa_request_id = RequestId::from(query.request_uri.clone()); 1256 let challenge = match state.oauth_repo.get_2fa_challenge(&twofa_request_id).await { 1257 Ok(Some(c)) => c, 1258 Ok(None) => { 1259 return redirect_to_frontend_error( 1260 "invalid_request", 1261 "No 2FA challenge found. Please start over.", 1262 ); 1263 } 1264 Err(_) => { 1265 return redirect_to_frontend_error( 1266 "server_error", 1267 "An error occurred. Please try again.", 1268 ); 1269 } 1270 }; 1271 if challenge.expires_at < Utc::now() { 1272 let _ = state.oauth_repo.delete_2fa_challenge(challenge.id).await; 1273 return redirect_to_frontend_error( 1274 "invalid_request", 1275 "2FA code has expired. Please start over.", 1276 ); 1277 } 1278 let _request_data = match state 1279 .oauth_repo 1280 .get_authorization_request(&twofa_request_id) 1281 .await 1282 { 1283 Ok(Some(d)) => d, 1284 Ok(None) => { 1285 return redirect_to_frontend_error( 1286 "invalid_request", 1287 "Authorization request not found. Please start over.", 1288 ); 1289 } 1290 Err(_) => { 1291 return redirect_to_frontend_error( 1292 "server_error", 1293 "An error occurred. Please try again.", 1294 ); 1295 } 1296 }; 1297 let channel = query.channel.as_deref().unwrap_or("email"); 1298 redirect_see_other(&format!( 1299 "/app/oauth/2fa?request_uri={}&channel={}", 1300 url_encode(&query.request_uri), 1301 url_encode(channel) 1302 )) 1303} 1304 1305#[derive(Debug, Serialize)] 1306pub struct ScopeInfo { 1307 pub scope: String, 1308 pub category: String, 1309 pub required: bool, 1310 pub description: String, 1311 pub display_name: String, 1312 pub granted: Option<bool>, 1313} 1314 1315#[derive(Debug, Serialize)] 1316pub struct ConsentResponse { 1317 pub request_uri: String, 1318 pub client_id: String, 1319 pub client_name: Option<String>, 1320 pub client_uri: Option<String>, 1321 pub logo_uri: Option<String>, 1322 pub scopes: Vec<ScopeInfo>, 1323 pub show_consent: bool, 1324 pub did: String, 1325 #[serde(skip_serializing_if = "Option::is_none")] 1326 pub handle: Option<String>, 1327 #[serde(skip_serializing_if = "Option::is_none")] 1328 pub is_delegation: Option<bool>, 1329 #[serde(skip_serializing_if = "Option::is_none")] 1330 pub controller_did: Option<String>, 1331 #[serde(skip_serializing_if = "Option::is_none")] 1332 pub controller_handle: Option<String>, 1333 #[serde(skip_serializing_if = "Option::is_none")] 1334 pub delegation_level: Option<String>, 1335} 1336 1337#[derive(Debug, Deserialize)] 1338pub struct ConsentQuery { 1339 pub request_uri: String, 1340} 1341 1342#[derive(Debug, Deserialize)] 1343pub struct ConsentSubmit { 1344 pub request_uri: String, 1345 pub approved_scopes: Vec<String>, 1346 pub remember: bool, 1347} 1348 1349pub async fn consent_get( 1350 State(state): State<AppState>, 1351 Query(query): Query<ConsentQuery>, 1352) -> Response { 1353 let consent_request_id = RequestId::from(query.request_uri.clone()); 1354 let request_data = match state 1355 .oauth_repo 1356 .get_authorization_request(&consent_request_id) 1357 .await 1358 { 1359 Ok(Some(data)) => data, 1360 Ok(None) => { 1361 return json_error( 1362 StatusCode::BAD_REQUEST, 1363 "invalid_request", 1364 "Invalid or expired request_uri", 1365 ); 1366 } 1367 Err(e) => { 1368 return json_error( 1369 StatusCode::INTERNAL_SERVER_ERROR, 1370 "server_error", 1371 &format!("Database error: {:?}", e), 1372 ); 1373 } 1374 }; 1375 let flow_with_user = match AuthFlow::from_request_data(request_data.clone()) { 1376 Ok(flow) => match flow.require_user() { 1377 Ok(u) => u, 1378 Err(_) => { 1379 return json_error(StatusCode::FORBIDDEN, "access_denied", "Not authenticated"); 1380 } 1381 }, 1382 Err(_) => { 1383 return json_error( 1384 StatusCode::BAD_REQUEST, 1385 "expired_request", 1386 "Authorization request has expired", 1387 ); 1388 } 1389 }; 1390 1391 let did = flow_with_user.did().clone(); 1392 let client_cache = ClientMetadataCache::new(3600); 1393 let client_metadata = client_cache 1394 .get(&request_data.parameters.client_id) 1395 .await 1396 .ok(); 1397 let requested_scope_str = request_data 1398 .parameters 1399 .scope 1400 .as_deref() 1401 .filter(|s| !s.trim().is_empty()) 1402 .unwrap_or("atproto"); 1403 1404 let controller_did_parsed: Option<Did> = request_data 1405 .controller_did 1406 .as_ref() 1407 .and_then(|s| s.parse().ok()); 1408 let delegation_grant = if let Some(ref ctrl_did) = controller_did_parsed { 1409 state 1410 .delegation_repo 1411 .get_delegation(&did, ctrl_did) 1412 .await 1413 .ok() 1414 .flatten() 1415 } else { 1416 None 1417 }; 1418 1419 let effective_scope_str = if let Some(ref grant) = delegation_grant { 1420 tranquil_pds::delegation::intersect_scopes( 1421 requested_scope_str, 1422 grant.granted_scopes.as_str(), 1423 ) 1424 } else { 1425 requested_scope_str.to_string() 1426 }; 1427 1428 let expanded_scope_str = expand_include_scopes(&effective_scope_str).await; 1429 let requested_scopes: Vec<&str> = expanded_scope_str.split_whitespace().collect(); 1430 let consent_client_id = ClientId::from(request_data.parameters.client_id.clone()); 1431 let preferences = state 1432 .oauth_repo 1433 .get_scope_preferences(&did, &consent_client_id) 1434 .await 1435 .unwrap_or_default(); 1436 let pref_map: std::collections::HashMap<_, _> = preferences 1437 .iter() 1438 .map(|p| (p.scope.as_str(), p.granted)) 1439 .collect(); 1440 let requested_scope_strings: Vec<String> = 1441 requested_scopes.iter().map(|s| s.to_string()).collect(); 1442 let show_consent = should_show_consent( 1443 state.oauth_repo.as_ref(), 1444 &did, 1445 &consent_client_id, 1446 &requested_scope_strings, 1447 ) 1448 .await 1449 .unwrap_or(true); 1450 let has_granular_scopes = requested_scopes.iter().any(|s| is_granular_scope(s)); 1451 let scopes: Vec<ScopeInfo> = requested_scopes 1452 .iter() 1453 .map(|scope| { 1454 let (category, required, description, display_name) = if let Some(def) = 1455 tranquil_pds::oauth::scopes::SCOPE_DEFINITIONS.get(*scope) 1456 { 1457 let desc = if *scope == "atproto" && has_granular_scopes { 1458 "AT Protocol baseline scope (permissions determined by selected options below)" 1459 .to_string() 1460 } else { 1461 def.description.to_string() 1462 }; 1463 let name = if *scope == "atproto" && has_granular_scopes { 1464 "AT Protocol Access".to_string() 1465 } else { 1466 def.display_name.to_string() 1467 }; 1468 ( 1469 def.category.display_name().to_string(), 1470 def.required, 1471 desc, 1472 name, 1473 ) 1474 } else if scope.starts_with("ref:") { 1475 ( 1476 "Reference".to_string(), 1477 false, 1478 "Referenced scope".to_string(), 1479 scope.to_string(), 1480 ) 1481 } else { 1482 ( 1483 "Other".to_string(), 1484 false, 1485 format!("Access to {}", scope), 1486 scope.to_string(), 1487 ) 1488 }; 1489 let granted = pref_map.get(*scope).copied(); 1490 ScopeInfo { 1491 scope: scope.to_string(), 1492 category, 1493 required, 1494 description, 1495 display_name, 1496 granted, 1497 } 1498 }) 1499 .collect(); 1500 1501 let account_handle = state 1502 .user_repo 1503 .get_handle_by_did(&did) 1504 .await 1505 .ok() 1506 .flatten() 1507 .map(|h| h.to_string()); 1508 1509 let (is_delegation, controller_did_resp, controller_handle, delegation_level) = 1510 if let Some(ref ctrl_did) = controller_did_parsed { 1511 let ctrl_handle = state 1512 .user_repo 1513 .get_handle_by_did(ctrl_did) 1514 .await 1515 .ok() 1516 .flatten() 1517 .map(|h| h.to_string()); 1518 1519 let level = if let Some(ref grant) = delegation_grant { 1520 let preset = tranquil_pds::delegation::SCOPE_PRESETS 1521 .iter() 1522 .find(|p| p.scopes == grant.granted_scopes.as_str()); 1523 preset 1524 .map(|p| p.label.to_string()) 1525 .unwrap_or_else(|| "Custom".to_string()) 1526 } else { 1527 "Unknown".to_string() 1528 }; 1529 1530 ( 1531 Some(true), 1532 Some(ctrl_did.to_string()), 1533 ctrl_handle, 1534 Some(level), 1535 ) 1536 } else { 1537 (None, None, None, None) 1538 }; 1539 1540 Json(ConsentResponse { 1541 request_uri: query.request_uri.clone(), 1542 client_id: request_data.parameters.client_id.clone(), 1543 client_name: client_metadata.as_ref().and_then(|m| m.client_name.clone()), 1544 client_uri: client_metadata.as_ref().and_then(|m| m.client_uri.clone()), 1545 logo_uri: client_metadata.as_ref().and_then(|m| m.logo_uri.clone()), 1546 scopes, 1547 show_consent, 1548 did: did.to_string(), 1549 handle: account_handle, 1550 is_delegation, 1551 controller_did: controller_did_resp, 1552 controller_handle, 1553 delegation_level, 1554 }) 1555 .into_response() 1556} 1557 1558pub async fn consent_post( 1559 State(state): State<AppState>, 1560 Json(form): Json<ConsentSubmit>, 1561) -> Response { 1562 tracing::info!( 1563 "consent_post: approved_scopes={:?}, remember={}", 1564 form.approved_scopes, 1565 form.remember 1566 ); 1567 let consent_post_request_id = RequestId::from(form.request_uri.clone()); 1568 let request_data = match state 1569 .oauth_repo 1570 .get_authorization_request(&consent_post_request_id) 1571 .await 1572 { 1573 Ok(Some(data)) => data, 1574 Ok(None) => { 1575 return json_error( 1576 StatusCode::BAD_REQUEST, 1577 "invalid_request", 1578 "Invalid or expired request_uri", 1579 ); 1580 } 1581 Err(e) => { 1582 return json_error( 1583 StatusCode::INTERNAL_SERVER_ERROR, 1584 "server_error", 1585 &format!("Database error: {:?}", e), 1586 ); 1587 } 1588 }; 1589 let flow_with_user = match AuthFlow::from_request_data(request_data.clone()) { 1590 Ok(flow) => match flow.require_user() { 1591 Ok(u) => u, 1592 Err(_) => { 1593 return json_error(StatusCode::FORBIDDEN, "access_denied", "Not authenticated"); 1594 } 1595 }, 1596 Err(_) => { 1597 let _ = state 1598 .oauth_repo 1599 .delete_authorization_request(&consent_post_request_id) 1600 .await; 1601 return json_error( 1602 StatusCode::BAD_REQUEST, 1603 "invalid_request", 1604 "Authorization request has expired", 1605 ); 1606 } 1607 }; 1608 1609 let did = flow_with_user.did().clone(); 1610 let original_scope_str = request_data 1611 .parameters 1612 .scope 1613 .as_deref() 1614 .unwrap_or("atproto"); 1615 1616 let controller_did_parsed: Option<Did> = request_data 1617 .controller_did 1618 .as_ref() 1619 .and_then(|s| s.parse().ok()); 1620 1621 let delegation_grant = match controller_did_parsed.as_ref() { 1622 Some(ctrl_did) => state 1623 .delegation_repo 1624 .get_delegation(&did, ctrl_did) 1625 .await 1626 .ok() 1627 .flatten(), 1628 None => None, 1629 }; 1630 1631 let effective_scope_str = if let Some(ref grant) = delegation_grant { 1632 tranquil_pds::delegation::intersect_scopes( 1633 original_scope_str, 1634 grant.granted_scopes.as_str(), 1635 ) 1636 } else { 1637 original_scope_str.to_string() 1638 }; 1639 1640 let requested_scopes: Vec<&str> = effective_scope_str.split_whitespace().collect(); 1641 let has_granular_scopes = requested_scopes.iter().any(|s| is_granular_scope(s)); 1642 let user_denied_some_granular = has_granular_scopes 1643 && requested_scopes 1644 .iter() 1645 .filter(|s| is_granular_scope(s)) 1646 .any(|s| !form.approved_scopes.contains(&s.to_string())); 1647 let atproto_was_requested = requested_scopes.contains(&"atproto"); 1648 if atproto_was_requested 1649 && !has_granular_scopes 1650 && !form.approved_scopes.contains(&"atproto".to_string()) 1651 { 1652 return json_error( 1653 StatusCode::BAD_REQUEST, 1654 "invalid_request", 1655 "The atproto scope was requested and must be approved", 1656 ); 1657 } 1658 let final_approved: Vec<String> = if user_denied_some_granular { 1659 form.approved_scopes 1660 .iter() 1661 .filter(|s| *s != "atproto") 1662 .cloned() 1663 .collect() 1664 } else { 1665 form.approved_scopes.clone() 1666 }; 1667 if final_approved.is_empty() { 1668 return json_error( 1669 StatusCode::BAD_REQUEST, 1670 "invalid_request", 1671 "At least one scope must be approved", 1672 ); 1673 } 1674 let approved_scope_str = final_approved.join(" "); 1675 let has_valid_scope = final_approved.iter().all(|s| is_valid_scope(s)); 1676 if !has_valid_scope { 1677 return json_error( 1678 StatusCode::BAD_REQUEST, 1679 "invalid_request", 1680 "Invalid scope format", 1681 ); 1682 } 1683 if form.remember { 1684 let preferences: Vec<ScopePreference> = requested_scopes 1685 .iter() 1686 .map(|s| ScopePreference { 1687 scope: s.to_string(), 1688 granted: form.approved_scopes.contains(&s.to_string()), 1689 }) 1690 .collect(); 1691 let consent_post_client_id = ClientId::from(request_data.parameters.client_id.clone()); 1692 let _ = state 1693 .oauth_repo 1694 .upsert_scope_preferences(&did, &consent_post_client_id, &preferences) 1695 .await; 1696 } 1697 if let Err(e) = state 1698 .oauth_repo 1699 .update_request_scope(&consent_post_request_id, &approved_scope_str) 1700 .await 1701 { 1702 tracing::warn!("Failed to update request scope: {:?}", e); 1703 } 1704 let code = Code::generate(); 1705 let consent_post_device_id = request_data 1706 .device_id 1707 .as_ref() 1708 .map(|d| DeviceIdType::new(d.0.clone())); 1709 let consent_post_code = AuthorizationCode::from(code.0.clone()); 1710 if state 1711 .oauth_repo 1712 .update_authorization_request( 1713 &consent_post_request_id, 1714 &did, 1715 consent_post_device_id.as_ref(), 1716 &consent_post_code, 1717 ) 1718 .await 1719 .is_err() 1720 { 1721 return json_error( 1722 StatusCode::INTERNAL_SERVER_ERROR, 1723 "server_error", 1724 "Failed to complete authorization", 1725 ); 1726 } 1727 let redirect_uri = &request_data.parameters.redirect_uri; 1728 let intermediate_url = build_intermediate_redirect_url( 1729 redirect_uri, 1730 &code.0, 1731 request_data.parameters.state.as_deref(), 1732 request_data.parameters.response_mode.map(|m| m.as_str()), 1733 ); 1734 tracing::info!( 1735 intermediate_url = %intermediate_url, 1736 client_redirect = %redirect_uri, 1737 "consent_post returning JSON with intermediate URL (for 303 redirect)" 1738 ); 1739 Json(serde_json::json!({ "redirect_uri": intermediate_url })).into_response() 1740} 1741 1742#[derive(Debug, Deserialize)] 1743pub struct RenewRequest { 1744 pub request_uri: String, 1745} 1746 1747pub async fn authorize_renew( 1748 State(state): State<AppState>, 1749 _rate_limit: OAuthRateLimited<OAuthAuthorizeLimit>, 1750 Json(form): Json<RenewRequest>, 1751) -> Response { 1752 let request_id = RequestId::from(form.request_uri.clone()); 1753 let request_data = match state 1754 .oauth_repo 1755 .get_authorization_request(&request_id) 1756 .await 1757 { 1758 Ok(Some(data)) => data, 1759 Ok(None) => { 1760 return json_error( 1761 StatusCode::BAD_REQUEST, 1762 "invalid_request", 1763 "Unknown authorization request", 1764 ); 1765 } 1766 Err(_) => { 1767 return json_error( 1768 StatusCode::INTERNAL_SERVER_ERROR, 1769 "server_error", 1770 "Database error", 1771 ); 1772 } 1773 }; 1774 1775 if request_data.did.is_none() { 1776 return json_error( 1777 StatusCode::BAD_REQUEST, 1778 "invalid_request", 1779 "Authorization request not yet authenticated", 1780 ); 1781 } 1782 1783 let now = Utc::now(); 1784 if request_data.expires_at >= now { 1785 return Json(serde_json::json!({ 1786 "request_uri": form.request_uri, 1787 "renewed": false 1788 })) 1789 .into_response(); 1790 } 1791 1792 let staleness = now - request_data.expires_at; 1793 if staleness.num_seconds() > MAX_RENEWAL_STALENESS_SECONDS { 1794 let _ = state 1795 .oauth_repo 1796 .delete_authorization_request(&request_id) 1797 .await; 1798 return json_error( 1799 StatusCode::BAD_REQUEST, 1800 "invalid_request", 1801 "Authorization request expired too long ago to renew", 1802 ); 1803 } 1804 1805 let new_expires_at = now + chrono::Duration::seconds(RENEW_EXPIRY_SECONDS); 1806 match state 1807 .oauth_repo 1808 .extend_authorization_request_expiry(&request_id, new_expires_at) 1809 .await 1810 { 1811 Ok(true) => Json(serde_json::json!({ 1812 "request_uri": form.request_uri, 1813 "renewed": true 1814 })) 1815 .into_response(), 1816 Ok(false) => json_error( 1817 StatusCode::BAD_REQUEST, 1818 "invalid_request", 1819 "Authorization request could not be renewed", 1820 ), 1821 Err(_) => json_error( 1822 StatusCode::INTERNAL_SERVER_ERROR, 1823 "server_error", 1824 "Database error", 1825 ), 1826 } 1827} 1828 1829pub async fn authorize_2fa_post( 1830 State(state): State<AppState>, 1831 _rate_limit: OAuthRateLimited<OAuthAuthorizeLimit>, 1832 headers: HeaderMap, 1833 Json(form): Json<Authorize2faSubmit>, 1834) -> Response { 1835 let json_error = |status: StatusCode, error: &str, description: &str| -> Response { 1836 ( 1837 status, 1838 Json(serde_json::json!({ 1839 "error": error, 1840 "error_description": description 1841 })), 1842 ) 1843 .into_response() 1844 }; 1845 let twofa_post_request_id = RequestId::from(form.request_uri.clone()); 1846 let request_data = match state 1847 .oauth_repo 1848 .get_authorization_request(&twofa_post_request_id) 1849 .await 1850 { 1851 Ok(Some(d)) => d, 1852 Ok(None) => { 1853 return json_error( 1854 StatusCode::BAD_REQUEST, 1855 "invalid_request", 1856 "Authorization request not found.", 1857 ); 1858 } 1859 Err(_) => { 1860 return json_error( 1861 StatusCode::INTERNAL_SERVER_ERROR, 1862 "server_error", 1863 "An error occurred.", 1864 ); 1865 } 1866 }; 1867 if request_data.expires_at < Utc::now() { 1868 let _ = state 1869 .oauth_repo 1870 .delete_authorization_request(&twofa_post_request_id) 1871 .await; 1872 return json_error( 1873 StatusCode::BAD_REQUEST, 1874 "invalid_request", 1875 "Authorization request has expired.", 1876 ); 1877 } 1878 let challenge = state 1879 .oauth_repo 1880 .get_2fa_challenge(&twofa_post_request_id) 1881 .await 1882 .ok() 1883 .flatten(); 1884 if let Some(challenge) = challenge { 1885 if challenge.expires_at < Utc::now() { 1886 let _ = state.oauth_repo.delete_2fa_challenge(challenge.id).await; 1887 return json_error( 1888 StatusCode::BAD_REQUEST, 1889 "invalid_request", 1890 "2FA code has expired. Please start over.", 1891 ); 1892 } 1893 if challenge.attempts >= MAX_2FA_ATTEMPTS { 1894 let _ = state.oauth_repo.delete_2fa_challenge(challenge.id).await; 1895 return json_error( 1896 StatusCode::FORBIDDEN, 1897 "access_denied", 1898 "Too many failed attempts. Please start over.", 1899 ); 1900 } 1901 let code_valid: bool = form 1902 .code 1903 .trim() 1904 .as_bytes() 1905 .ct_eq(challenge.code.as_bytes()) 1906 .into(); 1907 if !code_valid { 1908 let _ = state.oauth_repo.increment_2fa_attempts(challenge.id).await; 1909 return json_error( 1910 StatusCode::FORBIDDEN, 1911 "invalid_code", 1912 "Invalid verification code. Please try again.", 1913 ); 1914 } 1915 let _ = state.oauth_repo.delete_2fa_challenge(challenge.id).await; 1916 let code = Code::generate(); 1917 let device_id = extract_device_cookie(&headers); 1918 let twofa_totp_device_id = device_id.clone(); 1919 let twofa_totp_code = AuthorizationCode::from(code.0.clone()); 1920 if state 1921 .oauth_repo 1922 .update_authorization_request( 1923 &twofa_post_request_id, 1924 &challenge.did, 1925 twofa_totp_device_id.as_ref(), 1926 &twofa_totp_code, 1927 ) 1928 .await 1929 .is_err() 1930 { 1931 return json_error( 1932 StatusCode::INTERNAL_SERVER_ERROR, 1933 "server_error", 1934 "An error occurred. Please try again.", 1935 ); 1936 } 1937 let redirect_url = build_intermediate_redirect_url( 1938 &request_data.parameters.redirect_uri, 1939 &code.0, 1940 request_data.parameters.state.as_deref(), 1941 request_data.parameters.response_mode.map(|m| m.as_str()), 1942 ); 1943 return Json(serde_json::json!({ 1944 "redirect_uri": redirect_url 1945 })) 1946 .into_response(); 1947 } 1948 let did_str = match &request_data.did { 1949 Some(d) => d.clone(), 1950 None => { 1951 return json_error( 1952 StatusCode::BAD_REQUEST, 1953 "invalid_request", 1954 "No 2FA challenge found. Please start over.", 1955 ); 1956 } 1957 }; 1958 let did: tranquil_types::Did = match did_str.parse() { 1959 Ok(d) => d, 1960 Err(_) => { 1961 return json_error( 1962 StatusCode::BAD_REQUEST, 1963 "invalid_request", 1964 "Invalid DID format.", 1965 ); 1966 } 1967 }; 1968 if !tranquil_api::server::has_totp_enabled(&state, &did).await { 1969 return json_error( 1970 StatusCode::BAD_REQUEST, 1971 "invalid_request", 1972 "No 2FA challenge found. Please start over.", 1973 ); 1974 } 1975 let _rate_proof = match check_user_rate_limit::<TotpVerifyLimit>(&state, &did).await { 1976 Ok(proof) => proof, 1977 Err(_) => { 1978 return json_error( 1979 StatusCode::TOO_MANY_REQUESTS, 1980 "RateLimitExceeded", 1981 "Too many verification attempts. Please try again in a few minutes.", 1982 ); 1983 } 1984 }; 1985 let totp_valid = 1986 tranquil_api::server::verify_totp_or_backup_for_user(&state, &did, &form.code).await; 1987 if !totp_valid { 1988 return json_error( 1989 StatusCode::FORBIDDEN, 1990 "invalid_code", 1991 "Invalid verification code. Please try again.", 1992 ); 1993 } 1994 let mut device_id = extract_device_cookie(&headers); 1995 let mut new_cookie: Option<String> = None; 1996 if form.trust_device { 1997 let trust_device_id = match &device_id { 1998 Some(existing_id) => existing_id.clone(), 1999 None => { 2000 let new_id = DeviceId::generate(); 2001 let new_device_id_typed = DeviceIdType::new(new_id.0.clone()); 2002 let device_data = DeviceData { 2003 session_id: SessionId::generate(), 2004 user_agent: extract_user_agent(&headers), 2005 ip_address: extract_client_ip(&headers, None), 2006 last_seen_at: Utc::now(), 2007 }; 2008 if state 2009 .oauth_repo 2010 .create_device(&new_device_id_typed, &device_data) 2011 .await 2012 .is_ok() 2013 { 2014 new_cookie = Some(make_device_cookie(&new_device_id_typed)); 2015 device_id = Some(new_device_id_typed.clone()); 2016 } 2017 new_device_id_typed 2018 } 2019 }; 2020 let _ = state 2021 .oauth_repo 2022 .upsert_account_device(&did, &trust_device_id) 2023 .await; 2024 let _ = 2025 tranquil_api::server::trust_device(state.oauth_repo.as_ref(), &trust_device_id).await; 2026 } 2027 let requested_scope_str = request_data 2028 .parameters 2029 .scope 2030 .as_deref() 2031 .unwrap_or("atproto"); 2032 let requested_scopes: Vec<String> = requested_scope_str 2033 .split_whitespace() 2034 .map(|s| s.to_string()) 2035 .collect(); 2036 let twofa_post_client_id = ClientId::from(request_data.parameters.client_id.clone()); 2037 let needs_consent = should_show_consent( 2038 state.oauth_repo.as_ref(), 2039 &did, 2040 &twofa_post_client_id, 2041 &requested_scopes, 2042 ) 2043 .await 2044 .unwrap_or(true); 2045 if needs_consent { 2046 let consent_url = format!( 2047 "/app/oauth/consent?request_uri={}", 2048 url_encode(&form.request_uri) 2049 ); 2050 if let Some(cookie) = new_cookie { 2051 return ( 2052 StatusCode::OK, 2053 [(SET_COOKIE, cookie)], 2054 Json(serde_json::json!({"redirect_uri": consent_url})), 2055 ) 2056 .into_response(); 2057 } 2058 return Json(serde_json::json!({"redirect_uri": consent_url})).into_response(); 2059 } 2060 let code = Code::generate(); 2061 let twofa_final_device_id = device_id.clone(); 2062 let twofa_final_code = AuthorizationCode::from(code.0.clone()); 2063 if state 2064 .oauth_repo 2065 .update_authorization_request( 2066 &twofa_post_request_id, 2067 &did, 2068 twofa_final_device_id.as_ref(), 2069 &twofa_final_code, 2070 ) 2071 .await 2072 .is_err() 2073 { 2074 return json_error( 2075 StatusCode::INTERNAL_SERVER_ERROR, 2076 "server_error", 2077 "An error occurred. Please try again.", 2078 ); 2079 } 2080 let redirect_url = build_intermediate_redirect_url( 2081 &request_data.parameters.redirect_uri, 2082 &code.0, 2083 request_data.parameters.state.as_deref(), 2084 request_data.parameters.response_mode.map(|m| m.as_str()), 2085 ); 2086 if let Some(cookie) = new_cookie { 2087 ( 2088 StatusCode::OK, 2089 [(SET_COOKIE, cookie)], 2090 Json(serde_json::json!({"redirect_uri": redirect_url})), 2091 ) 2092 .into_response() 2093 } else { 2094 Json(serde_json::json!({"redirect_uri": redirect_url})).into_response() 2095 } 2096} 2097 2098#[derive(Debug, Deserialize)] 2099#[serde(rename_all = "camelCase")] 2100pub struct CheckPasskeysQuery { 2101 pub identifier: String, 2102} 2103 2104#[derive(Debug, Serialize)] 2105#[serde(rename_all = "camelCase")] 2106pub struct CheckPasskeysResponse { 2107 pub has_passkeys: bool, 2108} 2109 2110pub async fn check_user_has_passkeys( 2111 State(state): State<AppState>, 2112 Query(query): Query<CheckPasskeysQuery>, 2113) -> Response { 2114 let hostname_for_handles = tranquil_config::get().server.hostname_without_port(); 2115 let bare_identifier = 2116 BareLoginIdentifier::from_identifier(&query.identifier, hostname_for_handles); 2117 2118 let user = state 2119 .user_repo 2120 .get_login_check_by_handle_or_email(bare_identifier.as_str()) 2121 .await; 2122 2123 let has_passkeys = match user { 2124 Ok(Some(u)) => tranquil_api::server::has_passkeys_for_user(&state, &u.did).await, 2125 _ => false, 2126 }; 2127 2128 Json(CheckPasskeysResponse { has_passkeys }).into_response() 2129} 2130 2131#[derive(Debug, Serialize)] 2132#[serde(rename_all = "camelCase")] 2133pub struct SecurityStatusResponse { 2134 pub has_passkeys: bool, 2135 pub has_totp: bool, 2136 pub has_password: bool, 2137 pub is_delegated: bool, 2138 #[serde(skip_serializing_if = "Option::is_none")] 2139 pub did: Option<String>, 2140} 2141 2142pub async fn check_user_security_status( 2143 State(state): State<AppState>, 2144 Query(query): Query<CheckPasskeysQuery>, 2145) -> Response { 2146 let hostname_for_handles = tranquil_config::get().server.hostname_without_port(); 2147 let normalized_identifier = 2148 NormalizedLoginIdentifier::normalize(&query.identifier, hostname_for_handles); 2149 2150 let user = state 2151 .user_repo 2152 .get_login_check_by_handle_or_email(normalized_identifier.as_str()) 2153 .await; 2154 2155 let (has_passkeys, has_totp, has_password, is_delegated, did): ( 2156 bool, 2157 bool, 2158 bool, 2159 bool, 2160 Option<String>, 2161 ) = match user { 2162 Ok(Some(u)) => { 2163 let passkeys = tranquil_api::server::has_passkeys_for_user(&state, &u.did).await; 2164 let totp = tranquil_api::server::has_totp_enabled(&state, &u.did).await; 2165 let has_pw = u.password_hash.is_some(); 2166 let has_controllers = state 2167 .delegation_repo 2168 .is_delegated_account(&u.did) 2169 .await 2170 .unwrap_or(false); 2171 ( 2172 passkeys, 2173 totp, 2174 has_pw, 2175 has_controllers, 2176 Some(u.did.to_string()), 2177 ) 2178 } 2179 _ => (false, false, false, false, None), 2180 }; 2181 2182 Json(SecurityStatusResponse { 2183 has_passkeys, 2184 has_totp, 2185 has_password, 2186 is_delegated, 2187 did, 2188 }) 2189 .into_response() 2190} 2191 2192#[derive(Debug, Deserialize)] 2193pub struct PasskeyStartInput { 2194 pub request_uri: String, 2195 pub identifier: String, 2196 pub delegated_did: Option<String>, 2197} 2198 2199#[derive(Debug, Serialize)] 2200#[serde(rename_all = "camelCase")] 2201pub struct PasskeyStartResponse { 2202 pub options: serde_json::Value, 2203} 2204 2205pub async fn passkey_start( 2206 State(state): State<AppState>, 2207 _rate_limit: OAuthRateLimited<OAuthAuthorizeLimit>, 2208 Json(form): Json<PasskeyStartInput>, 2209) -> Response { 2210 let passkey_start_request_id = RequestId::from(form.request_uri.clone()); 2211 let request_data = match state 2212 .oauth_repo 2213 .get_authorization_request(&passkey_start_request_id) 2214 .await 2215 { 2216 Ok(Some(data)) => data, 2217 Ok(None) => { 2218 return ( 2219 StatusCode::BAD_REQUEST, 2220 Json(serde_json::json!({ 2221 "error": "invalid_request", 2222 "error_description": "Invalid or expired request_uri." 2223 })), 2224 ) 2225 .into_response(); 2226 } 2227 Err(_) => { 2228 return ( 2229 StatusCode::INTERNAL_SERVER_ERROR, 2230 Json(serde_json::json!({ 2231 "error": "server_error", 2232 "error_description": "An error occurred." 2233 })), 2234 ) 2235 .into_response(); 2236 } 2237 }; 2238 2239 if request_data.expires_at < Utc::now() { 2240 let _ = state 2241 .oauth_repo 2242 .delete_authorization_request(&passkey_start_request_id) 2243 .await; 2244 return ( 2245 StatusCode::BAD_REQUEST, 2246 Json(serde_json::json!({ 2247 "error": "invalid_request", 2248 "error_description": "Authorization request has expired." 2249 })), 2250 ) 2251 .into_response(); 2252 } 2253 2254 let hostname_for_handles = tranquil_config::get().server.hostname_without_port(); 2255 let normalized_username = 2256 NormalizedLoginIdentifier::normalize(&form.identifier, hostname_for_handles); 2257 2258 let user = match state 2259 .user_repo 2260 .get_login_info_by_handle_or_email(normalized_username.as_str()) 2261 .await 2262 { 2263 Ok(Some(u)) => u, 2264 Ok(None) => { 2265 return ( 2266 StatusCode::FORBIDDEN, 2267 Json(serde_json::json!({ 2268 "error": "access_denied", 2269 "error_description": "User not found or has no passkeys." 2270 })), 2271 ) 2272 .into_response(); 2273 } 2274 Err(_) => { 2275 return ( 2276 StatusCode::INTERNAL_SERVER_ERROR, 2277 Json(serde_json::json!({ 2278 "error": "server_error", 2279 "error_description": "An error occurred." 2280 })), 2281 ) 2282 .into_response(); 2283 } 2284 }; 2285 2286 if user.deactivated_at.is_some() { 2287 return ( 2288 StatusCode::FORBIDDEN, 2289 Json(serde_json::json!({ 2290 "error": "access_denied", 2291 "error_description": "This account has been deactivated." 2292 })), 2293 ) 2294 .into_response(); 2295 } 2296 2297 if user.takedown_ref.is_some() { 2298 return ( 2299 StatusCode::FORBIDDEN, 2300 Json(serde_json::json!({ 2301 "error": "access_denied", 2302 "error_description": "This account has been taken down." 2303 })), 2304 ) 2305 .into_response(); 2306 } 2307 2308 let is_verified = user.channel_verification.has_any_verified(); 2309 2310 if !is_verified { 2311 let resend_info = tranquil_api::server::auto_resend_verification(&state, &user.did).await; 2312 return ( 2313 StatusCode::FORBIDDEN, 2314 Json(serde_json::json!({ 2315 "error": "account_not_verified", 2316 "error_description": "Please verify your account before logging in.", 2317 "did": user.did, 2318 "handle": resend_info.as_ref().map(|r| r.handle.to_string()), 2319 "channel": resend_info.as_ref().map(|r| r.channel.as_str()) 2320 })), 2321 ) 2322 .into_response(); 2323 } 2324 2325 let stored_passkeys = match state.user_repo.get_passkeys_for_user(&user.did).await { 2326 Ok(pks) => pks, 2327 Err(e) => { 2328 tracing::error!(error = %e, "Failed to get passkeys"); 2329 return ( 2330 StatusCode::INTERNAL_SERVER_ERROR, 2331 Json(serde_json::json!({ 2332 "error": "server_error", 2333 "error_description": "An error occurred." 2334 })), 2335 ) 2336 .into_response(); 2337 } 2338 }; 2339 2340 if stored_passkeys.is_empty() { 2341 return ( 2342 StatusCode::FORBIDDEN, 2343 Json(serde_json::json!({ 2344 "error": "access_denied", 2345 "error_description": "User not found or has no passkeys." 2346 })), 2347 ) 2348 .into_response(); 2349 } 2350 2351 let passkeys: Vec<webauthn_rs::prelude::SecurityKey> = stored_passkeys 2352 .iter() 2353 .filter_map(|sp| serde_json::from_slice(&sp.public_key).ok()) 2354 .collect(); 2355 2356 if passkeys.is_empty() { 2357 return ( 2358 StatusCode::INTERNAL_SERVER_ERROR, 2359 Json(serde_json::json!({ 2360 "error": "server_error", 2361 "error_description": "Failed to load passkeys." 2362 })), 2363 ) 2364 .into_response(); 2365 } 2366 2367 let (rcr, auth_state) = match state.webauthn_config.start_authentication(passkeys) { 2368 Ok(result) => result, 2369 Err(e) => { 2370 tracing::error!(error = %e, "Failed to start passkey authentication"); 2371 return ( 2372 StatusCode::INTERNAL_SERVER_ERROR, 2373 Json(serde_json::json!({ 2374 "error": "server_error", 2375 "error_description": "Failed to start authentication." 2376 })), 2377 ) 2378 .into_response(); 2379 } 2380 }; 2381 2382 let state_json = match serde_json::to_string(&auth_state) { 2383 Ok(j) => j, 2384 Err(e) => { 2385 tracing::error!(error = %e, "Failed to serialize authentication state"); 2386 return ( 2387 StatusCode::INTERNAL_SERVER_ERROR, 2388 Json(serde_json::json!({ 2389 "error": "server_error", 2390 "error_description": "An error occurred." 2391 })), 2392 ) 2393 .into_response(); 2394 } 2395 }; 2396 2397 if let Err(e) = state 2398 .user_repo 2399 .save_webauthn_challenge( 2400 &user.did, 2401 WebauthnChallengeType::Authentication, 2402 &state_json, 2403 ) 2404 .await 2405 { 2406 tracing::error!(error = %e, "Failed to save authentication state"); 2407 return ( 2408 StatusCode::INTERNAL_SERVER_ERROR, 2409 Json(serde_json::json!({ 2410 "error": "server_error", 2411 "error_description": "An error occurred." 2412 })), 2413 ) 2414 .into_response(); 2415 } 2416 2417 let delegation_from_param = match &form.delegated_did { 2418 Some(delegated_did_str) => match delegated_did_str.parse::<tranquil_types::Did>() { 2419 Ok(delegated_did) if delegated_did != user.did => { 2420 match state 2421 .delegation_repo 2422 .get_delegation(&delegated_did, &user.did) 2423 .await 2424 { 2425 Ok(Some(_)) => Some(delegated_did), 2426 Ok(None) => None, 2427 Err(e) => { 2428 tracing::warn!( 2429 error = %e, 2430 delegated_did = %delegated_did, 2431 controller_did = %user.did, 2432 "Failed to verify delegation relationship" 2433 ); 2434 None 2435 } 2436 } 2437 } 2438 _ => None, 2439 }, 2440 None => None, 2441 }; 2442 2443 let is_delegation_flow = delegation_from_param.is_some() 2444 || request_data.did.as_ref().is_some_and(|existing_did| { 2445 existing_did 2446 .parse::<tranquil_types::Did>() 2447 .ok() 2448 .is_some_and(|parsed| parsed != user.did) 2449 }); 2450 2451 if let Some(delegated_did) = delegation_from_param { 2452 tracing::info!( 2453 delegated_did = %delegated_did, 2454 controller_did = %user.did, 2455 "Passkey auth with delegated_did param - setting delegation flow" 2456 ); 2457 if state 2458 .oauth_repo 2459 .set_authorization_did(&passkey_start_request_id, &delegated_did, None) 2460 .await 2461 .is_err() 2462 { 2463 return OAuthError::ServerError("An error occurred.".into()).into_response(); 2464 } 2465 if state 2466 .oauth_repo 2467 .set_controller_did(&passkey_start_request_id, &user.did) 2468 .await 2469 .is_err() 2470 { 2471 return OAuthError::ServerError("An error occurred.".into()).into_response(); 2472 } 2473 } else if is_delegation_flow { 2474 tracing::info!( 2475 delegated_did = ?request_data.did, 2476 controller_did = %user.did, 2477 "Passkey auth in delegation flow - preserving delegated DID" 2478 ); 2479 if state 2480 .oauth_repo 2481 .set_controller_did(&passkey_start_request_id, &user.did) 2482 .await 2483 .is_err() 2484 { 2485 return OAuthError::ServerError("An error occurred.".into()).into_response(); 2486 } 2487 } else if state 2488 .oauth_repo 2489 .set_authorization_did(&passkey_start_request_id, &user.did, None) 2490 .await 2491 .is_err() 2492 { 2493 return OAuthError::ServerError("An error occurred.".into()).into_response(); 2494 } 2495 2496 let options = serde_json::to_value(&rcr).unwrap_or(serde_json::json!({})); 2497 2498 Json(PasskeyStartResponse { options }).into_response() 2499} 2500 2501#[derive(Debug, Deserialize)] 2502pub struct PasskeyFinishInput { 2503 pub request_uri: String, 2504 pub credential: serde_json::Value, 2505} 2506 2507pub async fn passkey_finish( 2508 State(state): State<AppState>, 2509 headers: HeaderMap, 2510 Json(form): Json<PasskeyFinishInput>, 2511) -> Response { 2512 let passkey_finish_request_id = RequestId::from(form.request_uri.clone()); 2513 let request_data = match state 2514 .oauth_repo 2515 .get_authorization_request(&passkey_finish_request_id) 2516 .await 2517 { 2518 Ok(Some(data)) => data, 2519 Ok(None) => { 2520 return ( 2521 StatusCode::BAD_REQUEST, 2522 Json(serde_json::json!({ 2523 "error": "invalid_request", 2524 "error_description": "Invalid or expired request_uri." 2525 })), 2526 ) 2527 .into_response(); 2528 } 2529 Err(_) => { 2530 return ( 2531 StatusCode::INTERNAL_SERVER_ERROR, 2532 Json(serde_json::json!({ 2533 "error": "server_error", 2534 "error_description": "An error occurred." 2535 })), 2536 ) 2537 .into_response(); 2538 } 2539 }; 2540 2541 if request_data.expires_at < Utc::now() { 2542 let _ = state 2543 .oauth_repo 2544 .delete_authorization_request(&passkey_finish_request_id) 2545 .await; 2546 return ( 2547 StatusCode::BAD_REQUEST, 2548 Json(serde_json::json!({ 2549 "error": "invalid_request", 2550 "error_description": "Authorization request has expired." 2551 })), 2552 ) 2553 .into_response(); 2554 } 2555 2556 let did_str = match request_data.did { 2557 Some(d) => d, 2558 None => { 2559 return ( 2560 StatusCode::BAD_REQUEST, 2561 Json(serde_json::json!({ 2562 "error": "invalid_request", 2563 "error_description": "No passkey authentication in progress." 2564 })), 2565 ) 2566 .into_response(); 2567 } 2568 }; 2569 let did: tranquil_types::Did = match did_str.parse() { 2570 Ok(d) => d, 2571 Err(_) => { 2572 return ( 2573 StatusCode::BAD_REQUEST, 2574 Json(serde_json::json!({ 2575 "error": "invalid_request", 2576 "error_description": "Invalid DID format." 2577 })), 2578 ) 2579 .into_response(); 2580 } 2581 }; 2582 2583 let controller_did: Option<tranquil_types::Did> = request_data 2584 .controller_did 2585 .as_ref() 2586 .and_then(|s| s.parse().ok()); 2587 let passkey_owner_did = controller_did.as_ref().unwrap_or(&did); 2588 2589 let auth_state_json = match state 2590 .user_repo 2591 .load_webauthn_challenge(passkey_owner_did, WebauthnChallengeType::Authentication) 2592 .await 2593 { 2594 Ok(Some(s)) => s, 2595 Ok(None) => { 2596 return ( 2597 StatusCode::BAD_REQUEST, 2598 Json(serde_json::json!({ 2599 "error": "invalid_request", 2600 "error_description": "No passkey authentication in progress or challenge expired." 2601 })), 2602 ) 2603 .into_response(); 2604 } 2605 Err(e) => { 2606 tracing::error!(error = %e, "Failed to load authentication state"); 2607 return ( 2608 StatusCode::INTERNAL_SERVER_ERROR, 2609 Json(serde_json::json!({ 2610 "error": "server_error", 2611 "error_description": "An error occurred." 2612 })), 2613 ) 2614 .into_response(); 2615 } 2616 }; 2617 2618 let auth_state: webauthn_rs::prelude::SecurityKeyAuthentication = 2619 match serde_json::from_str(&auth_state_json) { 2620 Ok(s) => s, 2621 Err(e) => { 2622 tracing::error!(error = %e, "Failed to deserialize authentication state"); 2623 return ( 2624 StatusCode::INTERNAL_SERVER_ERROR, 2625 Json(serde_json::json!({ 2626 "error": "server_error", 2627 "error_description": "An error occurred." 2628 })), 2629 ) 2630 .into_response(); 2631 } 2632 }; 2633 2634 let credential: webauthn_rs::prelude::PublicKeyCredential = 2635 match serde_json::from_value(form.credential) { 2636 Ok(c) => c, 2637 Err(e) => { 2638 tracing::warn!(error = %e, "Failed to parse credential"); 2639 return ( 2640 StatusCode::BAD_REQUEST, 2641 Json(serde_json::json!({ 2642 "error": "invalid_request", 2643 "error_description": "Failed to parse credential response." 2644 })), 2645 ) 2646 .into_response(); 2647 } 2648 }; 2649 2650 let auth_result = match state 2651 .webauthn_config 2652 .finish_authentication(&credential, &auth_state) 2653 { 2654 Ok(r) => r, 2655 Err(e) => { 2656 tracing::warn!(error = %e, did = %did, "Failed to verify passkey authentication"); 2657 return ( 2658 StatusCode::FORBIDDEN, 2659 Json(serde_json::json!({ 2660 "error": "access_denied", 2661 "error_description": "Passkey verification failed." 2662 })), 2663 ) 2664 .into_response(); 2665 } 2666 }; 2667 2668 if let Err(e) = state 2669 .user_repo 2670 .delete_webauthn_challenge(passkey_owner_did, WebauthnChallengeType::Authentication) 2671 .await 2672 { 2673 tracing::warn!(error = %e, "Failed to delete authentication state"); 2674 } 2675 2676 if auth_result.needs_update() { 2677 let cred_id_bytes = auth_result.cred_id().as_slice(); 2678 match state 2679 .user_repo 2680 .update_passkey_counter( 2681 cred_id_bytes, 2682 i32::try_from(auth_result.counter()).unwrap_or(i32::MAX), 2683 ) 2684 .await 2685 { 2686 Ok(false) => { 2687 tracing::warn!(did = %did, "Passkey counter anomaly detected - possible cloned key"); 2688 return ( 2689 StatusCode::FORBIDDEN, 2690 Json(serde_json::json!({ 2691 "error": "access_denied", 2692 "error_description": "Security key counter anomaly detected. This may indicate a cloned key." 2693 })), 2694 ) 2695 .into_response(); 2696 } 2697 Err(e) => { 2698 tracing::warn!(error = %e, "Failed to update passkey counter"); 2699 } 2700 Ok(true) => {} 2701 } 2702 } 2703 2704 tracing::info!(did = %did, "Passkey authentication successful"); 2705 2706 let device_id = extract_device_cookie(&headers); 2707 let requested_scope_str = request_data 2708 .parameters 2709 .scope 2710 .as_deref() 2711 .unwrap_or("atproto"); 2712 let requested_scopes: Vec<String> = requested_scope_str 2713 .split_whitespace() 2714 .map(|s| s.to_string()) 2715 .collect(); 2716 2717 let passkey_finish_client_id = ClientId::from(request_data.parameters.client_id.clone()); 2718 let needs_consent = should_show_consent( 2719 state.oauth_repo.as_ref(), 2720 &did, 2721 &passkey_finish_client_id, 2722 &requested_scopes, 2723 ) 2724 .await 2725 .unwrap_or(true); 2726 2727 if needs_consent { 2728 let consent_url = format!( 2729 "/app/oauth/consent?request_uri={}", 2730 url_encode(&form.request_uri) 2731 ); 2732 return Json(serde_json::json!({"redirect_uri": consent_url})).into_response(); 2733 } 2734 2735 let code = Code::generate(); 2736 let passkey_final_device_id = device_id.clone(); 2737 let passkey_final_code = AuthorizationCode::from(code.0.clone()); 2738 if state 2739 .oauth_repo 2740 .update_authorization_request( 2741 &passkey_finish_request_id, 2742 &did, 2743 passkey_final_device_id.as_ref(), 2744 &passkey_final_code, 2745 ) 2746 .await 2747 .is_err() 2748 { 2749 return ( 2750 StatusCode::INTERNAL_SERVER_ERROR, 2751 Json(serde_json::json!({ 2752 "error": "server_error", 2753 "error_description": "An error occurred." 2754 })), 2755 ) 2756 .into_response(); 2757 } 2758 2759 let redirect_url = build_intermediate_redirect_url( 2760 &request_data.parameters.redirect_uri, 2761 &code.0, 2762 request_data.parameters.state.as_deref(), 2763 request_data.parameters.response_mode.map(|m| m.as_str()), 2764 ); 2765 2766 Json(serde_json::json!({ 2767 "redirect_uri": redirect_url 2768 })) 2769 .into_response() 2770} 2771 2772#[derive(Debug, Deserialize)] 2773pub struct AuthorizePasskeyQuery { 2774 pub request_uri: String, 2775} 2776 2777#[derive(Debug, Serialize)] 2778#[serde(rename_all = "camelCase")] 2779pub struct PasskeyAuthResponse { 2780 pub options: serde_json::Value, 2781 pub request_uri: String, 2782} 2783 2784pub async fn authorize_passkey_start( 2785 State(state): State<AppState>, 2786 Query(query): Query<AuthorizePasskeyQuery>, 2787) -> Response { 2788 let auth_passkey_start_request_id = RequestId::from(query.request_uri.clone()); 2789 let request_data = match state 2790 .oauth_repo 2791 .get_authorization_request(&auth_passkey_start_request_id) 2792 .await 2793 { 2794 Ok(Some(d)) => d, 2795 Ok(None) => { 2796 return ( 2797 StatusCode::BAD_REQUEST, 2798 Json(serde_json::json!({ 2799 "error": "invalid_request", 2800 "error_description": "Authorization request not found." 2801 })), 2802 ) 2803 .into_response(); 2804 } 2805 Err(_) => { 2806 return ( 2807 StatusCode::INTERNAL_SERVER_ERROR, 2808 Json(serde_json::json!({ 2809 "error": "server_error", 2810 "error_description": "An error occurred." 2811 })), 2812 ) 2813 .into_response(); 2814 } 2815 }; 2816 2817 if request_data.expires_at < Utc::now() { 2818 let _ = state 2819 .oauth_repo 2820 .delete_authorization_request(&auth_passkey_start_request_id) 2821 .await; 2822 return ( 2823 StatusCode::BAD_REQUEST, 2824 Json(serde_json::json!({ 2825 "error": "invalid_request", 2826 "error_description": "Authorization request has expired." 2827 })), 2828 ) 2829 .into_response(); 2830 } 2831 2832 let did_str = match &request_data.did { 2833 Some(d) => d.clone(), 2834 None => { 2835 return ( 2836 StatusCode::BAD_REQUEST, 2837 Json(serde_json::json!({ 2838 "error": "invalid_request", 2839 "error_description": "User not authenticated yet." 2840 })), 2841 ) 2842 .into_response(); 2843 } 2844 }; 2845 2846 let did: tranquil_types::Did = match did_str.parse() { 2847 Ok(d) => d, 2848 Err(_) => { 2849 return ( 2850 StatusCode::BAD_REQUEST, 2851 Json(serde_json::json!({ 2852 "error": "invalid_request", 2853 "error_description": "Invalid DID format." 2854 })), 2855 ) 2856 .into_response(); 2857 } 2858 }; 2859 2860 let stored_passkeys = match state.user_repo.get_passkeys_for_user(&did).await { 2861 Ok(pks) => pks, 2862 Err(e) => { 2863 tracing::error!("Failed to get passkeys: {:?}", e); 2864 return ( 2865 StatusCode::INTERNAL_SERVER_ERROR, 2866 Json(serde_json::json!({"error": "server_error", "error_description": "An error occurred."})), 2867 ) 2868 .into_response(); 2869 } 2870 }; 2871 2872 if stored_passkeys.is_empty() { 2873 return ( 2874 StatusCode::BAD_REQUEST, 2875 Json(serde_json::json!({ 2876 "error": "invalid_request", 2877 "error_description": "No passkeys registered for this account." 2878 })), 2879 ) 2880 .into_response(); 2881 } 2882 2883 let passkeys: Vec<webauthn_rs::prelude::SecurityKey> = stored_passkeys 2884 .iter() 2885 .filter_map(|sp| serde_json::from_slice(&sp.public_key).ok()) 2886 .collect(); 2887 2888 if passkeys.is_empty() { 2889 return ( 2890 StatusCode::INTERNAL_SERVER_ERROR, 2891 Json(serde_json::json!({"error": "server_error", "error_description": "Failed to load passkeys."})), 2892 ) 2893 .into_response(); 2894 } 2895 2896 let (rcr, auth_state) = match state.webauthn_config.start_authentication(passkeys) { 2897 Ok(result) => result, 2898 Err(e) => { 2899 tracing::error!("Failed to start passkey authentication: {:?}", e); 2900 return ( 2901 StatusCode::INTERNAL_SERVER_ERROR, 2902 Json(serde_json::json!({"error": "server_error", "error_description": "An error occurred."})), 2903 ) 2904 .into_response(); 2905 } 2906 }; 2907 2908 let state_json = match serde_json::to_string(&auth_state) { 2909 Ok(j) => j, 2910 Err(e) => { 2911 tracing::error!("Failed to serialize authentication state: {:?}", e); 2912 return ( 2913 StatusCode::INTERNAL_SERVER_ERROR, 2914 Json(serde_json::json!({"error": "server_error", "error_description": "An error occurred."})), 2915 ) 2916 .into_response(); 2917 } 2918 }; 2919 2920 if let Err(e) = state 2921 .user_repo 2922 .save_webauthn_challenge(&did, WebauthnChallengeType::Authentication, &state_json) 2923 .await 2924 { 2925 tracing::error!("Failed to save authentication state: {:?}", e); 2926 return ( 2927 StatusCode::INTERNAL_SERVER_ERROR, 2928 Json(serde_json::json!({"error": "server_error", "error_description": "An error occurred."})), 2929 ) 2930 .into_response(); 2931 } 2932 2933 let options = serde_json::to_value(&rcr).unwrap_or(serde_json::json!({})); 2934 Json(PasskeyAuthResponse { 2935 options, 2936 request_uri: query.request_uri, 2937 }) 2938 .into_response() 2939} 2940 2941#[derive(Debug, Deserialize)] 2942#[serde(rename_all = "camelCase")] 2943pub struct AuthorizePasskeySubmit { 2944 pub request_uri: String, 2945 pub credential: serde_json::Value, 2946} 2947 2948pub async fn authorize_passkey_finish( 2949 State(state): State<AppState>, 2950 headers: HeaderMap, 2951 Json(form): Json<AuthorizePasskeySubmit>, 2952) -> Response { 2953 let pds_hostname = &tranquil_config::get().server.hostname; 2954 let passkey_finish_request_id = RequestId::from(form.request_uri.clone()); 2955 2956 let request_data = match state 2957 .oauth_repo 2958 .get_authorization_request(&passkey_finish_request_id) 2959 .await 2960 { 2961 Ok(Some(d)) => d, 2962 Ok(None) => { 2963 return ( 2964 StatusCode::BAD_REQUEST, 2965 Json(serde_json::json!({ 2966 "error": "invalid_request", 2967 "error_description": "Authorization request not found." 2968 })), 2969 ) 2970 .into_response(); 2971 } 2972 Err(_) => { 2973 return ( 2974 StatusCode::INTERNAL_SERVER_ERROR, 2975 Json(serde_json::json!({ 2976 "error": "server_error", 2977 "error_description": "An error occurred." 2978 })), 2979 ) 2980 .into_response(); 2981 } 2982 }; 2983 2984 if request_data.expires_at < Utc::now() { 2985 let _ = state 2986 .oauth_repo 2987 .delete_authorization_request(&passkey_finish_request_id) 2988 .await; 2989 return ( 2990 StatusCode::BAD_REQUEST, 2991 Json(serde_json::json!({ 2992 "error": "invalid_request", 2993 "error_description": "Authorization request has expired." 2994 })), 2995 ) 2996 .into_response(); 2997 } 2998 2999 let did_str = match &request_data.did { 3000 Some(d) => d.clone(), 3001 None => { 3002 return ( 3003 StatusCode::BAD_REQUEST, 3004 Json(serde_json::json!({ 3005 "error": "invalid_request", 3006 "error_description": "User not authenticated yet." 3007 })), 3008 ) 3009 .into_response(); 3010 } 3011 }; 3012 3013 let did: tranquil_types::Did = match did_str.parse() { 3014 Ok(d) => d, 3015 Err(_) => { 3016 return ( 3017 StatusCode::BAD_REQUEST, 3018 Json(serde_json::json!({ 3019 "error": "invalid_request", 3020 "error_description": "Invalid DID format." 3021 })), 3022 ) 3023 .into_response(); 3024 } 3025 }; 3026 3027 let auth_state_json = match state 3028 .user_repo 3029 .load_webauthn_challenge(&did, WebauthnChallengeType::Authentication) 3030 .await 3031 { 3032 Ok(Some(s)) => s, 3033 Ok(None) => { 3034 return ( 3035 StatusCode::BAD_REQUEST, 3036 Json(serde_json::json!({ 3037 "error": "invalid_request", 3038 "error_description": "No passkey challenge found. Please start over." 3039 })), 3040 ) 3041 .into_response(); 3042 } 3043 Err(e) => { 3044 tracing::error!("Failed to load authentication state: {:?}", e); 3045 return ( 3046 StatusCode::INTERNAL_SERVER_ERROR, 3047 Json(serde_json::json!({"error": "server_error", "error_description": "An error occurred."})), 3048 ) 3049 .into_response(); 3050 } 3051 }; 3052 3053 let auth_state: webauthn_rs::prelude::SecurityKeyAuthentication = match serde_json::from_str( 3054 &auth_state_json, 3055 ) { 3056 Ok(s) => s, 3057 Err(e) => { 3058 tracing::error!("Failed to deserialize authentication state: {:?}", e); 3059 return ( 3060 StatusCode::INTERNAL_SERVER_ERROR, 3061 Json(serde_json::json!({"error": "server_error", "error_description": "An error occurred."})), 3062 ) 3063 .into_response(); 3064 } 3065 }; 3066 3067 let credential: webauthn_rs::prelude::PublicKeyCredential = 3068 match serde_json::from_value(form.credential.clone()) { 3069 Ok(c) => c, 3070 Err(e) => { 3071 tracing::error!("Failed to parse credential: {:?}", e); 3072 return ( 3073 StatusCode::BAD_REQUEST, 3074 Json(serde_json::json!({ 3075 "error": "invalid_request", 3076 "error_description": "Invalid credential format." 3077 })), 3078 ) 3079 .into_response(); 3080 } 3081 }; 3082 3083 let auth_result = match state 3084 .webauthn_config 3085 .finish_authentication(&credential, &auth_state) 3086 { 3087 Ok(r) => r, 3088 Err(e) => { 3089 tracing::warn!("Passkey authentication failed: {:?}", e); 3090 return ( 3091 StatusCode::FORBIDDEN, 3092 Json(serde_json::json!({ 3093 "error": "access_denied", 3094 "error_description": "Passkey authentication failed." 3095 })), 3096 ) 3097 .into_response(); 3098 } 3099 }; 3100 3101 let _ = state 3102 .user_repo 3103 .delete_webauthn_challenge(&did, WebauthnChallengeType::Authentication) 3104 .await; 3105 3106 match state 3107 .user_repo 3108 .update_passkey_counter( 3109 credential.id.as_ref(), 3110 i32::try_from(auth_result.counter()).unwrap_or(i32::MAX), 3111 ) 3112 .await 3113 { 3114 Ok(false) => { 3115 tracing::warn!(did = %did, "Passkey counter anomaly detected - possible cloned key"); 3116 return ( 3117 StatusCode::FORBIDDEN, 3118 Json(serde_json::json!({ 3119 "error": "access_denied", 3120 "error_description": "Security key counter anomaly detected. This may indicate a cloned key." 3121 })), 3122 ) 3123 .into_response(); 3124 } 3125 Err(e) => { 3126 tracing::warn!("Failed to update passkey counter: {:?}", e); 3127 } 3128 Ok(true) => {} 3129 } 3130 3131 let has_totp = state 3132 .user_repo 3133 .has_totp_enabled(&did) 3134 .await 3135 .unwrap_or(false); 3136 if has_totp { 3137 let device_cookie = extract_device_cookie(&headers); 3138 let device_is_trusted = if let Some(ref dev_id) = device_cookie { 3139 tranquil_api::server::is_device_trusted(state.oauth_repo.as_ref(), dev_id, &did).await 3140 } else { 3141 false 3142 }; 3143 3144 if device_is_trusted { 3145 if let Some(ref dev_id) = device_cookie { 3146 let _ = 3147 tranquil_api::server::extend_device_trust(state.oauth_repo.as_ref(), dev_id) 3148 .await; 3149 } 3150 } else { 3151 let user = match state.user_repo.get_2fa_status_by_did(&did).await { 3152 Ok(Some(u)) => u, 3153 _ => { 3154 return ( 3155 StatusCode::INTERNAL_SERVER_ERROR, 3156 Json(serde_json::json!({"error": "server_error", "error_description": "An error occurred."})), 3157 ) 3158 .into_response(); 3159 } 3160 }; 3161 3162 let _ = state 3163 .oauth_repo 3164 .delete_2fa_challenge_by_request_uri(&passkey_finish_request_id) 3165 .await; 3166 match state 3167 .oauth_repo 3168 .create_2fa_challenge(&did, &passkey_finish_request_id) 3169 .await 3170 { 3171 Ok(challenge) => { 3172 if let Err(e) = enqueue_2fa_code( 3173 state.user_repo.as_ref(), 3174 state.infra_repo.as_ref(), 3175 user.id, 3176 &challenge.code, 3177 pds_hostname, 3178 ) 3179 .await 3180 { 3181 tracing::warn!(did = %did, error = %e, "Failed to enqueue 2FA notification"); 3182 } 3183 let channel_name = user.preferred_comms_channel.display_name(); 3184 let redirect_url = format!( 3185 "/app/oauth/2fa?request_uri={}&channel={}", 3186 url_encode(&form.request_uri), 3187 url_encode(channel_name) 3188 ); 3189 return ( 3190 StatusCode::OK, 3191 Json(serde_json::json!({ 3192 "next": "2fa", 3193 "redirect": redirect_url 3194 })), 3195 ) 3196 .into_response(); 3197 } 3198 Err(_) => { 3199 return ( 3200 StatusCode::INTERNAL_SERVER_ERROR, 3201 Json(serde_json::json!({"error": "server_error", "error_description": "An error occurred."})), 3202 ) 3203 .into_response(); 3204 } 3205 } 3206 } 3207 } 3208 3209 let redirect_url = format!( 3210 "/app/oauth/consent?request_uri={}", 3211 url_encode(&form.request_uri) 3212 ); 3213 ( 3214 StatusCode::OK, 3215 Json(serde_json::json!({ 3216 "next": "consent", 3217 "redirect": redirect_url 3218 })), 3219 ) 3220 .into_response() 3221} 3222 3223#[derive(Debug, Deserialize)] 3224pub struct RegisterCompleteInput { 3225 pub request_uri: String, 3226 pub did: String, 3227 pub app_password: String, 3228} 3229 3230pub async fn register_complete( 3231 State(state): State<AppState>, 3232 _rate_limit: OAuthRateLimited<OAuthRegisterCompleteLimit>, 3233 Json(form): Json<RegisterCompleteInput>, 3234) -> Response { 3235 let did = Did::from(form.did.clone()); 3236 3237 let request_id = RequestId::from(form.request_uri.clone()); 3238 let request_data = match state 3239 .oauth_repo 3240 .get_authorization_request(&request_id) 3241 .await 3242 { 3243 Ok(Some(data)) => data, 3244 Ok(None) => { 3245 return ( 3246 StatusCode::BAD_REQUEST, 3247 Json(serde_json::json!({ 3248 "error": "invalid_request", 3249 "error_description": "Invalid or expired request_uri." 3250 })), 3251 ) 3252 .into_response(); 3253 } 3254 Err(e) => { 3255 tracing::error!( 3256 request_uri = %form.request_uri, 3257 error = ?e, 3258 "register_complete: failed to fetch authorization request" 3259 ); 3260 return ( 3261 StatusCode::INTERNAL_SERVER_ERROR, 3262 Json(serde_json::json!({ 3263 "error": "server_error", 3264 "error_description": "An error occurred." 3265 })), 3266 ) 3267 .into_response(); 3268 } 3269 }; 3270 3271 if request_data.expires_at < Utc::now() { 3272 let _ = state 3273 .oauth_repo 3274 .delete_authorization_request(&request_id) 3275 .await; 3276 return ( 3277 StatusCode::BAD_REQUEST, 3278 Json(serde_json::json!({ 3279 "error": "invalid_request", 3280 "error_description": "Authorization request has expired." 3281 })), 3282 ) 3283 .into_response(); 3284 } 3285 3286 if request_data.parameters.prompt != Some(Prompt::Create) { 3287 tracing::warn!( 3288 request_uri = %form.request_uri, 3289 prompt = ?request_data.parameters.prompt, 3290 "register_complete called on non-registration OAuth flow" 3291 ); 3292 return ( 3293 StatusCode::BAD_REQUEST, 3294 Json(serde_json::json!({ 3295 "error": "invalid_request", 3296 "error_description": "This endpoint is only for registration flows." 3297 })), 3298 ) 3299 .into_response(); 3300 } 3301 3302 if request_data.code.is_some() { 3303 tracing::warn!( 3304 request_uri = %form.request_uri, 3305 "register_complete called on already-completed OAuth flow" 3306 ); 3307 return ( 3308 StatusCode::BAD_REQUEST, 3309 Json(serde_json::json!({ 3310 "error": "invalid_request", 3311 "error_description": "Authorization has already been completed." 3312 })), 3313 ) 3314 .into_response(); 3315 } 3316 3317 if let Some(existing_did) = &request_data.did 3318 && existing_did != &form.did 3319 { 3320 tracing::warn!( 3321 request_uri = %form.request_uri, 3322 existing_did = %existing_did, 3323 attempted_did = %form.did, 3324 "register_complete attempted with different DID than already bound" 3325 ); 3326 return ( 3327 StatusCode::BAD_REQUEST, 3328 Json(serde_json::json!({ 3329 "error": "invalid_request", 3330 "error_description": "Authorization request is already bound to a different account." 3331 })), 3332 ) 3333 .into_response(); 3334 } 3335 3336 let password_hashes = match state 3337 .session_repo 3338 .get_app_password_hashes_by_did(&did) 3339 .await 3340 { 3341 Ok(hashes) => hashes, 3342 Err(e) => { 3343 tracing::error!( 3344 did = %did, 3345 error = ?e, 3346 "register_complete: failed to fetch app password hashes" 3347 ); 3348 return ( 3349 StatusCode::INTERNAL_SERVER_ERROR, 3350 Json(serde_json::json!({ 3351 "error": "server_error", 3352 "error_description": "An error occurred." 3353 })), 3354 ) 3355 .into_response(); 3356 } 3357 }; 3358 3359 let mut password_valid = password_hashes.iter().fold(false, |acc, hash| { 3360 acc | bcrypt::verify(&form.app_password, hash).unwrap_or(false) 3361 }); 3362 3363 if !password_valid 3364 && let Ok(Some(account_hash)) = state.user_repo.get_password_hash_by_did(&did).await 3365 { 3366 password_valid = bcrypt::verify(&form.app_password, &account_hash).unwrap_or(false); 3367 } 3368 3369 if !password_valid { 3370 return ( 3371 StatusCode::FORBIDDEN, 3372 Json(serde_json::json!({ 3373 "error": "access_denied", 3374 "error_description": "Invalid credentials." 3375 })), 3376 ) 3377 .into_response(); 3378 } 3379 3380 let is_verified = match state.user_repo.get_session_info_by_did(&did).await { 3381 Ok(Some(info)) => info.channel_verification.has_any_verified(), 3382 Ok(None) => { 3383 return ( 3384 StatusCode::FORBIDDEN, 3385 Json(serde_json::json!({ 3386 "error": "access_denied", 3387 "error_description": "Account not found." 3388 })), 3389 ) 3390 .into_response(); 3391 } 3392 Err(e) => { 3393 tracing::error!( 3394 did = %did, 3395 error = ?e, 3396 "register_complete: failed to fetch session info" 3397 ); 3398 return ( 3399 StatusCode::INTERNAL_SERVER_ERROR, 3400 Json(serde_json::json!({ 3401 "error": "server_error", 3402 "error_description": "An error occurred." 3403 })), 3404 ) 3405 .into_response(); 3406 } 3407 }; 3408 3409 if !is_verified { 3410 let resend_info = tranquil_api::server::auto_resend_verification(&state, &did).await; 3411 return ( 3412 StatusCode::FORBIDDEN, 3413 Json(serde_json::json!({ 3414 "error": "account_not_verified", 3415 "error_description": "Please verify your account before continuing.", 3416 "did": did, 3417 "handle": resend_info.as_ref().map(|r| r.handle.to_string()), 3418 "channel": resend_info.as_ref().map(|r| r.channel.as_str()) 3419 })), 3420 ) 3421 .into_response(); 3422 } 3423 3424 if let Err(e) = state 3425 .oauth_repo 3426 .set_authorization_did(&request_id, &did, None) 3427 .await 3428 { 3429 tracing::error!( 3430 request_uri = %form.request_uri, 3431 did = %did, 3432 error = ?e, 3433 "register_complete: failed to set authorization DID" 3434 ); 3435 return ( 3436 StatusCode::INTERNAL_SERVER_ERROR, 3437 Json(serde_json::json!({ 3438 "error": "server_error", 3439 "error_description": "An error occurred." 3440 })), 3441 ) 3442 .into_response(); 3443 } 3444 3445 let requested_scope_str = request_data 3446 .parameters 3447 .scope 3448 .as_deref() 3449 .unwrap_or("atproto"); 3450 let requested_scopes: Vec<String> = requested_scope_str 3451 .split_whitespace() 3452 .map(|s| s.to_string()) 3453 .collect(); 3454 let client_id_typed = ClientId::from(request_data.parameters.client_id.clone()); 3455 let needs_consent = should_show_consent( 3456 state.oauth_repo.as_ref(), 3457 &did, 3458 &client_id_typed, 3459 &requested_scopes, 3460 ) 3461 .await 3462 .unwrap_or(true); 3463 3464 if needs_consent { 3465 tracing::info!( 3466 did = %did, 3467 client_id = %request_data.parameters.client_id, 3468 "OAuth registration complete, redirecting to consent" 3469 ); 3470 let consent_url = format!( 3471 "/app/oauth/consent?request_uri={}", 3472 url_encode(&form.request_uri) 3473 ); 3474 return Json(serde_json::json!({"redirect_uri": consent_url})).into_response(); 3475 } 3476 3477 let code = Code::generate(); 3478 let auth_code = AuthorizationCode::from(code.0.clone()); 3479 if let Err(e) = state 3480 .oauth_repo 3481 .update_authorization_request(&request_id, &did, None, &auth_code) 3482 .await 3483 { 3484 tracing::error!( 3485 request_uri = %form.request_uri, 3486 did = %did, 3487 error = ?e, 3488 "register_complete: failed to update authorization request with code" 3489 ); 3490 return ( 3491 StatusCode::INTERNAL_SERVER_ERROR, 3492 Json(serde_json::json!({ 3493 "error": "server_error", 3494 "error_description": "An error occurred." 3495 })), 3496 ) 3497 .into_response(); 3498 } 3499 3500 tracing::info!( 3501 did = %did, 3502 client_id = %request_data.parameters.client_id, 3503 "OAuth registration flow completed successfully" 3504 ); 3505 3506 let redirect_url = build_intermediate_redirect_url( 3507 &request_data.parameters.redirect_uri, 3508 &code.0, 3509 request_data.parameters.state.as_deref(), 3510 request_data.parameters.response_mode.map(|m| m.as_str()), 3511 ); 3512 Json(serde_json::json!({"redirect_uri": redirect_url})).into_response() 3513} 3514 3515pub async fn establish_session( 3516 State(state): State<AppState>, 3517 headers: HeaderMap, 3518 auth: tranquil_pds::auth::Auth<tranquil_pds::auth::Active>, 3519) -> Response { 3520 let did = &auth.did; 3521 3522 let existing_device = extract_device_cookie(&headers); 3523 3524 let (device_id, new_cookie) = match existing_device { 3525 Some(id) => { 3526 let _ = state.oauth_repo.upsert_account_device(did, &id).await; 3527 (id, None) 3528 } 3529 None => { 3530 let new_id = DeviceId::generate(); 3531 let device_typed = DeviceIdType::new(new_id.0.clone()); 3532 let device_data = DeviceData { 3533 session_id: SessionId::generate(), 3534 user_agent: extract_user_agent(&headers), 3535 ip_address: extract_client_ip(&headers, None), 3536 last_seen_at: Utc::now(), 3537 }; 3538 3539 if let Err(e) = state 3540 .oauth_repo 3541 .create_device(&device_typed, &device_data) 3542 .await 3543 { 3544 tracing::error!(error = ?e, "Failed to create device"); 3545 return ( 3546 StatusCode::INTERNAL_SERVER_ERROR, 3547 Json(serde_json::json!({ 3548 "error": "server_error", 3549 "error_description": "Failed to establish session" 3550 })), 3551 ) 3552 .into_response(); 3553 } 3554 3555 if let Err(e) = state 3556 .oauth_repo 3557 .upsert_account_device(did, &device_typed) 3558 .await 3559 { 3560 tracing::error!(error = ?e, "Failed to link device to account"); 3561 return ( 3562 StatusCode::INTERNAL_SERVER_ERROR, 3563 Json(serde_json::json!({ 3564 "error": "server_error", 3565 "error_description": "Failed to establish session" 3566 })), 3567 ) 3568 .into_response(); 3569 } 3570 3571 let cookie = make_device_cookie(&device_typed); 3572 (device_typed, Some(cookie)) 3573 } 3574 }; 3575 3576 tracing::info!(did = %did, device_id = %device_id, "Device session established"); 3577 3578 match new_cookie { 3579 Some(cookie) => ( 3580 StatusCode::OK, 3581 [(SET_COOKIE, cookie)], 3582 Json(serde_json::json!({ 3583 "success": true, 3584 "device_id": device_id 3585 })), 3586 ) 3587 .into_response(), 3588 None => Json(serde_json::json!({ 3589 "success": true, 3590 "device_id": device_id 3591 })) 3592 .into_response(), 3593 } 3594}