Labxus Auth Multitenant con RLS, Next.js, NestJS, PostgreSQL, Drizzle, Zod y TypeScript


1. Stack obligatorio

Gestor de paquetes

Usar obligatoriamente:

Lenguaje

Usar obligatoriamente:


2. Versiones obligatorias

Usar estas versiones o rangos específicos:

Frontend

Backend

ORM

Usar:

Instalación esperada con pnpm:

pnpm add drizzle-orm pg
pnpm add -D drizzle-kit @types/pg

3. Stack técnico final

Frontend

Backend

Base de datos

Arquitectura


4. Arquitectura general esperada

Crear una arquitectura tipo monorepo:

labxus-platform/
├── apps/
│   ├── web/                 # Frontend Next.js v16.2.x
│   └── api/                 # Backend NestJS v11.1.19
│
├── packages/
│   ├── shared/              # Zod schemas, types, roles, permisos
│   ├── ui/                  # Opcional: componentes compartidos
│   └── config/              # Configuración compartida
│
├── package.json
├── pnpm-workspace.yaml
├── turbo.json
├── README.md
└── .gitignore

Usar preferiblemente:


5. package.json raíz esperado

Crear un package.json raíz parecido a este:

{
  "name": "labxus-platform",
  "version": "0.1.0",
  "private": true,
  "packageManager": "pnpm@latest",
  "scripts": {
    "dev": "turbo dev",
    "build": "turbo build",
    "lint": "turbo lint",
    "typecheck": "turbo typecheck",
    "format": "prettier --write .",
    "db:generate": "pnpm --filter @labxus/api db:generate",
    "db:migrate": "pnpm --filter @labxus/api db:migrate",
    "db:studio": "pnpm --filter @labxus/api db:studio",
    "db:seed": "pnpm --filter @labxus/api db:seed"
  },
  "devDependencies": {
    "turbo": "latest",
    "typescript": "latest",
    "prettier": "latest"
  }
}

6. pnpm-workspace.yaml esperado

Crear:

packages:
  - "apps/*"
  - "packages/*"

7. Modelo de negocio y reglas principales

La plataforma será SaaS multitenant.

Reglas obligatorias:


8. Módulos iniciales a construir

Crear los siguientes módulos:

auth
companies
users
roles
permissions
database
common

Estructura esperada en backend:

apps/api/src/
├── app.module.ts
├── main.ts
├── config/
│   ├── env.schema.ts
│   └── config.module.ts
│
├── database/
│   ├── db.ts
│   ├── database.module.ts
│   ├── drizzle.config.ts
│   ├── schema/
│   │   ├── companies.schema.ts
│   │   ├── users.schema.ts
│   │   ├── roles.schema.ts
│   │   ├── permissions.schema.ts
│   │   ├── role-permissions.schema.ts
│   │   ├── refresh-tokens.schema.ts
│   │   ├── password-reset-tokens.schema.ts
│   │   ├── modules.schema.ts
│   │   ├── company-modules.schema.ts
│   │   └── audit-logs.schema.ts
│   ├── migrations/
│   └── seed.ts
│
├── common/
│   ├── decorators/
│   │   ├── current-user.decorator.ts
│   │   ├── require-roles.decorator.ts
│   │   └── require-permissions.decorator.ts
│   │
│   ├── guards/
│   │   ├── jwt-auth.guard.ts
│   │   ├── roles.guard.ts
│   │   ├── permissions.guard.ts
│   │   └── tenant.guard.ts
│   │
│   ├── filters/
│   ├── interceptors/
│   ├── pipes/
│   ├── utils/
│   └── types/
│
├── auth/
│   ├── auth.module.ts
│   ├── auth.controller.ts
│   ├── auth.service.ts
│   ├── auth.repository.ts
│   ├── strategies/
│   └── dto/
│
├── companies/
│   ├── companies.module.ts
│   ├── companies.controller.ts
│   ├── companies.service.ts
│   ├── companies.repository.ts
│   └── dto/
│
├── users/
│   ├── users.module.ts
│   ├── users.controller.ts
│   ├── users.service.ts
│   ├── users.repository.ts
│   └── dto/
│
├── roles/
│   ├── roles.module.ts
│   ├── roles.controller.ts
│   ├── roles.service.ts
│   ├── roles.repository.ts
│   └── dto/
│
└── permissions/
    ├── permissions.module.ts
    ├── permissions.controller.ts
    ├── permissions.service.ts
    ├── permissions.repository.ts
    └── dto/

