Files
root 3dc5dd8fe9
CI / Test (Python 3.11) (push) Has been cancelled
CI / Test (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Type Check (push) Has been cancelled
CI / Summary (push) Has been cancelled
feat: fix auth, provider health checks, search, and redesign UI
- Fix register/login: dict-style access on UserTable ORM objects
- Fix HTMX auth: inject JWT token in all HTMX request headers
- Fix FS7 search: use DLE AJAX endpoint /engine/ajax/search.php
- Fix ZT search: use ?p=series&search=QUERY (not DLE format)
- Fix provider health: load hardcoded providers + domain manager
- Add self.id to all anime/series providers
- Redesign homepage: Netflix-style horizontal scroll cards (.hc)
- Redesign search results: grouped by title, poster + synopsis + 3 buttons
- Add Télécharger dropdown: season download + episode picker
- Fix navbar CSS: restore .tabs flex layout, remove orphan rules
- Fix HTMX spinner: remove inline display:none, use CSS indicator
- Add AGENTS.md files across project for developer documentation
2026-03-28 00:14:31 +00:00

117 lines
3.2 KiB
JavaScript

/**
* Authentication management for web interface
*/
// Use relative path for API
const AUTH_API_BASE = '/api';
const COOKIE_NAME = 'auth_token';
const COOKIE_MAX_AGE = 60 * 60 * 24 * 7; // 7 days
function setToken(token) {
localStorage.setItem('auth_token', token);
const expires = new Date();
expires.setTime(expires.getTime() + COOKIE_MAX_AGE * 1000);
document.cookie = `${COOKIE_NAME}=${token};expires=${expires.toUTCString()};path=/;SameSite=Strict`;
}
function getToken() {
const cookieToken = getTokenFromCookie();
if (cookieToken) return cookieToken;
return localStorage.getItem('auth_token');
}
function getTokenFromCookie() {
const name = COOKIE_NAME + '=';
const decodedCookie = decodeURIComponent(document.cookie);
const cookieArray = decodedCookie.split(';');
for (let i = 0; i < cookieArray.length; i++) {
let cookie = cookieArray[i];
while (cookie.charAt(0) === ' ') cookie = cookie.substring(1);
if (cookie.indexOf(name) === 0) return cookie.substring(name.length, cookie.length);
}
return null;
}
function removeToken() {
localStorage.removeItem('auth_token');
localStorage.removeItem('user');
document.cookie = `${COOKIE_NAME}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;`;
}
// Check if user is authenticated
async function checkAuth() {
const token = getToken();
if (!token) {
window.dispatchEvent(new CustomEvent('auth-logout'));
return false;
}
try {
const response = await fetch(`${AUTH_API_BASE}/auth/me`, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.ok) {
const data = await response.json();
window.dispatchEvent(new CustomEvent('auth-success', {
detail: { username: data.user.full_name || data.user.username }
}));
return true;
} else {
removeToken();
window.dispatchEvent(new CustomEvent('auth-logout'));
return false;
}
} catch (error) {
return false;
}
}
function redirectToLogin() {
if (!window.location.pathname.includes('/login')) {
window.location.href = '/login';
}
}
async function handleLogout() {
if (!confirm('Êtes-vous sûr de vouloir vous déconnecter?')) return;
removeToken();
try {
await fetch(`${AUTH_API_BASE}/auth/logout`, { method: 'POST' });
} catch (error) {
console.error('Logout error:', error);
}
window.location.href = '/login';
}
function addAuthHeader(options = {}) {
const token = getToken();
if (token) {
options.headers = options.headers || {};
options.headers['Authorization'] = `Bearer ${token}`;
}
return options;
}
async function authFetch(url, options = {}) {
options = addAuthHeader(options);
return fetch(url, options);
}
// Global exposure
window.checkAuth = checkAuth;
window.handleLogout = handleLogout;
window.authFetch = authFetch;
window.addAuthHeader = addAuthHeader;
window.getToken = getToken;
window.setToken = setToken;
window.removeToken = removeToken;
document.addEventListener('DOMContentLoaded', () => {
checkAuth();
});