Skip to main content

confium_deployment/identity/
store.rs

1//! Identity store abstraction.
2
3use std::collections::HashMap;
4use std::sync::{Mutex, MutexGuard};
5
6use crate::identity::actor::{ActorIdentity, IdentityError};
7
8/// Backend trait for persistent identity storage.
9pub trait IdentityBackend: Send + Sync {
10    /// Store an identity.
11    fn store(&self, identity: &ActorIdentity) -> Result<(), IdentityError>;
12    /// Look up an identity by ID.
13    fn load(&self, actor_id: &str) -> Result<ActorIdentity, IdentityError>;
14    /// Delete an identity.
15    fn delete(&self, actor_id: &str) -> Result<(), IdentityError>;
16    /// List all actor IDs.
17    fn list(&self) -> Result<Vec<String>, IdentityError>;
18}
19
20/// In-memory identity store (testing only).
21#[derive(Default)]
22pub struct MemoryIdentityBackend {
23    inner: Mutex<HashMap<String, ActorIdentity>>,
24}
25
26impl MemoryIdentityBackend {
27    /// Construct a new empty store.
28    pub fn new() -> Self {
29        Self::default()
30    }
31
32    fn lock(&self) -> Result<MutexGuard<'_, HashMap<String, ActorIdentity>>, IdentityError> {
33        self.inner
34            .lock()
35            .map_err(|_| IdentityError::NotFound("lock poisoned".into()))
36    }
37}
38
39impl IdentityBackend for MemoryIdentityBackend {
40    fn store(&self, identity: &ActorIdentity) -> Result<(), IdentityError> {
41        let mut map = self.lock()?;
42        map.insert(identity.actor_id.clone(), identity.clone());
43        Ok(())
44    }
45
46    fn load(&self, actor_id: &str) -> Result<ActorIdentity, IdentityError> {
47        let map = self.lock()?;
48        map.get(actor_id)
49            .cloned()
50            .ok_or_else(|| IdentityError::NotFound(actor_id.into()))
51    }
52
53    fn delete(&self, actor_id: &str) -> Result<(), IdentityError> {
54        let mut map = self.lock()?;
55        map.remove(actor_id);
56        Ok(())
57    }
58
59    fn list(&self) -> Result<Vec<String>, IdentityError> {
60        let map = self.lock()?;
61        Ok(map.keys().cloned().collect())
62    }
63}
64
65/// High-level identity store.
66pub struct IdentityStore {
67    backend: Box<dyn IdentityBackend>,
68}
69
70impl IdentityStore {
71    /// Construct a new store wrapping a backend.
72    pub fn new(backend: Box<dyn IdentityBackend>) -> Self {
73        Self { backend }
74    }
75
76    /// Construct an in-memory store (testing only).
77    pub fn in_memory() -> Self {
78        Self::new(Box::new(MemoryIdentityBackend::new()))
79    }
80
81    /// Register a new identity. Fails if the actor already exists.
82    pub fn register(&self, identity: ActorIdentity) -> Result<(), IdentityError> {
83        if self.backend.load(&identity.actor_id).is_ok() {
84            return Err(IdentityError::AlreadyExists(identity.actor_id));
85        }
86        self.backend.store(&identity)
87    }
88
89    /// Look up an identity.
90    pub fn lookup(&self, actor_id: &str) -> Result<ActorIdentity, IdentityError> {
91        self.backend.load(actor_id)
92    }
93
94    /// Delete an identity.
95    pub fn revoke(&self, actor_id: &str) -> Result<(), IdentityError> {
96        self.backend.delete(actor_id)
97    }
98
99    /// List all actors.
100    pub fn list(&self) -> Result<Vec<String>, IdentityError> {
101        self.backend.list()
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use crate::identity::actor::{ActorType, SigningKeyHandle};
109
110    fn sample_identity(id: &str) -> ActorIdentity {
111        ActorIdentity::builder()
112            .actor_id(id)
113            .actor_type(ActorType::BimlDirector)
114            .signing_key(SigningKeyHandle::Software {
115                key_id: "k".into(),
116                algorithm: "Ed25519".into(),
117            })
118            .build()
119            .unwrap()
120    }
121
122    #[test]
123    fn in_memory_store_round_trip() {
124        let store = IdentityStore::in_memory();
125        let id = sample_identity("director-1");
126        store.register(id.clone()).unwrap();
127        let recovered = store.lookup("director-1").unwrap();
128        assert_eq!(recovered.actor_id, "director-1");
129        assert_eq!(recovered.actor_type, ActorType::BimlDirector);
130    }
131
132    #[test]
133    fn duplicate_register_fails() {
134        let store = IdentityStore::in_memory();
135        let id = sample_identity("director-1");
136        store.register(id.clone()).unwrap();
137        let result = store.register(id);
138        assert!(matches!(result, Err(IdentityError::AlreadyExists(_))));
139    }
140
141    #[test]
142    fn lookup_missing_fails() {
143        let store = IdentityStore::in_memory();
144        let result = store.lookup("nobody");
145        assert!(matches!(result, Err(IdentityError::NotFound(_))));
146    }
147
148    #[test]
149    fn revoke_removes_actor() {
150        let store = IdentityStore::in_memory();
151        store.register(sample_identity("director-1")).unwrap();
152        store.revoke("director-1").unwrap();
153        let result = store.lookup("director-1");
154        assert!(result.is_err());
155    }
156}