@lenne.tech/nest-server 11.6.2 → 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 +19 -12
- package/dist/config.env.js.map +1 -1
- package/dist/core/common/helpers/filter.helper.d.ts +9 -9
- package/dist/core/common/helpers/filter.helper.js +2 -4
- package/dist/core/common/helpers/filter.helper.js.map +1 -1
- package/dist/core/common/helpers/gridfs.helper.js +3 -3
- package/dist/core/common/helpers/gridfs.helper.js.map +1 -1
- package/dist/core/common/interfaces/server-options.interface.d.ts +21 -3
- package/dist/core/common/services/crud.service.d.ts +16 -16
- package/dist/core/common/services/crud.service.js +1 -1
- package/dist/core/common/services/crud.service.js.map +1 -1
- 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-models.d.ts +0 -1
- package/dist/core/modules/better-auth/better-auth-models.js +0 -4
- package/dist/core/modules/better-auth/better-auth-models.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 +443 -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 +3 -0
- package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.module.d.ts +10 -2
- package/dist/core/modules/better-auth/better-auth.module.js +40 -52
- package/dist/core/modules/better-auth/better-auth.module.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.resolver.d.ts +8 -12
- package/dist/core/modules/better-auth/better-auth.resolver.js +33 -351
- package/dist/core/modules/better-auth/better-auth.resolver.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.service.d.ts +0 -1
- package/dist/core/modules/better-auth/better-auth.service.js +0 -3
- package/dist/core/modules/better-auth/better-auth.service.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.types.d.ts +9 -8
- package/dist/core/modules/better-auth/better-auth.types.js +14 -3
- 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 +67 -0
- package/dist/core/modules/better-auth/core-better-auth.controller.js +504 -0
- package/dist/core/modules/better-auth/core-better-auth.controller.js.map +1 -0
- package/dist/core/modules/better-auth/core-better-auth.resolver.d.ts +61 -0
- package/dist/core/modules/better-auth/core-better-auth.resolver.js +552 -0
- package/dist/core/modules/better-auth/core-better-auth.resolver.js.map +1 -0
- package/dist/core/modules/better-auth/index.d.ts +3 -0
- package/dist/core/modules/better-auth/index.js +3 -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 +132 -54
- 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.controller.d.ts +10 -0
- package/dist/server/modules/better-auth/better-auth.controller.js +36 -0
- package/dist/server/modules/better-auth/better-auth.controller.js.map +1 -0
- package/dist/server/modules/better-auth/better-auth.module.d.ts +9 -0
- package/dist/server/modules/better-auth/better-auth.module.js +44 -0
- package/dist/server/modules/better-auth/better-auth.module.js.map +1 -0
- package/dist/server/modules/better-auth/better-auth.resolver.d.ts +47 -0
- package/dist/server/modules/better-auth/better-auth.resolver.js +234 -0
- package/dist/server/modules/better-auth/better-auth.resolver.js.map +1 -0
- package/dist/server/modules/file/file-info.model.d.ts +71 -3
- package/dist/server/modules/user/user.model.d.ts +169 -3
- 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/server/server.module.js +6 -1
- package/dist/server/server.module.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +20 -29
- package/src/config.env.ts +34 -13
- package/src/core/common/helpers/filter.helper.ts +15 -17
- package/src/core/common/helpers/gridfs.helper.ts +5 -5
- package/src/core/common/interfaces/server-options.interface.ts +222 -14
- package/src/core/common/services/crud.service.ts +22 -22
- 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 +698 -54
- package/src/core/modules/better-auth/better-auth-migration-status.model.ts +73 -0
- package/src/core/modules/better-auth/better-auth-models.ts +0 -3
- package/src/core/modules/better-auth/better-auth-user.mapper.ts +805 -0
- package/src/core/modules/better-auth/better-auth.config.ts +5 -0
- package/src/core/modules/better-auth/better-auth.module.ts +107 -66
- package/src/core/modules/better-auth/better-auth.resolver.ts +88 -553
- package/src/core/modules/better-auth/better-auth.service.ts +0 -9
- package/src/core/modules/better-auth/better-auth.types.ts +25 -10
- package/src/core/modules/better-auth/core-better-auth.controller.ts +646 -0
- package/src/core/modules/better-auth/core-better-auth.resolver.ts +730 -0
- package/src/core/modules/better-auth/index.ts +9 -1
- 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 +257 -74
- package/src/index.ts +5 -0
- package/src/server/modules/auth/auth.resolver.ts +8 -0
- package/src/server/modules/better-auth/better-auth.controller.ts +41 -0
- package/src/server/modules/better-auth/better-auth.module.ts +88 -0
- package/src/server/modules/better-auth/better-auth.resolver.ts +210 -0
- package/src/server/modules/user/user.service.ts +4 -2
- package/src/server/server.module.ts +10 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { UseGuards } from '@nestjs/common';
|
|
2
2
|
import { Args, Context, Mutation, Query, Resolver } from '@nestjs/graphql';
|
|
3
3
|
import { Request, Response } from 'express';
|
|
4
4
|
|
|
@@ -13,666 +13,201 @@ import {
|
|
|
13
13
|
BetterAuthPasskeyChallengeModel,
|
|
14
14
|
BetterAuthPasskeyModel,
|
|
15
15
|
BetterAuthSessionModel,
|
|
16
|
-
BetterAuthUserModel,
|
|
17
16
|
} from './better-auth-models';
|
|
18
|
-
import {
|
|
17
|
+
import { BetterAuthUserMapper } from './better-auth-user.mapper';
|
|
19
18
|
import { BetterAuthService } from './better-auth.service';
|
|
20
|
-
import {
|
|
21
|
-
BetterAuth2FAResponse,
|
|
22
|
-
BetterAuthSignInResponse,
|
|
23
|
-
BetterAuthSignUpResponse,
|
|
24
|
-
hasSession,
|
|
25
|
-
hasUser,
|
|
26
|
-
requires2FA,
|
|
27
|
-
} from './better-auth.types';
|
|
19
|
+
import { CoreBetterAuthResolver } from './core-better-auth.resolver';
|
|
28
20
|
|
|
29
21
|
/**
|
|
30
|
-
* GraphQL Resolver
|
|
22
|
+
* Default BetterAuth GraphQL Resolver
|
|
23
|
+
*
|
|
24
|
+
* This resolver extends CoreBetterAuthResolver and provides the default
|
|
25
|
+
* Better-Auth GraphQL operations. It re-declares all methods with decorators
|
|
26
|
+
* because CoreBetterAuthResolver uses `isAbstract: true`, which means its
|
|
27
|
+
* methods are not registered in the GraphQL schema.
|
|
28
|
+
*
|
|
29
|
+
* Override in your project if you need custom behavior (e.g., sending emails after sign-up).
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* // In your project - src/server/modules/better-auth/better-auth.resolver.ts
|
|
34
|
+
* @Resolver(() => BetterAuthAuthModel)
|
|
35
|
+
* export class BetterAuthResolver extends CoreBetterAuthResolver {
|
|
36
|
+
* constructor(
|
|
37
|
+
* betterAuthService: BetterAuthService,
|
|
38
|
+
* userMapper: BetterAuthUserMapper,
|
|
39
|
+
* private readonly emailService: EmailService,
|
|
40
|
+
* ) {
|
|
41
|
+
* super(betterAuthService, userMapper);
|
|
42
|
+
* }
|
|
31
43
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* for authentication.
|
|
44
|
+
* override async betterAuthSignUp(email: string, password: string, name?: string) {
|
|
45
|
+
* const result = await super.betterAuthSignUp(email, password, name);
|
|
35
46
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
47
|
+
* // Send welcome email after successful sign-up
|
|
48
|
+
* if (result.success && result.user) {
|
|
49
|
+
* await this.emailService.sendWelcomeEmail(result.user.email);
|
|
50
|
+
* }
|
|
51
|
+
*
|
|
52
|
+
* return result;
|
|
53
|
+
* }
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
38
56
|
*/
|
|
39
|
-
@Resolver()
|
|
57
|
+
@Resolver(() => BetterAuthAuthModel)
|
|
40
58
|
@Roles(RoleEnum.ADMIN)
|
|
41
|
-
export class BetterAuthResolver {
|
|
42
|
-
private readonly logger = new Logger(BetterAuthResolver.name);
|
|
43
|
-
|
|
59
|
+
export class BetterAuthResolver extends CoreBetterAuthResolver {
|
|
44
60
|
constructor(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
) {
|
|
61
|
+
protected override readonly betterAuthService: BetterAuthService,
|
|
62
|
+
protected override readonly userMapper: BetterAuthUserMapper,
|
|
63
|
+
) {
|
|
64
|
+
super(betterAuthService, userMapper);
|
|
65
|
+
}
|
|
48
66
|
|
|
49
67
|
// ===========================================================================
|
|
50
68
|
// Queries
|
|
51
69
|
// ===========================================================================
|
|
52
70
|
|
|
53
|
-
/**
|
|
54
|
-
* Get current Better-Auth session
|
|
55
|
-
*/
|
|
56
71
|
@Query(() => BetterAuthSessionModel, {
|
|
57
72
|
description: 'Get current Better-Auth session',
|
|
58
73
|
nullable: true,
|
|
59
74
|
})
|
|
60
75
|
@Roles(RoleEnum.S_USER)
|
|
61
76
|
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
62
|
-
async betterAuthSession(@Context() ctx: { req: Request }): Promise<BetterAuthSessionModel | null> {
|
|
63
|
-
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const { session, user } = await this.betterAuthService.getSession(ctx.req);
|
|
68
|
-
|
|
69
|
-
if (!session || !user) {
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
expiresAt: session.expiresAt,
|
|
75
|
-
id: session.id,
|
|
76
|
-
user: {
|
|
77
|
-
email: user.email,
|
|
78
|
-
emailVerified: user.emailVerified,
|
|
79
|
-
id: user.id,
|
|
80
|
-
name: user.name,
|
|
81
|
-
},
|
|
82
|
-
};
|
|
77
|
+
override async betterAuthSession(@Context() ctx: { req: Request }): Promise<BetterAuthSessionModel | null> {
|
|
78
|
+
return super.betterAuthSession(ctx);
|
|
83
79
|
}
|
|
84
80
|
|
|
85
|
-
/**
|
|
86
|
-
* Check if Better-Auth is enabled
|
|
87
|
-
*/
|
|
88
81
|
@Query(() => Boolean, { description: 'Check if Better-Auth is enabled' })
|
|
89
82
|
@Roles(RoleEnum.S_EVERYONE)
|
|
90
|
-
betterAuthEnabled(): boolean {
|
|
91
|
-
return
|
|
83
|
+
override betterAuthEnabled(): boolean {
|
|
84
|
+
return super.betterAuthEnabled();
|
|
92
85
|
}
|
|
93
86
|
|
|
94
|
-
/**
|
|
95
|
-
* Get enabled Better-Auth features
|
|
96
|
-
*/
|
|
97
87
|
@Query(() => BetterAuthFeaturesModel, { description: 'Get enabled Better-Auth features' })
|
|
98
88
|
@Roles(RoleEnum.S_EVERYONE)
|
|
99
|
-
betterAuthFeatures(): BetterAuthFeaturesModel {
|
|
100
|
-
return
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
89
|
+
override betterAuthFeatures(): BetterAuthFeaturesModel {
|
|
90
|
+
return super.betterAuthFeatures();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@Query(() => [BetterAuthPasskeyModel], {
|
|
94
|
+
description: 'List passkeys for the current user',
|
|
95
|
+
nullable: true,
|
|
96
|
+
})
|
|
97
|
+
@Roles(RoleEnum.S_USER)
|
|
98
|
+
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
99
|
+
override async betterAuthListPasskeys(@Context() ctx: { req: Request }): Promise<BetterAuthPasskeyModel[] | null> {
|
|
100
|
+
return super.betterAuthListPasskeys(ctx);
|
|
108
101
|
}
|
|
109
102
|
|
|
110
103
|
// ===========================================================================
|
|
111
|
-
// Mutations
|
|
104
|
+
// Authentication Mutations
|
|
112
105
|
// ===========================================================================
|
|
113
106
|
|
|
114
|
-
/**
|
|
115
|
-
* Sign in via Better-Auth
|
|
116
|
-
*
|
|
117
|
-
* This mutation wraps Better-Auth's sign-in endpoint and returns a response
|
|
118
|
-
* compatible with the existing auth system.
|
|
119
|
-
*/
|
|
120
107
|
@Mutation(() => BetterAuthAuthModel, {
|
|
121
108
|
description: 'Sign in via Better-Auth (email/password)',
|
|
122
109
|
})
|
|
123
110
|
@Roles(RoleEnum.S_EVERYONE)
|
|
124
|
-
async betterAuthSignIn(
|
|
111
|
+
override async betterAuthSignIn(
|
|
125
112
|
@Args('email') email: string,
|
|
126
113
|
@Args('password') password: string,
|
|
127
|
-
|
|
128
|
-
@Context() _ctx: { req: Request; res: Response },
|
|
114
|
+
@Context() ctx: { req: Request; res: Response },
|
|
129
115
|
): Promise<BetterAuthAuthModel> {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const api = this.betterAuthService.getApi();
|
|
133
|
-
if (!api) {
|
|
134
|
-
throw new BadRequestException('Better-Auth API not available');
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
try {
|
|
138
|
-
// Call Better-Auth's sign-in endpoint
|
|
139
|
-
const response = (await api.signInEmail({
|
|
140
|
-
body: { email, password },
|
|
141
|
-
})) as BetterAuthSignInResponse | null;
|
|
142
|
-
|
|
143
|
-
if (!response) {
|
|
144
|
-
throw new UnauthorizedException('Invalid credentials');
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Check for 2FA requirement
|
|
148
|
-
if (requires2FA(response)) {
|
|
149
|
-
return {
|
|
150
|
-
requiresTwoFactor: true,
|
|
151
|
-
success: false,
|
|
152
|
-
user: null,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Get user data
|
|
157
|
-
if (hasUser(response)) {
|
|
158
|
-
const sessionUser: BetterAuthSessionUser = response.user;
|
|
159
|
-
const mappedUser = await this.userMapper.mapSessionUser(sessionUser);
|
|
160
|
-
|
|
161
|
-
// Get token if JWT plugin is enabled
|
|
162
|
-
const token = this.betterAuthService.isJwtEnabled() ? response.token : undefined;
|
|
163
|
-
|
|
164
|
-
return {
|
|
165
|
-
requiresTwoFactor: false,
|
|
166
|
-
session: hasSession(response) ? this.mapSessionInfo(response.session) : null,
|
|
167
|
-
success: true,
|
|
168
|
-
token,
|
|
169
|
-
user: mappedUser ? this.mapToUserModel(mappedUser) : null,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
throw new UnauthorizedException('Invalid credentials');
|
|
174
|
-
} catch (error) {
|
|
175
|
-
this.logger.debug(`Sign-in error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
176
|
-
throw new UnauthorizedException('Invalid credentials');
|
|
177
|
-
}
|
|
116
|
+
return super.betterAuthSignIn(email, password, ctx);
|
|
178
117
|
}
|
|
179
118
|
|
|
180
|
-
/**
|
|
181
|
-
* Sign up via Better-Auth
|
|
182
|
-
*/
|
|
183
119
|
@Mutation(() => BetterAuthAuthModel, {
|
|
184
120
|
description: 'Sign up via Better-Auth (email/password)',
|
|
185
121
|
})
|
|
186
122
|
@Roles(RoleEnum.S_EVERYONE)
|
|
187
|
-
async betterAuthSignUp(
|
|
123
|
+
override async betterAuthSignUp(
|
|
188
124
|
@Args('email') email: string,
|
|
189
125
|
@Args('password') password: string,
|
|
190
126
|
@Args('name', { nullable: true }) name?: string,
|
|
191
127
|
): Promise<BetterAuthAuthModel> {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const api = this.betterAuthService.getApi();
|
|
195
|
-
if (!api) {
|
|
196
|
-
throw new BadRequestException('Better-Auth API not available');
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
try {
|
|
200
|
-
const response = (await api.signUpEmail({
|
|
201
|
-
body: {
|
|
202
|
-
email,
|
|
203
|
-
name: name || email.split('@')[0],
|
|
204
|
-
password,
|
|
205
|
-
},
|
|
206
|
-
})) as BetterAuthSignUpResponse | null;
|
|
207
|
-
|
|
208
|
-
if (!response) {
|
|
209
|
-
throw new BadRequestException('Sign-up failed');
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (hasUser(response)) {
|
|
213
|
-
const sessionUser: BetterAuthSessionUser = response.user;
|
|
214
|
-
|
|
215
|
-
// Link or create user in our database
|
|
216
|
-
await this.userMapper.linkOrCreateUser(sessionUser);
|
|
217
|
-
const mappedUser = await this.userMapper.mapSessionUser(sessionUser);
|
|
218
|
-
|
|
219
|
-
return {
|
|
220
|
-
requiresTwoFactor: false,
|
|
221
|
-
session: hasSession(response) ? this.mapSessionInfo(response.session) : null,
|
|
222
|
-
success: true,
|
|
223
|
-
user: mappedUser ? this.mapToUserModel(mappedUser) : null,
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
throw new BadRequestException('Sign-up failed');
|
|
228
|
-
} catch (error) {
|
|
229
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
230
|
-
this.logger.debug(`Sign-up error: ${errorMessage}`);
|
|
231
|
-
if (errorMessage.includes('already exists')) {
|
|
232
|
-
throw new BadRequestException('User with this email already exists');
|
|
233
|
-
}
|
|
234
|
-
throw new BadRequestException('Sign-up failed');
|
|
235
|
-
}
|
|
128
|
+
return super.betterAuthSignUp(email, password, name);
|
|
236
129
|
}
|
|
237
130
|
|
|
238
|
-
/**
|
|
239
|
-
* Sign out via Better-Auth
|
|
240
|
-
*/
|
|
241
131
|
@Mutation(() => Boolean, { description: 'Sign out via Better-Auth' })
|
|
242
132
|
@Roles(RoleEnum.S_USER)
|
|
243
133
|
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
244
|
-
async betterAuthSignOut(@Context() ctx: { req: Request }): Promise<boolean> {
|
|
245
|
-
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const api = this.betterAuthService.getApi();
|
|
250
|
-
if (!api) {
|
|
251
|
-
return false;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
try {
|
|
255
|
-
const headers = this.convertHeaders(ctx.req.headers);
|
|
256
|
-
await api.signOut({ headers });
|
|
257
|
-
return true;
|
|
258
|
-
} catch (error) {
|
|
259
|
-
this.logger.debug(`Sign-out error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
260
|
-
return false;
|
|
261
|
-
}
|
|
134
|
+
override async betterAuthSignOut(@Context() ctx: { req: Request }): Promise<boolean> {
|
|
135
|
+
return super.betterAuthSignOut(ctx);
|
|
262
136
|
}
|
|
263
137
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
138
|
+
// ===========================================================================
|
|
139
|
+
// 2FA Mutations
|
|
140
|
+
// ===========================================================================
|
|
141
|
+
|
|
267
142
|
@Mutation(() => BetterAuthAuthModel, {
|
|
268
143
|
description: 'Verify 2FA code during sign-in',
|
|
269
144
|
})
|
|
270
145
|
@Roles(RoleEnum.S_EVERYONE)
|
|
271
|
-
async betterAuthVerify2FA(
|
|
146
|
+
override async betterAuthVerify2FA(
|
|
272
147
|
@Args('code') code: string,
|
|
273
148
|
@Context() ctx: { req: Request },
|
|
274
149
|
): Promise<BetterAuthAuthModel> {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
if (!this.betterAuthService.isTwoFactorEnabled()) {
|
|
278
|
-
throw new BadRequestException('Two-factor authentication is not enabled');
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const api = this.betterAuthService.getApi();
|
|
282
|
-
if (!api) {
|
|
283
|
-
throw new BadRequestException('Better-Auth API not available');
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
try {
|
|
287
|
-
// Convert headers
|
|
288
|
-
const headers = this.convertHeaders(ctx.req.headers);
|
|
289
|
-
|
|
290
|
-
// Better-Auth's 2FA plugin adds twoFactor methods dynamically
|
|
291
|
-
|
|
292
|
-
const twoFactorApi = (api as Record<string, unknown>).twoFactor as
|
|
293
|
-
| undefined
|
|
294
|
-
| {
|
|
295
|
-
verifyTotp?: (params: { body: { code: string }; headers: Headers }) => Promise<BetterAuth2FAResponse>;
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
if (!twoFactorApi?.verifyTotp) {
|
|
299
|
-
throw new BadRequestException('2FA verification method not available');
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const response = await twoFactorApi.verifyTotp({
|
|
303
|
-
body: { code },
|
|
304
|
-
headers,
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
if (response && hasUser(response)) {
|
|
308
|
-
const sessionUser: BetterAuthSessionUser = response.user;
|
|
309
|
-
const mappedUser = await this.userMapper.mapSessionUser(sessionUser);
|
|
310
|
-
|
|
311
|
-
return {
|
|
312
|
-
requiresTwoFactor: false,
|
|
313
|
-
success: true,
|
|
314
|
-
user: mappedUser ? this.mapToUserModel(mappedUser) : null,
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
throw new UnauthorizedException('Invalid 2FA code');
|
|
319
|
-
} catch (error) {
|
|
320
|
-
this.logger.debug(`2FA verification error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
321
|
-
throw new UnauthorizedException('Invalid 2FA code');
|
|
322
|
-
}
|
|
150
|
+
return super.betterAuthVerify2FA(code, ctx);
|
|
323
151
|
}
|
|
324
152
|
|
|
325
|
-
// ===========================================================================
|
|
326
|
-
// 2FA Management Mutations
|
|
327
|
-
// ===========================================================================
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Enable 2FA for the current user
|
|
331
|
-
* Returns TOTP URI for QR code generation and backup codes
|
|
332
|
-
*/
|
|
333
153
|
@Mutation(() => BetterAuth2FASetupModel, {
|
|
334
154
|
description: 'Enable 2FA for the current user',
|
|
335
155
|
})
|
|
336
156
|
@Roles(RoleEnum.S_USER)
|
|
337
157
|
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
338
|
-
async betterAuthEnable2FA(
|
|
158
|
+
override async betterAuthEnable2FA(
|
|
339
159
|
@Args('password') password: string,
|
|
340
160
|
@Context() ctx: { req: Request },
|
|
341
161
|
): Promise<BetterAuth2FASetupModel> {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
if (!this.betterAuthService.isTwoFactorEnabled()) {
|
|
345
|
-
return { error: 'Two-factor authentication is not enabled on this server', success: false };
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
const api = this.betterAuthService.getApi();
|
|
349
|
-
if (!api) {
|
|
350
|
-
return { error: 'Better-Auth API not available', success: false };
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
try {
|
|
354
|
-
const headers = this.convertHeaders(ctx.req.headers);
|
|
355
|
-
|
|
356
|
-
const twoFactorApi = (api as Record<string, unknown>).twoFactor as
|
|
357
|
-
| undefined
|
|
358
|
-
| {
|
|
359
|
-
enable?: (params: { body: { password: string }; headers: Headers }) => Promise<{
|
|
360
|
-
backupCodes?: string[];
|
|
361
|
-
totpURI?: string;
|
|
362
|
-
}>;
|
|
363
|
-
};
|
|
364
|
-
|
|
365
|
-
if (!twoFactorApi?.enable) {
|
|
366
|
-
return { error: '2FA enable method not available', success: false };
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const response = await twoFactorApi.enable({
|
|
370
|
-
body: { password },
|
|
371
|
-
headers,
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
return {
|
|
375
|
-
backupCodes: response.backupCodes,
|
|
376
|
-
success: true,
|
|
377
|
-
totpUri: response.totpURI,
|
|
378
|
-
};
|
|
379
|
-
} catch (error) {
|
|
380
|
-
this.logger.debug(`2FA enable error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
381
|
-
return { error: error instanceof Error ? error.message : 'Failed to enable 2FA', success: false };
|
|
382
|
-
}
|
|
162
|
+
return super.betterAuthEnable2FA(password, ctx);
|
|
383
163
|
}
|
|
384
164
|
|
|
385
|
-
/**
|
|
386
|
-
* Disable 2FA for the current user
|
|
387
|
-
*/
|
|
388
165
|
@Mutation(() => Boolean, {
|
|
389
166
|
description: 'Disable 2FA for the current user',
|
|
390
167
|
})
|
|
391
168
|
@Roles(RoleEnum.S_USER)
|
|
392
169
|
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
393
|
-
async betterAuthDisable2FA(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
const api = this.betterAuthService.getApi();
|
|
401
|
-
if (!api) {
|
|
402
|
-
return false;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
try {
|
|
406
|
-
const headers = this.convertHeaders(ctx.req.headers);
|
|
407
|
-
|
|
408
|
-
const twoFactorApi = (api as Record<string, unknown>).twoFactor as
|
|
409
|
-
| undefined
|
|
410
|
-
| {
|
|
411
|
-
disable?: (params: { body: { password: string }; headers: Headers }) => Promise<{ status: boolean }>;
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
if (!twoFactorApi?.disable) {
|
|
415
|
-
throw new BadRequestException('2FA disable method not available');
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
const response = await twoFactorApi.disable({
|
|
419
|
-
body: { password },
|
|
420
|
-
headers,
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
return response?.status === true;
|
|
424
|
-
} catch (error) {
|
|
425
|
-
this.logger.debug(`2FA disable error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
426
|
-
return false;
|
|
427
|
-
}
|
|
170
|
+
override async betterAuthDisable2FA(
|
|
171
|
+
@Args('password') password: string,
|
|
172
|
+
@Context() ctx: { req: Request },
|
|
173
|
+
): Promise<boolean> {
|
|
174
|
+
return super.betterAuthDisable2FA(password, ctx);
|
|
428
175
|
}
|
|
429
176
|
|
|
430
|
-
/**
|
|
431
|
-
* Generate new backup codes for 2FA
|
|
432
|
-
*/
|
|
433
177
|
@Mutation(() => [String], {
|
|
434
178
|
description: 'Generate new backup codes for 2FA',
|
|
435
179
|
nullable: true,
|
|
436
180
|
})
|
|
437
181
|
@Roles(RoleEnum.S_USER)
|
|
438
182
|
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
439
|
-
async betterAuthGenerateBackupCodes(@Context() ctx: { req: Request }): Promise<null | string[]> {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
if (!this.betterAuthService.isTwoFactorEnabled()) {
|
|
443
|
-
throw new BadRequestException('Two-factor authentication is not enabled on this server');
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
const api = this.betterAuthService.getApi();
|
|
447
|
-
if (!api) {
|
|
448
|
-
return null;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
try {
|
|
452
|
-
const headers = this.convertHeaders(ctx.req.headers);
|
|
453
|
-
|
|
454
|
-
const twoFactorApi = (api as Record<string, unknown>).twoFactor as
|
|
455
|
-
| undefined
|
|
456
|
-
| {
|
|
457
|
-
generateBackupCodes?: (params: { headers: Headers }) => Promise<{ backupCodes?: string[] }>;
|
|
458
|
-
};
|
|
459
|
-
|
|
460
|
-
if (!twoFactorApi?.generateBackupCodes) {
|
|
461
|
-
throw new BadRequestException('Generate backup codes method not available');
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
const response = await twoFactorApi.generateBackupCodes({ headers });
|
|
465
|
-
|
|
466
|
-
return response?.backupCodes || null;
|
|
467
|
-
} catch (error) {
|
|
468
|
-
this.logger.debug(`Generate backup codes error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
469
|
-
return null;
|
|
470
|
-
}
|
|
183
|
+
override async betterAuthGenerateBackupCodes(@Context() ctx: { req: Request }): Promise<null | string[]> {
|
|
184
|
+
return super.betterAuthGenerateBackupCodes(ctx);
|
|
471
185
|
}
|
|
472
186
|
|
|
473
187
|
// ===========================================================================
|
|
474
|
-
// Passkey
|
|
188
|
+
// Passkey Mutations
|
|
475
189
|
// ===========================================================================
|
|
476
190
|
|
|
477
|
-
/**
|
|
478
|
-
* Get passkey registration challenge
|
|
479
|
-
* Returns the challenge data needed for WebAuthn registration
|
|
480
|
-
*/
|
|
481
191
|
@Mutation(() => BetterAuthPasskeyChallengeModel, {
|
|
482
192
|
description: 'Get passkey registration challenge for WebAuthn',
|
|
483
193
|
})
|
|
484
194
|
@Roles(RoleEnum.S_USER)
|
|
485
195
|
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
486
|
-
async betterAuthGetPasskeyChallenge(
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
return { error: 'Passkey authentication is not enabled on this server', success: false };
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
const api = this.betterAuthService.getApi();
|
|
494
|
-
if (!api) {
|
|
495
|
-
return { error: 'Better-Auth API not available', success: false };
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
try {
|
|
499
|
-
const headers = this.convertHeaders(ctx.req.headers);
|
|
500
|
-
|
|
501
|
-
const passkeyApi = (api as Record<string, unknown>).passkey as
|
|
502
|
-
| undefined
|
|
503
|
-
| {
|
|
504
|
-
generateRegisterOptions?: (params: { headers: Headers }) => Promise<unknown>;
|
|
505
|
-
};
|
|
506
|
-
|
|
507
|
-
if (!passkeyApi?.generateRegisterOptions) {
|
|
508
|
-
return { error: 'Passkey registration method not available', success: false };
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
const challenge = await passkeyApi.generateRegisterOptions({ headers });
|
|
512
|
-
|
|
513
|
-
return {
|
|
514
|
-
challenge: JSON.stringify(challenge),
|
|
515
|
-
success: true,
|
|
516
|
-
};
|
|
517
|
-
} catch (error) {
|
|
518
|
-
this.logger.debug(`Passkey challenge error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
519
|
-
return { error: error instanceof Error ? error.message : 'Failed to get passkey challenge', success: false };
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
/**
|
|
524
|
-
* List passkeys for the current user
|
|
525
|
-
*/
|
|
526
|
-
@Query(() => [BetterAuthPasskeyModel], {
|
|
527
|
-
description: 'List passkeys for the current user',
|
|
528
|
-
nullable: true,
|
|
529
|
-
})
|
|
530
|
-
@Roles(RoleEnum.S_USER)
|
|
531
|
-
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
532
|
-
async betterAuthListPasskeys(@Context() ctx: { req: Request }): Promise<BetterAuthPasskeyModel[] | null> {
|
|
533
|
-
if (!this.betterAuthService.isEnabled() || !this.betterAuthService.isPasskeyEnabled()) {
|
|
534
|
-
return null;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
const api = this.betterAuthService.getApi();
|
|
538
|
-
if (!api) {
|
|
539
|
-
return null;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
try {
|
|
543
|
-
const headers = this.convertHeaders(ctx.req.headers);
|
|
544
|
-
|
|
545
|
-
const passkeyApi = (api as Record<string, unknown>).passkey as
|
|
546
|
-
| undefined
|
|
547
|
-
| {
|
|
548
|
-
listUserPasskeys?: (params: {
|
|
549
|
-
headers: Headers;
|
|
550
|
-
}) => Promise<{ createdAt: Date; credentialID: string; id: string; name?: string }[]>;
|
|
551
|
-
};
|
|
552
|
-
|
|
553
|
-
if (!passkeyApi?.listUserPasskeys) {
|
|
554
|
-
return null;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
const passkeys = await passkeyApi.listUserPasskeys({ headers });
|
|
558
|
-
|
|
559
|
-
return passkeys.map((pk) => ({
|
|
560
|
-
createdAt: pk.createdAt,
|
|
561
|
-
credentialId: pk.credentialID,
|
|
562
|
-
id: pk.id,
|
|
563
|
-
name: pk.name,
|
|
564
|
-
}));
|
|
565
|
-
} catch (error) {
|
|
566
|
-
this.logger.debug(`List passkeys error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
567
|
-
return null;
|
|
568
|
-
}
|
|
196
|
+
override async betterAuthGetPasskeyChallenge(
|
|
197
|
+
@Context() ctx: { req: Request },
|
|
198
|
+
): Promise<BetterAuthPasskeyChallengeModel> {
|
|
199
|
+
return super.betterAuthGetPasskeyChallenge(ctx);
|
|
569
200
|
}
|
|
570
201
|
|
|
571
|
-
/**
|
|
572
|
-
* Delete a passkey
|
|
573
|
-
*/
|
|
574
202
|
@Mutation(() => Boolean, {
|
|
575
203
|
description: 'Delete a passkey by ID',
|
|
576
204
|
})
|
|
577
205
|
@Roles(RoleEnum.S_USER)
|
|
578
206
|
@UseGuards(AuthGuard(AuthGuardStrategy.JWT))
|
|
579
|
-
async betterAuthDeletePasskey(
|
|
207
|
+
override async betterAuthDeletePasskey(
|
|
580
208
|
@Args('passkeyId') passkeyId: string,
|
|
581
209
|
@Context() ctx: { req: Request },
|
|
582
210
|
): Promise<boolean> {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
if (!this.betterAuthService.isPasskeyEnabled()) {
|
|
586
|
-
throw new BadRequestException('Passkey authentication is not enabled on this server');
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
const api = this.betterAuthService.getApi();
|
|
590
|
-
if (!api) {
|
|
591
|
-
return false;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
try {
|
|
595
|
-
const headers = this.convertHeaders(ctx.req.headers);
|
|
596
|
-
|
|
597
|
-
const passkeyApi = (api as Record<string, unknown>).passkey as
|
|
598
|
-
| undefined
|
|
599
|
-
| {
|
|
600
|
-
deletePasskey?: (params: { body: { id: string }; headers: Headers }) => Promise<{ status: boolean }>;
|
|
601
|
-
};
|
|
602
|
-
|
|
603
|
-
if (!passkeyApi?.deletePasskey) {
|
|
604
|
-
throw new BadRequestException('Delete passkey method not available');
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
const response = await passkeyApi.deletePasskey({
|
|
608
|
-
body: { id: passkeyId },
|
|
609
|
-
headers,
|
|
610
|
-
});
|
|
611
|
-
|
|
612
|
-
return response?.status === true;
|
|
613
|
-
} catch (error) {
|
|
614
|
-
this.logger.debug(`Delete passkey error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
615
|
-
return false;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
// ===========================================================================
|
|
620
|
-
// Helper Methods
|
|
621
|
-
// ===========================================================================
|
|
622
|
-
|
|
623
|
-
/**
|
|
624
|
-
* Ensure Better-Auth is enabled
|
|
625
|
-
*/
|
|
626
|
-
private ensureEnabled(): void {
|
|
627
|
-
if (!this.betterAuthService.isEnabled()) {
|
|
628
|
-
throw new BadRequestException(
|
|
629
|
-
'Better-Auth is not enabled. Check that betterAuth.enabled is not set to false in your environment.',
|
|
630
|
-
);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
/**
|
|
635
|
-
* Convert Express headers to Web API Headers
|
|
636
|
-
*/
|
|
637
|
-
private convertHeaders(headers: Record<string, string | string[] | undefined>): Headers {
|
|
638
|
-
const result = new Headers();
|
|
639
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
640
|
-
if (typeof value === 'string') {
|
|
641
|
-
result.set(key, value);
|
|
642
|
-
} else if (Array.isArray(value)) {
|
|
643
|
-
result.set(key, value.join(', '));
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
return result;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
/**
|
|
650
|
-
* Map session response to session info model
|
|
651
|
-
*/
|
|
652
|
-
private mapSessionInfo(session: { createdAt?: Date; expiresAt?: Date; id?: string; token?: string }): {
|
|
653
|
-
expiresAt?: Date;
|
|
654
|
-
id?: string;
|
|
655
|
-
token?: string;
|
|
656
|
-
} {
|
|
657
|
-
return {
|
|
658
|
-
expiresAt: session.expiresAt,
|
|
659
|
-
id: session.id,
|
|
660
|
-
token: session.token,
|
|
661
|
-
};
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
/**
|
|
665
|
-
* Map MappedUser to BetterAuthUserModel
|
|
666
|
-
*/
|
|
667
|
-
private mapToUserModel(user: MappedUser): BetterAuthUserModel {
|
|
668
|
-
return {
|
|
669
|
-
email: user.email,
|
|
670
|
-
emailVerified: user.emailVerified,
|
|
671
|
-
iamId: user.iamId,
|
|
672
|
-
id: user.id,
|
|
673
|
-
name: user.name,
|
|
674
|
-
roles: user.roles,
|
|
675
|
-
verified: user.verified,
|
|
676
|
-
};
|
|
211
|
+
return super.betterAuthDeletePasskey(passkeyId, ctx);
|
|
677
212
|
}
|
|
678
213
|
}
|