9. Package compartido

Crear un package compartido:

packages/shared/
├── src/
│   ├── schemas/
│   │   ├── auth.schema.ts
│   │   ├── company.schema.ts
│   │   ├── user.schema.ts
│   │   ├── role.schema.ts
│   │   ├── permission.schema.ts
│   │   └── common.schema.ts
│   │
│   ├── types/
│   │   ├── auth.types.ts
│   │   ├── company.types.ts
│   │   ├── user.types.ts
│   │   ├── role.types.ts
│   │   ├── permission.types.ts
│   │   └── common.types.ts
│   │
│   ├── constants/
│   │   ├── roles.ts
│   │   ├── permissions.ts
│   │   ├── modules.ts
│   │   └── statuses.ts
│   │
│   └── index.ts
│
├── package.json
└── tsconfig.json

Este package debe usarse tanto en:

Reglas:


10. Zod schemas compartidos

Crear schemas para:

Auth

loginSchema
forgotPasswordSchema
resetPasswordSchema
refreshTokenSchema

Companies

createCompanySchema
updateCompanySchema
companyStatusSchema

Users

createUserSchema
updateUserSchema
userStatusSchema
changePasswordSchema

Roles

createRoleSchema
updateRoleSchema
assignPermissionsToRoleSchema

Permissions

permissionSchema
permissionCodeSchema

Reglas de password

La contraseña debe cumplir:


11. Roles base

Crear constantes compartidas para roles:

SUPER_ADMIN
COMPANY_ADMIN
MANAGER
EMPLOYEE
READ_ONLY

Reglas:


12. Permisos base

Crear constantes compartidas para permisos:

companies.create
companies.read
companies.update
companies.delete

users.create
users.read
users.update
users.delete

roles.create
roles.read
roles.update
roles.delete

permissions.read
permissions.assign

dashboard.read

settings.read
settings.update

Preparar también permisos futuros para módulos:

inventory.create
inventory.read
inventory.update
inventory.delete

warehouses.create
warehouses.read
warehouses.update
warehouses.delete

orders.create
orders.read
orders.update
orders.delete

customers.create
customers.read
customers.update
customers.delete

invoicing.create
invoicing.read
invoicing.update
invoicing.delete

employees.create
employees.read
employees.update
employees.delete

payroll.create
payroll.read
payroll.update
payroll.delete

13. Base de datos con Drizzle ORM

Crear schemas de Drizzle para las siguientes tablas:

companies
users
roles
permissions
role_permissions
refresh_tokens
password_reset_tokens
audit_logs
modules
company_modules

Usar Drizzle ORM con PostgreSQL y pg.

Instalación esperada en apps/api:

pnpm add drizzle-orm pg
pnpm add -D drizzle-kit @types/pg

Ejemplo esperado de conexión:

import "dotenv/config";
import { drizzle } from "drizzle-orm/node-postgres";

export const db = drizzle(process.env.DATABASE_URL!);

Ejemplo esperado de drizzle.config.ts:

import "dotenv/config";
import { defineConfig } from "drizzle-kit";

export default defineConfig({
  schema: "./src/database/schema/index.ts",
  out: "./src/database/migrations",
  dialect: "postgresql",
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});

Scripts esperados en apps/api/package.json:

{
  "scripts": {
    "db:generate": "drizzle-kit generate",
    "db:migrate": "drizzle-kit migrate",
    "db:studio": "drizzle-kit studio",
    "db:seed": "tsx src/database/seed.ts"
  }
}

