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,647 +0,0 @@
1
- /**
2
- * Authorization Module
3
- *
4
- * Core permission checking logic that evaluates user roles against requested
5
- * actions and resources. This module is called internally by all CMS operations
6
- * to enforce access control.
7
- *
8
- * Key concepts:
9
- * - Users are mapped to roles via the getUserRole hook (configured in ComponentConfig)
10
- * - Roles have permissions that define what actions can be performed on resources
11
- * - Permissions can be scoped to "all" (any resource) or "own" (resources created by the user)
12
- *
13
- * @example
14
- * ```typescript
15
- * import { requirePermission, checkPermission, UnauthorizedError } from './authorization';
16
- *
17
- * // In a mutation handler:
18
- * const userRole = await getUserRole(userId);
19
- * await requirePermission({
20
- * userId,
21
- * role: userRole,
22
- * resource: 'contentEntries',
23
- * action: 'update',
24
- * resourceOwnerId: entry.createdBy,
25
- * });
26
- *
27
- * // Throws UnauthorizedError if permission denied
28
- * ```
29
- */
30
-
31
- import {
32
- hasPermission,
33
- getRole,
34
- type Resource,
35
- type Action,
36
- type OwnershipScope,
37
- type RoleDefinition,
38
- } from "./roles.js";
39
-
40
- /**
41
- * Error codes for authorization failures.
42
- * These provide machine-readable error classification.
43
- */
44
- export type AuthorizationErrorCode =
45
- | "NO_ROLE"
46
- | "UNKNOWN_ROLE"
47
- | "PERMISSION_DENIED"
48
- | "OWNERSHIP_REQUIRED"
49
- | "INVALID_RESOURCE"
50
- | "INVALID_ACTION";
51
-
52
- /**
53
- * Error thrown when a user lacks permission to perform an action.
54
- *
55
- * Includes detailed context about the failed authorization check,
56
- * making it easy to understand why access was denied.
57
- *
58
- * @example
59
- * ```typescript
60
- * try {
61
- * await requirePermission({ ... });
62
- * } catch (error) {
63
- * if (error instanceof UnauthorizedError) {
64
- * console.log(error.code); // 'PERMISSION_DENIED'
65
- * console.log(error.resource); // 'contentEntries'
66
- * console.log(error.action); // 'delete'
67
- * console.log(error.message); // Human-readable message
68
- * }
69
- * }
70
- * ```
71
- */
72
- export class UnauthorizedError extends Error {
73
- /** Machine-readable error code for classification */
74
- readonly code: AuthorizationErrorCode;
75
-
76
- /** The resource being accessed (if applicable) */
77
- readonly resource?: Resource;
78
-
79
- /** The action being attempted (if applicable) */
80
- readonly action?: Action;
81
-
82
- /** The user's role (if known) */
83
- readonly role?: string;
84
-
85
- /** The user ID (if provided) */
86
- readonly userId?: string;
87
-
88
- /** The scope that was required but not granted */
89
- readonly requiredScope?: OwnershipScope;
90
-
91
- constructor(
92
- message: string,
93
- options: {
94
- code: AuthorizationErrorCode;
95
- resource?: Resource;
96
- action?: Action;
97
- role?: string;
98
- userId?: string;
99
- requiredScope?: OwnershipScope;
100
- }
101
- ) {
102
- super(message);
103
- this.name = "UnauthorizedError";
104
- this.code = options.code;
105
- this.resource = options.resource;
106
- this.action = options.action;
107
- this.role = options.role;
108
- this.userId = options.userId;
109
- this.requiredScope = options.requiredScope;
110
-
111
- // Maintains proper stack trace in V8 environments
112
- if (Error.captureStackTrace) {
113
- Error.captureStackTrace(this, UnauthorizedError);
114
- }
115
- }
116
-
117
- /**
118
- * Create a JSON-serializable representation of the error.
119
- * Useful for logging or API responses.
120
- */
121
- toJSON(): Record<string, unknown> {
122
- return {
123
- name: this.name,
124
- message: this.message,
125
- code: this.code,
126
- resource: this.resource,
127
- action: this.action,
128
- role: this.role,
129
- userId: this.userId,
130
- requiredScope: this.requiredScope,
131
- };
132
- }
133
- }
134
-
135
- /**
136
- * Options for checking a user's permission to perform an action.
137
- */
138
- export interface PermissionCheckOptions {
139
- /** The user ID performing the action (for error messages and ownership checks) */
140
- userId?: string;
141
-
142
- /** The role name to check permissions for (null means no role assigned) */
143
- role: string | null;
144
-
145
- /** The resource type being accessed */
146
- resource: Resource;
147
-
148
- /** The action being performed on the resource */
149
- action: Action;
150
-
151
- /**
152
- * The ID of the user who owns the resource.
153
- * Required when the user's role only has "own" scope permission.
154
- * If not provided and "own" scope is needed, the check will fail.
155
- */
156
- resourceOwnerId?: string;
157
-
158
- /**
159
- * Custom roles to check in addition to default roles.
160
- * Use this to support organization-specific role definitions.
161
- */
162
- customRoles?: Record<string, RoleDefinition>;
163
- }
164
-
165
- /**
166
- * Result of a permission check that passed.
167
- */
168
- export interface PermissionGranted {
169
- allowed: true;
170
- /** The scope that was granted (how the permission was satisfied) */
171
- grantedScope: OwnershipScope;
172
- /** Whether ownership was verified (true if resourceOwnerId matched userId) */
173
- ownershipVerified: boolean;
174
- }
175
-
176
- /**
177
- * Result of a permission check that failed.
178
- */
179
- export interface PermissionDenied {
180
- allowed: false;
181
- /** The reason the permission was denied */
182
- reason: string;
183
- /** Machine-readable error code */
184
- code: AuthorizationErrorCode;
185
- }
186
-
187
- /**
188
- * Result of a permission check.
189
- */
190
- export type PermissionCheckResult = PermissionGranted | PermissionDenied;
191
-
192
- /**
193
- * Get a human-readable label for a resource.
194
- */
195
- function getResourceLabel(resource: Resource): string {
196
- const labels: Record<Resource, string> = {
197
- contentTypes: "content types",
198
- contentEntries: "content entries",
199
- mediaItems: "media items",
200
- settings: "settings",
201
- };
202
- return labels[resource] ?? resource;
203
- }
204
-
205
- /**
206
- * Get a human-readable label for an action.
207
- */
208
- function getActionLabel(action: Action): string {
209
- const labels: Record<Action, string> = {
210
- create: "create",
211
- read: "view",
212
- update: "update",
213
- delete: "delete",
214
- publish: "publish",
215
- unpublish: "unpublish",
216
- restore: "restore",
217
- manage: "manage",
218
- move: "move",
219
- };
220
- return labels[action] ?? action;
221
- }
222
-
223
- /**
224
- * Generate a descriptive error message for a permission denial.
225
- */
226
- function generateDenialMessage(options: {
227
- code: AuthorizationErrorCode;
228
- role: string | null;
229
- resource: Resource;
230
- action: Action;
231
- requiredScope?: OwnershipScope;
232
- }): string {
233
- const { code, role, resource, action, requiredScope: _requiredScope } = options;
234
- const resourceLabel = getResourceLabel(resource);
235
- const actionLabel = getActionLabel(action);
236
-
237
- switch (code) {
238
- case "NO_ROLE":
239
- return `Access denied: No role assigned. You need a valid role to ${actionLabel} ${resourceLabel}.`;
240
-
241
- case "UNKNOWN_ROLE":
242
- return `Access denied: Unknown role "${role}". Contact an administrator to fix your role assignment.`;
243
-
244
- case "PERMISSION_DENIED":
245
- return `Access denied: The "${role}" role does not have permission to ${actionLabel} ${resourceLabel}.`;
246
-
247
- case "OWNERSHIP_REQUIRED":
248
- return `Access denied: The "${role}" role can only ${actionLabel} their own ${resourceLabel}. ` +
249
- `You can only perform this action on items you created.`;
250
-
251
- case "INVALID_RESOURCE":
252
- return `Invalid resource type: "${resource}". This is a system error.`;
253
-
254
- case "INVALID_ACTION":
255
- return `Invalid action type: "${action}". This is a system error.`;
256
-
257
- default:
258
- return `Access denied: Cannot ${actionLabel} ${resourceLabel}.`;
259
- }
260
- }
261
-
262
- /**
263
- * Check if a user has permission to perform an action on a resource.
264
- *
265
- * This is the core permission evaluation function. It returns a result object
266
- * indicating whether the permission was granted or denied, with details about
267
- * why.
268
- *
269
- * The function checks permissions in the following order:
270
- * 1. Validates that the user has a role assigned
271
- * 2. Validates that the role exists (in default or custom roles)
272
- * 3. Checks if the role has the required permission
273
- * 4. For "own" scope permissions, validates ownership if resourceOwnerId is provided
274
- *
275
- * @param options - The permission check configuration
276
- * @returns Result indicating whether permission was granted or denied
277
- *
278
- * @example
279
- * ```typescript
280
- * // Check if an editor can update any content entry
281
- * const result = checkPermission({
282
- * role: 'editor',
283
- * resource: 'contentEntries',
284
- * action: 'update',
285
- * });
286
- * if (result.allowed) {
287
- * console.log('Permission granted with scope:', result.grantedScope);
288
- * }
289
- *
290
- * // Check if an author can update their own content entry
291
- * const result = checkPermission({
292
- * userId: 'user123',
293
- * role: 'author',
294
- * resource: 'contentEntries',
295
- * action: 'update',
296
- * resourceOwnerId: 'user123', // Same as userId - ownership verified
297
- * });
298
- * ```
299
- */
300
- export function checkPermission(
301
- options: PermissionCheckOptions
302
- ): PermissionCheckResult {
303
- const { userId, role, resource, action, resourceOwnerId, customRoles } = options;
304
-
305
- // Check 1: User must have a role assigned
306
- if (role === null || role === undefined) {
307
- return {
308
- allowed: false,
309
- reason: "No role assigned to user",
310
- code: "NO_ROLE",
311
- };
312
- }
313
-
314
- // Check 2: Role must exist
315
- const roleDefinition = getRole(role, customRoles);
316
- if (!roleDefinition) {
317
- return {
318
- allowed: false,
319
- reason: `Unknown role: ${role}`,
320
- code: "UNKNOWN_ROLE",
321
- };
322
- }
323
-
324
- // Check 3: Check if role has permission with "all" scope
325
- if (hasPermission(role, { resource, action, scope: "all" }, customRoles)) {
326
- return {
327
- allowed: true,
328
- grantedScope: "all",
329
- ownershipVerified: false,
330
- };
331
- }
332
-
333
- // Check 4: Check if role has permission with "own" scope
334
- if (hasPermission(role, { resource, action, scope: "own" }, customRoles)) {
335
- // If no resourceOwnerId provided, we cannot verify ownership - deny access
336
- // for defense-in-depth (callers must always provide resourceOwnerId for
337
- // ownership-scoped operations)
338
- if (resourceOwnerId === undefined) {
339
- return {
340
- allowed: false,
341
- reason: "Ownership cannot be verified: resourceOwnerId not provided",
342
- code: "OWNERSHIP_REQUIRED",
343
- };
344
- }
345
-
346
- // Verify ownership: user must own the resource
347
- if (userId !== undefined && resourceOwnerId === userId) {
348
- return {
349
- allowed: true,
350
- grantedScope: "own",
351
- ownershipVerified: true,
352
- };
353
- }
354
-
355
- // User has "own" permission but doesn't own this resource
356
- return {
357
- allowed: false,
358
- reason: `Ownership required: user does not own this resource`,
359
- code: "OWNERSHIP_REQUIRED",
360
- };
361
- }
362
-
363
- // No matching permission found
364
- return {
365
- allowed: false,
366
- reason: `Role "${role}" does not have ${action} permission on ${resource}`,
367
- code: "PERMISSION_DENIED",
368
- };
369
- }
370
-
371
- /**
372
- * Require that a user has permission to perform an action.
373
- *
374
- * This is the throwing version of `checkPermission`. It's designed to be used
375
- * at the start of mutation/query handlers to enforce access control. If the
376
- * permission check fails, it throws an UnauthorizedError with a descriptive
377
- * message.
378
- *
379
- * @param options - The permission check configuration
380
- * @returns The granted permission details (if allowed)
381
- * @throws UnauthorizedError if the permission is denied
382
- *
383
- * @example
384
- * ```typescript
385
- * // In a content entry update mutation:
386
- * export const updateEntry = mutation({
387
- * args: { id: v.id("contentEntries"), data: v.any() },
388
- * handler: async (ctx, { id, data }) => {
389
- * const entry = await ctx.db.get(id);
390
- * if (!entry) throw new Error("Entry not found");
391
- *
392
- * // Check authorization before proceeding
393
- * const userRole = await getUserRole(ctx.auth.userId);
394
- * await requirePermission({
395
- * userId: ctx.auth.userId,
396
- * role: userRole,
397
- * resource: 'contentEntries',
398
- * action: 'update',
399
- * resourceOwnerId: entry.createdBy,
400
- * });
401
- *
402
- * // If we get here, the user is authorized
403
- * await ctx.db.patch(id, data);
404
- * },
405
- * });
406
- * ```
407
- */
408
- export function requirePermission(
409
- options: PermissionCheckOptions
410
- ): PermissionGranted {
411
- const result = checkPermission(options);
412
-
413
- if (!result.allowed) {
414
- // Type narrowing: result is PermissionDenied when allowed is false
415
- const denied = result as PermissionDenied;
416
- throw new UnauthorizedError(
417
- generateDenialMessage({
418
- code: denied.code,
419
- role: options.role,
420
- resource: options.resource,
421
- action: options.action,
422
- }),
423
- {
424
- code: denied.code,
425
- resource: options.resource,
426
- action: options.action,
427
- role: options.role ?? undefined,
428
- userId: options.userId,
429
- }
430
- );
431
- }
432
-
433
- return result;
434
- }
435
-
436
- /**
437
- * Check if a user owns a resource.
438
- *
439
- * This is a simple helper for ownership checks without full permission validation.
440
- * Use this when you've already verified the permission and just need to check
441
- * ownership for scope enforcement.
442
- *
443
- * @param userId - The ID of the user performing the action
444
- * @param resourceOwnerId - The ID of the user who created/owns the resource
445
- * @returns True if the user owns the resource
446
- *
447
- * @example
448
- * ```typescript
449
- * // Check ownership before allowing a delete
450
- * if (!isResourceOwner(currentUserId, entry.createdBy)) {
451
- * throw new UnauthorizedError(
452
- * 'You can only delete your own content entries',
453
- * { code: 'OWNERSHIP_REQUIRED', resource: 'contentEntries', action: 'delete' }
454
- * );
455
- * }
456
- * ```
457
- */
458
- export function isResourceOwner(
459
- userId: string | undefined,
460
- resourceOwnerId: string | undefined
461
- ): boolean {
462
- // Both must be defined and equal for ownership to be verified
463
- if (userId === undefined || resourceOwnerId === undefined) {
464
- return false;
465
- }
466
- return userId === resourceOwnerId;
467
- }
468
-
469
- /**
470
- * Require that a user owns a resource.
471
- *
472
- * Throws an UnauthorizedError if the user doesn't own the resource.
473
- * Use this when you need to enforce "own" scope on a resource.
474
- *
475
- * @param userId - The ID of the user performing the action
476
- * @param resourceOwnerId - The ID of the user who created/owns the resource
477
- * @param options - Additional context for the error message
478
- * @throws UnauthorizedError if the user doesn't own the resource
479
- *
480
- * @example
481
- * ```typescript
482
- * // Require ownership before allowing update
483
- * requireResourceOwnership(currentUserId, entry.createdBy, {
484
- * resource: 'contentEntries',
485
- * action: 'update',
486
- * role: userRole,
487
- * });
488
- * ```
489
- */
490
- export function requireResourceOwnership(
491
- userId: string | undefined,
492
- resourceOwnerId: string | undefined,
493
- options: {
494
- resource: Resource;
495
- action: Action;
496
- role?: string;
497
- }
498
- ): void {
499
- if (!isResourceOwner(userId, resourceOwnerId)) {
500
- throw new UnauthorizedError(
501
- generateDenialMessage({
502
- code: "OWNERSHIP_REQUIRED",
503
- role: options.role ?? null,
504
- resource: options.resource,
505
- action: options.action,
506
- }),
507
- {
508
- code: "OWNERSHIP_REQUIRED",
509
- resource: options.resource,
510
- action: options.action,
511
- role: options.role,
512
- userId: userId,
513
- requiredScope: "own",
514
- }
515
- );
516
- }
517
- }
518
-
519
- /**
520
- * Context for performing authorization checks within a request.
521
- * This can be built up at the start of a handler and reused for multiple checks.
522
- */
523
- export interface AuthorizationContext {
524
- /** The user's ID */
525
- userId: string;
526
- /** The user's CMS role */
527
- role: string;
528
- /** Optional custom roles to check */
529
- customRoles?: Record<string, RoleDefinition>;
530
- }
531
-
532
- /**
533
- * Create an authorization context for a user.
534
- *
535
- * This is a convenience function for building the context object used by
536
- * authorization functions. It validates that the user has a role assigned.
537
- *
538
- * @param userId - The user's ID
539
- * @param role - The user's role (from getUserRole hook)
540
- * @param customRoles - Optional custom role definitions
541
- * @returns Authorization context for permission checks
542
- * @throws UnauthorizedError if the user has no role assigned
543
- *
544
- * @example
545
- * ```typescript
546
- * // At the start of a mutation handler:
547
- * const userRole = await getUserRole({ userId });
548
- * const authCtx = createAuthContext(userId, userRole);
549
- *
550
- * // Later, check permissions with the context:
551
- * requirePermission({
552
- * ...authCtx,
553
- * resource: 'contentEntries',
554
- * action: 'create',
555
- * });
556
- * ```
557
- */
558
- export function createAuthContext(
559
- userId: string,
560
- role: string | null,
561
- customRoles?: Record<string, RoleDefinition>
562
- ): AuthorizationContext {
563
- if (role === null) {
564
- throw new UnauthorizedError(
565
- "No CMS role assigned. Contact an administrator to get access.",
566
- {
567
- code: "NO_ROLE",
568
- userId,
569
- }
570
- );
571
- }
572
-
573
- const roleDefinition = getRole(role, customRoles);
574
- if (!roleDefinition) {
575
- throw new UnauthorizedError(
576
- `Unknown role "${role}". Contact an administrator to fix your role assignment.`,
577
- {
578
- code: "UNKNOWN_ROLE",
579
- role,
580
- userId,
581
- }
582
- );
583
- }
584
-
585
- return {
586
- userId,
587
- role,
588
- customRoles,
589
- };
590
- }
591
-
592
- /**
593
- * Check if a user can perform an action using an authorization context.
594
- *
595
- * This is a convenience wrapper around checkPermission that uses a pre-built
596
- * authorization context.
597
- *
598
- * @param authCtx - The authorization context
599
- * @param resource - The resource type being accessed
600
- * @param action - The action being performed
601
- * @param resourceOwnerId - Optional owner ID for ownership validation
602
- * @returns Permission check result
603
- */
604
- export function canPerform(
605
- authCtx: AuthorizationContext,
606
- resource: Resource,
607
- action: Action,
608
- resourceOwnerId?: string
609
- ): PermissionCheckResult {
610
- return checkPermission({
611
- userId: authCtx.userId,
612
- role: authCtx.role,
613
- resource,
614
- action,
615
- resourceOwnerId,
616
- customRoles: authCtx.customRoles,
617
- });
618
- }
619
-
620
- /**
621
- * Require that a user can perform an action using an authorization context.
622
- *
623
- * This is a convenience wrapper around requirePermission that uses a pre-built
624
- * authorization context.
625
- *
626
- * @param authCtx - The authorization context
627
- * @param resource - The resource type being accessed
628
- * @param action - The action being performed
629
- * @param resourceOwnerId - Optional owner ID for ownership validation
630
- * @returns The granted permission details
631
- * @throws UnauthorizedError if permission is denied
632
- */
633
- export function mustPerform(
634
- authCtx: AuthorizationContext,
635
- resource: Resource,
636
- action: Action,
637
- resourceOwnerId?: string
638
- ): PermissionGranted {
639
- return requirePermission({
640
- userId: authCtx.userId,
641
- role: authCtx.role,
642
- resource,
643
- action,
644
- resourceOwnerId,
645
- customRoles: authCtx.customRoles,
646
- });
647
- }