Sistema de Usuarios por Tenant
Descripción
Sistema de autenticación y autorización para usuarios dentro de cada tenant. Cada tenant tiene su propia tabla de usuarios aislada, separada de los admin_users globales.
Arquitectura
Schema de Usuarios
Cada tenant tiene dos tablas en su schema:
- users: Usuarios del tenant
- user_sessions: Sesiones activas de usuarios
-- tenant_{slug}.users
CREATE TABLE users (
id UUID PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
password TEXT NOT NULL, -- bcrypt hash
name TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'member',
is_active BOOLEAN DEFAULT true,
permissions JSONB DEFAULT '{}',
metadata JSONB DEFAULT '{}',
last_login_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
-- tenant_{slug}.user_sessions
CREATE TABLE user_sessions (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
token TEXT UNIQUE NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ DEFAULT now()
);
Roles
| Role | Descripción | Permisos por defecto |
|---|---|---|
owner | Dueño del tenant | Full control |
admin | Administrador | Gestionar usuarios y settings |
member | Miembro | Crear y editar records |
viewer | Observador | Solo lectura |
Permisos
Los permisos se almacenan en el campo permissions (JSONB):
{
"entities": {
"products": ["create", "read", "update", "delete"],
"invoices": ["read", "update"],
"customers": ["read"]
},
"canManageUsers": false,
"canManageSettings": false
}
API REST
Crear Usuario
POST /auth/tenant/users
Authorization: Bearer {admin_token}
{
"email": "user@example.com",
"password": "secure123",
"name": "John Doe",
"role": "member",
"permissions": {
"entities": {
"products": ["create", "read", "update"]
}
},
"metadata": {
"department": "Sales",
"position": "Sales Rep"
}
}
Response:
{
"success": true,
"data": {
"id": "uuid",
"email": "user@example.com",
"name": "John Doe",
"role": "member",
"permissions": {},
"metadata": {},
"createdAt": "2026-02-03T..."
}
}
Login de Usuario
POST /auth/tenant/login
Authorization: Bearer {admin_token}
{
"email": "user@example.com",
"password": "secure123"
}
Response:
{
"success": true,
"data": {
"token": "uuid-session-token",
"user": {
"id": "uuid",
"email": "user@example.com",
"name": "John Doe",
"role": "member"
}
}
}
Usar el Token
Una vez que el usuario tiene un token, puede usarlo de dos formas:
Opción 1: Authorization Header
GET /api/entities/products/records
Authorization: Bearer {user_token}
X-Tenant-ID: fyso
Opción 2: X-API-Key Header (Recomendado para APIs externas)
GET /api/entities/products/records
X-API-Key: {user_token}
X-Tenant-ID: fyso
Otros Endpoints
# Logout
POST /auth/tenant/logout
X-API-Key: {user_token}
# Obtener info del usuario actual
GET /auth/tenant/me
X-API-Key: {user_token}
# Listar usuarios
GET /auth/tenant/users
Authorization: Bearer {admin_token}
# Obtener usuario por ID
GET /auth/tenant/users/{id}
Authorization: Bearer {admin_token}
# Actualizar usuario
PUT /auth/tenant/users/{id}
Authorization: Bearer {admin_token}
{
"name": "New Name",
"role": "admin",
"permissions": {}
}
# Cambiar contraseña
PUT /auth/tenant/users/{id}/password
Authorization: Bearer {admin_token}
{
"password": "newpass123"
}
# Eliminar usuario
DELETE /auth/tenant/users/{id}
Authorization: Bearer {admin_token}
MCP Tool
create_user
Crea un usuario en el tenant desde Claude Desktop.
// Ejemplo
create_user({
tenantSlug: "fyso",
email: "sales@company.com",
password: "secure123",
name: "Sales Manager",
role: "admin",
permissions: {
entities: {
products: ["create", "read", "update", "delete"],
customers: ["create", "read", "update"],
invoices: ["read"]
},
canManageUsers: true,
canManageSettings: false
},
metadata: {
department: "Sales",
position: "Manager",
phone: "+1234567890"
}
})
Response:
{
"success": true,
"user": {
"id": "uuid",
"email": "sales@company.com",
"name": "Sales Manager",
"role": "admin"
},
"loginInfo": {
"endpoint": "POST /auth/tenant/login",
"body": {
"email": "sales@company.com",
"password": "(provided)"
},
"headers": {
"X-Tenant-ID": "fyso"
}
}
}
Middleware
El middleware requireTenantContext ahora soporta ambos:
-
Admin tokens (flujo original)
- Token en cookie o Authorization header
- Tenant ID en el JWT
-
Tenant user tokens (nuevo)
- Token en X-API-Key o Authorization header
- Tenant slug en X-Tenant-ID header
- Valida sesión en la tabla
user_sessionsdel tenant
El middleware automáticamente:
- Detecta el tipo de token
- Valida la sesión
- Establece el contexto (
userId,userRole,userPermissions,db) - Configura el
search_pathal schema del tenant
Flujo Completo
1. Admin crea el tenant
POST /auth/tenants
Authorization: Bearer {admin_token}
{
"name": "ACME Corp",
"description": "..."
}
2. Admin crea usuarios en el tenant
POST /auth/tenant/users
Authorization: Bearer {admin_token} (con tenant seleccionado)
{
"email": "owner@acme.com",
"password": "secure123",
"name": "Owner User",
"role": "owner"
}
3. Usuario se loguea
POST /auth/tenant/login
Authorization: Bearer {admin_token}
{
"email": "owner@acme.com",
"password": "secure123"
}
# Retorna user_token
4. Usuario usa la API
GET /api/entities/products/records
X-API-Key: {user_token}
X-Tenant-ID: acme-corp
Seguridad
Passwords
- Hasheados con bcrypt (10 rounds)
- Nunca se retornan en las respuestas
- Mínimo 8 caracteres
Tokens
- UUIDs generados con
crypto.randomUUID() - Válidos por 7 días
- Almacenados en tabla
user_sessions - Se eliminan al logout
Aislamiento
- Usuarios aislados por schema del tenant
- No hay acceso cross-tenant
- Middleware valida tenant antes de ejecutar queries
Permisos
- Roles con permisos predefinidos
- Permisos custom por entity
- Validación en cada request (futuro)
Testing
Ejecutar tests:
cd packages/api
bun test tenant-users.test.ts
Tests incluidos:
- TU1: Crear usuario
- TU2: Login
- TU3: Get current user
- TU4: List users
- TU5: Update user
- TU6: Access records with user token
- TU7: Update password
- TU8: Logout
- TU9: Delete user
Migración de Tenants Existentes
Para tenants creados antes de este feature, correr:
cd packages/db
bun src/scripts/add-users-to-tenants.ts
Este script agregará las tablas users y user_sessions a todos los tenants existentes.