14. Tabla companies

Campos:

id uuid primary key
name varchar not null
legal_name varchar
nit varchar unique
email varchar
phone varchar
address text
status varchar not null default 'active'
created_at timestamp
updated_at timestamp

Estados:

active
inactive
suspended

Reglas:


15. Tabla users

Campos:

id uuid primary key
company_id uuid nullable references companies(id)
name varchar not null
email varchar not null unique
password_hash text not null
status varchar not null default 'active'
is_super_admin boolean default false
created_at timestamp
updated_at timestamp

Estados:

active
inactive
pending
blocked

Reglas:


16. Tabla roles

Campos:

id uuid primary key
company_id uuid nullable references companies(id)
name varchar not null
code varchar not null
description text
is_system_role boolean default false
created_at timestamp
updated_at timestamp

Reglas:


17. Tabla permissions

Campos:

id uuid primary key
name varchar not null
code varchar not null unique
description text
module varchar not null
created_at timestamp
updated_at timestamp

Reglas:


18. Tabla role_permissions

Campos:

id uuid primary key
role_id uuid references roles(id)
permission_id uuid references permissions(id)
created_at timestamp

Reglas:


19. Tabla refresh_tokens

Campos:

id uuid primary key
user_id uuid references users(id)
token_hash text not null
expires_at timestamp not null
revoked_at timestamp nullable
created_at timestamp

Reglas:


20. Tabla password_reset_tokens

Campos:

id uuid primary key
user_id uuid references users(id)
token_hash text not null
expires_at timestamp not null
used_at timestamp nullable
created_at timestamp

Reglas:


21. Tabla audit_logs

Campos:

id uuid primary key
company_id uuid nullable references companies(id)
user_id uuid nullable references users(id)
action varchar not null
entity varchar not null
entity_id uuid nullable
metadata jsonb
ip_address varchar
user_agent text
created_at timestamp

Reglas:


22. Tabla modules

Campos:

id uuid primary key
name varchar not null
code varchar not null unique
description text
is_active boolean default true
created_at timestamp
updated_at timestamp

Ejemplos:

inventory
orders
invoicing
employees
payroll
reports

Reglas:


23. Tabla company_modules

Campos:

id uuid primary key
company_id uuid references companies(id)
module_id uuid references modules(id)
is_active boolean default true
created_at timestamp
updated_at timestamp

Reglas:


24. PostgreSQL Row Level Security, RLS

Implementar Row Level Security, RLS, en PostgreSQL para reforzar el aislamiento multitenant por company_id.

Objetivo

Reglas obligatorias


25. Variables de contexto para RLS

Implementar variables personalizadas de PostgreSQL:

app.current_company_id
app.current_user_id
app.is_super_admin

Ejemplo:

SELECT set_config('app.current_company_id', 'uuid-de-la-empresa', true);
SELECT set_config('app.current_user_id', 'uuid-del-usuario', true);
SELECT set_config('app.is_super_admin', 'false', true);

Para SUPER_ADMIN:

SELECT set_config('app.current_company_id', '', true);
SELECT set_config('app.current_user_id', 'uuid-del-super-admin', true);
SELECT set_config('app.is_super_admin', 'true', true);

26. Integración de RLS con NestJS y Drizzle

Crear un helper, servicio o middleware para establecer el contexto RLS por request.

Ejemplo conceptual:

await db.execute(sql`
  SELECT set_config('app.current_company_id', ${currentUser.companyId ?? ""}, true)
`);

await db.execute(sql`
  SELECT set_config('app.current_user_id', ${currentUser.id}, true)
`);

await db.execute(sql`
  SELECT set_config('app.is_super_admin', ${currentUser.isSuperAdmin ? "true" : "false"}, true)
`);

Reglas:


27. Ejemplo de política RLS para una tabla multitenant

Ejemplo para products:

ALTER TABLE products ENABLE ROW LEVEL SECURITY;
ALTER TABLE products FORCE ROW LEVEL SECURITY;

