2025-05-09 11:17:50 +02:00
|
|
|
use conduwuit_web::oidc::{
|
|
|
|
|
oidc_consent_form,
|
|
|
|
|
AuthorizationQuery,
|
|
|
|
|
LoginError,
|
|
|
|
|
LoginQuery,
|
|
|
|
|
OidcRequest,
|
|
|
|
|
OidcResponse,
|
2025-04-16 15:12:00 +02:00
|
|
|
};
|
2025-05-09 11:17:50 +02:00
|
|
|
use axum::extract::State;
|
2025-04-16 15:12:00 +02:00
|
|
|
use conduwuit::{
|
|
|
|
|
Result,
|
|
|
|
|
err,
|
|
|
|
|
utils::hash::verify_password,
|
|
|
|
|
};
|
|
|
|
|
use ruma::user_id::UserId;
|
|
|
|
|
|
2025-05-09 11:17:50 +02:00
|
|
|
//#[axum::debug_handler]
|
2025-04-16 15:12:00 +02:00
|
|
|
/// # `POST /_matrix/client/unstable/org.matrix.msc2964/login`
|
|
|
|
|
///
|
|
|
|
|
/// Display a login UI to the user and return an authorization code on success.
|
|
|
|
|
/// We presume that the OAuth2 query parameters are provided in the form.
|
|
|
|
|
/// With the code, the client may then access stage two,
|
|
|
|
|
/// [super::authorize::authorize_consent].
|
|
|
|
|
pub(crate) async fn oidc_login(
|
|
|
|
|
State(services): State<crate::State>,
|
2025-05-09 11:17:50 +02:00
|
|
|
request: OidcRequest,
|
|
|
|
|
) -> Result<OidcResponse> {
|
|
|
|
|
let query: LoginQuery = request.clone().try_into().map_err(|LoginError(err)|
|
|
|
|
|
err!(Request(InvalidParam("Cannot process login form. {err}")))
|
|
|
|
|
)?;
|
2025-04-16 15:12:00 +02:00
|
|
|
// Only accept local usernames. Mostly to simplify things at first.
|
|
|
|
|
let user_id = UserId::parse_with_server_name(
|
2025-05-09 11:17:50 +02:00
|
|
|
query.username.clone(),
|
2025-04-16 15:12:00 +02:00
|
|
|
&services.config.server_name
|
|
|
|
|
)
|
2025-05-09 11:17:50 +02:00
|
|
|
.map_err(|e| err!(Request(InvalidUsername("Username is invalid: {e}"))))?;
|
2025-04-16 15:12:00 +02:00
|
|
|
|
|
|
|
|
if ! services.users.exists(&user_id).await {
|
|
|
|
|
return Err(err!(Request(Unknown("unknown username"))));
|
|
|
|
|
}
|
|
|
|
|
tracing::info!("logging in: {user_id:?}");
|
|
|
|
|
let valid_hash = services
|
|
|
|
|
.users
|
|
|
|
|
.password_hash(&user_id)
|
|
|
|
|
.await
|
|
|
|
|
.inspect_err(|e| tracing::info!("could not get user's hash: {e:?}"))?;
|
|
|
|
|
|
|
|
|
|
if valid_hash.is_empty() {
|
|
|
|
|
return Err(err!(Request(UserDeactivated("the user's hash was not found"))));
|
|
|
|
|
}
|
2025-05-09 11:17:50 +02:00
|
|
|
if let Err(_) = verify_password(&query.password, &valid_hash) {
|
2025-04-16 15:12:00 +02:00
|
|
|
return Err(err!(Request(InvalidParam("password does not match"))));
|
|
|
|
|
}
|
2025-05-09 11:17:50 +02:00
|
|
|
tracing::info!("{user_id:?} passed, forwarding to consent page");
|
2025-04-16 15:12:00 +02:00
|
|
|
|
|
|
|
|
let hostname = services
|
|
|
|
|
.config
|
|
|
|
|
.well_known
|
|
|
|
|
.client
|
|
|
|
|
.as_ref()
|
2025-05-09 11:17:50 +02:00
|
|
|
.map(|s| s.domain().expect("well-known client should have a domain"));
|
|
|
|
|
|
|
|
|
|
let hostname = hostname.unwrap_or("Continuwuity");
|
|
|
|
|
let authorization_query: AuthorizationQuery = query.into();
|
2025-04-16 15:12:00 +02:00
|
|
|
|
|
|
|
|
services
|
|
|
|
|
.oidc
|
|
|
|
|
.endpoint()
|
2025-05-09 11:17:50 +02:00
|
|
|
.with_solicitor(oidc_consent_form(hostname, &authorization_query))
|
2025-04-16 15:12:00 +02:00
|
|
|
.authorization_flow()
|
2025-05-09 11:17:50 +02:00
|
|
|
.execute(request)
|
2025-04-16 15:12:00 +02:00
|
|
|
.map_err(|err| err!(Request(Unknown("authorization failed: {err:?}"))))
|
|
|
|
|
}
|