@flowdular/sdk 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (908) hide show
  1. package/.ai/agents/sandbox/business-manager.md +5 -1
  2. package/.ai/blueprints/author-spec/README.md +1 -1
  3. package/.ai/blueprints/author-spec/spec-requirements.yaml +44 -0
  4. package/.ai/blueprints/author-spec/steps.yaml +5 -5
  5. package/.ai/blueprints/author-spec/templates/module.yaml +99 -3
  6. package/.ai/blueprints/edit-module/gates.yaml +4 -0
  7. package/.ai/blueprints/edit-module/required-files.yaml +9 -0
  8. package/.ai/blueprints/new-module/gates.yaml +4 -0
  9. package/.ai/blueprints/new-module/spec-requirements.yaml +2 -2
  10. package/.ai/blueprints/release/gates.yaml +4 -0
  11. package/.ai/platform-capabilities.md +128 -0
  12. package/.ai/policies/capabilities.yaml +100 -0
  13. package/.ai/policies/path-ownership.yaml +5 -2
  14. package/.ai/policies/task-budgets.yaml +5 -3
  15. package/.ai/references/catalog/module.json +4 -4
  16. package/.ai/references/catalog/package.json +2 -2
  17. package/.ai/references/catalog/spec/module.yaml +5 -3
  18. package/.ai/references/catalog/src/platform.ts +2 -0
  19. package/.ai/references/catalog/src/services/catalog-service.ts +89 -1
  20. package/.ai/references/catalog/src/services/data-classes.ts +47 -0
  21. package/.ai/references/catalog/src/services/database-repository.ts +98 -1
  22. package/.ai/references/catalog/src/services/repository.ts +22 -1
  23. package/.ai/references/catalog/tests/data-classes.test.ts +157 -0
  24. package/.ai/references/catalog.provenance.json +12 -10
  25. package/.ai/skills/README.md +10 -0
  26. package/.ai/skills/agent-tool-design/SKILL.md +1 -2
  27. package/.ai/skills/auth-security-review/SKILL.md +1 -1
  28. package/.ai/skills/business-agent-design/SKILL.md +0 -1
  29. package/.ai/skills/deploy-operate/SKILL.md +114 -0
  30. package/.ai/skills/module-new/SKILL.md +29 -3
  31. package/.ai/skills/module-update/SKILL.md +9 -3
  32. package/.ai/skills/perf-audit/SKILL.md +0 -1
  33. package/.ai/skills/release-eject-pr/SKILL.md +0 -1
  34. package/.ai/skills/spec-interview/SKILL.md +120 -0
  35. package/.ai/skills/test-hardening/SKILL.md +1 -0
  36. package/.ai/skills/ux-design/SKILL.md +34 -3
  37. package/.ai/skills/variables/SKILL.md +0 -2
  38. package/.ai/skills/workflow-development/SKILL.md +0 -1
  39. package/AGENTS.md +4 -0
  40. package/docs/agent-contract.md +2 -2
  41. package/docs/design-system.md +185 -31
  42. package/modules/access/migrations/0001_access_core.down.sql +1 -0
  43. package/modules/access/migrations/0001_access_core.up.sql +28 -0
  44. package/modules/access/migrations/README.md +3 -0
  45. package/modules/access/module.json +33 -0
  46. package/modules/access/package.json +43 -0
  47. package/modules/access/spec/module.yaml +225 -0
  48. package/modules/access/src/acl/permissions.ts +6 -0
  49. package/modules/access/src/api/endpoints.ts +244 -0
  50. package/modules/access/src/client/ActivityView.tsrx +245 -0
  51. package/modules/access/src/client/AttestationsView.tsrx +176 -0
  52. package/modules/access/src/client/ReviewView.tsrx +586 -0
  53. package/modules/access/src/client/api.ts +132 -0
  54. package/modules/access/src/client/contribution.tsrx +41 -0
  55. package/modules/access/src/client/index.ts +41 -0
  56. package/modules/access/src/client/navigation.ts +57 -0
  57. package/modules/access/src/client/presentation.ts +69 -0
  58. package/modules/access/src/client/state.ts +72 -0
  59. package/modules/access/src/domain/types.ts +189 -0
  60. package/modules/access/src/index.ts +38 -0
  61. package/modules/access/src/platform.ts +60 -0
  62. package/modules/access/src/server/index.ts +42 -0
  63. package/modules/access/src/server/runtime.ts +105 -0
  64. package/modules/access/src/services/access-service.ts +298 -0
  65. package/modules/access/src/services/audit-window.ts +78 -0
  66. package/modules/access/src/services/auth-directory.ts +72 -0
  67. package/modules/access/src/services/changes.ts +91 -0
  68. package/modules/access/src/services/data-classes.ts +28 -0
  69. package/modules/access/src/services/database-repository.ts +185 -0
  70. package/modules/access/src/services/directory.ts +102 -0
  71. package/modules/access/src/services/index.ts +33 -0
  72. package/modules/access/src/services/list-exports.ts +150 -0
  73. package/modules/access/src/services/migration.ts +54 -0
  74. package/modules/access/src/services/repository.ts +30 -0
  75. package/modules/access/src/services/review.ts +178 -0
  76. package/modules/access/translations/en.json +155 -0
  77. package/modules/access/translations/pl.json +155 -0
  78. package/modules/agents/migrations/0022_credential_rotation_inventory.down.sql +2 -0
  79. package/modules/agents/migrations/0022_credential_rotation_inventory.up.sql +7 -0
  80. package/modules/agents/migrations/0023_agents_retention_indexes.down.sql +2 -0
  81. package/modules/agents/migrations/0023_agents_retention_indexes.up.sql +9 -0
  82. package/modules/agents/migrations/0024_agent_meter_refusals.down.sql +3 -0
  83. package/modules/agents/migrations/0024_agent_meter_refusals.up.sql +22 -0
  84. package/modules/agents/migrations/0025_agent_list_indexes.down.sql +3 -0
  85. package/modules/agents/migrations/0025_agent_list_indexes.up.sql +11 -0
  86. package/modules/agents/module.json +30 -4
  87. package/modules/agents/package.json +2 -1
  88. package/modules/agents/spec/module.yaml +63 -5
  89. package/modules/agents/src/api/endpoints.ts +291 -33
  90. package/modules/agents/src/cli/commands.json +11 -0
  91. package/modules/agents/src/cli/index.ts +45 -0
  92. package/modules/agents/src/client/AgentDashboardWidget.tsrx +19 -11
  93. package/modules/agents/src/client/AgentDefinitionList.tsrx +53 -10
  94. package/modules/agents/src/client/AgentPlaygroundView.tsrx +4 -4
  95. package/modules/agents/src/client/AgentProceduresView.tsrx +2 -2
  96. package/modules/agents/src/client/AgentRunsView.tsrx +143 -40
  97. package/modules/agents/src/client/AgentsView.tsrx +109 -33
  98. package/modules/agents/src/client/api.ts +99 -21
  99. package/modules/agents/src/client/state.ts +97 -0
  100. package/modules/agents/src/domain/types.ts +44 -4
  101. package/modules/agents/src/platform.ts +42 -0
  102. package/modules/agents/src/server/action-execution.ts +201 -106
  103. package/modules/agents/src/server/runtime.ts +32 -0
  104. package/modules/agents/src/services/agent-service.ts +158 -30
  105. package/modules/agents/src/services/credential-rotation.ts +185 -0
  106. package/modules/agents/src/services/credential-vault.ts +71 -46
  107. package/modules/agents/src/services/data-classes.ts +176 -0
  108. package/modules/agents/src/services/database-repository.ts +393 -101
  109. package/modules/agents/src/services/metering.ts +111 -0
  110. package/modules/agents/src/services/migration.ts +109 -0
  111. package/modules/agents/src/services/module-agent-preflight.ts +17 -9
  112. package/modules/agents/src/services/notifications.ts +98 -0
  113. package/modules/agents/src/services/provider-repository.ts +9 -18
  114. package/modules/agents/src/services/provider-service.ts +1 -9
  115. package/modules/agents/src/services/reports.ts +107 -0
  116. package/modules/agents/src/services/repository.ts +86 -6
  117. package/modules/agents/src/services/usage-service.ts +2 -0
  118. package/modules/agents/src/services/worker.ts +114 -4
  119. package/modules/agents/src/settings.ts +14 -3
  120. package/modules/agents/translations/en.json +14 -4
  121. package/modules/agents/translations/pl.json +14 -4
  122. package/modules/approvals/migrations/0001_approvals_core.down.sql +3 -0
  123. package/modules/approvals/migrations/0001_approvals_core.up.sql +78 -0
  124. package/modules/approvals/migrations/0002_approvals_expiry_routing_role.down.sql +2 -0
  125. package/modules/approvals/migrations/0002_approvals_expiry_routing_role.up.sql +19 -0
  126. package/modules/approvals/migrations/0003_approvals_retention_indexes.down.sql +6 -0
  127. package/modules/approvals/migrations/0003_approvals_retention_indexes.up.sql +14 -0
  128. package/modules/approvals/migrations/0004_approvals_inbox_keyset_indexes.down.sql +5 -0
  129. package/modules/approvals/migrations/0004_approvals_inbox_keyset_indexes.up.sql +12 -0
  130. package/modules/approvals/migrations/README.md +3 -0
  131. package/modules/approvals/module.json +34 -0
  132. package/modules/approvals/package.json +43 -0
  133. package/modules/approvals/spec/module.yaml +318 -0
  134. package/modules/approvals/src/acl/permissions.ts +7 -0
  135. package/modules/approvals/src/api/endpoints.ts +457 -0
  136. package/modules/approvals/src/client/ApprovalRequestDrawer.tsrx +138 -0
  137. package/modules/approvals/src/client/ApprovalsView.tsrx +562 -0
  138. package/modules/approvals/src/client/PendingCountWidget.tsrx +52 -0
  139. package/modules/approvals/src/client/api.ts +203 -0
  140. package/modules/approvals/src/client/approvals.css +51 -0
  141. package/modules/approvals/src/client/contribution.tsrx +44 -0
  142. package/modules/approvals/src/client/index.ts +26 -0
  143. package/modules/approvals/src/client/navigation.ts +39 -0
  144. package/modules/approvals/src/client/pending-count.ts +26 -0
  145. package/modules/approvals/src/client/presentation.ts +52 -0
  146. package/modules/approvals/src/client/state.ts +141 -0
  147. package/modules/approvals/src/domain/capability.ts +95 -0
  148. package/modules/approvals/src/domain/types.ts +154 -0
  149. package/modules/approvals/src/index.ts +52 -0
  150. package/modules/approvals/src/platform.ts +128 -0
  151. package/modules/approvals/src/server/index.ts +42 -0
  152. package/modules/approvals/src/server/runtime.ts +181 -0
  153. package/modules/approvals/src/services/approvals-service.ts +557 -0
  154. package/modules/approvals/src/services/callbacks.ts +75 -0
  155. package/modules/approvals/src/services/data-classes.ts +167 -0
  156. package/modules/approvals/src/services/database-repository.ts +724 -0
  157. package/modules/approvals/src/services/eligibility.ts +121 -0
  158. package/modules/approvals/src/services/expiry-runner.ts +81 -0
  159. package/modules/approvals/src/services/index.ts +31 -0
  160. package/modules/approvals/src/services/migration.ts +222 -0
  161. package/modules/approvals/src/services/notifications.ts +95 -0
  162. package/modules/approvals/src/services/repository.ts +161 -0
  163. package/modules/approvals/src/services/service-error.ts +70 -0
  164. package/modules/approvals/src/settings.ts +102 -0
  165. package/modules/approvals/translations/en.json +104 -0
  166. package/modules/approvals/translations/pl.json +104 -0
  167. package/modules/audit/migrations/0001_audit_core.down.sql +8 -0
  168. package/modules/audit/migrations/0001_audit_core.up.sql +107 -0
  169. package/modules/audit/migrations/0002_audit_sweep_routing_role.down.sql +2 -0
  170. package/modules/audit/migrations/0002_audit_sweep_routing_role.up.sql +21 -0
  171. package/modules/audit/migrations/0003_audit_export_request.down.sql +12 -0
  172. package/modules/audit/migrations/0003_audit_export_request.up.sql +40 -0
  173. package/modules/audit/migrations/0004_audit_hold_seal_erasure.down.sql +17 -0
  174. package/modules/audit/migrations/0004_audit_hold_seal_erasure.up.sql +192 -0
  175. package/modules/audit/migrations/0005_audit_event_format.down.sql +17 -0
  176. package/modules/audit/migrations/0005_audit_event_format.up.sql +60 -0
  177. package/modules/audit/migrations/0006_audit_own_class_walks.down.sql +3 -0
  178. package/modules/audit/migrations/0006_audit_own_class_walks.up.sql +14 -0
  179. package/modules/audit/migrations/README.md +3 -0
  180. package/modules/audit/module.json +32 -0
  181. package/modules/audit/package.json +49 -0
  182. package/modules/audit/spec/module.yaml +809 -0
  183. package/modules/audit/src/acl/permissions.ts +7 -0
  184. package/modules/audit/src/api/endpoints.ts +315 -0
  185. package/modules/audit/src/cli/commands.json +95 -0
  186. package/modules/audit/src/cli/index.ts +658 -0
  187. package/modules/audit/src/client/DataClassesView.tsrx +420 -0
  188. package/modules/audit/src/client/ExportsView.tsrx +256 -0
  189. package/modules/audit/src/client/HoldsView.tsrx +604 -0
  190. package/modules/audit/src/client/SweepsView.tsrx +251 -0
  191. package/modules/audit/src/client/api.ts +185 -0
  192. package/modules/audit/src/client/contribution.tsrx +52 -0
  193. package/modules/audit/src/client/index.ts +23 -0
  194. package/modules/audit/src/client/navigation.ts +74 -0
  195. package/modules/audit/src/client/presentation.ts +105 -0
  196. package/modules/audit/src/client/state.ts +86 -0
  197. package/modules/audit/src/domain/data-classes.ts +16 -0
  198. package/modules/audit/src/domain/types.ts +339 -0
  199. package/modules/audit/src/index.ts +76 -0
  200. package/modules/audit/src/platform.ts +44 -0
  201. package/modules/audit/src/server/index.ts +177 -0
  202. package/modules/audit/src/server/runtime.ts +303 -0
  203. package/modules/audit/src/services/anchor-key.ts +143 -0
  204. package/modules/audit/src/services/anchor-rotation.ts +147 -0
  205. package/modules/audit/src/services/audit-runners.ts +278 -0
  206. package/modules/audit/src/services/backup-guard.ts +95 -0
  207. package/modules/audit/src/services/database-repository.ts +1946 -0
  208. package/modules/audit/src/services/declared-classes.ts +64 -0
  209. package/modules/audit/src/services/erasure-port.ts +126 -0
  210. package/modules/audit/src/services/erasure-service.ts +770 -0
  211. package/modules/audit/src/services/export-directory.ts +106 -0
  212. package/modules/audit/src/services/export-service.ts +714 -0
  213. package/modules/audit/src/services/hold-service.ts +295 -0
  214. package/modules/audit/src/services/index.ts +17 -0
  215. package/modules/audit/src/services/migration.ts +658 -0
  216. package/modules/audit/src/services/own-classes.ts +318 -0
  217. package/modules/audit/src/services/platform-version.ts +23 -0
  218. package/modules/audit/src/services/repository.ts +495 -0
  219. package/modules/audit/src/services/retention-service.ts +191 -0
  220. package/modules/audit/src/services/seal-service.ts +769 -0
  221. package/modules/audit/src/services/service-error.ts +61 -0
  222. package/modules/audit/src/services/subject-keys.ts +90 -0
  223. package/modules/audit/src/services/sweep-service.ts +298 -0
  224. package/modules/audit/src/services/zip.ts +217 -0
  225. package/modules/audit/src/settings.ts +69 -0
  226. package/modules/audit/translations/en.json +210 -0
  227. package/modules/audit/translations/pl.json +210 -0
  228. package/modules/auth/README.md +77 -29
  229. package/modules/auth/migrations/0016_external_identities.up.sql +16 -0
  230. package/modules/auth/migrations/0017_mfa_key_id.up.sql +8 -0
  231. package/modules/auth/migrations/0018_retire_bundled_module_scopes.up.sql +29 -0
  232. package/modules/auth/migrations/0019_notifications_member_scopes.up.sql +44 -0
  233. package/modules/auth/migrations/0020_identity_providers.up.sql +61 -0
  234. package/modules/auth/migrations/0021_membership_status.up.sql +19 -0
  235. package/modules/auth/migrations/0022_auth_provider_scopes.up.sql +45 -0
  236. package/modules/auth/migrations/0023_enterprise_module_scopes.down.sql +19 -0
  237. package/modules/auth/migrations/0023_enterprise_module_scopes.up.sql +71 -0
  238. package/modules/auth/migrations/0024_approvals_member_scopes.down.sql +22 -0
  239. package/modules/auth/migrations/0024_approvals_member_scopes.up.sql +48 -0
  240. package/modules/auth/migrations/0025_audit_holds_owner_scope.down.sql +21 -0
  241. package/modules/auth/migrations/0025_audit_holds_owner_scope.up.sql +47 -0
  242. package/modules/auth/migrations/0026_auth_export_keyset_indexes.down.sql +6 -0
  243. package/modules/auth/migrations/0026_auth_export_keyset_indexes.up.sql +15 -0
  244. package/modules/auth/migrations/0027_workflow_automation_profile_scopes.down.sql +34 -0
  245. package/modules/auth/migrations/0027_workflow_automation_profile_scopes.up.sql +72 -0
  246. package/modules/auth/migrations/0028_auth_member_search_prefix_indexes.down.sql +6 -0
  247. package/modules/auth/migrations/0028_auth_member_search_prefix_indexes.up.sql +21 -0
  248. package/modules/auth/migrations/0029_reports_exports_access_scopes.down.sql +22 -0
  249. package/modules/auth/migrations/0029_reports_exports_access_scopes.up.sql +56 -0
  250. package/modules/auth/migrations/0030_auth_membership_keyset_index.down.sql +4 -0
  251. package/modules/auth/migrations/0030_auth_membership_keyset_index.up.sql +10 -0
  252. package/modules/auth/migrations/0031_membership_scope_backfills_under_rls.down.sql +4 -0
  253. package/modules/auth/migrations/0031_membership_scope_backfills_under_rls.up.sql +31 -0
  254. package/modules/auth/migrations/0032_audit_actor_service.down.sql +9 -0
  255. package/modules/auth/migrations/0032_audit_actor_service.up.sql +10 -0
  256. package/modules/auth/migrations/0033_auth_account_keyset_indexes.down.sql +5 -0
  257. package/modules/auth/migrations/0033_auth_account_keyset_indexes.up.sql +23 -0
  258. package/modules/auth/module.json +3 -3
  259. package/modules/auth/package.json +2 -1
  260. package/modules/auth/spec/module.yaml +372 -3
  261. package/modules/auth/src/acl/scopes.ts +50 -6
  262. package/modules/auth/src/cli/commands.json +11 -0
  263. package/modules/auth/src/cli/greenfield.ts +2 -2
  264. package/modules/auth/src/cli/index.ts +14 -0
  265. package/modules/auth/src/cli/secrets.ts +114 -0
  266. package/modules/auth/src/client/AuthenticationCore.tsrx +61 -8
  267. package/modules/auth/src/client/api.ts +72 -6
  268. package/modules/auth/src/client/contribution.tsrx +23 -0
  269. package/modules/auth/src/client/index.ts +6 -0
  270. package/modules/auth/src/client/providers/IdentityProviderForm.tsrx +191 -0
  271. package/modules/auth/src/client/providers/IdentityProvidersView.tsrx +358 -0
  272. package/modules/auth/src/client/providers/api.ts +128 -0
  273. package/modules/auth/src/client/providers/provider-columns.tsrx +60 -0
  274. package/modules/auth/src/client/providers/state.ts +35 -0
  275. package/modules/auth/src/client/security/SecurityView.tsrx +9 -1
  276. package/modules/auth/src/client/state.ts +15 -1
  277. package/modules/auth/src/domain/types.ts +55 -0
  278. package/modules/auth/src/index.ts +11 -1
  279. package/modules/auth/src/middleware/authentication.ts +4 -0
  280. package/modules/auth/src/server/endpoints.ts +467 -171
  281. package/modules/auth/src/server/http.ts +3 -2
  282. package/modules/auth/src/server/index.ts +22 -1
  283. package/modules/auth/src/server/membership-endpoints.ts +62 -0
  284. package/modules/auth/src/server/mfa-enforcement.ts +156 -0
  285. package/modules/auth/src/server/oidc.ts +389 -0
  286. package/modules/auth/src/server/provider-endpoints.ts +246 -0
  287. package/modules/auth/src/server/provider-host.ts +77 -0
  288. package/modules/auth/src/server/runtime.ts +231 -55
  289. package/modules/auth/src/services/auth-service-error.ts +7 -2
  290. package/modules/auth/src/services/auth-service.ts +948 -51
  291. package/modules/auth/src/services/data-classes.ts +236 -0
  292. package/modules/auth/src/services/database-repository.ts +853 -71
  293. package/modules/auth/src/services/identity-provider-service.ts +571 -0
  294. package/modules/auth/src/services/mail-delivery.ts +16 -16
  295. package/modules/auth/src/services/mail-message.ts +54 -0
  296. package/modules/auth/src/services/mail-port.ts +24 -0
  297. package/modules/auth/src/services/mail-smtp.ts +85 -0
  298. package/modules/auth/src/services/mfa-rotation.ts +144 -0
  299. package/modules/auth/src/services/migration.ts +810 -0
  300. package/modules/auth/src/services/password.ts +14 -0
  301. package/modules/auth/src/services/provider-secret-rotation.ts +169 -0
  302. package/modules/auth/src/services/provider-secrets.ts +136 -0
  303. package/modules/auth/src/services/repository.ts +321 -5
  304. package/modules/auth/src/services/session-sweep-runner.ts +73 -0
  305. package/modules/auth/src/services/settings-store.ts +10 -83
  306. package/modules/auth/src/services/totp.ts +88 -62
  307. package/modules/auth/src/services/validation.ts +194 -7
  308. package/modules/auth/src/settings.ts +18 -0
  309. package/modules/auth/translations/en.json +84 -1
  310. package/modules/auth/translations/pl.json +84 -1
  311. package/modules/automations/migrations/0005_secret_rotation_inventory.down.sql +1 -0
  312. package/modules/automations/migrations/0005_secret_rotation_inventory.up.sql +6 -0
  313. package/modules/automations/migrations/0006_automations_list_sort_indexes.down.sql +4 -0
  314. package/modules/automations/migrations/0006_automations_list_sort_indexes.up.sql +11 -0
  315. package/modules/automations/module.json +24 -6
  316. package/modules/automations/package.json +4 -1
  317. package/modules/automations/spec/module.yaml +81 -7
  318. package/modules/automations/src/api/endpoints.ts +202 -28
  319. package/modules/automations/src/cli/commands.json +17 -0
  320. package/modules/automations/src/cli/index.ts +114 -0
  321. package/modules/automations/src/client/AutomationScheduleForm.tsrx +77 -19
  322. package/modules/automations/src/client/AutomationSchedulesView.tsrx +144 -40
  323. package/modules/automations/src/client/AutomationTriggersView.tsrx +131 -32
  324. package/modules/automations/src/client/api.ts +62 -19
  325. package/modules/automations/src/client/presentation.ts +20 -2
  326. package/modules/automations/src/client/state.ts +84 -0
  327. package/modules/automations/src/domain/cadence.ts +99 -3
  328. package/modules/automations/src/domain/cron.ts +366 -0
  329. package/modules/automations/src/domain/time-zone.ts +61 -0
  330. package/modules/automations/src/platform.ts +38 -4
  331. package/modules/automations/src/server/index.ts +11 -0
  332. package/modules/automations/src/server/runtime.ts +46 -22
  333. package/modules/{automations-workflows-integration/src/server/adapter.ts → automations/src/server/workflow-target.ts} +13 -14
  334. package/modules/{automations-workflows-integration/src/server/tools.ts → automations/src/server/workflow-tools.ts} +0 -0
  335. package/modules/automations/src/services/data-classes.ts +62 -0
  336. package/modules/automations/src/services/database-repository.ts +152 -38
  337. package/modules/automations/src/services/migration.ts +119 -9
  338. package/modules/automations/src/services/repository.ts +52 -1
  339. package/modules/automations/src/services/schedule-runner.ts +84 -0
  340. package/modules/automations/src/services/schedule-service.ts +152 -26
  341. package/modules/automations/src/services/secret-rotation.ts +176 -0
  342. package/modules/automations/src/services/secret-vault.ts +68 -41
  343. package/modules/automations/src/services/trigger-service.ts +13 -10
  344. package/modules/automations/translations/en.json +15 -2
  345. package/modules/automations/translations/pl.json +15 -2
  346. package/modules/connectors/migrations/0001_connectors_core.down.sql +3 -0
  347. package/modules/connectors/migrations/0001_connectors_core.up.sql +72 -0
  348. package/modules/connectors/migrations/0002_connectors_call_keys.down.sql +3 -0
  349. package/modules/connectors/migrations/0002_connectors_call_keys.up.sql +18 -0
  350. package/modules/connectors/migrations/README.md +3 -0
  351. package/modules/connectors/module.json +28 -0
  352. package/modules/{automations-workflows-integration → connectors}/package.json +27 -26
  353. package/modules/connectors/spec/module.yaml +447 -0
  354. package/modules/connectors/src/acl/permissions.ts +6 -0
  355. package/modules/connectors/src/agent/tools.ts +180 -0
  356. package/modules/connectors/src/api/endpoints.ts +572 -0
  357. package/modules/connectors/src/client/ConnectorCallsView.tsrx +358 -0
  358. package/modules/connectors/src/client/ConnectorForm.tsrx +424 -0
  359. package/modules/connectors/src/client/ConnectorsView.tsrx +711 -0
  360. package/modules/connectors/src/client/api.ts +281 -0
  361. package/modules/connectors/src/client/consent.ts +48 -0
  362. package/modules/connectors/src/client/contribution.tsrx +37 -0
  363. package/modules/connectors/src/client/index.ts +22 -0
  364. package/modules/connectors/src/client/navigation.ts +42 -0
  365. package/modules/connectors/src/client/presentation.ts +38 -0
  366. package/modules/connectors/src/client/state.ts +175 -0
  367. package/modules/connectors/src/domain/calls.ts +70 -0
  368. package/modules/connectors/src/domain/definitions.ts +190 -0
  369. package/modules/connectors/src/domain/http-json.ts +68 -0
  370. package/modules/connectors/src/domain/types.ts +185 -0
  371. package/modules/connectors/src/index.ts +64 -0
  372. package/modules/connectors/src/platform.ts +56 -0
  373. package/modules/connectors/src/server/index.ts +66 -0
  374. package/modules/connectors/src/server/runtime.ts +163 -0
  375. package/modules/connectors/src/services/call-service.ts +903 -0
  376. package/modules/connectors/src/services/connectors-service.ts +653 -0
  377. package/modules/connectors/src/services/credential-vault.ts +188 -0
  378. package/modules/connectors/src/services/data-classes.ts +158 -0
  379. package/modules/connectors/src/services/database-repository.ts +897 -0
  380. package/modules/connectors/src/services/egress.ts +261 -0
  381. package/modules/connectors/src/services/index.ts +19 -0
  382. package/modules/connectors/src/services/migration.ts +166 -0
  383. package/modules/connectors/src/services/repository.ts +175 -0
  384. package/modules/connectors/src/services/service-error.ts +46 -0
  385. package/modules/connectors/src/settings.ts +69 -0
  386. package/modules/connectors/translations/en.json +191 -0
  387. package/modules/connectors/translations/pl.json +191 -0
  388. package/modules/directory/migrations/0001_directory_core.down.sql +5 -0
  389. package/modules/directory/migrations/0001_directory_core.up.sql +111 -0
  390. package/modules/directory/migrations/README.md +3 -0
  391. package/modules/directory/module.json +27 -0
  392. package/modules/directory/package.json +46 -0
  393. package/modules/directory/spec/module.yaml +351 -0
  394. package/modules/directory/src/acl/permissions.ts +7 -0
  395. package/modules/directory/src/api/endpoints.ts +478 -0
  396. package/modules/directory/src/api/scim-endpoints.ts +516 -0
  397. package/modules/directory/src/client/GroupMappingsView.tsrx +430 -0
  398. package/modules/directory/src/client/ProvisioningLogView.tsrx +248 -0
  399. package/modules/directory/src/client/ScimTokensView.tsrx +587 -0
  400. package/modules/directory/src/client/api.ts +222 -0
  401. package/modules/directory/src/client/contribution.tsrx +46 -0
  402. package/modules/directory/src/client/index.ts +22 -0
  403. package/modules/directory/src/client/navigation.ts +57 -0
  404. package/modules/directory/src/client/presentation.ts +143 -0
  405. package/modules/directory/src/client/state.ts +173 -0
  406. package/modules/directory/src/domain/scim.ts +346 -0
  407. package/modules/directory/src/domain/types.ts +166 -0
  408. package/modules/directory/src/index.ts +44 -0
  409. package/modules/directory/src/platform.ts +43 -0
  410. package/modules/directory/src/server/index.ts +17 -0
  411. package/modules/directory/src/server/runtime.ts +124 -0
  412. package/modules/directory/src/services/auth-port.ts +127 -0
  413. package/modules/directory/src/services/data-classes.ts +41 -0
  414. package/modules/directory/src/services/database-repository.ts +1067 -0
  415. package/modules/directory/src/services/directory-service.ts +311 -0
  416. package/modules/directory/src/services/index.ts +43 -0
  417. package/modules/directory/src/services/migration.ts +146 -0
  418. package/modules/directory/src/services/provisioning-service.ts +1420 -0
  419. package/modules/directory/src/services/rate-limiter.ts +103 -0
  420. package/modules/directory/src/services/repository.ts +208 -0
  421. package/modules/directory/src/services/service-error.ts +42 -0
  422. package/modules/directory/src/services/token-service.ts +215 -0
  423. package/modules/directory/src/settings.ts +86 -0
  424. package/modules/directory/translations/en.json +185 -0
  425. package/modules/directory/translations/pl.json +185 -0
  426. package/modules/documents/migrations/0001_documents_core.down.sql +1 -0
  427. package/modules/documents/migrations/0001_documents_core.up.sql +35 -0
  428. package/modules/documents/migrations/0002_documents_files_page.down.sql +1 -0
  429. package/modules/documents/migrations/0002_documents_files_page.up.sql +6 -0
  430. package/modules/documents/migrations/README.md +3 -0
  431. package/modules/documents/module.json +28 -0
  432. package/modules/documents/package.json +49 -0
  433. package/modules/documents/spec/module.yaml +247 -0
  434. package/modules/documents/src/acl/permissions.ts +6 -0
  435. package/modules/documents/src/api/endpoints.ts +401 -0
  436. package/modules/documents/src/client/DocumentUploader.tsrx +148 -0
  437. package/modules/documents/src/client/DocumentsView.tsrx +540 -0
  438. package/modules/documents/src/client/api.ts +209 -0
  439. package/modules/documents/src/client/contribution.tsrx +29 -0
  440. package/modules/documents/src/client/download.ts +70 -0
  441. package/modules/documents/src/client/index.ts +22 -0
  442. package/modules/documents/src/client/navigation.ts +25 -0
  443. package/modules/documents/src/client/presentation.ts +110 -0
  444. package/modules/documents/src/client/state.ts +117 -0
  445. package/modules/documents/src/domain/attachments.ts +84 -0
  446. package/modules/documents/src/domain/types.ts +80 -0
  447. package/modules/documents/src/index.ts +46 -0
  448. package/modules/documents/src/platform.ts +61 -0
  449. package/modules/documents/src/server/index.ts +30 -0
  450. package/modules/documents/src/server/runtime.ts +107 -0
  451. package/modules/documents/src/services/attachments.ts +39 -0
  452. package/modules/documents/src/services/data-classes.ts +25 -0
  453. package/modules/documents/src/services/database-repository.ts +259 -0
  454. package/modules/documents/src/services/documents-service.ts +586 -0
  455. package/modules/documents/src/services/index.ts +18 -0
  456. package/modules/documents/src/services/migration.ts +82 -0
  457. package/modules/documents/src/services/repository.ts +48 -0
  458. package/modules/documents/src/settings.ts +107 -0
  459. package/modules/documents/translations/en.json +111 -0
  460. package/modules/documents/translations/pl.json +111 -0
  461. package/modules/exports/migrations/0001_exports_core.down.sql +2 -0
  462. package/modules/exports/migrations/0001_exports_core.up.sql +28 -0
  463. package/modules/exports/migrations/0002_exports_job_routing_role.down.sql +4 -0
  464. package/modules/exports/migrations/0002_exports_job_routing_role.up.sql +21 -0
  465. package/modules/exports/migrations/README.md +3 -0
  466. package/modules/exports/module.json +28 -0
  467. package/modules/exports/package.json +44 -0
  468. package/modules/exports/spec/module.yaml +229 -0
  469. package/modules/exports/src/acl/permissions.ts +6 -0
  470. package/modules/exports/src/api/endpoints.ts +211 -0
  471. package/modules/exports/src/client/ExportsView.tsrx +327 -0
  472. package/modules/exports/src/client/api.ts +133 -0
  473. package/modules/exports/src/client/contribution.tsrx +31 -0
  474. package/modules/exports/src/client/index.ts +20 -0
  475. package/modules/exports/src/client/navigation.ts +25 -0
  476. package/modules/exports/src/client/presentation.ts +53 -0
  477. package/modules/exports/src/client/state.ts +56 -0
  478. package/modules/exports/src/domain/lists.ts +29 -0
  479. package/modules/exports/src/domain/types.ts +120 -0
  480. package/modules/exports/src/index.ts +41 -0
  481. package/modules/exports/src/platform.ts +80 -0
  482. package/modules/exports/src/server/index.ts +51 -0
  483. package/modules/exports/src/server/runtime.ts +160 -0
  484. package/modules/exports/src/services/data-classes.ts +56 -0
  485. package/modules/exports/src/services/database-repository.ts +405 -0
  486. package/modules/exports/src/services/export-runner.ts +123 -0
  487. package/modules/exports/src/services/export-service.ts +356 -0
  488. package/modules/exports/src/services/index.ts +6 -0
  489. package/modules/exports/src/services/list-registry.ts +105 -0
  490. package/modules/exports/src/services/migration.ts +89 -0
  491. package/modules/exports/src/services/repository.ts +97 -0
  492. package/modules/exports/src/settings.ts +70 -0
  493. package/modules/exports/translations/en.json +69 -0
  494. package/modules/exports/translations/pl.json +69 -0
  495. package/modules/import/migrations/0001_import_core.down.sql +6 -0
  496. package/modules/import/migrations/0001_import_core.up.sql +61 -0
  497. package/modules/import/migrations/0002_import_job_routing_role.down.sql +4 -0
  498. package/modules/import/migrations/0002_import_job_routing_role.up.sql +20 -0
  499. package/modules/import/migrations/0003_import_jobs_export_index.down.sql +1 -0
  500. package/modules/import/migrations/0003_import_jobs_export_index.up.sql +7 -0
  501. package/modules/import/migrations/0004_import_jobs_traceparent.down.sql +1 -0
  502. package/modules/import/migrations/0004_import_jobs_traceparent.up.sql +7 -0
  503. package/modules/import/migrations/README.md +3 -0
  504. package/modules/import/module.json +37 -0
  505. package/modules/import/package.json +50 -0
  506. package/modules/import/spec/module.yaml +335 -0
  507. package/modules/import/src/acl/permissions.ts +6 -0
  508. package/modules/import/src/api/endpoints.ts +400 -0
  509. package/modules/import/src/client/ImportJobDrawer.tsrx +229 -0
  510. package/modules/import/src/client/ImportsView.tsrx +455 -0
  511. package/modules/import/src/client/NewImportForm.tsrx +274 -0
  512. package/modules/import/src/client/api.ts +243 -0
  513. package/modules/import/src/client/contribution.tsrx +29 -0
  514. package/modules/import/src/client/index.ts +23 -0
  515. package/modules/import/src/client/navigation.ts +25 -0
  516. package/modules/import/src/client/presentation.ts +59 -0
  517. package/modules/import/src/client/state.ts +43 -0
  518. package/modules/import/src/domain/csv.ts +258 -0
  519. package/modules/import/src/domain/ports.ts +157 -0
  520. package/modules/import/src/domain/types.ts +179 -0
  521. package/modules/import/src/index.ts +58 -0
  522. package/modules/import/src/platform.ts +60 -0
  523. package/modules/import/src/server/index.ts +49 -0
  524. package/modules/import/src/server/runtime.ts +158 -0
  525. package/modules/import/src/services/csv-source.ts +121 -0
  526. package/modules/import/src/services/data-classes.ts +63 -0
  527. package/modules/import/src/services/database-repository.ts +780 -0
  528. package/modules/import/src/services/import-runner.ts +127 -0
  529. package/modules/import/src/services/import-service.ts +872 -0
  530. package/modules/import/src/services/index.ts +6 -0
  531. package/modules/import/src/services/migration.ts +156 -0
  532. package/modules/import/src/services/port-registry.ts +158 -0
  533. package/modules/import/src/services/repository.ts +147 -0
  534. package/modules/import/src/settings.ts +70 -0
  535. package/modules/import/translations/en.json +175 -0
  536. package/modules/import/translations/pl.json +175 -0
  537. package/modules/metering/migrations/0001_metering_core.down.sql +6 -0
  538. package/modules/metering/migrations/0001_metering_core.up.sql +119 -0
  539. package/modules/metering/migrations/0002_metering_bucket_export.down.sql +3 -0
  540. package/modules/metering/migrations/0002_metering_bucket_export.up.sql +5 -0
  541. package/modules/metering/migrations/README.md +3 -0
  542. package/modules/metering/module.json +46 -0
  543. package/modules/metering/package.json +50 -0
  544. package/modules/metering/spec/module.yaml +269 -0
  545. package/modules/metering/src/acl/permissions.ts +5 -0
  546. package/modules/metering/src/api/endpoints.ts +104 -0
  547. package/modules/metering/src/cli/commands.json +28 -0
  548. package/modules/metering/src/cli/index.ts +246 -0
  549. package/modules/metering/src/client/LimitsView.tsrx +170 -0
  550. package/modules/metering/src/client/UsageSummaryWidget.tsrx +63 -0
  551. package/modules/metering/src/client/UsageView.tsrx +205 -0
  552. package/modules/metering/src/client/api.ts +91 -0
  553. package/modules/metering/src/client/contribution.tsrx +42 -0
  554. package/modules/metering/src/client/index.ts +26 -0
  555. package/modules/metering/src/client/navigation.ts +54 -0
  556. package/modules/metering/src/client/presentation.ts +63 -0
  557. package/modules/metering/src/client/state.ts +31 -0
  558. package/modules/metering/src/domain/meters.ts +100 -0
  559. package/modules/metering/src/domain/types.ts +71 -0
  560. package/modules/metering/src/index.ts +51 -0
  561. package/modules/metering/src/platform.ts +84 -0
  562. package/modules/metering/src/server/index.ts +40 -0
  563. package/modules/metering/src/server/runtime.ts +165 -0
  564. package/modules/metering/src/services/data-classes.ts +34 -0
  565. package/modules/metering/src/services/database-repository.ts +574 -0
  566. package/modules/metering/src/services/index.ts +9 -0
  567. package/modules/metering/src/services/meter-registry.ts +145 -0
  568. package/modules/metering/src/services/metering-service.ts +441 -0
  569. package/modules/metering/src/services/migration.ts +183 -0
  570. package/modules/metering/src/services/notifications.ts +75 -0
  571. package/modules/metering/src/services/reports.ts +105 -0
  572. package/modules/metering/src/services/repository.ts +132 -0
  573. package/modules/metering/src/services/service-error.ts +46 -0
  574. package/modules/metering/src/settings.ts +47 -0
  575. package/modules/metering/translations/en.json +68 -0
  576. package/modules/metering/translations/pl.json +68 -0
  577. package/modules/notifications/migrations/0001_notifications_core.down.sql +1 -0
  578. package/modules/notifications/migrations/0001_notifications_core.up.sql +26 -0
  579. package/modules/notifications/migrations/0002_notifications_preferences.down.sql +1 -0
  580. package/modules/notifications/migrations/0002_notifications_preferences.up.sql +17 -0
  581. package/modules/notifications/migrations/0003_notifications_webhook_subscriptions.down.sql +1 -0
  582. package/modules/notifications/migrations/0003_notifications_webhook_subscriptions.up.sql +30 -0
  583. package/modules/notifications/migrations/0004_notifications_deliveries.down.sql +1 -0
  584. package/modules/notifications/migrations/0004_notifications_deliveries.up.sql +35 -0
  585. package/modules/notifications/migrations/0005_notifications_delivery_routing_role.down.sql +2 -0
  586. package/modules/notifications/migrations/0005_notifications_delivery_routing_role.up.sql +17 -0
  587. package/modules/notifications/migrations/0006_notifications_delivery_title.down.sql +1 -0
  588. package/modules/notifications/migrations/0006_notifications_delivery_title.up.sql +11 -0
  589. package/modules/notifications/migrations/0007_notifications_secret_rotation_inventory.down.sql +2 -0
  590. package/modules/notifications/migrations/0007_notifications_secret_rotation_inventory.up.sql +19 -0
  591. package/modules/notifications/migrations/0008_notifications_delivery_claim.down.sql +8 -0
  592. package/modules/notifications/migrations/0008_notifications_delivery_claim.up.sql +19 -0
  593. package/modules/notifications/migrations/0009_notifications_kind_approvals.down.sql +16 -0
  594. package/modules/notifications/migrations/0009_notifications_kind_approvals.up.sql +18 -0
  595. package/modules/notifications/migrations/0010_notifications_kind_meter_threshold.down.sql +16 -0
  596. package/modules/notifications/migrations/0010_notifications_kind_meter_threshold.up.sql +18 -0
  597. package/modules/notifications/migrations/0011_notifications_retention_keyset.down.sql +5 -0
  598. package/modules/notifications/migrations/0011_notifications_retention_keyset.up.sql +15 -0
  599. package/modules/notifications/migrations/0012_notifications_email_channel.down.sql +12 -0
  600. package/modules/notifications/migrations/0012_notifications_email_channel.up.sql +38 -0
  601. package/modules/notifications/migrations/0013_notifications_list_pages.down.sql +5 -0
  602. package/modules/notifications/migrations/0013_notifications_list_pages.up.sql +13 -0
  603. package/modules/notifications/migrations/README.md +3 -0
  604. package/modules/notifications/module.json +32 -0
  605. package/modules/notifications/package.json +49 -0
  606. package/modules/notifications/spec/module.yaml +718 -0
  607. package/modules/notifications/src/acl/permissions.ts +11 -0
  608. package/modules/notifications/src/api/endpoints.ts +814 -0
  609. package/modules/notifications/src/cli/commands.json +17 -0
  610. package/modules/notifications/src/cli/index.ts +126 -0
  611. package/modules/notifications/src/client/DeliveriesView.tsrx +483 -0
  612. package/modules/notifications/src/client/FactList.tsrx +24 -0
  613. package/modules/notifications/src/client/NotificationPreferencesView.tsrx +240 -0
  614. package/modules/notifications/src/client/NotificationsInboxView.tsrx +592 -0
  615. package/modules/notifications/src/client/UnreadCountWidget.tsrx +62 -0
  616. package/modules/notifications/src/client/WebhookForm.tsrx +297 -0
  617. package/modules/notifications/src/client/WebhooksView.tsrx +637 -0
  618. package/modules/notifications/src/client/api.ts +382 -0
  619. package/modules/notifications/src/client/contribution.tsrx +83 -0
  620. package/modules/notifications/src/client/inbox.ts +41 -0
  621. package/modules/notifications/src/client/index.ts +29 -0
  622. package/modules/notifications/src/client/navigation.ts +92 -0
  623. package/modules/notifications/src/client/notifications.css +70 -0
  624. package/modules/notifications/src/client/presentation.ts +81 -0
  625. package/modules/notifications/src/client/state.ts +246 -0
  626. package/modules/notifications/src/client/unread-count.ts +26 -0
  627. package/modules/notifications/src/domain/locale.ts +47 -0
  628. package/modules/notifications/src/domain/publish.ts +46 -0
  629. package/modules/notifications/src/domain/types.ts +186 -0
  630. package/modules/notifications/src/index.ts +62 -0
  631. package/modules/notifications/src/platform.ts +85 -0
  632. package/modules/notifications/src/server/index.ts +82 -0
  633. package/modules/notifications/src/server/runtime.ts +251 -0
  634. package/modules/notifications/src/services/data-classes.ts +139 -0
  635. package/modules/notifications/src/services/database-repository.ts +1334 -0
  636. package/modules/notifications/src/services/delivery-payload.ts +93 -0
  637. package/modules/notifications/src/services/delivery-runner.ts +170 -0
  638. package/modules/notifications/src/services/delivery-service.ts +811 -0
  639. package/modules/notifications/src/services/egress.ts +239 -0
  640. package/modules/notifications/src/services/email-channel.ts +93 -0
  641. package/modules/notifications/src/services/index.ts +23 -0
  642. package/modules/notifications/src/services/migration.ts +572 -0
  643. package/modules/notifications/src/services/notifications-service.ts +297 -0
  644. package/modules/notifications/src/services/paging.ts +60 -0
  645. package/modules/notifications/src/services/publish-service.ts +133 -0
  646. package/modules/notifications/src/services/repository.ts +342 -0
  647. package/modules/notifications/src/services/secret-rotation.ts +177 -0
  648. package/modules/notifications/src/services/secret-vault.ts +168 -0
  649. package/modules/notifications/src/services/service-error.ts +67 -0
  650. package/modules/notifications/src/services/signature.ts +81 -0
  651. package/modules/notifications/src/services/webhook-service.ts +307 -0
  652. package/modules/notifications/src/settings.ts +130 -0
  653. package/modules/notifications/translations/en.json +255 -0
  654. package/modules/notifications/translations/pl.json +255 -0
  655. package/modules/profile/module.json +3 -3
  656. package/modules/profile/package.json +1 -1
  657. package/modules/profile/spec/module.yaml +3 -3
  658. package/modules/profile/src/services/database-repository.ts +3 -13
  659. package/modules/reports/module.json +28 -0
  660. package/modules/reports/package.json +42 -0
  661. package/modules/reports/spec/module.yaml +231 -0
  662. package/modules/reports/src/acl/permissions.ts +5 -0
  663. package/modules/reports/src/api/endpoints.ts +72 -0
  664. package/modules/reports/src/client/ReportsSummaryWidget.tsrx +52 -0
  665. package/modules/reports/src/client/ReportsView.tsrx +246 -0
  666. package/modules/reports/src/client/api.ts +65 -0
  667. package/modules/reports/src/client/contribution.tsrx +36 -0
  668. package/modules/reports/src/client/index.ts +18 -0
  669. package/modules/reports/src/client/navigation.ts +39 -0
  670. package/modules/reports/src/client/presentation.ts +123 -0
  671. package/modules/reports/src/client/state.ts +28 -0
  672. package/modules/reports/src/domain/providers.ts +115 -0
  673. package/modules/reports/src/domain/types.ts +44 -0
  674. package/modules/reports/src/index.ts +47 -0
  675. package/modules/reports/src/platform.ts +37 -0
  676. package/modules/reports/src/server/index.ts +21 -0
  677. package/modules/reports/src/server/runtime.ts +40 -0
  678. package/modules/reports/src/services/index.ts +17 -0
  679. package/modules/reports/src/services/provider-registry.ts +144 -0
  680. package/modules/reports/src/services/range.ts +65 -0
  681. package/modules/reports/src/services/reports-service.ts +316 -0
  682. package/modules/reports/src/services/service-error.ts +26 -0
  683. package/modules/reports/src/settings.ts +49 -0
  684. package/modules/reports/translations/en.json +35 -0
  685. package/modules/reports/translations/pl.json +35 -0
  686. package/modules/sandbox/module.json +4 -4
  687. package/modules/sandbox/package.json +1 -1
  688. package/modules/sandbox/spec/module.yaml +3 -3
  689. package/modules/sandbox/src/services/database-repository.ts +14 -23
  690. package/modules/sandbox/src/services/directory.ts +16 -0
  691. package/modules/sandbox/src/services/sandbox-service.ts +13 -11
  692. package/modules/search/migrations/0001_search_core.down.sql +1 -0
  693. package/modules/search/migrations/0001_search_core.up.sql +22 -0
  694. package/modules/search/migrations/0002_search_recent_export.down.sql +3 -0
  695. package/modules/search/migrations/0002_search_recent_export.up.sql +5 -0
  696. package/modules/search/migrations/README.md +3 -0
  697. package/modules/search/module.json +28 -0
  698. package/modules/search/package.json +43 -0
  699. package/modules/search/spec/module.yaml +177 -0
  700. package/modules/search/src/acl/permissions.ts +5 -0
  701. package/modules/search/src/api/endpoints.ts +221 -0
  702. package/modules/search/src/client/SearchView.tsrx +346 -0
  703. package/modules/search/src/client/api.ts +157 -0
  704. package/modules/search/src/client/contribution.tsrx +31 -0
  705. package/modules/search/src/client/index.ts +17 -0
  706. package/modules/search/src/client/navigation.ts +97 -0
  707. package/modules/search/src/client/search.css +56 -0
  708. package/modules/search/src/client/state.ts +113 -0
  709. package/modules/search/src/domain/data-classes.ts +73 -0
  710. package/modules/search/src/domain/providers.ts +88 -0
  711. package/modules/search/src/domain/types.ts +56 -0
  712. package/modules/search/src/index.ts +46 -0
  713. package/modules/search/src/platform.ts +42 -0
  714. package/modules/search/src/server/index.ts +25 -0
  715. package/modules/search/src/server/runtime.ts +123 -0
  716. package/modules/search/src/services/database-repository.ts +163 -0
  717. package/modules/search/src/services/index.ts +21 -0
  718. package/modules/search/src/services/migration.ts +65 -0
  719. package/modules/search/src/services/provider-registry.ts +137 -0
  720. package/modules/search/src/services/repository.ts +43 -0
  721. package/modules/search/src/services/search-service.ts +442 -0
  722. package/modules/search/src/services/service-error.ts +35 -0
  723. package/modules/search/src/settings.ts +68 -0
  724. package/modules/search/translations/en.json +45 -0
  725. package/modules/search/translations/pl.json +45 -0
  726. package/modules/system/module.json +2 -2
  727. package/modules/system/package.json +1 -1
  728. package/modules/system/spec/module.yaml +30 -3
  729. package/modules/system/src/client/FlagsPanel.tsrx +127 -0
  730. package/modules/system/src/client/ModuleSettingRow.tsrx +17 -7
  731. package/modules/system/src/client/ModuleSettingsSection.tsrx +10 -71
  732. package/modules/system/src/client/ModulesView.tsrx +91 -65
  733. package/modules/system/src/client/flags.ts +35 -0
  734. package/modules/system/src/client/index.ts +8 -0
  735. package/modules/system/src/client/navigation.ts +8 -0
  736. package/modules/system/src/client/settings-state.ts +159 -0
  737. package/modules/system/src/client/state.ts +2 -0
  738. package/modules/system/src/domain/time-zone.ts +105 -0
  739. package/modules/system/src/index.ts +12 -0
  740. package/modules/system/src/platform.ts +5 -1
  741. package/modules/system/src/server/endpoints.ts +72 -21
  742. package/modules/system/src/settings.ts +28 -0
  743. package/modules/system/translations/en.json +17 -0
  744. package/modules/system/translations/pl.json +17 -0
  745. package/modules/users/module.json +30 -4
  746. package/modules/users/package.json +4 -1
  747. package/modules/users/spec/module.yaml +290 -7
  748. package/modules/users/src/api/endpoints.ts +228 -15
  749. package/modules/users/src/client/InviteMemberForm.tsrx +2 -2
  750. package/modules/users/src/client/MemberDrawer.tsrx +142 -26
  751. package/modules/users/src/client/UserAccountForm.tsrx +2 -2
  752. package/modules/users/src/client/UsersView.tsrx +432 -71
  753. package/modules/users/src/client/api.ts +106 -12
  754. package/modules/users/src/client/contribution.tsrx +5 -0
  755. package/modules/users/src/client/member-columns.tsrx +22 -9
  756. package/modules/users/src/client/state.ts +172 -7
  757. package/modules/users/src/domain/lists.ts +9 -0
  758. package/modules/users/src/index.ts +11 -0
  759. package/modules/users/src/platform.ts +50 -1
  760. package/modules/users/src/services/member-export.ts +51 -0
  761. package/modules/users/src/services/member-import.ts +249 -0
  762. package/modules/users/src/services/member-search.ts +145 -0
  763. package/modules/users/src/services/users-service.ts +117 -4
  764. package/modules/users/translations/en.json +57 -8
  765. package/modules/users/translations/pl.json +57 -8
  766. package/modules/workflows/migrations/0004_payload_rotation_inventory.up.sql +17 -0
  767. package/modules/workflows/migrations/0005_workflows_human_approval.up.sql +28 -0
  768. package/modules/workflows/migrations/0006_workflows_retention_indexes.up.sql +11 -0
  769. package/modules/workflows/migrations/0007_workflows_erasure_subject_indexes.up.sql +17 -0
  770. package/modules/workflows/migrations/0008_workflows_subject_account.up.sql +24 -0
  771. package/modules/workflows/migrations/0009_workflows_definition_list_indexes.up.sql +9 -0
  772. package/modules/workflows/module.json +34 -6
  773. package/modules/workflows/package.json +2 -1
  774. package/modules/workflows/spec/module.yaml +26 -9
  775. package/modules/workflows/src/api/endpoints.ts +131 -25
  776. package/modules/workflows/src/cli/commands.json +17 -0
  777. package/modules/workflows/src/cli/index.ts +114 -0
  778. package/modules/workflows/src/client/WorkflowExecutionHistory.tsrx +36 -27
  779. package/modules/workflows/src/client/WorkflowInspector.tsrx +116 -0
  780. package/modules/workflows/src/client/WorkflowNodePalette.tsrx +1 -0
  781. package/modules/workflows/src/client/WorkflowsView.tsrx +236 -99
  782. package/modules/workflows/src/client/api.ts +54 -34
  783. package/modules/workflows/src/client/canvas-model.ts +12 -0
  784. package/modules/workflows/src/client/state.ts +95 -0
  785. package/modules/workflows/src/domain/events.ts +25 -11
  786. package/modules/workflows/src/domain/graph.ts +135 -1
  787. package/modules/workflows/src/domain/types.ts +91 -4
  788. package/modules/workflows/src/platform.ts +12 -0
  789. package/modules/workflows/src/server/runtime.ts +78 -5
  790. package/modules/workflows/src/services/approvals.ts +62 -0
  791. package/modules/workflows/src/services/cursors.ts +37 -0
  792. package/modules/workflows/src/services/data-classes.ts +232 -0
  793. package/modules/workflows/src/services/database-repository.ts +660 -171
  794. package/modules/workflows/src/services/migration.ts +240 -0
  795. package/modules/workflows/src/services/notifications.ts +98 -0
  796. package/modules/workflows/src/services/payload-codec.ts +51 -28
  797. package/modules/workflows/src/services/payload-rotation.ts +171 -0
  798. package/modules/workflows/src/services/repository.ts +123 -8
  799. package/modules/workflows/src/services/simulation.ts +7 -0
  800. package/modules/workflows/src/services/worker.ts +446 -25
  801. package/modules/workflows/src/services/workflows-service.ts +264 -103
  802. package/modules/workflows/translations/en.json +12 -4
  803. package/modules/workflows/translations/pl.json +12 -4
  804. package/modules.json +48 -4
  805. package/package.json +64 -6
  806. package/packages/client/package.json +1 -0
  807. package/packages/client/src/ApplicationShell.tsrx +43 -0
  808. package/packages/client/src/contributions.ts +55 -0
  809. package/packages/client/src/i18n/locales/en.json +5 -0
  810. package/packages/client/src/i18n/locales/pl.json +5 -0
  811. package/packages/client/src/index.ts +13 -1
  812. package/packages/client/src/routing.ts +22 -0
  813. package/packages/client/src/shell/CommandPalette.tsrx +223 -11
  814. package/packages/client/src/shell/command-search.ts +126 -0
  815. package/packages/client/src/shell/command.ts +30 -0
  816. package/packages/client/src/shell/navigation.ts +12 -0
  817. package/packages/client/src/shell/palette-keys.ts +27 -0
  818. package/packages/client/src/shell/shell.css +7 -0
  819. package/packages/contracts/schemas/module-spec.schema.json +287 -1
  820. package/packages/contracts/schemas/module.schema.json +25 -0
  821. package/packages/contracts/src/index.ts +144 -1
  822. package/packages/database/src/backup.ts +179 -0
  823. package/packages/database/src/decoders.ts +25 -0
  824. package/packages/database/src/index.ts +17 -0
  825. package/packages/database/src/record-history.ts +5 -14
  826. package/packages/harness/src/index.ts +2 -0
  827. package/packages/harness/src/runtime.ts +218 -1
  828. package/packages/harness/src/tool-adapters.ts +19 -1
  829. package/packages/kernel/src/acl.ts +179 -0
  830. package/packages/kernel/src/capability-registry.ts +69 -2
  831. package/packages/kernel/src/data-class-registry.ts +354 -0
  832. package/packages/kernel/src/index.ts +65 -4
  833. package/packages/kernel/src/keyring.ts +208 -0
  834. package/packages/kernel/src/module-compatibility.ts +35 -4
  835. package/packages/kernel/src/module-registry.ts +49 -2
  836. package/packages/kernel/src/module-settings.ts +189 -27
  837. package/packages/server/package.json +2 -1
  838. package/packages/server/src/composition.ts +38 -0
  839. package/packages/server/src/endpoint.ts +104 -47
  840. package/packages/server/src/export/csv.ts +34 -0
  841. package/packages/server/src/export/definition.ts +346 -0
  842. package/packages/server/src/export/index.ts +61 -0
  843. package/packages/server/src/export/run.ts +116 -0
  844. package/packages/server/src/index.ts +155 -0
  845. package/packages/server/src/jobs/index.ts +93 -0
  846. package/packages/server/src/jobs/runner.ts +486 -0
  847. package/packages/server/src/log.ts +291 -0
  848. package/packages/server/src/mail/config.ts +184 -0
  849. package/packages/server/src/mail/contracts.ts +251 -0
  850. package/packages/server/src/mail/index.ts +31 -0
  851. package/packages/server/src/mail/port.ts +101 -0
  852. package/packages/server/src/mail/smtp.ts +191 -0
  853. package/packages/server/src/mail/template.ts +105 -0
  854. package/packages/server/src/metrics.ts +447 -0
  855. package/packages/server/src/pagination.ts +246 -0
  856. package/packages/server/src/trace/context.ts +103 -0
  857. package/packages/server/src/trace/egress.ts +66 -0
  858. package/packages/server/src/trace/error-sink.ts +296 -0
  859. package/packages/server/src/trace/exporter.ts +295 -0
  860. package/packages/server/src/trace/index.ts +60 -0
  861. package/packages/server/src/trace/job-sink.ts +172 -0
  862. package/packages/server/src/trace/tracer.ts +338 -0
  863. package/packages/server/src/web.ts +5 -1
  864. package/packages/storage/package.json +40 -0
  865. package/packages/storage/src/config.ts +189 -0
  866. package/packages/storage/src/content-type.ts +124 -0
  867. package/packages/storage/src/contracts.ts +112 -0
  868. package/packages/storage/src/envelope.ts +189 -0
  869. package/packages/storage/src/index.ts +43 -0
  870. package/packages/storage/src/local.ts +75 -0
  871. package/packages/storage/src/port.ts +251 -0
  872. package/packages/storage/src/read-token.ts +130 -0
  873. package/packages/storage/src/s3.ts +144 -0
  874. package/packages/storage/src/scanner.ts +22 -0
  875. package/packages/storage/src/sigv4.ts +104 -0
  876. package/packages/storage/src/store.ts +13 -0
  877. package/packages/ui/package.json +1 -0
  878. package/packages/ui/src/components/Button.tsrx +3 -0
  879. package/packages/ui/src/components/ConfirmDialog.tsrx +13 -2
  880. package/packages/ui/src/components/DateField.tsrx +92 -0
  881. package/packages/ui/src/components/DatePicker.tsrx +525 -0
  882. package/packages/ui/src/components/DateRangeField.tsrx +93 -0
  883. package/packages/ui/src/components/Drawer.tsrx +10 -1
  884. package/packages/ui/src/components/FileUpload.tsrx +158 -0
  885. package/packages/ui/src/components/Pagination.tsrx +130 -0
  886. package/packages/ui/src/components/Select.tsrx +78 -0
  887. package/packages/ui/src/components/Table.tsrx +498 -81
  888. package/packages/ui/src/components/Tabs.tsrx +53 -0
  889. package/packages/ui/src/components/ToastHost.tsrx +48 -0
  890. package/packages/ui/src/components/calendar.ts +243 -0
  891. package/packages/ui/src/components/date-field.ts +62 -0
  892. package/packages/ui/src/components/file-upload.ts +49 -0
  893. package/packages/ui/src/components/focus-trap.ts +62 -0
  894. package/packages/ui/src/components/pagination.ts +68 -0
  895. package/packages/ui/src/components/table-sorting.ts +49 -0
  896. package/packages/ui/src/components/tabs.ts +61 -0
  897. package/packages/ui/src/components/toast-store.ts +116 -0
  898. package/packages/ui/src/icons/Icon.tsrx +5 -0
  899. package/packages/ui/src/index.ts +45 -0
  900. package/packages/ui/src/styles/components.css +598 -0
  901. package/modules/automations-workflows-integration/module.json +0 -27
  902. package/modules/automations-workflows-integration/spec/module.yaml +0 -93
  903. package/modules/automations-workflows-integration/src/index.ts +0 -14
  904. package/modules/automations-workflows-integration/src/platform.ts +0 -24
  905. package/modules/automations-workflows-integration/src/server/index.ts +0 -11
  906. package/modules/automations-workflows-integration/translations/en.json +0 -21
  907. package/modules/automations-workflows-integration/translations/pl.json +0 -21
  908. package/modules/workflows/src/services/cursor-codec.ts +0 -62