CREATE POLICY products_select_policy
ON products
FOR SELECT
USING (
  current_setting('app.is_super_admin', true) = 'true'
  OR company_id::text = current_setting('app.current_company_id', true)
);

CREATE POLICY products_insert_policy
ON products
FOR INSERT
WITH CHECK (
  current_setting('app.is_super_admin', true) = 'true'
  OR company_id::text = current_setting('app.current_company_id', true)
);

CREATE POLICY products_update_policy
ON products
FOR UPDATE
USING (
  current_setting('app.is_super_admin', true) = 'true'
  OR company_id::text = current_setting('app.current_company_id', true)
)
WITH CHECK (
  current_setting('app.is_super_admin', true) = 'true'
  OR company_id::text = current_setting('app.current_company_id', true)
);

CREATE POLICY products_delete_policy
ON products
FOR DELETE
USING (
  current_setting('app.is_super_admin', true) = 'true'
  OR company_id::text = current_setting('app.current_company_id', true)
);

28. Tablas que deben tener RLS

Aplicar RLS a tablas como:

companies
users
roles
role_permissions
company_modules
audit_logs

Y futuras tablas de negocio:

customers
products
warehouses
product_stocks
orders
order_items
employees
payroll
invoices

29. Reglas especiales de RLS por tabla

companies

Reglas:

users

Reglas:

roles

Reglas:

permissions

Reglas:

modules

Reglas:

company_modules

Reglas:

refresh_tokens

Reglas:

password_reset_tokens

Reglas:


30. Migraciones RLS

Crear migraciones de Drizzle o SQL para:

ALTER TABLE ... ENABLE ROW LEVEL SECURITY
ALTER TABLE ... FORCE ROW LEVEL SECURITY
CREATE POLICY ...
DROP POLICY ...

Las migraciones deben incluir:


31. Pruebas de RLS

Crear pruebas o ejemplos para verificar:


32. Módulo Auth

Crear estas rutas backend:

POST /auth/login
POST /auth/logout
POST /auth/refresh
POST /auth/forgot-password
POST /auth/reset-password
GET /auth/me

Login

Reglas:

El access token debe incluir:

user_id
company_id
role
permissions
is_super_admin

Logout

Reglas:

Refresh token

Reglas:

Auth me

Reglas:


33. Recuperación de contraseña

Crear flujo:

Usuario solicita recuperación
↓
Escribe email
↓
Si existe, crear token seguro
↓
Guardar hash del token
↓
Enviar email con enlace
↓
Usuario entra a /reset-password?token=...
↓
Escribe nueva contraseña
↓
Backend valida token
↓
Backend cambia contraseña
↓
Backend marca token como usado
↓
Backend revoca refresh tokens anteriores

Reglas:

Mensaje recomendado:

Si el correo existe en nuestra plataforma, enviaremos instrucciones para recuperar la contraseña.

34. Módulo Companies

Crear rutas:

POST /companies
GET /companies
GET /companies/:id
PATCH /companies/:id
DELETE /companies/:id

Reglas:


35. Módulo Users

Crear rutas:

POST /users
GET /users
GET /users/:id
PATCH /users/:id
DELETE /users/:id

Reglas:


36. Módulo Roles

Crear rutas:

POST /roles
GET /roles
GET /roles/:id
PATCH /roles/:id
DELETE /roles/:id

Reglas:


37. Módulo Permissions

Crear rutas:

GET /permissions
POST /roles/:id/permissions
DELETE /roles/:id/permissions/:permissionId

Reglas:


38. Guards, decorators y helpers

Crear:

@CurrentUser()
@RequireRoles()
@RequirePermissions()
JwtAuthGuard
RolesGuard
PermissionsGuard
TenantGuard

CurrentUser

Debe permitir obtener:

id
companyId
email
role
permissions
isSuperAdmin

JwtAuthGuard

Debe:

RolesGuard

Debe:

PermissionsGuard

