diff --git a/crates/handlers/src/lib.rs b/crates/handlers/src/lib.rs index 856f43b9..84d28392 100644 --- a/crates/handlers/src/lib.rs +++ b/crates/handlers/src/lib.rs @@ -412,7 +412,7 @@ where ) .route( mas_router::DeviceCodeLink::route(), - get(self::oauth2::device::link::get).post(self::oauth2::device::link::post), + get(self::oauth2::device::link::get), ) .route( mas_router::DeviceCodeConsent::route(), diff --git a/crates/handlers/src/oauth2/device/link.rs b/crates/handlers/src/oauth2/device/link.rs index f3b6a90e..bb9f33ed 100644 --- a/crates/handlers/src/oauth2/device/link.rs +++ b/crates/handlers/src/oauth2/device/link.rs @@ -1,4 +1,4 @@ -// Copyright 2023 The Matrix.org Foundation C.I.C. +// Copyright 2023, 2024 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,17 +14,12 @@ use axum::{ extract::{Query, State}, - response::{IntoResponse, Response}, - Form, + response::IntoResponse, }; use axum_extra::response::Html; -use mas_axum_utils::{ - cookies::CookieJar, - csrf::{CsrfExt, ProtectedForm}, - FancyError, -}; +use mas_axum_utils::{cookies::CookieJar, FancyError}; use mas_router::UrlBuilder; -use mas_storage::{BoxClock, BoxRepository, BoxRng}; +use mas_storage::{BoxClock, BoxRepository}; use mas_templates::{ DeviceLinkContext, DeviceLinkFormField, FieldError, FormState, TemplateContext, Templates, }; @@ -32,10 +27,6 @@ use serde::{Deserialize, Serialize}; use crate::PreferredLanguage; -// We use this struct for both the form and the query parameters. This is useful -// to build a form state from the query parameters. The query parameter is only -// really used when the `verification_uri_complete` feature of the device code -// grant is used. #[derive(Serialize, Deserialize)] pub struct Params { code: String, @@ -43,76 +34,49 @@ pub struct Params { #[tracing::instrument(name = "handlers.oauth2.device.link.get", skip_all, err)] pub(crate) async fn get( - mut rng: BoxRng, - clock: BoxClock, - PreferredLanguage(locale): PreferredLanguage, - State(templates): State, - cookie_jar: CookieJar, - query: Option>, -) -> Result { - let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng); - - let mut form_state = FormState::default(); - - // XXX: right now we just get the code from the query to pre-fill the form. We - // may want to make the form readonly instead at some point? tbd - if let Some(Query(params)) = query { - // Validate that it's a full code - if params.code.len() == 6 && params.code.chars().all(|c| c.is_ascii_alphanumeric()) { - form_state = FormState::from_form(¶ms); - } - }; - - let ctx = DeviceLinkContext::new() - .with_form_state(form_state) - .with_csrf(csrf_token.form_value()) - .with_language(locale); - - let content = templates.render_device_link(&ctx)?; - - Ok((cookie_jar, Html(content))) -} - -#[tracing::instrument(name = "handlers.oauth2.device.link.post", skip_all, err)] -pub(crate) async fn post( - mut rng: BoxRng, clock: BoxClock, mut repo: BoxRepository, PreferredLanguage(locale): PreferredLanguage, State(templates): State, State(url_builder): State, cookie_jar: CookieJar, - Form(form): Form>, -) -> Result { - let form = cookie_jar.verify_form(&clock, form)?; - let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng); + query: Option>, +) -> Result { + let mut form_state = FormState::default(); - let code = form.code.to_uppercase(); - let grant = repo - .oauth2_device_code_grant() - .find_by_user_code(&code) - .await? - // XXX: We should have different error messages for already exchanged and expired - .filter(|grant| grant.is_pending()) - .filter(|grant| grant.expires_at > clock.now()); + // If we have a code in query, find it in the database + if let Some(Query(params)) = query { + // Save the form state so that we echo back the code + form_state = FormState::from_form(¶ms); - let Some(grant) = grant else { - let form_state = FormState::from_form(&form) - .with_error_on_field(DeviceLinkFormField::Code, FieldError::Invalid); + // Find the code in the database + let code = params.code.to_uppercase(); + let grant = repo + .oauth2_device_code_grant() + .find_by_user_code(&code) + .await? + // XXX: We should have different error messages for already exchanged and expired + .filter(|grant| grant.is_pending()) + .filter(|grant| grant.expires_at > clock.now()); - let ctx = DeviceLinkContext::new() - .with_form_state(form_state) - .with_csrf(csrf_token.form_value()) - .with_language(locale); + if let Some(grant) = grant { + // This is a valid code, redirect to the consent page + // This will in turn redirect to the login page if the user is not logged in + let destination = url_builder.redirect(&mas_router::DeviceCodeConsent::new(grant.id)); - let content = templates.render_device_link(&ctx)?; + return Ok((cookie_jar, destination).into_response()); + } - return Ok((cookie_jar, Html(content)).into_response()); + // The code isn't valid, set an error on the form + form_state = form_state.with_error_on_field(DeviceLinkFormField::Code, FieldError::Invalid); }; - // Redirect to the consent page - // This will in turn redirect to the login page if the user is not logged in - let destination = url_builder.redirect(&mas_router::DeviceCodeConsent::new(grant.id)); + // Rendre the form + let ctx = DeviceLinkContext::new() + .with_form_state(form_state) + .with_language(locale); - Ok((cookie_jar, destination).into_response()) + let content = templates.render_device_link(&ctx)?; + + Ok((cookie_jar, Html(content)).into_response()) } diff --git a/crates/templates/src/lib.rs b/crates/templates/src/lib.rs index a1f77ed3..a063dc0c 100644 --- a/crates/templates/src/lib.rs +++ b/crates/templates/src/lib.rs @@ -377,7 +377,7 @@ register_templates! { pub fn render_upstream_oauth2_do_register(WithLanguage>) { "pages/upstream_oauth2/do_register.html" } /// Render the device code link page - pub fn render_device_link(WithLanguage>) { "pages/device_link.html" } + pub fn render_device_link(WithLanguage) { "pages/device_link.html" } /// Render the device code consent page pub fn render_device_consent(WithLanguage>>) { "pages/device_consent.html" } diff --git a/templates/pages/device_link.html b/templates/pages/device_link.html index f204353c..796e8ff4 100644 --- a/templates/pages/device_link.html +++ b/templates/pages/device_link.html @@ -1,5 +1,5 @@ {# -Copyright 2023 The Matrix.org Foundation C.I.C. +Copyright 2023, 2024 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -28,9 +28,7 @@ limitations under the License. -
- - + {% call(f) field.field(label="Device code", name="code", class="mb-4 self-center", form_state=form_state) %}