const AUTH_TOKEN_KEY = 'ligbox_ops_token'; const AUTH_USER_KEY = 'ligbox_ops_user'; function getToken() { return sessionStorage.getItem(AUTH_TOKEN_KEY); } function getUser() { try { return JSON.parse(sessionStorage.getItem(AUTH_USER_KEY) || 'null'); } catch { return null; } } function setSession(token, user) { sessionStorage.setItem(AUTH_TOKEN_KEY, token); sessionStorage.setItem(AUTH_USER_KEY, JSON.stringify(user)); } function clearSession() { sessionStorage.removeItem(AUTH_TOKEN_KEY); sessionStorage.removeItem(AUTH_USER_KEY); } function isLoggedIn() { return Boolean(getToken()); } function authHeaders(extra = {}) { const token = getToken(); const headers = { ...extra }; if (token) headers.Authorization = `Bearer ${token}`; return headers; } const FETCH_TIMEOUT_MS = 12000; function fetchWithTimeout(url, options = {}, timeoutMs = FETCH_TIMEOUT_MS) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); return fetch(url, { ...options, signal: controller.signal }).finally(() => clearTimeout(timer)); } function requireAuth() { if (!isLoggedIn()) { window.location.href = '/login.html'; return false; } return true; } /** Valida JWT no servidor; limpa sessão se expirado/inválido (ex.: após rotação JWT). */ async function ensureValidSession() { const token = getToken(); if (!token) return false; try { const res = await fetchWithTimeout('/api/v1/auth/me', { headers: authHeaders() }); if (!res.ok) { clearSession(); return false; } const me = await res.json(); setSession(token, { username: me.username, role: me.role, display_name: me.display_name, }); return true; } catch (err) { console.warn('ensureValidSession:', err?.name || err); clearSession(); return false; } } function logout() { clearSession(); window.location.replace('/login.html?logout=1'); } function hasRole(...roles) { const user = getUser(); return user && roles.includes(user.role); } function canPatchTickets() { return hasRole('super_admin', 'ops_lead', 'technician'); } function canRunAudit() { return hasRole('super_admin', 'ops_lead'); } function canManageUsers() { return hasRole('super_admin'); } function canManageVm112Domains() { return hasRole('super_admin', 'ops_lead'); } function canAssist() { return hasRole('super_admin', 'ops_lead', 'technician'); } function canReadLeads() { return hasRole( 'super_admin', 'ops_lead', 'technician', 'sales_admin', 'sales_support', 'marketing', 'seo', ); } function canReadBilling() { return hasRole('super_admin', 'ops_lead', 'noc', 'finance', 'sales_admin', 'sales_support'); } function canManageBilling() { return hasRole('super_admin', 'ops_lead', 'finance', 'sales_admin'); } function canReadTickets() { return hasRole('super_admin', 'ops_lead', 'technician', 'noc'); } async function login(username, password) { const res = await fetchWithTimeout('/api/v1/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }), }); const data = await res.json().catch(() => ({})); if (!res.ok) { throw new Error(data.detail || `Login falhou (${res.status})`); } if (data.mfa_required) { return { mfaRequired: true, mfaToken: data.mfa_token, username: data.username }; } setSession(data.access_token, { username: data.username, role: data.role, display_name: data.display_name, }); return data; } async function loginMfa(mfaToken, totpCode, backupCode) { const payload = { mfa_token: mfaToken }; if (backupCode) payload.backup_code = backupCode; else payload.totp_code = totpCode; const res = await fetchWithTimeout('/api/v1/auth/login/mfa', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await res.json().catch(() => ({})); if (!res.ok) { throw new Error(data.detail || `Código 2FA inválido (${res.status})`); } setSession(data.access_token, { username: data.username, role: data.role, display_name: data.display_name, }); return data; } async function mfaRecoverySendEmail(mfaToken) { const res = await fetchWithTimeout('/api/v1/auth/mfa-recovery/send-email', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mfa_token: mfaToken }), }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.detail || `Falha ao enviar (${res.status})`); return data; } async function mfaRecoveryVerifyEmail(mfaToken, emailOtp) { const res = await fetchWithTimeout('/api/v1/auth/mfa-recovery/verify-email', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mfa_token: mfaToken, email_otp: emailOtp }), }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.detail || `Verificação falhou (${res.status})`); return data; } async function mfaRecoveryComplete(recoveryToken, totpCode) { const res = await fetchWithTimeout('/api/v1/auth/mfa-recovery/complete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ recovery_token: recoveryToken, totp_code: totpCode }), }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.detail || `Recuperação falhou (${res.status})`); if (data.access_token) { setSession(data.access_token, { username: data.username, role: data.role, display_name: data.display_name, }); } return data; }