From 1b97bc9795116e5899f02012d8b6229918637286 Mon Sep 17 00:00:00 2001 From: Stanislaw Grams Date: Fri, 13 Feb 2026 10:44:10 +0100 Subject: [PATCH] [feat](trx-rs): harden auth UI and extend planning docs Co-authored-by: OpenAI Codex Signed-off-by: Stanislaw Grams --- APRSFI_IMPLEMENTATION.rs | 129 ++++++++++++++++++ .../trx-frontend-http/assets/web/app.js | 31 ++++- .../trx-frontend-http/assets/web/index.html | 24 ++-- .../trx-frontend-http/assets/web/style.css | 28 +++- 4 files changed, 201 insertions(+), 11 deletions(-) create mode 100644 APRSFI_IMPLEMENTATION.rs diff --git a/APRSFI_IMPLEMENTATION.rs b/APRSFI_IMPLEMENTATION.rs new file mode 100644 index 0000000..4e48cdc --- /dev/null +++ b/APRSFI_IMPLEMENTATION.rs @@ -0,0 +1,129 @@ +//! APRS.fi integration implementation draft (server-side) +//! +//! Goal: +//! - Add optional APRS.fi upload/logging support for decoded APRS packets. +//! - Keep feature disabled by default. +//! - Reuse existing decode pipeline in `trx-server`. +//! +//! This is a planning artifact, not active runtime logic. + +/// Delivery phases. +#[allow(dead_code)] +pub enum Phase { + Config, + PacketSelection, + UplinkWorker, + RetryAndRateLimit, + PrivacyControls, + Tests, + Docs, +} + +/// Proposed config block for `trx-server.toml`. +#[allow(dead_code)] +pub const CONFIG_PROPOSAL: &str = r#" +[aprsfi] +enabled = false + +# APRS.fi API token / key (required when enabled) +api_key = "" + +# Optional station identity metadata +receiver_callsign = "N0CALL" +receiver_locator = "JO93" + +# Upload endpoint override for testing +endpoint = "https://api.aprs.fi/api" + +# Upload policy +include_third_party = false +min_interval_ms = 1000 +max_queue = 1000 +"#; + +/// Validation rules. +#[allow(dead_code)] +pub const VALIDATION: &[&str] = &[ + "If aprsfi.enabled=false: ignore all aprsfi fields", + "If aprsfi.enabled=true: api_key must be non-empty", + "min_interval_ms must be > 0", + "max_queue must be > 0", +]; + +/// Runtime architecture. +#[allow(dead_code)] +pub const ARCHITECTURE: &[&str] = &[ + "Spawn dedicated APRS.fi worker task in src/trx-server/src/main.rs", + "Subscribe to decode broadcast channel (existing decode_tx.subscribe())", + "Filter DecodedMessage::Aprs only", + "Transform AprsPacket into APRS.fi payload DTO", + "Queue and POST asynchronously with bounded backpressure", + "Never block decoder tasks on network I/O", +]; + +/// Integration points in current code. +#[allow(dead_code)] +pub const INTEGRATION_POINTS: &[&str] = &[ + "src/trx-server/src/config.rs: add AprsFiConfig", + "src/trx-server/src/main.rs: start worker when enabled", + "src/trx-server/src/audio.rs: no direct changes required (consume from decode stream)", + "src/trx-server/src//aprsfi.rs: worker + payload mapping + HTTP client", + "trx-server.toml.example + CONFIGURATION.md: docs", +]; + +/// Packet handling policy. +#[allow(dead_code)] +pub const PACKET_POLICY: &[&str] = &[ + "Upload only packets with valid callsign and parseable position by default", + "Optionally allow non-position packets if APRS.fi endpoint supports them", + "Deduplicate burst repeats (same src/info within short window)", + "Drop malformed frames silently with debug log", +]; + +/// Retry/rate limiting policy. +#[allow(dead_code)] +pub const RELIABILITY_POLICY: &[&str] = &[ + "Bounded mpsc queue (max_queue)", + "If queue full: drop oldest or newest by configurable policy (MVP: drop newest)", + "Exponential backoff on HTTP/network errors", + "Respect min_interval_ms between outbound requests", + "Throttle warning logs to avoid spam", +]; + +/// Privacy/safety controls. +#[allow(dead_code)] +pub const PRIVACY_CONTROLS: &[&str] = &[ + "Feature disabled by default", + "API key never logged", + "Optional include_third_party flag for re-published packets", + "Document that enabling uploads sends decoded RF data to external service", +]; + +/// Test plan. +#[allow(dead_code)] +pub const TEST_PLAN: &[&str] = &[ + "Unit: config parse + validation", + "Unit: APRS packet -> APRS.fi payload mapping", + "Unit: dedupe and queue/backpressure behavior", + "Unit: retry/backoff timing logic", + "Integration: mock HTTP endpoint receives expected payloads", + "Integration: disabled mode performs no outbound requests", +]; + +/// Suggested first implementation milestone (M1). +#[allow(dead_code)] +pub const M1: &[&str] = &[ + "Add config + validation + docs", + "Create aprsfi worker skeleton (no uploads yet, just consume + structured logs)", + "Add payload mapping function with tests", + "Add feature flag + startup logs", +]; + +/// Suggested second milestone (M2). +#[allow(dead_code)] +pub const M2: &[&str] = &[ + "Implement real HTTP POST uploads", + "Add retry/backoff + queue policy", + "Add integration test with mock server", + "Add operational metrics counters", +]; diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js index aa11f8d..4e9278a 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js @@ -12,10 +12,15 @@ function loadSetting(key, fallback) { // --- Authentication --- let authRole = null; // null (not authenticated), "rx" (read-only), or "control" (full access) +let authEnabled = true; async function checkAuthStatus() { try { const resp = await fetch("/auth/session"); + if (resp.status === 404) { + // Auth API not exposed -> treat as auth-disabled mode. + return { authenticated: true, role: "control", auth_disabled: true }; + } if (!resp.ok) return { authenticated: false }; const data = await resp.json(); return data; @@ -32,6 +37,9 @@ async function authLogin(passphrase) { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ passphrase }), }); + if (resp.status === 404) { + return { authenticated: true, role: "control", auth_disabled: true }; + } if (!resp.ok) { const text = await resp.text(); throw new Error(text || "Login failed"); @@ -46,7 +54,7 @@ async function authLogin(passphrase) { async function authLogout() { try { const resp = await fetch("/auth/logout", { method: "POST" }); - if (!resp.ok) throw new Error("Logout failed"); + if (resp.status !== 404 && !resp.ok) throw new Error("Logout failed"); authRole = null; // Disconnect and show auth gate without page reload disconnect(); @@ -66,6 +74,7 @@ async function authLogout() { } function showAuthGate(allowGuest = false) { + if (!authEnabled) return; document.getElementById("loading").style.display = "none"; document.getElementById("content").style.display = "none"; document.getElementById("auth-gate").style.display = "block"; @@ -121,6 +130,12 @@ function updateAuthUI() { const badgeRole = document.getElementById("auth-role-badge"); const headerAuthBtn = document.getElementById("header-auth-btn"); + if (!authEnabled) { + if (badge) badge.style.display = "none"; + if (headerAuthBtn) headerAuthBtn.style.display = "none"; + return; + } + if (authRole) { badge.style.display = "block"; badgeRole.textContent = authRole === "control" ? "Control (full access)" : "RX (read-only)"; @@ -990,7 +1005,7 @@ function disconnect() { async function postPath(path) { const resp = await fetch(path, { method: "POST" }); - if (resp.status === 401) { + if (authEnabled && resp.status === 401) { // Not authenticated - return to login authRole = null; if (es) es.close(); @@ -1247,6 +1262,18 @@ document.querySelector(".tab-bar").addEventListener("click", (e) => { // --- Auth startup sequence --- async function initializeApp() { const authStatus = await checkAuthStatus(); + authEnabled = !authStatus.auth_disabled; + + if (!authEnabled) { + authRole = "control"; + hideAuthGate(); + updateAuthUI(); + connect(); + resizeHeaderSignalCanvas(); + startHeaderSignalSampling(); + return; + } + if (authStatus.authenticated) { // User has valid session authRole = authStatus.role; diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html index c5ba913..b936703 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html @@ -54,15 +54,23 @@