2025-05-10 13:29:31 +01:00
|
|
|
use axum::extract::{Query, State};
|
|
|
|
|
use conduwuit::{Result, err};
|
|
|
|
|
use conduwuit_web::oidc::{
|
2025-08-11 22:04:07 +02:00
|
|
|
oidc_consent_form, oidc_login_form, AuthorizationQuery, OidcRequest, OidcResponse,
|
2025-05-10 13:29:31 +01:00
|
|
|
};
|
2025-04-16 15:12:00 +02:00
|
|
|
use oxide_auth::{
|
|
|
|
|
endpoint::{OwnerConsent, Solicitation},
|
|
|
|
|
frontends::simple::endpoint::FnSolicitor,
|
|
|
|
|
};
|
|
|
|
|
use percent_encoding::percent_decode_str;
|
|
|
|
|
|
|
|
|
|
/// # `GET /_matrix/client/unstable/org.matrix.msc2964/authorize`
|
|
|
|
|
///
|
|
|
|
|
/// Authenticate a user and device, and solicit the user's consent.
|
|
|
|
|
///
|
|
|
|
|
/// Redirects to the login page if no token or token not belonging to any user.
|
|
|
|
|
/// [super::login::oidc_login] takes it up at the same point, so it's either
|
|
|
|
|
/// the client has a token, or the user does user password. Then the user gets
|
|
|
|
|
/// access to stage two, [authorize_consent].
|
|
|
|
|
pub(crate) async fn authorize(
|
|
|
|
|
State(services): State<crate::State>,
|
2025-05-09 11:17:50 +02:00
|
|
|
Query(query): Query<AuthorizationQuery>,
|
|
|
|
|
oauth: OidcRequest,
|
|
|
|
|
) -> Result<OidcResponse> {
|
2025-08-04 17:57:47 +02:00
|
|
|
tracing::trace!("processing OAuth request: {query:#?}");
|
2025-04-16 15:12:00 +02:00
|
|
|
// Enforce MSC2964's restrictions on OAuth2 flow.
|
|
|
|
|
let Ok(scope) = percent_decode_str(&query.scope).decode_utf8() else {
|
|
|
|
|
return Err(err!(Request(Unknown("the scope could not be percent-decoded"))));
|
2025-05-10 13:29:31 +01:00
|
|
|
};
|
2025-04-16 15:12:00 +02:00
|
|
|
//if ! scope.contains("urn:matrix:api:*") {
|
2025-05-10 13:29:31 +01:00
|
|
|
if !scope.contains("urn:matrix:org.matrix.msc2967.client:api:*") {
|
2025-04-16 15:12:00 +02:00
|
|
|
return Err(err!(Request(Unknown("the scope does not include the client API"))));
|
|
|
|
|
}
|
2025-05-10 13:29:31 +01:00
|
|
|
if !scope.contains("urn:matrix:org.matrix.msc2967.client:device:") {
|
2025-04-16 15:12:00 +02:00
|
|
|
return Err(err!(Request(Unknown("the scope does not include a device ID"))));
|
|
|
|
|
}
|
|
|
|
|
if query.code_challenge_method != "S256" {
|
|
|
|
|
return Err(err!(Request(Unknown("unsupported code challenge method"))));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Redirect to the login page if no token or token not known.
|
2025-05-10 20:51:02 +01:00
|
|
|
let hostname = services.config.server_name.host();
|
2025-08-11 22:04:07 +02:00
|
|
|
let Some(token) = oauth.authorization_header() else {
|
|
|
|
|
return Ok(oidc_login_form(hostname, &query));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
tracing::debug!("submitting OIDC authorisation for token : {token:#?}");
|
|
|
|
|
// Get the user id from the token and add it to the query.
|
2025-08-12 02:31:40 +02:00
|
|
|
let (owner_id, _) = services.oidc.user_and_device_from_token(token)?;
|
2025-08-11 22:04:07 +02:00
|
|
|
let mut query_with_user_id = query.clone();
|
|
|
|
|
query_with_user_id.username = Some(owner_id.localpart().to_string());
|
2025-04-16 15:12:00 +02:00
|
|
|
|
|
|
|
|
services
|
|
|
|
|
.oidc
|
|
|
|
|
.endpoint()
|
2025-08-11 22:04:07 +02:00
|
|
|
.with_solicitor(oidc_consent_form(hostname, &query_with_user_id))
|
2025-04-16 15:12:00 +02:00
|
|
|
.authorization_flow()
|
|
|
|
|
.execute(oauth)
|
|
|
|
|
.map_err(|err| err!("authorization failed: {err:?}"))
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-10 13:29:31 +01:00
|
|
|
/// Whether a user allows their device to access this homeserver's resources.
|
2025-04-16 15:12:00 +02:00
|
|
|
#[derive(serde::Deserialize)]
|
|
|
|
|
pub(crate) struct Allowance {
|
2025-08-11 22:04:07 +02:00
|
|
|
allow: Option<String>,
|
2025-04-16 15:12:00 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-11 22:04:07 +02:00
|
|
|
/// # `POST /_matrix/client/unstable/org.matrix.msc2964/authorize?allow=[Option<String>]`
|
2025-04-16 15:12:00 +02:00
|
|
|
///
|
|
|
|
|
/// 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
|
|
|
|
|
/// [super::token::token] endpoint.
|
|
|
|
|
pub(crate) async fn authorize_consent(
|
|
|
|
|
Query(Allowance { allow }): Query<Allowance>,
|
|
|
|
|
State(services): State<crate::State>,
|
2025-05-09 12:08:12 +02:00
|
|
|
oauth: OidcRequest,
|
|
|
|
|
) -> Result<OidcResponse> {
|
2025-08-11 22:04:07 +02:00
|
|
|
tracing::debug!("processing user's consent: {:?} - {:?}", allow, oauth);
|
2025-04-16 15:12:00 +02:00
|
|
|
|
|
|
|
|
services
|
|
|
|
|
.oidc
|
|
|
|
|
.endpoint()
|
2025-05-10 13:29:31 +01:00
|
|
|
.with_solicitor(FnSolicitor(
|
2025-08-11 22:04:07 +02:00
|
|
|
move |_: &mut _, _: Solicitation<'_>| match allow.clone() {
|
|
|
|
|
| None => OwnerConsent::Denied,
|
|
|
|
|
| Some(user_id) => OwnerConsent::Authorized(user_id),
|
2025-05-10 13:29:31 +01:00
|
|
|
},
|
|
|
|
|
))
|
2025-04-16 15:12:00 +02:00
|
|
|
.authorization_flow()
|
|
|
|
|
.execute(oauth)
|
|
|
|
|
.map_err(|err| err!(Request(Unknown("consent request failed: {err:?}"))))
|
|
|
|
|
}
|