a3f5470549
The protocol changed a bit, I forgot to add room ids for some of the messages. Other big features are that there are now ways to process messages built into the library itself. Signature verification is stubbed out because its a low priority feature, but encoding it in the lib gives a standardized way to do it
284 lines
7.7 KiB
Rust
284 lines
7.7 KiB
Rust
use uuid::Uuid;
|
|
use crate::message::{MessageId,TaggedMessage,VerificationError,Relevance};
|
|
use crate::state::{StateType,StateValue,StatePath,StatePermission,StatePermissionKey};
|
|
use crate::{RoomId,Group,Role,User,GroupPower};
|
|
use serde::{Deserialize, Serialize};
|
|
use time::OffsetDateTime;
|
|
|
|
//StatePath [String]
|
|
//octal is fine for now
|
|
//maybe do ACL late???
|
|
//StatePermissions (u8,u8,u8)
|
|
//RoomId [i64]
|
|
|
|
|
|
|
|
#[derive(Serialize,Deserialize,Clone,Debug)]
|
|
/// This exists solely to allow the DB to finish tagging the message. It is every field of a
|
|
/// message except for the ID which can be created using a Postgres serial type
|
|
pub struct InsertableClientMessage {
|
|
pub message: ClientMessage,
|
|
#[serde(with="time::serde::timestamp")]
|
|
pub client_timestamp: OffsetDateTime,
|
|
#[serde(with="time::serde::timestamp")]
|
|
pub server_timestamp: OffsetDateTime,
|
|
#[serde(with = "serde_bytes")]
|
|
pub signature: Box<[u8]>,
|
|
pub user: User
|
|
}
|
|
|
|
|
|
#[derive(Serialize,Deserialize,Clone,Debug)]
|
|
pub struct SignedClientMessage {
|
|
pub message: ClientMessage,
|
|
// timestamp sent by client protects against replay attacks by untrusted server
|
|
#[serde(with="time::serde::timestamp")]
|
|
pub timestamp: OffsetDateTime,
|
|
// Should I enforce this being a ecdsa signature?
|
|
// What is the signature of????
|
|
// Can I reserialize the message and check the signature that way??
|
|
// That would be very brittle but there isn't a good way to extract the
|
|
// bytes using serde. I'll write something better at some point
|
|
#[serde(with = "serde_bytes")]
|
|
pub signature: Box<[u8]>
|
|
}
|
|
impl SignedClientMessage {
|
|
pub fn tag(self, username: User) -> InsertableClientMessage {
|
|
InsertableClientMessage {
|
|
message: self.message,
|
|
client_timestamp: self.timestamp,
|
|
server_timestamp: OffsetDateTime::now_utc(),
|
|
signature: self.signature,
|
|
user: username,
|
|
}
|
|
}
|
|
pub fn verify(&self) -> Result<bool,VerificationError> {
|
|
unimplemented!()
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize,Deserialize,Clone,Debug)]
|
|
pub enum ClientMessage {
|
|
|
|
Auth{
|
|
username: String,
|
|
password: String
|
|
},
|
|
// Maybe ask for email too? Or a potential invite code
|
|
UserCreate {
|
|
username: String,
|
|
password: String,
|
|
},
|
|
// Used to require accounts to complete some kind of challenge. Simplest
|
|
// is giving a password/invite code to create an account or join a room
|
|
ChallengeAnswer {
|
|
response: String
|
|
},
|
|
// TODO I still dont have key management commands
|
|
|
|
// Should it be one message type or mutiple?
|
|
Message {
|
|
body: String,
|
|
room_id: RoomId
|
|
},
|
|
// Private message/invite mechanism
|
|
MessagePost {
|
|
body: String,
|
|
user: User
|
|
},
|
|
// Replace the body of the message with a new one
|
|
MessageEdit {
|
|
room_id: RoomId,
|
|
body: String,
|
|
id: MessageId
|
|
},
|
|
MessageDelete {
|
|
room_id: RoomId,
|
|
id: MessageId
|
|
},
|
|
// State Actions
|
|
StateCreate {
|
|
room_id: RoomId,
|
|
path: StatePath,
|
|
ty: StateType,
|
|
permissions: Option<Vec<StatePermission>>
|
|
},
|
|
StateWrite {
|
|
room_id: RoomId,
|
|
path: StatePath,
|
|
content: StateValue
|
|
},
|
|
StateDelete {
|
|
room_id: RoomId,
|
|
path: StatePath,
|
|
},
|
|
StateAppend {
|
|
room_id: RoomId,
|
|
path: StatePath,
|
|
content: StateValue
|
|
},
|
|
StateMove {
|
|
room_id: RoomId,
|
|
path: StatePath,
|
|
target: StatePath,
|
|
},
|
|
StateRead {
|
|
room_id: RoomId,
|
|
path: StatePath
|
|
},
|
|
PermissionAdd {
|
|
permission: StatePermission
|
|
},
|
|
PermissionDelete {
|
|
permission: StatePermissionKey
|
|
},
|
|
// Groups really should have a way to add permissions by user
|
|
// specifically for who can join or invite others
|
|
//
|
|
// Maybe make a group -> role -> member hierarchy?
|
|
//
|
|
//
|
|
// Could always do this through a bot that owns a group??
|
|
GroupCreate {
|
|
group: Group,
|
|
users: Vec<User>
|
|
},
|
|
// Only the creator of a group or a server admin can delete groups
|
|
GroupDelete {
|
|
group: Group
|
|
},
|
|
// Only the creator of a group or a server admin can delete groups
|
|
// same with adding, though there should be a way to add group officers
|
|
// at some point
|
|
GroupUserAdd {
|
|
group: Group,
|
|
users: Vec<User>
|
|
},
|
|
GroupUserRemove {
|
|
group: Group,
|
|
users: Vec<User>
|
|
},
|
|
GroupRoleCreate {
|
|
group: Group,
|
|
role: Role
|
|
},
|
|
GroupRoleDelete {
|
|
group: Group,
|
|
role: Role
|
|
},
|
|
GroupRoleUserAdd {
|
|
group: Group,
|
|
role: Role,
|
|
users: Vec<User>
|
|
},
|
|
GroupRoleUserRemove {
|
|
group: Group,
|
|
role: Role,
|
|
users: Vec<User>
|
|
},
|
|
// Should work like discord roles
|
|
// Can control who can invite to the group
|
|
// Can be used with permissions to make rooms that are private for individual roles
|
|
GroupRolePowerAdd {
|
|
group: Group,
|
|
role: Role,
|
|
power: GroupPower
|
|
},
|
|
GroupRolePowerRemove {
|
|
group: Group,
|
|
role: Role,
|
|
power: GroupPower
|
|
},
|
|
// Returns an ID to use for message sending
|
|
// The server can potentially use the current username to associate media uploads
|
|
// with users
|
|
MediaUpload {
|
|
#[serde(with = "serde_bytes")]
|
|
bytes: Vec<u8>
|
|
},
|
|
// Join and subscribe
|
|
Join {
|
|
room_id: RoomId,
|
|
},
|
|
SubscribeMessages {
|
|
room_id: RoomId,
|
|
},
|
|
SubscribeState {
|
|
room_id: RoomId,
|
|
state: StatePath
|
|
},
|
|
FetchMessages {
|
|
count: u64,
|
|
end: MessageId,
|
|
}
|
|
}
|
|
impl ClientMessage<> {
|
|
pub fn get_relevance(&self) -> Option<Relevance> {
|
|
use ClientMessage::*;
|
|
Some(match self {
|
|
Message {
|
|
body: _,
|
|
room_id
|
|
// These don't necessarily need cloned, it might make sense to make an owned
|
|
// version of Relevance and a reference version of it
|
|
} => Relevance::Message(room_id.clone()),
|
|
// Private message/invite mechanism
|
|
MessagePost {
|
|
body: _,
|
|
user
|
|
} => Relevance::Post(user.clone()),
|
|
// Replace the body of the message with a new one
|
|
MessageEdit {
|
|
room_id,
|
|
body: _,
|
|
id: _
|
|
} => Relevance::Message(room_id.clone()),
|
|
MessageDelete {
|
|
room_id,
|
|
id: _,
|
|
} => Relevance::Message(room_id.clone()),
|
|
StateWrite {
|
|
room_id,
|
|
path,
|
|
content: _
|
|
} => Relevance::State(room_id.clone(),path.clone()),
|
|
StateDelete {
|
|
room_id,
|
|
path,
|
|
} => Relevance::State(room_id.clone(),path.clone()),
|
|
StateAppend {
|
|
room_id,
|
|
path,
|
|
content: _
|
|
} => Relevance::State(room_id.clone(),path.clone()),
|
|
_ => return None
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize,Deserialize)]
|
|
pub enum ServerError {
|
|
InvalidPermission,
|
|
// Server can specify the required challenge
|
|
ChallengeInvitecode,
|
|
NotAuthenticated,
|
|
AlreadyAuthenticated,
|
|
NotInChallenge
|
|
|
|
}
|
|
|
|
#[derive(Serialize,Deserialize)]
|
|
pub enum ServerMessage {
|
|
// Returned on fetch or naturally from subscribe
|
|
// This should be
|
|
Messages(Vec<TaggedMessage>),
|
|
MediaUploaded(Uuid),
|
|
Error(ServerError),
|
|
// Returned on read
|
|
State(StatePath,StateValue),
|
|
// Returned on subscribe, forwards state change message to client
|
|
StateChange(TaggedMessage),
|
|
OkMessage(MessageId),
|
|
Ok,
|
|
}
|