You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-08-10 15:23:07 +03:00
Skip the device code form when using the full verification URI
This changes the form to use a GET method, as it is only really doing a redirect.
This commit is contained in:
@@ -412,7 +412,7 @@ where
|
|||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
mas_router::DeviceCodeLink::route(),
|
mas_router::DeviceCodeLink::route(),
|
||||||
get(self::oauth2::device::link::get).post(self::oauth2::device::link::post),
|
get(self::oauth2::device::link::get),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
mas_router::DeviceCodeConsent::route(),
|
mas_router::DeviceCodeConsent::route(),
|
||||||
|
@@ -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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -14,17 +14,12 @@
|
|||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Query, State},
|
extract::{Query, State},
|
||||||
response::{IntoResponse, Response},
|
response::IntoResponse,
|
||||||
Form,
|
|
||||||
};
|
};
|
||||||
use axum_extra::response::Html;
|
use axum_extra::response::Html;
|
||||||
use mas_axum_utils::{
|
use mas_axum_utils::{cookies::CookieJar, FancyError};
|
||||||
cookies::CookieJar,
|
|
||||||
csrf::{CsrfExt, ProtectedForm},
|
|
||||||
FancyError,
|
|
||||||
};
|
|
||||||
use mas_router::UrlBuilder;
|
use mas_router::UrlBuilder;
|
||||||
use mas_storage::{BoxClock, BoxRepository, BoxRng};
|
use mas_storage::{BoxClock, BoxRepository};
|
||||||
use mas_templates::{
|
use mas_templates::{
|
||||||
DeviceLinkContext, DeviceLinkFormField, FieldError, FormState, TemplateContext, Templates,
|
DeviceLinkContext, DeviceLinkFormField, FieldError, FormState, TemplateContext, Templates,
|
||||||
};
|
};
|
||||||
@@ -32,10 +27,6 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::PreferredLanguage;
|
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)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Params {
|
pub struct Params {
|
||||||
code: String,
|
code: String,
|
||||||
@@ -43,76 +34,49 @@ pub struct Params {
|
|||||||
|
|
||||||
#[tracing::instrument(name = "handlers.oauth2.device.link.get", skip_all, err)]
|
#[tracing::instrument(name = "handlers.oauth2.device.link.get", skip_all, err)]
|
||||||
pub(crate) async fn get(
|
pub(crate) async fn get(
|
||||||
mut rng: BoxRng,
|
|
||||||
clock: BoxClock,
|
|
||||||
PreferredLanguage(locale): PreferredLanguage,
|
|
||||||
State(templates): State<Templates>,
|
|
||||||
cookie_jar: CookieJar,
|
|
||||||
query: Option<Query<Params>>,
|
|
||||||
) -> Result<impl IntoResponse, FancyError> {
|
|
||||||
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,
|
clock: BoxClock,
|
||||||
mut repo: BoxRepository,
|
mut repo: BoxRepository,
|
||||||
PreferredLanguage(locale): PreferredLanguage,
|
PreferredLanguage(locale): PreferredLanguage,
|
||||||
State(templates): State<Templates>,
|
State(templates): State<Templates>,
|
||||||
State(url_builder): State<UrlBuilder>,
|
State(url_builder): State<UrlBuilder>,
|
||||||
cookie_jar: CookieJar,
|
cookie_jar: CookieJar,
|
||||||
Form(form): Form<ProtectedForm<Params>>,
|
query: Option<Query<Params>>,
|
||||||
) -> Result<Response, FancyError> {
|
) -> Result<impl IntoResponse, FancyError> {
|
||||||
let form = cookie_jar.verify_form(&clock, form)?;
|
let mut form_state = FormState::default();
|
||||||
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);
|
|
||||||
|
|
||||||
let code = form.code.to_uppercase();
|
// If we have a code in query, find it in the database
|
||||||
let grant = repo
|
if let Some(Query(params)) = query {
|
||||||
.oauth2_device_code_grant()
|
// Save the form state so that we echo back the code
|
||||||
.find_by_user_code(&code)
|
form_state = FormState::from_form(¶ms);
|
||||||
.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 Some(grant) = grant else {
|
// Find the code in the database
|
||||||
let form_state = FormState::from_form(&form)
|
let code = params.code.to_uppercase();
|
||||||
.with_error_on_field(DeviceLinkFormField::Code, FieldError::Invalid);
|
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()
|
if let Some(grant) = grant {
|
||||||
.with_form_state(form_state)
|
// This is a valid code, redirect to the consent page
|
||||||
.with_csrf(csrf_token.form_value())
|
// This will in turn redirect to the login page if the user is not logged in
|
||||||
.with_language(locale);
|
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
|
// Rendre the form
|
||||||
// This will in turn redirect to the login page if the user is not logged in
|
let ctx = DeviceLinkContext::new()
|
||||||
let destination = url_builder.redirect(&mas_router::DeviceCodeConsent::new(grant.id));
|
.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())
|
||||||
}
|
}
|
||||||
|
@@ -377,7 +377,7 @@ register_templates! {
|
|||||||
pub fn render_upstream_oauth2_do_register(WithLanguage<WithCsrf<UpstreamRegister>>) { "pages/upstream_oauth2/do_register.html" }
|
pub fn render_upstream_oauth2_do_register(WithLanguage<WithCsrf<UpstreamRegister>>) { "pages/upstream_oauth2/do_register.html" }
|
||||||
|
|
||||||
/// Render the device code link page
|
/// Render the device code link page
|
||||||
pub fn render_device_link(WithLanguage<WithCsrf<DeviceLinkContext>>) { "pages/device_link.html" }
|
pub fn render_device_link(WithLanguage<DeviceLinkContext>) { "pages/device_link.html" }
|
||||||
|
|
||||||
/// Render the device code consent page
|
/// Render the device code consent page
|
||||||
pub fn render_device_consent(WithLanguage<WithCsrf<WithSession<DeviceConsentContext>>>) { "pages/device_consent.html" }
|
pub fn render_device_consent(WithLanguage<WithCsrf<WithSession<DeviceConsentContext>>>) { "pages/device_consent.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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -28,9 +28,7 @@ limitations under the License.
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<form method="POST" class="cpd-form-root">
|
<form method="GET" class="cpd-form-root">
|
||||||
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
|
|
||||||
|
|
||||||
{% call(f) field.field(label="Device code", name="code", class="mb-4 self-center", form_state=form_state) %}
|
{% call(f) field.field(label="Device code", name="code", class="mb-4 self-center", form_state=form_state) %}
|
||||||
<div class="cpd-mfa-container">
|
<div class="cpd-mfa-container">
|
||||||
<input {{ field.attributes(f) }}
|
<input {{ field.attributes(f) }}
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
},
|
},
|
||||||
"continue": "Continue",
|
"continue": "Continue",
|
||||||
"@continue": {
|
"@continue": {
|
||||||
"context": "pages/account/emails/add.html:45:26-46, pages/account/emails/verify.html:60:26-46, pages/consent.html:60:28-48, pages/device_consent.html:129:13-33, pages/device_link.html:50:26-46, pages/login.html:62:30-50, pages/reauth.html:40:28-48, pages/register.html:72:28-48, pages/sso.html:45:28-48"
|
"context": "pages/account/emails/add.html:45:26-46, pages/account/emails/verify.html:60:26-46, pages/consent.html:60:28-48, pages/device_consent.html:129:13-33, pages/device_link.html:48:26-46, pages/login.html:62:30-50, pages/reauth.html:40:28-48, pages/register.html:72:28-48, pages/sso.html:45:28-48"
|
||||||
},
|
},
|
||||||
"create_account": "Create Account",
|
"create_account": "Create Account",
|
||||||
"@create_account": {
|
"@create_account": {
|
||||||
|
Reference in New Issue
Block a user