@lenne.tech/nest-server 11.6.2 → 11.7.1
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/dist/config.env.js +19 -12
- package/dist/config.env.js.map +1 -1
- package/dist/core/common/helpers/filter.helper.d.ts +9 -9
- package/dist/core/common/helpers/filter.helper.js +2 -4
- package/dist/core/common/helpers/filter.helper.js.map +1 -1
- package/dist/core/common/helpers/gridfs.helper.js +3 -3
- package/dist/core/common/helpers/gridfs.helper.js.map +1 -1
- package/dist/core/common/interfaces/server-options.interface.d.ts +21 -3
- package/dist/core/common/services/crud.service.d.ts +16 -16
- package/dist/core/common/services/crud.service.js +1 -1
- package/dist/core/common/services/crud.service.js.map +1 -1
- package/dist/core/modules/auth/core-auth.controller.d.ts +1 -0
- package/dist/core/modules/auth/core-auth.controller.js +28 -2
- package/dist/core/modules/auth/core-auth.controller.js.map +1 -1
- package/dist/core/modules/auth/core-auth.module.js +14 -1
- package/dist/core/modules/auth/core-auth.module.js.map +1 -1
- package/dist/core/modules/auth/core-auth.resolver.d.ts +1 -0
- package/dist/core/modules/auth/core-auth.resolver.js +20 -2
- package/dist/core/modules/auth/core-auth.resolver.js.map +1 -1
- package/dist/core/modules/auth/exceptions/legacy-auth-disabled.exception.d.ts +4 -0
- package/dist/core/modules/auth/exceptions/legacy-auth-disabled.exception.js +17 -0
- package/dist/core/modules/auth/exceptions/legacy-auth-disabled.exception.js.map +1 -0
- package/dist/core/modules/auth/guards/legacy-auth-rate-limit.guard.d.ts +9 -0
- package/dist/core/modules/auth/guards/legacy-auth-rate-limit.guard.js +74 -0
- package/dist/core/modules/auth/guards/legacy-auth-rate-limit.guard.js.map +1 -0
- package/dist/core/modules/auth/interfaces/auth-provider.interface.d.ts +7 -0
- package/dist/core/modules/auth/interfaces/auth-provider.interface.js +5 -0
- package/dist/core/modules/auth/interfaces/auth-provider.interface.js.map +1 -0
- package/dist/core/modules/auth/interfaces/core-auth-user.interface.d.ts +1 -0
- package/dist/core/modules/auth/services/core-auth.service.d.ts +10 -1
- package/dist/core/modules/auth/services/core-auth.service.js +141 -9
- package/dist/core/modules/auth/services/core-auth.service.js.map +1 -1
- package/dist/core/modules/auth/services/legacy-auth-rate-limiter.service.d.ts +31 -0
- package/dist/core/modules/auth/services/legacy-auth-rate-limiter.service.js +153 -0
- package/dist/core/modules/auth/services/legacy-auth-rate-limiter.service.js.map +1 -0
- package/dist/core/modules/better-auth/better-auth-migration-status.model.d.ts +10 -0
- package/dist/core/modules/better-auth/better-auth-migration-status.model.js +57 -0
- package/dist/core/modules/better-auth/better-auth-migration-status.model.js.map +1 -0
- package/dist/core/modules/better-auth/better-auth-models.d.ts +0 -1
- package/dist/core/modules/better-auth/better-auth-models.js +0 -4
- package/dist/core/modules/better-auth/better-auth-models.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth-user.mapper.d.ts +33 -0
- package/dist/core/modules/better-auth/better-auth-user.mapper.js +443 -0
- package/dist/core/modules/better-auth/better-auth-user.mapper.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.config.js +3 -0
- package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.module.d.ts +10 -2
- package/dist/core/modules/better-auth/better-auth.module.js +40 -52
- package/dist/core/modules/better-auth/better-auth.module.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.resolver.d.ts +8 -12
- package/dist/core/modules/better-auth/better-auth.resolver.js +33 -351
- package/dist/core/modules/better-auth/better-auth.resolver.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.service.d.ts +0 -1
- package/dist/core/modules/better-auth/better-auth.service.js +0 -3
- package/dist/core/modules/better-auth/better-auth.service.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.types.d.ts +9 -8
- package/dist/core/modules/better-auth/better-auth.types.js +14 -3
- package/dist/core/modules/better-auth/better-auth.types.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.controller.d.ts +67 -0
- package/dist/core/modules/better-auth/core-better-auth.controller.js +504 -0
- package/dist/core/modules/better-auth/core-better-auth.controller.js.map +1 -0
- package/dist/core/modules/better-auth/core-better-auth.resolver.d.ts +61 -0
- package/dist/core/modules/better-auth/core-better-auth.resolver.js +552 -0
- package/dist/core/modules/better-auth/core-better-auth.resolver.js.map +1 -0
- package/dist/core/modules/better-auth/index.d.ts +3 -0
- package/dist/core/modules/better-auth/index.js +3 -0
- package/dist/core/modules/better-auth/index.js.map +1 -1
- package/dist/core/modules/user/core-user.service.d.ts +7 -1
- package/dist/core/modules/user/core-user.service.js +57 -3
- package/dist/core/modules/user/core-user.service.js.map +1 -1
- package/dist/core/modules/user/interfaces/core-user-service-options.interface.d.ts +4 -0
- package/dist/core/modules/user/interfaces/core-user-service-options.interface.js +3 -0
- package/dist/core/modules/user/interfaces/core-user-service-options.interface.js.map +1 -0
- package/dist/core.module.d.ts +3 -0
- package/dist/core.module.js +132 -54
- package/dist/core.module.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/server/modules/auth/auth.resolver.js +2 -0
- package/dist/server/modules/auth/auth.resolver.js.map +1 -1
- package/dist/server/modules/better-auth/better-auth.controller.d.ts +10 -0
- package/dist/server/modules/better-auth/better-auth.controller.js +36 -0
- package/dist/server/modules/better-auth/better-auth.controller.js.map +1 -0
- package/dist/server/modules/better-auth/better-auth.module.d.ts +9 -0
- package/dist/server/modules/better-auth/better-auth.module.js +44 -0
- package/dist/server/modules/better-auth/better-auth.module.js.map +1 -0
- package/dist/server/modules/better-auth/better-auth.resolver.d.ts +47 -0
- package/dist/server/modules/better-auth/better-auth.resolver.js +234 -0
- package/dist/server/modules/better-auth/better-auth.resolver.js.map +1 -0
- package/dist/server/modules/file/file-info.model.d.ts +71 -3
- package/dist/server/modules/user/user.model.d.ts +169 -3
- package/dist/server/modules/user/user.service.d.ts +3 -1
- package/dist/server/modules/user/user.service.js +7 -3
- package/dist/server/modules/user/user.service.js.map +1 -1
- package/dist/server/server.module.js +6 -1
- package/dist/server/server.module.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +20 -29
- package/src/config.env.ts +34 -13
- package/src/core/common/helpers/filter.helper.ts +15 -17
- package/src/core/common/helpers/gridfs.helper.ts +5 -5
- package/src/core/common/interfaces/server-options.interface.ts +222 -14
- package/src/core/common/services/crud.service.ts +22 -22
- package/src/core/modules/auth/core-auth.controller.ts +93 -5
- package/src/core/modules/auth/core-auth.module.ts +15 -1
- package/src/core/modules/auth/core-auth.resolver.ts +70 -2
- package/src/core/modules/auth/exceptions/legacy-auth-disabled.exception.ts +35 -0
- package/src/core/modules/auth/guards/legacy-auth-rate-limit.guard.ts +109 -0
- package/src/core/modules/auth/interfaces/auth-provider.interface.ts +86 -0
- package/src/core/modules/auth/interfaces/core-auth-user.interface.ts +6 -0
- package/src/core/modules/auth/services/core-auth.service.ts +245 -6
- package/src/core/modules/auth/services/legacy-auth-rate-limiter.service.ts +283 -0
- package/src/core/modules/better-auth/INTEGRATION-CHECKLIST.md +254 -0
- package/src/core/modules/better-auth/README.md +698 -54
- package/src/core/modules/better-auth/better-auth-migration-status.model.ts +73 -0
- package/src/core/modules/better-auth/better-auth-models.ts +0 -3
- package/src/core/modules/better-auth/better-auth-user.mapper.ts +805 -0
- package/src/core/modules/better-auth/better-auth.config.ts +5 -0
- package/src/core/modules/better-auth/better-auth.module.ts +107 -66
- package/src/core/modules/better-auth/better-auth.resolver.ts +88 -553
- package/src/core/modules/better-auth/better-auth.service.ts +0 -9
- package/src/core/modules/better-auth/better-auth.types.ts +25 -10
- package/src/core/modules/better-auth/core-better-auth.controller.ts +646 -0
- package/src/core/modules/better-auth/core-better-auth.resolver.ts +730 -0
- package/src/core/modules/better-auth/index.ts +9 -1
- package/src/core/modules/user/core-user.service.ts +131 -4
- package/src/core/modules/user/interfaces/core-user-service-options.interface.ts +15 -0
- package/src/core.module.ts +257 -74
- package/src/index.ts +5 -0
- package/src/server/modules/auth/auth.resolver.ts +8 -0
- package/src/server/modules/better-auth/better-auth.controller.ts +41 -0
- package/src/server/modules/better-auth/better-auth.module.ts +88 -0
- package/src/server/modules/better-auth/better-auth.resolver.ts +210 -0
- package/src/server/modules/user/user.service.ts +4 -2
- package/src/server/server.module.ts +10 -1
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
import { IAuthRateLimit } from '../../../common/interfaces/server-options.interface';
|
|
4
|
+
import { ConfigService } from '../../../common/services/config.service';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Rate limit entry for tracking requests
|
|
8
|
+
*/
|
|
9
|
+
interface RateLimitEntry {
|
|
10
|
+
count: number;
|
|
11
|
+
resetTime: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Result of a rate limit check
|
|
16
|
+
*
|
|
17
|
+
* @internal This interface is identical to BetterAuthRateLimiter's RateLimitResult.
|
|
18
|
+
* Use the exported RateLimitResult from better-auth module if needed externally.
|
|
19
|
+
*/
|
|
20
|
+
interface RateLimitResult {
|
|
21
|
+
/** Whether the request is allowed */
|
|
22
|
+
allowed: boolean;
|
|
23
|
+
/** Current request count in the window */
|
|
24
|
+
current: number;
|
|
25
|
+
/** Maximum requests allowed */
|
|
26
|
+
limit: number;
|
|
27
|
+
/** Number of remaining requests in the window */
|
|
28
|
+
remaining: number;
|
|
29
|
+
/** Seconds until the rate limit resets */
|
|
30
|
+
resetIn: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Default rate limiting configuration
|
|
35
|
+
*/
|
|
36
|
+
const DEFAULT_CONFIG: Required<IAuthRateLimit> = {
|
|
37
|
+
enabled: false,
|
|
38
|
+
max: 10,
|
|
39
|
+
message: 'Too many requests, please try again later.',
|
|
40
|
+
windowSeconds: 60,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* In-memory rate limiter for Legacy Auth endpoints
|
|
45
|
+
*
|
|
46
|
+
* This service provides rate limiting to protect against brute-force attacks
|
|
47
|
+
* on authentication endpoints. It uses an in-memory store with automatic cleanup.
|
|
48
|
+
*
|
|
49
|
+
* Features:
|
|
50
|
+
* - Configurable request limits and time windows
|
|
51
|
+
* - Automatic cleanup of expired entries
|
|
52
|
+
* - IP-based tracking
|
|
53
|
+
* - Auto-configuration from ConfigService
|
|
54
|
+
*
|
|
55
|
+
* Configuration via config.env.ts:
|
|
56
|
+
* ```typescript
|
|
57
|
+
* auth: {
|
|
58
|
+
* rateLimit: {
|
|
59
|
+
* enabled: true,
|
|
60
|
+
* max: 10,
|
|
61
|
+
* windowSeconds: 60,
|
|
62
|
+
* message: 'Too many login attempts, please try again later.',
|
|
63
|
+
* }
|
|
64
|
+
* }
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* @since 11.7.x
|
|
68
|
+
*/
|
|
69
|
+
@Injectable()
|
|
70
|
+
export class LegacyAuthRateLimiter implements OnModuleInit {
|
|
71
|
+
private readonly logger = new Logger(LegacyAuthRateLimiter.name);
|
|
72
|
+
private readonly store = new Map<string, RateLimitEntry>();
|
|
73
|
+
private config: Required<IAuthRateLimit> = DEFAULT_CONFIG;
|
|
74
|
+
private cleanupInterval: NodeJS.Timeout | null = null;
|
|
75
|
+
|
|
76
|
+
constructor() {
|
|
77
|
+
// Start cleanup interval (every 5 minutes)
|
|
78
|
+
this.startCleanup();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Auto-configure from ConfigService on module initialization
|
|
83
|
+
*/
|
|
84
|
+
onModuleInit(): void {
|
|
85
|
+
const rateLimitConfig = ConfigService.getFastButReadOnly<IAuthRateLimit>('auth.rateLimit');
|
|
86
|
+
if (rateLimitConfig) {
|
|
87
|
+
this.configure(rateLimitConfig);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Configure the rate limiter
|
|
93
|
+
*
|
|
94
|
+
* Follows the "presence implies enabled" pattern:
|
|
95
|
+
* - If config is undefined/null: rate limiting is disabled (backward compatible)
|
|
96
|
+
* - If config is an object (even empty {}): rate limiting is enabled by default
|
|
97
|
+
* - Unless `enabled: false` is explicitly set to disable while pre-configuring
|
|
98
|
+
*
|
|
99
|
+
* @param config - Rate limiting configuration (presence implies enabled)
|
|
100
|
+
*/
|
|
101
|
+
configure(config: IAuthRateLimit | null | undefined): void {
|
|
102
|
+
// If config is not provided, rate limiting stays disabled (backward compatible)
|
|
103
|
+
if (config === undefined || config === null) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Presence of config implies enabled, unless explicitly disabled
|
|
108
|
+
const enabled = config.enabled !== false;
|
|
109
|
+
|
|
110
|
+
this.config = {
|
|
111
|
+
...DEFAULT_CONFIG,
|
|
112
|
+
...config,
|
|
113
|
+
enabled,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
if (this.config.enabled) {
|
|
117
|
+
this.logger.log(
|
|
118
|
+
`Legacy Auth rate limiting enabled: ${this.config.max} requests per ${this.config.windowSeconds}s`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Check if a request is allowed under the rate limit
|
|
125
|
+
*
|
|
126
|
+
* @param ip - Client IP address
|
|
127
|
+
* @param endpoint - Endpoint name (e.g., 'signIn', 'signUp')
|
|
128
|
+
* @returns Rate limit check result
|
|
129
|
+
*/
|
|
130
|
+
check(ip: string, endpoint: string): RateLimitResult {
|
|
131
|
+
// If rate limiting is disabled, always allow
|
|
132
|
+
if (!this.config.enabled) {
|
|
133
|
+
return {
|
|
134
|
+
allowed: true,
|
|
135
|
+
current: 0,
|
|
136
|
+
limit: Infinity,
|
|
137
|
+
remaining: Infinity,
|
|
138
|
+
resetIn: 0,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const limit = this.config.max;
|
|
143
|
+
const key = `${ip}:${endpoint}`;
|
|
144
|
+
const now = Date.now();
|
|
145
|
+
|
|
146
|
+
// Get or create entry
|
|
147
|
+
let entry = this.store.get(key);
|
|
148
|
+
|
|
149
|
+
if (!entry || now >= entry.resetTime) {
|
|
150
|
+
// Create new entry or reset expired one
|
|
151
|
+
entry = {
|
|
152
|
+
count: 1,
|
|
153
|
+
resetTime: now + this.config.windowSeconds * 1000,
|
|
154
|
+
};
|
|
155
|
+
this.store.set(key, entry);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
allowed: true,
|
|
159
|
+
current: 1,
|
|
160
|
+
limit,
|
|
161
|
+
remaining: limit - 1,
|
|
162
|
+
resetIn: this.config.windowSeconds,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Increment count
|
|
167
|
+
entry.count++;
|
|
168
|
+
|
|
169
|
+
const resetIn = Math.ceil((entry.resetTime - now) / 1000);
|
|
170
|
+
const allowed = entry.count <= limit;
|
|
171
|
+
const remaining = Math.max(0, limit - entry.count);
|
|
172
|
+
|
|
173
|
+
if (!allowed) {
|
|
174
|
+
this.logger.warn(`Rate limit exceeded for IP ${this.maskIp(ip)} on ${endpoint}: ${entry.count}/${limit}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
allowed,
|
|
179
|
+
current: entry.count,
|
|
180
|
+
limit,
|
|
181
|
+
remaining,
|
|
182
|
+
resetIn,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get the configured error message
|
|
188
|
+
*/
|
|
189
|
+
getMessage(): string {
|
|
190
|
+
return this.config.message;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Check if rate limiting is enabled
|
|
195
|
+
*/
|
|
196
|
+
isEnabled(): boolean {
|
|
197
|
+
return this.config.enabled;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Reset rate limit for a specific IP (useful for testing or admin override)
|
|
202
|
+
*
|
|
203
|
+
* @param ip - Client IP address
|
|
204
|
+
*/
|
|
205
|
+
reset(ip: string): void {
|
|
206
|
+
for (const key of this.store.keys()) {
|
|
207
|
+
if (key.startsWith(`${ip}:`)) {
|
|
208
|
+
this.store.delete(key);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Clear all rate limit entries (useful for testing)
|
|
215
|
+
*/
|
|
216
|
+
clear(): void {
|
|
217
|
+
this.store.clear();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get statistics about the rate limiter
|
|
222
|
+
*/
|
|
223
|
+
getStats(): { activeEntries: number; enabled: boolean } {
|
|
224
|
+
return {
|
|
225
|
+
activeEntries: this.store.size,
|
|
226
|
+
enabled: this.config.enabled,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Stop the cleanup interval (for graceful shutdown)
|
|
232
|
+
*/
|
|
233
|
+
onModuleDestroy(): void {
|
|
234
|
+
if (this.cleanupInterval) {
|
|
235
|
+
clearInterval(this.cleanupInterval);
|
|
236
|
+
this.cleanupInterval = null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Mask IP address for logging (privacy)
|
|
242
|
+
*/
|
|
243
|
+
private maskIp(ip: string): string {
|
|
244
|
+
if (ip.includes('.')) {
|
|
245
|
+
// IPv4: show first two octets
|
|
246
|
+
const parts = ip.split('.');
|
|
247
|
+
return `${parts[0]}.${parts[1]}.*.*`;
|
|
248
|
+
}
|
|
249
|
+
// IPv6: show first segment
|
|
250
|
+
const parts = ip.split(':');
|
|
251
|
+
return `${parts[0]}:****`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Start periodic cleanup of expired entries
|
|
256
|
+
*/
|
|
257
|
+
private startCleanup(): void {
|
|
258
|
+
// Clean up every 5 minutes
|
|
259
|
+
this.cleanupInterval = setInterval(
|
|
260
|
+
() => {
|
|
261
|
+
const now = Date.now();
|
|
262
|
+
let cleaned = 0;
|
|
263
|
+
|
|
264
|
+
for (const [key, entry] of this.store.entries()) {
|
|
265
|
+
if (now >= entry.resetTime) {
|
|
266
|
+
this.store.delete(key);
|
|
267
|
+
cleaned++;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (cleaned > 0) {
|
|
272
|
+
this.logger.debug(`Cleaned up ${cleaned} expired rate limit entries`);
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
5 * 60 * 1000,
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
// Prevent the interval from keeping the process alive
|
|
279
|
+
if (this.cleanupInterval.unref) {
|
|
280
|
+
this.cleanupInterval.unref();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# BetterAuth Integration Checklist
|
|
2
|
+
|
|
3
|
+
**For integrating BetterAuth into projects using `@lenne.tech/nest-server`.**
|
|
4
|
+
|
|
5
|
+
> **Estimated time:** 10-15 minutes
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Choose Your Scenario
|
|
10
|
+
|
|
11
|
+
| Scenario | Use When | CoreModule Signature | Steps |
|
|
12
|
+
|----------|----------|---------------------|-------|
|
|
13
|
+
| **New Project (IAM-Only)** | Starting fresh, no legacy users | `CoreModule.forRoot(envConfig)` | 1-6 |
|
|
14
|
+
| **Existing Project (Migration)** | Have legacy users to migrate | `CoreModule.forRoot(AuthService, AuthModule, envConfig)` | 1-6 |
|
|
15
|
+
|
|
16
|
+
**Key difference:** New projects disable Legacy endpoints, existing projects keep them enabled during migration.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Reference Implementation
|
|
21
|
+
|
|
22
|
+
All files you need to create are already implemented as reference in the package:
|
|
23
|
+
|
|
24
|
+
**Local (in your node_modules):**
|
|
25
|
+
```
|
|
26
|
+
node_modules/@lenne.tech/nest-server/src/server/modules/better-auth/
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**GitHub:**
|
|
30
|
+
https://github.com/lenneTech/nest-server/tree/develop/src/server/modules/better-auth
|
|
31
|
+
|
|
32
|
+
**Also see the UserService integration:**
|
|
33
|
+
- Local: `node_modules/@lenne.tech/nest-server/src/server/modules/user/user.service.ts`
|
|
34
|
+
- GitHub: https://github.com/lenneTech/nest-server/blob/develop/src/server/modules/user/user.service.ts
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Required Files (Create in Order)
|
|
39
|
+
|
|
40
|
+
### 1. BetterAuth Module
|
|
41
|
+
**Create:** `src/server/modules/better-auth/better-auth.module.ts`
|
|
42
|
+
**Copy from:** `node_modules/@lenne.tech/nest-server/src/server/modules/better-auth/better-auth.module.ts`
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
### 2. BetterAuth Controller
|
|
47
|
+
**Create:** `src/server/modules/better-auth/better-auth.controller.ts`
|
|
48
|
+
**Copy from:** `node_modules/@lenne.tech/nest-server/src/server/modules/better-auth/better-auth.controller.ts`
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
### 3. BetterAuth Resolver (CRITICAL!)
|
|
53
|
+
**Create:** `src/server/modules/better-auth/better-auth.resolver.ts`
|
|
54
|
+
**Copy from:** `node_modules/@lenne.tech/nest-server/src/server/modules/better-auth/better-auth.resolver.ts`
|
|
55
|
+
|
|
56
|
+
**WHY must ALL decorators be re-declared?**
|
|
57
|
+
GraphQL schema is built from decorators at compile time. The parent class (`CoreBetterAuthResolver`) is marked as `isAbstract: true`, so its methods are not registered in the schema. You MUST re-declare `@Query`, `@Mutation`, `@Roles`, `@UseGuards` decorators in the child class for the methods to appear in the GraphQL schema.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
### 4. Update UserService (CRITICAL!)
|
|
62
|
+
**Modify:** `src/server/modules/user/user.service.ts`
|
|
63
|
+
**Reference:** `node_modules/@lenne.tech/nest-server/src/server/modules/user/user.service.ts`
|
|
64
|
+
|
|
65
|
+
**Required changes:**
|
|
66
|
+
|
|
67
|
+
1. Add import:
|
|
68
|
+
```typescript
|
|
69
|
+
import { BetterAuthUserMapper } from '@lenne.tech/nest-server';
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
2. Add constructor parameter:
|
|
73
|
+
```typescript
|
|
74
|
+
@Optional() private readonly betterAuthUserMapper?: BetterAuthUserMapper,
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
3. Pass to super() via options object:
|
|
78
|
+
```typescript
|
|
79
|
+
super(configService, emailService, mainDbModel, mainModelConstructor, { betterAuthUserMapper });
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**WHY is this critical?**
|
|
83
|
+
The `BetterAuthUserMapper` enables bidirectional password synchronization:
|
|
84
|
+
- User signs up via BetterAuth → password synced to Legacy Auth (bcrypt hash)
|
|
85
|
+
- User changes password → synced between both systems
|
|
86
|
+
- **Without this, users can only authenticate via ONE system!**
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
### 5. Update ServerModule
|
|
91
|
+
**Modify:** `src/server/server.module.ts`
|
|
92
|
+
**Reference:** `node_modules/@lenne.tech/nest-server/src/server/server.module.ts`
|
|
93
|
+
|
|
94
|
+
#### For New Projects (IAM-Only) - Recommended:
|
|
95
|
+
```typescript
|
|
96
|
+
@Module({
|
|
97
|
+
imports: [
|
|
98
|
+
CoreModule.forRoot(envConfig), // Simplified signature
|
|
99
|
+
BetterAuthModule.forRoot({
|
|
100
|
+
config: envConfig.betterAuth,
|
|
101
|
+
fallbackSecrets: [envConfig.jwt?.secret],
|
|
102
|
+
}),
|
|
103
|
+
// ... other modules
|
|
104
|
+
],
|
|
105
|
+
})
|
|
106
|
+
export class ServerModule {}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
#### For Existing Projects (Migration):
|
|
110
|
+
```typescript
|
|
111
|
+
@Module({
|
|
112
|
+
imports: [
|
|
113
|
+
CoreModule.forRoot(AuthService, AuthModule.forRoot(envConfig.jwt), envConfig),
|
|
114
|
+
BetterAuthModule.forRoot({
|
|
115
|
+
config: envConfig.betterAuth,
|
|
116
|
+
fallbackSecrets: [envConfig.jwt?.secret],
|
|
117
|
+
}),
|
|
118
|
+
// ... other modules
|
|
119
|
+
],
|
|
120
|
+
})
|
|
121
|
+
export class ServerModule {}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
### 6. Update config.env.ts
|
|
127
|
+
**Modify:** `src/config.env.ts`
|
|
128
|
+
**Reference:** `node_modules/@lenne.tech/nest-server/src/config.env.ts`
|
|
129
|
+
|
|
130
|
+
#### For New Projects (IAM-Only):
|
|
131
|
+
```typescript
|
|
132
|
+
const config = {
|
|
133
|
+
// Disable Legacy Auth endpoints
|
|
134
|
+
auth: {
|
|
135
|
+
legacyEndpoints: {
|
|
136
|
+
enabled: false,
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
// BetterAuth configuration
|
|
140
|
+
betterAuth: {
|
|
141
|
+
// enabled: true (default)
|
|
142
|
+
// basePath: '/iam' (default)
|
|
143
|
+
jwt: {}, // Enable JWT tokens
|
|
144
|
+
twoFactor: {}, // Enable 2FA
|
|
145
|
+
passkey: {}, // Enable Passkeys
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### For Existing Projects (Migration):
|
|
151
|
+
```typescript
|
|
152
|
+
const config = {
|
|
153
|
+
// Keep Legacy Auth endpoints enabled during migration
|
|
154
|
+
auth: {
|
|
155
|
+
legacyEndpoints: {
|
|
156
|
+
enabled: true, // Default - can disable after migration
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
// BetterAuth configuration
|
|
160
|
+
betterAuth: {
|
|
161
|
+
// ... same as above
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Verification Checklist
|
|
169
|
+
|
|
170
|
+
After integration, verify:
|
|
171
|
+
|
|
172
|
+
- [ ] `npm run build` succeeds without errors
|
|
173
|
+
- [ ] `npm test` passes
|
|
174
|
+
- [ ] GraphQL Playground shows `betterAuthEnabled` query
|
|
175
|
+
- [ ] REST endpoint `GET /iam/session` responds
|
|
176
|
+
- [ ] Sign-up via BetterAuth creates user in database with `iamId`
|
|
177
|
+
- [ ] Sign-in via BetterAuth works correctly
|
|
178
|
+
|
|
179
|
+
### Additional checks for Migration scenario:
|
|
180
|
+
- [ ] Sign-in via Legacy Auth works for BetterAuth-created users
|
|
181
|
+
- [ ] Sign-in via BetterAuth works for Legacy-created users
|
|
182
|
+
- [ ] `betterAuthMigrationStatus` query shows correct counts
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Common Mistakes
|
|
187
|
+
|
|
188
|
+
| Mistake | Symptom | Fix |
|
|
189
|
+
|---------|---------|-----|
|
|
190
|
+
| Forgot to re-declare decorators in Resolver | GraphQL endpoints missing (404) | Copy resolver from reference, keep ALL decorators |
|
|
191
|
+
| Forgot `BetterAuthUserMapper` in UserService | Auth systems not synced, users can't cross-authenticate | Add `@Optional()` parameter and pass to super() |
|
|
192
|
+
| Missing `fallbackSecrets` in ServerModule | Session issues without explicit secret | Add `fallbackSecrets: [envConfig.jwt?.secret, ...]` |
|
|
193
|
+
| Wrong `basePath` in config | 404 on BetterAuth endpoints | Ensure basePath matches controller (default: `/iam`) |
|
|
194
|
+
| Using wrong CoreModule signature | Build errors or missing features | New projects: 1-parameter, Existing: 3-parameter |
|
|
195
|
+
| AuthResolver override missing `checkLegacyGraphQLEnabled()` | Legacy endpoint disabling doesn't work (no HTTP 410) | Call `this.checkLegacyGraphQLEnabled('signIn')` in overrides |
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Important: AuthResolver Override Pattern
|
|
200
|
+
|
|
201
|
+
If your project has a custom `AuthResolver` that extends `CoreAuthResolver` and overrides `signIn()` or `signUp()`, you **MUST** call the protected check method:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
// src/server/modules/auth/auth.resolver.ts
|
|
205
|
+
@Mutation(() => Auth)
|
|
206
|
+
override async signIn(...): Promise<Auth> {
|
|
207
|
+
this.checkLegacyGraphQLEnabled('signIn'); // Required!
|
|
208
|
+
const result = await this.authService.signIn(input, serviceOptions);
|
|
209
|
+
return this.processCookies(ctx, result);
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**WHY?** When `auth.legacyEndpoints.enabled: false`, this method throws `LegacyAuthDisabledException` (HTTP 410). Without this call, legacy endpoints remain accessible even when configured as disabled.
|
|
214
|
+
|
|
215
|
+
See: `.claude/rules/module-inheritance.md` for the full pattern.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Client-Side Configuration
|
|
220
|
+
|
|
221
|
+
Clients must be configured to use the correct base path and hash passwords:
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
// auth-client.ts (e.g., for Nuxt/Vue)
|
|
225
|
+
import { createAuthClient } from 'better-auth/vue';
|
|
226
|
+
import { sha256 } from '~/utils/crypto';
|
|
227
|
+
|
|
228
|
+
const baseClient = createAuthClient({
|
|
229
|
+
baseURL: import.meta.env.VITE_API_URL,
|
|
230
|
+
basePath: '/iam', // Must match server config
|
|
231
|
+
plugins: [...],
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Wrap signIn/signUp to hash passwords before sending
|
|
235
|
+
export const authClient = {
|
|
236
|
+
...baseClient,
|
|
237
|
+
signIn: {
|
|
238
|
+
...baseClient.signIn,
|
|
239
|
+
email: async (params) => {
|
|
240
|
+
const hashedPassword = await sha256(params.password);
|
|
241
|
+
return baseClient.signIn.email({ ...params, password: hashedPassword });
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
// ... same for signUp, resetPassword, etc.
|
|
245
|
+
};
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Detailed Documentation
|
|
251
|
+
|
|
252
|
+
For complete configuration options, API reference, and advanced topics:
|
|
253
|
+
- **README.md:** `node_modules/@lenne.tech/nest-server/src/core/modules/better-auth/README.md`
|
|
254
|
+
- **GitHub:** https://github.com/lenneTech/nest-server/blob/develop/src/core/modules/better-auth/README.md
|