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

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 (512) hide show
  1. package/CHANGELOG.md +9517 -0
  2. package/README.md +19 -0
  3. package/dist/commands/run-tests.command.d.ts +9 -1
  4. package/dist/commands/run-tests.command.d.ts.map +1 -1
  5. package/dist/commands/run-tests.command.js +69 -6
  6. package/dist/commands/run-tests.command.js.map +1 -1
  7. package/dist/commands/seed.command.d.ts +2 -0
  8. package/dist/commands/seed.command.d.ts.map +1 -1
  9. package/dist/commands/seed.command.js +21 -0
  10. package/dist/commands/seed.command.js.map +1 -1
  11. package/dist/config/cache.options.d.ts.map +1 -1
  12. package/dist/config/cache.options.js +2 -2
  13. package/dist/config/cache.options.js.map +1 -1
  14. package/dist/constants/chatter-message.constants.d.ts +1 -0
  15. package/dist/constants/chatter-message.constants.d.ts.map +1 -1
  16. package/dist/constants/chatter-message.constants.js +3 -1
  17. package/dist/constants/chatter-message.constants.js.map +1 -1
  18. package/dist/constants/draft-publish-fields.d.ts +2 -0
  19. package/dist/constants/draft-publish-fields.d.ts.map +1 -0
  20. package/dist/constants/draft-publish-fields.js +5 -0
  21. package/dist/constants/draft-publish-fields.js.map +1 -0
  22. package/dist/constants/error-messages.d.ts +7 -0
  23. package/dist/constants/error-messages.d.ts.map +1 -1
  24. package/dist/constants/error-messages.js +7 -0
  25. package/dist/constants/error-messages.js.map +1 -1
  26. package/dist/constants/media-file-types.d.ts +12 -0
  27. package/dist/constants/media-file-types.d.ts.map +1 -1
  28. package/dist/constants/media-file-types.js +65 -1
  29. package/dist/constants/media-file-types.js.map +1 -1
  30. package/dist/constants/success-messages.d.ts +3 -0
  31. package/dist/constants/success-messages.d.ts.map +1 -1
  32. package/dist/constants/success-messages.js +3 -0
  33. package/dist/constants/success-messages.js.map +1 -1
  34. package/dist/constants/user.constants.d.ts +2 -0
  35. package/dist/constants/user.constants.d.ts.map +1 -0
  36. package/dist/constants/user.constants.js +5 -0
  37. package/dist/constants/user.constants.js.map +1 -0
  38. package/dist/controllers/action-metadata.controller.d.ts +1 -1
  39. package/dist/controllers/action-metadata.controller.js +1 -1
  40. package/dist/controllers/agent-event.controller.d.ts +1 -1
  41. package/dist/controllers/agent-event.controller.js +1 -1
  42. package/dist/controllers/agent-session.controller.d.ts +1 -1
  43. package/dist/controllers/agent-session.controller.js +1 -1
  44. package/dist/controllers/authentication.controller.d.ts +1 -1
  45. package/dist/controllers/authentication.controller.d.ts.map +1 -1
  46. package/dist/controllers/authentication.controller.js +4 -3
  47. package/dist/controllers/authentication.controller.js.map +1 -1
  48. package/dist/controllers/chatter-message.controller.d.ts +5 -0
  49. package/dist/controllers/chatter-message.controller.d.ts.map +1 -1
  50. package/dist/controllers/chatter-message.controller.js +14 -0
  51. package/dist/controllers/chatter-message.controller.js.map +1 -1
  52. package/dist/controllers/dashboard-user-layout.controller.d.ts +1 -1
  53. package/dist/controllers/dashboard-user-layout.controller.js +1 -1
  54. package/dist/controllers/email-template.controller.d.ts +1 -1
  55. package/dist/controllers/email-template.controller.js +1 -1
  56. package/dist/controllers/export-template.controller.d.ts +1 -1
  57. package/dist/controllers/export-template.controller.d.ts.map +1 -1
  58. package/dist/controllers/export-template.controller.js +6 -4
  59. package/dist/controllers/export-template.controller.js.map +1 -1
  60. package/dist/controllers/export-transaction.controller.d.ts +1 -1
  61. package/dist/controllers/export-transaction.controller.js +1 -1
  62. package/dist/controllers/field-metadata.controller.d.ts +2 -2
  63. package/dist/controllers/import-transaction-error-log.controller.d.ts +1 -1
  64. package/dist/controllers/import-transaction-error-log.controller.js +1 -1
  65. package/dist/controllers/import-transaction.controller.d.ts +1 -1
  66. package/dist/controllers/import-transaction.controller.d.ts.map +1 -1
  67. package/dist/controllers/import-transaction.controller.js +7 -7
  68. package/dist/controllers/import-transaction.controller.js.map +1 -1
  69. package/dist/controllers/list-of-values.controller.d.ts +1 -1
  70. package/dist/controllers/list-of-values.controller.js +1 -1
  71. package/dist/controllers/locale.controller.d.ts +1 -1
  72. package/dist/controllers/locale.controller.js +1 -1
  73. package/dist/controllers/mcp-audit-log.controller.d.ts +1 -1
  74. package/dist/controllers/mcp-audit-log.controller.js +1 -1
  75. package/dist/controllers/media.controller.d.ts +2 -2
  76. package/dist/controllers/media.controller.d.ts.map +1 -1
  77. package/dist/controllers/media.controller.js +13 -8
  78. package/dist/controllers/media.controller.js.map +1 -1
  79. package/dist/controllers/menu-item-metadata.controller.d.ts +1 -1
  80. package/dist/controllers/menu-item-metadata.controller.js +1 -1
  81. package/dist/controllers/model-sequence.controller.d.ts +1 -1
  82. package/dist/controllers/model-sequence.controller.js +1 -1
  83. package/dist/controllers/module-package.controller.d.ts.map +1 -1
  84. package/dist/controllers/module-package.controller.js +2 -3
  85. package/dist/controllers/module-package.controller.js.map +1 -1
  86. package/dist/controllers/mpin-authentication.controller.d.ts +44 -0
  87. package/dist/controllers/mpin-authentication.controller.d.ts.map +1 -0
  88. package/dist/controllers/mpin-authentication.controller.js +102 -0
  89. package/dist/controllers/mpin-authentication.controller.js.map +1 -0
  90. package/dist/controllers/mq-message-queue.controller.d.ts +1 -1
  91. package/dist/controllers/mq-message-queue.controller.js +1 -1
  92. package/dist/controllers/mq-message.controller.d.ts +1 -1
  93. package/dist/controllers/mq-message.controller.js +1 -1
  94. package/dist/controllers/permission-metadata.controller.d.ts +1 -1
  95. package/dist/controllers/permission-metadata.controller.js +1 -1
  96. package/dist/controllers/role-metadata.controller.d.ts +1 -1
  97. package/dist/controllers/role-metadata.controller.js +1 -1
  98. package/dist/controllers/saved-filters.controller.d.ts +1 -1
  99. package/dist/controllers/saved-filters.controller.js +1 -1
  100. package/dist/controllers/scheduled-job.controller.d.ts +1 -1
  101. package/dist/controllers/scheduled-job.controller.js +1 -1
  102. package/dist/controllers/security-rule.controller.d.ts +1 -1
  103. package/dist/controllers/security-rule.controller.js +1 -1
  104. package/dist/controllers/sms-template.controller.d.ts +1 -1
  105. package/dist/controllers/sms-template.controller.js +1 -1
  106. package/dist/controllers/user-activity-history.controller.d.ts +1 -1
  107. package/dist/controllers/user-activity-history.controller.js +1 -1
  108. package/dist/controllers/user-view-metadata.controller.d.ts +1 -1
  109. package/dist/controllers/user-view-metadata.controller.js +1 -1
  110. package/dist/controllers/user.controller.d.ts +2 -2
  111. package/dist/controllers/user.controller.js +2 -2
  112. package/dist/controllers/view-metadata.controller.d.ts +2 -2
  113. package/dist/controllers/view-metadata.controller.js +1 -1
  114. package/dist/controllers/workflow-definition.controller.d.ts +1 -1
  115. package/dist/controllers/workflow-definition.controller.js +1 -1
  116. package/dist/controllers/workflow-execution-artifact.controller.d.ts +1 -1
  117. package/dist/controllers/workflow-execution-artifact.controller.js +1 -1
  118. package/dist/controllers/workflow-execution-log.controller.d.ts +1 -1
  119. package/dist/controllers/workflow-execution-log.controller.js +1 -1
  120. package/dist/controllers/workflow-execution.controller.d.ts +1 -1
  121. package/dist/controllers/workflow-execution.controller.js +1 -1
  122. package/dist/controllers/workflow-secret.controller.d.ts +1 -1
  123. package/dist/controllers/workflow-secret.controller.js +1 -1
  124. package/dist/controllers/workflow-step-execution.controller.d.ts +1 -1
  125. package/dist/controllers/workflow-step-execution.controller.js +1 -1
  126. package/dist/controllers/workflow-trigger-execution.controller.d.ts +1 -1
  127. package/dist/controllers/workflow-trigger-execution.controller.js +1 -1
  128. package/dist/dtos/basic-filters.dto.d.ts +0 -1
  129. package/dist/dtos/basic-filters.dto.d.ts.map +1 -1
  130. package/dist/dtos/basic-filters.dto.js +1 -6
  131. package/dist/dtos/basic-filters.dto.js.map +1 -1
  132. package/dist/dtos/change-mpin.dto.d.ts +6 -0
  133. package/dist/dtos/change-mpin.dto.d.ts.map +1 -0
  134. package/dist/dtos/change-mpin.dto.js +39 -0
  135. package/dist/dtos/change-mpin.dto.js.map +1 -0
  136. package/dist/dtos/create-field-metadata.dto.d.ts +2 -6
  137. package/dist/dtos/create-field-metadata.dto.d.ts.map +1 -1
  138. package/dist/dtos/create-field-metadata.dto.js +4 -10
  139. package/dist/dtos/create-field-metadata.dto.js.map +1 -1
  140. package/dist/dtos/create-model-sequence.dto.d.ts +1 -1
  141. package/dist/dtos/create-model-sequence.dto.d.ts.map +1 -1
  142. package/dist/dtos/create-model-sequence.dto.js +1 -1
  143. package/dist/dtos/create-model-sequence.dto.js.map +1 -1
  144. package/dist/dtos/mpin-login.dto.d.ts +5 -0
  145. package/dist/dtos/mpin-login.dto.d.ts.map +1 -0
  146. package/dist/dtos/mpin-login.dto.js +33 -0
  147. package/dist/dtos/mpin-login.dto.js.map +1 -0
  148. package/dist/dtos/setup-mpin.dto.d.ts +7 -0
  149. package/dist/dtos/setup-mpin.dto.d.ts.map +1 -0
  150. package/dist/dtos/setup-mpin.dto.js +47 -0
  151. package/dist/dtos/setup-mpin.dto.js.map +1 -0
  152. package/dist/entities/common.entity.d.ts +4 -0
  153. package/dist/entities/common.entity.d.ts.map +1 -1
  154. package/dist/entities/common.entity.js +25 -1
  155. package/dist/entities/common.entity.js.map +1 -1
  156. package/dist/entities/dashboard-user-layout.entity.js +2 -2
  157. package/dist/entities/dashboard-user-layout.entity.js.map +1 -1
  158. package/dist/entities/saved-filters.entity.js +3 -3
  159. package/dist/entities/saved-filters.entity.js.map +1 -1
  160. package/dist/entities/user-device-credential.entity.d.ts +18 -0
  161. package/dist/entities/user-device-credential.entity.d.ts.map +1 -0
  162. package/dist/entities/user-device-credential.entity.js +91 -0
  163. package/dist/entities/user-device-credential.entity.js.map +1 -0
  164. package/dist/entities/user.entity.d.ts.map +1 -1
  165. package/dist/entities/user.entity.js +36 -0
  166. package/dist/entities/user.entity.js.map +1 -1
  167. package/dist/entities/view-metadata.entity.js.map +1 -1
  168. package/dist/entities/workflow-execution-artifact.entity.d.ts.map +1 -1
  169. package/dist/entities/workflow-execution-artifact.entity.js +4 -3
  170. package/dist/entities/workflow-execution-artifact.entity.js.map +1 -1
  171. package/dist/entities/workflow-execution-log.entity.d.ts.map +1 -1
  172. package/dist/entities/workflow-execution-log.entity.js +4 -3
  173. package/dist/entities/workflow-execution-log.entity.js.map +1 -1
  174. package/dist/entities/workflow-execution.entity.d.ts.map +1 -1
  175. package/dist/entities/workflow-execution.entity.js +3 -2
  176. package/dist/entities/workflow-execution.entity.js.map +1 -1
  177. package/dist/entities/workflow-secret.entity.d.ts.map +1 -1
  178. package/dist/entities/workflow-secret.entity.js +3 -2
  179. package/dist/entities/workflow-secret.entity.js.map +1 -1
  180. package/dist/entities/workflow-step-execution.entity.d.ts.map +1 -1
  181. package/dist/entities/workflow-step-execution.entity.js +3 -2
  182. package/dist/entities/workflow-step-execution.entity.js.map +1 -1
  183. package/dist/entities/workflow-trigger-execution.entity.d.ts.map +1 -1
  184. package/dist/entities/workflow-trigger-execution.entity.js +7 -6
  185. package/dist/entities/workflow-trigger-execution.entity.js.map +1 -1
  186. package/dist/factories/mail.factory.d.ts.map +1 -1
  187. package/dist/factories/mail.factory.js +4 -0
  188. package/dist/factories/mail.factory.js.map +1 -1
  189. package/dist/filters/http-exception.filter.d.ts.map +1 -1
  190. package/dist/filters/http-exception.filter.js +5 -1
  191. package/dist/filters/http-exception.filter.js.map +1 -1
  192. package/dist/helpers/bootstrap.helper.d.ts +5 -2
  193. package/dist/helpers/bootstrap.helper.d.ts.map +1 -1
  194. package/dist/helpers/bootstrap.helper.js +7 -3
  195. package/dist/helpers/bootstrap.helper.js.map +1 -1
  196. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts +1 -0
  197. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts.map +1 -1
  198. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js +18 -11
  199. package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js.map +1 -1
  200. package/dist/helpers/file-download.helper.d.ts +9 -0
  201. package/dist/helpers/file-download.helper.d.ts.map +1 -0
  202. package/dist/helpers/file-download.helper.js +19 -0
  203. package/dist/helpers/file-download.helper.js.map +1 -0
  204. package/dist/helpers/model-metadata-helper.service.d.ts.map +1 -1
  205. package/dist/helpers/model-metadata-helper.service.js +30 -1
  206. package/dist/helpers/model-metadata-helper.service.js.map +1 -1
  207. package/dist/helpers/security.helper.d.ts +5 -1
  208. package/dist/helpers/security.helper.d.ts.map +1 -1
  209. package/dist/helpers/security.helper.js +15 -4
  210. package/dist/helpers/security.helper.js.map +1 -1
  211. package/dist/helpers/solid-core-error-codes-provider.service.d.ts.map +1 -1
  212. package/dist/helpers/solid-core-error-codes-provider.service.js +45 -0
  213. package/dist/helpers/solid-core-error-codes-provider.service.js.map +1 -1
  214. package/dist/helpers/solid-microservice-adapter.service.d.ts.map +1 -1
  215. package/dist/helpers/solid-microservice-adapter.service.js +1 -1
  216. package/dist/helpers/solid-microservice-adapter.service.js.map +1 -1
  217. package/dist/helpers/solid-registry.js +1 -1
  218. package/dist/helpers/solid-registry.js.map +1 -1
  219. package/dist/helpers/typeorm-db-helper.d.ts.map +1 -1
  220. package/dist/helpers/typeorm-db-helper.js +9 -0
  221. package/dist/helpers/typeorm-db-helper.js.map +1 -1
  222. package/dist/index.d.ts +10 -0
  223. package/dist/index.d.ts.map +1 -1
  224. package/dist/index.js +10 -0
  225. package/dist/index.js.map +1 -1
  226. package/dist/interfaces/active-user-data.interface.d.ts +1 -0
  227. package/dist/interfaces/active-user-data.interface.d.ts.map +1 -1
  228. package/dist/interfaces/active-user-data.interface.js.map +1 -1
  229. package/dist/interfaces.d.ts +1 -0
  230. package/dist/interfaces.d.ts.map +1 -1
  231. package/dist/interfaces.js.map +1 -1
  232. package/dist/repository/user-device-credential.repository.d.ts +12 -0
  233. package/dist/repository/user-device-credential.repository.d.ts.map +1 -0
  234. package/dist/repository/user-device-credential.repository.js +34 -0
  235. package/dist/repository/user-device-credential.repository.js.map +1 -0
  236. package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
  237. package/dist/seeders/module-metadata-seeder.service.js +51 -30
  238. package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
  239. package/dist/seeders/module-test-data.service.d.ts +3 -1
  240. package/dist/seeders/module-test-data.service.d.ts.map +1 -1
  241. package/dist/seeders/module-test-data.service.js +8 -2
  242. package/dist/seeders/module-test-data.service.js.map +1 -1
  243. package/dist/seeders/seed-data/solid-core-metadata.json +290 -78
  244. package/dist/services/api-key.service.d.ts.map +1 -1
  245. package/dist/services/api-key.service.js +1 -1
  246. package/dist/services/api-key.service.js.map +1 -1
  247. package/dist/services/authentication.service.d.ts +9 -5
  248. package/dist/services/authentication.service.d.ts.map +1 -1
  249. package/dist/services/authentication.service.js +102 -23
  250. package/dist/services/authentication.service.js.map +1 -1
  251. package/dist/services/chatter-message.service.d.ts +15 -1
  252. package/dist/services/chatter-message.service.d.ts.map +1 -1
  253. package/dist/services/chatter-message.service.js +56 -4
  254. package/dist/services/chatter-message.service.js.map +1 -1
  255. package/dist/services/crud-helper.service.d.ts +6 -4
  256. package/dist/services/crud-helper.service.d.ts.map +1 -1
  257. package/dist/services/crud-helper.service.js +32 -48
  258. package/dist/services/crud-helper.service.js.map +1 -1
  259. package/dist/services/crud.service.d.ts +13 -1
  260. package/dist/services/crud.service.d.ts.map +1 -1
  261. package/dist/services/crud.service.js +93 -68
  262. package/dist/services/crud.service.js.map +1 -1
  263. package/dist/services/datasource-introspection.service.d.ts.map +1 -1
  264. package/dist/services/datasource-introspection.service.js.map +1 -1
  265. package/dist/services/draft-publish-helper.service.d.ts +53 -0
  266. package/dist/services/draft-publish-helper.service.d.ts.map +1 -0
  267. package/dist/services/draft-publish-helper.service.js +402 -0
  268. package/dist/services/draft-publish-helper.service.js.map +1 -0
  269. package/dist/services/field-metadata.service.d.ts +2 -2
  270. package/dist/services/field-metadata.service.d.ts.map +1 -1
  271. package/dist/services/field-metadata.service.js +2 -1
  272. package/dist/services/field-metadata.service.js.map +1 -1
  273. package/dist/services/internationalisation-helper.service.d.ts +19 -0
  274. package/dist/services/internationalisation-helper.service.d.ts.map +1 -0
  275. package/dist/services/internationalisation-helper.service.js +107 -0
  276. package/dist/services/internationalisation-helper.service.js.map +1 -0
  277. package/dist/services/locale.service.d.ts +1 -1
  278. package/dist/services/media-storage.utils.d.ts +1 -0
  279. package/dist/services/media-storage.utils.d.ts.map +1 -1
  280. package/dist/services/media-storage.utils.js +12 -1
  281. package/dist/services/media-storage.utils.js.map +1 -1
  282. package/dist/services/media.service.d.ts +3 -2
  283. package/dist/services/media.service.d.ts.map +1 -1
  284. package/dist/services/media.service.js +44 -29
  285. package/dist/services/media.service.js.map +1 -1
  286. package/dist/services/metadata-validation.service.d.ts +7 -0
  287. package/dist/services/metadata-validation.service.d.ts.map +1 -0
  288. package/dist/services/metadata-validation.service.js +42 -0
  289. package/dist/services/metadata-validation.service.js.map +1 -0
  290. package/dist/services/model-metadata.service.d.ts.map +1 -1
  291. package/dist/services/model-metadata.service.js +2 -1
  292. package/dist/services/model-metadata.service.js.map +1 -1
  293. package/dist/services/module-metadata.service.d.ts.map +1 -1
  294. package/dist/services/module-metadata.service.js +2 -1
  295. package/dist/services/module-metadata.service.js.map +1 -1
  296. package/dist/services/mpin.service.d.ts +67 -0
  297. package/dist/services/mpin.service.d.ts.map +1 -0
  298. package/dist/services/mpin.service.js +282 -0
  299. package/dist/services/mpin.service.js.map +1 -0
  300. package/dist/services/object.utils.d.ts +2 -0
  301. package/dist/services/object.utils.d.ts.map +1 -0
  302. package/dist/services/object.utils.js +11 -0
  303. package/dist/services/object.utils.js.map +1 -0
  304. package/dist/services/refresh-token-ids-storage.service.d.ts +25 -6
  305. package/dist/services/refresh-token-ids-storage.service.d.ts.map +1 -1
  306. package/dist/services/refresh-token-ids-storage.service.js +80 -42
  307. package/dist/services/refresh-token-ids-storage.service.js.map +1 -1
  308. package/dist/services/setting.service.d.ts.map +1 -1
  309. package/dist/services/setting.service.js +2 -1
  310. package/dist/services/setting.service.js.map +1 -1
  311. package/dist/services/settings/default-settings-provider.service.d.ts +228 -12
  312. package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
  313. package/dist/services/settings/default-settings-provider.service.js +129 -6
  314. package/dist/services/settings/default-settings-provider.service.js.map +1 -1
  315. package/dist/services/user.service.d.ts +2 -2
  316. package/dist/services/user.service.d.ts.map +1 -1
  317. package/dist/services/user.service.js +9 -3
  318. package/dist/services/user.service.js.map +1 -1
  319. package/dist/services/view-metadata.service.d.ts +1 -2
  320. package/dist/services/view-metadata.service.d.ts.map +1 -1
  321. package/dist/services/view-metadata.service.js +3 -55
  322. package/dist/services/view-metadata.service.js.map +1 -1
  323. package/dist/services/workflow/nodes/execution-fail.node.d.ts +6 -0
  324. package/dist/services/workflow/nodes/execution-fail.node.d.ts.map +1 -0
  325. package/dist/services/workflow/nodes/execution-fail.node.js +81 -0
  326. package/dist/services/workflow/nodes/execution-fail.node.js.map +1 -0
  327. package/dist/services/workflow/nodes/for-each.node.d.ts +2 -0
  328. package/dist/services/workflow/nodes/for-each.node.d.ts.map +1 -1
  329. package/dist/services/workflow/nodes/for-each.node.js +114 -5
  330. package/dist/services/workflow/nodes/for-each.node.js.map +1 -1
  331. package/dist/services/workflow/nodes/if.node.d.ts.map +1 -1
  332. package/dist/services/workflow/nodes/if.node.js +7 -0
  333. package/dist/services/workflow/nodes/if.node.js.map +1 -1
  334. package/dist/services/workflow/nodes/log-write.node.d.ts.map +1 -1
  335. package/dist/services/workflow/nodes/log-write.node.js +2 -0
  336. package/dist/services/workflow/nodes/log-write.node.js.map +1 -1
  337. package/dist/services/workflow/nodes/loop-until.node.d.ts +12 -0
  338. package/dist/services/workflow/nodes/loop-until.node.d.ts.map +1 -0
  339. package/dist/services/workflow/nodes/loop-until.node.js +281 -0
  340. package/dist/services/workflow/nodes/loop-until.node.js.map +1 -0
  341. package/dist/services/workflow/nodes/parallel.node.d.ts.map +1 -1
  342. package/dist/services/workflow/nodes/parallel.node.js +7 -0
  343. package/dist/services/workflow/nodes/parallel.node.js.map +1 -1
  344. package/dist/services/workflow/nodes/sequential.node.d.ts.map +1 -1
  345. package/dist/services/workflow/nodes/sequential.node.js +7 -0
  346. package/dist/services/workflow/nodes/sequential.node.js.map +1 -1
  347. package/dist/services/workflow/nodes/switch.node.d.ts.map +1 -1
  348. package/dist/services/workflow/nodes/switch.node.js +7 -0
  349. package/dist/services/workflow/nodes/switch.node.js.map +1 -1
  350. package/dist/services/workflow/workflow-definition-validator.service.d.ts.map +1 -1
  351. package/dist/services/workflow/workflow-definition-validator.service.js +5 -2
  352. package/dist/services/workflow/workflow-definition-validator.service.js.map +1 -1
  353. package/dist/services/workflow/workflow-expression.service.d.ts.map +1 -1
  354. package/dist/services/workflow/workflow-expression.service.js +5 -0
  355. package/dist/services/workflow/workflow-expression.service.js.map +1 -1
  356. package/dist/services/workflow/workflow-node-registry.service.d.ts +2 -1
  357. package/dist/services/workflow/workflow-node-registry.service.d.ts.map +1 -1
  358. package/dist/services/workflow/workflow-node-registry.service.js +7 -0
  359. package/dist/services/workflow/workflow-node-registry.service.js.map +1 -1
  360. package/dist/services/workflow/workflow-runtime.service.d.ts +8 -0
  361. package/dist/services/workflow/workflow-runtime.service.d.ts.map +1 -1
  362. package/dist/services/workflow/workflow-runtime.service.js +197 -32
  363. package/dist/services/workflow/workflow-runtime.service.js.map +1 -1
  364. package/dist/services/workflow-definition.service.d.ts +1 -1
  365. package/dist/solid-core.module.d.ts +1 -3
  366. package/dist/solid-core.module.d.ts.map +1 -1
  367. package/dist/solid-core.module.js +22 -40
  368. package/dist/solid-core.module.js.map +1 -1
  369. package/dist/testing/adapters/ui/playwright-adapter.d.ts +5 -0
  370. package/dist/testing/adapters/ui/playwright-adapter.d.ts.map +1 -1
  371. package/dist/testing/adapters/ui/playwright-adapter.js +17 -1
  372. package/dist/testing/adapters/ui/playwright-adapter.js.map +1 -1
  373. package/dist/testing/adapters/ui/ui.types.d.ts +2 -0
  374. package/dist/testing/adapters/ui/ui.types.d.ts.map +1 -1
  375. package/dist/testing/adapters/ui/ui.types.js.map +1 -1
  376. package/dist/testing/reporter/console-reporter.d.ts +3 -0
  377. package/dist/testing/reporter/console-reporter.d.ts.map +1 -1
  378. package/dist/testing/reporter/console-reporter.js +2 -0
  379. package/dist/testing/reporter/console-reporter.js.map +1 -1
  380. package/dist/testing/reporter/progress-reporter.d.ts +64 -0
  381. package/dist/testing/reporter/progress-reporter.d.ts.map +1 -0
  382. package/dist/testing/reporter/progress-reporter.js +224 -0
  383. package/dist/testing/reporter/progress-reporter.js.map +1 -0
  384. package/dist/testing/reporter/reporter.types.d.ts +3 -0
  385. package/dist/testing/reporter/reporter.types.d.ts.map +1 -1
  386. package/dist/testing/reporter/reporter.types.js.map +1 -1
  387. package/dist/testing/runner/run-from-metadata.d.ts.map +1 -1
  388. package/dist/testing/runner/run-from-metadata.js +1 -0
  389. package/dist/testing/runner/run-from-metadata.js.map +1 -1
  390. package/dist/testing/steps/ui/actions.step.d.ts.map +1 -1
  391. package/dist/testing/steps/ui/actions.step.js +6 -2
  392. package/dist/testing/steps/ui/actions.step.js.map +1 -1
  393. package/dist/testing/steps/ui/assertions.step.d.ts.map +1 -1
  394. package/dist/testing/steps/ui/assertions.step.js +11 -3
  395. package/dist/testing/steps/ui/assertions.step.js.map +1 -1
  396. package/dist/testing/steps/ui/form.step.d.ts.map +1 -1
  397. package/dist/testing/steps/ui/form.step.js +9 -3
  398. package/dist/testing/steps/ui/form.step.js.map +1 -1
  399. package/dist/testing/steps/ui/manual.step.js +1 -1
  400. package/dist/testing/steps/ui/manual.step.js.map +1 -1
  401. package/dist/testing/steps/ui/navigation.step.d.ts.map +1 -1
  402. package/dist/testing/steps/ui/navigation.step.js +3 -1
  403. package/dist/testing/steps/ui/navigation.step.js.map +1 -1
  404. package/dist/theme/theme-registry.d.ts +14 -0
  405. package/dist/theme/theme-registry.d.ts.map +1 -0
  406. package/dist/theme/theme-registry.js +147 -0
  407. package/dist/theme/theme-registry.js.map +1 -0
  408. package/dist/types/workflow-dsl.types.d.ts +14 -0
  409. package/dist/types/workflow-dsl.types.d.ts.map +1 -1
  410. package/dist/types/workflow-dsl.types.js.map +1 -1
  411. package/package.json +2 -2
  412. package/postman/mpin.postman_collection.json +1045 -0
  413. package/src/commands/run-tests.command.ts +64 -4
  414. package/src/commands/seed.command.ts +19 -0
  415. package/src/config/cache.options.ts +9 -1
  416. package/src/constants/chatter-message.constants.ts +12 -0
  417. package/src/constants/draft-publish-fields.ts +5 -0
  418. package/src/constants/error-messages.ts +14 -0
  419. package/src/constants/media-file-types.ts +127 -0
  420. package/src/constants/success-messages.ts +5 -0
  421. package/src/constants/user.constants.ts +13 -0
  422. package/src/controllers/authentication.controller.ts +5 -2
  423. package/src/controllers/chatter-message.controller.ts +8 -0
  424. package/src/controllers/export-template.controller.ts +5 -3
  425. package/src/controllers/import-transaction.controller.ts +6 -6
  426. package/src/controllers/media.controller.ts +17 -6
  427. package/src/controllers/module-package.controller.ts +2 -3
  428. package/src/controllers/mpin-authentication.controller.ts +67 -0
  429. package/src/dtos/basic-filters.dto.ts +0 -4
  430. package/src/dtos/change-mpin.dto.ts +18 -0
  431. package/src/dtos/create-field-metadata.dto.ts +4 -9
  432. package/src/dtos/create-model-sequence.dto.ts +1 -1
  433. package/src/dtos/mpin-login.dto.ts +24 -0
  434. package/src/dtos/setup-mpin.dto.ts +51 -0
  435. package/src/entities/common.entity.ts +24 -0
  436. package/src/entities/dashboard-user-layout.entity.ts +2 -2
  437. package/src/entities/saved-filters.entity.ts +3 -3
  438. package/src/entities/user-device-credential.entity.ts +130 -0
  439. package/src/entities/user.entity.ts +53 -36
  440. package/src/entities/view-metadata.entity.ts +2 -2
  441. package/src/entities/workflow-execution-artifact.entity.ts +4 -3
  442. package/src/entities/workflow-execution-log.entity.ts +4 -3
  443. package/src/entities/workflow-execution.entity.ts +3 -2
  444. package/src/entities/workflow-secret.entity.ts +3 -2
  445. package/src/entities/workflow-step-execution.entity.ts +3 -2
  446. package/src/entities/workflow-trigger-execution.entity.ts +7 -6
  447. package/src/factories/mail.factory.ts +7 -1
  448. package/src/filters/http-exception.filter.ts +11 -1
  449. package/src/helpers/bootstrap.helper.ts +43 -6
  450. package/src/helpers/field-crud-managers/MediaFieldCrudManager.ts +26 -12
  451. package/src/helpers/file-download.helper.ts +45 -0
  452. package/src/helpers/model-metadata-helper.service.ts +32 -1
  453. package/src/helpers/security.helper.ts +35 -8
  454. package/src/helpers/solid-core-error-codes-provider.service.ts +53 -0
  455. package/src/helpers/solid-microservice-adapter.service.ts +6 -1
  456. package/src/helpers/solid-registry.ts +1 -1
  457. package/src/helpers/typeorm-db-helper.ts +12 -1
  458. package/src/index.ts +10 -0
  459. package/src/interfaces/active-user-data.interface.ts +9 -0
  460. package/src/interfaces.ts +1 -0
  461. package/src/repository/user-device-credential.repository.ts +17 -0
  462. package/src/seeders/module-metadata-seeder.service.ts +61 -39
  463. package/src/seeders/module-test-data.service.ts +6 -0
  464. package/src/seeders/seed-data/solid-core-metadata.json +290 -78
  465. package/src/services/api-key.service.ts +13 -1
  466. package/src/services/authentication.service.ts +203 -41
  467. package/src/services/chatter-message.service.ts +124 -5
  468. package/src/services/crud-helper.service.ts +35 -50
  469. package/src/services/crud.service.ts +131 -105
  470. package/src/services/datasource-introspection.service.ts +5 -0
  471. package/src/services/draft-publish-helper.service.ts +581 -0
  472. package/src/services/field-metadata.service.ts +3 -2
  473. package/src/services/internationalisation-helper.service.ts +148 -0
  474. package/src/services/media-storage.utils.ts +26 -1
  475. package/src/services/media.service.ts +67 -36
  476. package/src/services/metadata-validation.service.ts +39 -0
  477. package/src/services/model-metadata.service.ts +4 -1
  478. package/src/services/module-metadata.service.ts +3 -1
  479. package/src/services/mpin.service.ts +447 -0
  480. package/src/services/object.utils.ts +10 -0
  481. package/src/services/refresh-token-ids-storage.service.ts +229 -90
  482. package/src/services/setting.service.ts +3 -1
  483. package/src/services/settings/default-settings-provider.service.ts +140 -6
  484. package/src/services/user.service.ts +18 -3
  485. package/src/services/view-metadata.service.ts +5 -145
  486. package/src/services/workflow/nodes/execution-fail.node.ts +82 -0
  487. package/src/services/workflow/nodes/for-each.node.ts +140 -6
  488. package/src/services/workflow/nodes/if.node.ts +7 -0
  489. package/src/services/workflow/nodes/log-write.node.ts +2 -0
  490. package/src/services/workflow/nodes/loop-until.node.ts +343 -0
  491. package/src/services/workflow/nodes/parallel.node.ts +7 -0
  492. package/src/services/workflow/nodes/sequential.node.ts +7 -0
  493. package/src/services/workflow/nodes/switch.node.ts +7 -0
  494. package/src/services/workflow/workflow-definition-validator.service.ts +5 -2
  495. package/src/services/workflow/workflow-expression.service.ts +5 -0
  496. package/src/services/workflow/workflow-node-registry.service.ts +11 -0
  497. package/src/services/workflow/workflow-runtime.service.ts +274 -39
  498. package/src/solid-core.module.ts +33 -15
  499. package/src/testing/README.md +75 -3
  500. package/src/testing/adapters/ui/playwright-adapter.ts +25 -0
  501. package/src/testing/adapters/ui/ui.types.ts +2 -0
  502. package/src/testing/reporter/console-reporter.ts +4 -0
  503. package/src/testing/reporter/progress-reporter.ts +251 -0
  504. package/src/testing/reporter/reporter.types.ts +3 -0
  505. package/src/testing/runner/run-from-metadata.ts +1 -0
  506. package/src/testing/steps/ui/actions.step.ts +8 -4
  507. package/src/testing/steps/ui/assertions.step.ts +18 -5
  508. package/src/testing/steps/ui/form.step.ts +16 -6
  509. package/src/testing/steps/ui/manual.step.ts +1 -1
  510. package/src/testing/steps/ui/navigation.step.ts +4 -2
  511. package/src/theme/theme-registry.ts +181 -0
  512. package/src/types/workflow-dsl.types.ts +43 -0
