feat: make authentication optional — no tokens = no auth
Lint & Test / test (push) Successful in 10s

When no api_tokens are configured (the new default), all endpoints
are accessible without authentication. The frontend detects this
via /api/health's auth_required field and skips the login form.

- Backend: auth.py skips verification when api_tokens is empty
- Frontend: shared getAuthHeaders()/hasCredentials() helpers replace
  scattered token logic across all JS modules
- Health endpoint exposes auth_required for frontend discovery
- config.example.yaml ships with tokens commented out
- CLI --show-token and startup log reflect disabled state

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-23 13:59:55 +03:00
parent f80f6e9299
commit 4d1bb78c83
14 changed files with 175 additions and 190 deletions
+11 -31
View File
@@ -2,21 +2,20 @@
// Display Brightness & Power Control + Links Management
// ============================================================
import { t, showToast, escapeHtml, closeDialog, showConfirm, resolveMdiIcons, fetchMdiIcon } from './core.js';
import { t, showToast, escapeHtml, closeDialog, showConfirm, resolveMdiIcons, fetchMdiIcon, getAuthHeaders, hasCredentials } from './core.js';
let displayBrightnessTimers = {};
const DISPLAY_THROTTLE_MS = 50;
export async function loadDisplayMonitors() {
const token = localStorage.getItem('media_server_token');
if (!token) return;
if (!hasCredentials()) return;
const container = document.getElementById('displayMonitors');
if (!container) return;
try {
const response = await fetch('/api/display/monitors?refresh=true', {
headers: { 'Authorization': `Bearer ${token}` }
headers: getAuthHeaders()
});
if (!response.ok) {
@@ -108,14 +107,10 @@ export function onDisplayBrightnessChange(monitorId, value) {
}
async function sendDisplayBrightness(monitorId, brightness) {
const token = localStorage.getItem('media_server_token');
try {
await fetch(`/api/display/brightness/${monitorId}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
body: JSON.stringify({ brightness })
});
} catch (e) {
@@ -128,14 +123,10 @@ export async function toggleDisplayPower(monitorId, monitorName) {
const isOn = btn && btn.classList.contains('on');
const newState = !isOn;
const token = localStorage.getItem('media_server_token');
try {
const response = await fetch(`/api/display/power/${monitorId}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
body: JSON.stringify({ on: newState })
});
const data = await response.json();
@@ -160,15 +151,14 @@ export async function toggleDisplayPower(monitorId, monitorName) {
// ============================================================
export async function loadHeaderLinks() {
const token = localStorage.getItem('media_server_token');
if (!token) return;
if (!hasCredentials()) return;
const container = document.getElementById('headerLinks');
if (!container) return;
try {
const response = await fetch('/api/links/list', {
headers: { 'Authorization': `Bearer ${token}` }
headers: getAuthHeaders()
});
if (!response.ok) return;
@@ -210,12 +200,11 @@ export async function loadLinksTable() {
}
async function _loadLinksTableImpl() {
const token = localStorage.getItem('media_server_token');
const tbody = document.getElementById('linksTableBody');
try {
const response = await fetch('/api/links/list', {
headers: { 'Authorization': `Bearer ${token}` }
headers: getAuthHeaders()
});
if (!response.ok) {
@@ -273,13 +262,12 @@ export function showAddLinkDialog() {
}
export async function showEditLinkDialog(linkName) {
const token = localStorage.getItem('media_server_token');
const dialog = document.getElementById('linkDialog');
const title = document.getElementById('linkDialogTitle');
try {
const response = await fetch('/api/links/list', {
headers: { 'Authorization': `Bearer ${token}` }
headers: getAuthHeaders()
});
if (!response.ok) {
@@ -342,7 +330,6 @@ export async function saveLink(event) {
const submitBtn = event.target.querySelector('button[type="submit"]');
if (submitBtn) submitBtn.disabled = true;
const token = localStorage.getItem('media_server_token');
const isEdit = document.getElementById('linkIsEdit').value === 'true';
const linkName = isEdit ?
document.getElementById('linkOriginalName').value :
@@ -364,10 +351,7 @@ export async function saveLink(event) {
try {
const response = await fetch(endpoint, {
method,
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
body: JSON.stringify(data)
});
@@ -393,14 +377,10 @@ export async function deleteLinkConfirm(linkName) {
return;
}
const token = localStorage.getItem('media_server_token');
try {
const response = await fetch(`/api/links/delete/${linkName}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`
}
headers: getAuthHeaders()
});
const result = await response.json();