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;
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 request_data = match db::get_authorization_request(&state.db, &form.request_uri).await {
276 Ok(Some(data)) => data,
277 Ok(None) => {
278 if json_response {
279 return (
280 axum::http::StatusCode::BAD_REQUEST,
281 Json(serde_json::json!({
282 "error": "invalid_request",
283 "error_description": "Invalid or expired request_uri."
284 })),
285 ).into_response();
286 }
287 return Html(templates::error_page(
288 "invalid_request",
289 Some("Invalid or expired request_uri. Please start a new authorization request."),
290 )).into_response();
291 }
292 Err(e) => {
293 if json_response {
294 return (
295 axum::http::StatusCode::INTERNAL_SERVER_ERROR,
296 Json(serde_json::json!({
297 "error": "server_error",
298 "error_description": format!("Database error: {:?}", e)
299 })),
300 ).into_response();
301 }
302 return Html(templates::error_page(
303 "server_error",
304 Some(&format!("Database error: {:?}", e)),
305 )).into_response();
306 }
307 };
308
309 if request_data.expires_at < Utc::now() {
310 let _ = db::delete_authorization_request(&state.db, &form.request_uri).await;
311 if json_response {
312 return (
313 axum::http::StatusCode::BAD_REQUEST,
314 Json(serde_json::json!({
315 "error": "invalid_request",
316 "error_description": "Authorization request has expired."
317 })),
318 ).into_response();
319 }
320 return Html(templates::error_page(
321 "invalid_request",
322 Some("Authorization request has expired. Please start a new request."),
323 )).into_response();
324 }
325
326 let show_login_error = |error_msg: &str, json: bool| -> Response {
327 if json {
328 return (
329 axum::http::StatusCode::FORBIDDEN,
330 Json(serde_json::json!({
331 "error": "access_denied",
332 "error_description": error_msg
333 })),
334 ).into_response();
335 }
336 Html(templates::login_page(
337 &request_data.parameters.client_id,
338 None,
339 request_data.parameters.scope.as_deref(),
340 &form.request_uri,
341 Some(error_msg),
342 Some(&form.username),
343 )).into_response()
344 };
345
346 let user = match sqlx::query!(
347 r#"
348 SELECT id, did, email, password_hash, two_factor_enabled,
349 preferred_notification_channel as "preferred_notification_channel: NotificationChannel",
350 deactivated_at, takedown_ref
351 FROM users
352 WHERE handle = $1 OR email = $1
353 "#,
354 form.username
355 )
356 .fetch_optional(&state.db)
357 .await
358 {
359 Ok(Some(u)) => u,
360 Ok(None) => return show_login_error("Invalid handle/email or password.", json_response),
361 Err(_) => return show_login_error("An error occurred. Please try again.", json_response),
362 };
363
364 if user.deactivated_at.is_some() {
365 return show_login_error("This account has been deactivated.", json_response);
366 }
367
368 if user.takedown_ref.is_some() {
369 return show_login_error("This account has been taken down.", json_response);
370 }
371
372 let password_valid = match bcrypt::verify(&form.password, &user.password_hash) {
373 Ok(valid) => valid,
374 Err(_) => return show_login_error("An error occurred. Please try again.", json_response),
375 };
376
377 if !password_valid {
378 return show_login_error("Invalid handle/email or password.", json_response);
379 }
380
381 if user.two_factor_enabled {
382 let _ = db::delete_2fa_challenge_by_request_uri(&state.db, &form.request_uri).await;
383
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
400 let channel_name = channel_display_name(user.preferred_notification_channel);
401 let redirect_url = format!(
402 "/oauth/authorize/2fa?request_uri={}&channel={}",
403 url_encode(&form.request_uri),
404 url_encode(channel_name)
405 );
406 return Redirect::temporary(&redirect_url).into_response();
407 }
408 Err(_) => {
409 return show_login_error("An error occurred. Please try again.", json_response);
410 }
411 }
412 }
413
414 let code = Code::generate();
415 let mut device_id: Option<String> = extract_device_cookie(&headers);
416 let mut new_cookie: Option<String> = None;
417
418 if form.remember_device {
419 let final_device_id = if let Some(existing_id) = &device_id {
420 existing_id.clone()
421 } else {
422 let new_id = DeviceId::generate();
423 let device_data = DeviceData {
424 session_id: SessionId::generate().0,
425 user_agent: extract_user_agent(&headers),
426 ip_address: extract_client_ip(&headers),
427 last_seen_at: Utc::now(),
428 };
429
430 if db::create_device(&state.db, &new_id.0, &device_data).await.is_ok() {
431 new_cookie = Some(make_device_cookie(&new_id.0));
432 device_id = Some(new_id.0.clone());
433 }
434 new_id.0
435 };
436
437 let _ = db::upsert_account_device(&state.db, &user.did, &final_device_id).await;
438 }
439
440 if let Err(_) = db::update_authorization_request(
441 &state.db,
442 &form.request_uri,
443 &user.did,
444 device_id.as_deref(),
445 &code.0,
446 )
447 .await
448 {
449 return show_login_error("An error occurred. Please try again.", json_response);
450 }
451
452 let redirect_url = build_success_redirect(
453 &request_data.parameters.redirect_uri,
454 &code.0,
455 request_data.parameters.state.as_deref(),
456 );
457
458 let redirect = Redirect::temporary(&redirect_url);
459
460 if let Some(cookie) = new_cookie {
461 ([(SET_COOKIE, cookie)], redirect).into_response()
462 } else {
463 redirect.into_response()
464 }
465}
466
467pub async fn authorize_select(
468 State(state): State<AppState>,
469 headers: HeaderMap,
470 Form(form): Form<AuthorizeSelectSubmit>,
471) -> Response {
472 let request_data = match db::get_authorization_request(&state.db, &form.request_uri).await {
473 Ok(Some(data)) => data,
474 Ok(None) => {
475 return Html(templates::error_page(
476 "invalid_request",
477 Some("Invalid or expired request_uri. Please start a new authorization request."),
478 )).into_response();
479 }
480 Err(_) => {
481 return Html(templates::error_page(
482 "server_error",
483 Some("An error occurred. Please try again."),
484 )).into_response();
485 }
486 };
487
488 if request_data.expires_at < Utc::now() {
489 let _ = db::delete_authorization_request(&state.db, &form.request_uri).await;
490 return Html(templates::error_page(
491 "invalid_request",
492 Some("Authorization request has expired. Please start a new request."),
493 )).into_response();
494 }
495
496 let device_id = match extract_device_cookie(&headers) {
497 Some(id) => id,
498 None => {
499 return Html(templates::error_page(
500 "invalid_request",
501 Some("No device session found. Please sign in."),
502 )).into_response();
503 }
504 };
505
506 let account_valid = match db::verify_account_on_device(&state.db, &device_id, &form.did).await {
507 Ok(valid) => valid,
508 Err(_) => {
509 return Html(templates::error_page(
510 "server_error",
511 Some("An error occurred. Please try again."),
512 )).into_response();
513 }
514 };
515
516 if !account_valid {
517 return Html(templates::error_page(
518 "access_denied",
519 Some("This account is not available on this device. Please sign in."),
520 )).into_response();
521 }
522
523 let user = match sqlx::query!(
524 r#"
525 SELECT id, two_factor_enabled,
526 preferred_notification_channel as "preferred_notification_channel: NotificationChannel"
527 FROM users
528 WHERE did = $1
529 "#,
530 form.did
531 )
532 .fetch_optional(&state.db)
533 .await
534 {
535 Ok(Some(u)) => u,
536 Ok(None) => {
537 return Html(templates::error_page(
538 "access_denied",
539 Some("Account not found. Please sign in."),
540 )).into_response();
541 }
542 Err(_) => {
543 return Html(templates::error_page(
544 "server_error",
545 Some("An error occurred. Please try again."),
546 )).into_response();
547 }
548 };
549
550 if user.two_factor_enabled {
551 let _ = db::delete_2fa_challenge_by_request_uri(&state.db, &form.request_uri).await;
552
553 match db::create_2fa_challenge(&state.db, &form.did, &form.request_uri).await {
554 Ok(challenge) => {
555 let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string());
556 if let Err(e) = enqueue_2fa_code(
557 &state.db,
558 user.id,
559 &challenge.code,
560 &hostname,
561 ).await {
562 tracing::warn!(
563 did = %form.did,
564 error = %e,
565 "Failed to enqueue 2FA notification"
566 );
567 }
568
569 let channel_name = channel_display_name(user.preferred_notification_channel);
570 let redirect_url = format!(
571 "/oauth/authorize/2fa?request_uri={}&channel={}",
572 url_encode(&form.request_uri),
573 url_encode(channel_name)
574 );
575 return Redirect::temporary(&redirect_url).into_response();
576 }
577 Err(_) => {
578 return Html(templates::error_page(
579 "server_error",
580 Some("An error occurred. Please try again."),
581 )).into_response();
582 }
583 }
584 }
585
586 let _ = db::upsert_account_device(&state.db, &form.did, &device_id).await;
587
588 let code = Code::generate();
589
590 if let Err(_) = db::update_authorization_request(
591 &state.db,
592 &form.request_uri,
593 &form.did,
594 Some(&device_id),
595 &code.0,
596 )
597 .await
598 {
599 return Html(templates::error_page(
600 "server_error",
601 Some("An error occurred. Please try again."),
602 )).into_response();
603 }
604
605 let redirect_url = build_success_redirect(
606 &request_data.parameters.redirect_uri,
607 &code.0,
608 request_data.parameters.state.as_deref(),
609 );
610
611 Redirect::temporary(&redirect_url).into_response()
612}
613
614fn build_success_redirect(redirect_uri: &str, code: &str, state: Option<&str>) -> String {
615 let mut redirect_url = redirect_uri.to_string();
616
617 let separator = if redirect_url.contains('?') { '&' } else { '?' };
618 redirect_url.push(separator);
619 redirect_url.push_str(&format!("code={}", url_encode(code)));
620
621 if let Some(req_state) = state {
622 redirect_url.push_str(&format!("&state={}", url_encode(req_state)));
623 }
624
625 let pds_hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string());
626 redirect_url.push_str(&format!("&iss={}", url_encode(&format!("https://{}", pds_hostname))));
627
628 redirect_url
629}
630
631#[derive(Debug, Serialize)]
632pub struct AuthorizeDenyResponse {
633 pub error: String,
634 pub error_description: String,
635}
636
637pub async fn authorize_deny(
638 State(state): State<AppState>,
639 Form(form): Form<AuthorizeDenyForm>,
640) -> Result<Response, OAuthError> {
641 let request_data = db::get_authorization_request(&state.db, &form.request_uri)
642 .await?
643 .ok_or_else(|| OAuthError::InvalidRequest("Invalid request_uri".to_string()))?;
644
645 db::delete_authorization_request(&state.db, &form.request_uri).await?;
646
647 let redirect_uri = &request_data.parameters.redirect_uri;
648 let mut redirect_url = redirect_uri.to_string();
649
650 let separator = if redirect_url.contains('?') { '&' } else { '?' };
651 redirect_url.push(separator);
652 redirect_url.push_str("error=access_denied");
653 redirect_url.push_str("&error_description=User%20denied%20the%20request");
654
655 if let Some(state) = &request_data.parameters.state {
656 redirect_url.push_str(&format!("&state={}", url_encode(state)));
657 }
658
659 Ok(Redirect::temporary(&redirect_url).into_response())
660}
661
662#[derive(Debug, Deserialize)]
663pub struct AuthorizeDenyForm {
664 pub request_uri: String,
665}
666
667#[derive(Debug, Deserialize)]
668pub struct Authorize2faQuery {
669 pub request_uri: String,
670 pub channel: Option<String>,
671}
672
673#[derive(Debug, Deserialize)]
674pub struct Authorize2faSubmit {
675 pub request_uri: String,
676 pub code: String,
677}
678
679const MAX_2FA_ATTEMPTS: i32 = 5;
680
681pub async fn authorize_2fa_get(
682 State(state): State<AppState>,
683 Query(query): Query<Authorize2faQuery>,
684) -> Response {
685 let challenge = match db::get_2fa_challenge(&state.db, &query.request_uri).await {
686 Ok(Some(c)) => c,
687 Ok(None) => {
688 return Html(templates::error_page(
689 "invalid_request",
690 Some("No 2FA challenge found. Please start over."),
691 )).into_response();
692 }
693 Err(_) => {
694 return Html(templates::error_page(
695 "server_error",
696 Some("An error occurred. Please try again."),
697 )).into_response();
698 }
699 };
700
701 if challenge.expires_at < Utc::now() {
702 let _ = db::delete_2fa_challenge(&state.db, challenge.id).await;
703 return Html(templates::error_page(
704 "invalid_request",
705 Some("2FA code has expired. Please start over."),
706 )).into_response();
707 }
708
709 let _request_data = match db::get_authorization_request(&state.db, &query.request_uri).await {
710 Ok(Some(d)) => d,
711 Ok(None) => {
712 return Html(templates::error_page(
713 "invalid_request",
714 Some("Authorization request not 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 let channel = query.channel.as_deref().unwrap_or("email");
726
727 Html(templates::two_factor_page(
728 &query.request_uri,
729 channel,
730 None,
731 )).into_response()
732}
733
734pub async fn authorize_2fa_post(
735 State(state): State<AppState>,
736 headers: HeaderMap,
737 Form(form): Form<Authorize2faSubmit>,
738) -> Response {
739 let challenge = match db::get_2fa_challenge(&state.db, &form.request_uri).await {
740 Ok(Some(c)) => c,
741 Ok(None) => {
742 return Html(templates::error_page(
743 "invalid_request",
744 Some("No 2FA challenge found. Please start over."),
745 )).into_response();
746 }
747 Err(_) => {
748 return Html(templates::error_page(
749 "server_error",
750 Some("An error occurred. Please try again."),
751 )).into_response();
752 }
753 };
754
755 if challenge.expires_at < Utc::now() {
756 let _ = db::delete_2fa_challenge(&state.db, challenge.id).await;
757 return Html(templates::error_page(
758 "invalid_request",
759 Some("2FA code has expired. Please start over."),
760 )).into_response();
761 }
762
763 if challenge.attempts >= MAX_2FA_ATTEMPTS {
764 let _ = db::delete_2fa_challenge(&state.db, challenge.id).await;
765 return Html(templates::error_page(
766 "access_denied",
767 Some("Too many failed attempts. Please start over."),
768 )).into_response();
769 }
770
771 let code_valid: bool = form.code.trim().as_bytes().ct_eq(challenge.code.as_bytes()).into();
772
773 if !code_valid {
774 let _ = db::increment_2fa_attempts(&state.db, challenge.id).await;
775
776 let channel = match sqlx::query_scalar!(
777 r#"SELECT preferred_notification_channel as "channel: NotificationChannel" FROM users WHERE did = $1"#,
778 challenge.did
779 )
780 .fetch_optional(&state.db)
781 .await
782 {
783 Ok(Some(ch)) => channel_display_name(ch).to_string(),
784 Ok(None) | Err(_) => "email".to_string(),
785 };
786
787 let _request_data = match db::get_authorization_request(&state.db, &form.request_uri).await {
788 Ok(Some(d)) => d,
789 Ok(None) => {
790 return Html(templates::error_page(
791 "invalid_request",
792 Some("Authorization request not found. Please start over."),
793 )).into_response();
794 }
795 Err(_) => {
796 return Html(templates::error_page(
797 "server_error",
798 Some("An error occurred. Please try again."),
799 )).into_response();
800 }
801 };
802
803 return Html(templates::two_factor_page(
804 &form.request_uri,
805 &channel,
806 Some("Invalid verification code. Please try again."),
807 )).into_response();
808 }
809
810 let _ = db::delete_2fa_challenge(&state.db, challenge.id).await;
811
812 let request_data = match db::get_authorization_request(&state.db, &form.request_uri).await {
813 Ok(Some(d)) => d,
814 Ok(None) => {
815 return Html(templates::error_page(
816 "invalid_request",
817 Some("Authorization request not found."),
818 )).into_response();
819 }
820 Err(_) => {
821 return Html(templates::error_page(
822 "server_error",
823 Some("An error occurred."),
824 )).into_response();
825 }
826 };
827
828 let code = Code::generate();
829 let device_id = extract_device_cookie(&headers);
830
831 if let Err(_) = db::update_authorization_request(
832 &state.db,
833 &form.request_uri,
834 &challenge.did,
835 device_id.as_deref(),
836 &code.0,
837 )
838 .await
839 {
840 return Html(templates::error_page(
841 "server_error",
842 Some("An error occurred. Please try again."),
843 )).into_response();
844 }
845
846 let redirect_url = build_success_redirect(
847 &request_data.parameters.redirect_uri,
848 &code.0,
849 request_data.parameters.state.as_deref(),
850 );
851
852 Redirect::temporary(&redirect_url).into_response()
853}