@antelopejs/dms 0.6.2 → 0.7.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 (444) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/db/models/notification-feed-filter.d.ts +0 -1
  3. package/dist/db/models/notification-feed-filter.js +0 -4
  4. package/dist/db/models/notification-feed-filter.js.map +1 -1
  5. package/dist/db/models/signInAttempts.model.d.ts +18 -0
  6. package/dist/db/models/signInAttempts.model.js +83 -12
  7. package/dist/db/models/signInAttempts.model.js.map +1 -1
  8. package/dist/db/models/userEmailChanges.model.d.ts +8 -1
  9. package/dist/db/models/userEmailChanges.model.js +27 -2
  10. package/dist/db/models/userEmailChanges.model.js.map +1 -1
  11. package/dist/db/models/userNotificationPreferences.model.d.ts +0 -2
  12. package/dist/db/models/userNotificationPreferences.model.js +0 -13
  13. package/dist/db/models/userNotificationPreferences.model.js.map +1 -1
  14. package/dist/db/models/userNotifications.model.d.ts +5 -2
  15. package/dist/db/models/userNotifications.model.js +19 -2
  16. package/dist/db/models/userNotifications.model.js.map +1 -1
  17. package/dist/db/tables/signInAttempts.table.d.ts +2 -1
  18. package/dist/db/tables/signInAttempts.table.js +4 -0
  19. package/dist/db/tables/signInAttempts.table.js.map +1 -1
  20. package/dist/db/tables/userEmailChanges.table.d.ts +2 -1
  21. package/dist/db/tables/userEmailChanges.table.js +6 -1
  22. package/dist/db/tables/userEmailChanges.table.js.map +1 -1
  23. package/dist/db/tables/userNotificationPreferences.table.d.ts +0 -1
  24. package/dist/db/tables/userNotificationPreferences.table.js +0 -4
  25. package/dist/db/tables/userNotificationPreferences.table.js.map +1 -1
  26. package/dist/implementations/dms/nav-badges.d.ts +6 -4
  27. package/dist/implementations/dms/nav-badges.js +6 -5
  28. package/dist/implementations/dms/nav-badges.js.map +1 -1
  29. package/dist/implementations/dms/page.d.ts +1 -0
  30. package/dist/implementations/dms/page.js +46 -8
  31. package/dist/implementations/dms/page.js.map +1 -1
  32. package/dist/implementations/dms/permission-preview.d.ts +1 -1
  33. package/dist/implementations/dms/permission-preview.js +9 -4
  34. package/dist/implementations/dms/permission-preview.js.map +1 -1
  35. package/dist/implementations/dms-auth/index.d.ts +3 -2
  36. package/dist/implementations/dms-auth/index.js +39 -35
  37. package/dist/implementations/dms-auth/index.js.map +1 -1
  38. package/dist/implementations/dms-auth/token-session.d.ts +3 -0
  39. package/dist/implementations/dms-auth/token-session.js +22 -0
  40. package/dist/implementations/dms-auth/token-session.js.map +1 -0
  41. package/dist/implementations/dms-notifications/index.d.ts +0 -1
  42. package/dist/implementations/dms-notifications/index.js +0 -7
  43. package/dist/implementations/dms-notifications/index.js.map +1 -1
  44. package/dist/pages/settings/settings-index.js +3 -2
  45. package/dist/pages/settings/settings-index.js.map +1 -1
  46. package/dist/pages/settings/users/account-activity-feed.d.ts +1 -1
  47. package/dist/pages/settings/users/account-activity-feed.js +1 -1
  48. package/dist/pages/settings/users/account-activity-feed.js.map +1 -1
  49. package/dist/pages/settings/users/account-activity.d.ts +1 -1
  50. package/dist/pages/settings/users/account-activity.js +1 -1
  51. package/dist/pages/settings/users/account-activity.js.map +1 -1
  52. package/dist/pages/settings/users/account-credentials.d.ts +3 -0
  53. package/dist/pages/settings/users/account-credentials.js +2 -0
  54. package/dist/pages/settings/users/account-credentials.js.map +1 -1
  55. package/dist/pages/settings/users/current-password.d.ts +1 -1
  56. package/dist/pages/settings/users/current-password.js +23 -2
  57. package/dist/pages/settings/users/current-password.js.map +1 -1
  58. package/dist/pages/settings/users/email-change.d.ts +1 -1
  59. package/dist/pages/settings/users/email-change.js +33 -26
  60. package/dist/pages/settings/users/email-change.js.map +1 -1
  61. package/dist/pages/settings/users/invite-resend.js +0 -1
  62. package/dist/pages/settings/users/invite-resend.js.map +1 -1
  63. package/dist/pages/settings/users/invites.d.ts +1 -2
  64. package/dist/pages/settings/users/invites.js +21 -19
  65. package/dist/pages/settings/users/invites.js.map +1 -1
  66. package/dist/pages/settings/users/member-invite-batch.js +0 -1
  67. package/dist/pages/settings/users/member-invite-batch.js.map +1 -1
  68. package/dist/pages/settings/users/member-invite-form.js +10 -22
  69. package/dist/pages/settings/users/member-invite-form.js.map +1 -1
  70. package/dist/pages/settings/users/member-role-options.d.ts +6 -4
  71. package/dist/pages/settings/users/member-role-options.js +15 -4
  72. package/dist/pages/settings/users/member-role-options.js.map +1 -1
  73. package/dist/pages/settings/users/members.d.ts +2 -3
  74. package/dist/pages/settings/users/members.js +7 -12
  75. package/dist/pages/settings/users/members.js.map +1 -1
  76. package/dist/pages/settings/users/notifications.d.ts +1 -8
  77. package/dist/pages/settings/users/notifications.js +6 -38
  78. package/dist/pages/settings/users/notifications.js.map +1 -1
  79. package/dist/pages/settings/users/permission-tree-nodes.d.ts +1 -1
  80. package/dist/pages/settings/users/permission-tree-nodes.js +23 -9
  81. package/dist/pages/settings/users/permission-tree-nodes.js.map +1 -1
  82. package/dist/pages/settings/users/permission-warnings.d.ts +7 -0
  83. package/dist/pages/settings/users/permission-warnings.js +42 -0
  84. package/dist/pages/settings/users/permission-warnings.js.map +1 -0
  85. package/dist/pages/settings/users/profile-helpers.d.ts +1 -3
  86. package/dist/pages/settings/users/profile-helpers.js +0 -16
  87. package/dist/pages/settings/users/profile-helpers.js.map +1 -1
  88. package/dist/pages/settings/users/role-editor-store.js +6 -3
  89. package/dist/pages/settings/users/role-editor-store.js.map +1 -1
  90. package/dist/pages/settings/users/role-editor.d.ts +2 -1
  91. package/dist/pages/settings/users/role-editor.js +8 -4
  92. package/dist/pages/settings/users/role-editor.js.map +1 -1
  93. package/dist/pages/settings/users/role-preview.js +2 -1
  94. package/dist/pages/settings/users/role-preview.js.map +1 -1
  95. package/dist/pages/settings/users/roles.js +5 -1
  96. package/dist/pages/settings/users/roles.js.map +1 -1
  97. package/dist/pages/settings/users/security-attention.d.ts +4 -0
  98. package/dist/pages/settings/users/security-attention.js +23 -7
  99. package/dist/pages/settings/users/security-attention.js.map +1 -1
  100. package/dist/pages/settings/users/security.d.ts +2 -1
  101. package/dist/pages/settings/users/security.js +13 -2
  102. package/dist/pages/settings/users/security.js.map +1 -1
  103. package/dist/pages/settings/users/two-factor-operations.d.ts +2 -1
  104. package/dist/pages/settings/users/two-factor-operations.js +28 -29
  105. package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
  106. package/dist/routes/auth/forgot-password.js +3 -0
  107. package/dist/routes/auth/forgot-password.js.map +1 -1
  108. package/dist/routes/auth/index.d.ts +1 -0
  109. package/dist/routes/auth/index.js +1 -0
  110. package/dist/routes/auth/index.js.map +1 -1
  111. package/dist/routes/auth/invite.js +28 -4
  112. package/dist/routes/auth/invite.js.map +1 -1
  113. package/dist/routes/auth/login-throttle.d.ts +2 -0
  114. package/dist/routes/auth/login-throttle.js +5 -4
  115. package/dist/routes/auth/login-throttle.js.map +1 -1
  116. package/dist/routes/auth/request-2fa-email.js +6 -18
  117. package/dist/routes/auth/request-2fa-email.js.map +1 -1
  118. package/dist/routes/auth/reset-code.d.ts +3 -0
  119. package/dist/routes/auth/reset-code.js +37 -0
  120. package/dist/routes/auth/reset-code.js.map +1 -0
  121. package/dist/routes/auth/reset-password.d.ts +2 -2
  122. package/dist/routes/auth/reset-password.js +5 -10
  123. package/dist/routes/auth/reset-password.js.map +1 -1
  124. package/dist/routes/auth/signup.js +10 -32
  125. package/dist/routes/auth/signup.js.map +1 -1
  126. package/dist/routes/auth/two-factor-throttle.d.ts +5 -0
  127. package/dist/routes/auth/two-factor-throttle.js +81 -0
  128. package/dist/routes/auth/two-factor-throttle.js.map +1 -0
  129. package/dist/routes/auth/validate-forgot-password-token.js +2 -8
  130. package/dist/routes/auth/validate-forgot-password-token.js.map +1 -1
  131. package/dist/routes/auth/validate-invite-token.d.ts +1 -0
  132. package/dist/routes/auth/validate-invite-token.js +11 -0
  133. package/dist/routes/auth/validate-invite-token.js.map +1 -0
  134. package/dist/routes/auth/verify-2fa.js +10 -13
  135. package/dist/routes/auth/verify-2fa.js.map +1 -1
  136. package/dist/routes/auth.d.ts +1 -0
  137. package/dist/routes/auth.js +11 -1
  138. package/dist/routes/auth.js.map +1 -1
  139. package/dist/test/helpers/auth.d.ts +0 -1
  140. package/dist/test/helpers/auth.js +0 -1
  141. package/dist/test/helpers/auth.js.map +1 -1
  142. package/dist/test/helpers/emails.d.ts +10 -0
  143. package/dist/test/helpers/emails.js +36 -0
  144. package/dist/test/helpers/emails.js.map +1 -0
  145. package/dist/test/helpers/fixtures.d.ts +0 -1
  146. package/dist/test/helpers/fixtures.js +1 -2
  147. package/dist/test/helpers/fixtures.js.map +1 -1
  148. package/dist/test/helpers/page-access.d.ts +1 -0
  149. package/dist/test/helpers/page-access.js +6 -1
  150. package/dist/test/helpers/page-access.js.map +1 -1
  151. package/dist/test/integration/auth/current-password.test.d.ts +1 -0
  152. package/dist/test/integration/auth/current-password.test.js +65 -0
  153. package/dist/test/integration/auth/current-password.test.js.map +1 -0
  154. package/dist/test/integration/auth/password-reset.test.d.ts +1 -0
  155. package/dist/test/integration/auth/password-reset.test.js +126 -0
  156. package/dist/test/integration/auth/password-reset.test.js.map +1 -0
  157. package/dist/test/integration/auth/session-revocation.test.d.ts +1 -0
  158. package/dist/test/integration/auth/session-revocation.test.js +45 -0
  159. package/dist/test/integration/auth/session-revocation.test.js.map +1 -0
  160. package/dist/test/integration/auth/two-factor-codes.test.d.ts +1 -0
  161. package/dist/test/integration/auth/two-factor-codes.test.js +335 -0
  162. package/dist/test/integration/auth/two-factor-codes.test.js.map +1 -0
  163. package/dist/test/integration/auth/two-factor-password.test.js +5 -1
  164. package/dist/test/integration/auth/two-factor-password.test.js.map +1 -1
  165. package/dist/test/integration/auth/two-factor-token.test.d.ts +1 -0
  166. package/dist/test/integration/auth/two-factor-token.test.js +92 -0
  167. package/dist/test/integration/auth/two-factor-token.test.js.map +1 -0
  168. package/dist/test/integration/email-change.test.js +36 -0
  169. package/dist/test/integration/email-change.test.js.map +1 -1
  170. package/dist/test/integration/invite-extension-edit.test.js +0 -1
  171. package/dist/test/integration/invite-extension-edit.test.js.map +1 -1
  172. package/dist/test/integration/invite-link-status.test.d.ts +1 -0
  173. package/dist/test/integration/invite-link-status.test.js +110 -0
  174. package/dist/test/integration/invite-link-status.test.js.map +1 -0
  175. package/dist/test/integration/invite-resolution.test.js +0 -3
  176. package/dist/test/integration/invite-resolution.test.js.map +1 -1
  177. package/dist/test/integration/member-settings-access.test.js +17 -0
  178. package/dist/test/integration/member-settings-access.test.js.map +1 -1
  179. package/dist/test/integration/notification-unread-badge.test.d.ts +1 -0
  180. package/dist/test/integration/notification-unread-badge.test.js +92 -0
  181. package/dist/test/integration/notification-unread-badge.test.js.map +1 -0
  182. package/dist/test/integration/permission-warnings.test.d.ts +1 -0
  183. package/dist/test/integration/permission-warnings.test.js +71 -0
  184. package/dist/test/integration/permission-warnings.test.js.map +1 -0
  185. package/dist/test/integration/removed-member-notifications.test.d.ts +1 -0
  186. package/dist/test/integration/removed-member-notifications.test.js +80 -0
  187. package/dist/test/integration/removed-member-notifications.test.js.map +1 -0
  188. package/dist/test/integration/role-permission-ancestors.test.d.ts +1 -0
  189. package/dist/test/integration/role-permission-ancestors.test.js +146 -0
  190. package/dist/test/integration/role-permission-ancestors.test.js.map +1 -0
  191. package/dist/test/integration/tenant-lifecycle.test.js +0 -4
  192. package/dist/test/integration/tenant-lifecycle.test.js.map +1 -1
  193. package/dist/test/unit/auth-session-revocation.test.d.ts +1 -0
  194. package/dist/test/unit/auth-session-revocation.test.js +105 -0
  195. package/dist/test/unit/auth-session-revocation.test.js.map +1 -0
  196. package/dist/test/unit/db/email-normalization.test.js +0 -1
  197. package/dist/test/unit/db/email-normalization.test.js.map +1 -1
  198. package/dist/test/unit/db/notification-feed-filter.test.js +0 -16
  199. package/dist/test/unit/db/notification-feed-filter.test.js.map +1 -1
  200. package/dist/test/unit/db/notification-inbox.test.js +63 -13
  201. package/dist/test/unit/db/notification-inbox.test.js.map +1 -1
  202. package/dist/test/unit/db/notification-preferences.test.js +0 -17
  203. package/dist/test/unit/db/notification-preferences.test.js.map +1 -1
  204. package/dist/test/unit/implementations/dms/empty-menu-group.test.d.ts +1 -0
  205. package/dist/test/unit/implementations/dms/empty-menu-group.test.js +133 -0
  206. package/dist/test/unit/implementations/dms/empty-menu-group.test.js.map +1 -0
  207. package/dist/test/unit/implementations/dms/layout-banners.test.js +2 -1
  208. package/dist/test/unit/implementations/dms/layout-banners.test.js.map +1 -1
  209. package/dist/test/unit/implementations/dms/menu-hidden-blocks.test.d.ts +1 -0
  210. package/dist/test/unit/implementations/dms/menu-hidden-blocks.test.js +149 -0
  211. package/dist/test/unit/implementations/dms/menu-hidden-blocks.test.js.map +1 -0
  212. package/dist/test/unit/implementations/dms/nav-badges.test.js +55 -4
  213. package/dist/test/unit/implementations/dms/nav-badges.test.js.map +1 -1
  214. package/dist/test/unit/implementations/dms/page-all-components-hidden.test.d.ts +1 -0
  215. package/dist/test/unit/implementations/dms/page-all-components-hidden.test.js +118 -0
  216. package/dist/test/unit/implementations/dms/page-all-components-hidden.test.js.map +1 -0
  217. package/dist/test/unit/implementations/dms/permission-preview-aggregate.test.js +16 -0
  218. package/dist/test/unit/implementations/dms/permission-preview-aggregate.test.js.map +1 -1
  219. package/dist/test/unit/implementations/dms/permission-preview-cost.test.js +5 -3
  220. package/dist/test/unit/implementations/dms/permission-preview-cost.test.js.map +1 -1
  221. package/dist/test/unit/implementations/dms/permission-preview-menu.test.js +29 -3
  222. package/dist/test/unit/implementations/dms/permission-preview-menu.test.js.map +1 -1
  223. package/dist/test/unit/implementations/dms/quick-actions-button.test.js +4 -2
  224. package/dist/test/unit/implementations/dms/quick-actions-button.test.js.map +1 -1
  225. package/dist/test/unit/implementations/dms/user-permissions-gate.test.js +2 -1
  226. package/dist/test/unit/implementations/dms/user-permissions-gate.test.js.map +1 -1
  227. package/dist/test/unit/implementations/dms-base/table-view-shared-controller.test.js +2 -1
  228. package/dist/test/unit/implementations/dms-base/table-view-shared-controller.test.js.map +1 -1
  229. package/dist/test/unit/interfaces/dms/component-children-permissions.test.js +7 -2
  230. package/dist/test/unit/interfaces/dms/component-children-permissions.test.js.map +1 -1
  231. package/dist/test/unit/interfaces/dms/form-watch-permissions.test.d.ts +1 -0
  232. package/dist/test/unit/interfaces/dms/form-watch-permissions.test.js +133 -0
  233. package/dist/test/unit/interfaces/dms/form-watch-permissions.test.js.map +1 -0
  234. package/dist/test/unit/interfaces/dms/has-permission-ancestors.test.d.ts +1 -0
  235. package/dist/test/unit/interfaces/dms/has-permission-ancestors.test.js +149 -0
  236. package/dist/test/unit/interfaces/dms/has-permission-ancestors.test.js.map +1 -0
  237. package/dist/test/unit/interfaces/dms/invite-extension.test.js +13 -0
  238. package/dist/test/unit/interfaces/dms/invite-extension.test.js.map +1 -1
  239. package/dist/test/unit/interfaces/dms/member-access.test.js +27 -1
  240. package/dist/test/unit/interfaces/dms/member-access.test.js.map +1 -1
  241. package/dist/test/unit/interfaces/dms/nav-badge-tones.test.d.ts +1 -0
  242. package/dist/test/unit/interfaces/dms/nav-badge-tones.test.js +31 -0
  243. package/dist/test/unit/interfaces/dms/nav-badge-tones.test.js.map +1 -0
  244. package/dist/test/unit/interfaces/dms/page-extension.test.js +4 -1
  245. package/dist/test/unit/interfaces/dms/page-extension.test.js.map +1 -1
  246. package/dist/test/unit/interfaces/dms/permission-requirements.test.d.ts +1 -0
  247. package/dist/test/unit/interfaces/dms/permission-requirements.test.js +144 -0
  248. package/dist/test/unit/interfaces/dms/permission-requirements.test.js.map +1 -0
  249. package/dist/test/unit/interfaces/dms-base/layout-header-actions.test.js +6 -2
  250. package/dist/test/unit/interfaces/dms-base/layout-header-actions.test.js.map +1 -1
  251. package/dist/test/unit/interfaces/dms-base/lists-and-settings-catalog.test.js +1 -1
  252. package/dist/test/unit/interfaces/dms-base/lists-and-settings-catalog.test.js.map +1 -1
  253. package/dist/test/unit/interfaces/dms-base/table-view-export-permission.test.js +2 -1
  254. package/dist/test/unit/interfaces/dms-base/table-view-export-permission.test.js.map +1 -1
  255. package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js +2 -1
  256. package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js.map +1 -1
  257. package/dist/test/unit/interfaces/dms-base/table-view-shared-controller-permissions.test.js +2 -1
  258. package/dist/test/unit/interfaces/dms-base/table-view-shared-controller-permissions.test.js.map +1 -1
  259. package/dist/test/unit/pages/settings/account-activity-feed.test.js +2 -2
  260. package/dist/test/unit/pages/settings/account-activity-feed.test.js.map +1 -1
  261. package/dist/test/unit/pages/settings/account-activity.test.js +11 -2
  262. package/dist/test/unit/pages/settings/account-activity.test.js.map +1 -1
  263. package/dist/test/unit/pages/settings/invite-email-delivery.test.js +0 -1
  264. package/dist/test/unit/pages/settings/invite-email-delivery.test.js.map +1 -1
  265. package/dist/test/unit/pages/settings/invite-email-validation-option.test.d.ts +1 -0
  266. package/dist/test/unit/pages/settings/invite-email-validation-option.test.js +33 -0
  267. package/dist/test/unit/pages/settings/invite-email-validation-option.test.js.map +1 -0
  268. package/dist/test/unit/pages/settings/member-lists-page-size.test.d.ts +1 -0
  269. package/dist/test/unit/pages/settings/member-lists-page-size.test.js +22 -0
  270. package/dist/test/unit/pages/settings/member-lists-page-size.test.js.map +1 -0
  271. package/dist/test/unit/pages/settings/member-table-widths.test.js +2 -2
  272. package/dist/test/unit/pages/settings/member-table-widths.test.js.map +1 -1
  273. package/dist/test/unit/pages/settings/members-page.test.js +7 -10
  274. package/dist/test/unit/pages/settings/members-page.test.js.map +1 -1
  275. package/dist/test/unit/pages/settings/notifications-access-gate.test.js +3 -10
  276. package/dist/test/unit/pages/settings/notifications-access-gate.test.js.map +1 -1
  277. package/dist/test/unit/pages/settings/permission-tree-nodes.test.js +57 -0
  278. package/dist/test/unit/pages/settings/permission-tree-nodes.test.js.map +1 -1
  279. package/dist/test/unit/pages/settings/permission-warnings.test.d.ts +1 -0
  280. package/dist/test/unit/pages/settings/permission-warnings.test.js +49 -0
  281. package/dist/test/unit/pages/settings/permission-warnings.test.js.map +1 -0
  282. package/dist/test/unit/pages/settings/role-editor.test.js +16 -0
  283. package/dist/test/unit/pages/settings/role-editor.test.js.map +1 -1
  284. package/dist/test/unit/pages/settings/role-preview.test.js +7 -0
  285. package/dist/test/unit/pages/settings/role-preview.test.js.map +1 -1
  286. package/dist/test/unit/pages/settings/security-attention.test.d.ts +1 -0
  287. package/dist/test/unit/pages/settings/security-attention.test.js +74 -0
  288. package/dist/test/unit/pages/settings/security-attention.test.js.map +1 -0
  289. package/dist/test/unit/sanitize-user.test.d.ts +1 -0
  290. package/dist/test/unit/sanitize-user.test.js +21 -0
  291. package/dist/test/unit/sanitize-user.test.js.map +1 -0
  292. package/dist/test/unit/signup-email-validation.test.d.ts +1 -0
  293. package/dist/test/unit/signup-email-validation.test.js +79 -0
  294. package/dist/test/unit/signup-email-validation.test.js.map +1 -0
  295. package/dist/test/unit/validation/member-invite.schema.test.js +14 -52
  296. package/dist/test/unit/validation/member-invite.schema.test.js.map +1 -1
  297. package/dist/utils/account-notifications.d.ts +5 -0
  298. package/dist/utils/account-notifications.js +8 -0
  299. package/dist/utils/account-notifications.js.map +1 -1
  300. package/dist/utils/notification-emitter.d.ts +1 -1
  301. package/dist/utils/notification-emitter.js +1 -1
  302. package/dist/utils/notification-emitter.js.map +1 -1
  303. package/dist/utils/notification-tones.d.ts +2 -1
  304. package/dist/utils/notification-tones.js +5 -1
  305. package/dist/utils/notification-tones.js.map +1 -1
  306. package/dist/utils/one-time-code.d.ts +3 -0
  307. package/dist/utils/one-time-code.js +21 -0
  308. package/dist/utils/one-time-code.js.map +1 -0
  309. package/dist/utils/sign-in-monitor.d.ts +1 -0
  310. package/dist/utils/sign-in-monitor.js +3 -2
  311. package/dist/utils/sign-in-monitor.js.map +1 -1
  312. package/dist/utils/two-factor-codes.d.ts +10 -0
  313. package/dist/utils/two-factor-codes.js +76 -0
  314. package/dist/utils/two-factor-codes.js.map +1 -0
  315. package/dist/validation/auth.schema.d.ts +17 -0
  316. package/dist/validation/auth.schema.js +17 -6
  317. package/dist/validation/auth.schema.js.map +1 -1
  318. package/dist/validation/issue-message.d.ts +1 -0
  319. package/dist/validation/issue-message.js +9 -0
  320. package/dist/validation/issue-message.js.map +1 -0
  321. package/dist/validation/member-invite.schema.d.ts +1 -25
  322. package/dist/validation/member-invite.schema.js +2 -23
  323. package/dist/validation/member-invite.schema.js.map +1 -1
  324. package/frontend-vue/dms.frontend.ts +2 -0
  325. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/forgot-validation.vue +3 -0
  326. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/signup.vue +105 -5
  327. package/frontend-vue/layers/dms-auth/i18n/locales/auth-en-GB.json +20 -4
  328. package/frontend-vue/layers/dms-auth/i18n/locales/auth-fr-FR.json +20 -4
  329. package/frontend-vue/layers/dms-core/app/composables/useFieldErrors.ts +3 -2
  330. package/frontend-vue/layers/dms-core/app/types/menu.ts +15 -0
  331. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +4 -1
  332. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +4 -1
  333. package/frontend-vue/layers/dms-layout/app/assets/css/main.css +20 -0
  334. package/frontend-vue/layers/dms-layout/app/build/components/layout/CommandPaletteAssistantAnswer.vue +75 -0
  335. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardFrame.vue +22 -0
  336. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +24 -10
  337. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidePanel.vue +139 -0
  338. package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +4 -2
  339. package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +90 -0
  340. package/frontend-vue/layers/dms-layout/app/build/components/layout/SidePanelHost.vue +17 -0
  341. package/frontend-vue/layers/dms-layout/app/build/components/notification/NotificationPopover.vue +25 -43
  342. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/members/MemberRolePicker.vue +24 -2
  343. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationMatrixCategory.vue +70 -25
  344. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationPreferencesForm.vue +25 -10
  345. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/notificationDisplay.ts +41 -5
  346. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfilePreferencesSummary.vue +2 -1
  347. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfileSecuritySummary.vue +20 -4
  348. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RoleEditor.vue +21 -0
  349. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionArea.vue +16 -2
  350. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionBranch.vue +1 -0
  351. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionLeaf.vue +13 -1
  352. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionTree.vue +9 -0
  353. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/RolePermissionWarning.vue +46 -0
  354. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +108 -29
  355. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-types.ts +2 -0
  356. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityDialogFrame.vue +6 -6
  357. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityEmail.vue +7 -1
  358. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecuritySessions.vue +5 -4
  359. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityStatus.vue +4 -3
  360. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityTwoFactor.vue +54 -18
  361. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/shell/SettingsNav.vue +13 -8
  362. package/frontend-vue/layers/dms-layout/app/build/composables/command-palette/assistantGroups.ts +143 -0
  363. package/frontend-vue/layers/dms-layout/app/build/composables/command-palette/useCommandPaletteAssistantMode.ts +249 -0
  364. package/frontend-vue/layers/dms-layout/app/build/composables/general/useGithubStarPrompt.ts +178 -0
  365. package/frontend-vue/layers/dms-layout/app/build/composables/layout/sidePanelState.ts +191 -0
  366. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useHeaderActionControls.ts +51 -0
  367. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +113 -0
  368. package/frontend-vue/layers/dms-layout/app/build/composables/navigation/useCategoryNavCards.ts +17 -10
  369. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +45 -0
  370. package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfilePreferences.ts +8 -1
  371. package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityOverview.ts +50 -3
  372. package/frontend-vue/layers/dms-layout/app/build/composables/settings/useSettingsNavigation.ts +3 -0
  373. package/frontend-vue/layers/dms-layout/app/build/utils/categoryPages.ts +1 -2
  374. package/frontend-vue/layers/dms-layout/app/build/utils/github-repository.ts +5 -0
  375. package/frontend-vue/layers/dms-layout/app/components/layout/DashboardSearch.vue +109 -7
  376. package/frontend-vue/layers/dms-layout/app/components/navigation/NavigationMenu.vue +19 -9
  377. package/frontend-vue/layers/dms-layout/app/composables/notification/useNotifications.ts +22 -40
  378. package/frontend-vue/layers/dms-layout/app/composables/useAppSidePanels.ts +96 -0
  379. package/frontend-vue/layers/dms-layout/app/composables/useCommandPaletteAssistant.ts +129 -0
  380. package/frontend-vue/layers/dms-layout/app/composables/useHeaderActions.ts +33 -11
  381. package/frontend-vue/layers/dms-layout/app/composables/useSidePanel.ts +41 -0
  382. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +15 -0
  383. package/frontend-vue/layers/dms-layout/app/plugins/notification-stream.client.ts +1 -7
  384. package/frontend-vue/layers/dms-layout/app/plugins/side-panel-host.ts +13 -0
  385. package/frontend-vue/layers/dms-layout/app/types/page.ts +4 -0
  386. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +43 -13
  387. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +43 -13
  388. package/frontend-vue/layers/dms-ui/app/build/components/form/FormLoadError.vue +45 -0
  389. package/frontend-vue/layers/dms-ui/app/build/components/modal/ModalEscHint.vue +28 -0
  390. package/frontend-vue/layers/dms-ui/app/build/composables/form/floatingSaveBars.ts +38 -0
  391. package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +66 -0
  392. package/frontend-vue/layers/dms-ui/app/build/composables/navigation/useNavBadges.ts +50 -9
  393. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +7 -1
  394. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/chrome.ts +2 -2
  395. package/frontend-vue/layers/dms-ui/app/build/types/tree.ts +14 -10
  396. package/frontend-vue/layers/dms-ui/app/build/utils/navBadge.ts +12 -0
  397. package/frontend-vue/layers/dms-ui/app/build/utils/tone.ts +22 -0
  398. package/frontend-vue/layers/dms-ui/app/components/activity-feed/ActivityFeed.vue +117 -82
  399. package/frontend-vue/layers/dms-ui/app/components/blocks/NavCardGridBlock.vue +12 -1
  400. package/frontend-vue/layers/dms-ui/app/components/card/NavCard.vue +16 -1
  401. package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +7 -8
  402. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +28 -6
  403. package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +5 -1
  404. package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +4 -0
  405. package/frontend-vue/layers/dms-ui/app/components/status-pill/StatusPill.vue +7 -2
  406. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +7 -2
  407. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +48 -34
  408. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +13 -3
  409. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +16 -14
  410. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +16 -14
  411. package/frontend-vue/package.json +2 -2
  412. package/frontend-vue/pnpm-lock.yaml +5 -5
  413. package/frontend-vue/tests/activity-feed-fill-height.test.ts +107 -0
  414. package/frontend-vue/tests/category-nav-cards.test.ts +21 -13
  415. package/frontend-vue/tests/command-palette-assistant.test.ts +344 -0
  416. package/frontend-vue/tests/fixtures/dashboard-search-stub.ts +91 -0
  417. package/frontend-vue/tests/forgot-validation-resend.test.ts +234 -0
  418. package/frontend-vue/tests/form-display-options.test.ts +64 -0
  419. package/frontend-vue/tests/form-load-error.test.ts +160 -0
  420. package/frontend-vue/tests/form-saved-before-success.test.ts +110 -0
  421. package/frontend-vue/tests/github-star-prompt.test.ts +328 -0
  422. package/frontend-vue/tests/header-actions.test.ts +191 -0
  423. package/frontend-vue/tests/invites-edit-modal.test.ts +157 -0
  424. package/frontend-vue/tests/member-role-picker.test.ts +26 -0
  425. package/frontend-vue/tests/menu-hidden-page.test.ts +93 -0
  426. package/frontend-vue/tests/nav-badge-tones.test.ts +274 -0
  427. package/frontend-vue/tests/nav-card-badge.test.ts +69 -0
  428. package/frontend-vue/tests/nav-card-grid-empty-category.test.ts +163 -0
  429. package/frontend-vue/tests/notification-popover-errors.test.ts +71 -37
  430. package/frontend-vue/tests/notification-preferences-groups.test.ts +268 -0
  431. package/frontend-vue/tests/notification-tone-contract.test.ts +70 -0
  432. package/frontend-vue/tests/page-fill-height.test.ts +9 -1
  433. package/frontend-vue/tests/page-no-visible-blocks.test.ts +202 -0
  434. package/frontend-vue/tests/profile-preferences-summary.test.ts +26 -2
  435. package/frontend-vue/tests/profile-security-summary.test.ts +121 -0
  436. package/frontend-vue/tests/role-permission-tree.test.ts +102 -8
  437. package/frontend-vue/tests/role-permissions.test.ts +178 -56
  438. package/frontend-vue/tests/side-panels.test.ts +470 -0
  439. package/frontend-vue/tests/signup-invitation-link.test.ts +71 -2
  440. package/frontend-vue/tests/table-actions-column-width.test.ts +51 -1
  441. package/frontend-vue/tests/table-view-chrome.test.ts +2 -2
  442. package/frontend-vue/tests/vue-inventory.test.ts +1 -1
  443. package/package.json +1 -1
  444. package/skills/dms/REFERENCE.md +1 -1
