diff --git a/composer.json b/composer.json index a2f4b03..8e1f3e0 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "dashboard/unifi", "description": "UniFi network management, WiFi stats, and captive portal authentication for the Dashboard platform", - "version": "1.13.2", + "version": "1.13.3", "type": "library", "license": "MIT", "autoload": { diff --git a/src/routes/unifi.php b/src/routes/unifi.php index 0ec716e..2f37a0f 100644 --- a/src/routes/unifi.php +++ b/src/routes/unifi.php @@ -121,3 +121,21 @@ Route::prefix('api/unifi')->name('unifi.api.')->group(function () { Route::middleware(['web', 'auth']) ->get('/portal/wifi/callback', [PortalController::class, 'captiveCallback']) ->name('unifi.portal.callback'); + +/* + * Let a user disconnect their OWN device from the captive-portal denial page. + * + * The admin route for this (unifi.portal.sessions.disconnect) sits behind + * `app.access:unifi` + `permission:unifi.auth`, so a student who is told "you + * already have 3 devices connected, disconnect one" was blocked by middleware + * before reaching the action — the page's `can_disconnect: true` was a promise + * the routing could not keep. + * + * Safe to expose with only `auth`: disconnectSession() already authorises + * owner-or-unifi.auth itself (`abort_if($session->user_id !== auth()->id() && ! + * auth()->user()->can('unifi.auth'), 403)`), so a non-owner still gets 403. The + * admin route is left exactly as it was rather than loosened. + */ +Route::middleware(['web', 'auth']) + ->post('/portal/wifi/sessions/{session}/disconnect', [PortalController::class, 'disconnectSession']) + ->name('unifi.portal.self-disconnect');