@solidxai/core 0.1.16 → 0.1.17-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/CHANGELOG.md +1986 -1
  2. package/CLAUDE.md +26 -0
  3. package/dist/constants/media-file-types.d.ts +2 -2
  4. package/dist/constants/media-file-types.d.ts.map +1 -1
  5. package/dist/constants/media-file-types.js +24 -10
  6. package/dist/constants/media-file-types.js.map +1 -1
  7. package/dist/controllers/chatter-message-details.controller.d.ts.map +1 -1
  8. package/dist/controllers/chatter-message-details.controller.js +2 -6
  9. package/dist/controllers/chatter-message-details.controller.js.map +1 -1
  10. package/dist/controllers/chatter-message.controller.d.ts.map +1 -1
  11. package/dist/controllers/chatter-message.controller.js +3 -7
  12. package/dist/controllers/chatter-message.controller.js.map +1 -1
  13. package/dist/controllers/dashboard-user-layout.controller.d.ts.map +1 -1
  14. package/dist/controllers/dashboard-user-layout.controller.js +2 -6
  15. package/dist/controllers/dashboard-user-layout.controller.js.map +1 -1
  16. package/dist/controllers/import-transaction-error-log.controller.d.ts.map +1 -1
  17. package/dist/controllers/import-transaction-error-log.controller.js +2 -6
  18. package/dist/controllers/import-transaction-error-log.controller.js.map +1 -1
  19. package/dist/controllers/import-transaction.controller.d.ts.map +1 -1
  20. package/dist/controllers/import-transaction.controller.js +2 -6
  21. package/dist/controllers/import-transaction.controller.js.map +1 -1
  22. package/dist/controllers/list-of-values.controller.d.ts.map +1 -1
  23. package/dist/controllers/list-of-values.controller.js +2 -6
  24. package/dist/controllers/list-of-values.controller.js.map +1 -1
  25. package/dist/controllers/locale.controller.d.ts.map +1 -1
  26. package/dist/controllers/locale.controller.js +2 -6
  27. package/dist/controllers/locale.controller.js.map +1 -1
  28. package/dist/controllers/media.controller.d.ts.map +1 -1
  29. package/dist/controllers/media.controller.js +9 -8
  30. package/dist/controllers/media.controller.js.map +1 -1
  31. package/dist/controllers/model-sequence.controller.d.ts.map +1 -1
  32. package/dist/controllers/model-sequence.controller.js +2 -6
  33. package/dist/controllers/model-sequence.controller.js.map +1 -1
  34. package/dist/controllers/saved-filters.controller.d.ts.map +1 -1
  35. package/dist/controllers/saved-filters.controller.js +2 -6
  36. package/dist/controllers/saved-filters.controller.js.map +1 -1
  37. package/dist/controllers/scheduled-job.controller.d.ts.map +1 -1
  38. package/dist/controllers/scheduled-job.controller.js +2 -6
  39. package/dist/controllers/scheduled-job.controller.js.map +1 -1
  40. package/dist/controllers/security-rule.controller.d.ts.map +1 -1
  41. package/dist/controllers/security-rule.controller.js +2 -6
  42. package/dist/controllers/security-rule.controller.js.map +1 -1
  43. package/dist/controllers/user-activity-history.controller.d.ts.map +1 -1
  44. package/dist/controllers/user-activity-history.controller.js +2 -6
  45. package/dist/controllers/user-activity-history.controller.js.map +1 -1
  46. package/dist/controllers/user-view-metadata.controller.d.ts.map +1 -1
  47. package/dist/controllers/user-view-metadata.controller.js +2 -6
  48. package/dist/controllers/user-view-metadata.controller.js.map +1 -1
  49. package/dist/controllers/workflow-definition.controller.d.ts.map +1 -1
  50. package/dist/controllers/workflow-definition.controller.js +2 -6
  51. package/dist/controllers/workflow-definition.controller.js.map +1 -1
  52. package/dist/controllers/workflow-execution-artifact.controller.d.ts.map +1 -1
  53. package/dist/controllers/workflow-execution-artifact.controller.js +2 -6
  54. package/dist/controllers/workflow-execution-artifact.controller.js.map +1 -1
  55. package/dist/controllers/workflow-execution-log.controller.d.ts.map +1 -1
  56. package/dist/controllers/workflow-execution-log.controller.js +2 -6
  57. package/dist/controllers/workflow-execution-log.controller.js.map +1 -1
  58. package/dist/controllers/workflow-execution.controller.d.ts.map +1 -1
  59. package/dist/controllers/workflow-execution.controller.js +2 -6
  60. package/dist/controllers/workflow-execution.controller.js.map +1 -1
  61. package/dist/controllers/workflow-secret.controller.d.ts.map +1 -1
  62. package/dist/controllers/workflow-secret.controller.js +2 -6
  63. package/dist/controllers/workflow-secret.controller.js.map +1 -1
  64. package/dist/controllers/workflow-step-execution.controller.d.ts.map +1 -1
  65. package/dist/controllers/workflow-step-execution.controller.js +2 -6
  66. package/dist/controllers/workflow-step-execution.controller.js.map +1 -1
  67. package/dist/controllers/workflow-trigger-execution.controller.d.ts.map +1 -1
  68. package/dist/controllers/workflow-trigger-execution.controller.js +2 -6
  69. package/dist/controllers/workflow-trigger-execution.controller.js.map +1 -1
  70. package/dist/dtos/basic-filters.dto.d.ts +3 -5
  71. package/dist/dtos/basic-filters.dto.d.ts.map +1 -1
  72. package/dist/dtos/basic-filters.dto.js +6 -8
  73. package/dist/dtos/basic-filters.dto.js.map +1 -1
  74. package/dist/dtos/otp-sign-up.dto.d.ts +1 -0
  75. package/dist/dtos/otp-sign-up.dto.d.ts.map +1 -1
  76. package/dist/dtos/otp-sign-up.dto.js +12 -1
  77. package/dist/dtos/otp-sign-up.dto.js.map +1 -1
  78. package/dist/enums/show-soft-deleted.enum.d.ts +5 -0
  79. package/dist/enums/show-soft-deleted.enum.d.ts.map +1 -0
  80. package/dist/enums/show-soft-deleted.enum.js +9 -0
  81. package/dist/enums/show-soft-deleted.enum.js.map +1 -0
  82. package/dist/index.d.ts +1 -0
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +1 -0
  85. package/dist/index.js.map +1 -1
  86. package/dist/services/authentication.service.d.ts.map +1 -1
  87. package/dist/services/authentication.service.js +15 -6
  88. package/dist/services/authentication.service.js.map +1 -1
  89. package/dist/services/chatter-message.service.d.ts +2 -0
  90. package/dist/services/chatter-message.service.d.ts.map +1 -1
  91. package/dist/services/chatter-message.service.js +48 -21
  92. package/dist/services/chatter-message.service.js.map +1 -1
  93. package/dist/services/settings/default-settings-provider.service.d.ts +20 -0
  94. package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
  95. package/dist/services/settings/default-settings-provider.service.js +12 -0
  96. package/dist/services/settings/default-settings-provider.service.js.map +1 -1
  97. package/dist/solid-core.module.d.ts.map +1 -1
  98. package/dist/solid-core.module.js +5 -1
  99. package/dist/solid-core.module.js.map +1 -1
  100. package/package.json +1 -1
  101. package/postman/signup-intent-verification.postman_collection.json +573 -0
  102. package/src/constants/media-file-types.ts +76 -24
  103. package/src/controllers/chatter-message-details.controller.ts +1 -4
  104. package/src/controllers/chatter-message.controller.ts +1 -4
  105. package/src/controllers/dashboard-user-layout.controller.ts +1 -4
  106. package/src/controllers/import-transaction-error-log.controller.ts +1 -4
  107. package/src/controllers/import-transaction.controller.ts +1 -4
  108. package/src/controllers/list-of-values.controller.ts +1 -4
  109. package/src/controllers/locale.controller.ts +1 -4
  110. package/src/controllers/media.controller.ts +19 -7
  111. package/src/controllers/model-sequence.controller.ts +1 -4
  112. package/src/controllers/saved-filters.controller.ts +1 -4
  113. package/src/controllers/scheduled-job.controller.ts +1 -4
  114. package/src/controllers/security-rule.controller.ts +1 -4
  115. package/src/controllers/user-activity-history.controller.ts +1 -4
  116. package/src/controllers/user-view-metadata.controller.ts +1 -4
  117. package/src/controllers/workflow-definition.controller.ts +1 -4
  118. package/src/controllers/workflow-execution-artifact.controller.ts +1 -4
  119. package/src/controllers/workflow-execution-log.controller.ts +1 -4
  120. package/src/controllers/workflow-execution.controller.ts +1 -4
  121. package/src/controllers/workflow-secret.controller.ts +1 -4
  122. package/src/controllers/workflow-step-execution.controller.ts +1 -4
  123. package/src/controllers/workflow-trigger-execution.controller.ts +1 -4
  124. package/src/dtos/basic-filters.dto.ts +6 -7
  125. package/src/dtos/otp-sign-up.dto.ts +11 -1
  126. package/src/enums/show-soft-deleted.enum.ts +4 -0
  127. package/src/index.ts +1 -0
  128. package/src/services/authentication.service.ts +61 -26
  129. package/src/services/chatter-message.service.ts +54 -23
  130. package/src/services/settings/default-settings-provider.service.ts +12 -0
  131. package/src/solid-core.module.ts +12 -2
