@lenne.tech/nest-server 11.10.0 → 11.10.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/core/modules/auth/guards/auth.guard.d.ts +2 -2
- package/dist/core/modules/auth/guards/auth.guard.js +68 -8
- package/dist/core/modules/auth/guards/auth.guard.js.map +1 -1
- package/dist/core/modules/auth/guards/roles.guard.d.ts +3 -4
- package/dist/core/modules/auth/guards/roles.guard.js +64 -159
- package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth-token.service.d.ts +21 -0
- package/dist/core/modules/better-auth/better-auth-token.service.js +153 -0
- package/dist/core/modules/better-auth/better-auth-token.service.js.map +1 -0
- package/dist/core/modules/better-auth/better-auth.types.d.ts +13 -0
- package/dist/core/modules/better-auth/better-auth.types.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.module.d.ts +2 -0
- package/dist/core/modules/better-auth/core-better-auth.module.js +33 -4
- package/dist/core/modules/better-auth/core-better-auth.module.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.service.d.ts +1 -0
- package/dist/core/modules/better-auth/core-better-auth.service.js +4 -0
- package/dist/core/modules/better-auth/core-better-auth.service.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.module.js +1 -0
- package/dist/core.module.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/core/modules/auth/guards/auth.guard.ts +136 -23
- package/src/core/modules/auth/guards/roles.guard.ts +119 -239
- package/src/core/modules/better-auth/better-auth-token.service.ts +241 -0
- package/src/core/modules/better-auth/better-auth.types.ts +37 -0
- package/src/core/modules/better-auth/core-better-auth.controller.ts +1 -1
- package/src/core/modules/better-auth/core-better-auth.module.ts +51 -4
- package/src/core/modules/better-auth/core-better-auth.resolver.ts +1 -1
- package/src/core/modules/better-auth/core-better-auth.service.ts +13 -0
- package/src/core/modules/better-auth/index.ts +1 -0
- package/src/core.module.ts +3 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { Injectable, Logger, Optional } from '@nestjs/common';
|
|
2
|
+
import { InjectConnection } from '@nestjs/mongoose';
|
|
3
|
+
import { Connection, Types } from 'mongoose';
|
|
4
|
+
|
|
5
|
+
import { BetterAuthenticatedUser } from './better-auth.types';
|
|
6
|
+
import { CoreBetterAuthService } from './core-better-auth.service';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Result of token extraction from a request
|
|
10
|
+
*/
|
|
11
|
+
export interface TokenExtractionResult {
|
|
12
|
+
/** Source of the token (header or cookie) */
|
|
13
|
+
source: 'cookie' | 'header' | null;
|
|
14
|
+
/** The extracted token, if found */
|
|
15
|
+
token: null | string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* BetterAuthTokenService provides centralized token extraction and user loading
|
|
20
|
+
* for BetterAuth authentication.
|
|
21
|
+
*
|
|
22
|
+
* This service consolidates the token verification logic that was previously
|
|
23
|
+
* duplicated in AuthGuard and RolesGuard, providing:
|
|
24
|
+
* - Token extraction from Authorization header or cookies
|
|
25
|
+
* - JWT token verification via BetterAuth
|
|
26
|
+
* - Session token verification via database lookup
|
|
27
|
+
* - User loading from MongoDB with hasRole() capability
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const token = this.tokenService.extractTokenFromRequest(request);
|
|
32
|
+
* if (token) {
|
|
33
|
+
* const user = await this.tokenService.verifyAndLoadUser(token);
|
|
34
|
+
* if (user) {
|
|
35
|
+
* request.user = user;
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
@Injectable()
|
|
41
|
+
export class BetterAuthTokenService {
|
|
42
|
+
private readonly logger = new Logger(BetterAuthTokenService.name);
|
|
43
|
+
|
|
44
|
+
constructor(
|
|
45
|
+
@Optional() private readonly betterAuthService?: CoreBetterAuthService,
|
|
46
|
+
@Optional() @InjectConnection() private readonly connection?: Connection,
|
|
47
|
+
) {}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Extracts a token from the request's Authorization header or cookies.
|
|
51
|
+
*
|
|
52
|
+
* Checks in order:
|
|
53
|
+
* 1. Authorization header (Bearer token)
|
|
54
|
+
* 2. Session cookies (iam.session_token, better-auth.session_token, token)
|
|
55
|
+
*
|
|
56
|
+
* @param request - HTTP request object with headers and cookies
|
|
57
|
+
* @returns Token extraction result with token and source
|
|
58
|
+
*/
|
|
59
|
+
extractTokenFromRequest(request: {
|
|
60
|
+
cookies?: Record<string, string>;
|
|
61
|
+
headers?: Record<string, string | string[] | undefined>;
|
|
62
|
+
}): TokenExtractionResult {
|
|
63
|
+
// Try Authorization header first
|
|
64
|
+
const authHeader = request.headers?.authorization || request.headers?.Authorization;
|
|
65
|
+
const headerValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;
|
|
66
|
+
|
|
67
|
+
if (headerValue) {
|
|
68
|
+
if (headerValue.startsWith('Bearer ') || headerValue.startsWith('bearer ')) {
|
|
69
|
+
return { source: 'header', token: headerValue.substring(7) };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Try cookies
|
|
74
|
+
if (request.cookies && this.betterAuthService) {
|
|
75
|
+
const cookieName = this.betterAuthService.getSessionCookieName();
|
|
76
|
+
const token =
|
|
77
|
+
request.cookies[cookieName] ||
|
|
78
|
+
request.cookies['better-auth.session_token'] ||
|
|
79
|
+
request.cookies['token'] ||
|
|
80
|
+
undefined;
|
|
81
|
+
|
|
82
|
+
if (token) {
|
|
83
|
+
return { source: 'cookie', token };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return { source: null, token: null };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Verifies a token (JWT or session) and loads the corresponding user from MongoDB.
|
|
92
|
+
*
|
|
93
|
+
* This method tries multiple verification strategies:
|
|
94
|
+
* 1. BetterAuth JWT verification (if JWT plugin is enabled)
|
|
95
|
+
* 2. BetterAuth session token lookup (database lookup)
|
|
96
|
+
*
|
|
97
|
+
* @param token - The token to verify
|
|
98
|
+
* @returns User object with hasRole method, or null if verification fails
|
|
99
|
+
*/
|
|
100
|
+
async verifyAndLoadUser(token: string): Promise<BetterAuthenticatedUser | null> {
|
|
101
|
+
if (!this.betterAuthService || !this.connection) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Strategy 1: Try JWT verification (if JWT plugin is enabled)
|
|
106
|
+
if (this.betterAuthService.isJwtEnabled()) {
|
|
107
|
+
try {
|
|
108
|
+
const payload = await this.betterAuthService.verifyJwtToken(token);
|
|
109
|
+
if (payload?.sub) {
|
|
110
|
+
const user = await this.loadUserFromPayload(payload);
|
|
111
|
+
if (user) {
|
|
112
|
+
return user;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
// Check for token expiration
|
|
117
|
+
if (error instanceof Error && error.message.includes('expired')) {
|
|
118
|
+
this.logger.debug('JWT token expired');
|
|
119
|
+
throw error; // Re-throw for proper handling by guards
|
|
120
|
+
}
|
|
121
|
+
// Other JWT verification failures - try session token next
|
|
122
|
+
this.logger.debug(
|
|
123
|
+
`JWT verification failed, trying session: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Strategy 2: Try session token lookup (database lookup)
|
|
129
|
+
try {
|
|
130
|
+
const sessionResult = await this.betterAuthService.getSessionByToken(token);
|
|
131
|
+
if (sessionResult?.user) {
|
|
132
|
+
return this.loadUserFromSessionResult(sessionResult.user);
|
|
133
|
+
}
|
|
134
|
+
} catch (error) {
|
|
135
|
+
this.logger.debug(`Session lookup failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Creates a user object with hasRole method from a MongoDB document.
|
|
143
|
+
*
|
|
144
|
+
* @param user - Raw MongoDB user document
|
|
145
|
+
* @returns User object with hasRole method
|
|
146
|
+
*/
|
|
147
|
+
createUserWithHasRole(user: Record<string, unknown>): BetterAuthenticatedUser {
|
|
148
|
+
return {
|
|
149
|
+
...user,
|
|
150
|
+
_authenticatedViaBetterAuth: true,
|
|
151
|
+
hasRole: (roles: string[]): boolean => {
|
|
152
|
+
const userRoles = user.roles;
|
|
153
|
+
if (!userRoles || !Array.isArray(userRoles)) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
return roles.some((role) => userRoles.includes(role));
|
|
157
|
+
},
|
|
158
|
+
id: (user._id as Types.ObjectId)?.toString() || (user.id as string),
|
|
159
|
+
} as BetterAuthenticatedUser;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Loads a user from JWT payload using direct MongoDB query.
|
|
164
|
+
*
|
|
165
|
+
* @param payload - JWT payload with sub (user ID or iamId)
|
|
166
|
+
* @returns User object with hasRole method, or null if not found
|
|
167
|
+
*/
|
|
168
|
+
private async loadUserFromPayload(payload: { [key: string]: unknown; sub: string }): Promise<BetterAuthenticatedUser | null> {
|
|
169
|
+
if (!this.connection) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const usersCollection = this.connection.collection('users');
|
|
175
|
+
let user: null | Record<string, unknown> = null;
|
|
176
|
+
|
|
177
|
+
// Try to find by MongoDB _id first
|
|
178
|
+
if (Types.ObjectId.isValid(payload.sub)) {
|
|
179
|
+
user = await usersCollection.findOne({ _id: new Types.ObjectId(payload.sub) });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// If not found, try by iamId
|
|
183
|
+
if (!user) {
|
|
184
|
+
user = await usersCollection.findOne({ iamId: payload.sub });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!user) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return this.createUserWithHasRole(user);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
this.logger.debug(`Failed to load user from payload: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Loads a user from session result (from getSessionByToken).
|
|
200
|
+
*
|
|
201
|
+
* @param sessionUser - User object from session lookup
|
|
202
|
+
* @returns User object with hasRole method, or null if not found
|
|
203
|
+
*/
|
|
204
|
+
private async loadUserFromSessionResult(sessionUser: {
|
|
205
|
+
email?: string;
|
|
206
|
+
id?: string;
|
|
207
|
+
}): Promise<BetterAuthenticatedUser | null> {
|
|
208
|
+
if (!this.connection || !sessionUser) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const usersCollection = this.connection.collection('users');
|
|
214
|
+
let user: null | Record<string, unknown> = null;
|
|
215
|
+
|
|
216
|
+
// Try to find by email (most reliable)
|
|
217
|
+
if (sessionUser.email) {
|
|
218
|
+
user = await usersCollection.findOne({ email: sessionUser.email });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// If not found by email, try by iamId
|
|
222
|
+
if (!user && sessionUser.id) {
|
|
223
|
+
user = await usersCollection.findOne({ iamId: sessionUser.id });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// If still not found, try by _id (if the ID looks like a MongoDB ObjectId)
|
|
227
|
+
if (!user && sessionUser.id && Types.ObjectId.isValid(sessionUser.id)) {
|
|
228
|
+
user = await usersCollection.findOne({ _id: new Types.ObjectId(sessionUser.id) });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (!user) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return this.createUserWithHasRole(user);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
this.logger.debug(`Failed to load user from session: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* and reduce the need for `as any` casts throughout the codebase.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { Types } from 'mongoose';
|
|
9
|
+
|
|
8
10
|
import { BetterAuthSessionUser } from './core-better-auth-user.mapper';
|
|
9
11
|
|
|
10
12
|
/**
|
|
@@ -23,6 +25,41 @@ export interface BetterAuth2FAResponse {
|
|
|
23
25
|
user?: BetterAuthSessionUser;
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Authenticated user object created from BetterAuth token verification.
|
|
30
|
+
*
|
|
31
|
+
* This interface represents a user that has been authenticated via BetterAuth
|
|
32
|
+
* (either JWT or session token). It includes the `hasRole` method for
|
|
33
|
+
* role-based access control and the `_authenticatedViaBetterAuth` flag
|
|
34
|
+
* to identify BetterAuth-authenticated users.
|
|
35
|
+
*/
|
|
36
|
+
export interface BetterAuthenticatedUser {
|
|
37
|
+
/** Allow additional properties from MongoDB document */
|
|
38
|
+
[key: string]: unknown;
|
|
39
|
+
/** Flag indicating this user was authenticated via BetterAuth */
|
|
40
|
+
_authenticatedViaBetterAuth: true;
|
|
41
|
+
/** MongoDB _id field */
|
|
42
|
+
_id?: Types.ObjectId;
|
|
43
|
+
/** User's email address */
|
|
44
|
+
email: string;
|
|
45
|
+
/** Whether user's email is verified */
|
|
46
|
+
emailVerified?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Check if user has any of the specified roles
|
|
49
|
+
* @param roles - Array of role names to check
|
|
50
|
+
* @returns true if user has at least one of the roles
|
|
51
|
+
*/
|
|
52
|
+
hasRole: (roles: string[]) => boolean;
|
|
53
|
+
/** User ID as string */
|
|
54
|
+
id: string;
|
|
55
|
+
/** User's assigned roles */
|
|
56
|
+
roles?: string[];
|
|
57
|
+
/** Whether the user is verified */
|
|
58
|
+
verified?: boolean;
|
|
59
|
+
/** Timestamp when user was verified */
|
|
60
|
+
verifiedAt?: Date;
|
|
61
|
+
}
|
|
62
|
+
|
|
26
63
|
/**
|
|
27
64
|
* Better-Auth session response from getSession API
|
|
28
65
|
*/
|
|
@@ -614,7 +614,7 @@ export class CoreBetterAuthController {
|
|
|
614
614
|
* @param sessionUser - The user from Better-Auth session
|
|
615
615
|
* @param _mappedUser - The synced user from legacy system (available for override customization)
|
|
616
616
|
*/
|
|
617
|
-
|
|
617
|
+
|
|
618
618
|
protected mapUser(sessionUser: BetterAuthSessionUser, _mappedUser: any): CoreBetterAuthUserResponse {
|
|
619
619
|
return {
|
|
620
620
|
email: sessionUser.email,
|
|
@@ -9,11 +9,14 @@ import {
|
|
|
9
9
|
Optional,
|
|
10
10
|
Type,
|
|
11
11
|
} from '@nestjs/common';
|
|
12
|
+
import { APP_GUARD } from '@nestjs/core';
|
|
12
13
|
import { getConnectionToken } from '@nestjs/mongoose';
|
|
13
14
|
import mongoose, { Connection } from 'mongoose';
|
|
14
15
|
|
|
15
16
|
import { IBetterAuth } from '../../common/interfaces/server-options.interface';
|
|
16
17
|
import { ConfigService } from '../../common/services/config.service';
|
|
18
|
+
import { RolesGuard } from '../auth/guards/roles.guard';
|
|
19
|
+
import { BetterAuthTokenService } from './better-auth-token.service';
|
|
17
20
|
import { BetterAuthInstance, createBetterAuthInstance } from './better-auth.config';
|
|
18
21
|
import { DefaultBetterAuthResolver } from './better-auth.resolver';
|
|
19
22
|
import { CoreBetterAuthApiMiddleware } from './core-better-auth-api.middleware';
|
|
@@ -79,6 +82,19 @@ export interface CoreBetterAuthModuleOptions {
|
|
|
79
82
|
*/
|
|
80
83
|
fallbackSecrets?: (string | undefined)[];
|
|
81
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Register RolesGuard as a global guard.
|
|
87
|
+
*
|
|
88
|
+
* This should be set to `true` for IAM-only setups (CoreModule.forRoot with 1 parameter)
|
|
89
|
+
* where CoreAuthModule is not imported (which normally registers RolesGuard globally).
|
|
90
|
+
*
|
|
91
|
+
* When `true`, all `@Roles()` decorators will be enforced automatically without
|
|
92
|
+
* needing explicit `@UseGuards(RolesGuard)` on each endpoint.
|
|
93
|
+
*
|
|
94
|
+
* @default false
|
|
95
|
+
*/
|
|
96
|
+
registerRolesGuardGlobally?: boolean;
|
|
97
|
+
|
|
82
98
|
/**
|
|
83
99
|
* Custom resolver class to use instead of the default DefaultBetterAuthResolver.
|
|
84
100
|
* The class must extend CoreBetterAuthResolver.
|
|
@@ -152,6 +168,7 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
|
|
|
152
168
|
private static currentConfig: IBetterAuth | null = null;
|
|
153
169
|
private static customController: null | Type<CoreBetterAuthController> = null;
|
|
154
170
|
private static customResolver: null | Type<CoreBetterAuthResolver> = null;
|
|
171
|
+
private static shouldRegisterRolesGuardGlobally = false;
|
|
155
172
|
|
|
156
173
|
/**
|
|
157
174
|
* Gets the controller class to use (custom or default)
|
|
@@ -251,7 +268,7 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
|
|
|
251
268
|
* @returns Dynamic module configuration
|
|
252
269
|
*/
|
|
253
270
|
static forRoot(options: CoreBetterAuthModuleOptions): DynamicModule {
|
|
254
|
-
const { config: rawConfig, controller, fallbackSecrets, resolver } = options;
|
|
271
|
+
const { config: rawConfig, controller, fallbackSecrets, registerRolesGuardGlobally, resolver } = options;
|
|
255
272
|
|
|
256
273
|
// Normalize config: true → {}, false/undefined → null
|
|
257
274
|
const config = normalizeBetterAuthConfig(rawConfig);
|
|
@@ -262,6 +279,8 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
|
|
|
262
279
|
this.customController = controller || null;
|
|
263
280
|
// Store custom resolver if provided
|
|
264
281
|
this.customResolver = resolver || null;
|
|
282
|
+
// Store whether to register RolesGuard globally (for IAM-only setups)
|
|
283
|
+
this.shouldRegisterRolesGuardGlobally = registerRolesGuardGlobally ?? false;
|
|
265
284
|
|
|
266
285
|
// If better-auth is disabled (config is null or enabled: false), return minimal module
|
|
267
286
|
// Note: We don't provide middleware classes when disabled because they depend on CoreBetterAuthService
|
|
@@ -270,7 +289,7 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
|
|
|
270
289
|
this.logger.debug('BetterAuth is disabled - skipping initialization');
|
|
271
290
|
this.betterAuthEnabled = false;
|
|
272
291
|
return {
|
|
273
|
-
exports: [BETTER_AUTH_INSTANCE, CoreBetterAuthService, CoreBetterAuthUserMapper, CoreBetterAuthRateLimiter],
|
|
292
|
+
exports: [BETTER_AUTH_INSTANCE, CoreBetterAuthService, CoreBetterAuthUserMapper, CoreBetterAuthRateLimiter, BetterAuthTokenService],
|
|
274
293
|
module: CoreBetterAuthModule,
|
|
275
294
|
providers: [
|
|
276
295
|
{
|
|
@@ -282,6 +301,7 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
|
|
|
282
301
|
CoreBetterAuthService,
|
|
283
302
|
CoreBetterAuthUserMapper,
|
|
284
303
|
CoreBetterAuthRateLimiter,
|
|
304
|
+
BetterAuthTokenService,
|
|
285
305
|
],
|
|
286
306
|
};
|
|
287
307
|
}
|
|
@@ -306,7 +326,7 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
|
|
|
306
326
|
static forRootAsync(): DynamicModule {
|
|
307
327
|
return {
|
|
308
328
|
controllers: [this.getControllerClass()],
|
|
309
|
-
exports: [BETTER_AUTH_INSTANCE, CoreBetterAuthService, CoreBetterAuthUserMapper, CoreBetterAuthRateLimiter],
|
|
329
|
+
exports: [BETTER_AUTH_INSTANCE, CoreBetterAuthService, CoreBetterAuthUserMapper, CoreBetterAuthRateLimiter, BetterAuthTokenService],
|
|
310
330
|
imports: [],
|
|
311
331
|
module: CoreBetterAuthModule,
|
|
312
332
|
providers: [
|
|
@@ -380,6 +400,14 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
|
|
|
380
400
|
CoreBetterAuthApiMiddleware,
|
|
381
401
|
CoreBetterAuthRateLimiter,
|
|
382
402
|
CoreBetterAuthRateLimitMiddleware,
|
|
403
|
+
// BetterAuthTokenService needs explicit factory to ensure proper dependency injection
|
|
404
|
+
{
|
|
405
|
+
inject: [CoreBetterAuthService, getConnectionToken()],
|
|
406
|
+
provide: BetterAuthTokenService,
|
|
407
|
+
useFactory: (betterAuthService: CoreBetterAuthService, connection: Connection) => {
|
|
408
|
+
return new BetterAuthTokenService(betterAuthService, connection);
|
|
409
|
+
},
|
|
410
|
+
},
|
|
383
411
|
this.getResolverClass(),
|
|
384
412
|
],
|
|
385
413
|
};
|
|
@@ -412,6 +440,7 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
|
|
|
412
440
|
this.currentConfig = null;
|
|
413
441
|
this.customController = null;
|
|
414
442
|
this.customResolver = null;
|
|
443
|
+
this.shouldRegisterRolesGuardGlobally = false;
|
|
415
444
|
}
|
|
416
445
|
|
|
417
446
|
/**
|
|
@@ -421,7 +450,7 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
|
|
|
421
450
|
private static createDeferredModule(config: IBetterAuth, fallbackSecrets?: (string | undefined)[]): DynamicModule {
|
|
422
451
|
return {
|
|
423
452
|
controllers: [this.getControllerClass()],
|
|
424
|
-
exports: [BETTER_AUTH_INSTANCE, CoreBetterAuthService, CoreBetterAuthUserMapper, CoreBetterAuthRateLimiter],
|
|
453
|
+
exports: [BETTER_AUTH_INSTANCE, CoreBetterAuthService, CoreBetterAuthUserMapper, CoreBetterAuthRateLimiter, BetterAuthTokenService],
|
|
425
454
|
module: CoreBetterAuthModule,
|
|
426
455
|
providers: [
|
|
427
456
|
{
|
|
@@ -479,7 +508,25 @@ export class CoreBetterAuthModule implements NestModule, OnModuleInit {
|
|
|
479
508
|
CoreBetterAuthApiMiddleware,
|
|
480
509
|
CoreBetterAuthRateLimiter,
|
|
481
510
|
CoreBetterAuthRateLimitMiddleware,
|
|
511
|
+
// BetterAuthTokenService needs explicit factory to ensure proper dependency injection
|
|
512
|
+
{
|
|
513
|
+
inject: [CoreBetterAuthService, getConnectionToken()],
|
|
514
|
+
provide: BetterAuthTokenService,
|
|
515
|
+
useFactory: (betterAuthService: CoreBetterAuthService, connection: Connection) => {
|
|
516
|
+
return new BetterAuthTokenService(betterAuthService, connection);
|
|
517
|
+
},
|
|
518
|
+
},
|
|
482
519
|
this.getResolverClass(),
|
|
520
|
+
// Conditionally register RolesGuard globally for IAM-only setups
|
|
521
|
+
// In Legacy mode, RolesGuard is already registered globally via CoreAuthModule
|
|
522
|
+
...(this.shouldRegisterRolesGuardGlobally
|
|
523
|
+
? [
|
|
524
|
+
{
|
|
525
|
+
provide: APP_GUARD,
|
|
526
|
+
useClass: RolesGuard,
|
|
527
|
+
},
|
|
528
|
+
]
|
|
529
|
+
: []),
|
|
483
530
|
],
|
|
484
531
|
};
|
|
485
532
|
}
|
|
@@ -195,7 +195,7 @@ export class CoreBetterAuthResolver {
|
|
|
195
195
|
async betterAuthSignIn(
|
|
196
196
|
@Args('email') email: string,
|
|
197
197
|
@Args('password') password: string,
|
|
198
|
-
|
|
198
|
+
|
|
199
199
|
@Context() _ctx: { req: Request; res: Response },
|
|
200
200
|
): Promise<CoreBetterAuthAuthModel> {
|
|
201
201
|
this.ensureEnabled();
|
|
@@ -201,6 +201,19 @@ export class CoreBetterAuthService {
|
|
|
201
201
|
return this.config.baseUrl || 'http://localhost:3000';
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Gets the session cookie name based on the configured base path.
|
|
206
|
+
*
|
|
207
|
+
* The cookie name follows the pattern: `{basePath}.session_token`
|
|
208
|
+
* For example, with basePath '/iam', the cookie name is 'iam.session_token'
|
|
209
|
+
*
|
|
210
|
+
* @returns The session cookie name
|
|
211
|
+
*/
|
|
212
|
+
getSessionCookieName(): string {
|
|
213
|
+
const basePath = this.getBasePath()?.replace(/^\//, '').replace(/\//g, '.') || 'iam';
|
|
214
|
+
return `${basePath}.session_token`;
|
|
215
|
+
}
|
|
216
|
+
|
|
204
217
|
// ===================================================================================================================
|
|
205
218
|
// JWT Token Methods
|
|
206
219
|
// ===================================================================================================================
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* - DefaultBetterAuthResolver: Default resolver implementation (use as fallback)
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
+
export * from './better-auth-token.service';
|
|
25
26
|
export * from './better-auth.config';
|
|
26
27
|
export * from './better-auth.resolver';
|
|
27
28
|
export * from './better-auth.types';
|
package/src/core.module.ts
CHANGED
|
@@ -262,6 +262,9 @@ export class CoreModule implements NestModule {
|
|
|
262
262
|
config: betterAuthConfig === true ? {} : betterAuthConfig || {},
|
|
263
263
|
// Pass JWT secrets for backwards compatibility fallback
|
|
264
264
|
fallbackSecrets: [config.jwt?.secret, config.jwt?.refresh?.secret],
|
|
265
|
+
// In IAM-only mode, register RolesGuard globally to enforce @Roles() decorators
|
|
266
|
+
// In Legacy mode (autoRegister), RolesGuard is already registered via CoreAuthModule
|
|
267
|
+
registerRolesGuardGlobally: isIamOnlyMode,
|
|
265
268
|
}),
|
|
266
269
|
);
|
|
267
270
|
}
|