@aranzatech/aranza-auth 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-07-03
10
+
11
+ First public release.
12
+
13
+ ### Added
14
+
15
+ - **`AuthModule.forRoot` / `forRootAsync`** — módulo dinámico global con configuración JWT.
16
+ - **Endpoints REST** bajo `/auth`:
17
+ - `POST /auth/register` — alta de cuenta
18
+ - `POST /auth/login` — login con access + refresh token
19
+ - `POST /auth/refresh` — renovación de tokens
20
+ - `POST /auth/logout` — invalidar refresh token (Bearer)
21
+ - `GET /auth/me` — usuario autenticado (Bearer)
22
+ - `POST /auth/verify-email` — verificación de email (opt-in)
23
+ - `POST /auth/forgot-password` — solicitar reset (opt-in)
24
+ - `POST /auth/reset-password` — nueva contraseña con token (opt-in)
25
+ - **`AuthService`**, **`TokenService`**, **`JwtStrategy`**, **`JwtAuthGuard`**, decorador **`@CurrentUser()`**.
26
+ - **Feature flags** (`features`) — todo desactivado por defecto, ideal para POCs:
27
+ - `emailVerification` — bloquea login hasta verificar email
28
+ - `passwordReset` — habilita forgot/reset password
29
+ - `refreshTokenRotation` — rota hash del refresh token en DB (default: `true`)
30
+ - **`AuthHooks`** — extiende JWT payload, `/auth/me`, lifecycle y envío de emails.
31
+ - **`DefaultAuthHooks`** — implementación mínima incluida.
32
+ - **Adapter MongoDB** (`@aranzatech/aranza-auth/mongo`):
33
+ - `BaseAuthAccountSchema` — schema base extensible
34
+ - `MongoAuthRepository` — implementación de `IAuthRepository`
35
+ - `MongoAuthModule.forFeature()` — registro de schema + repository
36
+ - Tokens de verificación/reset hasheados (SHA-256) con TTL configurable.
37
+ - Build dual ESM/CJS con types (`tsup`), tests (`vitest`), CI GitHub Actions.
38
+
39
+ [Unreleased]: https://github.com/aapa96/aranza-auth/compare/v0.1.0...HEAD
40
+ [0.1.0]: https://github.com/aapa96/aranza-auth/releases/tag/v0.1.0
package/README.md ADDED
@@ -0,0 +1,468 @@
1
+ # @aranzatech/aranza-auth
2
+
3
+ Módulo de autenticación **extensible** para NestJS. JWT, refresh tokens, register/login y adapter MongoDB — sin acoplar tu dominio (org, roles, users, etc.).
4
+
5
+ Ideal para reutilizar auth en todos tus proyectos AranzaTech: instalas, configuras, extiendes el modelo y listo.
6
+
7
+ ---
8
+
9
+ ## Tabla de contenidos
10
+
11
+ - [Instalación](#instalación)
12
+ - [Inicio rápido (5 minutos)](#inicio-rápido-5-minutos)
13
+ - [Variables de entorno](#variables-de-entorno)
14
+ - [Configuración del módulo](#configuración-del-módulo)
15
+ - [Feature flags](#feature-flags)
16
+ - [Endpoints](#endpoints)
17
+ - [Proteger rutas propias](#proteger-rutas-propias)
18
+ - [Extender con AuthHooks](#extender-con-authhooks)
19
+ - [Extender el schema MongoDB](#extender-el-schema-mongodb)
20
+ - [Flujos opcionales (email y password)](#flujos-opcionales-email-y-password)
21
+ - [Exports públicos](#exports-públicos)
22
+ - [Requisitos del proyecto consumidor](#requisitos-del-proyecto-consumidor)
23
+ - [Limitaciones conocidas (v0.1.0)](#limitaciones-conocidas-v010)
24
+ - [Roadmap](#roadmap)
25
+ - [Licencia](#licencia)
26
+
27
+ ---
28
+
29
+ ## Instalación
30
+
31
+ ```bash
32
+ npm install @aranzatech/aranza-auth
33
+ ```
34
+
35
+ **Peer dependencies** (instalar en tu app Nest):
36
+
37
+ ```bash
38
+ npm install @nestjs/common @nestjs/core @nestjs/jwt @nestjs/passport \
39
+ @nestjs/mongoose mongoose passport passport-jwt bcryptjs \
40
+ class-validator class-transformer reflect-metadata
41
+ ```
42
+
43
+ > NestJS **11+** recomendado.
44
+
45
+ ---
46
+
47
+ ## Inicio rápido (5 minutos)
48
+
49
+ ### 1. Variables de entorno
50
+
51
+ ```env
52
+ MONGODB_URI=mongodb://localhost:27017/myapp
53
+ JWT_SECRET=your-access-secret-min-32-chars
54
+ JWT_REFRESH_SECRET=your-refresh-secret-min-32-chars
55
+ ```
56
+
57
+ ### 2. Conectar el módulo
58
+
59
+ ```typescript
60
+ // app.module.ts
61
+ import { Module } from "@nestjs/common";
62
+ import { ConfigModule, ConfigService } from "@nestjs/config";
63
+ import { MongooseModule } from "@nestjs/mongoose";
64
+ import { AuthModule } from "@aranzatech/aranza-auth";
65
+ import { MongoAuthModule } from "@aranzatech/aranza-auth/mongo";
66
+
67
+ @Module({
68
+ imports: [
69
+ ConfigModule.forRoot({ isGlobal: true }),
70
+ MongooseModule.forRoot(process.env.MONGODB_URI!),
71
+ MongoAuthModule.forFeature(),
72
+ AuthModule.forRootAsync({
73
+ imports: [ConfigModule],
74
+ inject: [ConfigService],
75
+ useFactory: (config: ConfigService) => ({
76
+ secret: config.getOrThrow("JWT_SECRET"),
77
+ refreshSecret: config.getOrThrow("JWT_REFRESH_SECRET"),
78
+ expiresIn: "1h",
79
+ refreshExpiresIn: "7d",
80
+ identifierField: "email",
81
+ }),
82
+ }),
83
+ ],
84
+ })
85
+ export class AppModule {}
86
+ ```
87
+
88
+ ### 3. Habilitar validación global (requerido)
89
+
90
+ Los DTOs usan `class-validator`. Activa el pipe global en `main.ts`:
91
+
92
+ ```typescript
93
+ import { ValidationPipe } from "@nestjs/common";
94
+
95
+ app.useGlobalPipes(
96
+ new ValidationPipe({ whitelist: true, transform: true }),
97
+ );
98
+ ```
99
+
100
+ ### 4. Probar
101
+
102
+ ```bash
103
+ # Register
104
+ curl -X POST http://localhost:3000/auth/register \
105
+ -H "Content-Type: application/json" \
106
+ -d '{"email":"user@example.com","password":"SecurePass123!"}'
107
+
108
+ # Login
109
+ curl -X POST http://localhost:3000/auth/login \
110
+ -H "Content-Type: application/json" \
111
+ -d '{"email":"user@example.com","password":"SecurePass123!"}'
112
+
113
+ # Me (usa el accessToken del login)
114
+ curl http://localhost:3000/auth/me \
115
+ -H "Authorization: Bearer <accessToken>"
116
+ ```
117
+
118
+ Con eso ya tienes auth funcional para un POC.
119
+
120
+ ---
121
+
122
+ ## Variables de entorno
123
+
124
+ | Variable | Requerida | Descripción |
125
+ |----------|-----------|-------------|
126
+ | `MONGODB_URI` | Sí | Connection string de MongoDB |
127
+ | `JWT_SECRET` | Sí | Secret para access tokens |
128
+ | `JWT_REFRESH_SECRET` | Sí | Secret para refresh tokens (distinto al anterior) |
129
+ | `FRONTEND_URL` | No | Base URL para links en emails (verify/reset) |
130
+
131
+ ---
132
+
133
+ ## Configuración del módulo
134
+
135
+ `AuthModule.forRootAsync()` acepta un objeto `AuthModuleOptions`:
136
+
137
+ ```typescript
138
+ AuthModule.forRootAsync({
139
+ imports: [ConfigModule],
140
+ inject: [ConfigService],
141
+ useFactory: (config: ConfigService) => ({
142
+ // ── JWT (requerido) ──────────────────────────────────
143
+ secret: config.getOrThrow("JWT_SECRET"),
144
+ refreshSecret: config.getOrThrow("JWT_REFRESH_SECRET"),
145
+ expiresIn: "1h", // access token (default: "1h")
146
+ refreshExpiresIn: "7d", // refresh token (default: "7d")
147
+
148
+ // ── Identificador de login ───────────────────────────
149
+ identifierField: "email", // "email" | "username" (default: "email")
150
+
151
+ // ── Features opcionales ──────────────────────────────
152
+ features: {
153
+ emailVerification: false, // default: false
154
+ passwordReset: false, // default: false
155
+ refreshTokenRotation: true, // default: true
156
+ },
157
+
158
+ // ── TTL de tokens de email/reset ─────────────────────
159
+ emailVerificationTokenTtlMs: 24 * 60 * 60 * 1000, // 24h
160
+ passwordResetTokenTtlMs: 15 * 60 * 1000, // 15min
161
+
162
+ // ── Hooks personalizados ─────────────────────────────
163
+ hooks: AppAuthHooks, // default: DefaultAuthHooks
164
+ }),
165
+ }),
166
+ ```
167
+
168
+ ### Orden de imports
169
+
170
+ `MongoAuthModule.forFeature()` debe importarse **junto** con `AuthModule`. El orden recomendado:
171
+
172
+ ```typescript
173
+ imports: [
174
+ MongooseModule.forRoot(...),
175
+ MongoAuthModule.forFeature(...), // provee AUTH_REPOSITORY
176
+ AuthModule.forRootAsync(...), // consume AUTH_REPOSITORY
177
+ ]
178
+ ```
179
+
180
+ ---
181
+
182
+ ## Feature flags
183
+
184
+ Todo está **desactivado por defecto**. Sin declarar `features`, obtienes el mínimo para POCs.
185
+
186
+ | Flag | Default | Qué hace |
187
+ |------|---------|----------|
188
+ | `emailVerification` | `false` | Envía email al register, bloquea login hasta verificar |
189
+ | `passwordReset` | `false` | Habilita `forgot-password` y `reset-password` |
190
+ | `refreshTokenRotation` | `true` | Guarda hash del refresh token en DB y lo rota |
191
+
192
+ ### Modo POC (solo login/register)
193
+
194
+ ```typescript
195
+ // No declares features — o déjalo vacío:
196
+ AuthModule.forRootAsync({
197
+ useFactory: () => ({
198
+ secret: "...",
199
+ refreshSecret: "...",
200
+ }),
201
+ }),
202
+ ```
203
+
204
+ - Register crea cuenta con `emailVerified: true` → login inmediato.
205
+ - Endpoints `verify-email`, `forgot-password`, `reset-password` responden **404**.
206
+
207
+ ### Modo producción
208
+
209
+ ```typescript
210
+ features: {
211
+ emailVerification: true,
212
+ passwordReset: true,
213
+ refreshTokenRotation: true,
214
+ },
215
+ hooks: AppAuthHooks, // debe implementar sendEmail
216
+ ```
217
+
218
+ > Si activas `emailVerification` o `passwordReset`, **debes** implementar `AuthHooks.sendEmail`. Si no, register/forgot fallará con un error claro.
219
+
220
+ ---
221
+
222
+ ## Endpoints
223
+
224
+ | Método | Ruta | Auth | Feature | Body | Respuesta |
225
+ |--------|------|------|---------|------|-----------|
226
+ | `POST` | `/auth/register` | — | — | `{ email?, username?, password }` | `{ registered: true }` |
227
+ | `POST` | `/auth/login` | — | — | `{ email?, username?, password }` | `{ accessToken, refreshToken }` |
228
+ | `POST` | `/auth/refresh` | — | — | `{ refreshToken }` | `{ accessToken, refreshToken }` |
229
+ | `POST` | `/auth/logout` | Bearer | — | — | `{ loggedOut: true }` |
230
+ | `GET` | `/auth/me` | Bearer | — | — | objeto enriquecido via hooks |
231
+ | `POST` | `/auth/verify-email` | — | `emailVerification` | `{ token }` | `{ verified: true }` |
232
+ | `POST` | `/auth/forgot-password` | — | `passwordReset` | `{ email }` | `{ sent: true }` |
233
+ | `POST` | `/auth/reset-password` | — | `passwordReset` | `{ token, newPassword }` | `{ reset: true }` |
234
+
235
+ ### Errores comunes
236
+
237
+ | Código | Mensaje | Causa |
238
+ |--------|---------|-------|
239
+ | `401` | `Invalid credentials` | Email/username o password incorrectos |
240
+ | `401` | `EMAIL_NOT_VERIFIED` | Feature `emailVerification` activa y email sin verificar |
241
+ | `401` | `ACCOUNT_DISABLED` | Cuenta desactivada |
242
+ | `401` | `Invalid refresh token` | Refresh expirado, revocado o inválido |
243
+ | `404` | — | Feature desactivada (endpoint no disponible) |
244
+ | `400` | `TOKEN_INVALID_OR_EXPIRED` | Token de verify/reset inválido o expirado |
245
+
246
+ ---
247
+
248
+ ## Proteger rutas propias
249
+
250
+ Usa el guard y el decorador exportados por la lib:
251
+
252
+ ```typescript
253
+ import { Controller, Get, UseGuards } from "@nestjs/common";
254
+ import {
255
+ JwtAuthGuard,
256
+ CurrentUser,
257
+ type AuthJwtPayload,
258
+ } from "@aranzatech/aranza-auth";
259
+
260
+ @Controller("projects")
261
+ export class ProjectsController {
262
+ @Get()
263
+ @UseGuards(JwtAuthGuard)
264
+ list(@CurrentUser() user: AuthJwtPayload) {
265
+ // user.sub → ID de la cuenta auth
266
+ // user.email, user.orgId, etc. → lo que agregues en buildJwtPayload
267
+ return { ownerId: user.sub };
268
+ }
269
+ }
270
+ ```
271
+
272
+ También puedes registrar `JwtAuthGuard` como guard global:
273
+
274
+ ```typescript
275
+ { provide: APP_GUARD, useClass: JwtAuthGuard }
276
+ ```
277
+
278
+ ---
279
+
280
+ ## Extender con AuthHooks
281
+
282
+ La lib maneja auth genérico. Tu dominio (org, roles, users) va en **hooks**:
283
+
284
+ ```typescript
285
+ // app-auth.hooks.ts
286
+ import { Injectable } from "@nestjs/common";
287
+ import type { AuthHooks, BaseAuthAccount } from "@aranzatech/aranza-auth";
288
+
289
+ interface MyAuthAccount extends BaseAuthAccount {
290
+ orgId: string;
291
+ roleId: string;
292
+ }
293
+
294
+ @Injectable()
295
+ export class AppAuthHooks implements AuthHooks<MyAuthAccount> {
296
+ constructor(private readonly orgService: OrgService) {}
297
+
298
+ /** Campos extra en el JWT */
299
+ async buildJwtPayload(account: MyAuthAccount) {
300
+ return {
301
+ orgId: account.orgId,
302
+ roleId: account.roleId,
303
+ };
304
+ }
305
+
306
+ /** Respuesta de GET /auth/me */
307
+ async enrichMe(account: MyAuthAccount) {
308
+ const org = await this.orgService.findById(account.orgId);
309
+ return {
310
+ id: account.id,
311
+ email: account.email,
312
+ org: { id: org.id, name: org.name },
313
+ };
314
+ }
315
+
316
+ /** Validaciones antes de crear cuenta */
317
+ async onBeforeRegister(input) {
318
+ // ej: verificar invitación, orgId, etc.
319
+ }
320
+
321
+ /** Envío de emails (requerido si activas emailVerification o passwordReset) */
322
+ async sendEmail(type: "verify" | "reset", to: string, token: string) {
323
+ const base = process.env.FRONTEND_URL ?? "http://localhost:3001";
324
+ const path = type === "verify" ? "verify-email" : "reset-password";
325
+ const url = `${base}/auth/${path}?token=${token}`;
326
+ // await this.mailer.send({ to, subject: "...", html: url });
327
+ }
328
+ }
329
+ ```
330
+
331
+ Registra los hooks en la config:
332
+
333
+ ```typescript
334
+ AuthModule.forRootAsync({
335
+ useFactory: () => ({
336
+ secret: "...",
337
+ refreshSecret: "...",
338
+ hooks: AppAuthHooks,
339
+ }),
340
+ }),
341
+ ```
342
+
343
+ ### Métodos disponibles en AuthHooks
344
+
345
+ | Método | Cuándo se ejecuta | Requerido |
346
+ |--------|-------------------|-----------|
347
+ | `buildJwtPayload` | Login, refresh | Sí |
348
+ | `enrichMe` | GET /auth/me | No (usa default) |
349
+ | `onBeforeRegister` | Antes de crear cuenta | No |
350
+ | `onAfterRegister` | Después de crear cuenta | No |
351
+ | `onAfterLogin` | Después de login exitoso | No |
352
+ | `sendEmail` | Register (verify) o forgot (reset) | Sí si features de email activas |
353
+
354
+ ---
355
+
356
+ ## Extender el schema MongoDB
357
+
358
+ El schema base incluye: `email`, `username`, `passwordHash`, `refreshTokenHash`, `emailVerified`, `disabled` y campos de tokens.
359
+
360
+ ### Agregar campos de dominio
361
+
362
+ ```typescript
363
+ import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
364
+ import { Types } from "mongoose";
365
+ import { baseAuthAccountSchema } from "@aranzatech/aranza-auth/mongo";
366
+
367
+ @Schema()
368
+ export class AuthAccount {
369
+ @Prop({ type: Types.ObjectId, ref: "Organization", required: true })
370
+ orgId!: Types.ObjectId;
371
+
372
+ @Prop({ type: Types.ObjectId, ref: "Role", required: true })
373
+ roleId!: Types.ObjectId;
374
+ }
375
+
376
+ export const AuthAccountSchema = SchemaFactory.createForClass(AuthAccount);
377
+ AuthAccountSchema.add(baseAuthAccountSchema);
378
+ ```
379
+
380
+ ### Registrar schema extendido
381
+
382
+ ```typescript
383
+ import { AuthAccount, AuthAccountSchema } from "./schemas/auth-account.schema";
384
+
385
+ MongoAuthModule.forFeature({
386
+ name: AuthAccount.name,
387
+ schema: AuthAccountSchema,
388
+ identifierField: "username", // si login es por username
389
+ }),
390
+ ```
391
+
392
+ > `identifierField` en `MongoAuthModule.forFeature()` debe coincidir con el de `AuthModule`.
393
+
394
+ ---
395
+
396
+ ## Flujos opcionales (email y password)
397
+
398
+ ### Verificación de email
399
+
400
+ 1. Activa `features.emailVerification: true`
401
+ 2. Implementa `sendEmail` en hooks
402
+ 3. Register envía email con token → usuario visita link → `POST /auth/verify-email` con `{ token }`
403
+ 4. Login bloqueado hasta `emailVerified: true`
404
+
405
+ Si usas `identifierField: "username"`, el register **debe incluir `email`** además del username.
406
+
407
+ ### Forgot / Reset password
408
+
409
+ 1. Activa `features.passwordReset: true`
410
+ 2. Implementa `sendEmail` en hooks
411
+ 3. `POST /auth/forgot-password` con `{ email }` → siempre responde `{ sent: true }` (no revela si el email existe)
412
+ 4. Usuario recibe link → `POST /auth/reset-password` con `{ token, newPassword }`
413
+ 5. Reset invalida refresh tokens activos
414
+
415
+ ---
416
+
417
+ ## Exports públicos
418
+
419
+ | Import | Contenido |
420
+ |--------|-----------|
421
+ | `@aranzatech/aranza-auth` | `AuthModule`, `AuthService`, guards, DTOs, interfaces, tokens |
422
+ | `@aranzatech/aranza-auth/mongo` | `MongoAuthModule`, `MongoAuthRepository`, schemas |
423
+
424
+ ### Tokens de inyección
425
+
426
+ ```typescript
427
+ import { AUTH_MODULE_OPTIONS, AUTH_HOOKS, AUTH_REPOSITORY } from "@aranzatech/aranza-auth";
428
+ ```
429
+
430
+ Útiles si necesitas acceder a config o reemplazar el repository manualmente.
431
+
432
+ ---
433
+
434
+ ## Requisitos del proyecto consumidor
435
+
436
+ - [ ] NestJS 11+
437
+ - [ ] `ValidationPipe` global activo
438
+ - [ ] `reflect-metadata` importado al inicio de `main.ts`
439
+ - [ ] MongoDB conectado via `@nestjs/mongoose`
440
+ - [ ] `MongoAuthModule.forFeature()` importado antes o junto a `AuthModule`
441
+ - [ ] Secrets JWT distintos para access y refresh
442
+ - [ ] Si usas features de email: implementar `AuthHooks.sendEmail`
443
+
444
+ ---
445
+
446
+ ## Limitaciones conocidas (v0.1.0)
447
+
448
+ | Limitación | Workaround / versión futura |
449
+ |------------|----------------------------|
450
+ | Hooks se instancian con `new HooksClass()` sin DI de Nest | Registrar servicios manualmente o v0.2.0 con DI |
451
+ | Ruta fija `/auth` (routePrefix en config aún no aplicado) | Usar global prefix de Nest o v0.2.0 |
452
+ | Solo adapter MongoDB | Prisma en v0.3.0 |
453
+ | Sin OAuth (Google, GitHub, etc.) | v0.2.0 |
454
+ | Sin `change-password` autenticado | v0.2.0 |
455
+
456
+ ---
457
+
458
+ ## Roadmap
459
+
460
+ - [ ] **0.2.0** — OAuth (Google, GitHub), `change-password`, hooks con DI
461
+ - [ ] **0.3.0** — Adapter Prisma
462
+ - [ ] **0.4.0** — Migración de AranzaFlow como caso de referencia
463
+
464
+ ---
465
+
466
+ ## Licencia
467
+
468
+ MIT © [AranzaTech](https://github.com/aapa96)
@@ -0,0 +1,90 @@
1
+ interface BaseAuthAccount {
2
+ id: string;
3
+ email?: string;
4
+ username?: string;
5
+ emailVerified: boolean;
6
+ disabled: boolean;
7
+ createdAt?: Date;
8
+ updatedAt?: Date;
9
+ }
10
+ /** Internal shape returned by repository secret lookups. */
11
+ interface AuthAccountWithSecrets extends BaseAuthAccount {
12
+ passwordHash?: string;
13
+ refreshTokenHash?: string | null;
14
+ }
15
+ interface RegisterInput {
16
+ email?: string;
17
+ username?: string;
18
+ password: string;
19
+ }
20
+ interface AuthTokens {
21
+ accessToken: string;
22
+ refreshToken: string;
23
+ }
24
+ interface AuthHooks<TAccount extends BaseAuthAccount = BaseAuthAccount> {
25
+ buildJwtPayload(account: TAccount): Promise<Record<string, unknown>>;
26
+ onBeforeRegister?(input: RegisterInput): Promise<void>;
27
+ onAfterRegister?(account: TAccount): Promise<void>;
28
+ onAfterLogin?(account: TAccount): Promise<void>;
29
+ enrichMe?(account: TAccount): Promise<Record<string, unknown>>;
30
+ sendEmail?(type: "verify" | "reset", to: string, token: string): Promise<void>;
31
+ }
32
+
33
+ type AuthIdentifierField = "email" | "username";
34
+ interface AuthFeatures {
35
+ /** Require verified email before login. Sends verification email on register. Default: `false`. */
36
+ emailVerification?: boolean;
37
+ /** Enable forgot/reset password endpoints. Default: `false`. */
38
+ passwordReset?: boolean;
39
+ /** Rotate refresh token hash on login/refresh. Default: `true`. */
40
+ refreshTokenRotation?: boolean;
41
+ }
42
+ interface AuthJwtConfig {
43
+ secret: string;
44
+ expiresIn?: string;
45
+ refreshSecret: string;
46
+ refreshExpiresIn?: string;
47
+ }
48
+ interface AuthModuleOptions extends AuthJwtConfig {
49
+ /** Field used for login/register lookup. Default: `email`. */
50
+ identifierField?: AuthIdentifierField;
51
+ /** Optional feature flags — all off by default (POC-friendly). */
52
+ features?: AuthFeatures;
53
+ /** Email verification token TTL in ms. Default: 24h. */
54
+ emailVerificationTokenTtlMs?: number;
55
+ /** Password reset token TTL in ms. Default: 15min. */
56
+ passwordResetTokenTtlMs?: number;
57
+ /** Route prefix for auth controller. Default: `auth`. */
58
+ routePrefix?: string;
59
+ /** Custom hooks class. Defaults to built-in minimal payload. */
60
+ hooks?: new (...args: any[]) => AuthHooks;
61
+ }
62
+ interface AuthModuleAsyncOptions {
63
+ imports?: unknown[];
64
+ inject?: unknown[];
65
+ useFactory: (...args: unknown[]) => AuthModuleOptions | Promise<AuthModuleOptions>;
66
+ }
67
+
68
+ interface CreateAccountData extends RegisterInput {
69
+ passwordHash: string;
70
+ /** When `false`, account can login immediately. Default decided by AuthService. */
71
+ emailVerified?: boolean;
72
+ }
73
+ interface IAuthRepository<TAccount extends BaseAuthAccount = BaseAuthAccount> {
74
+ create(data: CreateAccountData): Promise<TAccount>;
75
+ findByIdentifier(identifier: string): Promise<TAccount | null>;
76
+ findByEmail(email: string): Promise<BaseAuthAccount | null>;
77
+ findByIdentifierWithSecrets(identifier: string): Promise<AuthAccountWithSecrets | null>;
78
+ findById(id: string): Promise<BaseAuthAccount | null>;
79
+ findByIdWithSecrets(id: string): Promise<AuthAccountWithSecrets | null>;
80
+ updateRefreshTokenHash(id: string, hash: string | null): Promise<void>;
81
+ updatePasswordHash(id: string, passwordHash: string): Promise<void>;
82
+ setEmailVerificationToken(id: string, tokenHash: string, expiresAt: Date): Promise<void>;
83
+ findByEmailVerificationTokenHash(tokenHash: string): Promise<BaseAuthAccount | null>;
84
+ markEmailVerified(id: string): Promise<void>;
85
+ setResetToken(id: string, tokenHash: string, expiresAt: Date): Promise<void>;
86
+ findByResetTokenHash(tokenHash: string): Promise<AuthAccountWithSecrets | null>;
87
+ clearResetToken(id: string): Promise<void>;
88
+ }
89
+
90
+ export type { AuthModuleOptions as A, BaseAuthAccount as B, CreateAccountData as C, IAuthRepository as I, RegisterInput as R, AuthModuleAsyncOptions as a, AuthHooks as b, AuthTokens as c, AuthAccountWithSecrets as d, AuthFeatures as e, AuthIdentifierField as f, AuthJwtConfig as g };
@@ -0,0 +1,90 @@
1
+ interface BaseAuthAccount {
2
+ id: string;
3
+ email?: string;
4
+ username?: string;
5
+ emailVerified: boolean;
6
+ disabled: boolean;
7
+ createdAt?: Date;
8
+ updatedAt?: Date;
9
+ }
10
+ /** Internal shape returned by repository secret lookups. */
11
+ interface AuthAccountWithSecrets extends BaseAuthAccount {
12
+ passwordHash?: string;
13
+ refreshTokenHash?: string | null;
14
+ }
15
+ interface RegisterInput {
16
+ email?: string;
17
+ username?: string;
18
+ password: string;
19
+ }
20
+ interface AuthTokens {
21
+ accessToken: string;
22
+ refreshToken: string;
23
+ }
24
+ interface AuthHooks<TAccount extends BaseAuthAccount = BaseAuthAccount> {
25
+ buildJwtPayload(account: TAccount): Promise<Record<string, unknown>>;
26
+ onBeforeRegister?(input: RegisterInput): Promise<void>;
27
+ onAfterRegister?(account: TAccount): Promise<void>;
28
+ onAfterLogin?(account: TAccount): Promise<void>;
29
+ enrichMe?(account: TAccount): Promise<Record<string, unknown>>;
30
+ sendEmail?(type: "verify" | "reset", to: string, token: string): Promise<void>;
31
+ }
32
+
33
+ type AuthIdentifierField = "email" | "username";
34
+ interface AuthFeatures {
35
+ /** Require verified email before login. Sends verification email on register. Default: `false`. */
36
+ emailVerification?: boolean;
37
+ /** Enable forgot/reset password endpoints. Default: `false`. */
38
+ passwordReset?: boolean;
39
+ /** Rotate refresh token hash on login/refresh. Default: `true`. */
40
+ refreshTokenRotation?: boolean;
41
+ }
42
+ interface AuthJwtConfig {
43
+ secret: string;
44
+ expiresIn?: string;
45
+ refreshSecret: string;
46
+ refreshExpiresIn?: string;
47
+ }
48
+ interface AuthModuleOptions extends AuthJwtConfig {
49
+ /** Field used for login/register lookup. Default: `email`. */
50
+ identifierField?: AuthIdentifierField;
51
+ /** Optional feature flags — all off by default (POC-friendly). */
52
+ features?: AuthFeatures;
53
+ /** Email verification token TTL in ms. Default: 24h. */
54
+ emailVerificationTokenTtlMs?: number;
55
+ /** Password reset token TTL in ms. Default: 15min. */
56
+ passwordResetTokenTtlMs?: number;
57
+ /** Route prefix for auth controller. Default: `auth`. */
58
+ routePrefix?: string;
59
+ /** Custom hooks class. Defaults to built-in minimal payload. */
60
+ hooks?: new (...args: any[]) => AuthHooks;
61
+ }
62
+ interface AuthModuleAsyncOptions {
63
+ imports?: unknown[];
64
+ inject?: unknown[];
65
+ useFactory: (...args: unknown[]) => AuthModuleOptions | Promise<AuthModuleOptions>;
66
+ }
67
+
68
+ interface CreateAccountData extends RegisterInput {
69
+ passwordHash: string;
70
+ /** When `false`, account can login immediately. Default decided by AuthService. */
71
+ emailVerified?: boolean;
72
+ }
73
+ interface IAuthRepository<TAccount extends BaseAuthAccount = BaseAuthAccount> {
74
+ create(data: CreateAccountData): Promise<TAccount>;
75
+ findByIdentifier(identifier: string): Promise<TAccount | null>;
76
+ findByEmail(email: string): Promise<BaseAuthAccount | null>;
77
+ findByIdentifierWithSecrets(identifier: string): Promise<AuthAccountWithSecrets | null>;
78
+ findById(id: string): Promise<BaseAuthAccount | null>;
79
+ findByIdWithSecrets(id: string): Promise<AuthAccountWithSecrets | null>;
80
+ updateRefreshTokenHash(id: string, hash: string | null): Promise<void>;
81
+ updatePasswordHash(id: string, passwordHash: string): Promise<void>;
82
+ setEmailVerificationToken(id: string, tokenHash: string, expiresAt: Date): Promise<void>;
83
+ findByEmailVerificationTokenHash(tokenHash: string): Promise<BaseAuthAccount | null>;
84
+ markEmailVerified(id: string): Promise<void>;
85
+ setResetToken(id: string, tokenHash: string, expiresAt: Date): Promise<void>;
86
+ findByResetTokenHash(tokenHash: string): Promise<AuthAccountWithSecrets | null>;
87
+ clearResetToken(id: string): Promise<void>;
88
+ }
89
+
90
+ export type { AuthModuleOptions as A, BaseAuthAccount as B, CreateAccountData as C, IAuthRepository as I, RegisterInput as R, AuthModuleAsyncOptions as a, AuthHooks as b, AuthTokens as c, AuthAccountWithSecrets as d, AuthFeatures as e, AuthIdentifierField as f, AuthJwtConfig as g };