@solidxai/core 0.1.13-beta.2 → 0.1.13-beta.21

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 (517) hide show
  1. package/CHANGELOG.md +10107 -0
  2. package/CLAUDE.md +5 -6
  3. package/README.md +19 -0
  4. package/dist/commands/run-tests.command.d.ts +9 -1
  5. package/dist/commands/run-tests.command.d.ts.map +1 -1
  6. package/dist/commands/run-tests.command.js +69 -6
  7. package/dist/commands/run-tests.command.js.map +1 -1
  8. package/dist/commands/seed.command.d.ts +2 -0
  9. package/dist/commands/seed.command.d.ts.map +1 -1
  10. package/dist/commands/seed.command.js +21 -0
  11. package/dist/commands/seed.command.js.map +1 -1
  12. package/dist/config/cache.options.d.ts.map +1 -1
  13. package/dist/config/cache.options.js +2 -2
  14. package/dist/config/cache.options.js.map +1 -1
  15. package/dist/constants/chatter-message.constants.d.ts +1 -0
  16. package/dist/constants/chatter-message.constants.d.ts.map +1 -1
  17. package/dist/constants/chatter-message.constants.js +3 -1
  18. package/dist/constants/chatter-message.constants.js.map +1 -1
  19. package/dist/constants/draft-publish-fields.d.ts +2 -0
  20. package/dist/constants/draft-publish-fields.d.ts.map +1 -0
  21. package/dist/constants/draft-publish-fields.js +5 -0
  22. package/dist/constants/draft-publish-fields.js.map +1 -0
  23. package/dist/constants/error-messages.d.ts +7 -0
  24. package/dist/constants/error-messages.d.ts.map +1 -1
  25. package/dist/constants/error-messages.js +7 -0
  26. package/dist/constants/error-messages.js.map +1 -1
  27. package/dist/constants/media-file-types.d.ts +12 -0
  28. package/dist/constants/media-file-types.d.ts.map +1 -1
  29. package/dist/constants/media-file-types.js +66 -1
  30. package/dist/constants/media-file-types.js.map +1 -1
  31. package/dist/constants/success-messages.d.ts +3 -0
  32. package/dist/constants/success-messages.d.ts.map +1 -1
  33. package/dist/constants/success-messages.js +3 -0
  34. package/dist/constants/success-messages.js.map +1 -1
  35. package/dist/constants/user.constants.d.ts +2 -0
  36. package/dist/constants/user.constants.d.ts.map +1 -0
  37. package/dist/constants/user.constants.js +5 -0
  38. package/dist/constants/user.constants.js.map +1 -0
  39. package/dist/controllers/action-metadata.controller.d.ts +1 -1
  40. package/dist/controllers/action-metadata.controller.js +1 -1
  41. package/dist/controllers/agent-event.controller.d.ts +1 -1
  42. package/dist/controllers/agent-event.controller.js +1 -1
  43. package/dist/controllers/agent-session.controller.d.ts +1 -1
  44. package/dist/controllers/agent-session.controller.js +1 -1
  45. package/dist/controllers/authentication.controller.d.ts +1 -1
  46. package/dist/controllers/authentication.controller.d.ts.map +1 -1
  47. package/dist/controllers/authentication.controller.js +4 -3
  48. package/dist/controllers/authentication.controller.js.map +1 -1
  49. package/dist/controllers/chatter-message.controller.d.ts +5 -0
  50. package/dist/controllers/chatter-message.controller.d.ts.map +1 -1
  51. package/dist/controllers/chatter-message.controller.js +14 -0
  52. package/dist/controllers/chatter-message.controller.js.map +1 -1
  53. package/dist/controllers/dashboard-user-layout.controller.d.ts +1 -1
  54. package/dist/controllers/dashboard-user-layout.controller.js +1 -1
  55. package/dist/controllers/email-template.controller.d.ts +1 -1
  56. package/dist/controllers/email-template.controller.js +1 -1
  57. package/dist/controllers/export-template.controller.d.ts +1 -1
  58. package/dist/controllers/export-template.controller.d.ts.map +1 -1
  59. package/dist/controllers/export-template.controller.js +6 -4
  60. package/dist/controllers/export-template.controller.js.map +1 -1
  61. package/dist/controllers/export-transaction.controller.d.ts +1 -1
  62. package/dist/controllers/export-transaction.controller.js +1 -1
  63. package/dist/controllers/field-metadata.controller.d.ts +2 -2
  64. package/dist/controllers/import-transaction-error-log.controller.d.ts +1 -1
  65. package/dist/controllers/import-transaction-error-log.controller.js +1 -1
  66. package/dist/controllers/import-transaction.controller.d.ts +1 -1
  67. package/dist/controllers/import-transaction.controller.d.ts.map +1 -1
  68. package/dist/controllers/import-transaction.controller.js +7 -7
  69. package/dist/controllers/import-transaction.controller.js.map +1 -1
  70. package/dist/controllers/list-of-values.controller.d.ts +1 -1
  71. package/dist/controllers/list-of-values.controller.js +1 -1
  72. package/dist/controllers/locale.controller.d.ts +1 -1
  73. package/dist/controllers/locale.controller.js +1 -1
  74. package/dist/controllers/mcp-audit-log.controller.d.ts +1 -1
  75. package/dist/controllers/mcp-audit-log.controller.js +1 -1
  76. package/dist/controllers/media.controller.d.ts +2 -2
  77. package/dist/controllers/media.controller.d.ts.map +1 -1
  78. package/dist/controllers/media.controller.js +13 -8
  79. package/dist/controllers/media.controller.js.map +1 -1
  80. package/dist/controllers/menu-item-metadata.controller.d.ts +1 -1
  81. package/dist/controllers/menu-item-metadata.controller.js +1 -1
  82. package/dist/controllers/model-sequence.controller.d.ts +1 -1
  83. package/dist/controllers/model-sequence.controller.js +1 -1
  84. package/dist/controllers/module-package.controller.d.ts.map +1 -1
  85. package/dist/controllers/module-package.controller.js +2 -3
  86. package/dist/controllers/module-package.controller.js.map +1 -1
  87. package/dist/controllers/mpin-authentication.controller.d.ts +44 -0
  88. package/dist/controllers/mpin-authentication.controller.d.ts.map +1 -0
  89. package/dist/controllers/mpin-authentication.controller.js +102 -0
  90. package/dist/controllers/mpin-authentication.controller.js.map +1 -0
  91. package/dist/controllers/mq-message-queue.controller.d.ts +1 -1
  92. package/dist/controllers/mq-message-queue.controller.js +1 -1
  93. package/dist/controllers/mq-message.controller.d.ts +1 -1
  94. package/dist/controllers/mq-message.controller.js +1 -1
  95. package/dist/controllers/permission-metadata.controller.d.ts +1 -1
  96. package/dist/controllers/permission-metadata.controller.js +1 -1
  97. package/dist/controllers/role-metadata.controller.d.ts +1 -1
  98. package/dist/controllers/role-metadata.controller.js +1 -1
  99. package/dist/controllers/saved-filters.controller.d.ts +1 -1
  100. package/dist/controllers/saved-filters.controller.js +1 -1
  101. package/dist/controllers/scheduled-job.controller.d.ts +1 -1
  102. package/dist/controllers/scheduled-job.controller.js +1 -1
  103. package/dist/controllers/security-rule.controller.d.ts +1 -1
  104. package/dist/controllers/security-rule.controller.js +1 -1
  105. package/dist/controllers/sms-template.controller.d.ts +1 -1
  106. package/dist/controllers/sms-template.controller.js +1 -1
  107. package/dist/controllers/user-activity-history.controller.d.ts +1 -1
  108. package/dist/controllers/user-activity-history.controller.js +1 -1
  109. package/dist/controllers/user-view-metadata.controller.d.ts +1 -1
  110. package/dist/controllers/user-view-metadata.controller.js +1 -1
  111. package/dist/controllers/user.controller.d.ts +2 -2
  112. package/dist/controllers/user.controller.js +2 -2
  113. package/dist/controllers/view-metadata.controller.d.ts +2 -2
  114. package/dist/controllers/view-metadata.controller.js +1 -1
  115. package/dist/controllers/workflow-definition.controller.d.ts +1 -1
  116. package/dist/controllers/workflow-definition.controller.js +1 -1
  117. package/dist/controllers/workflow-execution-artifact.controller.d.ts +1 -1
  118. package/dist/controllers/workflow-execution-artifact.controller.js +1 -1
  119. package/dist/controllers/workflow-execution-log.controller.d.ts +1 -1
  120. package/dist/controllers/workflow-execution-log.controller.js +1 -1
  121. package/dist/controllers/workflow-execution.controller.d.ts +1 -1
  122. package/dist/controllers/workflow-execution.controller.js +1 -1
  123. package/dist/controllers/workflow-secret.controller.d.ts +1 -1
  124. package/dist/controllers/workflow-secret.controller.js +1 -1
  125. package/dist/controllers/workflow-step-execution.controller.d.ts +1 -1
  126. package/dist/controllers/workflow-step-execution.controller.js +1 -1
  127. package/dist/controllers/workflow-trigger-execution.controller.d.ts +1 -1
  128. package/dist/controllers/workflow-trigger-execution.controller.js +1 -1
  129. package/dist/dtos/basic-filters.dto.d.ts +0 -1
  130. package/dist/dtos/basic-filters.dto.d.ts.map +1 -1
  131. package/dist/dtos/basic-filters.dto.js +1 -6
  132. package/dist/dtos/basic-filters.dto.js.map +1 -1
  133. package/dist/dtos/change-mpin.dto.d.ts +6 -0
  134. package/dist/dtos/change-mpin.dto.d.ts.map +1 -0
  135. package/dist/dtos/change-mpin.dto.js +39 -0
  136. package/dist/dtos/change-mpin.dto.js.map +1 -0
  137. package/dist/dtos/create-field-metadata.dto.d.ts +3 -6
  138. package/dist/dtos/create-field-metadata.dto.d.ts.map +1 -1
  139. package/dist/dtos/create-field-metadata.dto.js +15 -10
  140. package/dist/dtos/create-field-metadata.dto.js.map +1 -1
  141. package/dist/dtos/create-model-sequence.dto.d.ts +1 -1
  142. package/dist/dtos/create-model-sequence.dto.d.ts.map +1 -1
  143. package/dist/dtos/create-model-sequence.dto.js +1 -1
  144. package/dist/dtos/create-model-sequence.dto.js.map +1 -1
  145. package/dist/dtos/mpin-login.dto.d.ts +5 -0
  146. package/dist/dtos/mpin-login.dto.d.ts.map +1 -0
  147. package/dist/dtos/mpin-login.dto.js +33 -0
  148. package/dist/dtos/mpin-login.dto.js.map +1 -0
  149. package/dist/dtos/setup-mpin.dto.d.ts +7 -0
  150. package/dist/dtos/setup-mpin.dto.d.ts.map +1 -0
  151. package/dist/dtos/setup-mpin.dto.js +47 -0
  152. package/dist/dtos/setup-mpin.dto.js.map +1 -0
  153. package/dist/entities/common.entity.d.ts +4 -0
  154. package/dist/entities/common.entity.d.ts.map +1 -1
  155. package/dist/entities/common.entity.js +25 -1
  156. package/dist/entities/common.entity.js.map +1 -1
  157. package/dist/entities/dashboard-user-layout.entity.js +2 -2
  158. package/dist/entities/dashboard-user-layout.entity.js.map +1 -1
  159. package/dist/entities/field-metadata.entity.d.ts +1 -0
  160. package/dist/entities/field-metadata.entity.d.ts.map +1 -1
  161. package/dist/entities/field-metadata.entity.js +5 -1
  162. package/dist/entities/field-metadata.entity.js.map +1 -1
  163. package/dist/entities/saved-filters.entity.js +3 -3
  164. package/dist/entities/saved-filters.entity.js.map +1 -1
  165. package/dist/entities/user-device-credential.entity.d.ts +18 -0
  166. package/dist/entities/user-device-credential.entity.d.ts.map +1 -0
  167. package/dist/entities/user-device-credential.entity.js +91 -0
  168. package/dist/entities/user-device-credential.entity.js.map +1 -0
  169. package/dist/entities/user.entity.d.ts.map +1 -1
  170. package/dist/entities/user.entity.js +36 -0
  171. package/dist/entities/user.entity.js.map +1 -1
  172. package/dist/entities/view-metadata.entity.js.map +1 -1
  173. package/dist/entities/workflow-execution-artifact.entity.d.ts.map +1 -1
  174. package/dist/entities/workflow-execution-artifact.entity.js +4 -3
  175. package/dist/entities/workflow-execution-artifact.entity.js.map +1 -1
  176. package/dist/entities/workflow-execution-log.entity.d.ts.map +1 -1
  177. package/dist/entities/workflow-execution-log.entity.js +4 -3
  178. package/dist/entities/workflow-execution-log.entity.js.map +1 -1
  179. package/dist/entities/workflow-execution.entity.d.ts.map +1 -1
  180. package/dist/entities/workflow-execution.entity.js +3 -2
  181. package/dist/entities/workflow-execution.entity.js.map +1 -1
  182. package/dist/entities/workflow-secret.entity.d.ts.map +1 -1
  183. package/dist/entities/workflow-secret.entity.js +3 -2
  184. package/dist/entities/workflow-secret.entity.js.map +1 -1
  185. package/dist/entities/workflow-step-execution.entity.d.ts.map +1 -1
  186. package/dist/entities/workflow-step-execution.entity.js +3 -2
  187. package/dist/entities/workflow-step-execution.entity.js.map +1 -1
  188. package/dist/entities/workflow-trigger-execution.entity.d.ts.map +1 -1
  189. package/dist/entities/workflow-trigger-execution.entity.js +7 -6
  190. package/dist/entities/workflow-trigger-execution.entity.js.map +1 -1
  191. package/dist/factories/mail.factory.d.ts.map +1 -1
  192. package/dist/factories/mail.factory.js +4 -0
  193. package/dist/factories/mail.factory.js.map +1 -1
  194. package/dist/filters/http-exception.filter.d.ts.map +1 -1
  195. package/dist/filters/http-exception.filter.js +5 -1
  196. package/dist/filters/http-exception.filter.js.map +1 -1
  197. package/dist/helpers/bootstrap.helper.d.ts +5 -2
  198. package/dist/helpers/bootstrap.helper.d.ts.map +1 -1
  199. package/dist/helpers/bootstrap.helper.js +7 -3
  200. package/dist/helpers/bootstrap.helper.js.map +1 -1
  201. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts +7 -0
  202. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts.map +1 -1
  203. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js +66 -32
  204. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js.map +1 -1
  205. package/dist/helpers/file-download.helper.d.ts +9 -0
  206. package/dist/helpers/file-download.helper.d.ts.map +1 -0
  207. package/dist/helpers/file-download.helper.js +19 -0
  208. package/dist/helpers/file-download.helper.js.map +1 -0
  209. package/dist/helpers/model-metadata-helper.service.d.ts.map +1 -1
  210. package/dist/helpers/model-metadata-helper.service.js +30 -1
  211. package/dist/helpers/model-metadata-helper.service.js.map +1 -1
  212. package/dist/helpers/security.helper.d.ts +5 -1
  213. package/dist/helpers/security.helper.d.ts.map +1 -1
  214. package/dist/helpers/security.helper.js +15 -4
  215. package/dist/helpers/security.helper.js.map +1 -1
  216. package/dist/helpers/solid-core-error-codes-provider.service.d.ts.map +1 -1
  217. package/dist/helpers/solid-core-error-codes-provider.service.js +45 -0
  218. package/dist/helpers/solid-core-error-codes-provider.service.js.map +1 -1
  219. package/dist/helpers/solid-microservice-adapter.service.d.ts.map +1 -1
  220. package/dist/helpers/solid-microservice-adapter.service.js +1 -1
  221. package/dist/helpers/solid-microservice-adapter.service.js.map +1 -1
  222. package/dist/helpers/solid-registry.js +1 -1
  223. package/dist/helpers/solid-registry.js.map +1 -1
  224. package/dist/helpers/typeorm-db-helper.d.ts.map +1 -1
  225. package/dist/helpers/typeorm-db-helper.js +9 -0
  226. package/dist/helpers/typeorm-db-helper.js.map +1 -1
  227. package/dist/index.d.ts +10 -0
  228. package/dist/index.d.ts.map +1 -1
  229. package/dist/index.js +10 -0
  230. package/dist/index.js.map +1 -1
  231. package/dist/interfaces/active-user-data.interface.d.ts +1 -0
  232. package/dist/interfaces/active-user-data.interface.d.ts.map +1 -1
  233. package/dist/interfaces/active-user-data.interface.js.map +1 -1
  234. package/dist/interfaces.d.ts +1 -0
  235. package/dist/interfaces.d.ts.map +1 -1
  236. package/dist/interfaces.js.map +1 -1
  237. package/dist/repository/user-device-credential.repository.d.ts +12 -0
  238. package/dist/repository/user-device-credential.repository.d.ts.map +1 -0
  239. package/dist/repository/user-device-credential.repository.js +34 -0
  240. package/dist/repository/user-device-credential.repository.js.map +1 -0
  241. package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
  242. package/dist/seeders/module-metadata-seeder.service.js +51 -30
  243. package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
  244. package/dist/seeders/module-test-data.service.d.ts +3 -1
  245. package/dist/seeders/module-test-data.service.d.ts.map +1 -1
  246. package/dist/seeders/module-test-data.service.js +8 -2
  247. package/dist/seeders/module-test-data.service.js.map +1 -1
  248. package/dist/seeders/seed-data/solid-core-metadata.json +290 -78
  249. package/dist/services/api-key.service.d.ts.map +1 -1
  250. package/dist/services/api-key.service.js +1 -1
  251. package/dist/services/api-key.service.js.map +1 -1
  252. package/dist/services/authentication.service.d.ts +9 -5
  253. package/dist/services/authentication.service.d.ts.map +1 -1
  254. package/dist/services/authentication.service.js +102 -23
  255. package/dist/services/authentication.service.js.map +1 -1
  256. package/dist/services/chatter-message.service.d.ts +16 -1
  257. package/dist/services/chatter-message.service.d.ts.map +1 -1
  258. package/dist/services/chatter-message.service.js +100 -16
  259. package/dist/services/chatter-message.service.js.map +1 -1
  260. package/dist/services/crud-helper.service.d.ts +6 -4
  261. package/dist/services/crud-helper.service.d.ts.map +1 -1
  262. package/dist/services/crud-helper.service.js +32 -48
  263. package/dist/services/crud-helper.service.js.map +1 -1
  264. package/dist/services/crud.service.d.ts +13 -1
  265. package/dist/services/crud.service.d.ts.map +1 -1
  266. package/dist/services/crud.service.js +94 -68
  267. package/dist/services/crud.service.js.map +1 -1
  268. package/dist/services/datasource-introspection.service.d.ts.map +1 -1
  269. package/dist/services/datasource-introspection.service.js.map +1 -1
  270. package/dist/services/draft-publish-helper.service.d.ts +53 -0
  271. package/dist/services/draft-publish-helper.service.d.ts.map +1 -0
  272. package/dist/services/draft-publish-helper.service.js +402 -0
  273. package/dist/services/draft-publish-helper.service.js.map +1 -0
  274. package/dist/services/field-metadata.service.d.ts +2 -2
  275. package/dist/services/field-metadata.service.d.ts.map +1 -1
  276. package/dist/services/field-metadata.service.js +4 -1
  277. package/dist/services/field-metadata.service.js.map +1 -1
  278. package/dist/services/internationalisation-helper.service.d.ts +19 -0
  279. package/dist/services/internationalisation-helper.service.d.ts.map +1 -0
  280. package/dist/services/internationalisation-helper.service.js +107 -0
  281. package/dist/services/internationalisation-helper.service.js.map +1 -0
  282. package/dist/services/locale.service.d.ts +1 -1
  283. package/dist/services/media-storage.utils.d.ts +1 -0
  284. package/dist/services/media-storage.utils.d.ts.map +1 -1
  285. package/dist/services/media-storage.utils.js +12 -1
  286. package/dist/services/media-storage.utils.js.map +1 -1
  287. package/dist/services/media.service.d.ts +3 -2
  288. package/dist/services/media.service.d.ts.map +1 -1
  289. package/dist/services/media.service.js +45 -29
  290. package/dist/services/media.service.js.map +1 -1
  291. package/dist/services/metadata-validation.service.d.ts +7 -0
  292. package/dist/services/metadata-validation.service.d.ts.map +1 -0
  293. package/dist/services/metadata-validation.service.js +42 -0
  294. package/dist/services/metadata-validation.service.js.map +1 -0
  295. package/dist/services/model-metadata.service.d.ts.map +1 -1
  296. package/dist/services/model-metadata.service.js +2 -1
  297. package/dist/services/model-metadata.service.js.map +1 -1
  298. package/dist/services/module-metadata.service.d.ts.map +1 -1
  299. package/dist/services/module-metadata.service.js +9 -1
  300. package/dist/services/module-metadata.service.js.map +1 -1
  301. package/dist/services/mpin.service.d.ts +67 -0
  302. package/dist/services/mpin.service.d.ts.map +1 -0
  303. package/dist/services/mpin.service.js +282 -0
  304. package/dist/services/mpin.service.js.map +1 -0
  305. package/dist/services/object.utils.d.ts +2 -0
  306. package/dist/services/object.utils.d.ts.map +1 -0
  307. package/dist/services/object.utils.js +11 -0
  308. package/dist/services/object.utils.js.map +1 -0
  309. package/dist/services/refresh-token-ids-storage.service.d.ts +25 -6
  310. package/dist/services/refresh-token-ids-storage.service.d.ts.map +1 -1
  311. package/dist/services/refresh-token-ids-storage.service.js +80 -42
  312. package/dist/services/refresh-token-ids-storage.service.js.map +1 -1
  313. package/dist/services/setting.service.d.ts.map +1 -1
  314. package/dist/services/setting.service.js +6 -1
  315. package/dist/services/setting.service.js.map +1 -1
  316. package/dist/services/settings/default-settings-provider.service.d.ts +248 -12
  317. package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
  318. package/dist/services/settings/default-settings-provider.service.js +143 -6
  319. package/dist/services/settings/default-settings-provider.service.js.map +1 -1
  320. package/dist/services/user.service.d.ts +2 -2
  321. package/dist/services/user.service.d.ts.map +1 -1
  322. package/dist/services/user.service.js +9 -3
  323. package/dist/services/user.service.js.map +1 -1
  324. package/dist/services/view-metadata.service.d.ts +1 -2
  325. package/dist/services/view-metadata.service.d.ts.map +1 -1
  326. package/dist/services/view-metadata.service.js +3 -55
  327. package/dist/services/view-metadata.service.js.map +1 -1
  328. package/dist/services/workflow/nodes/execution-fail.node.d.ts +6 -0
  329. package/dist/services/workflow/nodes/execution-fail.node.d.ts.map +1 -0
  330. package/dist/services/workflow/nodes/execution-fail.node.js +81 -0
  331. package/dist/services/workflow/nodes/execution-fail.node.js.map +1 -0
  332. package/dist/services/workflow/nodes/for-each.node.d.ts +2 -0
  333. package/dist/services/workflow/nodes/for-each.node.d.ts.map +1 -1
  334. package/dist/services/workflow/nodes/for-each.node.js +114 -5
  335. package/dist/services/workflow/nodes/for-each.node.js.map +1 -1
  336. package/dist/services/workflow/nodes/if.node.d.ts.map +1 -1
  337. package/dist/services/workflow/nodes/if.node.js +7 -0
  338. package/dist/services/workflow/nodes/if.node.js.map +1 -1
  339. package/dist/services/workflow/nodes/log-write.node.d.ts.map +1 -1
  340. package/dist/services/workflow/nodes/log-write.node.js +2 -0
  341. package/dist/services/workflow/nodes/log-write.node.js.map +1 -1
  342. package/dist/services/workflow/nodes/loop-until.node.d.ts +12 -0
  343. package/dist/services/workflow/nodes/loop-until.node.d.ts.map +1 -0
  344. package/dist/services/workflow/nodes/loop-until.node.js +281 -0
  345. package/dist/services/workflow/nodes/loop-until.node.js.map +1 -0
  346. package/dist/services/workflow/nodes/parallel.node.d.ts.map +1 -1
  347. package/dist/services/workflow/nodes/parallel.node.js +7 -0
  348. package/dist/services/workflow/nodes/parallel.node.js.map +1 -1
  349. package/dist/services/workflow/nodes/sequential.node.d.ts.map +1 -1
  350. package/dist/services/workflow/nodes/sequential.node.js +7 -0
  351. package/dist/services/workflow/nodes/sequential.node.js.map +1 -1
  352. package/dist/services/workflow/nodes/switch.node.d.ts.map +1 -1
  353. package/dist/services/workflow/nodes/switch.node.js +7 -0
  354. package/dist/services/workflow/nodes/switch.node.js.map +1 -1
  355. package/dist/services/workflow/workflow-definition-validator.service.d.ts.map +1 -1
  356. package/dist/services/workflow/workflow-definition-validator.service.js +5 -2
  357. package/dist/services/workflow/workflow-definition-validator.service.js.map +1 -1
  358. package/dist/services/workflow/workflow-expression.service.d.ts.map +1 -1
  359. package/dist/services/workflow/workflow-expression.service.js +5 -0
  360. package/dist/services/workflow/workflow-expression.service.js.map +1 -1
  361. package/dist/services/workflow/workflow-node-registry.service.d.ts +2 -1
  362. package/dist/services/workflow/workflow-node-registry.service.d.ts.map +1 -1
  363. package/dist/services/workflow/workflow-node-registry.service.js +7 -0
  364. package/dist/services/workflow/workflow-node-registry.service.js.map +1 -1
  365. package/dist/services/workflow/workflow-runtime.service.d.ts +8 -0
  366. package/dist/services/workflow/workflow-runtime.service.d.ts.map +1 -1
  367. package/dist/services/workflow/workflow-runtime.service.js +197 -32
  368. package/dist/services/workflow/workflow-runtime.service.js.map +1 -1
  369. package/dist/services/workflow-definition.service.d.ts +1 -1
  370. package/dist/solid-core.module.d.ts +1 -3
  371. package/dist/solid-core.module.d.ts.map +1 -1
  372. package/dist/solid-core.module.js +28 -40
  373. package/dist/solid-core.module.js.map +1 -1
  374. package/dist/testing/adapters/ui/playwright-adapter.d.ts +5 -0
  375. package/dist/testing/adapters/ui/playwright-adapter.d.ts.map +1 -1
  376. package/dist/testing/adapters/ui/playwright-adapter.js +17 -1
  377. package/dist/testing/adapters/ui/playwright-adapter.js.map +1 -1
  378. package/dist/testing/adapters/ui/ui.types.d.ts +2 -0
  379. package/dist/testing/adapters/ui/ui.types.d.ts.map +1 -1
  380. package/dist/testing/adapters/ui/ui.types.js.map +1 -1
  381. package/dist/testing/reporter/console-reporter.d.ts +3 -0
  382. package/dist/testing/reporter/console-reporter.d.ts.map +1 -1
  383. package/dist/testing/reporter/console-reporter.js +2 -0
  384. package/dist/testing/reporter/console-reporter.js.map +1 -1
  385. package/dist/testing/reporter/progress-reporter.d.ts +64 -0
  386. package/dist/testing/reporter/progress-reporter.d.ts.map +1 -0
  387. package/dist/testing/reporter/progress-reporter.js +224 -0
  388. package/dist/testing/reporter/progress-reporter.js.map +1 -0
  389. package/dist/testing/reporter/reporter.types.d.ts +3 -0
  390. package/dist/testing/reporter/reporter.types.d.ts.map +1 -1
  391. package/dist/testing/reporter/reporter.types.js.map +1 -1
  392. package/dist/testing/runner/run-from-metadata.d.ts.map +1 -1
  393. package/dist/testing/runner/run-from-metadata.js +1 -0
  394. package/dist/testing/runner/run-from-metadata.js.map +1 -1
  395. package/dist/testing/steps/ui/actions.step.d.ts.map +1 -1
  396. package/dist/testing/steps/ui/actions.step.js +6 -2
  397. package/dist/testing/steps/ui/actions.step.js.map +1 -1
  398. package/dist/testing/steps/ui/assertions.step.d.ts.map +1 -1
  399. package/dist/testing/steps/ui/assertions.step.js +11 -3
  400. package/dist/testing/steps/ui/assertions.step.js.map +1 -1
  401. package/dist/testing/steps/ui/form.step.d.ts.map +1 -1
  402. package/dist/testing/steps/ui/form.step.js +9 -3
  403. package/dist/testing/steps/ui/form.step.js.map +1 -1
  404. package/dist/testing/steps/ui/manual.step.js +1 -1
  405. package/dist/testing/steps/ui/manual.step.js.map +1 -1
  406. package/dist/testing/steps/ui/navigation.step.d.ts.map +1 -1
  407. package/dist/testing/steps/ui/navigation.step.js +3 -1
  408. package/dist/testing/steps/ui/navigation.step.js.map +1 -1
  409. package/dist/theme/theme-registry.d.ts +14 -0
  410. package/dist/theme/theme-registry.d.ts.map +1 -0
  411. package/dist/theme/theme-registry.js +147 -0
  412. package/dist/theme/theme-registry.js.map +1 -0
  413. package/dist/types/workflow-dsl.types.d.ts +14 -0
  414. package/dist/types/workflow-dsl.types.d.ts.map +1 -1
  415. package/dist/types/workflow-dsl.types.js.map +1 -1
  416. package/package.json +2 -2
  417. package/src/commands/run-tests.command.ts +64 -4
  418. package/src/commands/seed.command.ts +19 -0
  419. package/src/config/cache.options.ts +9 -1
  420. package/src/constants/chatter-message.constants.ts +12 -0
  421. package/src/constants/draft-publish-fields.ts +5 -0
  422. package/src/constants/error-messages.ts +14 -0
  423. package/src/constants/media-file-types.ts +128 -0
  424. package/src/constants/success-messages.ts +5 -0
  425. package/src/constants/user.constants.ts +13 -0
  426. package/src/controllers/authentication.controller.ts +5 -2
  427. package/src/controllers/chatter-message.controller.ts +8 -0
  428. package/src/controllers/export-template.controller.ts +5 -3
  429. package/src/controllers/import-transaction.controller.ts +6 -6
  430. package/src/controllers/media.controller.ts +17 -6
  431. package/src/controllers/module-package.controller.ts +2 -3
  432. package/src/controllers/mpin-authentication.controller.ts +67 -0
  433. package/src/dtos/basic-filters.dto.ts +0 -4
  434. package/src/dtos/change-mpin.dto.ts +18 -0
  435. package/src/dtos/create-field-metadata.dto.ts +17 -9
  436. package/src/dtos/create-model-sequence.dto.ts +1 -1
  437. package/src/dtos/mpin-login.dto.ts +24 -0
  438. package/src/dtos/setup-mpin.dto.ts +51 -0
  439. package/src/entities/common.entity.ts +24 -0
  440. package/src/entities/dashboard-user-layout.entity.ts +2 -2
  441. package/src/entities/field-metadata.entity.ts +3 -0
  442. package/src/entities/saved-filters.entity.ts +3 -3
  443. package/src/entities/user-device-credential.entity.ts +130 -0
  444. package/src/entities/user.entity.ts +53 -36
  445. package/src/entities/view-metadata.entity.ts +2 -2
  446. package/src/entities/workflow-execution-artifact.entity.ts +4 -3
  447. package/src/entities/workflow-execution-log.entity.ts +4 -3
  448. package/src/entities/workflow-execution.entity.ts +3 -2
  449. package/src/entities/workflow-secret.entity.ts +3 -2
  450. package/src/entities/workflow-step-execution.entity.ts +3 -2
  451. package/src/entities/workflow-trigger-execution.entity.ts +7 -6
  452. package/src/factories/mail.factory.ts +7 -1
  453. package/src/filters/http-exception.filter.ts +11 -1
  454. package/src/helpers/bootstrap.helper.ts +43 -6
  455. package/src/helpers/field-crud-managers/MediaFieldCrudManager.ts +90 -37
  456. package/src/helpers/file-download.helper.ts +45 -0
  457. package/src/helpers/model-metadata-helper.service.ts +32 -1
  458. package/src/helpers/security.helper.ts +35 -8
  459. package/src/helpers/solid-core-error-codes-provider.service.ts +53 -0
  460. package/src/helpers/solid-microservice-adapter.service.ts +6 -1
  461. package/src/helpers/solid-registry.ts +1 -1
  462. package/src/helpers/typeorm-db-helper.ts +12 -1
  463. package/src/index.ts +10 -0
  464. package/src/interfaces/active-user-data.interface.ts +9 -0
  465. package/src/interfaces.ts +1 -0
  466. package/src/repository/user-device-credential.repository.ts +17 -0
  467. package/src/seeders/module-metadata-seeder.service.ts +61 -39
  468. package/src/seeders/module-test-data.service.ts +6 -0
  469. package/src/seeders/seed-data/solid-core-metadata.json +290 -78
  470. package/src/services/api-key.service.ts +13 -1
  471. package/src/services/authentication.service.ts +203 -41
  472. package/src/services/chatter-message.service.ts +180 -19
  473. package/src/services/crud-helper.service.ts +35 -50
  474. package/src/services/crud.service.ts +132 -105
  475. package/src/services/datasource-introspection.service.ts +5 -0
  476. package/src/services/draft-publish-helper.service.ts +581 -0
  477. package/src/services/field-metadata.service.ts +5 -2
  478. package/src/services/internationalisation-helper.service.ts +148 -0
  479. package/src/services/media-storage.utils.ts +26 -1
  480. package/src/services/media.service.ts +68 -36
  481. package/src/services/metadata-validation.service.ts +39 -0
  482. package/src/services/model-metadata.service.ts +4 -1
  483. package/src/services/module-metadata.service.ts +10 -1
  484. package/src/services/mpin.service.ts +447 -0
  485. package/src/services/object.utils.ts +10 -0
  486. package/src/services/refresh-token-ids-storage.service.ts +229 -90
  487. package/src/services/setting.service.ts +8 -1
  488. package/src/services/settings/default-settings-provider.service.ts +154 -6
  489. package/src/services/user.service.ts +18 -3
  490. package/src/services/view-metadata.service.ts +5 -145
  491. package/src/services/workflow/nodes/execution-fail.node.ts +82 -0
  492. package/src/services/workflow/nodes/for-each.node.ts +140 -6
  493. package/src/services/workflow/nodes/if.node.ts +7 -0
  494. package/src/services/workflow/nodes/log-write.node.ts +2 -0
  495. package/src/services/workflow/nodes/loop-until.node.ts +343 -0
  496. package/src/services/workflow/nodes/parallel.node.ts +7 -0
  497. package/src/services/workflow/nodes/sequential.node.ts +7 -0
  498. package/src/services/workflow/nodes/switch.node.ts +7 -0
  499. package/src/services/workflow/workflow-definition-validator.service.ts +5 -2
  500. package/src/services/workflow/workflow-expression.service.ts +5 -0
  501. package/src/services/workflow/workflow-node-registry.service.ts +11 -0
  502. package/src/services/workflow/workflow-runtime.service.ts +274 -39
  503. package/src/solid-core.module.ts +46 -15
  504. package/src/testing/README.md +75 -3
  505. package/src/testing/adapters/ui/playwright-adapter.ts +25 -0
  506. package/src/testing/adapters/ui/ui.types.ts +2 -0
  507. package/src/testing/reporter/console-reporter.ts +4 -0
  508. package/src/testing/reporter/progress-reporter.ts +251 -0
  509. package/src/testing/reporter/reporter.types.ts +3 -0
  510. package/src/testing/runner/run-from-metadata.ts +1 -0
  511. package/src/testing/steps/ui/actions.step.ts +8 -4
  512. package/src/testing/steps/ui/assertions.step.ts +18 -5
  513. package/src/testing/steps/ui/form.step.ts +16 -6
  514. package/src/testing/steps/ui/manual.step.ts +1 -1
  515. package/src/testing/steps/ui/navigation.step.ts +4 -2
  516. package/src/theme/theme-registry.ts +181 -0
  517. package/src/types/workflow-dsl.types.ts +43 -0
