OIDC: embed user_id in consent

This commit is contained in:
lafleur 2025-08-11 22:04:07 +02:00
commit c059dbb337
6 changed files with 30 additions and 29 deletions

View file

@ -1,7 +1,7 @@
use axum::extract::{Query, State}; use axum::extract::{Query, State};
use conduwuit::{Result, err}; use conduwuit::{Result, err};
use conduwuit_web::oidc::{ use conduwuit_web::oidc::{
AuthorizationQuery, OidcRequest, OidcResponse, oidc_consent_form, oidc_login_form, oidc_consent_form, oidc_login_form, AuthorizationQuery, OidcRequest, OidcResponse,
}; };
use oxide_auth::{ use oxide_auth::{
endpoint::{OwnerConsent, Solicitation}, endpoint::{OwnerConsent, Solicitation},
@ -40,25 +40,20 @@ pub(crate) async fn authorize(
// Redirect to the login page if no token or token not known. // Redirect to the login page if no token or token not known.
let hostname = services.config.server_name.host(); let hostname = services.config.server_name.host();
match oauth.authorization_header() { let Some(token) = oauth.authorization_header() else {
| None => { return Ok(oidc_login_form(hostname, &query));
return Ok(oidc_login_form(hostname, &query)); };
},
| Some(token) => tracing::debug!("submitting OIDC authorisation for token : {token:#?}");
if services.users.find_from_token(token).await.is_err() { // Get the user id from the token and add it to the query.
return Ok(oidc_login_form(hostname, &query)); let (owner_id, _) = services.oidc.get_user_for_token(token)?;
}, let mut query_with_user_id = query.clone();
} query_with_user_id.username = Some(owner_id.localpart().to_string());
// TODO register the device ID ?
tracing::debug!(
"submitting OIDC authorisation for token : {:#?}",
oauth.authorization_header().unwrap()
);
services services
.oidc .oidc
.endpoint() .endpoint()
.with_solicitor(oidc_consent_form(hostname, &query)) .with_solicitor(oidc_consent_form(hostname, &query_with_user_id))
.authorization_flow() .authorization_flow()
.execute(oauth) .execute(oauth)
.map_err(|err| err!("authorization failed: {err:?}")) .map_err(|err| err!("authorization failed: {err:?}"))
@ -67,10 +62,10 @@ pub(crate) async fn authorize(
/// Whether a user allows their device to access this homeserver's resources. /// Whether a user allows their device to access this homeserver's resources.
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
pub(crate) struct Allowance { pub(crate) struct Allowance {
allow: Option<bool>, allow: Option<String>,
} }
/// # `POST /_matrix/client/unstable/org.matrix.msc2964/authorize?allow=[Option<bool>]` /// # `POST /_matrix/client/unstable/org.matrix.msc2964/authorize?allow=[Option<String>]`
/// ///
/// Authorize the device based on the user's consent. If the user allows /// Authorize the device based on the user's consent. If the user allows
/// it to access their data, the client may request a token at the /// it to access their data, the client may request a token at the
@ -80,16 +75,15 @@ pub(crate) async fn authorize_consent(
State(services): State<crate::State>, State(services): State<crate::State>,
oauth: OidcRequest, oauth: OidcRequest,
) -> Result<OidcResponse> { ) -> Result<OidcResponse> {
let allowed = allow.unwrap_or(false); tracing::debug!("processing user's consent: {:?} - {:?}", allow, oauth);
tracing::debug!("processing user's consent: {:?} - {:?}", allowed, oauth);
services services
.oidc .oidc
.endpoint() .endpoint()
.with_solicitor(FnSolicitor( .with_solicitor(FnSolicitor(
move |_: &mut _, solicitation: Solicitation<'_>| match allowed { move |_: &mut _, _: Solicitation<'_>| match allow.clone() {
| false => OwnerConsent::Denied, | None => OwnerConsent::Denied,
| true => OwnerConsent::Authorized(solicitation.pre_grant().client_id.clone()), | Some(user_id) => OwnerConsent::Authorized(user_id),
}, },
)) ))
.authorization_flow() .authorization_flow()

View file

@ -1,7 +1,7 @@
use axum::extract::State; use axum::extract::State;
use conduwuit::{Result, err, utils::hash::verify_password}; use conduwuit::{Result, err, utils::hash::verify_password};
use conduwuit_web::oidc::{ use conduwuit_web::oidc::{
AuthorizationQuery, LoginError, LoginQuery, OidcRequest, OidcResponse, oidc_consent_form, LoginError, LoginQuery, OidcRequest, OidcResponse, oidc_consent_form,
}; };
use ruma::user_id::UserId; use ruma::user_id::UserId;
@ -38,14 +38,12 @@ pub(crate) async fn oidc_login(
} }
let hostname = services.config.server_name.host(); let hostname = services.config.server_name.host();
let authorization_query: AuthorizationQuery = query.into();
tracing::info!("logging in {user_id:?}"); tracing::info!("logging in {user_id:?}");
tracing::debug!("login {user_id} authorisation query : {authorization_query:#?}");
services services
.oidc .oidc
.endpoint() .endpoint()
.with_solicitor(oidc_consent_form(hostname, &authorization_query)) .with_solicitor(oidc_consent_form(hostname, &query.into()))
.authorization_flow() .authorization_flow()
.execute(request) .execute(request)
.map_err(|err| err!(Request(Unknown("authorisation failed: {err:?}")))) .map_err(|err| err!(Request(Unknown("authorisation failed: {err:?}"))))

View file

@ -43,6 +43,7 @@ pub(crate) struct ConsentPageTemplate<'a> {
nonce: &'a str, nonce: &'a str,
hostname: &'a str, hostname: &'a str,
route: &'a str, route: &'a str,
user_id: &'a str,
client_id: &'a str, client_id: &'a str,
client_secret: Option<&'a str>, client_secret: Option<&'a str>,
redirect_uri: &'a str, redirect_uri: &'a str,

View file

@ -3,7 +3,7 @@ use url::Url;
use super::LoginQuery; use super::LoginQuery;
/// The set of parameters required for an OIDC authorization request. /// The set of parameters required for an OIDC authorization request.
#[derive(serde::Deserialize, Debug)] #[derive(serde::Deserialize, Debug, Clone)]
pub struct AuthorizationQuery { pub struct AuthorizationQuery {
pub client_id: String, pub client_id: String,
pub client_secret: Option<String>, pub client_secret: Option<String>,
@ -14,6 +14,7 @@ pub struct AuthorizationQuery {
pub code_challenge_method: String, pub code_challenge_method: String,
pub response_type: String, pub response_type: String,
pub response_mode: Option<String>, pub response_mode: Option<String>,
pub username: Option<String>,
} }
impl From<LoginQuery> for AuthorizationQuery { impl From<LoginQuery> for AuthorizationQuery {
@ -28,6 +29,7 @@ impl From<LoginQuery> for AuthorizationQuery {
code_challenge_method, code_challenge_method,
response_type, response_type,
response_mode, response_mode,
username,
.. ..
} = value; } = value;
@ -41,6 +43,7 @@ impl From<LoginQuery> for AuthorizationQuery {
code_challenge_method, code_challenge_method,
response_type, response_type,
response_mode: Some(response_mode), response_mode: Some(response_mode),
username: Some(username),
} }
} }
} }

