mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-10-17 15:31:38 +02:00
Wire the settings overlay into the live config path: - AppState.config: Config -> Arc<ArcSwap<Config>> (hot-swappable effective config) + config_base: Arc<Config> (immutable env-only). New AppState::cfg() accessor; every config read goes through it (state.cfg().field), so a runtime-safe operator edit is seen on the next request. One Config type, no parallel path. - Boot (main.rs): load_settings_overlay -> Config::apply_overlay(validate) -> build every client/worker/router from the EFFECTIVE config. A persisted overlay that no longer validates is ignored (boot env-only + loud log), never bricks. Env-only when ADMIN_KEY_INTEGRITY_SECRET is unset. - Migrated ~59 state.config.X reads to state.cfg().X across api/*; bound guards where a reference outlives the statement; integrity_secret returns owned String. - Test harness builds AppState with ArcSwap + config_base (arc-swap dev-dep). Behavior-preserving: with no operator_settings rows, effective == env config. cargo check --all-targets clean; lib tests 256/0.
124 lines
4.1 KiB
Rust
124 lines
4.1 KiB
Rust
//! L1 integration: the relay event-admission (nauthz) gRPC handler.
|
|
//!
|
|
//! Drives `AdmissionService::event_admit` directly (no gRPC transport — that is
|
|
//! covered by the docker-relay E2E) to prove the handler wiring: p-tag
|
|
//! extraction, the DB registration lookup, and the policy decision, end to end
|
|
//! against live Postgres. The pure policy branches are covered in unit tests.
|
|
|
|
mod common;
|
|
|
|
use smirk_backend_core::infra::relay::nauthz::proto::authorization_server::Authorization;
|
|
use smirk_backend_core::infra::relay::nauthz::{proto, AdmissionService};
|
|
use smirk_backend_core::infra::relay::{self, RelayProvider};
|
|
use smirk_backend_core::models::db::NewUser;
|
|
use std::sync::Arc;
|
|
use tonic::Request;
|
|
|
|
/// A 64-char lowercase-hex pubkey/id (valid for hex::decode).
|
|
fn hex64(seed: &str) -> String {
|
|
let base = format!("{seed}{}", "0".repeat(64));
|
|
base[..64].to_string()
|
|
}
|
|
|
|
async fn register_npub(db: &smirk_backend_core::infra::db::Database, npub_hex: &str) {
|
|
db.create_user(NewUser {
|
|
username: None,
|
|
pubkey_hash: Some(format!("pk-{}", uuid::Uuid::new_v4())),
|
|
nostr_pubkey: Some(npub_hex.to_string()),
|
|
wallet_birthday: None,
|
|
seed_fingerprint: None,
|
|
xmr_start_height: None,
|
|
wow_start_height: None,
|
|
})
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
fn inbox_outbox_relay(base: &smirk_backend_core::config::Config) -> Arc<dyn RelayProvider> {
|
|
let mut cfg = base.clone();
|
|
cfg.messaging.relay.enabled = true;
|
|
cfg.messaging.relay.write_policy = "inbox-outbox".into();
|
|
cfg.messaging.relay.advertised_url = "wss://relay.example.org".into();
|
|
cfg.messaging.relay.inbound_pow_bits = 0;
|
|
relay::from_config(&cfg).unwrap().unwrap()
|
|
}
|
|
|
|
fn event(author_hex: &str, kind: u64, p_tags: &[&str], id_hex: &str) -> proto::EventRequest {
|
|
proto::EventRequest {
|
|
event: Some(proto::Event {
|
|
id: hex::decode(id_hex).unwrap(),
|
|
pubkey: hex::decode(author_hex).unwrap(),
|
|
created_at: 0,
|
|
kind,
|
|
content: String::new(),
|
|
tags: p_tags
|
|
.iter()
|
|
.map(|p| proto::TagEntry {
|
|
values: vec!["p".to_string(), (*p).to_string()],
|
|
})
|
|
.collect(),
|
|
sig: vec![],
|
|
}),
|
|
ip_addr: None,
|
|
origin: None,
|
|
user_agent: None,
|
|
auth_pubkey: None,
|
|
nip05: None,
|
|
}
|
|
}
|
|
|
|
async fn admit(svc: &AdmissionService, req: proto::EventRequest) -> bool {
|
|
let reply = svc
|
|
.event_admit(Request::new(req))
|
|
.await
|
|
.unwrap()
|
|
.into_inner();
|
|
reply.decision == proto::Decision::Permit as i32
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn is_registered_npub_membership() {
|
|
let app = require_app!();
|
|
let npub = hex64("aaaa");
|
|
assert!(!app.state.db.is_registered_npub(&npub).await.unwrap());
|
|
register_npub(&app.state.db, &npub).await;
|
|
assert!(app.state.db.is_registered_npub(&npub).await.unwrap());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn admission_inbox_outbox_end_to_end() {
|
|
let app = require_app!();
|
|
let svc = AdmissionService::new(inbox_outbox_relay(&app.state.cfg()), app.state.db.clone());
|
|
|
|
let author = hex64("b1"); // registered author (outbox)
|
|
let recipient = hex64("b2"); // registered recipient (inbox)
|
|
let stranger = hex64("b3"); // unregistered
|
|
register_npub(&app.state.db, &author).await;
|
|
register_npub(&app.state.db, &recipient).await;
|
|
let ff = "ff".repeat(32);
|
|
|
|
// Outbox: a registered author's own event → PERMIT.
|
|
assert!(
|
|
admit(&svc, event(&author, 1, &[], &ff)).await,
|
|
"registered author outbox"
|
|
);
|
|
|
|
// Inbox: an unregistered author's gift-wrap (1059) to a registered recipient → PERMIT.
|
|
assert!(
|
|
admit(&svc, event(&stranger, 1059, &[&recipient], &ff)).await,
|
|
"gift-wrap to registered recipient"
|
|
);
|
|
|
|
// External non-gift-wrap → DENY.
|
|
assert!(
|
|
!admit(&svc, event(&stranger, 1, &[&recipient], &ff)).await,
|
|
"external non-gift-wrap rejected"
|
|
);
|
|
|
|
// Gift-wrap to an UNREGISTERED recipient → DENY.
|
|
assert!(
|
|
!admit(&svc, event(&stranger, 1059, &[&hex64("b9")], &ff)).await,
|
|
"gift-wrap to unregistered recipient rejected"
|
|
);
|
|
}
|