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