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]]
|
[[package]]
|
||||||
name = "fedichat"
|
name = "fedichat"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://git.firechicken.net/fedichat/fedichat-lib#a3f54705495d9aa54ffe80501e1f68876e3e9480"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_bytes",
|
"serde_bytes",
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ media_directory = "/srv/confetti/media"
|
|||||||
statefile = "./confetti.state"
|
statefile = "./confetti.state"
|
||||||
loglevel = "debug"
|
loglevel = "debug"
|
||||||
max_message_len_kb = 10000
|
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
|
# Optional
|
||||||
# account_creation_code = "password1"
|
# account_creation_code = "password1"
|
||||||
|
|
||||||
|
|||||||
@@ -10,5 +10,6 @@ CREATE TABLE messages (
|
|||||||
signature TEXT NOT NULL,
|
signature TEXT NOT NULL,
|
||||||
client_timestamp BIGINT NOT NULL,
|
client_timestamp BIGINT NOT NULL,
|
||||||
server_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::message::{Relevance,TaggedMessage};
|
||||||
use fedichat::client::{ClientMessage,SignedClientMessage,ServerMessage,ServerError};
|
use fedichat::client::{ClientMessage,SignedClientMessage,ServerMessage,ServerError};
|
||||||
|
use fedichat::state::{StatePermissionKey,StatePermissionValue};
|
||||||
|
|
||||||
use diesel_async::AsyncPgConnection;
|
use diesel_async::AsyncPgConnection;
|
||||||
use diesel_async::pooled_connection::deadpool::{Pool,Object};
|
use diesel_async::pooled_connection::deadpool::{Pool,Object};
|
||||||
use rmp_serde;
|
use rmp_serde;
|
||||||
use std::collections::HashSet;
|
use std::collections::{HashMap,HashSet};
|
||||||
use std::sync::{Arc};
|
use std::sync::{Arc};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::sync::{broadcast,mpsc,RwLock};
|
use tokio::sync::{broadcast,mpsc,RwLock};
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use crate::Coordinate;
|
use crate::Coordinate;
|
||||||
use crate::db;
|
use crate::db;
|
||||||
use crate::state::State;
|
use crate::state::{self,State};
|
||||||
|
|
||||||
use tracing::{warn,error,debug};
|
use tracing::{warn,error,debug};
|
||||||
|
|
||||||
@@ -219,13 +220,14 @@ impl Client {
|
|||||||
|
|
||||||
};
|
};
|
||||||
use fedichat::client::ClientMessage::*;
|
use fedichat::client::ClientMessage::*;
|
||||||
|
let my_addr = fedichat::ServerAddr(config.hostname.clone());
|
||||||
// Forward the message if it is addressed to a remote server
|
// Forward the message if it is addressed to a remote server
|
||||||
if let Some(servername) = message.target.clone()
|
if let Some(servername) = message.target.clone()
|
||||||
&& servername != fedichat::ServerAddr(config.hostname.clone()) {
|
&& servername != fedichat::ServerAddr(config.hostname.clone()) {
|
||||||
|
|
||||||
if message.message.is_forwardable() {
|
if message.message.is_forwardable() {
|
||||||
let user = self.get_user(config)?;
|
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 {
|
} else {
|
||||||
Err(ServerError::MessageNotForwardable)
|
Err(ServerError::MessageNotForwardable)
|
||||||
}
|
}
|
||||||
@@ -264,9 +266,18 @@ impl Client {
|
|||||||
},
|
},
|
||||||
// Private message/invite mechanism
|
// Private message/invite mechanism
|
||||||
MessagePost {
|
MessagePost {
|
||||||
body,
|
// This just gets sent out ephemerally as a subscribed message
|
||||||
user,
|
body: ref _body,
|
||||||
} => {unimplemented!()},
|
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
|
// Replace the body of the message with a new one
|
||||||
MessageEdit {
|
MessageEdit {
|
||||||
body,
|
body,
|
||||||
@@ -278,49 +289,93 @@ impl Client {
|
|||||||
room_id,
|
room_id,
|
||||||
} => {unimplemented!()},
|
} => {unimplemented!()},
|
||||||
// State Actions
|
// State Actions
|
||||||
|
// These don't use the DB at all
|
||||||
StateCreate {
|
StateCreate {
|
||||||
room_id,
|
room_id,
|
||||||
path,
|
path,
|
||||||
content,
|
content,
|
||||||
permissions,
|
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 {
|
StateWrite {
|
||||||
room_id,
|
room_id,
|
||||||
path,
|
path,
|
||||||
content,
|
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 {
|
StateDelete {
|
||||||
room_id,
|
room_id,
|
||||||
path,
|
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 {
|
StateAppend {
|
||||||
room_id,
|
room_id,
|
||||||
path,
|
path,
|
||||||
content,
|
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 {
|
StateMove {
|
||||||
room_id,
|
room_id,
|
||||||
path,
|
path,
|
||||||
target,
|
target,
|
||||||
} => {unimplemented!()},
|
} => {
|
||||||
|
// This could be a read and a write maybe?
|
||||||
|
unimplemented!()
|
||||||
|
},
|
||||||
StateRead {
|
StateRead {
|
||||||
room_id,
|
room_id,
|
||||||
path,
|
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 {
|
PermissionAdd {
|
||||||
permission,
|
permission,
|
||||||
path,
|
path,
|
||||||
room_id
|
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 {
|
PermissionRead {
|
||||||
path,
|
path,
|
||||||
room_id
|
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 {
|
PermissionDelete {
|
||||||
permission,
|
permission,
|
||||||
path,
|
path,
|
||||||
room_id
|
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
|
// Groups really should have a way to add permissions by user
|
||||||
// specifically for who can join or invite others
|
// specifically for who can join or invite others
|
||||||
//
|
//
|
||||||
@@ -462,6 +517,52 @@ impl Client {
|
|||||||
return Ok(ServerMessage::Error(ServerError::NotAuthenticated));
|
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> {
|
fn get_user(&self,config: &crate::config::Config) -> Result<fedichat::User,ServerError> {
|
||||||
Ok(fedichat::User {
|
Ok(fedichat::User {
|
||||||
@@ -469,6 +570,37 @@ impl Client {
|
|||||||
server: config.hostname.clone()
|
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> {
|
async fn get_db(&self) -> Result<Object<AsyncPgConnection>,ServerError> {
|
||||||
match self.db_handle.get().await {
|
match self.db_handle.get().await {
|
||||||
Ok(conn) => Ok(conn),
|
Ok(conn) => Ok(conn),
|
||||||
|
|||||||
+3
-1
@@ -20,7 +20,9 @@ pub struct Config {
|
|||||||
pub loglevel: Option<String>,
|
pub loglevel: Option<String>,
|
||||||
pub media_directory: String,
|
pub media_directory: String,
|
||||||
pub max_message_len_kb: usize,
|
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)]
|
#[derive(Clone,Serialize,Deserialize)]
|
||||||
pub struct DBConfig {
|
pub struct DBConfig {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ diesel::table! {
|
|||||||
client_timestamp -> Int8,
|
client_timestamp -> Int8,
|
||||||
server_timestamp -> Int8,
|
server_timestamp -> Int8,
|
||||||
username -> UserT,
|
username -> UserT,
|
||||||
|
edited -> Nullable<Bool>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -780,7 +780,7 @@ impl StateNode {
|
|||||||
} => perms
|
} => perms
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn to_state_value(&self) -> fedichat::state::StateValue {
|
pub fn to_state_value(&self) -> fedichat::state::StateValue {
|
||||||
match self {
|
match self {
|
||||||
// Turn directory into a `ls`-like listing
|
// Turn directory into a `ls`-like listing
|
||||||
StateNode::Directory { nodes, perms: _perms } => {
|
StateNode::Directory { nodes, perms: _perms } => {
|
||||||
|
|||||||
Reference in New Issue
Block a user