@@ -0,0 +1,447 @@
1
+ import {
2
+ BadRequestException,
3
+ Injectable,
4
+ Logger,
5
+ NotFoundException,
6
+ UnauthorizedException,
7
+ } from '@nestjs/common';
8
+ import { createHash, randomBytes } from 'crypto';
9
+ import { ERROR_MESSAGES } from 'src/constants/error-messages';
10
+ import { SUCCESS_MESSAGES } from 'src/constants/success-messages';
11
+ import { ChangeMpinDto } from 'src/dtos/change-mpin.dto';
12
+ import { MpinLoginDto } from 'src/dtos/mpin-login.dto';
13
+ import { SetupMpinDto } from 'src/dtos/setup-mpin.dto';
14
+ import { UserDeviceCredential } from 'src/entities/user-device-credential.entity';
15
+ import { ActiveUserData } from 'src/interfaces/active-user-data.interface';
16
+ import { UserDeviceCredentialRepository } from 'src/repository/user-device-credential.repository';
17
+ import { UserRepository } from 'src/repository/user.repository';
18
+ import type { SolidCoreSetting } from 'src/services/settings/default-settings-provider.service';
19
+ import { AuthenticationService } from './authentication.service';
20
+ import { HashingService } from './hashing.service';
21
+ import { SettingService } from './setting.service';
22
+ import { UserActivityHistoryService } from './user-activity-history.service';
23
+
24
+ /**
25
+ * The most-chosen PINs in published analyses of breached sets. `1234` alone
26
+ * accounts for roughly a tenth of four-digit choices, so a list this short
27
+ * removes a disproportionate share of guessable values.
28
+ */
29
+ const MPIN_DENYLIST = new Set([
30
+ '0000', '1111', '2222', '3333', '4444', '5555', '6666', '7777', '8888', '9999',
31
+ '1234', '4321', '1212', '2001', '1004', '2000', '6969', '2580',
32
+ '000000', '111111', '123456', '654321', '121212', '112233', '123123',
33
+ ]);
34
+
35
+ @Injectable()
36
+ export class MpinService {
37
+ private readonly logger = new Logger(MpinService.name);
38
+
39
+ constructor(
40
+ private readonly credentialRepository: UserDeviceCredentialRepository,
41
+ private readonly userRepository: UserRepository,
42
+ private readonly hashingService: HashingService,
43
+ private readonly settingService: SettingService,
44
+ private readonly authenticationService: AuthenticationService,
45
+ private readonly userActivityHistoryService: UserActivityHistoryService,
46
+ ) { }
47
+
48
+ // ---------------------------------------------------------------- setup
49
+
50
+ async setupMpin(activeUser: ActiveUserData, dto: SetupMpinDto) {
51
+ this.assertEnabled();
52
+ this.assertMpinAcceptable(dto.mpin);
53
+
54
+ // The account comes from the access token, never from the body - a
55
+ // client can only ever create a credential for the user it is signed
56
+ // in as.
57
+ const user = await this.userRepository.findOne({ where: { id: activeUser.sub } });
58
+ if (!user) {
59
+ throw new NotFoundException(ERROR_MESSAGES.USER_NOT_FOUND);
60
+ }
61
+
62
+ // Lower-cased because @IsUUID is case-insensitive: iOS renders a UUID
63
+ // uppercase (identifierForVendor.uuidString) while Android renders it
64
+ // lowercase, and both validate. Without normalising, the same physical
65
+ // device could present two spellings, miss this exact-match lookup and
66
+ // end up with two credential rows consuming two slots against the cap.
67
+ const deviceId = dto.deviceId.toLowerCase();
68
+
69
+ const existing = await this.credentialRepository.findOne({
70
+ where: { user: { id: user.id }, deviceId },
71
+ });
72
+
73
+ // Re-running setup on the same device rotates the credential rather
74
+ // than adding a second one.
75
+ const record = existing ?? this.credentialRepository.create({
76
+ user,
77
+ deviceId,
78
+ });
79
+
80
+ // Recomputed on every setup so a renamed user does not keep a stale
81
+ // key. It is an identifier for humans and tooling, never a lookup key,
82
+ // so drift here costs nothing operationally.
83
+ record.credentialKey = this.buildCredentialKey(user.username, deviceId);
84
+
85
+ const credentialRef = this.mintCredentialRef();
86
+ record.hashedCredentialRef = this.hashCredentialRef(credentialRef);
87
+ record.hashedMpin = await this.hashingService.hash(dto.mpin);
88
+ record.mpinScheme = this.hashingService.name();
89
+ record.mpinSchemeVersion = this.hashingService.currentVersion();
90
+ record.deviceName = dto.deviceName ?? null;
91
+ record.platform = dto.platform ?? null;
92
+ record.isActive = true;
93
+ record.failedAttempts = 0;
94
+ record.lockedUntil = null;
95
+
96
+ await this.evictOldestIfAtCap(user.id, existing?.id);
97
+ await this.credentialRepository.save(record);
98
+
99
+ // Returned once. Only its SHA-256 is stored, so it can never be
100
+ // re-issued - the client must persist it before reporting success.
101
+ return { credentialRef, message: SUCCESS_MESSAGES.MPIN_SETUP_SUCCESS };
102
+ }
103
+
104
+ async changeMpin(activeUser: ActiveUserData, dto: ChangeMpinDto) {
105
+ this.assertEnabled();
106
+ this.assertMpinAcceptable(dto.newMpin);
107
+
108
+ // Scoped to the caller, so holding someone else's handle is not enough
109
+ // to change their MPIN.
110
+ const credential = await this.credentialRepository.findOne({
111
+ where: {
112
+ hashedCredentialRef: this.hashCredentialRef(dto.credentialRef),
113
+ user: { id: activeUser.sub },
114
+ },
115
+ });
116
+
117
+ if (!credential || !credential.isActive) {
118
+ throw new UnauthorizedException(ERROR_MESSAGES.MPIN_INVALID);
119
+ }
120
+ this.assertNotLocked(credential);
121
+
122
+ const matches = await this.hashingService.compare(
123
+ dto.currentMpin,
124
+ credential.hashedMpin,
125
+ credential.mpinSchemeVersion,
126
+ );
127
+ if (!matches) {
128
+ throw await this.registerFailedAttempt(credential);
129
+ }
130
+
131
+ // Rotate the handle alongside the MPIN: a changed credential should
132
+ // invalidate anything captured previously.
133
+ const credentialRef = this.mintCredentialRef();
134
+ credential.hashedCredentialRef = this.hashCredentialRef(credentialRef);
135
+ credential.hashedMpin = await this.hashingService.hash(dto.newMpin);
136
+ credential.mpinScheme = this.hashingService.name();
137
+ credential.mpinSchemeVersion = this.hashingService.currentVersion();
138
+ credential.failedAttempts = 0;
139
+ credential.lockedUntil = null;
140
+ await this.credentialRepository.save(credential);
141
+
142
+ return { credentialRef, message: SUCCESS_MESSAGES.MPIN_CHANGED_SUCCESS };
143
+ }
144
+
145
+ // ---------------------------------------------------------------- login
146
+
147
+ async loginWithMpin(dto: MpinLoginDto) {
148
+ this.assertEnabled();
149
+
150
+ // A single indexed exact-match lookup. The row already names the user,
151
+ // so there is nothing to resolve and no identifier to enumerate with.
152
+ const credential = await this.credentialRepository.findOne({
153
+ where: { hashedCredentialRef: this.hashCredentialRef(dto.credentialRef) },
154
+ relations: { user: { roles: true } },
155
+ });
156
+
157
+ // An unknown handle is reported exactly as a wrong MPIN is.
158
+ if (!credential || !credential.user) {
159
+ throw new UnauthorizedException(ERROR_MESSAGES.MPIN_INVALID);
160
+ }
161
+
162
+ // A dead credential is reported as such regardless of what MPIN was
163
+ // supplied. Gating this on a correct MPIN would be marginally more
164
+ // conservative, but it protects almost nothing - "this dead handle was
165
+ // once real" grants no capability - and it costs an asymmetry that
166
+ // reads as a bug: the attempt that causes deactivation reports
167
+ // MPIN_REVOKED, while the very next identical attempt would report
168
+ // MPIN_INVALID. The enumeration resistance that matters comes from the
169
+ // handle being unguessable, not from this branch.
170
+ if (!credential.isActive) {
171
+ throw new UnauthorizedException(ERROR_MESSAGES.MPIN_REVOKED);
172
+ }
173
+
174
+ // Checked before the hash comparison, so attempts made during a lockout
175
+ // neither extend it nor cost a bcrypt call.
176
+ this.assertNotLocked(credential);
177
+
178
+ const matches = await this.hashingService.compare(
179
+ dto.mpin,
180
+ credential.hashedMpin,
181
+ credential.mpinSchemeVersion,
182
+ );
183
+
184
+ if (!matches) {
185
+ throw await this.registerFailedAttempt(credential);
186
+ }
187
+
188
+ credential.failedAttempts = 0;
189
+ credential.lockedUntil = null;
190
+ credential.lastUsedAt = new Date();
191
+ if (this.hashingService.needsRehash(credential.hashedMpin, credential.mpinSchemeVersion)) {
192
+ credential.hashedMpin = await this.hashingService.hash(dto.mpin);
193
+ credential.mpinScheme = this.hashingService.name();
194
+ credential.mpinSchemeVersion = this.hashingService.currentVersion();
195
+ }
196
+ await this.credentialRepository.save(credential);
197
+
198
+ const user = credential.user;
199
+ await this.userActivityHistoryService.logEvent('login', user);
200
+
201
+ // A fresh token pair - never a replay of a stored refresh token. This
202
+ // is what makes MPIN survive logout and refresh-token expiry. The
203
+ // stable deviceId keeps repeat logins in one session bucket.
204
+ const tokens = await this.authenticationService.generateTokens(user, credential.deviceId);
205
+
206
+ return {
207
+ user: {
208
+ id: user.id,
209
+ email: user.email,
210
+ mobile: user.mobile,
211
+ username: user.username,
212
+ forcePasswordChange: user.forcePasswordChange,
213
+ roles: (user.roles ?? []).map((role) => role.name),
214
+ },
215
+ ...tokens,
216
+ };
217
+ }
218
+
219
+ // ----------------------------------------------------- device management
220
+
221
+ async listDevices(activeUser: ActiveUserData) {
222
+ this.assertEnabled();
223
+
224
+ const credentials = await this.credentialRepository.find({
225
+ where: { user: { id: activeUser.sub }, isActive: true },
226
+ });
227
+
228
+ return credentials
229
+ .sort((a, b) => this.lastUsedMillis(b) - this.lastUsedMillis(a))
230
+ .map((credential) => this.toDeviceView(credential));
231
+ }
232
+
233
+ async revokeDevice(activeUser: ActiveUserData, id: number) {
234
+ this.assertEnabled();
235
+
236
+ const credential = await this.credentialRepository.findOne({
237
+ where: { id, user: { id: activeUser.sub } },
238
+ });
239
+ if (!credential) {
240
+ throw new NotFoundException(ERROR_MESSAGES.MPIN_CREDENTIAL_NOT_FOUND);
241
+ }
242
+
243
+ // Soft-delete. The row survives so a correct MPIN can be answered with
244
+ // MPIN_REVOKED rather than a baffling MPIN_INVALID; only active rows
245
+ // count towards the device cap.
246
+ credential.isActive = false;
247
+ await this.credentialRepository.save(credential);
248
+
249
+ return { message: SUCCESS_MESSAGES.MPIN_DEVICE_REVOKED };
250
+ }
251
+
252
+ // -------------------------------------------------------------- internals
253
+
254
+ /**
255
+ * Never exposes the stored hashes, independently of whatever serialiser
256
+ * configuration a consuming app runs. The entity also withholds them via
257
+ * @Exclude()/@Expose(), but building the response explicitly means a
258
+ * carelessly added decorator cannot leak a credential.
259
+ */
260
+ private toDeviceView(credential: UserDeviceCredential) {
261
+ return {
262
+ id: credential.id,
263
+ deviceId: credential.deviceId,
264
+ deviceName: credential.deviceName,
265
+ platform: credential.platform,
266
+ isActive: credential.isActive,
267
+ failedAttempts: credential.failedAttempts,
268
+ lockedUntil: credential.lockedUntil,
269
+ lastUsedAt: credential.lastUsedAt,
270
+ createdAt: credential.createdAt,
271
+ };
272
+ }
273
+
274
+ private assertEnabled(): void {
275
+ if (!this.settingService.getConfigValue<SolidCoreSetting>('mpinEnabled')) {
276
+ // 404 rather than 403: a disabled feature should look absent.
277
+ throw new NotFoundException(ERROR_MESSAGES.MPIN_NOT_ENABLED);
278
+ }
279
+ }
280
+
281
+ private assertNotLocked(credential: UserDeviceCredential): void {
282
+ if (credential.lockedUntil && credential.lockedUntil.getTime() > Date.now()) {
283
+ throw new UnauthorizedException(ERROR_MESSAGES.MPIN_LOCKED);
284
+ }
285
+ }
286
+
287
+ private assertMpinAcceptable(mpin: string): void {
288
+ const pattern = String(
289
+ this.settingService.getConfigValue<SolidCoreSetting>('mpinRegex') ?? '^\\d{4,6}$',
290
+ );
291
+
292
+ // Fails closed. `matches` starts false and a malformed pattern - the
293
+ // setting is admin-editable free text - becomes a rejection rather
294
+ // than a skipped check, so a typo cannot silently let users set an
295
+ // MPIN weaker than policy allows.
296
+ let matches = false;
297
+ try {
298
+ matches = new RegExp(pattern).test(mpin);
299
+ } catch {
300
+ this.logger.error(`Invalid mpinRegex setting: ${pattern}`);
301
+ throw new BadRequestException(ERROR_MESSAGES.MPIN_FORMAT_INVALID);
302
+ }
303
+
304
+ if (!matches) {
305
+ throw new BadRequestException(ERROR_MESSAGES.MPIN_FORMAT_INVALID);
306
+ }
307
+
308
+ // Predictability is semantics, not shape - a regex expressing "not
309
+ // sequential" would be unreadable and easy to get subtly wrong.
310
+ if (this.isPredictable(mpin)) {
311
+ // Safe to distinguish, and 400 rather than 401: this only ever
312
+ // happens on a bearer-authenticated route, so it is a validation
313
+ // failure rather than an authentication one.
314
+ throw new BadRequestException(ERROR_MESSAGES.MPIN_TOO_PREDICTABLE);
315
+ }
316
+ }
317
+
318
+ private isPredictable(mpin: string): boolean {
319
+ if (/^(\d)\1+$/.test(mpin)) {
320
+ return true;
321
+ }
322
+ if (this.isSequential(mpin)) {
323
+ return true;
324
+ }
325
+ return MPIN_DENYLIST.has(mpin);
326
+ }
327
+
328
+ private isSequential(mpin: string): boolean {
329
+ let ascending = true;
330
+ let descending = true;
331
+ for (let i = 1; i < mpin.length; i++) {
332
+ const step = mpin.charCodeAt(i) - mpin.charCodeAt(i - 1);
333
+ if (step !== 1) ascending = false;
334
+ if (step !== -1) descending = false;
335
+ }
336
+ return ascending || descending;
337
+ }
338
+
339
+ /**
340
+ * Records a failure and escalates. `failedAttempts` is cumulative and
341
+ * resets only on success - never when a lockout expires - which is what
342
+ * makes the second threshold detectable without a separate counter.
343
+ *
344
+ * Returns the exception for the caller to throw, so the save is always
345
+ * awaited before the response leaves.
346
+ */
347
+ private async registerFailedAttempt(
348
+ credential: UserDeviceCredential,
349
+ ): Promise<UnauthorizedException> {
350
+ const lockThreshold = Number(
351
+ this.settingService.getConfigValue<SolidCoreSetting>('mpinMaxFailedAttempts'),
352
+ );
353
+ const deactivateThreshold = Number(
354
+ this.settingService.getConfigValue<SolidCoreSetting>('mpinMaxTotalFailedAttempts'),
355
+ );
356
+ const lockoutSeconds = Number(
357
+ this.settingService.getConfigValue<SolidCoreSetting>('mpinLockoutDuration'),
358
+ );
359
+
360
+ credential.failedAttempts += 1;
361
+ let error = new UnauthorizedException(ERROR_MESSAGES.MPIN_INVALID);
362
+
363
+ if (credential.isActive && deactivateThreshold > 0 && credential.failedAttempts >= deactivateThreshold) {
364
+ credential.isActive = false;
365
+ credential.lockedUntil = null;
366
+ // This attempt caused the deactivation, so it is reported. The
367
+ // caller reached the limit on a handle they demonstrably hold and
368
+ // caused this state themselves, so nothing is disclosed - and
369
+ // without it they would keep retrying a permanently dead credential.
370
+ error = new UnauthorizedException(ERROR_MESSAGES.MPIN_REVOKED);
371
+ } else if (credential.isActive && lockThreshold > 0 && credential.failedAttempts % lockThreshold === 0) {
372
+ credential.lockedUntil = new Date(Date.now() + lockoutSeconds * 1000);
373
+ error = new UnauthorizedException(ERROR_MESSAGES.MPIN_LOCKED);
374
+ }
375
+
376
+ await this.credentialRepository.save(credential);
377
+ return error;
378
+ }
379
+
380
+ /**
381
+ * The cap evicts rather than rejects. A reinstall wipes the client's
382
+ * deviceId, so the app generates a new one and setup adds a row instead of
383
+ * replacing the old, orphaned one. Rejecting at the cap would mean a user
384
+ * who reinstalls a few times could never set MPIN up again.
385
+ */
386
+ private async evictOldestIfAtCap(userId: number, excludeId?: number): Promise<void> {
387
+ const cap = Number(
388
+ this.settingService.getConfigValue<SolidCoreSetting>('mpinMaxDevicesPerUser'),
389
+ );
390
+ if (!cap || cap <= 0) {
391
+ return;
392
+ }
393
+
394
+ const active = (await this.credentialRepository.find({
395
+ where: { user: { id: userId }, isActive: true },
396
+ })).filter((credential) => credential.id !== excludeId);
397
+
398
+ // One slot is about to be taken by the credential being written.
399
+ const overflow = active.length - (cap - 1);
400
+ if (overflow <= 0) {
401
+ return;
402
+ }
403
+
404
+ // Sorted in JS rather than SQL because a never-used credential has a
405
+ // null lastUsedAt, and Postgres sorts nulls last on ASC - which would
406
+ // treat the least-used rows as the most recently used.
407
+ const doomed = active
408
+ .sort((a, b) => this.lastUsedMillis(a) - this.lastUsedMillis(b))
409
+ .slice(0, overflow);
410
+
411
+ for (const credential of doomed) {
412
+ credential.isActive = false;
413
+ }
414
+ await this.credentialRepository.save(doomed);
415
+ this.logger.log(
416
+ `Evicted ${doomed.length} MPIN credential(s) for user ${userId} at the device cap of ${cap}`,
417
+ );
418
+ }
419
+
420
+ private lastUsedMillis(credential: UserDeviceCredential): number {
421
+ return credential.lastUsedAt?.getTime() ?? credential.createdAt?.getTime() ?? 0;
422
+ }
423
+
424
+ /**
425
+ * `<username>-<deviceId>`, the model's natural key.
426
+ *
427
+ * Not slugified: lodash `kebabCase` splits digit/letter boundaries, so it
428
+ * would shred a UUID into `3-f-7-b-8-a-10-...`. The username is lower-cased
429
+ * and stripped of whitespace, which is enough for a key that is only ever
430
+ * read, never parsed back into its parts.
431
+ */
432
+ private buildCredentialKey(username: string, deviceId: string): string {
433
+ const normalizedUsername = (username ?? '').trim().toLowerCase().replace(/\s+/g, '-');
434
+ return `${normalizedUsername}-${deviceId}`;
435
+ }
436
+
437
+ private mintCredentialRef(): string {
438
+ return randomBytes(32).toString('hex');
439
+ }
440
+
441
+ // SHA-256, not bcrypt: 256 bits of randomness has nothing to brute-force,
442
+ // so a slow hash would only add latency to every login. Same reasoning as
443
+ // ApiKeyService.hash.
444
+ private hashCredentialRef(credentialRef: string): string {
445
+ return createHash('sha256').update(credentialRef).digest('hex');
446
+ }
447
+ }
@@ -0,0 +1,10 @@
1
+ // Some filter/sort DTOs arrive with bracket-wrapped keys (e.g. "[isPublished]") from
2
+ // query-string parsing. Both CrudHelperService and DraftPublishHelperService need to
3
+ // strip that wrapping before reading keys, so it lives here as a shared utility.
4
+ export function normalizeObjectKeys(obj: any): any {
5
+ return Object.keys(obj).reduce((acc, key) => {
6
+ const newKey = key.replace(/^\[(.*)\]$/, '$1');
7
+ acc[newKey] = obj[key];
8
+ return acc;
9
+ }, {});
10
+ }