fix(tollgate): wire up TollGate's real captive portal (was serving NDS's stock page)

Found live: after fixing DHCP, the user could join the archipelago SSID,
saw a splash page, clicked "Continue", and got straight to the internet —
no payment step at all. NoDogSplash was serving its own bundled generic
click-to-continue splash page, whose "Continue" button calls NDS's
built-in auth handler directly and authorizes the client unconditionally.

TollGate's actual payment UI — a React SPA with a Cashu/QR token entry
flow — was already sitting on disk at
/etc/tollgate/tollgate-captive-portal-site (staged by the .ipk's data
payload during install), just never wired up as NoDogSplash's webroot.

install_captive_portal_symlink() mirrors upstream's own
90-tollgate-captive-portal-symlink uci-defaults script exactly: swap
/etc/nodogsplash/htdocs for a symlink to the real portal directory,
backing up any existing real directory first. Confirmed live that
setting `option webroot` directly instead (rather than the symlink
swap) makes NoDogSplash 500 on every request for reasons not fully
understood — the symlink approach is what's actually shipped/tested
upstream, so that's what this uses.

Also restores `authenticated_users 'allow all'` (the stock package
default our from-scratch nodogsplash.main section never carried over)
for correctness, even though this router's default-ACCEPT FORWARD
policy happens to make an empty list behave the same.
This commit is contained in:
ssmithx 2026-07-02 22:56:53 +00:00
parent 6060ad23b2
commit 9902ffd31d
2 changed files with 56 additions and 0 deletions

View File

@ -48,6 +48,15 @@ pub async fn provision(router: &Router, config: &TollGateConfig) -> Result<()> {
nodogsplash::install_and_stop(router, pkg_mgr)
.context("install nodogsplash — tollgate-wrt cannot gate clients without it")?;
// Wire NoDogSplash's webroot to TollGate's actual payment portal instead
// of the generic stock splash page it ships with. Confirmed live: without
// this, "click continue" on the stock page authorizes the client via
// NoDogSplash's own built-in handler with zero payment involved.
nodogsplash::install_captive_portal_symlink(router).context(
"wire up TollGate's captive portal — without it NoDogSplash serves its own \
generic splash page, which authorizes clients on click with no payment",
)?;
config::apply(router, config)?;
wifi::provision_ssid(router, config)?;
// Must come after provision_ssid (which creates br-tollgate) and before

View File

@ -29,6 +29,45 @@ pub fn install_and_stop(router: &Router, pkg_mgr: PkgManager) -> Result<()> {
Ok(())
}
/// Point NoDogSplash's webroot at TollGate's own splash page instead of the
/// generic stock one NoDogSplash ships with.
///
/// Confirmed live: without this, NoDogSplash serves its own bundled
/// click-to-continue splash page — clicking "Continue" calls NDS's built-in
/// auth handler directly and authorizes the client with zero payment
/// involved. TollGate's actual payment UI (a QR/Cashu-token entry SPA) lives
/// at `/etc/tollgate/tollgate-captive-portal-site` — the .ipk's data payload
/// stages it there (see `packaging/files/tollgate-captive-portal-site/` in
/// the upstream repo), it's just never wired up as NoDogSplash's webroot.
///
/// Mirrors upstream's own `90-tollgate-captive-portal-symlink` uci-defaults
/// script exactly (symlink swap, not a `webroot` UCI override) — confirmed
/// live that setting `option webroot` directly instead causes NoDogSplash to
/// 500 on every request, for reasons not fully understood (worth filing
/// upstream, but the symlink approach is what's actually shipped/tested).
pub fn install_captive_portal_symlink(router: &Router) -> Result<()> {
let (_, exists) = router.run("test -d /etc/tollgate/tollgate-captive-portal-site")?;
if exists != 0 {
anyhow::bail!(
"/etc/tollgate/tollgate-captive-portal-site missing — expected to be staged \
by the tollgate-wrt package install"
);
}
router.run_ok(
"if [ -L /etc/nodogsplash/htdocs ]; then \
true; \
else \
if [ -d /etc/nodogsplash/htdocs ]; then \
mv /etc/nodogsplash/htdocs /etc/nodogsplash/htdocs.backup; \
fi; \
rm -rf /etc/nodogsplash/htdocs; \
ln -sf /etc/tollgate/tollgate-captive-portal-site /etc/nodogsplash/htdocs; \
fi"
)?;
Ok(())
}
/// Configure NoDogSplash to gate the dedicated `br-tollgate` bridge (see
/// `wifi::provision_network`), not `br-lan` — the paid SSID here lives on its
/// own isolated network/subnet rather than the canonical upstream layout
@ -80,6 +119,14 @@ pub fn configure(router: &Router, cfg: &TollGateConfig) -> Result<()> {
router.uci_add_list("nodogsplash.main.users_to_router", "allow tcp port 2121")?;
router.uci_add_list("nodogsplash.main.users_to_router", "allow tcp port 2050")?;
// Post-auth (paid) clients get full access — matches the stock package
// default (`list authenticated_users 'allow all'`), which our from-scratch
// named section never carried over. Under this router's default-ACCEPT
// FORWARD policy an empty list happens to behave the same, but that's an
// accident of this specific setup, not something to depend on.
let _ = router.uci_delete("nodogsplash.main.authenticated_users");
router.uci_add_list("nodogsplash.main.authenticated_users", "allow all")?;
router.uci_commit(Some("nodogsplash"))?;
Ok(())
}