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

eidetica/user/
system_databases.rs

1//! System database initialization for the user system
2//!
3//! Creates and manages _users and _databases system databases.
4
5use handle_trait::Handle;
6
7use super::{
8    User,
9    crypto::{derive_encryption_key, encrypt_private_key},
10    errors::UserError,
11    key_manager::UserKeyManager,
12    types::{KeyStorage, UserCredentials, UserInfo, UserKey, UserStatus},
13};
14use crate::{
15    Database, Instance, Result,
16    auth::{
17        crypto::{PrivateKey, PublicKey, generate_keypair},
18        types::{AuthKey, Permission},
19    },
20    constants::{DATABASES, INSTANCE, USERS},
21    crdt::Doc,
22    store::Table,
23};
24
25/// Whether `_users.auth_settings` already lists an instance admin.
26///
27/// "Instance admin" here means any non-device-key entry with `Admin` permission
28/// in `_users`'s auth settings. The device key is `Admin(0)` on every system
29/// DB by construction (it bootstrapped them), so we exclude it explicitly —
30/// otherwise every fresh instance would look like it already had an admin.
31pub(crate) async fn has_instance_admin(
32    users_db: &Database,
33    device_pubkey: &PublicKey,
34) -> Result<bool> {
35    let tx = users_db.new_transaction().await?;
36    let settings = tx.get_settings()?;
37    let auth = settings.auth_snapshot().await?;
38    let device_pubkey_str = device_pubkey.to_string();
39    for (pubkey_str, key) in auth.get_all_keys()? {
40        if pubkey_str == device_pubkey_str {
41            continue;
42        }
43        if matches!(key.permissions(), Permission::Admin(_)) {
44            return Ok(true);
45        }
46    }
47    Ok(false)
48}
49
50/// Add `pubkey` as `Admin(0)` to every system database that gates instance-level
51/// admin operations.
52///
53/// Today that's `_users` (user directory + auth bootstrap) and `_databases`
54/// (instance-wide database registry). `_sync` is lazily created on
55/// `enable_sync()` and out of scope here.
56///
57/// The Database handles passed in must already be opened with a signing key
58/// that holds `Admin` on each respective DB. The first-admin bootstrap path
59/// (`create_user` below) passes device-keyed handles — `Database::create`
60/// registered the device key as `Admin(0)` on each system DB. The admin
61/// promotion path (`InstanceAdmin::grant_instance_admin`) passes handles keyed by an
62/// existing admin's own key; the same write then resolves against that
63/// admin's identity.
64pub(crate) async fn grant_admin_on_system_dbs(
65    users_db: &Database,
66    databases_db: &Database,
67    pubkey: &PublicKey,
68) -> Result<()> {
69    for database in [users_db, databases_db] {
70        database
71            .with_transaction(|tx| async move {
72                let settings = tx.get_settings()?;
73                settings
74                    .set_auth_key(pubkey, AuthKey::active(Some("admin"), Permission::Admin(0)))
75                    .await
76            })
77            .await?;
78    }
79    Ok(())
80}
81
82/// Create the _instance system database
83///
84/// This database stores Instance-level configuration and metadata.
85/// Auth is bootstrapped by `Database::create` with the device key as Admin(0).
86///
87/// # Arguments
88/// * `instance` - The Instance handle
89/// * `device_signing_key` - The device's Ed25519 signing key
90///
91/// # Returns
92/// The _instance Database
93pub async fn create_instance_database(
94    instance: &Instance,
95    device_signing_key: &PrivateKey,
96) -> Result<Database> {
97    let mut settings = Doc::new();
98    settings.set("name", INSTANCE);
99    settings.set("type", "system");
100    settings.set("description", "Instance configuration and management");
101
102    Database::create(instance, device_signing_key.clone(), settings).await
103}
104
105/// Create the _users system database
106///
107/// This database stores the user directory mapping user_id -> UserInfo.
108/// Auth is bootstrapped by `Database::create` with the device key as Admin(0).
109///
110/// # Arguments
111/// * `instance` - The Instance handle
112/// * `device_signing_key` - The device's Ed25519 signing key
113///
114/// # Returns
115/// The created _users Database
116pub async fn create_users_database(
117    instance: &Instance,
118    device_signing_key: &PrivateKey,
119) -> Result<Database> {
120    let mut settings = Doc::new();
121    settings.set("name", USERS);
122    settings.set("type", "system");
123    settings.set("description", "User directory database");
124
125    Database::create(instance, device_signing_key.clone(), settings).await
126}
127
128/// Create the _databases tracking database
129///
130/// This database stores the database tracking information mapping
131/// database_id -> DatabaseTracking.
132/// Auth is bootstrapped by `Database::create` with the device key as Admin(0).
133///
134/// # Arguments
135/// * `instance` - The Instance handle
136/// * `device_signing_key` - The device's Ed25519 signing key
137///
138/// # Returns
139/// The created _databases Database
140pub async fn create_databases_tracking(
141    instance: &Instance,
142    device_signing_key: &PrivateKey,
143) -> Result<Database> {
144    let mut settings = Doc::new();
145    settings.set("name", DATABASES);
146    settings.set("type", "system");
147    settings.set("description", "Database tracking and registry");
148
149    Database::create(instance, device_signing_key.clone(), settings).await
150}
151
152/// Create a new user account
153///
154/// This function:
155/// 1. Optionally hashes the user's password (if provided)
156/// 2. Generates a device keypair for the user
157/// 3. Creates a user database for storing keys (encrypted or unencrypted)
158/// 4. Creates UserInfo and stores it in _users database with auto-generated UUID
159/// 5. **First-admin bootstrap**: if no instance admin exists yet in `_users.auth_settings`,
160///    promotes this user by adding their pubkey as `Admin(0)` to the
161///    instance-admin system DBs (`_users` and `_databases`). Subsequent
162///    users land as non-admins.
163///
164/// # Arguments
165/// * `users_db` - The _users system database
166/// * `instance` - The Instance handle
167/// * `username` - Unique username for login
168/// * `password` - Optional password. If None, creates passwordless user (instant login, no encryption)
169///
170/// # Returns
171/// A tuple of `(user_uuid, UserInfo, root_private_key)`. The private key is
172/// the one just generated for this user; callers (e.g.
173/// [`Instance::create_backend`](crate::Instance::create_backend) or
174/// [`InstanceAdmin::create_user`](crate::user::InstanceAdmin::create_user))
175/// can hand it straight to [`build_user_session`] to materialise a logged-in
176/// [`User`](crate::user::User) without re-decrypting the key from storage.
177pub async fn create_user(
178    users_db: &Database,
179    instance: &Instance,
180    username: impl AsRef<str>,
181    password: Option<&str>,
182) -> Result<(String, UserInfo, crate::auth::crypto::PrivateKey)> {
183    let username = username.as_ref();
184    // FIXME: Race condition - multiple concurrent creates with same username
185    // can both succeed, creating duplicate users. This requires either:
186    // 1. Distributed locking mechanism
187    // 2. Backend-level unique constraints
188    // 3. Periodic cleanup/reconciliation process
189    // For now, duplicate detection happens at login time.
190
191    // Check if username already exists
192    let users_table = users_db
193        .get_store_viewer::<Table<UserInfo>>("users")
194        .await?;
195    let existing = users_table.search(|u| u.username == username).await?;
196    if !existing.is_empty() {
197        return Err(UserError::UsernameAlreadyExists {
198            username: username.to_string(),
199        }
200        .into());
201    }
202
203    // 1. Generate default keypair for this user
204    let (user_private_key, user_public_key) = generate_keypair();
205    // Keep a clone for the caller — the passwordless branch below moves
206    // `user_private_key` into `KeyStorage::Unencrypted`, so we hand back a
207    // detached copy. Ed25519 keys are 32 bytes; the clone is negligible.
208    let returned_private_key = user_private_key.clone();
209
210    // 2. Create user database with the user's key as owner (Admin(0))
211    let mut user_db_settings = Doc::new();
212    user_db_settings.set("name", format!("_user_{username}"));
213    user_db_settings.set("type", "user");
214    user_db_settings.set("description", format!("User database for {username}"));
215
216    let user_database =
217        Database::create(instance, user_private_key.clone(), user_db_settings).await?;
218    let user_database_id = user_database.root_id().clone();
219
220    // The two historical follow-up writes on the user's tree — granting the
221    // device key `Read` on `_settings.auth`, and persisting the root `UserKey`
222    // metadata into the `keys` table — are deferred to first login. The
223    // structural reason: this function may run over a remote (admin's)
224    // session whose `session_pubkey` is *not* a member of the new user's
225    // tree, so `Transaction::commit`'s reads on that tree would be denied by
226    // the server-side gate (gating is by connection session_pubkey, not by
227    // request identity hint). Deferring is also functionally correct: the
228    // device-Read grant only matters once the daemon needs to sync the
229    // user's tree on the user's behalf, and the `keys` row is layered on top
230    // of the root key already carried in `UserInfo.credentials` (which is
231    // the durable source of truth and reaches the user via `_users`).
232    // `build_user_session` performs the idempotent bootstrap on first login.
233    let device_pubkey = instance.id();
234
235    // 3. Build UserCredentials — encrypt root key if password provided
236    let (root_key, password_salt) = match password {
237        Some(pwd) => {
238            let salt_string = super::crypto::generate_salt();
239            let encryption_key = derive_encryption_key(pwd, &salt_string)?;
240            let (ciphertext, nonce) = encrypt_private_key(&user_private_key, &encryption_key)?;
241            (
242                KeyStorage::Encrypted {
243                    algorithm: "aes-256-gcm".to_string(),
244                    ciphertext,
245                    nonce,
246                },
247                Some(salt_string),
248            )
249        }
250        None => (
251            KeyStorage::Unencrypted {
252                key: user_private_key,
253            },
254            None,
255        ),
256    };
257
258    let credentials = UserCredentials {
259        root_key_id: user_public_key.clone(),
260        root_key,
261        password_salt,
262    };
263
264    // 4. Create UserInfo
265    let user_info = UserInfo {
266        username: username.to_string(),
267        user_database_id,
268        credentials,
269        created_at: instance.clock().now_secs(),
270        status: UserStatus::Active,
271    };
272
273    // 5. Store UserInfo in _users database with auto-generated UUID
274    let tx = users_db.new_transaction().await?;
275    let users_table = tx.get_store::<Table<UserInfo>>("users").await?;
276    let user_uuid = users_table.insert(user_info.clone()).await?; // Generate UUID primary key
277    tx.commit().await?;
278
279    // 6. First-admin bootstrap: if no instance admin exists yet, promote this
280    // user. Done last so that a failure here doesn't leave behind a partially
281    // created user. The auth-settings write itself is signed by the device
282    // key (the only Admin on the system DBs at bootstrap time); opening
283    // `databases_db()` here mirrors the `users_db` argument passed in and
284    // keeps the device-key signing detail inside `Instance`.
285    if !has_instance_admin(users_db, &device_pubkey).await? {
286        let databases_db = instance.databases_db().await?;
287        grant_admin_on_system_dbs(users_db, &databases_db, &user_public_key).await?;
288    }
289
290    Ok((user_uuid, user_info, returned_private_key))
291}
292
293/// Login a user
294///
295/// This function:
296/// 1. Searches for user by username in _users database
297/// 2. Verifies password (if provided and required)
298/// 3. Opens user's private database
299/// 4. Loads and decrypts user keys (or loads unencrypted for passwordless users)
300/// 5. Creates UserKeyManager with keys
301/// 6. Returns User session object
302///
303/// # Arguments
304/// * `users_db` - The _users system database
305/// * `instance` - The Instance handle
306/// * `username` - Username for login
307/// * `password` - Optional password. None for passwordless users.
308///
309/// # Returns
310/// A User session object with keys loaded
311pub async fn login_user(
312    users_db: &Database,
313    instance: &Instance,
314    username: impl AsRef<str>,
315    password: Option<&str>,
316) -> Result<super::User> {
317    let username = username.as_ref();
318
319    // 1. Search for user by username
320    let users_table = users_db
321        .get_store_viewer::<Table<UserInfo>>("users")
322        .await?;
323    let results = users_table.search(|u| u.username == username).await?;
324
325    // Check for duplicate users (race condition detection)
326    let (user_uuid, user_info) = match results.len() {
327        0 => {
328            return Err(UserError::UserNotFound {
329                username: username.to_string(),
330            }
331            .into());
332        }
333        1 => results.into_iter().next().unwrap(),
334        count => {
335            // FIXME: Multiple users with same username detected!
336            // This indicates the race condition occurred during user creation.
337            // Resolution requires manual intervention or automated cleanup.
338            return Err(UserError::DuplicateUsersDetected {
339                username: username.to_string(),
340                count,
341            }
342            .into());
343        }
344    };
345
346    // Check if user is disabled
347    if user_info.status != UserStatus::Active {
348        return Err(UserError::UserDisabled {
349            username: username.to_string(),
350        }
351        .into());
352    }
353
354    // 2. Verify password compatibility and construct root UserKey from credentials
355    let creds = &user_info.credentials;
356    let is_passwordless = creds.password_salt.is_none();
357    match (password, is_passwordless) {
358        (Some(_), false) => {
359            // Password provided for password-protected user: OK
360            // Decryption of the root key IS password verification
361        }
362        (None, true) => {
363            // No password for passwordless user: OK
364        }
365        (Some(_), true) => {
366            // Password provided for passwordless user: reject
367            return Err(UserError::InvalidPassword.into());
368        }
369        (None, false) => {
370            // No password for password-protected user: reject
371            return Err(UserError::PasswordRequired {
372                operation: "login for password-protected user".to_string(),
373            }
374            .into());
375        }
376    }
377
378    // 3. Decrypt root signing key from credentials (decryption IS password verification)
379    let root_signing_key = match (&creds.root_key, password) {
380        (
381            KeyStorage::Encrypted {
382                ciphertext, nonce, ..
383            },
384            Some(pwd),
385        ) => {
386            let salt = creds
387                .password_salt
388                .as_ref()
389                .ok_or_else(|| UserError::PasswordRequired {
390                    operation: "decrypt keys for password-protected user".to_string(),
391                })?;
392            let encryption_key = derive_encryption_key(pwd, salt)?;
393            super::crypto::decrypt_private_key(ciphertext, nonce, &encryption_key)?
394        }
395        (KeyStorage::Unencrypted { key }, None) => key.clone(),
396        _ => return Err(UserError::InvalidPassword.into()),
397    };
398
399    let user =
400        build_user_session(instance, &user_uuid, &user_info, root_signing_key, password).await?;
401
402    // Last-login bookkeeping. Best-effort: skip the write on remote instances
403    // (no local device key to sign with — the daemon owns this side) and
404    // downgrade any other failure to a debug log rather than blocking login.
405    // The last_login table is treated as a low-priority access log (per the
406    // older TODO noting it grows unbounded and should move to a log table);
407    // a missed entry is acceptable, a failed login over a transient table
408    // error would not be.
409    let now = instance.clock().now_secs();
410    let user_uuid_ref = user_uuid.clone();
411    if let Err(e) = users_db
412        .with_transaction(|tx| async move {
413            let last_login_table = tx.get_store::<Table<i64>>("last_login").await?;
414            last_login_table.set(&user_uuid_ref, now).await
415        })
416        .await
417    {
418        tracing::debug!("skipping last_login update for {username}: {e}");
419    }
420
421    Ok(user)
422}
423
424/// Build a `User` session from data already in hand: the decrypted root
425/// signing key, the user's record, and an optional password (the
426/// `UserKeyManager` re-derives the KEK from it for any per-key decryption).
427///
428/// Used by both the local path (`login_user`, after it reads `_users` and
429/// decrypts the root key from credentials) and the remote path
430/// (`Instance::login_user` over a service connection, after `trusted_login`
431/// has already shipped the `UserInfo` and decrypted the root key on the
432/// client). Keeping a single helper means new keys/state added to the User
433/// session are picked up on both paths without divergence.
434///
435/// Does NOT touch `_users` — that's the caller's responsibility on the local
436/// path (last-login bookkeeping). The remote path skips that intentionally:
437/// the daemon would be the natural place to record it, and burning a wire
438/// write per login is not worth it before the audit-log work lands.
439pub(crate) async fn build_user_session(
440    instance: &Instance,
441    user_uuid: &str,
442    user_info: &UserInfo,
443    root_signing_key: crate::auth::crypto::PrivateKey,
444    password: Option<&str>,
445) -> Result<super::User> {
446    // 1. Open user database authenticated with decrypted root key.
447    let user_database = Database::open(instance, &user_info.user_database_id)
448        .await?
449        .with_key(root_signing_key);
450
451    // 2. Load all keys from user database into key manager.
452    // TODO: load keys lazily
453    let keys_table = user_database
454        .get_store_viewer::<Table<UserKey>>("keys")
455        .await?;
456    let mut all_keys: Vec<UserKey> = keys_table
457        .search(|_| true)
458        .await?
459        .into_iter()
460        .map(|(_, key)| key)
461        .collect();
462
463    // The root key may not yet be persisted in the user-tree `keys` table —
464    // `create_user` no longer writes it there (the write would require reads
465    // on a tree the creator's session isn't a member of). `UserInfo.credentials`
466    // in `_users` is the durable source of truth; synthesize the in-memory
467    // `UserKey` from it when the table is missing the row. The durable write
468    // happens at `bootstrap_user_tree_if_needed` below, signed by the user.
469    let root_in_table = all_keys
470        .iter()
471        .any(|k| k.key_id == user_info.credentials.root_key_id);
472    if !root_in_table {
473        all_keys.push(UserKey {
474            key_id: user_info.credentials.root_key_id.clone(),
475            storage: user_info.credentials.root_key.clone(),
476            display_name: Some("Root Key".to_string()),
477            created_at: user_info.created_at,
478            last_used: None,
479            is_default: true,
480            database_sigkeys: std::collections::HashMap::new(),
481        });
482    }
483
484    let key_manager = if let Some(pwd) = password {
485        let salt = user_info
486            .credentials
487            .password_salt
488            .as_ref()
489            .ok_or(UserError::InvalidPassword)?;
490        UserKeyManager::new(pwd, salt, all_keys)?
491    } else {
492        UserKeyManager::new_passwordless(all_keys)?
493    };
494
495    // 3. First-login (and recovery) bootstrap on the user's own tree.
496    // Idempotent: each check short-circuits on subsequent logins. Best
497    // effort — failure here is logged and the login proceeds, because the
498    // in-memory `key_manager` already has the root key and the next login
499    // will retry on the same conditions.
500    if let Err(e) =
501        bootstrap_user_tree_if_needed(instance, &user_database, user_info, root_in_table).await
502    {
503        tracing::debug!(
504            user = %user_info.username,
505            error = %e,
506            "user-tree first-login bootstrap skipped"
507        );
508    }
509
510    Ok(User::new(
511        user_uuid.to_string(),
512        user_info.clone(),
513        user_database,
514        instance.handle(),
515        key_manager,
516    ))
517}
518
519/// Idempotently populate first-login state on the user's own tree.
520///
521/// Two writes, both Admin(0)-only and signed by the user (who is Admin(0)
522/// via `Database::create`'s genesis):
523///
524/// - **`keys` table** — persist the root `UserKey` row that mirrors
525///   `UserInfo.credentials`, so `User::track_database` and other writes
526///   that update per-database SigKey mappings have a row to update.
527/// - **`_settings.auth`** — grant this instance's device key `Read`, so the
528///   daemon can sync the user's tree on the user's behalf.
529///
530/// Each is gated on a pre-check via the read-only viewer; on a fresh login
531/// both checks miss and a single transaction commits both writes, on every
532/// subsequent login both checks hit and the function returns without
533/// touching the tree.
534async fn bootstrap_user_tree_if_needed(
535    instance: &Instance,
536    user_database: &Database,
537    user_info: &UserInfo,
538    root_already_persisted: bool,
539) -> Result<()> {
540    let device_pubkey = instance.id();
541
542    // Check whether the device key already has an auth entry on _settings.
543    // `get_settings()` builds a read-only transaction; `get_auth_key` returns
544    // `Err` if the key isn't present.
545    let settings_viewer = user_database.get_settings().await?;
546    let device_already_granted = settings_viewer.get_auth_key(&device_pubkey).await.is_ok();
547
548    if root_already_persisted && device_already_granted {
549        return Ok(());
550    }
551
552    // Capture by-value for the move into the closure.
553    let root_user_key = if root_already_persisted {
554        None
555    } else {
556        Some(UserKey {
557            key_id: user_info.credentials.root_key_id.clone(),
558            storage: user_info.credentials.root_key.clone(),
559            display_name: Some("Root Key".to_string()),
560            created_at: user_info.created_at,
561            last_used: None,
562            is_default: true,
563            database_sigkeys: std::collections::HashMap::new(),
564        })
565    };
566    let device_grant = if device_already_granted {
567        None
568    } else {
569        Some((
570            device_pubkey,
571            AuthKey::active(Some("device"), Permission::Read),
572        ))
573    };
574
575    user_database
576        .with_transaction(|tx| async move {
577            if let Some(row) = root_user_key {
578                let keys_table = tx.get_store::<Table<UserKey>>("keys").await?;
579                keys_table.insert(row).await?;
580            }
581            if let Some((pubkey, auth_key)) = device_grant {
582                let settings = tx.get_settings()?;
583                settings.set_auth_key(&pubkey, auth_key).await?;
584            }
585            Ok(())
586        })
587        .await?;
588
589    Ok(())
590}
591
592/// Look up a user's record by username, without requiring the password.
593///
594/// Used by the service daemon's challenge-response login flow: the daemon
595/// fetches the user's full `UserInfo` (including encrypted credentials and the
596/// user's private-database id) and ships it to the client so the client can
597/// derive the KEK locally, decrypt the root key, sign the challenge, and then
598/// build the `User` session entirely from the data already carried by the
599/// `TrustedLoginChallenge` response — no second wire read of `_users` is
600/// required. The encrypted blob is designed to survive at rest; shipping it
601/// over the local socket is the same trust boundary as filesystem read. See
602/// the Service Architecture doc § Trusted login threat model for the full
603/// rationale.
604///
605/// # Returns
606/// Tuple of `(user_uuid, UserInfo)` if the user exists and is active.
607pub async fn lookup_user_record(
608    users_db: &Database,
609    username: impl AsRef<str>,
610) -> Result<(String, UserInfo)> {
611    let username = username.as_ref();
612    let users_table = users_db
613        .get_store_viewer::<Table<UserInfo>>("users")
614        .await?;
615    let results = users_table.search(|u| u.username == username).await?;
616
617    let (user_uuid, user_info) = match results.len() {
618        0 => {
619            return Err(UserError::UserNotFound {
620                username: username.to_string(),
621            }
622            .into());
623        }
624        1 => results.into_iter().next().unwrap(),
625        count => {
626            return Err(UserError::DuplicateUsersDetected {
627                username: username.to_string(),
628                count,
629            }
630            .into());
631        }
632    };
633
634    if user_info.status != UserStatus::Active {
635        return Err(UserError::UserDisabled {
636            username: username.to_string(),
637        }
638        .into());
639    }
640
641    Ok((user_uuid, user_info))
642}
643
644/// List all users in the system
645///
646/// # Arguments
647/// * `users_db` - The _users system database
648///
649/// # Returns
650/// Vector of usernames
651pub async fn list_users(users_db: &Database) -> Result<Vec<String>> {
652    let users_table = users_db
653        .get_store_viewer::<Table<UserInfo>>("users")
654        .await?;
655    let users: Vec<UserInfo> = users_table
656        .search(|_| true)
657        .await? // Get all users
658        .into_iter()
659        .map(|(_, user)| user)
660        .collect();
661    Ok(users.into_iter().map(|u| u.username).collect())
662}
663
664#[cfg(test)]
665mod tests {
666    use super::*;
667    use crate::Instance;
668    use crate::backend::database::InMemory;
669    use crate::store::DocStore;
670    use crate::store::SettingsStore;
671
672    use std::sync::Arc;
673
674    /// Test helper: Create Instance with `admin` as the initial user and a
675    /// pinned clock, returning the instance, its device signing key, and the
676    /// just-bootstrapped admin User session.
677    ///
678    /// Tests that only need the device key destructure `(instance,
679    /// device_key, _admin)`; tests that need to drive admin operations
680    /// destructure `(instance, _device_key, admin)`. Uses [`FixedClock`] for
681    /// reproducible timestamps.
682    async fn setup_instance() -> (Instance, PrivateKey, crate::user::User) {
683        use crate::clock::FixedClock;
684
685        let (instance, admin) = Instance::create_backend_with_clock(
686            Box::new(InMemory::new()),
687            Arc::new(FixedClock::default()),
688            crate::NewUser::passwordless("admin"),
689        )
690        .await
691        .unwrap();
692
693        // Get the device key from the instance
694        let device_key = instance.signing_key().unwrap().clone();
695
696        (instance, device_key, admin)
697    }
698
699    #[tokio::test]
700    async fn test_create_instance_database() {
701        let (instance, device_key, _admin) = setup_instance().await;
702
703        let instance_db = create_instance_database(&instance, &device_key)
704            .await
705            .unwrap();
706
707        // Verify database was created
708        assert!(!instance_db.root_id().to_string().is_empty());
709
710        // Verify settings
711        let transaction = instance_db.new_transaction().await.unwrap();
712        let doc_store = transaction
713            .get_store::<DocStore>("_settings")
714            .await
715            .unwrap();
716        let name = doc_store.get_string("name").await.unwrap();
717        assert_eq!(name, INSTANCE);
718
719        // Verify auth settings - key is stored by pubkey
720        let device_pubkey = instance.id();
721        let settings_store = SettingsStore::new(&transaction).unwrap();
722        let auth_settings = settings_store.auth_snapshot().await.unwrap();
723        let device_key = auth_settings.get_key_by_pubkey(&device_pubkey).unwrap();
724        assert_eq!(device_key.permissions(), &Permission::Admin(0));
725        assert_eq!(device_key.name(), None);
726    }
727
728    #[tokio::test]
729    async fn test_create_users_database() {
730        let (instance, device_key, _admin) = setup_instance().await;
731
732        let users_db = create_users_database(&instance, &device_key).await.unwrap();
733
734        // Verify database was created
735        assert!(!users_db.root_id().to_string().is_empty());
736
737        // Verify settings
738        let transaction = users_db.new_transaction().await.unwrap();
739        let doc_store = transaction
740            .get_store::<DocStore>("_settings")
741            .await
742            .unwrap();
743        let name = doc_store.get_string("name").await.unwrap();
744        assert_eq!(name, USERS);
745    }
746
747    #[tokio::test]
748    async fn test_create_databases_tracking() {
749        let (instance, device_key, _admin) = setup_instance().await;
750
751        let databases_db = create_databases_tracking(&instance, &device_key)
752            .await
753            .unwrap();
754
755        // Verify database was created
756        assert!(!databases_db.root_id().to_string().is_empty());
757
758        // Verify settings
759        let transaction = databases_db.new_transaction().await.unwrap();
760        let doc_store = transaction
761            .get_store::<DocStore>("_settings")
762            .await
763            .unwrap();
764        let name = doc_store.get_string("name").await.unwrap();
765        assert_eq!(name, DATABASES);
766    }
767
768    #[tokio::test]
769    async fn test_system_databases_haveadmin_auth() {
770        let (instance, device_key, _admin) = setup_instance().await;
771
772        let users_db = create_users_database(&instance, &device_key).await.unwrap();
773
774        // Verify device key has admin access - key is stored by pubkey
775        let device_pubkey = instance.id();
776        let transaction = users_db.new_transaction().await.unwrap();
777        let settings_store = SettingsStore::new(&transaction).unwrap();
778        let auth_settings = settings_store.auth_snapshot().await.unwrap();
779        let device_key = auth_settings.get_key_by_pubkey(&device_pubkey).unwrap();
780
781        assert_eq!(device_key.permissions(), &Permission::Admin(0));
782        assert_eq!(device_key.name(), None);
783    }
784
785    #[tokio::test]
786    #[cfg_attr(miri, ignore)] // Uses Argon2 password hashing and SystemTime
787    async fn test_create_user() {
788        let (instance, device_key, _admin) = setup_instance().await;
789        let users_db = create_users_database(&instance, &device_key).await.unwrap();
790
791        // Create a user with password
792        let (user_uuid, user_info, _) =
793            create_user(&users_db, &instance, "alice", Some("password123"))
794                .await
795                .unwrap();
796
797        // Verify user info
798        assert_eq!(user_info.username, "alice");
799        assert_eq!(user_info.status, UserStatus::Active);
800        assert!(user_info.credentials.password_salt.is_some());
801        assert!(matches!(
802            user_info.credentials.root_key,
803            KeyStorage::Encrypted { .. }
804        ));
805        assert!(!user_uuid.is_empty());
806
807        // Verify user was stored in _users database
808        let users_table = users_db
809            .get_store_viewer::<Table<UserInfo>>("users")
810            .await
811            .unwrap();
812        let stored_user = users_table.get(&user_uuid).await.unwrap();
813        assert_eq!(stored_user.username, "alice");
814    }
815
816    #[tokio::test]
817    async fn test_create_user_passwordless() {
818        let (instance, device_key, _admin) = setup_instance().await;
819        let users_db = create_users_database(&instance, &device_key).await.unwrap();
820
821        // Create a passwordless user
822        let (user_uuid, user_info, _) = create_user(&users_db, &instance, "bob", None)
823            .await
824            .unwrap();
825
826        // Verify user info
827        assert_eq!(user_info.username, "bob");
828        assert_eq!(user_info.status, UserStatus::Active);
829        assert!(user_info.credentials.password_salt.is_none());
830        assert!(matches!(
831            user_info.credentials.root_key,
832            KeyStorage::Unencrypted { .. }
833        ));
834        assert!(!user_uuid.is_empty());
835
836        // Verify user was stored in _users database
837        let users_table = users_db
838            .get_store_viewer::<Table<UserInfo>>("users")
839            .await
840            .unwrap();
841        let stored_user = users_table.get(&user_uuid).await.unwrap();
842        assert_eq!(stored_user.username, "bob");
843    }
844
845    #[tokio::test]
846    #[cfg_attr(miri, ignore)] // Uses Argon2 password hashing and SystemTime
847    async fn test_create_duplicate_user() {
848        let (instance, device_key, _admin) = setup_instance().await;
849        let users_db = create_users_database(&instance, &device_key).await.unwrap();
850
851        // Create first user
852        create_user(&users_db, &instance, "alice", Some("password123"))
853            .await
854            .unwrap();
855
856        // Try to create duplicate
857        let result = create_user(&users_db, &instance, "alice", Some("password456")).await;
858        assert!(result.is_err());
859    }
860
861    #[tokio::test]
862    #[cfg_attr(miri, ignore)] // Uses Argon2 password hashing and SystemTime
863    async fn test_login_user() {
864        let (instance, device_key, _admin) = setup_instance().await;
865        let users_db = create_users_database(&instance, &device_key).await.unwrap();
866
867        // Create a user with password
868        create_user(&users_db, &instance, "bob", Some("bobpassword"))
869            .await
870            .unwrap();
871
872        // Login user
873        let user = login_user(&users_db, &instance, "bob", Some("bobpassword"))
874            .await
875            .unwrap();
876
877        // Verify user session
878        assert_eq!(user.username(), "bob");
879
880        // Verify last_login was recorded in separate table
881        let last_login_table = users_db
882            .get_store_viewer::<Table<i64>>("last_login")
883            .await
884            .unwrap();
885        let last_login = last_login_table.get(user.user_uuid()).await.unwrap();
886        assert!(last_login > 0);
887    }
888
889    #[tokio::test]
890    async fn test_login_user_passwordless() {
891        let (instance, device_key, _admin) = setup_instance().await;
892        let users_db = create_users_database(&instance, &device_key).await.unwrap();
893
894        // Create a passwordless user
895        create_user(&users_db, &instance, "charlie", None)
896            .await
897            .unwrap();
898
899        // Login user without password
900        let user = login_user(&users_db, &instance, "charlie", None)
901            .await
902            .unwrap();
903
904        // Verify user session
905        assert_eq!(user.username(), "charlie");
906
907        // Verify last_login was recorded
908        let last_login_table = users_db
909            .get_store_viewer::<Table<i64>>("last_login")
910            .await
911            .unwrap();
912        let last_login = last_login_table.get(user.user_uuid()).await.unwrap();
913        assert!(last_login > 0);
914    }
915
916    #[tokio::test]
917    #[cfg_attr(miri, ignore)] // Uses Argon2 password hashing and SystemTime
918    async fn test_login_wrong_password() {
919        let (instance, device_key, _admin) = setup_instance().await;
920        let users_db = create_users_database(&instance, &device_key).await.unwrap();
921
922        // Create a user
923        create_user(&users_db, &instance, "dave", Some("correct_password"))
924            .await
925            .unwrap();
926
927        // Try to login with wrong password
928        let result = login_user(&users_db, &instance, "dave", Some("wrong_password")).await;
929        assert!(result.is_err());
930    }
931
932    #[tokio::test]
933    #[cfg_attr(miri, ignore)] // Uses Argon2 password hashing and SystemTime
934    async fn test_login_password_mismatch() {
935        let (instance, device_key, _admin) = setup_instance().await;
936        let users_db = create_users_database(&instance, &device_key).await.unwrap();
937
938        // Create a passwordless user
939        create_user(&users_db, &instance, "eve", None)
940            .await
941            .unwrap();
942
943        // Try to login with password (should fail)
944        let result = login_user(&users_db, &instance, "eve", Some("password")).await;
945        assert!(result.is_err());
946
947        // Create a password-protected user
948        create_user(&users_db, &instance, "frank", Some("password"))
949            .await
950            .unwrap();
951
952        // Try to login without password (should fail)
953        let result = login_user(&users_db, &instance, "frank", None).await;
954        assert!(result.is_err());
955    }
956
957    #[tokio::test]
958    async fn test_login_nonexistent_user() {
959        let (instance, device_key, _admin) = setup_instance().await;
960        let users_db = create_users_database(&instance, &device_key).await.unwrap();
961
962        // Try to login user that doesn't exist
963        let result = login_user(&users_db, &instance, "nonexistent", Some("password")).await;
964        assert!(result.is_err());
965    }
966
967    #[tokio::test]
968    #[cfg_attr(miri, ignore)] // Uses Argon2 password hashing and SystemTime
969    async fn test_list_users() {
970        let (instance, device_key, _admin) = setup_instance().await;
971        let users_db = create_users_database(&instance, &device_key).await.unwrap();
972
973        // Initially no users
974        let users = list_users(&users_db).await.unwrap();
975        assert_eq!(users.len(), 0);
976
977        // Create some users (mix of password-protected and passwordless)
978        create_user(&users_db, &instance, "alice", Some("pass1"))
979            .await
980            .unwrap();
981        create_user(&users_db, &instance, "bob", None)
982            .await
983            .unwrap();
984        create_user(&users_db, &instance, "charlie", Some("pass3"))
985            .await
986            .unwrap();
987
988        // List users
989        let users = list_users(&users_db).await.unwrap();
990        assert_eq!(users.len(), 3);
991        assert!(users.contains(&"alice".into()));
992        assert!(users.contains(&"bob".into()));
993        assert!(users.contains(&"charlie".into()));
994    }
995
996    /// Read the auth permission for `pubkey` from a system database. Returns
997    /// `None` if the key isn't registered. Test-only helper.
998    async fn read_admin_permission(database: &Database, pubkey: &PublicKey) -> Option<Permission> {
999        let tx = database.new_transaction().await.unwrap();
1000        let settings = SettingsStore::new(&tx).unwrap();
1001        let auth = settings.auth_snapshot().await.unwrap();
1002        auth.get_key_by_pubkey(pubkey)
1003            .ok()
1004            .map(|k| *k.permissions())
1005    }
1006
1007    /// The bootstrapped admin/admin user is the instance admin: its pubkey
1008    /// lands as `Admin(0)` in both `_users` and `_databases`.
1009    #[tokio::test]
1010    async fn test_first_user_becomes_instance_admin() {
1011        let (instance, _device_key, _admin) = setup_instance().await;
1012
1013        // Admin is already bootstrapped — login and verify
1014        let admin_user = instance.login_user("admin", None).await.unwrap();
1015
1016        let users_db = instance.users_db().await.unwrap();
1017        let databases_db = instance.databases_db().await.unwrap();
1018
1019        let admin_pubkey = admin_user.key_manager().get_default_key_id().unwrap();
1020
1021        assert_eq!(
1022            read_admin_permission(&users_db, &admin_pubkey).await,
1023            Some(Permission::Admin(0)),
1024            "bootstrapped admin should be Admin(0) in _users"
1025        );
1026        assert_eq!(
1027            read_admin_permission(&databases_db, &admin_pubkey).await,
1028            Some(Permission::Admin(0)),
1029            "bootstrapped admin should be Admin(0) in _databases"
1030        );
1031    }
1032
1033    /// After the first user has bootstrapped, subsequent users land as
1034    /// non-admins. This test locks in that the auto-bootstrap doesn't fire a
1035    /// second time.
1036    #[tokio::test]
1037    async fn test_subsequent_users_are_not_admin() {
1038        let (instance, _device_key, admin) = setup_instance().await;
1039
1040        // Admin creates two more users via the admin path. They land as
1041        // non-admins because admin (the first user) already holds Admin(0)
1042        // on the system DBs.
1043        let admin_view = admin.admin().await.unwrap();
1044        admin_view
1045            .create_user(crate::NewUser::passwordless("alice"))
1046            .await
1047            .unwrap();
1048        let bob_uuid = admin_view
1049            .create_user(crate::NewUser::passwordless("bob"))
1050            .await
1051            .unwrap();
1052
1053        let users_db = instance.users_db().await.unwrap();
1054        let databases_db = instance.databases_db().await.unwrap();
1055
1056        // Bob's root pubkey lives in `UserInfo.credentials.root_key_id` —
1057        // the durable source of truth. The user-tree `keys` table is
1058        // populated only at first login (deferred bootstrap), so reading
1059        // it here would race a `bob` that hasn't logged in yet.
1060        let users_table = users_db
1061            .get_store_viewer::<Table<UserInfo>>("users")
1062            .await
1063            .unwrap();
1064        let bob_info = users_table.get(&bob_uuid).await.unwrap();
1065        let bob_pubkey = bob_info.credentials.root_key_id.clone();
1066
1067        assert!(
1068            read_admin_permission(&users_db, &bob_pubkey)
1069                .await
1070                .is_none(),
1071            "second user must not have any entry in _users.auth_settings"
1072        );
1073        assert!(
1074            read_admin_permission(&databases_db, &bob_pubkey)
1075                .await
1076                .is_none(),
1077            "second user must not have any entry in _databases.auth_settings"
1078        );
1079    }
1080
1081    /// `User::is_admin()` query: the bootstrapped admin is admin, newly created
1082    /// users are not.
1083    #[tokio::test]
1084    async fn test_user_is_admin_query() {
1085        let (instance, _device_key, admin) = setup_instance().await;
1086
1087        // Bootstrapped admin reports is_admin = true
1088        assert!(
1089            admin.is_admin().await.unwrap(),
1090            "bootstrapped admin must report is_admin = true"
1091        );
1092
1093        // A newly created user reports is_admin = false
1094        admin
1095            .admin()
1096            .await
1097            .unwrap()
1098            .create_user(crate::NewUser::passwordless("alice"))
1099            .await
1100            .unwrap();
1101        let alice = instance.login_user("alice", None).await.unwrap();
1102        assert!(
1103            !alice.is_admin().await.unwrap(),
1104            "newly created user must report is_admin = false"
1105        );
1106    }
1107
1108    /// The bootstrapped admin can promote another user: the new admin's pubkey
1109    /// lands as `Admin(0)` on both `_users` and `_databases`, and the
1110    /// promoted user then reports `is_admin()`.
1111    #[tokio::test]
1112    async fn test_admin_can_promote_user() {
1113        let (instance, _device_key, admin) = setup_instance().await;
1114
1115        admin
1116            .admin()
1117            .await
1118            .unwrap()
1119            .create_user(crate::NewUser::passwordless("bob"))
1120            .await
1121            .unwrap();
1122        let bob = instance.login_user("bob", None).await.unwrap();
1123        let bob_pubkey = bob.key_manager().get_default_key_id().unwrap();
1124
1125        assert!(
1126            !bob.is_admin().await.unwrap(),
1127            "precondition: bob starts as a non-admin"
1128        );
1129
1130        // Admin promotes bob
1131        admin
1132            .admin()
1133            .await
1134            .unwrap()
1135            .grant_instance_admin(&bob_pubkey)
1136            .await
1137            .unwrap();
1138
1139        let users_db = instance.users_db().await.unwrap();
1140        let databases_db = instance.databases_db().await.unwrap();
1141        assert_eq!(
1142            read_admin_permission(&users_db, &bob_pubkey).await,
1143            Some(Permission::Admin(0)),
1144            "promoted user should be Admin(0) in _users"
1145        );
1146        assert_eq!(
1147            read_admin_permission(&databases_db, &bob_pubkey).await,
1148            Some(Permission::Admin(0)),
1149            "promoted user should be Admin(0) in _databases"
1150        );
1151        assert!(
1152            bob.is_admin().await.unwrap(),
1153            "promoted user must now report is_admin = true"
1154        );
1155    }
1156
1157    /// A non-admin cannot promote anyone: the attempt fails with
1158    /// `InsufficientPermissions` and writes nothing.
1159    #[tokio::test]
1160    async fn test_non_admin_cannot_promote() {
1161        let (instance, _device_key, admin) = setup_instance().await;
1162
1163        // The bootstrapped `admin` is the first/only admin. Admin creates
1164        // bob and charlie as non-admin users.
1165        let admin_view = admin.admin().await.unwrap();
1166        admin_view
1167            .create_user(crate::NewUser::passwordless("bob"))
1168            .await
1169            .unwrap();
1170        admin_view
1171            .create_user(crate::NewUser::passwordless("charlie"))
1172            .await
1173            .unwrap();
1174        let bob = instance.login_user("bob", None).await.unwrap();
1175        let charlie = instance.login_user("charlie", None).await.unwrap();
1176        let charlie_pubkey = charlie.key_manager().get_default_key_id().unwrap();
1177
1178        // A non-admin can't even obtain the admin view — the privilege
1179        // boundary is enforced at `User::admin`, before any write.
1180        let err = bob
1181            .admin()
1182            .await
1183            .expect_err("non-admin must not be able to promote");
1184        assert!(
1185            matches!(
1186                &err,
1187                crate::Error::User(e)
1188                    if matches!(e.as_ref(), crate::user::UserError::InsufficientPermissions)
1189            ),
1190            "expected InsufficientPermissions, got: {err:?}"
1191        );
1192
1193        let users_db = instance.users_db().await.unwrap();
1194        assert!(
1195            read_admin_permission(&users_db, &charlie_pubkey)
1196                .await
1197                .is_none(),
1198            "a failed promotion must not write to _users.auth_settings"
1199        );
1200    }
1201
1202    /// Promotion is idempotent, and a freshly-promoted admin can itself
1203    /// promote further admins — exercising the admin-keyed write path
1204    /// (not the device key) end to end.
1205    #[tokio::test]
1206    async fn test_grant_instance_admin_idempotent_and_chains() {
1207        let (instance, _device_key, admin) = setup_instance().await;
1208
1209        admin
1210            .admin()
1211            .await
1212            .unwrap()
1213            .create_user(crate::NewUser::passwordless("bob"))
1214            .await
1215            .unwrap();
1216        let bob = instance.login_user("bob", None).await.unwrap();
1217        let bob_pubkey = bob.key_manager().get_default_key_id().unwrap();
1218
1219        admin
1220            .admin()
1221            .await
1222            .unwrap()
1223            .grant_instance_admin(&bob_pubkey)
1224            .await
1225            .unwrap();
1226        admin
1227            .admin()
1228            .await
1229            .unwrap()
1230            .grant_instance_admin(&bob_pubkey)
1231            .await
1232            .unwrap(); // idempotent: no error
1233
1234        let users_db = instance.users_db().await.unwrap();
1235        assert_eq!(
1236            read_admin_permission(&users_db, &bob_pubkey).await,
1237            Some(Permission::Admin(0)),
1238            "re-granting an existing admin re-asserts the same entry"
1239        );
1240
1241        // bob, now an admin, promotes charlie signing with bob's *own* key —
1242        // the device key is never involved here.
1243        bob.admin()
1244            .await
1245            .unwrap()
1246            .create_user(crate::NewUser::passwordless("charlie"))
1247            .await
1248            .unwrap();
1249        let charlie = instance.login_user("charlie", None).await.unwrap();
1250        let charlie_pubkey = charlie.key_manager().get_default_key_id().unwrap();
1251
1252        bob.admin()
1253            .await
1254            .unwrap()
1255            .grant_instance_admin(&charlie_pubkey)
1256            .await
1257            .unwrap();
1258
1259        let databases_db = instance.databases_db().await.unwrap();
1260        assert_eq!(
1261            read_admin_permission(&databases_db, &charlie_pubkey).await,
1262            Some(Permission::Admin(0)),
1263            "a promoted admin can promote further admins via their own key"
1264        );
1265    }
1266}