From 511e60b41d78d600cc311c3726a96d88408a7a01 Mon Sep 17 00:00:00 2001 From: lafleur Date: Sat, 9 Aug 2025 01:00:34 +0200 Subject: [PATCH] support OIDC private clients --- src/api/client/oidc/register.rs | 40 ++++++++++++++++++++++++------- src/web/oidc.rs | 2 ++ src/web/oidc/authorize.rs | 3 +++ src/web/oidc/consent.rs | 1 + src/web/oidc/login.rs | 4 ++++ src/web/templates/consent.html.j2 | 4 ++-- src/web/templates/login.html.j2 | 3 +++ 7 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/api/client/oidc/register.rs b/src/api/client/oidc/register.rs index 6b752f5b..d39b6414 100644 --- a/src/api/client/oidc/register.rs +++ b/src/api/client/oidc/register.rs @@ -2,7 +2,7 @@ use axum::{Json, extract::State}; use conduwuit::{Result, err}; use oxide_auth::primitives::prelude::Client; use reqwest::Url; -use ruma::DeviceId; +use ruma::{DeviceId, identifiers_validation}; /// The required parameters to register a new client for OAuth2 application. #[derive(serde::Deserialize, Clone, Debug)] @@ -35,6 +35,8 @@ pub(crate) struct ClientQuery { #[derive(serde::Serialize, Debug)] pub(crate) struct ClientResponse { client_id: String, + client_secret: Option, + client_secret_expires_at: Option, client_name: String, client_uri: Url, logo_uri: Option, @@ -68,18 +70,38 @@ pub(crate) async fn register_client( let scope = format!( "urn:matrix:org.matrix.msc2967.client:api:* \ urn:matrix:org.matrix.msc2967.client:device:{device_id}" - ); + ).parse().expect("parseable default Matrix scope"); // TODO check if the users service needs an update. //services.users.update_device_metadata(); - services.oidc.register_client(&Client::public( - device_id.as_ref(), - redirect_uri.into(), - scope - .parse() - .expect("device ID should parse in Matrix scope"), - ))?; + + // If the client cannot authenticate itself at the token endpoint, then + // it's a public client. + let is_private = client.token_endpoint_auth_method != "none"; + // TODO generate a device secret. + let secret = "cacestdubonsecretmonlouou=--".to_string(); + if let Err(err) = identifiers_validation::client_secret::validate(&secret) { + tracing::warn!("oops, we generated an invalid client_secret: {err}"); + } + let registerable = match is_private { + | true => &Client::confidential( + device_id.as_ref(), + redirect_uri, + scope, + secret.as_bytes(), + ).with_additional_redirect_uris(remaining_uris), + | _ => &Client::public( + device_id.as_ref(), + redirect_uri, + scope, + ).with_additional_redirect_uris(remaining_uris) + }; + tracing::trace!("registering OIDC device : {registerable:#?}"); + services.oidc.register_client(®isterable)?; + let client_response = ClientResponse { client_id: device_id.to_string(), + client_secret: if is_private { Some(secret) } else { None }, + client_secret_expires_at: if is_private { Some(0) } else { None }, client_name: client.client_name.clone(), client_uri: client.client_uri.clone(), redirect_uris: client.redirect_uris.clone(), diff --git a/src/web/oidc.rs b/src/web/oidc.rs index 1abe6f83..84349d0b 100644 --- a/src/web/oidc.rs +++ b/src/web/oidc.rs @@ -26,6 +26,7 @@ pub(crate) struct LoginPageTemplate<'a> { hostname: &'a str, route: &'a str, client_id: &'a str, + client_secret: Option<&'a str>, redirect_uri: &'a str, scope: &'a str, state: &'a str, @@ -43,6 +44,7 @@ pub(crate) struct ConsentPageTemplate<'a> { hostname: &'a str, route: &'a str, client_id: &'a str, + client_secret: Option<&'a str>, redirect_uri: &'a str, scope: &'a str, state: &'a str, diff --git a/src/web/oidc/authorize.rs b/src/web/oidc/authorize.rs index 1e851234..9721bd3f 100644 --- a/src/web/oidc/authorize.rs +++ b/src/web/oidc/authorize.rs @@ -6,6 +6,7 @@ use super::LoginQuery; #[derive(serde::Deserialize, Debug)] pub struct AuthorizationQuery { pub client_id: String, + pub client_secret: Option, pub redirect_uri: Url, pub scope: String, pub state: String, @@ -19,6 +20,7 @@ impl From for AuthorizationQuery { fn from(value: LoginQuery) -> Self { let LoginQuery { client_id, + client_secret, redirect_uri, scope, state, @@ -31,6 +33,7 @@ impl From for AuthorizationQuery { Self { client_id, + client_secret, redirect_uri, scope, state, diff --git a/src/web/oidc/consent.rs b/src/web/oidc/consent.rs index fbc2ce05..311117e0 100644 --- a/src/web/oidc/consent.rs +++ b/src/web/oidc/consent.rs @@ -37,6 +37,7 @@ fn consent_page(hostname: &str, query: &AuthorizationQuery, route: &str, nonce: hostname, route, client_id: &encode(query.client_id.as_str()), + client_secret: query.client_secret.as_deref(), redirect_uri: &encode(query.redirect_uri.as_str()), scope: &encode(query.scope.as_str()), state: &encode(query.state.as_str()), diff --git a/src/web/oidc/login.rs b/src/web/oidc/login.rs index 60719f62..9c36268a 100644 --- a/src/web/oidc/login.rs +++ b/src/web/oidc/login.rs @@ -13,6 +13,7 @@ pub struct LoginQuery { pub username: String, pub password: String, pub client_id: String, + pub client_secret: Option, pub redirect_uri: Url, pub scope: String, pub state: String, @@ -68,11 +69,13 @@ impl TryFrom for LoginQuery { | "https" => Cow::Borrowed("fragment"), | _ => Cow::Borrowed("query") }); + let client_secret = body.unique_value("client_secret").map(|s| s.to_string()); Ok(Self { username: username.to_string(), password: password.to_string(), client_id: client_id.to_string(), + client_secret, redirect_uri, scope: scope.to_string(), state: state.to_string(), @@ -116,6 +119,7 @@ fn login_page(hostname: &str, query: &AuthorizationQuery, route: &str, nonce: &s hostname, route, client_id: query.client_id.as_str(), + client_secret: query.client_secret.as_deref(), redirect_uri: query.redirect_uri.as_str(), scope: query.scope.as_str(), state: query.state.as_str(), diff --git a/src/web/templates/consent.html.j2 b/src/web/templates/consent.html.j2 index 09501a74..054a9fb7 100644 --- a/src/web/templates/consent.html.j2 +++ b/src/web/templates/consent.html.j2 @@ -6,8 +6,8 @@ '{{ client_id }}' (at {{ redirect_uri }}) is requesting permission for '{{ scope }}'

- - + +
{%- endblock content -%} diff --git a/src/web/templates/login.html.j2 b/src/web/templates/login.html.j2 index 1748b037..5da9fb01 100644 --- a/src/web/templates/login.html.j2 +++ b/src/web/templates/login.html.j2 @@ -6,6 +6,9 @@ + {%- if let Some(secret) = client_secret -%} + + {%- endif -%}