Skip to main content

auths_sdk/
context.rs

1//! Runtime dependency container for auths-sdk operations.
2//!
3//! [`AuthsContext`] carries all injected infrastructure adapters. Config structs
4//! (e.g. [`crate::types::DeveloperSetupConfig`]) remain Plain Old Data with no
5//! trait objects.
6
7use std::fmt;
8use std::sync::Arc;
9
10use auths_core::ports::clock::ClockProvider;
11use auths_core::ports::id::{SystemUuidProvider, UuidProvider};
12use auths_core::signing::PassphraseProvider;
13use auths_core::storage::keychain::KeyStorage;
14use auths_id::attestation::export::AttestationSink;
15use auths_id::ports::registry::RegistryBackend;
16use auths_id::storage::attestation::AttestationSource;
17use auths_id::storage::identity::IdentityStorage;
18
19use crate::ports::agent::{AgentSigningPort, NoopAgentProvider};
20
21/// A required builder field was not set before calling `build()`.
22#[derive(Debug, Clone)]
23pub struct BuilderError(pub &'static str);
24
25impl fmt::Display for BuilderError {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        write!(f, "missing required builder field: {}", self.0)
28    }
29}
30
31impl std::error::Error for BuilderError {}
32
33/// Fire-and-forget sink for structured telemetry payloads emitted by SDK operations.
34///
35/// Implement this trait to route SDK audit events to a logging backend, SIEM, or
36/// stdout. The default implementation ([`NoopSink`]) discards all events.
37///
38/// Usage:
39/// ```ignore
40/// struct StderrSink;
41/// impl auths_sdk::context::EventSink for StderrSink {
42///     fn emit(&self, payload: &str) { eprintln!("{payload}"); }
43///     fn flush(&self) {}
44/// }
45/// ```
46pub trait EventSink: Send + Sync + 'static {
47    /// Emit a JSON-serialized event payload. Must not block.
48    fn emit(&self, payload: &str);
49
50    /// Block until all previously emitted payloads have been written.
51    fn flush(&self);
52}
53
54struct NoopSink;
55
56impl EventSink for NoopSink {
57    fn emit(&self, _payload: &str) {}
58    fn flush(&self) {}
59}
60
61struct NoopPassphraseProvider;
62
63impl PassphraseProvider for NoopPassphraseProvider {
64    fn get_passphrase(
65        &self,
66        _prompt: &str,
67    ) -> Result<zeroize::Zeroizing<String>, auths_core::AgentError> {
68        Err(auths_core::AgentError::SigningFailed(
69            "no passphrase provider configured — call .passphrase_provider(...) on AuthsContextBuilder".into(),
70        ))
71    }
72}
73
74/// All runtime dependencies for auths-sdk operations.
75///
76/// Construct via [`AuthsContext::builder()`]. Config structs carry serializable
77/// data; `AuthsContext` carries injected infrastructure adapters. This separation
78/// allows the SDK to operate as a headless, storage-agnostic library that can be
79/// embedded in cloud SaaS, WASM, or C-FFI runtimes without pulling in tokio,
80/// git2, or std::fs.
81///
82/// Usage:
83/// ```ignore
84/// use std::sync::Arc;
85/// use auths_sdk::context::AuthsContext;
86///
87/// let ctx = AuthsContext::builder()
88///     .registry(Arc::new(my_registry))
89///     .key_storage(Arc::new(my_keychain))
90///     .clock(Arc::new(SystemClock))
91///     .build();
92/// sdk::setup_developer(config, &ctx)?;
93/// ```
94pub struct AuthsContext {
95    /// Pre-initialized registry storage backend.
96    pub registry: Arc<dyn RegistryBackend + Send + Sync>,
97    /// Platform keychain or test fake for key material storage.
98    pub key_storage: Arc<dyn KeyStorage + Send + Sync>,
99    /// Wall-clock provider for deterministic testing.
100    pub clock: Arc<dyn ClockProvider + Send + Sync>,
101    /// Telemetry sink (defaults to [`NoopSink`] when not specified).
102    pub event_sink: Arc<dyn EventSink>,
103    /// Identity storage adapter (load/save managed identity).
104    pub identity_storage: Arc<dyn IdentityStorage + Send + Sync>,
105    /// Attestation sink for writing signed attestations.
106    pub attestation_sink: Arc<dyn AttestationSink + Send + Sync>,
107    /// Attestation source for reading existing attestations.
108    pub attestation_source: Arc<dyn AttestationSource + Send + Sync>,
109    /// Passphrase provider for key decryption during signing operations.
110    /// Defaults to [`NoopPassphraseProvider`] — set via `.passphrase_provider(...)` when
111    /// SDK functions need to sign with encrypted key material.
112    pub passphrase_provider: Arc<dyn PassphraseProvider + Send + Sync>,
113    /// UUID generator port. Defaults to [`SystemUuidProvider`] (random v4 UUIDs).
114    /// Override with a deterministic stub in tests.
115    pub uuid_provider: Arc<dyn UuidProvider + Send + Sync>,
116    /// Agent-based signing port for delegating operations to a running agent process.
117    /// Defaults to [`NoopAgentProvider`] — set via `.agent_signing(...)` when the
118    /// platform supports agent-based signing (Unix with auths-agent).
119    pub agent_signing: Arc<dyn AgentSigningPort + Send + Sync>,
120}
121
122impl AuthsContext {
123    /// Creates a builder for [`AuthsContext`].
124    ///
125    /// Required fields are `registry`, `key_storage`, and `clock`. Omitting any
126    /// of these produces a compile-time error — the `build()` method is only
127    /// available once all three are set.
128    ///
129    /// Usage:
130    /// ```ignore
131    /// let ctx = AuthsContext::builder()
132    ///     .registry(Arc::new(my_registry))
133    ///     .key_storage(Arc::new(my_keychain))
134    ///     .clock(Arc::new(SystemClock))
135    ///     .build();
136    /// ```
137    pub fn builder() -> AuthsContextBuilder<Missing, Missing, Missing> {
138        AuthsContextBuilder {
139            registry: Missing,
140            key_storage: Missing,
141            clock: Missing,
142            event_sink: None,
143            identity_storage: None,
144            attestation_sink: None,
145            attestation_source: None,
146            passphrase_provider: None,
147            uuid_provider: None,
148            agent_signing: None,
149        }
150    }
151}
152
153/// Typestate marker: required field not yet set.
154pub struct Missing;
155
156/// Typestate marker: required field has been set.
157pub struct Set<T>(T);
158
159/// Typestate builder for [`AuthsContext`].
160///
161/// Call [`AuthsContext::builder()`] to obtain an instance. The `build()` method
162/// is only available once `registry`, `key_storage`, and `clock` have all been
163/// supplied.
164pub struct AuthsContextBuilder<R, K, C> {
165    registry: R,
166    key_storage: K,
167    clock: C,
168    event_sink: Option<Arc<dyn EventSink>>,
169    identity_storage: Option<Arc<dyn IdentityStorage + Send + Sync>>,
170    attestation_sink: Option<Arc<dyn AttestationSink + Send + Sync>>,
171    attestation_source: Option<Arc<dyn AttestationSource + Send + Sync>>,
172    passphrase_provider: Option<Arc<dyn PassphraseProvider + Send + Sync>>,
173    uuid_provider: Option<Arc<dyn UuidProvider + Send + Sync>>,
174    agent_signing: Option<Arc<dyn AgentSigningPort + Send + Sync>>,
175}
176
177impl<K, C> AuthsContextBuilder<Missing, K, C> {
178    /// Set the registry storage backend.
179    ///
180    /// Args:
181    /// * `registry`: Pre-initialized registry backend.
182    ///
183    /// Usage:
184    /// ```ignore
185    /// builder.registry(Arc::new(my_git_backend))
186    /// ```
187    pub fn registry(
188        self,
189        registry: Arc<dyn RegistryBackend + Send + Sync>,
190    ) -> AuthsContextBuilder<Set<Arc<dyn RegistryBackend + Send + Sync>>, K, C> {
191        AuthsContextBuilder {
192            registry: Set(registry),
193            key_storage: self.key_storage,
194            clock: self.clock,
195            event_sink: self.event_sink,
196            identity_storage: self.identity_storage,
197            attestation_sink: self.attestation_sink,
198            attestation_source: self.attestation_source,
199            passphrase_provider: self.passphrase_provider,
200            uuid_provider: self.uuid_provider,
201            agent_signing: self.agent_signing,
202        }
203    }
204}
205
206impl<R, C> AuthsContextBuilder<R, Missing, C> {
207    /// Set the key storage backend.
208    ///
209    /// Args:
210    /// * `key_storage`: Platform keychain or in-memory test fake.
211    ///
212    /// Usage:
213    /// ```ignore
214    /// builder.key_storage(Arc::new(my_keychain))
215    /// ```
216    pub fn key_storage(
217        self,
218        key_storage: Arc<dyn KeyStorage + Send + Sync>,
219    ) -> AuthsContextBuilder<R, Set<Arc<dyn KeyStorage + Send + Sync>>, C> {
220        AuthsContextBuilder {
221            registry: self.registry,
222            key_storage: Set(key_storage),
223            clock: self.clock,
224            event_sink: self.event_sink,
225            identity_storage: self.identity_storage,
226            attestation_sink: self.attestation_sink,
227            attestation_source: self.attestation_source,
228            passphrase_provider: self.passphrase_provider,
229            uuid_provider: self.uuid_provider,
230            agent_signing: self.agent_signing,
231        }
232    }
233}
234
235impl<R, K> AuthsContextBuilder<R, K, Missing> {
236    /// Set the clock provider.
237    ///
238    /// Args:
239    /// * `clock`: Wall-clock implementation (`SystemClock` in production,
240    ///   `MockClock` in tests).
241    ///
242    /// Usage:
243    /// ```ignore
244    /// builder.clock(Arc::new(SystemClock))
245    /// ```
246    pub fn clock(
247        self,
248        clock: Arc<dyn ClockProvider + Send + Sync>,
249    ) -> AuthsContextBuilder<R, K, Set<Arc<dyn ClockProvider + Send + Sync>>> {
250        AuthsContextBuilder {
251            registry: self.registry,
252            key_storage: self.key_storage,
253            clock: Set(clock),
254            event_sink: self.event_sink,
255            identity_storage: self.identity_storage,
256            attestation_sink: self.attestation_sink,
257            attestation_source: self.attestation_source,
258            passphrase_provider: self.passphrase_provider,
259            uuid_provider: self.uuid_provider,
260            agent_signing: self.agent_signing,
261        }
262    }
263}
264
265impl<R, K, C> AuthsContextBuilder<R, K, C> {
266    /// Set an optional event sink.
267    ///
268    /// Defaults to [`NoopSink`] (all events discarded) when not called.
269    ///
270    /// Args:
271    /// * `sink`: Any type implementing [`EventSink`].
272    ///
273    /// Usage:
274    /// ```ignore
275    /// builder.event_sink(Arc::new(my_sink))
276    /// ```
277    pub fn event_sink(self, sink: Arc<dyn EventSink>) -> AuthsContextBuilder<R, K, C> {
278        AuthsContextBuilder {
279            registry: self.registry,
280            key_storage: self.key_storage,
281            clock: self.clock,
282            event_sink: Some(sink),
283            identity_storage: self.identity_storage,
284            attestation_sink: self.attestation_sink,
285            attestation_source: self.attestation_source,
286            passphrase_provider: self.passphrase_provider,
287            uuid_provider: self.uuid_provider,
288            agent_signing: self.agent_signing,
289        }
290    }
291
292    /// Set the identity storage adapter.
293    ///
294    /// Args:
295    /// * `storage`: Pre-initialized identity storage implementation.
296    ///
297    /// Usage:
298    /// ```ignore
299    /// builder.identity_storage(Arc::new(my_identity_storage))
300    /// ```
301    pub fn identity_storage(
302        self,
303        storage: Arc<dyn IdentityStorage + Send + Sync>,
304    ) -> AuthsContextBuilder<R, K, C> {
305        AuthsContextBuilder {
306            registry: self.registry,
307            key_storage: self.key_storage,
308            clock: self.clock,
309            event_sink: self.event_sink,
310            identity_storage: Some(storage),
311            attestation_sink: self.attestation_sink,
312            attestation_source: self.attestation_source,
313            passphrase_provider: self.passphrase_provider,
314            uuid_provider: self.uuid_provider,
315            agent_signing: self.agent_signing,
316        }
317    }
318
319    /// Set the attestation sink adapter.
320    ///
321    /// Args:
322    /// * `sink`: Pre-initialized attestation sink implementation.
323    ///
324    /// Usage:
325    /// ```ignore
326    /// builder.attestation_sink(Arc::new(my_attestation_store))
327    /// ```
328    pub fn attestation_sink(
329        self,
330        sink: Arc<dyn AttestationSink + Send + Sync>,
331    ) -> AuthsContextBuilder<R, K, C> {
332        AuthsContextBuilder {
333            registry: self.registry,
334            key_storage: self.key_storage,
335            clock: self.clock,
336            event_sink: self.event_sink,
337            identity_storage: self.identity_storage,
338            attestation_sink: Some(sink),
339            attestation_source: self.attestation_source,
340            passphrase_provider: self.passphrase_provider,
341            uuid_provider: self.uuid_provider,
342            agent_signing: self.agent_signing,
343        }
344    }
345
346    /// Set the attestation source adapter.
347    ///
348    /// Args:
349    /// * `source`: Pre-initialized attestation source implementation.
350    ///
351    /// Usage:
352    /// ```ignore
353    /// builder.attestation_source(Arc::new(my_attestation_store))
354    /// ```
355    pub fn attestation_source(
356        self,
357        source: Arc<dyn AttestationSource + Send + Sync>,
358    ) -> AuthsContextBuilder<R, K, C> {
359        AuthsContextBuilder {
360            registry: self.registry,
361            key_storage: self.key_storage,
362            clock: self.clock,
363            event_sink: self.event_sink,
364            identity_storage: self.identity_storage,
365            attestation_sink: self.attestation_sink,
366            attestation_source: Some(source),
367            passphrase_provider: self.passphrase_provider,
368            uuid_provider: self.uuid_provider,
369            agent_signing: self.agent_signing,
370        }
371    }
372
373    /// Set the passphrase provider for key decryption during signing operations.
374    ///
375    /// Defaults to a noop provider that returns an error. Set this when SDK
376    /// workflow functions will perform signing with encrypted key material.
377    ///
378    /// Args:
379    /// * `provider`: Any type implementing [`PassphraseProvider`].
380    ///
381    /// Usage:
382    /// ```ignore
383    /// builder.passphrase_provider(Arc::new(PrefilledPassphraseProvider::new(passphrase)))
384    /// ```
385    pub fn passphrase_provider(
386        self,
387        provider: Arc<dyn PassphraseProvider + Send + Sync>,
388    ) -> AuthsContextBuilder<R, K, C> {
389        AuthsContextBuilder {
390            registry: self.registry,
391            key_storage: self.key_storage,
392            clock: self.clock,
393            event_sink: self.event_sink,
394            identity_storage: self.identity_storage,
395            attestation_sink: self.attestation_sink,
396            attestation_source: self.attestation_source,
397            passphrase_provider: Some(provider),
398            uuid_provider: self.uuid_provider,
399            agent_signing: self.agent_signing,
400        }
401    }
402
403    /// Set the UUID provider.
404    ///
405    /// Defaults to [`SystemUuidProvider`] (random v4 UUIDs) when not called.
406    /// Override with a deterministic stub in tests.
407    ///
408    /// Args:
409    /// * `provider`: Any type implementing [`UuidProvider`].
410    ///
411    /// Usage:
412    /// ```ignore
413    /// builder.uuid_provider(Arc::new(my_uuid_stub))
414    /// ```
415    pub fn uuid_provider(
416        self,
417        provider: Arc<dyn UuidProvider + Send + Sync>,
418    ) -> AuthsContextBuilder<R, K, C> {
419        AuthsContextBuilder {
420            registry: self.registry,
421            key_storage: self.key_storage,
422            clock: self.clock,
423            event_sink: self.event_sink,
424            identity_storage: self.identity_storage,
425            attestation_sink: self.attestation_sink,
426            attestation_source: self.attestation_source,
427            passphrase_provider: self.passphrase_provider,
428            uuid_provider: Some(provider),
429            agent_signing: self.agent_signing,
430        }
431    }
432
433    /// Set the agent signing port for delegating signing to a running agent process.
434    ///
435    /// Defaults to [`NoopAgentProvider`] (all operations return `Unavailable`)
436    /// when not called. Set this on Unix platforms where the auths-agent daemon
437    /// is available.
438    ///
439    /// Args:
440    /// * `provider`: Any type implementing [`AgentSigningPort`].
441    ///
442    /// Usage:
443    /// ```ignore
444    /// builder.agent_signing(Arc::new(CliAgentAdapter::new(socket_path)))
445    /// ```
446    pub fn agent_signing(
447        self,
448        provider: Arc<dyn AgentSigningPort + Send + Sync>,
449    ) -> AuthsContextBuilder<R, K, C> {
450        AuthsContextBuilder {
451            registry: self.registry,
452            key_storage: self.key_storage,
453            clock: self.clock,
454            event_sink: self.event_sink,
455            identity_storage: self.identity_storage,
456            attestation_sink: self.attestation_sink,
457            attestation_source: self.attestation_source,
458            passphrase_provider: self.passphrase_provider,
459            uuid_provider: self.uuid_provider,
460            agent_signing: Some(provider),
461        }
462    }
463}
464
465impl
466    AuthsContextBuilder<
467        Set<Arc<dyn RegistryBackend + Send + Sync>>,
468        Set<Arc<dyn KeyStorage + Send + Sync>>,
469        Set<Arc<dyn ClockProvider + Send + Sync>>,
470    >
471{
472    /// Build the [`AuthsContext`].
473    ///
474    /// Only callable once `registry`, `key_storage`, and `clock` have been set.
475    /// Omitting any of these fields produces a compile-time error.
476    ///
477    /// Usage:
478    /// ```ignore
479    /// let ctx = AuthsContext::builder()
480    ///     .registry(Arc::new(my_registry))
481    ///     .key_storage(Arc::new(my_keychain))
482    ///     .clock(Arc::new(SystemClock))
483    ///     .build();
484    /// ```
485    pub fn build(self) -> Result<AuthsContext, BuilderError> {
486        Ok(AuthsContext {
487            registry: self.registry.0,
488            key_storage: self.key_storage.0,
489            clock: self.clock.0,
490            event_sink: self.event_sink.unwrap_or_else(|| Arc::new(NoopSink)),
491            identity_storage: self
492                .identity_storage
493                .ok_or(BuilderError("identity_storage"))?,
494            attestation_sink: self
495                .attestation_sink
496                .ok_or(BuilderError("attestation_sink"))?,
497            attestation_source: self
498                .attestation_source
499                .ok_or(BuilderError("attestation_source"))?,
500            passphrase_provider: self
501                .passphrase_provider
502                .unwrap_or_else(|| Arc::new(NoopPassphraseProvider)),
503            uuid_provider: self
504                .uuid_provider
505                .unwrap_or_else(|| Arc::new(SystemUuidProvider)),
506            agent_signing: self
507                .agent_signing
508                .unwrap_or_else(|| Arc::new(NoopAgentProvider)),
509        })
510    }
511}