@aranzatech/aranza-auth 0.1.2 → 0.2.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 +47 -24
- package/README.md +106 -10
- package/SECURITY.md +58 -0
- package/dist/{auth-repository.interface-BMlJc-98.d.cts → auth-repository.interface-9PpDVOs8.d.cts} +32 -2
- package/dist/{auth-repository.interface-BMlJc-98.d.ts → auth-repository.interface-9PpDVOs8.d.ts} +32 -2
- package/dist/{chunk-JLRBMDLH.js → chunk-QNEFN5ES.js} +5 -5
- package/dist/chunk-QNEFN5ES.js.map +1 -0
- package/dist/index.cjs +274 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -3
- package/dist/index.d.ts +65 -3
- package/dist/index.js +273 -69
- package/dist/index.js.map +1 -1
- package/dist/mongo/index.cjs +56 -5
- package/dist/mongo/index.cjs.map +1 -1
- package/dist/mongo/index.d.cts +7 -1
- package/dist/mongo/index.d.ts +7 -1
- package/dist/mongo/index.js +57 -3
- package/dist/mongo/index.js.map +1 -1
- package/package.json +3 -2
- package/dist/chunk-JLRBMDLH.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,44 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.0] - 2026-07-03
|
|
10
|
+
|
|
11
|
+
Release de hardening y madurez — incluye fixes de seguridad (0.1.3), hardening profesional y features enterprise (sin OAuth/Prisma).
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`POST /auth/change-password`**: cambio de contraseña autenticado (invalida refresh tokens).
|
|
16
|
+
- **Hooks con Nest DI**: `useClass` + `ModuleRef.create()` en `forRootAsync`; `hooksProvider` personalizable.
|
|
17
|
+
- **`routePrefix`**: ruta configurable del controller (`forRoot` y `forRootAsync.routePrefix`).
|
|
18
|
+
- **Audit trail Mongo**: `lastLoginAt`, `passwordChangedAt`, `failedLoginAttempts`, `lockedUntil`.
|
|
19
|
+
- **Account lockout** (`features.accountLockout`): bloqueo tras N intentos fallidos.
|
|
20
|
+
- **Refresh theft detection**: refresh reutilizado revoca sesiones (`REFRESH_TOKEN_REUSE`).
|
|
21
|
+
- **Validación al boot**: secrets ≥32 chars, access ≠ refresh, `bcryptRounds` 10–14.
|
|
22
|
+
- **JWT estricto**: algoritmo `HS256` fijado en sign/verify y Passport strategy.
|
|
23
|
+
- **`bcryptRounds`**, **`passwordComplexity`**, **`lockout`** configurables.
|
|
24
|
+
- **`AuthErrorCode`**: códigos exportados (`ACCOUNT_LOCKED`, `REFRESH_TOKEN_REUSE`, etc.).
|
|
25
|
+
- **`AUTH_RATE_LIMIT_PRESETS`**: presets para `@nestjs/throttler`.
|
|
26
|
+
- **SECURITY.md** con tradeoffs y checklist de producción.
|
|
27
|
+
- Índices Mongo en hashes de tokens y fechas de expiración.
|
|
28
|
+
- CI: coverage gate + `npm audit`.
|
|
29
|
+
- **GitHub Releases** automáticos al publicar tags.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- **Login timing attack**: siempre ejecuta `bcrypt.compare` (hash dummy si no hay cuenta).
|
|
34
|
+
- **Register user enumeration**: duplicados responden `{ registered: true }`.
|
|
35
|
+
- **`refreshTokenRotation: false`**: refresh valida solo JWT, sin exigir hash en DB.
|
|
36
|
+
- **`JwtStrategy`**: rechaza tokens de cuentas no verificadas con `emailVerification` activo.
|
|
37
|
+
- **`JwtAuthGuard`**: trata `user === false` de Passport como no autenticado (`401`).
|
|
38
|
+
- **`resolveRegisterIdentifier`**: `BadRequestException` en lugar de `Error` genérico.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- **Register/Login DTOs**: `@IsEmail()` cuando el campo email está presente.
|
|
43
|
+
- Password hashing usa `bcryptRounds` configurable (default 10).
|
|
44
|
+
- `DefaultAuthHooks.enrichMe` incluye `lastLoginAt` y `passwordChangedAt`.
|
|
45
|
+
- Login registra éxito/fallo cuando `accountLockout` está activo.
|
|
46
|
+
|
|
9
47
|
## [0.1.2] - 2026-07-03
|
|
10
48
|
|
|
11
49
|
### Fixed
|
|
@@ -33,30 +71,15 @@ First public release.
|
|
|
33
71
|
### Added
|
|
34
72
|
|
|
35
73
|
- **`AuthModule.forRoot` / `forRootAsync`** — módulo dinámico global con configuración JWT.
|
|
36
|
-
- **Endpoints REST** bajo `/auth`:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
- **`AuthService`**, **`TokenService`**, **`JwtStrategy`**, **`JwtAuthGuard`**, decorador **`@CurrentUser()`**.
|
|
46
|
-
- **Feature flags** (`features`) — todo desactivado por defecto, ideal para POCs:
|
|
47
|
-
- `emailVerification` — bloquea login hasta verificar email
|
|
48
|
-
- `passwordReset` — habilita forgot/reset password
|
|
49
|
-
- `refreshTokenRotation` — rota hash del refresh token en DB (default: `true`)
|
|
50
|
-
- **`AuthHooks`** — extiende JWT payload, `/auth/me`, lifecycle y envío de emails.
|
|
51
|
-
- **`DefaultAuthHooks`** — implementación mínima incluida.
|
|
52
|
-
- **Adapter MongoDB** (`@aranzatech/aranza-auth/mongo`):
|
|
53
|
-
- `BaseAuthAccountSchema` — schema base extensible
|
|
54
|
-
- `MongoAuthRepository` — implementación de `IAuthRepository`
|
|
55
|
-
- `MongoAuthModule.forFeature()` — registro de schema + repository
|
|
56
|
-
- Tokens de verificación/reset hasheados (SHA-256) con TTL configurable.
|
|
57
|
-
- Build dual ESM/CJS con types (`tsup`), tests (`vitest`), CI GitHub Actions.
|
|
58
|
-
|
|
59
|
-
[Unreleased]: https://github.com/aranzatech/aranza-auth/compare/v0.1.2...HEAD
|
|
74
|
+
- **Endpoints REST** bajo `/auth`: register, login, refresh, logout, me, verify-email, forgot/reset password.
|
|
75
|
+
- **`AuthService`**, **`TokenService`**, **`JwtStrategy`**, **`JwtAuthGuard`**, **`@CurrentUser()`**.
|
|
76
|
+
- **Feature flags** (`features`): `emailVerification`, `passwordReset`, `refreshTokenRotation`.
|
|
77
|
+
- **`AuthHooks`** y **`DefaultAuthHooks`**.
|
|
78
|
+
- **Adapter MongoDB** (`@aranzatech/aranza-auth/mongo`).
|
|
79
|
+
- Build dual ESM/CJS, tests (`vitest`), CI GitHub Actions.
|
|
80
|
+
|
|
81
|
+
[Unreleased]: https://github.com/aranzatech/aranza-auth/compare/v0.2.0...HEAD
|
|
82
|
+
[0.2.0]: https://github.com/aranzatech/aranza-auth/compare/v0.1.2...v0.2.0
|
|
60
83
|
[0.1.2]: https://github.com/aranzatech/aranza-auth/compare/v0.1.1...v0.1.2
|
|
61
84
|
[0.1.1]: https://github.com/aranzatech/aranza-auth/compare/v0.1.0...v0.1.1
|
|
62
85
|
[0.1.0]: https://github.com/aranzatech/aranza-auth/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -15,6 +15,8 @@ Ideal para reutilizar auth en todos tus proyectos AranzaTech: instalas, configur
|
|
|
15
15
|
- [Feature flags](#feature-flags)
|
|
16
16
|
- [Endpoints](#endpoints)
|
|
17
17
|
- [Proteger rutas propias](#proteger-rutas-propias)
|
|
18
|
+
- [Rate limiting (producción)](#rate-limiting-producción)
|
|
19
|
+
- [Seguridad en producción](#seguridad-en-producción)
|
|
18
20
|
- [Extender con AuthHooks](#extender-con-authhooks)
|
|
19
21
|
- [Extender el schema MongoDB](#extender-el-schema-mongodb)
|
|
20
22
|
- [Flujos opcionales (email y password)](#flujos-opcionales-email-y-password)
|
|
@@ -239,6 +241,9 @@ hooks: AppAuthHooks, // debe implementar sendEmail
|
|
|
239
241
|
| `POST` | `/auth/verify-email` | — | `emailVerification` | `{ token }` | `{ verified: true }` |
|
|
240
242
|
| `POST` | `/auth/forgot-password` | — | `passwordReset` | `{ email }` | `{ sent: true }` |
|
|
241
243
|
| `POST` | `/auth/reset-password` | — | `passwordReset` | `{ token, newPassword }` | `{ reset: true }` |
|
|
244
|
+
| `POST` | `/auth/change-password` | Bearer | — | `{ currentPassword, newPassword }` | `{ changed: true }` |
|
|
245
|
+
|
|
246
|
+
> Rutas usan `routePrefix` (default `auth`). Ej: `routePrefix: "v1/auth"` → `/v1/auth/login`.
|
|
242
247
|
|
|
243
248
|
### Errores comunes
|
|
244
249
|
|
|
@@ -247,9 +252,13 @@ hooks: AppAuthHooks, // debe implementar sendEmail
|
|
|
247
252
|
| `401` | `Invalid credentials` | Email/username o password incorrectos |
|
|
248
253
|
| `401` | `EMAIL_NOT_VERIFIED` | Feature `emailVerification` activa y email sin verificar |
|
|
249
254
|
| `401` | `ACCOUNT_DISABLED` | Cuenta desactivada |
|
|
255
|
+
| `401` | `ACCOUNT_LOCKED` | Demasiados intentos fallidos (`features.accountLockout`) |
|
|
256
|
+
| `401` | `REFRESH_TOKEN_REUSE` | Refresh reutilizado; sesiones revocadas |
|
|
257
|
+
| `401` | `INVALID_CURRENT_PASSWORD` | Contraseña actual incorrecta en change-password |
|
|
250
258
|
| `401` | `Invalid refresh token` | Refresh expirado, revocado o inválido |
|
|
251
259
|
| `404` | — | Feature desactivada (endpoint no disponible) |
|
|
252
260
|
| `400` | `TOKEN_INVALID_OR_EXPIRED` | Token de verify/reset inválido o expirado |
|
|
261
|
+
| `400` | `PASSWORD_UNCHANGED` | Nueva contraseña igual a la actual |
|
|
253
262
|
|
|
254
263
|
---
|
|
255
264
|
|
|
@@ -285,6 +294,93 @@ También puedes registrar `JwtAuthGuard` como guard global:
|
|
|
285
294
|
|
|
286
295
|
---
|
|
287
296
|
|
|
297
|
+
## Rate limiting (producción)
|
|
298
|
+
|
|
299
|
+
La librería **no incluye** throttling interno — debes aplicarlo en tu app con `@nestjs/throttler`:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
npm install @nestjs/throttler
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import { ThrottlerModule, ThrottlerGuard } from "@nestjs/throttler";
|
|
307
|
+
import { APP_GUARD } from "@nestjs/core";
|
|
308
|
+
import { AUTH_RATE_LIMIT_PRESETS } from "@aranzatech/aranza-auth";
|
|
309
|
+
|
|
310
|
+
@Module({
|
|
311
|
+
imports: [
|
|
312
|
+
ThrottlerModule.forRoot([
|
|
313
|
+
AUTH_RATE_LIMIT_PRESETS.default,
|
|
314
|
+
AUTH_RATE_LIMIT_PRESETS.credentials,
|
|
315
|
+
AUTH_RATE_LIMIT_PRESETS.passwordReset,
|
|
316
|
+
]),
|
|
317
|
+
// ...
|
|
318
|
+
],
|
|
319
|
+
providers: [{ provide: APP_GUARD, useClass: ThrottlerGuard }],
|
|
320
|
+
})
|
|
321
|
+
export class AppModule {}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Presets exportados:
|
|
325
|
+
|
|
326
|
+
| Preset | Uso recomendado | Límite |
|
|
327
|
+
|--------|-----------------|--------|
|
|
328
|
+
| `default` | Rutas auth generales | 10 req/min |
|
|
329
|
+
| `credentials` | `/auth/login`, `/auth/register`, `/auth/refresh` | 5 req/min |
|
|
330
|
+
| `passwordReset` | `/auth/forgot-password` | 3 req/min |
|
|
331
|
+
|
|
332
|
+
> Aplica `@Throttle()` por controlador o ruta según tu política de seguridad.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Seguridad en producción
|
|
337
|
+
|
|
338
|
+
Desde **v0.2.0** la lib valida configuración al arrancar (secrets ≥32 chars, access ≠ refresh).
|
|
339
|
+
|
|
340
|
+
### Opciones recomendadas
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
AuthModule.forRootAsync({
|
|
344
|
+
useFactory: (config: ConfigService) => ({
|
|
345
|
+
secret: config.getOrThrow("JWT_SECRET"),
|
|
346
|
+
refreshSecret: config.getOrThrow("JWT_REFRESH_SECRET"),
|
|
347
|
+
expiresIn: "30m", // access corto en prod
|
|
348
|
+
refreshExpiresIn: "7d",
|
|
349
|
+
bcryptRounds: 12, // opcional, default 10
|
|
350
|
+
passwordComplexity: true, // upper + lower + digit
|
|
351
|
+
features: {
|
|
352
|
+
emailVerification: true,
|
|
353
|
+
passwordReset: true,
|
|
354
|
+
refreshTokenRotation: true,
|
|
355
|
+
},
|
|
356
|
+
}),
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Refresh tokens en cookies (recomendado)
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
// En login/refresh handler de tu app — no devolver refresh en JSON body
|
|
364
|
+
res.cookie("refreshToken", tokens.refreshToken, {
|
|
365
|
+
httpOnly: true,
|
|
366
|
+
secure: true,
|
|
367
|
+
sameSite: "strict",
|
|
368
|
+
maxAge: 7 * 24 * 60 * 60 * 1000,
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Códigos de error
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
import { AuthErrorCode } from "@aranzatech/aranza-auth";
|
|
376
|
+
|
|
377
|
+
// AuthErrorCode.REFRESH_TOKEN_REUSE → posible robo de token; sesiones revocadas
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Ver [SECURITY.md](./SECURITY.md) para tradeoffs (logout vs access JWT) y reporte de vulnerabilidades.
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
288
384
|
## Extender con AuthHooks
|
|
289
385
|
|
|
290
386
|
La lib maneja auth genérico. Tu dominio (org, roles, users) va en **hooks**:
|
|
@@ -426,7 +522,7 @@ Si usas `identifierField: "username"`, el register **debe incluir `email`** adem
|
|
|
426
522
|
|
|
427
523
|
| Import | Contenido |
|
|
428
524
|
|--------|-----------|
|
|
429
|
-
| `@aranzatech/aranza-auth` | `AuthModule`, `AuthService`, guards, DTOs, interfaces, tokens |
|
|
525
|
+
| `@aranzatech/aranza-auth` | `AuthModule`, `AuthService`, guards, DTOs, interfaces, tokens, `AUTH_RATE_LIMIT_PRESETS` |
|
|
430
526
|
| `@aranzatech/aranza-auth/mongo` | `MongoAuthModule`, `MongoAuthRepository`, schemas |
|
|
431
527
|
|
|
432
528
|
### Tokens de inyección
|
|
@@ -447,27 +543,27 @@ import { AUTH_MODULE_OPTIONS, AUTH_HOOKS, AUTH_REPOSITORY } from "@aranzatech/ar
|
|
|
447
543
|
- [ ] MongoDB conectado via `@nestjs/mongoose` (**mongoose@8** recomendado)
|
|
448
544
|
- [ ] `MongoAuthModule.forFeature()` importado **dentro** de `AuthModule.forRootAsync({ imports })`
|
|
449
545
|
- [ ] Secrets JWT distintos para access y refresh
|
|
546
|
+
- [ ] **`@nestjs/throttler`** en rutas `/auth/*` (ver [Rate limiting](#rate-limiting-producción))
|
|
547
|
+
- [ ] Access token TTL corto (15–60 min) en producción
|
|
450
548
|
- [ ] Si usas features de email: implementar `AuthHooks.sendEmail`
|
|
451
549
|
|
|
452
550
|
---
|
|
453
551
|
|
|
454
|
-
## Limitaciones conocidas (v0.1.
|
|
552
|
+
## Limitaciones conocidas (v0.1.x)
|
|
455
553
|
|
|
456
554
|
| Limitación | Workaround / versión futura |
|
|
457
555
|
|------------|----------------------------|
|
|
458
|
-
|
|
|
459
|
-
|
|
|
460
|
-
| Solo adapter MongoDB | Prisma en v0.3.0 |
|
|
461
|
-
| Sin OAuth (Google, GitHub, etc.) | v0.2.0 |
|
|
462
|
-
| Sin `change-password` autenticado | v0.2.0 |
|
|
556
|
+
| Solo adapter MongoDB | Prisma en v0.4.0 |
|
|
557
|
+
| Sin OAuth (Google, GitHub, etc.) | v0.3.0 |
|
|
463
558
|
|
|
464
559
|
---
|
|
465
560
|
|
|
466
561
|
## Roadmap
|
|
467
562
|
|
|
468
|
-
- [
|
|
469
|
-
- [ ] **0.3.0** —
|
|
470
|
-
- [ ] **0.4.0** —
|
|
563
|
+
- [x] **0.2.0** — Security hardening, `change-password`, hooks con DI, audit trail, account lockout, `routePrefix`
|
|
564
|
+
- [ ] **0.3.0** — OAuth (Google, GitHub)
|
|
565
|
+
- [ ] **0.4.0** — Adapter Prisma
|
|
566
|
+
- [ ] **0.5.0** — Migración de AranzaFlow como caso de referencia
|
|
471
567
|
|
|
472
568
|
---
|
|
473
569
|
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
| Version | Supported |
|
|
6
|
+
| ------- | ------------------ |
|
|
7
|
+
| 0.2.x | :white_check_mark: |
|
|
8
|
+
| 0.1.x | :white_check_mark: |
|
|
9
|
+
|
|
10
|
+
## Reporting a Vulnerability
|
|
11
|
+
|
|
12
|
+
**Do not open public GitHub issues for security vulnerabilities.**
|
|
13
|
+
|
|
14
|
+
Email: [security@aranzatech.com](mailto:security@aranzatech.com) (or open a private advisory on GitHub if enabled).
|
|
15
|
+
|
|
16
|
+
We aim to respond within **72 hours** and publish a fix or mitigation within **14 days** for confirmed issues.
|
|
17
|
+
|
|
18
|
+
## Security Model
|
|
19
|
+
|
|
20
|
+
`@aranzatech/aranza-auth` provides authentication primitives. **Your host application** is responsible for:
|
|
21
|
+
|
|
22
|
+
- HTTPS in production
|
|
23
|
+
- Rate limiting (`AUTH_RATE_LIMIT_PRESETS` + `@nestjs/throttler`)
|
|
24
|
+
- Secure storage of refresh tokens (prefer **httpOnly + Secure cookies** over `localStorage`)
|
|
25
|
+
- JWT secrets in a secrets manager (not committed `.env` files)
|
|
26
|
+
- Global `ValidationPipe({ whitelist: true, forbidNonWhitelisted: true })`
|
|
27
|
+
|
|
28
|
+
## Known Tradeoffs
|
|
29
|
+
|
|
30
|
+
| Behavior | Mitigation |
|
|
31
|
+
|----------|------------|
|
|
32
|
+
| **Logout** clears refresh token hash only; access JWT remains valid until expiry | Use short access TTL (15–60 min) |
|
|
33
|
+
| **Register duplicate** returns `{ registered: true }` without revealing existence | By design (anti-enumeration) |
|
|
34
|
+
| **Forgot password** always returns `{ sent: true }` | By design (anti-enumeration) |
|
|
35
|
+
| **Refresh token reuse** revokes all refresh sessions for the account | Monitor `REFRESH_TOKEN_REUSE` errors |
|
|
36
|
+
|
|
37
|
+
## Hardening Checklist (Production)
|
|
38
|
+
|
|
39
|
+
- [ ] Secrets ≥ 32 characters; access ≠ refresh (validated at boot since v0.2.0)
|
|
40
|
+
- [ ] `features.refreshTokenRotation: true`
|
|
41
|
+
- [ ] `features.emailVerification: true` when using email login
|
|
42
|
+
- [ ] Access token TTL ≤ 60 minutes
|
|
43
|
+
- [ ] `@nestjs/throttler` on all `/auth/*` routes
|
|
44
|
+
- [ ] `passwordComplexity: true` for user-facing apps
|
|
45
|
+
- [ ] Log and alert on `REFRESH_TOKEN_REUSE` responses
|
|
46
|
+
|
|
47
|
+
## Error Codes
|
|
48
|
+
|
|
49
|
+
| Code | Meaning |
|
|
50
|
+
|------|---------|
|
|
51
|
+
| `REFRESH_TOKEN_REUSE` | Stale refresh token used — all refresh sessions revoked |
|
|
52
|
+
| `ACCOUNT_LOCKED` | Too many failed login attempts |
|
|
53
|
+
| `INVALID_CURRENT_PASSWORD` | Wrong password on change-password |
|
|
54
|
+
| `EMAIL_NOT_VERIFIED` | Login or access blocked until email verified |
|
|
55
|
+
| `ACCOUNT_DISABLED` | Account disabled by admin |
|
|
56
|
+
| `TOKEN_INVALID_OR_EXPIRED` | Email verify or password reset token invalid |
|
|
57
|
+
|
|
58
|
+
Export: `AuthErrorCode` from `@aranzatech/aranza-auth`.
|
package/dist/{auth-repository.interface-BMlJc-98.d.cts → auth-repository.interface-9PpDVOs8.d.cts}
RENAMED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import * as _nestjs_common from '@nestjs/common';
|
|
2
|
+
|
|
1
3
|
interface BaseAuthAccount {
|
|
2
4
|
id: string;
|
|
3
5
|
email?: string;
|
|
4
6
|
username?: string;
|
|
5
7
|
emailVerified: boolean;
|
|
6
8
|
disabled: boolean;
|
|
9
|
+
lastLoginAt?: Date;
|
|
10
|
+
passwordChangedAt?: Date;
|
|
7
11
|
createdAt?: Date;
|
|
8
12
|
updatedAt?: Date;
|
|
9
13
|
}
|
|
@@ -11,6 +15,8 @@ interface BaseAuthAccount {
|
|
|
11
15
|
interface AuthAccountWithSecrets extends BaseAuthAccount {
|
|
12
16
|
passwordHash?: string;
|
|
13
17
|
refreshTokenHash?: string | null;
|
|
18
|
+
failedLoginAttempts?: number;
|
|
19
|
+
lockedUntil?: Date | null;
|
|
14
20
|
}
|
|
15
21
|
interface RegisterInput {
|
|
16
22
|
email?: string;
|
|
@@ -38,6 +44,14 @@ interface AuthFeatures {
|
|
|
38
44
|
passwordReset?: boolean;
|
|
39
45
|
/** Rotate refresh token hash on login/refresh. Default: `true`. */
|
|
40
46
|
refreshTokenRotation?: boolean;
|
|
47
|
+
/** Lock account after repeated failed logins. Default: `false`. */
|
|
48
|
+
accountLockout?: boolean;
|
|
49
|
+
}
|
|
50
|
+
interface AuthLockoutOptions {
|
|
51
|
+
/** Max failed attempts before lockout. Default: `5`. */
|
|
52
|
+
maxAttempts?: number;
|
|
53
|
+
/** Lock duration in ms. Default: `15` minutes. */
|
|
54
|
+
lockoutDurationMs?: number;
|
|
41
55
|
}
|
|
42
56
|
interface AuthJwtConfig {
|
|
43
57
|
secret: string;
|
|
@@ -56,10 +70,24 @@ interface AuthModuleOptions extends AuthJwtConfig {
|
|
|
56
70
|
passwordResetTokenTtlMs?: number;
|
|
57
71
|
/** Route prefix for auth controller. Default: `auth`. */
|
|
58
72
|
routePrefix?: string;
|
|
59
|
-
/**
|
|
73
|
+
/** bcrypt cost factor. Default: `10`. Range: 10–14. */
|
|
74
|
+
bcryptRounds?: number;
|
|
75
|
+
/** Require uppercase, lowercase, and digit on register/reset. Default: `false`. */
|
|
76
|
+
passwordComplexity?: boolean;
|
|
77
|
+
/** Lockout settings when `features.accountLockout` is enabled. */
|
|
78
|
+
lockout?: AuthLockoutOptions;
|
|
79
|
+
/** Custom hooks class (`useClass` — supports Nest DI). Defaults to `DefaultAuthHooks`. */
|
|
60
80
|
hooks?: new (...args: any[]) => AuthHooks;
|
|
81
|
+
/** Full Nest provider for hooks. Takes precedence over `hooks`. */
|
|
82
|
+
hooksProvider?: _nestjs_common.Provider;
|
|
61
83
|
}
|
|
62
84
|
interface AuthModuleAsyncOptions {
|
|
85
|
+
/** Route prefix for auth controller. Default: `auth`. */
|
|
86
|
+
routePrefix?: string;
|
|
87
|
+
/** Custom hooks class (`useClass` with Nest DI via `ModuleRef`). */
|
|
88
|
+
hooks?: new (...args: any[]) => AuthHooks;
|
|
89
|
+
/** Full Nest provider for hooks. Takes precedence over `hooks`. */
|
|
90
|
+
hooksProvider?: _nestjs_common.Provider;
|
|
63
91
|
imports?: unknown[];
|
|
64
92
|
inject?: unknown[];
|
|
65
93
|
useFactory: (...args: unknown[]) => AuthModuleOptions | Promise<AuthModuleOptions>;
|
|
@@ -85,6 +113,8 @@ interface IAuthRepository<TAccount extends BaseAuthAccount = BaseAuthAccount> {
|
|
|
85
113
|
setResetToken(id: string, tokenHash: string, expiresAt: Date): Promise<void>;
|
|
86
114
|
findByResetTokenHash(tokenHash: string): Promise<AuthAccountWithSecrets | null>;
|
|
87
115
|
clearResetToken(id: string): Promise<void>;
|
|
116
|
+
recordLoginSuccess(id: string): Promise<void>;
|
|
117
|
+
recordLoginFailure(id: string, lockout?: AuthLockoutOptions): Promise<void>;
|
|
88
118
|
}
|
|
89
119
|
|
|
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 };
|
|
120
|
+
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, AuthLockoutOptions as h };
|
package/dist/{auth-repository.interface-BMlJc-98.d.ts → auth-repository.interface-9PpDVOs8.d.ts}
RENAMED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import * as _nestjs_common from '@nestjs/common';
|
|
2
|
+
|
|
1
3
|
interface BaseAuthAccount {
|
|
2
4
|
id: string;
|
|
3
5
|
email?: string;
|
|
4
6
|
username?: string;
|
|
5
7
|
emailVerified: boolean;
|
|
6
8
|
disabled: boolean;
|
|
9
|
+
lastLoginAt?: Date;
|
|
10
|
+
passwordChangedAt?: Date;
|
|
7
11
|
createdAt?: Date;
|
|
8
12
|
updatedAt?: Date;
|
|
9
13
|
}
|
|
@@ -11,6 +15,8 @@ interface BaseAuthAccount {
|
|
|
11
15
|
interface AuthAccountWithSecrets extends BaseAuthAccount {
|
|
12
16
|
passwordHash?: string;
|
|
13
17
|
refreshTokenHash?: string | null;
|
|
18
|
+
failedLoginAttempts?: number;
|
|
19
|
+
lockedUntil?: Date | null;
|
|
14
20
|
}
|
|
15
21
|
interface RegisterInput {
|
|
16
22
|
email?: string;
|
|
@@ -38,6 +44,14 @@ interface AuthFeatures {
|
|
|
38
44
|
passwordReset?: boolean;
|
|
39
45
|
/** Rotate refresh token hash on login/refresh. Default: `true`. */
|
|
40
46
|
refreshTokenRotation?: boolean;
|
|
47
|
+
/** Lock account after repeated failed logins. Default: `false`. */
|
|
48
|
+
accountLockout?: boolean;
|
|
49
|
+
}
|
|
50
|
+
interface AuthLockoutOptions {
|
|
51
|
+
/** Max failed attempts before lockout. Default: `5`. */
|
|
52
|
+
maxAttempts?: number;
|
|
53
|
+
/** Lock duration in ms. Default: `15` minutes. */
|
|
54
|
+
lockoutDurationMs?: number;
|
|
41
55
|
}
|
|
42
56
|
interface AuthJwtConfig {
|
|
43
57
|
secret: string;
|
|
@@ -56,10 +70,24 @@ interface AuthModuleOptions extends AuthJwtConfig {
|
|
|
56
70
|
passwordResetTokenTtlMs?: number;
|
|
57
71
|
/** Route prefix for auth controller. Default: `auth`. */
|
|
58
72
|
routePrefix?: string;
|
|
59
|
-
/**
|
|
73
|
+
/** bcrypt cost factor. Default: `10`. Range: 10–14. */
|
|
74
|
+
bcryptRounds?: number;
|
|
75
|
+
/** Require uppercase, lowercase, and digit on register/reset. Default: `false`. */
|
|
76
|
+
passwordComplexity?: boolean;
|
|
77
|
+
/** Lockout settings when `features.accountLockout` is enabled. */
|
|
78
|
+
lockout?: AuthLockoutOptions;
|
|
79
|
+
/** Custom hooks class (`useClass` — supports Nest DI). Defaults to `DefaultAuthHooks`. */
|
|
60
80
|
hooks?: new (...args: any[]) => AuthHooks;
|
|
81
|
+
/** Full Nest provider for hooks. Takes precedence over `hooks`. */
|
|
82
|
+
hooksProvider?: _nestjs_common.Provider;
|
|
61
83
|
}
|
|
62
84
|
interface AuthModuleAsyncOptions {
|
|
85
|
+
/** Route prefix for auth controller. Default: `auth`. */
|
|
86
|
+
routePrefix?: string;
|
|
87
|
+
/** Custom hooks class (`useClass` with Nest DI via `ModuleRef`). */
|
|
88
|
+
hooks?: new (...args: any[]) => AuthHooks;
|
|
89
|
+
/** Full Nest provider for hooks. Takes precedence over `hooks`. */
|
|
90
|
+
hooksProvider?: _nestjs_common.Provider;
|
|
63
91
|
imports?: unknown[];
|
|
64
92
|
inject?: unknown[];
|
|
65
93
|
useFactory: (...args: unknown[]) => AuthModuleOptions | Promise<AuthModuleOptions>;
|
|
@@ -85,6 +113,8 @@ interface IAuthRepository<TAccount extends BaseAuthAccount = BaseAuthAccount> {
|
|
|
85
113
|
setResetToken(id: string, tokenHash: string, expiresAt: Date): Promise<void>;
|
|
86
114
|
findByResetTokenHash(tokenHash: string): Promise<AuthAccountWithSecrets | null>;
|
|
87
115
|
clearResetToken(id: string): Promise<void>;
|
|
116
|
+
recordLoginSuccess(id: string): Promise<void>;
|
|
117
|
+
recordLoginFailure(id: string, lockout?: AuthLockoutOptions): Promise<void>;
|
|
88
118
|
}
|
|
89
119
|
|
|
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 };
|
|
120
|
+
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, AuthLockoutOptions as h };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { BadRequestException } from '@nestjs/common';
|
|
2
|
+
|
|
1
3
|
var __defProp = Object.defineProperty;
|
|
2
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
5
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
@@ -14,15 +16,13 @@ var __decorateParam = (index, decorator) => (target, key) => decorator(target, k
|
|
|
14
16
|
var AUTH_MODULE_OPTIONS = "AUTH_MODULE_OPTIONS";
|
|
15
17
|
var AUTH_HOOKS = "AUTH_HOOKS";
|
|
16
18
|
var AUTH_REPOSITORY = "AUTH_REPOSITORY";
|
|
17
|
-
|
|
18
|
-
// src/utils/identifier.util.ts
|
|
19
19
|
function normalizeIdentifier(value) {
|
|
20
20
|
return value.trim().toLowerCase();
|
|
21
21
|
}
|
|
22
22
|
function resolveRegisterIdentifier(input, field) {
|
|
23
23
|
const value = field === "email" ? input.email : input.username;
|
|
24
24
|
if (value == null || value.trim() === "") {
|
|
25
|
-
throw new
|
|
25
|
+
throw new BadRequestException(`Register input requires ${field}`);
|
|
26
26
|
}
|
|
27
27
|
return normalizeIdentifier(value);
|
|
28
28
|
}
|
|
@@ -32,5 +32,5 @@ function readAccountIdentifier(account, field) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export { AUTH_HOOKS, AUTH_MODULE_OPTIONS, AUTH_REPOSITORY, __decorateClass, __decorateParam, normalizeIdentifier, readAccountIdentifier, resolveRegisterIdentifier };
|
|
35
|
-
//# sourceMappingURL=chunk-
|
|
36
|
-
//# sourceMappingURL=chunk-
|
|
35
|
+
//# sourceMappingURL=chunk-QNEFN5ES.js.map
|
|
36
|
+
//# sourceMappingURL=chunk-QNEFN5ES.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/constants/tokens.ts","../src/utils/identifier.util.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AACO,IAAM,mBAAA,GAAsB;AAC5B,IAAM,UAAA,GAAa;AACnB,IAAM,eAAA,GAAkB;ACExB,SAAS,oBAAoB,KAAA,EAAuB;AACzD,EAAA,OAAO,KAAA,CAAM,IAAA,EAAK,CAAE,WAAA,EAAY;AAClC;AAEO,SAAS,yBAAA,CACd,OACA,KAAA,EACQ;AACR,EAAA,MAAM,KAAA,GAAQ,KAAA,KAAU,OAAA,GAAU,KAAA,CAAM,QAAQ,KAAA,CAAM,QAAA;AACtD,EAAA,IAAI,KAAA,IAAS,IAAA,IAAQ,KAAA,CAAM,IAAA,OAAW,EAAA,EAAI;AACxC,IAAA,MAAM,IAAI,mBAAA,CAAoB,CAAA,wBAAA,EAA2B,KAAK,CAAA,CAAE,CAAA;AAAA,EAClE;AACA,EAAA,OAAO,oBAAoB,KAAK,CAAA;AAClC;AAEO,SAAS,qBAAA,CACd,SACA,KAAA,EACoB;AACpB,EAAA,MAAM,KAAA,GAAQ,KAAA,KAAU,OAAA,GAAU,OAAA,CAAQ,QAAQ,OAAA,CAAQ,QAAA;AAC1D,EAAA,OAAO,KAAA,IAAS,IAAA,GAAO,mBAAA,CAAoB,KAAK,CAAA,GAAI,MAAA;AACtD","file":"chunk-QNEFN5ES.js","sourcesContent":["/** String tokens — stable across tsup entry points (index + mongo). */\nexport const AUTH_MODULE_OPTIONS = \"AUTH_MODULE_OPTIONS\";\nexport const AUTH_HOOKS = \"AUTH_HOOKS\";\nexport const AUTH_REPOSITORY = \"AUTH_REPOSITORY\";\n","import { BadRequestException } from \"@nestjs/common\";\n\nimport type { AuthIdentifierField } from \"../interfaces/auth-config.interface\";\nimport type { BaseAuthAccount, RegisterInput } from \"../interfaces/auth-hooks.interface\";\n\nexport function normalizeIdentifier(value: string): string {\n return value.trim().toLowerCase();\n}\n\nexport function resolveRegisterIdentifier(\n input: RegisterInput,\n field: AuthIdentifierField,\n): string {\n const value = field === \"email\" ? input.email : input.username;\n if (value == null || value.trim() === \"\") {\n throw new BadRequestException(`Register input requires ${field}`);\n }\n return normalizeIdentifier(value);\n}\n\nexport function readAccountIdentifier(\n account: BaseAuthAccount,\n field: AuthIdentifierField,\n): string | undefined {\n const value = field === \"email\" ? account.email : account.username;\n return value != null ? normalizeIdentifier(value) : undefined;\n}\n"]}
|