server/lib.rs
1#![deny(clippy::await_holding_lock)]
2
3mod access_keys;
4mod cors;
5mod http;
6mod json;
7mod mcp;
8mod mcp_tasks;
9mod subscribe;
10#[cfg(feature = "tls")]
11pub mod tls;
12mod ws;
13
14use mushroomdb::{AccessKeys, SharedDb};
15
16pub use http::{
17 register_backup_metrics, register_bolt_metrics, BackupMetrics, BoltMetrics, TxLimits,
18 TX_MAX_ENV, TX_MAX_LIFETIME_ENV, TX_TIMEOUT_ENV,
19};
20pub use mcp::{run_mcp_stdio, run_mcp_stdio_with, ASSOCIATION_TOOLS};
21
22/// The root this crate's `tracing` events are filtered by.
23///
24/// It is the **lib** name, which is not the package name — `server`, not
25/// `mushroomdb-server`. A log filter naming the package matches nothing, and a
26/// filter that matches nothing looks exactly like a server with nothing to
27/// report. Exported so the binary's default filter can be checked against it
28/// rather than against a guess.
29pub const LOG_TARGET_ROOT: &str = module_path!();
30
31/// Resolved authentication identity for a single request.
32///
33/// Injected into request extensions by `auth_middleware` before any handler
34/// runs. Handlers that need to enforce role-based access control extract it
35/// via `Extension<AuthIdentity>`.
36#[derive(Clone, Debug)]
37pub(crate) enum AuthIdentity {
38 /// Full-access token (or no auth configured), or an `admin` key.
39 Full,
40 /// Write-tier key: mutations allowed; `/backup`, `/stats`, `/metrics`,
41 /// `/watch` and `/subscribe` are refused. Reads as full visibility.
42 Write,
43 /// Role-bound token; the inner string is the role name.
44 Role(String),
45 /// Reached an endpoint that is open by design without presenting a token
46 /// that resolves to anything — today only `GET /health`, which a load
47 /// balancer must be able to call before it has a credential.
48 ///
49 /// Distinct from [`Full`](AuthIdentity::Full) because the difference is
50 /// what may be disclosed: an anonymous caller gets liveness and nothing
51 /// that describes the graph.
52 Anonymous,
53}
54
55/// Marks a request made with a `read`-tier key. It rides beside
56/// [`AuthIdentity::Role`] (the role's mask) and means the request must not
57/// write, whatever write scope the role carries.
58#[derive(Clone, Copy, Debug)]
59pub(crate) struct ReadOnlyKey;
60
61/// Router state: the database plus the watch broadcast fan-out.
62///
63/// `watch` carries pre-serialised JSON strings so that `MutationEvent`
64/// variants (externally-tagged, from the event sink) and edge-prop events
65/// (derived from `DbEvent` subscriptions, row 155) share one channel without
66/// changing the pub `MutationEvent` type.
67#[derive(Clone)]
68struct AppState {
69 db: SharedDb,
70 watch: tokio::sync::broadcast::Sender<String>,
71 /// Full-access bearer token (`--token` / `MUSHROOMDB_TOKEN`).
72 token: Option<String>,
73 /// Role-bound tokens: bearer value → role name.
74 /// A non-empty map enables role enforcement on every request.
75 role_tokens: std::collections::HashMap<String, String>,
76 /// The `keys.json` set, when the caller supplied one. Shared, so a reload
77 /// is seen by the next request.
78 access_keys: Option<AccessKeys>,
79 /// Bind address advertised in `GET /health`.
80 addr: std::net::SocketAddr,
81 /// True when the server is serving over TLS (via the `tls` feature).
82 /// When true, the auth cookie gains the `Secure` attribute.
83 tls_active: bool,
84 /// Instant the router was first built; used by `GET /metrics` uptime_s.
85 started_at: std::time::Instant,
86 /// The same instant as Unix seconds; `mushroomdb_process_start_time_seconds`.
87 started_unix_s: u64,
88 /// `GET /ready` answers 200 while this is true and 503 while it is false.
89 /// True at every construction site except the `serve` command, which
90 /// starts it false and sets it once the store is open.
91 ready: std::sync::Arc<std::sync::atomic::AtomicBool>,
92 /// Open interactive transactions (`/tx/*`).
93 txs: http::TxStore,
94 /// Keepalive handle for the edge-prop bridge background thread (row 155).
95 /// The bridge holds a `Weak` reference to this `Arc`; when every
96 /// `AppState` clone is dropped — meaning the server is shutting down —
97 /// the `Weak` becomes dangling and the bridge thread exits its loop.
98 _bridge_keepalive: std::sync::Arc<()>,
99}
100
101pub use access_keys::watch_sighup;
102pub use cors::{cors_layer_from_env, parse_cors_origins, CORS_ORIGINS_ENV};
103
104#[allow(deprecated)]
105pub use http::{
106 router, router_with_access_keys, router_with_auth, router_with_cors, router_with_ready_flag,
107 router_with_role_tokens, router_with_tx_limits, router_with_ui, router_with_ui_tls, serve,
108 serve_with_role_tokens, serve_with_role_tokens_and_shutdown,
109 serve_with_role_tokens_and_shutdown_with_ready_flag, serve_with_shutdown, serve_with_ui,
110 serve_with_ui_and_role_tokens, serve_with_ui_and_role_tokens_and_shutdown,
111 serve_with_ui_and_role_tokens_and_shutdown_with_ready_flag,
112};
113#[cfg(feature = "embed-ui")]
114pub use http::{
115 router_with_embedded_ui, serve_with_embedded_ui, serve_with_embedded_ui_and_shutdown,
116 serve_with_embedded_ui_and_shutdown_with_ready_flag,
117};
118#[cfg(feature = "tls")]
119pub use http::{serve_tls, serve_tls_with_shutdown, serve_tls_with_shutdown_with_ready_flag};
120#[cfg(feature = "tls")]
121pub use tls::load_server_config;