Skip to main content

conclavelib/
server.rs

1//! The central server (`conclave serve`): the axum WSS endpoint and control plane.
2//!
3//! A single self-contained binary that owns: the `axum` WSS endpoint and control RPCs; the
4//! embedded store (see [`crate::store`]); in-memory presence with heartbeat reaping and the
5//! fan-out router; and admin authorization against the config `users` allowlist plus each
6//! channel's `created_by` (DESIGN.md §7).
7//!
8//! Durable state is config only — no message history. Presence, subscriptions, permission levels,
9//! and the admin allowlist are deliberately *not* in the DB (DESIGN.md §15).
10//!
11//! The subsystem is split by responsibility: `hub` is the transport-agnostic core (store +
12//! in-memory presence, subscriptions, and the fan-out router); `session` is the per-connection
13//! handshake + frame loop; `wss` is the axum WebSocket adapter and the [`serve`] entrypoint. This
14//! module owns the [`AclError`] authorization boundary type and re-exports the public surface.
15
16mod hub;
17mod session;
18mod wss;
19
20#[cfg(test)]
21mod integration;
22
23pub use wss::{ServerConfig, serve};
24
25use crate::protocol::ProtocolError;
26
27/// The server-admin allowlist (DESIGN.md §7): each admin username mapped to the public key
28/// (base64) permitted to claim it, or `None` if unpinned. Pinning stops a fresh-deploy admin
29/// username from being squatted by the first client to register it (PRD-0007 T-002).
30pub type AdminAllowlist = std::collections::HashMap<String, Option<String>>;
31
32/// Errors at the access-control / authorization boundary (DESIGN.md §7, §16), matched on by the
33/// server and surfaced to the caller as a wire error frame.
34#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
35pub enum AclError {
36    /// The issuing user is not an admin for the attempted action.
37    #[error("not authorized: admin role required")]
38    NotAdmin,
39    /// The user is not a member of the channel.
40    #[error("not a member of channel `{0}`")]
41    NotMember(String),
42    /// The channel is private and the user has no ACL entry or valid invite.
43    #[error("channel `{0}` is private")]
44    ChannelPrivate(String),
45    /// The named channel does not exist.
46    #[error("channel `{0}` not found")]
47    ChannelNotFound(String),
48}
49
50impl From<AclError> for ProtocolError {
51    fn from(err: AclError) -> Self {
52        let message = err.to_string();
53        match err {
54            AclError::ChannelNotFound(_) => Self::NotFound(message),
55            _ => Self::Unauthorized(message),
56        }
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    use pretty_assertions::assert_eq;
64
65    #[test]
66    fn acl_errors_map_onto_wire_protocol_errors() {
67        assert!(matches!(ProtocolError::from(AclError::NotAdmin), ProtocolError::Unauthorized(_)));
68        assert!(matches!(ProtocolError::from(AclError::ChannelNotFound("ops".to_owned())), ProtocolError::NotFound(_)));
69    }
70
71    #[test]
72    fn acl_error_messages_are_descriptive() {
73        assert_eq!(AclError::NotMember("ops".to_owned()).to_string(), "not a member of channel `ops`");
74    }
75}