Lines
100 %
Functions
33.33 %
Branches
//! One-time global seeding of the admin database.
//!
//! The seed data (admin user, infra/system config, signing keys) used to live
//! in a migration (`9999_default_config.sql`). It was moved out so the shared
//! migration set is DDL-only and can be applied to per-user databases without
//! replicating secrets. The seed now runs here, against the admin database
//! only.
//! All correctness guarantees live inside `seed.sql` itself: it is a single
//! guarded `DO` block that takes a transaction-scoped advisory lock (serializing
//! concurrent boots), skips if the `seed_complete` marker is already present
//! (idempotent), and runs atomically (a crash rolls back rather than leaving a
//! partial seed). So this function only has to ship that script to the admin
//! pool — which keeps the future `Send` (no `&mut PgConnection` held across an
//! await, unlike an in-Rust transaction) so `boot()` can be spawned.
use crate::db::{DBError, admin_database_url, execute_raw, get_connection};
use rust_i18n::t;
use sqlx::Connection;
const SEED_SQL: &str = include_str!("../seed.sql");
/// The seeded admin user's id (see `seed.sql`).
const ADMIN_USER_ID: &str = "22d36bde-d987-4927-b070-842efdcf9c59";
/// Seeds the admin database (idempotent, concurrency-safe, atomic — see the
/// module docs and `seed.sql`).
///
/// After seeding, points the admin user's `db_name` at this deployment's admin
/// `DATABASE_URL` (the seed leaves it empty rather than hardcoding a
/// machine-specific DSN). The update is **unconditional** (compare-and-update on
/// every boot) so that if the deployment's DSN later changes while reusing the
/// same admin database, the stored value tracks it rather than going stale. The
/// `WHERE db_name <> $1` keeps it a no-op write when already correct.
/// # Errors
/// Returns [`DBError`] on a connection failure or if the seed transaction fails.
pub async fn seed() -> Result<(), DBError> {
execute_raw(SEED_SQL).await?;
let admin_url = admin_database_url()?;
let mut conn = get_connection().await?;
sqlx::query("UPDATE users SET db_name = $1 WHERE id = $2::uuid AND db_name <> $1")
.bind(&admin_url)
.bind(ADMIN_USER_ID)
.execute(&mut *conn)
.await?;
backfill_admin_keypair(&mut conn).await?;
log::debug!("{}", t!("Admin database seed checked"));
Ok(())
}
/// Advisory-lock key serializing admin-keypair backfill across concurrent boots.
const ADMIN_KEY_LOCK: i64 = 0x6E6F_6D69_5F61_6B31;
/// Generates and stores a signing keypair for the seeded admin user if it has
/// none yet. The admin's data lives in the admin DB itself, so both halves are
/// written here: the public key into `users.jwt_public_key`, the private key
/// into the admin DB's `user_auth_keys`.
/// Concurrency- and partial-failure-safe:
/// - A transaction-scoped advisory lock serializes concurrent boots.
/// - Idempotency checks BOTH halves — the public key on the admin row AND a
/// matching private key in `user_auth_keys` — so a prior run that wrote only
/// the public key (then crashed) is repaired rather than treated as done.
/// - Both writes happen in one transaction; the private insert replaces any
/// prior rows so the stored private key always matches the public key, and a
/// crash rolls back rather than leaving a half-written keypair.
async fn backfill_admin_keypair(conn: &mut sqlx::PgConnection) -> Result<(), DBError> {
let mut tx = conn.begin().await?;
sqlx::query("SELECT pg_advisory_xact_lock($1)")
.bind(ADMIN_KEY_LOCK)
.execute(&mut *tx)
let public_key: Option<String> =
sqlx::query_scalar("SELECT jwt_public_key FROM users WHERE id = $1::uuid")
.fetch_optional(&mut *tx)
.await?
.flatten();
let private_count: i64 = sqlx::query_scalar("SELECT count(*) FROM user_auth_keys")
.fetch_one(&mut *tx)
// Healthy ONLY when both halves are present and there is exactly one private
// key. Anything else (missing public, missing private, or a duplicated row
// left by a pre-fix race) is repaired by rewriting the keypair below.
if public_key.is_some() && private_count == 1 {
tx.commit().await?;
return Ok(());
let keypair = crate::auth_keys::generate().await?;
sqlx::query("UPDATE users SET jwt_public_key = $1 WHERE id = $2::uuid")
.bind(&keypair.public_pem_b64)
// Clear any prior rows (incl. a duplicated pre-fix state) then insert the
// one key matching the public key just written. The singleton constraint
// (0005) also guarantees at most one row going forward.
sqlx::query("DELETE FROM user_auth_keys")
sqlx::query("INSERT INTO user_auth_keys (singleton, private_key) VALUES (TRUE, $1)")
.bind(&keypair.private_pem_b64)