@solidxai/core 0.1.13-beta.9 → 0.1.14-beta.0

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 (276) hide show
  1. package/CHANGELOG.md +9244 -0
  2. package/README.md +19 -0
  3. package/dist/commands/run-tests.command.d.ts +2 -0
  4. package/dist/commands/run-tests.command.d.ts.map +1 -1
  5. package/dist/commands/run-tests.command.js +20 -3
  6. package/dist/commands/run-tests.command.js.map +1 -1
  7. package/dist/config/cache.options.d.ts.map +1 -1
  8. package/dist/config/cache.options.js +2 -2
  9. package/dist/config/cache.options.js.map +1 -1
  10. package/dist/constants/error-messages.d.ts +7 -0
  11. package/dist/constants/error-messages.d.ts.map +1 -1
  12. package/dist/constants/error-messages.js +7 -0
  13. package/dist/constants/error-messages.js.map +1 -1
  14. package/dist/constants/media-file-types.d.ts +17 -0
  15. package/dist/constants/media-file-types.d.ts.map +1 -1
  16. package/dist/constants/media-file-types.js +129 -22
  17. package/dist/constants/media-file-types.js.map +1 -1
  18. package/dist/constants/success-messages.d.ts +3 -0
  19. package/dist/constants/success-messages.d.ts.map +1 -1
  20. package/dist/constants/success-messages.js +3 -0
  21. package/dist/constants/success-messages.js.map +1 -1
  22. package/dist/controllers/authentication.controller.d.ts +1 -1
  23. package/dist/controllers/authentication.controller.d.ts.map +1 -1
  24. package/dist/controllers/authentication.controller.js +4 -3
  25. package/dist/controllers/authentication.controller.js.map +1 -1
  26. package/dist/controllers/field-metadata.controller.d.ts +6 -2
  27. package/dist/controllers/field-metadata.controller.d.ts.map +1 -1
  28. package/dist/controllers/mpin-authentication.controller.d.ts +44 -0
  29. package/dist/controllers/mpin-authentication.controller.d.ts.map +1 -0
  30. package/dist/controllers/mpin-authentication.controller.js +102 -0
  31. package/dist/controllers/mpin-authentication.controller.js.map +1 -0
  32. package/dist/controllers/view-metadata.controller.d.ts +4 -0
  33. package/dist/controllers/view-metadata.controller.d.ts.map +1 -1
  34. package/dist/dtos/change-mpin.dto.d.ts +6 -0
  35. package/dist/dtos/change-mpin.dto.d.ts.map +1 -0
  36. package/dist/dtos/change-mpin.dto.js +39 -0
  37. package/dist/dtos/change-mpin.dto.js.map +1 -0
  38. package/dist/dtos/create-field-metadata.dto.d.ts +3 -6
  39. package/dist/dtos/create-field-metadata.dto.d.ts.map +1 -1
  40. package/dist/dtos/create-field-metadata.dto.js +15 -10
  41. package/dist/dtos/create-field-metadata.dto.js.map +1 -1
  42. package/dist/dtos/create-model-sequence.dto.d.ts +1 -1
  43. package/dist/dtos/create-model-sequence.dto.d.ts.map +1 -1
  44. package/dist/dtos/create-model-sequence.dto.js +1 -1
  45. package/dist/dtos/create-model-sequence.dto.js.map +1 -1
  46. package/dist/dtos/mpin-login.dto.d.ts +5 -0
  47. package/dist/dtos/mpin-login.dto.d.ts.map +1 -0
  48. package/dist/dtos/mpin-login.dto.js +33 -0
  49. package/dist/dtos/mpin-login.dto.js.map +1 -0
  50. package/dist/dtos/setup-mpin.dto.d.ts +7 -0
  51. package/dist/dtos/setup-mpin.dto.d.ts.map +1 -0
  52. package/dist/dtos/setup-mpin.dto.js +47 -0
  53. package/dist/dtos/setup-mpin.dto.js.map +1 -0
  54. package/dist/entities/dashboard-user-layout.entity.js +2 -2
  55. package/dist/entities/dashboard-user-layout.entity.js.map +1 -1
  56. package/dist/entities/field-metadata.entity.d.ts +1 -0
  57. package/dist/entities/field-metadata.entity.d.ts.map +1 -1
  58. package/dist/entities/field-metadata.entity.js +5 -1
  59. package/dist/entities/field-metadata.entity.js.map +1 -1
  60. package/dist/entities/user-device-credential.entity.d.ts +18 -0
  61. package/dist/entities/user-device-credential.entity.d.ts.map +1 -0
  62. package/dist/entities/user-device-credential.entity.js +91 -0
  63. package/dist/entities/user-device-credential.entity.js.map +1 -0
  64. package/dist/entities/user.entity.js +2 -2
  65. package/dist/entities/user.entity.js.map +1 -1
  66. package/dist/entities/view-metadata.entity.js.map +1 -1
  67. package/dist/entities/workflow-execution-artifact.entity.d.ts.map +1 -1
  68. package/dist/entities/workflow-execution-artifact.entity.js +4 -3
  69. package/dist/entities/workflow-execution-artifact.entity.js.map +1 -1
  70. package/dist/entities/workflow-execution-log.entity.d.ts.map +1 -1
  71. package/dist/entities/workflow-execution-log.entity.js +4 -3
  72. package/dist/entities/workflow-execution-log.entity.js.map +1 -1
  73. package/dist/entities/workflow-execution.entity.d.ts.map +1 -1
  74. package/dist/entities/workflow-execution.entity.js +3 -2
  75. package/dist/entities/workflow-execution.entity.js.map +1 -1
  76. package/dist/entities/workflow-secret.entity.d.ts.map +1 -1
  77. package/dist/entities/workflow-secret.entity.js +3 -2
  78. package/dist/entities/workflow-secret.entity.js.map +1 -1
  79. package/dist/entities/workflow-step-execution.entity.d.ts.map +1 -1
  80. package/dist/entities/workflow-step-execution.entity.js +3 -2
  81. package/dist/entities/workflow-step-execution.entity.js.map +1 -1
  82. package/dist/entities/workflow-trigger-execution.entity.d.ts.map +1 -1
  83. package/dist/entities/workflow-trigger-execution.entity.js +6 -5
  84. package/dist/entities/workflow-trigger-execution.entity.js.map +1 -1
  85. package/dist/filters/http-exception.filter.d.ts.map +1 -1
  86. package/dist/filters/http-exception.filter.js +5 -1
  87. package/dist/filters/http-exception.filter.js.map +1 -1
  88. package/dist/helpers/bootstrap.helper.d.ts +5 -2
  89. package/dist/helpers/bootstrap.helper.d.ts.map +1 -1
  90. package/dist/helpers/bootstrap.helper.js +7 -3
  91. package/dist/helpers/bootstrap.helper.js.map +1 -1
  92. package/dist/helpers/field-crud-managers/LongTextFieldCrudManager.d.ts.map +1 -1
  93. package/dist/helpers/field-crud-managers/LongTextFieldCrudManager.js +3 -2
  94. package/dist/helpers/field-crud-managers/LongTextFieldCrudManager.js.map +1 -1
  95. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts +8 -0
  96. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts.map +1 -1
  97. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js +61 -76
  98. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js.map +1 -1
  99. package/dist/helpers/field-crud-managers/ShortTextFieldCrudManager.d.ts.map +1 -1
  100. package/dist/helpers/field-crud-managers/ShortTextFieldCrudManager.js +3 -2
  101. package/dist/helpers/field-crud-managers/ShortTextFieldCrudManager.js.map +1 -1
  102. package/dist/helpers/field-crud-managers/textFieldCrudManagerUtils.d.ts +3 -0
  103. package/dist/helpers/field-crud-managers/textFieldCrudManagerUtils.d.ts.map +1 -0
  104. package/dist/helpers/field-crud-managers/textFieldCrudManagerUtils.js +17 -0
  105. package/dist/helpers/field-crud-managers/textFieldCrudManagerUtils.js.map +1 -0
  106. package/dist/helpers/file-download.helper.d.ts.map +1 -1
  107. package/dist/helpers/file-download.helper.js +6 -1
  108. package/dist/helpers/file-download.helper.js.map +1 -1
  109. package/dist/helpers/security.helper.d.ts +5 -1
  110. package/dist/helpers/security.helper.d.ts.map +1 -1
  111. package/dist/helpers/security.helper.js +15 -4
  112. package/dist/helpers/security.helper.js.map +1 -1
  113. package/dist/helpers/solid-core-error-codes-provider.service.d.ts.map +1 -1
  114. package/dist/helpers/solid-core-error-codes-provider.service.js +45 -0
  115. package/dist/helpers/solid-core-error-codes-provider.service.js.map +1 -1
  116. package/dist/helpers/solid-microservice-adapter.service.d.ts.map +1 -1
  117. package/dist/helpers/solid-microservice-adapter.service.js +1 -1
  118. package/dist/helpers/solid-microservice-adapter.service.js.map +1 -1
  119. package/dist/helpers/typeorm-db-helper.d.ts.map +1 -1
  120. package/dist/helpers/typeorm-db-helper.js +9 -0
  121. package/dist/helpers/typeorm-db-helper.js.map +1 -1
  122. package/dist/index.d.ts +6 -0
  123. package/dist/index.d.ts.map +1 -1
  124. package/dist/index.js +6 -0
  125. package/dist/index.js.map +1 -1
  126. package/dist/interfaces/active-user-data.interface.d.ts +1 -0
  127. package/dist/interfaces/active-user-data.interface.d.ts.map +1 -1
  128. package/dist/interfaces/active-user-data.interface.js.map +1 -1
  129. package/dist/repository/user-device-credential.repository.d.ts +12 -0
  130. package/dist/repository/user-device-credential.repository.d.ts.map +1 -0
  131. package/dist/repository/user-device-credential.repository.js +34 -0
  132. package/dist/repository/user-device-credential.repository.js.map +1 -0
  133. package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
  134. package/dist/seeders/module-metadata-seeder.service.js +7 -3
  135. package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
  136. package/dist/seeders/seed-data/solid-core-metadata.json +168 -2
  137. package/dist/services/api-key.service.d.ts.map +1 -1
  138. package/dist/services/api-key.service.js +1 -1
  139. package/dist/services/api-key.service.js.map +1 -1
  140. package/dist/services/authentication.service.d.ts +5 -4
  141. package/dist/services/authentication.service.d.ts.map +1 -1
  142. package/dist/services/authentication.service.js +50 -19
  143. package/dist/services/authentication.service.js.map +1 -1
  144. package/dist/services/chatter-message.service.d.ts +5 -1
  145. package/dist/services/chatter-message.service.d.ts.map +1 -1
  146. package/dist/services/chatter-message.service.js +48 -13
  147. package/dist/services/chatter-message.service.js.map +1 -1
  148. package/dist/services/crud-helper.service.d.ts +1 -2
  149. package/dist/services/crud-helper.service.d.ts.map +1 -1
  150. package/dist/services/crud-helper.service.js +2 -16
  151. package/dist/services/crud-helper.service.js.map +1 -1
  152. package/dist/services/crud.service.d.ts.map +1 -1
  153. package/dist/services/crud.service.js +4 -2
  154. package/dist/services/crud.service.js.map +1 -1
  155. package/dist/services/draft-publish-helper.service.d.ts +1 -0
  156. package/dist/services/draft-publish-helper.service.d.ts.map +1 -1
  157. package/dist/services/draft-publish-helper.service.js +38 -4
  158. package/dist/services/draft-publish-helper.service.js.map +1 -1
  159. package/dist/services/field-metadata.service.d.ts +6 -2
  160. package/dist/services/field-metadata.service.d.ts.map +1 -1
  161. package/dist/services/field-metadata.service.js +5 -1
  162. package/dist/services/field-metadata.service.js.map +1 -1
  163. package/dist/services/media-storage.utils.d.ts +1 -0
  164. package/dist/services/media-storage.utils.d.ts.map +1 -1
  165. package/dist/services/media-storage.utils.js +12 -1
  166. package/dist/services/media-storage.utils.js.map +1 -1
  167. package/dist/services/media.service.d.ts.map +1 -1
  168. package/dist/services/media.service.js +1 -0
  169. package/dist/services/media.service.js.map +1 -1
  170. package/dist/services/module-metadata.service.d.ts.map +1 -1
  171. package/dist/services/module-metadata.service.js +9 -1
  172. package/dist/services/module-metadata.service.js.map +1 -1
  173. package/dist/services/mpin.service.d.ts +67 -0
  174. package/dist/services/mpin.service.d.ts.map +1 -0
  175. package/dist/services/mpin.service.js +282 -0
  176. package/dist/services/mpin.service.js.map +1 -0
  177. package/dist/services/refresh-token-ids-storage.service.d.ts +25 -6
  178. package/dist/services/refresh-token-ids-storage.service.d.ts.map +1 -1
  179. package/dist/services/refresh-token-ids-storage.service.js +80 -42
  180. package/dist/services/refresh-token-ids-storage.service.js.map +1 -1
  181. package/dist/services/setting.service.d.ts.map +1 -1
  182. package/dist/services/setting.service.js +6 -1
  183. package/dist/services/setting.service.js.map +1 -1
  184. package/dist/services/settings/default-settings-provider.service.d.ts +202 -6
  185. package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
  186. package/dist/services/settings/default-settings-provider.service.js +114 -3
  187. package/dist/services/settings/default-settings-provider.service.js.map +1 -1
  188. package/dist/services/view-metadata.service.d.ts +4 -0
  189. package/dist/services/view-metadata.service.d.ts.map +1 -1
  190. package/dist/services/view-metadata.service.js +2 -0
  191. package/dist/services/view-metadata.service.js.map +1 -1
  192. package/dist/solid-core.module.d.ts +1 -3
  193. package/dist/solid-core.module.d.ts.map +1 -1
  194. package/dist/solid-core.module.js +14 -39
  195. package/dist/solid-core.module.js.map +1 -1
  196. package/dist/testing/reporter/console-reporter.d.ts +3 -0
  197. package/dist/testing/reporter/console-reporter.d.ts.map +1 -1
  198. package/dist/testing/reporter/console-reporter.js +2 -0
  199. package/dist/testing/reporter/console-reporter.js.map +1 -1
  200. package/dist/testing/reporter/progress-reporter.d.ts +64 -0
  201. package/dist/testing/reporter/progress-reporter.d.ts.map +1 -0
  202. package/dist/testing/reporter/progress-reporter.js +224 -0
  203. package/dist/testing/reporter/progress-reporter.js.map +1 -0
  204. package/dist/testing/reporter/reporter.types.d.ts +3 -0
  205. package/dist/testing/reporter/reporter.types.d.ts.map +1 -1
  206. package/dist/testing/reporter/reporter.types.js.map +1 -1
  207. package/dist/testing/runner/run-from-metadata.d.ts.map +1 -1
  208. package/dist/testing/runner/run-from-metadata.js +1 -0
  209. package/dist/testing/runner/run-from-metadata.js.map +1 -1
  210. package/dist/theme/theme-registry.d.ts +14 -0
  211. package/dist/theme/theme-registry.d.ts.map +1 -0
  212. package/dist/theme/theme-registry.js +147 -0
  213. package/dist/theme/theme-registry.js.map +1 -0
  214. package/package.json +2 -2
  215. package/src/commands/run-tests.command.ts +17 -3
  216. package/src/config/cache.options.ts +9 -1
  217. package/src/constants/error-messages.ts +14 -0
  218. package/src/constants/media-file-types.ts +182 -19
  219. package/src/constants/success-messages.ts +5 -0
  220. package/src/controllers/authentication.controller.ts +5 -2
  221. package/src/controllers/mpin-authentication.controller.ts +67 -0
  222. package/src/dtos/change-mpin.dto.ts +18 -0
  223. package/src/dtos/create-field-metadata.dto.ts +17 -9
  224. package/src/dtos/create-model-sequence.dto.ts +1 -1
  225. package/src/dtos/mpin-login.dto.ts +24 -0
  226. package/src/dtos/setup-mpin.dto.ts +51 -0
  227. package/src/entities/dashboard-user-layout.entity.ts +2 -2
  228. package/src/entities/field-metadata.entity.ts +3 -0
  229. package/src/entities/user-device-credential.entity.ts +130 -0
  230. package/src/entities/user.entity.ts +2 -2
  231. package/src/entities/view-metadata.entity.ts +2 -2
  232. package/src/entities/workflow-execution-artifact.entity.ts +4 -3
  233. package/src/entities/workflow-execution-log.entity.ts +4 -3
  234. package/src/entities/workflow-execution.entity.ts +3 -2
  235. package/src/entities/workflow-secret.entity.ts +3 -2
  236. package/src/entities/workflow-step-execution.entity.ts +3 -2
  237. package/src/entities/workflow-trigger-execution.entity.ts +6 -5
  238. package/src/filters/http-exception.filter.ts +11 -1
  239. package/src/helpers/bootstrap.helper.ts +43 -6
  240. package/src/helpers/field-crud-managers/LongTextFieldCrudManager.ts +3 -2
  241. package/src/helpers/field-crud-managers/MediaFieldCrudManager.ts +83 -96
  242. package/src/helpers/field-crud-managers/ShortTextFieldCrudManager.ts +3 -2
  243. package/src/helpers/field-crud-managers/textFieldCrudManagerUtils.ts +14 -0
  244. package/src/helpers/file-download.helper.ts +15 -1
  245. package/src/helpers/security.helper.ts +35 -8
  246. package/src/helpers/solid-core-error-codes-provider.service.ts +53 -0
  247. package/src/helpers/solid-microservice-adapter.service.ts +6 -1
  248. package/src/helpers/typeorm-db-helper.ts +12 -1
  249. package/src/index.ts +6 -0
  250. package/src/interfaces/active-user-data.interface.ts +9 -0
  251. package/src/repository/user-device-credential.repository.ts +17 -0
  252. package/src/seeders/module-metadata-seeder.service.ts +11 -3
  253. package/src/seeders/seed-data/solid-core-metadata.json +168 -2
  254. package/src/services/api-key.service.ts +13 -1
  255. package/src/services/authentication.service.ts +127 -36
  256. package/src/services/chatter-message.service.ts +78 -15
  257. package/src/services/crud-helper.service.ts +19 -16
  258. package/src/services/crud.service.ts +5 -2
  259. package/src/services/draft-publish-helper.service.ts +56 -4
  260. package/src/services/field-metadata.service.ts +6 -2
  261. package/src/services/media-storage.utils.ts +26 -1
  262. package/src/services/media.service.ts +1 -0
  263. package/src/services/module-metadata.service.ts +10 -1
  264. package/src/services/mpin.service.ts +447 -0
  265. package/src/services/refresh-token-ids-storage.service.ts +229 -90
  266. package/src/services/setting.service.ts +8 -1
  267. package/src/services/settings/default-settings-provider.service.ts +119 -3
  268. package/src/services/view-metadata.service.ts +4 -0
  269. package/src/solid-core.module.ts +26 -12
  270. package/src/testing/README.md +2 -1
  271. package/src/testing/reporter/console-reporter.ts +4 -0
  272. package/src/testing/reporter/progress-reporter.ts +251 -0
  273. package/src/testing/reporter/reporter.types.ts +3 -0
  274. package/src/testing/runner/run-from-metadata.ts +1 -0
  275. package/src/theme/theme-registry.ts +181 -0
  276. package/CLAUDE.md +0 -26
