@lenne.tech/nest-server 11.11.1 → 11.13.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/dist/config.env.js +1 -0
- package/dist/config.env.js.map +1 -1
- package/dist/core/common/interfaces/server-options.interface.d.ts +16 -0
- package/dist/core/modules/auth/core-auth.controller.js +1 -1
- package/dist/core/modules/auth/core-auth.controller.js.map +1 -1
- package/dist/core/modules/auth/core-auth.resolver.js +1 -1
- package/dist/core/modules/auth/core-auth.resolver.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth-token.service.js +1 -4
- package/dist/core/modules/better-auth/better-auth-token.service.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.config.d.ts +13 -0
- package/dist/core/modules/better-auth/better-auth.config.js +114 -17
- package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.resolver.d.ts +7 -3
- package/dist/core/modules/better-auth/better-auth.resolver.js +16 -6
- package/dist/core/modules/better-auth/better-auth.resolver.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-api.middleware.d.ts +4 -2
- package/dist/core/modules/better-auth/core-better-auth-api.middleware.js +63 -18
- package/dist/core/modules/better-auth/core-better-auth-api.middleware.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-auth.model.d.ts +1 -0
- package/dist/core/modules/better-auth/core-better-auth-auth.model.js +7 -0
- package/dist/core/modules/better-auth/core-better-auth-auth.model.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-cookie.helper.d.ts +41 -0
- package/dist/core/modules/better-auth/core-better-auth-cookie.helper.js +107 -0
- package/dist/core/modules/better-auth/core-better-auth-cookie.helper.js.map +1 -0
- package/dist/core/modules/better-auth/core-better-auth-email-verification.service.d.ts +48 -0
- package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js +241 -0
- package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js.map +1 -0
- package/dist/core/modules/better-auth/core-better-auth-models.d.ts +2 -1
- package/dist/core/modules/better-auth/core-better-auth-models.js +8 -4
- package/dist/core/modules/better-auth/core-better-auth-models.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-signup-validator.service.d.ts +18 -0
- package/dist/core/modules/better-auth/core-better-auth-signup-validator.service.js +82 -0
- package/dist/core/modules/better-auth/core-better-auth-signup-validator.service.js.map +1 -0
- package/dist/core/modules/better-auth/core-better-auth-token.helper.d.ts +16 -0
- package/dist/core/modules/better-auth/core-better-auth-token.helper.js +66 -0
- package/dist/core/modules/better-auth/core-better-auth-token.helper.js.map +1 -0
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.d.ts +0 -1
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.js +15 -8
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-web.helper.d.ts +3 -3
- package/dist/core/modules/better-auth/core-better-auth-web.helper.js +64 -44
- package/dist/core/modules/better-auth/core-better-auth-web.helper.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.controller.d.ts +13 -1
- package/dist/core/modules/better-auth/core-better-auth.controller.js +108 -49
- package/dist/core/modules/better-auth/core-better-auth.controller.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.middleware.d.ts +0 -1
- package/dist/core/modules/better-auth/core-better-auth.middleware.js +57 -39
- package/dist/core/modules/better-auth/core-better-auth.middleware.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.module.d.ts +6 -0
- package/dist/core/modules/better-auth/core-better-auth.module.js +129 -24
- package/dist/core/modules/better-auth/core-better-auth.module.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.resolver.d.ts +12 -5
- package/dist/core/modules/better-auth/core-better-auth.resolver.js +64 -17
- package/dist/core/modules/better-auth/core-better-auth.resolver.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.service.d.ts +4 -1
- package/dist/core/modules/better-auth/core-better-auth.service.js +143 -23
- package/dist/core/modules/better-auth/core-better-auth.service.js.map +1 -1
- package/dist/core/modules/better-auth/index.d.ts +4 -0
- package/dist/core/modules/better-auth/index.js +4 -0
- package/dist/core/modules/better-auth/index.js.map +1 -1
- package/dist/core/modules/error-code/error-codes.d.ts +45 -0
- package/dist/core/modules/error-code/error-codes.js +40 -0
- package/dist/core/modules/error-code/error-codes.js.map +1 -1
- package/dist/core/modules/user/core-user.model.d.ts +1 -0
- package/dist/core/modules/user/core-user.model.js +11 -0
- package/dist/core/modules/user/core-user.model.js.map +1 -1
- package/dist/server/modules/better-auth/better-auth.controller.d.ts +3 -1
- package/dist/server/modules/better-auth/better-auth.controller.js +12 -3
- package/dist/server/modules/better-auth/better-auth.controller.js.map +1 -1
- package/dist/server/modules/better-auth/better-auth.resolver.d.ts +7 -3
- package/dist/server/modules/better-auth/better-auth.resolver.js +16 -6
- package/dist/server/modules/better-auth/better-auth.resolver.js.map +1 -1
- package/dist/server/modules/error-code/error-codes.d.ts +5 -0
- package/dist/server/modules/user/user.model.d.ts +5 -0
- package/dist/templates/email-verification-de.ejs +78 -0
- package/dist/templates/email-verification-en.ejs +78 -0
- package/dist/test/test.helper.d.ts +4 -0
- package/dist/test/test.helper.js +54 -1
- package/dist/test/test.helper.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/src/config.env.ts +2 -0
- package/src/core/common/interfaces/server-options.interface.ts +240 -0
- package/src/core/modules/auth/core-auth.controller.ts +2 -2
- package/src/core/modules/auth/core-auth.resolver.ts +2 -2
- package/src/core/modules/better-auth/INTEGRATION-CHECKLIST.md +113 -0
- package/src/core/modules/better-auth/README.md +72 -7
- package/src/core/modules/better-auth/better-auth-token.service.ts +5 -8
- package/src/core/modules/better-auth/better-auth.config.ts +282 -29
- package/src/core/modules/better-auth/better-auth.resolver.ts +16 -5
- package/src/core/modules/better-auth/core-better-auth-api.middleware.ts +100 -22
- package/src/core/modules/better-auth/core-better-auth-auth.model.ts +10 -0
- package/src/core/modules/better-auth/core-better-auth-cookie.helper.ts +323 -0
- package/src/core/modules/better-auth/core-better-auth-email-verification.service.ts +433 -0
- package/src/core/modules/better-auth/core-better-auth-models.ts +6 -3
- package/src/core/modules/better-auth/core-better-auth-signup-validator.service.ts +178 -0
- package/src/core/modules/better-auth/core-better-auth-token.helper.ts +200 -0
- package/src/core/modules/better-auth/core-better-auth-user.mapper.ts +18 -14
- package/src/core/modules/better-auth/core-better-auth-web.helper.ts +119 -69
- package/src/core/modules/better-auth/core-better-auth.controller.ts +197 -84
- package/src/core/modules/better-auth/core-better-auth.middleware.ts +93 -64
- package/src/core/modules/better-auth/core-better-auth.module.ts +215 -38
- package/src/core/modules/better-auth/core-better-auth.resolver.ts +140 -20
- package/src/core/modules/better-auth/core-better-auth.service.ts +210 -32
- package/src/core/modules/better-auth/index.ts +4 -0
- package/src/core/modules/error-code/error-codes.ts +45 -0
- package/src/core/modules/user/core-user.model.ts +15 -0
- package/src/server/modules/better-auth/better-auth.controller.ts +6 -2
- package/src/server/modules/better-auth/better-auth.resolver.ts +16 -5
- package/src/templates/email-verification-de.ejs +78 -0
- package/src/templates/email-verification-en.ejs +78 -0
- package/src/test/README.md +190 -0
- package/src/test/test.helper.ts +82 -1
|
@@ -4,6 +4,8 @@ import { betterAuth, BetterAuthPlugin } from 'better-auth';
|
|
|
4
4
|
import { mongodbAdapter } from 'better-auth/adapters/mongodb';
|
|
5
5
|
import { jwt, twoFactor } from 'better-auth/plugins';
|
|
6
6
|
import * as crypto from 'crypto';
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
7
9
|
|
|
8
10
|
import { IBetterAuth } from '../../common/interfaces/server-options.interface';
|
|
9
11
|
|
|
@@ -27,13 +29,16 @@ function generateSecureSecret(): string {
|
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
31
|
* Cached auto-generated secret for the current server instance.
|
|
30
|
-
* Generated once at module load to ensure consistency within a single run.
|
|
32
|
+
* Generated once at a module load to ensure consistency within a single run.
|
|
31
33
|
*/
|
|
32
34
|
let cachedAutoGeneratedSecret: null | string = null;
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
|
-
*
|
|
37
|
+
* Cached project app name for the current server instance.
|
|
38
|
+
* Read once from package.json to avoid repeated file reads.
|
|
36
39
|
*/
|
|
40
|
+
let cachedProjectAppName: null | string = null;
|
|
41
|
+
|
|
37
42
|
export interface CreateBetterAuthOptions {
|
|
38
43
|
/**
|
|
39
44
|
* Better-auth configuration from server options
|
|
@@ -63,6 +68,18 @@ export interface CreateBetterAuthOptions {
|
|
|
63
68
|
*/
|
|
64
69
|
fallbackSecrets?: (string | undefined)[];
|
|
65
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Callback for when email is verified (to sync verifiedAt)
|
|
73
|
+
* Injected from CoreBetterAuthModule
|
|
74
|
+
*/
|
|
75
|
+
onEmailVerified?: OnEmailVerifiedCallback;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Callback for sending verification email
|
|
79
|
+
* Injected from CoreBetterAuthModule to use NestJS services
|
|
80
|
+
*/
|
|
81
|
+
sendVerificationEmail?: SendVerificationEmailCallback;
|
|
82
|
+
|
|
66
83
|
/**
|
|
67
84
|
* Server-level app/frontend URL (from IServerOptions.appUrl).
|
|
68
85
|
* Used for Passkey origin and CORS trustedOrigins.
|
|
@@ -82,6 +99,25 @@ export interface CreateBetterAuthOptions {
|
|
|
82
99
|
serverEnv?: string;
|
|
83
100
|
}
|
|
84
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Callback for when email is verified
|
|
104
|
+
* Injected from CoreBetterAuthModule to sync verifiedAt
|
|
105
|
+
*/
|
|
106
|
+
export type OnEmailVerifiedCallback = (userId: string) => Promise<void>;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Options for creating a better-auth instance
|
|
110
|
+
*/
|
|
111
|
+
/**
|
|
112
|
+
* Callback for sending verification email
|
|
113
|
+
* Injected from CoreBetterAuthModule to use NestJS services
|
|
114
|
+
*/
|
|
115
|
+
export type SendVerificationEmailCallback = (options: {
|
|
116
|
+
token: string;
|
|
117
|
+
url: string;
|
|
118
|
+
user: { email: string; id: string; name?: null | string };
|
|
119
|
+
}) => Promise<void>;
|
|
120
|
+
|
|
85
121
|
/**
|
|
86
122
|
* Better-Auth field type definition
|
|
87
123
|
* Matches the DBFieldType from better-auth
|
|
@@ -152,6 +188,7 @@ interface UserFieldConfig {
|
|
|
152
188
|
*/
|
|
153
189
|
interface ValidationResult {
|
|
154
190
|
errors: string[];
|
|
191
|
+
resolvedSecret?: string;
|
|
155
192
|
valid: boolean;
|
|
156
193
|
warnings: string[];
|
|
157
194
|
}
|
|
@@ -165,7 +202,7 @@ interface ValidationResult {
|
|
|
165
202
|
*/
|
|
166
203
|
export function createBetterAuthInstance(options: CreateBetterAuthOptions): BetterAuthInstance | null {
|
|
167
204
|
const logger = new Logger('BetterAuthConfig');
|
|
168
|
-
const { config, db, fallbackSecrets } = options;
|
|
205
|
+
const { config, db, fallbackSecrets, onEmailVerified, sendVerificationEmail, serverEnv } = options;
|
|
169
206
|
|
|
170
207
|
// Return null only if better-auth is explicitly disabled
|
|
171
208
|
// BetterAuth is enabled by default (zero-config)
|
|
@@ -184,7 +221,7 @@ export function createBetterAuthInstance(options: CreateBetterAuthOptions): Bett
|
|
|
184
221
|
// Normalize Passkey configuration with auto-detection from resolved URLs
|
|
185
222
|
// This must happen BEFORE validation to allow graceful degradation
|
|
186
223
|
// Passkey is now AUTO-ACTIVATED by default (not opt-in)
|
|
187
|
-
const passkeyNormalization = normalizePasskeyConfig(config, resolvedUrls);
|
|
224
|
+
const passkeyNormalization = normalizePasskeyConfig(config, { resolvedUrls, serverEnv });
|
|
188
225
|
|
|
189
226
|
// Log passkey normalization warnings (info about auto-detection or disabled status)
|
|
190
227
|
for (const warning of passkeyNormalization.warnings) {
|
|
@@ -192,7 +229,7 @@ export function createBetterAuthInstance(options: CreateBetterAuthOptions): Bett
|
|
|
192
229
|
}
|
|
193
230
|
|
|
194
231
|
// Validate configuration (with fallback secrets for backwards compatibility)
|
|
195
|
-
const validation = validateConfig(config, fallbackSecrets, passkeyNormalization);
|
|
232
|
+
const validation = validateConfig(config, { fallbackSecrets, passkeyNormalization });
|
|
196
233
|
|
|
197
234
|
// Log warnings
|
|
198
235
|
for (const warning of validation.warnings) {
|
|
@@ -205,15 +242,26 @@ export function createBetterAuthInstance(options: CreateBetterAuthOptions): Bett
|
|
|
205
242
|
}
|
|
206
243
|
|
|
207
244
|
// Build configuration components (pass normalized passkey config and resolved URLs)
|
|
208
|
-
const plugins = buildPlugins(config, passkeyNormalization);
|
|
245
|
+
const plugins = buildPlugins(config, { passkeyNormalization, serverEnv });
|
|
209
246
|
const socialProviders = buildSocialProviders(config);
|
|
210
|
-
const trustedOrigins = buildTrustedOrigins(config, passkeyNormalization, resolvedUrls);
|
|
247
|
+
const trustedOrigins = buildTrustedOrigins(config, { passkeyNormalization, resolvedUrls });
|
|
211
248
|
const additionalFields = buildUserFields(config);
|
|
212
249
|
|
|
250
|
+
// Build email verification configuration
|
|
251
|
+
const emailVerificationConfig = buildEmailVerificationConfig(config, { onEmailVerified, sendVerificationEmail });
|
|
252
|
+
|
|
213
253
|
// Build the base Better-Auth configuration
|
|
214
254
|
// Use resolved baseUrl (with local defaults) or fallback
|
|
255
|
+
const basePath = config.basePath || '/iam';
|
|
256
|
+
// Cookie prefix derived from basePath (e.g., '/iam' → 'iam')
|
|
257
|
+
// This ensures Better-Auth looks for cookies like 'iam.session_token' instead of 'better-auth.session_token'
|
|
258
|
+
const cookiePrefix = basePath.replace(/^\//, '').replace(/\//g, '.');
|
|
259
|
+
|
|
215
260
|
const betterAuthConfig: Record<string, unknown> = {
|
|
216
|
-
|
|
261
|
+
advanced: {
|
|
262
|
+
cookiePrefix,
|
|
263
|
+
},
|
|
264
|
+
basePath,
|
|
217
265
|
baseURL: resolvedUrls.baseUrl || config.baseUrl || 'http://localhost:3000',
|
|
218
266
|
database: mongodbAdapter(db),
|
|
219
267
|
// Enable email/password authentication by default (required by Better-Auth 1.x)
|
|
@@ -222,7 +270,7 @@ export function createBetterAuthInstance(options: CreateBetterAuthOptions): Bett
|
|
|
222
270
|
enabled: config.emailAndPassword?.enabled !== false,
|
|
223
271
|
},
|
|
224
272
|
plugins,
|
|
225
|
-
secret: config.secret,
|
|
273
|
+
secret: validation.resolvedSecret || config.secret,
|
|
226
274
|
socialProviders,
|
|
227
275
|
user: {
|
|
228
276
|
additionalFields,
|
|
@@ -230,6 +278,11 @@ export function createBetterAuthInstance(options: CreateBetterAuthOptions): Bett
|
|
|
230
278
|
},
|
|
231
279
|
};
|
|
232
280
|
|
|
281
|
+
// Add email verification config if enabled
|
|
282
|
+
if (emailVerificationConfig) {
|
|
283
|
+
betterAuthConfig.emailVerification = emailVerificationConfig;
|
|
284
|
+
}
|
|
285
|
+
|
|
233
286
|
// Only add trustedOrigins if explicitly configured
|
|
234
287
|
// When undefined, Better-Auth uses its default CORS behavior (allows all origins)
|
|
235
288
|
if (trustedOrigins) {
|
|
@@ -246,6 +299,99 @@ export function createBetterAuthInstance(options: CreateBetterAuthOptions): Bett
|
|
|
246
299
|
return betterAuth(finalConfig as any);
|
|
247
300
|
}
|
|
248
301
|
|
|
302
|
+
/**
|
|
303
|
+
* Formats a package name to a human-readable display name.
|
|
304
|
+
* Converts kebab-case and snake_case to Title Case.
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* formatProjectName('my-awesome-app') // → 'My Awesome App'
|
|
308
|
+
* formatProjectName('@org/my-app') // → 'My App'
|
|
309
|
+
* formatProjectName('nest_server_starter') // → 'Nest Server Starter'
|
|
310
|
+
*/
|
|
311
|
+
export function formatProjectName(name: string): string {
|
|
312
|
+
// Remove scope (e.g., '@org/my-app' → 'my-app')
|
|
313
|
+
let formatted = name.replace(/^@[^/]+\//, '');
|
|
314
|
+
|
|
315
|
+
// Split by hyphens and underscores, capitalize each word
|
|
316
|
+
formatted = formatted
|
|
317
|
+
.split(/[-_]/)
|
|
318
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
319
|
+
.join(' ');
|
|
320
|
+
|
|
321
|
+
return formatted;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Builds the email verification configuration for Better-Auth.
|
|
326
|
+
*
|
|
327
|
+
* Email verification is enabled by default unless explicitly disabled.
|
|
328
|
+
* Uses the sendVerificationEmail callback to send emails via NestJS services.
|
|
329
|
+
*
|
|
330
|
+
* @param config - Better-auth configuration
|
|
331
|
+
* @param callbacks - Optional callbacks for email verification lifecycle
|
|
332
|
+
* @returns Email verification config for Better-Auth or null if disabled
|
|
333
|
+
*/
|
|
334
|
+
function buildEmailVerificationConfig(
|
|
335
|
+
config: IBetterAuth,
|
|
336
|
+
callbacks?: {
|
|
337
|
+
onEmailVerified?: OnEmailVerifiedCallback;
|
|
338
|
+
sendVerificationEmail?: SendVerificationEmailCallback;
|
|
339
|
+
},
|
|
340
|
+
): null | Record<string, unknown> {
|
|
341
|
+
const { onEmailVerified, sendVerificationEmail } = callbacks ?? {};
|
|
342
|
+
// Email verification is enabled by default (zero-config):
|
|
343
|
+
// - undefined/null: enabled with defaults
|
|
344
|
+
// - true: enabled with defaults
|
|
345
|
+
// - false: explicitly disabled
|
|
346
|
+
// - { ... }: enabled with custom settings (unless enabled: false)
|
|
347
|
+
const emailVerificationConfig = config.emailVerification;
|
|
348
|
+
|
|
349
|
+
if (emailVerificationConfig === false) {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (typeof emailVerificationConfig === 'object' && emailVerificationConfig.enabled === false) {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Get configuration values (with defaults)
|
|
358
|
+
const configObj = typeof emailVerificationConfig === 'object' ? emailVerificationConfig : {};
|
|
359
|
+
const expiresIn = configObj.expiresIn ?? 86400; // 24 hours
|
|
360
|
+
const autoSignInAfterVerification = configObj.autoSignInAfterVerification ?? true;
|
|
361
|
+
|
|
362
|
+
const result: Record<string, unknown> = {
|
|
363
|
+
autoSignInAfterVerification,
|
|
364
|
+
expiresIn,
|
|
365
|
+
// Also send on sign-in if user is not verified
|
|
366
|
+
sendOnSignIn: true,
|
|
367
|
+
// Send verification email on sign-up by default
|
|
368
|
+
sendOnSignUp: true,
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// Add sendVerificationEmail callback if provided
|
|
372
|
+
if (sendVerificationEmail) {
|
|
373
|
+
result.sendVerificationEmail = async (
|
|
374
|
+
data: { token: string; url: string; user: { email: string; id: string; name?: null | string } },
|
|
375
|
+
_request?: Request,
|
|
376
|
+
) => {
|
|
377
|
+
// Don't await to prevent timing attacks (as recommended by Better-Auth docs)
|
|
378
|
+
|
|
379
|
+
sendVerificationEmail(data);
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Add callback for when email is verified (to sync verifiedAt)
|
|
384
|
+
if (onEmailVerified) {
|
|
385
|
+
result.afterEmailVerification = async (user: string | { id: string }, _request?: Request) => {
|
|
386
|
+
// Better-Auth passes a user object { id: string } to afterEmailVerification
|
|
387
|
+
const userId = typeof user === 'string' ? user : user.id;
|
|
388
|
+
await onEmailVerified(userId);
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return result;
|
|
393
|
+
}
|
|
394
|
+
|
|
249
395
|
/**
|
|
250
396
|
* Builds the plugins array based on configuration.
|
|
251
397
|
* Merges built-in plugins (jwt, twoFactor, passkey) with custom plugins from config.
|
|
@@ -257,9 +403,15 @@ export function createBetterAuthInstance(options: CreateBetterAuthOptions): Bett
|
|
|
257
403
|
* - `undefined`: Disabled (default)
|
|
258
404
|
*
|
|
259
405
|
* @param config - Better-auth configuration
|
|
260
|
-
* @param
|
|
406
|
+
* @param options - Additional options
|
|
407
|
+
* @param options.passkeyNormalization - Normalized passkey configuration from normalizePasskeyConfig()
|
|
408
|
+
* @param options.serverEnv - Server environment for auto-detected app name suffix
|
|
261
409
|
*/
|
|
262
|
-
function buildPlugins(
|
|
410
|
+
function buildPlugins(
|
|
411
|
+
config: IBetterAuth,
|
|
412
|
+
options: { passkeyNormalization: PasskeyNormalizationResult; serverEnv?: string },
|
|
413
|
+
): BetterAuthPlugin[] {
|
|
414
|
+
const { passkeyNormalization, serverEnv } = options;
|
|
263
415
|
const plugins: BetterAuthPlugin[] = [];
|
|
264
416
|
|
|
265
417
|
// JWT Plugin for API client compatibility
|
|
@@ -285,7 +437,8 @@ function buildPlugins(config: IBetterAuth, passkeyNormalization: PasskeyNormaliz
|
|
|
285
437
|
const twoFactorConfig = typeof config.twoFactor === 'object' ? config.twoFactor : {};
|
|
286
438
|
plugins.push(
|
|
287
439
|
twoFactor({
|
|
288
|
-
issuer:
|
|
440
|
+
// issuer: Use explicit config, or auto-detect from package.json with environment suffix
|
|
441
|
+
issuer: twoFactorConfig.appName || getProjectAppName(serverEnv),
|
|
289
442
|
}),
|
|
290
443
|
);
|
|
291
444
|
}
|
|
@@ -363,14 +516,16 @@ function buildSocialProviders(config: IBetterAuth): Record<string, SocialProvide
|
|
|
363
516
|
* - Otherwise → return undefined (allows all origins via Better-Auth's default)
|
|
364
517
|
*
|
|
365
518
|
* @param config - Better-auth configuration
|
|
366
|
-
* @param
|
|
367
|
-
* @param resolvedUrls - Resolved URLs from resolveUrls()
|
|
519
|
+
* @param options - Passkey normalization and resolved URLs context
|
|
368
520
|
*/
|
|
369
521
|
function buildTrustedOrigins(
|
|
370
522
|
config: IBetterAuth,
|
|
371
|
-
|
|
372
|
-
|
|
523
|
+
options: {
|
|
524
|
+
passkeyNormalization: PasskeyNormalizationResult;
|
|
525
|
+
resolvedUrls: ResolvedUrls;
|
|
526
|
+
},
|
|
373
527
|
): string[] | undefined {
|
|
528
|
+
const { passkeyNormalization, resolvedUrls } = options;
|
|
374
529
|
// If trustedOrigins is explicitly configured, use it
|
|
375
530
|
if (config.trustedOrigins?.length) {
|
|
376
531
|
return config.trustedOrigins;
|
|
@@ -419,6 +574,12 @@ function buildUserFields(config: IBetterAuth): Record<string, UserFieldConfig> {
|
|
|
419
574
|
fieldName: 'roles',
|
|
420
575
|
type: 'string[]',
|
|
421
576
|
},
|
|
577
|
+
// Track when terms and privacy policy were accepted (for sign-up checks)
|
|
578
|
+
termsAndPrivacyAcceptedAt: {
|
|
579
|
+
defaultValue: null,
|
|
580
|
+
fieldName: 'termsAndPrivacyAcceptedAt',
|
|
581
|
+
type: 'date',
|
|
582
|
+
},
|
|
422
583
|
twoFactorEnabled: {
|
|
423
584
|
defaultValue: false,
|
|
424
585
|
fieldName: 'twoFactorEnabled',
|
|
@@ -429,6 +590,12 @@ function buildUserFields(config: IBetterAuth): Record<string, UserFieldConfig> {
|
|
|
429
590
|
fieldName: 'verified',
|
|
430
591
|
type: 'boolean',
|
|
431
592
|
},
|
|
593
|
+
// Track when email was verified (synced from Better-Auth)
|
|
594
|
+
verifiedAt: {
|
|
595
|
+
defaultValue: null,
|
|
596
|
+
fieldName: 'verifiedAt',
|
|
597
|
+
type: 'date',
|
|
598
|
+
},
|
|
432
599
|
};
|
|
433
600
|
|
|
434
601
|
// Merge with custom additional fields from configuration
|
|
@@ -447,6 +614,28 @@ function buildUserFields(config: IBetterAuth): Record<string, UserFieldConfig> {
|
|
|
447
614
|
return coreFields;
|
|
448
615
|
}
|
|
449
616
|
|
|
617
|
+
/**
|
|
618
|
+
* Formats the environment name as a suffix.
|
|
619
|
+
* Only adds suffix for development/test environments.
|
|
620
|
+
*
|
|
621
|
+
* @example
|
|
622
|
+
* formatEnvSuffix('local') // → '(Local)'
|
|
623
|
+
* formatEnvSuffix('development') // → '(Development)'
|
|
624
|
+
* formatEnvSuffix('test') // → '(Test)'
|
|
625
|
+
* formatEnvSuffix('production') // → '' (no suffix for production)
|
|
626
|
+
* formatEnvSuffix(undefined) // → ''
|
|
627
|
+
*/
|
|
628
|
+
function formatEnvSuffix(env?: string): string {
|
|
629
|
+
if (!env) return '';
|
|
630
|
+
|
|
631
|
+
// Don't add suffix for production
|
|
632
|
+
if (env === 'production' || env === 'prod') return '';
|
|
633
|
+
|
|
634
|
+
// Capitalize first letter
|
|
635
|
+
const formatted = env.charAt(0).toUpperCase() + env.slice(1).toLowerCase();
|
|
636
|
+
return `(${formatted})`;
|
|
637
|
+
}
|
|
638
|
+
|
|
450
639
|
/**
|
|
451
640
|
* Gets or generates the fallback secret for development.
|
|
452
641
|
* The secret is cached to ensure consistency during the server's lifetime.
|
|
@@ -458,6 +647,37 @@ function getAutoGeneratedSecret(): string {
|
|
|
458
647
|
return cachedAutoGeneratedSecret;
|
|
459
648
|
}
|
|
460
649
|
|
|
650
|
+
/**
|
|
651
|
+
* Gets the project's app name from package.json.
|
|
652
|
+
*
|
|
653
|
+
* This function reads the `name` field from the project's package.json
|
|
654
|
+
* and formats it for display (converts a kebab-case to Title Case).
|
|
655
|
+
*
|
|
656
|
+
* Used as default for:
|
|
657
|
+
* - `betterAuth.passkey.rpName` (displayed in browser Passkey prompts)
|
|
658
|
+
* - `betterAuth.twoFactor.appName` (displayed in authenticator apps)
|
|
659
|
+
*
|
|
660
|
+
* @param serverEnv - Optional server environment to append as suffix (e.g., 'local' → '(Local)')
|
|
661
|
+
* @returns The formatted project name, or 'Nest Server' as fallback
|
|
662
|
+
*
|
|
663
|
+
* @example
|
|
664
|
+
* // package.json: { "name": "my-awesome-app" }
|
|
665
|
+
* getProjectAppName() // → 'My Awesome App'
|
|
666
|
+
* getProjectAppName('local') // → 'My Awesome App (Local)'
|
|
667
|
+
* getProjectAppName('test') // → 'My Awesome App (Test)'
|
|
668
|
+
*/
|
|
669
|
+
function getProjectAppName(serverEnv?: string): string {
|
|
670
|
+
// Return cached value if available (without env suffix, will be added after)
|
|
671
|
+
if (cachedProjectAppName === null) {
|
|
672
|
+
cachedProjectAppName = readProjectNameFromPackageJson();
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Add environment suffix for non-production environments
|
|
676
|
+
// This helps developers distinguish Passkeys when running multiple projects locally
|
|
677
|
+
const envSuffix = formatEnvSuffix(serverEnv);
|
|
678
|
+
return envSuffix ? `${cachedProjectAppName} ${envSuffix}` : cachedProjectAppName;
|
|
679
|
+
}
|
|
680
|
+
|
|
461
681
|
/**
|
|
462
682
|
* Checks if a secret has valid minimum length (32 characters)
|
|
463
683
|
*/
|
|
@@ -477,6 +697,28 @@ function isValidUrl(url: string): boolean {
|
|
|
477
697
|
}
|
|
478
698
|
}
|
|
479
699
|
|
|
700
|
+
/**
|
|
701
|
+
* Reads the project name from package.json in the current working directory.
|
|
702
|
+
* Falls back to 'Nest Server' if package.json cannot be read or has no name.
|
|
703
|
+
*/
|
|
704
|
+
function readProjectNameFromPackageJson(): string {
|
|
705
|
+
const fallback = 'Nest Server';
|
|
706
|
+
|
|
707
|
+
try {
|
|
708
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
709
|
+
const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf-8');
|
|
710
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
711
|
+
|
|
712
|
+
if (packageJson.name && typeof packageJson.name === 'string') {
|
|
713
|
+
return formatProjectName(packageJson.name);
|
|
714
|
+
}
|
|
715
|
+
} catch {
|
|
716
|
+
// Ignore errors - use fallback
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return fallback;
|
|
720
|
+
}
|
|
721
|
+
|
|
480
722
|
/**
|
|
481
723
|
* Default URLs for local/test environments (local, ci, e2e)
|
|
482
724
|
* These environments typically run on localhost and don't have a deployed domain.
|
|
@@ -598,10 +840,16 @@ function extractRpIdFromUrl(url: string): string {
|
|
|
598
840
|
* - `trustedOrigins`: from config.trustedOrigins > [resolvedUrls.appUrl]
|
|
599
841
|
*
|
|
600
842
|
* @param config - Better-auth configuration
|
|
601
|
-
* @param
|
|
843
|
+
* @param options - Additional options
|
|
844
|
+
* @param options.resolvedUrls - Resolved URLs from resolveUrls()
|
|
845
|
+
* @param options.serverEnv - Server environment for auto-detected app name suffix
|
|
602
846
|
* @returns Normalization result with enabled status, config, and warnings
|
|
603
847
|
*/
|
|
604
|
-
function normalizePasskeyConfig(
|
|
848
|
+
function normalizePasskeyConfig(
|
|
849
|
+
config: IBetterAuth,
|
|
850
|
+
options: { resolvedUrls: ResolvedUrls; serverEnv?: string },
|
|
851
|
+
): PasskeyNormalizationResult {
|
|
852
|
+
const { resolvedUrls, serverEnv } = options;
|
|
605
853
|
const warnings: string[] = [];
|
|
606
854
|
|
|
607
855
|
// Check if Passkey is explicitly DISABLED
|
|
@@ -657,10 +905,11 @@ function normalizePasskeyConfig(config: IBetterAuth, resolvedUrls: ResolvedUrls)
|
|
|
657
905
|
}
|
|
658
906
|
|
|
659
907
|
// Build normalized config
|
|
908
|
+
// rpName: Use explicit config, or auto-detect from package.json with environment suffix
|
|
660
909
|
const normalizedConfig: NormalizedPasskeyConfig = {
|
|
661
910
|
origin: finalOrigin,
|
|
662
911
|
rpId: finalRpId,
|
|
663
|
-
rpName: rawConfig.rpName ||
|
|
912
|
+
rpName: rawConfig.rpName || getProjectAppName(serverEnv),
|
|
664
913
|
};
|
|
665
914
|
|
|
666
915
|
// Copy optional fields from explicit config
|
|
@@ -744,37 +993,40 @@ function resolveUrls(options: CreateBetterAuthOptions): ResolvedUrls {
|
|
|
744
993
|
* 3. Auto-generated secure secret (with warning)
|
|
745
994
|
*
|
|
746
995
|
* @param config - Better-auth configuration
|
|
747
|
-
* @param
|
|
748
|
-
* @param passkeyNormalization - Result of passkey normalization (handles graceful degradation)
|
|
996
|
+
* @param options - Optional validation context (fallback secrets, passkey normalization)
|
|
749
997
|
*/
|
|
750
998
|
function validateConfig(
|
|
751
999
|
config: IBetterAuth,
|
|
752
|
-
|
|
753
|
-
|
|
1000
|
+
options?: {
|
|
1001
|
+
fallbackSecrets?: (string | undefined)[];
|
|
1002
|
+
passkeyNormalization?: PasskeyNormalizationResult;
|
|
1003
|
+
},
|
|
754
1004
|
): ValidationResult {
|
|
1005
|
+
const { fallbackSecrets, passkeyNormalization } = options ?? {};
|
|
755
1006
|
const errors: string[] = [];
|
|
756
1007
|
const warnings: string[] = [];
|
|
757
1008
|
|
|
758
1009
|
// Track secret source for appropriate messaging
|
|
759
1010
|
let secretSource: 'auto-generated' | 'explicit' | 'fallback' = 'explicit';
|
|
1011
|
+
let resolvedSecret = config.secret;
|
|
760
1012
|
|
|
761
|
-
// Resolve secret with fallback chain
|
|
762
|
-
if (!
|
|
1013
|
+
// Resolve secret with fallback chain (without mutating the config object)
|
|
1014
|
+
if (!resolvedSecret || resolvedSecret.trim() === '') {
|
|
763
1015
|
// Try fallback secrets in order
|
|
764
1016
|
const validFallback = fallbackSecrets?.find((secret) => secret && isValidSecretLength(secret));
|
|
765
1017
|
|
|
766
1018
|
if (validFallback) {
|
|
767
|
-
|
|
1019
|
+
resolvedSecret = validFallback;
|
|
768
1020
|
secretSource = 'fallback';
|
|
769
1021
|
} else {
|
|
770
1022
|
// Last resort: auto-generate
|
|
771
|
-
|
|
1023
|
+
resolvedSecret = getAutoGeneratedSecret();
|
|
772
1024
|
secretSource = 'auto-generated';
|
|
773
1025
|
}
|
|
774
1026
|
}
|
|
775
1027
|
|
|
776
1028
|
// Validate the resolved secret
|
|
777
|
-
const secretValidation = validateSecret(
|
|
1029
|
+
const secretValidation = validateSecret(resolvedSecret);
|
|
778
1030
|
if (!secretValidation.valid) {
|
|
779
1031
|
errors.push(secretValidation.message!);
|
|
780
1032
|
} else if (secretValidation.message) {
|
|
@@ -853,7 +1105,7 @@ function validateConfig(
|
|
|
853
1105
|
errors.push(`Social provider '${name}' is missing clientSecret`);
|
|
854
1106
|
}
|
|
855
1107
|
} else {
|
|
856
|
-
// No credentials provided but provider is configured and not disabled
|
|
1108
|
+
// No credentials provided, but the provider is configured and not disabled
|
|
857
1109
|
// This is likely a configuration mistake - warn the user
|
|
858
1110
|
warnings.push(
|
|
859
1111
|
`Social provider '${name}' is configured but missing both clientId and clientSecret. ` +
|
|
@@ -866,6 +1118,7 @@ function validateConfig(
|
|
|
866
1118
|
|
|
867
1119
|
return {
|
|
868
1120
|
errors,
|
|
1121
|
+
resolvedSecret,
|
|
869
1122
|
valid: errors.length === 0,
|
|
870
1123
|
warnings,
|
|
871
1124
|
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { Optional } from '@nestjs/common';
|
|
1
2
|
import { Args, Context, Mutation, Query, Resolver } from '@nestjs/graphql';
|
|
2
3
|
import { Request, Response } from 'express';
|
|
3
4
|
|
|
4
5
|
import { Roles } from '../../common/decorators/roles.decorator';
|
|
5
6
|
import { RoleEnum } from '../../common/enums/role.enum';
|
|
6
7
|
import { CoreBetterAuthAuthModel } from './core-better-auth-auth.model';
|
|
8
|
+
import { CoreBetterAuthEmailVerificationService } from './core-better-auth-email-verification.service';
|
|
7
9
|
import {
|
|
8
10
|
CoreBetterAuth2FASetupModel,
|
|
9
11
|
CoreBetterAuthFeaturesModel,
|
|
@@ -11,6 +13,7 @@ import {
|
|
|
11
13
|
CoreBetterAuthPasskeyModel,
|
|
12
14
|
CoreBetterAuthSessionModel,
|
|
13
15
|
} from './core-better-auth-models';
|
|
16
|
+
import { CoreBetterAuthSignUpValidatorService } from './core-better-auth-signup-validator.service';
|
|
14
17
|
import { CoreBetterAuthUserMapper } from './core-better-auth-user.mapper';
|
|
15
18
|
import { CoreBetterAuthResolver } from './core-better-auth.resolver';
|
|
16
19
|
import { CoreBetterAuthService } from './core-better-auth.service';
|
|
@@ -38,8 +41,13 @@ import { CoreBetterAuthService } from './core-better-auth.service';
|
|
|
38
41
|
* super(betterAuthService, userMapper);
|
|
39
42
|
* }
|
|
40
43
|
*
|
|
41
|
-
* override async betterAuthSignUp(
|
|
42
|
-
*
|
|
44
|
+
* override async betterAuthSignUp(
|
|
45
|
+
* email: string,
|
|
46
|
+
* password: string,
|
|
47
|
+
* name?: string,
|
|
48
|
+
* termsAndPrivacyAccepted?: boolean,
|
|
49
|
+
* ) {
|
|
50
|
+
* const result = await super.betterAuthSignUp(email, password, name, termsAndPrivacyAccepted);
|
|
43
51
|
*
|
|
44
52
|
* // Send welcome email after successful sign-up
|
|
45
53
|
* if (result.success && result.user) {
|
|
@@ -57,8 +65,10 @@ export class DefaultBetterAuthResolver extends CoreBetterAuthResolver {
|
|
|
57
65
|
constructor(
|
|
58
66
|
protected override readonly betterAuthService: CoreBetterAuthService,
|
|
59
67
|
protected override readonly userMapper: CoreBetterAuthUserMapper,
|
|
68
|
+
@Optional() protected override readonly signUpValidator?: CoreBetterAuthSignUpValidatorService,
|
|
69
|
+
@Optional() protected override readonly emailVerificationService?: CoreBetterAuthEmailVerificationService,
|
|
60
70
|
) {
|
|
61
|
-
super(betterAuthService, userMapper);
|
|
71
|
+
super(betterAuthService, userMapper, signUpValidator, emailVerificationService);
|
|
62
72
|
}
|
|
63
73
|
|
|
64
74
|
// ===========================================================================
|
|
@@ -106,7 +116,7 @@ export class DefaultBetterAuthResolver extends CoreBetterAuthResolver {
|
|
|
106
116
|
override async betterAuthSignIn(
|
|
107
117
|
@Args('email') email: string,
|
|
108
118
|
@Args('password') password: string,
|
|
109
|
-
@Context() ctx
|
|
119
|
+
@Context() ctx?: { req: Request; res: Response },
|
|
110
120
|
): Promise<CoreBetterAuthAuthModel> {
|
|
111
121
|
return super.betterAuthSignIn(email, password, ctx);
|
|
112
122
|
}
|
|
@@ -119,8 +129,9 @@ export class DefaultBetterAuthResolver extends CoreBetterAuthResolver {
|
|
|
119
129
|
@Args('email') email: string,
|
|
120
130
|
@Args('password') password: string,
|
|
121
131
|
@Args('name', { nullable: true }) name?: string,
|
|
132
|
+
@Args('termsAndPrivacyAccepted', { nullable: true }) termsAndPrivacyAccepted?: boolean,
|
|
122
133
|
): Promise<CoreBetterAuthAuthModel> {
|
|
123
|
-
return super.betterAuthSignUp(email, password, name);
|
|
134
|
+
return super.betterAuthSignUp(email, password, name, termsAndPrivacyAccepted);
|
|
124
135
|
}
|
|
125
136
|
|
|
126
137
|
@Mutation(() => Boolean, { description: 'Sign out via Better-Auth' })
|