Skip to main content

cairn_mod/server/admin/
mod.rs

1//! `tools.cairn.admin.*` handlers (§F12).
2//!
3//! Each endpoint lives in its own file; [`admin_router`] wires them
4//! together under one `Extension<AdminState>` so sub-modules don't
5//! need to duplicate state plumbing. Per-request auth + role + CORS
6//! gating is centralized in the shared `common::verify_and_authorize`
7//! helper inside this module.
8
9use std::sync::Arc;
10
11use axum::Extension;
12use axum::Router;
13use axum::routing::{get, post};
14use sqlx::{Pool, Sqlite};
15
16use crate::auth::AuthContext;
17use crate::writer::WriterHandle;
18
19mod apply_label;
20mod audit_view;
21mod common;
22mod flag_reporter;
23mod get_audit_log;
24mod get_report;
25mod get_trust_chain;
26mod list_audit_log;
27mod list_labels;
28mod list_reports;
29mod negate_label;
30mod report_view;
31mod resolve_report;
32mod retention_sweep;
33
34/// Operator configuration for admin endpoints. Kept separate from the
35/// subscribe/query configs so operators can tune label-value policy
36/// without touching read-side knobs.
37#[derive(Debug, Clone, Default)]
38pub struct AdminConfig {
39    /// Operational allowlist for `applyLabel` (§F12 `InvalidLabelValue`).
40    /// When `Some`, `applyLabel` rejects values not in this set.
41    /// When `None`, any val ≤128 bytes is accepted — the §F11
42    /// anti-leak principle applies: the error message on reject
43    /// does NOT enumerate the allowed values.
44    ///
45    /// Distinct from [`Self::declared_label_values`] (the trust-chain
46    /// surface for the labeler's *declared* taxonomy). They typically
47    /// match in production but conceptually differ — the allowlist
48    /// gates incoming writes; the declared list documents what the
49    /// labeler publishes.
50    ///
51    /// Future: a #9 service-record update may derive this from the
52    /// published `app.bsky.labeler.service` record so the lexicon set
53    /// and the runtime policy stay in lockstep.
54    pub label_values: Option<Vec<String>>,
55
56    /// Service DID surfaced in `tools.cairn.admin.getTrustChain` (#36).
57    /// Mirrors `Config::service_did`; populated at admin_router
58    /// construction in `serve::run`. Default empty for tests that
59    /// don't exercise the trust-chain endpoint.
60    pub service_did: String,
61
62    /// Service endpoint URL surfaced in
63    /// `tools.cairn.admin.getTrustChain` (#36). Mirrors
64    /// `Config::service_endpoint`. Default empty for tests that
65    /// don't exercise the trust-chain endpoint.
66    pub service_endpoint: String,
67
68    /// Labeler-declared label values from the `[labeler]` config
69    /// block — surfaced by `tools.cairn.admin.getTrustChain` as the
70    /// trust-chain "taxonomy" snapshot. `None` when the deployment
71    /// runs without `[labeler]` (§F19 labeler-absent path); the
72    /// trust-chain endpoint then reports `serviceRecord: null`.
73    /// Distinct from [`Self::label_values`] above — see that field's
74    /// doc comment.
75    pub declared_label_values: Option<Vec<String>>,
76}
77
78/// Build a Router exposing the tools.cairn.admin.* endpoints
79/// registered so far. Compose with subscribe/query/createReport
80/// routers via `Router::merge`.
81pub fn admin_router(
82    pool: Pool<Sqlite>,
83    writer: WriterHandle,
84    auth: Arc<AuthContext>,
85    config: AdminConfig,
86) -> Router {
87    let state = common::AdminState {
88        pool,
89        writer,
90        auth,
91        config: Arc::new(config),
92    };
93    Router::new()
94        .route(
95            "/xrpc/tools.cairn.admin.applyLabel",
96            post(apply_label::handler),
97        )
98        .route(
99            "/xrpc/tools.cairn.admin.negateLabel",
100            post(negate_label::handler),
101        )
102        .route(
103            "/xrpc/tools.cairn.admin.listLabels",
104            get(list_labels::handler),
105        )
106        .route(
107            "/xrpc/tools.cairn.admin.listReports",
108            get(list_reports::handler),
109        )
110        .route(
111            "/xrpc/tools.cairn.admin.getReport",
112            get(get_report::handler),
113        )
114        .route(
115            "/xrpc/tools.cairn.admin.resolveReport",
116            post(resolve_report::handler),
117        )
118        .route(
119            "/xrpc/tools.cairn.admin.flagReporter",
120            post(flag_reporter::handler),
121        )
122        .route(
123            "/xrpc/tools.cairn.admin.listAuditLog",
124            get(list_audit_log::handler),
125        )
126        .route(
127            "/xrpc/tools.cairn.admin.getAuditLog",
128            get(get_audit_log::handler),
129        )
130        .route(
131            "/xrpc/tools.cairn.admin.retentionSweep",
132            post(retention_sweep::handler),
133        )
134        .route(
135            "/xrpc/tools.cairn.admin.getTrustChain",
136            get(get_trust_chain::handler),
137        )
138        .layer(Extension(state))
139}