eidetica/user/types.rs
1//! Core data types for the user 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/// User information stored in _users database
16///
17/// Users are stored in a Table with auto-generated UUID primary keys.
18/// The username field is used for login and must be unique.
19///
20/// Passwordless users have None for password fields.
21#[derive(Clone, Debug, Serialize, Deserialize)]
22pub struct UserInfo {
23 /// Unique username (login identifier)
24 pub username: String,
25
26 /// ID of the user's private database
27 pub user_database_id: ID,
28
29 /// Password hash (using Argon2id)
30 /// None for passwordless users
31 pub password_hash: Option<String>,
32
33 /// Salt for password hashing (base64 encoded string)
34 /// None for passwordless users
35 pub password_salt: Option<String>,
36
37 /// User account creation timestamp (Unix timestamp)
38 pub created_at: i64,
39
40 /// Account status
41 pub status: UserStatus,
42}
43
44/// User account status
45#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
46pub enum UserStatus {
47 Active,
48 Disabled,
49 Locked,
50}
51
52/// User profile stored in user's private database
53#[derive(Clone, Debug, Serialize, Deserialize)]
54pub struct UserProfile {
55 /// Username
56 pub username: String,
57
58 /// Display name
59 pub display_name: Option<String>,
60
61 /// Email or other contact info
62 pub contact_info: Option<String>,
63
64 /// User preferences
65 pub preferences: UserPreferences,
66}
67
68/// User-specific preferences
69#[derive(Clone, Debug, Serialize, Deserialize, Default)]
70pub struct UserPreferences {
71 /// Default sync tracked databases
72 pub default_sync_enabled: bool,
73
74 /// Other user-specific settings
75 pub properties: HashMap<String, String>,
76}
77
78/// How a user's private key is stored
79///
80/// Encrypted keys store AES-256-GCM ciphertext of a prefixed-string-encoded `PrivateKey`.
81/// Unencrypted keys store the `PrivateKey` directly (passwordless users only).
82#[derive(Clone, Debug, Serialize, Deserialize)]
83#[serde(tag = "type", rename_all = "lowercase")]
84pub enum KeyStorage {
85 /// Key is encrypted with a password-derived key (AES-256-GCM)
86 Encrypted {
87 /// Encryption algorithm identifier
88 algorithm: String,
89 /// Encrypted prefixed-string-encoded PrivateKey
90 ciphertext: Vec<u8>,
91 /// Encryption nonce/IV (12 bytes for AES-GCM)
92 nonce: Vec<u8>,
93 },
94 /// Key is stored unencrypted (passwordless users only)
95 Unencrypted {
96 /// PrivateKey stored directly — serde carries the signing algorithm tag
97 key: PrivateKey,
98 },
99}
100
101/// User's private key with database mappings
102///
103/// Keys can be either encrypted (for password-protected users) or
104/// unencrypted (for passwordless single-user mode).
105#[derive(Clone, Debug, Serialize, Deserialize)]
106pub struct UserKey {
107 /// Key identifier (the public key)
108 pub key_id: PublicKey,
109
110 /// Key storage (encrypted ciphertext or plaintext PrivateKey)
111 pub storage: KeyStorage,
112
113 /// Display name for this key
114 pub display_name: Option<String>,
115
116 /// When this key was created (Unix timestamp)
117 pub created_at: i64,
118
119 /// Last time this key was used (Unix timestamp)
120 pub last_used: Option<i64>,
121
122 /// Whether this is the user's default key, which has admin access on the user's DB
123 /// Only one key should be marked as default at a time
124 pub is_default: bool,
125
126 /// Database-specific SigKey mappings
127 /// Maps: Database ID → SigKey identity for that database
128 ///
129 /// `None` = default identity (pubkey derived from this key's `key_id`)
130 /// `Some(sigkey)` = non-default identity (global wildcard, name-based, delegation)
131 pub database_sigkeys: HashMap<ID, Option<SigKey>>,
132}
133
134/// A database tracked by a user.
135///
136/// Stored in the user's private database "databases" Table.
137/// Records which databases the user has added to their list, along with
138/// which key to use and sync preferences.
139#[derive(Clone, Debug, Serialize, Deserialize)]
140pub struct TrackedDatabase {
141 /// Database ID
142 pub database_id: ID,
143
144 /// Which user key to use for this database
145 pub key_id: PublicKey,
146
147 /// Sync preferences for this database
148 pub sync_settings: SyncSettings,
149}
150
151/// Synchronization settings for a database
152///
153/// Per-user-per-database sync configuration.
154/// Different users may have different sync preferences for the same database.
155///
156/// # Construction
157///
158/// Use factory methods for common configurations:
159/// - `SyncSettings::disabled()` — sync disabled (this is the `Default`)
160/// - `SyncSettings::enabled()` — sync enabled, no sync on commit
161/// - `SyncSettings::on_commit()` — sync enabled with sync on commit on every transaction
162///
163/// Chain `.with_interval()` to override the periodic sync interval:
164/// ```
165/// # use eidetica::user::types::SyncSettings;
166/// let settings = SyncSettings::enabled().with_interval(60);
167/// ```
168#[derive(Clone, Debug, Serialize, Deserialize)]
169pub struct SyncSettings {
170 /// Whether user wants to sync this database
171 pub sync_enabled: bool,
172
173 /// Sync on commit
174 /// Whether to sync after every commit
175 pub sync_on_commit: bool,
176
177 /// Sync interval in seconds (for periodic sync).
178 /// `None` uses the background sync default interval.
179 pub interval_seconds: Option<u64>,
180
181 /// Additional sync configuration
182 pub properties: HashMap<String, String>,
183}
184
185impl Default for SyncSettings {
186 fn default() -> Self {
187 Self::disabled()
188 }
189}
190
191impl SyncSettings {
192 /// Sync disabled. This is the `Default`.
193 pub fn disabled() -> Self {
194 Self {
195 sync_enabled: false,
196 sync_on_commit: false,
197 interval_seconds: None,
198 properties: HashMap::new(),
199 }
200 }
201
202 /// Sync enabled, no sync on commit.
203 pub fn enabled() -> Self {
204 Self {
205 sync_enabled: true,
206 ..Self::disabled()
207 }
208 }
209
210 /// Sync enabled with sync on commit on every transaction.
211 pub fn on_commit() -> Self {
212 Self {
213 sync_enabled: true,
214 sync_on_commit: true,
215 ..Self::disabled()
216 }
217 }
218
219 /// Set periodic sync interval in seconds.
220 pub fn with_interval(mut self, seconds: u64) -> Self {
221 self.interval_seconds = Some(seconds);
222 self
223 }
224}
225
226/// Database tracking information in _databases table
227#[derive(Clone, Debug, Serialize, Deserialize)]
228pub struct DatabaseTracking {
229 /// Database ID
230 pub database_id: ID,
231
232 /// Cached database name (for quick lookup)
233 pub name: Option<String>,
234
235 /// User UUIDs who have this database in their preferences
236 /// (stores internal UUIDs, not usernames)
237 pub users: Vec<String>,
238
239 /// Database creation time (Unix timestamp)
240 pub created_at: i64,
241
242 /// Last modification time (Unix timestamp)
243 pub last_modified: i64,
244
245 /// Additional metadata
246 pub metadata: HashMap<String, String>,
247}