@@ -1,5 +1,12 @@
1
1
  import { createHash, randomBytes, randomUUID } from 'node:crypto';
2
- import { userActor, type Actor } from '@flowdular/sdk/kernel';
2
+ import {
3
+ serviceActor,
4
+ userActor,
5
+ type Actor,
6
+ type ModuleSettingChange,
7
+ type UserActor,
8
+ } from '@flowdular/sdk/kernel';
9
+ import type { ErrorSink, ModuleMetrics } from '@flowdular/sdk/server';
3
10
  import { AUTH_SCOPES, MEMBER_SCOPES, OWNER_SCOPES } from '../acl/scopes.ts';
4
11
  import type {
5
12
  ApiTokenRecord,
@@ -11,8 +18,10 @@ import type {
11
18
  CreateApiTokenInput,
12
19
  CreateRoleInput,
13
20
  CreateTenantMemberInput,
21
+ CreateTenantMemberWithoutPasswordInput,
14
22
  IssuedApiToken,
15
23
  IssuedSession,
24
+ MembershipStatus,
16
25
  SessionSummary,
17
26
  SignInInput,
18
27
  SignUpInput,
@@ -25,9 +34,20 @@ import {
25
34
  DEFAULT_SESSION_TTL_HOURS,
26
35
  } from '../settings.ts';
27
36
  import { AuthServiceError } from './auth-service-error.ts';
37
+ import {
38
+ IdentityProviderService,
39
+ PROVIDER_AUDIT_ACTIONS,
40
+ type OidcDiscoveryPort,
41
+ } from './identity-provider-service.ts';
42
+ import {
43
+ createProviderSecretVault,
44
+ type ProviderSecretVault,
45
+ } from './provider-secrets.ts';
28
46
  import {
29
47
  DEFAULT_PASSWORD_HASH_OPTIONS,
30
48
  hashPassword,
49
+ passwordIsSet,
50
+ UNUSABLE_PASSWORD_HASH,
31
51
  verifyPassword,
32
52
  type PasswordHashOptions,
33
53
  } from './password.ts';
@@ -38,20 +58,26 @@ import {
38
58
  type AccountCredential,
39
59
  type AuditActorEvent,
40
60
  type AuthRepository,
61
+ type ExternalIdentityPage,
41
62
  type TenantMember,
63
+ type TenantMemberKeyset,
64
+ type TenantMemberPage,
65
+ type TenantMemberSort,
66
+ type TenantMemberSortedPage,
42
67
  type TenantSummary,
43
68
  } from './repository.ts';
44
69
  import type { AuthMailDelivery } from './mail-delivery.ts';
45
70
  import {
71
+ createMfaSecretVault,
46
72
  createTotpSecret,
47
- decryptMfaSecret,
48
- encryptMfaSecret,
49
73
  verifyTotp,
74
+ type MfaSecretVault,
50
75
  } from './totp.ts';
51
76
  import {
52
77
  assertPasswordPolicy,
53
78
  normalizeEmail,
54
79
  validateCreateTenantMember,
80
+ validateCreateTenantMemberWithoutPassword,
55
81
  validateDisplayName,
56
82
  validateEmailAddress,
57
83
  validateRoleDescription,
@@ -84,10 +110,37 @@ export interface AuthServiceOptions {
84
110
  readonly now?: () => number;
85
111
  /** AES-256-GCM key used only to protect enrolled TOTP secrets at rest. */
86
112
  readonly mfaEncryptionKey?: string;
113
+ /**
114
+ * Keys a rotation has not finished retiring. They open stored factors that
115
+ * were sealed before the rotation, and nothing is ever written with them.
116
+ */
117
+ readonly mfaPreviousEncryptionKeys?: readonly string[];
118
+ /**
119
+ * Verifies an issuer through its discovery document when a workspace saves
120
+ * a provider. The server layer installs the HTTP adapter; without one, a
121
+ * save is refused instead of trusting an unverified issuer.
122
+ */
123
+ readonly oidcDiscovery?: OidcDiscoveryPort;
124
+ /**
125
+ * Drops the ID token verifier's cached key set for a provider whose row
126
+ * changed. The server layer installs the verifier it serves with; a
127
+ * composition without one caches nothing to invalidate.
128
+ */
129
+ readonly forgetProviderKeys?: (providerId: string) => void;
87
130
  /** Deployment-owned delivery adapter. auth.core never selects an email vendor. */
88
131
  readonly mailDelivery?: AuthMailDelivery;
132
+ /**
133
+ * The locale a workspace's messages are worded in. The server resolves it
134
+ * from the workspace's defaultLocale setting; without one every message is
135
+ * English.
136
+ */
137
+ readonly mailLocale?: (tenantId: string) => Promise<string> | string;
89
138
  /** Public origin used to form opaque, one-time delivery links. */
90
139
  readonly publicBaseUrl?: string;
140
+ /** Counts the audit writes that failed; a failed write never fails its operation. */
141
+ readonly metrics?: ModuleMetrics;
142
+ /** Receives a failed audit write as a name and a module, never as the driver's words. */
143
+ readonly errorSink?: ErrorSink;
91
144
  }
92
145
 
93
146
  export interface MfaChallenge extends IssuedSession {
@@ -199,6 +252,34 @@ export interface SignInContext {
199
252
  readonly address?: string | null;
200
253
  }
201
254
 
255
+ /** What a sign-in through a tenant-owned provider needs about the workspace. */
256
+ export interface TenantProviderSignIn {
257
+ readonly tenantId: string;
258
+ readonly jitEnabled: boolean;
259
+ readonly allowedDomains: readonly string[];
260
+ readonly jitRole: string;
261
+ }
262
+
263
+ /* A workspace saving a provider verifies its issuer through discovery, which is
264
+ an outbound request the server layer owns. Without that adapter the save is
265
+ refused rather than trusting the issuer a request supplied. */
266
+ function discoveryUnavailable(): AuthServiceError {
267
+ return new AuthServiceError(
268
+ 'PROVIDER_DISCOVERY_UNAVAILABLE',
269
+ 'Issuer discovery is not available in this composition.',
270
+ 503,
271
+ );
272
+ }
273
+
274
+ /* A provisioned account has only the address the provider verified. The local
275
+ part is the readable half of it and is what the member sees until they
276
+ change it. */
277
+ function displayNameFromEmail(email: string): string {
278
+ const local = email.slice(0, email.lastIndexOf('@'));
279
+ const candidate = local.length >= 2 ? local : email;
280
+ return candidate.slice(0, 80);
281
+ }
282
+
202
283
  export function hashSessionToken(token: string): string {
203
284
  return createHash('sha256').update(token, 'utf8').digest('base64url');
204
285
  }
@@ -221,6 +302,50 @@ const OPERATOR_SETUP_TTL_MS = 24 * 60 * 60 * 1000;
221
302
  const MFA_CHALLENGE_TTL_MS = 5 * 60 * 1000;
222
303
  const RECOVERY_CODE_COUNT = 10;
223
304
 
305
+ /**
306
+ * Addresses one `findTenantMembersByEmail` call may name. A bulk caller splits
307
+ * a larger batch, so the statement never carries an unbounded list and a
308
+ * workspace of unknown size is never listed to answer a handful of addresses.
309
+ */
310
+ export const TENANT_MEMBER_LOOKUP_LIMIT = 500;
311
+
312
+ /** Members one `searchTenantMembers` call may answer. */
313
+ export const TENANT_MEMBER_SEARCH_LIMIT = 500;
314
+
315
+ /** Members one paged `listTenantMembers` call may answer. */
316
+ export const TENANT_MEMBER_PAGE_LIMIT = 500;
317
+
318
+ /** Sort keys `listTenantMembersSorted` accepts. */
319
+ export const TENANT_MEMBER_SORTS: readonly TenantMemberSort[] = [
320
+ 'displayName',
321
+ 'email',
322
+ ];
323
+
324
+ /** Characters a sorted page's keyset value may carry; a display name or an address is shorter. */
325
+ const TENANT_MEMBER_KEYSET_LENGTH = 512;
326
+
327
+ export interface TenantMemberSortedInput {
328
+ readonly sort: TenantMemberSort;
329
+ readonly direction: 'asc' | 'desc';
330
+ readonly limit: number;
331
+ /** A prefix of the display name or the address, folded like a search term; empty narrows nothing. */
332
+ readonly query?: string | null;
333
+ readonly membershipStatus?: MembershipStatus | null;
334
+ readonly after?: TenantMemberKeyset | null;
335
+ }
336
+
337
+ /** Bindings one `listExternalIdentities` call may answer. */
338
+ export const EXTERNAL_IDENTITY_PAGE_LIMIT = 500;
339
+
340
+ /** Characters a search term may carry; a longer one matches nothing useful. */
341
+ export const TENANT_MEMBER_SEARCH_TERM_LENGTH = 200;
342
+
343
+ /* The LIKE wildcards, so a term a person typed is matched literally. The
344
+ statements that use it declare ESCAPE '\'. */
345
+ function escapeLikeTerm(term: string): string {
346
+ return term.replace(/[\\%_]/g, (character) => `\\${character}`);
347
+ }
348
+
224
349
  export const AUDIT_ACTIONS = Object.freeze({
225
350
  signInSucceeded: 'auth.sign-in.succeeded',
226
351
  signInFailed: 'auth.sign-in.failed',
@@ -237,11 +362,20 @@ export const AUDIT_ACTIONS = Object.freeze({
237
362
  mfaEnrolled: 'auth.mfa.enrolled',
238
363
  mfaConfirmed: 'auth.mfa.confirmed',
239
364
  mfaChallengeSucceeded: 'auth.mfa.challenge-succeeded',
365
+ mfaReset: 'auth.mfa.reset',
240
366
  tenantRenamed: 'auth.tenant.renamed',
241
367
  workspaceProvisioned: 'auth.workspace.provisioned',
242
368
  memberCreated: 'users.member.created',
243
369
  memberUpdated: 'users.member.updated',
244
370
  memberStatus: 'users.member.status',
371
+ memberProvisioned: 'auth.member.provisioned',
372
+ jitRefused: 'auth.jit.refused',
373
+ membershipStatus: 'auth.membership.status',
374
+ providerCreated: PROVIDER_AUDIT_ACTIONS.created,
375
+ providerUpdated: PROVIDER_AUDIT_ACTIONS.updated,
376
+ providerStatusChanged: PROVIDER_AUDIT_ACTIONS.status,
377
+ providerSecretRotated: PROVIDER_AUDIT_ACTIONS.secretRotated,
378
+ providerDeleted: PROVIDER_AUDIT_ACTIONS.deleted,
245
379
  memberRemoved: 'users.member.removed',
246
380
  memberPasswordReset: 'users.member.password-reset',
247
381
  memberScopes: 'users.member.scopes',
@@ -250,16 +384,44 @@ export const AUDIT_ACTIONS = Object.freeze({
250
384
  roleUpdated: 'auth.role.updated',
251
385
  roleDeleted: 'auth.role.deleted',
252
386
  settingsUpdated: 'settings.updated',
387
+ settingsFlagChanged: 'settings.flag.changed',
253
388
  });
254
389
 
255
390
  export const AUDIT_ACTION_LIST = Object.freeze(Object.values(AUDIT_ACTIONS));
256
391
 
392
+ /* The actor an audit row records. A kernel service actor always names its
393
+ configuring user; a provider row stores none, so the trail admits a service
394
+ actor whose configuring user is unknown and persists it as null. */
395
+ interface ServiceAuditActor {
396
+ readonly kind: 'service';
397
+ readonly id: string;
398
+ readonly label: string;
399
+ readonly configuredBy: UserActor | null;
400
+ }
401
+
402
+ type AuditActor = Actor | ServiceAuditActor;
403
+
257
404
  /* An operator command runs from a deployment shell and belongs to no account.
258
- The audit actor_kind column accepts only 'user' or 'agent', so the operator
259
- is recorded as a user whose identifier carries the cli: prefix the label
260
- filter can search for. */
405
+ The shell is the service and the person at it, whose identifier carries the
406
+ cli: prefix the label filter can search for, is who configured it. */
261
407
  function operatorActor(operator: string): Actor {
262
- return { kind: 'user', id: operator, label: operator };
408
+ return serviceActor({
409
+ serviceId: operator,
410
+ label: operator,
411
+ configuredBy: { kind: 'user', id: operator, label: operator },
412
+ });
413
+ }
414
+
415
+ /* A refused just-in-time sign-in belongs to no account of this workspace, so
416
+ the provider that asserted the identity is the actor and the address it
417
+ reported never reaches the trail. */
418
+ function providerActor(provider: string): ServiceAuditActor {
419
+ return {
420
+ kind: 'service',
421
+ id: `oidc:${provider}`,
422
+ label: `oidc:${provider}`,
423
+ configuredBy: null,
424
+ };
263
425
  }
264
426
 
265
427
  function member(account: AccountCredential, createdAt: number): TenantMember {
@@ -270,6 +432,7 @@ function member(account: AccountCredential, createdAt: number): TenantMember {
270
432
  role: account.role,
271
433
  roleId: account.roleId,
272
434
  status: account.status,
435
+ membershipStatus: account.membershipStatus,
273
436
  scopes: account.scopes,
274
437
  passwordChangeRequired: account.passwordChangeRequired,
275
438
  createdAt,
@@ -282,8 +445,17 @@ export class AuthService {
282
445
  readonly #passwordHash: PasswordHashOptions;
283
446
  readonly #now: () => number;
284
447
  readonly #mfaEncryptionKey: string | undefined;
448
+ readonly #mfaPreviousEncryptionKeys: readonly string[];
449
+ #mfaVault: MfaSecretVault | undefined;
450
+ #providerSecretVault: ProviderSecretVault | undefined;
451
+ #identityProviders: IdentityProviderService | undefined;
452
+ readonly #oidcDiscovery: OidcDiscoveryPort;
453
+ readonly #forgetProviderKeys: (providerId: string) => void;
285
454
  readonly #mailDelivery: AuthMailDelivery | undefined;
455
+ readonly #mailLocale: (tenantId: string) => Promise<string> | string;
286
456
  readonly #publicBaseUrl: string;
457
+ readonly #metrics: ModuleMetrics | undefined;
458
+ readonly #errorSink: ErrorSink | undefined;
287
459
 
288
460
  constructor(repository: AuthRepository, options: AuthServiceOptions = {}) {
289
461
  this.#repository = repository;
@@ -297,11 +469,18 @@ export class AuthService {
297
469
  this.#passwordHash = options.passwordHash ?? DEFAULT_PASSWORD_HASH_OPTIONS;
298
470
  this.#now = options.now ?? Date.now;
299
471
  this.#mfaEncryptionKey = options.mfaEncryptionKey;
472
+ this.#mfaPreviousEncryptionKeys = options.mfaPreviousEncryptionKeys ?? [];
473
+ this.#oidcDiscovery =
474
+ options.oidcDiscovery ?? (() => Promise.reject(discoveryUnavailable()));
475
+ this.#forgetProviderKeys = options.forgetProviderKeys ?? (() => undefined);
300
476
  this.#mailDelivery = options.mailDelivery;
477
+ this.#mailLocale = options.mailLocale ?? (() => 'en');
301
478
  this.#publicBaseUrl = (options.publicBaseUrl ?? 'http://localhost').replace(
302
479
  /\/$/,
303
480
  '',
304
481
  );
482
+ this.#metrics = options.metrics;
483
+ this.#errorSink = options.errorSink;
305
484
  }
306
485
 
307
486
  get policy(): AuthPolicy {
@@ -312,7 +491,7 @@ export class AuthService {
312
491
  describe; a failing write is reported and swallowed. */
313
492
  async #audit(
314
493
  tenantId: string,
315
- actor: Actor,
494
+ actor: AuditActor,
316
495
  action: string,
317
496
  subjectType: string,
318
497
  subjectId: string,
@@ -325,6 +504,7 @@ export class AuthService {
325
504
  actorLabel: actor.label,
326
505
  actorKind: actor.kind,
327
506
  actorRunId: actor.kind === 'agent' ? actor.runId : null,
507
+ configuredBy: actor.kind === 'service' ? actor.configuredBy : null,
328
508
  action,
329
509
  subjectType,
330
510
  subjectId,
@@ -336,6 +516,13 @@ export class AuthService {
336
516
  bound values from the operation being audited. */
337
517
  void error;
338
518
  console.error('[auth.core] audit write failed');
519
+ this.#metrics?.counter('audit_write_failures_total', { action });
520
+ this.#errorSink?.report({
521
+ at: this.#now(),
522
+ name: 'AuditWriteFailed',
523
+ module: 'auth.core',
524
+ message: `Audit write failed for ${action}.`,
525
+ });
339
526
  }
340
527
  }
341
528
 
@@ -463,12 +650,22 @@ export class AuthService {
463
650
  423,
464
651
  );
465
652
  }
466
- const account = await this.#repository.findAccountByEmail(input.email);
653
+ const account = await this.#signInAccount(input);
467
654
  if (!account) {
468
655
  await hashPassword(input.password, this.#passwordHash);
469
656
  await this.#recordFailure(input.email, null, context);
470
657
  throw this.#invalidCredentials();
471
658
  }
659
+ /* An account created without a password answers exactly as an unknown
660
+ address does, down to burning the same derivation: a distinct code here
661
+ would tell an attacker which addresses are registered and not yet
662
+ activated, which is the set most worth attacking. PASSWORD_NOT_SET is
663
+ reported where the caller is already authenticated as the account. */
664
+ if (!passwordIsSet(account.passwordHash)) {
665
+ await hashPassword(input.password, this.#passwordHash);
666
+ await this.#recordFailure(input.email, account, context);
667
+ throw this.#invalidCredentials();
668
+ }
472
669
  const valid = await verifyPassword(input.password, account.passwordHash);
473
670
  if (!valid || account.status !== 'active') {
474
671
  await this.#recordFailure(input.email, account, context);
@@ -490,6 +687,26 @@ export class AuthService {
490
687
  return issued;
491
688
  }
492
689
 
690
+ /* The workspace the sign-in screen names is the one the session opens in:
691
+ the membership has to exist there and be active. An unknown workspace and
692
+ a membership the account does not hold answer as a wrong password does,
693
+ so the form never reports which workspaces hold an address. Without a
694
+ named workspace the oldest membership answers, exactly as before. */
695
+ async #signInAccount(input: SignInInput): Promise<AccountCredential | null> {
696
+ if (input.workspace === undefined) {
697
+ return this.#repository.findAccountByEmail(input.email);
698
+ }
699
+ const tenant = await this.findTenant(input.workspace);
700
+ if (!tenant) return null;
701
+ const identity = await this.#repository.findAccountIdentity(input.email);
702
+ if (!identity) return null;
703
+ const membership = await this.#repository.findAccountMembership(
704
+ identity.accountId,
705
+ tenant.tenantId,
706
+ );
707
+ return membership?.membershipStatus === 'active' ? membership : null;
708
+ }
709
+
493
710
  async #issueMfaChallenge(
494
711
  account: AccountCredential,
495
712
  now = this.#now(),
@@ -557,8 +774,232 @@ export class AuthService {
557
774
  );
558
775
  }
559
776
 
560
- async listTenantMembers(tenantId: string): Promise<readonly TenantMember[]> {
561
- return this.#repository.listTenantMembers(tenantId);
777
+ listTenantMembers(tenantId: string): Promise<readonly TenantMember[]>;
778
+ listTenantMembers(
779
+ tenantId: string,
780
+ page: { readonly cursor?: string | null; readonly limit: number },
781
+ ): Promise<TenantMemberPage>;
782
+ /**
783
+ * The members of one workspace. The unbounded call answers the whole roll
784
+ * ordered by display name and stays what every caller holding a workspace of
785
+ * known size already reads. The paged call walks the same members by account
786
+ * id, one bounded page and the cursor of the next, for a caller that must
787
+ * not read a workspace of unknown size in one statement.
788
+ */
789
+ async listTenantMembers(
790
+ tenantId: string,
791
+ page?: { readonly cursor?: string | null; readonly limit: number },
792
+ ): Promise<readonly TenantMember[] | TenantMemberPage> {
793
+ if (page === undefined) return this.#repository.listTenantMembers(tenantId);
794
+ const limit = this.#pageLimit(page.limit, TENANT_MEMBER_PAGE_LIMIT);
795
+ const members = await this.#repository.listTenantMembersPage(
796
+ this.#identifier(tenantId, 'tenantId'),
797
+ page.cursor ?? '',
798
+ limit + 1,
799
+ );
800
+ const answered = members.slice(0, limit);
801
+ const last = answered[answered.length - 1];
802
+ return {
803
+ members: answered,
804
+ nextCursor: members.length > limit && last ? last.accountId : null,
805
+ };
806
+ }
807
+
808
+ /**
809
+ * One page of the workspace's members in the order a screen shows them,
810
+ * by display name or by address, ascending or descending, narrowed by a
811
+ * prefix the way the search narrows and by membership status, all in SQL.
812
+ * The page answers the keyset of its last member when it is full; the
813
+ * caller carries that keyset back as `after` to continue. The paged walk
814
+ * by account id stays for the callers that must not lose a member to a
815
+ * rename mid-walk; this read is for a screen, which shows one page at a time.
816
+ */
817
+ async listTenantMembersSorted(
818
+ tenantId: string,
819
+ input: TenantMemberSortedInput,
820
+ ): Promise<TenantMemberSortedPage> {
821
+ if (!TENANT_MEMBER_SORTS.includes(input.sort)) {
822
+ throw new AuthServiceError(
823
+ 'INVALID_INPUT',
824
+ `Sort must be one of ${TENANT_MEMBER_SORTS.join(', ')}.`,
825
+ 400,
826
+ );
827
+ }
828
+ if (input.direction !== 'asc' && input.direction !== 'desc') {
829
+ throw new AuthServiceError(
830
+ 'INVALID_INPUT',
831
+ 'Direction must be asc or desc.',
832
+ 400,
833
+ );
834
+ }
835
+ const status = input.membershipStatus ?? null;
836
+ if (status !== null && status !== 'active' && status !== 'disabled') {
837
+ throw new AuthServiceError(
838
+ 'INVALID_INPUT',
839
+ 'Membership status must be active or disabled.',
840
+ 400,
841
+ );
842
+ }
843
+ const term = normalizeEmail(input.query ?? '');
844
+ if (term.length > TENANT_MEMBER_SEARCH_TERM_LENGTH) {
845
+ throw new AuthServiceError(
846
+ 'INVALID_INPUT',
847
+ `Search term must contain at most ${TENANT_MEMBER_SEARCH_TERM_LENGTH} characters.`,
848
+ 400,
849
+ );
850
+ }
851
+ const after = input.after ?? null;
852
+ if (
853
+ after !== null &&
854
+ (typeof after.sortValue !== 'string' ||
855
+ after.sortValue.length > TENANT_MEMBER_KEYSET_LENGTH)
856
+ ) {
857
+ throw new AuthServiceError(
858
+ 'INVALID_INPUT',
859
+ 'The page keyset is not valid.',
860
+ 400,
861
+ );
862
+ }
863
+ return this.#repository.listTenantMembersSorted(
864
+ this.#identifier(tenantId, 'tenantId'),
865
+ {
866
+ sort: input.sort,
867
+ direction: input.direction,
868
+ limit: this.#pageLimit(input.limit, TENANT_MEMBER_PAGE_LIMIT),
869
+ term: term.length === 0 ? null : escapeLikeTerm(term),
870
+ membershipStatus: status,
871
+ after:
872
+ after === null
873
+ ? null
874
+ : {
875
+ sortValue: after.sortValue,
876
+ accountId: this.#identifier(after.accountId, 'accountId'),
877
+ },
878
+ },
879
+ );
880
+ }
881
+
882
+ /** How many members the workspace holds; the count is an index range, so a screen may show it. */
883
+ countTenantMembers(tenantId: string): Promise<number> {
884
+ return this.#repository.countTenantMembers(
885
+ this.#identifier(tenantId, 'tenantId'),
886
+ );
887
+ }
888
+
889
+ /**
890
+ * The identity bindings this workspace's own providers assert about its
891
+ * members, one bounded page per call. A binding names the account, the
892
+ * provider key, the subject the provider keeps stable and when it was first
893
+ * recorded; no token, secret, ciphertext or fingerprint is part of one, and
894
+ * neither is the address the provider reported. A binding a platform
895
+ * provider made carries no workspace and is not answered here. The
896
+ * permission a reader needs is the calling module's to enforce; this is
897
+ * scoped to the workspace it is given and nothing wider.
898
+ */
899
+ async listExternalIdentities(
900
+ tenantId: string,
901
+ page: { readonly cursor?: string | null; readonly limit: number },
902
+ ): Promise<ExternalIdentityPage> {
903
+ const limit = this.#pageLimit(page.limit, EXTERNAL_IDENTITY_PAGE_LIMIT);
904
+ /* A provider key carries no colon, so the first one separates the pair; a
905
+ cursor that is not one starts the walk over rather than half-reading a
906
+ position, the way a malformed audit cursor narrows nothing. */
907
+ const separator = page.cursor?.indexOf(':') ?? -1;
908
+ const after =
909
+ page.cursor && separator > 0
910
+ ? {
911
+ provider: page.cursor.slice(0, separator),
912
+ subject: page.cursor.slice(separator + 1),
913
+ }
914
+ : { provider: '', subject: '' };
915
+ const identities = await this.#repository.listExternalIdentitiesPage(
916
+ this.#identifier(tenantId, 'tenantId'),
917
+ after,
918
+ limit + 1,
919
+ );
920
+ const answered = identities.slice(0, limit);
921
+ const last = answered[answered.length - 1];
922
+ return {
923
+ identities: answered,
924
+ nextCursor:
925
+ identities.length > limit && last
926
+ ? `${last.provider}:${last.subject}`
927
+ : null,
928
+ };
929
+ }
930
+
931
+ /**
932
+ * The members of one workspace holding any of these addresses. The natural
933
+ * key a bulk caller resolves per batch, answered by the database rather than
934
+ * by listing the workspace and matching in memory. Addresses are folded the
935
+ * way auth.core folds a stored address, so the caller passes what a person
936
+ * typed. An address the workspace does not hold is simply absent from the
937
+ * answer, and an account that exists in another workspace stays invisible
938
+ * here.
939
+ */
940
+ async findTenantMembersByEmail(
941
+ tenantId: string,
942
+ emails: readonly string[],
943
+ ): Promise<readonly TenantMember[]> {
944
+ if (emails.length > TENANT_MEMBER_LOOKUP_LIMIT) {
945
+ throw new AuthServiceError(
946
+ 'INVALID_INPUT',
947
+ `At most ${TENANT_MEMBER_LOOKUP_LIMIT} addresses can be looked up at once.`,
948
+ 400,
949
+ );
950
+ }
951
+ const normalized = [
952
+ ...new Set(emails.map(normalizeEmail).filter((email) => email !== '')),
953
+ ];
954
+ return this.#repository.findTenantMembersByEmail(
955
+ this.#identifier(tenantId, 'tenantId'),
956
+ normalized,
957
+ );
958
+ }
959
+
960
+ /**
961
+ * Members of one workspace whose display name or address starts with the
962
+ * term, cut to `limit` in the database. The caller ranks what comes back;
963
+ * this only bounds what a search reads.
964
+ */
965
+ async searchTenantMembers(
966
+ tenantId: string,
967
+ input: { readonly query: string; readonly limit: number },
968
+ ): Promise<readonly TenantMember[]> {
969
+ const term = normalizeEmail(input.query);
970
+ if (term.length === 0 || term.length > TENANT_MEMBER_SEARCH_TERM_LENGTH) {
971
+ throw new AuthServiceError(
972
+ 'INVALID_INPUT',
973
+ `Search term must contain 1 to ${TENANT_MEMBER_SEARCH_TERM_LENGTH} characters.`,
974
+ 400,
975
+ );
976
+ }
977
+ if (
978
+ !Number.isSafeInteger(input.limit) ||
979
+ input.limit < 1 ||
980
+ input.limit > TENANT_MEMBER_SEARCH_LIMIT
981
+ ) {
982
+ throw new AuthServiceError(
983
+ 'INVALID_INPUT',
984
+ `Limit must be 1 to ${TENANT_MEMBER_SEARCH_LIMIT}.`,
985
+ 400,
986
+ );
987
+ }
988
+ return this.#repository.searchTenantMembers(
989
+ this.#identifier(tenantId, 'tenantId'),
990
+ escapeLikeTerm(term),
991
+ input.limit,
992
+ );
993
+ }
994
+
995
+ /* A dependent module that needs the role and scopes of one account asks for
996
+ that account: reading the whole workspace to find it costs the roll on
997
+ every decision a member takes. */
998
+ async findTenantMember(
999
+ tenantId: string,
1000
+ accountId: string,
1001
+ ): Promise<TenantMember | null> {
1002
+ return this.#repository.findTenantMember(tenantId, accountId);
562
1003
  }
563
1004
 
564
1005
  /* Scopes are authorization metadata, not credentials. Dependent modules read
@@ -605,6 +1046,16 @@ export class AuthService {
605
1046
  404,
606
1047
  );
607
1048
  }
1049
+ /* The caller is already authenticated as this account, so naming the cause
1050
+ reveals nothing it does not already know, and "the current password is
1051
+ incorrect" would be a dead end for a member who never had one. */
1052
+ if (!passwordIsSet(account.passwordHash)) {
1053
+ throw new AuthServiceError(
1054
+ 'PASSWORD_NOT_SET',
1055
+ 'This account has no password yet. Use the password reset link to set the first one.',
1056
+ 409,
1057
+ );
1058
+ }
608
1059
  const valid = await verifyPassword(
609
1060
  input.currentPassword,
610
1061
  account.passwordHash,
@@ -616,7 +1067,11 @@ export class AuthService {
616
1067
  401,
617
1068
  );
618
1069
  }
619
- assertPasswordPolicy(input.newPassword, this.#policy().passwordMinLength);
1070
+ assertPasswordPolicy(
1071
+ input.newPassword,
1072
+ this.#policy().passwordMinLength,
1073
+ account.email,
1074
+ );
620
1075
  if (input.newPassword === input.currentPassword) {
621
1076
  throw new AuthServiceError(
622
1077
  'PASSWORD_UNCHANGED',
@@ -800,6 +1255,37 @@ export class AuthService {
800
1255
  raw,
801
1256
  this.#policy().passwordMinLength,
802
1257
  );
1258
+ return this.#createTenantMember(
1259
+ input,
1260
+ actor,
1261
+ await hashPassword(input.password, this.#passwordHash),
1262
+ );
1263
+ }
1264
+
1265
+ /**
1266
+ * A member who holds no password at all. The account stores the unusable
1267
+ * credential marker rather than a secret nobody keeps: no password can match
1268
+ * it, so a sign-in by password is refused, and the member reaches the
1269
+ * workspace through the reset flow, an administrative temporary password or
1270
+ * an external identity. A bulk creator calls this instead of drawing a random
1271
+ * secret it immediately throws away.
1272
+ */
1273
+ async createTenantMemberWithoutPassword(
1274
+ raw: CreateTenantMemberWithoutPasswordInput,
1275
+ actor: AuthActor | null = null,
1276
+ ): Promise<TenantMember> {
1277
+ return this.#createTenantMember(
1278
+ validateCreateTenantMemberWithoutPassword(raw),
1279
+ actor,
1280
+ UNUSABLE_PASSWORD_HASH,
1281
+ );
1282
+ }
1283
+
1284
+ async #createTenantMember(
1285
+ input: CreateTenantMemberWithoutPasswordInput,
1286
+ actor: AuthActor | null,
1287
+ passwordHash: string,
1288
+ ): Promise<TenantMember> {
803
1289
  if (actor && actor.tenantId !== input.tenantId) {
804
1290
  throw new AuthServiceError(
805
1291
  'TENANT_ACCESS_DENIED',
@@ -815,7 +1301,6 @@ export class AuthService {
815
1301
  : input.role === 'owner'
816
1302
  ? OWNER_SCOPES
817
1303
  : MEMBER_SCOPES;
818
- const passwordHash = await hashPassword(input.password, this.#passwordHash);
819
1304
  try {
820
1305
  const createdAt = this.#now();
821
1306
  const account = await this.#repository.createAccountInTenant({
@@ -837,7 +1322,12 @@ export class AuthService {
837
1322
  AUDIT_ACTIONS.memberCreated,
838
1323
  'account',
839
1324
  account.accountId,
840
- { email: account.email, role: input.role },
1325
+ /* Only the credential-less creation is named, so the trail of an
1326
+ ordinary one keeps the shape it had and carries no word about
1327
+ a secret at all. */
1328
+ passwordHash === UNUSABLE_PASSWORD_HASH
1329
+ ? { email: account.email, role: input.role, credential: 'none' }
1330
+ : { email: account.email, role: input.role },
841
1331
  );
842
1332
  }
843
1333
  return member(account, createdAt);
@@ -882,13 +1372,20 @@ export class AuthService {
882
1372
  return target;
883
1373
  }
884
1374
 
1375
+ /* `counted` says whether the target is one of the active owners
1376
+ countActiveOwners still counts, which is what makes `<= 1` mean "the last
1377
+ one". A caller that changes the membership alone must not read the account
1378
+ block as the membership's own state, or disabling an owner whose
1379
+ membership is already disabled would answer LAST_OWNER instead of doing
1380
+ nothing. */
885
1381
  async #assertOwnerRemains(
886
1382
  tenantId: string,
887
1383
  target: AccountCredential,
1384
+ counted = target.status === 'active',
888
1385
  ): Promise<void> {
889
1386
  if (
890
1387
  target.role === 'owner' &&
891
- target.status === 'active' &&
1388
+ counted &&
892
1389
  (await this.#repository.countActiveOwners(tenantId)) <= 1
893
1390
  ) {
894
1391
  throw new AuthServiceError(
@@ -989,7 +1486,11 @@ export class AuthService {
989
1486
  const target = await this.#targetMember(actor, accountId, {
990
1487
  allowSelf: false,
991
1488
  });
992
- assertPasswordPolicy(temporaryPassword, this.#policy().passwordMinLength);
1489
+ assertPasswordPolicy(
1490
+ temporaryPassword,
1491
+ this.#policy().passwordMinLength,
1492
+ target.email,
1493
+ );
993
1494
  await this.#repository.updatePasswordHash(
994
1495
  target.accountId,
995
1496
  await hashPassword(temporaryPassword, this.#passwordHash),
@@ -1245,6 +1746,20 @@ export class AuthService {
1245
1746
  409,
1246
1747
  );
1247
1748
  }
1749
+ /* A provider provisions into a role by key, and the key is all the row
1750
+ holds. Deleting the role behind it would leave every just-in-time
1751
+ sign-in through that provider refusing silently, so the provider is
1752
+ pointed elsewhere first. */
1753
+ const named = (
1754
+ await this.#repository.listIdentityProviders(actor.tenantId)
1755
+ ).find((provider) => provider.jitRole === role.key);
1756
+ if (named) {
1757
+ throw new AuthServiceError(
1758
+ 'ROLE_NAMED_BY_PROVIDER',
1759
+ `The identity provider ${named.key} provisions members into this role. Point it at another role first.`,
1760
+ 409,
1761
+ );
1762
+ }
1248
1763
  await this.#repository.deleteRole(actor.tenantId, role.id);
1249
1764
  await this.#audit(
1250
1765
  actor.tenantId,
@@ -1320,17 +1835,25 @@ export class AuthService {
1320
1835
  const normalized = normalizeEmail(email);
1321
1836
  if (normalized.length > 254) return;
1322
1837
  const account = await this.#repository.findAccountByEmail(normalized);
1323
- if (!account || !this.#mailDelivery) return;
1838
+ if (!account) return;
1839
+ if (!this.#mailDelivery) {
1840
+ /* The client still gets the non-enumerating success, so the deployment
1841
+ log is the only place this dead end is visible. The address stays out
1842
+ of it. */
1843
+ console.warn(
1844
+ '[auth.core] mail transport is none; password reset for an existing account was not delivered',
1845
+ );
1846
+ return;
1847
+ }
1324
1848
  const reset = await this.#mintPasswordResetToken(
1325
1849
  account.accountId,
1326
1850
  PASSWORD_RESET_TTL_MS,
1327
1851
  );
1328
1852
  try {
1329
- await this.#mailDelivery.send({
1330
- to: account.email,
1331
- kind: 'password-reset',
1332
- url: reset.url,
1333
- });
1853
+ await this.#mailDelivery.send(
1854
+ { to: account.email, kind: 'password-reset', url: reset.url },
1855
+ await this.#mailLocale(account.tenantId),
1856
+ );
1334
1857
  await this.#audit(
1335
1858
  account.tenantId,
1336
1859
  { kind: 'user', id: account.accountId, label: account.email },
@@ -1352,20 +1875,36 @@ export class AuthService {
1352
1875
  400,
1353
1876
  );
1354
1877
  }
1355
- assertPasswordPolicy(password, this.#policy().passwordMinLength);
1356
- const accountId = await this.#repository.consumePasswordResetToken(
1357
- hashSessionToken(token),
1878
+ const tokenHash = hashSessionToken(token);
1879
+ const named = await this.#repository.findPasswordResetTokenAccount(
1880
+ tokenHash,
1358
1881
  this.#now(),
1359
1882
  );
1360
- if (!accountId) {
1883
+ const account = named
1884
+ ? await this.#repository.findAccountCredentialById(named)
1885
+ : null;
1886
+ if (!account || account.status !== 'active') {
1361
1887
  throw new AuthServiceError(
1362
1888
  'RESET_TOKEN_INVALID',
1363
1889
  'This password reset link is invalid or has expired.',
1364
1890
  400,
1365
1891
  );
1366
1892
  }
1367
- const account = await this.#repository.findAccountCredentialById(accountId);
1368
- if (!account || account.status !== 'active') {
1893
+ /* The whole policy, including the address rule, runs on the account the
1894
+ link names before anything spends it: a refused password leaves the
1895
+ link usable instead of stranding the visitor with a dead one. */
1896
+ assertPasswordPolicy(
1897
+ password,
1898
+ this.#policy().passwordMinLength,
1899
+ account.email,
1900
+ );
1901
+ /* Claiming the token is still one statement, so two submissions of the
1902
+ same link cannot both change the password. */
1903
+ const accountId = await this.#repository.consumePasswordResetToken(
1904
+ tokenHash,
1905
+ this.#now(),
1906
+ );
1907
+ if (!accountId) {
1369
1908
  throw new AuthServiceError(
1370
1909
  'RESET_TOKEN_INVALID',
1371
1910
  'This password reset link is invalid or has expired.',
@@ -1420,11 +1959,10 @@ export class AuthService {
1420
1959
  createdBy: actor.accountId,
1421
1960
  });
1422
1961
  try {
1423
- await this.#mailDelivery.send({
1424
- to: normalized,
1425
- kind: 'tenant-invitation',
1426
- url: invitation.url,
1427
- });
1962
+ await this.#mailDelivery.send(
1963
+ { to: normalized, kind: 'tenant-invitation', url: invitation.url },
1964
+ await this.#mailLocale(actor.tenantId),
1965
+ );
1428
1966
  } catch {
1429
1967
  throw new AuthServiceError(
1430
1968
  'MAIL_DELIVERY_FAILED',
@@ -1534,7 +2072,11 @@ export class AuthService {
1534
2072
  const displayName = validateDisplayName(input.ownerDisplayName);
1535
2073
  const email = validateEmailAddress(input.ownerEmail);
1536
2074
  if (input.password !== undefined) {
1537
- assertPasswordPolicy(input.password, this.#policy().passwordMinLength);
2075
+ assertPasswordPolicy(
2076
+ input.password,
2077
+ this.#policy().passwordMinLength,
2078
+ email,
2079
+ );
1538
2080
  }
1539
2081
  if (await this.#repository.isTenantSlugTaken(slug)) {
1540
2082
  throw new AuthServiceError(
@@ -1786,6 +2328,45 @@ export class AuthService {
1786
2328
  };
1787
2329
  }
1788
2330
 
2331
+ /* Built on first use, so a deployment that configures no MFA key still
2332
+ constructs the service and an unusable key is refused where a factor is
2333
+ actually sealed or opened, with MFA_NOT_CONFIGURED. */
2334
+ #vault(): MfaSecretVault {
2335
+ return (this.#mfaVault ??= createMfaSecretVault(
2336
+ this.#mfaEncryptionKey,
2337
+ this.#mfaPreviousEncryptionKeys,
2338
+ ));
2339
+ }
2340
+
2341
+ /* The same deployment keys under a provider-specific context, refused with
2342
+ PROVIDER_KEY_REQUIRED where a provider secret is actually sealed. */
2343
+ #providerVault(): ProviderSecretVault {
2344
+ return (this.#providerSecretVault ??= createProviderSecretVault(
2345
+ this.#mfaEncryptionKey,
2346
+ this.#mfaPreviousEncryptionKeys,
2347
+ ));
2348
+ }
2349
+
2350
+ /** Tenant-owned identity providers of the workspaces this service serves. */
2351
+ get identityProviders(): IdentityProviderService {
2352
+ return (this.#identityProviders ??= new IdentityProviderService({
2353
+ repository: this.#repository,
2354
+ vault: () => this.#providerVault(),
2355
+ now: this.#now,
2356
+ discover: this.#oidcDiscovery,
2357
+ forgetKeys: (providerId) => this.#forgetProviderKeys(providerId),
2358
+ audit: (tenantId, actor, action, subjectType, subjectId, metadata) =>
2359
+ this.#audit(
2360
+ tenantId,
2361
+ actor,
2362
+ action,
2363
+ subjectType,
2364
+ subjectId,
2365
+ metadata ?? {},
2366
+ ),
2367
+ }));
2368
+ }
2369
+
1789
2370
  async enrollTotp(
1790
2371
  accountId: string,
1791
2372
  issuer = 'Flowdular',
@@ -1820,7 +2401,7 @@ export class AuthService {
1820
2401
  const now = this.#now();
1821
2402
  await this.#repository.upsertMfaTotp(
1822
2403
  account.accountId,
1823
- encryptMfaSecret(secret, this.#mfaEncryptionKey),
2404
+ this.#vault().seal(secret),
1824
2405
  now,
1825
2406
  );
1826
2407
  await this.#repository.replaceMfaRecoveryCodes(
@@ -1860,6 +2441,31 @@ export class AuthService {
1860
2441
  };
1861
2442
  }
1862
2443
 
2444
+ /** Whether the account holds a confirmed second factor; one indexed read. */
2445
+ async hasConfirmedMfa(accountId: string): Promise<boolean> {
2446
+ return this.#repository.hasConfirmedMfaTotp(
2447
+ this.#identifier(accountId, 'accountId'),
2448
+ );
2449
+ }
2450
+
2451
+ /* An administrator clears a lost factor. The target is resolved inside the
2452
+ acting workspace, so an account that belongs only to another workspace
2453
+ answers exactly as an unknown one does. */
2454
+ async resetMemberMfa(actor: AuthActor, accountId: string): Promise<void> {
2455
+ const target = await this.#targetMember(actor, accountId, {
2456
+ allowSelf: false,
2457
+ });
2458
+ await this.#repository.deleteMfaEnrolment(target.accountId);
2459
+ await this.#audit(
2460
+ actor.tenantId,
2461
+ this.#actorOf(actor),
2462
+ AUDIT_ACTIONS.mfaReset,
2463
+ 'account',
2464
+ target.accountId,
2465
+ { email: target.email },
2466
+ );
2467
+ }
2468
+
1863
2469
  async confirmTotp(accountId: string, code: string): Promise<void> {
1864
2470
  const account = await this.#repository.findAccountCredentialById(
1865
2471
  this.#identifier(accountId, 'accountId'),
@@ -1871,7 +2477,7 @@ export class AuthService {
1871
2477
  !account ||
1872
2478
  !record ||
1873
2479
  !verifyTotp(
1874
- decryptMfaSecret(record.secretCiphertext, this.#mfaEncryptionKey),
2480
+ this.#vault().open(record.secretCiphertext, record.keyId),
1875
2481
  code,
1876
2482
  this.#now(),
1877
2483
  )
@@ -1926,7 +2532,7 @@ export class AuthService {
1926
2532
  record !== null &&
1927
2533
  ((typeof code === 'string' &&
1928
2534
  verifyTotp(
1929
- decryptMfaSecret(record.secretCiphertext, this.#mfaEncryptionKey),
2535
+ this.#vault().open(record.secretCiphertext, record.keyId),
1930
2536
  code,
1931
2537
  this.#now(),
1932
2538
  )) ||
@@ -1955,14 +2561,54 @@ export class AuthService {
1955
2561
  return issued;
1956
2562
  }
1957
2563
 
1958
- async signInVerifiedExternalEmail(
1959
- email: string,
1960
- ): Promise<IssuedSession | MfaChallenge> {
1961
- const account = await this.#repository.findAccountByEmail(
1962
- normalizeEmail(email),
2564
+ /* The provider subject is the identity; the address it reports only
2565
+ bootstraps the first link. Resolving by subject afterwards is what keeps a
2566
+ reassigned address at the provider from reaching another account here. */
2567
+ async signInExternalIdentity(input: {
2568
+ readonly provider: string;
2569
+ readonly subject: string;
2570
+ readonly email: string;
2571
+ /**
2572
+ * Present when a tenant-owned provider asserted the identity: the binding
2573
+ * lives in that workspace, the session opens in that workspace, and
2574
+ * nothing the provider says reaches another one.
2575
+ */
2576
+ readonly workspace?: TenantProviderSignIn;
2577
+ }): Promise<IssuedSession | MfaChallenge> {
2578
+ const provider = this.#identifier(input.provider, 'provider');
2579
+ const subject = this.#identifier(input.subject, 'subject');
2580
+ const workspace = input.workspace ?? null;
2581
+ const tenantId = workspace?.tenantId ?? null;
2582
+ const linkedAccountId = await this.#repository.findExternalIdentity(
2583
+ provider,
2584
+ subject,
2585
+ tenantId,
1963
2586
  );
2587
+ const account = linkedAccountId
2588
+ ? await this.#resolveLinkedAccount(linkedAccountId, tenantId)
2589
+ : await this.#resolveExternalAccount(input.email, provider, workspace);
1964
2590
  if (!account || account.status !== 'active')
1965
2591
  throw this.#invalidCredentials();
2592
+ if (
2593
+ !linkedAccountId &&
2594
+ (await this.#repository.findExternalIdentitySubject(
2595
+ provider,
2596
+ account.accountId,
2597
+ tenantId,
2598
+ )) !== null
2599
+ ) {
2600
+ /* The account already answers to another subject at this provider. An
2601
+ address the provider reports never re-binds it, which is what a
2602
+ reused or transferred address would otherwise do. */
2603
+ throw this.#invalidCredentials();
2604
+ }
2605
+ await this.#repository.linkExternalIdentity({
2606
+ provider,
2607
+ subject,
2608
+ accountId: account.accountId,
2609
+ tenantId,
2610
+ now: this.#now(),
2611
+ });
1966
2612
  if (
1967
2613
  (await this.#repository.findMfaTotp(account.accountId))?.confirmedAt !=
1968
2614
  null
@@ -1980,6 +2626,193 @@ export class AuthService {
1980
2626
  return issued;
1981
2627
  }
1982
2628
 
2629
+ /* A binding already made resolves the membership it was made in. A platform
2630
+ binding names no workspace and keeps resolving the account's own. */
2631
+ async #resolveLinkedAccount(
2632
+ accountId: string,
2633
+ tenantId: string | null,
2634
+ ): Promise<AccountCredential | null> {
2635
+ if (!tenantId) {
2636
+ return this.#repository.findAccountCredentialById(accountId);
2637
+ }
2638
+ const membership = await this.#repository.findAccountMembership(
2639
+ accountId,
2640
+ tenantId,
2641
+ );
2642
+ return membership?.membershipStatus === 'active' ? membership : null;
2643
+ }
2644
+
2645
+ /**
2646
+ * The first sign-in through a provider, before any binding exists. The
2647
+ * verified address only bootstraps the link: for a platform provider it
2648
+ * resolves the account exactly as before, and for a workspace provider it
2649
+ * resolves the membership of that workspace, provisioning one when the
2650
+ * provider is configured to and the address is inside its domains.
2651
+ */
2652
+ async #resolveExternalAccount(
2653
+ email: string,
2654
+ provider: string,
2655
+ workspace: TenantProviderSignIn | null,
2656
+ ): Promise<AccountCredential | null> {
2657
+ const normalized = normalizeEmail(email);
2658
+ if (!workspace) return this.#repository.findAccountByEmail(normalized);
2659
+ const identity = await this.#repository.findAccountIdentity(normalized);
2660
+ if (identity && identity.status !== 'active') return null;
2661
+ if (identity) {
2662
+ const membership = await this.#repository.findAccountMembership(
2663
+ identity.accountId,
2664
+ workspace.tenantId,
2665
+ );
2666
+ if (membership) {
2667
+ return membership.membershipStatus === 'active' ? membership : null;
2668
+ }
2669
+ }
2670
+ return this.#provisionExternalMember(
2671
+ normalized,
2672
+ identity?.accountId ?? null,
2673
+ provider,
2674
+ workspace,
2675
+ );
2676
+ }
2677
+
2678
+ /* Just-in-time provisioning. Every refusal happens before the first write,
2679
+ so a provider that is not allowed to create members creates nothing. */
2680
+ async #provisionExternalMember(
2681
+ normalizedEmail: string,
2682
+ accountId: string | null,
2683
+ provider: string,
2684
+ workspace: TenantProviderSignIn,
2685
+ ): Promise<AccountCredential | null> {
2686
+ if (!workspace.jitEnabled) return null;
2687
+ const domain = normalizedEmail.slice(normalizedEmail.lastIndexOf('@') + 1);
2688
+ if (!domain || !workspace.allowedDomains.includes(domain)) return null;
2689
+ /* A tenant-owned provider may not absorb an account other workspaces
2690
+ rely on. Reuse is limited to an account no workspace holds; anyone
2691
+ else is invited by this workspace instead. */
2692
+ if (accountId && (await this.#repository.countMemberships(accountId)) > 0) {
2693
+ await this.#audit(
2694
+ workspace.tenantId,
2695
+ providerActor(provider),
2696
+ AUDIT_ACTIONS.jitRefused,
2697
+ 'sign-in',
2698
+ provider,
2699
+ { provider, reason: 'account-has-other-membership' },
2700
+ );
2701
+ throw new AuthServiceError(
2702
+ 'JIT_ACCOUNT_EXISTS',
2703
+ 'The account already belongs to another workspace.',
2704
+ 401,
2705
+ );
2706
+ }
2707
+ const role = await this.#repository.findRoleByKey(
2708
+ workspace.tenantId,
2709
+ workspace.jitRole,
2710
+ );
2711
+ /* Saving a provider proves the role exists and deleting that role is
2712
+ refused, so reaching this means the row and the roles drifted apart.
2713
+ The refusal is recorded under its own reason rather than looking like
2714
+ a domain or account refusal. */
2715
+ if (!role) {
2716
+ await this.#audit(
2717
+ workspace.tenantId,
2718
+ providerActor(provider),
2719
+ AUDIT_ACTIONS.jitRefused,
2720
+ 'sign-in',
2721
+ provider,
2722
+ { provider, reason: 'role-missing', role: workspace.jitRole },
2723
+ );
2724
+ return null;
2725
+ }
2726
+ const createdAt = this.#now();
2727
+ const member = accountId
2728
+ ? await this.#repository.createMembershipInTenant({
2729
+ accountId,
2730
+ tenantId: workspace.tenantId,
2731
+ role: role.key,
2732
+ roleId: role.id,
2733
+ scopes: role.scopes,
2734
+ createdAt,
2735
+ })
2736
+ : await this.#repository.createAccountInTenant({
2737
+ accountId: randomUUID(),
2738
+ tenantId: workspace.tenantId,
2739
+ email: normalizedEmail,
2740
+ normalizedEmail,
2741
+ /* The account signs in through the provider. A random hash nobody
2742
+ holds keeps the password path closed until a reset sets one. */
2743
+ passwordHash: await hashPassword(
2744
+ randomBytes(32).toString('base64url'),
2745
+ this.#passwordHash,
2746
+ ),
2747
+ displayName: displayNameFromEmail(normalizedEmail),
2748
+ role: role.key,
2749
+ roleId: role.id,
2750
+ scopes: role.scopes,
2751
+ createdAt,
2752
+ });
2753
+ await this.#audit(
2754
+ workspace.tenantId,
2755
+ { kind: 'user', id: member.accountId, label: member.email },
2756
+ AUDIT_ACTIONS.memberProvisioned,
2757
+ 'account',
2758
+ member.accountId,
2759
+ { provider, role: role.key },
2760
+ );
2761
+ return member;
2762
+ }
2763
+
2764
+ /**
2765
+ * The workspace's own status for one member. Disabling revokes that
2766
+ * membership's sessions and API tokens and refuses its sign-in; the other
2767
+ * workspaces of the same account are untouched, and the account status stays
2768
+ * the deployment operator's platform-level block.
2769
+ */
2770
+ async setMembershipStatus(
2771
+ actor: AuthActor,
2772
+ accountId: string,
2773
+ status: MembershipStatus,
2774
+ ): Promise<{
2775
+ readonly accountId: string;
2776
+ readonly status: MembershipStatus;
2777
+ }> {
2778
+ const target = await this.#targetMember(actor, accountId, {
2779
+ allowSelf: false,
2780
+ });
2781
+ if (status === 'disabled') {
2782
+ await this.#assertOwnerRemains(
2783
+ actor.tenantId,
2784
+ target,
2785
+ target.status === 'active' && target.membershipStatus === 'active',
2786
+ );
2787
+ }
2788
+ await this.#repository.setMembershipStatus(
2789
+ target.accountId,
2790
+ actor.tenantId,
2791
+ status,
2792
+ );
2793
+ if (status === 'disabled') {
2794
+ await this.#repository.deleteMembershipSessions(
2795
+ target.accountId,
2796
+ actor.tenantId,
2797
+ );
2798
+ await this.#repository.revokeMembershipApiTokens(
2799
+ actor.tenantId,
2800
+ target.accountId,
2801
+ this.#now(),
2802
+ actor.accountId,
2803
+ );
2804
+ }
2805
+ await this.#audit(
2806
+ actor.tenantId,
2807
+ this.#actorOf(actor),
2808
+ AUDIT_ACTIONS.membershipStatus,
2809
+ 'account',
2810
+ target.accountId,
2811
+ { status },
2812
+ );
2813
+ return { accountId: target.accountId, status };
2814
+ }
2815
+
1983
2816
  async resolveSession(token: string | null): Promise<AuthSession | null> {
1984
2817
  if (!token) return null;
1985
2818
  return this.#repository.findSession(
@@ -2051,19 +2884,31 @@ export class AuthService {
2051
2884
  return this.#repository.deleteExpiredSessions(this.#now());
2052
2885
  }
2053
2886
 
2887
+ /**
2888
+ * The one audit row a committed settings change owes. A flag is recorded
2889
+ * under its own action with the values around the change, because an
2890
+ * operator reviewing a flag needs to see what it was turned to; every other
2891
+ * setting keeps the plain record, so no declared value ever reaches the
2892
+ * trail by the side door.
2893
+ */
2054
2894
  async recordSettingsUpdate(
2055
2895
  actor: AuthActor,
2056
- moduleId: string,
2057
- key: string,
2058
- cleared: boolean,
2896
+ change: ModuleSettingChange,
2059
2897
  ): Promise<void> {
2898
+ const flag = change.kind === 'flag';
2060
2899
  await this.#audit(
2061
2900
  actor.tenantId,
2062
2901
  this.#actorOf(actor),
2063
- AUDIT_ACTIONS.settingsUpdated,
2902
+ flag ? AUDIT_ACTIONS.settingsFlagChanged : AUDIT_ACTIONS.settingsUpdated,
2064
2903
  'setting',
2065
- `${moduleId}.${key}`,
2066
- { cleared },
2904
+ `${change.moduleId}.${change.key}`,
2905
+ flag
2906
+ ? {
2907
+ cleared: change.cleared,
2908
+ previous: change.previous,
2909
+ next: change.next,
2910
+ }
2911
+ : { cleared: change.cleared },
2067
2912
  );
2068
2913
  }
