@solidstarters/solid-core 1.2.153 → 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 (137) 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/crud.service.d.ts.map +1 -1
  93. package/dist/services/crud.service.js +4 -1
  94. package/dist/services/crud.service.js.map +1 -1
  95. package/dist/services/model-metadata.service.d.ts.map +1 -1
  96. package/dist/services/model-metadata.service.js +4 -1
  97. package/dist/services/model-metadata.service.js.map +1 -1
  98. package/dist/services/request-context.service.d.ts +3 -0
  99. package/dist/services/request-context.service.d.ts.map +1 -1
  100. package/dist/services/request-context.service.js +6 -0
  101. package/dist/services/request-context.service.js.map +1 -1
  102. package/dist/solid-core.module.d.ts.map +1 -1
  103. package/dist/solid-core.module.js +23 -5
  104. package/dist/solid-core.module.js.map +1 -1
  105. package/dist/tsconfig.tsbuildinfo +1 -1
  106. package/package.json +3 -1
  107. package/src/config/iam.config.ts +3 -0
  108. package/src/constants/error-messages.ts +1 -1
  109. package/src/controllers/ai-interaction.controller.ts +4 -2
  110. package/src/controllers/authentication.controller.ts +16 -10
  111. package/src/controllers/email-template.controller.ts +4 -1
  112. package/src/controllers/google-authentication.controller.ts +4 -0
  113. package/src/controllers/media.controller.ts +5 -1
  114. package/src/controllers/model-metadata.controller.ts +7 -2
  115. package/src/controllers/otp-authentication.controller.ts +4 -1
  116. package/src/controllers/service.controller.ts +5 -1
  117. package/src/controllers/sms-template.controller.ts +8 -7
  118. package/src/decorators/solid-password.decorator.ts +51 -0
  119. package/src/dtos/change-password.dto.ts +2 -0
  120. package/src/dtos/confirm-forgot-password.dto.ts +2 -0
  121. package/src/helpers/cors.helper.ts +34 -0
  122. package/src/helpers/security.helper.ts +53 -0
  123. package/src/index.ts +3 -0
  124. package/src/repository/chatter-message-details.repository.ts +109 -0
  125. package/src/repository/chatter-message.repository.ts +68 -0
  126. package/src/repository/security-rule.repository.ts +2 -2
  127. package/src/repository/solid-base.repository.ts +66 -0
  128. package/src/seeders/seed-data/email-templates/password-changed.handlebars.html +158 -0
  129. package/src/seeders/seed-data/solid-core-metadata.json +9 -0
  130. package/src/services/ai-interaction.service.ts +5 -5
  131. package/src/services/authentication.service.ts +181 -56
  132. package/src/services/chatter-message-details.service.ts +3 -2
  133. package/src/services/chatter-message.service.ts +3 -2
  134. package/src/services/crud.service.ts +7 -2
  135. package/src/services/model-metadata.service.ts +15 -1
  136. package/src/services/request-context.service.ts +9 -0
  137. package/src/solid-core.module.ts +29 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidstarters/solid-core",
3
- "version": "1.2.153",
3
+ "version": "1.2.154",
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",
@@ -39,6 +39,7 @@
39
39
  "@elasticemail/elasticemail-client": "^4.0.23",
40
40
  "@hapi/joi": "^17.1.1",
41
41
  "@nestjs/schedule": "^6.0.0",
42
+ "@nestjs/throttler": "^6.4.0",
42
43
  "amqplib": "^0.10.4",
43
44
  "axios": "^1.7.0",
44
45
  "bcrypt": "^5.1.1",
@@ -50,6 +51,7 @@
50
51
  "exceljs": "^4.4.0",
51
52
  "fast-csv": "^5.0.2",
52
53
  "handlebars": "^4.7.8",
54
+ "helmet": "^8.1.0",
53
55
  "ioredis": "^5.4.1",
54
56
  "locale-codes": "^1.3.1",
55
57
  "lodash": "^4.17.21",
