Clean up old confirm page copy

This commit is contained in:
Jonathon Leight
2026-08-09 14:04:12 -04:00
parent 93985c98c6
commit 935d6ff1e9
10 changed files with 23 additions and 23 deletions
+1 -1
View File
@@ -202,7 +202,7 @@ func run(logger *slog.Logger) error {
}
// Shutdown doesn't close hijacked/WebSocket connections, so close the active
// console/confirm sockets and give their handlers time to unwind and stamp
// console sockets and give their handlers time to unwind and stamp
// their session's ended_at (see core.WSDrainTimeout) before we stop the flusher
// and close the pool. Too short and drained sessions stay "in progress" forever.
wsCtx, wsCancel := context.WithTimeout(context.Background(), core.WSDrainTimeout)
+6 -6
View File
@@ -268,8 +268,7 @@ func (s *Handlers) wsConsole(w http.ResponseWriter, r *http.Request) {
debug := r.URL.Query().Get("debug") == "1"
if debug {
// Dump every inbound KISS frame as hex so we can see exactly what the modem
// reports back (e.g. whether the repeater replies at all). Same aid the
// dedicated confirm page used to offer.
// reports back (e.g. whether the repeater replies at all).
bridge.SetObserver(func(f *hardware.KissFrame) {
_ = bridge.Status("debug", fmt.Sprintf("rx frame cmd=0x%02x len=%d data=%x", f.Command, len(f.Data), f.Data))
})
@@ -378,9 +377,9 @@ func (s *Handlers) wsConsole(w http.ResponseWriter, r *http.Request) {
if userPathSet {
reportPathOutcome(bridge, lr)
}
// A successful login proves we reached the repeater, so treat connecting from
// the console as a confirmation (the same as the dedicated confirm flow) — for
// guest access too, which records the access level. This is cheap (no extra
// A successful login proves we reached the repeater, so connecting the console
// IS how a repeater becomes confirmed — for guest access too, which records the
// access level. This is cheap (no extra
// packets). Fetching the location is deferred to an explicit "getloc" request
// so a plain console session doesn't pay for a location round-trip it doesn't
// need; the page offers a "Fetch location" button that sends it.
@@ -389,7 +388,8 @@ func (s *Handlers) wsConsole(w http.ResponseWriter, r *http.Request) {
} else if lr.IsAdmin {
_ = bridge.Status("confirmed", "Repeater confirmed with admin access. ✓")
} else {
// Guests can't run CLI commands, so warn as the confirm flow did.
// Guests can't run CLI commands, so say so rather than leaving the user to
// discover it one refused command at a time.
_ = bridge.Status("warning", fmt.Sprintf("Repeater reached, but MeshTender only has GUEST access (permissions=%d). Guest is open to anyone with a blank password, so MeshTender can't administer this repeater — re-run `%s` to grant admin.", lr.Permissions, s.Identity.SetPermCommand()))
}
}
+2 -2
View File
@@ -180,7 +180,7 @@ func TestConsoleRoundTrip(t *testing.T) {
}
// Connecting from the console with a successful admin login confirms the
// repeater — the same as running the dedicated confirm flow.
// repeater: connecting the console is what confirms it.
confirmed, err := st.GetRepeaterForUser(ctx, user.ID, rep.ID)
must(err, "reload repeater")
if !confirmed.Confirmed {
@@ -343,7 +343,7 @@ func TestConsoleGetLatUpdatesLocation(t *testing.T) {
// TestConsoleGuestLoginConfirms: connecting the console to a repeater that only
// grants GUEST access still records the confirmation (with is_admin=false) and
// warns the user — the same as the dedicated confirm flow did. It must NOT emit a
// warns the user. It must NOT emit a
// "confirmed" (admin) status.
func TestConsoleGuestLoginConfirms(t *testing.T) {
t.Parallel()
+5 -5
View File
@@ -91,8 +91,8 @@ func TestConsoleFetchesLocation(t *testing.T) {
shared, _ := repeater.SharedSecret(serverID)
_ = ws.Write(rw, websocket.MessageText, []byte(`{"type":"ready"}`))
// The console fetches location only on request (unlike the old confirm flow's
// eager fetch). Queue the getloc; the console processes it after login.
// The console fetches location only on request. Queue the getloc; the console
// processes it after login.
_ = ws.Write(rw, websocket.MessageText, []byte(`{"type":"getloc"}`))
// Reply to login (PATH) then to get lat / get lon (TXT_MSG).
@@ -166,9 +166,9 @@ func TestConsoleFetchesLocation(t *testing.T) {
break // socket closed; check the DB below
}
if typ == websocket.MessageText {
// Unlike the old confirm flow, the console stays open after fetching
// location (it's an interactive session), so it never closes the socket
// on its own. It signals success with a "location" status — stop then.
// The console is an interactive session: it stays open after fetching the
// location and never closes the socket on its own. It signals success with
// a "location" status — stop then.
var m struct{ State, Message string }
if json.Unmarshal(data, &m) == nil && m.State == "location" {
break
+1 -1
View File
@@ -137,7 +137,7 @@
</script>
</div>
{{else}}
<div class="card-body"><p class="text-secondary mb-0">No repeater locations yet. Enable “store location” when editing a repeater and confirm it with a modem to place it on the map.</p></div>
<div class="card-body"><p class="text-secondary mb-0">No repeater locations yet. Open a repeater's console, connect your modem, and use “Fetch location” to read its coordinates onto the map — or set them when you add it.</p></div>
{{end}}
</div>
</div>
+1 -1
View File
@@ -32,7 +32,7 @@ type Handlers struct {
Auth *auth.Service
// wsCtx is the base context for WebSocket handlers; cancelling it (on shutdown)
// signals active console/confirm sockets to close. wsWG tracks those handlers so
// signals active console sockets to close. wsWG tracks those handlers so
// shutdown can wait for them — http.Server.Shutdown does not close hijacked
// (WebSocket) connections.
wsCtx context.Context
+2 -2
View File
@@ -10,8 +10,8 @@ import (
"github.com/chromedp/chromedp"
)
// TestE2EConsoleConfirmBanner is the browser regression for folding the old
// confirm page into the console: an unconfirmed repeater shows the "not
// TestE2EConsoleConfirmBanner covers the console's two state banners, which are
// the only place confirmation surfaces: an unconfirmed repeater shows the "not
// confirmed yet" banner, and a confirmed repeater with no known location shows
// the location prompt with a "Fetch location" button. Both must render under the
// strict CSP with no violations (the console's inline nonce'd bootstrap plus
+3 -3
View File
@@ -29,7 +29,7 @@
aria-label="setperm command with MeshTender's public key" readonly>
<button type="button" class="btn" data-copy-target="#setperm-cmd">Copy</button>
</div>
<p class="text-secondary small mb-0">The long value is <strong>MeshTender's</strong> public key — not your repeater's. If you see a guest-access warning after confirming a repeater, this is the fix: run the command above, then confirm again.</p>
<p class="text-secondary small mb-0">The long value is <strong>MeshTender's</strong> public key — not your repeater's. If you see a guest-access warning when the console connects, this is the fix: run the command above, then connect again.</p>
{{else}}
<p class="text-secondary mb-0">Run <code>setperm &lt;MeshTender's public key&gt; 3</code> on your repeater. Sign in and start adding a repeater to see the exact command with the key filled in.</p>
{{end}}
@@ -42,9 +42,9 @@
<p>There are two ways in, matching how you reach the repeater:</p>
<ul class="mb-2">
<li><strong>Set up a brand-new repeater (USB)</strong> — connect a freshly-flashed repeater over USB serial and MeshTender runs the whole setup for you: identity, radio, region, name, and the admin grant. Best for a repeater that isn't on the mesh yet.</li>
<li><strong>Add one that's already on the network (over the air)</strong> — for a repeater you've already configured, MeshTender reaches it over the air through a <a href="#kiss">KISS modem</a> attached to your browser. You grant MeshTender admin with the <a href="#setperm"><code>setperm</code></a> command yourself first, then <em>confirm</em> it here.</li>
<li><strong>Add one that's already on the network (over the air)</strong> — for a repeater you've already configured, MeshTender reaches it over the air through a <a href="#kiss">KISS modem</a> attached to your browser. You grant MeshTender admin with the <a href="#setperm"><code>setperm</code></a> command yourself first, then register it here.</li>
</ul>
<p>Confirming does a live login round-trip to prove MeshTender can reach and administer the repeater. If it reports guest access, the <code>setperm</code> grant hasn't been applied yet.</p>
<p>A registered repeater shows as <strong>confirmed</strong> once MeshTender has actually reached it. There's nothing to click for that: open the repeater's <strong>console</strong> and connect your modem, and the signed login that opens every session doubles as the proof — it confirms the repeater the first time it replies. If the reply reports guest access, the <code>setperm</code> grant hasn't been applied yet; run it, then connect again.</p>
<p class="mb-0">Either way, the device must already be running MeshCore's <strong>Repeater</strong> firmware — MeshTender configures repeaters, it doesn't flash them. A MeshCore device runs one firmware at a time (Repeater, a Companion variant, or KISS modem), so switching roles means re-flashing.</p>
</div>
</div>
+1 -1
View File
@@ -251,7 +251,7 @@ func seedRepeaters(ctx context.Context, st *store.Store, f *gofakeit.Faker, user
_ = st.UpdateRepeater(ctx, u.ID, rep.ID, rep.Name, preset.freq, preset.bw, preset.sf, preset.cr, showOnOrg, true)
}
_ = st.UpdateRepeaterDocs(ctx, u.ID, rep.ID, f.Paragraph(1, 3, 14, " "), f.Paragraph(1, 2, 10, " "))
// ~70% confirmed with admin access (perms 3 = admin, per the confirm flow).
// ~70% confirmed with admin access (perms 3 = admin).
if chance(f, 70) {
_ = st.SetRepeaterConfirmed(ctx, rep.ID, u.ID, true, 3)
}
+1 -1
View File
@@ -435,7 +435,7 @@ var compressHTML = middleware.Compress(5, compressibleTypes...)
// command lists, markdown docs) — so 1 MiB is generous while stopping a client
// from streaming an arbitrarily large body into memory before per-field limits
// can apply. WebSocket upgrades carry no request body, so this doesn't affect
// the console/confirm sockets.
// the console socket.
const maxRequestBody = 1 << 20 // 1 MiB
// limitBody caps r.Body so an oversized request fails fast — a read past the