Add 7 new management commands:

listadmins,
  listmoderators, grantmoderator, revokemoderator
  listusers, grantuser, revokeuser
This commit is contained in:
Mike Dilger 2025-02-11 10:32:44 +13:00
parent 61d26c0d85
commit cbd3d0fa19
No known key found for this signature in database
GPG Key ID: 47581A78D4329BA4
3 changed files with 119 additions and 33 deletions

View File

@ -904,3 +904,8 @@ pub fn is_moderator(pubkey: Pubkey) -> bool {
Ok(Some(moderator)) => moderator,
}
}
/// Is the pubkey an admin?
pub fn is_admin(pubkey: Pubkey) -> bool {
GLOBALS.config.read().admin_keys.contains(&pubkey)
}

View File

@ -5,15 +5,15 @@ use http::header::AUTHORIZATION;
use http_body_util::BodyExt;
use hyper::body::Incoming;
use hyper::Request;
use pocket_types::Event;
use pocket_types::{Event, Pubkey};
use secp256k1::hashes::{sha256, Hash};
use serde_json::Value;
fn s_err(s: &str) -> Result<Value, Error> {
fn s_err(s: &str) -> Result<(Pubkey, Value), Error> {
Err(ChorusError::ManagementAuthFailure(s.to_owned()).into())
}
pub async fn check_auth(request: Request<Incoming>) -> Result<Value, Error> {
pub async fn check_auth(request: Request<Incoming>) -> Result<(Pubkey, Value), Error> {
// Must be POST
if request.method() != hyper::Method::POST {
return s_err("Management RPC only supports POST method");
@ -112,5 +112,5 @@ pub async fn check_auth(request: Request<Incoming>) -> Result<Value, Error> {
return s_err("Authorization event payload missing");
}
Ok(serde_json::from_slice(&body)?)
Ok((event.pubkey(), serde_json::from_slice(&body)?))
}

View File

@ -32,8 +32,8 @@ pub async fn handle(
_peer: HashedPeer,
request: Request<Incoming>,
) -> Result<Response<BoxBody<Bytes, Error>>, Error> {
let command: Value = match auth::check_auth(request).await {
Ok(v) => v,
let (pubkey, command) = match auth::check_auth(request).await {
Ok((pk, v)) => (pk, v),
Err(e) => {
let result = json!({
"result": {},
@ -43,7 +43,7 @@ pub async fn handle(
}
};
match handle_inner(command) {
match handle_inner(pubkey, command) {
Ok(Some(value)) => respond(value, StatusCode::OK),
Ok(None) => {
let result = json!({
@ -80,7 +80,7 @@ pub async fn handle(
}
}
pub fn handle_inner(command: Value) -> Result<Option<Value>, Error> {
pub fn handle_inner(pubkey: Pubkey, command: Value) -> Result<Option<Value>, Error> {
let obj = match command.as_object() {
Some(o) => o,
None => return Err(ChorusError::BadRequest("Command was not a JSON object").into()),
@ -107,11 +107,16 @@ pub fn handle_inner(command: Value) -> Result<Option<Value>, Error> {
"listbannedpubkeys",
"supportedmethods",
"numconnections",
"uptime"
"uptime",
"listadmins",
"listmoderators",
"grantmoderator",
"revokemoderator",
"listusers",
"grantuser",
"revokeuser",
]
}))),
// Pubkeys
"banpubkey" => {
let pk = get_pubkey_param(obj)?;
crate::mark_pubkey_approval(GLOBALS.store.get().unwrap(), pk, false)?;
@ -154,7 +159,6 @@ pub fn handle_inner(command: Value) -> Result<Option<Value>, Error> {
"result": pubkeys
})))
}
// Events
"banevent" => {
let id = get_id_param(obj)?;
crate::mark_event_approval(GLOBALS.store.get().unwrap(), id, false)?;
@ -197,39 +201,116 @@ pub fn handle_inner(command: Value) -> Result<Option<Value>, Error> {
"result": ids
})))
}
"listeventsneedingmoderation" => Err(ChorusError::NotImplemented.into()),
// Kinds
"allowkind" => Err(ChorusError::NotImplemented.into()),
"disallowkind" => Err(ChorusError::NotImplemented.into()),
"listbannedkinds" => Err(ChorusError::NotImplemented.into()),
"listallowedkinds" => Err(ChorusError::NotImplemented.into()),
// IP addresses
"blockip" => Err(ChorusError::NotImplemented.into()),
"unblockip" => Err(ChorusError::NotImplemented.into()),
"listblockedips" => Err(ChorusError::NotImplemented.into()),
// Config
"changerelayname" => Err(ChorusError::NotImplemented.into()),
"changerelaydescription" => Err(ChorusError::NotImplemented.into()),
"changerelayicon" => Err(ChorusError::NotImplemented.into()),
// System
"numconnections" => {
let num = &GLOBALS.num_connections;
Ok(Some(json!({
"result": num,
})))
}
"uptime" => {
let uptime_in_secs = GLOBALS.start_time.elapsed().as_secs();
Ok(Some(json!({
"result": uptime_in_secs,
})))
}
"listadmins" => {
let keys = GLOBALS.config.read().admin_hex_keys.clone();
Ok(Some(json!({
"result": keys
})))
}
"listmoderators" => {
let moderators: Vec<String> =
crate::dump_authorized_users(GLOBALS.store.get().unwrap())?
.iter()
.filter_map(|(pk, moderator)| {
if *moderator {
Some(pk.as_hex_string())
} else {
None
}
})
.collect();
Ok(Some(json!({
"result": moderators
})))
}
"grantmoderator" => {
if !crate::is_admin(pubkey) {
Ok(Some(json!({
"result": {},
"error": "Unauthorized: Only admins can grant moderator status"
})))
} else {
let pk = get_pubkey_param(obj)?;
crate::add_authorized_user(GLOBALS.store.get().unwrap(), pk, true)?;
Ok(None)
}
}
"revokemoderator" => {
if !crate::is_admin(pubkey) {
Ok(Some(json!({
"result": {},
"error": "Unauthorized: Only admins can revoke moderator status"
})))
} else {
let pk = get_pubkey_param(obj)?;
// Do not do this if they aren't already an authorized user
if !crate::is_authorized_user(pk) {
Ok(None)
} else {
crate::add_authorized_user(GLOBALS.store.get().unwrap(), pk, false)?;
Ok(None)
}
}
}
"listusers" => {
let users: Vec<String> = crate::dump_authorized_users(GLOBALS.store.get().unwrap())?
.iter()
.map(|(pk, _moderator)| pk.as_hex_string())
.collect();
Ok(Some(json!({
"result": users
})))
}
"grantuser" => {
if !crate::is_admin(pubkey) {
Ok(Some(json!({
"result": {},
"error": "Unauthorized: Only admins can grant user status"
})))
} else {
let pk = get_pubkey_param(obj)?;
crate::add_authorized_user(GLOBALS.store.get().unwrap(), pk, false)?;
Ok(None)
}
}
"revokeuser" => {
if !crate::is_admin(pubkey) {
Ok(Some(json!({
"result": {},
"error": "Unauthorized: Only admins can revoke user status"
})))
} else {
let pk = get_pubkey_param(obj)?;
crate::rm_authorized_user(GLOBALS.store.get().unwrap(), pk)?;
Ok(None)
}
}
// Commands we do not support (yet)
"listeventsneedingmoderation" => Err(ChorusError::NotImplemented.into()),
"allowkind" => Err(ChorusError::NotImplemented.into()),
"disallowkind" => Err(ChorusError::NotImplemented.into()),
"listbannedkinds" => Err(ChorusError::NotImplemented.into()),
"listallowedkinds" => Err(ChorusError::NotImplemented.into()),
"blockip" => Err(ChorusError::NotImplemented.into()),
"unblockip" => Err(ChorusError::NotImplemented.into()),
"listblockedips" => Err(ChorusError::NotImplemented.into()),
"changerelayname" => Err(ChorusError::NotImplemented.into()),
"changerelaydescription" => Err(ChorusError::NotImplemented.into()),
"changerelayicon" => Err(ChorusError::NotImplemented.into()),
_ => Err(ChorusError::NotImplemented.into()),
}