@@ -9,10 +9,13 @@ export const iamConfig = registerAs('iam', () => {
9
9
  activateUserOnRegistration: (process.env.IAM_ACTIVATE_USER_ON_REGISTRATION ?? 'true') === 'true',
10
10
  autoLoginUserOnRegistration: (process.env.IAM_AUTO_LOGIN_USER_ON_REGISTRATION ?? 'false') === 'true',
11
11
  otpExpiry: parseInt(process.env.IAM_OTP_EXPIRY ?? '10'),
12
+ forgotPasswordVerificationTokenExpiry: parseInt(process.env.IAM_FORGOT_PASSWORD_VERIFICATION_TOKEN_EXPIRY ?? '10'),
12
13
  defaultRole: process.env.IAM_DEFAULT_ROLE ?? 'Public',
13
14
  dummyOtp: process.env.IAM_OTP_DUMMY,
14
15
  forgotPasswordSendVerificationTokenOn: process.env.IAM_FORGOT_PASSWORD_SEND_VERIFICATION_TOKEN_ON ?? 'email',
15
16
  forceChangePasswordOnFirstLogin:true,
17
+ PASSWORD_REGEX: process.env.PASSWORD_REGEX || '^$|^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[^\\da-zA-Z]).*$',
18
+ PASSWORD_COMPLEXITY_DESC : process.env.PASSWORD_COMPLEXITY_DESC || 'Password must contain at least one uppercase, one lowercase, one number, and one special character.',
16
19
  googleOauth: {
17
20
  clientID: process.env.IAM_GOOGLE_OAUTH_CLIENT_ID,
18
21
  clientSecret: process.env.IAM_GOOGLE_OAUTH_CLIENT_SECRET,
@@ -21,7 +21,7 @@ export const ERROR_MESSAGES = {
21
21
  USER_ID_MISMATCH: "User ID's do not match.",
22
22
  USERNAME_MISMATCH: "User username's do not match.",
23
23
  INCORRECT_CURRENT_PASSWORD: 'Incorrect current password specified.',
24
- PASSWORD_REUSED: 'This password was previously used, please use a different password.',
24
+ PASSWORD_REUSED: 'Try a different password',
25
25
  INVALID_VERIFICATION_TOKEN: 'Invalid verification token',
26
26
  ACCESS_DENIED: 'Access denied',
27
27
  INVALID_USER_PROFILE: 'Invalid user profile',
@@ -5,6 +5,8 @@ import { AiInteractionService } from '../services/ai-interaction.service';
5
5
  import { CreateAiInteractionDto } from '../dtos/create-ai-interaction.dto';
6
6
  import { UpdateAiInteractionDto } from '../dtos/update-ai-interaction.dto';
7
7
  import { InvokeAiPromptDto } from '../dtos/invoke-ai-prompt.dto';
8
+ import { ActiveUser } from 'src/decorators/active-user.decorator';
9
+ import { ActiveUserData } from 'src/interfaces/active-user-data.interface';
8
10
 
9
11
  enum ShowSoftDeleted {
10
12
  INCLUSIVE = "inclusive",
@@ -92,8 +94,8 @@ export class AiInteractionController {
92
94
 
93
95
  @ApiBearerAuth("jwt")
94
96
  @Post('/trigger-mcp-client-job')
95
- async triggerMcpClientJob(@Body() dto: InvokeAiPromptDto) {
96
- return this.service.triggerMcpClientJob(dto.prompt);
97
+ async triggerMcpClientJob(@Body() dto: InvokeAiPromptDto, @ActiveUser() activeUser: ActiveUserData) {
98
+ return this.service.triggerMcpClientJob(dto.prompt, activeUser.sub);
97
99
  }
98
100
 
99
101
  @ApiBearerAuth("jwt")
@@ -1,29 +1,31 @@
1
1
  import { Body, Controller, Get, HttpCode, HttpStatus, Logger, Post, Res, UseGuards } from '@nestjs/common';
2
- import { AuthenticationService } from '../services/authentication.service';
3
- import { SignInDto } from '../dtos/sign-in.dto';
4
- import { SignUpDto } from '../dtos/sign-up.dto';
5
- import { Response } from 'express';
6
- import { RefreshTokenDto } from '../dtos/refresh-token.dto';
7
2
  import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
8
- import { LocalAuthGuard } from '../passport-strategies/local.strategy';
3
+ import { SkipThrottle, ThrottlerGuard } from '@nestjs/throttler';
4
+ import { Response } from 'express';
5
+ import { ActiveUser } from "../decorators/active-user.decorator";
9
6
  import { Public } from '../decorators/public.decorator';
10
- import { InitiateForgotPasswordDto } from '../dtos/initiate-forgot-password.dto';
11
- import { ConfirmForgotPasswordDto } from '../dtos/confirm-forgot-password.dto';
12
7
  import { ChangePasswordDto } from "../dtos/change-password.dto";
13
- import { ActiveUser } from "../decorators/active-user.decorator";
8
+ import { ConfirmForgotPasswordDto } from '../dtos/confirm-forgot-password.dto';
9
+ import { InitiateForgotPasswordDto } from '../dtos/initiate-forgot-password.dto';
10
+ import { RefreshTokenDto } from '../dtos/refresh-token.dto';
11
+ import { SignInDto } from '../dtos/sign-in.dto';
12
+ import { SignUpDto } from '../dtos/sign-up.dto';
14
13
  import { ActiveUserData } from "../interfaces/active-user-data.interface";
15
- import { RegisterPrivateDto } from '../dtos/register-private.dto';
14
+ import { AuthenticationService } from '../services/authentication.service';
16
15
 
17
16
 
18
17
  // @Auth(AuthType.None)
19
18
  @Controller('iam')
20
19
  @ApiTags("Iam")
20
+ @UseGuards(ThrottlerGuard)
21
+ @SkipThrottle({login: true, short: true, burst: true, sustained: true}) // disable all sets by default for this controller
21
22
  export class AuthenticationController {
22
23
  private readonly logger = new Logger(AuthenticationController.name);
23
24
 
24
25
  constructor(private readonly authService: AuthenticationService) { }
25
26
 
26
27
  @Public()
28
+ @SkipThrottle({ login: false }) //Enable the login throttle only
27
29
  @Post('register')
28
30
  signUp(@Body() signUpDto: SignUpDto) {
29
31
  return this.authService.signUp(signUpDto);
@@ -37,6 +39,7 @@ export class AuthenticationController {
37
39
 
38
40
  @Public()
39
41
  // @UseGuards(LocalAuthGuard)
42
+ @SkipThrottle({ login: false }) //Enable the login throttle only
40
43
  @HttpCode(HttpStatus.OK) // by default @Post does 201, we wanted 200 - hence using @HttpCode(HttpStatus.OK)
41
44
  @Post('authenticate')
42
45
  async signIn(
@@ -59,6 +62,7 @@ export class AuthenticationController {
59
62
  }
60
63
 
61
64
  @Public()
65
+ @SkipThrottle({ login: false }) //Enable the login throttle only
62
66
  @HttpCode(HttpStatus.OK) // changed since the default is 201
63
67
  @Post('refresh-tokens')
64
68
  refreshTokens(@Body() refreshTokenDto: RefreshTokenDto) {
@@ -66,12 +70,14 @@ export class AuthenticationController {
66
70
  }
67
71
 
68
72
  @Public()
73
+ @SkipThrottle({ login: false }) //Enable the login throttle only
69
74
  @Post('initiate/forgot-password')
70
75
  initiateForgotPassword(@Body() initiateForgotPasswordDto: InitiateForgotPasswordDto) {
71
76
  return this.authService.initiateForgotPassword(initiateForgotPasswordDto);
72
77
  }
73
78
 
74
79
  @Public()
80
+ @SkipThrottle({ login: false }) //Enable the login throttle only
75
81
  @Post('confirm/forgot-password')
76
82
  confirmForgotPassword(@Body() confirmForgotPasswordDto: ConfirmForgotPasswordDto) {
77
83
  return this.authService.confirmForgotPassword(confirmForgotPasswordDto);
@@ -1,4 +1,4 @@
1
- import { Body, Controller, Delete, Get, Header, Param, Patch, Post, Put, Query, UploadedFiles, UseInterceptors } from '@nestjs/common';
1
+ import { Body, Controller, Delete, Get, Header, Param, Patch, Post, Put, Query, UploadedFiles, UseGuards, UseInterceptors } from '@nestjs/common';
2
2
  import { ApiBearerAuth, ApiQuery, ApiTags } from '@nestjs/swagger';
3
3
  import { PaginationQueryDto } from 'src/dtos/pagination-query.dto';
4
4
  import { Roles } from 'src/decorators/roles.decorator';
@@ -13,10 +13,13 @@ import { Public } from 'src/decorators/public.decorator';
13
13
  // import { Mailgen } from 'mailgen';
14
14
  import Mailgen = require('mailgen');
15
15
  import { AnyFilesInterceptor } from '@nestjs/platform-express';
16
+ import { ThrottlerGuard, SkipThrottle } from '@nestjs/throttler';
16
17
 
17
18
 
18
19
  @Controller('email-template')
19
20
  @ApiTags("Common")
21
+ @UseGuards(ThrottlerGuard)
22
+ @SkipThrottle({ short: false, login: true, burst: true, sustained: true }) //Enable the short throttle only
20
23
  export class EmailTemplateController {
21
24
  constructor(private readonly service: EmailTemplateService) { }
22
25
 
@@ -10,11 +10,15 @@ import { UserService } from '../services/user.service';
10
10
  import { Public } from '../decorators/public.decorator';
11
11
  import { iamConfig } from '../config/iam.config';
12
12
  import { isGoogleOAuthConfigured } from 'src/helpers/google-oauth.helper';
13
+ import { ThrottlerGuard, SkipThrottle } from '@nestjs/throttler';
14
+
13
15
 
14
16
 
15
17
  @Auth(AuthType.None)
16
18
  @Controller('iam/google')
17
19
  @ApiTags("Iam")
20
+ @UseGuards(ThrottlerGuard)
21
+ @SkipThrottle({ login: false, short: false, burst: true, sustained: true }) //Enable the login throttle only
18
22
  export class GoogleAuthenticationController {
19
23
  constructor(
20
24
  @Inject(iamConfig.KEY) private iamConfiguration: ConfigType<typeof iamConfig>,
@@ -1,10 +1,11 @@
1
- import { Controller, Post, Body, Param, UploadedFiles, UseInterceptors, Put, Get, Query, Delete, Patch } from '@nestjs/common';
1
+ import { Controller, Post, Body, Param, UploadedFiles, UseInterceptors, Put, Get, Query, Delete, Patch, UseGuards } from '@nestjs/common';
2
2
  import { AnyFilesInterceptor } from "@nestjs/platform-express";
3
3
  import { ApiBearerAuth, ApiQuery, ApiTags } from '@nestjs/swagger';
4
4
  import { Public } from 'src/decorators/public.decorator';
5
5
  import { MediaService } from 'src/services/media.service';
6
6
  import { CreateMediaDto } from 'src/dtos/create-media.dto';
7
7
  import { UpdateMediaDto } from 'src/dtos/update-media.dto';
8
+ import { ThrottlerGuard, SkipThrottle } from '@nestjs/throttler';
8
9
 
9
10
  enum ShowSoftDeleted {
10
11
  INCLUSIVE = "inclusive",
@@ -13,6 +14,8 @@ enum ShowSoftDeleted {
13
14
 
14
15
  @ApiTags('Solid Core')
15
16
  @Controller('media')
17
+ @UseGuards(ThrottlerGuard)
18
+ @SkipThrottle({ short: true, login: true, burst: true, sustained: true }) //Skip all
16
19
  export class MediaController {
17
20
  constructor(private readonly service: MediaService) {}
18
21
 
@@ -46,6 +49,7 @@ export class MediaController {
46
49
  }
47
50
 
48
51
  @Public()
52
+ @SkipThrottle({ short: false, login: true, burst: true, sustained: true }) //Enable the short throttle only
49
53
  @ApiBearerAuth("jwt")
50
54
  @Post('/upload')
51
55
  @UseInterceptors(AnyFilesInterceptor())
@@ -1,13 +1,16 @@
1
- import { Body, Controller, Delete, Get, Logger, Param, ParseIntPipe, Post, Put, Query } from '@nestjs/common';
1
+ import { Body, Controller, Delete, Get, Logger, Param, ParseIntPipe, Post, Put, Query, UseGuards } from '@nestjs/common';
2
2
  import { ApiBearerAuth, ApiQuery, ApiTags } from '@nestjs/swagger';
3
3
  import { Public } from 'src/decorators/public.decorator';
4
4
  import { BasicFilterDto } from '../dtos/basic-filters.dto';
5
5
  import { CreateModelMetadataDto } from '../dtos/create-model-metadata.dto';
6
6
  import { UpdateModelMetaDataDto } from '../dtos/update-model-metadata.dto';
7
7
  import { ModelMetadataService } from '../services/model-metadata.service';
8
+ import { ThrottlerGuard, SkipThrottle } from '@nestjs/throttler';
8
9
 
9
10
  @Controller('model-metadata')
10
11
  @ApiTags("App Builder")
12
+ @UseGuards(ThrottlerGuard)
13
+ @SkipThrottle({ short: true, login: true, burst: true, sustained: true }) //Skip all
11
14
  export class ModelMetadataController {
12
15
  private logger = new Logger('ModelMetadataController');
13
16
 
@@ -32,6 +35,7 @@ export class ModelMetadataController {
32
35
  }
33
36
 
34
37
  @Public()
38
+ @SkipThrottle({ burst: false, short: true, login: true, sustained: true }) //Enable burst only
35
39
  @Get('public')
36
40
  async findManyPublic() {
37
41
  const basicFilterDto: BasicFilterDto = {
@@ -40,7 +44,7 @@ export class ModelMetadataController {
40
44
  offset: 0,
41
45
  filters: [],
42
46
  groupBy: [],
43
- populate: [],
47
+ populate: [],
44
48
  populateMedia: [],
45
49
  sort: []
46
50
  }
@@ -61,6 +65,7 @@ export class ModelMetadataController {
61
65
  }
62
66
 
63
67
  @Public()
68
+ @SkipThrottle({ short: false, burst: true, login: true, sustained: true }) //Enable short only
64
69
  @Post('/update-user-key')
65
70
  updateUserKey(@Body() data: any) {
66
71
  return this.modelMetadataService.updateUserKey(data);
@@ -1,4 +1,4 @@
1
- import { Body, Controller, HttpCode, HttpStatus, Post, Res } from '@nestjs/common';
1
+ import { Body, Controller, HttpCode, HttpStatus, Post, Res, UseGuards } from '@nestjs/common';
2
2
  import { ApiTags } from '@nestjs/swagger';
3
3
  import { Response } from 'express';
4
4
  import { Auth } from '../decorators/auth.decorator';
@@ -8,11 +8,14 @@ import { OTPSignInDto } from '../dtos/otp-sign-in.dto';
8
8
  import { OTPSignUpDto } from '../dtos/otp-sign-up.dto';
9
9
  import { AuthType } from '../enums/auth-type.enum';
10
10
  import { AuthenticationService } from '../services/authentication.service';
11
+ import { ThrottlerGuard, SkipThrottle } from '@nestjs/throttler';
11
12
 
12
13
 
13
14
  @Auth(AuthType.None)
14
15
  @Controller('iam/otp')
15
16
  @ApiTags("Iam")
17
+ @UseGuards(ThrottlerGuard)
18
+ @SkipThrottle({ login: false, short: true, burst: true, sustained: true }) //Enable the login throttle only
16
19
  export class OTPAuthenticationController {
17
20
  constructor(private readonly authService: AuthenticationService) { }
18
21
 
@@ -1,12 +1,15 @@
1
- import { Body, Controller, Get, Logger, Post } from '@nestjs/common';
1
+ import { Body, Controller, Get, Logger, Post, UseGuards } from '@nestjs/common';
2
2
  import { DiscoveryService, MetadataScanner, Reflector } from '@nestjs/core';
3
3
  import { Public } from 'src/decorators/public.decorator';
4
4
  import { SolidRegistry } from '../helpers/solid-registry';
5
5
  import { ApiTags } from '@nestjs/swagger';
6
+ import { ThrottlerGuard, SkipThrottle } from '@nestjs/throttler';
6
7
 
7
8
 
8
9
  @Controller('')
9
10
  @ApiTags("Common")
11
+ @UseGuards(ThrottlerGuard)
12
+ @SkipThrottle({ short: true, login: true, burst: true, sustained: true }) //Skip all
10
13
  export class ServiceController {
11
14
  private readonly logger = new Logger(ServiceController.name);
12
15
 
@@ -21,6 +24,7 @@ export class ServiceController {
21
24
  }
22
25
 
23
26
  @Public()
27
+ @SkipThrottle({ short: false, login: true, burst: true, sustained: true }) //Enable the short throttle only
24
28
  @Post('seed')
25
29
  async seedData(@Body() seedData: any) {
26
30
  const seeder = this.solidRegistry
@@ -1,16 +1,17 @@
1
- import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query, UploadedFiles, UseInterceptors } from '@nestjs/common';
2
- import { ApiBearerAuth, ApiQuery, ApiTags } from '@nestjs/swagger';
3
- import { PaginationQueryDto } from 'src/dtos/pagination-query.dto';
4
- import { Roles } from 'src/decorators/roles.decorator';
5
- import { SmsTemplateService } from '../services/sms-template.service';
1
+ import { Body, Controller, Delete, Get, Param, Post, Put, Query, UploadedFiles, UseGuards, UseInterceptors } from '@nestjs/common';
2
+ import { AnyFilesInterceptor } from '@nestjs/platform-express';
3
+ import { ApiQuery, ApiTags } from '@nestjs/swagger';
4
+ import { SkipThrottle, ThrottlerGuard } from '@nestjs/throttler';
5
+ import { Public } from 'src/decorators/public.decorator';
6
6
  import { CreateSmsTemplateDto } from '../dtos/create-sms-template.dto';
7
7
  import { UpdateSmsTemplateDto } from '../dtos/update-sms-template.dto';
8
- import { Public } from 'src/decorators/public.decorator';
9
- import { AnyFilesInterceptor } from '@nestjs/platform-express';
8
+ import { SmsTemplateService } from '../services/sms-template.service';
10
9
 
11
10
 
12
11
  @Controller('sms-template')
13
12
  @ApiTags("Common")
13
+ @UseGuards(ThrottlerGuard)
14
+ @SkipThrottle({ short: false, login: true, burst: true, sustained: true }) //Enable the short throttle only
14
15
  export class SmsTemplateController {
15
16
  constructor(private readonly service: SmsTemplateService) { }
16
17
 
@@ -0,0 +1,51 @@
1
+ // solid-password.decorator.ts
2
+ import {
3
+ registerDecorator,
4
+ ValidationArguments,
5
+ ValidationOptions,
6
+ ValidatorConstraint,
7
+ ValidatorConstraintInterface,
8
+ } from 'class-validator';
9
+ import { Injectable } from '@nestjs/common';
10
+ import { iamConfig } from '../config/iam.config';
11
+
12
+ interface SolidPasswordOptions extends ValidationOptions {
13
+ regex?: RegExp | string;
14
+ message ?: string;
15
+ }
16
+
17
+ @ValidatorConstraint({ async: false })
18
+ @Injectable()
19
+ export class SolidPasswordConstraint implements ValidatorConstraintInterface {
20
+ validate(value: string, args: ValidationArguments) {
21
+ if (!value) return false;
22
+
23
+ const opts = args.constraints[0] as SolidPasswordOptions;
24
+
25
+ // priority: decorator-provided regex → iamConfig().PASSWORD_REGEX
26
+ const regex = opts?.regex || iamConfig().PASSWORD_REGEX;
27
+
28
+ return new RegExp(regex).test(value);
29
+ }
30
+
31
+ defaultMessage(args?: ValidationArguments): string {
32
+ const opts = args?.constraints?.[0] as SolidPasswordOptions;
33
+
34
+ // Just use the string from decorator if passed, otherwise use iamConfig
35
+ return opts?.message || iamConfig().PASSWORD_COMPLEXITY_DESC;
36
+ }
37
+ }
38
+
39
+ export function SolidPasswordRegex(
40
+ options?: SolidPasswordOptions,
41
+ ): PropertyDecorator {
42
+ return (object: Object, propertyName: string) => {
43
+ registerDecorator({
44
+ target: object.constructor,
45
+ propertyName,
46
+ options,
47
+ constraints: [options],
48
+ validator: SolidPasswordConstraint,
49
+ });
50
+ };
51
+ }
@@ -1,4 +1,5 @@
1
1
  import { IsNotEmpty } from "class-validator";
2
+ import { SolidPasswordRegex } from "src/decorators/solid-password.decorator";
2
3
 
3
4
  export class ChangePasswordDto {
4
5
  @IsNotEmpty()
@@ -11,5 +12,6 @@ export class ChangePasswordDto {
11
12
  currentPassword: string;
12
13
 
13
14
  @IsNotEmpty()
15
+ @SolidPasswordRegex({ regex: /^$|^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\da-zA-Z]).*$/})
14
16
  newPassword: string;
15
17
  }
@@ -1,4 +1,5 @@
1
1
  import { IsNotEmpty, IsOptional } from "class-validator";
2
+ import { SolidPasswordRegex } from "src/decorators/solid-password.decorator";
2
3
 
3
4
  export class ConfirmForgotPasswordDto {
4
5
  @IsNotEmpty()
@@ -13,5 +14,6 @@ export class ConfirmForgotPasswordDto {
13
14
  verificationToken: string;
14
15
 
15
16
  @IsNotEmpty()
17
+ @SolidPasswordRegex({ regex: /^$|^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\da-zA-Z]).*$/})
16
18
  password: string;
17
19
  }
@@ -0,0 +1,34 @@
1
+ import { CorsOptions } from 'cors';
2
+ import { ConfigService } from '@nestjs/config';
3
+
4
+ /** Build CorsOptions from env; supports wildcards like https://*.example.com */
5
+ export function buildDefaultCorsOptions(configService: ConfigService): CorsOptions {
6
+ const rawOrigins = configService.get<string>('CORS_ORIGINS') ?? '';
7
+ const allowed = rawOrigins.split(',').map(s => s.trim()).filter(Boolean);
8
+
9
+ const escapeRx = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
10
+ const patternToRegex = (pattern: string): RegExp => {
11
+ const hasScheme = /^https?:\/\//i.test(pattern);
12
+ const schemePart = hasScheme ? '' : 'https?:\\/\\/';
13
+ if (pattern === '*' || pattern === '.*') return /^.*$/i;
14
+ const escaped = escapeRx(pattern)
15
+ .replace(/^https?:\/\//i, '') // strip scheme if present
16
+ .replace(/\*/g, '[^.]+'); // * => one subdomain segment
17
+ return new RegExp(`^${schemePart}${escaped}(?::\\d+)?$`, 'i');
18
+ };
19
+
20
+ const matchers = allowed.map(patternToRegex);
21
+ const isAllowed = (origin: string) =>
22
+ matchers.length > 0 && matchers.some(rx => rx.test(origin));
23
+
24
+ return {
25
+ origin: (origin, cb) => {
26
+ if (!origin) return cb(null, true); // allow no-origin (CLI/mobile/internal)
27
+ if (isAllowed(origin)) return cb(null, true);
28
+ return cb(new Error(`Origin ${origin} not allowed by CORS`), false);
29
+ },
30
+ methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
31
+ allowedHeaders: ['Content-Type', 'Authorization'],
32
+ credentials: true,
33
+ };
34
+ }
@@ -0,0 +1,53 @@
1
+ import { Environment } from "src/decorators/disallow-in-production.decorator";
2
+ import { HelmetOptions } from "helmet";
3
+
4
+ export function buildDefaultSecurityHeaderOptions(): Readonly<HelmetOptions> {
5
+ return {
6
+ referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
7
+ crossOriginEmbedderPolicy: false,
8
+ crossOriginResourcePolicy: { policy: 'same-site' },
9
+ frameguard: { action: 'sameorigin' }, // or { action: 'deny' }
10
+ // HSTS: send only in prod over HTTPS
11
+ hsts:
12
+ process.env.NODE_ENV === Environment.Production
13
+ ? { maxAge: 31536000, includeSubDomains: true, preload: true } // 1 year
14
+ : false,
15
+ }
16
+ }
17
+
18
+ type Source = 'self' | 'none' | string; // string = an origin like 'https://cdn.example.com'
19
+ type DirectiveConfig = 'self' | 'none' | Source[];
20
+
21
+ export type PermissionsPolicyConfig = Record<string, DirectiveConfig>;
22
+
23
+ export const DEFAULT_PERMISSIONS_POLICY: PermissionsPolicyConfig = {
24
+ camera: 'none',
25
+ microphone: 'none',
26
+ geolocation: 'none',
27
+ fullscreen: 'self', // allow same-origin fullscreen
28
+ payment: 'none',
29
+ accelerometer: 'none',
30
+ autoplay: 'none',
31
+ 'clipboard-read': 'none',
32
+ 'clipboard-write': 'none',
33
+ gyroscope: 'none',
34
+ magnetometer: 'none',
35
+ usb: 'none',
36
+ };
37
+
38
+ export function buildPermissionsPolicyHeader(
39
+ overrides: Partial<PermissionsPolicyConfig> = {}
40
+ ): string {
41
+ const merged: PermissionsPolicyConfig = { ...DEFAULT_PERMISSIONS_POLICY, ...overrides };
42
+ return Object.entries(merged)
43
+ .map(([feature, value]) => `${feature}=${serializeValue(value)}`)
44
+ .join(', ');
45
+ }
46
+
47
+ function serializeValue(v: DirectiveConfig): string {
48
+ if (v === 'none') return '()';
49
+ if (v === 'self') return '(self)';
50
+ // array of sources: allow 'self' and/or explicit origins
51
+ const parts = v.map(src => (src === 'self' ? 'self' : src)).join(' ');
52
+ return `(${parts})`;
53
+ }
package/src/index.ts CHANGED
@@ -134,6 +134,7 @@ export * from './entities/dashboard-question-sql-dataset-config.entity'
134
134
  export * from './entities/ai-interaction.entity'
135
135
 
136
136
  export * from './enums/auth-type.enum'
137
+ export * from './decorators/disallow-in-production.decorator'
137
138
 
138
139
  export * from './filters/http-exception.filter'
139
140
 
@@ -167,6 +168,8 @@ export * from './helpers/field-crud-managers/SelectionStaticFieldCrudManager' //
167
168
  export * from './helpers/field-crud-managers/ShortTextFieldCrudManager' //rename
168
169
  export * from './helpers/field-crud-managers/UUIDFieldCrudManager' //rename
169
170
  export * from './helpers/environment.helper'
171
+ export * from './helpers/cors.helper'
172
+ export * from './helpers/security.helper'
170
173
 
171
174
  export * from './services/crud.service'
172
175
  export * from './interceptors/logging.interceptor'
@@ -0,0 +1,109 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import {
3
+ DataSource,
4
+ QueryRunner,
5
+ SelectQueryBuilder,
6
+ } from 'typeorm';
7
+ import { camelize, classify } from '@angular-devkit/core/src/utils/strings';
8
+
9
+ import { ChatterMessageDetails } from 'src/entities/chatter-message-details.entity';
10
+ import { ActiveUserData } from 'src/interfaces/active-user-data.interface';
11
+ import { RequestContextService } from 'src/services/request-context.service';
12
+ import { SecurityRuleRepository } from './security-rule.repository';
13
+ import { SolidBaseRepository } from './solid-base.repository';
14
+ import {get} from "lodash"
15
+
16
+
17
+ @Injectable()
18
+ export class ChatterMessageDetailsRepository extends SolidBaseRepository<ChatterMessageDetails> {
19
+ constructor(
20
+ readonly dataSource: DataSource,
21
+ readonly requestContextService: RequestContextService,
22
+ readonly securityRuleRepository: SecurityRuleRepository,
23
+ ) {
24
+ super(ChatterMessageDetails, dataSource, requestContextService, securityRuleRepository);
25
+ }
26
+ private readonly CO_MODEL_NAME_PATH = 'filters.chatterMessage.coModelName.$eq';
27
+
28
+ /**
29
+ * Build a security-aware QB:
30
+ * - join the real relation to ChatterMessage (alias: "message")
31
+ * - left join the polymorphic co-model table using message.co_model_* fields
32
+ * - (optionally) apply security rules on the co-model alias
33
+ */
34
+ override createQueryBuilder(
35
+ alias = 'detail',
36
+ queryRunner?: QueryRunner,
37
+ ): SelectQueryBuilder<ChatterMessageDetails> {
38
+ const activeUser = this.requestContextService.getActiveUser();
39
+ let qb = super.createQueryBuilder(alias, queryRunner);
40
+
41
+ // Join the real relation so we can access co_model_* fields
42
+ qb = qb.leftJoin(`${alias}.chatterMessage`, 'chatterMessage');
43
+
44
+ // Example: join the "client" co-model (pass whatever co-model name you need)
45
+ const [coModelName, coModelAlias] = this.getCoModelNameAndAlias();
46
+
47
+ qb = this.leftJoinCoModel(qb, coModelName, 'chatterMessage');
48
+
49
+ if (!activeUser) return qb;
50
+
51
+ // If your security rules should apply to the co-model rows, pass the co-model alias.
52
+ // Here we use the co-model name "client" both as model key and alias base for consistency.
53
+ return this.securityRuleRepository.applySecurityRules(
54
+ qb,
55
+ coModelName, // modelSingularName (or whatever your rules expect)
56
+ activeUser as ActiveUserData,
57
+ coModelAlias, // the alias we used inside leftJoinCoModel
58
+ );
59
+ }
60
+
61
+ /**
62
+ * Left-join the polymorphic co-model table, matching:
63
+ * <coModelAlias>.id = <messageAlias>.co_model_entity_id
64
+ * AND <messageAlias>.co_model_name = :model
65
+ *
66
+ * @param qb QB built on ChatterMessageDetails
67
+ * @param coModelName e.g. "client" | "invoice" | ...
68
+ * @param messageAlias alias used for joined ChatterMessage (default: "message")
69
+ *
70
+ * Notes:
71
+ * - We resolve the entity metadata from the classified name (e.g., "Client"),
72
+ * then use metadata.tablePath to get schema-qualified table.
73
+ * - We build a stable alias from metadata.name (camelized).
74
+ */
75
+ leftJoinCoModel<T>(
76
+ qb: SelectQueryBuilder<T>,
77
+ coModelName: string,
78
+ messageAlias: string = 'message',
79
+ ): SelectQueryBuilder<T> {
80
+ // Resolve entity metadata from your naming convention
81
+ const entityName = classify(coModelName); // "client" -> "Client"
82
+ const meta = this.dataSource.getMetadata(entityName); // throws if not registered
83
+ // const table = meta.tablePath; // schema-qualified
84
+ const coAlias = camelize(meta.name); // stable alias, e.g., "client"
85
+
86
+ // LEFT JOIN "<schema>"."<table>" "<coAlias>"
87
+ // ON "<coAlias>"."id" = "message"."co_model_entity_id"
88
+ // AND "message"."co_model_name" = :model
89
+ qb.leftJoin(
90
+ entityName,
91
+ coAlias,
92
+ `"${coAlias}"."id" = "${messageAlias}"."co_model_entity_id" AND "${messageAlias}"."co_model_name" = :model`,
93
+ { model: coModelName },
94
+ );
95
+
96
+ return qb;
97
+ }
98
+
99
+ // This uses the requestContextService.getRequestFilter method and extracts the coModelName and creates the alias and returns the name and alias tuple
100
+ private getCoModelNameAndAlias() {
101
+ const requestFilter = this.requestContextService.getRequestFilter();
102
+ if (!requestFilter) return [undefined, undefined];
103
+
104
+ const coModelName = get(requestFilter, this.CO_MODEL_NAME_PATH);
105
+ const alias = camelize(coModelName);
106
+ return [coModelName, alias];
107
+ }
108
+
109
+ }