convex-cms 0.0.2 → 0.0.3

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 (265) hide show
  1. package/admin-dist/nitro.json +15 -0
  2. package/admin-dist/public/assets/CmsEmptyState-CRswfTzk.js +5 -0
  3. package/admin-dist/public/assets/CmsPageHeader-CirpXndm.js +1 -0
  4. package/admin-dist/public/assets/CmsStatusBadge-CbEUpQu-.js +1 -0
  5. package/admin-dist/public/assets/CmsToolbar-BI2nZOXp.js +1 -0
  6. package/admin-dist/public/assets/ContentEntryEditor-CBeCyK_m.js +4 -0
  7. package/admin-dist/public/assets/ErrorState-BIVaWmom.js +1 -0
  8. package/admin-dist/public/assets/TaxonomyFilter-ChaY6Y_x.js +1 -0
  9. package/admin-dist/public/assets/_contentTypeId-DQ8k_Rvw.js +1 -0
  10. package/admin-dist/public/assets/_entryId-CKU_glsK.js +1 -0
  11. package/admin-dist/public/assets/alert-BXjTqrwQ.js +1 -0
  12. package/admin-dist/public/assets/badge-hvUOzpVZ.js +1 -0
  13. package/admin-dist/public/assets/circle-check-big-CF_pR17r.js +1 -0
  14. package/admin-dist/public/assets/command-DU82cJlt.js +1 -0
  15. package/admin-dist/public/assets/content-_LXl3pp7.js +1 -0
  16. package/admin-dist/public/assets/content-types-KjxaXGxY.js +2 -0
  17. package/admin-dist/public/assets/globals-CS6BZ0zp.css +1 -0
  18. package/admin-dist/public/assets/index-DNGIZHL-.js +1 -0
  19. package/admin-dist/public/assets/label-KNtpL71g.js +1 -0
  20. package/admin-dist/public/assets/link-2-Bw2aI4V4.js +1 -0
  21. package/admin-dist/public/assets/list-sYepHjt_.js +1 -0
  22. package/admin-dist/public/assets/main-CKj5yfEi.js +97 -0
  23. package/admin-dist/public/assets/media-Bkrkffm7.js +1 -0
  24. package/admin-dist/public/assets/new._contentTypeId-C3LstjNs.js +1 -0
  25. package/admin-dist/public/assets/plus-DUn8v_Xf.js +1 -0
  26. package/admin-dist/public/assets/rotate-ccw-DJEoHcRI.js +1 -0
  27. package/admin-dist/public/assets/scroll-area-DfIlT0in.js +1 -0
  28. package/admin-dist/public/assets/search-MuAUDJKR.js +1 -0
  29. package/admin-dist/public/assets/select-BD29IXCI.js +1 -0
  30. package/admin-dist/public/assets/settings-DmMyn_6A.js +1 -0
  31. package/admin-dist/public/assets/switch-h3Rrnl5i.js +1 -0
  32. package/admin-dist/public/assets/tabs-imc8h-Dp.js +1 -0
  33. package/admin-dist/public/assets/taxonomies-dAsrT65H.js +1 -0
  34. package/admin-dist/public/assets/textarea-BTy7nwzR.js +1 -0
  35. package/admin-dist/public/assets/trash-SAWKZZHv.js +1 -0
  36. package/admin-dist/public/assets/triangle-alert-E52Vfeuh.js +1 -0
  37. package/admin-dist/public/assets/useBreadcrumbLabel-BECBMCzM.js +1 -0
  38. package/admin-dist/public/assets/usePermissions-Basjs9BT.js +1 -0
  39. package/admin-dist/public/favicon.ico +0 -0
  40. package/admin-dist/server/_chunks/_libs/@date-fns/tz.mjs +217 -0
  41. package/admin-dist/server/_chunks/_libs/@floating-ui/core.mjs +719 -0
  42. package/admin-dist/server/_chunks/_libs/@floating-ui/dom.mjs +622 -0
  43. package/admin-dist/server/_chunks/_libs/@floating-ui/react-dom.mjs +292 -0
  44. package/admin-dist/server/_chunks/_libs/@floating-ui/utils.mjs +320 -0
  45. package/admin-dist/server/_chunks/_libs/@radix-ui/number.mjs +6 -0
  46. package/admin-dist/server/_chunks/_libs/@radix-ui/primitive.mjs +11 -0
  47. package/admin-dist/server/_chunks/_libs/@radix-ui/react-arrow.mjs +23 -0
  48. package/admin-dist/server/_chunks/_libs/@radix-ui/react-avatar.mjs +119 -0
  49. package/admin-dist/server/_chunks/_libs/@radix-ui/react-checkbox.mjs +270 -0
  50. package/admin-dist/server/_chunks/_libs/@radix-ui/react-collection.mjs +69 -0
  51. package/admin-dist/server/_chunks/_libs/@radix-ui/react-compose-refs.mjs +39 -0
  52. package/admin-dist/server/_chunks/_libs/@radix-ui/react-context.mjs +137 -0
  53. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dialog.mjs +325 -0
  54. package/admin-dist/server/_chunks/_libs/@radix-ui/react-direction.mjs +9 -0
  55. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dismissable-layer.mjs +210 -0
  56. package/admin-dist/server/_chunks/_libs/@radix-ui/react-dropdown-menu.mjs +253 -0
  57. package/admin-dist/server/_chunks/_libs/@radix-ui/react-focus-guards.mjs +29 -0
  58. package/admin-dist/server/_chunks/_libs/@radix-ui/react-focus-scope.mjs +206 -0
  59. package/admin-dist/server/_chunks/_libs/@radix-ui/react-id.mjs +14 -0
  60. package/admin-dist/server/_chunks/_libs/@radix-ui/react-label.mjs +23 -0
  61. package/admin-dist/server/_chunks/_libs/@radix-ui/react-menu.mjs +812 -0
  62. package/admin-dist/server/_chunks/_libs/@radix-ui/react-popover.mjs +300 -0
  63. package/admin-dist/server/_chunks/_libs/@radix-ui/react-popper.mjs +286 -0
  64. package/admin-dist/server/_chunks/_libs/@radix-ui/react-portal.mjs +16 -0
  65. package/admin-dist/server/_chunks/_libs/@radix-ui/react-presence.mjs +128 -0
  66. package/admin-dist/server/_chunks/_libs/@radix-ui/react-primitive.mjs +141 -0
  67. package/admin-dist/server/_chunks/_libs/@radix-ui/react-roving-focus.mjs +224 -0
  68. package/admin-dist/server/_chunks/_libs/@radix-ui/react-scroll-area.mjs +721 -0
  69. package/admin-dist/server/_chunks/_libs/@radix-ui/react-select.mjs +1163 -0
  70. package/admin-dist/server/_chunks/_libs/@radix-ui/react-separator.mjs +28 -0
  71. package/admin-dist/server/_chunks/_libs/@radix-ui/react-slot.mjs +601 -0
  72. package/admin-dist/server/_chunks/_libs/@radix-ui/react-switch.mjs +152 -0
  73. package/admin-dist/server/_chunks/_libs/@radix-ui/react-tabs.mjs +189 -0
  74. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-callback-ref.mjs +11 -0
  75. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-controllable-state.mjs +69 -0
  76. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-effect-event.mjs +1 -0
  77. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-escape-keydown.mjs +17 -0
  78. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-is-hydrated.mjs +15 -0
  79. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-layout-effect.mjs +6 -0
  80. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-previous.mjs +14 -0
  81. package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-size.mjs +39 -0
  82. package/admin-dist/server/_chunks/_libs/@radix-ui/react-visually-hidden.mjs +33 -0
  83. package/admin-dist/server/_chunks/_libs/@tanstack/history.mjs +409 -0
  84. package/admin-dist/server/_chunks/_libs/@tanstack/react-router.mjs +1711 -0
  85. package/admin-dist/server/_chunks/_libs/@tanstack/react-store.mjs +56 -0
  86. package/admin-dist/server/_chunks/_libs/@tanstack/router-core.mjs +4829 -0
  87. package/admin-dist/server/_chunks/_libs/@tanstack/store.mjs +134 -0
  88. package/admin-dist/server/_chunks/_libs/react-dom.mjs +10781 -0
  89. package/admin-dist/server/_chunks/_libs/react.mjs +513 -0
  90. package/admin-dist/server/_libs/aria-hidden.mjs +122 -0
  91. package/admin-dist/server/_libs/class-variance-authority.mjs +44 -0
  92. package/admin-dist/server/_libs/clsx.mjs +16 -0
  93. package/admin-dist/server/_libs/cmdk.mjs +315 -0
  94. package/admin-dist/server/_libs/convex.mjs +4841 -0
  95. package/admin-dist/server/_libs/cookie-es.mjs +58 -0
  96. package/admin-dist/server/_libs/croner.mjs +1 -0
  97. package/admin-dist/server/_libs/crossws.mjs +1 -0
  98. package/admin-dist/server/_libs/date-fns.mjs +1716 -0
  99. package/admin-dist/server/_libs/detect-node-es.mjs +1 -0
  100. package/admin-dist/server/_libs/get-nonce.mjs +9 -0
  101. package/admin-dist/server/_libs/h3-v2.mjs +277 -0
  102. package/admin-dist/server/_libs/h3.mjs +401 -0
  103. package/admin-dist/server/_libs/hookable.mjs +1 -0
  104. package/admin-dist/server/_libs/isbot.mjs +20 -0
  105. package/admin-dist/server/_libs/lucide-react.mjs +850 -0
  106. package/admin-dist/server/_libs/ohash.mjs +1 -0
  107. package/admin-dist/server/_libs/react-day-picker.mjs +2201 -0
  108. package/admin-dist/server/_libs/react-remove-scroll-bar.mjs +82 -0
  109. package/admin-dist/server/_libs/react-remove-scroll.mjs +328 -0
  110. package/admin-dist/server/_libs/react-style-singleton.mjs +69 -0
  111. package/admin-dist/server/_libs/rou3.mjs +8 -0
  112. package/admin-dist/server/_libs/seroval-plugins.mjs +58 -0
  113. package/admin-dist/server/_libs/seroval.mjs +1765 -0
  114. package/admin-dist/server/_libs/srvx.mjs +719 -0
  115. package/admin-dist/server/_libs/tailwind-merge.mjs +3010 -0
  116. package/admin-dist/server/_libs/tiny-invariant.mjs +12 -0
  117. package/admin-dist/server/_libs/tiny-warning.mjs +5 -0
  118. package/admin-dist/server/_libs/tslib.mjs +39 -0
  119. package/admin-dist/server/_libs/ufo.mjs +54 -0
  120. package/admin-dist/server/_libs/unctx.mjs +1 -0
  121. package/admin-dist/server/_libs/unstorage.mjs +1 -0
  122. package/admin-dist/server/_libs/use-callback-ref.mjs +66 -0
  123. package/admin-dist/server/_libs/use-sidecar.mjs +106 -0
  124. package/admin-dist/server/_libs/use-sync-external-store.mjs +139 -0
  125. package/admin-dist/server/_libs/zod.mjs +4223 -0
  126. package/admin-dist/server/_ssr/CmsEmptyState-DU7-7-mV.mjs +290 -0
  127. package/admin-dist/server/_ssr/CmsPageHeader-CseW0AHm.mjs +24 -0
  128. package/admin-dist/server/_ssr/CmsStatusBadge-B_pi4KCp.mjs +127 -0
  129. package/admin-dist/server/_ssr/CmsToolbar-X75ex6ek.mjs +49 -0
  130. package/admin-dist/server/_ssr/ContentEntryEditor-CepusRsA.mjs +3720 -0
  131. package/admin-dist/server/_ssr/ErrorState-cI-bKLez.mjs +89 -0
  132. package/admin-dist/server/_ssr/TaxonomyFilter-Bwrq0-cz.mjs +188 -0
  133. package/admin-dist/server/_ssr/_contentTypeId-BqYKEcLr.mjs +379 -0
  134. package/admin-dist/server/_ssr/_entryId-CRfnqeDf.mjs +161 -0
  135. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-BwDlABVk.mjs +4 -0
  136. package/admin-dist/server/_ssr/alert-CVt45UUP.mjs +92 -0
  137. package/admin-dist/server/_ssr/badge-6BsP37vG.mjs +125 -0
  138. package/admin-dist/server/_ssr/command-fy8epIKf.mjs +128 -0
  139. package/admin-dist/server/_ssr/config.server-D7JHDcDv.mjs +117 -0
  140. package/admin-dist/server/_ssr/content-B5RhL7uW.mjs +532 -0
  141. package/admin-dist/server/_ssr/content-types-BIOqCQYN.mjs +1166 -0
  142. package/admin-dist/server/_ssr/index-DHSHDPt1.mjs +193 -0
  143. package/admin-dist/server/_ssr/index.mjs +1275 -0
  144. package/admin-dist/server/_ssr/label-C8Dko1j7.mjs +22 -0
  145. package/admin-dist/server/_ssr/media-CSx3XttC.mjs +1832 -0
  146. package/admin-dist/server/_ssr/new._contentTypeId-DzanEZQM.mjs +144 -0
  147. package/admin-dist/server/_ssr/router-DDWcF-kt.mjs +1556 -0
  148. package/admin-dist/server/_ssr/scroll-area-bjPYwhXN.mjs +59 -0
  149. package/admin-dist/server/_ssr/select-BUhDDf4T.mjs +142 -0
  150. package/admin-dist/server/_ssr/settings-DAsxnw2q.mjs +348 -0
  151. package/admin-dist/server/_ssr/start-HYkvq4Ni.mjs +4 -0
  152. package/admin-dist/server/_ssr/switch-BgyRtQ1Z.mjs +31 -0
  153. package/admin-dist/server/_ssr/tabs-DzMdRB1A.mjs +628 -0
  154. package/admin-dist/server/_ssr/taxonomies-C8j8g5Q5.mjs +915 -0
  155. package/admin-dist/server/_ssr/textarea-9jNeYJSc.mjs +18 -0
  156. package/admin-dist/server/_ssr/trash-DYMxwhZB.mjs +291 -0
  157. package/admin-dist/server/_ssr/useBreadcrumbLabel-FNSAr2Ha.mjs +16 -0
  158. package/admin-dist/server/_ssr/usePermissions-BJGGahrJ.mjs +68 -0
  159. package/admin-dist/server/favicon.ico +0 -0
  160. package/admin-dist/server/index.mjs +627 -0
  161. package/dist/cli/index.js +0 -0
  162. package/dist/client/admin-config.d.ts +0 -1
  163. package/dist/client/admin-config.d.ts.map +1 -1
  164. package/dist/client/admin-config.js +0 -1
  165. package/dist/client/admin-config.js.map +1 -1
  166. package/dist/client/adminApi.d.ts.map +1 -1
  167. package/dist/client/agentTools.d.ts +1237 -135
  168. package/dist/client/agentTools.d.ts.map +1 -1
  169. package/dist/client/agentTools.js +33 -9
  170. package/dist/client/agentTools.js.map +1 -1
  171. package/dist/client/index.d.ts +1 -1
  172. package/dist/client/index.d.ts.map +1 -1
  173. package/dist/client/index.js.map +1 -1
  174. package/dist/component/_generated/component.d.ts +9 -0
  175. package/dist/component/_generated/component.d.ts.map +1 -1
  176. package/dist/component/mediaAssets.d.ts +35 -0
  177. package/dist/component/mediaAssets.d.ts.map +1 -1
  178. package/dist/component/mediaAssets.js +81 -0
  179. package/dist/component/mediaAssets.js.map +1 -1
  180. package/dist/test.d.ts.map +1 -1
  181. package/dist/test.js +2 -1
  182. package/dist/test.js.map +1 -1
  183. package/package.json +9 -5
  184. package/dist/component/auditLog.d.ts +0 -410
  185. package/dist/component/auditLog.d.ts.map +0 -1
  186. package/dist/component/auditLog.js +0 -607
  187. package/dist/component/auditLog.js.map +0 -1
  188. package/dist/component/types.d.ts +0 -4
  189. package/dist/component/types.d.ts.map +0 -1
  190. package/dist/component/types.js +0 -2
  191. package/dist/component/types.js.map +0 -1
  192. package/src/cli/commands/admin.ts +0 -104
  193. package/src/cli/index.ts +0 -21
  194. package/src/cli/utils/detectConvexUrl.ts +0 -54
  195. package/src/cli/utils/openBrowser.ts +0 -16
  196. package/src/client/admin-config.ts +0 -138
  197. package/src/client/adminApi.ts +0 -942
  198. package/src/client/agentTools.ts +0 -1311
  199. package/src/client/argTypes.ts +0 -316
  200. package/src/client/field-types.ts +0 -187
  201. package/src/client/index.ts +0 -1301
  202. package/src/client/queryBuilder.ts +0 -1100
  203. package/src/client/schema/codegen.ts +0 -500
  204. package/src/client/schema/defineContentType.ts +0 -501
  205. package/src/client/schema/index.ts +0 -169
  206. package/src/client/schema/schemaDrift.ts +0 -574
  207. package/src/client/schema/typedClient.ts +0 -688
  208. package/src/client/schema/types.ts +0 -666
  209. package/src/client/types.ts +0 -723
  210. package/src/client/workflows.ts +0 -141
  211. package/src/client/wrapper.ts +0 -4304
  212. package/src/component/_generated/api.ts +0 -140
  213. package/src/component/_generated/component.ts +0 -5029
  214. package/src/component/_generated/dataModel.ts +0 -60
  215. package/src/component/_generated/server.ts +0 -156
  216. package/src/component/authorization.ts +0 -647
  217. package/src/component/authorizationHooks.ts +0 -668
  218. package/src/component/bulkOperations.ts +0 -687
  219. package/src/component/contentEntries.ts +0 -1976
  220. package/src/component/contentEntryMutations.ts +0 -1223
  221. package/src/component/contentEntryValidation.ts +0 -707
  222. package/src/component/contentLock.ts +0 -550
  223. package/src/component/contentTypeMigration.ts +0 -1064
  224. package/src/component/contentTypeMutations.ts +0 -969
  225. package/src/component/contentTypes.ts +0 -346
  226. package/src/component/convex.config.ts +0 -44
  227. package/src/component/documentTypes.ts +0 -240
  228. package/src/component/eventEmitter.ts +0 -485
  229. package/src/component/exportImport.ts +0 -1169
  230. package/src/component/index.ts +0 -491
  231. package/src/component/lib/deepReferenceResolver.ts +0 -999
  232. package/src/component/lib/errors.ts +0 -816
  233. package/src/component/lib/index.ts +0 -145
  234. package/src/component/lib/mediaReferenceResolver.ts +0 -495
  235. package/src/component/lib/metadataExtractor.ts +0 -792
  236. package/src/component/lib/mutationAuth.ts +0 -199
  237. package/src/component/lib/queries.ts +0 -79
  238. package/src/component/lib/ragContentChunker.ts +0 -1371
  239. package/src/component/lib/referenceResolver.ts +0 -430
  240. package/src/component/lib/slugGenerator.ts +0 -262
  241. package/src/component/lib/slugUniqueness.ts +0 -333
  242. package/src/component/lib/softDelete.ts +0 -44
  243. package/src/component/localeFallbackChain.ts +0 -673
  244. package/src/component/localeFields.ts +0 -896
  245. package/src/component/mediaAssetMutations.ts +0 -725
  246. package/src/component/mediaAssets.ts +0 -932
  247. package/src/component/mediaFolderMutations.ts +0 -1046
  248. package/src/component/mediaUploadMutations.ts +0 -224
  249. package/src/component/mediaVariantMutations.ts +0 -900
  250. package/src/component/mediaVariants.ts +0 -793
  251. package/src/component/ragContentIndexer.ts +0 -1067
  252. package/src/component/rateLimitHooks.ts +0 -572
  253. package/src/component/roles.ts +0 -1360
  254. package/src/component/scheduledPublish.ts +0 -358
  255. package/src/component/schema.ts +0 -617
  256. package/src/component/taxonomies.ts +0 -949
  257. package/src/component/taxonomyMutations.ts +0 -1210
  258. package/src/component/trash.ts +0 -724
  259. package/src/component/userContext.ts +0 -898
  260. package/src/component/validation.ts +0 -1388
  261. package/src/component/validators.ts +0 -949
  262. package/src/component/versionMutations.ts +0 -392
  263. package/src/component/webhookTrigger.ts +0 -1922
  264. package/src/react/index.ts +0 -898
  265. package/src/test.ts +0 -1580
