mas_handlers/oauth2/device/
link.rs1use axum::{
8 extract::{Query, State},
9 response::{Html, IntoResponse},
10};
11use mas_axum_utils::{FancyError, cookies::CookieJar};
12use mas_router::UrlBuilder;
13use mas_storage::{BoxClock, BoxRepository};
14use mas_templates::{
15 DeviceLinkContext, DeviceLinkFormField, FieldError, FormState, TemplateContext, Templates,
16};
17use serde::{Deserialize, Serialize};
18
19use crate::PreferredLanguage;
20
21#[derive(Serialize, Deserialize)]
22pub struct Params {
23 #[serde(default)]
24 code: Option<String>,
25}
26
27#[tracing::instrument(name = "handlers.oauth2.device.link.get", skip_all, err)]
28pub(crate) async fn get(
29 clock: BoxClock,
30 mut repo: BoxRepository,
31 PreferredLanguage(locale): PreferredLanguage,
32 State(templates): State<Templates>,
33 State(url_builder): State<UrlBuilder>,
34 cookie_jar: CookieJar,
35 Query(query): Query<Params>,
36) -> Result<impl IntoResponse, FancyError> {
37 let mut form_state = FormState::from_form(&query);
38
39 if let Some(code) = &query.code {
41 let code = code.to_uppercase();
43 let grant = repo
44 .oauth2_device_code_grant()
45 .find_by_user_code(&code)
46 .await?
47 .filter(|grant| grant.is_pending())
49 .filter(|grant| grant.expires_at > clock.now());
50
51 if let Some(grant) = grant {
52 let destination = url_builder.redirect(&mas_router::DeviceCodeConsent::new(grant.id));
55
56 return Ok((cookie_jar, destination).into_response());
57 }
58
59 form_state = form_state.with_error_on_field(DeviceLinkFormField::Code, FieldError::Invalid);
61 }
62
63 let ctx = DeviceLinkContext::new()
65 .with_form_state(form_state)
66 .with_language(locale);
67
68 let content = templates.render_device_link(&ctx)?;
69
70 Ok((cookie_jar, Html(content)).into_response())
71}