Debe:

TenantGuard

Debe:


39. Seguridad obligatoria

Implementar:


40. Variables de entorno backend

Crear archivo .env.example para backend:

NODE_ENV=development
PORT=4000

DATABASE_URL=postgresql://postgres:postgres@localhost:5432/labxus

JWT_ACCESS_SECRET=change-me-access-secret
JWT_REFRESH_SECRET=change-me-refresh-secret
JWT_ACCESS_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=7d

PASSWORD_RESET_EXPIRES_IN=30m

FRONTEND_URL=http://localhost:3000

SMTP_HOST=
SMTP_PORT=
SMTP_USER=
SMTP_PASS=
SMTP_FROM=

BCRYPT_SALT_ROUNDS=10

41. Variables de entorno frontend

Crear archivo .env.example para frontend:

NEXT_PUBLIC_API_URL=http://localhost:4000

42. Frontend Next.js

Crear estas rutas públicas:

/login
/forgot-password
/reset-password

Crear estas rutas privadas:

/dashboard
/dashboard/companies
/dashboard/companies/new
/dashboard/users
/dashboard/users/new
/dashboard/roles
/dashboard/roles/new
/dashboard/settings

Reglas frontend:


43. Componentes frontend

Crear:

LoginForm
ForgotPasswordForm
ResetPasswordForm
CreateCompanyForm
CreateUserForm
CreateRoleForm
Sidebar
DashboardLayout
ProtectedRoute
PermissionGate
UserMenu
DataTable
EmptyState
LoadingState
AccessDenied
ThemeToggle

44. PermissionGate

Crear componente para mostrar contenido según permisos.

Ejemplo esperado:

<PermissionGate permission="users.create">
  <Button>Crear usuario</Button>
</PermissionGate>

Reglas:


45. Sidebar dinámico

El menú debe renderizarse según permisos.

Ejemplo:

Dashboard
Empresas
Usuarios
Roles
Configuración

Reglas:


46. Diseño visual con HSL

Configurar Tailwind CSS v4.1 y CSS variables con HSL.

Usar marca visual de Labxus:

Ejemplo base:

:root {
  --background: 240 20% 99%;
  --foreground: 240 10% 8%;

  --card: 0 0% 100%;
  --card-foreground: 240 10% 8%;

  --primary: 258 90% 58%;
  --primary-foreground: 0 0% 100%;

  --secondary: 221 83% 53%;
  --secondary-foreground: 0 0% 100%;

  --muted: 240 10% 96%;
  --muted-foreground: 240 5% 45%;

  --border: 240 8% 90%;
  --input: 240 8% 90%;
  --ring: 258 90% 58%;

  --destructive: 0 84% 60%;
  --destructive-foreground: 0 0% 100%;

  --radius: 0.75rem;
}

.dark {
  --background: 240 10% 4%;
  --foreground: 0 0% 98%;

  --card: 240 10% 7%;
  --card-foreground: 0 0% 98%;

  --primary: 258 90% 65%;
  --primary-foreground: 0 0% 100%;

  --secondary: 221 83% 60%;
  --secondary-foreground: 0 0% 100%;

  --muted: 240 8% 14%;
  --muted-foreground: 240 5% 65%;

  --border: 240 8% 18%;
  --input: 240 8% 18%;
  --ring: 258 90% 65%;

  --destructive: 0 72% 51%;
  --destructive-foreground: 0 0% 100%;
}

47. Backend endpoints esperados

Auth

POST /auth/login
POST /auth/logout
POST /auth/refresh
POST /auth/forgot-password
POST /auth/reset-password
GET /auth/me

Companies

POST /companies
GET /companies
GET /companies/:id
PATCH /companies/:id
DELETE /companies/:id

Users

POST /users
GET /users
GET /users/:id
PATCH /users/:id
DELETE /users/:id

Roles

POST /roles
GET /roles
GET /roles/:id
PATCH /roles/:id
DELETE /roles/:id

Permissions