@@ -1,668 +0,0 @@
1
- /**
2
- * Authorization Hooks Execution Module
3
- *
4
- * This module provides the infrastructure for executing authorization hooks
5
- * that enable custom permission logic beyond the built-in RBAC system.
6
- *
7
- * Authorization hooks allow parent applications to:
8
- * - Implement custom authorization logic (team-based, subscription-based, etc.)
9
- * - Add additional restrictions beyond RBAC
10
- * - Override RBAC decisions in special cases
11
- * - Log and audit authorization decisions
12
- *
13
- * Hook execution order:
14
- * 1. beforeRbac hook (can reject early or skip RBAC)
15
- * 2. Built-in RBAC permission check (if not skipped)
16
- * 3. afterRbac hook (additional restrictions)
17
- * 4. Operation-specific hook (fine-grained control)
18
- * 5. onDeny hook (if denied, can override)
19
- *
20
- * @example
21
- * ```typescript
22
- * import { executeAuthorizationHooks } from './authorizationHooks';
23
- *
24
- * // In a mutation handler
25
- * const authResult = await executeAuthorizationHooks({
26
- * hooks: config.authorizationHooks,
27
- * context: {
28
- * operation: 'contentEntries.create',
29
- * userId: args.createdBy,
30
- * role: userRole,
31
- * contentTypeId: args.contentTypeId,
32
- * operationData: args,
33
- * },
34
- * rbacCheck: () => checkPermission({ role: userRole, ... }),
35
- * skipRbac: config.skipRbac,
36
- * });
37
- *
38
- * if (!authResult.allowed) {
39
- * throw new UnauthorizedError(authResult.reason ?? 'Access denied', { ... });
40
- * }
41
- * ```
42
- */
43
-
44
- import type {
45
- AuthorizationHooks,
46
- AuthorizationHookContext,
47
- AuthorizationHookResult,
48
- AuthorizeHookContext,
49
- CmsOperation,
50
- } from "../client/types.js";
51
-
52
- import {
53
- checkPermission,
54
- type PermissionCheckOptions,
55
- type PermissionCheckResult,
56
- UnauthorizedError,
57
- } from "./authorization.js";
58
-
59
- import type { Resource, Action, RoleDefinition } from "./roles.js";
60
-
61
- // =============================================================================
62
- // Types
63
- // =============================================================================
64
-
65
- /**
66
- * Extended authorization result with skipRbac flag.
67
- * Used internally during hook execution.
68
- */
69
- interface ExtendedAuthResult extends AuthorizationHookResult {
70
- /**
71
- * If true, skip the built-in RBAC check.
72
- * Only applicable for beforeRbac hook.
73
- */
74
- skipRbac?: boolean;
75
- }
76
-
77
- /**
78
- * Options for executing the authorization hook chain.
79
- */
80
- export interface ExecuteAuthorizationOptions {
81
- /**
82
- * The authorization hooks configuration from ComponentConfig.
83
- */
84
- hooks?: AuthorizationHooks;
85
-
86
- /**
87
- * The context for this authorization check.
88
- */
89
- context: AuthorizationHookContext;
90
-
91
- /**
92
- * Options for the built-in RBAC permission check.
93
- * If not provided, RBAC is skipped.
94
- */
95
- rbacOptions?: PermissionCheckOptions;
96
-
97
- /**
98
- * Whether to skip built-in RBAC checks entirely.
99
- * From ComponentConfig.skipRbac
100
- */
101
- skipRbac?: boolean;
102
-
103
- /**
104
- * Custom role definitions to use for RBAC checks.
105
- */
106
- customRoles?: Record<string, RoleDefinition>;
107
- }
108
-
109
- /**
110
- * Result from executing the authorization hook chain.
111
- */
112
- export interface AuthorizationResult {
113
- /**
114
- * Whether the operation is allowed.
115
- */
116
- allowed: boolean;
117
-
118
- /**
119
- * The reason for denial (if denied).
120
- */
121
- reason?: string;
122
-
123
- /**
124
- * Modified operation data from hooks (if any).
125
- */
126
- modifiedData?: Record<string, unknown>;
127
-
128
- /**
129
- * Which check denied the operation.
130
- */
131
- deniedBy?: "beforeRbac" | "rbac" | "authorize" | "afterRbac" | "operationHook";
132
-
133
- /**
134
- * The RBAC check result (if RBAC was run).
135
- */
136
- rbacResult?: PermissionCheckResult;
137
- }
138
-
139
- // =============================================================================
140
- // Helper Functions
141
- // =============================================================================
142
-
143
- /**
144
- * Maps a CMS operation to an RBAC resource and action.
145
- */
146
- export function operationToRbac(operation: CmsOperation): { resource: Resource; action: Action } | null {
147
- const mapping: Record<CmsOperation, { resource: Resource; action: Action }> = {
148
- // Content Types
149
- "contentTypes.create": { resource: "contentTypes", action: "create" },
150
- "contentTypes.update": { resource: "contentTypes", action: "update" },
151
- "contentTypes.delete": { resource: "contentTypes", action: "delete" },
152
- "contentTypes.read": { resource: "contentTypes", action: "read" },
153
-
154
- // Content Entries
155
- "contentEntries.create": { resource: "contentEntries", action: "create" },
156
- "contentEntries.update": { resource: "contentEntries", action: "update" },
157
- "contentEntries.delete": { resource: "contentEntries", action: "delete" },
158
- "contentEntries.read": { resource: "contentEntries", action: "read" },
159
- "contentEntries.publish": { resource: "contentEntries", action: "publish" },
160
- "contentEntries.unpublish": { resource: "contentEntries", action: "unpublish" },
161
- "contentEntries.restore": { resource: "contentEntries", action: "restore" },
162
- "contentEntries.schedule": { resource: "contentEntries", action: "update" }, // Schedule uses update permission
163
-
164
- // Media Items (unified assets and folders)
165
- "mediaItems.create": { resource: "mediaItems", action: "create" },
166
- "mediaItems.update": { resource: "mediaItems", action: "update" },
167
- "mediaItems.delete": { resource: "mediaItems", action: "delete" },
168
- "mediaItems.read": { resource: "mediaItems", action: "read" },
169
- "mediaItems.move": { resource: "mediaItems", action: "move" },
170
-
171
- // Versions
172
- "versions.read": { resource: "contentEntries", action: "read" }, // Version read uses entry read
173
- "versions.rollback": { resource: "contentEntries", action: "update" }, // Rollback uses entry update
174
- };
175
-
176
- return mapping[operation] ?? null;
177
- }
178
-
179
- /**
180
- * Execute a single authorization hook safely.
181
- * Catches errors and converts them to denied results.
182
- */
183
- async function executeHook(
184
- hook: ((context: AuthorizationHookContext) => Promise<AuthorizationHookResult> | AuthorizationHookResult) | undefined,
185
- context: AuthorizationHookContext
186
- ): Promise<ExtendedAuthResult> {
187
- if (!hook) {
188
- return { allowed: true };
189
- }
190
-
191
- try {
192
- const result = await hook(context);
193
- return result as ExtendedAuthResult;
194
- } catch (error) {
195
- // Hook threw an error - treat as denial
196
- const message = error instanceof Error ? error.message : "Authorization hook failed";
197
- return {
198
- allowed: false,
199
- reason: message,
200
- };
201
- }
202
- }
203
-
204
- /**
205
- * Execute the authorize hook with the extended context that includes the RBAC decision.
206
- * This hook receives the default RBAC decision and can override it.
207
- */
208
- async function executeAuthorizeHook(
209
- hook: ((context: AuthorizeHookContext) => Promise<AuthorizationHookResult> | AuthorizationHookResult) | undefined,
210
- context: AuthorizationHookContext,
211
- defaultDecision: AuthorizeHookContext["defaultDecision"]
212
- ): Promise<ExtendedAuthResult> {
213
- if (!hook) {
214
- // If no authorize hook, return the default decision
215
- return {
216
- allowed: defaultDecision.allowed,
217
- reason: defaultDecision.reason,
218
- };
219
- }
220
-
221
- const authorizeContext: AuthorizeHookContext = {
222
- ...context,
223
- defaultDecision,
224
- };
225
-
226
- try {
227
- const result = await hook(authorizeContext);
228
- return result as ExtendedAuthResult;
229
- } catch (error) {
230
- // Hook threw an error - treat as denial
231
- const message = error instanceof Error ? error.message : "Authorize hook failed";
232
- return {
233
- allowed: false,
234
- reason: message,
235
- };
236
- }
237
- }
238
-
239
- // =============================================================================
240
- // Main Authorization Execution Function
241
- // =============================================================================
242
-
243
- /**
244
- * Execute the full authorization hook chain for an operation.
245
- *
246
- * This function orchestrates the execution of all authorization hooks and the
247
- * built-in RBAC check in the correct order:
248
- *
249
- * 1. **beforeRbac hook**: Can reject early or skip RBAC
250
- * 2. **Built-in RBAC**: Standard role-based permission check
251
- * 3. **authorize hook**: Receives RBAC decision, can override allow/deny
252
- * 4. **afterRbac hook**: Additional restrictions after authorize passes
253
- * 5. **Operation hook**: Operation-specific restrictions
254
- * 6. **onDeny hook**: Can override denials
255
- *
256
- * @param options - Configuration for the authorization execution
257
- * @returns AuthorizationResult indicating if the operation is allowed
258
- *
259
- * @example
260
- * ```typescript
261
- * const result = await executeAuthorizationHooks({
262
- * hooks: config.authorizationHooks,
263
- * context: {
264
- * operation: 'contentEntries.publish',
265
- * userId: currentUser,
266
- * role: 'editor',
267
- * resourceId: entryId,
268
- * resourceOwnerId: entry.createdBy,
269
- * },
270
- * rbacOptions: {
271
- * role: 'editor',
272
- * resource: 'contentEntries',
273
- * action: 'publish',
274
- * userId: currentUser,
275
- * resourceOwnerId: entry.createdBy,
276
- * },
277
- * });
278
- *
279
- * if (!result.allowed) {
280
- * throw new Error(result.reason ?? 'Operation not allowed');
281
- * }
282
- * ```
283
- */
284
- export async function executeAuthorizationHooks(
285
- options: ExecuteAuthorizationOptions
286
- ): Promise<AuthorizationResult> {
287
- const { hooks, context, rbacOptions, skipRbac = false, customRoles } = options;
288
-
289
- let modifiedData: Record<string, unknown> | undefined;
290
- let rbacResult: PermissionCheckResult | undefined;
291
- let shouldSkipRbac = skipRbac;
292
-
293
- // -------------------------------------------------------------------------
294
- // Step 1: Execute beforeRbac hook
295
- // -------------------------------------------------------------------------
296
- if (hooks?.beforeRbac) {
297
- const beforeResult = await executeHook(hooks.beforeRbac, context);
298
-
299
- if (!beforeResult.allowed) {
300
- // beforeRbac denied - check onDeny hook
301
- if (hooks.onDeny) {
302
- const denyResult = await executeHook(hooks.onDeny, {
303
- ...context,
304
- operationData: {
305
- ...context.operationData,
306
- deniedBy: "beforeRbac",
307
- reason: beforeResult.reason,
308
- },
309
- });
310
-
311
- if (denyResult.allowed) {
312
- // onDeny overrode the denial
313
- modifiedData = denyResult.modifiedData;
314
- } else {
315
- return {
316
- allowed: false,
317
- reason: beforeResult.reason ?? "Denied by beforeRbac hook",
318
- deniedBy: "beforeRbac",
319
- };
320
- }
321
- } else {
322
- return {
323
- allowed: false,
324
- reason: beforeResult.reason ?? "Denied by beforeRbac hook",
325
- deniedBy: "beforeRbac",
326
- };
327
- }
328
- } else {
329
- // beforeRbac allowed - check if we should skip RBAC
330
- if (beforeResult.skipRbac) {
331
- shouldSkipRbac = true;
332
- }
333
- if (beforeResult.modifiedData) {
334
- modifiedData = beforeResult.modifiedData;
335
- }
336
- }
337
- }
338
-
339
- // -------------------------------------------------------------------------
340
- // Step 2: Execute built-in RBAC check (if not skipped)
341
- // -------------------------------------------------------------------------
342
- let rbacDecision: AuthorizeHookContext["defaultDecision"] = {
343
- allowed: true, // Default if RBAC is skipped
344
- };
345
-
346
- if (!shouldSkipRbac && rbacOptions) {
347
- // Add custom roles if provided
348
- const rbacOptionsWithRoles: PermissionCheckOptions = {
349
- ...rbacOptions,
350
- customRoles: customRoles ?? rbacOptions.customRoles,
351
- };
352
-
353
- rbacResult = checkPermission(rbacOptionsWithRoles);
354
-
355
- if (rbacResult.allowed) {
356
- // Type narrowing: rbacResult is PermissionGranted when allowed is true
357
- const grantedResult = rbacResult as {
358
- allowed: true;
359
- grantedScope: "all" | "own";
360
- ownershipVerified: boolean;
361
- };
362
- rbacDecision = {
363
- allowed: true,
364
- grantedScope: grantedResult.grantedScope,
365
- ownershipVerified: grantedResult.ownershipVerified,
366
- };
367
- } else {
368
- // Type narrowing: rbacResult is PermissionDenied when allowed is false
369
- const deniedResult = rbacResult as { allowed: false; reason: string; code: string };
370
- rbacDecision = {
371
- allowed: false,
372
- reason: deniedResult.reason,
373
- code: deniedResult.code,
374
- };
375
- }
376
- }
377
-
378
- // -------------------------------------------------------------------------
379
- // Step 3: Execute authorize hook (receives RBAC decision)
380
- // -------------------------------------------------------------------------
381
- // The authorize hook can override the RBAC decision in either direction
382
- if (hooks?.authorize) {
383
- const authorizeResult = await executeAuthorizeHook(
384
- hooks.authorize,
385
- {
386
- ...context,
387
- operationData: {
388
- ...context.operationData,
389
- ...(modifiedData ?? {}),
390
- },
391
- },
392
- rbacDecision
393
- );
394
-
395
- if (!authorizeResult.allowed) {
396
- // authorize hook denied - check onDeny hook
397
- if (hooks.onDeny) {
398
- const denyResult = await executeHook(hooks.onDeny, {
399
- ...context,
400
- operationData: {
401
- ...context.operationData,
402
- deniedBy: "authorize",
403
- reason: authorizeResult.reason,
404
- defaultDecision: rbacDecision,
405
- },
406
- });
407
-
408
- if (denyResult.allowed) {
409
- // onDeny overrode the denial
410
- modifiedData = denyResult.modifiedData ?? modifiedData;
411
- } else {
412
- return {
413
- allowed: false,
414
- reason: authorizeResult.reason ?? "Denied by authorize hook",
415
- deniedBy: "authorize",
416
- rbacResult,
417
- };
418
- }
419
- } else {
420
- return {
421
- allowed: false,
422
- reason: authorizeResult.reason ?? "Denied by authorize hook",
423
- deniedBy: "authorize",
424
- rbacResult,
425
- };
426
- }
427
- } else if (authorizeResult.modifiedData) {
428
- modifiedData = { ...modifiedData, ...authorizeResult.modifiedData };
429
- }
430
- } else if (!rbacDecision.allowed) {
431
- // No authorize hook and RBAC denied - check onDeny hook
432
- if (hooks?.onDeny) {
433
- const denyResult = await executeHook(hooks.onDeny, {
434
- ...context,
435
- operationData: {
436
- ...context.operationData,
437
- deniedBy: "rbac",
438
- reason: rbacDecision.reason,
439
- rbacCode: rbacDecision.code,
440
- },
441
- });
442
-
443
- if (denyResult.allowed) {
444
- // onDeny overrode the denial
445
- modifiedData = denyResult.modifiedData ?? modifiedData;
446
- } else {
447
- return {
448
- allowed: false,
449
- reason: rbacDecision.reason ?? "Denied by RBAC",
450
- deniedBy: "rbac",
451
- rbacResult,
452
- };
453
- }
454
- } else {
455
- return {
456
- allowed: false,
457
- reason: rbacDecision.reason ?? "Denied by RBAC",
458
- deniedBy: "rbac",
459
- rbacResult,
460
- };
461
- }
462
- }
463
-
464
- // -------------------------------------------------------------------------
465
- // Step 4: Execute afterRbac hook
466
- // -------------------------------------------------------------------------
467
- if (hooks?.afterRbac) {
468
- const afterResult = await executeHook(hooks.afterRbac, {
469
- ...context,
470
- operationData: {
471
- ...context.operationData,
472
- ...(modifiedData ?? {}),
473
- },
474
- });
475
-
476
- if (!afterResult.allowed) {
477
- // afterRbac denied - check onDeny hook
478
- if (hooks.onDeny) {
479
- const denyResult = await executeHook(hooks.onDeny, {
480
- ...context,
481
- operationData: {
482
- ...context.operationData,
483
- deniedBy: "afterRbac",
484
- reason: afterResult.reason,
485
- },
486
- });
487
-
488
- if (denyResult.allowed) {
489
- // onDeny overrode the denial
490
- modifiedData = denyResult.modifiedData ?? modifiedData;
491
- } else {
492
- return {
493
- allowed: false,
494
- reason: afterResult.reason ?? "Denied by afterRbac hook",
495
- deniedBy: "afterRbac",
496
- rbacResult,
497
- };
498
- }
499
- } else {
500
- return {
501
- allowed: false,
502
- reason: afterResult.reason ?? "Denied by afterRbac hook",
503
- deniedBy: "afterRbac",
504
- rbacResult,
505
- };
506
- }
507
- } else if (afterResult.modifiedData) {
508
- modifiedData = { ...modifiedData, ...afterResult.modifiedData };
509
- }
510
- }
511
-
512
- // -------------------------------------------------------------------------
513
- // Step 5: Execute operation-specific hook
514
- // -------------------------------------------------------------------------
515
- const operationHook = hooks?.operationHooks?.[context.operation];
516
- if (operationHook) {
517
- const opResult = await executeHook(operationHook, {
518
- ...context,
519
- operationData: {
520
- ...context.operationData,
521
- ...(modifiedData ?? {}),
522
- },
523
- });
524
-
525
- if (!opResult.allowed) {
526
- // Operation hook denied - check onDeny hook
527
- if (hooks?.onDeny) {
528
- const denyResult = await executeHook(hooks.onDeny, {
529
- ...context,
530
- operationData: {
531
- ...context.operationData,
532
- deniedBy: "operationHook",
533
- reason: opResult.reason,
534
- },
535
- });
536
-
537
- if (denyResult.allowed) {
538
- // onDeny overrode the denial
539
- modifiedData = denyResult.modifiedData ?? modifiedData;
540
- } else {
541
- return {
542
- allowed: false,
543
- reason: opResult.reason ?? `Denied by ${context.operation} hook`,
544
- deniedBy: "operationHook",
545
- rbacResult,
546
- };
547
- }
548
- } else {
549
- return {
550
- allowed: false,
551
- reason: opResult.reason ?? `Denied by ${context.operation} hook`,
552
- deniedBy: "operationHook",
553
- rbacResult,
554
- };
555
- }
556
- } else if (opResult.modifiedData) {
557
- modifiedData = { ...modifiedData, ...opResult.modifiedData };
558
- }
559
- }
560
-
561
- // -------------------------------------------------------------------------
562
- // All checks passed
563
- // -------------------------------------------------------------------------
564
- return {
565
- allowed: true,
566
- modifiedData,
567
- rbacResult,
568
- };
569
- }
570
-
571
- // =============================================================================
572
- // Convenience Functions
573
- // =============================================================================
574
-
575
- /**
576
- * Create an authorization context for a content entry operation.
577
- *
578
- * @param operation - The CMS operation being performed
579
- * @param userId - The user performing the operation
580
- * @param role - The user's CMS role
581
- * @param entry - The content entry (if available)
582
- * @param contentType - The content type (if available)
583
- * @param operationData - Additional operation data (args)
584
- * @returns AuthorizationHookContext
585
- */
586
- export function createContentEntryAuthContext(
587
- operation: CmsOperation,
588
- userId: string | undefined,
589
- role: string | null | undefined,
590
- entry?: { _id: unknown; createdBy?: string; contentTypeId: unknown },
591
- contentType?: { _id: unknown; name: string },
592
- operationData?: Record<string, unknown>
593
- ): Omit<AuthorizationHookContext, "ctx"> {
594
- return {
595
- operation,
596
- userId,
597
- role,
598
- resourceId: entry?._id as string | undefined,
599
- resourceOwnerId: entry?.createdBy,
600
- contentTypeId: (entry?.contentTypeId ?? contentType?._id) as string | undefined,
601
- contentTypeName: contentType?.name,
602
- operationData,
603
- };
604
- }
605
-
606
- /**
607
- * Create RBAC options from an authorization context.
608
- *
609
- * @param context - The authorization context
610
- * @returns PermissionCheckOptions for the RBAC check
611
- */
612
- export function contextToRbacOptions(
613
- context: AuthorizationHookContext
614
- ): PermissionCheckOptions | null {
615
- const rbacMapping = operationToRbac(context.operation);
616
- if (!rbacMapping) {
617
- return null;
618
- }
619
-
620
- return {
621
- userId: context.userId,
622
- role: context.role ?? null,
623
- resource: rbacMapping.resource,
624
- action: rbacMapping.action,
625
- resourceOwnerId: context.resourceOwnerId,
626
- };
627
- }
628
-
629
- /**
630
- * Execute authorization for an operation and throw if denied.
631
- *
632
- * This is a convenience wrapper that executes hooks and throws
633
- * UnauthorizedError if the operation is not allowed.
634
- *
635
- * @param options - Authorization execution options
636
- * @throws UnauthorizedError if the operation is denied
637
- * @returns The authorization result (if allowed)
638
- */
639
- export async function requireAuthorization(
640
- options: ExecuteAuthorizationOptions
641
- ): Promise<AuthorizationResult> {
642
- const result = await executeAuthorizationHooks(options);
643
-
644
- if (!result.allowed) {
645
- const rbacMapping = operationToRbac(options.context.operation);
646
-
647
- // Get the error code from RBAC result if available
648
- let errorCode: "PERMISSION_DENIED" | "NO_ROLE" | "UNKNOWN_ROLE" | "OWNERSHIP_REQUIRED" = "PERMISSION_DENIED";
649
- if (result.rbacResult && !result.rbacResult.allowed) {
650
- // Type narrowing: rbacResult is PermissionDenied when allowed is false
651
- const deniedRbac = result.rbacResult as { allowed: false; code: string };
652
- errorCode = deniedRbac.code as typeof errorCode;
653
- }
654
-
655
- throw new UnauthorizedError(
656
- result.reason ?? "Operation not allowed",
657
- {
658
- code: errorCode,
659
- resource: rbacMapping?.resource,
660
- action: rbacMapping?.action,
661
- role: options.context.role ?? undefined,
662
- userId: options.context.userId,
663
- }
664
- );
665
- }
666
-
667
- return result;
668
- }