@solidstarters/solid-core 1.2.165 → 1.2.168

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 (80) hide show
  1. package/dist/config/iam.config.d.ts +2 -0
  2. package/dist/config/iam.config.d.ts.map +1 -1
  3. package/dist/config/iam.config.js +1 -0
  4. package/dist/config/iam.config.js.map +1 -1
  5. package/dist/decorators/error-codes-provider.decorator.d.ts +4 -0
  6. package/dist/decorators/error-codes-provider.decorator.d.ts.map +1 -0
  7. package/dist/decorators/error-codes-provider.decorator.js +12 -0
  8. package/dist/decorators/error-codes-provider.decorator.js.map +1 -0
  9. package/dist/dtos/post-chatter-message.dto.js.map +1 -1
  10. package/dist/entities/chatter-message.entity.js.map +1 -1
  11. package/dist/entities/security-rule.entity.js +0 -1
  12. package/dist/entities/security-rule.entity.js.map +1 -1
  13. package/dist/filters/http-exception.filter.d.ts +3 -2
  14. package/dist/filters/http-exception.filter.d.ts.map +1 -1
  15. package/dist/filters/http-exception.filter.js +23 -16
  16. package/dist/filters/http-exception.filter.js.map +1 -1
  17. package/dist/helpers/error-mapper.service.d.ts +12 -2
  18. package/dist/helpers/error-mapper.service.d.ts.map +1 -1
  19. package/dist/helpers/error-mapper.service.js +85 -72
  20. package/dist/helpers/error-mapper.service.js.map +1 -1
  21. package/dist/helpers/security.helper.d.ts +4 -2
  22. package/dist/helpers/security.helper.d.ts.map +1 -1
  23. package/dist/helpers/security.helper.js +38 -23
  24. package/dist/helpers/security.helper.js.map +1 -1
  25. package/dist/helpers/solid-core-error-codes-provider.service.d.ts +7 -0
  26. package/dist/helpers/solid-core-error-codes-provider.service.d.ts.map +1 -0
  27. package/dist/helpers/solid-core-error-codes-provider.service.js +67 -0
  28. package/dist/helpers/solid-core-error-codes-provider.service.js.map +1 -0
  29. package/dist/helpers/solid-registry.d.ts +5 -1
  30. package/dist/helpers/solid-registry.d.ts.map +1 -1
  31. package/dist/helpers/solid-registry.js +16 -0
  32. package/dist/helpers/solid-registry.js.map +1 -1
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -0
  36. package/dist/index.js.map +1 -1
  37. package/dist/interfaces.d.ts +16 -0
  38. package/dist/interfaces.d.ts.map +1 -1
  39. package/dist/interfaces.js.map +1 -1
  40. package/dist/repository/security-rule.repository.js +2 -2
  41. package/dist/repository/security-rule.repository.js.map +1 -1
  42. package/dist/seeders/seed-data/solid-core-metadata.json +2 -2
  43. package/dist/services/authentication.service.js +5 -4
  44. package/dist/services/authentication.service.js.map +1 -1
  45. package/dist/services/chatter-message.service.d.ts.map +1 -1
  46. package/dist/services/chatter-message.service.js.map +1 -1
  47. package/dist/services/model-metadata.service.js +1 -1
  48. package/dist/services/model-metadata.service.js.map +1 -1
  49. package/dist/services/setting.service.d.ts.map +1 -1
  50. package/dist/services/setting.service.js +2 -1
  51. package/dist/services/setting.service.js.map +1 -1
  52. package/dist/services/solid-introspect.service.d.ts +1 -0
  53. package/dist/services/solid-introspect.service.d.ts.map +1 -1
  54. package/dist/services/solid-introspect.service.js +14 -0
  55. package/dist/services/solid-introspect.service.js.map +1 -1
  56. package/dist/solid-core.module.d.ts.map +1 -1
  57. package/dist/solid-core.module.js +2 -0
  58. package/dist/solid-core.module.js.map +1 -1
  59. package/dist/tsconfig.tsbuildinfo +1 -1
  60. package/package.json +1 -1
  61. package/src/config/iam.config.ts +1 -0
  62. package/src/decorators/error-codes-provider.decorator.ts +9 -0
  63. package/src/dtos/post-chatter-message.dto.ts +1 -1
  64. package/src/entities/chatter-message.entity.ts +3 -3
  65. package/src/entities/security-rule.entity.ts +1 -1
  66. package/src/filters/http-exception.filter.ts +48 -23
  67. package/src/helpers/error-mapper.service.ts +117 -176
  68. package/src/helpers/security.helper.ts +95 -30
  69. package/src/helpers/solid-core-error-codes-provider.service.ts +63 -0
  70. package/src/helpers/solid-registry.ts +20 -1
  71. package/src/index.ts +1 -0
  72. package/src/interfaces.ts +36 -0
  73. package/src/repository/security-rule.repository.ts +2 -2
  74. package/src/seeders/seed-data/solid-core-metadata.json +2 -2
  75. package/src/services/authentication.service.ts +6 -6
  76. package/src/services/chatter-message.service.ts +373 -374
  77. package/src/services/model-metadata.service.ts +1 -1
  78. package/src/services/setting.service.ts +2 -1
  79. package/src/services/solid-introspect.service.ts +22 -0
  80. package/src/solid-core.module.ts +2 -0