@@ -0,0 +1,121 @@
1
+ // @vitest-environment jsdom
2
+ import { afterEach, beforeEach, expect, it, vi } from "vitest";
3
+ import {
4
+ computed,
5
+ createApp,
6
+ defineComponent,
7
+ h,
8
+ nextTick,
9
+ onMounted,
10
+ ref,
11
+ type App,
12
+ } from "vue";
13
+ import ProfileSecuritySummary from "../layers/dms-layout/app/build/components/pages/settings/profile/ProfileSecuritySummary.vue";
14
+
15
+ // The profile's "Sign-in and security" row takes the tone of the Security
16
+ // page's navigation badge, the one the server weighs what needs attention
17
+ // with: red when the backup codes run low, amber for two-factor off or codes
18
+ // not saved, quiet when nothing needs attention.
19
+
20
+ const overview = ref<Record<string, unknown> | null>(null);
21
+ const attention = ref<string[]>([]);
22
+ const attentionTone = ref<string | undefined>(undefined);
23
+
24
+ vi.mock(
25
+ "../layers/dms-layout/app/build/composables/settings/security/useSecurityOverview",
26
+ () => ({
27
+ SECURITY_PAGE_PATH: "/settings/user/security",
28
+ useSecurityOverview: () => ({
29
+ overview,
30
+ attention,
31
+ attentionTone,
32
+ isUnavailable: ref(false),
33
+ refresh: vi.fn(),
34
+ }),
35
+ }),
36
+ );
37
+ vi.mock("#dms-ui/app/components/icon-well/IconWell.vue", async () => {
38
+ const { defineComponent, h } = await import("vue");
39
+ return {
40
+ default: defineComponent({
41
+ props: { icon: String, tone: String },
42
+ setup: (props) => () =>
43
+ h("span", { "data-icon": props.icon, "data-tone": props.tone }),
44
+ }),
45
+ };
46
+ });
47
+
48
+ let app: App | undefined;
49
+ let host: HTMLDivElement;
50
+
51
+ const OVERVIEW = {
52
+ activeSessions: 9,
53
+ twoFactor: { methods: ["totp"] },
54
+ };
55
+
56
+ const wellTone = () =>
57
+ host.querySelector("[data-icon]")?.getAttribute("data-tone");
58
+
59
+ function show(items: string[], tone: string | undefined): void {
60
+ overview.value = OVERVIEW;
61
+ attention.value = items;
62
+ attentionTone.value = tone;
63
+ }
64
+
65
+ beforeEach(() => {
66
+ overview.value = null;
67
+ attention.value = [];
68
+ attentionTone.value = undefined;
69
+ Object.entries({ computed, onMounted }).forEach(([key, value]) =>
70
+ vi.stubGlobal(key, value),
71
+ );
72
+ vi.stubGlobal("useI18n", () => ({
73
+ t: (key: string) => key,
74
+ locale: ref("en-GB"),
75
+ }));
76
+ host = document.createElement("div");
77
+ document.body.append(host);
78
+ app = createApp(ProfileSecuritySummary);
79
+ app.component(
80
+ "USkeleton",
81
+ defineComponent({ setup: () => () => h("span", { "data-skeleton": "" }) }),
82
+ );
83
+ app.component("UButton", defineComponent({ setup: () => () => h("a") }));
84
+ app.mount(host);
85
+ });
86
+
87
+ afterEach(() => {
88
+ app?.unmount();
89
+ app = undefined;
90
+ host.remove();
91
+ vi.unstubAllGlobals();
92
+ });
93
+
94
+ it("turns red when the server weighs the attention an error", async () => {
95
+ show(["backup_codes_low", "backup_codes_unsaved"], "error");
96
+ await nextTick();
97
+ expect(wellTone()).toBe("error");
98
+ });
99
+
100
+ it("stays amber for two-factor off", async () => {
101
+ show(["two_factor_off"], "warning");
102
+ await nextTick();
103
+ expect(wellTone()).toBe("warning");
104
+ });
105
+
106
+ it("is quiet when nothing needs attention", async () => {
107
+ show([], undefined);
108
+ await nextTick();
109
+ expect(wellTone()).toBe("muted");
110
+ expect(host.querySelector("[data-icon]")?.getAttribute("data-icon")).toBe(
111
+ "i-ph-shield-check",
112
+ );
113
+ });
114
+
115
+ it("names every point that needs attention, not only the first", async () => {
116
+ show(["backup_codes_low", "backup_codes_unsaved"], "error");
117
+ await nextTick();
118
+ expect(host.textContent).toContain(
119
+ "page.settings.profile.security_attention — page.settings.security.attention.backup_codes_low and page.settings.security.attention.backup_codes_unsaved.",
120
+ );
121
+ });
@@ -132,10 +132,10 @@ beforeAll(async () => {
132
132
 
133
133
  afterEach(() => app?.unmount());
134
134
 
135
- function mountTree() {
135
+ function mountTree(tree = TREE, selection = new Set<string>()) {
136
136
  const host = document.createElement("div");
137
- const index = helpers.buildPermissionIndex(TREE);
138
- const areas = helpers.buildPermissionAreas(TREE, (label) => label);
137
+ const index = helpers.buildPermissionIndex(tree);
138
+ const areas = helpers.buildPermissionAreas(tree, (label) => label);
139
139
  const expanded = ref(new Set<string>());
140
140
  const toggles: Array<[string, boolean]> = [];
141
141
  app = createApp({
@@ -143,7 +143,6 @@ function mountTree() {
143
143
  h(RolePermissionTree, {
144
144
  areas,
145
145
  index,
146
- selection: new Set<string>(),
147
146
  saved: new Set<string>(),
148
147
  autoAdded: new Map(),
149
148
  expanded: expanded.value,
@@ -151,6 +150,7 @@ function mountTree() {
151
150
  query: "",
152
151
  canGrant: () => true,
153
152
  readonly: false,
153
+ selection,
154
154
  onToggle: (id: string, checked: boolean) => toggles.push([id, checked]),
155
155
  "onToggle-area": (id: string) => {
156
156
  const next = new Set(expanded.value);
@@ -292,10 +292,19 @@ const slotStub = defineComponent({
292
292
  h("div", slots.default?.()),
293
293
  });
294
294
 
295
- function mountEditor() {
295
+ // DmsBanner: its title and description as text.
296
+ const BannerStub = defineComponent({
297
+ props: { title: String, description: String },
298
+ setup:
299
+ (props, { attrs }) =>
300
+ () =>
301
+ h("div", attrs, `${props.title} ${props.description}`),
302
+ });
303
+
304
+ function mountEditor(tree = TREE, selection = new Set<string>()) {
296
305
  const host = document.createElement("div");
297
- const index = helpers.buildPermissionIndex(TREE);
298
- const areas = helpers.buildPermissionAreas(TREE, (label) => label);
306
+ const index = helpers.buildPermissionIndex(tree);
307
+ const areas = helpers.buildPermissionAreas(tree, (label) => label);
299
308
  app = createApp({
300
309
  setup: () => () =>
301
310
  h(RoleEditor, {
@@ -304,7 +313,7 @@ function mountEditor() {
304
313
  members: [],
305
314
  areas,
306
315
  index,
307
- selection: new Set<string>(),
316
+ selection,
308
317
  saved: new Set<string>(),
309
318
  autoAdded: new Map(),
310
319
  totalPermissions: index.allIds.length,
@@ -328,6 +337,7 @@ function mountEditor() {
328
337
  app.component("DmsMeter", stub("span"));
329
338
  app.component("DmsIconWell", stub("span"));
330
339
  app.component("DmsSaveBar", stub("div"));
340
+ app.component("DmsBanner", BannerStub);
331
341
  app.mount(host);
332
342
  return { host };
333
343
  }
@@ -468,3 +478,87 @@ describe("role editor level buttons", () => {
468
478
  expect(levelButton(host, "collapse").disabled).toBe(false);
469
479
  });
470
480
  });
481
+
482
+ const WARNING = "$page.settings.roles.warning.members";
483
+ const WARNED_TREE: RolePermissionNode[] = [
484
+ {
485
+ id: "settings",
486
+ label: "Settings",
487
+ children: [
488
+ {
489
+ id: "settings.members",
490
+ label: "Members",
491
+ children: [
492
+ {
493
+ id: "settings.members.table",
494
+ label: "Members table",
495
+ children: [
496
+ { id: "settings.members.table.list", label: "List" },
497
+ {
498
+ id: "settings.members.table.edit",
499
+ label: "Edit",
500
+ warning: WARNING,
501
+ },
502
+ ],
503
+ },
504
+ ],
505
+ },
506
+ ],
507
+ },
508
+ ];
509
+ const WITHOUT_EDIT = [
510
+ "settings",
511
+ "settings.members",
512
+ "settings.members.table",
513
+ "settings.members.table.list",
514
+ ];
515
+ const ALL_WARNED = new Set([...WITHOUT_EDIT, "settings.members.table.edit"]);
516
+
517
+ const textOf = (host: HTMLElement, selector: string) =>
518
+ [...host.querySelectorAll(selector)].map((element) =>
519
+ element.textContent?.trim(),
520
+ );
521
+
522
+ describe("owner-level warnings in the roles editor", () => {
523
+ it("shows a permission's warning on its row, ticked or not", async () => {
524
+ const { host } = mountTree(WARNED_TREE);
525
+ caretOf(host, "settings.members").click();
526
+ await nextTick();
527
+ caretOf(host, "settings.members.table").click();
528
+ await nextTick();
529
+
530
+ expect(
531
+ textOf(
532
+ rowOf(host, "settings.members.table.edit"),
533
+ "[data-permission-warning]",
534
+ ),
535
+ ).toEqual([WARNING]);
536
+ expect(
537
+ host.querySelectorAll("[data-permission-warning-below]"),
538
+ ).toHaveLength(0);
539
+ });
540
+
541
+ it("flags the closed row hiding a warned permission a parent ticked", async () => {
542
+ const { host } = mountTree(WARNED_TREE, ALL_WARNED);
543
+ const below = (id: string) =>
544
+ rowOf(host, id).querySelector("[data-permission-warning-below]");
545
+ expect(below("settings.members")?.getAttribute("title")).toBe(WARNING);
546
+
547
+ caretOf(host, "settings.members").click();
548
+ await nextTick();
549
+ expect(below("settings.members")).toBeNull();
550
+ expect(below("settings.members.table")).not.toBeNull();
551
+ });
552
+
553
+ it("sums up in the editor the warned permissions the role holds", () => {
554
+ const { host } = mountEditor(WARNED_TREE, ALL_WARNED);
555
+ expect(textOf(host, "[data-role-owner-level]")).toEqual([
556
+ "page.settings.roles.editor.owner_level_title page.settings.roles.editor.owner_level_description Members › Edit",
557
+ ]);
558
+ });
559
+
560
+ it("says nothing while the role holds none of them", () => {
561
+ const { host } = mountEditor(WARNED_TREE, new Set(WITHOUT_EDIT));
562
+ expect(host.querySelector("[data-role-owner-level]")).toBeNull();
563
+ });
564
+ });
@@ -10,13 +10,14 @@ import {
10
10
  expandOneLevel,
11
11
  grantPermission,
12
12
  highlightSegments,
13
- idPrefixes,
14
13
  nextCollapseDepth,
15
14
  nextExpandDepth,
16
15
  openDepth,
17
16
  type PermissionIndex,
18
17
  revokePermission,
19
18
  searchPermissions,
19
+ selectedWarningLabels,
20
+ selectedWarningsBelow,
20
21
  selectionState,
21
22
  } from "../layers/dms-layout/app/build/components/pages/settings/roles/role-permissions";
22
23
  import type { RolePermissionNode } from "../layers/dms-layout/app/build/components/pages/settings/roles/role-types";
@@ -56,15 +57,20 @@ const TREE: RolePermissionNode[] = [
56
57
  label: "User settings",
57
58
  children: [
58
59
  {
59
- id: "settings.workspace.roles",
60
+ id: "settings.user.roles",
60
61
  label: "Roles",
61
62
  children: [
62
63
  {
63
- id: "settings.workspace.roles.table",
64
+ id: "settings.user.roles.table",
64
65
  label: "Roles list",
65
66
  children: [
66
- { id: "settings.workspace.roles.table.list", label: "List" },
67
- { id: "settings.workspace.roles.table.edit", label: "Edit" },
67
+ { id: "settings.user.roles.table.list", label: "List" },
68
+ {
69
+ id: "settings.user.roles.table.edit",
70
+ label: "Edit",
71
+ dependencies: ["settings.user.roles.table.view"],
72
+ },
73
+ { id: "settings.user.roles.table.view", label: "View" },
68
74
  ],
69
75
  },
70
76
  ],
@@ -194,6 +200,15 @@ function seeded(seed: number): () => number {
194
200
 
195
201
  const sorted = (ids: Iterable<string>) => [...new Set(ids)].sort();
196
202
 
203
+ function withoutDependencies(
204
+ nodes: RolePermissionNode[],
205
+ ): RolePermissionNode[] {
206
+ return nodes.map(({ dependencies: _dependencies, children, ...node }) => ({
207
+ ...node,
208
+ ...(children ? { children: withoutDependencies(children) } : {}),
209
+ }));
210
+ }
211
+
197
212
  describe("role permissions", () => {
198
213
  const index = buildPermissionIndex(TREE);
199
214
 
@@ -217,25 +232,16 @@ describe("role permissions", () => {
217
232
  // without children (`media.upload.form`, `examples`) never open.
218
233
  expect(levels).toEqual([
219
234
  ["pages.sales", "settings.user"],
220
- [
221
- "pages.sales.orders",
222
- "settings.workspace.roles",
223
- "settings.user.profile",
224
- ],
225
- ["settings.workspace.roles.table"],
235
+ ["pages.sales.orders", "settings.user.roles", "settings.user.profile"],
236
+ ["settings.user.roles.table"],
226
237
  ]);
227
- expect(parents.get("settings.workspace.roles.table")).toBe(
228
- "settings.workspace.roles",
238
+ expect(parents.get("settings.user.roles.table")).toBe(
239
+ "settings.user.roles",
229
240
  );
230
241
  expect(parents.has("settings.user")).toBe(false);
231
242
  });
232
243
 
233
- it("lists the ids a permission sits under", () => {
234
- expect(idPrefixes("a.b.c")).toEqual(["a.b.c", "a.b", "a"]);
235
- expect(idPrefixes("pages")).toEqual(["pages"]);
236
- });
237
-
238
- it("grants a permission with the ids it sits under, not its dependencies", () => {
244
+ it("grants a permission with the nodes above it and what it depends on", () => {
239
245
  const grant = grantPermission(
240
246
  index,
241
247
  new Set(),
@@ -246,23 +252,47 @@ describe("role permissions", () => {
246
252
  "pages",
247
253
  "pages.sales",
248
254
  "pages.sales.orders",
255
+ "pages.sales.orders.read",
249
256
  "pages.sales.orders.refund",
250
257
  ]);
251
258
  expect(sorted(grant.autoAdded)).toEqual([
252
259
  "pages",
253
260
  "pages.sales",
254
261
  "pages.sales.orders",
262
+ "pages.sales.orders.read",
255
263
  ]);
256
264
  });
257
265
 
258
- it("grants the unregistered id a lifted permission sits under", () => {
266
+ it("grants a table's view with its edit, where the form loads the row", () => {
267
+ const grant = grantPermission(
268
+ index,
269
+ new Set(),
270
+ "settings.user.roles.table.edit",
271
+ anyone,
272
+ );
273
+ expect(grant.selection.has("settings.user.roles.table.view")).toBe(true);
274
+ expect(grant.autoAdded).toContain("settings.user.roles.table.view");
275
+ expect(grant.selection.has("settings.user.roles.table.list")).toBe(false);
276
+ });
277
+
278
+ it("leaves a dependency the user may not grant", () => {
279
+ const grant = grantPermission(
280
+ index,
281
+ new Set(),
282
+ "pages.sales.orders.refund",
283
+ (id) => id !== "pages.sales.orders.read",
284
+ );
285
+ expect(grant.selection.has("pages.sales.orders.read")).toBe(false);
286
+ expect(grant.selection.has("pages.sales.orders.refund")).toBe(true);
287
+ });
288
+
289
+ it("leaves the unregistered id a lifted permission sits under to the server", () => {
259
290
  const grant = grantPermission(index, new Set(), "media.upload", anyone);
291
+ // `media` is no node of the tree: the server adds it when the role is saved.
260
292
  expect(sorted(grant.selection)).toEqual([
261
- "media",
262
293
  "media.upload",
263
294
  "media.upload.form",
264
295
  ]);
265
- // Not a node: no "added automatically" hint to label.
266
296
  expect(grant.autoAdded).toEqual([]);
267
297
  });
268
298
 
@@ -277,18 +307,35 @@ describe("role permissions", () => {
277
307
  expect(grant.selection.has("pages.sales.orders.read")).toBe(true);
278
308
  });
279
309
 
280
- it("revokes ancestors left without descendants, never dependents", () => {
310
+ it("revokes what depends on a revoked permission, then the emptied ancestors", () => {
281
311
  const all = new Set(index.allIds);
282
312
  const afterRead = revokePermission(index, all, "pages.sales.orders.read");
283
- expect(afterRead.has("pages.sales.orders.refund")).toBe(true);
284
- expect(afterRead.has("pages.sales.orders")).toBe(true);
285
- const afterBoth = revokePermission(
313
+ expect(afterRead.has("pages.sales.orders.refund")).toBe(false);
314
+ expect(afterRead.has("pages.sales.orders")).toBe(false);
315
+ expect(afterRead.has("pages.sales")).toBe(true);
316
+ const afterRefund = revokePermission(
286
317
  index,
287
- afterRead,
318
+ all,
288
319
  "pages.sales.orders.refund",
289
320
  );
290
- expect(afterBoth.has("pages.sales.orders")).toBe(false);
291
- expect(afterBoth.has("pages.sales")).toBe(true);
321
+ expect(afterRefund.has("pages.sales.orders.read")).toBe(true);
322
+ expect(afterRefund.has("pages.sales.orders")).toBe(true);
323
+ });
324
+
325
+ it("revokes a table's edit with its view", () => {
326
+ const granted = grantPermission(
327
+ index,
328
+ new Set(),
329
+ "settings.user.roles.table.edit",
330
+ anyone,
331
+ ).selection;
332
+ const revoked = revokePermission(
333
+ index,
334
+ new Set([...granted, "settings.user.roles.table.list"]),
335
+ "settings.user.roles.table.view",
336
+ );
337
+ expect(revoked.has("settings.user.roles.table.edit")).toBe(false);
338
+ expect(revoked.has("settings.user.roles.table.list")).toBe(true);
292
339
  });
293
340
 
294
341
  it("judges every ancestor before dropping any, like the original form", () => {
@@ -297,18 +344,18 @@ describe("role permissions", () => {
297
344
  new Set([
298
345
  "settings",
299
346
  "settings.user",
300
- "settings.workspace.roles",
301
- "settings.workspace.roles.table",
302
- "settings.workspace.roles.table.list",
347
+ "settings.user.roles",
348
+ "settings.user.roles.table",
349
+ "settings.user.roles.table.list",
303
350
  ]),
304
- "settings.workspace.roles.table.list",
351
+ "settings.user.roles.table.list",
305
352
  );
306
- // Only `settings.workspace.roles.table` is judged emptied: the ancestors above
353
+ // Only `settings.user.roles.table` is judged emptied: the ancestors above
307
354
  // it still saw it selected, so they stay.
308
355
  expect(sorted(next)).toEqual([
309
356
  "settings",
310
357
  "settings.user",
311
- "settings.workspace.roles",
358
+ "settings.user.roles",
312
359
  ]);
313
360
  });
314
361
 
@@ -349,9 +396,12 @@ describe("role permissions", () => {
349
396
  ).toBe(false);
350
397
  });
351
398
 
352
- it("stores exactly what the original form stored for the same clicks", () => {
399
+ it("stores what the original form stored for the same clicks", () => {
400
+ // The original form knew no dependencies: the editor matches it on a
401
+ // tree that declares none.
402
+ const plain = buildPermissionIndex(withoutDependencies(TREE));
353
403
  const random = seeded(20261001);
354
- const ids = index.allIds;
404
+ const ids = plain.allIds;
355
405
  for (let run = 0; run < 200; run++) {
356
406
  // Start from arbitrary saved roles, legacy and unknown ids included.
357
407
  const start = ids.filter(() => random() < 0.3);
@@ -360,12 +410,17 @@ describe("role permissions", () => {
360
410
  let after = new Set(start);
361
411
  for (let click = 0; click < 12; click++) {
362
412
  const id = ids[Math.floor(random() * ids.length)]!;
363
- const node = index.nodes.get(id)!;
413
+ const node = plain.nodes.get(id)!;
364
414
  const originalState = original.checkbox(before, node);
365
- expect(checkboxState(index, id, after)).toBe(originalState);
415
+ expect(checkboxState(plain, id, after)).toBe(originalState);
366
416
  before = original.check(before, node, clickedValue(originalState));
367
- after = editorClick(index, after, id);
368
- expect(sorted(after)).toEqual(sorted(before));
417
+ after = editorClick(plain, after, id);
418
+ // Bar the unregistered ids the original form added above a lifted
419
+ // node (`media`), which the server now adds when the role is saved.
420
+ const known = before.filter(
421
+ (kept) => plain.nodes.has(kept) || start.includes(kept),
422
+ );
423
+ expect(sorted(after)).toEqual(sorted(known));
369
424
  }
370
425
  }
371
426
  });
@@ -409,11 +464,7 @@ describe("role permissions", () => {
409
464
  expect.arrayContaining([
410
465
  ["settings", "", "settings"],
411
466
  ["settings.user", "Settings", "settings.user"],
412
- [
413
- "settings.workspace.roles",
414
- "Settings › User settings",
415
- "settings.user",
416
- ],
467
+ ["settings.user.roles", "Settings › User settings", "settings.user"],
417
468
  ]),
418
469
  );
419
470
  });
@@ -455,15 +506,15 @@ describe("permission tree levels", () => {
455
506
  "pages.sales.orders",
456
507
  "settings.user",
457
508
  "settings.user.profile",
458
- "settings.workspace.roles",
509
+ "settings.user.roles",
459
510
  ],
460
511
  [
461
512
  "pages.sales",
462
513
  "pages.sales.orders",
463
514
  "settings.user",
464
515
  "settings.user.profile",
465
- "settings.workspace.roles",
466
- "settings.workspace.roles.table",
516
+ "settings.user.roles",
517
+ "settings.user.roles.table",
467
518
  ],
468
519
  ]);
469
520
  expect(openDepth(levels, expanded)).toBe(3);
@@ -487,33 +538,33 @@ describe("permission tree levels", () => {
487
538
 
488
539
  it("fills the shallowest incomplete depth of a tree opened by hand", () => {
489
540
  // One area and one of its rows opened by hand, the other area closed.
490
- let expanded = open("settings.user", "settings.workspace.roles");
541
+ let expanded = open("settings.user", "settings.user.roles");
491
542
  expect(openDepth(levels, expanded)).toBe(0);
492
543
  expect(nextExpandDepth(levels, expanded)).toBe(1);
493
544
  expanded = expandOneLevel(levels, expanded);
494
545
  expect(sorted(expanded)).toEqual([
495
546
  "pages.sales",
496
547
  "settings.user",
497
- "settings.workspace.roles",
548
+ "settings.user.roles",
498
549
  ]);
499
550
  expect(nextExpandDepth(levels, expanded)).toBe(2);
500
551
  // Depth 3 is opened by hand, but depth 2 is still incomplete: it comes first.
501
552
  expanded = open(
502
553
  "pages.sales",
503
554
  "settings.user",
504
- "settings.workspace.roles",
505
- "settings.workspace.roles.table",
555
+ "settings.user.roles",
556
+ "settings.user.roles.table",
506
557
  );
507
558
  expect(nextExpandDepth(levels, expanded)).toBe(2);
508
559
  expect(openDepth(levels, expanded)).toBe(1);
509
560
  });
510
561
 
511
562
  it("collapses the deepest depth shown open, not a hidden open row", () => {
512
- // `settings.workspace.roles.table` is open but hidden under a closed area.
563
+ // `settings.user.roles.table` is open but hidden under a closed area.
513
564
  const expanded = open(
514
565
  "pages.sales",
515
- "settings.workspace.roles",
516
- "settings.workspace.roles.table",
566
+ "settings.user.roles",
567
+ "settings.user.roles.table",
517
568
  );
518
569
  expect(nextCollapseDepth(levels, expanded)).toBe(1);
519
570
  // The hidden open rows below that depth close with it.
@@ -537,3 +588,74 @@ describe("permission tree levels", () => {
537
588
  expect(openDepth(flat, open())).toBe(0);
538
589
  });
539
590
  });
591
+
592
+ describe("owner-level warnings", () => {
593
+ const WARNING = "$warning.members";
594
+ const WARNED: RolePermissionNode[] = [
595
+ {
596
+ id: "settings",
597
+ label: "Settings",
598
+ children: [
599
+ {
600
+ id: "settings.members",
601
+ label: "Members",
602
+ children: [
603
+ {
604
+ id: "settings.members.table",
605
+ label: "Members table",
606
+ children: [
607
+ { id: "settings.members.table.list", label: "List" },
608
+ {
609
+ id: "settings.members.table.edit",
610
+ label: "Edit",
611
+ warning: WARNING,
612
+ },
613
+ ],
614
+ },
615
+ ],
616
+ },
617
+ ],
618
+ },
619
+ ];
620
+ const index = buildPermissionIndex(WARNED);
621
+ const areas = buildPermissionAreas(WARNED, (label) => label);
622
+
623
+ it("tells a closed row the warnings of what it holds selected", () => {
624
+ const granted = grantPermission(
625
+ index,
626
+ new Set(),
627
+ "settings.members",
628
+ () => true,
629
+ ).selection;
630
+
631
+ expect(selectedWarningsBelow(index, "settings.members", granted)).toEqual([
632
+ WARNING,
633
+ ]);
634
+ expect(
635
+ selectedWarningsBelow(index, "settings.members.table.edit", granted),
636
+ ).toEqual([]);
637
+ expect(
638
+ selectedWarningsBelow(
639
+ index,
640
+ "settings.members",
641
+ new Set(["settings.members", "settings.members.table.list"]),
642
+ ),
643
+ ).toEqual([]);
644
+ });
645
+
646
+ it("names the selected warned permissions under their area", () => {
647
+ const granted = grantPermission(
648
+ index,
649
+ new Set(),
650
+ "settings",
651
+ () => true,
652
+ ).selection;
653
+
654
+ expect(
655
+ selectedWarningLabels(index, areas, granted, (label) => label),
656
+ ).toEqual(["Members › Edit"]);
657
+ expect(
658
+ selectedWarningLabels(index, areas, new Set(), (label) => label),
659
+ ).toEqual([]);
660
+ });
661
+ });