mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-10-17 07:21:32 +02:00
Stage 1 of the operator-editable config layer (additive; no behavior change yet): - migration operator_settings: one MAC(ADMIN_KEY_INTEGRITY_SECRET) JSONB row per section, FK to admin_keys, audited on write. Missing section = env-only. - OperatorSettingRow model. - SettingsOverlay + section overlays (landing/retention/restore) with secrets EXCLUDED BY CONSTRUCTION; Config::apply_overlay merges onto the env-derived Config then re-runs the SAME Config::validate boot uses (a bad edit yields the identical error, nothing persists, the instance cannot be bricked). - runtime_class() single source of truth (RuntimeSafe hot-swap vs RestartRequired). - operator_settings storage: canonical-JSON MAC (stable across JSONB key reorder), load_settings_overlay (fail-closed per section -> env fallback), put_setting_audited (upsert + admin_audit append in one tx). - sqlx json feature. cargo check clean; 4 new unit tests pass. Next stages: ArcSwap AppState + ~59 read-site migration, GET/PUT /admin/config (+ restart_apply_mode), remaining sections.
93 lines
2.7 KiB
Rust
93 lines
2.7 KiB
Rust
//! Database access layer.
|
|
//!
|
|
//! `Database` wraps the connection pool and the secrets needed to pepper
|
|
//! identity values at rest. Per-entity query methods are implemented on it in
|
|
//! sibling modules (`users`, ...). Identity values (`pubkey_hash`,
|
|
//! `seed_fingerprint`) are peppered inside these methods so the plaintext never
|
|
//! reaches a column and the peppering cannot drift between call sites.
|
|
|
|
mod admin_audit;
|
|
mod admin_keys;
|
|
mod admin_sessions;
|
|
mod audit;
|
|
mod challenges;
|
|
mod erasure;
|
|
mod grin_slatepacks;
|
|
mod invite_codes;
|
|
mod login_events;
|
|
mod operator_settings;
|
|
mod payment_invoices;
|
|
mod premium_invoices;
|
|
mod restore_attempts;
|
|
mod server_config;
|
|
mod sessions;
|
|
mod social_tips;
|
|
mod user_keys;
|
|
mod users;
|
|
|
|
pub use admin_keys::{AddKeyOutcome, RevokeKeyOutcome};
|
|
pub use challenges::ConsumedChallenge;
|
|
pub use login_events::LoginStats;
|
|
pub use payment_invoices::PaymentInvoiceRow;
|
|
pub use premium_invoices::PremiumInvoiceRow;
|
|
pub use server_config::SetupState;
|
|
pub use social_tips::{GcCancelledTip, NewSocialTip, SocialTipRow, StuckClaimingTip};
|
|
|
|
use sqlx::PgPool;
|
|
|
|
use crate::core::crypto::pepper::peppered_hex;
|
|
use crate::error::AppError;
|
|
|
|
/// Connection pool + at-rest peppering secrets. Does not derive `Debug`.
|
|
#[derive(Clone)]
|
|
pub struct Database {
|
|
pool: PgPool,
|
|
/// HMAC pepper for identity columns (pubkey_hash, seed_fingerprint).
|
|
identity_pepper: String,
|
|
/// Salt for hashing client IPs in analytics/abuse tables.
|
|
ip_salt: String,
|
|
}
|
|
|
|
impl Database {
|
|
pub fn new(pool: PgPool, identity_pepper: String, ip_salt: String) -> Self {
|
|
Self {
|
|
pool,
|
|
identity_pepper,
|
|
ip_salt,
|
|
}
|
|
}
|
|
|
|
pub fn pool(&self) -> &PgPool {
|
|
&self.pool
|
|
}
|
|
|
|
/// Salted hash of a client IP for analytics/abuse tables (never store raw).
|
|
pub(crate) fn hash_ip(&self, ip: &str) -> String {
|
|
peppered_hex(&self.ip_salt, "ip", ip)
|
|
}
|
|
|
|
/// Pepper an identity value for storage/lookup (see [`peppered_hex`]).
|
|
fn pepper(&self, domain: &str, value: &str) -> String {
|
|
peppered_hex(&self.identity_pepper, domain, value)
|
|
}
|
|
|
|
/// Liveness check against the database.
|
|
pub async fn health_check(&self) -> Result<bool, AppError> {
|
|
sqlx::query_scalar::<_, i32>("SELECT 1")
|
|
.fetch_one(&self.pool)
|
|
.await?;
|
|
Ok(true)
|
|
}
|
|
}
|
|
|
|
/// Map a unique-constraint violation to a 409 CONFLICT with `msg`; pass other
|
|
/// errors through. Shared by the entity query modules.
|
|
pub(crate) fn unique_violation_as(msg: &'static str) -> impl Fn(sqlx::Error) -> AppError {
|
|
move |e| match &e {
|
|
sqlx::Error::Database(db_err) if db_err.is_unique_violation() => {
|
|
AppError::Conflict(msg.to_string())
|
|
}
|
|
_ => AppError::from(e),
|
|
}
|
|
}
|