Development Documentation (main branch) - For stable release docs, see docs.rs/eidetica
Skip to main content

eidetica/account/
signer.rs

1//! Immutable acting handles, independent of bundle storage and login names.
2
3use crate::{
4    Database, ID, Instance, Result,
5    auth::{SigKey, crypto::PublicKey},
6    database::DatabaseKey,
7};
8
9/// An Account locator paired with one actual signer and explicit Auth identity.
10///
11/// The locator is organizational context, never authorization. A direct key
12/// needs no Identity database. Delegated use passes the exact `DatabaseKey`
13/// identity produced by the Identity API; no referenced Identity or default
14/// signer is implicitly selected. Constructing another handle never rebinds
15/// this one, nor does dropping a bundle invalidate a retained signer handle.
16#[derive(Clone, Debug)]
17pub struct AccountSigner {
18    account_id: ID,
19    key: DatabaseKey,
20}
21
22impl AccountSigner {
23    /// Bind an explicit signer and Account locator. This performs no storage
24    /// lookup, authorization grant, key import, or daemon login.
25    pub fn new(account_id: ID, key: impl Into<DatabaseKey>) -> Self {
26        let (signer, mut identity) = key.into().into_parts();
27        // A name-only hint must not fall back to the service connection's login
28        // key. Narrow the hint to this actual signer, without changing its path.
29        if identity.hint().pubkey.is_none() {
30            identity.hint_mut().pubkey = Some(signer.public_key());
31        }
32        Self {
33            account_id,
34            key: DatabaseKey::with_identity(signer, identity),
35        }
36    }
37
38    pub(crate) fn signing_key(&self) -> &crate::auth::crypto::PrivateKey {
39        self.key.signing_key()
40    }
41
42    pub fn account_id(&self) -> &ID {
43        &self.account_id
44    }
45    pub fn key_id(&self) -> PublicKey {
46        self.key.public_key()
47    }
48    pub fn identity(&self) -> &SigKey {
49        self.key.identity()
50    }
51
52    /// Return a newly bound handle using this actual key and an explicit Auth
53    /// identity. The old handle retains its original identity and signer.
54    pub fn with_identity(&self, identity: SigKey) -> Self {
55        Self::new(
56            self.account_id.clone(),
57            DatabaseKey::with_identity(self.key.signing_key().clone(), identity),
58        )
59    }
60
61    /// Open an already-initialized instance and prove signer possession to a
62    /// service without fetching or registering a daemon-held login bundle.
63    /// Embedded connections use the same signer API without a wire handshake.
64    /// Each call creates its own connection; no existing connection is rebound.
65    pub async fn connect(&self, url: impl AsRef<str>) -> Result<Instance> {
66        let instance = Instance::connect(url).await?;
67        #[cfg(all(unix, feature = "service"))]
68        if let Some(connection) = instance.remote_connection() {
69            connection
70                .authenticate_signer(self.key.signing_key())
71                .await?;
72        }
73        Ok(instance)
74    }
75
76    /// Open this locator with this signer under ordinary Account Auth.
77    pub async fn open_account(&self, instance: &Instance) -> Result<super::Account> {
78        self.open_database(instance, &self.account_id)
79            .await
80            .map(super::Account::new)
81    }
82
83    /// Open exactly this target with this actual signer/identity. A locator or
84    /// reference is not a grant; target Auth and proof validation are unchanged.
85    pub async fn open_database(&self, instance: &Instance, root_id: &ID) -> Result<Database> {
86        if self.key.identity().hint().pubkey.as_ref() != Some(&self.key.public_key()) {
87            return Err(crate::auth::AuthError::InvalidAuthConfiguration {
88                reason: "Signer does not match the Auth identity public-key hint".to_owned(),
89            }
90            .into());
91        }
92        #[cfg(all(unix, feature = "service"))]
93        if let Some(connection) = instance.remote_connection() {
94            connection
95                .register_session_key(self.key.signing_key())
96                .await?;
97            return Ok(Database::open_remote(
98                instance,
99                connection.with_signer(self.key.signing_key().clone()),
100                root_id,
101                self.key.identity().clone(),
102            )
103            .await?
104            .with_key(self.key.clone()));
105        }
106        let database = Database::open(instance, root_id)
107            .await?
108            .with_key(self.key.clone());
109        // Local opening alone is a handle construction. Check the selected Auth
110        // path as the remote open gate does; do not return a fallback identity.
111        database.current_permission().await?;
112        Ok(database)
113    }
114}