@@ -1,5 +1,6 @@
1
1
  import { isEmpty, isNotEmpty, isString, length, matches } from "class-validator";
2
2
  import { FieldCrudManager, ValidationError } from "src/interfaces";
3
+ import { normalizeTextFieldInDto, normalizeTextFieldValue } from "./textFieldCrudManagerUtils";
3
4
 
4
5
  export interface ShortTextFieldOptions {
5
6
  length: number | undefined | null;
@@ -16,7 +17,7 @@ export class ShortTextFieldCrudManager implements FieldCrudManager {
16
17
  }
17
18
 
18
19
  validate(dto: any, _files:Array<Express.Multer.File>): ValidationError[] {
19
- const fieldValue: any = dto[this.options.fieldName];
20
+ const fieldValue: any = normalizeTextFieldValue(dto[this.options.fieldName]);
20
21
  return this.applyValidations(fieldValue);
21
22
  }
22
23
 
@@ -39,7 +40,7 @@ export class ShortTextFieldCrudManager implements FieldCrudManager {
39
40
  }
40
41
 
41
42
  transformForCreate(dto: any): any {
42
- return dto;
43
+ return normalizeTextFieldInDto(dto, this.options.fieldName);
43
44
  }
44
45
 
45
46
  // Validation to be applied
@@ -0,0 +1,14 @@
1
+ import { isString } from "class-validator";
2
+
3
+ export const normalizeTextFieldValue = (fieldValue: any): any => {
4
+ return isString(fieldValue) ? fieldValue.trim() : fieldValue;
5
+ };
6
+
7
+ export const normalizeTextFieldInDto = (dto: any, fieldName?: string | null): any => {
8
+ if (!fieldName || !Object.prototype.hasOwnProperty.call(dto, fieldName)) {
9
+ return dto;
10
+ }
11
+
12
+ dto[fieldName] = normalizeTextFieldValue(dto[fieldName]);
13
+ return dto;
14
+ };
@@ -9,6 +9,20 @@ export interface FileDownloadHeaderOptions {
9
9
  crossOriginResourcePolicy?: boolean;
10
10
  }
11
11
 
12
+ /**
13
+ * Builds an RFC 6266 Content-Disposition value. The file name is attacker-controlled (it comes
14
+ * from the uploaded originalFileName), so it can't be interpolated raw: a quote would close the
15
+ * quoted-string early and let the rest of the name forge further header parameters. The ASCII
16
+ * fallback is stripped to a safe subset and the real name is carried in the RFC 5987
17
+ * `filename*` parameter, which is also what preserves non-ASCII names correctly.
18
+ */
19
+ function buildContentDisposition(disposition: "inline" | "attachment", fileName: string): string {
20
+ const name = (fileName || "file").replace(/[\r\n]/g, "");
21
+ const asciiFallback = name.replace(/[^\x20-\x7E]/g, "_").replace(/["\\]/g, "_") || "file";
22
+
23
+ return `${disposition}; filename="${asciiFallback}"; filename*=UTF-8''${encodeURIComponent(name)}`;
24
+ }
25
+
12
26
  /**
13
27
  * Single place that stamps the response headers for every streamed file download, so the
14
28
  * security headers can't be applied to one endpoint and forgotten on the next.
@@ -16,7 +30,7 @@ export interface FileDownloadHeaderOptions {
16
30
  export function setFileDownloadHeaders(res: Response, options: FileDownloadHeaderOptions): void {
17
31
  const disposition = options.disposition ?? "attachment";
18
32
 
19
- res.setHeader("Content-Disposition", `${disposition}; filename="${options.fileName}"`);
33
+ res.setHeader("Content-Disposition", buildContentDisposition(disposition, options.fileName));
20
34
  res.setHeader("Content-Type", options.mimeType);
21
35
  res.setHeader("Access-Control-Expose-Headers", "Content-Disposition, Content-Type");
22
36
 
@@ -1,15 +1,39 @@
1
1
  import { HelmetOptions } from "helmet";
2
2
  import { Environment } from "src/decorators/disallow-in-production.decorator";
3
3
 
4
+ export interface SolidSecurityOptions {
5
+ /**
6
+ * Origins that may embed this application in an iframe.
7
+ * Defaults to the local HTTP/HTTPS loopback origins on any port.
8
+ */
9
+ frameAncestors?: string[];
10
+ }
11
+
12
+ export const DEFAULT_FRAME_ANCESTORS = [
13
+ "'self'",
14
+ "http://localhost:*",
15
+ "https://localhost:*",
16
+ "http://127.0.0.1:*",
17
+ "https://127.0.0.1:*",
18
+ "http://[::1]:*",
19
+ "https://[::1]:*",
20
+ ];
21
+
4
22
  /**
5
23
  * Default security headers for SolidX apps.
6
24
  * - HSTS only in prod over HTTPS
7
- * - CSP with frame-ancestors 'none' (prevents clickjacking)
8
- * - X-Frame-Options: DENY (legacy fallback)
25
+ * - CSP with configurable frame-ancestors
26
+ * - X-Frame-Options disabled when cross-origin frame ancestors are allowed
9
27
  * - No X-XSS-Protection (deprecated)
10
28
  */
11
- export function buildDefaultSecurityHeaderOptions(): Readonly<HelmetOptions> {
29
+ export function buildDefaultSecurityHeaderOptions(
30
+ options: SolidSecurityOptions = {},
31
+ ): Readonly<HelmetOptions> {
12
32
  const isProd = process.env.ENV === Environment.Production;
33
+ const frameAncestors = options.frameAncestors ?? DEFAULT_FRAME_ANCESTORS;
34
+ const allowsCrossOriginFrames = frameAncestors.some(
35
+ (source) => source !== "'self'" && source !== "'none'",
36
+ );
13
37
 
14
38
  return {
15
39
  // Modern CSP. Add more directives as your app needs (script-src, connect-src, etc.)
@@ -23,7 +47,7 @@ export function buildDefaultSecurityHeaderOptions(): Readonly<HelmetOptions> {
23
47
  // "form-action": ["'self'"],
24
48
 
25
49
  // clickjacking defense (modern)
26
- "frame-ancestors": ["'none'"],
50
+ "frame-ancestors": frameAncestors,
27
51
  "style-src": ["'self'"],
28
52
  // add/adjust as needed for your app:
29
53
  // "script-src": ["'self'"], // add hashes/nonces/CSPRO if needed
@@ -34,9 +58,12 @@ export function buildDefaultSecurityHeaderOptions(): Readonly<HelmetOptions> {
34
58
  },
35
59
  },
36
60
 
37
-
38
- // Legacy clickjacking defense (kept for older UAs)
39
- frameguard: { action: "deny" },
61
+ // Legacy clickjacking defense for configurations that do not allow
62
+ // cross-origin parents (kept for older UAs).
63
+ // X-Frame-Options cannot express an allowlist. It must be omitted when
64
+ // CSP allows a parent on another origin, otherwise older browsers may
65
+ // still block the iframe despite the CSP configuration.
66
+ frameguard: allowsCrossOriginFrames ? false : { action: "deny" },
40
67
 
41
68
  // Referrer/cross-origin policies
42
69
  referrerPolicy: { policy: "strict-origin-when-cross-origin" },
@@ -115,4 +142,4 @@ export function setDefaultCacheControl() {
115
142
  // res.setHeader("Permissions-Policy", buildPermissionsPolicyHeader());
116
143
  // next();
117
144
  // });
118
- // app.use(setDefaultCacheControl());
145
+ // app.use(setDefaultCacheControl());
@@ -32,6 +32,59 @@ export class SolidCoreErrorCodesProvider implements IErrorCodeProvider {
32
32
  httpStatus: 401,
33
33
  },
34
34
  },
35
+ // MPIN rules sit above the generic session rules so that an MPIN
36
+ // failure is never reported as a session problem. The three codes
37
+ // drive three different client behaviours: retry, wait, and
38
+ // discard-the-handle.
39
+ {
40
+ code: 'solidx-mpin-locked',
41
+ priority: 120,
42
+ match: (txt) => txt.includes(ERROR_MESSAGES.MPIN_LOCKED.toLowerCase()),
43
+ meta: {
44
+ message: ERROR_MESSAGES.MPIN_LOCKED,
45
+ httpStatus: 401,
46
+ },
47
+ },
48
+ {
49
+ code: 'solidx-mpin-revoked',
50
+ priority: 120,
51
+ match: (txt) => txt.includes(ERROR_MESSAGES.MPIN_REVOKED.toLowerCase()),
52
+ meta: {
53
+ message: ERROR_MESSAGES.MPIN_REVOKED,
54
+ httpStatus: 401,
55
+ },
56
+ },
57
+ {
58
+ code: 'solidx-mpin-invalid',
59
+ priority: 120,
60
+ match: (txt) => txt.includes(ERROR_MESSAGES.MPIN_INVALID.toLowerCase()),
61
+ meta: {
62
+ message: ERROR_MESSAGES.MPIN_INVALID,
63
+ httpStatus: 401,
64
+ },
65
+ },
66
+ // Setup-time rejections. Distinct codes so the client can say
67
+ // "choose a less predictable PIN" rather than parsing a message.
68
+ // Safe to distinguish: both occur only on bearer-authenticated
69
+ // routes, so neither discloses anything.
70
+ {
71
+ code: 'solidx-mpin-too-predictable',
72
+ priority: 120,
73
+ match: (txt) => txt.includes(ERROR_MESSAGES.MPIN_TOO_PREDICTABLE.toLowerCase()),
74
+ meta: {
75
+ message: ERROR_MESSAGES.MPIN_TOO_PREDICTABLE,
76
+ httpStatus: 400,
77
+ },
78
+ },
79
+ {
80
+ code: 'solidx-mpin-format-invalid',
81
+ priority: 120,
82
+ match: (txt) => txt.includes(ERROR_MESSAGES.MPIN_FORMAT_INVALID.toLowerCase()),
83
+ meta: {
84
+ message: ERROR_MESSAGES.MPIN_FORMAT_INVALID,
85
+ httpStatus: 400,
86
+ },
87
+ },
35
88
  {
36
89
  code: 'solidx-mcp-server-unavailable',
37
90
  priority: 100,
@@ -79,7 +79,12 @@ export class SolidMicroserviceAdapter {
79
79
  const responseData = response.data as SolidxIamAuthResponse;
80
80
  const ttlSeconds = this.getJwtTtlSeconds(responseData?.data?.accessToken);
81
81
  if (ttlSeconds && ttlSeconds > 0) {
82
- await this.cacheManager.set(cacheKey, responseData, ttlSeconds);
82
+ // cache-manager v5 expects milliseconds. This previously passed seconds,
83
+ // which the old Redis store discarded outright - so the entry simply
84
+ // never expired. Now that TTLs are honoured, passing seconds unconverted
85
+ // would expire the cached token ~1000x too early and re-authenticate
86
+ // against IAM on nearly every call.
87
+ await this.cacheManager.set(cacheKey, responseData, ttlSeconds * 1000);
83
88
  }
84
89
 
85
90
  return responseData;
@@ -40,6 +40,14 @@ const DECIMAL_MAP: Record<DatasourceType, ColumnOptions> = {
40
40
  [DatasourceType.oracle]: { type: "float" },
41
41
  };
42
42
 
43
+ const DATE_TIME_MAP: Record<DatasourceType, ColumnOptions> = {
44
+ [DatasourceType.postgres]: { type: "timestamp" },
45
+ [DatasourceType.mssql]: { type: "datetime2" },
46
+ [DatasourceType.mysql]: { type: "datetime" },
47
+ [DatasourceType.mariadb]: { type: "datetime" },
48
+ [DatasourceType.oracle]: { type: "timestamp" },
49
+ };
50
+
43
51
  const solidCoreDbType: DatasourceType =
44
52
  Object.values(DatasourceType).includes(process.env.SOLID_CORE_DB_TYPE as DatasourceType)
45
53
  ? (process.env.SOLID_CORE_DB_TYPE as DatasourceType)
@@ -72,7 +80,10 @@ export function getColumnType(solidType: string): ColumnOptions {
72
80
  case "decimal":
73
81
  return DECIMAL_MAP[solidCoreDbType];
74
82
 
83
+ case "datetime":
84
+ return DATE_TIME_MAP[solidCoreDbType];
85
+
75
86
  default:
76
87
  return {};
77
88
  }
78
- }
89
+ }
package/src/index.ts CHANGED
@@ -160,6 +160,12 @@ export * from './entities/role-metadata.entity'
160
160
  export * from './entities/sms-template.entity'
161
161
  export * from './entities/user.entity'
162
162
  export * from './entities/user-api-key.entity'
163
+ export * from './entities/user-device-credential.entity'
164
+ export * from './repository/user-device-credential.repository'
165
+ export * from './services/mpin.service'
166
+ export * from './dtos/setup-mpin.dto'
167
+ export * from './dtos/change-mpin.dto'
168
+ export * from './dtos/mpin-login.dto'
163
169
  export * from './entities/view-metadata.entity'
164
170
  export * from './entities/setting.entity'
165
171
  export * from './entities/saved-filters.entity'
@@ -22,6 +22,15 @@ export interface ActiveUserData {
22
22
  */
23
23
  sessionId?: string;
24
24
 
25
+ /**
26
+ * Identifies which per-device refresh-token bucket this session belongs to.
27
+ * Present only while concurrent logins are permitted; mutually exclusive with
28
+ * sessionId, which exists only when they are not. Carried on the access token
29
+ * so bearer-authenticated endpoints that need this session's refresh token
30
+ * can find it.
31
+ */
32
+ deviceKey?: string;
33
+
25
34
  /**
26
35
  * The subject's (user) roles.
27
36
  * These are part of the JWT token, we simply decode them.
@@ -0,0 +1,17 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { UserDeviceCredential } from 'src/entities/user-device-credential.entity';
3
+ import { RequestContextService } from 'src/services/request-context.service';
4
+ import { DataSource } from 'typeorm';
5
+ import { SecurityRuleRepository } from './security-rule.repository';
6
+ import { SolidBaseRepository } from './solid-base.repository';
7
+
8
+ @Injectable()
9
+ export class UserDeviceCredentialRepository extends SolidBaseRepository<UserDeviceCredential> {
10
+ constructor(
11
+ readonly dataSource: DataSource,
12
+ readonly requestContextService: RequestContextService,
13
+ readonly securityRuleRepository: SecurityRuleRepository,
14
+ ) {
15
+ super(UserDeviceCredential, dataSource, requestContextService, securityRuleRepository);
16
+ }
17
+ }
@@ -1049,17 +1049,25 @@ export class ModuleMetadataSeederService {
1049
1049
 
1050
1050
  private async seedModelSequences(overallMetadata: any): Promise<{ pruned: number; upserted: number }> {
1051
1051
  const modelSequences = this.getSeedArray<CreateModelSequenceDto>(overallMetadata?.modelSequences);
1052
- const pruned = this.enablePruning ? await this.timeOperation('prune-model-sequences', () => this.pruneModelSequences(modelSequences, overallMetadata?.moduleMetadata?.name), {
1052
+
1053
+ // Getting rid of currentValue even if it is present in the seed data.
1054
+ // This is because we don't want to overwrite the currentValue in the database with the seed data.
1055
+ // The currentValue should be managed by the application and not by the seed data.
1056
+ const cleanModelSequences = modelSequences.map((sequence) => {
1057
+ const { currentValue, ...cleanModelSequence }: CreateModelSequenceDto = sequence;
1058
+ return cleanModelSequence;
1059
+ });
1060
+ const pruned = this.enablePruning ? await this.timeOperation('prune-model-sequences', () => this.pruneModelSequences(cleanModelSequences, overallMetadata?.moduleMetadata?.name), {
1053
1061
  moduleName: overallMetadata?.moduleMetadata?.name,
1054
1062
  component: 'model-sequences',
1055
1063
  serviceCall: 'pruneModelSequences',
1056
1064
  }) : 0;
1057
- await this.timeOperation('handle-model-sequences', () => this.handleSeedModelSequences(modelSequences), {
1065
+ await this.timeOperation('handle-model-sequences', () => this.handleSeedModelSequences(cleanModelSequences), {
1058
1066
  moduleName: overallMetadata?.moduleMetadata?.name,
1059
1067
  component: 'model-sequences',
1060
1068
  serviceCall: 'handleSeedModelSequences',
1061
1069
  });
1062
- return { pruned, upserted: modelSequences.length };
1070
+ return { pruned, upserted: cleanModelSequences.length };
1063
1071
  }
1064
1072
 
1065
1073
  // OK
@@ -2360,6 +2360,164 @@
2360
2360
  }
2361
2361
  ]
2362
2362
  },
2363
+ {
2364
+ "singularName": "userDeviceCredential",
2365
+ "tableName": "ss_user_device_credential",
2366
+ "pluralName": "userDeviceCredentials",
2367
+ "displayName": "User Device Credential",
2368
+ "description": "Per-device MPIN credentials",
2369
+ "dataSource": "default",
2370
+ "dataSourceType": "postgres",
2371
+ "userKeyFieldUserKey": "credentialKey",
2372
+ "isSystem": true,
2373
+ "fields": [
2374
+ {
2375
+ "name": "credentialKey",
2376
+ "displayName": "Credential Key",
2377
+ "description": "Natural key: <username>-<deviceId>. Populated by MpinService, not by the computed-field pipeline.",
2378
+ "type": "shortText",
2379
+ "length": 512,
2380
+ "required": true,
2381
+ "unique": true,
2382
+ "index": true,
2383
+ "private": false,
2384
+ "encrypt": false,
2385
+ "isUserKey": true,
2386
+ "isSystem": true
2387
+ },
2388
+ {
2389
+ "name": "hashedCredentialRef",
2390
+ "displayName": "Hashed Credential Ref",
2391
+ "type": "shortText",
2392
+ "length": 512,
2393
+ "required": true,
2394
+ "unique": true,
2395
+ "index": true,
2396
+ "private": true,
2397
+ "encrypt": false,
2398
+ "isSystem": true
2399
+ },
2400
+ {
2401
+ "name": "hashedMpin",
2402
+ "displayName": "Hashed MPIN",
2403
+ "type": "shortText",
2404
+ "length": 512,
2405
+ "required": true,
2406
+ "unique": false,
2407
+ "index": false,
2408
+ "private": true,
2409
+ "encrypt": false,
2410
+ "isSystem": true
2411
+ },
2412
+ {
2413
+ "name": "mpinScheme",
2414
+ "displayName": "MPIN Scheme",
2415
+ "type": "selectionStatic",
2416
+ "required": true,
2417
+ "private": false,
2418
+ "encrypt": false,
2419
+ "isSystem": true,
2420
+ "selectionStaticValues": [
2421
+ "bcrypt:bcrypt"
2422
+ ]
2423
+ },
2424
+ {
2425
+ "name": "mpinSchemeVersion",
2426
+ "displayName": "MPIN Scheme Version",
2427
+ "type": "int",
2428
+ "required": true,
2429
+ "private": false,
2430
+ "encrypt": false,
2431
+ "defaultValue": "1",
2432
+ "isSystem": true
2433
+ },
2434
+ {
2435
+ "name": "deviceId",
2436
+ "displayName": "Device Id",
2437
+ "type": "shortText",
2438
+ "length": 512,
2439
+ "required": true,
2440
+ "unique": false,
2441
+ "index": true,
2442
+ "private": false,
2443
+ "encrypt": false,
2444
+ "isSystem": true
2445
+ },
2446
+ {
2447
+ "name": "deviceName",
2448
+ "displayName": "Device Name",
2449
+ "type": "shortText",
2450
+ "length": 512,
2451
+ "required": false,
2452
+ "unique": false,
2453
+ "index": false,
2454
+ "private": false,
2455
+ "encrypt": false,
2456
+ "isSystem": true
2457
+ },
2458
+ {
2459
+ "name": "platform",
2460
+ "displayName": "Platform",
2461
+ "type": "shortText",
2462
+ "length": 512,
2463
+ "required": false,
2464
+ "unique": false,
2465
+ "index": false,
2466
+ "private": false,
2467
+ "encrypt": false,
2468
+ "isSystem": true
2469
+ },
2470
+ {
2471
+ "name": "isActive",
2472
+ "displayName": "Is Active",
2473
+ "type": "boolean",
2474
+ "required": true,
2475
+ "unique": false,
2476
+ "index": false,
2477
+ "private": false,
2478
+ "encrypt": false,
2479
+ "defaultValue": "true",
2480
+ "isSystem": true
2481
+ },
2482
+ {
2483
+ "name": "failedAttempts",
2484
+ "displayName": "Failed Attempts",
2485
+ "type": "int",
2486
+ "required": true,
2487
+ "private": false,
2488
+ "encrypt": false,
2489
+ "defaultValue": "0",
2490
+ "isSystem": true
2491
+ },
2492
+ {
2493
+ "name": "lockedUntil",
2494
+ "displayName": "Locked Until",
2495
+ "type": "datetime",
2496
+ "required": false,
2497
+ "private": false,
2498
+ "encrypt": false,
2499
+ "isSystem": true
2500
+ },
2501
+ {
2502
+ "name": "lastUsedAt",
2503
+ "displayName": "Last Used At",
2504
+ "type": "datetime",
2505
+ "required": false,
2506
+ "private": false,
2507
+ "encrypt": false,
2508
+ "isSystem": true
2509
+ },
2510
+ {
2511
+ "name": "user",
2512
+ "displayName": "User",
2513
+ "type": "relation",
2514
+ "required": true,
2515
+ "private": false,
2516
+ "encrypt": false,
2517
+ "isSystem": true
2518
+ }
2519
+ ]
2520
+ },
2363
2521
  {
2364
2522
  "singularName": "userActivityHistory",
2365
2523
  "tableName": "ss_user_activity_history",
@@ -11642,7 +11800,8 @@
11642
11800
  "type": "field",
11643
11801
  "attrs": {
11644
11802
  "name": "fileSize",
11645
- "sortable": true
11803
+ "sortable": true,
11804
+ "viewWidget": "MediaFileSizeListWidget"
11646
11805
  }
11647
11806
  }
11648
11807
  ]
@@ -11807,6 +11966,13 @@
11807
11966
  "attrs": {
11808
11967
  "name": "fieldMetadata"
11809
11968
  }
11969
+ },
11970
+ {
11971
+ "type": "field",
11972
+ "attrs": {
11973
+ "name": "fileSize",
11974
+ "viewWidget": "MediaFileSizeFormViewWidget"
11975
+ }
11810
11976
  }
11811
11977
  ]
11812
11978
  }
@@ -12583,7 +12749,7 @@
12583
12749
  50
12584
12750
  ],
12585
12751
  "enableGlobalSearch": true,
12586
- "create": true,
12752
+ "create": false,
12587
12753
  "edit": true,
12588
12754
  "delete": true,
12589
12755
  "import": true,
@@ -131,7 +131,19 @@ export class ApiKeyService {
131
131
 
132
132
  const user = apiKeyRecord.user;
133
133
 
134
- const tokens = await this.authenticationService.generateTokens(user);
134
+ // Keyed on the API key record so every call for this key reuses one
135
+ // refresh-token bucket. Without it each call mints a fresh key and
136
+ // therefore a new bucket with a refreshTokenTtl lifetime - a client
137
+ // polling this endpoint would accumulate them without bound.
138
+ //
139
+ // The consequence is that replicas sharing one API key share one
140
+ // bucket and invalidate each other, which is exactly the behaviour
141
+ // before per-device sessions existed, and is benign here: an API key
142
+ // is a permanent credential, so a failed refresh just re-mints.
143
+ const tokens = await this.authenticationService.generateTokens(
144
+ user,
145
+ `apikey-${apiKeyRecord.id}`,
146
+ );
135
147
 
136
148
 
137
149
  return {