diff --git a/docs/superpowers/specs/2026-08-21-byod-wifi-portal-hardening-design.md b/docs/superpowers/specs/2026-08-21-byod-wifi-portal-hardening-design.md index c3207fe..759bd18 100644 --- a/docs/superpowers/specs/2026-08-21-byod-wifi-portal-hardening-design.md +++ b/docs/superpowers/specs/2026-08-21-byod-wifi-portal-hardening-design.md @@ -53,6 +53,8 @@ The portal is not the obstacle. **MAC randomization is** — see decision 2. | 4 | Add `role` as a `match_type` | Quotas were asked for per role; roles are the platform's own authority and need no external sync | | 5 | One Wi-Fi Devices page grouped by person; no profile card | Matches the requirement exactly; keeps `/admin/users` as the one directory | | 6 | Device type is **reported, not enforced**, for now | Whether `dev_cat` is populated in this environment is unverified; enforcing on unvalidated data can lock out the wrong people | +| 7 | **Explicit allow-list only** — no `default` mapping | No Wi-Fi unless an admin has added that role and set an amount. A `default` row silently grants everyone, which is the opposite of the requirement | +| 8 | Roles are chosen from a dropdown; session length entered in days | A free-text role slug that matches nothing fails silently — it presents as "this student has no Wi-Fi" with no error anywhere to explain it | --- @@ -173,6 +175,41 @@ so "one term" means one term. --- +## C2. Settings: who gets Wi-Fi, and how much + +Access is an **explicit allow-list**. A person gets Wi-Fi only if one of their +roles has been added here with an amount. No role added means no Wi-Fi, and that +is the intended answer rather than a gap to be filled by a catch-all. + +Consequences, stated so nobody re-adds the hole later: + +- **`default` is removed from the available match types.** A `default` row matches + everybody, so its existence makes the allow-list advisory. `resolveMapping()` + already returns null when nothing matches, and null already produces the + "No WiFi access configured for your account." denial — so deny-by-default is + the behaviour once the catch-all cannot be created. +- Safe to remove now: **there are zero rows in `unifi_vlan_mappings` in any + environment**, so nothing is being revoked from anyone. Doing this later, after + a school had leaned on a catch-all, would cut off every unmapped person at once. +- `email_domain`, `group` and `ou` stay available — they are narrow matches, not + catch-alls, and removing working options is not part of this. + +### The settings screen + +Extends the existing Portal settings page rather than adding another one. Per row: + +| Field | Control | Why | +|---|---|---| +| Role | **dropdown of real seeded roles** | Free text is the defect. `match_value` is a string compared exactly, so `Student` or `studnet` matches nobody and the failure surfaces as a pupil with no Wi-Fi and nothing logged. The dropdown makes an unmatchable value impossible to enter. | +| Devices | number, 1..50 | The quota. Column is already bounded. | +| Session length | **days, with presets** (a week / a month / a term / a year) | `session_minutes` stays the stored unit, but a term is 172,800 minutes and nobody should type that. Same reasoning as presenting cron schedules as presets rather than raw strings. | +| VLAN | existing select | **Still required** — the network needs to know where to put the device, and this was not in the original request, so it is called out rather than quietly dropped. | +| Order | existing `sort_order` | Precedence for multi-role people: staff above parent gives a staff-parent 4. | + +Roles already in the system and available to add: staff, student, parent, +oncall-staff, coach, alumni, applicant, board-member, contact, homeschool, +homeschool-parent, homeschool-student, open-house, tuition-payee, forms-payer. + ## D. The Wi-Fi Devices page New page under Unifi, gated on the existing `unifi.auth`, grouped by person.