View file

@ -27,10 +27,12 @@ pub fn oidc_consent_form(hostname: &str, query: &AuthorizationQuery) -> OidcResp
/// Render the html contents of the user consent page. /// Render the html contents of the user consent page.
fn consent_page(hostname: &str, query: &AuthorizationQuery, route: &str, nonce: &str) -> String { fn consent_page(hostname: &str, query: &AuthorizationQuery, route: &str, nonce: &str) -> String {
let response_mode = &query.response_mode.clone().unwrap_or("fragment".to_string()); let response_mode = &query.response_mode.clone().unwrap_or("fragment".to_string());
let user_id = query.username.clone().expect("user_id in authorization query");
let template = ConsentPageTemplate { let template = ConsentPageTemplate {
nonce, nonce,
hostname, hostname,
route, route,
user_id: &encode(&user_id),
client_id: &encode(query.client_id.as_str()), client_id: &encode(query.client_id.as_str()),
client_secret: query.client_secret.as_deref(), client_secret: query.client_secret.as_deref(),
redirect_uri: &encode(query.redirect_uri.as_str()), redirect_uri: &encode(query.redirect_uri.as_str()),

View file

@ -6,7 +6,10 @@
'{{ client_id }}' (at {{ redirect_uri }}) is requesting permission for '{{ scope }}' '{{ client_id }}' (at {{ redirect_uri }}) is requesting permission for '{{ scope }}'
</p> </p>
<form method="post"> <form method="post">
<input type="submit" value="Accept" formaction="{{ route }}?client_id={{ client_id }}{%- if let Some(secret) = client_secret -%}&client_secret={{ secret }}{%- endif -%}&redirect_uri={{ redirect_uri }}&scope={{ scope }}&state={{ state }}&code_challenge={{ code_challenge }}&code_challenge_method={{ code_challenge_method }}&response_type={{ response_type }}&response_mode={{ response_mode }}&allow=true"> <input type="submit" value="Accept" formaction="{{ route }}?client_id={{ client_id }}{%- if let Some(secret)
= client_secret -%}&client_secret={{ secret }}{%- endif -%}&redirect_uri={{ redirect_uri }}&scope={{ scope
}}&state={{ state }}&code_challenge={{ code_challenge }}&code_challenge_method={{ code_challenge_method
}}&response_type={{ response_type }}&response_mode={{ response_mode }}&allow={{ user_id }}">
<input type="submit" value="Deny" formaction="{{ route }}?client_id={{ client_id }}{%- if let Some(secret) = client_secret -%}&client_secret={{ secret }}{%- endif -%}&redirect_uri={{ redirect_uri }}&scope={{scope }}&state={{ state }}&code_challenge={{ code_challenge }}&code_challenge_method={{ code_challenge_method }}&response_type={{ response_type }}&response_mode={{ response_mode }}&deny=true"> <input type="submit" value="Deny" formaction="{{ route }}?client_id={{ client_id }}{%- if let Some(secret) = client_secret -%}&client_secret={{ secret }}{%- endif -%}&redirect_uri={{ redirect_uri }}&scope={{scope }}&state={{ state }}&code_challenge={{ code_challenge }}&code_challenge_method={{ code_challenge_method }}&response_type={{ response_type }}&response_mode={{ response_mode }}&deny=true">
</form> </form>
</div> </div>