@@ -1,38 +1,74 @@
1
+ import { HelmetOptions } from "helmet";
1
2
  import { Environment } from "src/decorators/disallow-in-production.decorator";
2
- import { HelmetOptions } from "helmet";
3
3
 
4
+ /**
5
+ * Default security headers for SolidX apps.
6
+ * - HSTS only in prod over HTTPS
7
+ * - CSP with frame-ancestors 'none' (prevents clickjacking)
8
+ * - X-Frame-Options: DENY (legacy fallback)
9
+ * - No X-XSS-Protection (deprecated)
10
+ */
4
11
  export function buildDefaultSecurityHeaderOptions(): Readonly<HelmetOptions> {
5
- return {
6
- referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
12
+ const isProd = process.env.ENV === Environment.Production;
13
+
14
+ return {
15
+ // Modern CSP. Add more directives as your app needs (script-src, connect-src, etc.)
16
+ contentSecurityPolicy: {
17
+ useDefaults: true,
18
+ directives: {
19
+ // sensible secure defaults
20
+ // "default-src": ["'self'"],
21
+ // "base-uri": ["'self'"],
22
+ // "object-src": ["'none'"],
23
+ // "form-action": ["'self'"],
24
+
25
+ // clickjacking defense (modern)
26
+ "frame-ancestors": ["'none'"],
27
+
28
+ // add/adjust as needed for your app:
29
+ // "script-src": ["'self'"], // add hashes/nonces/CSPRO if needed
30
+ // "style-src": ["'self'", "'unsafe-inline'"],
31
+ // "img-src": ["'self'", "data:"],
32
+ // "connect-src": ["'self'", "https://api.example.com"],
33
+ // "frame-src": ["'none'"], // iframes you intentionally allow
34
+ },
35
+ },
36
+
37
+
38
+ // Legacy clickjacking defense (kept for older UAs)
39
+ frameguard: { action: "deny" },
40
+
41
+ // Referrer/cross-origin policies
42
+ referrerPolicy: { policy: "strict-origin-when-cross-origin" },
7
43
  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
- }
44
+ crossOriginResourcePolicy: { policy: "same-site" },
45
+
46
+ // HSTS only when you’re on HTTPS in production
47
+ hsts: isProd
48
+ ? { maxAge: 31536000, includeSubDomains: true, preload: true }
49
+ : false,
50
+ };
16
51
  }
17
52
 
18
- type Source = 'self' | 'none' | string; // string = an origin like 'https://cdn.example.com'
19
- type DirectiveConfig = 'self' | 'none' | Source[];
53
+ /* ---------------- Permissions-Policy (formerly Feature-Policy) ---------------- */
20
54
 
55
+ type Source = "self" | "none" | string;
56
+ type DirectiveConfig = "self" | "none" | Source[];
21
57
  export type PermissionsPolicyConfig = Record<string, DirectiveConfig>;
22
58
 
23
59
  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',
60
+ camera: "none",
61
+ microphone: "none",
62
+ geolocation: "none",
63
+ fullscreen: "self",
64
+ payment: "none",
65
+ accelerometer: "none",
66
+ autoplay: "none",
67
+ "clipboard-read": "none",
68
+ "clipboard-write": "none",
69
+ gyroscope: "none",
70
+ magnetometer: "none",
71
+ usb: "none",
36
72
  };
