feat: Implement oauth auth code and refresh token flows

This commit is contained in:
Ginger
2026-04-30 08:54:55 -04:00
parent f269fb5cfc
commit 13917bb5c3
37 changed files with 1057 additions and 157 deletions
+1
View File
@@ -179,6 +179,7 @@ pub(crate) async fn register_route(
&user_id,
&device_id,
&new_token,
None,
body.initial_device_display_name.clone(),
Some(client.to_string()),
)
+1
View File
@@ -94,6 +94,7 @@ pub(crate) async fn update_device_route(
&device_id,
&appservice.registration.as_token,
None,
None,
Some(client.to_string()),
)
.await?;
+15 -1
View File
@@ -1,17 +1,31 @@
mod register_client;
mod server_metadata;
mod token;
use axum::{
Json, Router,
extract::State,
routing::method_routing::{get, post},
};
use serde_json::json;
pub(crate) use server_metadata::*;
pub(crate) const BASE_PATH: &str = "/_continuwuity/oauth2/";
const BASE_PATH: &str = "/_continuwuity/oauth2/";
pub(crate) fn router() -> Router<crate::State> {
Router::new().nest(BASE_PATH, oauth_router())
// TODO(unspecced): used by old versions of the matrix-js-sdk
// .route("/.well-known/openid-configuration", get(
// async |State(services): State<crate::State>| {
// Json(authorization_server_metadata(&services).await)
// }
// ))
}
fn oauth_router() -> Router<crate::State> {
Router::new()
.route("/client/register", post(register_client::register_client_route))
// TODO(unspecced): used by old versions of the matrix-js-sdk
.route("/client/keys.json", get(async || Json(json!({"keys": []}))))
.route("/grant/token", post(token::token_route))
}
+10 -6
View File
@@ -1,7 +1,8 @@
use axum::extract::State;
use conduwuit::Result;
use ruma::{api::client::discovery::get_authorization_server_metadata, serde::Raw};
use serde_json::json;
use serde_json::{Value, json};
use service::Services;
use crate::Ruma;
@@ -9,13 +10,19 @@ pub(crate) async fn get_authorization_server_metadata_route(
State(services): State<crate::State>,
_body: Ruma<get_authorization_server_metadata::v1::Request>,
) -> Result<get_authorization_server_metadata::v1::Response> {
let metadata = Raw::new(&authorization_server_metadata(&services).await).unwrap();
Ok(get_authorization_server_metadata::v1::Response::new(metadata.cast_unchecked()))
}
pub(crate) async fn authorization_server_metadata(services: &Services) -> Value {
let endpoint_base = services
.config
.get_client_domain()
.join(super::BASE_PATH)
.unwrap();
let metadata = Raw::new(&json!({
json!({
"authorization_endpoint": endpoint_base.join("grant/authorization_code").unwrap(),
"code_challenge_methods_supported": ["S256"],
"grant_types_supported": ["authorization_code", "refresh_token"],
@@ -27,8 +34,5 @@ pub(crate) async fn get_authorization_server_metadata_route(
"response_types_supported": ["code"],
"revocation_endpoint": endpoint_base.join("client/revoke").unwrap(),
"token_endpoint": endpoint_base.join("grant/token").unwrap(),
}))
.unwrap();
Ok(get_authorization_server_metadata::v1::Response::new(metadata.cast_unchecked()))
})
}
+13
View File
@@ -0,0 +1,13 @@
use axum::{Form, Json, extract::State, response::IntoResponse};
use http::StatusCode;
use service::oauth::grant::TokenRequest;
pub(crate) async fn token_route(
State(services): State<crate::State>,
Form(request): Form<TokenRequest>,
) -> impl IntoResponse {
match services.oauth.issue_token(request).await {
| Ok(response) => Ok(Json(response).into_response()),
| Err(err) => Err((StatusCode::BAD_REQUEST, err.message())),
}
}
+2 -1
View File
@@ -202,7 +202,7 @@ pub(crate) async fn login_route(
if device_exists {
services
.users
.set_token(&user_id, &device_id, &token)
.set_token(&user_id, &device_id, &token, None)
.await?;
} else {
services
@@ -211,6 +211,7 @@ pub(crate) async fn login_route(
&user_id,
&device_id,
&token,
None,
body.initial_device_display_name.clone(),
Some(client.to_string()),
)
-1
View File
@@ -69,7 +69,6 @@ pub(crate) async fn sync_events_v5_route(
ClientIp(client_ip): ClientIp,
body: Ruma<sync_events::v5::Request>,
) -> Result<sync_events::v5::Response> {
debug_assert!(DEFAULT_BUMP_TYPES.is_sorted(), "DEFAULT_BUMP_TYPES is not sorted");
let sender_user = body.identity.sender_user();
let sender_device = body.identity.expect_sender_device()?;
+1 -1
View File
@@ -187,7 +187,7 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
.ruma_route(&client::get_rtc_transports)
.ruma_route(&client::room_initial_sync_route)
.ruma_route(&client::get_authorization_server_metadata_route)
.nest(client::oauth::BASE_PATH, client::oauth::router())
.merge(client::oauth::router())
.route("/_conduwuit/server_version", get(client::conduwuit_server_version))
.route("/_continuwuity/server_version", get(client::conduwuit_server_version))
.ruma_route(&admin::rooms::ban::ban_room)
+1 -1
View File
@@ -153,7 +153,7 @@ impl CheckAuth for AccessToken {
query: AuthQueryParams,
route: TypeId,
) -> Result<Self::Identity> {
if let Ok((sender_user, sender_device)) = services.users.find_from_token(&output).await {
if let Some((sender_user, sender_device)) = services.users.find_from_token(&output).await {
// Locked users can only use /logout and /logout/all
if services
.users