@@ -0,0 +1,1045 @@
1
+ {
2
+ "info": {
3
+ "name": "SolidX — MPIN",
4
+ "_postman_id": "b7c1e2a4-5d38-4f61-9a2b-0c7d5e91f3aa",
5
+ "description": "End-to-end scenarios for MPIN sign-in (Deliverable C).\n\n## Running it\n\n1. Set `baseUrl`, `username` and `password` in the collection variables.\n2. Ensure `mpinEnabled = true` in settings.\n3. **Set `mpinLockoutDuration` to 5 (seconds)** before running folder 4 — at the default 900s the ladder cannot complete in one pass.\n4. Run the collection top to bottom. Requests chain through collection variables, so no manual copying is needed.\n\nFolders 8 and 9 need settings changed mid-run and are marked manual; they are skipped by a Collection Run.\n\n## Response shapes\n\nSuccess is wrapped by `WrapResponseInterceptor`:\n```\n{ \"statusCode\": 200, \"message\": [], \"error\": \"\", \"data\": { ... } }\n```\nErrors come from `HttpExceptionFilter` and carry a canonical code — the three MPIN failures are all HTTP 401 and are distinguished **only** by `errorCode`:\n```\n{ \"statusCode\": 401, \"errorCode\": \"solidx-mpin-invalid\", \"message\": \"...\" }\n```",
6
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
7
+ },
8
+ "variable": [
9
+ { "key": "baseUrl", "value": "http://localhost:3000/api", "type": "string" },
10
+ { "key": "username", "value": "sa", "type": "string" },
11
+ { "key": "password", "value": "", "type": "string" },
12
+ { "key": "mpin", "value": "482915", "type": "string" },
13
+ { "key": "newMpin", "value": "739104", "type": "string" },
14
+ { "key": "wrongMpin", "value": "999919", "type": "string" },
15
+ { "key": "accessToken", "value": "", "type": "string" },
16
+ { "key": "refreshToken", "value": "", "type": "string" },
17
+ { "key": "deviceId", "value": "", "type": "string" },
18
+ { "key": "credentialRef", "value": "", "type": "string" },
19
+ { "key": "credentialId", "value": "", "type": "string" },
20
+ { "key": "lockDeviceId", "value": "", "type": "string" },
21
+ { "key": "lockCredentialRef", "value": "", "type": "string" },
22
+ { "key": "changeDeviceId", "value": "", "type": "string" },
23
+ { "key": "changeCredentialRef", "value": "", "type": "string" },
24
+ { "key": "revokeDeviceId", "value": "", "type": "string" },
25
+ { "key": "revokeCredentialRef", "value": "", "type": "string" },
26
+ { "key": "invalidBody", "value": "", "type": "string" },
27
+ { "key": "capIteration", "value": "0", "type": "string" },
28
+ { "key": "capDeviceId", "value": "", "type": "string" },
29
+ { "key": "mpinMaxDevicesPerUser", "value": "5", "type": "string" }
30
+ ],
31
+ "event": [
32
+ {
33
+ "listen": "test",
34
+ "script": {
35
+ "type": "text/javascript",
36
+ "exec": [
37
+ "// Shared helpers, available to every request in the collection.",
38
+ "pm.expectCode = (expectedStatus, expectedErrorCode) => {",
39
+ " pm.test(`HTTP ${expectedStatus}`, () => pm.response.to.have.status(expectedStatus));",
40
+ " if (expectedErrorCode) {",
41
+ " pm.test(`errorCode = ${expectedErrorCode}`, () => {",
42
+ " pm.expect(pm.response.json().errorCode).to.eql(expectedErrorCode);",
43
+ " });",
44
+ " }",
45
+ "};"
46
+ ]
47
+ }
48
+ }
49
+ ],
50
+ "item": [
51
+ {
52
+ "name": "0 · Session",
53
+ "description": "Obtain a normal session. Everything in folder 1 is bearer-authenticated.",
54
+ "item": [
55
+ {
56
+ "name": "Login (password)",
57
+ "event": [
58
+ {
59
+ "listen": "test",
60
+ "script": {
61
+ "type": "text/javascript",
62
+ "exec": [
63
+ "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
64
+ "const body = pm.response.json();",
65
+ "const d = body.data || body;",
66
+ "const access = d.accessToken || d.access_token;",
67
+ "const refresh = d.refreshToken || d.refresh_token;",
68
+ "pm.test('access token returned', () => pm.expect(access).to.be.a('string').and.not.empty);",
69
+ "pm.collectionVariables.set('accessToken', access);",
70
+ "pm.collectionVariables.set('refreshToken', refresh);",
71
+ "// One deviceId for the whole run, as a real app would generate once and reuse.",
72
+ "pm.collectionVariables.set('deviceId', pm.variables.replaceIn('{{$guid}}'));"
73
+ ]
74
+ }
75
+ }
76
+ ],
77
+ "request": {
78
+ "method": "POST",
79
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
80
+ "body": {
81
+ "mode": "raw",
82
+ "raw": "{\n \"username\": \"{{username}}\",\n \"password\": \"{{password}}\"\n}"
83
+ },
84
+ "url": { "raw": "{{baseUrl}}/iam/authenticate", "host": ["{{baseUrl}}"], "path": ["iam", "authenticate"] }
85
+ }
86
+ }
87
+ ]
88
+ },
89
+
90
+ {
91
+ "name": "1 · Setup — happy path and validation",
92
+ "item": [
93
+ {
94
+ "name": "Setup MPIN",
95
+ "event": [
96
+ {
97
+ "listen": "test",
98
+ "script": {
99
+ "type": "text/javascript",
100
+ "exec": [
101
+ "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
102
+ "const d = pm.response.json().data;",
103
+ "pm.test('credentialRef is 64 hex chars', () => pm.expect(d.credentialRef).to.match(/^[0-9a-f]{64}$/));",
104
+ "pm.test('the MPIN is never echoed back', () => pm.expect(JSON.stringify(d)).to.not.include(pm.collectionVariables.get('mpin')));",
105
+ "pm.collectionVariables.set('credentialRef', d.credentialRef);"
106
+ ]
107
+ }
108
+ }
109
+ ],
110
+ "request": {
111
+ "method": "POST",
112
+ "header": [
113
+ { "key": "Content-Type", "value": "application/json" },
114
+ { "key": "Authorization", "value": "Bearer {{accessToken}}" }
115
+ ],
116
+ "body": {
117
+ "mode": "raw",
118
+ "raw": "{\n \"deviceId\": \"{{deviceId}}\",\n \"mpin\": \"{{mpin}}\",\n \"deviceName\": \"Postman Device\",\n \"platform\": \"ios\"\n}"
119
+ },
120
+ "url": { "raw": "{{baseUrl}}/iam/mpin/setup", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "setup"] },
121
+ "description": "The handle is returned **once**. Only its SHA-256 is stored, so it can never be re-issued."
122
+ }
123
+ },
124
+ {
125
+ "name": "Reject · too short",
126
+ "event": [{ "listen": "test", "script": { "type": "text/javascript", "exec": ["pm.expectCode(400, 'solidx-mpin-format-invalid');"] } }],
127
+ "request": {
128
+ "method": "POST",
129
+ "header": [
130
+ { "key": "Content-Type", "value": "application/json" },
131
+ { "key": "Authorization", "value": "Bearer {{accessToken}}" }
132
+ ],
133
+ "body": { "mode": "raw", "raw": "{\n \"deviceId\": \"{{$guid}}\",\n \"mpin\": \"123\"\n}" },
134
+ "url": { "raw": "{{baseUrl}}/iam/mpin/setup", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "setup"] }
135
+ }
136
+ },
137
+ {
138
+ "name": "Reject · non-digits",
139
+ "event": [{ "listen": "test", "script": { "type": "text/javascript", "exec": ["pm.expectCode(400, 'solidx-mpin-format-invalid');"] } }],
140
+ "request": {
141
+ "method": "POST",
142
+ "header": [
143
+ { "key": "Content-Type", "value": "application/json" },
144
+ { "key": "Authorization", "value": "Bearer {{accessToken}}" }
145
+ ],
146
+ "body": { "mode": "raw", "raw": "{\n \"deviceId\": \"{{$guid}}\",\n \"mpin\": \"abcd12\"\n}" },
147
+ "url": { "raw": "{{baseUrl}}/iam/mpin/setup", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "setup"] }
148
+ }
149
+ },
150
+ {
151
+ "name": "Reject · all identical (111111)",
152
+ "event": [{ "listen": "test", "script": { "type": "text/javascript", "exec": ["pm.expectCode(400, 'solidx-mpin-too-predictable');"] } }],
153
+ "request": {
154
+ "method": "POST",
155
+ "header": [
156
+ { "key": "Content-Type", "value": "application/json" },
157
+ { "key": "Authorization", "value": "Bearer {{accessToken}}" }
158
+ ],
159
+ "body": { "mode": "raw", "raw": "{\n \"deviceId\": \"{{$guid}}\",\n \"mpin\": \"111111\"\n}" },
160
+ "url": { "raw": "{{baseUrl}}/iam/mpin/setup", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "setup"] }
161
+ }
162
+ },
163
+ {
164
+ "name": "Reject · sequential (3456)",
165
+ "event": [{ "listen": "test", "script": { "type": "text/javascript", "exec": ["pm.expectCode(400, 'solidx-mpin-too-predictable');"] } }],
166
+ "request": {
167
+ "method": "POST",
168
+ "header": [
169
+ { "key": "Content-Type", "value": "application/json" },
170
+ { "key": "Authorization", "value": "Bearer {{accessToken}}" }
171
+ ],
172
+ "body": { "mode": "raw", "raw": "{\n \"deviceId\": \"{{$guid}}\",\n \"mpin\": \"3456\"\n}" },
173
+ "url": { "raw": "{{baseUrl}}/iam/mpin/setup", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "setup"] }
174
+ }
175
+ },
176
+ {
177
+ "name": "Reject · denylisted (2580)",
178
+ "event": [{ "listen": "test", "script": { "type": "text/javascript", "exec": ["pm.expectCode(400, 'solidx-mpin-too-predictable');"] } }],
179
+ "request": {
180
+ "method": "POST",
181
+ "header": [
182
+ { "key": "Content-Type", "value": "application/json" },
183
+ { "key": "Authorization", "value": "Bearer {{accessToken}}" }
184
+ ],
185
+ "body": { "mode": "raw", "raw": "{\n \"deviceId\": \"{{$guid}}\",\n \"mpin\": \"2580\"\n}" },
186
+ "url": { "raw": "{{baseUrl}}/iam/mpin/setup", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "setup"] },
187
+ "description": "The keypad-column PIN. Caught by the denylist rather than by the identical/sequential rules."
188
+ }
189
+ },
190
+ {
191
+ "name": "Reject · ANDROID_ID as deviceId",
192
+ "event": [
193
+ {
194
+ "listen": "test",
195
+ "script": {
196
+ "type": "text/javascript",
197
+ "exec": [
198
+ "pm.test('HTTP 400', () => pm.response.to.have.status(400));",
199
+ "pm.test('rejected by class-validator, not by MpinService', () => {",
200
+ " // No MPIN-specific errorCode: this never reaches the service.",
201
+ " pm.expect(pm.response.json().errorCode).to.not.match(/^solidx-mpin-/);",
202
+ "});"
203
+ ]
204
+ }
205
+ }
206
+ ],
207
+ "request": {
208
+ "method": "POST",
209
+ "header": [
210
+ { "key": "Content-Type", "value": "application/json" },
211
+ { "key": "Authorization", "value": "Bearer {{accessToken}}" }
212
+ ],
213
+ "body": { "mode": "raw", "raw": "{\n \"deviceId\": \"9774d56d682e549c\",\n \"mpin\": \"{{mpin}}\"\n}" },
214
+ "url": { "raw": "{{baseUrl}}/iam/mpin/setup", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "setup"] },
215
+ "description": "Android's Settings.Secure.ANDROID_ID is 16 hex chars, not a UUID. Clients must generate their own UUID v4."
216
+ }
217
+ },
218
+ {
219
+ "name": "Numeric MPIN — documents the coercion trap",
220
+ "event": [
221
+ {
222
+ "listen": "test",
223
+ "script": {
224
+ "type": "text/javascript",
225
+ "exec": [
226
+ "// enableImplicitConversion on the global ValidationPipe coerces a JSON",
227
+ "// number to a string, so @IsString() does NOT reject this. A client",
228
+ "// sending 012345 numerically would emit 12345 and silently store a",
229
+ "// SHORTER, DIFFERENT MPIN. Always send a quoted string.",
230
+ "pm.test('numeric payload is accepted (coerced) — hence the client rule', () => {",
231
+ " pm.expect(pm.response.code).to.be.oneOf([200, 400]);",
232
+ "});",
233
+ "if (pm.response.code === 200) {",
234
+ " pm.test('coerced to a string, proving the trap is real', () => {",
235
+ " pm.expect(pm.response.json().data.credentialRef).to.be.a('string');",
236
+ " });",
237
+ "}"
238
+ ]
239
+ }
240
+ }
241
+ ],
242
+ "request": {
243
+ "method": "POST",
244
+ "header": [
245
+ { "key": "Content-Type", "value": "application/json" },
246
+ { "key": "Authorization", "value": "Bearer {{accessToken}}" }
247
+ ],
248
+ "body": { "mode": "raw", "raw": "{\n \"deviceId\": \"{{$guid}}\",\n \"mpin\": 482915\n}" },
249
+ "url": { "raw": "{{baseUrl}}/iam/mpin/setup", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "setup"] }
250
+ }
251
+ },
252
+ {
253
+ "name": "Re-setup on the same device rotates the handle",
254
+ "event": [
255
+ {
256
+ "listen": "test",
257
+ "script": {
258
+ "type": "text/javascript",
259
+ "exec": [
260
+ "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
261
+ "const fresh = pm.response.json().data.credentialRef;",
262
+ "pm.test('a NEW handle is issued', () => pm.expect(fresh).to.not.eql(pm.collectionVariables.get('credentialRef')));",
263
+ "pm.collectionVariables.set('credentialRef', fresh);"
264
+ ]
265
+ }
266
+ }
267
+ ],
268
+ "request": {
269
+ "method": "POST",
270
+ "header": [
271
+ { "key": "Content-Type", "value": "application/json" },
272
+ { "key": "Authorization", "value": "Bearer {{accessToken}}" }
273
+ ],
274
+ "body": {
275
+ "mode": "raw",
276
+ "raw": "{\n \"deviceId\": \"{{deviceId}}\",\n \"mpin\": \"{{mpin}}\",\n \"deviceName\": \"Postman Device\",\n \"platform\": \"ios\"\n}"
277
+ },
278
+ "url": { "raw": "{{baseUrl}}/iam/mpin/setup", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "setup"] },
279
+ "description": "Same deviceId upserts the existing row rather than adding a second one. The previous handle is dead from here."
280
+ }
281
+ }
282
+ ]
283
+ },
284
+
285
+ {
286
+ "name": "2 · Login",
287
+ "item": [
288
+ {
289
+ "name": "Login with MPIN",
290
+ "event": [
291
+ {
292
+ "listen": "test",
293
+ "script": {
294
+ "type": "text/javascript",
295
+ "exec": [
296
+ "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
297
+ "const d = pm.response.json().data;",
298
+ "pm.test('a fresh token pair is minted', () => {",
299
+ " pm.expect(d.accessToken).to.be.a('string').and.not.empty;",
300
+ " pm.expect(d.refreshToken).to.be.a('string').and.not.empty;",
301
+ "});",
302
+ "pm.test('refreshToken differs from the session one — not a replay', () => {",
303
+ " pm.expect(d.refreshToken).to.not.eql(pm.collectionVariables.get('refreshToken'));",
304
+ "});",
305
+ "pm.test('no request body identifier was needed', () => {",
306
+ " pm.expect(pm.request.body.raw).to.not.include('username');",
307
+ " pm.expect(pm.request.body.raw).to.not.include('mobile');",
308
+ "});",
309
+ "pm.collectionVariables.set('refreshToken', d.refreshToken);",
310
+ "pm.collectionVariables.set('accessToken', d.accessToken);"
311
+ ]
312
+ }
313
+ }
314
+ ],
315
+ "request": {
316
+ "method": "POST",
317
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
318
+ "body": { "mode": "raw", "raw": "{\n \"credentialRef\": \"{{credentialRef}}\",\n \"mpin\": \"{{mpin}}\"\n}" },
319
+ "url": { "raw": "{{baseUrl}}/iam/mpin/login", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "login"] },
320
+ "description": "Public route — no Authorization header. The handle identifies the account, so no mobile/email is sent and there is no enumeration surface."
321
+ }
322
+ },
323
+ {
324
+ "name": "Wrong MPIN",
325
+ "event": [
326
+ {
327
+ "listen": "test",
328
+ "script": {
329
+ "type": "text/javascript",
330
+ "exec": [
331
+ "pm.expectCode(401, 'solidx-mpin-invalid');",
332
+ "// Stored so the next request can prove the two are indistinguishable.",
333
+ "const b = pm.response.json();",
334
+ "pm.collectionVariables.set('invalidBody', JSON.stringify({ statusCode: b.statusCode, errorCode: b.errorCode, message: b.message }));"
335
+ ]
336
+ }
337
+ }
338
+ ],
339
+ "request": {
340
+ "method": "POST",
341
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
342
+ "body": { "mode": "raw", "raw": "{\n \"credentialRef\": \"{{credentialRef}}\",\n \"mpin\": \"{{wrongMpin}}\"\n}" },
343
+ "url": { "raw": "{{baseUrl}}/iam/mpin/login", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "login"] }
344
+ }
345
+ },
346
+ {
347
+ "name": "Unknown handle — must be indistinguishable",
348
+ "event": [
349
+ {
350
+ "listen": "test",
351
+ "script": {
352
+ "type": "text/javascript",
353
+ "exec": [
354
+ "pm.expectCode(401, 'solidx-mpin-invalid');",
355
+ "const b = pm.response.json();",
356
+ "const now = JSON.stringify({ statusCode: b.statusCode, errorCode: b.errorCode, message: b.message });",
357
+ "pm.test('identical to a wrong MPIN — no enumeration oracle', () => {",
358
+ " pm.expect(now).to.eql(pm.collectionVariables.get('invalidBody'));",
359
+ "});"
360
+ ]
361
+ }
362
+ }
363
+ ],
364
+ "request": {
365
+ "method": "POST",
366
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
367
+ "body": { "mode": "raw", "raw": "{\n \"credentialRef\": \"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n \"mpin\": \"{{mpin}}\"\n}" },
368
+ "url": { "raw": "{{baseUrl}}/iam/mpin/login", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "login"] }
369
+ }
370
+ }
371
+ ]
372
+ },
373
+
374
+ {
375
+ "name": "3 · The bug that started this",
376
+ "description": "MPIN must survive logout and refresh-token expiry, because it mints a fresh pair rather than replaying a stored token.",
377
+ "item": [
378
+ {
379
+ "name": "Logout",
380
+ "event": [{ "listen": "test", "script": { "type": "text/javascript", "exec": ["pm.test('HTTP 200', () => pm.response.to.have.status(200));"] } }],
381
+ "request": {
382
+ "method": "POST",
383
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
384
+ "body": { "mode": "raw", "raw": "{\n \"refreshToken\": \"{{refreshToken}}\"\n}" },
385
+ "url": { "raw": "{{baseUrl}}/iam/logout", "host": ["{{baseUrl}}"], "path": ["iam", "logout"] }
386
+ }
387
+ },
388
+ {
389
+ "name": "Refresh with the logged-out token fails",
390
+ "event": [{ "listen": "test", "script": { "type": "text/javascript", "exec": ["pm.test('HTTP 401 — the session really is gone', () => pm.response.to.have.status(401));"] } }],
391
+ "request": {
392
+ "method": "POST",
393
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
394
+ "body": { "mode": "raw", "raw": "{\n \"refreshToken\": \"{{refreshToken}}\"\n}" },
395
+ "url": { "raw": "{{baseUrl}}/iam/refresh-tokens", "host": ["{{baseUrl}}"], "path": ["iam", "refresh-tokens"] }
396
+ }
397
+ },
398
+ {
399
+ "name": "MPIN login STILL works after logout",
400
+ "event": [
401
+ {
402
+ "listen": "test",
403
+ "script": {
404
+ "type": "text/javascript",
405
+ "exec": [
406
+ "pm.test('HTTP 200 — the original design failed here', () => pm.response.to.have.status(200));",
407
+ "const d = pm.response.json().data;",
408
+ "pm.collectionVariables.set('accessToken', d.accessToken);",
409
+ "pm.collectionVariables.set('refreshToken', d.refreshToken);"
410
+ ]
411
+ }
412
+ }
413
+ ],
414
+ "request": {
415
+ "method": "POST",
416
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
417
+ "body": { "mode": "raw", "raw": "{\n \"credentialRef\": \"{{credentialRef}}\",\n \"mpin\": \"{{mpin}}\"\n}" },
418
+ "url": { "raw": "{{baseUrl}}/iam/mpin/login", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "login"] }
419
+ }
420
+ },
421
+ {
422
+ "name": "Logout · expired/garbage token is idempotent",
423
+ "event": [
424
+ {
425
+ "listen": "test",
426
+ "script": {
427
+ "type": "text/javascript",
428
+ "exec": [
429
+ "pm.test('HTTP 200 — logout must never 500', () => pm.response.to.have.status(200));",
430
+ "pm.test('not a 500 LOGOUT_FAILED', () => pm.expect(pm.response.code).to.not.eql(500));"
431
+ ]
432
+ }
433
+ }
434
+ ],
435
+ "request": {
436
+ "method": "POST",
437
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
438
+ "body": { "mode": "raw", "raw": "{\n \"refreshToken\": \"not-a-real-token\"\n}" },
439
+ "url": { "raw": "{{baseUrl}}/iam/logout", "host": ["{{baseUrl}}"], "path": ["iam", "logout"] },
440
+ "description": "Deliverable A: verifyAsync uses ignoreExpiration, and an unverifiable token returns success rather than falling into the generic 500."
441
+ }
442
+ }
443
+ ]
444
+ },
445
+
446
+ {
447
+ "name": "4 · Lockout ladder (destructive)",
448
+ "description": "Permanently deactivates its credential, so it uses a throwaway device.\n\n**Set `mpinLockoutDuration` to 5 seconds first** — the default 900s makes this unrunnable in one pass.",
449
+ "item": [
450
+ {
451
+ "name": "Setup a throwaway device",
452
+ "event": [
453
+ {
454
+ "listen": "prerequest",
455
+ "script": { "type": "text/javascript", "exec": ["pm.collectionVariables.set('lockDeviceId', pm.variables.replaceIn('{{$guid}}'));"] }
456
+ },
457
+ {
458
+ "listen": "test",
459
+ "script": {
460
+ "type": "text/javascript",
461
+ "exec": [
462
+ "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
463
+ "pm.collectionVariables.set('lockCredentialRef', pm.response.json().data.credentialRef);"
464
+ ]
465
+ }
466
+ }
467
+ ],
468
+ "request": {
469
+ "method": "POST",
470
+ "header": [
471
+ { "key": "Content-Type", "value": "application/json" },
472
+ { "key": "Authorization", "value": "Bearer {{accessToken}}" }
473
+ ],
474
+ "body": { "mode": "raw", "raw": "{\n \"deviceId\": \"{{lockDeviceId}}\",\n \"mpin\": \"{{mpin}}\",\n \"deviceName\": \"Lockout Test\"\n}" },
475
+ "url": { "raw": "{{baseUrl}}/iam/mpin/setup", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "setup"] }
476
+ }
477
+ },
478
+ {
479
+ "name": "Attempts 1–4 · invalid",
480
+ "event": [
481
+ {
482
+ "listen": "prerequest",
483
+ "script": {
484
+ "type": "text/javascript",
485
+ "exec": [
486
+ "let i = parseInt(pm.collectionVariables.get('capIteration') || '0');",
487
+ "// reuse the counter slot for this loop",
488
+ "if (!pm.collectionVariables.get('ladderCount')) pm.collectionVariables.set('ladderCount', '0');"
489
+ ]
490
+ }
491
+ },
492
+ {
493
+ "listen": "test",
494
+ "script": {
495
+ "type": "text/javascript",
496
+ "exec": [
497
+ "let n = parseInt(pm.collectionVariables.get('ladderCount')) + 1;",
498
+ "pm.collectionVariables.set('ladderCount', String(n));",
499
+ "pm.expectCode(401, 'solidx-mpin-invalid');",
500
+ "if (n < 4) { postman.setNextRequest('Attempts 1–4 · invalid'); }"
501
+ ]
502
+ }
503
+ }
504
+ ],
505
+ "request": {
506
+ "method": "POST",
507
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
508
+ "body": { "mode": "raw", "raw": "{\n \"credentialRef\": \"{{lockCredentialRef}}\",\n \"mpin\": \"{{wrongMpin}}\"\n}" },
509
+ "url": { "raw": "{{baseUrl}}/iam/mpin/login", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "login"] },
510
+ "description": "Loops four times via setNextRequest."
511
+ }
512
+ },
513
+ {
514
+ "name": "Attempt 5 · locked",
515
+ "event": [{ "listen": "test", "script": { "type": "text/javascript", "exec": ["pm.expectCode(401, 'solidx-mpin-locked');"] } }],
516
+ "request": {
517
+ "method": "POST",
518
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
519
+ "body": { "mode": "raw", "raw": "{\n \"credentialRef\": \"{{lockCredentialRef}}\",\n \"mpin\": \"{{wrongMpin}}\"\n}" },
520
+ "url": { "raw": "{{baseUrl}}/iam/mpin/login", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "login"] }
521
+ }
522
+ },
523
+ {
524
+ "name": "Correct MPIN during lockout still fails",
525
+ "event": [
526
+ {
527
+ "listen": "test",
528
+ "script": {
529
+ "type": "text/javascript",
530
+ "exec": [
531
+ "pm.expectCode(401, 'solidx-mpin-locked');",
532
+ "pm.test('locked, not invalid — the PIN was right', () => {",
533
+ " pm.expect(pm.response.json().errorCode).to.not.eql('solidx-mpin-invalid');",
534
+ "});"
535
+ ]
536
+ }
537
+ }
538
+ ],
539
+ "request": {
540
+ "method": "POST",
541
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
542
+ "body": { "mode": "raw", "raw": "{\n \"credentialRef\": \"{{lockCredentialRef}}\",\n \"mpin\": \"{{mpin}}\"\n}" },
543
+ "url": { "raw": "{{baseUrl}}/iam/mpin/login", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "login"] },
544
+ "description": "Rejected before the hash comparison, so the lockout window is not consumed and no bcrypt work is done."
545
+ }
546
+ },
547
+ {
548
+ "name": "Wait for the lockout to expire",
549
+ "event": [
550
+ {
551
+ "listen": "prerequest",
552
+ "script": {
553
+ "type": "text/javascript",
554
+ "exec": [
555
+ "// Blocking sleep — assumes mpinLockoutDuration was lowered to ~5s.",
556
+ "const until = Date.now() + 7000;",
557
+ "while (Date.now() < until) { /* wait */ }"
558
+ ]
559
+ }
560
+ },
561
+ { "listen": "test", "script": { "type": "text/javascript", "exec": ["pm.test('lockout window elapsed', () => true);"] } }
562
+ ],
563
+ "request": {
564
+ "method": "GET",
565
+ "header": [],
566
+ "url": { "raw": "{{baseUrl}}/iam/mpin/devices", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "devices"] },
567
+ "description": "Any cheap call; the point is the pre-request delay."
568
+ }
569
+ },
570
+ {
571
+ "name": "Attempts 6–9 · counter did NOT reset",
572
+ "event": [
573
+ {
574
+ "listen": "test",
575
+ "script": {
576
+ "type": "text/javascript",
577
+ "exec": [
578
+ "let n = parseInt(pm.collectionVariables.get('ladderCount2') || '0') + 1;",
579
+ "pm.collectionVariables.set('ladderCount2', String(n));",
580
+ "pm.expectCode(401, 'solidx-mpin-invalid');",
581
+ "if (n < 4) { postman.setNextRequest('Attempts 6–9 · counter did NOT reset'); }"
582
+ ]
583
+ }
584
+ }
585
+ ],
586
+ "request": {
587
+ "method": "POST",
588
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
589
+ "body": { "mode": "raw", "raw": "{\n \"credentialRef\": \"{{lockCredentialRef}}\",\n \"mpin\": \"{{wrongMpin}}\"\n}" },
590
+ "url": { "raw": "{{baseUrl}}/iam/mpin/login", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "login"] },
591
+ "description": "failedAttempts is cumulative and resets only on success — never when a lockout expires."
592
+ }
593
+ },
594
+ {
595
+ "name": "Attempt 10 · terminal, revoked",
596
+ "event": [
597
+ {
598
+ "listen": "test",
599
+ "script": {
600
+ "type": "text/javascript",
601
+ "exec": [
602
+ "pm.expectCode(401, 'solidx-mpin-revoked');",
603
+ "pm.test('the attempt that kills it says so — the client must stop retrying', () => {",
604
+ " pm.expect(pm.response.json().errorCode).to.eql('solidx-mpin-revoked');",
605
+ "});"
606
+ ]
607
+ }
608
+ }
609
+ ],
610
+ "request": {
611
+ "method": "POST",
612
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
613
+ "body": { "mode": "raw", "raw": "{\n \"credentialRef\": \"{{lockCredentialRef}}\",\n \"mpin\": \"{{wrongMpin}}\"\n}" },
614
+ "url": { "raw": "{{baseUrl}}/iam/mpin/login", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "login"] }
615
+ }
616
+ },
617
+ {
618
+ "name": "Attempt 11 · still revoked (no asymmetry)",
619
+ "event": [{ "listen": "test", "script": { "type": "text/javascript", "exec": ["pm.expectCode(401, 'solidx-mpin-revoked');"] } }],
620
+ "request": {
621
+ "method": "POST",
622
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
623
+ "body": { "mode": "raw", "raw": "{\n \"credentialRef\": \"{{lockCredentialRef}}\",\n \"mpin\": \"{{wrongMpin}}\"\n}" },
624
+ "url": { "raw": "{{baseUrl}}/iam/mpin/login", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "login"] },
625
+ "description": "A dead credential reports as dead regardless of the MPIN supplied."
626
+ }
627
+ },
628
+ {
629
+ "name": "Normal login still works — the ACCOUNT is not locked",
630
+ "event": [
631
+ {
632
+ "listen": "test",
633
+ "script": {
634
+ "type": "text/javascript",
635
+ "exec": [
636
+ "pm.test('HTTP 200 — only the credential died, never the account', () => pm.response.to.have.status(200));",
637
+ "const d = pm.response.json().data || {};",
638
+ "if (d.accessToken) pm.collectionVariables.set('accessToken', d.accessToken);"
639
+ ]
640
+ }
641
+ }
642
+ ],
643
+ "request": {
644
+ "method": "POST",
645
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
646
+ "body": { "mode": "raw", "raw": "{\n \"username\": \"{{username}}\",\n \"password\": \"{{password}}\"\n}" },
647
+ "url": { "raw": "{{baseUrl}}/iam/authenticate", "host": ["{{baseUrl}}"], "path": ["iam", "authenticate"] }
648
+ }
649
+ }
650
+ ]
651
+ },
652
+
653
+ {
654
+ "name": "5 · Change MPIN",
655
+ "item": [
656
+ {
657
+ "name": "Setup a device for change tests",
658
+ "event": [
659
+ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": ["pm.collectionVariables.set('changeDeviceId', pm.variables.replaceIn('{{$guid}}'));"] } },
660
+ { "listen": "test", "script": { "type": "text/javascript", "exec": ["pm.test('HTTP 200', () => pm.response.to.have.status(200));", "pm.collectionVariables.set('changeCredentialRef', pm.response.json().data.credentialRef);"] } }
661
+ ],
662
+ "request": {
663
+ "method": "POST",
664
+ "header": [
665
+ { "key": "Content-Type", "value": "application/json" },
666
+ { "key": "Authorization", "value": "Bearer {{accessToken}}" }
667
+ ],
668
+ "body": { "mode": "raw", "raw": "{\n \"deviceId\": \"{{changeDeviceId}}\",\n \"mpin\": \"{{mpin}}\",\n \"deviceName\": \"Change Test\"\n}" },
669
+ "url": { "raw": "{{baseUrl}}/iam/mpin/setup", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "setup"] }
670
+ }
671
+ },
672
+ {
673
+ "name": "Change MPIN — returns a NEW handle",
674
+ "event": [
675
+ {
676
+ "listen": "test",
677
+ "script": {
678
+ "type": "text/javascript",
679
+ "exec": [
680
+ "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
681
+ "const fresh = pm.response.json().data.credentialRef;",
682
+ "pm.test('handle rotated — the client MUST store this', () => {",
683
+ " pm.expect(fresh).to.match(/^[0-9a-f]{64}$/);",
684
+ " pm.expect(fresh).to.not.eql(pm.collectionVariables.get('changeCredentialRef'));",
685
+ "});",
686
+ "pm.collectionVariables.set('oldChangeRef', pm.collectionVariables.get('changeCredentialRef'));",
687
+ "pm.collectionVariables.set('changeCredentialRef', fresh);"
688
+ ]
689
+ }
690
+ }
691
+ ],
692
+ "request": {
693
+ "method": "POST",
694
+ "header": [
695
+ { "key": "Content-Type", "value": "application/json" },
696
+ { "key": "Authorization", "value": "Bearer {{accessToken}}" }
697
+ ],
698
+ "body": { "mode": "raw", "raw": "{\n \"credentialRef\": \"{{changeCredentialRef}}\",\n \"currentMpin\": \"{{mpin}}\",\n \"newMpin\": \"{{newMpin}}\"\n}" },
699
+ "url": { "raw": "{{baseUrl}}/iam/mpin/change", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "change"] },
700
+ "description": "Discarding this response leaves the app holding a dead handle and MPIN silently broken."
701
+ }
702
+ },
703
+ {
704
+ "name": "Old handle is dead",
705
+ "event": [{ "listen": "test", "script": { "type": "text/javascript", "exec": ["pm.expectCode(401, 'solidx-mpin-invalid');"] } }],
706
+ "request": {
707
+ "method": "POST",
708
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
709
+ "body": { "mode": "raw", "raw": "{\n \"credentialRef\": \"{{oldChangeRef}}\",\n \"mpin\": \"{{newMpin}}\"\n}" },
710
+ "url": { "raw": "{{baseUrl}}/iam/mpin/login", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "login"] }
711
+ }
712
+ },
713
+ {
714
+ "name": "New handle + new MPIN works",
715
+ "event": [{ "listen": "test", "script": { "type": "text/javascript", "exec": ["pm.test('HTTP 200', () => pm.response.to.have.status(200));"] } }],
716
+ "request": {
717
+ "method": "POST",
718
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
719
+ "body": { "mode": "raw", "raw": "{\n \"credentialRef\": \"{{changeCredentialRef}}\",\n \"mpin\": \"{{newMpin}}\"\n}" },
720
+ "url": { "raw": "{{baseUrl}}/iam/mpin/login", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "login"] }
721
+ }
722
+ },
723
+ {
724
+ "name": "Change with wrong currentMpin",
725
+ "event": [{ "listen": "test", "script": { "type": "text/javascript", "exec": ["pm.expectCode(401, 'solidx-mpin-invalid');"] } }],
726
+ "request": {
727
+ "method": "POST",
728
+ "header": [
729
+ { "key": "Content-Type", "value": "application/json" },
730
+ { "key": "Authorization", "value": "Bearer {{accessToken}}" }
731
+ ],
732
+ "body": { "mode": "raw", "raw": "{\n \"credentialRef\": \"{{changeCredentialRef}}\",\n \"currentMpin\": \"{{wrongMpin}}\",\n \"newMpin\": \"135791\"\n}" },
733
+ "url": { "raw": "{{baseUrl}}/iam/mpin/change", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "change"] },
734
+ "description": "Failures here count towards the same lockout ladder as login."
735
+ }
736
+ }
737
+ ]
738
+ },
739
+
740
+ {
741
+ "name": "6 · Devices and revocation",
742
+ "item": [
743
+ {
744
+ "name": "List my devices",
745
+ "event": [
746
+ {
747
+ "listen": "test",
748
+ "script": {
749
+ "type": "text/javascript",
750
+ "exec": [
751
+ "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
752
+ "const rows = pm.response.json().data;",
753
+ "pm.test('an array is returned', () => pm.expect(rows).to.be.an('array'));",
754
+ "pm.test('NO secrets leak in the list', () => {",
755
+ " const raw = JSON.stringify(rows);",
756
+ " pm.expect(raw).to.not.include('hashedMpin');",
757
+ " pm.expect(raw).to.not.include('hashedCredentialRef');",
758
+ " pm.expect(raw).to.not.include('$2b$');",
759
+ "});",
760
+ "pm.test('revoked credentials are not listed', () => {",
761
+ " rows.forEach(r => pm.expect(r.isActive).to.eql(true));",
762
+ "});",
763
+ "if (rows.length) pm.collectionVariables.set('credentialId', rows[0].id);"
764
+ ]
765
+ }
766
+ }
767
+ ],
768
+ "request": {
769
+ "method": "GET",
770
+ "header": [{ "key": "Authorization", "value": "Bearer {{accessToken}}" }],
771
+ "url": { "raw": "{{baseUrl}}/iam/mpin/devices", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "devices"] }
772
+ }
773
+ },
774
+ {
775
+ "name": "Setup a device to revoke",
776
+ "event": [
777
+ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": ["pm.collectionVariables.set('revokeDeviceId', pm.variables.replaceIn('{{$guid}}'));"] } },
778
+ {
779
+ "listen": "test",
780
+ "script": {
781
+ "type": "text/javascript",
782
+ "exec": [
783
+ "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
784
+ "pm.collectionVariables.set('revokeCredentialRef', pm.response.json().data.credentialRef);"
785
+ ]
786
+ }
787
+ }
788
+ ],
789
+ "request": {
790
+ "method": "POST",
791
+ "header": [
792
+ { "key": "Content-Type", "value": "application/json" },
793
+ { "key": "Authorization", "value": "Bearer {{accessToken}}" }
794
+ ],
795
+ "body": { "mode": "raw", "raw": "{\n \"deviceId\": \"{{revokeDeviceId}}\",\n \"mpin\": \"{{mpin}}\",\n \"deviceName\": \"To Be Revoked\"\n}" },
796
+ "url": { "raw": "{{baseUrl}}/iam/mpin/setup", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "setup"] }
797
+ }
798
+ },
799
+ {
800
+ "name": "Find its row id",
801
+ "event": [
802
+ {
803
+ "listen": "test",
804
+ "script": {
805
+ "type": "text/javascript",
806
+ "exec": [
807
+ "const rows = pm.response.json().data;",
808
+ "const row = rows.find(r => r.deviceId === pm.collectionVariables.get('revokeDeviceId'));",
809
+ "pm.test('the new device appears in the list', () => pm.expect(row).to.be.an('object'));",
810
+ "pm.collectionVariables.set('credentialId', row.id);"
811
+ ]
812
+ }
813
+ }
814
+ ],
815
+ "request": {
816
+ "method": "GET",
817
+ "header": [{ "key": "Authorization", "value": "Bearer {{accessToken}}" }],
818
+ "url": { "raw": "{{baseUrl}}/iam/mpin/devices", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "devices"] }
819
+ }
820
+ },
821
+ {
822
+ "name": "Revoke it",
823
+ "event": [{ "listen": "test", "script": { "type": "text/javascript", "exec": ["pm.test('HTTP 200', () => pm.response.to.have.status(200));"] } }],
824
+ "request": {
825
+ "method": "DELETE",
826
+ "header": [{ "key": "Authorization", "value": "Bearer {{accessToken}}" }],
827
+ "url": { "raw": "{{baseUrl}}/iam/mpin/devices/{{credentialId}}", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "devices", "{{credentialId}}"] },
828
+ "description": "A soft delete — the row survives with isActive=false so a login can be answered with MPIN_REVOKED rather than a baffling MPIN_INVALID."
829
+ }
830
+ },
831
+ {
832
+ "name": "Login with the correct MPIN · revoked",
833
+ "event": [{ "listen": "test", "script": { "type": "text/javascript", "exec": ["pm.expectCode(401, 'solidx-mpin-revoked');"] } }],
834
+ "request": {
835
+ "method": "POST",
836
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
837
+ "body": { "mode": "raw", "raw": "{\n \"credentialRef\": \"{{revokeCredentialRef}}\",\n \"mpin\": \"{{mpin}}\"\n}" },
838
+ "url": { "raw": "{{baseUrl}}/iam/mpin/login", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "login"] }
839
+ }
840
+ },
841
+ {
842
+ "name": "Login with a wrong MPIN · also revoked",
843
+ "event": [{ "listen": "test", "script": { "type": "text/javascript", "exec": ["pm.expectCode(401, 'solidx-mpin-revoked');"] } }],
844
+ "request": {
845
+ "method": "POST",
846
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
847
+ "body": { "mode": "raw", "raw": "{\n \"credentialRef\": \"{{revokeCredentialRef}}\",\n \"mpin\": \"{{wrongMpin}}\"\n}" },
848
+ "url": { "raw": "{{baseUrl}}/iam/mpin/login", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "login"] },
849
+ "description": "The isActive check runs before the hash comparison, so a dead credential reports consistently. No asymmetry between the attempt that kills it and the next one."
850
+ }
851
+ },
852
+ {
853
+ "name": "Revoked device no longer listed",
854
+ "event": [
855
+ {
856
+ "listen": "test",
857
+ "script": {
858
+ "type": "text/javascript",
859
+ "exec": [
860
+ "const rows = pm.response.json().data;",
861
+ "const still = rows.find(r => r.deviceId === pm.collectionVariables.get('revokeDeviceId'));",
862
+ "pm.test('gone from the active list', () => pm.expect(still).to.be.undefined);"
863
+ ]
864
+ }
865
+ }
866
+ ],
867
+ "request": {
868
+ "method": "GET",
869
+ "header": [{ "key": "Authorization", "value": "Bearer {{accessToken}}" }],
870
+ "url": { "raw": "{{baseUrl}}/iam/mpin/devices", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "devices"] }
871
+ }
872
+ }
873
+ ]
874
+ },
875
+
876
+ {
877
+ "name": "7 · Device cap evicts, never rejects",
878
+ "description": "Simulates repeated reinstalls: every setup uses a FRESH deviceId, as a reinstalled app would. All must succeed; the active count must hold at the cap.",
879
+ "item": [
880
+ {
881
+ "name": "Cap · setup on a fresh device",
882
+ "event": [
883
+ {
884
+ "listen": "prerequest",
885
+ "script": {
886
+ "type": "text/javascript",
887
+ "exec": [
888
+ "let i = parseInt(pm.collectionVariables.get('capIteration') || '0');",
889
+ "pm.collectionVariables.set('capIteration', String(i + 1));",
890
+ "pm.collectionVariables.set('capDeviceId', pm.variables.replaceIn('{{$guid}}'));"
891
+ ]
892
+ }
893
+ },
894
+ {
895
+ "listen": "test",
896
+ "script": {
897
+ "type": "text/javascript",
898
+ "exec": [
899
+ "const i = parseInt(pm.collectionVariables.get('capIteration'));",
900
+ "const cap = parseInt(pm.collectionVariables.get('mpinMaxDevicesPerUser'));",
901
+ "pm.test(`reinstall #${i} succeeds — the cap must evict, not reject`, () => pm.response.to.have.status(200));",
902
+ "if (i < cap + 2) { postman.setNextRequest('Cap · setup on a fresh device'); }"
903
+ ]
904
+ }
905
+ }
906
+ ],
907
+ "request": {
908
+ "method": "POST",
909
+ "header": [
910
+ { "key": "Content-Type", "value": "application/json" },
911
+ { "key": "Authorization", "value": "Bearer {{accessToken}}" }
912
+ ],
913
+ "body": { "mode": "raw", "raw": "{\n \"deviceId\": \"{{capDeviceId}}\",\n \"mpin\": \"{{mpin}}\",\n \"deviceName\": \"Reinstall {{capIteration}}\"\n}" },
914
+ "url": { "raw": "{{baseUrl}}/iam/mpin/setup", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "setup"] },
915
+ "description": "Loops cap+2 times. Rejecting at the cap would mean a user who reinstalls a few times could never set MPIN up again."
916
+ }
917
+ },
918
+ {
919
+ "name": "Active devices held at the cap",
920
+ "event": [
921
+ {
922
+ "listen": "test",
923
+ "script": {
924
+ "type": "text/javascript",
925
+ "exec": [
926
+ "const rows = pm.response.json().data;",
927
+ "const cap = parseInt(pm.collectionVariables.get('mpinMaxDevicesPerUser'));",
928
+ "pm.test(`active count <= cap (${cap})`, () => pm.expect(rows.length).to.be.at.most(cap));",
929
+ "pm.test('oldest were evicted, newest survive', () => {",
930
+ " pm.expect(rows.some(r => (r.deviceName || '').startsWith('Reinstall'))).to.be.true;",
931
+ "});"
932
+ ]
933
+ }
934
+ }
935
+ ],
936
+ "request": {
937
+ "method": "GET",
938
+ "header": [{ "key": "Authorization", "value": "Bearer {{accessToken}}" }],
939
+ "url": { "raw": "{{baseUrl}}/iam/mpin/devices", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "devices"] }
940
+ }
941
+ }
942
+ ]
943
+ },
944
+
945
+ {
946
+ "name": "8 · Feature gate (manual)",
947
+ "description": "Set `mpinEnabled = false` in settings, then run these. Skipped by an automated run because the setting must change mid-pass.",
948
+ "item": [
949
+ {
950
+ "name": "Login 404s when disabled",
951
+ "event": [
952
+ {
953
+ "listen": "test",
954
+ "script": {
955
+ "type": "text/javascript",
956
+ "exec": [
957
+ "pm.test('HTTP 404 — a disabled feature should look absent', () => pm.response.to.have.status(404));",
958
+ "pm.test('not 401/403, which would imply it exists', () => pm.expect(pm.response.code).to.not.be.oneOf([401, 403]));"
959
+ ]
960
+ }
961
+ }
962
+ ],
963
+ "request": {
964
+ "method": "POST",
965
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
966
+ "body": { "mode": "raw", "raw": "{\n \"credentialRef\": \"{{credentialRef}}\",\n \"mpin\": \"{{mpin}}\"\n}" },
967
+ "url": { "raw": "{{baseUrl}}/iam/mpin/login", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "login"] }
968
+ }
969
+ },
970
+ {
971
+ "name": "Setup 404s when disabled",
972
+ "event": [{ "listen": "test", "script": { "type": "text/javascript", "exec": ["pm.test('HTTP 404', () => pm.response.to.have.status(404));"] } }],
973
+ "request": {
974
+ "method": "POST",
975
+ "header": [
976
+ { "key": "Content-Type", "value": "application/json" },
977
+ { "key": "Authorization", "value": "Bearer {{accessToken}}" }
978
+ ],
979
+ "body": { "mode": "raw", "raw": "{\n \"deviceId\": \"{{$guid}}\",\n \"mpin\": \"{{mpin}}\"\n}" },
980
+ "url": { "raw": "{{baseUrl}}/iam/mpin/setup", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "setup"] }
981
+ }
982
+ }
983
+ ]
984
+ },
985
+
986
+ {
987
+ "name": "9 · Session interaction (manual)",
988
+ "description": "Requires `preventConcurrentLogins = false` (the default). Proves Deliverable B: an MPIN session and a web session coexist.",
989
+ "item": [
990
+ {
991
+ "name": "MPIN login",
992
+ "event": [
993
+ {
994
+ "listen": "test",
995
+ "script": {
996
+ "type": "text/javascript",
997
+ "exec": [
998
+ "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
999
+ "pm.collectionVariables.set('mpinRefreshToken', pm.response.json().data.refreshToken);"
1000
+ ]
1001
+ }
1002
+ }
1003
+ ],
1004
+ "request": {
1005
+ "method": "POST",
1006
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
1007
+ "body": { "mode": "raw", "raw": "{\n \"credentialRef\": \"{{credentialRef}}\",\n \"mpin\": \"{{mpin}}\"\n}" },
1008
+ "url": { "raw": "{{baseUrl}}/iam/mpin/login", "host": ["{{baseUrl}}"], "path": ["iam", "mpin", "login"] }
1009
+ }
1010
+ },
1011
+ {
1012
+ "name": "Password login as a second 'device'",
1013
+ "event": [{ "listen": "test", "script": { "type": "text/javascript", "exec": ["pm.test('HTTP 200', () => pm.response.to.have.status(200));"] } }],
1014
+ "request": {
1015
+ "method": "POST",
1016
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
1017
+ "body": { "mode": "raw", "raw": "{\n \"username\": \"{{username}}\",\n \"password\": \"{{password}}\"\n}" },
1018
+ "url": { "raw": "{{baseUrl}}/iam/authenticate", "host": ["{{baseUrl}}"], "path": ["iam", "authenticate"] }
1019
+ }
1020
+ },
1021
+ {
1022
+ "name": "MPIN session still refreshes — not evicted",
1023
+ "event": [
1024
+ {
1025
+ "listen": "test",
1026
+ "script": {
1027
+ "type": "text/javascript",
1028
+ "exec": [
1029
+ "pm.test('HTTP 200 — this is the per-device bucket fix', () => pm.response.to.have.status(200));",
1030
+ "pm.test('before Deliverable B this returned 401', () => pm.expect(pm.response.code).to.eql(200));"
1031
+ ]
1032
+ }
1033
+ }
1034
+ ],
1035
+ "request": {
1036
+ "method": "POST",
1037
+ "header": [{ "key": "Content-Type", "value": "application/json" }],
1038
+ "body": { "mode": "raw", "raw": "{\n \"refreshToken\": \"{{mpinRefreshToken}}\"\n}" },
1039
+ "url": { "raw": "{{baseUrl}}/iam/refresh-tokens", "host": ["{{baseUrl}}"], "path": ["iam", "refresh-tokens"] }
1040
+ }
1041
+ }
1042
+ ]
1043
+ }
1044
+ ]
1045
+ }