Mejora de seguridad

This commit is contained in:
2026-03-03 09:29:53 -07:00
parent b187aa1b46
commit 49dfb3ef24
19 changed files with 428 additions and 657 deletions

View File

@@ -60,32 +60,34 @@ const initialState: AuthState = {
function createAuthStore() {
const { subscribe, set, update } = writable<AuthState>(initialState);
// Track current state for uso interno (evita dependencias circulares)
let _state = initialState;
subscribe(s => { _state = s; });
return {
subscribe,
// Initialize auth from localStorage
init: () => {
// Rehidrata sesión desde cookie HttpOnly (no toca localStorage)
init: async () => {
if (typeof window !== 'undefined') {
const token = localStorage.getItem('internal_auth_token');
const refreshToken = localStorage.getItem('internal_auth_refresh_token');
const user = localStorage.getItem('internal_auth_user');
if (token && user) {
try {
const parsedUser = JSON.parse(user);
try {
const response = await fetch('/api/v1/auth/me', {
credentials: 'include',
headers: { 'X-App': 'internal' }
});
if (response.ok) {
const user = await response.json();
set({
user: parsedUser,
token,
refreshToken: refreshToken || null,
user,
token: null,
refreshToken: null,
isAuthenticated: true,
isLoading: false
});
} catch (error) {
console.error('Error parsing stored auth data:', error);
localStorage.removeItem('internal_auth_token');
localStorage.removeItem('internal_auth_refresh_token');
localStorage.removeItem('internal_auth_user');
}
// 401/400 es esperado cuando no hay sesión activa — no es un error
} catch (error) {
// Ignorar errores de red en init
}
}
},
@@ -97,6 +99,7 @@ function createAuthStore() {
try {
const response = await fetch('/api/v1/auth/login', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
@@ -109,15 +112,6 @@ function createAuthStore() {
}
const data: LoginResponse = await response.json();
// Store auth data
if (typeof window !== 'undefined') {
localStorage.setItem('internal_auth_token', data.access_token);
if (data.refresh_token) {
localStorage.setItem('internal_auth_refresh_token', data.refresh_token);
}
localStorage.setItem('internal_auth_user', JSON.stringify(data.user));
}
set({
user: data.user,
@@ -134,21 +128,18 @@ function createAuthStore() {
// Refresh Session
refreshSession: async (): Promise<void> => {
// Need to get current state to access refresh token, logic simplified
let currentRefreshToken: string | null = null;
if (typeof window !== 'undefined') {
currentRefreshToken = localStorage.getItem('internal_auth_refresh_token');
}
const currentRefreshToken = _state.refreshToken;
if (!currentRefreshToken) {
throw new Error("No refresh token available");
}
update (state => ({ ...state, isLoading: true }));
update(state => ({ ...state, isLoading: true }));
try {
const response = await fetch('/api/v1/auth/refresh', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
@@ -166,11 +157,6 @@ function createAuthStore() {
const data: TokenResponse = await response.json();
// Update token in storage and state
if (typeof window !== 'undefined') {
localStorage.setItem('internal_auth_token', data.access_token);
}
update(state => ({
...state,
token: data.access_token,
@@ -184,25 +170,24 @@ function createAuthStore() {
},
// Logout
logout: () => {
if (typeof window !== 'undefined') {
localStorage.removeItem('internal_auth_token');
localStorage.removeItem('internal_auth_refresh_token');
localStorage.removeItem('internal_auth_user');
}
logout: async () => {
// Llamar al backend para que borre la cookie HttpOnly
try {
await fetch('/api/v1/auth/logout', {
method: 'POST',
credentials: 'include',
headers: { 'X-App': 'internal' }
});
} catch { /* ignorar errores de red */ }
set(initialState);
// Optional: Redirect to login
if (typeof window !== 'undefined') {
window.location.href = '/login';
window.location.href = '/login';
}
},
// Update user data
updateUser: (user: InternalUser) => {
update(state => ({ ...state, user }));
if (typeof window !== 'undefined') {
localStorage.setItem('internal_auth_user', JSON.stringify(user));
}
},
// Set loading state

View File

@@ -24,16 +24,8 @@ async function request<T>(endpoint: string, options: RequestOptions = {}): Promi
}
const authState = get(auth);
const token = authState.token || (typeof window !== 'undefined' ? localStorage.getItem('internal_auth_token') : null);
// Resolve tenant_id from store or from the persisted user object in localStorage
let tenantId = authState.user?.tenant_id ?? null;
if (!tenantId && typeof window !== 'undefined') {
try {
const stored = localStorage.getItem('internal_auth_user');
if (stored) tenantId = JSON.parse(stored)?.tenant_id ?? null;
} catch { /* ignore */ }
}
const token = authState.token;
const tenantId = authState.user?.tenant_id ?? null;
const headers = new Headers(init.headers);
if (token) {
@@ -45,17 +37,18 @@ async function request<T>(endpoint: string, options: RequestOptions = {}): Promi
if (!headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json');
}
// Identifica este frontend para que el backend use la cookie correcta
headers.set('X-App', 'internal');
const response = await fetch(url, {
...init,
credentials: 'include',
headers
});
if (response.status === 401) {
// Token expired or invalid
if (typeof window !== 'undefined') {
localStorage.removeItem('internal_auth_token');
localStorage.removeItem('internal_auth_user');
window.location.href = '/login';
}
throw new Error('Unauthorized');
@@ -76,15 +69,8 @@ async function request<T>(endpoint: string, options: RequestOptions = {}): Promi
async function downloadFile(endpoint: string, filename: string): Promise<void> {
const authState = get(auth);
const token = authState.token || (typeof window !== 'undefined' ? localStorage.getItem('internal_auth_token') : null);
let tenantId = authState.user?.tenant_id ?? null;
if (!tenantId && typeof window !== 'undefined') {
try {
const stored = localStorage.getItem('internal_auth_user');
if (stored) tenantId = JSON.parse(stored)?.tenant_id ?? null;
} catch { /* ignore */ }
}
const token = authState.token;
const tenantId = authState.user?.tenant_id ?? null;
const headers = new Headers();
if (token) {
@@ -93,16 +79,16 @@ async function downloadFile(endpoint: string, filename: string): Promise<void> {
if (tenantId) {
headers.set('X-Tenant-ID', tenantId);
}
headers.set('X-App', 'internal');
const response = await fetch(`${API_BASE}${endpoint}`, {
method: 'GET',
credentials: 'include',
headers
});
if (response.status === 401) {
if (typeof window !== 'undefined') {
localStorage.removeItem('internal_auth_token');
localStorage.removeItem('internal_auth_user');
window.location.href = '/login';
}
throw new Error('Unauthorized');