Support JWT authentication on deactivate route.

Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
Jason Volk
2025-09-09 01:31:35 +00:00
parent 29c2c8a333
commit deda746222
5 changed files with 59 additions and 38 deletions

20
Cargo.lock generated
View File

@@ -3478,7 +3478,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma" name = "ruma"
version = "0.12.6" version = "0.12.6"
source = "git+https://github.com/matrix-construct/ruma?rev=93f28d777073e60687e8013e59d8d16b5adbdf9a#93f28d777073e60687e8013e59d8d16b5adbdf9a" source = "git+https://github.com/matrix-construct/ruma?rev=5682b88cf1bcaf0f47805d614b476b242ef075d4#5682b88cf1bcaf0f47805d614b476b242ef075d4"
dependencies = [ dependencies = [
"assign", "assign",
"js_int", "js_int",
@@ -3497,7 +3497,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-appservice-api" name = "ruma-appservice-api"
version = "0.12.2" version = "0.12.2"
source = "git+https://github.com/matrix-construct/ruma?rev=93f28d777073e60687e8013e59d8d16b5adbdf9a#93f28d777073e60687e8013e59d8d16b5adbdf9a" source = "git+https://github.com/matrix-construct/ruma?rev=5682b88cf1bcaf0f47805d614b476b242ef075d4#5682b88cf1bcaf0f47805d614b476b242ef075d4"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@@ -3509,7 +3509,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-client-api" name = "ruma-client-api"
version = "0.20.4" version = "0.20.4"
source = "git+https://github.com/matrix-construct/ruma?rev=93f28d777073e60687e8013e59d8d16b5adbdf9a#93f28d777073e60687e8013e59d8d16b5adbdf9a" source = "git+https://github.com/matrix-construct/ruma?rev=5682b88cf1bcaf0f47805d614b476b242ef075d4#5682b88cf1bcaf0f47805d614b476b242ef075d4"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"assign", "assign",
@@ -3532,7 +3532,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-common" name = "ruma-common"
version = "0.15.4" version = "0.15.4"
source = "git+https://github.com/matrix-construct/ruma?rev=93f28d777073e60687e8013e59d8d16b5adbdf9a#93f28d777073e60687e8013e59d8d16b5adbdf9a" source = "git+https://github.com/matrix-construct/ruma?rev=5682b88cf1bcaf0f47805d614b476b242ef075d4#5682b88cf1bcaf0f47805d614b476b242ef075d4"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"base64", "base64",
@@ -3565,7 +3565,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-events" name = "ruma-events"
version = "0.30.5" version = "0.30.5"
source = "git+https://github.com/matrix-construct/ruma?rev=93f28d777073e60687e8013e59d8d16b5adbdf9a#93f28d777073e60687e8013e59d8d16b5adbdf9a" source = "git+https://github.com/matrix-construct/ruma?rev=5682b88cf1bcaf0f47805d614b476b242ef075d4#5682b88cf1bcaf0f47805d614b476b242ef075d4"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"indexmap", "indexmap",
@@ -3591,7 +3591,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-federation-api" name = "ruma-federation-api"
version = "0.11.2" version = "0.11.2"
source = "git+https://github.com/matrix-construct/ruma?rev=93f28d777073e60687e8013e59d8d16b5adbdf9a#93f28d777073e60687e8013e59d8d16b5adbdf9a" source = "git+https://github.com/matrix-construct/ruma?rev=5682b88cf1bcaf0f47805d614b476b242ef075d4#5682b88cf1bcaf0f47805d614b476b242ef075d4"
dependencies = [ dependencies = [
"bytes", "bytes",
"headers", "headers",
@@ -3613,7 +3613,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-identifiers-validation" name = "ruma-identifiers-validation"
version = "0.10.1" version = "0.10.1"
source = "git+https://github.com/matrix-construct/ruma?rev=93f28d777073e60687e8013e59d8d16b5adbdf9a#93f28d777073e60687e8013e59d8d16b5adbdf9a" source = "git+https://github.com/matrix-construct/ruma?rev=5682b88cf1bcaf0f47805d614b476b242ef075d4#5682b88cf1bcaf0f47805d614b476b242ef075d4"
dependencies = [ dependencies = [
"js_int", "js_int",
"thiserror 2.0.16", "thiserror 2.0.16",
@@ -3622,7 +3622,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-macros" name = "ruma-macros"
version = "0.15.2" version = "0.15.2"
source = "git+https://github.com/matrix-construct/ruma?rev=93f28d777073e60687e8013e59d8d16b5adbdf9a#93f28d777073e60687e8013e59d8d16b5adbdf9a" source = "git+https://github.com/matrix-construct/ruma?rev=5682b88cf1bcaf0f47805d614b476b242ef075d4#5682b88cf1bcaf0f47805d614b476b242ef075d4"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"proc-macro-crate", "proc-macro-crate",
@@ -3637,7 +3637,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-push-gateway-api" name = "ruma-push-gateway-api"
version = "0.11.0" version = "0.11.0"
source = "git+https://github.com/matrix-construct/ruma?rev=93f28d777073e60687e8013e59d8d16b5adbdf9a#93f28d777073e60687e8013e59d8d16b5adbdf9a" source = "git+https://github.com/matrix-construct/ruma?rev=5682b88cf1bcaf0f47805d614b476b242ef075d4#5682b88cf1bcaf0f47805d614b476b242ef075d4"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@@ -3649,7 +3649,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-signatures" name = "ruma-signatures"
version = "0.17.1" version = "0.17.1"
source = "git+https://github.com/matrix-construct/ruma?rev=93f28d777073e60687e8013e59d8d16b5adbdf9a#93f28d777073e60687e8013e59d8d16b5adbdf9a" source = "git+https://github.com/matrix-construct/ruma?rev=5682b88cf1bcaf0f47805d614b476b242ef075d4#5682b88cf1bcaf0f47805d614b476b242ef075d4"
dependencies = [ dependencies = [
"base64", "base64",
"ed25519-dalek", "ed25519-dalek",

View File

@@ -317,7 +317,7 @@ default-features = false
[workspace.dependencies.ruma] [workspace.dependencies.ruma]
git = "https://github.com/matrix-construct/ruma" git = "https://github.com/matrix-construct/ruma"
rev = "93f28d777073e60687e8013e59d8d16b5adbdf9a" rev = "5682b88cf1bcaf0f47805d614b476b242ef075d4"
features = [ features = [
"__compat", "__compat",
"appservice-api-c", "appservice-api-c",

View File

@@ -7,11 +7,11 @@ use ruma::api::client::{
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn, request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
whoami, whoami,
}, },
uiaa::{AuthFlow, AuthType, UiaaInfo}, uiaa::{AuthData, AuthFlow, AuthType, Jwt, UiaaInfo},
}; };
use tuwunel_core::{Err, Error, Result, err, info, utils, utils::ReadyExt}; use tuwunel_core::{Err, Error, Result, err, info, utils, utils::ReadyExt};
use super::SESSION_ID_LENGTH; use super::{SESSION_ID_LENGTH, session::jwt::validate_user};
use crate::Ruma; use crate::Ruma;
/// # `POST /_matrix/client/r0/account/password` /// # `POST /_matrix/client/r0/account/password`
@@ -140,20 +140,29 @@ pub(crate) async fn deactivate_route(
InsecureClientIp(client): InsecureClientIp, InsecureClientIp(client): InsecureClientIp,
body: Ruma<deactivate::v3::Request>, body: Ruma<deactivate::v3::Request>,
) -> Result<deactivate::v3::Response> { ) -> Result<deactivate::v3::Response> {
// Authentication for this endpoint was made optional, but we need let pass_flow = AuthFlow { stages: vec![AuthType::Password] };
// authentication currently let jwt_flow = AuthFlow { stages: vec![AuthType::Jwt] };
let sender_user = body
.sender_user
.as_ref()
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
let mut uiaainfo = UiaaInfo { let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow { stages: vec![AuthType::Password] }], flows: [pass_flow, jwt_flow].into(),
..Default::default() ..Default::default()
}; };
match &body.auth { let sender_user = match &body.auth {
| Some(AuthData::Jwt(Jwt { token, .. })) => {
let sender_user = validate_user(&services, token)?;
if !services.users.exists(&sender_user).await {
return Err!(Request(NotFound("User {sender_user} is not registered.")));
}
// Success!
sender_user
},
| Some(auth) => { | Some(auth) => {
let sender_user = body
.sender_user
.as_deref()
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
let (worked, uiaainfo) = services let (worked, uiaainfo) = services
.uiaa .uiaa
.try_auth(sender_user, body.sender_device(), auth, &uiaainfo) .try_auth(sender_user, body.sender_device(), auth, &uiaainfo)
@@ -162,10 +171,17 @@ pub(crate) async fn deactivate_route(
if !worked { if !worked {
return Err(Error::Uiaa(uiaainfo)); return Err(Error::Uiaa(uiaainfo));
} }
// Success! // Success!
sender_user.to_owned()
}, },
| _ => match body.json_body { | _ => match body.json_body {
| Some(ref json) => { | Some(ref json) => {
let sender_user = body
.sender_user
.as_ref()
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services services
.uiaa .uiaa
@@ -177,11 +193,11 @@ pub(crate) async fn deactivate_route(
return Err!(Request(NotJson("JSON body is not valid"))); return Err!(Request(NotJson("JSON body is not valid")));
}, },
}, },
} };
services services
.deactivate .deactivate
.full_deactivate(sender_user) .full_deactivate(&sender_user)
.boxed() .boxed()
.await?; .await?;

View File

@@ -22,20 +22,9 @@ pub(super) async fn handle_login(
_body: &Ruma<Request>, _body: &Ruma<Request>,
info: &Token, info: &Token,
) -> Result<OwnedUserId> { ) -> Result<OwnedUserId> {
let config = &services.config.jwt; let user_id = validate_user(services, &info.token)?;
if !config.enable {
return Err!(Request(Unknown("JWT login is not enabled.")));
}
let claim = validate(config, &info.token)?;
let local = claim.sub.to_lowercase();
let server = &services.server.name;
let user_id = UserId::parse_with_server_name(local, server).map_err(|e| {
err!(Request(InvalidUsername("JWT subject is not a valid user MXID: {e}")))
})?;
if !services.users.exists(&user_id).await { if !services.users.exists(&user_id).await {
let config = &services.config.jwt;
if !config.register_user { if !config.register_user {
return Err!(Request(NotFound("User {user_id} is not registered on this server."))); return Err!(Request(NotFound("User {user_id} is not registered on this server.")));
} }
@@ -49,6 +38,22 @@ pub(super) async fn handle_login(
Ok(user_id) Ok(user_id)
} }
pub(crate) fn validate_user(services: &Services, token: &str) -> Result<OwnedUserId> {
let config = &services.config.jwt;
if !config.enable {
return Err!(Request(Unauthorized("JWT login is not enabled.")));
}
let claim = validate(config, token)?;
let local = claim.sub.to_lowercase();
let server = &services.server.name;
let user_id = UserId::parse_with_server_name(local, server).map_err(|e| {
err!(Request(InvalidUsername("JWT subject is not a valid user MXID: {e}")))
})?;
Ok(user_id)
}
fn validate(config: &JwtConfig, token: &str) -> Result<Claim> { fn validate(config: &JwtConfig, token: &str) -> Result<Claim> {
let verifier = init_verifier(config)?; let verifier = init_verifier(config)?;
let validator = init_validator(config)?; let validator = init_validator(config)?;

View File

@@ -1,5 +1,5 @@
mod appservice; mod appservice;
mod jwt; pub(crate) mod jwt;
mod ldap; mod ldap;
mod logout; mod logout;
mod password; mod password;