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