2024-06-14 22:08:44 +00:00
use std ::collections ::BTreeMap ;
2024-06-10 06:02:17 +00:00
2025-07-08 11:48:45 +00:00
use futures ::FutureExt ;
2024-06-10 06:02:17 +00:00
use ruma ::{
2025-02-23 01:17:45 -05:00
RoomId , UserId ,
2024-06-10 06:02:17 +00:00
events ::{
2025-03-02 23:16:30 -05:00
RoomAccountDataEventType , StateEventType ,
2024-06-10 06:02:17 +00:00
room ::{
member ::{ MembershipState , RoomMemberEventContent } ,
message ::RoomMessageEventContent ,
power_levels ::RoomPowerLevelsEventContent ,
} ,
2024-08-28 07:05:13 +00:00
tag ::{ TagEvent , TagEventContent , TagInfo } ,
2024-06-10 06:02:17 +00:00
} ,
} ;
2025-04-22 01:41:02 +00:00
use tuwunel_core ::{
Err , Result , debug_info , debug_warn , error , implement , matrix ::pdu ::PduBuilder ,
} ;
2024-06-10 06:02:17 +00:00
2025-05-14 03:04:11 +00:00
/// Invite the user to the tuwunel admin room.
2024-08-08 17:18:30 +00:00
///
2024-12-14 21:58:01 -05:00
/// This is equivalent to granting server admin privileges.
2024-08-08 17:18:30 +00:00
#[ implement(super::Service) ]
2025-03-02 23:16:30 -05:00
pub async fn make_user_admin ( & self , user_id : & UserId ) -> Result {
2024-08-08 17:18:30 +00:00
let Ok ( room_id ) = self . get_admin_room ( ) . await else {
2025-03-02 23:16:30 -05:00
debug_warn! (
" make_user_admin was called without an admin room being available or created "
) ;
2024-08-08 17:18:30 +00:00
return Ok ( ( ) ) ;
} ;
2024-07-20 23:38:20 +00:00
2024-08-08 17:18:30 +00:00
let state_lock = self . services . state . mutex . lock ( & room_id ) . await ;
2024-06-10 06:02:17 +00:00
2025-04-22 04:42:26 +00:00
if self
. services
. state_cache
. is_joined ( user_id , & room_id )
. await
{
2025-03-02 23:16:30 -05:00
return Err ! ( debug_warn! ( " User is already joined in the admin room " ) ) ;
}
2025-07-08 12:27:29 +00:00
2025-03-02 23:16:30 -05:00
if self
. services
. state_cache
. is_invited ( user_id , & room_id )
. await
{
return Err ! ( debug_warn! ( " User is already pending an invitation to the admin room " ) ) ;
}
2024-08-08 17:18:30 +00:00
// Use the server user to grant the new admin's power level
2025-03-02 23:16:30 -05:00
let server_user = self . services . globals . server_user . as_ref ( ) ;
2024-06-10 06:02:17 +00:00
2025-03-02 23:16:30 -05:00
// if this is our local user, just forcefully join them in the room. otherwise,
// invite the remote user.
if self . services . globals . user_is_local ( user_id ) {
debug_info! ( " Inviting local user {user_id} to admin room {room_id} " ) ;
self . services
. timeline
. build_and_append_pdu (
PduBuilder ::state (
String ::from ( user_id ) ,
& RoomMemberEventContent ::new ( MembershipState ::Invite ) ,
) ,
server_user ,
& room_id ,
& state_lock ,
)
. await ? ;
debug_info! ( " Force joining local user {user_id} to admin room {room_id} " ) ;
self . services
. timeline
. build_and_append_pdu (
PduBuilder ::state (
String ::from ( user_id ) ,
& RoomMemberEventContent ::new ( MembershipState ::Join ) ,
) ,
user_id ,
& room_id ,
& state_lock ,
)
. await ? ;
} else {
debug_info! ( " Inviting remote user {user_id} to admin room {room_id} " ) ;
self . services
. timeline
. build_and_append_pdu (
PduBuilder ::state (
user_id . to_string ( ) ,
& RoomMemberEventContent ::new ( MembershipState ::Invite ) ,
) ,
server_user ,
& room_id ,
& state_lock ,
)
. await ? ;
}
// Set power levels
let mut room_power_levels = self
. services
. state_accessor
. room_state_get_content ::< RoomPowerLevelsEventContent > (
2024-08-08 17:18:30 +00:00
& room_id ,
2025-03-02 23:16:30 -05:00
& StateEventType ::RoomPowerLevels ,
" " ,
2024-08-08 17:18:30 +00:00
)
2025-03-02 23:16:30 -05:00
. await
. unwrap_or_default ( ) ;
2024-06-10 06:02:17 +00:00
2025-03-02 23:16:30 -05:00
room_power_levels
. users
. insert ( server_user . into ( ) , 69420. into ( ) ) ;
2025-04-22 04:42:26 +00:00
room_power_levels
. users
. insert ( user_id . into ( ) , 100. into ( ) ) ;
2024-06-10 06:02:17 +00:00
2024-08-08 17:18:30 +00:00
self . services
. timeline
. build_and_append_pdu (
2025-03-02 23:16:30 -05:00
PduBuilder ::state ( String ::new ( ) , & room_power_levels ) ,
2024-08-08 17:18:30 +00:00
server_user ,
& room_id ,
& state_lock ,
)
. await ? ;
2024-06-10 06:02:17 +00:00
2024-08-08 17:18:30 +00:00
// Set room tag
2025-04-22 04:42:26 +00:00
let room_tag = self
. services
. server
. config
. admin_room_tag
. as_str ( ) ;
2025-07-08 12:27:29 +00:00
2024-08-08 17:18:30 +00:00
if ! room_tag . is_empty ( ) {
2025-04-22 04:42:26 +00:00
if let Err ( e ) = self
. set_room_tag ( & room_id , user_id , room_tag )
. await
{
2025-03-02 23:16:30 -05:00
error! ( ? room_id , ? user_id , ? room_tag , " Failed to set tag for admin grant: {e} " ) ;
2024-08-28 07:05:13 +00:00
}
2024-08-08 17:18:30 +00:00
}
2024-08-28 07:05:13 +00:00
2024-11-20 20:23:13 -05:00
if self . services . server . config . admin_room_notices {
2025-03-02 23:16:30 -05:00
let welcome_message = String ::from (
2025-05-14 03:04:11 +00:00
" ## Thank you for trying out tuwunel! \n \n Tuwunel is a continuation of conduwuit which was technically a hard fork of Conduit. \n \n Helpful links: \n > GitHub Repo: https://github.com/matrix-construct/tuwunel \n > Documentation: https://github.com/matrix-construct/tuwunel \n > Report issues: https://github.com/matri-construct/tuwunel/issues \n \n For a list of available commands, send the following message in this room: `!admin --help` "
2025-03-02 23:16:30 -05:00
) ;
2024-11-20 20:23:13 -05:00
// Send welcome message
self . services
. timeline
. build_and_append_pdu (
PduBuilder ::timeline ( & RoomMessageEventContent ::text_markdown ( welcome_message ) ) ,
server_user ,
& room_id ,
& state_lock ,
)
. await ? ;
}
2024-06-10 06:02:17 +00:00
2024-08-08 17:18:30 +00:00
Ok ( ( ) )
2024-06-10 06:02:17 +00:00
}
2024-08-28 07:05:13 +00:00
#[ implement(super::Service) ]
2025-03-02 23:16:30 -05:00
async fn set_room_tag ( & self , room_id : & RoomId , user_id : & UserId , tag : & str ) -> Result {
2024-08-28 07:05:13 +00:00
let mut event = self
. services
. account_data
2024-10-02 07:57:18 +00:00
. get_room ( room_id , user_id , RoomAccountDataEventType ::Tag )
2024-08-08 17:18:30 +00:00
. await
. unwrap_or_else ( | _ | TagEvent {
2024-12-15 00:05:47 -05:00
content : TagEventContent { tags : BTreeMap ::new ( ) } ,
2024-08-28 07:05:13 +00:00
} ) ;
event
. content
. tags
. insert ( tag . to_owned ( ) . into ( ) , TagInfo ::new ( ) ) ;
2024-08-08 17:18:30 +00:00
self . services
. account_data
. update (
Some ( room_id ) ,
user_id ,
RoomAccountDataEventType ::Tag ,
& serde_json ::to_value ( event ) ? ,
)
2025-03-02 23:16:30 -05:00
. await
2024-08-28 07:05:13 +00:00
}
2025-05-14 00:33:31 +00:00
/// Demote an admin, removing its rights.
#[ implement(super::Service) ]
pub async fn revoke_admin ( & self , user_id : & UserId ) -> Result {
use MembershipState ::{ Invite , Join , Knock , Leave } ;
let Ok ( room_id ) = self . get_admin_room ( ) . await else {
return Err ! ( error! ( " No admin room available or created. " ) ) ;
} ;
let state_lock = self . services . state . mutex . lock ( & room_id ) . await ;
let event = match self
. services
. state_accessor
. get_member ( & room_id , user_id )
. await
{
| Err ( e ) if e . is_not_found ( ) = > return Err ! ( " {user_id} was never an admin. " ) ,
| Err ( e ) = > return Err ! ( error! ( ? e , " Failure occurred while attempting revoke. " ) ) ,
| Ok ( event ) if ! matches! ( event . membership , Invite | Knock | Join ) = >
return Err ! ( " Cannot revoke {user_id} in membership state {:?}. " , event . membership ) ,
| Ok ( event ) = > {
assert! (
matches! ( event . membership , Invite | Knock | Join ) ,
" Incorrect membership state to remove user. "
) ;
event
} ,
} ;
self . services
. timeline
. build_and_append_pdu (
PduBuilder ::state ( user_id . to_string ( ) , & RoomMemberEventContent {
membership : Leave ,
reason : Some ( " Admin Revoked " . into ( ) ) ,
is_direct : None ,
join_authorized_via_users_server : None ,
third_party_invite : None ,
.. event
} ) ,
self . services . globals . server_user . as_ref ( ) ,
& room_id ,
& state_lock ,
)
2025-07-08 11:48:45 +00:00
. boxed ( )
2025-05-14 00:33:31 +00:00
. await
. map ( | _ | ( ) )
}