@@ -70,9 +70,18 @@ interface otp {
70
70
 
71
71
  /** Where a signup's role names come from. */
72
72
  enum RolesSource {
73
- /** None from the caller: performSignUp applies the configured `defaultRole`. */
73
+ /**
74
+ * Ask nobody; returning [] lets performSignUp apply the configured `defaultRole`.
75
+ *
76
+ * No intent maps here today - it is kept for paths that deliberately bypass the
77
+ * provider. OAuth is the live example: its DTO can never carry a discriminator, so
78
+ * it takes `defaultRole` outright (currently inline in UserService).
79
+ */
74
80
  Default,
75
- /** `provider.roles(dto)`, unconditionally. */
81
+ /**
82
+ * `provider.roles(dto)` - and nothing else. Yields [] when no provider is registered
83
+ * or when the provider declines to name any, which falls through to `defaultRole`.
84
+ */
76
85
  Provider,
77
86
  /** The caller's own `dto.roles`. */
78
87
  Caller,
@@ -82,10 +91,18 @@ enum RolesSource {
82
91
  * The single place that decides, per intent, which entity a signup builds and where
83
92
  * its roles come from.
84
93
  *
85
- * `useProvider` is currently computable from `rolesSource` (`Caller` implies false),
86
- * but the two answer different questions - "which entity?" and "which roles?" - that
87
- * merely coincide across these three intents. Keep both, and change behaviour here
88
- * rather than at a call site.
94
+ * Where an extension user provider is registered it is the authority on that app's user
95
+ * roles, so every provider-backed intent asks it. `defaultRole` is the *fallback* for
96
+ * when it names none - or when there is no provider at all - not the rule.
97
+ *
98
+ * SelfRegistration and ExtensionModel resolve identically today. They stay separate
99
+ * intents because only SelfRegistration is gated on `allowPublicRegistration`, and
100
+ * because "an anonymous visitor signing themselves up" and "an admin creating a user
101
+ * from the model's own form" are different things that may yet need to diverge.
102
+ *
103
+ * `useProvider` is computable from `rolesSource` today (`Caller` implies false), but the
104
+ * two answer different questions - "which entity?" and "which roles?" - that merely
105
+ * coincide across these intents. Keep both, and change behaviour here, not at a call site.
89
106
  */
90
107
  const SIGNUP_POLICY: Record<
91
108
  SignupIntent,
@@ -93,7 +110,7 @@ const SIGNUP_POLICY: Record<
93
110
  > = {
94
111
  [SignupIntent.SelfRegistration]: {
95
112
  useProvider: true,
96
- rolesSource: RolesSource.Default,
113
+ rolesSource: RolesSource.Provider,
97
114
  },
98
115
  [SignupIntent.ExtensionModel]: {
99
116
  useProvider: true,
@@ -246,12 +263,24 @@ export class AuthenticationService {
246
263
  dto: Record<string, any>,
247
264
  source: RolesSource,
248
265
  ): string[] {
266
+ // Only `Caller` reads dto.roles. Anywhere else, roles in the body are ignored -
267
+ // which is what keeps an anonymous caller from naming their own on a @Public()
268
+ // endpoint - so say so rather than dropping them silently.
269
+ if (source !== RolesSource.Caller && dto.roles?.length) {
270
+ this.logger.warn(
271
+ `Ignoring caller-supplied roles for "${dto.username}": roles on this path come from ` +
272
+ (source === RolesSource.Provider
273
+ ? "the extension user provider"
274
+ : "the configured defaultRole"),
275
+ );
276
+ }
277
+
249
278
  switch (source) {
250
279
  case RolesSource.Provider:
251
- // Sole authority. `dto.roles` is deliberately not read - not as a preference,
252
- // not as a fallback. Reading it would skip roles(), which is where a provider
253
- // validates its discriminator, and CreateUserDto types roles as
254
- // UpdateRoleMetadataDto[] where performSignUp expects role-name strings.
280
+ // Sole authority. Reading dto.roles as a preference or fallback would skip
281
+ // roles(), which is where a provider validates its discriminator, and
282
+ // CreateUserDto types roles as UpdateRoleMetadataDto[] where performSignUp
283
+ // expects role-name strings. [] here falls through to `defaultRole`.
255
284
  return (
256
285
  this.solidRegistry
257
286
  .getExtensionUserCreationProvider()
@@ -262,14 +291,6 @@ export class AuthenticationService {
262
291
  return dto.roles ?? [];
263
292
 
264
293
  case RolesSource.Default:
265
- // Public signup: the form has no business naming roles. Returning empty lets
266
- // performSignUp apply the configured `defaultRole`, rather than adding a
267
- // second mechanism for the same thing.
268
- if (dto.roles?.length) {
269
- this.logger.warn(
270
- `Ignoring caller-supplied roles on public registration for "${dto.username}"`,
271
- );
272
- }
273
294
  return [];
274
295
  }
275
296
  }
@@ -772,16 +793,31 @@ export class AuthenticationService {
772
793
  validationSource: string,
773
794
  ): Promise<User> {
774
795
  if (isEmpty(existingUser)) {
796
+ // Resolved before anything is written: where a provider requires its discriminator
797
+ // roles() throws, and that should surface as a rejected request rather than as a
798
+ // half-registered user with no roles.
799
+ const roles = this.resolveSignupRoles(
800
+ signUpDto,
801
+ SIGNUP_POLICY[SignupIntent.SelfRegistration].rolesSource,
802
+ );
803
+
775
804
  // A new registration is saved through whichever repository createUser resolved,
776
805
  // which is the provider's when the app registers one.
777
806
  const { entity: user, repo } = await this.createUser(signUpDto);
778
807
  user.active = false; // User will be activated only after OTP verification, hence setting active to false for new user.
779
808
  await this.assignRegistrationOtp(validationSource, user);
780
809
  await repo.save(user);
781
- await this.userService.addRoleToUser(
782
- user.username,
783
- this.settingService.getConfigValue<SolidCoreSetting>("defaultRole"),
784
- );
810
+
811
+ if (roles.length) {
812
+ await this.userService.addRolesToUser(user.username, roles);
813
+ } else {
814
+ // The provider named none, or there is no provider: fall back to the configured
815
+ // default, matching what performSignUp does on the password paths.
816
+ await this.userService.addRoleToUser(
817
+ user.username,
818
+ this.settingService.getConfigValue<SolidCoreSetting>("defaultRole"),
819
+ );
820
+ }
785
821
  return user;
786
822
  }
787
823
 
@@ -812,9 +848,8 @@ export class AuthenticationService {
812
848
  entity.username = signUpDto.username;
813
849
  entity.email = signUpDto.email;
814
850
  entity.mobile = signUpDto.mobile;
815
- // OTPSignUpDto declares no `fullName`, so username is the only source here. Mirrors
816
- // the fallback in populateForSignup, which this path does not go through.
817
- entity.fullName = signUpDto.username;
851
+ // OTPSignUpDto declares no `fullName`, so username is the only source here. Mirrors the fallback in populateForSignup, which this path does not go through.
852
+ entity.fullName = signUpDto.fullName?.trim() || signUpDto.username;
818
853
  entity.customPayload = signUpDto.customPayload;
819
854
  entity.lastLoginProvider = LoginProvider.OTP;
820
855
 
@@ -771,38 +771,69 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
771
771
 
772
772
  if (field.type === 'relation') {
773
773
  if (field.relationType === "many-to-one") {
774
- if (value.name) {
775
- return value.name;
776
- }
777
-
778
- try {
779
- const relatedModel = await this.modelMetadataRepo.findOne({
780
- where: { singularName: field.relationCoModelSingularName || field.relation },
781
- relations: { userKeyField: true }
782
- });
783
-
784
- if (relatedModel && relatedModel.userKeyField) {
785
- const userKeyFieldName = relatedModel.userKeyField.name;
786
- return value[userKeyFieldName] ? value[userKeyFieldName].toString() : '';
787
- }
788
-
789
- if (value.id) {
790
- return value.id.toString();
791
- }
792
- } catch (error: any) {
793
- console.error('Error fetching related model metadata:', error);
794
- return value.id ? value.id.toString() : '';
795
- }
774
+ const relatedModel = await this.loadRelatedModelForDisplay(field);
775
+ return this.resolveRelationDisplayValue(value, relatedModel?.userKeyField?.name);
796
776
  }
797
777
 
798
778
  if (field.relationType === 'many-to-many') {
799
- return value.map(item => item.name).join(', ');
779
+ if (!Array.isArray(value)) {
780
+ const relatedModel = await this.loadRelatedModelForDisplay(field);
781
+ return this.resolveRelationDisplayValue(value, relatedModel?.userKeyField?.name);
782
+ }
783
+
784
+ const relatedModel = await this.loadRelatedModelForDisplay(field);
785
+ const userKeyFieldName = relatedModel?.userKeyField?.name;
786
+ return value
787
+ .map(item => this.resolveRelationDisplayValue(item, userKeyFieldName))
788
+ .filter(Boolean)
789
+ .join(', ');
800
790
  }
801
791
  }
802
792
 
803
793
  return value.toString();
804
794
  }
805
795
 
796
+ private async loadRelatedModelForDisplay(field: any): Promise<any | null> {
797
+ try {
798
+ return await this.modelMetadataRepo.findOne({
799
+ where: { singularName: field.relationCoModelSingularName || field.relation },
800
+ relations: { userKeyField: true }
801
+ });
802
+ } catch (error: any) {
803
+ this._logger.warn(`Unable to load related model metadata for audit display: ${error?.message ?? error}`);
804
+ return null;
805
+ }
806
+ }
807
+
808
+ private resolveRelationDisplayValue(value: any, userKeyFieldName?: string): string {
809
+ if (value === null || value === undefined) {
810
+ return '';
811
+ }
812
+
813
+ if (typeof value !== 'object') {
814
+ return String(value);
815
+ }
816
+
817
+ const candidateKeys = [
818
+ userKeyFieldName,
819
+ 'fullName',
820
+ 'displayName',
821
+ 'username',
822
+ 'name',
823
+ 'title',
824
+ 'id',
825
+ ].filter(Boolean) as string[];
826
+
827
+ for (const key of candidateKeys) {
828
+ const candidate = value[key];
829
+ if (candidate !== null && candidate !== undefined && String(candidate).trim() !== '') {
830
+ return String(candidate);
831
+ }
832
+ }
833
+
834
+ return '';
835
+ }
836
+
806
837
  private hasValueChanged(newValue: any, oldValue: any): boolean {
807
838
  if (newValue === oldValue) {
808
839
  return false;
@@ -7,6 +7,7 @@ import {
7
7
  SettingLevel,
8
8
  } from "src/interfaces";
9
9
  import { getDefaultThemeKey, getThemesByMode } from "src/theme/theme-registry";
10
+ import { DANGEROUS_EXTENSIONS } from "src/constants/media-file-types";
10
11
 
11
12
  export const DEFAULT_MEDIA_UPLOAD_DIR = "media-uploads";
12
13
  export const DEFAULT_MEDIA_FILE_STORAGE_DIR = "media-files-storage";
@@ -546,6 +547,17 @@ const getSolidCoreSettings = (isProd: boolean) =>
546
547
  controlType: "numeric",
547
548
  helpText: "Global upload size limit enforced by Multer under every per-field mediaMaxSizeKb restriction. Read-only here: it's applied once at process start, so changing AB_MEDIA_MAX_FILE_SIZE_MB requires an app restart to take effect - editing this in the admin UI would not do that.",
548
549
  },
550
+ {
551
+ moduleName: "solid-core",
552
+ key: "dangerousExtensions",
553
+ value: [...DANGEROUS_EXTENSIONS].join(','),
554
+ level: SettingLevel.SystemAdminReadonly,
555
+ label: "Blocked Upload Extensions",
556
+ group: "storage-settings",
557
+ sortOrder: 46,
558
+ controlType: "shortText",
559
+ helpText: "File extensions rejected on every upload path, regardless of field configuration. Read-only here: it's applied once at process start. To change it, copy this value into AB_DANGEROUS_EXTENSIONS, edit it, and restart - note that variable REPLACES this list rather than adding to it, so any extension you leave out becomes an accepted upload type.",
560
+ },
549
561
 
550
562
  // aws-s3-settings-provider.service.ts
551
563
  {
@@ -541,17 +541,27 @@ import { SwitchNode } from './services/workflow/nodes/switch.node';
541
541
  res.setHeader("X-Content-Type-Options", "nosniff");
542
542
 
543
543
  // Only a small allowlist of media types is ever safe to render inline. Everything
544
- // else (including svg, html, and any other uploaded file) is forced to download
544
+ // else (including html and any other uploaded file) is forced to download
545
545
  // rather than be displayed/executed by the browser, regardless of what mimetype or
546
546
  // extension it was uploaded with.
547
547
  // basename first: getLowercaseFileExtension takes a file name, not a path, so a
548
548
  // directory containing a dot must not be mistaken for the extension. An
549
549
  // extensionless file yields undefined, which is not inline-safe - so it correctly
550
550
  // falls through to the forced-download branch.
551
- if (!INLINE_SAFE_EXTENSIONS.has(getLowercaseFileExtension(basename(path)) ?? '')) {
551
+ const ext = getLowercaseFileExtension(basename(path)) ?? '';
552
+ if (!INLINE_SAFE_EXTENSIONS.has(ext)) {
552
553
  res.setHeader("Content-Type", "application/octet-stream");
553
554
  res.setHeader("Content-Disposition", "attachment");
554
555
  }
556
+
557
+ // svg is only ever inline-safe (see INLINE_SAFE_EXTENSIONS) once it can also render
558
+ // as an active document (<script>, event handlers). <img>/CSS never execute it, but
559
+ // a direct navigation to this URL would - so pin it to a locked-down document: no
560
+ // script, no network, and `sandbox` gives it an opaque origin so even a parser bypass
561
+ // can't reach app cookies or storage.
562
+ if (ext === 'svg') {
563
+ res.setHeader("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; img-src data:; sandbox");
564
+ }
555
565
  },
556
566
  },
557
567
  }),