Files
Autonomous-Bug-Explorer/.ralph/specs/phase-09-auth-module.md

4.6 KiB

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

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

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 <jwt>
  3. Check header X-ABE-API-Key: <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

-- 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.