Gate the guest Wi-Fi card on its own key, and gate its poll endpoint properly

The home card used to be grant-gated by the shell's card table, which left the
poll endpoint behind `auth` alone — and that endpoint returns a LIVE CREDENTIAL:
the guest network's current rotating password and its join QR code. "Any logged-in
user" was never an adequate gate for that; the card table was doing the work, and
the card table is gone.

New key rather than reuse: unifi.guest_wifi exists precisely so front-desk staff
can be given the guest password WITHOUT any other network access. Reusing
unifi.settings would have meant handing out controller configuration to get a
Wi-Fi password read.

Authorised inside the controller rather than by route middleware, so the endpoint
stays outside the unifi page-permission groups — it is a home-page widget, not a
unifi page.

Nobody holds the new key until it is granted, so the card is invisible until then.
That is the intended direction: capability is assigned deliberately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDA3fQbsa3gaHCFoV1Q8Gg
This commit is contained in:
2026-07-30 12:15:05 -04:00
parent c93f73ce02
commit 186c1eaf2b
4 changed files with 47 additions and 18 deletions

View File

@@ -22,9 +22,22 @@ class UnifiServiceProvider extends ServiceProvider
$this->loadMigrationsFrom(__DIR__ . '/../database/migrations');
// "/" home card: guest Wi-Fi network + current rotating password +
// QR (re-renders when the password changes). Grant-gated via
// Admin → Settings → Dashboard Cards; self-hides until enabled and
// a password has been rotated.
// QR (re-renders when the password changes). Self-hides until enabled
// and a password has been rotated.
//
// The ONE card in the platform that earned a NEW permission key rather
// than reusing one. It cannot be universal — the payload is a live
// credential, and publishing it to every student and parent is the exact
// hole GuestCardController::data was written to close. And no existing
// unifi key fits: unifi.stats is the network dashboards, unifi.settings is
// the controller connection, and handing the front desk either one to let
// them read a guest password would widen access, not reuse it. So the
// granularity goes in the catalogue (same move as the papercut/inventory
// split), and the key is grantable to non-unifi staff — which is the whole
// point of a home-page card.
//
// Declared in composer.json extra.dashboard.permissions, so the catalogue
// row appears when unifi is installed/updated — NOT seeded to any group.
try {
app(\App\Support\DashboardCardRegistry::class)->register(
'unifi.guest_wifi',
@@ -32,6 +45,7 @@ class UnifiServiceProvider extends ServiceProvider
'UnifiGuestWifi',
fn (\App\Models\User $user) => \Dashboard\Unifi\Http\Controllers\GuestCardController::resolve($user),
'The guest network name, current password, and a join QR code — updates automatically when the password rotates.',
permission: 'unifi.guest_wifi',
);
} catch (\Throwable) {
// Older shell without the card registry — skip.