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