feat(web): optional web login and API tokens, off by default (stacked on #674) (#683)

Optional web login, off by default: a device that sets no password behaves
exactly as before. Set under General > Security; then every page and API
route needs a session login or an API token (Authorization: Bearer).
Loopback, the Wi-Fi setup flow in AP mode, static files, captive-portal
probes and a reduced /api/v3/health stay open. Secrets live in the web_auth
section of config_secrets.json and no API returns them.
scripts/reset_web_password.py turns login off. Stacked on #674.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-30 09:09:40 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent ba38a83c2c
commit e3c85cece6
23 changed files with 2310 additions and 26 deletions
+31
View File
@@ -23,6 +23,27 @@
};
</script>
<!-- Optional web login (web_interface/auth.py): a fetch() answered 401
with X-LEDMatrix-Login -- the session ran out, or login was turned on
from another browser -- goes to the login page instead of leaving
every panel showing an error. HTMX requests follow HX-Redirect on
their own. Only a same-origin answer can expose that header. -->
<script>
(function() {
if (typeof window.fetch !== 'function') return;
var originalFetch = window.fetch;
window.fetch = function() {
return originalFetch.apply(this, arguments).then(function(response) {
var login = response.status === 401 && response.headers.get('X-LEDMatrix-Login');
if (login && login.charAt(0) === '/' && login.charAt(1) !== '/') {
window.location.assign(login);
}
return response;
});
};
})();
</script>
<!-- Theme initialization (must run before CSS to prevent flash) -->
<script>
(function() {
@@ -352,6 +373,16 @@
class="hidden absolute right-0 mt-1 w-80 max-h-96 overflow-y-auto"></div>
</div>
{% if web_auth_state and web_auth_state.signed_in %}
<!-- Log out (only shown when the optional web login is on) -->
<form method="post" action="{{ url_for('ledmatrix_auth.logout') }}" class="flex items-center">
<button type="submit" class="theme-toggle-btn p-2 rounded-md"
title="Log out" aria-label="Log out">
<i class="fas fa-sign-out-alt" aria-hidden="true"></i>
</button>
</form>
{% endif %}
<!-- Theme toggle -->
<button id="theme-toggle"
type="button"