@solidstarters/solid-core 1.2.152 → 1.2.154

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.
Files changed (142) hide show
  1. package/dist/config/iam.config.d.ts +6 -0
  2. package/dist/config/iam.config.d.ts.map +1 -1
  3. package/dist/config/iam.config.js +3 -0
  4. package/dist/config/iam.config.js.map +1 -1
  5. package/dist/constants/error-messages.js +1 -1
  6. package/dist/constants/error-messages.js.map +1 -1
  7. package/dist/controllers/ai-interaction.controller.d.ts +2 -1
  8. package/dist/controllers/ai-interaction.controller.d.ts.map +1 -1
  9. package/dist/controllers/ai-interaction.controller.js +5 -3
  10. package/dist/controllers/ai-interaction.controller.js.map +1 -1
  11. package/dist/controllers/authentication.controller.d.ts +6 -6
  12. package/dist/controllers/authentication.controller.d.ts.map +1 -1
  13. package/dist/controllers/authentication.controller.js +15 -7
  14. package/dist/controllers/authentication.controller.js.map +1 -1
  15. package/dist/controllers/email-template.controller.d.ts.map +1 -1
  16. package/dist/controllers/email-template.controller.js +3 -0
  17. package/dist/controllers/email-template.controller.js.map +1 -1
  18. package/dist/controllers/google-authentication.controller.d.ts.map +1 -1
  19. package/dist/controllers/google-authentication.controller.js +3 -0
  20. package/dist/controllers/google-authentication.controller.js.map +1 -1
  21. package/dist/controllers/media.controller.d.ts.map +1 -1
  22. package/dist/controllers/media.controller.js +4 -0
  23. package/dist/controllers/media.controller.js.map +1 -1
  24. package/dist/controllers/model-metadata.controller.d.ts.map +1 -1
  25. package/dist/controllers/model-metadata.controller.js +5 -0
  26. package/dist/controllers/model-metadata.controller.js.map +1 -1
  27. package/dist/controllers/otp-authentication.controller.d.ts.map +1 -1
  28. package/dist/controllers/otp-authentication.controller.js +3 -0
  29. package/dist/controllers/otp-authentication.controller.js.map +1 -1
  30. package/dist/controllers/service.controller.d.ts.map +1 -1
  31. package/dist/controllers/service.controller.js +4 -0
  32. package/dist/controllers/service.controller.js.map +1 -1
  33. package/dist/controllers/sms-template.controller.d.ts +1 -1
  34. package/dist/controllers/sms-template.controller.d.ts.map +1 -1
  35. package/dist/controllers/sms-template.controller.js +6 -3
  36. package/dist/controllers/sms-template.controller.js.map +1 -1
  37. package/dist/decorators/solid-password.decorator.d.ts +12 -0
  38. package/dist/decorators/solid-password.decorator.d.ts.map +1 -0
  39. package/dist/decorators/solid-password.decorator.js +43 -0
  40. package/dist/decorators/solid-password.decorator.js.map +1 -0
  41. package/dist/dtos/change-password.dto.d.ts.map +1 -1
  42. package/dist/dtos/change-password.dto.js +2 -0
  43. package/dist/dtos/change-password.dto.js.map +1 -1
  44. package/dist/dtos/confirm-forgot-password.dto.d.ts.map +1 -1
  45. package/dist/dtos/confirm-forgot-password.dto.js +2 -0
  46. package/dist/dtos/confirm-forgot-password.dto.js.map +1 -1
  47. package/dist/helpers/cors.helper.d.ts +4 -0
  48. package/dist/helpers/cors.helper.d.ts.map +1 -0
  49. package/dist/helpers/cors.helper.js +33 -0
  50. package/dist/helpers/cors.helper.js.map +1 -0
  51. package/dist/helpers/security.helper.d.ts +9 -0
  52. package/dist/helpers/security.helper.d.ts.map +1 -0
  53. package/dist/helpers/security.helper.js +46 -0
  54. package/dist/helpers/security.helper.js.map +1 -0
  55. package/dist/index.d.ts +3 -0
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +3 -0
  58. package/dist/index.js.map +1 -1
  59. package/dist/repository/chatter-message-details.repository.d.ts +16 -0
  60. package/dist/repository/chatter-message-details.repository.d.ts.map +1 -0
  61. package/dist/repository/chatter-message-details.repository.js +62 -0
  62. package/dist/repository/chatter-message-details.repository.js.map +1 -0
  63. package/dist/repository/chatter-message.repository.d.ts +16 -0
  64. package/dist/repository/chatter-message.repository.d.ts.map +1 -0
  65. package/dist/repository/chatter-message.repository.js +61 -0
  66. package/dist/repository/chatter-message.repository.js.map +1 -0
  67. package/dist/repository/security-rule.repository.d.ts +1 -1
  68. package/dist/repository/security-rule.repository.d.ts.map +1 -1
  69. package/dist/repository/security-rule.repository.js +2 -2
  70. package/dist/repository/security-rule.repository.js.map +1 -1
  71. package/dist/repository/solid-base.repository.d.ts +6 -1
  72. package/dist/repository/solid-base.repository.d.ts.map +1 -1
  73. package/dist/repository/solid-base.repository.js +35 -0
  74. package/dist/repository/solid-base.repository.js.map +1 -1
  75. package/dist/seeders/seed-data/solid-core-metadata.json +9 -0
  76. package/dist/services/ai-interaction.service.d.ts +2 -4
  77. package/dist/services/ai-interaction.service.d.ts.map +1 -1
  78. package/dist/services/ai-interaction.service.js +4 -8
  79. package/dist/services/ai-interaction.service.js.map +1 -1
  80. package/dist/services/authentication.service.d.ts +6 -2
  81. package/dist/services/authentication.service.d.ts.map +1 -1
  82. package/dist/services/authentication.service.js +94 -43
  83. package/dist/services/authentication.service.js.map +1 -1
  84. package/dist/services/chatter-message-details.service.d.ts +4 -3
  85. package/dist/services/chatter-message-details.service.d.ts.map +1 -1
  86. package/dist/services/chatter-message-details.service.js +2 -3
  87. package/dist/services/chatter-message-details.service.js.map +1 -1
  88. package/dist/services/chatter-message.service.d.ts +3 -2
  89. package/dist/services/chatter-message.service.d.ts.map +1 -1
  90. package/dist/services/chatter-message.service.js +2 -2
  91. package/dist/services/chatter-message.service.js.map +1 -1
  92. package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.d.ts +2 -1
  93. package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.d.ts.map +1 -1
  94. package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.js +13 -5
  95. package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.js.map +1 -1
  96. package/dist/services/crud.service.d.ts.map +1 -1
  97. package/dist/services/crud.service.js +4 -1
  98. package/dist/services/crud.service.js.map +1 -1
  99. package/dist/services/model-metadata.service.d.ts.map +1 -1
  100. package/dist/services/model-metadata.service.js +4 -1
  101. package/dist/services/model-metadata.service.js.map +1 -1
  102. package/dist/services/request-context.service.d.ts +3 -0
  103. package/dist/services/request-context.service.d.ts.map +1 -1
  104. package/dist/services/request-context.service.js +6 -0
  105. package/dist/services/request-context.service.js.map +1 -1
  106. package/dist/solid-core.module.d.ts.map +1 -1
  107. package/dist/solid-core.module.js +23 -5
  108. package/dist/solid-core.module.js.map +1 -1
  109. package/dist/tsconfig.tsbuildinfo +1 -1
  110. package/package.json +3 -1
  111. package/src/config/iam.config.ts +3 -0
  112. package/src/constants/error-messages.ts +1 -1
  113. package/src/controllers/ai-interaction.controller.ts +4 -2
  114. package/src/controllers/authentication.controller.ts +16 -10
  115. package/src/controllers/email-template.controller.ts +4 -1
  116. package/src/controllers/google-authentication.controller.ts +4 -0
  117. package/src/controllers/media.controller.ts +5 -1
  118. package/src/controllers/model-metadata.controller.ts +7 -2
  119. package/src/controllers/otp-authentication.controller.ts +4 -1
  120. package/src/controllers/service.controller.ts +5 -1
  121. package/src/controllers/sms-template.controller.ts +8 -7
  122. package/src/decorators/solid-password.decorator.ts +51 -0
  123. package/src/dtos/change-password.dto.ts +2 -0
  124. package/src/dtos/confirm-forgot-password.dto.ts +2 -0
  125. package/src/helpers/cors.helper.ts +34 -0
  126. package/src/helpers/security.helper.ts +53 -0
  127. package/src/index.ts +3 -0
  128. package/src/repository/chatter-message-details.repository.ts +109 -0
  129. package/src/repository/chatter-message.repository.ts +68 -0
  130. package/src/repository/security-rule.repository.ts +2 -2
  131. package/src/repository/solid-base.repository.ts +66 -0
  132. package/src/seeders/seed-data/email-templates/password-changed.handlebars.html +158 -0
  133. package/src/seeders/seed-data/solid-core-metadata.json +9 -0
  134. package/src/services/ai-interaction.service.ts +5 -5
  135. package/src/services/authentication.service.ts +181 -56
  136. package/src/services/chatter-message-details.service.ts +3 -2
  137. package/src/services/chatter-message.service.ts +3 -2
  138. package/src/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.ts +34 -16
  139. package/src/services/crud.service.ts +7 -2
  140. package/src/services/model-metadata.service.ts +15 -1
  141. package/src/services/request-context.service.ts +9 -0
  142. package/src/solid-core.module.ts +29 -5
