# Phase 9: Auth Module ## Objetivo Sistema completo de autenticación y autorización para ABE como plataforma. ## Roles y permisos | Role | Sessions | Findings | Reports | Integrations | Org/Users | Settings | License | |---------|----------|----------|---------|--------------|-----------|----------|---------| | owner | CRUD | CRUD | CRUD | CRUD | CRUD | CRUD | CRUD | | admin | CRUD | CRUD | CRUD | CRUD | CRU | CRUD | R | | member | CR | CRU | CR | R | R | R | R | | viewer | R | R | R | R | R | R | R | ## Better Auth config ```typescript import { betterAuth } from 'better-auth'; export const auth = betterAuth({ database: { // Usar Kysely adapter o direct SQLite type: 'sqlite', url: config.db.path, }, emailAndPassword: { enabled: true }, session: { maxAge: config.auth.sessionMaxAge, updateAge: 60 * 60, // refresh cada hora }, // Organization plugin si disponible, sino implementar manual }); ``` Si Better Auth no soporta organizaciones directamente, implementar manualmente: - Tabla organizations (id, name, slug, created_at) - Tabla org_members (id, org_id, user_id, role, invited_at, joined_at) ## CASL AbilityFactory ```typescript import { AbilityBuilder, createMongoAbility } from '@casl/ability'; export function defineAbilityFor(role: string) { const { can, cannot, build } = new AbilityBuilder(createMongoAbility); switch (role) { case 'owner': can('manage', 'all'); break; case 'admin': can('manage', 'all'); cannot('delete', 'Organization'); cannot('manage', 'License'); can('read', 'License'); break; case 'member': can('create', ['Session', 'Finding', 'Report']); can('read', 'all'); can('update', 'Finding'); break; case 'viewer': can('read', 'all'); break; } return build(); } ``` ## AuthMiddleware — orden de verificación 1. Check cookie de session (web UI) via Better Auth 2. Check header `Authorization: Bearer ` 3. Check header `X-ABE-API-Key: ` (API keys para CI/CD) 4. Si ninguno → 401 ## API Key system - POST /api/auth/api-keys — crear key (retorna key UNA vez, después solo hash) - GET /api/auth/api-keys — listar (sin mostrar key, solo nombre + último uso) - DELETE /api/auth/api-keys/:id — revocar - Keys hasheadas con SHA-256 en DB - Cada key tiene: name, permissions (array de roles), expiresAt, lastUsedAt ## First-run flow 1. Backend: si tabla users tiene 0 rows → flag `setupRequired = true` 2. GET /api/auth/setup-required → `{ required: boolean }` 3. Si required, POST /api/auth/setup con { email, password, name, orgName } 4. Crea user con role owner + organization default 5. Después de setup, requiere login normal ## Migraciones ```sql -- users (Better Auth maneja su propia tabla, pero añadir campos custom) CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, email TEXT UNIQUE NOT NULL, name TEXT NOT NULL, password_hash TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'member', created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS organizations ( id TEXT PRIMARY KEY, name TEXT NOT NULL, slug TEXT UNIQUE NOT NULL, created_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS org_members ( id TEXT PRIMARY KEY, org_id TEXT NOT NULL REFERENCES organizations(id), user_id TEXT NOT NULL REFERENCES users(id), role TEXT NOT NULL DEFAULT 'member', joined_at INTEGER NOT NULL, UNIQUE(org_id, user_id) ); CREATE TABLE IF NOT EXISTS api_keys ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id), org_id TEXT NOT NULL REFERENCES organizations(id), name TEXT NOT NULL, key_hash TEXT NOT NULL, key_prefix TEXT NOT NULL, -- primeros 8 chars para identificar permissions TEXT NOT NULL DEFAULT '["member"]', expires_at INTEGER, last_used_at INTEGER, created_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS auth_sessions ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id), token TEXT UNIQUE NOT NULL, expires_at INTEGER NOT NULL, created_at INTEGER NOT NULL ); ``` ## NOTA sobre Better Auth Si Better Auth resulta demasiado complejo de integrar con Express puro o tiene incompatibilidades, implementar auth manualmente: - argon2 para hash passwords - crypto.randomUUID() para session tokens - Cookie httpOnly + secure + sameSite para sessions - Middleware custom que lee cookie → busca en auth_sessions → adjunta user a req Esto es PERFECTAMENTE VÁLIDO. No over-engineer la auth. La prioridad es que funcione, sea seguro, y tenga RBAC.