@solidstarters/solid-core 1.2.168 → 1.2.170
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/dtos/create-ai-interaction.dto.d.ts +3 -0
- package/dist/dtos/create-ai-interaction.dto.d.ts.map +1 -1
- package/dist/dtos/create-ai-interaction.dto.js +19 -1
- package/dist/dtos/create-ai-interaction.dto.js.map +1 -1
- package/dist/dtos/update-ai-interaction.dto.d.ts +3 -0
- package/dist/dtos/update-ai-interaction.dto.d.ts.map +1 -1
- package/dist/dtos/update-ai-interaction.dto.js +19 -1
- package/dist/dtos/update-ai-interaction.dto.js.map +1 -1
- package/dist/entities/ai-interaction.entity.d.ts +3 -0
- package/dist/entities/ai-interaction.entity.d.ts.map +1 -1
- package/dist/entities/ai-interaction.entity.js +13 -1
- package/dist/entities/ai-interaction.entity.js.map +1 -1
- package/dist/filters/http-exception.filter.d.ts.map +1 -1
- package/dist/filters/http-exception.filter.js +2 -1
- package/dist/filters/http-exception.filter.js.map +1 -1
- package/dist/helpers/error-mapper.service.d.ts.map +1 -1
- package/dist/helpers/error-mapper.service.js +1 -1
- package/dist/helpers/error-mapper.service.js.map +1 -1
- package/dist/helpers/model-metadata-helper.service.js.map +1 -1
- package/dist/helpers/solid-core-error-codes-provider.service.js +3 -3
- package/dist/helpers/solid-core-error-codes-provider.service.js.map +1 -1
- package/dist/helpers/solid-registry.d.ts +0 -1
- package/dist/helpers/solid-registry.d.ts.map +1 -1
- package/dist/helpers/solid-registry.js +0 -1
- package/dist/helpers/solid-registry.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.js +2 -2
- package/dist/jobs/database/trigger-mcp-client-subscriber-database.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +33 -0
- package/dist/services/authentication.service.d.ts +1 -5
- package/dist/services/authentication.service.d.ts.map +1 -1
- package/dist/services/authentication.service.js +22 -64
- package/dist/services/authentication.service.js.map +1 -1
- package/dist/services/export-transaction.service.d.ts +3 -1
- package/dist/services/export-transaction.service.d.ts.map +1 -1
- package/dist/services/export-transaction.service.js +70 -16
- package/dist/services/export-transaction.service.js.map +1 -1
- package/dist/services/mcp-tool-response-handlers/solid-create-module-mcp-tool-response-handler.service.js +1 -1
- package/dist/services/mcp-tool-response-handlers/solid-create-module-mcp-tool-response-handler.service.js.map +1 -1
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +0 -2
- package/dist/solid-core.module.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/dtos/create-ai-interaction.dto.ts +15 -0
- package/src/dtos/update-ai-interaction.dto.ts +15 -0
- package/src/entities/ai-interaction.entity.ts +9 -0
- package/src/filters/http-exception.filter.ts +5 -2
- package/src/helpers/error-mapper.service.ts +1 -35
- package/src/helpers/model-metadata-helper.service.ts +1 -1
- package/src/helpers/solid-core-error-codes-provider.service.ts +4 -4
- package/src/helpers/solid-registry.ts +0 -1
- package/src/index.ts +0 -1
- package/src/jobs/database/trigger-mcp-client-subscriber-database.service.ts +2 -2
- package/src/seeders/seed-data/solid-core-metadata.json +35 -2
- package/src/services/authentication.service.ts +31 -142
- package/src/services/export-transaction.service.ts +118 -55
- package/src/services/mcp-tool-response-handlers/solid-create-module-mcp-tool-response-handler.service.ts +1 -1
- package/src/solid-core.module.ts +0 -8
- package/dist/entities/user-password-history.entity.d.ts +0 -7
- package/dist/entities/user-password-history.entity.d.ts.map +0 -1
- package/dist/entities/user-password-history.entity.js +0 -35
- package/dist/entities/user-password-history.entity.js.map +0 -1
- package/src/entities/user-password-history.entity.ts +0 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidstarters/solid-core",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.170",
|
|
4
4
|
"description": "This module is a NestJS module containing all the required core providers required by a Solid application",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -64,4 +64,19 @@ export class CreateAiInteractionDto {
|
|
|
64
64
|
@IsBoolean()
|
|
65
65
|
@ApiProperty()
|
|
66
66
|
isAutoApply: boolean = false;
|
|
67
|
+
|
|
68
|
+
@IsOptional()
|
|
69
|
+
@IsInt()
|
|
70
|
+
@ApiProperty()
|
|
71
|
+
inputTokens: number;
|
|
72
|
+
|
|
73
|
+
@IsOptional()
|
|
74
|
+
@IsInt()
|
|
75
|
+
@ApiProperty()
|
|
76
|
+
outputTokens: number;
|
|
77
|
+
|
|
78
|
+
@IsOptional()
|
|
79
|
+
@IsInt()
|
|
80
|
+
@ApiProperty()
|
|
81
|
+
totalTokens: number;
|
|
67
82
|
}
|
|
@@ -68,4 +68,19 @@ export class UpdateAiInteractionDto {
|
|
|
68
68
|
@IsBoolean()
|
|
69
69
|
@ApiProperty()
|
|
70
70
|
isAutoApply: boolean;
|
|
71
|
+
|
|
72
|
+
@IsOptional()
|
|
73
|
+
@IsInt()
|
|
74
|
+
@ApiProperty()
|
|
75
|
+
inputTokens: number;
|
|
76
|
+
|
|
77
|
+
@IsOptional()
|
|
78
|
+
@IsInt()
|
|
79
|
+
@ApiProperty()
|
|
80
|
+
outputTokens: number;
|
|
81
|
+
|
|
82
|
+
@IsOptional()
|
|
83
|
+
@IsInt()
|
|
84
|
+
@ApiProperty()
|
|
85
|
+
totalTokens: number;
|
|
71
86
|
}
|
|
@@ -39,4 +39,13 @@ export class AiInteraction extends CommonEntity {
|
|
|
39
39
|
externalId: string;
|
|
40
40
|
@Column({ type: "boolean", nullable: true, default: false })
|
|
41
41
|
isAutoApply: boolean = false;
|
|
42
|
+
|
|
43
|
+
@Column({ type: "integer", nullable: true })
|
|
44
|
+
inputTokens: number;
|
|
45
|
+
|
|
46
|
+
@Column({ type: "integer", nullable: true })
|
|
47
|
+
outputTokens: number;
|
|
48
|
+
|
|
49
|
+
@Column({ type: "integer", nullable: true })
|
|
50
|
+
totalTokens: number;
|
|
42
51
|
}
|
|
@@ -33,7 +33,7 @@ export class HttpExceptionFilter implements ExceptionFilter {
|
|
|
33
33
|
// Canonical code + static message
|
|
34
34
|
const code: ErrorCode = this.errorMapper.mapException(exception);
|
|
35
35
|
const defaultStatus = this.errorMapper.getHttpStatus(code);
|
|
36
|
-
const message = this.errorMapper.getMessage(code);
|
|
36
|
+
const message = code === 'solidx-unknown-error' ? `${exception?.message}` : this.errorMapper.getMessage(code);
|
|
37
37
|
|
|
38
38
|
const status = explicitStatus ?? defaultStatus ?? 500;
|
|
39
39
|
|
|
@@ -55,9 +55,12 @@ export class HttpExceptionFilter implements ExceptionFilter {
|
|
|
55
55
|
response.status(status).json({
|
|
56
56
|
statusCode: status,
|
|
57
57
|
statusCodeMessage: HttpStatusCodeMessages[status] || 'Internal Server Error',
|
|
58
|
-
//
|
|
58
|
+
// Keeping this for backward compatibility..
|
|
59
|
+
message: message,
|
|
59
60
|
errorCode: code,
|
|
60
61
|
error: message,
|
|
62
|
+
// We can make this conditional based on whether we are running in prod mode or dev mode...
|
|
63
|
+
// errorStack: exception.stack,
|
|
61
64
|
data: extra,
|
|
62
65
|
});
|
|
63
66
|
}
|
|
@@ -1,40 +1,6 @@
|
|
|
1
1
|
import { Injectable, Logger } from '@nestjs/common';
|
|
2
2
|
import { SolidRegistry } from 'src/helpers/solid-registry';
|
|
3
3
|
import { ErrorCode, ErrorMeta, ErrorRule, IErrorCodeProvider } from 'src/interfaces';
|
|
4
|
-
|
|
5
|
-
// export const ERROR_CODES = [
|
|
6
|
-
// 'db-duplicate-key',
|
|
7
|
-
// 'db-foreign-key-error',
|
|
8
|
-
// 'solidx-mcp-server-unavailable',
|
|
9
|
-
// 'unknown-error',
|
|
10
|
-
// ] as const;
|
|
11
|
-
|
|
12
|
-
// export type ErrorCode = typeof ERROR_CODES[number];
|
|
13
|
-
|
|
14
|
-
// type ErrorMeta = {
|
|
15
|
-
// message: string;
|
|
16
|
-
// httpStatus?: number;
|
|
17
|
-
// };
|
|
18
|
-
|
|
19
|
-
// const ERROR_MESSAGES: Record<ErrorCode, ErrorMeta> = {
|
|
20
|
-
// 'db-duplicate-key': {
|
|
21
|
-
// message: 'Duplicate key violation. A record with these values already exists.',
|
|
22
|
-
// httpStatus: 409,
|
|
23
|
-
// },
|
|
24
|
-
// 'db-foreign-key-error': {
|
|
25
|
-
// message: 'Foreign key constraint prevents this operation due to related records.',
|
|
26
|
-
// httpStatus: 409,
|
|
27
|
-
// },
|
|
28
|
-
// 'solidx-mcp-server-unavailable': {
|
|
29
|
-
// message: 'SolidX MCP server is unreachable. Please verify the MCP endpoint.',
|
|
30
|
-
// httpStatus: 503,
|
|
31
|
-
// },
|
|
32
|
-
// 'unknown-error': {
|
|
33
|
-
// message: 'An unexpected error occurred.',
|
|
34
|
-
// httpStatus: 500,
|
|
35
|
-
// },
|
|
36
|
-
// };
|
|
37
|
-
|
|
38
4
|
@Injectable()
|
|
39
5
|
export class ErrorMapperService {
|
|
40
6
|
private readonly logger = new Logger(ErrorMapperService.name);
|
|
@@ -76,7 +42,7 @@ export class ErrorMapperService {
|
|
|
76
42
|
this.logger.warn(`Error rule threw in match(): code=${rule.code} provider? — ${e}`);
|
|
77
43
|
}
|
|
78
44
|
}
|
|
79
|
-
return 'unknown-error';
|
|
45
|
+
return 'solidx-unknown-error';
|
|
80
46
|
}
|
|
81
47
|
|
|
82
48
|
private lookupMeta(code: ErrorCode): ErrorMeta | undefined {
|
|
@@ -15,7 +15,7 @@ export class SolidCoreErrorCodesProvider implements IErrorCodeProvider {
|
|
|
15
15
|
return [
|
|
16
16
|
{
|
|
17
17
|
code: 'solidx-mcp-server-unavailable',
|
|
18
|
-
priority: 100,
|
|
18
|
+
priority: 100,
|
|
19
19
|
match: (txt) =>
|
|
20
20
|
txt.includes('all connection attempts failed') &&
|
|
21
21
|
txt.includes('unhandled errors in a taskgroup (1 sub-exception)'),
|
|
@@ -25,7 +25,7 @@ export class SolidCoreErrorCodesProvider implements IErrorCodeProvider {
|
|
|
25
25
|
},
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
|
-
code: 'db-duplicate-key',
|
|
28
|
+
code: 'solidx-db-duplicate-key',
|
|
29
29
|
priority: 90,
|
|
30
30
|
match: (txt) => txt.includes('unique constraint') || txt.includes('duplicate key'),
|
|
31
31
|
meta: {
|
|
@@ -34,7 +34,7 @@ export class SolidCoreErrorCodesProvider implements IErrorCodeProvider {
|
|
|
34
34
|
},
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
|
-
code: 'db-foreign-key-error',
|
|
37
|
+
code: 'solidx-db-foreign-key-error',
|
|
38
38
|
priority: 90,
|
|
39
39
|
match: (txt) => txt.includes('violates foreign key'),
|
|
40
40
|
meta: {
|
|
@@ -44,7 +44,7 @@ export class SolidCoreErrorCodesProvider implements IErrorCodeProvider {
|
|
|
44
44
|
},
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
|
-
code: 'unknown-error',
|
|
47
|
+
code: 'solidx-unknown-error',
|
|
48
48
|
priority: -1, // last resort
|
|
49
49
|
match: (_txt) => true, // fallback catch-all
|
|
50
50
|
meta: {
|
package/src/index.ts
CHANGED
|
@@ -112,7 +112,6 @@ export * from './entities/scheduled-job.entity'
|
|
|
112
112
|
export * from './entities/permission-metadata.entity'
|
|
113
113
|
export * from './entities/role-metadata.entity'
|
|
114
114
|
export * from './entities/sms-template.entity'
|
|
115
|
-
export * from './entities/user-password-history.entity'
|
|
116
115
|
export * from './entities/user.entity'
|
|
117
116
|
export * from './entities/view-metadata.entity'
|
|
118
117
|
export * from './entities/setting.entity'
|
|
@@ -67,7 +67,7 @@ export class TriggerMcpClientSubscriberDatabase extends DatabaseSubscriber<Trigg
|
|
|
67
67
|
errorMessage: errorsStr,
|
|
68
68
|
modelUsed: aiResponse.model,
|
|
69
69
|
responseTimeMs: aiResponse.duration_ms,
|
|
70
|
-
metadata: JSON.stringify(aiResponse),
|
|
70
|
+
metadata: JSON.stringify(aiResponse, null, 2),
|
|
71
71
|
isApplied: aiInteraction.isApplied,
|
|
72
72
|
status: aiResponse.success ? 'succeeded' : 'failed'
|
|
73
73
|
});
|
|
@@ -88,7 +88,7 @@ export class TriggerMcpClientSubscriberDatabase extends DatabaseSubscriber<Trigg
|
|
|
88
88
|
errorMessage: '',
|
|
89
89
|
modelUsed: aiResponse.model,
|
|
90
90
|
responseTimeMs: aiResponse.duration_ms,
|
|
91
|
-
metadata: JSON.stringify(aiResponse),
|
|
91
|
+
metadata: JSON.stringify(aiResponse, null, 2),
|
|
92
92
|
isApplied: aiInteraction.isApplied,
|
|
93
93
|
status: aiResponse.success ? 'succeeded' : 'failed'
|
|
94
94
|
});
|
|
@@ -5152,6 +5152,39 @@
|
|
|
5152
5152
|
"index": false,
|
|
5153
5153
|
"private": false,
|
|
5154
5154
|
"encrypt": false
|
|
5155
|
+
},
|
|
5156
|
+
{
|
|
5157
|
+
"name": "inputTokens",
|
|
5158
|
+
"displayName": "Input Tokens",
|
|
5159
|
+
"type": "int",
|
|
5160
|
+
"ormType": "integer",
|
|
5161
|
+
"required": false,
|
|
5162
|
+
"unique": false,
|
|
5163
|
+
"index": false,
|
|
5164
|
+
"private": false,
|
|
5165
|
+
"encrypt": false
|
|
5166
|
+
},
|
|
5167
|
+
{
|
|
5168
|
+
"name": "outputTokens",
|
|
5169
|
+
"displayName": "Output Tokens",
|
|
5170
|
+
"type": "int",
|
|
5171
|
+
"ormType": "integer",
|
|
5172
|
+
"required": false,
|
|
5173
|
+
"unique": false,
|
|
5174
|
+
"index": false,
|
|
5175
|
+
"private": false,
|
|
5176
|
+
"encrypt": false
|
|
5177
|
+
},
|
|
5178
|
+
{
|
|
5179
|
+
"name": "totalTokens",
|
|
5180
|
+
"displayName": "Total Tokens",
|
|
5181
|
+
"type": "int",
|
|
5182
|
+
"ormType": "integer",
|
|
5183
|
+
"required": false,
|
|
5184
|
+
"unique": false,
|
|
5185
|
+
"index": false,
|
|
5186
|
+
"private": false,
|
|
5187
|
+
"encrypt": false
|
|
5155
5188
|
}
|
|
5156
5189
|
]
|
|
5157
5190
|
}
|
|
@@ -5584,7 +5617,7 @@
|
|
|
5584
5617
|
"moduleUserKey": "solid-core",
|
|
5585
5618
|
"modelUserKey": "setting"
|
|
5586
5619
|
},
|
|
5587
|
-
|
|
5620
|
+
{
|
|
5588
5621
|
"displayName": "Ai settings",
|
|
5589
5622
|
"name": "ai-settings-action",
|
|
5590
5623
|
"type": "custom",
|
|
@@ -5965,7 +5998,7 @@
|
|
|
5965
5998
|
"moduleUserKey": "solid-core",
|
|
5966
5999
|
"parentMenuItemUserKey": "settings-menu-item"
|
|
5967
6000
|
},
|
|
5968
|
-
|
|
6001
|
+
{
|
|
5969
6002
|
"displayName": "Ai Settings",
|
|
5970
6003
|
"name": "ai-settings-menu-item",
|
|
5971
6004
|
"sequenceNumber": 3,
|
|
@@ -28,7 +28,6 @@ import { OTPSignUpDto } from '../dtos/otp-sign-up.dto';
|
|
|
28
28
|
import { RefreshTokenDto } from '../dtos/refresh-token.dto';
|
|
29
29
|
import { SignInDto } from '../dtos/sign-in.dto';
|
|
30
30
|
import { SignUpDto } from '../dtos/sign-up.dto';
|
|
31
|
-
import { UserPasswordHistory } from '../entities/user-password-history.entity';
|
|
32
31
|
import { User } from '../entities/user.entity';
|
|
33
32
|
import { ActiveUserData } from '../interfaces/active-user-data.interface';
|
|
34
33
|
import { HashingService } from './hashing.service';
|
|
@@ -69,7 +68,6 @@ export class AuthenticationService {
|
|
|
69
68
|
constructor(
|
|
70
69
|
private readonly userService: UserService,
|
|
71
70
|
@InjectRepository(User) private readonly userRepository: Repository<User>,
|
|
72
|
-
@InjectRepository(UserPasswordHistory) private readonly userPasswordHistoryRepository: Repository<UserPasswordHistory>,
|
|
73
71
|
private readonly hashingService: HashingService,
|
|
74
72
|
private readonly jwtService: JwtService,
|
|
75
73
|
@Inject(jwtConfig.KEY)
|
|
@@ -210,22 +208,39 @@ export class AuthenticationService {
|
|
|
210
208
|
if (signUpDto.mobile) {
|
|
211
209
|
user.mobile = signUpDto.mobile;
|
|
212
210
|
}
|
|
213
|
-
this.logger.debug("user", user);
|
|
211
|
+
// this.logger.debug("user", user);
|
|
212
|
+
|
|
214
213
|
// If password has been specified by the user, then we simply create & activate the user based on the configuration parameter "activateUserOnRegistration".
|
|
215
214
|
let pwd = '';
|
|
216
215
|
let autoGeneratedPwd = '';
|
|
216
|
+
|
|
217
|
+
// User has specified password
|
|
217
218
|
if (signUpDto.password) {
|
|
218
219
|
pwd = await this.hashingService.hash(signUpDto.password);
|
|
219
220
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
221
|
+
// User has not specified password
|
|
222
|
+
else {
|
|
223
|
+
// When user does not specify password, and system is configured to auto generate passwords.
|
|
224
|
+
if (autoGeneratedPwdPermission?.toString().toLowerCase() === 'true') {
|
|
225
|
+
autoGeneratedPwd = this.generatePassword();
|
|
226
|
+
pwd = await this.hashingService.hash(autoGeneratedPwd);
|
|
227
|
+
user.forcePasswordChange = true;
|
|
228
|
+
}
|
|
229
|
+
// When user does not specify password, and system is not configured to auto generate passwords.
|
|
230
|
+
else {
|
|
231
|
+
// This means that most likely the system is going to be using password-less login.
|
|
232
|
+
// If that is not the case then we can raise a bad request exception...
|
|
233
|
+
if (!this.isPasswordlessRegistrationEnabled()) {
|
|
234
|
+
this.logger.error('User being created without password, and password less login is also not enabled in the system. Is this intentional?');
|
|
235
|
+
throw new BadRequestException(ERROR_MESSAGES.PASSWORDLESS_REGISTRATION_DISABLED);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Save the hash of the blank password, anyways since passwordless login is enabled it does not matter.
|
|
239
|
+
pwd = await this.hashingService.hash(pwd);
|
|
240
|
+
}
|
|
226
241
|
}
|
|
242
|
+
|
|
227
243
|
user.password = pwd;
|
|
228
|
-
// user.active = this.iamConfiguration.activateUserOnRegistration;
|
|
229
244
|
user.active = isUserActive;
|
|
230
245
|
return { user, pwd, autoGeneratedPwd };
|
|
231
246
|
}
|
|
@@ -233,11 +248,6 @@ export class AuthenticationService {
|
|
|
233
248
|
|
|
234
249
|
private async handlePostSignup(user: User, roles: string[] = [], pwd: string, autoGeneratedPwd: string) {
|
|
235
250
|
await this.userService.initializeRolesForNewUser(roles, user);
|
|
236
|
-
// Tanay: Adding user password to history table
|
|
237
|
-
const userPasswordHistory = new UserPasswordHistory();
|
|
238
|
-
userPasswordHistory.passwordHash = pwd;
|
|
239
|
-
userPasswordHistory.user = user;
|
|
240
|
-
await this.userPasswordHistoryRepository.save(userPasswordHistory);
|
|
241
251
|
|
|
242
252
|
// if forcePasswordChange is true, then we trigger an email to the user to change the password, this needs to be done using a queue.
|
|
243
253
|
// Create a new method like notifyUserOnForcePasswordChange, create a new email template we can call it on-force-password-change this template to include the random password
|
|
@@ -524,7 +534,7 @@ export class AuthenticationService {
|
|
|
524
534
|
username: user.username,
|
|
525
535
|
forcePasswordChange: user.forcePasswordChange,
|
|
526
536
|
id: user.id,
|
|
527
|
-
roles: user.roles.map((role
|
|
537
|
+
roles: user.roles.map((role) => role.name)
|
|
528
538
|
},
|
|
529
539
|
...tokens
|
|
530
540
|
}
|
|
@@ -721,19 +731,9 @@ export class AuthenticationService {
|
|
|
721
731
|
|
|
722
732
|
// Everytime the user changes the password we reset the forcePasswordChange flag back to false.
|
|
723
733
|
user.forcePasswordChange = false;
|
|
724
|
-
|
|
725
|
-
if (await this.isPasswordDuplicate(user)) {
|
|
726
|
-
throw new BadRequestException(ERROR_MESSAGES.PASSWORD_REUSED);
|
|
727
|
-
}
|
|
728
|
-
await this.deleteOldPasswords(user);
|
|
729
|
-
|
|
730
734
|
user.password = newPwd;
|
|
731
|
-
const userPasswordHistory = new UserPasswordHistory();
|
|
732
|
-
userPasswordHistory.passwordHash = newPwd;
|
|
733
|
-
userPasswordHistory.user = user;
|
|
734
735
|
|
|
735
736
|
await this.userRepository.save(user);
|
|
736
|
-
await this.userPasswordHistoryRepository.save(userPasswordHistory);
|
|
737
737
|
|
|
738
738
|
return true;
|
|
739
739
|
}
|
|
@@ -761,7 +761,7 @@ export class AuthenticationService {
|
|
|
761
761
|
|
|
762
762
|
let isValidUser = true // Instead of throwing exceptions we will simply return success message, this is to avoid user enumeration attacks.
|
|
763
763
|
if (!user) {
|
|
764
|
-
isValidUser = false
|
|
764
|
+
isValidUser = false
|
|
765
765
|
// throw new NotFoundException(ERROR_MESSAGES.INVALID_CREDENTIALS);
|
|
766
766
|
}
|
|
767
767
|
if (isValidUser && !user?.active) {
|
|
@@ -792,7 +792,7 @@ export class AuthenticationService {
|
|
|
792
792
|
error: '',
|
|
793
793
|
errorCode: '',
|
|
794
794
|
data: {
|
|
795
|
-
user:
|
|
795
|
+
user: {
|
|
796
796
|
email: user?.email,
|
|
797
797
|
// mobile: user.mobile,
|
|
798
798
|
// username: user.username,
|
|
@@ -873,24 +873,8 @@ export class AuthenticationService {
|
|
|
873
873
|
// 2) Now update the password & history (still inside the same transaction)
|
|
874
874
|
const pwdHash = await this.hashingService.hash(confirmForgotPasswordDto.password);
|
|
875
875
|
|
|
876
|
-
// Avoid ever assigning plaintext:
|
|
877
|
-
// user.password = dto.password <-- remove this line in your original code
|
|
878
|
-
|
|
879
876
|
// Check reuse with your existing method (ensure it looks at hashes).
|
|
880
|
-
const tempUser = { ...user, password: pwdHash } as User; // if your helper expects it
|
|
881
|
-
if (await this.isPasswordDuplicate(tempUser)) {
|
|
882
|
-
throw new BadRequestException(ERROR_MESSAGES.PASSWORD_REUSED);
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
await this.deleteOldPasswords(user);
|
|
886
|
-
|
|
887
877
|
await m.getRepository(User).update({ id: user.id }, { password: pwdHash });
|
|
888
|
-
|
|
889
|
-
const history = m.getRepository(UserPasswordHistory).create({
|
|
890
|
-
user: { id: user.id } as any,
|
|
891
|
-
passwordHash: pwdHash,
|
|
892
|
-
});
|
|
893
|
-
await m.getRepository(UserPasswordHistory).save(history);
|
|
894
878
|
this.notifyUserOnPasswordChanged(user);
|
|
895
879
|
|
|
896
880
|
return {
|
|
@@ -946,101 +930,6 @@ export class AuthenticationService {
|
|
|
946
930
|
}
|
|
947
931
|
}
|
|
948
932
|
|
|
949
|
-
// async confirmForgotPassword(confirmForgotPasswordDto: ConfirmForgotPasswordDto) {
|
|
950
|
-
// // Steps / Algorithm:
|
|
951
|
-
// // 1. Identify the user using the specified "username", if not found exit.
|
|
952
|
-
// // const user = await this.userRepository.findOne({
|
|
953
|
-
// // where: { username: confirmForgotPasswordDto.username, }
|
|
954
|
-
// // });
|
|
955
|
-
// const user = await this.resolveUserByVerificationToken(confirmForgotPasswordDto.verificationToken);
|
|
956
|
-
|
|
957
|
-
// if (!user) {
|
|
958
|
-
// throw new NotFoundException(ERROR_MESSAGES.USER_NOT_FOUND);
|
|
959
|
-
// }
|
|
960
|
-
|
|
961
|
-
// // 2. Validate if user has used a provider which is "local", only then it makes sense for us to initiate the forgot password routine.
|
|
962
|
-
// if (user.lastLoginProvider !== 'local') {
|
|
963
|
-
// throw new BadRequestException(ERROR_MESSAGES.NON_LOCAL_PROVIDER);
|
|
964
|
-
// }
|
|
965
|
-
// if (!user.active) {
|
|
966
|
-
// throw new UnauthorizedException(ERROR_MESSAGES.USER_INACTIVE);
|
|
967
|
-
// }
|
|
968
|
-
|
|
969
|
-
// // 3. Validate the verification token is proper & update the user record.
|
|
970
|
-
// if (user.verificationTokenOnForgotPassword !== confirmForgotPasswordDto.verificationToken) {
|
|
971
|
-
// throw new UnauthorizedException(ERROR_MESSAGES.INVALID_VERIFICATION_TOKEN);
|
|
972
|
-
// }
|
|
973
|
-
// if (user.verificationTokenOnForgotPasswordExpiresAt < new Date()) {
|
|
974
|
-
// throw new UnauthorizedException(ERROR_MESSAGES.INVALID_VERIFICATION_TOKEN);
|
|
975
|
-
// }
|
|
976
|
-
// user.forgotPasswordConfirmedAt = new Date();
|
|
977
|
-
// user.verificationTokenOnForgotPassword = null;
|
|
978
|
-
// user.verificationTokenOnForgotPasswordExpiresAt = null;
|
|
979
|
-
|
|
980
|
-
// // 4. Update the users password while encrypting it.
|
|
981
|
-
// const pwd = await this.hashingService.hash(confirmForgotPasswordDto.password);
|
|
982
|
-
// user.password = confirmForgotPasswordDto.password
|
|
983
|
-
|
|
984
|
-
// if (await this.isPasswordDuplicate(user)) {
|
|
985
|
-
// throw new BadRequestException(ERROR_MESSAGES.PASSWORD_REUSED);
|
|
986
|
-
// }
|
|
987
|
-
// await this.deleteOldPasswords(user);
|
|
988
|
-
|
|
989
|
-
// user.password = pwd;
|
|
990
|
-
// const userPasswordHistory = new UserPasswordHistory();
|
|
991
|
-
// userPasswordHistory.passwordHash = pwd;
|
|
992
|
-
// userPasswordHistory.user = user;
|
|
993
|
-
|
|
994
|
-
// await this.userRepository.save(user);
|
|
995
|
-
// //FIXME: Do this check conditionally, basis a configuration parameter i.e if IAM_ALLOW_PREVIOUS_PASSWORDS=false, default true
|
|
996
|
-
// await this.userPasswordHistoryRepository.save(userPasswordHistory);
|
|
997
|
-
|
|
998
|
-
// return {
|
|
999
|
-
// status: 'success',
|
|
1000
|
-
// message: SUCCESS_MESSAGES.FORGOT_PASSWORD_CONFIRMED,
|
|
1001
|
-
// error: '',
|
|
1002
|
-
// errorCode: '',
|
|
1003
|
-
// data: {}
|
|
1004
|
-
// }
|
|
1005
|
-
// }
|
|
1006
|
-
|
|
1007
|
-
//FIXME: Do this check conditionally, basis a configuration parameter i.e if IAM_ALLOW_PREVIOUS_PASSWORDS=true, return immediately without processing, i.e false.
|
|
1008
|
-
private async isPasswordDuplicate(user: User) {
|
|
1009
|
-
const userPwdHistoryEntityArray = await this.userPasswordHistoryRepository.findBy(
|
|
1010
|
-
{ user: { id: user.id } }
|
|
1011
|
-
)
|
|
1012
|
-
let userPwdHistoryArray = [];
|
|
1013
|
-
// O(n)
|
|
1014
|
-
for (const entity of userPwdHistoryEntityArray) {
|
|
1015
|
-
userPwdHistoryArray.push(entity.passwordHash);
|
|
1016
|
-
}
|
|
1017
|
-
// O(n)
|
|
1018
|
-
for (const pwdHash of userPwdHistoryArray) {
|
|
1019
|
-
const isEqual = await this.hashingService.compare(user.password, pwdHash);
|
|
1020
|
-
if (isEqual) {
|
|
1021
|
-
return true;
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
return false;
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
//FIXME: Do this check conditionally, basis a configuration parameter i.e if IAM_ALLOW_PREVIOUS_PASSWORDS=true, return immediately without processing
|
|
1028
|
-
private async deleteOldPasswords(user: User) {
|
|
1029
|
-
const userPwdHistoryArray = await this.userPasswordHistoryRepository.findBy(
|
|
1030
|
-
{ user: { id: user.id } }
|
|
1031
|
-
)
|
|
1032
|
-
const pwdLimit = 2; //FIXME: Should this be moved into the env? IAM_PREVIOUS_PASSWORDS_LIMIT
|
|
1033
|
-
|
|
1034
|
-
// TODO: Check what slice() or splice() does.
|
|
1035
|
-
//FIXME - Delete passwords which are older than the latest n passwords. n is configurable
|
|
1036
|
-
if (userPwdHistoryArray.length >= pwdLimit) {
|
|
1037
|
-
const numToDelete = pwdLimit + 1 - userPwdHistoryArray.length;
|
|
1038
|
-
for (let i = 0; i < numToDelete; i++) {
|
|
1039
|
-
await this.userPasswordHistoryRepository.remove(userPwdHistoryArray[i]);
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
933
|
async generateTokens(user: User) {
|
|
1045
934
|
|
|
1046
935
|
const [accessToken, refreshToken] = await Promise.all([
|
|
@@ -1083,7 +972,7 @@ export class AuthenticationService {
|
|
|
1083
972
|
|
|
1084
973
|
async refreshTokens(refreshTokenDto: RefreshTokenDto) {
|
|
1085
974
|
try {
|
|
1086
|
-
const { sub
|
|
975
|
+
const { sub } = await this.jwtService.verifyAsync<Pick<ActiveUserData, 'sub'> & { refreshTokenId: string }>(refreshTokenDto.refreshToken, {
|
|
1087
976
|
secret: this.jwtConfiguration.secret,
|
|
1088
977
|
audience: this.jwtConfiguration.audience,
|
|
1089
978
|
issuer: this.jwtConfiguration.issuer,
|
|
@@ -1185,7 +1074,7 @@ export class AuthenticationService {
|
|
|
1185
1074
|
username: user.username,
|
|
1186
1075
|
// forcePasswordChange: user.forcePasswordChange,
|
|
1187
1076
|
id: user.id,
|
|
1188
|
-
roles: user.roles.map((role
|
|
1077
|
+
roles: user.roles.map((role) => role.name)
|
|
1189
1078
|
},
|
|
1190
1079
|
...tokens
|
|
1191
1080
|
}
|
|
@@ -1255,7 +1144,7 @@ export class AuthenticationService {
|
|
|
1255
1144
|
username: user.username,
|
|
1256
1145
|
// forcePasswordChange: user.forcePasswordChange,
|
|
1257
1146
|
id: user.id,
|
|
1258
|
-
roles: user.roles.map((role
|
|
1147
|
+
roles: user.roles.map((role) => role.name)
|
|
1259
1148
|
},
|
|
1260
1149
|
...tokens
|
|
1261
1150
|
}
|