Skip to main content

cratefield_module_privacy/
lib.rs

1//! `cratefield-module-privacy`: what this deployment holds about a person, and
2//! everything it holds about one.
3//!
4//! The module knows no venture's schema. Every table it reads is one another
5//! module declared through
6//! [`Module::personal_data`](cratefield_core::Module::personal_data), composed
7//! at build into the
8//! [`PersonalDataCatalog`](cratefield_core::PersonalDataCatalog) this reads —
9//! declaring a table is the whole wiring, so a composed venture gets subject
10//! access over whatever else it composed, per table, with none of its own.
11//!
12//! Data held **outside** the harness — a warehouse, a CRM, another product's
13//! database — is reached by registering a provider with
14//! [`Privacy::provider`]; each is called over one signed HTTP contract, and
15//! `requires()` grows the `HttpClient` and `Defer` ports it needs with it.
16//! The other end of that same contract is [`Privacy::serve_provider`]: a
17//! deployment that holds such a system of its own, or is itself the system
18//! another deployment reaches, can answer `/v1/privacy/provider/*` over the
19//! same declarations, with the same signature and the same answer.
20//!
21//! What catches a table nobody declared is `cratefield_core::undeclared_tables`,
22//! through `cratefield_testing::conformance` — a **kit check, not a build
23//! error** (issue #268). Silence is legitimate for a module with no tables,
24//! and a venture should not be unable to boot because some module's author
25//! has not got to this yet. Declaring a table the module does *not* own is
26//! still a build error, which is the rule this one was confused with.
27
28#![doc = include_str!("../README.md")]
29#![forbid(unsafe_code)]
30
31mod erase;
32mod handlers;
33mod provider;
34mod provider_server;
35
36use cratefield_core::{ConfigError, Migrations, Module, ModuleContext, Port};
37use std::sync::Arc;
38
39pub use provider::{HttpProvider, SIGNATURE_HEADER};
40
41/// Subject access and erasure over whatever the venture composed, plus the
42/// external providers a venture registered.
43#[derive(Clone, Debug, Default)]
44pub struct Privacy {
45    providers: Arc<Vec<HttpProvider>>,
46    /// The env var holding this deployment's *provider server* secret, or
47    /// `None` where the deployment does not serve the protocol. `Option`, not
48    /// an empty `String`: "serving" and "serving with an unset secret" are
49    /// different states, and the second one must refuse rather than serve.
50    server_secret_env: Option<Arc<str>>,
51}
52
53impl Privacy {
54    /// The module, with no providers. What it reads is what the other modules
55    /// declared; add a system the harness never sees with [`Self::provider`].
56    #[must_use]
57    pub fn new() -> Self {
58        Self::default()
59    }
60
61    /// Registers an external system holding data about subjects (issue
62    /// #653). A provider is called on export and erasure, and its sections
63    /// are merged into the response under `providers`; a failure there never
64    /// obscures the local answer.
65    #[must_use]
66    pub fn provider(mut self, provider: HttpProvider) -> Self {
67        Arc::make_mut(&mut self.providers).push(provider);
68        self
69    }
70
71    /// Serves the same protocol this module calls out on (issue #656), so
72    /// this deployment can be the provider *another* deployment reaches:
73    /// `/v1/privacy/provider/export`, `/provider/erase/plan` and
74    /// `/provider/erase/apply` answer over this composition's declarations.
75    ///
76    /// `secret_env` names the config/env variable holding the shared HMAC
77    /// secret — the same one an outbound [`HttpProvider::secret_env`] points
78    /// at. The **name** is taken here; the value is read from the config port
79    /// at request time, so one build works in every environment and a
80    /// deployment that was built before the secret existed does not have to be
81    /// rebuilt to start answering.
82    ///
83    /// Opt-in rather than always-on because the routes are a public door: a
84    /// deployment that never opted in does not answer, and a deployment that
85    /// did but has no secret refuses every call rather than opening.
86    ///
87    /// **No admin token, deliberately.** The HMAC over the raw body *is* the
88    /// authorisation — a caller on another deployment has no account here and
89    /// no `ADMIN_TOKEN` to present, so an admin guard would refuse every
90    /// legitimate call while authorising nothing the protocol lacks.
91    #[must_use]
92    pub fn serve_provider(mut self, secret_env: impl Into<String>) -> Self {
93        self.server_secret_env = Some(Arc::from(secret_env.into()));
94        self
95    }
96}
97
98impl Module for Privacy {
99    fn name(&self) -> &'static str {
100        "privacy"
101    }
102
103    fn version(&self) -> &'static str {
104        env!("CARGO_PKG_VERSION")
105    }
106
107    /// The database it reads and erases is other modules' tables; it owns
108    /// none itself.
109    /// `Signer` is required, not optional: without it there is no
110    /// confirmation token, and erasure would have to be a single call.
111    /// `HttpClient` and `Defer` join only when a provider is registered —
112    /// with none, this module makes no outbound call and defers nothing.
113    /// Serving the protocol ([`Self::serve_provider`]) adds no port: it
114    /// reads the same tables through the `Db` already required here and
115    /// takes its secret from the config every route already has, so a
116    /// deployment that only serves does not ask for an outbound client it
117    /// will never use.
118    fn requires(&self) -> &'static [Port] {
119        if self.providers.is_empty() {
120            &[Port::Db, Port::Signer]
121        } else {
122            &[Port::Db, Port::Signer, Port::HttpClient, Port::Defer]
123        }
124    }
125
126    /// No tables. A module that owned one would have to declare its own
127    /// personal data, and a privacy module keeping records about the people who
128    /// asked what it keeps is a joke that writes itself. Erasure keeps none
129    /// either: its receipt is the confirm response, not a stored record.
130    fn tables(&self) -> &'static [&'static str] {
131        &[]
132    }
133
134    fn migrations(&self) -> Migrations {
135        Migrations::EMPTY
136    }
137
138    /// Nothing to validate: the module has no configuration of its own, and
139    /// what it reads is what other modules declared, which
140    /// `HarnessBuilder::build` already checked. A provider's secret is read
141    /// at call time and its absence reported as `not_configured`, not
142    /// refused at build.
143    fn validate_config(&self, _cfg: &dyn cratefield_core::Config) -> Result<(), ConfigError> {
144        Ok(())
145    }
146
147    fn router(&self, ctx: ModuleContext) -> axum::Router {
148        let ctx = Arc::new(ctx);
149        let mut router = handlers::router(Arc::clone(&ctx), Arc::clone(&self.providers));
150        if let Some(secret_env) = &self.server_secret_env {
151            router = router.merge(provider_server::router(
152                Arc::clone(&ctx),
153                Arc::clone(secret_env),
154            ));
155        }
156        router
157    }
158}