Added some user message handlers
Handlers now exist for user creation, auth, state messages, and post messages. Still need to do group permissions and messages
This commit is contained in:
Generated
-1
@@ -543,7 +543,6 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "fedichat"
|
||||
version = "0.1.0"
|
||||
source = "git+https://git.firechicken.net/fedichat/fedichat-lib#a3f54705495d9aa54ffe80501e1f68876e3e9480"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
|
||||
@@ -8,6 +8,10 @@ media_directory = "/srv/confetti/media"
|
||||
statefile = "./confetti.state"
|
||||
loglevel = "debug"
|
||||
max_message_len_kb = 10000
|
||||
# List of users with server-level permissions that are allowed to change anything in
|
||||
# any room
|
||||
# admins = ["alice"]
|
||||
admins = []
|
||||
# Optional
|
||||
# account_creation_code = "password1"
|
||||
|
||||
|
||||
@@ -10,5 +10,6 @@ CREATE TABLE messages (
|
||||
signature TEXT NOT NULL,
|
||||
client_timestamp BIGINT NOT NULL,
|
||||
server_timestamp BIGINT NOT NULL,
|
||||
username user_t NOT NULL
|
||||
username user_t NOT NULL,
|
||||
edited BOOLEAN DEFAULT false
|
||||
)
|
||||
|
||||
+147
-15
@@ -1,17 +1,18 @@
|
||||
use fedichat::message::{Relevance,TaggedMessage};
|
||||
use fedichat::client::{ClientMessage,SignedClientMessage,ServerMessage,ServerError};
|
||||
use fedichat::state::{StatePermissionKey,StatePermissionValue};
|
||||
|
||||
use diesel_async::AsyncPgConnection;
|
||||
use diesel_async::pooled_connection::deadpool::{Pool,Object};
|
||||
use rmp_serde;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{HashMap,HashSet};
|
||||
use std::sync::{Arc};
|
||||
use thiserror::Error;
|
||||
use tokio::sync::{broadcast,mpsc,RwLock};
|
||||
use tokio::select;
|
||||
use crate::Coordinate;
|
||||
use crate::db;
|
||||
use crate::state::State;
|
||||
use crate::state::{self,State};
|
||||
|
||||
use tracing::{warn,error,debug};
|
||||
|
||||
@@ -219,13 +220,14 @@ impl Client {
|
||||
|
||||
};
|
||||
use fedichat::client::ClientMessage::*;
|
||||
let my_addr = fedichat::ServerAddr(config.hostname.clone());
|
||||
// Forward the message if it is addressed to a remote server
|
||||
if let Some(servername) = message.target.clone()
|
||||
&& servername != fedichat::ServerAddr(config.hostname.clone()) {
|
||||
|
||||
if message.message.is_forwardable() {
|
||||
let user = self.get_user(config)?;
|
||||
self.forward(&servername,message.tag(user,fedichat::ServerAddr(config.hostname.clone()))).await
|
||||
self.forward(&servername,message.tag(user,my_addr)).await
|
||||
} else {
|
||||
Err(ServerError::MessageNotForwardable)
|
||||
}
|
||||
@@ -264,9 +266,18 @@ impl Client {
|
||||
},
|
||||
// Private message/invite mechanism
|
||||
MessagePost {
|
||||
body,
|
||||
user,
|
||||
} => {unimplemented!()},
|
||||
// This just gets sent out ephemerally as a subscribed message
|
||||
body: ref _body,
|
||||
user: ref _other_user,
|
||||
} => {
|
||||
let tagged = message.tag(user,my_addr);
|
||||
match self.message_send.send(tagged) {
|
||||
Ok(_) => Ok(ServerMessage::Ok),
|
||||
// Note: we are a receiver so there should always be at least one
|
||||
// This error should never happen
|
||||
Err(_e) => Err(ServerError::Generic)
|
||||
}
|
||||
},
|
||||
// Replace the body of the message with a new one
|
||||
MessageEdit {
|
||||
body,
|
||||
@@ -278,49 +289,93 @@ impl Client {
|
||||
room_id,
|
||||
} => {unimplemented!()},
|
||||
// State Actions
|
||||
// These don't use the DB at all
|
||||
StateCreate {
|
||||
room_id,
|
||||
path,
|
||||
content,
|
||||
permissions,
|
||||
} => {unimplemented!()},
|
||||
} => {
|
||||
let mut permissions = permissions
|
||||
.unwrap_or(fedichat::state::PermissionTable(HashMap::new()));
|
||||
permissions.0.insert(StatePermissionKey::User(user.clone()),StatePermissionValue::Owner);
|
||||
let operation = state::Operation::Create(content,permissions);
|
||||
let operation = state::StateOperation::new(room_id.clone().into(),path,operation);
|
||||
self.run_operation(config,&room_id.into(),user,operation).await
|
||||
},
|
||||
StateWrite {
|
||||
room_id,
|
||||
path,
|
||||
content,
|
||||
} => {unimplemented!()},
|
||||
} => {
|
||||
let operation = state::Operation::Write(content);
|
||||
let operation = state::StateOperation::new(room_id.clone().into(),path,operation);
|
||||
self.run_operation(config,&room_id.into(),user,operation).await
|
||||
},
|
||||
StateDelete {
|
||||
room_id,
|
||||
path,
|
||||
} => {unimplemented!()},
|
||||
} => {
|
||||
let operation = state::Operation::Delete;
|
||||
let operation = state::StateOperation::new(room_id.clone().into(),path,operation);
|
||||
self.run_operation(config,&room_id.into(),user,operation).await
|
||||
},
|
||||
StateAppend {
|
||||
room_id,
|
||||
path,
|
||||
content,
|
||||
} => {unimplemented!()},
|
||||
} => {
|
||||
let operation = state::Operation::Append(content);
|
||||
let operation = state::StateOperation::new(room_id.clone().into(),path,operation);
|
||||
self.run_operation(config,&room_id.into(),user,operation).await
|
||||
},
|
||||
StateMove {
|
||||
room_id,
|
||||
path,
|
||||
target,
|
||||
} => {unimplemented!()},
|
||||
} => {
|
||||
// This could be a read and a write maybe?
|
||||
unimplemented!()
|
||||
},
|
||||
StateRead {
|
||||
room_id,
|
||||
path,
|
||||
} => {unimplemented!()},
|
||||
} => {
|
||||
let operation = state::Operation::Read;
|
||||
let operation = state::StateOperation::new(room_id.clone().into(),path,operation);
|
||||
self.run_operation(config,&room_id.into(),user,operation).await
|
||||
},
|
||||
PermissionAdd {
|
||||
permission,
|
||||
path,
|
||||
room_id
|
||||
} => {unimplemented!()},
|
||||
} => {
|
||||
let operation = state::Operation::PermAdd(permission);
|
||||
let operation = state::StateOperation::new(room_id.clone().into(),path,operation);
|
||||
self.run_operation(config,&room_id.into(),user,operation).await
|
||||
},
|
||||
PermissionRead {
|
||||
path,
|
||||
room_id
|
||||
} => {unimplemented!()},
|
||||
} => {
|
||||
let operation = state::Operation::PermRead;
|
||||
let operation = state::StateOperation::new(room_id.clone().into(),path.clone(),operation);
|
||||
match self.run_partial_operation(config,&room_id.into(),user,operation).await? {
|
||||
Some(val) => Ok(ServerMessage::StatePermission(path,val.get_perms().clone())),
|
||||
// Should never occur
|
||||
None => Err(ServerError::Generic)
|
||||
|
||||
}
|
||||
},
|
||||
PermissionDelete {
|
||||
permission,
|
||||
path,
|
||||
room_id
|
||||
} => {unimplemented!()},
|
||||
} => {
|
||||
let operation = state::Operation::PermDel(permission);
|
||||
let operation = state::StateOperation::new(room_id.clone().into(),path,operation);
|
||||
self.run_operation(config,&room_id.into(),user,operation).await
|
||||
},
|
||||
// Groups really should have a way to add permissions by user
|
||||
// specifically for who can join or invite others
|
||||
//
|
||||
@@ -462,6 +517,52 @@ impl Client {
|
||||
return Ok(ServerMessage::Error(ServerError::NotAuthenticated));
|
||||
}
|
||||
|
||||
}
|
||||
async fn unlock_state(
|
||||
&self,
|
||||
config: &crate::config::Config,
|
||||
locks: &[HashSet<fedichat::state::StatePermissionKey>],
|
||||
coord: &Coordinate,
|
||||
user: fedichat::User
|
||||
) -> Result<Vec<fedichat::state::StatePermissionKey>,ServerError>
|
||||
{
|
||||
let mut allowed = HashSet::new();
|
||||
allowed.insert(StatePermissionKey::Everyone);
|
||||
allowed.insert(StatePermissionKey::User(user.clone()));
|
||||
if user.server == config.hostname && config.admins.contains(&user.name) {
|
||||
allowed.insert(StatePermissionKey::Server);
|
||||
}
|
||||
if self.statehandle.read().await.get_operators(coord).await?.0.contains_key(&StatePermissionKey::User(user)) {
|
||||
allowed.insert(StatePermissionKey::Operator);
|
||||
}
|
||||
|
||||
let mut keys = Vec::with_capacity(locks.len());
|
||||
// Operators and server admins can do whatever they want. This might need to be
|
||||
// an optional sudo-like feature as it makes it very easy to accidentally delete
|
||||
// important, privileged nodes
|
||||
if allowed.contains(&StatePermissionKey::Server) {
|
||||
for _ in locks.iter() {
|
||||
keys.push(StatePermissionKey::Server);
|
||||
}
|
||||
} else if allowed.contains(&StatePermissionKey::Operator) {
|
||||
for _ in locks.iter() {
|
||||
keys.push(StatePermissionKey::Operator);
|
||||
}
|
||||
} else {
|
||||
for lock in locks {
|
||||
match allowed.intersection(&lock).next() {
|
||||
Some(key) => keys.push(key.clone()),
|
||||
// Try to resolve groups to see if we have any relevant permissions
|
||||
// TODO, just fail for now if we would need a group perm
|
||||
None => {
|
||||
return Err(ServerError::MissingPermission)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return Ok(keys);
|
||||
|
||||
}
|
||||
fn get_user(&self,config: &crate::config::Config) -> Result<fedichat::User,ServerError> {
|
||||
Ok(fedichat::User {
|
||||
@@ -469,6 +570,37 @@ impl Client {
|
||||
server: config.hostname.clone()
|
||||
})
|
||||
}
|
||||
async fn run_partial_operation(
|
||||
&self,
|
||||
config: &crate::config::Config,
|
||||
coord: &Coordinate,
|
||||
user: fedichat::User,
|
||||
operation: state::StateOperation
|
||||
) -> Result<Option<state::StateNode>,ServerError>
|
||||
{
|
||||
let unlockable = self.statehandle.read().await.to_unlockable_operation(operation).await?;
|
||||
let locks = unlockable.get_locks();
|
||||
let room_id = coord.clone().into();
|
||||
|
||||
let keys = self.unlock_state(config,locks,&room_id,user).await?;
|
||||
let unlocked = unlockable.unlock(keys)?;
|
||||
Ok(self.statehandle.write().await.execute_operation(unlocked).await?)
|
||||
|
||||
}
|
||||
async fn run_operation(
|
||||
&self,
|
||||
config: &crate::config::Config,
|
||||
coord: &Coordinate,
|
||||
user: fedichat::User,
|
||||
operation: state::StateOperation
|
||||
) -> Result<ServerMessage,ServerError>
|
||||
{
|
||||
let path = operation.path.clone();
|
||||
match self.run_partial_operation(config,coord,user,operation).await? {
|
||||
Some(val) => Ok(ServerMessage::State(path,val.to_state_value())),
|
||||
None => Ok(ServerMessage::Ok)
|
||||
}
|
||||
}
|
||||
async fn get_db(&self) -> Result<Object<AsyncPgConnection>,ServerError> {
|
||||
match self.db_handle.get().await {
|
||||
Ok(conn) => Ok(conn),
|
||||
|
||||
+3
-1
@@ -20,7 +20,9 @@ pub struct Config {
|
||||
pub loglevel: Option<String>,
|
||||
pub media_directory: String,
|
||||
pub max_message_len_kb: usize,
|
||||
pub account_creation_code: Option<String>
|
||||
pub account_creation_code: Option<String>,
|
||||
// Local users that get server-level permissions
|
||||
pub admins: Vec<String>
|
||||
}
|
||||
#[derive(Clone,Serialize,Deserialize)]
|
||||
pub struct DBConfig {
|
||||
|
||||
@@ -35,6 +35,7 @@ diesel::table! {
|
||||
client_timestamp -> Int8,
|
||||
server_timestamp -> Int8,
|
||||
username -> UserT,
|
||||
edited -> Nullable<Bool>,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -780,7 +780,7 @@ impl StateNode {
|
||||
} => perms
|
||||
}
|
||||
}
|
||||
fn to_state_value(&self) -> fedichat::state::StateValue {
|
||||
pub fn to_state_value(&self) -> fedichat::state::StateValue {
|
||||
match self {
|
||||
// Turn directory into a `ls`-like listing
|
||||
StateNode::Directory { nodes, perms: _perms } => {
|
||||
|
||||
Reference in New Issue
Block a user