2069
2914
 
@@ -2072,9 +2917,22 @@ export class AuthService {
2072
2917
  Math.max(1, Math.trunc(query.limit)),
2073
2918
  MAX_AUDIT_PAGE,
2074
2919
  );
2920
+ const from = this.#auditBound(query.from, 'from');
2921
+ const to = this.#auditBound(query.to, 'to');
2922
+ /* A window that ends before it starts would answer an empty page, which a
2923
+ caller cannot tell from a workspace with no events in the window. */
2924
+ if (from !== null && to !== null && from > to) {
2925
+ throw new AuthServiceError(
2926
+ 'INVALID_INPUT',
2927
+ 'from must not be later than to.',
2928
+ 400,
2929
+ );
2930
+ }
2075
2931
  const events = await this.#repository.queryAudit({
2076
2932
  ...query,
2077
2933
  tenantId: this.#identifier(query.tenantId, 'tenantId'),
2934
+ from,
2935
+ to,
2078
2936
  limit: limit + 1,
2079
2937
  });
2080
2938
  const page = events.slice(0, limit);
@@ -2106,7 +2964,9 @@ export class AuthService {
2106
2964
  current.principal.accountId,
2107
2965
  tenantId,
2108
2966
  );
2109
- if (!account) {
2967
+ /* A membership the workspace disabled is not a workspace this account may
2968
+ enter, and it answers exactly as one it never held. */
2969
+ if (!account || account.membershipStatus !== 'active') {
2110
2970
  throw new AuthServiceError(
2111
2971
  'TENANT_ACCESS_DENIED',
2112
2972
  'The requested tenant is not available.',
@@ -2137,7 +2997,11 @@ export class AuthService {
2137
2997
  accountId,
2138
2998
  tenantId,
2139
2999
  );
2140
- if (!membership || membership.status !== 'active') {
3000
+ if (
3001
+ !membership ||
3002
+ membership.status !== 'active' ||
3003
+ membership.membershipStatus !== 'active'
3004
+ ) {
2141
3005
  throw new AuthServiceError(
2142
3006
  'ACCOUNT_NOT_FOUND',
2143
3007
  'The account is not an active member of this workspace.',
@@ -2256,7 +3120,12 @@ export class AuthService {
2256
3120
  record.accountId,
2257
3121
  record.tenantId,
2258
3122
  );
2259
- if (!membership || membership.status !== 'active') return null;
3123
+ if (
3124
+ !membership ||
3125
+ membership.status !== 'active' ||
3126
+ membership.membershipStatus !== 'active'
3127
+ )
3128
+ return null;
2260
3129
  const held = new Set(membership.scopes);
2261
3130
  const scopes = record.scopes.filter((scope) => held.has(scope));
2262
3131
  if (scopes.length === 0) return null;
@@ -2277,6 +3146,34 @@ export class AuthService {
2277
3146
  };
2278
3147
  }
2279
3148
 
3149
+ /* One end of the audit window. An absent end is open; a bound that is not an
3150
+ epoch millisecond is a caller defect rather than an open end, because
3151
+ silently dropping it would answer a wider trail than was asked for. */
3152
+ #auditBound(value: number | null | undefined, field: string): number | null {
3153
+ if (value === null || value === undefined) return null;
3154
+ if (!Number.isSafeInteger(value)) {
3155
+ throw new AuthServiceError(
3156
+ 'INVALID_INPUT',
3157
+ `${field} must be an epoch millisecond timestamp.`,
3158
+ 400,
3159
+ );
3160
+ }
3161
+ return value;
3162
+ }
3163
+
3164
+ /* The bound a paged read surface carries into SQL, so no caller of one can
3165
+ ask a workspace of unknown size for all of it. */
3166
+ #pageLimit(limit: number, max: number): number {
3167
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > max) {
3168
+ throw new AuthServiceError(
3169
+ 'INVALID_INPUT',
3170
+ `Limit must be 1 to ${max}.`,
3171
+ 400,
3172
+ );
3173
+ }
3174
+ return limit;
3175
+ }
3176
+
2280
3177
  #identifier(value: string, field: string): string {
2281
3178
  const normalized = value.trim();
2282
3179
  if (normalized.length < 1 || normalized.length > 128) {