this repo has no description
1use axum::{ 2 Form, Json, 3 extract::{Query, State}, 4 http::{HeaderMap, header::SET_COOKIE}, 5 response::{IntoResponse, Redirect, Response, Html}, 6}; 7use chrono::Utc; 8use serde::{Deserialize, Serialize}; 9use subtle::ConstantTimeEq; 10use urlencoding::encode as url_encode; 11 12use crate::state::{AppState, RateLimitKind}; 13use crate::oauth::{Code, DeviceAccount, DeviceData, DeviceId, OAuthError, SessionId, db, templates}; 14use crate::notifications::{NotificationChannel, channel_display_name, enqueue_2fa_code}; 15 16const DEVICE_COOKIE_NAME: &str = "oauth_device_id"; 17 18fn extract_device_cookie(headers: &HeaderMap) -> Option<String> { 19 headers 20 .get("cookie") 21 .and_then(|v| v.to_str().ok()) 22 .and_then(|cookie_str| { 23 for cookie in cookie_str.split(';') { 24 let cookie = cookie.trim(); 25 if let Some(value) = cookie.strip_prefix(&format!("{}=", DEVICE_COOKIE_NAME)) { 26 return Some(value.to_string()); 27 } 28 } 29 None 30 }) 31} 32 33fn extract_client_ip(headers: &HeaderMap) -> String { 34 if let Some(forwarded) = headers.get("x-forwarded-for") { 35 if let Ok(value) = forwarded.to_str() { 36 if let Some(first_ip) = value.split(',').next() { 37 return first_ip.trim().to_string(); 38 } 39 } 40 } 41 42 if let Some(real_ip) = headers.get("x-real-ip") { 43 if let Ok(value) = real_ip.to_str() { 44 return value.trim().to_string(); 45 } 46 } 47 48 "0.0.0.0".to_string() 49} 50 51fn extract_user_agent(headers: &HeaderMap) -> Option<String> { 52 headers 53 .get("user-agent") 54 .and_then(|v| v.to_str().ok()) 55 .map(|s| s.to_string()) 56} 57 58fn make_device_cookie(device_id: &str) -> String { 59 format!( 60 "{}={}; Path=/oauth; HttpOnly; Secure; SameSite=Lax; Max-Age=31536000", 61 DEVICE_COOKIE_NAME, 62 device_id 63 ) 64} 65 66#[derive(Debug, Deserialize)] 67pub struct AuthorizeQuery { 68 pub request_uri: Option<String>, 69 pub client_id: Option<String>, 70 pub new_account: Option<bool>, 71} 72 73#[derive(Debug, Serialize)] 74pub struct AuthorizeResponse { 75 pub client_id: String, 76 pub client_name: Option<String>, 77 pub scope: Option<String>, 78 pub redirect_uri: String, 79 pub state: Option<String>, 80 pub login_hint: Option<String>, 81} 82 83#[derive(Debug, Deserialize)] 84pub struct AuthorizeSubmit { 85 pub request_uri: String, 86 pub username: String, 87 pub password: String, 88 #[serde(default)] 89 pub remember_device: bool, 90} 91 92#[derive(Debug, Deserialize)] 93pub struct AuthorizeSelectSubmit { 94 pub request_uri: String, 95 pub did: String, 96} 97 98fn wants_json(headers: &HeaderMap) -> bool { 99 headers 100 .get("accept") 101 .and_then(|v| v.to_str().ok()) 102 .map(|accept| accept.contains("application/json")) 103 .unwrap_or(false) 104} 105 106pub async fn authorize_get( 107 State(state): State<AppState>, 108 headers: HeaderMap, 109 Query(query): Query<AuthorizeQuery>, 110) -> Response { 111 let request_uri = match query.request_uri { 112 Some(uri) => uri, 113 None => { 114 if wants_json(&headers) { 115 return ( 116 axum::http::StatusCode::BAD_REQUEST, 117 Json(serde_json::json!({ 118 "error": "invalid_request", 119 "error_description": "Missing request_uri parameter. Use PAR to initiate authorization." 120 })), 121 ).into_response(); 122 } 123 return ( 124 axum::http::StatusCode::BAD_REQUEST, 125 Html(templates::error_page( 126 "invalid_request", 127 Some("Missing request_uri parameter. Use PAR to initiate authorization."), 128 )), 129 ).into_response(); 130 } 131 }; 132 133 let request_data = match db::get_authorization_request(&state.db, &request_uri).await { 134 Ok(Some(data)) => data, 135 Ok(None) => { 136 if wants_json(&headers) { 137 return ( 138 axum::http::StatusCode::BAD_REQUEST, 139 Json(serde_json::json!({ 140 "error": "invalid_request", 141 "error_description": "Invalid or expired request_uri. Please start a new authorization request." 142 })), 143 ).into_response(); 144 } 145 return ( 146 axum::http::StatusCode::BAD_REQUEST, 147 Html(templates::error_page( 148 "invalid_request", 149 Some("Invalid or expired request_uri. Please start a new authorization request."), 150 )), 151 ).into_response(); 152 } 153 Err(e) => { 154 if wants_json(&headers) { 155 return ( 156 axum::http::StatusCode::INTERNAL_SERVER_ERROR, 157 Json(serde_json::json!({ 158 "error": "server_error", 159 "error_description": format!("Database error: {:?}", e) 160 })), 161 ).into_response(); 162 } 163 return ( 164 axum::http::StatusCode::INTERNAL_SERVER_ERROR, 165 Html(templates::error_page( 166 "server_error", 167 Some(&format!("Database error: {:?}", e)), 168 )), 169 ).into_response(); 170 } 171 }; 172 173 if request_data.expires_at < Utc::now() { 174 let _ = db::delete_authorization_request(&state.db, &request_uri).await; 175 if wants_json(&headers) { 176 return ( 177 axum::http::StatusCode::BAD_REQUEST, 178 Json(serde_json::json!({ 179 "error": "invalid_request", 180 "error_description": "Authorization request has expired. Please start a new request." 181 })), 182 ).into_response(); 183 } 184 return ( 185 axum::http::StatusCode::BAD_REQUEST, 186 Html(templates::error_page( 187 "invalid_request", 188 Some("Authorization request has expired. Please start a new request."), 189 )), 190 ).into_response(); 191 } 192 193 if wants_json(&headers) { 194 return Json(AuthorizeResponse { 195 client_id: request_data.parameters.client_id.clone(), 196 client_name: None, 197 scope: request_data.parameters.scope.clone(), 198 redirect_uri: request_data.parameters.redirect_uri.clone(), 199 state: request_data.parameters.state.clone(), 200 login_hint: request_data.parameters.login_hint.clone(), 201 }).into_response(); 202 } 203 204 let force_new_account = query.new_account.unwrap_or(false); 205 206 if !force_new_account { 207 if let Some(device_id) = extract_device_cookie(&headers) { 208 if let Ok(accounts) = db::get_device_accounts(&state.db, &device_id).await { 209 if !accounts.is_empty() { 210 let device_accounts: Vec<DeviceAccount> = accounts 211 .into_iter() 212 .map(|row| DeviceAccount { 213 did: row.did, 214 handle: row.handle, 215 email: row.email, 216 last_used_at: row.last_used_at, 217 }) 218 .collect(); 219 220 return Html(templates::account_selector_page( 221 &request_data.parameters.client_id, 222 None, 223 &request_uri, 224 &device_accounts, 225 )).into_response(); 226 } 227 } 228 } 229 } 230 231 Html(templates::login_page( 232 &request_data.parameters.client_id, 233 None, 234 request_data.parameters.scope.as_deref(), 235 &request_uri, 236 None, 237 request_data.parameters.login_hint.as_deref(), 238 )).into_response() 239} 240 241pub async fn authorize_get_json( 242 State(state): State<AppState>, 243 Query(query): Query<AuthorizeQuery>, 244) -> Result<Json<AuthorizeResponse>, OAuthError> { 245 let request_uri = query.request_uri.ok_or_else(|| { 246 OAuthError::InvalidRequest("request_uri is required".to_string()) 247 })?; 248 249 let request_data = db::get_authorization_request(&state.db, &request_uri) 250 .await? 251 .ok_or_else(|| OAuthError::InvalidRequest("Invalid or expired request_uri".to_string()))?; 252 253 if request_data.expires_at < Utc::now() { 254 db::delete_authorization_request(&state.db, &request_uri).await?; 255 return Err(OAuthError::InvalidRequest("request_uri has expired".to_string())); 256 } 257 258 Ok(Json(AuthorizeResponse { 259 client_id: request_data.parameters.client_id.clone(), 260 client_name: None, 261 scope: request_data.parameters.scope.clone(), 262 redirect_uri: request_data.parameters.redirect_uri.clone(), 263 state: request_data.parameters.state.clone(), 264 login_hint: request_data.parameters.login_hint.clone(), 265 })) 266} 267 268pub async fn authorize_post( 269 State(state): State<AppState>, 270 headers: HeaderMap, 271 Form(form): Form<AuthorizeSubmit>, 272) -> Response { 273 let json_response = wants_json(&headers); 274 275 let client_ip = extract_client_ip(&headers); 276 if !state.check_rate_limit(RateLimitKind::OAuthAuthorize, &client_ip).await { 277 tracing::warn!(ip = %client_ip, "OAuth authorize rate limit exceeded"); 278 if json_response { 279 return ( 280 axum::http::StatusCode::TOO_MANY_REQUESTS, 281 Json(serde_json::json!({ 282 "error": "RateLimitExceeded", 283 "error_description": "Too many login attempts. Please try again later." 284 })), 285 ).into_response(); 286 } 287 return ( 288 axum::http::StatusCode::TOO_MANY_REQUESTS, 289 Html(templates::error_page( 290 "RateLimitExceeded", 291 Some("Too many login attempts. Please try again later."), 292 )), 293 ).into_response(); 294 } 295 296 let request_data = match db::get_authorization_request(&state.db, &form.request_uri).await { 297 Ok(Some(data)) => data, 298 Ok(None) => { 299 if json_response { 300 return ( 301 axum::http::StatusCode::BAD_REQUEST, 302 Json(serde_json::json!({ 303 "error": "invalid_request", 304 "error_description": "Invalid or expired request_uri." 305 })), 306 ).into_response(); 307 } 308 return Html(templates::error_page( 309 "invalid_request", 310 Some("Invalid or expired request_uri. Please start a new authorization request."), 311 )).into_response(); 312 } 313 Err(e) => { 314 if json_response { 315 return ( 316 axum::http::StatusCode::INTERNAL_SERVER_ERROR, 317 Json(serde_json::json!({ 318 "error": "server_error", 319 "error_description": format!("Database error: {:?}", e) 320 })), 321 ).into_response(); 322 } 323 return Html(templates::error_page( 324 "server_error", 325 Some(&format!("Database error: {:?}", e)), 326 )).into_response(); 327 } 328 }; 329 330 if request_data.expires_at < Utc::now() { 331 let _ = db::delete_authorization_request(&state.db, &form.request_uri).await; 332 if json_response { 333 return ( 334 axum::http::StatusCode::BAD_REQUEST, 335 Json(serde_json::json!({ 336 "error": "invalid_request", 337 "error_description": "Authorization request has expired." 338 })), 339 ).into_response(); 340 } 341 return Html(templates::error_page( 342 "invalid_request", 343 Some("Authorization request has expired. Please start a new request."), 344 )).into_response(); 345 } 346 347 let show_login_error = |error_msg: &str, json: bool| -> Response { 348 if json { 349 return ( 350 axum::http::StatusCode::FORBIDDEN, 351 Json(serde_json::json!({ 352 "error": "access_denied", 353 "error_description": error_msg 354 })), 355 ).into_response(); 356 } 357 Html(templates::login_page( 358 &request_data.parameters.client_id, 359 None, 360 request_data.parameters.scope.as_deref(), 361 &form.request_uri, 362 Some(error_msg), 363 Some(&form.username), 364 )).into_response() 365 }; 366 367 let user = match sqlx::query!( 368 r#" 369 SELECT id, did, email, password_hash, two_factor_enabled, 370 preferred_notification_channel as "preferred_notification_channel: NotificationChannel", 371 deactivated_at, takedown_ref 372 FROM users 373 WHERE handle = $1 OR email = $1 374 "#, 375 form.username 376 ) 377 .fetch_optional(&state.db) 378 .await 379 { 380 Ok(Some(u)) => u, 381 Ok(None) => { 382 let _ = bcrypt::verify(&form.password, "$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.VTtYw1ZzQKZqmK"); 383 return show_login_error("Invalid handle/email or password.", json_response); 384 } 385 Err(_) => return show_login_error("An error occurred. Please try again.", json_response), 386 }; 387 388 if user.deactivated_at.is_some() { 389 return show_login_error("This account has been deactivated.", json_response); 390 } 391 392 if user.takedown_ref.is_some() { 393 return show_login_error("This account has been taken down.", json_response); 394 } 395 396 let password_valid = match bcrypt::verify(&form.password, &user.password_hash) { 397 Ok(valid) => valid, 398 Err(_) => return show_login_error("An error occurred. Please try again.", json_response), 399 }; 400 401 if !password_valid { 402 return show_login_error("Invalid handle/email or password.", json_response); 403 } 404 405 if user.two_factor_enabled { 406 let _ = db::delete_2fa_challenge_by_request_uri(&state.db, &form.request_uri).await; 407 408 match db::create_2fa_challenge(&state.db, &user.did, &form.request_uri).await { 409 Ok(challenge) => { 410 let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 411 if let Err(e) = enqueue_2fa_code( 412 &state.db, 413 user.id, 414 &challenge.code, 415 &hostname, 416 ).await { 417 tracing::warn!( 418 did = %user.did, 419 error = %e, 420 "Failed to enqueue 2FA notification" 421 ); 422 } 423 424 let channel_name = channel_display_name(user.preferred_notification_channel); 425 let redirect_url = format!( 426 "/oauth/authorize/2fa?request_uri={}&channel={}", 427 url_encode(&form.request_uri), 428 url_encode(channel_name) 429 ); 430 return Redirect::temporary(&redirect_url).into_response(); 431 } 432 Err(_) => { 433 return show_login_error("An error occurred. Please try again.", json_response); 434 } 435 } 436 } 437 438 let code = Code::generate(); 439 let mut device_id: Option<String> = extract_device_cookie(&headers); 440 let mut new_cookie: Option<String> = None; 441 442 if form.remember_device { 443 let final_device_id = if let Some(existing_id) = &device_id { 444 existing_id.clone() 445 } else { 446 let new_id = DeviceId::generate(); 447 let device_data = DeviceData { 448 session_id: SessionId::generate().0, 449 user_agent: extract_user_agent(&headers), 450 ip_address: extract_client_ip(&headers), 451 last_seen_at: Utc::now(), 452 }; 453 454 if db::create_device(&state.db, &new_id.0, &device_data).await.is_ok() { 455 new_cookie = Some(make_device_cookie(&new_id.0)); 456 device_id = Some(new_id.0.clone()); 457 } 458 new_id.0 459 }; 460 461 let _ = db::upsert_account_device(&state.db, &user.did, &final_device_id).await; 462 } 463 464 if let Err(_) = db::update_authorization_request( 465 &state.db, 466 &form.request_uri, 467 &user.did, 468 device_id.as_deref(), 469 &code.0, 470 ) 471 .await 472 { 473 return show_login_error("An error occurred. Please try again.", json_response); 474 } 475 476 let redirect_url = build_success_redirect( 477 &request_data.parameters.redirect_uri, 478 &code.0, 479 request_data.parameters.state.as_deref(), 480 ); 481 482 let redirect = Redirect::temporary(&redirect_url); 483 484 if let Some(cookie) = new_cookie { 485 ([(SET_COOKIE, cookie)], redirect).into_response() 486 } else { 487 redirect.into_response() 488 } 489} 490 491pub async fn authorize_select( 492 State(state): State<AppState>, 493 headers: HeaderMap, 494 Form(form): Form<AuthorizeSelectSubmit>, 495) -> Response { 496 let request_data = match db::get_authorization_request(&state.db, &form.request_uri).await { 497 Ok(Some(data)) => data, 498 Ok(None) => { 499 return Html(templates::error_page( 500 "invalid_request", 501 Some("Invalid or expired request_uri. Please start a new authorization request."), 502 )).into_response(); 503 } 504 Err(_) => { 505 return Html(templates::error_page( 506 "server_error", 507 Some("An error occurred. Please try again."), 508 )).into_response(); 509 } 510 }; 511 512 if request_data.expires_at < Utc::now() { 513 let _ = db::delete_authorization_request(&state.db, &form.request_uri).await; 514 return Html(templates::error_page( 515 "invalid_request", 516 Some("Authorization request has expired. Please start a new request."), 517 )).into_response(); 518 } 519 520 let device_id = match extract_device_cookie(&headers) { 521 Some(id) => id, 522 None => { 523 return Html(templates::error_page( 524 "invalid_request", 525 Some("No device session found. Please sign in."), 526 )).into_response(); 527 } 528 }; 529 530 let account_valid = match db::verify_account_on_device(&state.db, &device_id, &form.did).await { 531 Ok(valid) => valid, 532 Err(_) => { 533 return Html(templates::error_page( 534 "server_error", 535 Some("An error occurred. Please try again."), 536 )).into_response(); 537 } 538 }; 539 540 if !account_valid { 541 return Html(templates::error_page( 542 "access_denied", 543 Some("This account is not available on this device. Please sign in."), 544 )).into_response(); 545 } 546 547 let user = match sqlx::query!( 548 r#" 549 SELECT id, two_factor_enabled, 550 preferred_notification_channel as "preferred_notification_channel: NotificationChannel" 551 FROM users 552 WHERE did = $1 553 "#, 554 form.did 555 ) 556 .fetch_optional(&state.db) 557 .await 558 { 559 Ok(Some(u)) => u, 560 Ok(None) => { 561 return Html(templates::error_page( 562 "access_denied", 563 Some("Account not found. Please sign in."), 564 )).into_response(); 565 } 566 Err(_) => { 567 return Html(templates::error_page( 568 "server_error", 569 Some("An error occurred. Please try again."), 570 )).into_response(); 571 } 572 }; 573 574 if user.two_factor_enabled { 575 let _ = db::delete_2fa_challenge_by_request_uri(&state.db, &form.request_uri).await; 576 577 match db::create_2fa_challenge(&state.db, &form.did, &form.request_uri).await { 578 Ok(challenge) => { 579 let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 580 if let Err(e) = enqueue_2fa_code( 581 &state.db, 582 user.id, 583 &challenge.code, 584 &hostname, 585 ).await { 586 tracing::warn!( 587 did = %form.did, 588 error = %e, 589 "Failed to enqueue 2FA notification" 590 ); 591 } 592 593 let channel_name = channel_display_name(user.preferred_notification_channel); 594 let redirect_url = format!( 595 "/oauth/authorize/2fa?request_uri={}&channel={}", 596 url_encode(&form.request_uri), 597 url_encode(channel_name) 598 ); 599 return Redirect::temporary(&redirect_url).into_response(); 600 } 601 Err(_) => { 602 return Html(templates::error_page( 603 "server_error", 604 Some("An error occurred. Please try again."), 605 )).into_response(); 606 } 607 } 608 } 609 610 let _ = db::upsert_account_device(&state.db, &form.did, &device_id).await; 611 612 let code = Code::generate(); 613 614 if let Err(_) = db::update_authorization_request( 615 &state.db, 616 &form.request_uri, 617 &form.did, 618 Some(&device_id), 619 &code.0, 620 ) 621 .await 622 { 623 return Html(templates::error_page( 624 "server_error", 625 Some("An error occurred. Please try again."), 626 )).into_response(); 627 } 628 629 let redirect_url = build_success_redirect( 630 &request_data.parameters.redirect_uri, 631 &code.0, 632 request_data.parameters.state.as_deref(), 633 ); 634 635 Redirect::temporary(&redirect_url).into_response() 636} 637 638fn build_success_redirect(redirect_uri: &str, code: &str, state: Option<&str>) -> String { 639 let mut redirect_url = redirect_uri.to_string(); 640 641 let separator = if redirect_url.contains('?') { '&' } else { '?' }; 642 redirect_url.push(separator); 643 redirect_url.push_str(&format!("code={}", url_encode(code))); 644 645 if let Some(req_state) = state { 646 redirect_url.push_str(&format!("&state={}", url_encode(req_state))); 647 } 648 649 let pds_hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 650 redirect_url.push_str(&format!("&iss={}", url_encode(&format!("https://{}", pds_hostname)))); 651 652 redirect_url 653} 654 655#[derive(Debug, Serialize)] 656pub struct AuthorizeDenyResponse { 657 pub error: String, 658 pub error_description: String, 659} 660 661pub async fn authorize_deny( 662 State(state): State<AppState>, 663 Form(form): Form<AuthorizeDenyForm>, 664) -> Result<Response, OAuthError> { 665 let request_data = db::get_authorization_request(&state.db, &form.request_uri) 666 .await? 667 .ok_or_else(|| OAuthError::InvalidRequest("Invalid request_uri".to_string()))?; 668 669 db::delete_authorization_request(&state.db, &form.request_uri).await?; 670 671 let redirect_uri = &request_data.parameters.redirect_uri; 672 let mut redirect_url = redirect_uri.to_string(); 673 674 let separator = if redirect_url.contains('?') { '&' } else { '?' }; 675 redirect_url.push(separator); 676 redirect_url.push_str("error=access_denied"); 677 redirect_url.push_str("&error_description=User%20denied%20the%20request"); 678 679 if let Some(state) = &request_data.parameters.state { 680 redirect_url.push_str(&format!("&state={}", url_encode(state))); 681 } 682 683 Ok(Redirect::temporary(&redirect_url).into_response()) 684} 685 686#[derive(Debug, Deserialize)] 687pub struct AuthorizeDenyForm { 688 pub request_uri: String, 689} 690 691#[derive(Debug, Deserialize)] 692pub struct Authorize2faQuery { 693 pub request_uri: String, 694 pub channel: Option<String>, 695} 696 697#[derive(Debug, Deserialize)] 698pub struct Authorize2faSubmit { 699 pub request_uri: String, 700 pub code: String, 701} 702 703const MAX_2FA_ATTEMPTS: i32 = 5; 704 705pub async fn authorize_2fa_get( 706 State(state): State<AppState>, 707 Query(query): Query<Authorize2faQuery>, 708) -> Response { 709 let challenge = match db::get_2fa_challenge(&state.db, &query.request_uri).await { 710 Ok(Some(c)) => c, 711 Ok(None) => { 712 return Html(templates::error_page( 713 "invalid_request", 714 Some("No 2FA challenge found. Please start over."), 715 )).into_response(); 716 } 717 Err(_) => { 718 return Html(templates::error_page( 719 "server_error", 720 Some("An error occurred. Please try again."), 721 )).into_response(); 722 } 723 }; 724 725 if challenge.expires_at < Utc::now() { 726 let _ = db::delete_2fa_challenge(&state.db, challenge.id).await; 727 return Html(templates::error_page( 728 "invalid_request", 729 Some("2FA code has expired. Please start over."), 730 )).into_response(); 731 } 732 733 let _request_data = match db::get_authorization_request(&state.db, &query.request_uri).await { 734 Ok(Some(d)) => d, 735 Ok(None) => { 736 return Html(templates::error_page( 737 "invalid_request", 738 Some("Authorization request not found. Please start over."), 739 )).into_response(); 740 } 741 Err(_) => { 742 return Html(templates::error_page( 743 "server_error", 744 Some("An error occurred. Please try again."), 745 )).into_response(); 746 } 747 }; 748 749 let channel = query.channel.as_deref().unwrap_or("email"); 750 751 Html(templates::two_factor_page( 752 &query.request_uri, 753 channel, 754 None, 755 )).into_response() 756} 757 758pub async fn authorize_2fa_post( 759 State(state): State<AppState>, 760 headers: HeaderMap, 761 Form(form): Form<Authorize2faSubmit>, 762) -> Response { 763 let client_ip = extract_client_ip(&headers); 764 if !state.check_rate_limit(RateLimitKind::OAuthAuthorize, &client_ip).await { 765 tracing::warn!(ip = %client_ip, "OAuth 2FA rate limit exceeded"); 766 return ( 767 axum::http::StatusCode::TOO_MANY_REQUESTS, 768 Html(templates::error_page( 769 "RateLimitExceeded", 770 Some("Too many attempts. Please try again later."), 771 )), 772 ).into_response(); 773 } 774 775 let challenge = match db::get_2fa_challenge(&state.db, &form.request_uri).await { 776 Ok(Some(c)) => c, 777 Ok(None) => { 778 return Html(templates::error_page( 779 "invalid_request", 780 Some("No 2FA challenge found. Please start over."), 781 )).into_response(); 782 } 783 Err(_) => { 784 return Html(templates::error_page( 785 "server_error", 786 Some("An error occurred. Please try again."), 787 )).into_response(); 788 } 789 }; 790 791 if challenge.expires_at < Utc::now() { 792 let _ = db::delete_2fa_challenge(&state.db, challenge.id).await; 793 return Html(templates::error_page( 794 "invalid_request", 795 Some("2FA code has expired. Please start over."), 796 )).into_response(); 797 } 798 799 if challenge.attempts >= MAX_2FA_ATTEMPTS { 800 let _ = db::delete_2fa_challenge(&state.db, challenge.id).await; 801 return Html(templates::error_page( 802 "access_denied", 803 Some("Too many failed attempts. Please start over."), 804 )).into_response(); 805 } 806 807 let code_valid: bool = form.code.trim().as_bytes().ct_eq(challenge.code.as_bytes()).into(); 808 809 if !code_valid { 810 let _ = db::increment_2fa_attempts(&state.db, challenge.id).await; 811 812 let channel = match sqlx::query_scalar!( 813 r#"SELECT preferred_notification_channel as "channel: NotificationChannel" FROM users WHERE did = $1"#, 814 challenge.did 815 ) 816 .fetch_optional(&state.db) 817 .await 818 { 819 Ok(Some(ch)) => channel_display_name(ch).to_string(), 820 Ok(None) | Err(_) => "email".to_string(), 821 }; 822 823 let _request_data = match db::get_authorization_request(&state.db, &form.request_uri).await { 824 Ok(Some(d)) => d, 825 Ok(None) => { 826 return Html(templates::error_page( 827 "invalid_request", 828 Some("Authorization request not found. Please start over."), 829 )).into_response(); 830 } 831 Err(_) => { 832 return Html(templates::error_page( 833 "server_error", 834 Some("An error occurred. Please try again."), 835 )).into_response(); 836 } 837 }; 838 839 return Html(templates::two_factor_page( 840 &form.request_uri, 841 &channel, 842 Some("Invalid verification code. Please try again."), 843 )).into_response(); 844 } 845 846 let _ = db::delete_2fa_challenge(&state.db, challenge.id).await; 847 848 let request_data = match db::get_authorization_request(&state.db, &form.request_uri).await { 849 Ok(Some(d)) => d, 850 Ok(None) => { 851 return Html(templates::error_page( 852 "invalid_request", 853 Some("Authorization request not found."), 854 )).into_response(); 855 } 856 Err(_) => { 857 return Html(templates::error_page( 858 "server_error", 859 Some("An error occurred."), 860 )).into_response(); 861 } 862 }; 863 864 let code = Code::generate(); 865 let device_id = extract_device_cookie(&headers); 866 867 if let Err(_) = db::update_authorization_request( 868 &state.db, 869 &form.request_uri, 870 &challenge.did, 871 device_id.as_deref(), 872 &code.0, 873 ) 874 .await 875 { 876 return Html(templates::error_page( 877 "server_error", 878 Some("An error occurred. Please try again."), 879 )).into_response(); 880 } 881 882 let redirect_url = build_success_redirect( 883 &request_data.parameters.redirect_uri, 884 &code.0, 885 request_data.parameters.state.as_deref(), 886 ); 887 888 Redirect::temporary(&redirect_url).into_response() 889}