37
73
 
38
74
  export function buildPermissionsPolicyHeader(
@@ -41,13 +77,42 @@ export function buildPermissionsPolicyHeader(
41
77
  const merged: PermissionsPolicyConfig = { ...DEFAULT_PERMISSIONS_POLICY, ...overrides };
42
78
  return Object.entries(merged)
43
79
  .map(([feature, value]) => `${feature}=${serializeValue(value)}`)
44
- .join(', ');
80
+ .join(", ");
45
81
  }
46
82
 
47
83
  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(' ');
84
+ if (v === "none") return "()";
85
+ if (v === "self") return "(self)";
86
+ const parts = v.map((src) => (src === "self" ? "self" : src)).join(" ");
52
87
  return `(${parts})`;
53
88
  }
89
+
90
+ /* ---------------- Cache-Control helpers ---------------- */
91
+
92
+ /**
93
+ * Default: no-store for HTML/API responses unless you have a reason to cache.
94
+ * Attach as a global middleware or on selected routes.
95
+ */
96
+ export const DEFAULT_CACHE_CONTROL =
97
+ "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0, s-maxage=0";
98
+
99
+ export function setDefaultCacheControl() {
100
+ return function cacheControlMiddleware(
101
+ _req: import("express").Request,
102
+ _res: import("express").Response,
103
+ next: import("express").NextFunction
104
+ ) {
105
+ _res.setHeader("Cache-Control", DEFAULT_CACHE_CONTROL);
106
+ next();
107
+ };
108
+ }
109
+
110
+ /* ---------------- Example Express wiring ---------------- */
111
+ // import express from "express";
112
+ // const app = express();
113
+ // app.use(helmet(buildDefaultSecurityHeaderOptions()));
114
+ // app.use((req, res, next) => {
115
+ // res.setHeader("Permissions-Policy", buildPermissionsPolicyHeader());
116
+ // next();
117
+ // });
118
+ // app.use(setDefaultCacheControl());
@@ -0,0 +1,63 @@
1
+ // src/common/errors/providers/solidcore-error-code.provider.ts
2
+ import { Injectable } from '@nestjs/common';
3
+ import { ErrorCodeProvider } from 'src/decorators/error-codes-provider.decorator';
4
+ import { ErrorMeta, ErrorRule, IErrorCodeProvider } from 'src/interfaces';
5
+
6
+
7
+ @ErrorCodeProvider()
8
+ @Injectable()
9
+ export class SolidCoreErrorCodesProvider implements IErrorCodeProvider {
10
+ name(): string {
11
+ return 'SolidCoreErrorCodeProvider';
12
+ }
13
+
14
+ rules(): ReadonlyArray<ErrorRule> {
15
+ return [
16
+ {
17
+ code: 'solidx-mcp-server-unavailable',
18
+ priority: 100, // run early
19
+ match: (txt) =>
20
+ txt.includes('all connection attempts failed') &&
21
+ txt.includes('unhandled errors in a taskgroup (1 sub-exception)'),
22
+ meta: {
23
+ message: 'SolidX MCP server is unreachable. Please verify the MCP endpoint.',
24
+ httpStatus: 503,
25
+ },
26
+ },
27
+ {
28
+ code: 'db-duplicate-key',
29
+ priority: 90,
30
+ match: (txt) => txt.includes('unique constraint') || txt.includes('duplicate key'),
31
+ meta: {
32
+ message: 'Duplicate key violation. A record with these values already exists.',
33
+ httpStatus: 409,
34
+ },
35
+ },
36
+ {
37
+ code: 'db-foreign-key-error',
38
+ priority: 90,
39
+ match: (txt) => txt.includes('violates foreign key'),
40
+ meta: {
41
+ message:
42
+ 'Foreign key constraint prevents this operation due to related records.',
43
+ httpStatus: 409,
44
+ },
45
+ },
46
+ {
47
+ code: 'unknown-error',
48
+ priority: -1, // last resort
49
+ match: (_txt) => true, // fallback catch-all
50
+ meta: {
51
+ message: 'An unexpected error occurred.',
52
+ httpStatus: 500,
53
+ },
54
+ },
55
+ ];
56
+ }
57
+
58
+ // Optional explicit meta resolution (if you want)
59
+ resolve(code: string): ErrorMeta | undefined {
60
+ const rule = this.rules().find((r) => r.code === code);
61
+ return rule?.meta;
62
+ }
63
+ }
@@ -5,7 +5,7 @@ import { CommonEntity } from 'src/entities/common.entity';
5
5
  import { Locale } from 'src/entities/locale.entity';
