feat: Add optional web interface authentication with session management
Local Docker Build (Dev) / build-dev (pull_request) Successful in 3s
Local Docker Build (Dev) / build-dev (pull_request) Successful in 3s
Authentication Features: - Optional HTTP Basic Auth with session cookies for web interface protection - Configurable username/password with session timeout (default: 1 hour) - Selective route protection (web interface protected, webhooks/APIs remain public) - Authentication status display with logout functionality in web UI - Session cleanup and graceful logout with browser re-authentication prompt Configuration: - WEB_AUTH_ENABLED in .env (default: false - no breaking changes) - WEB_AUTH_USERNAME/WEB_AUTH_PASSWORD in .env.secrets - WEB_AUTH_SESSION_TIMEOUT configurable (5min-24h range) Development Tools: - Added debug_tv.py for TV series/episode debugging (series, season, episode levels) - Comprehensive episode data inspection with validation and sources breakdown - Complements existing debug_movie.py for complete media debugging coverage Technical Implementation: - SimpleAuthMiddleware with FastAPI integration - Session management with automatic cleanup - Authentication status API endpoints - Responsive CSS styling for auth UI elements - JavaScript functions for auth checking and logout
This commit is contained in:
@@ -39,6 +39,7 @@ body {
|
||||
color: white;
|
||||
padding: 1rem 0;
|
||||
box-shadow: var(--shadow-lg);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
@@ -63,6 +64,45 @@ body {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Authentication Status */
|
||||
.auth-status {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
color: white;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.auth-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.auth-logout {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.auth-logout:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.nav-tabs {
|
||||
max-width: 1200px;
|
||||
margin: 1rem auto 0;
|
||||
|
||||
@@ -15,6 +15,14 @@
|
||||
<h1><i class="fas fa-shield-alt"></i> NFOGuard</h1>
|
||||
<p>Database Management & Reporting</p>
|
||||
</div>
|
||||
<div class="auth-status" id="auth-status" style="display: none;">
|
||||
<span class="auth-user">
|
||||
<i class="fas fa-user"></i> <span id="auth-username">Loading...</span>
|
||||
</span>
|
||||
<button class="auth-logout" id="logout-btn" onclick="logout()">
|
||||
<i class="fas fa-sign-out-alt"></i> Logout
|
||||
</button>
|
||||
</div>
|
||||
<nav class="nav-tabs">
|
||||
<button class="nav-tab active" data-tab="dashboard">
|
||||
<i class="fas fa-tachometer-alt"></i> Dashboard
|
||||
|
||||
@@ -10,6 +10,7 @@ let dashboardData = null;
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initializeTabs();
|
||||
initializeEventListeners();
|
||||
checkAuthStatus(); // Check authentication status on page load
|
||||
loadDashboard();
|
||||
loadSeriesSources();
|
||||
});
|
||||
@@ -1375,4 +1376,62 @@ function updateEpisodeModalCounts() {
|
||||
${videoCountText}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// Authentication Functions
|
||||
// ===========================
|
||||
|
||||
async function checkAuthStatus() {
|
||||
try {
|
||||
const response = await fetch('/api/auth/status');
|
||||
const authStatus = await response.json();
|
||||
|
||||
const authStatusDiv = document.getElementById('auth-status');
|
||||
const authUsernameSpan = document.getElementById('auth-username');
|
||||
|
||||
if (authStatus.auth_enabled && authStatus.authenticated) {
|
||||
// Show authentication status with username
|
||||
authUsernameSpan.textContent = authStatus.username;
|
||||
authStatusDiv.style.display = 'flex';
|
||||
} else if (authStatus.auth_enabled && !authStatus.authenticated) {
|
||||
// This shouldn't happen if middleware is working, but handle it
|
||||
console.warn('Auth enabled but not authenticated - middleware may be misconfigured');
|
||||
} else {
|
||||
// Authentication disabled - hide auth status
|
||||
authStatusDiv.style.display = 'none';
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to check authentication status:', error);
|
||||
// Hide auth status on error
|
||||
document.getElementById('auth-status').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
if (!confirm('Are you sure you want to logout?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/auth/logout', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
showToast('✅ Logged out successfully', 'success');
|
||||
// Reload page to trigger authentication prompt
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1500);
|
||||
} else {
|
||||
showToast('❌ Logout failed', 'error');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Logout failed:', error);
|
||||
showToast('❌ Logout error', 'error');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user