mas_handlers/oauth2/device/
link.rs

1// Copyright 2024, 2025 New Vector Ltd.
2// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
3//
4// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5// Please see LICENSE files in the repository root for full details.
6
7use 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 we have a code in query, find it in the database
41    if let Some(code) = &query.code {
42        // Find the code in the database
43        let code = code.to_uppercase();
44        let grant = repo
45            .oauth2_device_code_grant()
46            .find_by_user_code(&code)
47            .await?
48            // XXX: We should have different error messages for already exchanged and expired
49            .filter(|grant| grant.is_pending())
50            .filter(|grant| grant.expires_at > clock.now());
51
52        if let Some(grant) = grant {
53            // This is a valid code, redirect to the consent page
54            // This will in turn redirect to the login page if the user is not logged in
55            let destination = url_builder.redirect(&mas_router::DeviceCodeConsent::new(grant.id));
56
57            return Ok((cookie_jar, destination).into_response());
58        }
59
60        // The code isn't valid, set an error on the form
61        form_state = form_state.with_error_on_field(DeviceLinkFormField::Code, FieldError::Invalid);
62    }
63
64    // Rendre the form
65    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}