6
6
  import { SecurityRule } from 'src/entities/security-rule.entity';
7
7
  import { IScheduledJob } from 'src/services/scheduled-jobs/scheduled-job.interface';
8
- import { IDashboardQuestionDataProvider, IDashboardVariableSelectionProvider, ISelectionProvider, ISelectionProviderContext } from "../interfaces";
8
+ import { IDashboardQuestionDataProvider, IDashboardVariableSelectionProvider, IErrorCodeProvider, ISelectionProvider, ISelectionProviderContext } from "../interfaces";
9
9
 
10
10
  type ControllerMetadata = {
11
11
  name: string;
@@ -67,6 +67,12 @@ export class SolidRegistry {
67
67
  private dashboardQuestionDataProviders: Set<InstanceWrapper> = new Set();
68
68
  private mailProviders: Set<InstanceWrapper> = new Set();
69
69
  private whatsappProviders: Set<InstanceWrapper> = new Set();
70
+ private errorCodeProviders: Set<InstanceWrapper> = new Set();
71
+
72
+
73
+ registerErrorCodeProvider(errorCodeProvider: InstanceWrapper): void {
74
+ this.errorCodeProviders.add(errorCodeProvider);
75
+ }
70
76
 
71
77
  registerWhatsappProvider(whatsappProvider: InstanceWrapper): void {
72
78
  this.whatsappProviders.add(whatsappProvider);
@@ -162,6 +168,19 @@ export class SolidRegistry {
162
168
  }
163
169
  }
164
170
 
171
+ getErrorCodeProviders(): Array<InstanceWrapper> {
172
+ return Array.from(this.errorCodeProviders);
173
+ }
174
+
175
+ getErrorCodeProviderInstance(name: string): IErrorCodeProvider | undefined {
176
+ const providers = this.getErrorCodeProviders();
177
+ for (let i = 0; i < providers.length; i++) {
178
+ const p = providers[i];
179
+ if (p.instance?.name?.() === name) return p.instance as IErrorCodeProvider;
180
+ }
181
+ return undefined;
182
+ }
183
+
165
184
  getDashboardQuestionDataProviders(): Array<InstanceWrapper> {
166
185
  return Array.from(this.dashboardQuestionDataProviders)
167
186
  }
package/src/index.ts CHANGED
@@ -19,6 +19,7 @@ export * from './decorators/protocol.decorator'
19
19
  export * from './decorators/public.decorator'
20
20
  export * from './decorators/roles.decorator'
21
21
  export * from './decorators/selection-provider.decorator'
22
+ export * from './decorators/error-codes-provider.decorator'
22
23
  export * from './decorators/solid-database-module.decorator'
23
24
  export * from './decorators/solid-service.decorator'
24
25
  export * from './decorators/mail-provider.decorator'
package/src/interfaces.ts CHANGED
@@ -238,3 +238,39 @@ export interface QueuesModuleOptions {
238
238
  export type MediaWithFullUrl = Media & {
239
239
  _full_url: string;
240
240
  };
241
+
242
+
243
+ export type ErrorCode = string;
244
+
245
+ export type ErrorMeta = {
246
+ message: string;
247
+ httpStatus?: number;
248
+ };
249
+
250
+ export type ErrorRule = {
251
+ /** Canonical error code. Keep them kebab-case for consistency. */
252
+ code: ErrorCode;
253
+ /** Higher runs earlier. Defaults to 0 if not provided. */
254
+ priority?: number;
255
+ /** Return true if this rule matches the combined error text. */
256
+ match: (combinedErrorText: string) => boolean;
257
+ /** Display + HTTP mapping for this code. */
258
+ meta: ErrorMeta;
259
+ };
260
+
261
+ export interface IErrorCodeProvider {
262
+ /** Used for registry identity & logs */
263
+ name(): string;
264
+
265
+ /**
266
+ * Return all rules this provider contributes.
267
+ * These will be merged with other providers’ rules, then sorted by priority.
268
+ */
269
+ rules(): ReadonlyArray<ErrorRule>;
270
+
271
+ /**
272
+ * Optional fallback meta for codes this provider owns (when called by getMessage/getHttpStatus).
273
+ * If omitted, the ErrorMapperService will rely on the rule.meta of the first matching rule.
274
+ */
275
+ resolve?(code: ErrorCode): ErrorMeta | undefined;
276
+ }
@@ -91,7 +91,7 @@ export class SecurityRuleRepository extends Repository<SecurityRule> {
91
91
  async upsertWithDto(createDto: CreateSecurityRuleDto) {
92
92
  // Populate the role from roleId or roleUserKey
93
93
  const roleRepository = this.dataSource.getRepository(RoleMetadata);
94
- if (!createDto.roleId) {
94
+ if (createDto.roleId) {
95
95
  const role = await roleRepository.findOne({
96
96
  where: {
97
97
  id: createDto.roleId,
@@ -111,7 +111,7 @@ export class SecurityRuleRepository extends Repository<SecurityRule> {
111
111
 
112
112
  // Populate the model from modelMetadataId or modelMetadataUserKey
113
113
  const modelMetadataRepository = this.dataSource.getRepository(ModelMetadata);
114
- if (!createDto.modelMetadataId) {
114
+ if (createDto.modelMetadataId) {
115
115
  const modelMetadata = await modelMetadataRepository.findOne({
116
116
  where: {
117
117
  id: createDto.modelMetadataId,
@@ -3058,8 +3058,8 @@
3058
3058
  "ormType": "varchar",
3059
3059
  "length": 128,
3060
3060
  "required": true,
3061
- "unique": true,
3062
- "index": true,
3061
+ "unique": false,
3062
+ "index": false,
3063
3063
  "private": false,
3064
3064
  "encrypt": false,
3065
3065
  "isSystem": true
@@ -196,7 +196,7 @@ export class AuthenticationService {
196
196
 
197
197
  private async populateForSignup<T extends User>(user: T, signUpDto: SignUpDto, isUserActive: boolean = true, onForcePasswordChange?: boolean) {
198
198
  // const user = new User();
199
-
199
+ let autoGeneratedPwdPermission = await this.settingService.getConfigValue('iamAutoGeneratedPassword');
200
200
  if (signUpDto.roles && signUpDto.roles.length > 0) {
201
201
  for (let i = 0; i < signUpDto.roles.length; i++) {
202
202
  const roleName = signUpDto.roles[i];
@@ -217,7 +217,7 @@ export class AuthenticationService {
217
217
  if (signUpDto.password) {
218
218
  pwd = await this.hashingService.hash(signUpDto.password);
219
219
  }
220
- else {
220
+ if(autoGeneratedPwdPermission?.toString().toLowerCase() === 'true'){
221
221
  // TODO: If password is not specified then auto-generate a random password, trigger this password over an email to the user.
222
222
  // TODO: Also track if the user has to force reset / change their password, and then activate the user.
223
223
  autoGeneratedPwd = this.generatePassword();
@@ -764,13 +764,13 @@ export class AuthenticationService {
764
764
  isValidUser = false
765
765
  // throw new NotFoundException(ERROR_MESSAGES.INVALID_CREDENTIALS);
766
766
  }
767
- if (!user.active) {
767
+ if (isValidUser && !user?.active) {
768
768
  isValidUser = false
769
769
  // throw new UnauthorizedException(ERROR_MESSAGES.INVALID_CREDENTIALS);
770
770
  }
771
771
 
772
772
  // 2. Validate if user has used a provider which is "local", only then it makes sense for us to initiate the forgot password routine.
773
- if (user.lastLoginProvider !== 'local') {
773
+ if (isValidUser && user?.lastLoginProvider !== 'local') {
774
774
  isValidUser = false
775
775
  // throw new BadRequestException(ERROR_MESSAGES.INVALID_CREDENTIALS);
776
776
  }
@@ -792,8 +792,8 @@ export class AuthenticationService {
792
792
  error: '',
793
793
  errorCode: '',
794
794
  data: {
795
- user: {
796
- email: user.email,
795
+ user: {
796
+ email: user?.email,
797
797
  // mobile: user.mobile,
798
798
  // username: user.username,
799
799
  },