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}