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

149 lines
4.6 KiB
Markdown

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