@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
|
@@ -5,9 +5,11 @@ import { PassportModule } from '@nestjs/passport';
|
|
|
5
5
|
import { PubSub } from 'graphql-subscriptions';
|
|
6
6
|
|
|
7
7
|
import { AuthGuardStrategy } from './auth-guard-strategy.enum';
|
|
8
|
+
import { LegacyAuthRateLimitGuard } from './guards/legacy-auth-rate-limit.guard';
|
|
8
9
|
import { RolesGuard } from './guards/roles.guard';
|
|
9
10
|
import { CoreAuthUserService } from './services/core-auth-user.service';
|
|
10
11
|
import { CoreAuthService } from './services/core-auth.service';
|
|
12
|
+
import { LegacyAuthRateLimiter } from './services/legacy-auth-rate-limiter.service';
|
|
11
13
|
import { JwtRefreshStrategy } from './strategies/jwt-refresh.strategy';
|
|
12
14
|
import { JwtStrategy } from './strategies/jwt.strategy';
|
|
13
15
|
|
|
@@ -68,6 +70,9 @@ export class CoreAuthModule {
|
|
|
68
70
|
provide: JwtRefreshStrategy,
|
|
69
71
|
useClass: options.jwtRefreshStrategy || JwtRefreshStrategy,
|
|
70
72
|
},
|
|
73
|
+
// Rate limiting for Legacy Auth endpoints (disabled by default, configure via auth.rateLimit)
|
|
74
|
+
LegacyAuthRateLimiter,
|
|
75
|
+
LegacyAuthRateLimitGuard,
|
|
71
76
|
];
|
|
72
77
|
if (Array.isArray(options?.providers)) {
|
|
73
78
|
providers = imports.concat(options.providers);
|
|
@@ -75,7 +80,16 @@ export class CoreAuthModule {
|
|
|
75
80
|
|
|
76
81
|
// Return CoreAuthModule
|
|
77
82
|
return {
|
|
78
|
-
exports: [
|
|
83
|
+
exports: [
|
|
84
|
+
CoreAuthService,
|
|
85
|
+
JwtModule,
|
|
86
|
+
JwtStrategy,
|
|
87
|
+
JwtRefreshStrategy,
|
|
88
|
+
LegacyAuthRateLimiter,
|
|
89
|
+
LegacyAuthRateLimitGuard,
|
|
90
|
+
PassportModule,
|
|
91
|
+
UserModule,
|
|
92
|
+
],
|
|
79
93
|
imports,
|
|
80
94
|
module: CoreAuthModule,
|
|
81
95
|
providers,
|
|
@@ -10,7 +10,9 @@ import { ServiceOptions } from '../../common/interfaces/service-options.interfac
|
|
|
10
10
|
import { ConfigService } from '../../common/services/config.service';
|
|
11
11
|
import { AuthGuardStrategy } from './auth-guard-strategy.enum';
|
|
12
12
|
import { CoreAuthModel } from './core-auth.model';
|
|
13
|
+
import { LegacyAuthDisabledException } from './exceptions/legacy-auth-disabled.exception';
|
|
13
14
|
import { AuthGuard } from './guards/auth.guard';
|
|
15
|
+
import { LegacyAuthRateLimitGuard } from './guards/legacy-auth-rate-limit.guard';
|
|
14
16
|
import { CoreAuthSignInInput } from './inputs/core-auth-sign-in.input';
|
|
15
17
|
import { CoreAuthSignUpInput } from './inputs/core-auth-sign-up.input';
|
|
16
18
|
import { ICoreAuthUser } from './interfaces/core-auth-user.interface';
|
|
@@ -19,6 +21,26 @@ import { Tokens } from './tokens.decorator';
|
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* Authentication resolver for the sign in
|
|
24
|
+
*
|
|
25
|
+
* This resolver provides Legacy Auth endpoints via GraphQL.
|
|
26
|
+
* In a future version, BetterAuth (IAM) will become the default.
|
|
27
|
+
*
|
|
28
|
+
* ## Disabling Legacy Endpoints
|
|
29
|
+
*
|
|
30
|
+
* After all users have migrated to BetterAuth (IAM), these endpoints
|
|
31
|
+
* can be disabled via configuration:
|
|
32
|
+
*
|
|
33
|
+
* ```typescript
|
|
34
|
+
* auth: {
|
|
35
|
+
* legacyEndpoints: {
|
|
36
|
+
* enabled: false, // Disable all legacy endpoints
|
|
37
|
+
* // or
|
|
38
|
+
* graphql: false // Disable only GraphQL endpoints
|
|
39
|
+
* }
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @see https://github.com/lenneTech/nest-server/blob/develop/.claude/rules/module-deprecation.md
|
|
22
44
|
*/
|
|
23
45
|
@Resolver(() => CoreAuthModel, { isAbstract: true })
|
|
24
46
|
@Roles(RoleEnum.ADMIN)
|
|
@@ -31,67 +53,113 @@ export class CoreAuthResolver {
|
|
|
31
53
|
protected readonly configService: ConfigService,
|
|
32
54
|
) {}
|
|
33
55
|
|
|
56
|
+
// ===========================================================================
|
|
57
|
+
// Helper - Legacy Endpoint Check
|
|
58
|
+
// ===========================================================================
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if legacy GraphQL endpoints are enabled
|
|
62
|
+
*
|
|
63
|
+
* Throws LegacyAuthDisabledException if:
|
|
64
|
+
* - config.auth.legacyEndpoints.enabled is false
|
|
65
|
+
* - config.auth.legacyEndpoints.graphql is false
|
|
66
|
+
*
|
|
67
|
+
* @throws LegacyAuthDisabledException
|
|
68
|
+
*/
|
|
69
|
+
protected checkLegacyGraphQLEnabled(endpointName: string): void {
|
|
70
|
+
const authConfig = this.configService.getFastButReadOnly('auth');
|
|
71
|
+
const legacyConfig = authConfig?.legacyEndpoints;
|
|
72
|
+
|
|
73
|
+
// Check if legacy endpoints are globally disabled
|
|
74
|
+
if (legacyConfig?.enabled === false) {
|
|
75
|
+
throw new LegacyAuthDisabledException(endpointName);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check if GraphQL endpoints specifically are disabled
|
|
79
|
+
if (legacyConfig?.graphql === false) {
|
|
80
|
+
throw new LegacyAuthDisabledException(endpointName);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
34
84
|
// ===========================================================================
|
|
35
85
|
// Mutations
|
|
36
86
|
// ===========================================================================
|
|
37
87
|
|
|
38
88
|
/**
|
|
39
89
|
* Logout user (from specific device)
|
|
90
|
+
*
|
|
91
|
+
* @deprecated Will be replaced by BetterAuth signOut in a future version
|
|
92
|
+
* @throws LegacyAuthDisabledException if legacy endpoints are disabled
|
|
40
93
|
*/
|
|
41
94
|
@Mutation(() => Boolean, { description: 'Logout user (from specific device)' })
|
|
42
|
-
@Roles(RoleEnum.
|
|
43
|
-
@UseGuards(
|
|
95
|
+
@Roles(RoleEnum.S_USER)
|
|
96
|
+
@UseGuards(LegacyAuthRateLimitGuard)
|
|
44
97
|
async logout(
|
|
45
98
|
@CurrentUser() currentUser: ICoreAuthUser,
|
|
46
99
|
@Context() ctx: { res: ResponseType },
|
|
47
100
|
@Tokens('token') token: string,
|
|
48
101
|
@Args('allDevices', { nullable: true }) allDevices?: boolean,
|
|
49
102
|
): Promise<boolean> {
|
|
103
|
+
this.checkLegacyGraphQLEnabled('logout');
|
|
50
104
|
const result = await this.authService.logout(token, { allDevices, currentUser });
|
|
51
105
|
return this.processCookies(ctx, result);
|
|
52
106
|
}
|
|
53
107
|
|
|
54
108
|
/**
|
|
55
109
|
* Refresh token (for specific device)
|
|
110
|
+
*
|
|
111
|
+
* @deprecated Will be replaced by BetterAuth session refresh in a future version
|
|
112
|
+
* @throws LegacyAuthDisabledException if legacy endpoints are disabled
|
|
56
113
|
*/
|
|
57
114
|
@Mutation(() => CoreAuthModel, { description: 'Refresh tokens (for specific device)' })
|
|
58
115
|
@Roles(RoleEnum.S_EVERYONE)
|
|
59
|
-
@UseGuards(AuthGuard(AuthGuardStrategy.JWT_REFRESH))
|
|
116
|
+
@UseGuards(LegacyAuthRateLimitGuard, AuthGuard(AuthGuardStrategy.JWT_REFRESH))
|
|
60
117
|
async refreshToken(
|
|
61
118
|
@CurrentUser() user: ICoreAuthUser,
|
|
62
119
|
@Tokens('refreshToken') refreshToken: string,
|
|
63
120
|
@Context() ctx: { res: ResponseType },
|
|
64
121
|
): Promise<CoreAuthModel> {
|
|
122
|
+
this.checkLegacyGraphQLEnabled('refreshToken');
|
|
65
123
|
const result = await this.authService.refreshTokens(user, refreshToken);
|
|
66
124
|
return this.processCookies(ctx, result);
|
|
67
125
|
}
|
|
68
126
|
|
|
69
127
|
/**
|
|
70
128
|
* Sign in user via email and password (on specific device)
|
|
129
|
+
*
|
|
130
|
+
* @deprecated Will be replaced by BetterAuth signIn in a future version
|
|
131
|
+
* @throws LegacyAuthDisabledException if legacy endpoints are disabled
|
|
71
132
|
*/
|
|
72
133
|
@Mutation(() => CoreAuthModel, {
|
|
73
134
|
description: 'Sign in user via email and password and get JWT tokens (for specific device)',
|
|
74
135
|
})
|
|
75
136
|
@Roles(RoleEnum.S_EVERYONE)
|
|
137
|
+
@UseGuards(LegacyAuthRateLimitGuard)
|
|
76
138
|
async signIn(
|
|
77
139
|
@GraphQLServiceOptions({ gqlPath: 'signIn.user' }) serviceOptions: ServiceOptions,
|
|
78
140
|
@Context() ctx: { res: ResponseType },
|
|
79
141
|
@Args('input') input: CoreAuthSignInInput,
|
|
80
142
|
): Promise<CoreAuthModel> {
|
|
143
|
+
this.checkLegacyGraphQLEnabled('signIn');
|
|
81
144
|
const result = await this.authService.signIn(input, serviceOptions);
|
|
82
145
|
return this.processCookies(ctx, result);
|
|
83
146
|
}
|
|
84
147
|
|
|
85
148
|
/**
|
|
86
149
|
* Register a new user account (on specific device)
|
|
150
|
+
*
|
|
151
|
+
* @deprecated Will be replaced by BetterAuth signUp in a future version
|
|
152
|
+
* @throws LegacyAuthDisabledException if legacy endpoints are disabled
|
|
87
153
|
*/
|
|
88
154
|
@Mutation(() => CoreAuthModel, { description: 'Register a new user account (on specific device)' })
|
|
89
155
|
@Roles(RoleEnum.S_EVERYONE)
|
|
156
|
+
@UseGuards(LegacyAuthRateLimitGuard)
|
|
90
157
|
async signUp(
|
|
91
158
|
@GraphQLServiceOptions({ gqlPath: 'signUp.user' }) serviceOptions: ServiceOptions,
|
|
92
159
|
@Context() ctx: { res: ResponseType },
|
|
93
160
|
@Args('input') input: CoreAuthSignUpInput,
|
|
94
161
|
): Promise<CoreAuthModel> {
|
|
162
|
+
this.checkLegacyGraphQLEnabled('signUp');
|
|
95
163
|
const result = await this.authService.signUp(input, serviceOptions);
|
|
96
164
|
return this.processCookies(ctx, result);
|
|
97
165
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { GoneException } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Exception thrown when Legacy Auth endpoints are accessed but disabled
|
|
5
|
+
*
|
|
6
|
+
* This exception is thrown when:
|
|
7
|
+
* - config.auth.legacyEndpoints.enabled is false
|
|
8
|
+
* - config.auth.legacyEndpoints.graphql is false (for GraphQL endpoints)
|
|
9
|
+
* - config.auth.legacyEndpoints.rest is false (for REST endpoints)
|
|
10
|
+
*
|
|
11
|
+
* HTTP Status: 410 Gone
|
|
12
|
+
*
|
|
13
|
+
* This status code indicates that the resource is no longer available
|
|
14
|
+
* and will not be available again - appropriate for deprecated endpoints.
|
|
15
|
+
*
|
|
16
|
+
* @since 11.7.1
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* if (!this.isLegacyEndpointEnabled()) {
|
|
21
|
+
* throw new LegacyAuthDisabledException();
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export class LegacyAuthDisabledException extends GoneException {
|
|
26
|
+
constructor(endpoint?: string) {
|
|
27
|
+
super({
|
|
28
|
+
error: 'Legacy Auth Disabled',
|
|
29
|
+
message: endpoint
|
|
30
|
+
? `Legacy Auth endpoint '${endpoint}' is disabled. Use BetterAuth (IAM) endpoints instead.`
|
|
31
|
+
: 'Legacy Auth endpoints are disabled. Use BetterAuth (IAM) endpoints instead.',
|
|
32
|
+
statusCode: 410,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { CanActivate, ExecutionContext, HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
|
2
|
+
import { GqlExecutionContext } from '@nestjs/graphql';
|
|
3
|
+
|
|
4
|
+
import { LegacyAuthRateLimiter } from '../services/legacy-auth-rate-limiter.service';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Guard for rate limiting Legacy Auth endpoints
|
|
8
|
+
*
|
|
9
|
+
* This guard applies rate limiting to protect against brute-force attacks.
|
|
10
|
+
* It works with both REST and GraphQL endpoints.
|
|
11
|
+
*
|
|
12
|
+
* Rate limiting must be enabled via configuration:
|
|
13
|
+
* ```typescript
|
|
14
|
+
* auth: {
|
|
15
|
+
* rateLimit: {
|
|
16
|
+
* enabled: true,
|
|
17
|
+
* max: 10,
|
|
18
|
+
* windowSeconds: 60,
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @since 11.7.x
|
|
24
|
+
*/
|
|
25
|
+
@Injectable()
|
|
26
|
+
export class LegacyAuthRateLimitGuard implements CanActivate {
|
|
27
|
+
constructor(private readonly rateLimiter: LegacyAuthRateLimiter) {}
|
|
28
|
+
|
|
29
|
+
canActivate(context: ExecutionContext): boolean {
|
|
30
|
+
// If rate limiting is disabled, always allow
|
|
31
|
+
if (!this.rateLimiter.isEnabled()) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const { endpoint, ip } = this.extractRequestInfo(context);
|
|
36
|
+
const result = this.rateLimiter.check(ip, endpoint);
|
|
37
|
+
|
|
38
|
+
if (!result.allowed) {
|
|
39
|
+
throw new HttpException(
|
|
40
|
+
{
|
|
41
|
+
error: 'Too Many Requests',
|
|
42
|
+
message: this.rateLimiter.getMessage(),
|
|
43
|
+
remaining: result.remaining,
|
|
44
|
+
retryAfter: result.resetIn,
|
|
45
|
+
statusCode: HttpStatus.TOO_MANY_REQUESTS,
|
|
46
|
+
},
|
|
47
|
+
HttpStatus.TOO_MANY_REQUESTS,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Extract IP and endpoint from the execution context
|
|
56
|
+
*/
|
|
57
|
+
private extractRequestInfo(context: ExecutionContext): { endpoint: string; ip: string } {
|
|
58
|
+
const contextType = context.getType<'graphql' | 'http'>();
|
|
59
|
+
|
|
60
|
+
if (contextType === 'graphql') {
|
|
61
|
+
const gqlContext = GqlExecutionContext.create(context);
|
|
62
|
+
const info = gqlContext.getInfo();
|
|
63
|
+
const ctx = gqlContext.getContext();
|
|
64
|
+
|
|
65
|
+
// Get IP from request
|
|
66
|
+
const req = ctx.req;
|
|
67
|
+
const ip = this.getClientIp(req);
|
|
68
|
+
|
|
69
|
+
// Get endpoint from GraphQL field name
|
|
70
|
+
const endpoint = info?.fieldName || 'unknown';
|
|
71
|
+
|
|
72
|
+
return { endpoint, ip };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// HTTP context
|
|
76
|
+
const request = context.switchToHttp().getRequest();
|
|
77
|
+
const ip = this.getClientIp(request);
|
|
78
|
+
|
|
79
|
+
// Get endpoint from URL path
|
|
80
|
+
const url = request.url || request.path || '';
|
|
81
|
+
const endpoint = url.split('/').pop() || 'unknown';
|
|
82
|
+
|
|
83
|
+
return { endpoint, ip };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get client IP from request, handling proxies
|
|
88
|
+
*/
|
|
89
|
+
private getClientIp(request: any): string {
|
|
90
|
+
if (!request) {
|
|
91
|
+
return 'unknown';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check common proxy headers
|
|
95
|
+
const forwardedFor = request.headers?.['x-forwarded-for'];
|
|
96
|
+
if (forwardedFor) {
|
|
97
|
+
// Take the first IP in the chain (original client)
|
|
98
|
+
return forwardedFor.split(',')[0].trim();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const realIp = request.headers?.['x-real-ip'];
|
|
102
|
+
if (realIp) {
|
|
103
|
+
return realIp;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Fall back to direct connection IP
|
|
107
|
+
return request.ip || request.connection?.remoteAddress || 'unknown';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { JwtPayload } from './jwt-payload.interface';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Auth Provider Interface
|
|
5
|
+
*
|
|
6
|
+
* This interface defines the contract for authentication providers.
|
|
7
|
+
* Both Legacy Auth (CoreAuthService) and BetterAuth can implement this interface,
|
|
8
|
+
* allowing CoreModule to work with either system transparently.
|
|
9
|
+
*
|
|
10
|
+
* @since 11.8.0
|
|
11
|
+
* @see https://github.com/lenneTech/nest-server/blob/develop/src/core/modules/auth/interfaces/auth-provider.interface.ts
|
|
12
|
+
*
|
|
13
|
+
* ## Roadmap
|
|
14
|
+
*
|
|
15
|
+
* ### v11.x (Current)
|
|
16
|
+
* - Interface introduced for future flexibility
|
|
17
|
+
* - Legacy Auth (CoreAuthService) is the default implementation
|
|
18
|
+
* - BetterAuth can be used alongside Legacy Auth
|
|
19
|
+
*
|
|
20
|
+
* ### Future Version (Planned)
|
|
21
|
+
* - CoreModule.forRoot will use IAuthProvider instead of concrete AuthService
|
|
22
|
+
* - Legacy Auth becomes optional (must be explicitly enabled)
|
|
23
|
+
* - BetterAuth becomes the recommended default
|
|
24
|
+
*
|
|
25
|
+
* ## Implementation Example
|
|
26
|
+
*
|
|
27
|
+
* ```typescript
|
|
28
|
+
* @Injectable()
|
|
29
|
+
* export class BetterAuthProvider implements IAuthProvider {
|
|
30
|
+
* constructor(private readonly betterAuthService: BetterAuthService) {}
|
|
31
|
+
*
|
|
32
|
+
* decodeJwt(token: string): JwtPayload {
|
|
33
|
+
* return this.betterAuthService.decodeJwt(token);
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* async validateUser(payload: JwtPayload): Promise<any> {
|
|
37
|
+
* return this.betterAuthService.validateUser(payload);
|
|
38
|
+
* }
|
|
39
|
+
*
|
|
40
|
+
* signToken(user: any, expiresIn?: string): string {
|
|
41
|
+
* return this.betterAuthService.signToken(user, expiresIn);
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export interface IAuthProvider {
|
|
47
|
+
/**
|
|
48
|
+
* Decode a JWT token without verification
|
|
49
|
+
* Used for extracting payload information
|
|
50
|
+
*
|
|
51
|
+
* @param token - The JWT token to decode
|
|
52
|
+
* @returns The decoded JWT payload
|
|
53
|
+
*/
|
|
54
|
+
decodeJwt(token: string): JwtPayload;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Sign a new JWT token for a user
|
|
58
|
+
*
|
|
59
|
+
* @param user - The user to create a token for
|
|
60
|
+
* @param expiresIn - Optional expiration time (e.g., '15m', '7d')
|
|
61
|
+
* @returns The signed JWT token
|
|
62
|
+
*/
|
|
63
|
+
signToken(user: any, expiresIn?: string): string;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Validate a user based on JWT payload
|
|
67
|
+
* Called during authentication to verify the user exists and is valid
|
|
68
|
+
*
|
|
69
|
+
* @param payload - The JWT payload containing user information
|
|
70
|
+
* @returns The validated user object, or null if invalid
|
|
71
|
+
*/
|
|
72
|
+
validateUser(payload: JwtPayload): Promise<any>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Auth Provider Token for dependency injection
|
|
77
|
+
*
|
|
78
|
+
* Use this token to inject the auth provider in your services:
|
|
79
|
+
*
|
|
80
|
+
* ```typescript
|
|
81
|
+
* constructor(
|
|
82
|
+
* @Inject(AUTH_PROVIDER) private readonly authProvider: IAuthProvider,
|
|
83
|
+
* ) {}
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export const AUTH_PROVIDER = 'AUTH_PROVIDER';
|