@@ -12,12 +12,12 @@ import {
12
12
  import { ConfigType } from '@nestjs/config';
13
13
  import { EventEmitter2 } from '@nestjs/event-emitter';
14
14
  import { JwtService } from '@nestjs/jwt';
15
- import { InjectRepository } from '@nestjs/typeorm';
15
+ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
16
16
  import { isEmpty, isNotEmpty } from 'class-validator';
17
17
  import { randomInt, randomUUID } from 'crypto';
18
18
  import { SMTPEMailService } from 'src/services/mail/smtp-email.service';
19
19
  import { Msg91OTPService } from 'src/services/sms/Msg91OTPService';
20
- import { Repository } from 'typeorm';
20
+ import { DataSource, Repository } from 'typeorm';
21
21
  import { iamConfig, jwtConfig } from '../config/iam.config';
22
22
  import { ChangePasswordDto } from "../dtos/change-password.dto";
23
23
  import { ConfirmForgotPasswordDto } from '../dtos/confirm-forgot-password.dto';
@@ -49,7 +49,7 @@ import { RequestContextService } from './request-context.service';
49
49
  import { ERROR_MESSAGES } from 'src/constants/error-messages';
50
50
  import { SUCCESS_MESSAGES } from 'src/constants/success-messages';
51
51
  import { MailFactory } from 'src/factories/mail.factory';
52
-
52
+ import { v4 as uuidv4 } from 'uuid';
53
53
 
54
54
  enum LoginProvider {
55
55
  LOCAL = 'local',
@@ -88,9 +88,11 @@ export class AuthenticationService {
88
88
  private readonly commonConfiguration: ConfigType<typeof commonConfig>,
89
89
  private readonly userActivityHistoryService: UserActivityHistoryService,
90
90
  private readonly requestContextService: RequestContextService,
91
+ @InjectDataSource()
92
+ private readonly dataSource: DataSource,
91
93
  ) {
92
94
  // this.mailService = this.mailServiceFactory.getMailService();
93
- }
95
+ }
94
96
 
95
97
  private async getConfig(key: string): Promise<any> {
96
98
  return this.settingService.getConfigValue(key);
@@ -112,6 +114,13 @@ export class AuthenticationService {
112
114
  });
113
115
  }