GET /permissions
POST /roles/:id/permissions
DELETE /roles/:id/permissions/:permissionId

48. Seeds iniciales

Crear seed inicial para:

Usuario SUPER_ADMIN

Debe crear un usuario super administrador.

Ejemplo:

name: David Labxus Admin
email: admin@labxus.com
password: cambiar123*
is_super_admin: true
company_id: null
status: active

La contraseña debe hashearse con bcrypt.

Roles base

Crear:

SUPER_ADMIN
COMPANY_ADMIN
MANAGER
EMPLOYEE
READ_ONLY

Permisos base

Crear todos los permisos definidos anteriormente.

Módulos base

Crear:

inventory
orders
invoicing
employees
payroll
reports

49. Testing esperado

Crear pruebas o ejemplos para validar:

Auth

Empresas

Usuarios

Roles y permisos

RLS


50. Documentación esperada

Entregar documentación con:


51. Comandos esperados

Usar comandos con pnpm.

Ejemplos:

pnpm install
pnpm dev
pnpm build
pnpm lint
pnpm typecheck
pnpm --filter @labxus/api db:generate
pnpm --filter @labxus/api db:migrate
pnpm --filter @labxus/api db:seed
pnpm --filter @labxus/api start:dev
pnpm --filter @labxus/web dev

52. Fases de implementación

No generes todo de golpe de forma desordenada.

Primero entrega el diseño técnico completo:

  1. Arquitectura general.
  2. Estructura de carpetas.
  3. Modelo de base de datos.
  4. Relaciones entre tablas.
  5. Flujo de autenticación.
  6. Flujo multitenant.
  7. Estrategia RLS.
  8. Roles y permisos.
  9. Flujo frontend.
  10. Fases de implementación.

Después empieza la implementación por fases.


Fase 1

Crear estructura base del monorepo:

apps/web
apps/api
packages/shared

Configurar:


Fase 2

Crear base de datos:


Fase 3

Crear autenticación:


Fase 4

Crear recuperación de contraseña:


Fase 5

Crear empresas:


Fase 6

Crear usuarios:


Fase 7

Crear roles y permisos:


Fase 8

Implementar RLS:


Fase 9

Crear frontend:


Fase 10

Testing y documentación:


53. Resultado esperado

Quiero que entregues:

  1. Diseño técnico completo.
  2. Estructura completa de carpetas.
  3. Modelo de base de datos.
  4. Relaciones entre tablas.
  5. Schemas Drizzle.
  6. Migraciones Drizzle.
  7. Migraciones SQL para RLS.
  8. Políticas RLS por tabla.
  9. Seeds iniciales.
  10. Schemas Zod compartidos.
  11. Types compartidos.
  12. Constantes compartidas de roles.
  13. Constantes compartidas de permisos.
  14. Código backend NestJS por módulos.
  15. Código frontend Next.js por páginas y componentes.
  16. Guards de autenticación.
  17. Guards de roles.
  18. Guards de permisos.
  19. TenantGuard.
  20. Decoradores personalizados.
  21. Helper para configurar contexto RLS.
  22. Formularios con Shadcn UI.
  23. Validaciones con React Hook Form + Zod.
  24. Ejemplos de peticiones API.
  25. Documentación para ejecutar el proyecto.
  26. Pruebas para seguridad multitenant.
  27. Explicación sobre connection pooling con RLS.
  28. Explicación de cómo agregar módulos futuros.

54. Restricciones importantes

No usar:

Sí usar:


55. Regla final muy importante

No basta con ocultar botones en el frontend.

La seguridad debe existir en 3 niveles:

1. Frontend:
   Ocultar opciones según permisos.

2. Backend:
   Proteger rutas con guards de auth, roles, permisos y tenant.

3. Base de datos:
   Proteger filas con PostgreSQL Row Level Security.

El sistema debe estar preparado para crecer después con módulos como:

inventario
órdenes
facturación electrónica
empleados
nómina
reportes
clientes
bodegas
productos
kardex