Specs stay at repo root (cross-VM). Move deploy and code into logical projects with README per domain, updated manifest.yaml, and symlinks at legacy paths for VM122 backward compatibility.
211 lines
5.6 KiB
JavaScript
211 lines
5.6 KiB
JavaScript
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;
|
|
}
|