@mihaiku/auth-module
v0.1.1
Published
Embeddable user management & auth module (JWT + refresh, RBAC, OAuth) — microservice-ready
Maintainers
Readme
auth-module
Módulo embebible de user management & autenticación para proyectos personales. Diseñado library-first, microservice-ready: la lógica vive en un núcleo agnóstico de transporte; hoy lo importas como paquete, mañana le pones Express delante y es un servicio.
Decisiones
- Stack: TypeScript (ESM) · Express · Drizzle ORM · PostgreSQL
- Tokens: JWT (access, stateless) + refresh tokens con rotación y detección de reuso
- Hash de contraseñas: argon2id (
@node-rs/argon2) - Hash de tokens en DB: SHA-256 (
node:crypto) - Cifrado de tokens OAuth: AES-256-GCM (
node:crypto) - MFA: fuera de la v1
- Dependencias externas: mínimas (ver
DEPENDENCIES.mden la raíz del proyecto)
Estructura
src/
├── core/ # lógica pura: puertos (interfaces) y tipos de dominio. No importa db/ ni http/.
├── db/ # schema Drizzle + cliente + (repositorios que implementan los puertos del core)
├── http/ # router y middleware Express (la capa que lo vuelve microservicio)
├── crypto/ # helpers sobre node:crypto (tokens, hash, AES-GCM)
└── index.ts # superficie pública del paqueteRegla de dependencias: core/ no importa de db/ ni de http/. db/ y http/
dependen de core/. Respetarla es lo que mantiene el módulo reutilizable.
Puesta en marcha
pnpm install
cp .env.example .env # rellena DATABASE_URL, claves JWT y ENCRYPTION_KEY
pnpm db:generate # genera la migración desde el schema
pnpm db:migrate # la aplica
pnpm dev # desarrolloProbarlo en local (Swagger UI + endpoints reales)
Hay un ejemplo ejecutable en examples/server.ts que monta el
router en un Express real con Swagger UI, contra tu Postgres.
# 1. Levanta un Postgres y pon su DATABASE_URL en .env
# 2. Genera claves JWT + ENCRYPTION_KEY y pégalas en .env:
pnpm keygen
# 3. Crea y aplica el esquema:
pnpm db:generate
pnpm db:migrate
# 4. Arranca el servidor de ejemplo:
pnpm example:serverLuego abre http://localhost:3000/docs. El ejemplo siembra una aplicación de prueba;
usa el header x-client-id: demo-client en las llamadas (register → login → /me).
El spec crudo está en http://localhost:3000/openapi.json.
Solo quieres ver la spec sin base de datos: importa
openApiDocument(o vuelca el JSON) y súbelo a editor.swagger.io.
Uso como dependencia
El core y la capa HTTP se importan por separado (Express es peerDependency opcional):
import express from "express";
import { createRepositories, createDb, createArgon2Hasher } from "auth-module";
import { createAuthRouter, errorHandler } from "auth-module/express";
const app = express();
app.use("/auth", createAuthRouter({ deps })); // el Express lo aportas tú
app.use(errorHandler);Importar auth-module (el core) nunca carga Express. Solo auth-module/express lo requiere.
Email (mailer y plantillas)
El módulo envía dos correos transaccionales: verificación de email (email_verification)
y restablecimiento de contraseña (password_reset). Eliges el transporte al construir
el mailer y lo inyectas como dependencia (deps.mailer):
import { createSmtpMailer, createConsoleMailer } from "auth-module";
// Producción: SMTP real
const mailer = createSmtpMailer({
from: "Auth <[email protected]>",
host: "smtp.tuproveedor.com",
port: 587, // 465 → SMTPS; 587 → STARTTLS
auth: { user: "...", pass: "..." },
});
// Desarrollo: no envía nada, escribe el email por consola
const mailer = createConsoleMailer();Personalizar las plantillas desde tu proyecto
Las plantillas por defecto son las de la librería. Para usar tu propio asunto/cuerpo,
construye un registry con createTemplateRegistry y pásalo al mailer en templates.
Solo sobrescribes lo que quieras; las plantillas que no toques usan el default:
import { createTemplateRegistry, createSmtpMailer } from "auth-module";
const templates = createTemplateRegistry({
// `data` va tipado por plantilla:
// password_reset → { token: string; resetUrl?: string }
// email_verification → { token: string; verifyUrl?: string }
password_reset: (data) => ({
subject: "Recupera tu acceso a MiApp",
text: `Tu código de recuperación es: ${data.token}`,
html: `<h1>MiApp</h1>
<p><a href="${data.resetUrl}?token=${data.token}">Cambiar contraseña</a></p>`,
}),
// email_verification no está aquí → se usa la plantilla por defecto.
});
const mailer = createSmtpMailer(smtpConfig, { templates });
// El mismo registry sirve para el mailer de consola:
const devMailer = createConsoleMailer({ templates });Cada renderer devuelve { subject, text, html }. El tipado es estricto: si el nombre de
plantilla o la forma de data no encajan, es error de compilación. Reutiliza el mismo
registry entre varios mailers (p. ej. SMTP en prod y consola en dev).
Escapado HTML: si construyes el
htmla mano, recuerda escapar los valores que interpolas. Las plantillas por defecto ya lo hacen; en las tuyas es tu responsabilidad.
OpenAPI / Swagger
El documento OpenAPI 3.1 se genera desde los mismos esquemas Zod que validan las
rutas, así que no se desincroniza. Vive en el subpath auth-module/express:
import { buildOpenApiDocument, openApiDocument } from "auth-module/express";
// documento por defecto, o personalizado:
const doc = buildOpenApiDocument({
basePath: "/auth", // prefijo donde montas el router
servers: [{ url: "https://api.example.com" }],
});Servir Swagger UI desde tu app (la dep swagger-ui-express la pones tú, no el paquete):
import swaggerUi from "swagger-ui-express";
app.use("/docs", swaggerUi.serve, swaggerUi.setup(doc)); // UI interactiva en /docs
app.get("/openapi.json", (_req, res) => res.json(doc)); // spec crudoTodas las rutas del router quedan documentadas (register, login, refresh, logout,
verify-email, password-reset, me, audit-logs y OAuth), con el esquema de seguridad
Bearer JWT y la cabecera multi-tenant x-client-id.
Scripts
| Script | Qué hace |
|---|---|
| npm run build | Compila el paquete (tsup → ESM + CJS + tipos) |
| npm run typecheck | Chequeo de tipos sin emitir |
| npm run lint / npm run format | Biome |
| npm test | Vitest |
| npm run db:generate / db:migrate / db:studio | Drizzle Kit |
Las versiones de dependencias en
package.jsonson un punto de partida; ejecutanpm outdatedy actualiza a las últimas estables al iniciar.
