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
+13 -36
View File
@@ -5,6 +5,7 @@
import {
t, showToast, escapeHtml, closeDialog,
SEARCH_DEBOUNCE_MS, EMPTY_SVG_FILE, EMPTY_SVG_FOLDER, emptyStateHtml,
getAuthHeaders, hasCredentials,
} from './core.js';
// Browser state
@@ -24,14 +25,10 @@ const THUMBNAIL_CACHE_MAX = 200;
// Load media folders on page load
export async function loadMediaFolders() {
try {
const token = localStorage.getItem('media_server_token');
if (!token) {
console.error('No API token found');
return;
}
if (!hasCredentials()) return;
const response = await fetch('/api/browser/folders', {
headers: { 'Authorization': `Bearer ${token}` }
headers: getAuthHeaders()
});
if (!response.ok) throw new Error('Failed to load folders');
@@ -119,11 +116,7 @@ async function browsePath(folderId, path, offset = 0, nocache = false) {
showBrowserSearch(false);
try {
const token = localStorage.getItem('media_server_token');
if (!token) {
console.error('No API token found');
return;
}
if (!hasCredentials()) return;
// Show loading spinner
const container = document.getElementById('browserGrid');
@@ -135,7 +128,7 @@ async function browsePath(folderId, path, offset = 0, nocache = false) {
if (nocache) url += '&nocache=true';
const response = await fetch(
url,
{ headers: { 'Authorization': `Bearer ${token}` } }
{ headers: getAuthHeaders() }
);
if (!response.ok) {
@@ -487,11 +480,7 @@ function formatBitrate(bps) {
async function loadThumbnail(imgElement, fileName) {
try {
const token = localStorage.getItem('media_server_token');
if (!token) {
console.error('No API token found');
return;
}
if (!hasCredentials()) return;
const absolutePath = buildAbsolutePath(currentFolderId, currentPath, fileName);
@@ -510,7 +499,7 @@ async function loadThumbnail(imgElement, fileName) {
const response = await fetch(
`/api/browser/thumbnail?path=${encodedPath}&size=medium`,
{ headers: { 'Authorization': `Bearer ${token}` } }
{ headers: getAuthHeaders() }
);
if (response.status === 200) {
@@ -576,20 +565,13 @@ async function playMediaFile(fileName) {
if (playInProgress) return;
playInProgress = true;
try {
const token = localStorage.getItem('media_server_token');
if (!token) {
console.error('No API token found');
return;
}
if (!hasCredentials()) return;
const absolutePath = buildAbsolutePath(currentFolderId, currentPath, fileName);
const response = await fetch('/api/browser/play', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
body: JSON.stringify({ path: absolutePath })
});
@@ -610,15 +592,11 @@ export async function playAllFolder() {
const btn = document.getElementById('playAllBtn');
if (btn) btn.disabled = true;
try {
const token = localStorage.getItem('media_server_token');
if (!token || !currentFolderId) return;
if (!hasCredentials() || !currentFolderId) return;
const response = await fetch('/api/browser/play-folder', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
body: JSON.stringify({ folder_id: currentFolderId, path: currentPath })
});
@@ -640,8 +618,7 @@ export async function playAllFolder() {
export async function downloadFile(fileName, event) {
if (event) event.stopPropagation();
const token = localStorage.getItem('media_server_token');
if (!token) return;
if (!hasCredentials()) return;
const fullPath = currentPath === '/'
? '/' + fileName
@@ -651,7 +628,7 @@ export async function downloadFile(fileName, event) {
try {
const response = await fetch(
`/api/browser/download?folder_id=${currentFolderId}&path=${encodedPath}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
{ headers: getAuthHeaders() }
);
if (!response.ok) throw new Error('Download failed');