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 -37
View File
@@ -6,19 +6,16 @@ import {
t, showToast, escapeHtml, closeDialog, showConfirm,
resolveMdiIcons, fetchMdiIcon,
scripts, setScripts,
getAuthHeaders, hasCredentials,
} from './core.js';
export let scriptFormDirty = false;
export function setScriptFormDirty(value) { scriptFormDirty = value; }
export async function loadScripts() {
const token = localStorage.getItem('media_server_token');
try {
const response = await fetch('/api/scripts/list', {
headers: {
'Authorization': `Bearer ${token}`
}
headers: getAuthHeaders()
});
if (response.ok) {
@@ -67,10 +64,9 @@ export async function displayQuickAccess() {
});
try {
const token = localStorage.getItem('media_server_token');
if (token) {
if (hasCredentials()) {
const response = await fetch('/api/links/list', {
headers: { 'Authorization': `Bearer ${token}` }
headers: getAuthHeaders()
});
if (gen !== _quickAccessGen) return;
if (response.ok) {
@@ -124,16 +120,12 @@ export async function displayQuickAccess() {
}
async function executeScript(scriptName, buttonElement) {
const token = localStorage.getItem('media_server_token');
buttonElement.classList.add('executing');
try {
const response = await fetch(`/api/scripts/execute/${scriptName}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
body: JSON.stringify({ args: [] })
});
@@ -165,12 +157,11 @@ export async function loadScriptsTable() {
}
async function _loadScriptsTableImpl() {
const token = localStorage.getItem('media_server_token');
const tbody = document.getElementById('scriptsTableBody');
try {
const response = await fetch('/api/scripts/list', {
headers: { 'Authorization': `Bearer ${token}` }
headers: getAuthHeaders()
});
if (!response.ok) {
@@ -232,13 +223,12 @@ export function showAddScriptDialog() {
}
export async function showEditScriptDialog(scriptName) {
const token = localStorage.getItem('media_server_token');
const dialog = document.getElementById('scriptDialog');
const title = document.getElementById('dialogTitle');
try {
const response = await fetch('/api/scripts/list', {
headers: { 'Authorization': `Bearer ${token}` }
headers: getAuthHeaders()
});
if (!response.ok) {
@@ -300,7 +290,6 @@ export async function saveScript(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('scriptIsEdit').value === 'true';
const scriptName = isEdit ?
document.getElementById('scriptOriginalName').value :
@@ -324,10 +313,7 @@ export async function saveScript(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)
});
@@ -353,14 +339,10 @@ export async function deleteScriptConfirm(scriptName) {
return;
}
const token = localStorage.getItem('media_server_token');
try {
const response = await fetch(`/api/scripts/delete/${scriptName}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`
}
headers: getAuthHeaders()
});
const result = await response.json();
@@ -443,7 +425,6 @@ function showExecutionResult(name, result, type = 'script') {
}
export async function executeScriptDebug(scriptName) {
const token = localStorage.getItem('media_server_token');
const dialog = document.getElementById('executionDialog');
const title = document.getElementById('executionDialogTitle');
const statusDiv = document.getElementById('executionStatus');
@@ -463,10 +444,7 @@ export async function executeScriptDebug(scriptName) {
try {
const response = await fetch(`/api/scripts/execute/${scriptName}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
body: JSON.stringify({ args: [] })
});
@@ -494,7 +472,6 @@ export async function executeScriptDebug(scriptName) {
}
export async function executeCallbackDebug(callbackName) {
const token = localStorage.getItem('media_server_token');
const dialog = document.getElementById('executionDialog');
const title = document.getElementById('executionDialogTitle');
const statusDiv = document.getElementById('executionStatus');
@@ -514,10 +491,7 @@ export async function executeCallbackDebug(callbackName) {
try {
const response = await fetch(`/api/callbacks/execute/${callbackName}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() }
});
const result = await response.json();