eidetica/account/types.rs
1//! Core data types for the account system
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::{
8 auth::{
9 SigKey,
10 crypto::{PrivateKey, PublicKey},
11 },
12 entry::ID,
13};
14
15/// Local login anchor: an explicit Account locator/key bundle and its login key.
16/// The public key is cross-checked against the unlocked authenticated payload.
17#[derive(Clone, Debug, Serialize, Deserialize)]
18#[serde(deny_unknown_fields)]
19pub struct AccountCredentials {
20 pub root_key_id: PublicKey,
21 pub bundle: KeyStorage,
22}
23
24impl AccountCredentials {
25 pub(crate) fn unlock(
26 &self,
27 account_id: &ID,
28 password: &str,
29 ) -> crate::Result<super::UnlockedCredentials> {
30 let unlocked = self.bundle.unlock(account_id, password)?;
31 unlocked.signer(&self.root_key_id)?;
32 Ok(unlocked)
33 }
34}
35
36/// Instance-local login registration stored outside all Entry DAGs.
37///
38/// The Table UUID and `username` identify a login on this instance, not the
39/// Account itself. `account_id` identifies the ordinary private root database.
40/// Every login uses the authenticated CredentialBundle format, including empty passwords.
41/// This is not a portable export or recovery backup.
42#[derive(Clone, Debug, Serialize, Deserialize)]
43pub struct AccountInfo {
44 /// Unique username (login identifier)
45 pub username: String,
46
47 /// ID of the account's private database
48 #[serde(rename = "user_database_id")]
49 pub account_id: ID,
50
51 /// Credentials containing the account's root signing key
52 pub credentials: AccountCredentials,
53
54 /// Account creation timestamp (Unix timestamp)
55 pub created_at: i64,
56
57 /// Account status
58 pub status: AccountStatus,
59}
60
61/// Account status
62#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
63pub enum AccountStatus {
64 Active,
65 Disabled,
66 Locked,
67}
68
69/// Account profile stored in account's private database
70#[derive(Clone, Debug, Serialize, Deserialize)]
71pub struct AccountProfile {
72 /// Username
73 pub username: String,
74
75 /// Display name
76 pub display_name: Option<String>,
77
78 /// Email or other contact info
79 pub contact_info: Option<String>,
80
81 /// Account preferences
82 pub preferences: AccountPreferences,
83}
84
85/// Account-specific preferences
86#[derive(Clone, Debug, Serialize, Deserialize, Default)]
87pub struct AccountPreferences {
88 /// Default sync tracked databases
89 pub default_sync_enabled: bool,
90
91 /// Other account-specific settings
92 pub properties: HashMap<String, String>,
93}
94
95/// Installation-local storage of one authenticated encrypted credential bundle.
96/// Every password string, including empty, uses the same format and KDF.
97#[derive(Clone, Debug, Serialize, Deserialize)]
98#[serde(tag = "type", rename_all = "lowercase", deny_unknown_fields)]
99pub enum KeyStorage {
100 Encrypted { bundle: super::CredentialBundle },
101}
102
103impl KeyStorage {
104 pub(crate) fn seal(
105 account_id: &ID,
106 keys: &[PrivateKey],
107 password: &str,
108 ) -> crate::Result<Self> {
109 Ok(Self::Encrypted {
110 bundle: super::CredentialBundle::seal(account_id, keys, password)?,
111 })
112 }
113
114 pub(crate) fn unlock(
115 &self,
116 account_id: &ID,
117 password: &str,
118 ) -> crate::Result<super::UnlockedCredentials> {
119 let Self::Encrypted { bundle } = self;
120 bundle.unlock_for(account_id, password)
121 }
122}
123
124/// Public installation-local key metadata and database lookup hints.
125/// Private keys live together in the authenticated credential bundle, not here.
126#[derive(Clone, Debug, Serialize, Deserialize)]
127#[serde(deny_unknown_fields)]
128pub struct AccountKey {
129 /// Key identifier (the public key)
130 pub key_id: PublicKey,
131
132 /// Display name for this key
133 pub display_name: Option<String>,
134
135 /// When this key was created (Unix timestamp)
136 pub created_at: i64,
137
138 /// Last time this key was used (Unix timestamp)
139 pub last_used: Option<i64>,
140
141 /// Whether this is the account's default key, which has admin access on the account's DB
142 /// Only one key should be marked as default at a time
143 pub is_default: bool,
144
145 /// Database-specific SigKey mappings
146 /// Maps: Database ID → SigKey identity for that database
147 ///
148 /// `None` = default identity (pubkey derived from this key's `key_id`)
149 /// `Some(sigkey)` = non-default identity (global wildcard, name-based, delegation)
150 pub database_sigkeys: HashMap<ID, Option<SigKey>>,
151}
152
153/// A database tracked by an account.
154///
155/// Stored in this installation's native account-state record.
156/// Records which databases the account has added to their list, along with
157/// which key to use and sync preferences.
158#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
159pub struct TrackedDatabase {
160 /// Database ID
161 pub database_id: ID,
162
163 /// Which account key to use for this database
164 pub key_id: PublicKey,
165
166 /// Sync preferences for this database
167 pub sync_settings: SyncSettings,
168}
169
170/// Synchronization settings for a database
171///
172/// Per-account-per-database sync configuration.
173/// Different accounts may have different sync preferences for the same database.
174///
175/// # Construction
176///
177/// Use factory methods for common configurations:
178/// - `SyncSettings::disabled()` — sync disabled (this is the `Default`)
179/// - `SyncSettings::enabled()` — sync enabled, no sync on commit
180/// - `SyncSettings::on_commit()` — sync enabled with sync on commit on every transaction
181///
182/// Chain `.with_interval()` to override the periodic sync interval:
183/// ```
184/// # use eidetica::account::types::SyncSettings;
185/// let settings = SyncSettings::enabled().with_interval(60);
186/// ```
187#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
188pub struct SyncSettings {
189 /// Whether account wants to sync this database
190 pub sync_enabled: bool,
191
192 /// Sync on commit
193 /// Whether to sync after every commit
194 pub sync_on_commit: bool,
195
196 /// Sync interval in seconds (for periodic sync).
197 /// `None` uses the background sync default interval.
198 pub interval_seconds: Option<u64>,
199
200 /// Additional sync configuration
201 pub properties: HashMap<String, String>,
202}
203
204impl Default for SyncSettings {
205 fn default() -> Self {
206 Self::disabled()
207 }
208}
209
210impl SyncSettings {
211 /// Sync disabled. This is the `Default`.
212 pub fn disabled() -> Self {
213 Self {
214 sync_enabled: false,
215 sync_on_commit: false,
216 interval_seconds: None,
217 properties: HashMap::new(),
218 }
219 }
220
221 /// Sync enabled, no sync on commit.
222 pub fn enabled() -> Self {
223 Self {
224 sync_enabled: true,
225 ..Self::disabled()
226 }
227 }
228
229 /// Sync enabled with sync on commit on every transaction.
230 pub fn on_commit() -> Self {
231 Self {
232 sync_enabled: true,
233 sync_on_commit: true,
234 ..Self::disabled()
235 }
236 }
237
238 /// Set periodic sync interval in seconds.
239 pub fn with_interval(mut self, seconds: u64) -> Self {
240 self.interval_seconds = Some(seconds);
241 self
242 }
243}
244
245/// Database tracking information in _databases table
246#[derive(Clone, Debug, Serialize, Deserialize)]
247pub struct DatabaseTracking {
248 /// Database ID
249 pub database_id: ID,
250
251 /// Cached database name (for quick lookup)
252 pub name: Option<String>,
253
254 /// AccountSession UUIDs who have this database in their preferences
255 /// (stores internal UUIDs, not usernames)
256 pub users: Vec<String>,
257
258 /// Database creation time (Unix timestamp)
259 pub created_at: i64,
260
261 /// Last modification time (Unix timestamp)
262 pub last_modified: i64,
263
264 /// Additional metadata
265 pub metadata: HashMap<String, String>,
266}