implement MSC4133 only with MSC4175 for GET/PUT/DELETE
Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
parent
5ae9a5ff31
commit
f163ebf3bb
7 changed files with 217 additions and 10 deletions
|
@ -1,5 +1,5 @@
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduit::{pdu::PduBuilder, warn, Error, Result};
|
use conduit::{pdu::PduBuilder, warn, Err, Error, Result};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::{
|
api::{
|
||||||
client::{
|
client::{
|
||||||
|
@ -26,20 +26,25 @@ pub(crate) async fn set_displayname_route(
|
||||||
State(services): State<crate::State>, body: Ruma<set_display_name::v3::Request>,
|
State(services): State<crate::State>, body: Ruma<set_display_name::v3::Request>,
|
||||||
) -> Result<set_display_name::v3::Response> {
|
) -> Result<set_display_name::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
|
if *sender_user != body.user_id && body.appservice_info.is_none() {
|
||||||
|
return Err!(Request(Forbidden("You cannot update the profile of another user")));
|
||||||
|
}
|
||||||
|
|
||||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.rooms_joined(sender_user)
|
.rooms_joined(&body.user_id)
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
update_displayname(&services, sender_user.clone(), body.displayname.clone(), all_joined_rooms).await?;
|
update_displayname(&services, body.user_id.clone(), body.displayname.clone(), all_joined_rooms).await?;
|
||||||
|
|
||||||
if services.globals.allow_local_presence() {
|
if services.globals.allow_local_presence() {
|
||||||
// Presence update
|
// Presence update
|
||||||
services
|
services
|
||||||
.presence
|
.presence
|
||||||
.ping_presence(sender_user, &PresenceState::Online)?;
|
.ping_presence(&body.user_id, &PresenceState::Online)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(set_display_name::v3::Response {})
|
Ok(set_display_name::v3::Response {})
|
||||||
|
@ -110,16 +115,21 @@ pub(crate) async fn set_avatar_url_route(
|
||||||
State(services): State<crate::State>, body: Ruma<set_avatar_url::v3::Request>,
|
State(services): State<crate::State>, body: Ruma<set_avatar_url::v3::Request>,
|
||||||
) -> Result<set_avatar_url::v3::Response> {
|
) -> Result<set_avatar_url::v3::Response> {
|
||||||
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
|
if *sender_user != body.user_id && body.appservice_info.is_none() {
|
||||||
|
return Err!(Request(Forbidden("You cannot update the profile of another user")));
|
||||||
|
}
|
||||||
|
|
||||||
let all_joined_rooms: Vec<OwnedRoomId> = services
|
let all_joined_rooms: Vec<OwnedRoomId> = services
|
||||||
.rooms
|
.rooms
|
||||||
.state_cache
|
.state_cache
|
||||||
.rooms_joined(sender_user)
|
.rooms_joined(&body.user_id)
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
update_avatar_url(
|
update_avatar_url(
|
||||||
&services,
|
&services,
|
||||||
sender_user.clone(),
|
body.user_id.clone(),
|
||||||
body.avatar_url.clone(),
|
body.avatar_url.clone(),
|
||||||
body.blurhash.clone(),
|
body.blurhash.clone(),
|
||||||
all_joined_rooms,
|
all_joined_rooms,
|
||||||
|
@ -130,7 +140,7 @@ pub(crate) async fn set_avatar_url_route(
|
||||||
// Presence update
|
// Presence update
|
||||||
services
|
services
|
||||||
.presence
|
.presence
|
||||||
.ping_presence(sender_user, &PresenceState::Online)?;
|
.ping_presence(&body.user_id, &PresenceState::Online)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(set_avatar_url::v3::Response {})
|
Ok(set_avatar_url::v3::Response {})
|
||||||
|
@ -196,7 +206,7 @@ pub(crate) async fn get_avatar_url_route(
|
||||||
|
|
||||||
/// # `GET /_matrix/client/v3/profile/{userId}`
|
/// # `GET /_matrix/client/v3/profile/{userId}`
|
||||||
///
|
///
|
||||||
/// Returns the displayname, avatar_url and blurhash of the user.
|
/// Returns the displayname, avatar_url, blurhash, and tz of the user.
|
||||||
///
|
///
|
||||||
/// - If user is on another server and we do not have a local copy already,
|
/// - If user is on another server and we do not have a local copy already,
|
||||||
/// fetch profile over federation.
|
/// fetch profile over federation.
|
||||||
|
@ -232,11 +242,16 @@ pub(crate) async fn get_profile_route(
|
||||||
.users
|
.users
|
||||||
.set_blurhash(&body.user_id, response.blurhash.clone())
|
.set_blurhash(&body.user_id, response.blurhash.clone())
|
||||||
.await?;
|
.await?;
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.set_timezone(&body.user_id, response.tz.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
return Ok(get_profile::v3::Response {
|
return Ok(get_profile::v3::Response {
|
||||||
displayname: response.displayname,
|
displayname: response.displayname,
|
||||||
avatar_url: response.avatar_url,
|
avatar_url: response.avatar_url,
|
||||||
blurhash: response.blurhash,
|
blurhash: response.blurhash,
|
||||||
|
tz: response.tz,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -251,6 +266,7 @@ pub(crate) async fn get_profile_route(
|
||||||
avatar_url: services.users.avatar_url(&body.user_id)?,
|
avatar_url: services.users.avatar_url(&body.user_id)?,
|
||||||
blurhash: services.users.blurhash(&body.user_id)?,
|
blurhash: services.users.blurhash(&body.user_id)?,
|
||||||
displayname: services.users.displayname(&body.user_id)?,
|
displayname: services.users.displayname(&body.user_id)?,
|
||||||
|
tz: services.users.timezone(&body.user_id)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum_client_ip::InsecureClientIp;
|
use axum_client_ip::InsecureClientIp;
|
||||||
use conduit::warn;
|
use conduit::{warn, Err};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
api::client::{error::ErrorKind, membership::mutual_rooms, room::get_summary},
|
api::{
|
||||||
|
client::{
|
||||||
|
error::ErrorKind,
|
||||||
|
membership::mutual_rooms,
|
||||||
|
profile::{delete_timezone_key, get_timezone_key, set_timezone_key},
|
||||||
|
room::get_summary,
|
||||||
|
},
|
||||||
|
federation,
|
||||||
|
},
|
||||||
events::room::member::MembershipState,
|
events::room::member::MembershipState,
|
||||||
|
presence::PresenceState,
|
||||||
OwnedRoomId,
|
OwnedRoomId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -161,3 +170,118 @@ pub(crate) async fn get_room_summary(
|
||||||
.unwrap_or_else(|_e| None),
|
.unwrap_or_else(|_e| None),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # `DELETE /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz`
|
||||||
|
///
|
||||||
|
/// Deletes the `tz` (timezone) of a user, as per MSC4133 and MSC4175.
|
||||||
|
///
|
||||||
|
/// - Also makes sure other users receive the update using presence EDUs
|
||||||
|
pub(crate) async fn delete_timezone_key_route(
|
||||||
|
State(services): State<crate::State>, body: Ruma<delete_timezone_key::unstable::Request>,
|
||||||
|
) -> Result<delete_timezone_key::unstable::Response> {
|
||||||
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
|
if *sender_user != body.user_id && body.appservice_info.is_none() {
|
||||||
|
return Err!(Request(Forbidden("You cannot update the profile of another user")));
|
||||||
|
}
|
||||||
|
|
||||||
|
services.users.set_timezone(&body.user_id, None).await?;
|
||||||
|
|
||||||
|
if services.globals.allow_local_presence() {
|
||||||
|
// Presence update
|
||||||
|
services
|
||||||
|
.presence
|
||||||
|
.ping_presence(&body.user_id, &PresenceState::Online)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(delete_timezone_key::unstable::Response {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # `PUT /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz`
|
||||||
|
///
|
||||||
|
/// Updates the `tz` (timezone) of a user, as per MSC4133 and MSC4175.
|
||||||
|
///
|
||||||
|
/// - Also makes sure other users receive the update using presence EDUs
|
||||||
|
pub(crate) async fn set_timezone_key_route(
|
||||||
|
State(services): State<crate::State>, body: Ruma<set_timezone_key::unstable::Request>,
|
||||||
|
) -> Result<set_timezone_key::unstable::Response> {
|
||||||
|
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
|
||||||
|
|
||||||
|
if *sender_user != body.user_id && body.appservice_info.is_none() {
|
||||||
|
return Err!(Request(Forbidden("You cannot update the profile of another user")));
|
||||||
|
}
|
||||||
|
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.set_timezone(&body.user_id, body.tz.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if services.globals.allow_local_presence() {
|
||||||
|
// Presence update
|
||||||
|
services
|
||||||
|
.presence
|
||||||
|
.ping_presence(&body.user_id, &PresenceState::Online)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(set_timezone_key::unstable::Response {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # `GET /_matrix/client/unstable/uk.tcpip.msc4133/profile/:user_id/us.cloke.msc4175.tz`
|
||||||
|
///
|
||||||
|
/// Returns the `timezone` of the user as per MSC4133 and MSC4175.
|
||||||
|
///
|
||||||
|
/// - If user is on another server and we do not have a local copy already fetch
|
||||||
|
/// `timezone` over federation
|
||||||
|
pub(crate) async fn get_timezone_key_route(
|
||||||
|
State(services): State<crate::State>, body: Ruma<get_timezone_key::unstable::Request>,
|
||||||
|
) -> Result<get_timezone_key::unstable::Response> {
|
||||||
|
if !services.globals.user_is_local(&body.user_id) {
|
||||||
|
// Create and update our local copy of the user
|
||||||
|
if let Ok(response) = services
|
||||||
|
.sending
|
||||||
|
.send_federation_request(
|
||||||
|
body.user_id.server_name(),
|
||||||
|
federation::query::get_profile_information::v1::Request {
|
||||||
|
user_id: body.user_id.clone(),
|
||||||
|
field: None, // we want the full user's profile to update locally as well
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
if !services.users.exists(&body.user_id)? {
|
||||||
|
services.users.create(&body.user_id, None)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.set_displayname(&body.user_id, response.displayname.clone())
|
||||||
|
.await?;
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.set_avatar_url(&body.user_id, response.avatar_url.clone())
|
||||||
|
.await?;
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.set_blurhash(&body.user_id, response.blurhash.clone())
|
||||||
|
.await?;
|
||||||
|
services
|
||||||
|
.users
|
||||||
|
.set_timezone(&body.user_id, response.tz.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
return Ok(get_timezone_key::unstable::Response {
|
||||||
|
tz: response.tz,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !services.users.exists(&body.user_id)? {
|
||||||
|
// Return 404 if this user doesn't exist and we couldn't fetch it over
|
||||||
|
// federation
|
||||||
|
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(get_timezone_key::unstable::Response {
|
||||||
|
tz: services.users.timezone(&body.user_id)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,9 @@ use crate::{client, server};
|
||||||
pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||||
let config = &server.config;
|
let config = &server.config;
|
||||||
let mut router = router
|
let mut router = router
|
||||||
|
.ruma_route(client::get_timezone_key_route)
|
||||||
|
.ruma_route(client::set_timezone_key_route)
|
||||||
|
.ruma_route(client::delete_timezone_key_route)
|
||||||
.ruma_route(client::appservice_ping)
|
.ruma_route(client::appservice_ping)
|
||||||
.ruma_route(client::get_supported_versions_route)
|
.ruma_route(client::get_supported_versions_route)
|
||||||
.ruma_route(client::get_register_available_route)
|
.ruma_route(client::get_register_available_route)
|
||||||
|
|
|
@ -75,6 +75,7 @@ pub(crate) async fn get_profile_information_route(
|
||||||
let mut displayname = None;
|
let mut displayname = None;
|
||||||
let mut avatar_url = None;
|
let mut avatar_url = None;
|
||||||
let mut blurhash = None;
|
let mut blurhash = None;
|
||||||
|
let mut tz = None;
|
||||||
|
|
||||||
match &body.field {
|
match &body.field {
|
||||||
Some(ProfileField::DisplayName) => {
|
Some(ProfileField::DisplayName) => {
|
||||||
|
@ -90,6 +91,7 @@ pub(crate) async fn get_profile_information_route(
|
||||||
displayname = services.users.displayname(&body.user_id)?;
|
displayname = services.users.displayname(&body.user_id)?;
|
||||||
avatar_url = services.users.avatar_url(&body.user_id)?;
|
avatar_url = services.users.avatar_url(&body.user_id)?;
|
||||||
blurhash = services.users.blurhash(&body.user_id)?;
|
blurhash = services.users.blurhash(&body.user_id)?;
|
||||||
|
tz = services.users.timezone(&body.user_id)?;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,5 +99,6 @@ pub(crate) async fn get_profile_information_route(
|
||||||
displayname,
|
displayname,
|
||||||
avatar_url,
|
avatar_url,
|
||||||
blurhash,
|
blurhash,
|
||||||
|
tz,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,7 @@ pub const MAPS: &[&str] = &[
|
||||||
"userid_presenceid",
|
"userid_presenceid",
|
||||||
"userid_selfsigningkeyid",
|
"userid_selfsigningkeyid",
|
||||||
"userid_usersigningkeyid",
|
"userid_usersigningkeyid",
|
||||||
|
"useridprofilekey_value",
|
||||||
"openidtoken_expiresatuserid",
|
"openidtoken_expiresatuserid",
|
||||||
"userroomid_highlightcount",
|
"userroomid_highlightcount",
|
||||||
"userroomid_invitestate",
|
"userroomid_invitestate",
|
||||||
|
|
|
@ -32,6 +32,7 @@ pub struct Data {
|
||||||
userid_password: Arc<Map>,
|
userid_password: Arc<Map>,
|
||||||
userid_selfsigningkeyid: Arc<Map>,
|
userid_selfsigningkeyid: Arc<Map>,
|
||||||
userid_usersigningkeyid: Arc<Map>,
|
userid_usersigningkeyid: Arc<Map>,
|
||||||
|
useridprofilekey_value: Arc<Map>,
|
||||||
services: Services,
|
services: Services,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +65,7 @@ impl Data {
|
||||||
userid_password: db["userid_password"].clone(),
|
userid_password: db["userid_password"].clone(),
|
||||||
userid_selfsigningkeyid: db["userid_selfsigningkeyid"].clone(),
|
userid_selfsigningkeyid: db["userid_selfsigningkeyid"].clone(),
|
||||||
userid_usersigningkeyid: db["userid_usersigningkeyid"].clone(),
|
userid_usersigningkeyid: db["userid_usersigningkeyid"].clone(),
|
||||||
|
useridprofilekey_value: db["useridprofilekey_value"].clone(),
|
||||||
services: Services {
|
services: Services {
|
||||||
server: args.server.clone(),
|
server: args.server.clone(),
|
||||||
globals: args.depend::<globals::Service>("globals"),
|
globals: args.depend::<globals::Service>("globals"),
|
||||||
|
@ -231,6 +233,57 @@ impl Data {
|
||||||
.transpose()
|
.transpose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the timezone of a user.
|
||||||
|
pub(super) fn timezone(&self, user_id: &UserId) -> Result<Option<String>> {
|
||||||
|
// first check the unstable prefix
|
||||||
|
let mut key = user_id.as_bytes().to_vec();
|
||||||
|
key.push(0xFF);
|
||||||
|
key.extend_from_slice(b"us.cloke.msc4175.tz");
|
||||||
|
|
||||||
|
let value = self
|
||||||
|
.useridprofilekey_value
|
||||||
|
.get(&key)?
|
||||||
|
.map(|bytes| utils::string_from_bytes(&bytes).map_err(|e| err!(Database("Timezone in db is invalid. {e}"))))
|
||||||
|
.transpose()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// TODO: transparently migrate unstable key usage to the stable key once MSC4133
|
||||||
|
// and MSC4175 are stable, likely a remove/insert in this block
|
||||||
|
if value.is_none() || value.as_ref().is_some_and(String::is_empty) {
|
||||||
|
// check the stable prefix
|
||||||
|
let mut key = user_id.as_bytes().to_vec();
|
||||||
|
key.push(0xFF);
|
||||||
|
key.extend_from_slice(b"m.tz");
|
||||||
|
|
||||||
|
return self
|
||||||
|
.useridprofilekey_value
|
||||||
|
.get(&key)?
|
||||||
|
.map(|bytes| {
|
||||||
|
utils::string_from_bytes(&bytes).map_err(|e| err!(Database("Timezone in db is invalid. {e}")))
|
||||||
|
})
|
||||||
|
.transpose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets a new timezone or removes it if timezone is None.
|
||||||
|
pub(super) fn set_timezone(&self, user_id: &UserId, timezone: Option<String>) -> Result<()> {
|
||||||
|
let mut key = user_id.as_bytes().to_vec();
|
||||||
|
key.push(0xFF);
|
||||||
|
key.extend_from_slice(b"us.cloke.msc4175.tz");
|
||||||
|
|
||||||
|
// TODO: insert to the stable MSC4175 key when it's stable
|
||||||
|
if let Some(timezone) = timezone {
|
||||||
|
self.useridprofilekey_value
|
||||||
|
.insert(&key, timezone.as_bytes())?;
|
||||||
|
} else {
|
||||||
|
self.useridprofilekey_value.remove(&key)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets a new avatar_url or removes it if avatar_url is None.
|
/// Sets a new avatar_url or removes it if avatar_url is None.
|
||||||
pub(super) fn set_blurhash(&self, user_id: &UserId, blurhash: Option<String>) -> Result<()> {
|
pub(super) fn set_blurhash(&self, user_id: &UserId, blurhash: Option<String>) -> Result<()> {
|
||||||
if let Some(blurhash) = blurhash {
|
if let Some(blurhash) = blurhash {
|
||||||
|
|
|
@ -327,6 +327,13 @@ impl Service {
|
||||||
/// Get the blurhash of a user.
|
/// Get the blurhash of a user.
|
||||||
pub fn blurhash(&self, user_id: &UserId) -> Result<Option<String>> { self.db.blurhash(user_id) }
|
pub fn blurhash(&self, user_id: &UserId) -> Result<Option<String>> { self.db.blurhash(user_id) }
|
||||||
|
|
||||||
|
pub fn timezone(&self, user_id: &UserId) -> Result<Option<String>> { self.db.timezone(user_id) }
|
||||||
|
|
||||||
|
/// Sets a new tz or removes it if tz is None.
|
||||||
|
pub async fn set_timezone(&self, user_id: &UserId, tz: Option<String>) -> Result<()> {
|
||||||
|
self.db.set_timezone(user_id, tz)
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets a new blurhash or removes it if blurhash is None.
|
/// Sets a new blurhash or removes it if blurhash is None.
|
||||||
pub async fn set_blurhash(&self, user_id: &UserId, blurhash: Option<String>) -> Result<()> {
|
pub async fn set_blurhash(&self, user_id: &UserId, blurhash: Option<String>) -> Result<()> {
|
||||||
self.db.set_blurhash(user_id, blurhash)
|
self.db.set_blurhash(user_id, blurhash)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue