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 { 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> }, 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 }, // 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 }, GroupUserRemove { group: Group, users: Vec }, GroupRoleCreate { group: Group, role: Role }, GroupRoleDelete { group: Group, role: Role }, GroupRoleUserAdd { group: Group, role: Role, users: Vec }, GroupRoleUserRemove { group: Group, role: Role, users: Vec }, // 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 }, // 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 { 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), MediaUploaded(Uuid), Error(ServerError), // Returned on read State(StatePath,StateValue), // Returned on subscribe, forwards state change message to client StateChange(TaggedMessage), OkMessage(MessageId), Ok, }