195 lines
5.3 KiB
JavaScript
195 lines
5.3 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');
|
|
}
|
|
|
|
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;
|
|
}
|