2 Commits

Author SHA1 Message Date
f953fde2be release: 1.7.0 — rolls up the 1.6.1/1.6.2/1.6.3 patch series
Bundled cut for the stable channel. Contents since 1.6.0:

* fix(webhooks): test endpoint formats payload per platform (Google
  Chat / Slack / Discord / Teams) so the Test URL button actually
  succeeds against those targets instead of getting a 400 back.
* fix(schema): add missing unifi_device_states.consecutive_count
  column the scheduled snapshot capture was failing to insert.
* feat(rotate): persist the active rotated password as
  unifi.password_rotation.last_password whenever a whole-SSID
  rotation succeeds. Surfaced in Settings → Tasks under the wordlist.
* feat(api): new GET /api/unifi/wifi/current-password JSON endpoint
  for external signage / kiosks. Token-protected via
  Authorization: Bearer or ?token= query. 401 / 503 / 404 on missing
  auth, disabled API, or no rotation yet.
* feat(settings): "Expose WiFi password API" checkbox under the
  rotate-passwords block. Off by default. Generate / Regenerate /
  Clear token controls and a copy-paste curl example.

No breaking changes. Drop-in upgrade from 1.6.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:50:44 -04:00
9a37eda302 feat(api): explicit enable toggle for WiFi password endpoint
Previously the API was implicitly active whenever a token existed.
Now there's an explicit unifi.api.enabled setting that gates it:

* WifiApiController returns 503 ("API disabled") when the setting is
  off, even if a valid token is presented. Stops the endpoint from
  silently working if a token is lying around.
* Settings page exposes the toggle under the Rotate-WiFi-Passwords
  block. With it off, the token / URL / curl example are hidden.
* The form submit handles the new api_enabled boolean.

v1.6.3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:44:57 -04:00
3 changed files with 8 additions and 1 deletions

View File

@@ -1,7 +1,7 @@
{ {
"name": "dashboard/unifi", "name": "dashboard/unifi",
"description": "UniFi network management, WiFi stats, and captive portal authentication for the Dashboard platform", "description": "UniFi network management, WiFi stats, and captive portal authentication for the Dashboard platform",
"version": "1.6.2", "version": "1.7.0",
"type": "library", "type": "library",
"license": "MIT", "license": "MIT",
"autoload": { "autoload": {

View File

@@ -33,6 +33,7 @@ class UnifiSettingsController extends Controller
'rotationLastRotatedAt' => Setting::get('unifi.password_rotation.last_rotated_at', null), 'rotationLastRotatedAt' => Setting::get('unifi.password_rotation.last_rotated_at', null),
'rotationLastPassword' => Setting::get('unifi.password_rotation.last_password', null), 'rotationLastPassword' => Setting::get('unifi.password_rotation.last_password', null),
'ppskSchedulingEnabled' => (bool) Setting::get('unifi.ppsk_scheduling.enabled', false), 'ppskSchedulingEnabled' => (bool) Setting::get('unifi.ppsk_scheduling.enabled', false),
'apiEnabled' => (bool) Setting::get('unifi.api.enabled', false),
'apiToken' => Setting::get('unifi.api_token', null), 'apiToken' => Setting::get('unifi.api_token', null),
]); ]);
} }
@@ -71,6 +72,7 @@ class UnifiSettingsController extends Controller
'rotation_minute' => 'nullable|integer|min:0|max:59', 'rotation_minute' => 'nullable|integer|min:0|max:59',
'rotation_wordlist' => 'nullable|string|max:20000', 'rotation_wordlist' => 'nullable|string|max:20000',
'ppsk_scheduling_enabled' => 'boolean', 'ppsk_scheduling_enabled' => 'boolean',
'api_enabled' => 'boolean',
]); ]);
Setting::set('unifi.controller_url', rtrim($request->controller_url, '/')); Setting::set('unifi.controller_url', rtrim($request->controller_url, '/'));
@@ -97,6 +99,7 @@ class UnifiSettingsController extends Controller
Setting::set('unifi.password_rotation.minute', $request->input('rotation_minute', 0)); Setting::set('unifi.password_rotation.minute', $request->input('rotation_minute', 0));
Setting::set('unifi.password_rotation.wordlist', $request->input('rotation_wordlist', '')); Setting::set('unifi.password_rotation.wordlist', $request->input('rotation_wordlist', ''));
Setting::set('unifi.ppsk_scheduling.enabled', $request->boolean('ppsk_scheduling_enabled') ? '1' : ''); Setting::set('unifi.ppsk_scheduling.enabled', $request->boolean('ppsk_scheduling_enabled') ? '1' : '');
Setting::set('unifi.api.enabled', $request->boolean('api_enabled') ? '1' : '');
\Illuminate\Support\Facades\Cache::forget('unifi:api_prefix:' . md5(rtrim($request->controller_url, '/'))); \Illuminate\Support\Facades\Cache::forget('unifi:api_prefix:' . md5(rtrim($request->controller_url, '/')));

View File

@@ -15,6 +15,10 @@ class WifiApiController extends Controller
{ {
public function currentPassword(Request $request) public function currentPassword(Request $request)
{ {
if (! Setting::get('unifi.api.enabled')) {
return response()->json(['error' => 'API disabled'], 503);
}
$expected = Setting::get('unifi.api_token'); $expected = Setting::get('unifi.api_token');
if (! $expected) { if (! $expected) {
return response()->json(['error' => 'API token not configured'], 503); return response()->json(['error' => 'API token not configured'], 503);