@byline/admin 2.4.0 → 2.4.2

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 (177) hide show
  1. package/dist/abilities.js +5 -24
  2. package/dist/index.js +8 -30
  3. package/dist/lib/assert-admin-actor.js +13 -74
  4. package/dist/lib/create-command.js +6 -16
  5. package/dist/modules/admin-account/commands.js +35 -24
  6. package/dist/modules/admin-account/components/change-password.d.ts +8 -0
  7. package/dist/modules/admin-account/components/change-password.js +192 -0
  8. package/dist/modules/admin-account/components/change-password.module.js +8 -0
  9. package/dist/modules/admin-account/components/change-password_module.css +27 -0
  10. package/dist/modules/admin-account/components/container.d.ts +29 -0
  11. package/dist/modules/admin-account/components/container.js +298 -0
  12. package/dist/modules/admin-account/components/container.module.js +28 -0
  13. package/dist/modules/admin-account/components/container_module.css +106 -0
  14. package/dist/modules/admin-account/components/update.d.ts +8 -0
  15. package/dist/modules/admin-account/components/update.js +207 -0
  16. package/dist/modules/admin-account/components/update.module.js +8 -0
  17. package/dist/modules/admin-account/components/update_module.css +27 -0
  18. package/dist/modules/admin-account/errors.js +14 -45
  19. package/dist/modules/admin-account/index.js +4 -34
  20. package/dist/modules/admin-account/schemas.js +25 -59
  21. package/dist/modules/admin-account/service.js +56 -61
  22. package/dist/modules/admin-permissions/abilities.js +6 -24
  23. package/dist/modules/admin-permissions/commands.js +42 -28
  24. package/dist/modules/admin-permissions/components/inspector.d.ts +4 -0
  25. package/dist/modules/admin-permissions/components/inspector.js +284 -0
  26. package/dist/modules/admin-permissions/components/inspector.module.js +56 -0
  27. package/dist/modules/admin-permissions/components/inspector_module.css +238 -0
  28. package/dist/modules/admin-permissions/dto.js +3 -16
  29. package/dist/modules/admin-permissions/errors.js +14 -27
  30. package/dist/modules/admin-permissions/index.js +6 -26
  31. package/dist/modules/admin-permissions/repository.js +1 -8
  32. package/dist/modules/admin-permissions/schemas.js +33 -70
  33. package/dist/modules/admin-permissions/service.js +88 -92
  34. package/dist/modules/admin-roles/abilities.js +8 -30
  35. package/dist/modules/admin-roles/commands.js +89 -55
  36. package/dist/modules/admin-roles/components/create.d.ts +7 -0
  37. package/dist/modules/admin-roles/components/create.js +177 -0
  38. package/dist/modules/admin-roles/components/create.module.js +8 -0
  39. package/dist/modules/admin-roles/components/create_module.css +27 -0
  40. package/dist/modules/admin-roles/components/permissions.d.ts +10 -0
  41. package/dist/modules/admin-roles/components/permissions.js +303 -0
  42. package/dist/modules/admin-roles/components/permissions.module.js +44 -0
  43. package/dist/modules/admin-roles/components/permissions_module.css +192 -0
  44. package/dist/modules/admin-roles/components/update.d.ts +8 -0
  45. package/dist/modules/admin-roles/components/update.js +166 -0
  46. package/dist/modules/admin-roles/components/update.module.js +8 -0
  47. package/dist/modules/admin-roles/components/update_module.css +27 -0
  48. package/dist/modules/admin-roles/dto.js +3 -16
  49. package/dist/modules/admin-roles/errors.js +16 -40
  50. package/dist/modules/admin-roles/index.js +6 -26
  51. package/dist/modules/admin-roles/repository.js +1 -8
  52. package/dist/modules/admin-roles/schemas.js +41 -71
  53. package/dist/modules/admin-roles/service.js +79 -82
  54. package/dist/modules/admin-users/abilities.js +9 -38
  55. package/dist/modules/admin-users/commands.js +92 -50
  56. package/dist/modules/admin-users/components/create.d.ts +8 -0
  57. package/dist/modules/admin-users/components/create.js +268 -0
  58. package/dist/modules/admin-users/components/create.module.js +10 -0
  59. package/dist/modules/admin-users/components/create_module.css +45 -0
  60. package/dist/modules/admin-users/components/roles.d.ts +11 -0
  61. package/dist/modules/admin-users/components/roles.js +148 -0
  62. package/dist/modules/admin-users/components/roles.module.js +18 -0
  63. package/dist/modules/admin-users/components/roles_module.css +75 -0
  64. package/dist/modules/admin-users/components/set-password.d.ts +8 -0
  65. package/dist/modules/admin-users/components/set-password.js +170 -0
  66. package/dist/modules/admin-users/components/set-password.module.js +9 -0
  67. package/dist/modules/admin-users/components/set-password_module.css +31 -0
  68. package/dist/modules/admin-users/components/update.d.ts +8 -0
  69. package/dist/modules/admin-users/components/update.js +254 -0
  70. package/dist/modules/admin-users/components/update.module.js +9 -0
  71. package/dist/modules/admin-users/components/update_module.css +34 -0
  72. package/dist/modules/admin-users/dto.js +3 -18
  73. package/dist/modules/admin-users/errors.js +17 -43
  74. package/dist/modules/admin-users/index.js +7 -27
  75. package/dist/modules/admin-users/repository.js +1 -8
  76. package/dist/modules/admin-users/schemas.js +44 -75
  77. package/dist/modules/admin-users/seed-super-admin.js +9 -34
  78. package/dist/modules/admin-users/service.js +76 -91
  79. package/dist/modules/auth/components/sign-in-form.d.ts +12 -0
  80. package/dist/modules/auth/components/sign-in-form.js +115 -0
  81. package/dist/modules/auth/components/sign-in-form.module.js +12 -0
  82. package/dist/modules/auth/components/sign-in-form_module.css +41 -0
  83. package/dist/modules/auth/index.js +3 -24
  84. package/dist/modules/auth/jwt-session-provider.js +179 -149
  85. package/dist/modules/auth/password.js +11 -53
  86. package/dist/modules/auth/phc.js +21 -54
  87. package/dist/modules/auth/refresh-tokens-repository.js +1 -8
  88. package/dist/modules/auth/resolve-actor.js +6 -28
  89. package/dist/services/admin-services-context.d.ts +16 -0
  90. package/dist/services/admin-services-context.js +13 -0
  91. package/dist/services/admin-services-types.d.ts +129 -0
  92. package/dist/services/admin-services-types.js +1 -0
  93. package/dist/store.js +1 -8
  94. package/dist/vendor/noble-argon2/_blake.js +277 -45
  95. package/dist/vendor/noble-argon2/_md.js +81 -136
  96. package/dist/vendor/noble-argon2/_u64.js +65 -67
  97. package/dist/vendor/noble-argon2/argon2.js +181 -342
  98. package/dist/vendor/noble-argon2/blake2.js +252 -327
  99. package/dist/vendor/noble-argon2/utils.js +110 -490
  100. package/dist/vendor/noble-argon2/utils.js.LICENSE.txt +1 -0
  101. package/package.json +89 -10
  102. package/src/abilities.ts +32 -0
  103. package/src/declarations.d.ts +4 -0
  104. package/src/index.ts +39 -0
  105. package/src/lib/assert-admin-actor.ts +90 -0
  106. package/src/lib/create-command.ts +109 -0
  107. package/src/modules/admin-account/commands.ts +76 -0
  108. package/src/modules/admin-account/components/change-password.module.css +40 -0
  109. package/src/modules/admin-account/components/change-password.tsx +232 -0
  110. package/src/modules/admin-account/components/container.module.css +158 -0
  111. package/src/modules/admin-account/components/container.tsx +229 -0
  112. package/src/modules/admin-account/components/update.module.css +40 -0
  113. package/src/modules/admin-account/components/update.tsx +263 -0
  114. package/src/modules/admin-account/errors.ts +75 -0
  115. package/src/modules/admin-account/index.ts +60 -0
  116. package/src/modules/admin-account/schemas.ts +84 -0
  117. package/src/modules/admin-account/service.ts +92 -0
  118. package/src/modules/admin-permissions/abilities.ts +46 -0
  119. package/src/modules/admin-permissions/commands.ts +103 -0
  120. package/src/modules/admin-permissions/components/inspector.module.css +326 -0
  121. package/src/modules/admin-permissions/components/inspector.tsx +298 -0
  122. package/src/modules/admin-permissions/dto.ts +28 -0
  123. package/src/modules/admin-permissions/errors.ts +57 -0
  124. package/src/modules/admin-permissions/index.ts +72 -0
  125. package/src/modules/admin-permissions/repository.ts +49 -0
  126. package/src/modules/admin-permissions/schemas.ts +128 -0
  127. package/src/modules/admin-permissions/service.ts +137 -0
  128. package/src/modules/admin-roles/abilities.ts +62 -0
  129. package/src/modules/admin-roles/commands.ts +161 -0
  130. package/src/modules/admin-roles/components/create.module.css +40 -0
  131. package/src/modules/admin-roles/components/create.tsx +218 -0
  132. package/src/modules/admin-roles/components/permissions.module.css +279 -0
  133. package/src/modules/admin-roles/components/permissions.tsx +396 -0
  134. package/src/modules/admin-roles/components/update.module.css +40 -0
  135. package/src/modules/admin-roles/components/update.tsx +218 -0
  136. package/src/modules/admin-roles/dto.ts +30 -0
  137. package/src/modules/admin-roles/errors.ts +76 -0
  138. package/src/modules/admin-roles/index.ts +81 -0
  139. package/src/modules/admin-roles/repository.ts +96 -0
  140. package/src/modules/admin-roles/schemas.ts +139 -0
  141. package/src/modules/admin-roles/service.ts +136 -0
  142. package/src/modules/admin-users/abilities.ts +76 -0
  143. package/src/modules/admin-users/commands.ts +157 -0
  144. package/src/modules/admin-users/components/create.module.css +63 -0
  145. package/src/modules/admin-users/components/create.tsx +323 -0
  146. package/src/modules/admin-users/components/roles.module.css +119 -0
  147. package/src/modules/admin-users/components/roles.tsx +172 -0
  148. package/src/modules/admin-users/components/set-password.module.css +46 -0
  149. package/src/modules/admin-users/components/set-password.tsx +199 -0
  150. package/src/modules/admin-users/components/update.module.css +49 -0
  151. package/src/modules/admin-users/components/update.tsx +328 -0
  152. package/src/modules/admin-users/dto.ts +39 -0
  153. package/src/modules/admin-users/errors.ts +84 -0
  154. package/src/modules/admin-users/index.ts +91 -0
  155. package/src/modules/admin-users/repository.ts +161 -0
  156. package/src/modules/admin-users/schemas.ts +168 -0
  157. package/src/modules/admin-users/seed-super-admin.ts +102 -0
  158. package/src/modules/admin-users/service.ts +166 -0
  159. package/src/modules/auth/components/sign-in-form.module.css +62 -0
  160. package/src/modules/auth/components/sign-in-form.tsx +132 -0
  161. package/src/modules/auth/index.ts +31 -0
  162. package/src/modules/auth/jwt-session-provider.ts +301 -0
  163. package/src/modules/auth/password.ts +94 -0
  164. package/src/modules/auth/phc.ts +121 -0
  165. package/src/modules/auth/refresh-tokens-repository.ts +74 -0
  166. package/src/modules/auth/resolve-actor.ts +42 -0
  167. package/src/services/admin-services-context.tsx +52 -0
  168. package/src/services/admin-services-types.ts +177 -0
  169. package/src/store.ts +32 -0
  170. package/src/vendor/noble-argon2/LICENSE +21 -0
  171. package/src/vendor/noble-argon2/README.md +87 -0
  172. package/src/vendor/noble-argon2/_blake.ts +58 -0
  173. package/src/vendor/noble-argon2/_md.ts +223 -0
  174. package/src/vendor/noble-argon2/_u64.ts +118 -0
  175. package/src/vendor/noble-argon2/argon2.ts +668 -0
  176. package/src/vendor/noble-argon2/blake2.ts +583 -0
  177. package/src/vendor/noble-argon2/utils.ts +849 -0