114
116
 
117
+ async resolveUserByVerificationToken(token: string) {
118
+ return await this.userRepository.findOne({
119
+ where: { verificationTokenOnForgotPassword: token },
120
+ relations: { roles: true }
121
+ });
122
+ }
123
+
115
124
  async validateUser(signInDto: SignInDto) {
116
125
 
117
126
  const user = await this.resolveUser(signInDto.username, signInDto.email);
@@ -729,6 +738,19 @@ export class AuthenticationService {
729
738
  return true;
730
739
  }
731
740
 
741
+ // generate uuid token for forgot password
742
+ private generateForgotPasswordToken() {
743
+ const expiryTime = new Date();
744
+ expiryTime.setMinutes(expiryTime.getMinutes() + this.iamConfiguration.forgotPasswordVerificationTokenExpiry);
745
+
746
+ return {
747
+ token: this.iamConfiguration.dummyOtp
748
+ ? this.iamConfiguration.dummyOtp
749
+ : uuidv4(), // UUID instead of numeric OTP
750
+ expiresAt: expiryTime,
751
+ };
752
+ }
753
+
732
754
  async initiateForgotPassword(initiateForgotPasswordDto: InitiateForgotPasswordDto) {
733
755
  // Steps / Algorithm:
734
756
  // 1. Identify the user using the specified "username", if not found exit.
@@ -738,20 +760,20 @@ export class AuthenticationService {
738
760
  const user = await this.resolveUser(initiateForgotPasswordDto.username, initiateForgotPasswordDto.email);
739
761
 
740
762
  if (!user) {
741
- throw new NotFoundException(ERROR_MESSAGES.USER_NOT_FOUND);
763
+ throw new NotFoundException(ERROR_MESSAGES.INVALID_CREDENTIALS);
742
764
  }
743
765
  if (!user.active) {
744
- throw new UnauthorizedException(ERROR_MESSAGES.USER_INACTIVE);
766
+ throw new UnauthorizedException(ERROR_MESSAGES.INVALID_CREDENTIALS);
745
767
  }
746
768
 
747
769
  // 2. Validate if user has used a provider which is "local", only then it makes sense for us to initiate the forgot password routine.
748
770
  if (user.lastLoginProvider !== 'local') {
749
- throw new BadRequestException(ERROR_MESSAGES.NON_LOCAL_PROVIDER);
771
+ throw new BadRequestException(ERROR_MESSAGES.INVALID_CREDENTIALS);
750
772
  }
751
773
 
752
774
  // 3. Generate a 6 digit validation token, we send this token to the user over their email & mobile number (controlled using configuration).
753
775
  // 4. Save this validation token in new fields on the user record.
754
- const { token, expiresAt } = this.otp();
776
+ const { token, expiresAt } = this.generateForgotPasswordToken();
755
777
  user.verificationTokenOnForgotPassword = token;
756
778
  user.verificationTokenOnForgotPasswordExpiresAt = expiresAt;
757
779
  await this.userRepository.save(user);
@@ -789,7 +811,7 @@ export class AuthenticationService {
789
811
  firstName: user.username,
790
812
  fullName: user.fullName,
791
813
  // TODO: Need to prefix this with the page url where the forgot password page will open up.
792
- passwordResetLink: `${process.env.IAM_FRONTEND_APP_FORGOT_PASSWORD_PAGE_URL}?token=${user.verificationTokenOnForgotPassword}&username=${user.username}`,
814
+ passwordResetLink: `${process.env.IAM_FRONTEND_APP_FORGOT_PASSWORD_PAGE_URL}?token=${user.verificationTokenOnForgotPassword}`,
793
815
  companyLogoUrl: companyLogo
794
816
  },
795
817
  this.commonConfiguration.shouldQueueEmails,
@@ -816,63 +838,166 @@ export class AuthenticationService {
816
838
  }
817
839
 
818
840
  async confirmForgotPassword(confirmForgotPasswordDto: ConfirmForgotPasswordDto) {
819
- // Steps / Algorithm:
820
- // 1. Identify the user using the specified "username", if not found exit.
821
- // const user = await this.userRepository.findOne({
822
- // where: { username: confirmForgotPasswordDto.username, }
823
- // });
824
- const user = await this.resolveUser(confirmForgotPasswordDto.username, confirmForgotPasswordDto.email);
841
+ return this.dataSource.transaction(async (m) => {
842
+ // Resolve the user id first (by username/email), but DON'T check the token in JS.
843
+ const user = await this.resolveUserByVerificationToken(confirmForgotPasswordDto.verificationToken);
844
+ if (!user) throw new NotFoundException(ERROR_MESSAGES.INVALID_CREDENTIALS);
845
+ if (user.lastLoginProvider !== 'local') throw new BadRequestException(ERROR_MESSAGES.INVALID_CREDENTIALS);
846
+ if (!user.active) throw new UnauthorizedException(ERROR_MESSAGES.INVALID_CREDENTIALS);
847
+
848
+ // 1) Atomically consume the token (only one request can succeed)
849
+ const { affected } = await m
850
+ .createQueryBuilder()
851
+ .update(User)
852
+ .set({
853
+ forgotPasswordConfirmedAt: () => 'NOW()',
854
+ verificationTokenOnForgotPassword: () => 'NULL',
855
+ verificationTokenOnForgotPasswordExpiresAt: () => 'NULL',
856
+ })
857
+ .where('id = :id', { id: user.id })
858
+ .andWhere('verificationTokenOnForgotPassword = :token', { token: confirmForgotPasswordDto.verificationToken })
859
+ .andWhere('verificationTokenOnForgotPasswordExpiresAt > NOW()')
860
+ .execute();
861
+
862
+ if (affected !== 1) {
863
+ // Token invalid/expired/already used (or a parallel call already consumed it)
864
+ throw new UnauthorizedException(ERROR_MESSAGES.INVALID_CREDENTIALS);
865
+ }
825
866
 
826
- if (!user) {
827
- throw new NotFoundException(ERROR_MESSAGES.USER_NOT_FOUND);
828
- }
867
+ // 2) Now update the password & history (still inside the same transaction)
868
+ const pwdHash = await this.hashingService.hash(confirmForgotPasswordDto.password);
829
869
 
830
- // 2. Validate if user has used a provider which is "local", only then it makes sense for us to initiate the forgot password routine.
831
- if (user.lastLoginProvider !== 'local') {
832
- throw new BadRequestException(ERROR_MESSAGES.NON_LOCAL_PROVIDER);
833
- }
834
- if (!user.active) {
835
- throw new UnauthorizedException(ERROR_MESSAGES.USER_INACTIVE);
836
- }
870
+ // Avoid ever assigning plaintext:
871
+ // user.password = dto.password <-- remove this line in your original code
837
872
 
838
- // 3. Validate the verification token is proper & update the user record.
839
- if (user.verificationTokenOnForgotPassword !== confirmForgotPasswordDto.verificationToken) {
840
- throw new UnauthorizedException(ERROR_MESSAGES.INVALID_VERIFICATION_TOKEN);
841
- }
842
- if (user.verificationTokenOnForgotPasswordExpiresAt < new Date()) {
843
- throw new UnauthorizedException(ERROR_MESSAGES.INVALID_VERIFICATION_TOKEN);
844
- }
845
- user.forgotPasswordConfirmedAt = new Date();
846
- user.verificationTokenOnForgotPassword = null;
847
- user.verificationTokenOnForgotPasswordExpiresAt = null;
873
+ // Check reuse with your existing method (ensure it looks at hashes).
874
+ const tempUser = { ...user, password: pwdHash } as User; // if your helper expects it
875
+ if (await this.isPasswordDuplicate(tempUser)) {
876
+ throw new BadRequestException(ERROR_MESSAGES.PASSWORD_REUSED);
877
+ }
848
878
 
849
- // 4. Update the users password while encrypting it.
850
- const pwd = await this.hashingService.hash(confirmForgotPasswordDto.password);
851
- user.password = confirmForgotPasswordDto.password
879
+ await this.deleteOldPasswords(user);
852
880
 
853
- if (await this.isPasswordDuplicate(user)) {
854
- throw new BadRequestException(ERROR_MESSAGES.PASSWORD_REUSED);
855
- }
856
- await this.deleteOldPasswords(user);
881
+ await m.getRepository(User).update({ id: user.id }, { password: pwdHash });
857
882
 
858
- user.password = pwd;
859
- const userPasswordHistory = new UserPasswordHistory();
860
- userPasswordHistory.passwordHash = pwd;
861
- userPasswordHistory.user = user;
883
+ const history = m.getRepository(UserPasswordHistory).create({
884
+ user: { id: user.id } as any,
885
+ passwordHash: pwdHash,
886
+ });
887
+ await m.getRepository(UserPasswordHistory).save(history);
888
+ this.notifyUserOnPasswordChanged(user);
862
889
 
863
- await this.userRepository.save(user);
864
- //FIXME: Do this check conditionally, basis a configuration parameter i.e if IAM_ALLOW_PREVIOUS_PASSWORDS=false, default true
865
- await this.userPasswordHistoryRepository.save(userPasswordHistory);
890
+ return {
891
+ status: 'success',
892
+ message: SUCCESS_MESSAGES.FORGOT_PASSWORD_CONFIRMED,
893
+ error: '',
894
+ errorCode: '',
895
+ data: {},
896
+ };
897
+ });
898
+ }
866
899
 
867
- return {
868
- status: 'success',
869
- message: SUCCESS_MESSAGES.FORGOT_PASSWORD_CONFIRMED,
870
- error: '',
871
- errorCode: '',
872
- data: {}
900
+ private async notifyUserOnPasswordChanged(user: User) {
901
+ const companyLogo = await this.getCompanyLogo();
902
+
903
+ const forgotPasswordSendVerificationTokenOn = this.iamConfiguration.forgotPasswordSendVerificationTokenOn;
904
+
905
+ if (forgotPasswordSendVerificationTokenOn == ForgotPasswordSendVerificationTokenOn.EMAIL) {
906
+ const mailService = this.mailServiceFactory.getMailService();
907
+ mailService.sendEmailUsingTemplate(
908
+ user.email,
909
+ 'password-changed',
910
+ {
911
+ solidAppName: process.env.SOLID_APP_NAME,
912
+ solidAppWebsiteUrl: process.env.SOLID_APP_WEBSITE_URL,
913
+ email: user.email,
914
+ firstName: user.username,
915
+ fullName: user.fullName,
916
+ // TODO: Need to prefix this with the page url where the forgot password page will open up.
917
+ passwordResetLink: `${process.env.IAM_FRONTEND_APP_FORGOT_PASSWORD_PAGE_URL}?token=${user.verificationTokenOnForgotPassword}`,
918
+ companyLogoUrl: companyLogo
919
+ },
920
+ this.commonConfiguration.shouldQueueEmails,
921
+ null,
922
+ null,
923
+ 'user',
924
+ user.id
925
+ );
926
+ }
927
+ // Assuming all users do not have mobile as mandatory.
928
+ if (forgotPasswordSendVerificationTokenOn == ForgotPasswordSendVerificationTokenOn.MOBILE && user.mobile) {
929
+ this.smsService.sendSMSUsingTemplate(
930
+ user.mobile,
931
+ 'forgot-password',
932
+ {
933
+ solidAppName: process.env.SOLID_APP_NAME,
934
+ otp: user.verificationTokenOnForgotPassword,
935
+ verificationTokenOnForgotPassword: user.verificationTokenOnForgotPassword,
936
+ firstName: user.username,
937
+ companyLogoUrl: companyLogo
938
+ }
939
+ );
873
940
  }
874
941
  }
875
942
 
943
+ // async confirmForgotPassword(confirmForgotPasswordDto: ConfirmForgotPasswordDto) {
944
+ // // Steps / Algorithm:
945
+ // // 1. Identify the user using the specified "username", if not found exit.
946
+ // // const user = await this.userRepository.findOne({
947
+ // // where: { username: confirmForgotPasswordDto.username, }
948
+ // // });
949
+ // const user = await this.resolveUserByVerificationToken(confirmForgotPasswordDto.verificationToken);
950
+
951
+ // if (!user) {
952
+ // throw new NotFoundException(ERROR_MESSAGES.USER_NOT_FOUND);
953
+ // }
954
+
955
+ // // 2. Validate if user has used a provider which is "local", only then it makes sense for us to initiate the forgot password routine.
956
+ // if (user.lastLoginProvider !== 'local') {
957
+ // throw new BadRequestException(ERROR_MESSAGES.NON_LOCAL_PROVIDER);
958
+ // }
959
+ // if (!user.active) {
960
+ // throw new UnauthorizedException(ERROR_MESSAGES.USER_INACTIVE);
961
+ // }
962
+
963
+ // // 3. Validate the verification token is proper & update the user record.
964
+ // if (user.verificationTokenOnForgotPassword !== confirmForgotPasswordDto.verificationToken) {
965
+ // throw new UnauthorizedException(ERROR_MESSAGES.INVALID_VERIFICATION_TOKEN);
966
+ // }
967
+ // if (user.verificationTokenOnForgotPasswordExpiresAt < new Date()) {
968
+ // throw new UnauthorizedException(ERROR_MESSAGES.INVALID_VERIFICATION_TOKEN);
969
+ // }
970
+ // user.forgotPasswordConfirmedAt = new Date();
971
+ // user.verificationTokenOnForgotPassword = null;
972
+ // user.verificationTokenOnForgotPasswordExpiresAt = null;
973
+
974
+ // // 4. Update the users password while encrypting it.
975
+ // const pwd = await this.hashingService.hash(confirmForgotPasswordDto.password);
976
+ // user.password = confirmForgotPasswordDto.password
977
+
978
+ // if (await this.isPasswordDuplicate(user)) {
979
+ // throw new BadRequestException(ERROR_MESSAGES.PASSWORD_REUSED);
980
+ // }
981
+ // await this.deleteOldPasswords(user);
982
+
983
+ // user.password = pwd;
984
+ // const userPasswordHistory = new UserPasswordHistory();
985
+ // userPasswordHistory.passwordHash = pwd;
986
+ // userPasswordHistory.user = user;
987
+
988
+ // await this.userRepository.save(user);
989
+ // //FIXME: Do this check conditionally, basis a configuration parameter i.e if IAM_ALLOW_PREVIOUS_PASSWORDS=false, default true
990
+ // await this.userPasswordHistoryRepository.save(userPasswordHistory);
991
+
992
+ // return {
993
+ // status: 'success',
994
+ // message: SUCCESS_MESSAGES.FORGOT_PASSWORD_CONFIRMED,
995
+ // error: '',
996
+ // errorCode: '',
997
+ // data: {}
998
+ // }
999
+ // }
1000
+
876
1001
  //FIXME: Do this check conditionally, basis a configuration parameter i.e if IAM_ALLOW_PREVIOUS_PASSWORDS=true, return immediately without processing, i.e false.
877
1002
  private async isPasswordDuplicate(user: User) {
878
1003
  const userPwdHistoryEntityArray = await this.userPasswordHistoryRepository.findBy(
@@ -1088,7 +1213,7 @@ export class AuthenticationService {
1088
1213
  await this.userActivityHistoryService.logEvent('logout', user);
1089
1214
 
1090
1215
 
1091
- return { message: SUCCESS_MESSAGES.LOGOUT_SUCCESS};
1216
+ return { message: SUCCESS_MESSAGES.LOGOUT_SUCCESS };
1092
1217
  } catch (err) {
1093
1218
  throw err instanceof UnauthorizedException || err instanceof InternalServerErrorException
1094
1219
  ? err
@@ -11,6 +11,7 @@ import { FileService } from 'src/services/file.service';
11
11
  import { CrudHelperService } from 'src/services/crud-helper.service';
12
12
 
13
13
  import { ChatterMessageDetails } from '../entities/chatter-message-details.entity';
14
+ import { ChatterMessageDetailsRepository } from 'src/repository/chatter-message-details.repository';
14
15
 
15
16
  @Injectable()
16
17
  export class ChatterMessageDetailsService extends CRUDService<ChatterMessageDetails>{
@@ -23,8 +24,8 @@ export class ChatterMessageDetailsService extends CRUDService<ChatterMessageDeta
23
24
  readonly crudHelperService: CrudHelperService,
24
25
  @InjectEntityManager()
25
26
  readonly entityManager: EntityManager,
26
- @InjectRepository(ChatterMessageDetails, 'default')
27
- readonly repo: Repository<ChatterMessageDetails>,
27
+ // @InjectRepository(ChatterMessageDetails, 'default')
28
+ readonly repo: ChatterMessageDetailsRepository,
28
29
  readonly moduleRef: ModuleRef,
29
30
  ) {
30
31
  super(modelMetadataService, moduleMetadataService, configService, fileService, discoveryService, crudHelperService, entityManager, repo, 'chatterMessageDetails', 'solid-core', moduleRef);
@@ -17,6 +17,7 @@ import { MediaStorageProviderType } from '../dtos/create-media-storage-provider-
17
17
  import { ChatterMessageDetails } from '../entities/chatter-message-details.entity';
18
18
  import { ModelMetadata } from 'src/entities/model-metadata.entity';
19
19
  import { RequestContextService } from './request-context.service';
20
+ import { ChatterMessageRepository } from 'src/repository/chatter-message.repository';
20
21
  @Injectable()
21
22
  export class ChatterMessageService extends CRUDService<ChatterMessage>{
22
23
  constructor(
@@ -28,8 +29,8 @@ export class ChatterMessageService extends CRUDService<ChatterMessage>{
28
29
  readonly crudHelperService: CrudHelperService,
29
30
  @InjectEntityManager()
30
31
  readonly entityManager: EntityManager,
31
- @InjectRepository(ChatterMessage, 'default')
32
- readonly repo: Repository<ChatterMessage>,
32
+ // @InjectRepository(ChatterMessage, 'default')
33
+ readonly repo: ChatterMessageRepository,
33
34
  @InjectRepository(ChatterMessageDetails, 'default')
34
35
  readonly chatterMessageDetailsRepo: Repository<ChatterMessageDetails>,
35
36
  readonly moduleRef: ModuleRef,
@@ -5,15 +5,17 @@ import { CommonEntity } from 'src/entities/common.entity';
5
5
  import { ComputedFieldMetadata } from 'src/helpers/solid-registry';
6
6
  import { IEntityPreComputeFieldProvider } from 'src/interfaces';
7
7
  import { EntityManager } from 'typeorm';
8
-
8
+ import { get } from "lodash";
9
9
  export interface AlphaNumExternalIdContext {
10
- prefix: string; // The prefix to use for the external ID
11
- length?: number; // Optional: length of the unique code to generate, default is 5
10
+ prefix?: string; // alias -> staticPrefix
11
+ length?: number; // Optional: length of the unique code to generate, default is 5
12
+ dynamicFieldPrefix?: string; // NEW: field name on the entity
12
13
  }
13
14
 
14
15
  @ComputedFieldProvider()
15
16
  @Injectable()
16
- export class AlphaNumExternalIdComputationProvider<T extends CommonEntity> implements IEntityPreComputeFieldProvider<T, AlphaNumExternalIdContext> {
17
+ export class AlphaNumExternalIdComputationProvider<T extends CommonEntity> implements IEntityPreComputeFieldProvider<T, AlphaNumExternalIdContext>
18
+ {
17
19
  constructor(
18
20
  @InjectEntityManager()
19
21
  private readonly entityManager: EntityManager
@@ -24,14 +26,28 @@ export class AlphaNumExternalIdComputationProvider<T extends CommonEntity> imple
24
26
  }
25
27
 
26
28
  help(): string {
27
- return 'Provider used to compute external ID for a CommonEntity.';
29
+ return 'Provider used to compute external ID for a CommonEntity with support for static or dynamic prefix.';
28
30
  }
29
31
 
30
- async preComputeValue(triggerEntity: T, computedFieldMetadata: ComputedFieldMetadata<AlphaNumExternalIdContext>) {
31
- const prefix = computedFieldMetadata.computedFieldValueProviderCtxt.prefix;
32
- const codeLength = computedFieldMetadata.computedFieldValueProviderCtxt.length || 5;
33
- const uniqueCode = await this.generateUniqueExternalId(codeLength, triggerEntity, computedFieldMetadata.fieldName);
34
- triggerEntity[computedFieldMetadata.fieldName] = `${prefix}-${uniqueCode}`;
32
+ async preComputeValue( triggerEntity: T, computedFieldMetadata: ComputedFieldMetadata<AlphaNumExternalIdContext>
33
+ ) {
34
+ const { prefix, length, dynamicFieldPrefix } =
35
+ computedFieldMetadata.computedFieldValueProviderCtxt;
36
+
37
+ const codeLength = length || 5;
38
+
39
+ // Determine prefix
40
+ let resolvedPrefix = prefix || '';
41
+
42
+ if (dynamicFieldPrefix) {
43
+ const dynamicValue = get(triggerEntity as any, dynamicFieldPrefix);
44
+ if (dynamicValue) {
45
+ resolvedPrefix = String(dynamicValue).trim().toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9\-]/g, '');
46
+ }
47
+ }
48
+
49
+ const uniqueCode = await this.generateUniqueExternalId( codeLength, triggerEntity, computedFieldMetadata.fieldName );
50
+ triggerEntity[computedFieldMetadata.fieldName] = resolvedPrefix ? `${resolvedPrefix}-${uniqueCode}` : uniqueCode;
35
51
  }
36
52
 
37
53
  private generateRandomCode(length = 5): string {
@@ -43,10 +59,12 @@ export class AlphaNumExternalIdComputationProvider<T extends CommonEntity> imple
43
59
  return result;
44
60
  }
45
61
 
46
- private async isExternalIdUnique(externalId: string, triggerEntity: T, fieldName: string): Promise<boolean> {
47
- const count = await this.entityManager.count(triggerEntity.constructor.name, {
48
- where: { [fieldName]: externalId },
49
- });
62
+ private async isExternalIdUnique( externalId: string, triggerEntity: T, fieldName: string ): Promise<boolean> {
63
+ const count = await this.entityManager.count(triggerEntity.constructor as any,
64
+ {
65
+ where: { [fieldName]: externalId },
66
+ }
67
+ );
50
68
  return count === 0;
51
69
  }
52
70
 
@@ -56,7 +74,7 @@ export class AlphaNumExternalIdComputationProvider<T extends CommonEntity> imple
56
74
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
57
75
  const newId = this.generateRandomCode(codeLength);
58
76
 
59
- const isUnique = await this.isExternalIdUnique(newId, triggerEntity, fieldName);
77
+ const isUnique = await this.isExternalIdUnique(newId,triggerEntity,fieldName);
60
78
 
61
79
  if (isUnique) {
62
80
  return newId;
@@ -65,4 +83,4 @@ export class AlphaNumExternalIdComputationProvider<T extends CommonEntity> imple
65
83
 
66
84
  throw new Error('Failed to generate a unique external ID after multiple attempts');
67
85
  }
68
- }
86
+ }
@@ -1,4 +1,4 @@
1
- import { BadRequestException, Inject, Optional } from "@nestjs/common";
1
+ import { BadRequestException, Inject, NotFoundException, Optional } from "@nestjs/common";
2
2
  import { ConfigService, ConfigType } from "@nestjs/config";
3
3
  import { DiscoveryService, ModuleRef } from "@nestjs/core";
4
4
  import { EntityManager, In, IsNull, Not, QueryFailedError, SelectQueryBuilder } from "typeorm";
@@ -37,6 +37,7 @@ import { ModuleMetadataService } from "./module-metadata.service";
37
37
  import { isArray } from "class-validator";
38
38
  import { ERROR_MESSAGES } from "src/constants/error-messages";
39
39
  import { SUCCESS_MESSAGES } from "src/constants/success-messages";
40
+ import { RequestContextService } from "./request-context.service";
40
41
 
41
42
  export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDto, so we get the proper types in our service
42
43
 
@@ -437,6 +438,10 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
437
438
  }
438
439
  }
439
440
 
441
+ // Set the request filter in the request context service
442
+ const requestContextService = this.moduleRef.get(RequestContextService, { strict: false });
443
+ requestContextService.setRequestFilter(basicFilterDto);
444
+
440
445
  // Create above query on pincode table using query builder
441
446
  var qb: SelectQueryBuilder<T> = this.repo.createQueryBuilder(alias)
442
447
  qb = this.crudHelperService.buildFilterQuery(qb, basicFilterDto, alias);
@@ -639,7 +644,7 @@ export class CRUDService<T> { // Add two generic value i.e Person,CreatePersonDt
639
644
  select: fields,
640
645
  });
641
646
  if (!entity) {
642
- throw new Error(`Entity [${this.moduleName}.${this.modelName}] with id ${id} not found`);
647
+ throw new NotFoundException(`Entity [${this.moduleName}.${this.modelName}] with id ${id} not found`);
643
648
  }
644
649
  // Populate the entity with the media
645
650
  if (normalizedPopulateMedia.length > 0) {
@@ -660,9 +660,23 @@ export class ModelMetadataService {
660
660
  const existingModelIndex = metaData.moduleMetadata.models.findIndex(
661
661
  (existingModel: any) => existingModel.singularName === modelEntity.singularName
662
662
  );
663
+
664
+ // Remove the model to be deleted from the metadata
663
665
  if (existingModelIndex !== -1) {
664
666
  metaData.moduleMetadata.models.splice(existingModelIndex, 1);
665
667
  }
668
+
669
+ // Remove references to this model in the menu, action & view sections.
670
+ metaData.moduleMetadata.menus = metaData.moduleMetadata.menus.filter(
671
+ (menu: any) => menu.modelUserKey !== modelEntity.singularName
672
+ );
673
+ metaData.moduleMetadata.actions = metaData.moduleMetadata.actions.filter(
674
+ (action: any) => action.modelUserKey !== modelEntity.singularName
675
+ );
676
+ metaData.moduleMetadata.views = metaData.moduleMetadata.views.filter(
677
+ (view: any) => view.modelUserKey !== modelEntity.singularName
678
+ );
679
+
666
680
  const updatedContent = JSON.stringify(metaData, null, 2);
667
681
  await fs.writeFile(filePath, updatedContent);
668
682
 
@@ -1034,7 +1048,7 @@ export class ModelMetadataService {
1034
1048
  type: "solid",
1035
1049
  domain: "" as any,
1036
1050
  context: "" as any,
1037
- customComponent: `/admin/address-master/${model.singularName}/all`,
1051
+ customComponent: "",
1038
1052
  customIsModal: true,
1039
1053
  serverEndpoint: "",
1040
1054
  view: view,
@@ -1,6 +1,7 @@
1
1
  import { Injectable } from "@nestjs/common";
2
2
  import { ClsService } from "nestjs-cls";
3
3
  import { REQUEST_USER_KEY } from "src/constants";
4
+ import { BasicFilterDto } from "src/dtos/basic-filters.dto";
4
5
 
5
6
  @Injectable()
6
7
  export class RequestContextService {
@@ -20,4 +21,12 @@ export class RequestContextService {
20
21
  return this.cls.get('userAgent');
21
22
  }
22
23
 
24
+ setRequestFilter(filter: BasicFilterDto) {
25
+ this.cls.set('filter', filter);
26
+ }
27
+
28
+ getRequestFilter(): BasicFilterDto | undefined {
29
+ return this.cls.get('filter');
30
+ }
31
+
23
32
  }
@@ -270,6 +270,9 @@ import { MailFactory } from './factories/mail.factory';
270
270
  import { TwilioSMSService } from './services/sms/TwilioSMSService';
271
271
  import { PollerService } from './services/poller.service';
272
272
  import { TextractService } from './services/textract.service';
273
+ import { seconds, ThrottlerModule } from '@nestjs/throttler';
274
+ import { ChatterMessageRepository } from './repository/chatter-message.repository';
275
+ import { ChatterMessageDetailsRepository } from './repository/chatter-message-details.repository';
273
276
 
274
277
 
275
278
  @Global()
@@ -309,6 +312,10 @@ import { TextractService } from './services/textract.service';
309
312
  ImportTransactionErrorLog,
310
313
  UserActivityHistory,
311
314
  AiInteraction,
315
+ Dashboard,
316
+ DashboardVariable,
317
+ DashboardQuestion,
318
+ DashboardQuestionSqlDatasetConfig,
312
319
  ]),
313
320
  ConfigModule.forFeature(appBuilderConfig),
314
321
  ConfigModule.forFeature(commonConfig),
@@ -319,6 +326,17 @@ import { TextractService } from './services/textract.service';
319
326
  ServeStaticModule.forRoot({
320
327
  rootPath: join(process.cwd(), 'media-files-storage'),
321
328
  serveRoot: '/media-files-storage',
329
+ serveStaticOptions: {
330
+ setHeaders: (res /*, path, stat*/) => {
331
+ // Allow use of these files from a different origin (e.g., :3000 UI)
332
+ // Use 'same-site' if both origins are on the same site (localhost:* counts as same-site)
333
+ res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin'); // or 'same-site'
334
+
335
+ // If you need to load into <canvas> without tainting or fetch images via XHR,
336
+ // you can also expose CORS here (not needed for simple <img>):
337
+ // res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
338
+ },
339
+ }
322
340
  }),
323
341
  MulterModule.registerAsync({
324
342
  imports: [ConfigModule],
@@ -330,11 +348,14 @@ import { TextractService } from './services/textract.service';
330
348
  HttpModule,
331
349
  ConfigModule,
332
350
  ClsModule,
333
- TypeOrmModule.forFeature([Dashboard]),
334
- TypeOrmModule.forFeature([DashboardVariable]),
335
- TypeOrmModule.forFeature([DashboardQuestion]),
336
- TypeOrmModule.forFeature([DashboardQuestionSqlDatasetConfig]),
337
- TypeOrmModule.forFeature([AiInteraction]),
351
+ ThrottlerModule.forRoot({
352
+ throttlers: [
353
+ { name: 'short', ttl: seconds(10), limit: 10 },
354
+ { name: 'login', ttl: seconds(10), limit: 5 },
355
+ { name: 'burst', ttl: seconds(1), limit: 100 },
356
+ { name: 'sustained', ttl: seconds(300), limit: 500 },
357
+ ],
358
+ }),
338
359
  ],
339
360
  controllers: [
340
361
  ModuleMetadataController,
@@ -567,6 +588,8 @@ import { TextractService } from './services/textract.service';
567
588
  ScheduledJobSubscriber,
568
589
  AlphaNumExternalIdComputationProvider,
569
590
  MailFactory,
591
+ ChatterMessageRepository,
592
+ ChatterMessageDetailsRepository,
570
593
  ],
571
594
  exports: [
572
595
  ModuleMetadataService,
@@ -613,6 +636,7 @@ import { TextractService } from './services/textract.service';
613
636
  MailFactory,
614
637
  PollerService,
615
638
  AiInteractionService,
639
+ ThrottlerModule,
616
640
  ],
617
641
  })
618
642
  export class SolidCoreModule { }