@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "11.10.
|
|
3
|
+
"version": "11.10.2",
|
|
4
4
|
"description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"dependencies": {
|
|
81
81
|
"@apollo/server": "5.2.0",
|
|
82
82
|
"@as-integrations/express5": "1.1.2",
|
|
83
|
-
"@better-auth/passkey": "
|
|
83
|
+
"@better-auth/passkey": "1.4.16",
|
|
84
84
|
"@getbrevo/brevo": "3.0.1",
|
|
85
85
|
"@nestjs/apollo": "13.2.3",
|
|
86
86
|
"@nestjs/common": "11.1.9",
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
"@tus/server": "2.3.0",
|
|
99
99
|
"apollo-server-core": "3.13.0",
|
|
100
100
|
"bcrypt": "6.0.0",
|
|
101
|
-
"better-auth": "
|
|
101
|
+
"better-auth": "1.4.16",
|
|
102
102
|
"class-transformer": "0.5.1",
|
|
103
103
|
"class-validator": "0.14.3",
|
|
104
104
|
"compression": "1.8.1",
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { CanActivate, ExecutionContext, Logger, mixin, Optional } from '@nestjs/common';
|
|
2
|
+
import { ModuleRef } from '@nestjs/core';
|
|
2
3
|
import { GqlExecutionContext } from '@nestjs/graphql';
|
|
3
4
|
import { AuthModuleOptions, Type } from '@nestjs/passport';
|
|
4
5
|
import { defaultOptions } from '@nestjs/passport/dist/options';
|
|
5
6
|
import { memoize } from '@nestjs/passport/dist/utils/memoize.util';
|
|
6
7
|
import passport = require('passport');
|
|
7
8
|
|
|
9
|
+
import { BetterAuthTokenService } from '../../better-auth/better-auth-token.service';
|
|
10
|
+
import { BetterAuthenticatedUser } from '../../better-auth/better-auth.types';
|
|
11
|
+
import { CoreBetterAuthService } from '../../better-auth/core-better-auth.service';
|
|
8
12
|
import { AuthGuardStrategy } from '../auth-guard-strategy.enum';
|
|
9
13
|
import { ExpiredRefreshTokenException } from '../exceptions/expired-refresh-token.exception';
|
|
10
14
|
import { ExpiredTokenException } from '../exceptions/expired-token.exception';
|
|
@@ -21,7 +25,7 @@ const NO_STRATEGY_ERROR =
|
|
|
21
25
|
* Interface for auth guard
|
|
22
26
|
*/
|
|
23
27
|
export type IAuthGuard = CanActivate & {
|
|
24
|
-
handleRequest<TUser = any>(err, user, info, context): TUser;
|
|
28
|
+
handleRequest<TUser = any>(err: Error | null, user: any, info: any, context: ExecutionContext): TUser;
|
|
25
29
|
};
|
|
26
30
|
|
|
27
31
|
/**
|
|
@@ -29,39 +33,82 @@ export type IAuthGuard = CanActivate & {
|
|
|
29
33
|
* @param request
|
|
30
34
|
* @param response
|
|
31
35
|
*/
|
|
32
|
-
const createPassportContext =
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
const createPassportContext =
|
|
37
|
+
(request: any, response: any) => (type: any, options: any, callback: (...params: any[]) => any) =>
|
|
38
|
+
new Promise((resolve, reject) =>
|
|
39
|
+
passport.authenticate(type, options, (err: any, user: any, info: any) => {
|
|
40
|
+
try {
|
|
41
|
+
request.authInfo = info;
|
|
42
|
+
return resolve(callback(err, user, info));
|
|
43
|
+
} catch (err) {
|
|
44
|
+
reject(err);
|
|
45
|
+
}
|
|
46
|
+
})(request, response, (err: any) => (err ? reject(err) : resolve(undefined))),
|
|
47
|
+
);
|
|
43
48
|
|
|
44
49
|
/**
|
|
45
50
|
* Extension of AuthGuard to get context in handleRequest method
|
|
46
51
|
* See: https://github.com/nestjs/passport/blob/master/lib/auth.guard.ts
|
|
47
52
|
*
|
|
53
|
+
* MULTI-TOKEN SUPPORT:
|
|
54
|
+
* This guard supports multiple authentication strategies:
|
|
55
|
+
* 1. JWT (Legacy Auth) - Uses Passport JWT strategy
|
|
56
|
+
* 2. JWT_REFRESH (Legacy Auth) - Uses Passport JWT refresh strategy
|
|
57
|
+
* 3. BETTER_AUTH (IAM) - Uses BetterAuthTokenService directly (no Passport)
|
|
58
|
+
*
|
|
59
|
+
* For BETTER_AUTH strategy:
|
|
60
|
+
* - First checks if user is already authenticated via middleware (_authenticatedViaBetterAuth)
|
|
61
|
+
* - If not, validates the token via BetterAuthTokenService (JWT or session token)
|
|
62
|
+
* - Loads the full user from MongoDB with hasRole() capability
|
|
63
|
+
*
|
|
48
64
|
* Can be removed when pull request is merged:
|
|
49
65
|
* https://github.com/nestjs/passport/pull/66
|
|
50
66
|
*/
|
|
51
67
|
function createAuthGuard(type?: AuthGuardStrategy | string | string[]): Type<IAuthGuard> {
|
|
52
68
|
class MixinAuthGuard<TUser = any> {
|
|
69
|
+
private readonly logger = new Logger('AuthGuard');
|
|
70
|
+
private betterAuthService: CoreBetterAuthService | null = null;
|
|
71
|
+
private tokenService: BetterAuthTokenService | null = null;
|
|
72
|
+
private servicesResolved = false;
|
|
73
|
+
|
|
53
74
|
/**
|
|
54
75
|
* Integrate options
|
|
55
76
|
*/
|
|
56
|
-
constructor(
|
|
77
|
+
constructor(
|
|
78
|
+
@Optional() protected readonly options?: AuthModuleOptions,
|
|
79
|
+
@Optional() private readonly moduleRef?: ModuleRef,
|
|
80
|
+
) {
|
|
57
81
|
this.options = this.options || {};
|
|
58
82
|
if (!type && !this.options.defaultStrategy) {
|
|
59
|
-
|
|
83
|
+
this.logger.error(NO_STRATEGY_ERROR);
|
|
60
84
|
}
|
|
61
85
|
}
|
|
62
86
|
|
|
63
87
|
/**
|
|
64
|
-
*
|
|
88
|
+
* Lazily resolve BetterAuth services
|
|
89
|
+
*/
|
|
90
|
+
private resolveServices(): void {
|
|
91
|
+
if (this.servicesResolved || !this.moduleRef) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
this.betterAuthService = this.moduleRef.get(CoreBetterAuthService, { strict: false });
|
|
97
|
+
} catch {
|
|
98
|
+
// BetterAuth not available - that's fine for JWT-only setups
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
this.tokenService = this.moduleRef.get(BetterAuthTokenService, { strict: false });
|
|
103
|
+
} catch {
|
|
104
|
+
// BetterAuthTokenService not available
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
this.servicesResolved = true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if user can activate the route
|
|
65
112
|
*/
|
|
66
113
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
67
114
|
const args = context.getArgs();
|
|
@@ -84,16 +131,81 @@ function createAuthGuard(type?: AuthGuardStrategy | string | string[]): Type<IAu
|
|
|
84
131
|
return true;
|
|
85
132
|
}
|
|
86
133
|
|
|
87
|
-
//
|
|
134
|
+
// For BETTER_AUTH strategy, use BetterAuthTokenService directly (no Passport)
|
|
135
|
+
if (type === AuthGuardStrategy.BETTER_AUTH) {
|
|
136
|
+
return this.handleBetterAuthStrategy(context, request, options);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Proceed with Passport authentication for other strategies
|
|
88
140
|
const response = context?.switchToHttp()?.getResponse();
|
|
89
141
|
const passportFn = createPassportContext(request, response);
|
|
90
|
-
const user = await passportFn(type || this.options
|
|
142
|
+
const user = await passportFn(type || this.options?.defaultStrategy, options, (err: any, currentUser: any, info: any) =>
|
|
91
143
|
this.handleRequest(err, currentUser, info, context),
|
|
92
144
|
);
|
|
93
145
|
request[options.property || defaultOptions.property] = user;
|
|
94
146
|
return true;
|
|
95
147
|
}
|
|
96
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Handle BETTER_AUTH strategy authentication.
|
|
151
|
+
* Validates tokens via BetterAuthTokenService without using Passport.
|
|
152
|
+
*/
|
|
153
|
+
private async handleBetterAuthStrategy(
|
|
154
|
+
context: ExecutionContext,
|
|
155
|
+
request: any,
|
|
156
|
+
options: AuthModuleOptions,
|
|
157
|
+
): Promise<boolean> {
|
|
158
|
+
// Resolve services lazily
|
|
159
|
+
this.resolveServices();
|
|
160
|
+
|
|
161
|
+
if (!this.betterAuthService?.isEnabled()) {
|
|
162
|
+
this.logger.warn('BETTER_AUTH strategy used but BetterAuth is not enabled');
|
|
163
|
+
throw new InvalidTokenException();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Try to validate token via BetterAuthTokenService
|
|
167
|
+
const user = await this.verifyBetterAuthToken(request);
|
|
168
|
+
|
|
169
|
+
if (!user) {
|
|
170
|
+
throw new InvalidTokenException();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Validate through handleRequest and set user on request
|
|
174
|
+
const validatedUser = this.handleRequest(null, user, null, context);
|
|
175
|
+
request[options.property || defaultOptions.property] = validatedUser;
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Verify BetterAuth token (JWT or session) and load the corresponding user.
|
|
181
|
+
*
|
|
182
|
+
* Delegates to BetterAuthTokenService for token verification and user loading.
|
|
183
|
+
*
|
|
184
|
+
* @param request - HTTP request object
|
|
185
|
+
* @returns User object if verification succeeds, null otherwise
|
|
186
|
+
*/
|
|
187
|
+
private async verifyBetterAuthToken(request: any): Promise<BetterAuthenticatedUser | null> {
|
|
188
|
+
if (!this.tokenService) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
// Extract token from request
|
|
194
|
+
const { token } = this.tokenService.extractTokenFromRequest(request);
|
|
195
|
+
if (!token) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Verify token and load user
|
|
200
|
+
return await this.tokenService.verifyAndLoadUser(token);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
this.logger.debug(
|
|
203
|
+
`BetterAuth token verification failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
204
|
+
);
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
97
209
|
/**
|
|
98
210
|
* Prepare request
|
|
99
211
|
*/
|
|
@@ -104,7 +216,9 @@ function createAuthGuard(type?: AuthGuardStrategy | string | string[]): Type<IAu
|
|
|
104
216
|
if (ctx?.req) {
|
|
105
217
|
return ctx.req;
|
|
106
218
|
}
|
|
107
|
-
} catch
|
|
219
|
+
} catch {
|
|
220
|
+
// GraphQL context not available
|
|
221
|
+
}
|
|
108
222
|
|
|
109
223
|
// Else return HTTP request
|
|
110
224
|
return context && context.switchToHttp() ? context.switchToHttp().getRequest() : null;
|
|
@@ -113,16 +227,15 @@ function createAuthGuard(type?: AuthGuardStrategy | string | string[]): Type<IAu
|
|
|
113
227
|
/**
|
|
114
228
|
* Login for session handling
|
|
115
229
|
*/
|
|
116
|
-
async logIn<TRequest extends { logIn: (...params) => any } = any>(request: TRequest) {
|
|
117
|
-
const user = request[this.options
|
|
118
|
-
await new Promise<void>((resolve, reject) => request.logIn(user, (err) => (err ? reject(err) : resolve())));
|
|
230
|
+
async logIn<TRequest extends { logIn: (...params: any[]) => any } = any>(request: TRequest) {
|
|
231
|
+
const user = request[this.options?.property || defaultOptions.property];
|
|
232
|
+
await new Promise<void>((resolve, reject) => request.logIn(user, (err: any) => (err ? reject(err) : resolve())));
|
|
119
233
|
}
|
|
120
234
|
|
|
121
235
|
/**
|
|
122
236
|
* Process request
|
|
123
237
|
*/
|
|
124
|
-
|
|
125
|
-
handleRequest(err, user, info, context): TUser {
|
|
238
|
+
handleRequest(err: Error | null, user: any, info: any, _context: ExecutionContext): TUser {
|
|
126
239
|
if (err) {
|
|
127
240
|
throw new InvalidTokenException();
|
|
128
241
|
}
|