@lenne.tech/nest-server 11.7.0 → 11.7.1
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 +17 -0
- package/dist/core/modules/auth/core-auth.controller.d.ts +1 -0
- package/dist/core/modules/auth/core-auth.controller.js +28 -2
- 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 +20 -2
- 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-user.mapper.d.ts +33 -0
- package/dist/core/modules/better-auth/better-auth-user.mapper.js +443 -0
- package/dist/core/modules/better-auth/better-auth-user.mapper.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 +2 -0
- package/dist/core/modules/better-auth/core-better-auth.resolver.js +14 -0
- 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 +133 -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.resolver.d.ts +2 -0
- package/dist/server/modules/better-auth/better-auth.resolver.js +13 -0
- package/dist/server/modules/better-auth/better-auth.resolver.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 +175 -0
- package/src/core/modules/auth/core-auth.controller.ts +93 -5
- package/src/core/modules/auth/core-auth.module.ts +15 -1
- package/src/core/modules/auth/core-auth.resolver.ts +70 -2
- 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 +254 -0
- package/src/core/modules/better-auth/README.md +487 -169
- package/src/core/modules/better-auth/better-auth-migration-status.model.ts +73 -0
- package/src/core/modules/better-auth/better-auth-user.mapper.ts +805 -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 +25 -0
- 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 +258 -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.resolver.ts +9 -0
- package/src/server/modules/user/user.service.ts +4 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "11.7.
|
|
3
|
+
"version": "11.7.1",
|
|
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",
|
package/src/config.env.ts
CHANGED
|
@@ -14,6 +14,16 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
14
14
|
// Development environment
|
|
15
15
|
// ===========================================================================
|
|
16
16
|
development: {
|
|
17
|
+
// Legacy Auth endpoint controls (for migration to BetterAuth)
|
|
18
|
+
// Set to false after all users have migrated to BetterAuth (IAM)
|
|
19
|
+
// See: .claude/rules/module-deprecation.md
|
|
20
|
+
auth: {
|
|
21
|
+
legacyEndpoints: {
|
|
22
|
+
enabled: true, // Set to false to disable legacy auth endpoints (returns HTTP 410)
|
|
23
|
+
// graphql: true, // Optionally disable only GraphQL endpoints
|
|
24
|
+
// rest: true, // Optionally disable only REST endpoints
|
|
25
|
+
},
|
|
26
|
+
},
|
|
17
27
|
automaticObjectIdFiltering: true,
|
|
18
28
|
betterAuth: {
|
|
19
29
|
basePath: '/iam',
|
|
@@ -162,11 +172,21 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
162
172
|
// Local environment
|
|
163
173
|
// ===========================================================================
|
|
164
174
|
local: {
|
|
175
|
+
// Legacy Auth endpoint controls (for migration to BetterAuth)
|
|
176
|
+
// Set to false after all users have migrated to BetterAuth (IAM)
|
|
177
|
+
// See: .claude/rules/module-deprecation.md
|
|
178
|
+
auth: {
|
|
179
|
+
legacyEndpoints: {
|
|
180
|
+
enabled: true, // Set to false to disable legacy auth endpoints (returns HTTP 410)
|
|
181
|
+
// graphql: true, // Optionally disable only GraphQL endpoints
|
|
182
|
+
// rest: true, // Optionally disable only REST endpoints
|
|
183
|
+
},
|
|
184
|
+
},
|
|
165
185
|
automaticObjectIdFiltering: true,
|
|
166
186
|
betterAuth: {
|
|
167
187
|
basePath: '/iam',
|
|
168
188
|
baseUrl: 'http://localhost:3000',
|
|
169
|
-
|
|
189
|
+
enabled: true, // Enable for Scenario 2 (Legacy + IAM) testing
|
|
170
190
|
jwt: {
|
|
171
191
|
enabled: true,
|
|
172
192
|
expiresIn: '15m',
|
|
@@ -179,7 +199,7 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
179
199
|
},
|
|
180
200
|
rateLimit: {
|
|
181
201
|
enabled: true,
|
|
182
|
-
max:
|
|
202
|
+
max: 100, // Higher limit for local testing
|
|
183
203
|
message: 'Too many requests, please try again later.',
|
|
184
204
|
skipEndpoints: ['/session', '/callback'],
|
|
185
205
|
strictEndpoints: ['/sign-in', '/sign-up', '/forgot-password', '/reset-password'],
|
|
@@ -321,6 +341,16 @@ const config: { [env: string]: IServerOptions } = {
|
|
|
321
341
|
// Production environment
|
|
322
342
|
// ===========================================================================
|
|
323
343
|
production: {
|
|
344
|
+
// Legacy Auth endpoint controls (for migration to BetterAuth)
|
|
345
|
+
// Set to false after all users have migrated to BetterAuth (IAM)
|
|
346
|
+
// See: .claude/rules/module-deprecation.md
|
|
347
|
+
auth: {
|
|
348
|
+
legacyEndpoints: {
|
|
349
|
+
enabled: process.env.LEGACY_AUTH_ENABLED !== 'false', // Disable via env var
|
|
350
|
+
// graphql: true, // Optionally disable only GraphQL endpoints
|
|
351
|
+
// rest: true, // Optionally disable only REST endpoints
|
|
352
|
+
},
|
|
353
|
+
},
|
|
324
354
|
automaticObjectIdFiltering: true,
|
|
325
355
|
betterAuth: {
|
|
326
356
|
basePath: '/iam',
|
|
@@ -22,6 +22,170 @@ import { MailjetOptions } from './mailjet-options.interface';
|
|
|
22
22
|
*/
|
|
23
23
|
export type BetterAuthFieldType = 'boolean' | 'date' | 'json' | 'number' | 'number[]' | 'string' | 'string[]';
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Interface for Auth configuration
|
|
27
|
+
*
|
|
28
|
+
* This configuration controls the authentication system behavior.
|
|
29
|
+
* In v11.x, Legacy Auth (CoreAuthService) is the default.
|
|
30
|
+
* In a future version, BetterAuth (IAM) will become the default.
|
|
31
|
+
*
|
|
32
|
+
* @since 11.7.1
|
|
33
|
+
*
|
|
34
|
+
* ## Migration Roadmap
|
|
35
|
+
*
|
|
36
|
+
* ### v11.x (Current)
|
|
37
|
+
* - Legacy Auth is the default and required for GraphQL Subscriptions
|
|
38
|
+
* - BetterAuth can be used alongside Legacy Auth
|
|
39
|
+
* - Use `legacyEndpoints.enabled: false` after all users migrated to IAM
|
|
40
|
+
*
|
|
41
|
+
* ### Future Version (Planned)
|
|
42
|
+
* - BetterAuth becomes the default
|
|
43
|
+
* - Legacy Auth becomes optional (must be explicitly enabled)
|
|
44
|
+
* - CoreModule.forRoot signature simplifies to `CoreModule.forRoot(options)`
|
|
45
|
+
*
|
|
46
|
+
* @see https://github.com/lenneTech/nest-server/blob/develop/.claude/rules/module-deprecation.md
|
|
47
|
+
*/
|
|
48
|
+
export interface IAuth {
|
|
49
|
+
/**
|
|
50
|
+
* Configuration for legacy auth endpoints
|
|
51
|
+
*
|
|
52
|
+
* Legacy endpoints include:
|
|
53
|
+
* - GraphQL: signIn, signUp, signOut, refreshToken mutations
|
|
54
|
+
* - REST: /api/auth/* endpoints
|
|
55
|
+
*
|
|
56
|
+
* These can be disabled once all users have migrated to BetterAuth (IAM).
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* auth: {
|
|
61
|
+
* legacyEndpoints: {
|
|
62
|
+
* enabled: false // Disable all legacy endpoints after migration
|
|
63
|
+
* }
|
|
64
|
+
* }
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
legacyEndpoints?: IAuthLegacyEndpoints;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Prevent user enumeration via unified error messages
|
|
71
|
+
*
|
|
72
|
+
* When enabled, authentication errors return a generic "Invalid credentials"
|
|
73
|
+
* message instead of specific messages like "Unknown email" or "Wrong password".
|
|
74
|
+
*
|
|
75
|
+
* This prevents attackers from determining whether an email address exists
|
|
76
|
+
* in the system, but reduces UX clarity for legitimate users.
|
|
77
|
+
*
|
|
78
|
+
* @since 11.7.x
|
|
79
|
+
* @default false (backward compatible - specific error messages)
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* auth: {
|
|
84
|
+
* preventUserEnumeration: true // Returns "Invalid credentials" for all auth errors
|
|
85
|
+
* }
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
preventUserEnumeration?: boolean;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Rate limiting configuration for Legacy Auth endpoints
|
|
92
|
+
*
|
|
93
|
+
* Protects against brute-force attacks on signIn, signUp, and other
|
|
94
|
+
* authentication endpoints.
|
|
95
|
+
*
|
|
96
|
+
* Follows the same pattern as `betterAuth.rateLimit`.
|
|
97
|
+
*
|
|
98
|
+
* @since 11.7.x
|
|
99
|
+
* @default { enabled: false }
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* auth: {
|
|
104
|
+
* rateLimit: {
|
|
105
|
+
* enabled: true,
|
|
106
|
+
* max: 10,
|
|
107
|
+
* windowSeconds: 60,
|
|
108
|
+
* message: 'Too many login attempts, please try again later.',
|
|
109
|
+
* }
|
|
110
|
+
* }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
rateLimit?: IAuthRateLimit;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Interface for Legacy Auth endpoints configuration
|
|
118
|
+
*
|
|
119
|
+
* These endpoints are part of the Legacy Auth system (CoreAuthService).
|
|
120
|
+
* In a future version, BetterAuth (IAM) will become the default and these endpoints
|
|
121
|
+
* can be disabled once all users have migrated.
|
|
122
|
+
*
|
|
123
|
+
* @since 11.7.1
|
|
124
|
+
* @see https://github.com/lenneTech/nest-server/blob/develop/.claude/rules/module-deprecation.md
|
|
125
|
+
*/
|
|
126
|
+
export interface IAuthLegacyEndpoints {
|
|
127
|
+
/**
|
|
128
|
+
* Whether legacy auth endpoints are enabled.
|
|
129
|
+
*
|
|
130
|
+
* Set to false to disable all legacy auth endpoints (GraphQL and REST).
|
|
131
|
+
* Use this after all users have migrated to BetterAuth (IAM).
|
|
132
|
+
*
|
|
133
|
+
* Check migration status via the `betterAuthMigrationStatus` query.
|
|
134
|
+
*
|
|
135
|
+
* @default true
|
|
136
|
+
*/
|
|
137
|
+
enabled?: boolean;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Whether legacy GraphQL auth endpoints are enabled.
|
|
141
|
+
* Affects: signIn, signUp, signOut, refreshToken mutations
|
|
142
|
+
*
|
|
143
|
+
* @default true (inherits from `enabled`)
|
|
144
|
+
*/
|
|
145
|
+
graphql?: boolean;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Whether legacy REST auth endpoints are enabled.
|
|
149
|
+
* Affects: /api/auth/sign-in, /api/auth/sign-up, etc.
|
|
150
|
+
*
|
|
151
|
+
* @default true (inherits from `enabled`)
|
|
152
|
+
*/
|
|
153
|
+
rest?: boolean;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Interface for Legacy Auth rate limiting configuration
|
|
158
|
+
*
|
|
159
|
+
* Same structure as IBetterAuthRateLimit for consistency.
|
|
160
|
+
*
|
|
161
|
+
* @since 11.7.x
|
|
162
|
+
*/
|
|
163
|
+
export interface IAuthRateLimit {
|
|
164
|
+
/**
|
|
165
|
+
* Whether rate limiting is enabled
|
|
166
|
+
* @default false
|
|
167
|
+
*/
|
|
168
|
+
enabled?: boolean;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Maximum number of requests within the time window
|
|
172
|
+
* @default 10
|
|
173
|
+
*/
|
|
174
|
+
max?: number;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Custom message when rate limit is exceeded
|
|
178
|
+
* @default 'Too many requests, please try again later.'
|
|
179
|
+
*/
|
|
180
|
+
message?: string;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Time window in seconds
|
|
184
|
+
* @default 60
|
|
185
|
+
*/
|
|
186
|
+
windowSeconds?: number;
|
|
187
|
+
}
|
|
188
|
+
|
|
25
189
|
/**
|
|
26
190
|
* Interface for better-auth configuration
|
|
27
191
|
*/
|
|
@@ -413,6 +577,17 @@ export interface IJwt {
|
|
|
413
577
|
* Options for the server
|
|
414
578
|
*/
|
|
415
579
|
export interface IServerOptions {
|
|
580
|
+
/**
|
|
581
|
+
* Authentication system configuration
|
|
582
|
+
*
|
|
583
|
+
* Controls Legacy Auth endpoints and behavior.
|
|
584
|
+
* In a future version, this will also control BetterAuth as the default system.
|
|
585
|
+
*
|
|
586
|
+
* @since 11.7.1
|
|
587
|
+
* @see IAuth
|
|
588
|
+
*/
|
|
589
|
+
auth?: IAuth;
|
|
590
|
+
|
|
416
591
|
/**
|
|
417
592
|
* Automatically detect ObjectIds in string values in FilterQueries
|
|
418
593
|
* and expand them as OR query with string and ObjectId.
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { Body, Controller, Get, ParseBoolPipe, Post, Query, Res, UseGuards } from '@nestjs/common';
|
|
2
2
|
import {
|
|
3
|
-
ApiBody,
|
|
3
|
+
ApiBody,
|
|
4
|
+
ApiCreatedResponse,
|
|
5
|
+
ApiGoneResponse,
|
|
4
6
|
ApiOkResponse,
|
|
5
7
|
ApiOperation,
|
|
6
8
|
ApiQuery,
|
|
9
|
+
ApiTooManyRequestsResponse,
|
|
7
10
|
} from '@nestjs/swagger';
|
|
8
11
|
import { Response as ResponseType } from 'express';
|
|
9
12
|
|
|
@@ -14,13 +17,38 @@ import { RoleEnum } from '../../common/enums/role.enum';
|
|
|
14
17
|
import { ConfigService } from '../../common/services/config.service';
|
|
15
18
|
import { AuthGuardStrategy } from './auth-guard-strategy.enum';
|
|
16
19
|
import { CoreAuthModel } from './core-auth.model';
|
|
20
|
+
import { LegacyAuthDisabledException } from './exceptions/legacy-auth-disabled.exception';
|
|
17
21
|
import { AuthGuard } from './guards/auth.guard';
|
|
22
|
+
import { LegacyAuthRateLimitGuard } from './guards/legacy-auth-rate-limit.guard';
|
|
18
23
|
import { CoreAuthSignInInput } from './inputs/core-auth-sign-in.input';
|
|
19
24
|
import { CoreAuthSignUpInput } from './inputs/core-auth-sign-up.input';
|
|
20
25
|
import { ICoreAuthUser } from './interfaces/core-auth-user.interface';
|
|
21
26
|
import { CoreAuthService } from './services/core-auth.service';
|
|
22
27
|
import { Tokens } from './tokens.decorator';
|
|
23
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Authentication controller for REST endpoints
|
|
31
|
+
*
|
|
32
|
+
* This controller provides Legacy Auth endpoints via REST.
|
|
33
|
+
* In a future version, BetterAuth (IAM) will become the default.
|
|
34
|
+
*
|
|
35
|
+
* ## Disabling Legacy Endpoints
|
|
36
|
+
*
|
|
37
|
+
* After all users have migrated to BetterAuth (IAM), these endpoints
|
|
38
|
+
* can be disabled via configuration:
|
|
39
|
+
*
|
|
40
|
+
* ```typescript
|
|
41
|
+
* auth: {
|
|
42
|
+
* legacyEndpoints: {
|
|
43
|
+
* enabled: false, // Disable all legacy endpoints
|
|
44
|
+
* // or
|
|
45
|
+
* rest: false // Disable only REST endpoints
|
|
46
|
+
* }
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @see https://github.com/lenneTech/nest-server/blob/develop/.claude/rules/module-deprecation.md
|
|
51
|
+
*/
|
|
24
52
|
@ApiCommonErrorResponses()
|
|
25
53
|
@Controller('auth')
|
|
26
54
|
@Roles(RoleEnum.ADMIN)
|
|
@@ -33,63 +61,123 @@ export class CoreAuthController {
|
|
|
33
61
|
protected readonly configService: ConfigService,
|
|
34
62
|
) {}
|
|
35
63
|
|
|
64
|
+
// ===========================================================================
|
|
65
|
+
// Helper - Legacy Endpoint Check
|
|
66
|
+
// ===========================================================================
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Check if legacy REST endpoints are enabled
|
|
70
|
+
*
|
|
71
|
+
* Throws LegacyAuthDisabledException if:
|
|
72
|
+
* - config.auth.legacyEndpoints.enabled is false
|
|
73
|
+
* - config.auth.legacyEndpoints.rest is false
|
|
74
|
+
*
|
|
75
|
+
* @throws LegacyAuthDisabledException
|
|
76
|
+
*/
|
|
77
|
+
protected checkLegacyRESTEnabled(endpointName: string): void {
|
|
78
|
+
const authConfig = this.configService.getFastButReadOnly('auth');
|
|
79
|
+
const legacyConfig = authConfig?.legacyEndpoints;
|
|
80
|
+
|
|
81
|
+
// Check if legacy endpoints are globally disabled
|
|
82
|
+
if (legacyConfig?.enabled === false) {
|
|
83
|
+
throw new LegacyAuthDisabledException(endpointName);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check if REST endpoints specifically are disabled
|
|
87
|
+
if (legacyConfig?.rest === false) {
|
|
88
|
+
throw new LegacyAuthDisabledException(endpointName);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
36
92
|
/**
|
|
37
93
|
* Logout user (from specific device)
|
|
94
|
+
*
|
|
95
|
+
* @deprecated Will be replaced by BetterAuth signOut in a future version
|
|
96
|
+
* @throws LegacyAuthDisabledException if legacy endpoints are disabled
|
|
38
97
|
*/
|
|
98
|
+
@ApiGoneResponse({ description: 'Legacy Auth endpoints are disabled' })
|
|
39
99
|
@ApiOkResponse({ type: Boolean })
|
|
40
100
|
@ApiOperation({ description: 'Logs a user out from a specific device' })
|
|
41
101
|
@ApiQuery({ description: 'If all devices should be logged out,', name: 'allDevices', required: false, type: Boolean })
|
|
102
|
+
@ApiTooManyRequestsResponse({ description: 'Rate limit exceeded' })
|
|
42
103
|
@Get('logout')
|
|
43
104
|
@Roles(RoleEnum.S_EVERYONE)
|
|
44
|
-
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
105
|
+
@UseGuards(LegacyAuthRateLimitGuard, AuthGuard(AuthGuardStrategy.JWT))
|
|
45
106
|
async logout(
|
|
46
107
|
@CurrentUser() currentUser: ICoreAuthUser,
|
|
47
108
|
@Tokens('token') token: string,
|
|
48
109
|
@Res({ passthrough: true }) res: ResponseType,
|
|
49
110
|
@Query('allDevices', new ParseBoolPipe({ optional: true })) allDevices?: boolean,
|
|
50
111
|
): Promise<boolean> {
|
|
112
|
+
this.checkLegacyRESTEnabled('logout');
|
|
51
113
|
const result = await this.authService.logout(token, { allDevices, currentUser });
|
|
52
114
|
return this.processCookies(res, result);
|
|
53
115
|
}
|
|
54
116
|
|
|
55
117
|
/**
|
|
56
118
|
* Refresh token (for specific device)
|
|
119
|
+
*
|
|
120
|
+
* @deprecated Will be replaced by BetterAuth session refresh in a future version
|
|
121
|
+
* @throws LegacyAuthDisabledException if legacy endpoints are disabled
|
|
57
122
|
*/
|
|
123
|
+
@ApiGoneResponse({ description: 'Legacy Auth endpoints are disabled' })
|
|
58
124
|
@ApiOkResponse({ type: CoreAuthModel })
|
|
59
125
|
@ApiOperation({ description: 'Refresh token (for specific device)' })
|
|
126
|
+
@ApiTooManyRequestsResponse({ description: 'Rate limit exceeded' })
|
|
60
127
|
@Get('refresh-token')
|
|
61
128
|
@Roles(RoleEnum.S_EVERYONE)
|
|
62
|
-
@UseGuards(AuthGuard(AuthGuardStrategy.JWT_REFRESH))
|
|
129
|
+
@UseGuards(LegacyAuthRateLimitGuard, AuthGuard(AuthGuardStrategy.JWT_REFRESH))
|
|
63
130
|
async refreshToken(
|
|
64
131
|
@CurrentUser() user: ICoreAuthUser,
|
|
65
132
|
@Tokens('refreshToken') refreshToken: string,
|
|
66
133
|
@Res({ passthrough: true }) res: ResponseType,
|
|
67
134
|
): Promise<CoreAuthModel> {
|
|
135
|
+
this.checkLegacyRESTEnabled('refresh-token');
|
|
68
136
|
const result = await this.authService.refreshTokens(user, refreshToken);
|
|
69
137
|
return this.processCookies(res, result);
|
|
70
138
|
}
|
|
71
139
|
|
|
72
140
|
/**
|
|
73
141
|
* Sign in user via email and password (on specific device)
|
|
142
|
+
*
|
|
143
|
+
* @deprecated Will be replaced by BetterAuth signIn in a future version
|
|
144
|
+
* @throws LegacyAuthDisabledException if legacy endpoints are disabled
|
|
74
145
|
*/
|
|
75
146
|
@ApiCreatedResponse({ description: 'Signed in successfully', type: CoreAuthModel })
|
|
147
|
+
@ApiGoneResponse({ description: 'Legacy Auth endpoints are disabled' })
|
|
76
148
|
@ApiOperation({ description: 'Sign in via email and password' })
|
|
149
|
+
@ApiTooManyRequestsResponse({ description: 'Rate limit exceeded' })
|
|
77
150
|
@Post('signin')
|
|
78
151
|
@Roles(RoleEnum.S_EVERYONE)
|
|
79
|
-
|
|
152
|
+
@UseGuards(LegacyAuthRateLimitGuard)
|
|
153
|
+
async signIn(
|
|
154
|
+
@Res({ passthrough: true }) res: ResponseType,
|
|
155
|
+
@Body() input: CoreAuthSignInInput,
|
|
156
|
+
): Promise<CoreAuthModel> {
|
|
157
|
+
this.checkLegacyRESTEnabled('signin');
|
|
80
158
|
const result = await this.authService.signIn(input);
|
|
81
159
|
return this.processCookies(res, result);
|
|
82
160
|
}
|
|
83
161
|
|
|
84
162
|
/**
|
|
85
163
|
* Register a new user account (on specific device)
|
|
164
|
+
*
|
|
165
|
+
* @deprecated Will be replaced by BetterAuth signUp in a future version
|
|
166
|
+
* @throws LegacyAuthDisabledException if legacy endpoints are disabled
|
|
86
167
|
*/
|
|
87
168
|
@ApiBody({ type: CoreAuthSignUpInput })
|
|
88
169
|
@ApiCreatedResponse({ type: CoreAuthSignUpInput })
|
|
170
|
+
@ApiGoneResponse({ description: 'Legacy Auth endpoints are disabled' })
|
|
89
171
|
@ApiOperation({ description: 'Sign up via email and password' })
|
|
172
|
+
@ApiTooManyRequestsResponse({ description: 'Rate limit exceeded' })
|
|
90
173
|
@Post('signup')
|
|
91
174
|
@Roles(RoleEnum.S_EVERYONE)
|
|
92
|
-
|
|
175
|
+
@UseGuards(LegacyAuthRateLimitGuard)
|
|
176
|
+
async signUp(
|
|
177
|
+
@Res({ passthrough: true }) res: ResponseType,
|
|
178
|
+
@Body() input: CoreAuthSignUpInput,
|
|
179
|
+
): Promise<CoreAuthModel> {
|
|
180
|
+
this.checkLegacyRESTEnabled('signup');
|
|
93
181
|
const result = await this.authService.signUp(input);
|
|
94
182
|
return this.processCookies(res, result);
|
|
95
183
|
}
|
|
@@ -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
95
|
@Roles(RoleEnum.S_EVERYONE)
|
|
43
|
-
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
96
|
+
@UseGuards(LegacyAuthRateLimitGuard, AuthGuard(AuthGuardStrategy.JWT))
|
|
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
|
+
}
|