Files
project.linkedcorp.net/frontend/src/services/api.js
T
Milton Gonzalez 0be0712ff0 feat: version 2.7.0 -- menu Contraseñas (repositorio de credenciales privado)
Nuevo modulo backend/src/modules/vault/: contraseña y notas cifradas
en el servidor (AES-256-GCM, mismo mecanismo ya usado para la clave
SMTP). Acceso al menu protegido con un PIN de 4 digitos independiente
del login (token corto propio, header X-Vault-Token, nunca el JWT de
sesion). El PIN no participa del cifrado -- cambiarlo o restablecerlo
por correo nunca afecta las contraseñas ya guardadas. Privado por
usuario, sin excepcion. Cada accion sensible (crear/editar/eliminar/
revelar/desbloquear) queda en audit_log. Compartir con otro usuario
queda para una proxima version.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-01 12:12:54 -05:00

54 lines
1.8 KiB
JavaScript

const API_URL = import.meta.env.VITE_API_URL || '/api';
let onUnauthorized = () => {};
export function setUnauthorizedHandler(fn) {
onUnauthorized = fn;
}
function getToken() {
return localStorage.getItem('plc_token');
}
async function request(method, path, { body, isForm, extraHeaders } = {}) {
const headers = { ...extraHeaders };
const token = getToken();
if (token) headers.Authorization = `Bearer ${token}`;
if (!isForm && body !== undefined) headers['Content-Type'] = 'application/json';
const res = await fetch(`${API_URL}${path}`, {
method,
headers,
body: isForm ? body : body !== undefined ? JSON.stringify(body) : undefined,
});
if (res.status === 401) {
onUnauthorized();
}
if (res.status === 204) return null;
const isJson = (res.headers.get('content-type') || '').includes('application/json');
const payload = isJson ? await res.json() : null;
if (!res.ok) {
const message = payload?.error || `Error ${res.status}`;
throw new Error(message);
}
return payload?.data;
}
export const api = {
get: (path, extraHeaders) => request('GET', path, { extraHeaders }),
post: (path, body, extraHeaders) => request('POST', path, { body, extraHeaders }),
put: (path, body, extraHeaders) => request('PUT', path, { body, extraHeaders }),
patch: (path, body) => request('PATCH', path, { body }),
delete: (path, extraHeaders) => request('DELETE', path, { extraHeaders }),
postForm: (path, formData) => request('POST', path, { body: formData, isForm: true }),
putForm: (path, formData) => request('PUT', path, { body: formData, isForm: true }),
setToken: (token) => {
if (token) localStorage.setItem('plc_token', token);
else localStorage.removeItem('plc_token');
},
downloadUrl: (path) => `${API_URL}${path}`,
};