# auth `auth` es la capa activa de autenticación para Svelte. Su trabajo no es decidir permisos ni guardar sesiones por su cuenta: su trabajo es reflejar en cliente el resultado de un motor autoritativo server-side. El módulo está partido en tres capas, igual que `perm` y `cache`: | Capa | Entrada | Qué contiene | | ---------------- | ------------ | ---------------------------------------------------------------------------------- | | Lenguaje común | `$libs/auth` | Constantes, tipos, errores, eventos, helpers CSRF/token, contratos de adapters | | Autoridad server | `$svrs/auth` | `createEngineAuth()`, handlers HTTP, password flow, CSRF, devices, session binding | | Cliente activo | `$auth` | `createActiveAuth()`, estado reactivo, llamadas HTTP, CSRF header wiring | La regla mental es sencilla: ```txt auth prueba identidad session mantiene continuidad de sesión perm decide autorización cache invalida datos derivados de identidad storage no guarda secretos ``` ## Qué Está Implementado En esta versión el camino sólido es: - Password sign-up. - Password sign-in. - Current session snapshot. - Local sign-out. - Global sign-out. - CSRF issue/verify. - Email verification request/complete sobre flows. - Password reset request/complete sobre flows. - Device records básicos. - Eventos/auditoría. - Invalidación de cache por identidad. - Typed errors con `code` (`ErrCode` canónico tipo `'auth::credential_invalid'`). - Memory/test adapters. - Adapter DB genérico sin dependencia de ORM. - Adapter `scrypt` Node sin dependencia externa. - Contratos para OAuth, MFA y WebAuthn. OAuth, MFA y WebAuthn existen como superficie de extensión y algunos métodos base, pero no deben documentarse como un flujo production-ready completo aún. ## Uso Rápido en Cliente La forma normal en UI es declararlo desde `active-app`, porque `App` ya puede componer `http`, `cache` y `session`: ```ts import { createActiveApp } from '$active-app'; import { defineActiveAuth, defineEngineHttp } from '$active-app/services'; const App = createActiveApp({ services: { http: defineEngineHttp({ baseUrl: '' }), auth: defineActiveAuth({ initial: data.auth }) } }); const Auth = App.auth; ``` `App.auth` existe cuando la app declara el slot `auth` en `services`. Igual que `session` y `perm`, solo puede haber un active auth por App: ```ts const Auth = App.auth; Auth.authenticated; Auth.loading; Auth.lastError; Auth.current.session.status; ``` Sign-in: ```ts try { await Auth.signInPassword({ identifier: 'ada@example.com', password: 'correct horse battery staple' }); } catch { // El error seguro queda normalizado en Auth.lastError. console.log(Auth.lastError?.code); } ``` Sign-up: ```ts await Auth.signUpPassword({ identifier: 'ada@example.com', password: 'correct horse battery staple', profile: { displayName: 'Ada Lovelace' } }); ``` Logout: ```ts await Auth.signOut(); ``` Logout global: ```ts await Auth.signOutGlobal(); ``` Current session: ```ts await Auth.loadCurrent(); if (Auth.authenticated) { console.log(Auth.current.actor?.primaryIdentifier); } ``` Devices: ```ts const devices = await Auth.listDevices(); await Auth.revokeDevice({ deviceId: devices[0].id }); ``` ## Estado Reactivo `ActiveAuth` sigue el contrato común `ActiveEngine`: getters directos, `snapshot()`, `onChange()`, `clearError()` y `dispose()`. ```ts Auth.current; // AuthCurrentView Auth.loading; // boolean Auth.lastError; // AuthClientSafeError | null Auth.authenticated; // boolean Auth.mfaRequired; // boolean Auth.disposed; // boolean Auth.snapshot(); // AuthCurrentView ``` En Svelte: ```svelte {#if Auth.loading}
Validando...
{:else if Auth.authenticated}Hola {Auth.current.actor?.displayName}
{:else}Sesión anónima
{/if} {#if Auth.lastError}{Lang.t(codeToLangPath(Auth.lastError.code))}
{/if} ``` ## Cómo Funciona CSRF Las operaciones mutables del active client hacen esto automáticamente: 1. `GET AUTH_ROUTE_PATHS.CSRF` 2. reciben `{ token, expiresAt }` 3. envían el token en `AUTH_HEADER_NAMES.CSRF` 4. el server compara header + cookie firmada El token no se guarda en `storage`. Si se pasa `storage`, solo se marca que CSRF fue emitido para debugging/UX, pero no se persiste un secreto. Ejemplo interno equivalente: ```ts const csrf = await http.get(AUTH_ROUTE_PATHS.CSRF); await http.post(AUTH_ROUTE_PATHS.SIGN_IN_PASSWORD, body, { headers: { [AUTH_HEADER_NAMES.CSRF]: csrf.token } }); ``` ## Patrón SvelteKit Recomendado El server resuelve la sesión y se la pasa al layout o página: ```ts // +layout.server.ts import { Auth } from '$lib/server/auth'; import { resolveTenantId } from '$lib/server/tenant'; export async function load({ request }) { const tenantId = resolveTenantId(request); const auth = await Auth.current({ tenantId, request }); return { auth }; } ``` En cliente: ```svelte {@render children()} ``` Las rutas HTTP de auth se montan server-side: ```ts // src/web/routes/api/auth/[...path]/+server.ts import { Auth } from '$lib/server/auth'; import { resolveTenantId } from '$lib/server/tenant'; function input(request: Request) { return { request, tenantId: resolveTenantId(request) }; } export const GET = ({ request }) => Auth.handlers.handle(input(request)); export const POST = ({ request }) => Auth.handlers.handle(input(request)); ``` El handler despacha por las rutas constantes: ```ts AUTH_ROUTE_PATHS.CURRENT; AUTH_ROUTE_PATHS.CSRF; AUTH_ROUTE_PATHS.SIGN_UP_PASSWORD; AUTH_ROUTE_PATHS.SIGN_IN_PASSWORD; AUTH_ROUTE_PATHS.SIGN_OUT; AUTH_ROUTE_PATHS.SIGN_OUT_GLOBAL; AUTH_ROUTE_PATHS.EMAIL_VERIFY_REQUEST; AUTH_ROUTE_PATHS.EMAIL_VERIFY_COMPLETE; AUTH_ROUTE_PATHS.PASSWORD_RESET_REQUEST; AUTH_ROUTE_PATHS.PASSWORD_RESET_COMPLETE; ``` ## Errores en Cliente Los errores seguros tienen esta forma: ```ts interface AuthClientSafeError { code: AuthErrorCode; // ErrCode canónico, p.ej. 'auth::credential_invalid' } ``` Ejemplo: ```ts import { AUTH_ERR_CREDENTIAL_INVALID } from '$auth'; try { await Auth.signInPassword({ identifier, password }); } catch { const error = Auth.lastError; if (error?.code === AUTH_ERR_CREDENTIAL_INVALID) { // Mostrar mensaje traducido desde Lang. } } ``` No uses el texto del error como lógica. Usa los constantes `AUTH_ERR_*` o `matches(err, AUTH_ERR)` para descubrir cualquier error de auth. ## Relación con `session` `auth` no emite la cookie principal de sesión. En un login correcto: 1. `auth` verifica credencial. 2. `auth` crea/actualiza device. 3. `auth` llama a `AuthSessPort.start(...)`. 4. `session` devuelve `sessionId` y snapshot. 5. `auth` guarda un binding `sessionId -> actorRef`. 6. `auth` emite eventos e invalida cache. En logout: 1. `auth` lee la sesión desde `session`. 2. revoca el binding auth. 3. llama a `session.end(...)` o `session.endMany(...)`. 4. invalida `cache`. 5. emite auditoría. ## Relación con `perm` `auth` no decide permisos. Lo único que entrega al resto del sistema es: ```ts actorRef; sessionId; aal; amr; authTime; deviceId; ``` `perm` puede usar ese contexto para decidir: ```ts Perms.can({ actor, action: 'project:update', resource }); ``` Si cambia la identidad, roles o permisos, el consumer debe invalidar las decisiones de `perm` y los datos en `cache`. Cuando `ActiveAuth` se declara en `active-app`, las reacciones entre auth/cache/session deben cablearse desde `orca`/presets o puertos explicitos; `auth` no debe limpiar caches de forma oculta. ## Relación con `storage` No guardes access tokens, refresh tokens, OTPs, CSRF tokens ni passwords en `storage`. Si necesitas persistencia de preferencias de auth, guarda solo datos no sensibles: ```ts storage.entry('last-login-email', '', { raw: true }); ``` ## Test Page La página `/test/auth` monta un harness en memoria y permite probar: - sign-up - sign-in - sign-out - global sign-out - reset del harness - CSRF roundtrip - CSRF expirado - snapshot actual - devices - credentials - audit events - cache invalidations Es una página de diagnóstico funcional, no un ejemplo de UI final. ## Archivos Principales ```txt src/libs/auth/ consts.ts # rutas, headers, cookies, eventos, códigos, defaults types.ts # tipos públicos contracts.ts # puertos/adapters errors.ts # errores tipados + guards csrf.ts # issue/verify tokens.ts # random/base64/hash helpers src/svrs/auth/ engine-auth.ts # createEngineAuth() handlers.ts # HTTP handlers options.ts # EngineAuthOptions adapters/ # memory, db, crypto, password, mail... integrations/ # bridges con session, perm, cache, logger, timer server-side... src/arts/auth/ active-auth.svelte.ts client.ts types.ts ``` ## Qué No Debe Hacer `auth` - No guarda perfiles completos de usuario. - No decide autorización. - No persiste secretos en cliente. - No hace magia dentro de `App.http`. - No mezcla tenants. - No autolinka OAuth por email no verificado. - No expone raw password hashes, tokens o OTPs a UI. ## Checklist de Producción Antes de usarlo fuera de tests: - Sustituir memory adapters por adapters reales. - Usar un `AuthStoreAdapter` transaccional. - Conectar `AuthSessPort` al módulo real `session`. - Conectar el puerto de logger de auth a `logger`. - Conectar `AuthCachePort` a `cache`. - Usar `createNodeScryptPasswordHasher()` o un adapter Argon2id propio. - Resolver `tenantId` desde request, subdominio, organización o app config. - Mantener `security.csrf.signingKey` fuera del repo. - Validar cookies `Secure` en producción. - Añadir rate-limit real a sign-in, reset y verification.