refactor: split package.rs, mod.rs, listener.rs, and lnd.rs into focused submodules
- R35: Split package.rs (1794 lines) into package/{mod,config,validation,lifecycle}.rs
- R36: Split mesh/listener.rs (1799 lines) into listener/{mod,session,frames,decode,dispatch,bitcoin}.rs
- R37: Split rpc/mod.rs into mod.rs + dispatcher.rs, middleware.rs, response.rs (54% reduction)
- R38: Split lnd.rs (1064 lines) into lnd/{mod,info,channels,wallet,payments}.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 02:26:28 +00:00
|
|
|
use crate::session::SessionStore;
|
|
|
|
|
use std::net::IpAddr;
|
|
|
|
|
|
|
|
|
|
/// Methods that do not require a valid session cookie.
|
|
|
|
|
pub(super) const UNAUTHENTICATED_METHODS: &[&str] = &[
|
|
|
|
|
"auth.login",
|
|
|
|
|
"auth.login.totp",
|
|
|
|
|
"auth.login.backup",
|
|
|
|
|
"auth.isOnboardingComplete",
|
|
|
|
|
"auth.isSetup",
|
2026-03-26 09:12:16 +00:00
|
|
|
"auth.setup",
|
|
|
|
|
"auth.onboardingComplete",
|
refactor: split package.rs, mod.rs, listener.rs, and lnd.rs into focused submodules
- R35: Split package.rs (1794 lines) into package/{mod,config,validation,lifecycle}.rs
- R36: Split mesh/listener.rs (1799 lines) into listener/{mod,session,frames,decode,dispatch,bitcoin}.rs
- R37: Split rpc/mod.rs into mod.rs + dispatcher.rs, middleware.rs, response.rs (54% reduction)
- R38: Split lnd.rs (1064 lines) into lnd/{mod,info,channels,wallet,payments}.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 02:26:28 +00:00
|
|
|
"health",
|
2026-03-27 19:19:51 +00:00
|
|
|
// Server readiness check (Login.vue polls this before showing form)
|
|
|
|
|
"server.echo",
|
2026-03-26 09:12:16 +00:00
|
|
|
// Onboarding flow (before user has a session — DID creation, signing, backup)
|
|
|
|
|
"node.did",
|
|
|
|
|
"node.signChallenge",
|
|
|
|
|
"node.nostr-pubkey",
|
|
|
|
|
"node.createBackup",
|
2026-03-27 19:19:51 +00:00
|
|
|
"identity.create",
|
2026-03-26 09:12:16 +00:00
|
|
|
"identity.verify",
|
|
|
|
|
"identity.resolve-did",
|
2026-03-31 01:41:24 +01:00
|
|
|
// Seed management (onboarding — before user has a session)
|
|
|
|
|
"seed.generate",
|
|
|
|
|
"seed.verify",
|
|
|
|
|
"seed.restore",
|
|
|
|
|
"seed.save-encrypted",
|
refactor: split package.rs, mod.rs, listener.rs, and lnd.rs into focused submodules
- R35: Split package.rs (1794 lines) into package/{mod,config,validation,lifecycle}.rs
- R36: Split mesh/listener.rs (1799 lines) into listener/{mod,session,frames,decode,dispatch,bitcoin}.rs
- R37: Split rpc/mod.rs into mod.rs + dispatcher.rs, middleware.rs, response.rs (54% reduction)
- R38: Split lnd.rs (1064 lines) into lnd/{mod,info,channels,wallet,payments}.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 02:26:28 +00:00
|
|
|
// Onboarding restore (before user account exists)
|
|
|
|
|
"backup.restore-identity",
|
|
|
|
|
// Inter-node RPC: called by federated peers over Tor, no session cookies
|
|
|
|
|
"federation.peer-joined",
|
|
|
|
|
"federation.peer-address-changed",
|
|
|
|
|
"federation.peer-did-changed",
|
|
|
|
|
"federation.get-state",
|
|
|
|
|
// Fleet telemetry ingest: called by remote nodes posting reports
|
|
|
|
|
"telemetry.ingest",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/// Methods whose responses can be cached for a few seconds.
|
chore(ci): rustfmt + clippy clean-up to unblock the Rust CI job
The .github/workflows/ci.yml Rust job runs cargo fmt --check, clippy
with -D warnings, and tests. All three were failing. This commit:
- Applies rustfmt across the tree (the bulk of the diff — untouched
since the last toolchain bump, so a wide sweep was unavoidable).
- Fixes the correctness-level clippy errors:
container/bitcoin_simulator.rs wildcard-in-or-pattern
container/manifest.rs from_str rename to parse (reserved name)
container/podman_client.rs .get(0) -> .first()
container/runtime.rs manual += collapse
archipelago/src/constants.rs doc-comment → module-doc
api/rpc/package/install.rs stray /// comment above a non-item
container/docker_packages.rs redundant field init
streaming/advertisement.rs missing Metric import in tests
tests/orchestration_tests.rs `vec!` in non-Vec contexts
mesh/listener/dispatch.rs unused store_plain_message import
api/rpc/tor/mod.rs and mesh/steganography.rs: push-after-new → vec!
- Quiets wide legacy surfaces with crate-level allows in main.rs for
stylistic lints (too_many_arguments, type_complexity, doc indent,
enum variant prefix, wildcard-in-or, assertions-on-constants,
drop_non_drop, unused_io_amount, ptr_arg) — these fired in dozens
of places with no correctness payoff and have been churning every
toolchain bump.
- Tags intentional-dead-code helpers: wallet/ and streaming/ modules
are WIP, mesh::send_chunked_payload and DM_V1_MARKER are kept for
rollback compatibility, vpn::get_nostr_vpn_status is surface-area
for a not-yet-landed RPC.
cargo fmt --check, cargo clippy --all-targets --all-features
-- -D warnings, and cargo test --all-features now all pass locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:23:46 -04:00
|
|
|
pub(super) const CACHEABLE_METHODS: &[&str] = &["system.stats", "federation.list-nodes"];
|
refactor: split package.rs, mod.rs, listener.rs, and lnd.rs into focused submodules
- R35: Split package.rs (1794 lines) into package/{mod,config,validation,lifecycle}.rs
- R36: Split mesh/listener.rs (1799 lines) into listener/{mod,session,frames,decode,dispatch,bitcoin}.rs
- R37: Split rpc/mod.rs into mod.rs + dispatcher.rs, middleware.rs, response.rs (54% reduction)
- R38: Split lnd.rs (1064 lines) into lnd/{mod,info,channels,wallet,payments}.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 02:26:28 +00:00
|
|
|
|
|
|
|
|
/// Sanitize error messages before returning to clients.
|
|
|
|
|
/// Keeps user-facing validation errors but strips internal system details.
|
|
|
|
|
pub(super) fn sanitize_error_message(msg: &str) -> String {
|
|
|
|
|
// Allow known validation errors through (these are user-actionable)
|
|
|
|
|
let user_facing_prefixes = [
|
|
|
|
|
"Invalid",
|
|
|
|
|
"Missing",
|
|
|
|
|
"Not found",
|
|
|
|
|
"Already exists",
|
|
|
|
|
"Rate limit",
|
|
|
|
|
"Unauthorized",
|
|
|
|
|
"Forbidden",
|
|
|
|
|
"Not supported",
|
2026-05-13 15:09:22 -04:00
|
|
|
"Requires",
|
refactor: split package.rs, mod.rs, listener.rs, and lnd.rs into focused submodules
- R35: Split package.rs (1794 lines) into package/{mod,config,validation,lifecycle}.rs
- R36: Split mesh/listener.rs (1799 lines) into listener/{mod,session,frames,decode,dispatch,bitcoin}.rs
- R37: Split rpc/mod.rs into mod.rs + dispatcher.rs, middleware.rs, response.rs (54% reduction)
- R38: Split lnd.rs (1064 lines) into lnd/{mod,info,channels,wallet,payments}.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 02:26:28 +00:00
|
|
|
"requires",
|
|
|
|
|
"must be",
|
|
|
|
|
"cannot",
|
|
|
|
|
"Password",
|
|
|
|
|
"Session",
|
2026-03-30 16:35:06 +01:00
|
|
|
"Failed to pull",
|
|
|
|
|
"Failed to start",
|
|
|
|
|
"Container",
|
|
|
|
|
"Image",
|
2026-06-12 04:42:23 -04:00
|
|
|
"Bitcoin address",
|
2026-06-29 00:58:30 +00:00
|
|
|
"No router",
|
|
|
|
|
"No OpenWrt",
|
2026-06-29 01:41:36 +00:00
|
|
|
"No space left",
|
2026-06-30 17:12:50 +00:00
|
|
|
"Not enough flash",
|
|
|
|
|
"Not enough space",
|
2026-06-29 01:41:36 +00:00
|
|
|
"TollGate installation failed",
|
|
|
|
|
"No pre-built TollGate",
|
2026-06-29 13:42:07 +00:00
|
|
|
"opkg not found",
|
2026-06-29 14:27:13 +00:00
|
|
|
"apk update failed",
|
2026-06-29 15:11:48 +00:00
|
|
|
"No wireless interface",
|
|
|
|
|
"No wireless radio",
|
2026-06-29 16:21:27 +00:00
|
|
|
"WiFi radio enabled but",
|
2026-06-29 15:11:48 +00:00
|
|
|
"Missing required field",
|
fix: fresh-ISO feedback bug-bash — onboarding, status truthfulness, recovery, kiosk, logs
Fixes from real fresh-install feedback (Framework node .81) + its log bundle:
Backend:
- websocket: subscribe before initial snapshot — broadcasts in the gap were
silently lost, stranding clients on stale state until a hard refresh
(the "everything needs ctrl-r" bug: My Apps stuck Loading, App Store
stuck Checking, containers-scanned never arriving)
- crash recovery: check the crash marker BEFORE writing our own PID —
recovery had never run on any node (always saw its own PID and skipped);
PID-reuse guard via /proc cmdline
- boot status: pending-boot-starts registry (recovery, stack recovery,
reconciler, adoption) — scanner overlays queued-but-down apps as
Restarting instead of Stopped after a reboot; scanner-authored
Restarting resolves immediately on a settled scan (no transitional wedge)
- install deps: bounded wait (36x5s) when a dependency is installed but
still starting ("Waiting for Bitcoin to start…") instead of instant
rejection; dependency-gate rejections remove the optimistic entry (no
phantom Stopped tile) and surface as a notification
- seed backup: auth.setup persists the onboarding mnemonic as the
encrypted seed backup (reveal previously failed on EVERY node — nothing
ever wrote master_seed.enc); seed.restore stashes too; error sanitizer
lets seed/2FA errors through instead of "Check server logs"
- lnd: bitcoind.rpchost resolved from the running Bitcoin variant
(hardcoded bitcoin-knots broke Core nodes); manifest uses derived_env
- bitcoin status: clean human message for connection-reset/startup; raw
URLs + os-error chains no longer reach the app card
- fedimint-clientd: chown /var/lib/archipelago/fmcd to 1000:1000 (root-
created dir crash-looped the rootless container, EACCES) — first-boot
script + pre-start self-heal
- log volume (>1GB/day on a day-old node): journald caps drop-in (ISO +
bootstrap self-heal), bitcoind -printtoconsole=0 everywhere (90% of the
journal was IBD UpdateTip spam), tracing default debug→info
Frontend:
- Login: Enter advances to confirm field then submits; submit always
clickable with inline errors (was silently disabled on mismatch);
Restart Onboarding needs a confirming second click (the mismatch →
"onboarding restarted" trap)
- sync store: 30s state reconciliation + refetch on re-entrant connect;
20s containers-scanned escape hatch so Checking can never show forever;
fresh empty node reaches the real "no apps yet" state
- intro video: CRF20 re-encode (SSIM 0.988) + faststart — moov was at EOF
so playback needed the full 15MB first (the intro lag)
- backgrounds: 10 heaviest JPEGs → WebP q90 (9.4MB→6.6MB); 7 stayed JPEG
(WebP larger on noisy sources)
- Web5ConnectedNodes: drop unused template ref that failed vue-tsc -b
ISO/kiosk:
- nginx: /assets/ 404s no longer cached immutable for a year; HTTPS block
gained the missing /assets/ location (served index.html as images)
- kiosk: launcher/service spliced from configs/ at ISO build (stale
heredoc force-disabled GPU); MemoryHigh/Max 1200/1500→2200/2800M (kiosk
rode the reclaim throttle = the lag); firmware-intel-graphics +
firmware-amd-graphics (trixie split DMC blobs out of misc-nonfree)
Verified: cargo test 898/898 green, npm run build green with dist
contents confirmed (webp refs, lnd.png, faststart video, new strings).
Handover for ISO build + deploy: docs/HANDOVER-2026-07-02-iso-feedback.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 08:00:39 -04:00
|
|
|
// seed.reveal / auth flows — user-actionable, no internals to leak.
|
|
|
|
|
// Without these the sanitizer collapsed every reveal failure into
|
|
|
|
|
// "Operation failed. Check server logs." (which isn't even a crash).
|
|
|
|
|
"Incorrect",
|
|
|
|
|
"This node has no encrypted seed",
|
|
|
|
|
"A 2FA code is required",
|
|
|
|
|
"2FA is enabled but",
|
|
|
|
|
"Could not decrypt the saved seed",
|
|
|
|
|
"Could not unlock 2FA",
|
|
|
|
|
"No mnemonic available",
|
|
|
|
|
"No pending seed generation",
|
|
|
|
|
"Submitted words",
|
|
|
|
|
"Already set up",
|
refactor: split package.rs, mod.rs, listener.rs, and lnd.rs into focused submodules
- R35: Split package.rs (1794 lines) into package/{mod,config,validation,lifecycle}.rs
- R36: Split mesh/listener.rs (1799 lines) into listener/{mod,session,frames,decode,dispatch,bitcoin}.rs
- R37: Split rpc/mod.rs into mod.rs + dispatcher.rs, middleware.rs, response.rs (54% reduction)
- R38: Split lnd.rs (1064 lines) into lnd/{mod,info,channels,wallet,payments}.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 02:26:28 +00:00
|
|
|
];
|
|
|
|
|
for prefix in &user_facing_prefixes {
|
|
|
|
|
if msg.starts_with(prefix) {
|
|
|
|
|
// Truncate long messages and strip file paths
|
chore(ci): rustfmt + clippy clean-up to unblock the Rust CI job
The .github/workflows/ci.yml Rust job runs cargo fmt --check, clippy
with -D warnings, and tests. All three were failing. This commit:
- Applies rustfmt across the tree (the bulk of the diff — untouched
since the last toolchain bump, so a wide sweep was unavoidable).
- Fixes the correctness-level clippy errors:
container/bitcoin_simulator.rs wildcard-in-or-pattern
container/manifest.rs from_str rename to parse (reserved name)
container/podman_client.rs .get(0) -> .first()
container/runtime.rs manual += collapse
archipelago/src/constants.rs doc-comment → module-doc
api/rpc/package/install.rs stray /// comment above a non-item
container/docker_packages.rs redundant field init
streaming/advertisement.rs missing Metric import in tests
tests/orchestration_tests.rs `vec!` in non-Vec contexts
mesh/listener/dispatch.rs unused store_plain_message import
api/rpc/tor/mod.rs and mesh/steganography.rs: push-after-new → vec!
- Quiets wide legacy surfaces with crate-level allows in main.rs for
stylistic lints (too_many_arguments, type_complexity, doc indent,
enum variant prefix, wildcard-in-or, assertions-on-constants,
drop_non_drop, unused_io_amount, ptr_arg) — these fired in dozens
of places with no correctness payoff and have been churning every
toolchain bump.
- Tags intentional-dead-code helpers: wallet/ and streaming/ modules
are WIP, mesh::send_chunked_payload and DM_V1_MARKER are kept for
rollback compatibility, vpn::get_nostr_vpn_status is surface-area
for a not-yet-landed RPC.
cargo fmt --check, cargo clippy --all-targets --all-features
-- -D warnings, and cargo test --all-features now all pass locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:23:46 -04:00
|
|
|
let sanitized = msg
|
|
|
|
|
.replace("/var/lib/archipelago/", "[data]/")
|
refactor: split package.rs, mod.rs, listener.rs, and lnd.rs into focused submodules
- R35: Split package.rs (1794 lines) into package/{mod,config,validation,lifecycle}.rs
- R36: Split mesh/listener.rs (1799 lines) into listener/{mod,session,frames,decode,dispatch,bitcoin}.rs
- R37: Split rpc/mod.rs into mod.rs + dispatcher.rs, middleware.rs, response.rs (54% reduction)
- R38: Split lnd.rs (1064 lines) into lnd/{mod,info,channels,wallet,payments}.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 02:26:28 +00:00
|
|
|
.replace("/usr/local/bin/", "[bin]/")
|
|
|
|
|
.replace("/etc/", "[config]/");
|
|
|
|
|
return if sanitized.len() > 200 {
|
|
|
|
|
format!("{}...", &sanitized[..200])
|
|
|
|
|
} else {
|
|
|
|
|
sanitized
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// For all other errors, return a generic message
|
|
|
|
|
"Operation failed. Check server logs for details.".to_string()
|
|
|
|
|
}
|
|
|
|
|
|
fix: fresh-ISO feedback bug-bash — onboarding, status truthfulness, recovery, kiosk, logs
Fixes from real fresh-install feedback (Framework node .81) + its log bundle:
Backend:
- websocket: subscribe before initial snapshot — broadcasts in the gap were
silently lost, stranding clients on stale state until a hard refresh
(the "everything needs ctrl-r" bug: My Apps stuck Loading, App Store
stuck Checking, containers-scanned never arriving)
- crash recovery: check the crash marker BEFORE writing our own PID —
recovery had never run on any node (always saw its own PID and skipped);
PID-reuse guard via /proc cmdline
- boot status: pending-boot-starts registry (recovery, stack recovery,
reconciler, adoption) — scanner overlays queued-but-down apps as
Restarting instead of Stopped after a reboot; scanner-authored
Restarting resolves immediately on a settled scan (no transitional wedge)
- install deps: bounded wait (36x5s) when a dependency is installed but
still starting ("Waiting for Bitcoin to start…") instead of instant
rejection; dependency-gate rejections remove the optimistic entry (no
phantom Stopped tile) and surface as a notification
- seed backup: auth.setup persists the onboarding mnemonic as the
encrypted seed backup (reveal previously failed on EVERY node — nothing
ever wrote master_seed.enc); seed.restore stashes too; error sanitizer
lets seed/2FA errors through instead of "Check server logs"
- lnd: bitcoind.rpchost resolved from the running Bitcoin variant
(hardcoded bitcoin-knots broke Core nodes); manifest uses derived_env
- bitcoin status: clean human message for connection-reset/startup; raw
URLs + os-error chains no longer reach the app card
- fedimint-clientd: chown /var/lib/archipelago/fmcd to 1000:1000 (root-
created dir crash-looped the rootless container, EACCES) — first-boot
script + pre-start self-heal
- log volume (>1GB/day on a day-old node): journald caps drop-in (ISO +
bootstrap self-heal), bitcoind -printtoconsole=0 everywhere (90% of the
journal was IBD UpdateTip spam), tracing default debug→info
Frontend:
- Login: Enter advances to confirm field then submits; submit always
clickable with inline errors (was silently disabled on mismatch);
Restart Onboarding needs a confirming second click (the mismatch →
"onboarding restarted" trap)
- sync store: 30s state reconciliation + refetch on re-entrant connect;
20s containers-scanned escape hatch so Checking can never show forever;
fresh empty node reaches the real "no apps yet" state
- intro video: CRF20 re-encode (SSIM 0.988) + faststart — moov was at EOF
so playback needed the full 15MB first (the intro lag)
- backgrounds: 10 heaviest JPEGs → WebP q90 (9.4MB→6.6MB); 7 stayed JPEG
(WebP larger on noisy sources)
- Web5ConnectedNodes: drop unused template ref that failed vue-tsc -b
ISO/kiosk:
- nginx: /assets/ 404s no longer cached immutable for a year; HTTPS block
gained the missing /assets/ location (served index.html as images)
- kiosk: launcher/service spliced from configs/ at ISO build (stale
heredoc force-disabled GPU); MemoryHigh/Max 1200/1500→2200/2800M (kiosk
rode the reclaim throttle = the lag); firmware-intel-graphics +
firmware-amd-graphics (trixie split DMC blobs out of misc-nonfree)
Verified: cargo test 898/898 green, npm run build green with dist
contents confirmed (webp refs, lnd.png, faststart video, new strings).
Handover for ISO build + deploy: docs/HANDOVER-2026-07-02-iso-feedback.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 08:00:39 -04:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod sanitize_tests {
|
|
|
|
|
use super::sanitize_error_message;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn seed_reveal_errors_pass_through() {
|
|
|
|
|
// Every user-actionable seed.reveal failure must reach the user —
|
|
|
|
|
// masking them as "Check server logs" sent a real user hunting a
|
|
|
|
|
// crash that never happened.
|
|
|
|
|
for msg in [
|
|
|
|
|
"Incorrect password",
|
|
|
|
|
"This node has no encrypted seed backup, so the recovery phrase cannot be shown. It was only displayed once during setup.",
|
|
|
|
|
"A 2FA code is required to reveal the recovery phrase",
|
|
|
|
|
"2FA is enabled but no TOTP data found",
|
|
|
|
|
"Could not decrypt the saved seed. If you set a separate backup passphrase during setup, enter that passphrase.",
|
|
|
|
|
"Could not unlock 2FA with this password",
|
|
|
|
|
"No mnemonic available. Generate or restore a seed first.",
|
|
|
|
|
"Submitted words do not match generated seed",
|
|
|
|
|
"Already set up. Use auth.changePassword to change.",
|
|
|
|
|
] {
|
|
|
|
|
assert_ne!(
|
|
|
|
|
sanitize_error_message(msg),
|
|
|
|
|
"Operation failed. Check server logs for details.",
|
|
|
|
|
"masked: {msg}"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn internal_errors_stay_generic() {
|
|
|
|
|
assert_eq!(
|
|
|
|
|
sanitize_error_message("thread panicked at src/foo.rs:42"),
|
|
|
|
|
"Operation failed. Check server logs for details."
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
refactor: split package.rs, mod.rs, listener.rs, and lnd.rs into focused submodules
- R35: Split package.rs (1794 lines) into package/{mod,config,validation,lifecycle}.rs
- R36: Split mesh/listener.rs (1799 lines) into listener/{mod,session,frames,decode,dispatch,bitcoin}.rs
- R37: Split rpc/mod.rs into mod.rs + dispatcher.rs, middleware.rs, response.rs (54% reduction)
- R38: Split lnd.rs (1064 lines) into lnd/{mod,info,channels,wallet,payments}.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 02:26:28 +00:00
|
|
|
/// Derive a CSRF token from the session token via HMAC.
|
|
|
|
|
/// Deterministic: same session token always produces the same CSRF token.
|
|
|
|
|
/// Survives backend restarts because it depends only on the session token
|
|
|
|
|
/// and the on-disk remember secret (not ephemeral state).
|
|
|
|
|
pub(super) async fn derive_csrf_token(session_token: &str) -> String {
|
|
|
|
|
use hmac::{Hmac, Mac};
|
|
|
|
|
use sha2::Sha256;
|
|
|
|
|
type HmacSha256 = Hmac<Sha256>;
|
|
|
|
|
let secret = SessionStore::load_or_create_remember_secret().await;
|
|
|
|
|
let mut mac = HmacSha256::new_from_slice(&secret).expect("HMAC key");
|
|
|
|
|
mac.update(format!("csrf:{}", session_token).as_bytes());
|
|
|
|
|
hex::encode(mac.finalize().into_bytes())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Extract a named cookie value from headers.
|
|
|
|
|
pub(super) fn extract_cookie(headers: &hyper::HeaderMap, name: &str) -> Option<String> {
|
|
|
|
|
let prefix = format!("{}=", name);
|
|
|
|
|
for value in headers.get_all("cookie") {
|
|
|
|
|
if let Ok(s) = value.to_str() {
|
|
|
|
|
for part in s.split(';') {
|
|
|
|
|
let part = part.trim();
|
|
|
|
|
if let Some(val) = part.strip_prefix(&prefix) {
|
|
|
|
|
let val = val.trim();
|
|
|
|
|
if !val.is_empty() {
|
|
|
|
|
return Some(val.to_string());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Extract the client IP from request headers (X-Real-IP or X-Forwarded-For).
|
|
|
|
|
pub(super) fn extract_client_ip(headers: &hyper::HeaderMap) -> IpAddr {
|
|
|
|
|
headers
|
|
|
|
|
.get("x-real-ip")
|
|
|
|
|
.or_else(|| headers.get("x-forwarded-for"))
|
|
|
|
|
.and_then(|v| v.to_str().ok())
|
|
|
|
|
.and_then(|s| s.split(',').next())
|
|
|
|
|
.and_then(|s| s.trim().parse::<IpAddr>().ok())
|
|
|
|
|
.unwrap_or(IpAddr::V4(std::net::Ipv4Addr::LOCALHOST))
|
|
|
|
|
}
|