Abstract and dedup the general UIAA pattern into api::router.
Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
@@ -1,18 +1,13 @@
|
|||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum_client_ip::InsecureClientIp;
|
use axum_client_ip::InsecureClientIp;
|
||||||
use futures::{FutureExt, StreamExt};
|
use futures::{FutureExt, StreamExt};
|
||||||
use ruma::api::client::{
|
use ruma::api::client::account::{
|
||||||
account::{
|
ThirdPartyIdRemovalStatus, change_password, deactivate, get_3pids,
|
||||||
ThirdPartyIdRemovalStatus, change_password, deactivate, get_3pids,
|
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn, whoami,
|
||||||
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
|
|
||||||
whoami,
|
|
||||||
},
|
|
||||||
uiaa::{AuthData, AuthFlow, AuthType, Jwt, UiaaInfo},
|
|
||||||
};
|
};
|
||||||
use tuwunel_core::{Err, Error, Result, err, info, utils, utils::ReadyExt};
|
use tuwunel_core::{Err, Result, info, utils::ReadyExt};
|
||||||
|
|
||||||
use super::{SESSION_ID_LENGTH, session::jwt::validate_user};
|
use crate::{Ruma, router::auth_uiaa};
|
||||||
use crate::Ruma;
|
|
||||||
|
|
||||||
/// # `POST /_matrix/client/r0/account/password`
|
/// # `POST /_matrix/client/r0/account/password`
|
||||||
///
|
///
|
||||||
@@ -37,45 +32,7 @@ pub(crate) async fn change_password_route(
|
|||||||
InsecureClientIp(client): InsecureClientIp,
|
InsecureClientIp(client): InsecureClientIp,
|
||||||
body: Ruma<change_password::v3::Request>,
|
body: Ruma<change_password::v3::Request>,
|
||||||
) -> Result<change_password::v3::Response> {
|
) -> Result<change_password::v3::Response> {
|
||||||
// Authentication for this endpoint was made optional, but we need
|
let ref sender_user = auth_uiaa(&services, &body).await?;
|
||||||
// authentication currently
|
|
||||||
let sender_user = body
|
|
||||||
.sender_user
|
|
||||||
.as_ref()
|
|
||||||
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
|
|
||||||
|
|
||||||
let mut uiaainfo = UiaaInfo {
|
|
||||||
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
match &body.auth {
|
|
||||||
| Some(auth) => {
|
|
||||||
let (worked, uiaainfo) = services
|
|
||||||
.uiaa
|
|
||||||
.try_auth(sender_user, body.sender_device(), auth, &uiaainfo)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if !worked {
|
|
||||||
return Err(Error::Uiaa(uiaainfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Success!
|
|
||||||
},
|
|
||||||
| _ => match body.json_body {
|
|
||||||
| Some(ref json) => {
|
|
||||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
|
||||||
services
|
|
||||||
.uiaa
|
|
||||||
.create(sender_user, body.sender_device(), &uiaainfo, json);
|
|
||||||
|
|
||||||
return Err(Error::Uiaa(uiaainfo));
|
|
||||||
},
|
|
||||||
| _ => {
|
|
||||||
return Err!(Request(NotJson("JSON body is not valid")));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
services
|
services
|
||||||
.users
|
.users
|
||||||
@@ -87,7 +44,7 @@ pub(crate) async fn change_password_route(
|
|||||||
services
|
services
|
||||||
.users
|
.users
|
||||||
.all_device_ids(sender_user)
|
.all_device_ids(sender_user)
|
||||||
.ready_filter(|id| *id != body.sender_device())
|
.ready_filter(|&id| Some(id) != body.sender_device.as_deref())
|
||||||
.for_each(|id| services.users.remove_device(sender_user, id))
|
.for_each(|id| services.users.remove_device(sender_user, id))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
@@ -140,69 +97,15 @@ 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> {
|
||||||
let pass_flow = AuthFlow { stages: vec![AuthType::Password] };
|
let ref sender_user = auth_uiaa(&services, &body).await?;
|
||||||
let jwt_flow = AuthFlow { stages: vec![AuthType::Jwt] };
|
|
||||||
let mut uiaainfo = UiaaInfo {
|
|
||||||
flows: [pass_flow, jwt_flow].into(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
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) => {
|
|
||||||
let sender_user = body
|
|
||||||
.sender_user
|
|
||||||
.as_deref()
|
|
||||||
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
|
|
||||||
|
|
||||||
let (worked, uiaainfo) = services
|
|
||||||
.uiaa
|
|
||||||
.try_auth(sender_user, body.sender_device(), auth, &uiaainfo)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if !worked {
|
|
||||||
return Err(Error::Uiaa(uiaainfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Success!
|
|
||||||
sender_user.to_owned()
|
|
||||||
},
|
|
||||||
| _ => match body.json_body {
|
|
||||||
| 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));
|
|
||||||
services
|
|
||||||
.uiaa
|
|
||||||
.create(sender_user, body.sender_device(), &uiaainfo, json);
|
|
||||||
|
|
||||||
return Err(Error::Uiaa(uiaainfo));
|
|
||||||
},
|
|
||||||
| _ => {
|
|
||||||
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?;
|
||||||
|
|
||||||
info!("User {sender_user} deactivated their account.");
|
info!("User {sender_user} deactivated their account.");
|
||||||
|
|
||||||
if services.server.config.admin_room_notices {
|
if services.server.config.admin_room_notices {
|
||||||
services
|
services
|
||||||
.admin
|
.admin
|
||||||
|
|||||||
@@ -3,16 +3,13 @@ use axum_client_ip::InsecureClientIp;
|
|||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
MilliSecondsSinceUnixEpoch, OwnedDeviceId,
|
MilliSecondsSinceUnixEpoch, OwnedDeviceId,
|
||||||
api::client::{
|
api::client::device::{
|
||||||
device::{self, delete_device, delete_devices, get_device, get_devices, update_device},
|
self, delete_device, delete_devices, get_device, get_devices, update_device,
|
||||||
error::ErrorKind,
|
|
||||||
uiaa::{AuthFlow, AuthType, UiaaInfo},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use tuwunel_core::{Err, Error, Result, debug, err, utils};
|
use tuwunel_core::{Err, Result, debug, err, utils};
|
||||||
|
|
||||||
use super::SESSION_ID_LENGTH;
|
use crate::{Ruma, client::DEVICE_ID_LENGTH, router::auth_uiaa};
|
||||||
use crate::{Ruma, client::DEVICE_ID_LENGTH};
|
|
||||||
|
|
||||||
/// # `GET /_matrix/client/r0/devices`
|
/// # `GET /_matrix/client/r0/devices`
|
||||||
///
|
///
|
||||||
@@ -126,10 +123,10 @@ pub(crate) async fn delete_device_route(
|
|||||||
State(services): State<crate::State>,
|
State(services): State<crate::State>,
|
||||||
body: Ruma<delete_device::v3::Request>,
|
body: Ruma<delete_device::v3::Request>,
|
||||||
) -> Result<delete_device::v3::Response> {
|
) -> Result<delete_device::v3::Response> {
|
||||||
let (sender_user, sender_device) = body.sender();
|
|
||||||
let appservice = body.appservice_info.as_ref();
|
let appservice = body.appservice_info.as_ref();
|
||||||
|
|
||||||
if appservice.is_some_and(|appservice| appservice.registration.device_management) {
|
if appservice.is_some_and(|appservice| appservice.registration.device_management) {
|
||||||
|
let sender_user = body.sender_user();
|
||||||
debug!(
|
debug!(
|
||||||
"Skipping UIAA for {sender_user} as this is from an appservice and MSC4190 is \
|
"Skipping UIAA for {sender_user} as this is from an appservice and MSC4190 is \
|
||||||
enabled"
|
enabled"
|
||||||
@@ -142,38 +139,7 @@ pub(crate) async fn delete_device_route(
|
|||||||
return Ok(delete_device::v3::Response {});
|
return Ok(delete_device::v3::Response {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// UIAA
|
let ref sender_user = auth_uiaa(&services, &body).await?;
|
||||||
let mut uiaainfo = UiaaInfo {
|
|
||||||
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
match &body.auth {
|
|
||||||
| Some(auth) => {
|
|
||||||
let (worked, uiaainfo) = services
|
|
||||||
.uiaa
|
|
||||||
.try_auth(sender_user, sender_device, auth, &uiaainfo)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if !worked {
|
|
||||||
return Err!(Uiaa(uiaainfo));
|
|
||||||
}
|
|
||||||
// Success!
|
|
||||||
},
|
|
||||||
| _ => match body.json_body {
|
|
||||||
| Some(ref json) => {
|
|
||||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
|
||||||
services
|
|
||||||
.uiaa
|
|
||||||
.create(sender_user, sender_device, &uiaainfo, json);
|
|
||||||
|
|
||||||
return Err!(Uiaa(uiaainfo));
|
|
||||||
},
|
|
||||||
| _ => {
|
|
||||||
return Err!(Request(NotJson("Not json.")));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
services
|
services
|
||||||
.users
|
.users
|
||||||
@@ -200,10 +166,10 @@ pub(crate) async fn delete_devices_route(
|
|||||||
State(services): State<crate::State>,
|
State(services): State<crate::State>,
|
||||||
body: Ruma<delete_devices::v3::Request>,
|
body: Ruma<delete_devices::v3::Request>,
|
||||||
) -> Result<delete_devices::v3::Response> {
|
) -> Result<delete_devices::v3::Response> {
|
||||||
let (sender_user, sender_device) = body.sender();
|
|
||||||
let appservice = body.appservice_info.as_ref();
|
let appservice = body.appservice_info.as_ref();
|
||||||
|
|
||||||
if appservice.is_some_and(|appservice| appservice.registration.device_management) {
|
if appservice.is_some_and(|appservice| appservice.registration.device_management) {
|
||||||
|
let sender_user = body.sender_user();
|
||||||
debug!(
|
debug!(
|
||||||
"Skipping UIAA for {sender_user} as this is from an appservice and MSC4190 is \
|
"Skipping UIAA for {sender_user} as this is from an appservice and MSC4190 is \
|
||||||
enabled"
|
enabled"
|
||||||
@@ -218,38 +184,7 @@ pub(crate) async fn delete_devices_route(
|
|||||||
return Ok(delete_devices::v3::Response {});
|
return Ok(delete_devices::v3::Response {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// UIAA
|
let ref sender_user = auth_uiaa(&services, &body).await?;
|
||||||
let mut uiaainfo = UiaaInfo {
|
|
||||||
flows: vec![AuthFlow { stages: vec![AuthType::Password] }],
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
match &body.auth {
|
|
||||||
| Some(auth) => {
|
|
||||||
let (worked, uiaainfo) = services
|
|
||||||
.uiaa
|
|
||||||
.try_auth(sender_user, sender_device, auth, &uiaainfo)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if !worked {
|
|
||||||
return Err(Error::Uiaa(uiaainfo));
|
|
||||||
}
|
|
||||||
// Success!
|
|
||||||
},
|
|
||||||
| _ => match body.json_body {
|
|
||||||
| Some(ref json) => {
|
|
||||||
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
|
|
||||||
services
|
|
||||||
.uiaa
|
|
||||||
.create(sender_user, sender_device, &uiaainfo, json);
|
|
||||||
|
|
||||||
return Err(Error::Uiaa(uiaainfo));
|
|
||||||
},
|
|
||||||
| _ => {
|
|
||||||
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for device_id in &body.devices {
|
for device_id in &body.devices {
|
||||||
services
|
services
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ use http::{Uri, uri};
|
|||||||
use tuwunel_core::{Server, err};
|
use tuwunel_core::{Server, err};
|
||||||
|
|
||||||
use self::handler::RouterExt;
|
use self::handler::RouterExt;
|
||||||
pub(super) use self::{args::Args as Ruma, response::RumaResponse, state::State};
|
pub(super) use self::{
|
||||||
|
args::Args as Ruma, auth::auth_uiaa, response::RumaResponse, state::State,
|
||||||
|
};
|
||||||
use crate::{client, server};
|
use crate::{client, server};
|
||||||
|
|
||||||
pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
mod appservice;
|
mod appservice;
|
||||||
mod server;
|
mod server;
|
||||||
|
mod uiaa;
|
||||||
|
|
||||||
use std::{fmt::Debug, time::SystemTime};
|
use std::{fmt::Debug, time::SystemTime};
|
||||||
|
|
||||||
@@ -35,6 +36,7 @@ use ruma::{
|
|||||||
use tuwunel_core::{Err, Error, Result, is_less_than, utils::result::LogDebugErr};
|
use tuwunel_core::{Err, Error, Result, is_less_than, utils::result::LogDebugErr};
|
||||||
use tuwunel_service::{Services, appservice::RegistrationInfo};
|
use tuwunel_service::{Services, appservice::RegistrationInfo};
|
||||||
|
|
||||||
|
pub(crate) use self::uiaa::auth_uiaa;
|
||||||
use self::{appservice::auth_appservice, server::auth_server};
|
use self::{appservice::auth_appservice, server::auth_server};
|
||||||
use super::request::Request;
|
use super::request::Request;
|
||||||
|
|
||||||
|
|||||||
81
src/api/router/auth/uiaa.rs
Normal file
81
src/api/router/auth/uiaa.rs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
use ruma::{
|
||||||
|
CanonicalJsonValue, OwnedUserId,
|
||||||
|
api::{
|
||||||
|
IncomingRequest,
|
||||||
|
client::uiaa::{AuthData, AuthFlow, AuthType, Jwt, UiaaInfo},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use tuwunel_core::{Err, Error, Result, err, utils};
|
||||||
|
use tuwunel_service::{Services, uiaa::SESSION_ID_LENGTH};
|
||||||
|
|
||||||
|
use crate::{Ruma, client::jwt};
|
||||||
|
|
||||||
|
pub(crate) async fn auth_uiaa<T>(services: &Services, body: &Ruma<T>) -> Result<OwnedUserId>
|
||||||
|
where
|
||||||
|
T: IncomingRequest + Send + Sync,
|
||||||
|
{
|
||||||
|
let flows = [
|
||||||
|
AuthFlow::new([AuthType::Password].into()),
|
||||||
|
AuthFlow::new([AuthType::Jwt].into()),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut uiaainfo = UiaaInfo {
|
||||||
|
flows: flows.into(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
match body
|
||||||
|
.json_body
|
||||||
|
.as_ref()
|
||||||
|
.and_then(CanonicalJsonValue::as_object)
|
||||||
|
.and_then(|body| body.get("auth"))
|
||||||
|
.cloned()
|
||||||
|
.map(CanonicalJsonValue::into)
|
||||||
|
.map(serde_json::from_value)
|
||||||
|
.transpose()?
|
||||||
|
{
|
||||||
|
| Some(AuthData::Jwt(Jwt { ref token, .. })) => {
|
||||||
|
let sender_user = jwt::validate_user(services, token)?;
|
||||||
|
if !services.users.exists(&sender_user).await {
|
||||||
|
return Err!(Request(NotFound("User {sender_user} is not registered.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success!
|
||||||
|
Ok(sender_user)
|
||||||
|
},
|
||||||
|
| Some(ref auth) => {
|
||||||
|
let sender_user = body
|
||||||
|
.sender_user
|
||||||
|
.as_deref()
|
||||||
|
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
|
||||||
|
|
||||||
|
let (worked, uiaainfo) = services
|
||||||
|
.uiaa
|
||||||
|
.try_auth(sender_user, body.sender_device(), auth, &uiaainfo)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !worked {
|
||||||
|
return Err(Error::Uiaa(uiaainfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success!
|
||||||
|
Ok(sender_user.to_owned())
|
||||||
|
},
|
||||||
|
| _ => match body.json_body {
|
||||||
|
| 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));
|
||||||
|
services
|
||||||
|
.uiaa
|
||||||
|
.create(sender_user, body.sender_device(), &uiaainfo, json);
|
||||||
|
|
||||||
|
Err(Error::Uiaa(uiaainfo))
|
||||||
|
},
|
||||||
|
| _ => Err!(Request(NotJson("JSON body is not valid"))),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user