@lenne.tech/nest-server 11.7.0 → 11.7.2
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 +17 -1
- package/dist/config.env.js.map +1 -1
- package/dist/core/common/interfaces/server-options.interface.d.ts +35 -15
- package/dist/core/modules/auth/core-auth.controller.d.ts +1 -0
- package/dist/core/modules/auth/core-auth.controller.js +29 -3
- 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 +21 -3
- 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-rate-limiter.service.js +1 -1
- package/dist/core/modules/better-auth/better-auth-rate-limiter.service.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 +395 -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 +29 -10
- package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.middleware.d.ts +1 -0
- package/dist/core/modules/better-auth/better-auth.middleware.js +55 -1
- package/dist/core/modules/better-auth/better-auth.middleware.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.module.d.ts +1 -1
- package/dist/core/modules/better-auth/better-auth.module.js +46 -18
- package/dist/core/modules/better-auth/better-auth.module.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.resolver.js +0 -11
- package/dist/core/modules/better-auth/better-auth.resolver.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.service.d.ts +22 -1
- package/dist/core/modules/better-auth/better-auth.service.js +209 -8
- package/dist/core/modules/better-auth/better-auth.service.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.types.d.ts +2 -0
- 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 +1 -0
- package/dist/core/modules/better-auth/core-better-auth.controller.js +15 -2
- package/dist/core/modules/better-auth/core-better-auth.controller.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.resolver.d.ts +7 -0
- package/dist/core/modules/better-auth/core-better-auth.resolver.js +72 -12
- package/dist/core/modules/better-auth/core-better-auth.resolver.js.map +1 -1
- package/dist/core/modules/better-auth/index.d.ts +1 -0
- package/dist/core/modules/better-auth/index.js +1 -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 +136 -55
- 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.module.d.ts +1 -1
- package/dist/server/modules/better-auth/better-auth.module.js +2 -1
- package/dist/server/modules/better-auth/better-auth.module.js.map +1 -1
- package/dist/server/modules/better-auth/better-auth.resolver.d.ts +5 -0
- package/dist/server/modules/better-auth/better-auth.resolver.js +27 -11
- package/dist/server/modules/better-auth/better-auth.resolver.js.map +1 -1
- package/dist/server/modules/user/user.controller.js +0 -8
- package/dist/server/modules/user/user.controller.js.map +1 -1
- 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/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/config.env.ts +32 -2
- package/src/core/common/interfaces/server-options.interface.ts +304 -58
- package/src/core/modules/auth/core-auth.controller.ts +94 -6
- package/src/core/modules/auth/core-auth.module.ts +15 -1
- package/src/core/modules/auth/core-auth.resolver.ts +71 -3
- 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 +255 -0
- package/src/core/modules/better-auth/README.md +565 -208
- package/src/core/modules/better-auth/better-auth-migration-status.model.ts +73 -0
- package/src/core/modules/better-auth/better-auth-rate-limiter.service.ts +1 -1
- package/src/core/modules/better-auth/better-auth-user.mapper.ts +737 -0
- package/src/core/modules/better-auth/better-auth.config.ts +45 -15
- package/src/core/modules/better-auth/better-auth.middleware.ts +85 -2
- package/src/core/modules/better-auth/better-auth.module.ts +83 -27
- package/src/core/modules/better-auth/better-auth.resolver.ts +0 -11
- package/src/core/modules/better-auth/better-auth.service.ts +367 -12
- package/src/core/modules/better-auth/better-auth.types.ts +16 -0
- package/src/core/modules/better-auth/core-better-auth.controller.ts +44 -3
- package/src/core/modules/better-auth/core-better-auth.resolver.ts +136 -16
- package/src/core/modules/better-auth/index.ts +1 -0
- 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 +264 -76
- package/src/index.ts +5 -0
- package/src/server/modules/auth/auth.resolver.ts +8 -0
- package/src/server/modules/better-auth/better-auth.module.ts +9 -3
- package/src/server/modules/better-auth/better-auth.resolver.ts +18 -11
- package/src/server/modules/user/user.controller.ts +1 -9
- package/src/server/modules/user/user.service.ts +4 -2
|
@@ -168,42 +168,48 @@ export function createBetterAuthInstance(options: CreateBetterAuthOptions): Bett
|
|
|
168
168
|
* Builds the plugins array based on configuration.
|
|
169
169
|
* Merges built-in plugins (jwt, twoFactor, passkey) with custom plugins from config.
|
|
170
170
|
*
|
|
171
|
-
* Plugins
|
|
172
|
-
*
|
|
171
|
+
* Plugins accept both boolean and object configuration:
|
|
172
|
+
* - `true` or `{}`: Enable with defaults
|
|
173
|
+
* - `{ option: value }`: Enable with custom settings
|
|
174
|
+
* - `false` or `{ enabled: false }`: Disable
|
|
175
|
+
* - `undefined`: Disabled (default)
|
|
173
176
|
*/
|
|
174
177
|
function buildPlugins(config: IBetterAuth): BetterAuthPlugin[] {
|
|
175
178
|
const plugins: BetterAuthPlugin[] = [];
|
|
176
179
|
|
|
177
180
|
// JWT Plugin for API client compatibility
|
|
178
|
-
//
|
|
179
|
-
|
|
181
|
+
// JWT is enabled by default unless explicitly disabled (jwt: false or jwt: { enabled: false })
|
|
182
|
+
const jwtExplicitlyDisabled =
|
|
183
|
+
config.jwt === false || (typeof config.jwt === 'object' && config.jwt?.enabled === false);
|
|
184
|
+
if (!jwtExplicitlyDisabled) {
|
|
185
|
+
const jwtConfig = typeof config.jwt === 'object' ? config.jwt : {};
|
|
180
186
|
plugins.push(
|
|
181
187
|
jwt({
|
|
182
188
|
jwt: {
|
|
183
|
-
expirationTime:
|
|
189
|
+
expirationTime: jwtConfig.expiresIn || '15m',
|
|
184
190
|
},
|
|
185
191
|
}),
|
|
186
192
|
);
|
|
187
193
|
}
|
|
188
194
|
|
|
189
195
|
// Two-Factor Authentication Plugin
|
|
190
|
-
|
|
191
|
-
if (
|
|
196
|
+
const twoFactorConfig = getPluginConfig(config.twoFactor);
|
|
197
|
+
if (twoFactorConfig) {
|
|
192
198
|
plugins.push(
|
|
193
199
|
twoFactor({
|
|
194
|
-
issuer:
|
|
200
|
+
issuer: twoFactorConfig.appName || 'Nest Server',
|
|
195
201
|
}),
|
|
196
202
|
);
|
|
197
203
|
}
|
|
198
204
|
|
|
199
205
|
// Passkey/WebAuthn Plugin
|
|
200
|
-
|
|
201
|
-
if (
|
|
206
|
+
const passkeyConfig = getPluginConfig(config.passkey);
|
|
207
|
+
if (passkeyConfig) {
|
|
202
208
|
plugins.push(
|
|
203
209
|
passkey({
|
|
204
|
-
origin:
|
|
205
|
-
rpID:
|
|
206
|
-
rpName:
|
|
210
|
+
origin: passkeyConfig.origin || 'http://localhost:3000',
|
|
211
|
+
rpID: passkeyConfig.rpId || 'localhost',
|
|
212
|
+
rpName: passkeyConfig.rpName || 'Nest Server',
|
|
207
213
|
}),
|
|
208
214
|
);
|
|
209
215
|
}
|
|
@@ -322,6 +328,29 @@ function getAutoGeneratedSecret(): string {
|
|
|
322
328
|
return cachedAutoGeneratedSecret;
|
|
323
329
|
}
|
|
324
330
|
|
|
331
|
+
/**
|
|
332
|
+
* Gets plugin configuration as object, handling boolean shorthand.
|
|
333
|
+
* Returns undefined if disabled, or the config object if enabled.
|
|
334
|
+
*/
|
|
335
|
+
function getPluginConfig<T extends { enabled?: boolean }>(config: boolean | T | undefined): T | undefined {
|
|
336
|
+
if (!isPluginEnabled(config)) return undefined;
|
|
337
|
+
if (typeof config === 'boolean') return {} as T;
|
|
338
|
+
return config;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Checks if a plugin configuration is enabled.
|
|
343
|
+
* Supports both boolean and object configuration:
|
|
344
|
+
* - `true` or `{}` or `{ enabled: true }` → enabled
|
|
345
|
+
* - `false` or `{ enabled: false }` → disabled
|
|
346
|
+
* - `undefined` → disabled
|
|
347
|
+
*/
|
|
348
|
+
function isPluginEnabled<T extends { enabled?: boolean }>(config: boolean | T | undefined): boolean {
|
|
349
|
+
if (config === undefined) return false;
|
|
350
|
+
if (typeof config === 'boolean') return config;
|
|
351
|
+
return config.enabled !== false;
|
|
352
|
+
}
|
|
353
|
+
|
|
325
354
|
/**
|
|
326
355
|
* Checks if a secret has valid minimum length (32 characters)
|
|
327
356
|
*/
|
|
@@ -416,8 +445,9 @@ function validateConfig(config: IBetterAuth, fallbackSecrets?: (string | undefin
|
|
|
416
445
|
}
|
|
417
446
|
|
|
418
447
|
// Validate passkey origin
|
|
419
|
-
|
|
420
|
-
|
|
448
|
+
const passkeyConfig = typeof config.passkey === 'object' ? config.passkey : null;
|
|
449
|
+
if (passkeyConfig?.enabled && passkeyConfig.origin && !isValidUrl(passkeyConfig.origin)) {
|
|
450
|
+
errors.push(`Invalid passkey origin format: ${passkeyConfig.origin}`);
|
|
421
451
|
}
|
|
422
452
|
|
|
423
453
|
// Validate social providers dynamically
|
|
@@ -49,7 +49,7 @@ export class BetterAuthMiddleware implements NestMiddleware {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
try {
|
|
52
|
-
//
|
|
52
|
+
// Strategy 1: Try session-based authentication (cookies)
|
|
53
53
|
const session = await this.getSession(req);
|
|
54
54
|
|
|
55
55
|
if (session?.user) {
|
|
@@ -63,7 +63,69 @@ export class BetterAuthMiddleware implements NestMiddleware {
|
|
|
63
63
|
if (mappedUser) {
|
|
64
64
|
// Attach the mapped user to the request
|
|
65
65
|
// This makes it compatible with @CurrentUser() and RolesGuard
|
|
66
|
-
|
|
66
|
+
// Set _authenticatedViaBetterAuth flag so AuthGuard skips Passport JWT verification
|
|
67
|
+
req.user = { ...mappedUser, _authenticatedViaBetterAuth: true };
|
|
68
|
+
return next();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Strategy 2: Try Authorization header (Bearer token)
|
|
73
|
+
// The token could be a BetterAuth JWT, a Legacy JWT, or a session token
|
|
74
|
+
if (req.headers.authorization) {
|
|
75
|
+
const authHeader = req.headers.authorization;
|
|
76
|
+
const token = authHeader.startsWith('Bearer ') ? authHeader.substring(7) : authHeader;
|
|
77
|
+
const tokenParts = token.split('.').length;
|
|
78
|
+
|
|
79
|
+
// Check if token looks like a JWT (has 3 parts)
|
|
80
|
+
if (tokenParts === 3) {
|
|
81
|
+
// Decode JWT payload to check if it's a Legacy JWT or BetterAuth JWT
|
|
82
|
+
// Legacy JWTs have 'id' claim, BetterAuth JWTs have 'sub' claim
|
|
83
|
+
const isLegacyJwt = this.isLegacyJwt(token);
|
|
84
|
+
if (isLegacyJwt) {
|
|
85
|
+
// Legacy JWT - skip BetterAuth processing, let Passport handle it
|
|
86
|
+
return next();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Try BetterAuth JWT verification
|
|
90
|
+
if (this.betterAuthService.isJwtEnabled()) {
|
|
91
|
+
const jwtPayload = await this.betterAuthService.verifyJwtFromRequest(req);
|
|
92
|
+
|
|
93
|
+
if (jwtPayload?.sub) {
|
|
94
|
+
// JWT payload contains user info - create a session-like user object
|
|
95
|
+
const sessionUser: BetterAuthSessionUser = {
|
|
96
|
+
email: jwtPayload.email || '',
|
|
97
|
+
emailVerified: jwtPayload.emailVerified,
|
|
98
|
+
id: jwtPayload.sub,
|
|
99
|
+
name: jwtPayload.name,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
req.betterAuthUser = sessionUser;
|
|
103
|
+
|
|
104
|
+
// Map the JWT user to our User model with hasRole()
|
|
105
|
+
const mappedUser = await this.userMapper.mapSessionUser(sessionUser);
|
|
106
|
+
|
|
107
|
+
if (mappedUser) {
|
|
108
|
+
req.user = { ...mappedUser, _authenticatedViaBetterAuth: true };
|
|
109
|
+
return next();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// If user is still not set, try session token verification as fallback
|
|
116
|
+
// This handles both non-JWT tokens and JWTs that couldn't be verified
|
|
117
|
+
if (!req.user) {
|
|
118
|
+
const sessionResult = await this.betterAuthService.getSessionByToken(token);
|
|
119
|
+
if (sessionResult?.user) {
|
|
120
|
+
req.betterAuthSession = { session: sessionResult.session, user: sessionResult.user };
|
|
121
|
+
req.betterAuthUser = sessionResult.user;
|
|
122
|
+
|
|
123
|
+
const mappedUser = await this.userMapper.mapSessionUser(sessionResult.user);
|
|
124
|
+
if (mappedUser) {
|
|
125
|
+
req.user = { ...mappedUser, _authenticatedViaBetterAuth: true };
|
|
126
|
+
return next();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
67
129
|
}
|
|
68
130
|
}
|
|
69
131
|
} catch (error) {
|
|
@@ -75,6 +137,27 @@ export class BetterAuthMiddleware implements NestMiddleware {
|
|
|
75
137
|
next();
|
|
76
138
|
}
|
|
77
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Checks if a JWT token is a Legacy Auth JWT (has 'id' claim but no 'sub' claim)
|
|
142
|
+
* Legacy JWTs use 'id' for user ID, BetterAuth JWTs use 'sub'
|
|
143
|
+
*/
|
|
144
|
+
private isLegacyJwt(token: string): boolean {
|
|
145
|
+
try {
|
|
146
|
+
const parts = token.split('.');
|
|
147
|
+
if (parts.length !== 3) return false;
|
|
148
|
+
|
|
149
|
+
// Decode the payload (second part)
|
|
150
|
+
const payloadStr = Buffer.from(parts[1], 'base64url').toString('utf-8');
|
|
151
|
+
const payload = JSON.parse(payloadStr);
|
|
152
|
+
|
|
153
|
+
// Legacy JWT has 'id' claim (and typically 'deviceId', 'tokenId')
|
|
154
|
+
// BetterAuth JWT has 'sub' claim
|
|
155
|
+
return payload.id !== undefined && payload.sub === undefined;
|
|
156
|
+
} catch {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
78
161
|
/**
|
|
79
162
|
* Gets the session from Better-Auth
|
|
80
163
|
*/
|
|
@@ -20,7 +20,7 @@ import { BetterAuthUserMapper } from './better-auth-user.mapper';
|
|
|
20
20
|
import { BetterAuthInstance, createBetterAuthInstance } from './better-auth.config';
|
|
21
21
|
import { BetterAuthMiddleware } from './better-auth.middleware';
|
|
22
22
|
import { BetterAuthResolver } from './better-auth.resolver';
|
|
23
|
-
import { BetterAuthService } from './better-auth.service';
|
|
23
|
+
import { BETTER_AUTH_CONFIG, BetterAuthService } from './better-auth.service';
|
|
24
24
|
import { CoreBetterAuthController } from './core-better-auth.controller';
|
|
25
25
|
import { CoreBetterAuthResolver } from './core-better-auth.resolver';
|
|
26
26
|
|
|
@@ -34,9 +34,13 @@ export const BETTER_AUTH_INSTANCE = 'BETTER_AUTH_INSTANCE';
|
|
|
34
34
|
*/
|
|
35
35
|
export interface BetterAuthModuleOptions {
|
|
36
36
|
/**
|
|
37
|
-
* Better-auth configuration
|
|
37
|
+
* Better-auth configuration.
|
|
38
|
+
* Accepts:
|
|
39
|
+
* - `true`: Enable with all defaults (including JWT)
|
|
40
|
+
* - `false`: Disable BetterAuth
|
|
41
|
+
* - `{ ... }`: Enable with custom configuration
|
|
38
42
|
*/
|
|
39
|
-
config: IBetterAuth;
|
|
43
|
+
config: boolean | IBetterAuth;
|
|
40
44
|
|
|
41
45
|
/**
|
|
42
46
|
* Custom controller class to use instead of the default CoreBetterAuthController.
|
|
@@ -100,6 +104,20 @@ export interface BetterAuthModuleOptions {
|
|
|
100
104
|
resolver?: Type<CoreBetterAuthResolver>;
|
|
101
105
|
}
|
|
102
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Normalizes betterAuth config from boolean | IBetterAuth to IBetterAuth | null
|
|
109
|
+
* - `true` → `{}` (enabled with defaults)
|
|
110
|
+
* - `false` → `null` (disabled)
|
|
111
|
+
* - `undefined` → `null` (disabled for backward compatibility)
|
|
112
|
+
* - `{ ... }` → `{ ... }` (pass through)
|
|
113
|
+
*/
|
|
114
|
+
function normalizeBetterAuthConfig(config: boolean | IBetterAuth | undefined): IBetterAuth | null {
|
|
115
|
+
if (config === undefined || config === null) return null;
|
|
116
|
+
if (config === true) return {};
|
|
117
|
+
if (config === false) return null;
|
|
118
|
+
return config;
|
|
119
|
+
}
|
|
120
|
+
|
|
103
121
|
/**
|
|
104
122
|
* BetterAuthModule provides integration with the better-auth authentication framework.
|
|
105
123
|
*
|
|
@@ -178,12 +196,12 @@ export class BetterAuthModule implements NestModule, OnModuleInit {
|
|
|
178
196
|
// Apply rate limiting to Better-Auth endpoints only
|
|
179
197
|
if (BetterAuthModule.currentConfig?.rateLimit?.enabled) {
|
|
180
198
|
consumer.apply(BetterAuthRateLimitMiddleware).forRoutes(`${basePath}/*path`);
|
|
181
|
-
BetterAuthModule.logger.
|
|
199
|
+
BetterAuthModule.logger.debug(`Rate limiting middleware registered for ${basePath}/*path endpoints`);
|
|
182
200
|
}
|
|
183
201
|
|
|
184
202
|
// Apply session middleware to all routes
|
|
185
203
|
consumer.apply(BetterAuthMiddleware).forRoutes('(.*)'); // New path-to-regexp syntax for wildcard
|
|
186
|
-
BetterAuthModule.logger.
|
|
204
|
+
BetterAuthModule.logger.debug('BetterAuthMiddleware registered for all routes');
|
|
187
205
|
}
|
|
188
206
|
}
|
|
189
207
|
|
|
@@ -224,7 +242,10 @@ export class BetterAuthModule implements NestModule, OnModuleInit {
|
|
|
224
242
|
* @returns Dynamic module configuration
|
|
225
243
|
*/
|
|
226
244
|
static forRoot(options: BetterAuthModuleOptions): DynamicModule {
|
|
227
|
-
const { config, controller, fallbackSecrets, resolver } = options;
|
|
245
|
+
const { config: rawConfig, controller, fallbackSecrets, resolver } = options;
|
|
246
|
+
|
|
247
|
+
// Normalize config: true → {}, false/undefined → null
|
|
248
|
+
const config = normalizeBetterAuthConfig(rawConfig);
|
|
228
249
|
|
|
229
250
|
// Store config for middleware configuration
|
|
230
251
|
this.currentConfig = config;
|
|
@@ -233,12 +254,11 @@ export class BetterAuthModule implements NestModule, OnModuleInit {
|
|
|
233
254
|
// Store custom resolver if provided
|
|
234
255
|
this.customResolver = resolver || null;
|
|
235
256
|
|
|
236
|
-
// If better-auth is
|
|
257
|
+
// If better-auth is disabled (config is null or enabled: false), return minimal module
|
|
237
258
|
// Note: We don't provide middleware classes when disabled because they depend on BetterAuthService
|
|
238
259
|
// and won't be used anyway (middleware is only applied when enabled)
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
this.logger.debug('BetterAuth is explicitly disabled - skipping initialization');
|
|
260
|
+
if (config === null || config?.enabled === false) {
|
|
261
|
+
this.logger.debug('BetterAuth is disabled - skipping initialization');
|
|
242
262
|
this.betterAuthEnabled = false;
|
|
243
263
|
return {
|
|
244
264
|
exports: [BETTER_AUTH_INSTANCE, BetterAuthService, BetterAuthUserMapper, BetterAuthRateLimiter],
|
|
@@ -285,15 +305,16 @@ export class BetterAuthModule implements NestModule, OnModuleInit {
|
|
|
285
305
|
inject: [ConfigService],
|
|
286
306
|
provide: BETTER_AUTH_INSTANCE,
|
|
287
307
|
useFactory: async (configService: ConfigService) => {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
//
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
// BetterAuth is
|
|
294
|
-
if (config?.enabled === false) {
|
|
295
|
-
this.logger.debug('BetterAuth is
|
|
308
|
+
// Get raw config (can be boolean or object)
|
|
309
|
+
const rawConfig = configService.get<boolean | IBetterAuth>('betterAuth');
|
|
310
|
+
// Normalize: true → {}, false/undefined → null
|
|
311
|
+
const config = normalizeBetterAuthConfig(rawConfig);
|
|
312
|
+
|
|
313
|
+
// BetterAuth is disabled if config is null or enabled: false
|
|
314
|
+
if (config === null || config?.enabled === false) {
|
|
315
|
+
this.logger.debug('BetterAuth is disabled');
|
|
296
316
|
this.betterAuthEnabled = false;
|
|
317
|
+
this.currentConfig = config;
|
|
297
318
|
return null;
|
|
298
319
|
}
|
|
299
320
|
|
|
@@ -315,6 +336,10 @@ export class BetterAuthModule implements NestModule, OnModuleInit {
|
|
|
315
336
|
// with fallback to jwt.secret, jwt.refresh.secret, or auto-generation
|
|
316
337
|
this.authInstance = createBetterAuthInstance({ config, db, fallbackSecrets });
|
|
317
338
|
|
|
339
|
+
// IMPORTANT: Store the config AFTER createBetterAuthInstance mutates it
|
|
340
|
+
// This ensures BetterAuthService has access to the resolved secret (with fallback applied)
|
|
341
|
+
this.currentConfig = config;
|
|
342
|
+
|
|
318
343
|
if (this.authInstance) {
|
|
319
344
|
this.logger.log('BetterAuth initialized successfully');
|
|
320
345
|
this.logEnabledFeatures(config);
|
|
@@ -323,13 +348,22 @@ export class BetterAuthModule implements NestModule, OnModuleInit {
|
|
|
323
348
|
return this.authInstance;
|
|
324
349
|
},
|
|
325
350
|
},
|
|
351
|
+
// Provide the resolved config for BetterAuthService
|
|
352
|
+
{
|
|
353
|
+
provide: BETTER_AUTH_CONFIG,
|
|
354
|
+
useFactory: () => this.currentConfig,
|
|
355
|
+
},
|
|
326
356
|
// BetterAuthService needs to be a factory that explicitly depends on BETTER_AUTH_INSTANCE
|
|
327
357
|
// to ensure proper initialization order
|
|
328
358
|
{
|
|
329
|
-
inject: [BETTER_AUTH_INSTANCE,
|
|
359
|
+
inject: [BETTER_AUTH_INSTANCE, BETTER_AUTH_CONFIG, getConnectionToken()],
|
|
330
360
|
provide: BetterAuthService,
|
|
331
|
-
useFactory: (
|
|
332
|
-
|
|
361
|
+
useFactory: (
|
|
362
|
+
authInstance: BetterAuthInstance | null,
|
|
363
|
+
resolvedConfig: IBetterAuth | null,
|
|
364
|
+
connection: Connection,
|
|
365
|
+
) => {
|
|
366
|
+
return new BetterAuthService(authInstance, connection, resolvedConfig);
|
|
333
367
|
},
|
|
334
368
|
},
|
|
335
369
|
BetterAuthUserMapper,
|
|
@@ -399,6 +433,10 @@ export class BetterAuthModule implements NestModule, OnModuleInit {
|
|
|
399
433
|
this.authInstance = createBetterAuthInstance({ config, db, fallbackSecrets });
|
|
400
434
|
}
|
|
401
435
|
|
|
436
|
+
// IMPORTANT: Store the config AFTER createBetterAuthInstance mutates it
|
|
437
|
+
// This ensures BetterAuthService has access to the resolved secret (with fallback applied)
|
|
438
|
+
this.currentConfig = config;
|
|
439
|
+
|
|
402
440
|
if (this.authInstance && !this.initLogged) {
|
|
403
441
|
this.initLogged = true;
|
|
404
442
|
this.logger.log('BetterAuth initialized');
|
|
@@ -408,13 +446,22 @@ export class BetterAuthModule implements NestModule, OnModuleInit {
|
|
|
408
446
|
return this.authInstance;
|
|
409
447
|
},
|
|
410
448
|
},
|
|
449
|
+
// Provide the resolved config for BetterAuthService
|
|
450
|
+
{
|
|
451
|
+
provide: BETTER_AUTH_CONFIG,
|
|
452
|
+
useFactory: () => this.currentConfig,
|
|
453
|
+
},
|
|
411
454
|
// BetterAuthService needs to be a factory that explicitly depends on BETTER_AUTH_INSTANCE
|
|
412
455
|
// to ensure proper initialization order
|
|
413
456
|
{
|
|
414
|
-
inject: [BETTER_AUTH_INSTANCE,
|
|
457
|
+
inject: [BETTER_AUTH_INSTANCE, BETTER_AUTH_CONFIG, getConnectionToken()],
|
|
415
458
|
provide: BetterAuthService,
|
|
416
|
-
useFactory: (
|
|
417
|
-
|
|
459
|
+
useFactory: (
|
|
460
|
+
authInstance: BetterAuthInstance | null,
|
|
461
|
+
resolvedConfig: IBetterAuth | null,
|
|
462
|
+
connection: Connection,
|
|
463
|
+
) => {
|
|
464
|
+
return new BetterAuthService(authInstance, connection, resolvedConfig);
|
|
418
465
|
},
|
|
419
466
|
},
|
|
420
467
|
BetterAuthUserMapper,
|
|
@@ -434,14 +481,23 @@ export class BetterAuthModule implements NestModule, OnModuleInit {
|
|
|
434
481
|
private static logEnabledFeatures(config: IBetterAuth): void {
|
|
435
482
|
const features: string[] = [];
|
|
436
483
|
|
|
484
|
+
// Helper to check if a plugin is enabled
|
|
485
|
+
// Supports: true, { enabled: true }, { ... } (enabled by default when config block present)
|
|
486
|
+
// Disabled only when: false or { enabled: false }
|
|
487
|
+
const isPluginEnabled = <T extends { enabled?: boolean }>(value: boolean | T | undefined): boolean => {
|
|
488
|
+
if (value === undefined || value === false) return false;
|
|
489
|
+
if (value === true) return true;
|
|
490
|
+
return value.enabled !== false;
|
|
491
|
+
};
|
|
492
|
+
|
|
437
493
|
// Plugins are enabled by default when config block is present
|
|
438
|
-
if (config.jwt
|
|
494
|
+
if (isPluginEnabled(config.jwt)) {
|
|
439
495
|
features.push('JWT');
|
|
440
496
|
}
|
|
441
|
-
if (config.twoFactor
|
|
497
|
+
if (isPluginEnabled(config.twoFactor)) {
|
|
442
498
|
features.push('2FA/TOTP');
|
|
443
499
|
}
|
|
444
|
-
if (config.passkey
|
|
500
|
+
if (isPluginEnabled(config.passkey)) {
|
|
445
501
|
features.push('Passkey/WebAuthn');
|
|
446
502
|
}
|
|
447
503
|
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import { UseGuards } from '@nestjs/common';
|
|
2
1
|
import { Args, Context, Mutation, Query, Resolver } from '@nestjs/graphql';
|
|
3
2
|
import { Request, Response } from 'express';
|
|
4
3
|
|
|
5
4
|
import { Roles } from '../../common/decorators/roles.decorator';
|
|
6
5
|
import { RoleEnum } from '../../common/enums/role.enum';
|
|
7
|
-
import { AuthGuardStrategy } from '../auth/auth-guard-strategy.enum';
|
|
8
|
-
import { AuthGuard } from '../auth/guards/auth.guard';
|
|
9
6
|
import { BetterAuthAuthModel } from './better-auth-auth.model';
|
|
10
7
|
import {
|
|
11
8
|
BetterAuth2FASetupModel,
|
|
@@ -73,7 +70,6 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
|
|
|
73
70
|
nullable: true,
|
|
74
71
|
})
|
|
75
72
|
@Roles(RoleEnum.S_USER)
|
|
76
|
-
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
77
73
|
override async betterAuthSession(@Context() ctx: { req: Request }): Promise<BetterAuthSessionModel | null> {
|
|
78
74
|
return super.betterAuthSession(ctx);
|
|
79
75
|
}
|
|
@@ -95,7 +91,6 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
|
|
|
95
91
|
nullable: true,
|
|
96
92
|
})
|
|
97
93
|
@Roles(RoleEnum.S_USER)
|
|
98
|
-
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
99
94
|
override async betterAuthListPasskeys(@Context() ctx: { req: Request }): Promise<BetterAuthPasskeyModel[] | null> {
|
|
100
95
|
return super.betterAuthListPasskeys(ctx);
|
|
101
96
|
}
|
|
@@ -130,7 +125,6 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
|
|
|
130
125
|
|
|
131
126
|
@Mutation(() => Boolean, { description: 'Sign out via Better-Auth' })
|
|
132
127
|
@Roles(RoleEnum.S_USER)
|
|
133
|
-
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
134
128
|
override async betterAuthSignOut(@Context() ctx: { req: Request }): Promise<boolean> {
|
|
135
129
|
return super.betterAuthSignOut(ctx);
|
|
136
130
|
}
|
|
@@ -154,7 +148,6 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
|
|
|
154
148
|
description: 'Enable 2FA for the current user',
|
|
155
149
|
})
|
|
156
150
|
@Roles(RoleEnum.S_USER)
|
|
157
|
-
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
158
151
|
override async betterAuthEnable2FA(
|
|
159
152
|
@Args('password') password: string,
|
|
160
153
|
@Context() ctx: { req: Request },
|
|
@@ -166,7 +159,6 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
|
|
|
166
159
|
description: 'Disable 2FA for the current user',
|
|
167
160
|
})
|
|
168
161
|
@Roles(RoleEnum.S_USER)
|
|
169
|
-
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
170
162
|
override async betterAuthDisable2FA(
|
|
171
163
|
@Args('password') password: string,
|
|
172
164
|
@Context() ctx: { req: Request },
|
|
@@ -179,7 +171,6 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
|
|
|
179
171
|
nullable: true,
|
|
180
172
|
})
|
|
181
173
|
@Roles(RoleEnum.S_USER)
|
|
182
|
-
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
183
174
|
override async betterAuthGenerateBackupCodes(@Context() ctx: { req: Request }): Promise<null | string[]> {
|
|
184
175
|
return super.betterAuthGenerateBackupCodes(ctx);
|
|
185
176
|
}
|
|
@@ -192,7 +183,6 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
|
|
|
192
183
|
description: 'Get passkey registration challenge for WebAuthn',
|
|
193
184
|
})
|
|
194
185
|
@Roles(RoleEnum.S_USER)
|
|
195
|
-
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
196
186
|
override async betterAuthGetPasskeyChallenge(
|
|
197
187
|
@Context() ctx: { req: Request },
|
|
198
188
|
): Promise<BetterAuthPasskeyChallengeModel> {
|
|
@@ -203,7 +193,6 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
|
|
|
203
193
|
description: 'Delete a passkey by ID',
|
|
204
194
|
})
|
|
205
195
|
@Roles(RoleEnum.S_USER)
|
|
206
|
-
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
207
196
|
override async betterAuthDeletePasskey(
|
|
208
197
|
@Args('passkeyId') passkeyId: string,
|
|
209
198
|
@Context() ctx: { req: Request },
|