@@ -0,0 +1,298 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * This Source Code is subject to the terms of the Mozilla Public
5
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
6
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
+ *
8
+ * Copyright (c) Infonomic Company Limited
9
+ */
10
+
11
+ /**
12
+ * Read-only abilities inspector — see docs/AUTHN-AUTHZ.md.
13
+ *
14
+ * Top level: a collapsible group per ability source (collections.docs,
15
+ * admin.users, etc.), each containing the abilities that group
16
+ * registered. Group buckets and ordering come straight from the
17
+ * `AbilityRegistry.byGroup()` shape (registration order preserved).
18
+ *
19
+ * Per-ability: an inline-expandable row showing the roles that grant
20
+ * the ability and the distinct admin users who hold it transitively.
21
+ * The matrix is fetched lazily on first expand and cached for the
22
+ * lifetime of the page — the registry is small (~40 keys) but the
23
+ * matrix queries are not free, and most visitors only inspect a few
24
+ * keys.
25
+ *
26
+ * Stable override handles: see `inspector.module.css`.
27
+ */
28
+
29
+ import { useState } from 'react'
30
+
31
+ import { Button, Container, LoaderRing, Section } from '@byline/ui/react'
32
+ import cx from 'classnames'
33
+
34
+ import { useBylineAdminServices } from '../../../services/admin-services-context.js'
35
+ import styles from './inspector.module.css'
36
+ import type {
37
+ AbilityDescriptorResponse,
38
+ AbilityGroupResponse,
39
+ ListRegisteredAbilitiesResponse,
40
+ WhoHasAbilityResponse,
41
+ } from '../index.js'
42
+
43
+ // --- helpers ---------------------------------------------------------------
44
+
45
+ function sourceVariant(source: AbilityDescriptorResponse['source']) {
46
+ switch (source) {
47
+ case 'collection':
48
+ return {
49
+ global: 'byline-inspector-row-source-collection',
50
+ local: styles['row-source-collection'],
51
+ }
52
+ case 'admin':
53
+ return {
54
+ global: 'byline-inspector-row-source-admin',
55
+ local: styles['row-source-admin'],
56
+ }
57
+ case 'plugin':
58
+ return {
59
+ global: 'byline-inspector-row-source-plugin',
60
+ local: styles['row-source-plugin'],
61
+ }
62
+ case 'core':
63
+ return {
64
+ global: 'byline-inspector-row-source-core',
65
+ local: styles['row-source-core'],
66
+ }
67
+ default:
68
+ return {
69
+ global: 'byline-inspector-row-source-unknown',
70
+ local: styles['row-source-unknown'],
71
+ }
72
+ }
73
+ }
74
+
75
+ function displayUser(user: WhoHasAbilityResponse['users'][number]): string {
76
+ const parts = [user.given_name, user.family_name].filter(
77
+ (p): p is string => typeof p === 'string' && p.length > 0
78
+ )
79
+ return parts.length > 0 ? `${parts.join(' ')} (${user.email})` : user.email
80
+ }
81
+
82
+ // --- expandable matrix row ------------------------------------------------
83
+
84
+ function MatrixPanel({ matrix }: { matrix: WhoHasAbilityResponse }) {
85
+ return (
86
+ <div className={cx('byline-inspector-matrix', styles.matrix)}>
87
+ <div>
88
+ <h4 className={cx('byline-inspector-matrix-title', styles['matrix-title'])}>
89
+ Roles ({matrix.roles.length})
90
+ </h4>
91
+ {matrix.roles.length === 0 ? (
92
+ <p className={cx('muted', 'byline-inspector-matrix-empty', styles['matrix-empty'])}>
93
+ No role grants this ability.
94
+ </p>
95
+ ) : (
96
+ <ul className={cx('byline-inspector-matrix-list', styles['matrix-list'])}>
97
+ {matrix.roles.map((role) => (
98
+ <li
99
+ key={role.id}
100
+ className={cx('byline-inspector-matrix-item', styles['matrix-item'])}
101
+ >
102
+ <span className={cx('byline-inspector-matrix-name', styles['matrix-name'])}>
103
+ {role.name}
104
+ </span>
105
+ <span className="muted">&nbsp;·&nbsp;{role.machine_name}</span>
106
+ </li>
107
+ ))}
108
+ </ul>
109
+ )}
110
+ </div>
111
+ <div>
112
+ <h4 className={cx('byline-inspector-matrix-title', styles['matrix-title'])}>
113
+ Admin users ({matrix.users.length})
114
+ </h4>
115
+ {matrix.users.length === 0 ? (
116
+ <p className={cx('muted', 'byline-inspector-matrix-empty', styles['matrix-empty'])}>
117
+ No admin user holds this ability.
118
+ </p>
119
+ ) : (
120
+ <ul className={cx('byline-inspector-matrix-list', styles['matrix-list'])}>
121
+ {matrix.users.map((user) => (
122
+ <li
123
+ key={user.id}
124
+ className={cx('byline-inspector-matrix-item', styles['matrix-item'])}
125
+ >
126
+ {displayUser(user)}
127
+ </li>
128
+ ))}
129
+ </ul>
130
+ )}
131
+ </div>
132
+ </div>
133
+ )
134
+ }
135
+
136
+ interface AbilityRowProps {
137
+ ability: AbilityDescriptorResponse
138
+ matrix: WhoHasAbilityResponse | undefined
139
+ loading: boolean
140
+ onToggle: () => void
141
+ expanded: boolean
142
+ }
143
+
144
+ function AbilityRow({ ability, matrix, loading, onToggle, expanded }: AbilityRowProps) {
145
+ const sv = sourceVariant(ability.source)
146
+ return (
147
+ <div className={cx('byline-inspector-row', styles.row)}>
148
+ <div className={cx('byline-inspector-row-head', styles['row-head'])}>
149
+ <div className={cx('byline-inspector-row-info', styles['row-info'])}>
150
+ <div className={cx('byline-inspector-row-meta', styles['row-meta'])}>
151
+ <code className={cx('byline-inspector-row-key', styles['row-key'])}>{ability.key}</code>
152
+ <span
153
+ className={cx(
154
+ 'byline-inspector-row-source',
155
+ styles['row-source'],
156
+ sv.global,
157
+ sv.local
158
+ )}
159
+ >
160
+ {ability.source ?? 'unknown'}
161
+ </span>
162
+ </div>
163
+ <p className={cx('byline-inspector-row-label', styles['row-label'])}>{ability.label}</p>
164
+ {ability.description ? (
165
+ <p
166
+ className={cx('muted', 'byline-inspector-row-description', styles['row-description'])}
167
+ >
168
+ {ability.description}
169
+ </p>
170
+ ) : null}
171
+ </div>
172
+ <Button size="xs" intent="secondary" onClick={onToggle}>
173
+ {expanded ? 'Hide' : 'Holders'}
174
+ </Button>
175
+ </div>
176
+ {expanded ? (
177
+ loading ? (
178
+ <div className={cx('byline-inspector-loader', styles.loader)}>
179
+ <LoaderRing size={20} color="#888" />
180
+ <span className="muted">Loading…</span>
181
+ </div>
182
+ ) : matrix ? (
183
+ <MatrixPanel matrix={matrix} />
184
+ ) : null
185
+ ) : null}
186
+ </div>
187
+ )
188
+ }
189
+
190
+ // --- group section --------------------------------------------------------
191
+
192
+ interface GroupSectionProps {
193
+ group: AbilityGroupResponse
194
+ matrices: Record<string, WhoHasAbilityResponse>
195
+ loading: Set<string>
196
+ expanded: Set<string>
197
+ onToggle: (abilityKey: string) => void
198
+ }
199
+
200
+ function GroupSection({ group, matrices, loading, expanded, onToggle }: GroupSectionProps) {
201
+ return (
202
+ <details open className={cx('byline-inspector-group', styles.group)}>
203
+ <summary className={cx('byline-inspector-group-summary', styles['group-summary'])}>
204
+ <span className={cx('byline-inspector-group-name', styles['group-name'])}>
205
+ {group.group}
206
+ </span>
207
+ <span className={cx('muted', 'byline-inspector-group-count', styles['group-count'])}>
208
+ {group.abilities.length} abilities
209
+ </span>
210
+ </summary>
211
+ <div className={cx('byline-inspector-group-body', styles['group-body'])}>
212
+ {group.abilities.map((ability) => (
213
+ <AbilityRow
214
+ key={ability.key}
215
+ ability={ability}
216
+ matrix={matrices[ability.key]}
217
+ loading={loading.has(ability.key)}
218
+ expanded={expanded.has(ability.key)}
219
+ onToggle={() => onToggle(ability.key)}
220
+ />
221
+ ))}
222
+ </div>
223
+ </details>
224
+ )
225
+ }
226
+
227
+ // --- top level ------------------------------------------------------------
228
+
229
+ export function AbilitiesInspector({ data }: { data: ListRegisteredAbilitiesResponse }) {
230
+ const { whoHasAbility } = useBylineAdminServices()
231
+ const [expanded, setExpanded] = useState<Set<string>>(new Set())
232
+ const [loading, setLoading] = useState<Set<string>>(new Set())
233
+ const [matrices, setMatrices] = useState<Record<string, WhoHasAbilityResponse>>({})
234
+
235
+ async function handleToggle(abilityKey: string): Promise<void> {
236
+ setExpanded((current) => {
237
+ const next = new Set(current)
238
+ if (next.has(abilityKey)) {
239
+ next.delete(abilityKey)
240
+ } else {
241
+ next.add(abilityKey)
242
+ }
243
+ return next
244
+ })
245
+
246
+ // Lazy-load the matrix the first time a row expands. Cached for
247
+ // the lifetime of the page after that.
248
+ if (!matrices[abilityKey] && !loading.has(abilityKey)) {
249
+ setLoading((current) => new Set(current).add(abilityKey))
250
+ try {
251
+ const result = await whoHasAbility({ data: { ability: abilityKey } })
252
+ setMatrices((current) => ({ ...current, [abilityKey]: result }))
253
+ } finally {
254
+ setLoading((current) => {
255
+ const next = new Set(current)
256
+ next.delete(abilityKey)
257
+ return next
258
+ })
259
+ }
260
+ }
261
+ }
262
+
263
+ return (
264
+ <Section>
265
+ <Container>
266
+ <div className={cx('byline-inspector-head', styles.head)}>
267
+ <h1 className={cx('byline-inspector-title', styles.title)}>Abilities Inspector</h1>
268
+ <span className={cx('byline-inspector-count-pill', styles['count-pill'])}>
269
+ {data.total} registered
270
+ </span>
271
+ </div>
272
+ <p className={cx('muted', 'byline-inspector-lead', styles.lead)}>
273
+ Read-only view of every ability registered through <code>bylineCore.abilities</code>.
274
+ Collections auto-register CRUD + workflow abilities; admin subsystems contribute their own
275
+ keys at composition root via <code>registerAdminAbilities</code>.
276
+ </p>
277
+ {data.groups.length === 0 ? (
278
+ <p className={cx('muted', 'byline-inspector-empty', styles.empty)}>
279
+ No abilities are registered.
280
+ </p>
281
+ ) : (
282
+ <div className={cx('byline-inspector-groups', styles.groups)}>
283
+ {data.groups.map((group) => (
284
+ <GroupSection
285
+ key={group.group}
286
+ group={group}
287
+ matrices={matrices}
288
+ loading={loading}
289
+ expanded={expanded}
290
+ onToggle={(key) => void handleToggle(key)}
291
+ />
292
+ ))}
293
+ </div>
294
+ )}
295
+ </Container>
296
+ </Section>
297
+ )
298
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import type { AbilityDescriptor } from '@byline/auth'
10
+
11
+ import type { AbilityDescriptorResponse } from './schemas.js'
12
+
13
+ /**
14
+ * Shape an `AbilityDescriptor` from the registry into its public
15
+ * response form. Identity-shaped today — the indirection exists so
16
+ * that future internal-only fields on `AbilityDescriptor` (e.g. a
17
+ * registration timestamp) stay opted out of the public shape by
18
+ * default.
19
+ */
20
+ export function toAbilityDescriptor(descriptor: AbilityDescriptor): AbilityDescriptorResponse {
21
+ return {
22
+ key: descriptor.key,
23
+ label: descriptor.label,
24
+ description: descriptor.description ?? null,
25
+ group: descriptor.group,
26
+ source: descriptor.source ?? null,
27
+ }
28
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /**
10
+ * Module-local error codes for admin-permissions.
11
+ *
12
+ * `ROLE_NOT_FOUND` covers the editor (and future grant/revoke) paths;
13
+ * `ABILITY_UNREGISTERED` is reserved for the editor too — when a client
14
+ * tries to grant an ability key that no subsystem has registered. The
15
+ * inspector is read-only and never throws either of these.
16
+ */
17
+
18
+ export const AdminPermissionsErrorCodes = {
19
+ ROLE_NOT_FOUND: 'admin.permissions.roleNotFound',
20
+ ABILITY_UNREGISTERED: 'admin.permissions.abilityUnregistered',
21
+ } as const
22
+
23
+ export type AdminPermissionsErrorCode =
24
+ (typeof AdminPermissionsErrorCodes)[keyof typeof AdminPermissionsErrorCodes]
25
+
26
+ export interface AdminPermissionsErrorOptions {
27
+ message?: string
28
+ cause?: unknown
29
+ }
30
+
31
+ export class AdminPermissionsError extends Error {
32
+ public readonly code: AdminPermissionsErrorCode
33
+
34
+ constructor(code: AdminPermissionsErrorCode, options: { message: string; cause?: unknown }) {
35
+ super(options.message, options.cause != null ? { cause: options.cause } : undefined)
36
+ this.name = 'AdminPermissionsError'
37
+ this.code = code
38
+ }
39
+ }
40
+
41
+ const make =
42
+ (code: AdminPermissionsErrorCode, defaultMessage: string) =>
43
+ (options?: AdminPermissionsErrorOptions): AdminPermissionsError =>
44
+ new AdminPermissionsError(code, {
45
+ message: options?.message ?? defaultMessage,
46
+ cause: options?.cause,
47
+ })
48
+
49
+ export const ERR_ADMIN_PERMISSIONS_ROLE_NOT_FOUND = make(
50
+ AdminPermissionsErrorCodes.ROLE_NOT_FOUND,
51
+ 'admin role not found'
52
+ )
53
+
54
+ export const ERR_ADMIN_PERMISSIONS_ABILITY_UNREGISTERED = make(
55
+ AdminPermissionsErrorCodes.ABILITY_UNREGISTERED,
56
+ 'one or more abilities are not registered'
57
+ )
@@ -0,0 +1,72 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /**
10
+ * `@byline/admin/admin-permissions` — ability grants against roles plus
11
+ * the read-only inspector view.
12
+ *
13
+ * Backs the `byline_admin_permissions` table. Ability keys are
14
+ * registered at `initBylineCore()` time through the `AbilityRegistry`
15
+ * from `@byline/auth`; this module owns the per-role grant data and the
16
+ * inspector that surfaces it.
17
+ *
18
+ * The editor surface (`getRoleAbilities` / `setRoleAbilities`) is
19
+ * deliberately out of scope on this first ship — it lands with Phase B
20
+ * and mounts on the admin-roles role detail page.
21
+ */
22
+
23
+ export {
24
+ ADMIN_PERMISSIONS_ABILITIES,
25
+ type AdminPermissionsAbilityKey,
26
+ registerAdminPermissionsAbilities,
27
+ } from './abilities.js'
28
+ export {
29
+ getRoleAbilitiesCommand,
30
+ listRegisteredAbilitiesCommand,
31
+ setRoleAbilitiesCommand,
32
+ whoHasAbilityCommand,
33
+ } from './commands.js'
34
+ export { toAbilityDescriptor } from './dto.js'
35
+ export {
36
+ AdminPermissionsError,
37
+ type AdminPermissionsErrorCode,
38
+ AdminPermissionsErrorCodes,
39
+ ERR_ADMIN_PERMISSIONS_ABILITY_UNREGISTERED,
40
+ ERR_ADMIN_PERMISSIONS_ROLE_NOT_FOUND,
41
+ } from './errors.js'
42
+ export {
43
+ abilityDescriptorResponseSchema,
44
+ abilityGroupResponseSchema,
45
+ abilityHolderRoleSchema,
46
+ abilityHolderUserSchema,
47
+ getRoleAbilitiesRequestSchema,
48
+ getRoleAbilitiesResponseSchema,
49
+ listRegisteredAbilitiesRequestSchema,
50
+ listRegisteredAbilitiesResponseSchema,
51
+ setRoleAbilitiesRequestSchema,
52
+ setRoleAbilitiesResponseSchema,
53
+ whoHasAbilityRequestSchema,
54
+ whoHasAbilityResponseSchema,
55
+ } from './schemas.js'
56
+ export { AdminPermissionsService } from './service.js'
57
+ export type { AdminPermissionsCommandDeps } from './commands.js'
58
+ export type { AdminPermissionsRepository } from './repository.js'
59
+ export type {
60
+ AbilityDescriptorResponse,
61
+ AbilityGroupResponse,
62
+ AbilityHolderRole,
63
+ AbilityHolderUser,
64
+ GetRoleAbilitiesRequest,
65
+ GetRoleAbilitiesResponse,
66
+ ListRegisteredAbilitiesRequest,
67
+ ListRegisteredAbilitiesResponse,
68
+ SetRoleAbilitiesRequest,
69
+ SetRoleAbilitiesResponse,
70
+ WhoHasAbilityRequest,
71
+ WhoHasAbilityResponse,
72
+ } from './schemas.js'
@@ -0,0 +1,49 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /**
10
+ * `AdminPermissionsRepository` — ability grants against roles.
11
+ *
12
+ * Backs the `byline_admin_permissions` table — one row per (role, ability)
13
+ * grant. `setAbilities` is the wholesale-replace operation the role-ability
14
+ * editor in the admin UI will drive; `grantAbility` / `revokeAbility` are
15
+ * the incremental operations for programmatic callers.
16
+ *
17
+ * `listAbilitiesForUser` is the join used by `resolveActor` to build an
18
+ * `AdminAuth` — distinct abilities across every role the user holds.
19
+ *
20
+ * `listRolesForAbility` and `listUsersForAbility` are the inverse joins
21
+ * driving the admin-permissions inspector view (which roles grant a given
22
+ * ability, and which admin users hold those roles transitively).
23
+ */
24
+
25
+ export interface AdminPermissionsRepository {
26
+ /** Grant an ability to a role. Idempotent via the unique constraint. */
27
+ grantAbility(roleId: string, ability: string): Promise<void>
28
+ revokeAbility(roleId: string, ability: string): Promise<void>
29
+ listAbilities(roleId: string): Promise<string[]>
30
+ /** Replace the ability set for a role wholesale. Runs inside a transaction. */
31
+ setAbilities(roleId: string, abilities: readonly string[]): Promise<void>
32
+ /**
33
+ * Distinct abilities granted to a user via every role they hold. Used by
34
+ * `resolveActor()` to build the ability set on an `AdminAuth`.
35
+ */
36
+ listAbilitiesForUser(userId: string): Promise<string[]>
37
+ /**
38
+ * Role ids that grant the given ability. Used by the inspector to render
39
+ * the per-ability "granted by these roles" list.
40
+ */
41
+ listRolesForAbility(ability: string): Promise<string[]>
42
+ /**
43
+ * Distinct admin user ids that hold a role granting the given ability.
44
+ * Single-query join through `byline_admin_role_admin_user` — preferred
45
+ * over chaining `listRolesForAbility` + `listUsersForRole` so the
46
+ * inspector stays O(1) queries per ability.
47
+ */
48
+ listUsersForAbility(ability: string): Promise<string[]>
49
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import { uuidSchema } from '@byline/core/validation'
10
+ import { z } from 'zod'
11
+
12
+ /**
13
+ * Zod request/response schemas for the admin-permissions inspector.
14
+ *
15
+ * The inspector ships two endpoints:
16
+ *
17
+ * - `listRegisteredAbilities` — flat list + grouped buckets straight
18
+ * out of the `AbilityRegistry`. No DB read.
19
+ * - `whoHasAbility` — for a given ability key, the list of roles that
20
+ * grant it and the distinct list of admin users transitively
21
+ * holding it. Two DB joins.
22
+ *
23
+ * Phase B will add `getRoleAbilities` / `setRoleAbilities` for the
24
+ * per-role editor on the admin-roles detail page; both are deliberately
25
+ * out of scope here.
26
+ */
27
+
28
+ const abilityKeySchema = z.string().min(1).max(128)
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Requests
32
+ // ---------------------------------------------------------------------------
33
+
34
+ export const listRegisteredAbilitiesRequestSchema = z.object({}).optional()
35
+ export type ListRegisteredAbilitiesRequest = z.infer<typeof listRegisteredAbilitiesRequestSchema>
36
+
37
+ export const whoHasAbilityRequestSchema = z.object({
38
+ ability: abilityKeySchema,
39
+ })
40
+ export type WhoHasAbilityRequest = z.infer<typeof whoHasAbilityRequestSchema>
41
+
42
+ export const getRoleAbilitiesRequestSchema = z.object({
43
+ id: uuidSchema,
44
+ })
45
+ export type GetRoleAbilitiesRequest = z.infer<typeof getRoleAbilitiesRequestSchema>
46
+
47
+ export const setRoleAbilitiesRequestSchema = z.object({
48
+ id: uuidSchema,
49
+ abilities: z.array(abilityKeySchema),
50
+ })
51
+ export type SetRoleAbilitiesRequest = z.infer<typeof setRoleAbilitiesRequestSchema>
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Responses
55
+ // ---------------------------------------------------------------------------
56
+
57
+ const abilitySourceSchema = z.enum(['collection', 'plugin', 'core', 'admin']).nullable()
58
+
59
+ export const abilityDescriptorResponseSchema = z.object({
60
+ key: z.string(),
61
+ label: z.string(),
62
+ description: z.string().nullable(),
63
+ group: z.string(),
64
+ source: abilitySourceSchema,
65
+ })
66
+ export type AbilityDescriptorResponse = z.infer<typeof abilityDescriptorResponseSchema>
67
+
68
+ export const abilityGroupResponseSchema = z.object({
69
+ group: z.string(),
70
+ abilities: z.array(abilityDescriptorResponseSchema),
71
+ })
72
+ export type AbilityGroupResponse = z.infer<typeof abilityGroupResponseSchema>
73
+
74
+ /**
75
+ * Inspector list payload. Returns both the flat list and the grouped
76
+ * buckets so the UI can render either shape without re-bucketing.
77
+ */
78
+ export const listRegisteredAbilitiesResponseSchema = z.object({
79
+ abilities: z.array(abilityDescriptorResponseSchema),
80
+ groups: z.array(abilityGroupResponseSchema),
81
+ total: z.number().int().min(0),
82
+ })
83
+ export type ListRegisteredAbilitiesResponse = z.infer<typeof listRegisteredAbilitiesResponseSchema>
84
+
85
+ /**
86
+ * Who-has-ability matrix entry. Roles and users are surfaced in the
87
+ * same response so the inline-expand row in the inspector renders in
88
+ * one round-trip.
89
+ */
90
+ export const abilityHolderRoleSchema = z.object({
91
+ id: z.string(),
92
+ name: z.string(),
93
+ machine_name: z.string(),
94
+ })
95
+ export type AbilityHolderRole = z.infer<typeof abilityHolderRoleSchema>
96
+
97
+ export const abilityHolderUserSchema = z.object({
98
+ id: z.string(),
99
+ email: z.string(),
100
+ given_name: z.string().nullable(),
101
+ family_name: z.string().nullable(),
102
+ })
103
+ export type AbilityHolderUser = z.infer<typeof abilityHolderUserSchema>
104
+
105
+ export const whoHasAbilityResponseSchema = z.object({
106
+ ability: z.string(),
107
+ roles: z.array(abilityHolderRoleSchema),
108
+ users: z.array(abilityHolderUserSchema),
109
+ })
110
+ export type WhoHasAbilityResponse = z.infer<typeof whoHasAbilityResponseSchema>
111
+
112
+ /**
113
+ * Editor payloads. `roleId` is echoed back on both responses so the
114
+ * caller can match async writes against the role they were editing
115
+ * without holding the id separately. `abilities` is the authoritative
116
+ * stored set after the write.
117
+ */
118
+ export const getRoleAbilitiesResponseSchema = z.object({
119
+ roleId: z.string(),
120
+ abilities: z.array(z.string()),
121
+ })
122
+ export type GetRoleAbilitiesResponse = z.infer<typeof getRoleAbilitiesResponseSchema>
123
+
124
+ export const setRoleAbilitiesResponseSchema = z.object({
125
+ roleId: z.string(),
126
+ abilities: z.array(z.string()),
127
+ })
128
+ export type SetRoleAbilitiesResponse = z.infer<typeof setRoleAbilitiesResponseSchema>