refactor: centralize access token handling in cookies and improve related functions
- Introduced utility functions for managing access tokens in cookies, including setting, getting, and clearing tokens. - Updated various components and server routes to utilize the new access token functions for better consistency and maintainability. - Removed redundant cookie handling code across the application, streamlining the authentication process.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import type { Handle } from '@sveltejs/kit';
|
||||
import { paraglideMiddleware } from '$lib/paraglide/server';
|
||||
import { sequence } from '@sveltejs/kit/hooks';
|
||||
import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie';
|
||||
|
||||
const handleParaglide: Handle = ({ event, resolve }) => paraglideMiddleware(event.request, ({ request, locale }) => {
|
||||
event.request = request;
|
||||
@@ -12,7 +13,7 @@ const handleParaglide: Handle = ({ event, resolve }) => paraglideMiddleware(even
|
||||
|
||||
const handleAuth: Handle = async ({ event, resolve }) => {
|
||||
// Obtener el token de las cookies
|
||||
const token = event.cookies.get('access_token');
|
||||
const token = getAccessTokenFromCookies(event.cookies);
|
||||
|
||||
// Agregar el token a los locals para que esté disponible en toda la app
|
||||
event.locals.token = token || null;
|
||||
|
||||
71
frontend/src/lib/access-token-cookie-browser.ts
Normal file
71
frontend/src/lib/access-token-cookie-browser.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { browser } from '$app/environment';
|
||||
import {
|
||||
ACCESS_TOKEN_CHUNK_COUNT,
|
||||
accessTokenChunkName,
|
||||
splitAccessTokenForCookies,
|
||||
ACCESS_TOKEN_MAX_CHUNKS
|
||||
} from '$lib/access-token-cookie.shared';
|
||||
|
||||
function readCookieRaw(name: string): string | null {
|
||||
if (!browser) return null;
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop()?.split(';').shift() ?? null;
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getAccessTokenFromDocument(): string | null {
|
||||
if (!browser) return null;
|
||||
const countRaw = readCookieRaw(ACCESS_TOKEN_CHUNK_COUNT);
|
||||
if (countRaw) {
|
||||
const n = parseInt(countRaw, 10);
|
||||
if (!Number.isFinite(n) || n < 1 || n > ACCESS_TOKEN_MAX_CHUNKS) return null;
|
||||
let out = '';
|
||||
for (let i = 0; i < n; i++) {
|
||||
const p = readCookieRaw(accessTokenChunkName(i));
|
||||
if (p == null) return null;
|
||||
out += p;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
return readCookieRaw('access_token');
|
||||
}
|
||||
|
||||
export function hasAccessTokenInDocument(): boolean {
|
||||
if (!browser) return false;
|
||||
return !!(readCookieRaw('access_token') || readCookieRaw(ACCESS_TOKEN_CHUNK_COUNT));
|
||||
}
|
||||
|
||||
export function clearAccessTokenOnDocument() {
|
||||
if (!browser) return;
|
||||
const secure = window.location.protocol === 'https:' ? '; Secure' : '';
|
||||
const blank = `; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; SameSite=Lax${secure}`;
|
||||
const clear = (name: string) => {
|
||||
document.cookie = `${name}=${blank}`;
|
||||
};
|
||||
clear('access_token');
|
||||
clear(ACCESS_TOKEN_CHUNK_COUNT);
|
||||
for (let i = 0; i < ACCESS_TOKEN_MAX_CHUNKS; i++) {
|
||||
clear(accessTokenChunkName(i));
|
||||
}
|
||||
}
|
||||
|
||||
/** Misma política que auth setCookie: expires + SameSite + Secure en HTTPS. */
|
||||
export function setAccessTokenOnDocument(token: string, days: number = 7) {
|
||||
if (!browser) return;
|
||||
clearAccessTokenOnDocument();
|
||||
const exp = new Date();
|
||||
exp.setDate(exp.getDate() + days);
|
||||
const secure = window.location.protocol === 'https:' ? '; Secure' : '';
|
||||
const suffix = `; path=/; expires=${exp.toUTCString()}; SameSite=Lax${secure}`;
|
||||
|
||||
const split = splitAccessTokenForCookies(token);
|
||||
if (split.kind === 'single') {
|
||||
document.cookie = `access_token=${split.value}${suffix}`;
|
||||
return;
|
||||
}
|
||||
document.cookie = `${ACCESS_TOKEN_CHUNK_COUNT}=${split.parts.length}${suffix}`;
|
||||
split.parts.forEach((part, i) => {
|
||||
document.cookie = `${accessTokenChunkName(i)}=${part}${suffix}`;
|
||||
});
|
||||
}
|
||||
32
frontend/src/lib/access-token-cookie.shared.ts
Normal file
32
frontend/src/lib/access-token-cookie.shared.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Fragmentación del JWT access_token en varias cookies cuando supera el límite ~4KB del navegador.
|
||||
* La lógica de fetch / Bearer no cambia: solo lectura/escritura de cookies.
|
||||
*/
|
||||
|
||||
/** Por debajo de esto se usa una sola cookie `access_token` (compatibilidad). */
|
||||
export const ACCESS_TOKEN_MAX_SINGLE = 3500;
|
||||
|
||||
export const ACCESS_TOKEN_CHUNK_SIZE = 3500;
|
||||
|
||||
/** Número de fragmentos; si existe, el token está en access_token_0..access_token_{n-1}. */
|
||||
export const ACCESS_TOKEN_CHUNK_COUNT = 'access_token_chunks';
|
||||
|
||||
export const accessTokenChunkName = (index: number) => `access_token_${index}`;
|
||||
|
||||
export type AccessTokenSplit =
|
||||
| { kind: 'single'; value: string }
|
||||
| { kind: 'chunks'; parts: string[] };
|
||||
|
||||
export function splitAccessTokenForCookies(token: string): AccessTokenSplit {
|
||||
if (token.length <= ACCESS_TOKEN_MAX_SINGLE) {
|
||||
return { kind: 'single', value: token };
|
||||
}
|
||||
const parts: string[] = [];
|
||||
for (let i = 0; i < token.length; i += ACCESS_TOKEN_CHUNK_SIZE) {
|
||||
parts.push(token.slice(i, i + ACCESS_TOKEN_CHUNK_SIZE));
|
||||
}
|
||||
return { kind: 'chunks', parts };
|
||||
}
|
||||
|
||||
/** Máximo de fragmentos soportados (JWT muy grande). */
|
||||
export const ACCESS_TOKEN_MAX_CHUNKS = 32;
|
||||
@@ -4,9 +4,23 @@
|
||||
import { getToken } from './auth';
|
||||
import { browser } from '$app/environment';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { clearAccessTokenOnDocument, setAccessTokenOnDocument } from '$lib/access-token-cookie-browser';
|
||||
|
||||
// Normalize API_BASE_URL to remove trailing slash
|
||||
const API_BASE_URL = (import.meta.env.VITE_API_URL || '').replace(/\/+$/, '');
|
||||
/** Base URL absoluta para fetch; corrige `http:host` sin `//` y añade `http://` si no hay esquema. */
|
||||
function normalizeAbsoluteApiBaseUrl(raw: string): string {
|
||||
let s = (raw ?? '').trim().replace(/\/+$/, '');
|
||||
if (!s) return '';
|
||||
if (s.startsWith('http:') && !s.startsWith('http://')) {
|
||||
s = 'http://' + s.slice('http:'.length).replace(/^\/+/, '');
|
||||
}
|
||||
if (s.startsWith('https:') && !s.startsWith('https://')) {
|
||||
s = 'https://' + s.slice('https:'.length).replace(/^\/+/, '');
|
||||
}
|
||||
if (/^https?:\/\//i.test(s)) return s;
|
||||
return `http://${s.replace(/^\/+/, '')}`;
|
||||
}
|
||||
|
||||
const API_BASE_URL = normalizeAbsoluteApiBaseUrl(String(import.meta.env.VITE_API_URL ?? ''));
|
||||
|
||||
export interface ApiResponse<T = any> {
|
||||
data?: T;
|
||||
@@ -164,8 +178,7 @@ async function refreshToken(): Promise<string | null> {
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('❌ [API] Silent refresh falló, status:', response.status);
|
||||
// Limpiar la cookie del access_token (no HttpOnly) para forzar re-login
|
||||
document.cookie = 'access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC';
|
||||
clearAccessTokenOnDocument();
|
||||
setTimeout(() => { window.location.href = '/login'; }, 1500);
|
||||
return null;
|
||||
}
|
||||
@@ -173,9 +186,7 @@ async function refreshToken(): Promise<string | null> {
|
||||
const data = await response.json() as { access_token?: string };
|
||||
|
||||
if (data.access_token) {
|
||||
// Actualizar cookie no-HttpOnly del access_token
|
||||
const secure = window.location.protocol === 'https:' ? '; Secure' : '';
|
||||
document.cookie = `access_token=${data.access_token}; path=/; max-age=${60 * 60 * 24 * 7}; SameSite=Lax${secure}`;
|
||||
setAccessTokenOnDocument(data.access_token);
|
||||
|
||||
// Actualizar authStore en memoria
|
||||
try {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Servicio de autenticación con Keycloak
|
||||
*
|
||||
* Seguridad de tokens:
|
||||
* - access_token → en memoria (authStore) + cookie no-HttpOnly (para SSR)
|
||||
* - access_token → en memoria (authStore) + cookies no-HttpOnly (una o varias si el JWT es grande)
|
||||
* - refresh_token → cookie HttpOnly únicamente (JS nunca lo lee directamente)
|
||||
* - El refresh se hace server-side via /api-sveltekit/auth/silent-refresh
|
||||
* - NO se usa localStorage para tokens
|
||||
@@ -11,6 +11,11 @@
|
||||
import Keycloak from 'keycloak-js';
|
||||
import { writable, derived } from 'svelte/store';
|
||||
import { browser } from '$app/environment';
|
||||
import {
|
||||
clearAccessTokenOnDocument,
|
||||
getAccessTokenFromDocument,
|
||||
setAccessTokenOnDocument
|
||||
} from '$lib/access-token-cookie-browser';
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// Tipos
|
||||
@@ -45,35 +50,6 @@ const keycloakConfig = {
|
||||
|
||||
let keycloakInstance: Keycloak | null = null;
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// Cookie helpers (solo para access_token no-HttpOnly)
|
||||
// ─────────────────────────────────────────────────────────
|
||||
|
||||
/** Lee el valor de una cookie no-HttpOnly */
|
||||
const getCookie = (name: string): string | null => {
|
||||
if (!browser) return null;
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop()?.split(';').shift() ?? null;
|
||||
return null;
|
||||
};
|
||||
|
||||
/** Escribe una cookie no-HttpOnly */
|
||||
const setCookie = (name: string, value: string, days: number = 7) => {
|
||||
if (!browser) return;
|
||||
const exp = new Date();
|
||||
exp.setDate(exp.getDate() + days);
|
||||
const secure = window.location.protocol === 'https:' ? '; Secure' : '';
|
||||
document.cookie = `${name}=${value}; path=/; expires=${exp.toUTCString()}; SameSite=Lax${secure}`;
|
||||
};
|
||||
|
||||
/** Elimina una cookie */
|
||||
const deleteCookie = (name: string) => {
|
||||
if (!browser) return;
|
||||
const secure = window.location.protocol === 'https:' ? '; Secure' : '';
|
||||
document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; SameSite=Lax${secure}`;
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// Auth store (tokens solo en memoria)
|
||||
// ─────────────────────────────────────────────────────────
|
||||
@@ -139,7 +115,7 @@ export const initAuth = async (): Promise<boolean> => {
|
||||
authStore.setLoading(true);
|
||||
|
||||
// Restaurar token desde cookie no-HttpOnly (password login flow)
|
||||
const cookieToken = getCookie('access_token');
|
||||
const cookieToken = getAccessTokenFromDocument();
|
||||
if (cookieToken) {
|
||||
authStore.setToken(cookieToken);
|
||||
authStore.setAuthenticated(true);
|
||||
@@ -309,7 +285,7 @@ export const loginWithKeycloak = async (tenantSlug?: string) => {
|
||||
|
||||
/**
|
||||
* Login con usuario/contraseña (legacy — el login principal es via form action del servidor).
|
||||
* Los tokens se guardan en cookies (vía setCookie) y en memoria (authStore).
|
||||
* Los tokens se guardan en cookies no-HttpOnly (fragmentadas si hace falta) y en memoria (authStore).
|
||||
* NO se guardan en localStorage.
|
||||
*/
|
||||
export const login = async (credentials: {
|
||||
@@ -330,7 +306,7 @@ export const login = async (credentials: {
|
||||
// Guardar en memoria y en cookie no-HttpOnly para SSR
|
||||
authStore.setToken(loginData.access_token);
|
||||
authStore.setAuthenticated(true);
|
||||
setCookie('access_token', loginData.access_token);
|
||||
setAccessTokenOnDocument(loginData.access_token);
|
||||
// El refresh_token llega en cookie HttpOnly desde el servidor.
|
||||
// NO lo guardamos en JS.
|
||||
await loadUserInfo(loginData.access_token);
|
||||
@@ -401,8 +377,7 @@ export const logout = async () => {
|
||||
// Limpiar estado en memoria
|
||||
authStore.reset();
|
||||
|
||||
// Eliminar cookie no-HttpOnly del access_token
|
||||
deleteCookie('access_token');
|
||||
clearAccessTokenOnDocument();
|
||||
// La cookie HttpOnly del refresh_token la limpia el servidor
|
||||
|
||||
// Logout unificado (SSO y password): POST al logout route del servidor.
|
||||
@@ -445,7 +420,7 @@ export const getToken = (): string | null => {
|
||||
if (token) return token;
|
||||
|
||||
// Prioridad 3: cookie no-HttpOnly (fallback para acceso inicial antes del onMount)
|
||||
if (browser) return getCookie('access_token');
|
||||
if (browser) return getAccessTokenFromDocument();
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -488,7 +463,7 @@ export const refreshAccessToken = async (): Promise<boolean> => {
|
||||
const data = await resp.json() as { access_token?: string };
|
||||
if (data.access_token) {
|
||||
authStore.setToken(data.access_token);
|
||||
setCookie('access_token', data.access_token);
|
||||
setAccessTokenOnDocument(data.access_token);
|
||||
return true;
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { page } from '$app/stores';
|
||||
import { browser } from '$app/environment';
|
||||
import { GLOBAL_NAV } from '$lib/config/shortcuts';
|
||||
import { hasAccessTokenInDocument } from '$lib/access-token-cookie-browser';
|
||||
import { shortcutStore, activeShortcutsList } from '$lib/stores/shortcut-store';
|
||||
import { focusStore, interactionMode } from '$lib/stores/focus-store';
|
||||
import ShortcutsHelpModal from './ShortcutsHelpModal.svelte';
|
||||
@@ -30,7 +31,7 @@
|
||||
const currentPath = $page?.url?.pathname || '';
|
||||
const isAuthenticatedRoute = currentPath.startsWith('/dashboard');
|
||||
const hasAccessToken =
|
||||
document.cookie.includes('access_token=') || localStorage.getItem('access_token');
|
||||
hasAccessTokenInDocument() || localStorage.getItem('access_token');
|
||||
return isAuthenticatedRoute && !!hasAccessToken;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
<script lang="ts">
|
||||
import * as Card from "$lib/components/ui/card/index.ts";
|
||||
import * as Card from "$lib/components/ui/card/index";
|
||||
import {
|
||||
FieldGroup,
|
||||
Field,
|
||||
FieldLabel,
|
||||
FieldDescription,
|
||||
} from "$lib/components/ui/field/index.ts";
|
||||
import { Input } from "$lib/components/ui/input/index.ts";
|
||||
import { Button } from "$lib/components/ui/button/index.ts";
|
||||
import { cn } from "$lib/utils.ts";
|
||||
} from "$lib/components/ui/field/index";
|
||||
import { Input } from "$lib/components/ui/input/index";
|
||||
import { Button } from "$lib/components/ui/button/index";
|
||||
import { cn } from "$lib/utils";
|
||||
import faviconUrl from '$lib/assets/favicon.svg';
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { page } from '$app/state';
|
||||
import { enhance } from '$app/forms';
|
||||
import { loginWithProvider } from '$lib/sso.ts';
|
||||
import { loginWithProvider } from '$lib/sso';
|
||||
import { onMount, tick } from 'svelte';
|
||||
import { clearAccessTokenOnDocument } from '$lib/access-token-cookie-browser';
|
||||
|
||||
let { class: className, ...restProps }: HTMLAttributes<HTMLDivElement> = $props();
|
||||
|
||||
@@ -51,7 +52,7 @@
|
||||
if (typeof document !== 'undefined') {
|
||||
const isSecure = window.location.protocol === 'https:';
|
||||
const secureFlag = isSecure ? '; Secure' : '';
|
||||
document.cookie = `access_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; SameSite=Lax${secureFlag}`;
|
||||
clearAccessTokenOnDocument();
|
||||
document.cookie = `refresh_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; SameSite=Lax${secureFlag}`;
|
||||
document.cookie = `active_company_id=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; SameSite=Lax${secureFlag}`;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { shortcutStore, type ShortcutDef } from '$lib/stores/shortcut-store';
|
||||
import { page } from '$app/stores';
|
||||
import { get } from 'svelte/store';
|
||||
import { browser } from '$app/environment';
|
||||
import { hasAccessTokenInDocument } from '$lib/access-token-cookie-browser';
|
||||
|
||||
/**
|
||||
* Hook to register shortcuts for a component lifecycle.
|
||||
@@ -19,7 +20,7 @@ export function useShortcuts(context: string, shortcuts: ShortcutDef[]) {
|
||||
const isAuthenticatedRoute = currentPath.startsWith('/dashboard');
|
||||
|
||||
// Verificar también que haya un token de acceso
|
||||
const hasAccessToken = document.cookie.includes('access_token=') ||
|
||||
const hasAccessToken = hasAccessTokenInDocument() ||
|
||||
localStorage.getItem('access_token');
|
||||
|
||||
if (isAuthenticatedRoute && hasAccessToken) {
|
||||
|
||||
55
frontend/src/lib/server/access-token-cookie.ts
Normal file
55
frontend/src/lib/server/access-token-cookie.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import type { Cookies } from '@sveltejs/kit';
|
||||
import {
|
||||
ACCESS_TOKEN_CHUNK_COUNT,
|
||||
accessTokenChunkName,
|
||||
splitAccessTokenForCookies,
|
||||
ACCESS_TOKEN_MAX_CHUNKS
|
||||
} from '$lib/access-token-cookie.shared';
|
||||
|
||||
export function getAccessTokenFromCookies(cookies: Cookies): string | null {
|
||||
const countRaw = cookies.get(ACCESS_TOKEN_CHUNK_COUNT);
|
||||
if (countRaw) {
|
||||
const n = parseInt(countRaw, 10);
|
||||
if (!Number.isFinite(n) || n < 1 || n > ACCESS_TOKEN_MAX_CHUNKS) return null;
|
||||
let out = '';
|
||||
for (let i = 0; i < n; i++) {
|
||||
const p = cookies.get(accessTokenChunkName(i));
|
||||
if (p == null) return null;
|
||||
out += p;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
return cookies.get('access_token') ?? null;
|
||||
}
|
||||
|
||||
export function clearAccessTokenCookies(cookies: Cookies) {
|
||||
cookies.delete('access_token', { path: '/' });
|
||||
cookies.delete(ACCESS_TOKEN_CHUNK_COUNT, { path: '/' });
|
||||
for (let i = 0; i < ACCESS_TOKEN_MAX_CHUNKS; i++) {
|
||||
cookies.delete(accessTokenChunkName(i), { path: '/' });
|
||||
}
|
||||
}
|
||||
|
||||
export function setAccessTokenCookies(
|
||||
cookies: Cookies,
|
||||
token: string,
|
||||
opts: { secure: boolean; maxAge: number }
|
||||
) {
|
||||
clearAccessTokenCookies(cookies);
|
||||
const split = splitAccessTokenForCookies(token);
|
||||
const base = {
|
||||
path: '/',
|
||||
httpOnly: false as const,
|
||||
sameSite: 'lax' as const,
|
||||
secure: opts.secure,
|
||||
maxAge: opts.maxAge
|
||||
};
|
||||
if (split.kind === 'single') {
|
||||
cookies.set('access_token', split.value, base);
|
||||
return;
|
||||
}
|
||||
cookies.set(ACCESS_TOKEN_CHUNK_COUNT, String(split.parts.length), base);
|
||||
split.parts.forEach((part, i) => {
|
||||
cookies.set(accessTokenChunkName(i), part, base);
|
||||
});
|
||||
}
|
||||
@@ -4,6 +4,11 @@
|
||||
*/
|
||||
|
||||
import { redirect, type Cookies } from '@sveltejs/kit';
|
||||
import {
|
||||
clearAccessTokenCookies,
|
||||
getAccessTokenFromCookies,
|
||||
setAccessTokenCookies
|
||||
} from '$lib/server/access-token-cookie';
|
||||
|
||||
/**
|
||||
* Obtiene y normaliza la URL base de la API para llamadas desde el servidor
|
||||
@@ -34,7 +39,7 @@ export function getServerApiUrl(): string {
|
||||
*/
|
||||
export function getAuthTokens(cookies: Cookies) {
|
||||
return {
|
||||
accessToken: cookies.get('access_token'),
|
||||
accessToken: getAccessTokenFromCookies(cookies),
|
||||
refreshToken: cookies.get('refresh_token')
|
||||
};
|
||||
}
|
||||
@@ -43,7 +48,7 @@ export function getAuthTokens(cookies: Cookies) {
|
||||
* Establece los tokens de autenticación en las cookies
|
||||
*
|
||||
* Política de seguridad:
|
||||
* - access_token → NO HttpOnly (el cliente necesita incluirlo en el header Authorization)
|
||||
* - access_token → NO HttpOnly (Bearer desde JS); si el JWT es muy grande, varias cookies fragmentadas
|
||||
* - refresh_token → HttpOnly=true (JS nunca lo lee; el servidor lo maneja via /api-sveltekit/auth/silent-refresh)
|
||||
*/
|
||||
export function setAuthTokens(
|
||||
@@ -51,10 +56,7 @@ export function setAuthTokens(
|
||||
accessToken: string,
|
||||
refreshToken?: string
|
||||
) {
|
||||
cookies.set('access_token', accessToken, {
|
||||
path: '/',
|
||||
httpOnly: false, // El cliente JS necesita leerlo para el header Bearer
|
||||
sameSite: 'lax',
|
||||
setAccessTokenCookies(cookies, accessToken, {
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
maxAge: 60 * 60 * 24 * 7 // 7 días
|
||||
});
|
||||
@@ -74,7 +76,7 @@ export function setAuthTokens(
|
||||
* Limpia todos los tokens de autenticación de las cookies
|
||||
*/
|
||||
export function clearAuthTokens(cookies: Cookies) {
|
||||
cookies.delete('access_token', { path: '/' });
|
||||
clearAccessTokenCookies(cookies);
|
||||
cookies.delete('refresh_token', { path: '/' });
|
||||
cookies.delete('active_company_id', { path: '/' });
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestEvent } from '@sveltejs/kit';
|
||||
import { getServerApiUrl, setAuthTokens } from '$lib/server/api';
|
||||
import { clearAccessTokenCookies } from '$lib/server/access-token-cookie';
|
||||
|
||||
export const POST = async ({ cookies, fetch }: RequestEvent) => {
|
||||
const refreshToken = cookies.get('refresh_token');
|
||||
@@ -36,7 +37,7 @@ export const POST = async ({ cookies, fetch }: RequestEvent) => {
|
||||
// El refresh token expiró o fue invalidado por Keycloak (sesión terminada).
|
||||
// Limpiar las cookies para que el servidor redirigir al login en la siguiente carga.
|
||||
cookies.delete('refresh_token', { path: '/' });
|
||||
cookies.delete('access_token', { path: '/' });
|
||||
clearAccessTokenCookies(cookies);
|
||||
cookies.delete('active_company_id', { path: '/' });
|
||||
|
||||
const status = response.status === 401 ? 401 : 400;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie';
|
||||
|
||||
export const GET: RequestHandler = async ({ cookies, url }) => {
|
||||
const token = cookies.get('access_token');
|
||||
const token = getAccessTokenFromCookies(cookies);
|
||||
|
||||
// Obtener company_id de la cookie
|
||||
const companyId = cookies.get('active_company_id');
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie';
|
||||
|
||||
export const GET: RequestHandler = async ({ cookies, url, params }) => {
|
||||
const token = cookies.get('access_token');
|
||||
const token = getAccessTokenFromCookies(cookies);
|
||||
const { id } = params;
|
||||
|
||||
// Obtener company_id de la cookie o query params
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
*/
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie';
|
||||
|
||||
export const GET: RequestHandler = async ({ cookies, fetch }) => {
|
||||
const token = cookies.get('access_token');
|
||||
const token = getAccessTokenFromCookies(cookies);
|
||||
|
||||
if (!token) {
|
||||
// Limpiar cualquier cookie de compañía si no hay autenticación
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie';
|
||||
|
||||
export const GET: RequestHandler = async ({ cookies, url }) => {
|
||||
const token = cookies.get('access_token');
|
||||
const token = getAccessTokenFromCookies(cookies);
|
||||
|
||||
// Configurar la URL de la API usando las variables de entorno
|
||||
let apiUrl = process.env.INTERNAL_API_URL;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie';
|
||||
|
||||
export const GET: RequestHandler = async ({ cookies, url }) => {
|
||||
const token = cookies.get('access_token');
|
||||
const token = getAccessTokenFromCookies(cookies);
|
||||
|
||||
// Obtener company_id de la cookie
|
||||
const companyId = cookies.get('active_company_id');
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie';
|
||||
|
||||
export const GET: RequestHandler = async ({ cookies, url }) => {
|
||||
const token = cookies.get('access_token');
|
||||
const token = getAccessTokenFromCookies(cookies);
|
||||
|
||||
// Obtener company_id de la cookie
|
||||
const companyId = cookies.get('active_company_id');
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie';
|
||||
|
||||
export const GET: RequestHandler = async ({ cookies, url, params }) => {
|
||||
const token = cookies.get('access_token');
|
||||
const token = getAccessTokenFromCookies(cookies);
|
||||
const { id } = params;
|
||||
|
||||
// Obtener company_id de la cookie o query params
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie';
|
||||
|
||||
export const GET: RequestHandler = async ({ cookies, url }) => {
|
||||
const token = cookies.get('access_token');
|
||||
const token = getAccessTokenFromCookies(cookies);
|
||||
|
||||
// Configurar la URL de la API usando las variables de entorno
|
||||
let apiUrl = process.env.INTERNAL_API_URL;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie';
|
||||
|
||||
export const GET: RequestHandler = async ({ cookies, url }) => {
|
||||
const token = cookies.get('access_token');
|
||||
const token = getAccessTokenFromCookies(cookies);
|
||||
|
||||
// Configurar la URL de la API usando las variables de entorno
|
||||
let apiUrl = process.env.INTERNAL_API_URL;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie';
|
||||
|
||||
export const GET: RequestHandler = async ({ cookies, url }) => {
|
||||
const token = cookies.get('access_token');
|
||||
const token = getAccessTokenFromCookies(cookies);
|
||||
|
||||
// Obtener company_id de la cookie
|
||||
const companyId = cookies.get('active_company_id');
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie';
|
||||
|
||||
export const GET: RequestHandler = async ({ cookies, params, url }) => {
|
||||
const token = cookies.get('access_token');
|
||||
const token = getAccessTokenFromCookies(cookies);
|
||||
const { id } = params;
|
||||
|
||||
// Obtener company_id de la cookie o query params
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { setAccessTokenCookies } from '$lib/server/access-token-cookie';
|
||||
|
||||
export const load: PageServerLoad = async ({ url, cookies, fetch }) => {
|
||||
// Obtener el código y state de los query params
|
||||
@@ -61,11 +62,8 @@ export const load: PageServerLoad = async ({ url, cookies, fetch }) => {
|
||||
// refresh_token → HttpOnly (el JS nunca lo lee; el servidor lo gestiona)
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
cookies.set('access_token', tokens.access_token, {
|
||||
path: '/',
|
||||
httpOnly: false, // El cliente necesita leerlo para Bearer
|
||||
setAccessTokenCookies(cookies, tokens.access_token, {
|
||||
secure: isProduction,
|
||||
sameSite: 'lax',
|
||||
maxAge: 60 * 60 * 24 * 7 // 7 días
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { setAccessTokenCookies } from '$lib/server/access-token-cookie';
|
||||
|
||||
export const load: PageServerLoad = async ({ url, cookies }) => {
|
||||
const relayToken = url.searchParams.get('relay');
|
||||
@@ -54,12 +55,9 @@ export const load: PageServerLoad = async ({ url, cookies }) => {
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
console.log('[SSO] NODE_ENV:', process.env.NODE_ENV, '→ isProduction:', isProduction);
|
||||
|
||||
// access_token — NO HttpOnly (client JS reads it for Bearer headers)
|
||||
cookies.set('access_token', tokens.access_token, {
|
||||
path: '/',
|
||||
httpOnly: false,
|
||||
// access_token — NO HttpOnly (Bearer desde JS); fragmentado si el JWT supera ~4KB
|
||||
setAccessTokenCookies(cookies, tokens.access_token, {
|
||||
secure: isProduction,
|
||||
sameSite: 'lax',
|
||||
maxAge: 60 * 60 * 24 * 7,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { getAccessTokenFromDocument } from '$lib/access-token-cookie-browser';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import {
|
||||
@@ -56,7 +57,7 @@
|
||||
if (parts.length === 2) return parts.pop()?.split(';').shift() || null;
|
||||
return null;
|
||||
};
|
||||
const cookieToken = getCookie('access_token');
|
||||
const cookieToken = getAccessTokenFromDocument();
|
||||
const localToken = localStorage.getItem('access_token');
|
||||
if (cookieToken && cookieToken !== localToken)
|
||||
localStorage.setItem('access_token', cookieToken);
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
import { getAccessTokenFromDocument } from '$lib/access-token-cookie-browser';
|
||||
import { currentUser } from '$lib/auth';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { getCompany } from '$lib/api/dashboard/a76/general_catalogs/company';
|
||||
@@ -193,7 +194,7 @@
|
||||
};
|
||||
|
||||
// Verificar si hay token en las cookies
|
||||
const cookieToken = getCookie('access_token');
|
||||
const cookieToken = getAccessTokenFromDocument();
|
||||
const localToken = localStorage.getItem('access_token');
|
||||
|
||||
if (cookieToken && cookieToken !== localToken) {
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import type { PageData } from './$types';
|
||||
import { browser } from '$app/environment';
|
||||
import { getAccessTokenFromDocument } from '$lib/access-token-cookie-browser';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { Edit, Send, Plus, Trash2, RefreshCw } from 'lucide-svelte';
|
||||
import { Edit, Send, Plus, Trash2, RefreshCw } from 'lucide-svelte';
|
||||
import { obtenerAtajosListaPedimento } from '$lib/config/shortcuts/dashboard/pedimentos/list';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { goto } from '$app/navigation';
|
||||
@@ -45,7 +46,7 @@ let filters = $state({
|
||||
};
|
||||
|
||||
// Verificar si hay token en las cookies
|
||||
const cookieToken = getCookie('access_token');
|
||||
const cookieToken = getAccessTokenFromDocument();
|
||||
const localToken = localStorage.getItem('access_token');
|
||||
|
||||
if (cookieToken && cookieToken !== localToken) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import { obtenerAtajosLista } from '$lib/config/shortcuts/dashboard/reference_data/countries/list';
|
||||
import { browser } from '$app/environment';
|
||||
import { currentUser, userHasPermission } from '$lib/auth';
|
||||
import { getAccessTokenFromDocument } from '$lib/access-token-cookie-browser';
|
||||
import ErrorState from '$lib/components/dashboard/common/error-state.svelte';
|
||||
|
||||
// Los datos iniciales vienen del servidor
|
||||
@@ -36,7 +37,7 @@
|
||||
return null;
|
||||
};
|
||||
|
||||
const cookieToken = getCookie('access_token');
|
||||
const cookieToken = getAccessTokenFromDocument();
|
||||
const localToken = localStorage.getItem('access_token');
|
||||
|
||||
if (cookieToken && cookieToken !== localToken) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import { obtenerAtajosLista } from '$lib/config/shortcuts/dashboard/reference_data/currency_types/list';
|
||||
import { browser } from '$app/environment';
|
||||
import { currentUser, userHasPermission } from '$lib/auth';
|
||||
import { getAccessTokenFromDocument } from '$lib/access-token-cookie-browser';
|
||||
import ErrorState from '$lib/components/dashboard/common/error-state.svelte';
|
||||
|
||||
// Los datos iniciales vienen del servidor
|
||||
@@ -36,7 +37,7 @@
|
||||
return null;
|
||||
};
|
||||
|
||||
const cookieToken = getCookie('access_token');
|
||||
const cookieToken = getAccessTokenFromDocument();
|
||||
const localToken = localStorage.getItem('access_token');
|
||||
if (cookieToken && cookieToken !== localToken) {
|
||||
localStorage.setItem('access_token', cookieToken);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { getAccessTokenFromDocument } from '$lib/access-token-cookie-browser';
|
||||
import { page } from '$app/stores';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
@@ -26,7 +27,7 @@
|
||||
if (parts.length === 2) return parts.pop()?.split(';').shift() || null;
|
||||
return null;
|
||||
};
|
||||
const cookieToken = getCookie('access_token');
|
||||
const cookieToken = getAccessTokenFromDocument();
|
||||
const localToken = localStorage.getItem('access_token');
|
||||
if (cookieToken && cookieToken !== localToken) localStorage.setItem('access_token', cookieToken);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { env } from '$env/dynamic/private';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { clearAccessTokenCookies } from '$lib/server/access-token-cookie';
|
||||
|
||||
export const POST: RequestHandler = async ({ cookies, request }) => {
|
||||
const refreshToken = cookies.get('refresh_token');
|
||||
@@ -14,8 +15,8 @@ export const POST: RequestHandler = async ({ cookies, request }) => {
|
||||
? `${hubPublicUrl}/login`
|
||||
: `${new URL(request.url).origin}/login`;
|
||||
|
||||
// Eliminar todas las cookies de autenticación
|
||||
cookies.delete('access_token', { path: '/' });
|
||||
// Eliminar todas las cookies de autenticación (access_token puede estar fragmentado)
|
||||
clearAccessTokenCookies(cookies);
|
||||
cookies.delete('refresh_token', { path: '/' });
|
||||
cookies.delete('active_company_id', { path: '/' });
|
||||
cookies.delete('sso_tenant_id', { path: '/' });
|
||||
|
||||
Reference in New Issue
Block a user