@aphexcms/cms-core 2.0.9 → 2.0.11

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 (381) hide show
  1. package/LICENSE +21 -0
  2. package/dist/api/api-keys.d.ts +8 -7
  3. package/dist/api/api-keys.d.ts.map +1 -1
  4. package/dist/api/assets.d.ts +7 -22
  5. package/dist/api/assets.d.ts.map +1 -1
  6. package/dist/api/assets.js +1 -10
  7. package/dist/api/documents.d.ts +17 -16
  8. package/dist/api/documents.d.ts.map +1 -1
  9. package/dist/api/documents.js +2 -2
  10. package/dist/api/index.d.ts +1 -0
  11. package/dist/api/index.d.ts.map +1 -1
  12. package/dist/api/index.js +1 -0
  13. package/dist/api/organizations.d.ts +15 -35
  14. package/dist/api/organizations.d.ts.map +1 -1
  15. package/dist/api/roles.d.ts +22 -0
  16. package/dist/api/roles.d.ts.map +1 -0
  17. package/dist/api/roles.js +26 -0
  18. package/dist/api/schemas/api-keys.d.ts +62 -0
  19. package/dist/api/schemas/api-keys.d.ts.map +1 -0
  20. package/dist/api/schemas/api-keys.js +28 -0
  21. package/dist/api/schemas/assets.d.ts +174 -0
  22. package/dist/api/schemas/assets.d.ts.map +1 -0
  23. package/dist/api/schemas/assets.js +99 -0
  24. package/dist/api/schemas/documents.d.ts +325 -0
  25. package/dist/api/schemas/documents.d.ts.map +1 -0
  26. package/dist/api/schemas/documents.js +161 -0
  27. package/dist/api/schemas/organizations.d.ts +41 -0
  28. package/dist/api/schemas/organizations.d.ts.map +1 -0
  29. package/dist/api/schemas/organizations.js +56 -0
  30. package/dist/api/schemas/roles.d.ts +77 -0
  31. package/dist/api/schemas/roles.d.ts.map +1 -0
  32. package/dist/api/schemas/roles.js +42 -0
  33. package/dist/api/schemas/user.d.ts +10 -0
  34. package/dist/api/schemas/user.d.ts.map +1 -0
  35. package/dist/api/schemas/user.js +11 -0
  36. package/dist/api/user.d.ts +4 -6
  37. package/dist/api/user.d.ts.map +1 -1
  38. package/dist/app-augment.d.ts +32 -0
  39. package/dist/app-augment.d.ts.map +1 -0
  40. package/dist/app-augment.js +18 -0
  41. package/dist/auth/auth-hooks.d.ts +2 -1
  42. package/dist/auth/auth-hooks.d.ts.map +1 -1
  43. package/dist/auth/auth-hooks.js +22 -2
  44. package/dist/auth/provider.d.ts +5 -0
  45. package/dist/auth/provider.d.ts.map +1 -1
  46. package/dist/cli/index.js +0 -0
  47. package/dist/client/index.d.ts +4 -0
  48. package/dist/client/index.d.ts.map +1 -1
  49. package/dist/client/index.js +7 -0
  50. package/dist/components/AdminApp.svelte +306 -142
  51. package/dist/components/AdminApp.svelte.d.ts +8 -0
  52. package/dist/components/AdminApp.svelte.d.ts.map +1 -1
  53. package/dist/components/admin/DocumentEditor.svelte +653 -365
  54. package/dist/components/admin/DocumentEditor.svelte.d.ts +2 -0
  55. package/dist/components/admin/DocumentEditor.svelte.d.ts.map +1 -1
  56. package/dist/components/admin/DocumentVersionPanel.svelte +57 -44
  57. package/dist/components/admin/DocumentVersionPanel.svelte.d.ts.map +1 -1
  58. package/dist/components/admin/MediaBrowser.svelte +70 -30
  59. package/dist/components/admin/MediaBrowser.svelte.d.ts.map +1 -1
  60. package/dist/components/admin/ObjectModal.svelte +70 -95
  61. package/dist/components/admin/ObjectModal.svelte.d.ts +1 -2
  62. package/dist/components/admin/ObjectModal.svelte.d.ts.map +1 -1
  63. package/dist/components/admin/PermissionsDebug.svelte +119 -0
  64. package/dist/components/admin/PermissionsDebug.svelte.d.ts +4 -0
  65. package/dist/components/admin/PermissionsDebug.svelte.d.ts.map +1 -0
  66. package/dist/components/admin/SchemaField.svelte +3 -1
  67. package/dist/components/admin/SchemaField.svelte.d.ts.map +1 -1
  68. package/dist/components/admin/confirm-dialog/ConfirmDialogHost.svelte +35 -0
  69. package/dist/components/admin/confirm-dialog/ConfirmDialogHost.svelte.d.ts +19 -0
  70. package/dist/components/admin/confirm-dialog/ConfirmDialogHost.svelte.d.ts.map +1 -0
  71. package/dist/components/admin/confirm-dialog/confirm-dialog.svelte.d.ts +16 -0
  72. package/dist/components/admin/confirm-dialog/confirm-dialog.svelte.d.ts.map +1 -0
  73. package/dist/components/admin/confirm-dialog/confirm-dialog.svelte.js +29 -0
  74. package/dist/components/admin/fields/ArrayField.svelte +104 -108
  75. package/dist/components/admin/fields/ArrayField.svelte.d.ts.map +1 -1
  76. package/dist/components/admin/fields/FileField.svelte +22 -17
  77. package/dist/components/admin/fields/FileField.svelte.d.ts.map +1 -1
  78. package/dist/components/admin/fields/ImageField.svelte +21 -15
  79. package/dist/components/admin/fields/ImageField.svelte.d.ts.map +1 -1
  80. package/dist/components/admin/fields/ReferenceField.svelte +142 -112
  81. package/dist/components/admin/fields/ReferenceField.svelte.d.ts.map +1 -1
  82. package/dist/components/admin/fields/TextareaField.svelte +4 -3
  83. package/dist/components/admin/fields/TextareaField.svelte.d.ts +2 -2
  84. package/dist/components/admin/fields/TextareaField.svelte.d.ts.map +1 -1
  85. package/dist/components/layout/Sidebar.svelte +21 -11
  86. package/dist/components/layout/Sidebar.svelte.d.ts.map +1 -1
  87. package/dist/components/layout/sidebar/NavMain.svelte +1 -0
  88. package/dist/db/interfaces/document.d.ts.map +1 -1
  89. package/dist/db/interfaces/index.d.ts +3 -1
  90. package/dist/db/interfaces/index.d.ts.map +1 -1
  91. package/dist/db/interfaces/organization.d.ts +1 -1
  92. package/dist/db/interfaces/organization.d.ts.map +1 -1
  93. package/dist/db/interfaces/role.d.ts +25 -0
  94. package/dist/db/interfaces/role.d.ts.map +1 -0
  95. package/dist/db/interfaces/role.js +1 -0
  96. package/dist/engine.d.ts.map +1 -1
  97. package/dist/engine.js +5 -0
  98. package/dist/field-access.d.ts +30 -0
  99. package/dist/field-access.d.ts.map +1 -0
  100. package/dist/field-access.js +79 -0
  101. package/dist/hooks.d.ts +2 -0
  102. package/dist/hooks.d.ts.map +1 -1
  103. package/dist/hooks.js +23 -13
  104. package/dist/lib/api/api-keys.d.ts +8 -7
  105. package/dist/lib/api/api-keys.d.ts.map +1 -1
  106. package/dist/lib/api/api-keys.js.map +1 -1
  107. package/dist/lib/api/assets.d.ts +7 -22
  108. package/dist/lib/api/assets.d.ts.map +1 -1
  109. package/dist/lib/api/assets.js +1 -10
  110. package/dist/lib/api/assets.js.map +1 -1
  111. package/dist/lib/api/documents.d.ts +17 -16
  112. package/dist/lib/api/documents.d.ts.map +1 -1
  113. package/dist/lib/api/documents.js +2 -2
  114. package/dist/lib/api/documents.js.map +1 -1
  115. package/dist/lib/api/index.d.ts +1 -0
  116. package/dist/lib/api/index.d.ts.map +1 -1
  117. package/dist/lib/api/index.js +1 -0
  118. package/dist/lib/api/index.js.map +1 -1
  119. package/dist/lib/api/organizations.d.ts +15 -35
  120. package/dist/lib/api/organizations.d.ts.map +1 -1
  121. package/dist/lib/api/organizations.js.map +1 -1
  122. package/dist/lib/api/roles.d.ts +22 -0
  123. package/dist/lib/api/roles.d.ts.map +1 -0
  124. package/dist/lib/api/roles.js +27 -0
  125. package/dist/lib/api/roles.js.map +1 -0
  126. package/dist/lib/api/schemas/api-keys.d.ts +62 -0
  127. package/dist/lib/api/schemas/api-keys.d.ts.map +1 -0
  128. package/dist/lib/api/schemas/api-keys.js +29 -0
  129. package/dist/lib/api/schemas/api-keys.js.map +1 -0
  130. package/dist/lib/api/schemas/assets.d.ts +174 -0
  131. package/dist/lib/api/schemas/assets.d.ts.map +1 -0
  132. package/dist/lib/api/schemas/assets.js +100 -0
  133. package/dist/lib/api/schemas/assets.js.map +1 -0
  134. package/dist/lib/api/schemas/documents.d.ts +325 -0
  135. package/dist/lib/api/schemas/documents.d.ts.map +1 -0
  136. package/dist/lib/api/schemas/documents.js +162 -0
  137. package/dist/lib/api/schemas/documents.js.map +1 -0
  138. package/dist/lib/api/schemas/organizations.d.ts +41 -0
  139. package/dist/lib/api/schemas/organizations.d.ts.map +1 -0
  140. package/dist/lib/api/schemas/organizations.js +57 -0
  141. package/dist/lib/api/schemas/organizations.js.map +1 -0
  142. package/dist/lib/api/schemas/roles.d.ts +77 -0
  143. package/dist/lib/api/schemas/roles.d.ts.map +1 -0
  144. package/dist/lib/api/schemas/roles.js +43 -0
  145. package/dist/lib/api/schemas/roles.js.map +1 -0
  146. package/dist/lib/api/schemas/user.d.ts +10 -0
  147. package/dist/lib/api/schemas/user.d.ts.map +1 -0
  148. package/dist/lib/api/schemas/user.js +12 -0
  149. package/dist/lib/api/schemas/user.js.map +1 -0
  150. package/dist/lib/api/user.d.ts +4 -6
  151. package/dist/lib/api/user.d.ts.map +1 -1
  152. package/dist/lib/api/user.js.map +1 -1
  153. package/dist/lib/app-augment.d.ts +32 -0
  154. package/dist/lib/app-augment.d.ts.map +1 -0
  155. package/dist/lib/app-augment.js +19 -0
  156. package/dist/lib/app-augment.js.map +1 -0
  157. package/dist/lib/auth/auth-hooks.d.ts +2 -1
  158. package/dist/lib/auth/auth-hooks.d.ts.map +1 -1
  159. package/dist/lib/auth/auth-hooks.js +22 -2
  160. package/dist/lib/auth/auth-hooks.js.map +1 -1
  161. package/dist/lib/auth/provider.d.ts +5 -0
  162. package/dist/lib/auth/provider.d.ts.map +1 -1
  163. package/dist/lib/client/index.d.ts +4 -0
  164. package/dist/lib/client/index.d.ts.map +1 -1
  165. package/dist/lib/client/index.js +7 -0
  166. package/dist/lib/client/index.js.map +1 -1
  167. package/dist/lib/components/admin/confirm-dialog/confirm-dialog.svelte.d.ts +16 -0
  168. package/dist/lib/components/admin/confirm-dialog/confirm-dialog.svelte.d.ts.map +1 -0
  169. package/dist/lib/components/admin/confirm-dialog/confirm-dialog.svelte.js +30 -0
  170. package/dist/lib/components/admin/confirm-dialog/confirm-dialog.svelte.js.map +1 -0
  171. package/dist/lib/db/interfaces/document.d.ts.map +1 -1
  172. package/dist/lib/db/interfaces/index.d.ts +3 -1
  173. package/dist/lib/db/interfaces/index.d.ts.map +1 -1
  174. package/dist/lib/db/interfaces/organization.d.ts +1 -1
  175. package/dist/lib/db/interfaces/organization.d.ts.map +1 -1
  176. package/dist/lib/db/interfaces/role.d.ts +25 -0
  177. package/dist/lib/db/interfaces/role.d.ts.map +1 -0
  178. package/dist/lib/db/interfaces/role.js +2 -0
  179. package/dist/lib/db/interfaces/role.js.map +1 -0
  180. package/dist/lib/engine.d.ts.map +1 -1
  181. package/dist/lib/engine.js +5 -0
  182. package/dist/lib/engine.js.map +1 -1
  183. package/dist/lib/field-access.d.ts +30 -0
  184. package/dist/lib/field-access.d.ts.map +1 -0
  185. package/dist/lib/field-access.js +80 -0
  186. package/dist/lib/field-access.js.map +1 -0
  187. package/dist/lib/hooks.d.ts +2 -0
  188. package/dist/lib/hooks.d.ts.map +1 -1
  189. package/dist/lib/hooks.js +23 -13
  190. package/dist/lib/hooks.js.map +1 -1
  191. package/dist/lib/local-api/collection-api.d.ts +2 -0
  192. package/dist/lib/local-api/collection-api.d.ts.map +1 -1
  193. package/dist/lib/local-api/collection-api.js +98 -30
  194. package/dist/lib/local-api/collection-api.js.map +1 -1
  195. package/dist/lib/local-api/index.d.ts.map +1 -1
  196. package/dist/lib/local-api/index.js.map +1 -1
  197. package/dist/lib/local-api/permissions.d.ts +26 -26
  198. package/dist/lib/local-api/permissions.d.ts.map +1 -1
  199. package/dist/lib/local-api/permissions.js +122 -80
  200. package/dist/lib/local-api/permissions.js.map +1 -1
  201. package/dist/lib/permissions-context.svelte.d.ts +44 -0
  202. package/dist/lib/permissions-context.svelte.d.ts.map +1 -0
  203. package/dist/lib/permissions-context.svelte.js +80 -0
  204. package/dist/lib/permissions-context.svelte.js.map +1 -0
  205. package/dist/lib/routes/assets-bulk.d.ts.map +1 -1
  206. package/dist/lib/routes/assets-bulk.js +10 -3
  207. package/dist/lib/routes/assets-bulk.js.map +1 -1
  208. package/dist/lib/routes/assets-by-id.d.ts.map +1 -1
  209. package/dist/lib/routes/assets-by-id.js +11 -1
  210. package/dist/lib/routes/assets-by-id.js.map +1 -1
  211. package/dist/lib/routes/assets-references-counts.d.ts.map +1 -1
  212. package/dist/lib/routes/assets-references-counts.js +12 -2
  213. package/dist/lib/routes/assets-references-counts.js.map +1 -1
  214. package/dist/lib/routes/assets.d.ts.map +1 -1
  215. package/dist/lib/routes/assets.js +14 -13
  216. package/dist/lib/routes/assets.js.map +1 -1
  217. package/dist/lib/routes/document-versions.d.ts.map +1 -1
  218. package/dist/lib/routes/document-versions.js +16 -2
  219. package/dist/lib/routes/document-versions.js.map +1 -1
  220. package/dist/lib/routes/documents-by-id.d.ts.map +1 -1
  221. package/dist/lib/routes/documents-by-id.js +15 -3
  222. package/dist/lib/routes/documents-by-id.js.map +1 -1
  223. package/dist/lib/routes/documents-query.d.ts.map +1 -1
  224. package/dist/lib/routes/documents-query.js +13 -10
  225. package/dist/lib/routes/documents-query.js.map +1 -1
  226. package/dist/lib/routes/documents.d.ts.map +1 -1
  227. package/dist/lib/routes/documents.js +29 -26
  228. package/dist/lib/routes/documents.js.map +1 -1
  229. package/dist/lib/routes/organizations-by-id.d.ts.map +1 -1
  230. package/dist/lib/routes/organizations-by-id.js +17 -4
  231. package/dist/lib/routes/organizations-by-id.js.map +1 -1
  232. package/dist/lib/routes/organizations-invitations.d.ts.map +1 -1
  233. package/dist/lib/routes/organizations-invitations.js +48 -18
  234. package/dist/lib/routes/organizations-invitations.js.map +1 -1
  235. package/dist/lib/routes/organizations-members.d.ts.map +1 -1
  236. package/dist/lib/routes/organizations-members.js +24 -18
  237. package/dist/lib/routes/organizations-members.js.map +1 -1
  238. package/dist/lib/routes/organizations-switch.d.ts.map +1 -1
  239. package/dist/lib/routes/organizations-switch.js +7 -3
  240. package/dist/lib/routes/organizations-switch.js.map +1 -1
  241. package/dist/lib/routes/organizations.d.ts.map +1 -1
  242. package/dist/lib/routes/organizations.js +11 -5
  243. package/dist/lib/routes/organizations.js.map +1 -1
  244. package/dist/lib/routes/roles-by-name.d.ts +4 -0
  245. package/dist/lib/routes/roles-by-name.d.ts.map +1 -0
  246. package/dist/lib/routes/roles-by-name.js +112 -0
  247. package/dist/lib/routes/roles-by-name.js.map +1 -0
  248. package/dist/lib/routes/roles.d.ts +4 -0
  249. package/dist/lib/routes/roles.d.ts.map +1 -0
  250. package/dist/lib/routes/roles.js +91 -0
  251. package/dist/lib/routes/roles.js.map +1 -0
  252. package/dist/lib/routes/user-preferences.d.ts.map +1 -1
  253. package/dist/lib/routes/user-preferences.js +7 -13
  254. package/dist/lib/routes/user-preferences.js.map +1 -1
  255. package/dist/lib/routes-exports.d.ts +2 -0
  256. package/dist/lib/routes-exports.d.ts.map +1 -1
  257. package/dist/lib/routes-exports.js +3 -0
  258. package/dist/lib/routes-exports.js.map +1 -1
  259. package/dist/lib/save-state-context.svelte.d.ts +8 -0
  260. package/dist/lib/save-state-context.svelte.d.ts.map +1 -0
  261. package/dist/lib/save-state-context.svelte.js +9 -0
  262. package/dist/lib/save-state-context.svelte.js.map +1 -0
  263. package/dist/lib/schema-utils/validator.d.ts.map +1 -1
  264. package/dist/lib/schema-utils/validator.js +14 -0
  265. package/dist/lib/schema-utils/validator.js.map +1 -1
  266. package/dist/lib/server/index.d.ts +1 -0
  267. package/dist/lib/server/index.d.ts.map +1 -1
  268. package/dist/lib/server/index.js +1 -0
  269. package/dist/lib/server/index.js.map +1 -1
  270. package/dist/lib/services/index.d.ts +1 -0
  271. package/dist/lib/services/index.d.ts.map +1 -1
  272. package/dist/lib/services/index.js +1 -0
  273. package/dist/lib/services/index.js.map +1 -1
  274. package/dist/lib/services/roles-service.d.ts +35 -0
  275. package/dist/lib/services/roles-service.d.ts.map +1 -0
  276. package/dist/lib/services/roles-service.js +91 -0
  277. package/dist/lib/services/roles-service.js.map +1 -0
  278. package/dist/lib/services/version-service.d.ts.map +1 -1
  279. package/dist/lib/services/version-service.js.map +1 -1
  280. package/dist/lib/types/auth.d.ts +43 -9
  281. package/dist/lib/types/auth.d.ts.map +1 -1
  282. package/dist/lib/types/auth.js +31 -26
  283. package/dist/lib/types/auth.js.map +1 -1
  284. package/dist/lib/types/capabilities.d.ts +104 -0
  285. package/dist/lib/types/capabilities.d.ts.map +1 -0
  286. package/dist/lib/types/capabilities.js +217 -0
  287. package/dist/lib/types/capabilities.js.map +1 -0
  288. package/dist/lib/types/index.d.ts +1 -0
  289. package/dist/lib/types/index.d.ts.map +1 -1
  290. package/dist/lib/types/index.js +1 -0
  291. package/dist/lib/types/index.js.map +1 -1
  292. package/dist/lib/types/organization.d.ts +13 -4
  293. package/dist/lib/types/organization.d.ts.map +1 -1
  294. package/dist/lib/types/schemas.d.ts +71 -0
  295. package/dist/lib/types/schemas.d.ts.map +1 -1
  296. package/dist/lib/types/sidebar.d.ts +1 -1
  297. package/dist/lib/types/sidebar.d.ts.map +1 -1
  298. package/dist/lib/utils/mime-detect.d.ts.map +1 -1
  299. package/dist/lib/utils/mime-detect.js +28 -6
  300. package/dist/lib/utils/mime-detect.js.map +1 -1
  301. package/dist/local-api/collection-api.d.ts +2 -0
  302. package/dist/local-api/collection-api.d.ts.map +1 -1
  303. package/dist/local-api/collection-api.js +98 -30
  304. package/dist/local-api/index.d.ts.map +1 -1
  305. package/dist/local-api/permissions.d.ts +26 -26
  306. package/dist/local-api/permissions.d.ts.map +1 -1
  307. package/dist/local-api/permissions.js +122 -80
  308. package/dist/permissions-context.svelte.d.ts +44 -0
  309. package/dist/permissions-context.svelte.d.ts.map +1 -0
  310. package/dist/permissions-context.svelte.js +79 -0
  311. package/dist/plugins/README.md +20 -20
  312. package/dist/routes/assets-bulk.d.ts.map +1 -1
  313. package/dist/routes/assets-bulk.js +10 -3
  314. package/dist/routes/assets-by-id.d.ts.map +1 -1
  315. package/dist/routes/assets-by-id.js +11 -1
  316. package/dist/routes/assets-references-counts.d.ts.map +1 -1
  317. package/dist/routes/assets-references-counts.js +12 -2
  318. package/dist/routes/assets.d.ts.map +1 -1
  319. package/dist/routes/assets.js +14 -13
  320. package/dist/routes/document-versions.d.ts.map +1 -1
  321. package/dist/routes/document-versions.js +16 -2
  322. package/dist/routes/documents-by-id.d.ts.map +1 -1
  323. package/dist/routes/documents-by-id.js +15 -3
  324. package/dist/routes/documents-query.d.ts.map +1 -1
  325. package/dist/routes/documents-query.js +13 -10
  326. package/dist/routes/documents.d.ts.map +1 -1
  327. package/dist/routes/documents.js +29 -26
  328. package/dist/routes/organizations-by-id.d.ts.map +1 -1
  329. package/dist/routes/organizations-by-id.js +17 -4
  330. package/dist/routes/organizations-invitations.d.ts.map +1 -1
  331. package/dist/routes/organizations-invitations.js +48 -18
  332. package/dist/routes/organizations-members.d.ts.map +1 -1
  333. package/dist/routes/organizations-members.js +24 -18
  334. package/dist/routes/organizations-switch.d.ts.map +1 -1
  335. package/dist/routes/organizations-switch.js +7 -3
  336. package/dist/routes/organizations.d.ts.map +1 -1
  337. package/dist/routes/organizations.js +11 -5
  338. package/dist/routes/roles-by-name.d.ts +4 -0
  339. package/dist/routes/roles-by-name.d.ts.map +1 -0
  340. package/dist/routes/roles-by-name.js +111 -0
  341. package/dist/routes/roles.d.ts +4 -0
  342. package/dist/routes/roles.d.ts.map +1 -0
  343. package/dist/routes/roles.js +90 -0
  344. package/dist/routes/user-preferences.d.ts.map +1 -1
  345. package/dist/routes/user-preferences.js +7 -13
  346. package/dist/routes-exports.d.ts +2 -0
  347. package/dist/routes-exports.d.ts.map +1 -1
  348. package/dist/routes-exports.js +3 -0
  349. package/dist/save-state-context.svelte.d.ts +8 -0
  350. package/dist/save-state-context.svelte.d.ts.map +1 -0
  351. package/dist/save-state-context.svelte.js +8 -0
  352. package/dist/schema-utils/validator.d.ts.map +1 -1
  353. package/dist/schema-utils/validator.js +14 -0
  354. package/dist/server/index.d.ts +1 -0
  355. package/dist/server/index.d.ts.map +1 -1
  356. package/dist/server/index.js +1 -0
  357. package/dist/services/index.d.ts +1 -0
  358. package/dist/services/index.d.ts.map +1 -1
  359. package/dist/services/index.js +1 -0
  360. package/dist/services/roles-service.d.ts +35 -0
  361. package/dist/services/roles-service.d.ts.map +1 -0
  362. package/dist/services/roles-service.js +90 -0
  363. package/dist/services/version-service.d.ts.map +1 -1
  364. package/dist/types/auth.d.ts +43 -9
  365. package/dist/types/auth.d.ts.map +1 -1
  366. package/dist/types/auth.js +31 -26
  367. package/dist/types/capabilities.d.ts +104 -0
  368. package/dist/types/capabilities.d.ts.map +1 -0
  369. package/dist/types/capabilities.js +216 -0
  370. package/dist/types/index.d.ts +1 -0
  371. package/dist/types/index.d.ts.map +1 -1
  372. package/dist/types/index.js +1 -0
  373. package/dist/types/organization.d.ts +13 -4
  374. package/dist/types/organization.d.ts.map +1 -1
  375. package/dist/types/schemas.d.ts +71 -0
  376. package/dist/types/schemas.d.ts.map +1 -1
  377. package/dist/types/sidebar.d.ts +1 -1
  378. package/dist/types/sidebar.d.ts.map +1 -1
  379. package/dist/utils/mime-detect.d.ts.map +1 -1
  380. package/dist/utils/mime-detect.js +28 -6
  381. package/package.json +142 -138
@@ -2,12 +2,41 @@
2
2
  //
3
3
  // Collection API - provides type-safe CRUD operations for a single collection
4
4
  import { validateDocumentData } from '../field-validation/utils.js';
5
+ import { hiddenReadFields, hiddenWriteFields, dropLockedWrites, stripHiddenFields } from '../field-access.js';
6
+ const EMPTY_SET = new Set();
7
+ /**
8
+ * Re-project a FindResult through a hidden-fields filter without mutating
9
+ * the shared (potentially cached) original.
10
+ */
11
+ function applyHiddenToResult(result, hidden) {
12
+ if (hidden.size === 0)
13
+ return result;
14
+ return {
15
+ ...result,
16
+ docs: result.docs.map((d) => applyHiddenToDoc(d, hidden))
17
+ };
18
+ }
19
+ function applyHiddenToDoc(doc, hidden) {
20
+ if (!doc || typeof doc !== 'object')
21
+ return doc;
22
+ const copy = {};
23
+ for (const [key, value] of Object.entries(doc)) {
24
+ if (hidden.has(key))
25
+ continue;
26
+ copy[key] = value;
27
+ }
28
+ return copy;
29
+ }
5
30
  /**
6
31
  * Transform a raw database document into a typed document with data extracted
7
- * based on perspective (draft or published)
32
+ * based on perspective (draft or published). Optionally strips field-level
33
+ * read-restricted fields from the data payload.
8
34
  */
9
- function transformDocument(doc, perspective = 'draft') {
10
- const data = perspective === 'draft' ? doc.draftData : doc.publishedData;
35
+ function transformDocument(doc, perspective = 'draft', hiddenFields) {
36
+ const raw = perspective === 'draft' ? doc.draftData : doc.publishedData;
37
+ const data = hiddenFields && hiddenFields.size > 0
38
+ ? stripHiddenFields(raw, hiddenFields)
39
+ : raw;
11
40
  // Merge document metadata with the content data
12
41
  return {
13
42
  id: doc.id,
@@ -77,11 +106,15 @@ export class CollectionAPI {
77
106
  // Permission check (unless overrideAccess)
78
107
  await this.permissions.canRead(context, this.collectionName);
79
108
  const perspective = options.perspective || 'draft';
109
+ // Field-level access: compute per-request so two users with different
110
+ // roles see different projections even when the underlying cache hit
111
+ // is shared. Cached payloads are stored unfiltered for this reason.
112
+ const hidden = this.resolveHiddenReadFields(context);
80
113
  // Check cache for published queries
81
114
  if (perspective === 'published' && this.documentCache) {
82
115
  const cached = await this.documentCache.getQuery(context.organizationId, this.collectionName, options);
83
116
  if (cached)
84
- return cached;
117
+ return applyHiddenToResult(cached, hidden);
85
118
  }
86
119
  // Resolve org IDs via hierarchy service (cached) and pass directly —
87
120
  // this avoids the adapter opening a transaction just to set RLS context
@@ -91,17 +124,28 @@ export class CollectionAPI {
91
124
  findOptions.filterOrganizationIds = orgIds;
92
125
  }
93
126
  const result = await this.databaseAdapter.findManyDocAdvanced(context.organizationId, this.collectionName, findOptions);
94
- // Transform documents to extract data based on perspective
95
- const transformedDocs = result.docs.map((doc) => transformDocument(doc, perspective));
96
- const findResult = {
127
+ // Transform documents to extract data based on perspective.
128
+ // Cache the unfiltered version; return the filtered one.
129
+ const unfilteredDocs = result.docs.map((doc) => transformDocument(doc, perspective));
130
+ const unfilteredResult = {
97
131
  ...result,
98
- docs: transformedDocs
132
+ docs: unfilteredDocs
99
133
  };
100
- // Populate cache for published queries
134
+ // Populate cache for published queries (unfiltered payload)
101
135
  if (perspective === 'published' && this.documentCache) {
102
- await this.documentCache.setQuery(context.organizationId, this.collectionName, options, findResult);
136
+ await this.documentCache.setQuery(context.organizationId, this.collectionName, options, unfilteredResult);
103
137
  }
104
- return findResult;
138
+ return applyHiddenToResult(unfilteredResult, hidden);
139
+ }
140
+ resolveHiddenReadFields(context) {
141
+ if (context.overrideAccess)
142
+ return EMPTY_SET;
143
+ return hiddenReadFields(this._schema, context.auth);
144
+ }
145
+ resolveHiddenWriteFields(context) {
146
+ if (context.overrideAccess)
147
+ return EMPTY_SET;
148
+ return hiddenWriteFields(this._schema, context.auth);
105
149
  }
106
150
  /**
107
151
  * Find a single document by ID
@@ -119,11 +163,12 @@ export class CollectionAPI {
119
163
  // Permission check (unless overrideAccess)
120
164
  await this.permissions.canRead(context, this.collectionName);
121
165
  const perspective = options?.perspective || 'draft';
166
+ const hidden = this.resolveHiddenReadFields(context);
122
167
  // Check cache for published lookups
123
168
  if (perspective === 'published' && this.documentCache) {
124
169
  const cached = await this.documentCache.getDocument(context.organizationId, id);
125
170
  if (cached)
126
- return cached;
171
+ return applyHiddenToDoc(cached, hidden);
127
172
  }
128
173
  // Resolve org IDs via hierarchy service (cached) — avoids RLS transaction
129
174
  const findOptions = { ...options };
@@ -135,11 +180,13 @@ export class CollectionAPI {
135
180
  if (!result) {
136
181
  return null;
137
182
  }
138
- const transformed = transformDocument(result, perspective);
139
- // Populate cache for published lookups
183
+ // Cache the unfiltered projection; apply hidden fields per-caller.
184
+ const unfiltered = transformDocument(result, perspective);
185
+ // Populate cache for published lookups (unfiltered payload)
140
186
  if (perspective === 'published' && this.documentCache) {
141
- await this.documentCache.setDocument(context.organizationId, id, transformed);
187
+ await this.documentCache.setDocument(context.organizationId, id, unfiltered);
142
188
  }
189
+ const transformed = applyHiddenToDoc(unfiltered, hidden);
143
190
  return transformed;
144
191
  }
145
192
  /**
@@ -177,9 +224,16 @@ export class CollectionAPI {
177
224
  */
178
225
  async create(context, data, options) {
179
226
  // Permission check (unless overrideAccess)
180
- await this.permissions.canWrite(context, this.collectionName);
227
+ await this.permissions.canCreate(context, this.collectionName);
228
+ // Drop any writes targeting field-locked names before validation.
229
+ // A caller with collection-level update may still lack write access on
230
+ // a specific field; silently dropping keeps the server authoritative.
231
+ const locked = this.resolveHiddenWriteFields(context);
232
+ const filteredData = dropLockedWrites(data, locked);
181
233
  // Validate and normalize data (dates converted to ISO)
182
- const validationResult = await validateDocumentData(this._schema, data, data);
234
+ const validationResult = await validateDocumentData(this._schema, filteredData, {
235
+ context: filteredData
236
+ });
183
237
  if (options?.publish) {
184
238
  await this.permissions.canPublish(context, this.collectionName);
185
239
  // Block publish if validation fails
@@ -234,13 +288,19 @@ export class CollectionAPI {
234
288
  * ```
235
289
  */
236
290
  async update(context, id, data, options) {
237
- // Permission check (unless overrideAccess)
238
- await this.permissions.canWrite(context, this.collectionName);
239
- // Get existing document to merge with updates
291
+ // Fetch the doc first so ownership policies have access to the target.
292
+ // A missing doc still returns null below; capability/role rules stay
293
+ // unaffected by the reorder.
240
294
  const existingDoc = await this.databaseAdapter.findByDocIdAdvanced(context.organizationId, id);
241
295
  if (!existingDoc) {
242
296
  return null;
243
297
  }
298
+ await this.permissions.canUpdate(context, this.collectionName, existingDoc);
299
+ // Drop writes targeting field-locked names so a caller with
300
+ // collection-level update can't bypass field-level restrictions.
301
+ // Field-locked values stay at their existing stored value via the merge.
302
+ const locked = this.resolveHiddenWriteFields(context);
303
+ const filteredData = dropLockedWrites(data, locked);
244
304
  // Merge existing data with updates, but only keep fields defined in schema
245
305
  // This prevents orphaned fields from persisting when schema changes
246
306
  const schemaFieldNames = new Set(this._schema.fields.map((f) => f.name));
@@ -251,8 +311,8 @@ export class CollectionAPI {
251
311
  cleanedExisting[key] = value;
252
312
  }
253
313
  }
254
- // Merge cleaned existing data with new updates
255
- const mergedData = { ...cleanedExisting, ...data };
314
+ // Merge cleaned existing data with filtered updates
315
+ const mergedData = { ...cleanedExisting, ...filteredData };
256
316
  // Validate and normalize the merged data
257
317
  const validationResult = await validateDocumentData(this._schema, mergedData, mergedData);
258
318
  // Update draft with normalized data (dates in ISO format)
@@ -264,7 +324,7 @@ export class CollectionAPI {
264
324
  return null;
265
325
  }
266
326
  if (options?.publish) {
267
- await this.permissions.canPublish(context, this.collectionName);
327
+ await this.permissions.canPublish(context, this.collectionName, document);
268
328
  // Block publish if validation fails
269
329
  if (!validationResult.isValid) {
270
330
  const errorMessage = validationResult.errors
@@ -304,8 +364,13 @@ export class CollectionAPI {
304
364
  * ```
305
365
  */
306
366
  async delete(context, id) {
307
- // Permission check (unless overrideAccess)
308
- await this.permissions.canDelete(context, this.collectionName);
367
+ // Fetch the target so ownership policies have a doc to inspect. For
368
+ // role/capability rules this extra read is a small cost; ergonomic
369
+ // wins outweigh it for operations that are rare by nature.
370
+ const existing = await this.databaseAdapter.findByDocIdAdvanced(context.organizationId, id);
371
+ if (!existing)
372
+ return false;
373
+ await this.permissions.canDelete(context, this.collectionName, existing);
309
374
  const result = await this.databaseAdapter.deleteDocById(context.organizationId, id);
310
375
  // Invalidate cache for deleted document
311
376
  if (result && this.documentCache) {
@@ -326,13 +391,13 @@ export class CollectionAPI {
326
391
  * ```
327
392
  */
328
393
  async publish(context, id) {
329
- // Permission check (unless overrideAccess)
330
- await this.permissions.canPublish(context, this.collectionName);
331
- // Get the document to access draft data for validation
394
+ // Fetch first so policies can inspect the target. Order matters here:
395
+ // 404s beat 403s for missing docs.
332
396
  const document = await this.databaseAdapter.findByDocIdAdvanced(context.organizationId, id);
333
397
  if (!document || !document.draftData) {
334
398
  throw new Error('Document not found or has no draft content to publish');
335
399
  }
400
+ await this.permissions.canPublish(context, this.collectionName, document);
336
401
  // Validate draft data (dates already in ISO, will be converted for validation)
337
402
  const validationResult = await validateDocumentData(this._schema, document.draftData, document.draftData);
338
403
  if (!validationResult.isValid) {
@@ -367,8 +432,11 @@ export class CollectionAPI {
367
432
  * ```
368
433
  */
369
434
  async unpublish(context, id) {
370
- // Permission check (unless overrideAccess)
371
- await this.permissions.canPublish(context, this.collectionName);
435
+ // Fetch target for policy inspection before mutating.
436
+ const existing = await this.databaseAdapter.findByDocIdAdvanced(context.organizationId, id);
437
+ if (!existing)
438
+ return null;
439
+ await this.permissions.canUnpublish(context, this.collectionName, existing);
372
440
  const document = await this.databaseAdapter.unpublishDoc(context.organizationId, id);
373
441
  if (!document) {
374
442
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/local-api/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;;GAGG;AACH,MAAM,WAAW,WAAW;IAE3B,CAAC,cAAc,EAAE,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;CAGjD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,QAAQ;IAWnB,OAAO,CAAC,MAAM;IAVR,WAAW,EAAE,WAAW,CAAqB;IACpD,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,gBAAgB,CAAmB;IACpC,cAAc,EAAE,cAAc,CAAC;IACtC,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,OAAO,CAA0B;gBAGhC,MAAM,EAAE,SAAS,EACzB,WAAW,EAAE,eAAe,EAC5B,aAAa,CAAC,EAAE,eAAe;IAwBhC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAgC7B;;;OAGG;IACH,OAAO,CAAC,UAAU;IAOlB;;OAEG;IACH,kBAAkB,IAAI,MAAM,EAAE;IAI9B;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIpC;;OAEG;IACH,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAIzD;;;;OAIG;IACG,gBAAgB,CACrB,OAAO,EAAE,eAAe,EACxB,EAAE,EAAE,MAAM,EACV,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,GACrC,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CAetD;AAKD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAC7B,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,eAAe,EAC5B,aAAa,CAAC,EAAE,eAAe,GAC7B,QAAQ,CAGV;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,IAAI,QAAQ,CAKtC;AAGD,OAAO,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACnE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/local-api/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;;GAGG;AACH,MAAM,WAAW,WAAW;IAE3B,CAAC,cAAc,EAAE,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;CAGjD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,QAAQ;IAWnB,OAAO,CAAC,MAAM;IAVR,WAAW,EAAE,WAAW,CAAqB;IACpD,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,gBAAgB,CAAmB;IACpC,cAAc,EAAE,cAAc,CAAC;IACtC,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,OAAO,CAA0B;gBAGhC,MAAM,EAAE,SAAS,EACzB,WAAW,EAAE,eAAe,EAC5B,aAAa,CAAC,EAAE,eAAe;IAwBhC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAgD7B;;;OAGG;IACH,OAAO,CAAC,UAAU;IAOlB;;OAEG;IACH,kBAAkB,IAAI,MAAM,EAAE;IAI9B;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIpC;;OAEG;IACH,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAIzD;;;;OAIG;IACG,gBAAgB,CACrB,OAAO,EAAE,eAAe,EACxB,EAAE,EAAE,MAAM,EACV,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,GACrC,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CAetD;AAKD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAC7B,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,eAAe,EAC5B,aAAa,CAAC,EAAE,eAAe,GAC7B,QAAQ,CAGV;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,IAAI,QAAQ,CAKtC;AAGD,OAAO,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACnE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
@@ -1,45 +1,45 @@
1
1
  import type { LocalAPIContext } from './types.js';
2
2
  import type { SchemaType } from '../types/schemas.js';
3
+ import type { Document } from '../types/document.js';
3
4
  import type { CMSConfig } from '../types/config.js';
4
- /**
5
- * Permission error thrown when access is denied
6
- */
7
5
  export declare class PermissionError extends Error {
8
6
  readonly operation: string;
9
7
  readonly resource: string;
10
8
  constructor(message: string, operation: string, resource: string);
11
9
  }
12
- /**
13
- * Checks permissions for Local API operations
14
- */
15
10
  export declare class PermissionChecker {
16
11
  private _config;
17
12
  private schemas;
18
13
  constructor(_config: CMSConfig, schemas: Map<string, SchemaType>);
19
- /**
20
- * Get the CMS config
21
- * Available for future per-collection permission logic
22
- */
23
14
  get config(): CMSConfig;
15
+ canRead(context: LocalAPIContext, collectionName: string, doc?: Document): Promise<void>;
16
+ canCreate(context: LocalAPIContext, collectionName: string): Promise<void>;
17
+ canUpdate(context: LocalAPIContext, collectionName: string, doc?: Document): Promise<void>;
24
18
  /**
25
- * Check if the current context has read permissions for a collection
26
- */
27
- canRead(context: LocalAPIContext, collectionName: string): Promise<void>;
28
- /**
29
- * Check if the current context has write permissions (create/update)
30
- */
31
- canWrite(context: LocalAPIContext, collectionName: string): Promise<void>;
32
- /**
33
- * Check if the current context has delete permissions
34
- */
35
- canDelete(context: LocalAPIContext, collectionName: string): Promise<void>;
36
- /**
37
- * Check if the current context has publish permissions
19
+ * @deprecated Prefer `canCreate` or `canUpdate` this method conflates the
20
+ * two and was only kept for legacy call sites. It now aliases `canUpdate`
21
+ * for safety (update is the more restrictive default for mutation).
38
22
  */
39
- canPublish(context: LocalAPIContext, collectionName: string): Promise<void>;
23
+ canWrite(context: LocalAPIContext, collectionName: string, doc?: Document): Promise<void>;
24
+ canDelete(context: LocalAPIContext, collectionName: string, doc?: Document): Promise<void>;
25
+ canPublish(context: LocalAPIContext, collectionName: string, doc?: Document): Promise<void>;
26
+ canUnpublish(context: LocalAPIContext, collectionName: string, doc?: Document): Promise<void>;
27
+ validateCollection(collectionName: string): void;
28
+ private assert;
29
+ private logDenial;
30
+ private requireAuth;
40
31
  /**
41
- * Validate that a collection exists in the schema
32
+ * Evaluate an access rule for a given operation.
33
+ *
34
+ * Three kinds of declared rules:
35
+ * - `OrganizationRole[]` — role allowlist (as before).
36
+ * - `(ctx) => boolean` — arbitrary policy, receives auth + optional doc.
37
+ * Use for ownership rules like `doc.createdBy === auth.user.id`.
38
+ * - `undefined` — fall back to capability check.
39
+ *
40
+ * A declared-but-excluded role/policy for a session caller is an explicit
41
+ * deny; the capability map does not re-grant access.
42
42
  */
43
- validateCollection(collectionName: string): void;
43
+ private isAllowed;
44
44
  }
45
45
  //# sourceMappingURL=permissions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/lib/local-api/permissions.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;GAEG;AACH,qBAAa,eAAgB,SAAQ,KAAK;aAGxB,SAAS,EAAE,MAAM;aACjB,QAAQ,EAAE,MAAM;gBAFhC,OAAO,EAAE,MAAM,EACC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM;CAKjC;AAED;;GAEG;AACH,qBAAa,iBAAiB;IAE5B,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,OAAO;gBADP,OAAO,EAAE,SAAS,EAClB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC;IAGzC;;;OAGG;IACH,IAAI,MAAM,IAAI,SAAS,CAEtB;IAED;;OAEG;IACG,OAAO,CAAC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB9E;;OAEG;IACG,QAAQ,CAAC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA6B/E;;OAEG;IACG,SAAS,CAAC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BhF;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BjF;;OAEG;IACH,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;CAShD"}
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/lib/local-api/permissions.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,kBAAkB,CAAC;AACjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAKjD,qBAAa,eAAgB,SAAQ,KAAK;aAGxB,SAAS,EAAE,MAAM;aACjB,QAAQ,EAAE,MAAM;gBAFhC,OAAO,EAAE,MAAM,EACC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM;CAKjC;AAmCD,qBAAa,iBAAiB;IAE5B,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,OAAO;gBADP,OAAO,EAAE,SAAS,EAClB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC;IAGzC,IAAI,MAAM,IAAI,SAAS,CAEtB;IAEK,OAAO,CAAC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxF,SAAS,CAAC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1E,SAAS,CAAC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhG;;;;OAIG;IACG,QAAQ,CAAC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzF,SAAS,CAAC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1F,UAAU,CACf,OAAO,EAAE,eAAe,EACxB,cAAc,EAAE,MAAM,EACtB,GAAG,CAAC,EAAE,QAAQ,GACZ,OAAO,CAAC,IAAI,CAAC;IAIV,YAAY,CACjB,OAAO,EAAE,eAAe,EACxB,cAAc,EAAE,MAAM,EACtB,GAAG,CAAC,EAAE,QAAQ,GACZ,OAAO,CAAC,IAAI,CAAC;IAIhB,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;YAYlC,MAAM;IAmBpB,OAAO,CAAC,SAAS;IAsBjB,OAAO,CAAC,WAAW;IAenB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,SAAS;CAmCjB"}
@@ -1,9 +1,16 @@
1
1
  // local-api/permissions.ts
2
2
  //
3
- // Permission checking for the Local API layer
4
- /**
5
- * Permission error thrown when access is denied
6
- */
3
+ // Permission checking for the Local API layer.
4
+ //
5
+ // Every operation resolves to a capability. A check passes when either:
6
+ // 1. The collection schema declares `access[operation]` and the caller's
7
+ // effective organization role appears in the list, OR
8
+ // 2. `auth.capabilities` (hydrated by the auth hook via RolesService)
9
+ // contains the capability.
10
+ //
11
+ // `overrideAccess` short-circuits everything for system operations.
12
+ import { hasCapability, effectiveOrganizationRole } from '../types/capabilities.js';
13
+ import { cmsLogger } from '../utils/logger.js';
7
14
  export class PermissionError extends Error {
8
15
  operation;
9
16
  resource;
@@ -14,9 +21,33 @@ export class PermissionError extends Error {
14
21
  this.name = 'PermissionError';
15
22
  }
16
23
  }
17
- /**
18
- * Checks permissions for Local API operations
19
- */
24
+ const OPERATION_CAPABILITY = {
25
+ read: 'document.read',
26
+ create: 'document.create',
27
+ update: 'document.update',
28
+ delete: 'document.delete',
29
+ publish: 'document.publish',
30
+ unpublish: 'document.unpublish'
31
+ };
32
+ // Human-readable action phrasing for error messages. Keep in sync with
33
+ // OPERATION_CAPABILITY and any new Operation keys.
34
+ const OPERATION_LABEL = {
35
+ read: 'view',
36
+ create: 'create',
37
+ update: 'edit',
38
+ delete: 'delete',
39
+ publish: 'publish',
40
+ unpublish: 'unpublish'
41
+ };
42
+ function denialMessage(operations, collectionName) {
43
+ const actions = operations.map((op) => OPERATION_LABEL[op]);
44
+ const action = actions.length === 1
45
+ ? actions[0]
46
+ : actions.length === 2
47
+ ? `${actions[0]} or ${actions[1]}`
48
+ : `${actions.slice(0, -1).join(', ')}, or ${actions[actions.length - 1]}`;
49
+ return `You don't have permission to ${action} ${collectionName} documents. Ask an admin if you need access.`;
50
+ }
20
51
  export class PermissionChecker {
21
52
  _config;
22
53
  schemas;
@@ -24,93 +55,104 @@ export class PermissionChecker {
24
55
  this._config = _config;
25
56
  this.schemas = schemas;
26
57
  }
27
- /**
28
- * Get the CMS config
29
- * Available for future per-collection permission logic
30
- */
31
58
  get config() {
32
59
  return this._config;
33
60
  }
34
- /**
35
- * Check if the current context has read permissions for a collection
36
- */
37
- async canRead(context, collectionName) {
38
- // Always allow if overrideAccess is true (system operations)
39
- if (context.overrideAccess) {
40
- return;
41
- }
42
- // Require authentication for read operations
43
- if (!context.user) {
44
- throw new PermissionError('Authentication required for read operations', 'read', collectionName);
45
- }
46
- // All authenticated users can read (role-based restrictions handled by RLS)
47
- // Additional per-collection permission logic can be added here in the future
48
- return;
61
+ async canRead(context, collectionName, doc) {
62
+ await this.assert(context, collectionName, 'read', doc);
49
63
  }
50
- /**
51
- * Check if the current context has write permissions (create/update)
52
- */
53
- async canWrite(context, collectionName) {
54
- // Always allow if overrideAccess is true (system operations)
55
- if (context.overrideAccess) {
56
- return;
57
- }
58
- // Require authentication for write operations
59
- if (!context.user) {
60
- throw new PermissionError('Authentication required for write operations', 'write', collectionName);
61
- }
62
- // Check if user has write role (not a viewer)
63
- // Viewers have read-only access
64
- if (context.user.role === 'viewer') {
65
- throw new PermissionError('Write permission denied. Viewers have read-only access.', 'write', collectionName);
66
- }
67
- // Additional per-collection permission logic can be added here in the future
68
- return;
64
+ async canCreate(context, collectionName) {
65
+ // `create` has no target doc by definition.
66
+ await this.assert(context, collectionName, 'create');
67
+ }
68
+ async canUpdate(context, collectionName, doc) {
69
+ await this.assert(context, collectionName, 'update', doc);
69
70
  }
70
71
  /**
71
- * Check if the current context has delete permissions
72
+ * @deprecated Prefer `canCreate` or `canUpdate` this method conflates the
73
+ * two and was only kept for legacy call sites. It now aliases `canUpdate`
74
+ * for safety (update is the more restrictive default for mutation).
72
75
  */
73
- async canDelete(context, collectionName) {
74
- // Always allow if overrideAccess is true (system operations)
75
- if (context.overrideAccess) {
76
- return;
77
- }
78
- // Require authentication for delete operations
79
- if (!context.user) {
80
- throw new PermissionError('Authentication required for delete operations', 'delete', collectionName);
81
- }
82
- // Viewers have read-only access
83
- if (context.user.role === 'viewer') {
84
- throw new PermissionError('Delete permission denied. Viewers have read-only access.', 'delete', collectionName);
76
+ async canWrite(context, collectionName, doc) {
77
+ await this.canUpdate(context, collectionName, doc);
78
+ }
79
+ async canDelete(context, collectionName, doc) {
80
+ await this.assert(context, collectionName, 'delete', doc);
81
+ }
82
+ async canPublish(context, collectionName, doc) {
83
+ await this.assert(context, collectionName, 'publish', doc);
84
+ }
85
+ async canUnpublish(context, collectionName, doc) {
86
+ await this.assert(context, collectionName, 'unpublish', doc);
87
+ }
88
+ validateCollection(collectionName) {
89
+ if (!this.schemas.has(collectionName)) {
90
+ throw new Error(`Collection "${collectionName}" not found in schema. Available collections: ${Array.from(this.schemas.keys()).join(', ')}`);
85
91
  }
86
- // Additional per-collection permission logic can be added here in the future
87
- return;
88
92
  }
89
- /**
90
- * Check if the current context has publish permissions
91
- */
92
- async canPublish(context, collectionName) {
93
- // Always allow if overrideAccess is true (system operations)
94
- if (context.overrideAccess) {
93
+ // ---- internals -----------------------------------------------------------
94
+ async assert(context, collectionName, operation, doc) {
95
+ if (context.overrideAccess)
95
96
  return;
97
+ const auth = this.requireAuth(context, operation, collectionName);
98
+ if (this.isAllowed(auth, collectionName, operation, doc))
99
+ return;
100
+ this.logDenial(auth, operation, collectionName, [OPERATION_CAPABILITY[operation]]);
101
+ throw new PermissionError(denialMessage([operation], collectionName), operation, collectionName);
102
+ }
103
+ logDenial(auth, operation, collectionName, missingCapabilities) {
104
+ const who = auth.type === 'session'
105
+ ? `user=${auth.user.id} role="${auth.organizationRole}"`
106
+ : auth.type === 'api_key'
107
+ ? `apiKey=${auth.keyId}`
108
+ : auth.type;
109
+ const caps = 'capabilities' in auth && Array.isArray(auth.capabilities)
110
+ ? auth.capabilities.join(',')
111
+ : '(none)';
112
+ cmsLogger.warn('[RBAC]', `DENY ${operation} on "${collectionName}" — ${who} has=[${caps}] needs=[${missingCapabilities.join('|')}]`);
113
+ }
114
+ requireAuth(context, operation, collectionName) {
115
+ if (!context.auth) {
116
+ throw new PermissionError(`You must be signed in to ${OPERATION_LABEL[operation]} ${collectionName} documents.`, operation, collectionName);
96
117
  }
97
- // Require authentication for publish operations
98
- if (!context.user) {
99
- throw new PermissionError('Authentication required for publish operations', 'publish', collectionName);
100
- }
101
- // Check if user has write role (not a viewer)
102
- if (context.user.role === 'viewer') {
103
- throw new PermissionError('Publish permission denied. Viewers have read-only access.', 'publish', collectionName);
104
- }
105
- // Additional per-collection permission logic can be added here in the future
106
- return;
118
+ return context.auth;
107
119
  }
108
120
  /**
109
- * Validate that a collection exists in the schema
121
+ * Evaluate an access rule for a given operation.
122
+ *
123
+ * Three kinds of declared rules:
124
+ * - `OrganizationRole[]` — role allowlist (as before).
125
+ * - `(ctx) => boolean` — arbitrary policy, receives auth + optional doc.
126
+ * Use for ownership rules like `doc.createdBy === auth.user.id`.
127
+ * - `undefined` — fall back to capability check.
128
+ *
129
+ * A declared-but-excluded role/policy for a session caller is an explicit
130
+ * deny; the capability map does not re-grant access.
110
131
  */
111
- validateCollection(collectionName) {
112
- if (!this.schemas.has(collectionName)) {
113
- throw new Error(`Collection "${collectionName}" not found in schema. Available collections: ${Array.from(this.schemas.keys()).join(', ')}`);
132
+ isAllowed(auth, collectionName, operation, doc) {
133
+ const schema = this.schemas.get(collectionName);
134
+ const declared = schema?.access?.[operation];
135
+ if (declared) {
136
+ if (typeof declared === 'function') {
137
+ try {
138
+ return declared({ auth, doc });
139
+ }
140
+ catch (err) {
141
+ // A throwing policy is a bug, not a grant — log and deny.
142
+ cmsLogger.error('[RBAC]', `access policy for "${collectionName}.${operation}" threw:`, err);
143
+ return false;
144
+ }
145
+ }
146
+ // Array-shaped rule (role allowlist)
147
+ const role = effectiveOrganizationRole(auth);
148
+ if (role && declared.includes(role))
149
+ return true;
150
+ if (auth.type === 'session' && role) {
151
+ return false;
152
+ }
153
+ // Unrecognized role (custom roles, API keys) → fall through to
154
+ // capability check so they can still gain access via capabilities.
114
155
  }
156
+ return hasCapability(auth, OPERATION_CAPABILITY[operation]);
115
157
  }
116
158
  }
@@ -0,0 +1,44 @@
1
+ import type { Capability } from './types/capabilities.js';
2
+ export interface PermissionsContext {
3
+ /** Current capability list (reactive). */
4
+ readonly capabilities: readonly string[];
5
+ /**
6
+ * Effective organization role name (reactive). `null` when the caller has
7
+ * no role (API key, unauthenticated). Used for role-name-based checks
8
+ * such as field-level access lists.
9
+ */
10
+ readonly role: string | null;
11
+ /** True if the session has the named capability. */
12
+ can(cap: Capability): boolean;
13
+ /** True if the session has at least one of the capabilities. */
14
+ canAny(...caps: Capability[]): boolean;
15
+ /** True if the session has every one of the capabilities. */
16
+ canAll(...caps: Capability[]): boolean;
17
+ }
18
+ /**
19
+ * Publish a capability context to descendants.
20
+ *
21
+ * `getCapabilities` is a getter rather than a static array so reactive
22
+ * sources (Svelte state, `$page.data.rbac.capabilities`) propagate: every
23
+ * `can()` call re-reads through the closure.
24
+ *
25
+ * @example
26
+ * ```svelte
27
+ * <script>
28
+ * import { page } from '$app/state';
29
+ * import { setPermissionsContext } from '@aphexcms/cms-core';
30
+ * setPermissionsContext(() => page.data.rbac?.capabilities ?? []);
31
+ * </script>
32
+ * ```
33
+ */
34
+ export declare function setPermissionsContext(getCapabilities: () => readonly string[], getRole?: () => string | null): PermissionsContext;
35
+ /**
36
+ * Read the capability context set by an ancestor.
37
+ *
38
+ * When no provider is present (e.g. a component rendered in isolation during
39
+ * tests or Storybook), returns a deny-all stub with a one-time dev warning
40
+ * rather than throwing — that way consumers degrade to "no affordances shown"
41
+ * instead of crashing the tree.
42
+ */
43
+ export declare function usePermissions(): PermissionsContext;
44
+ //# sourceMappingURL=permissions-context.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions-context.svelte.d.ts","sourceRoot":"","sources":["../src/lib/permissions-context.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAIvD,MAAM,WAAW,kBAAkB;IAClC,0CAA0C;IAC1C,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oDAAoD;IACpD,GAAG,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC;IAC9B,gEAAgE;IAChE,MAAM,CAAC,GAAG,IAAI,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IACvC,6DAA6D;IAC7D,MAAM,CAAC,GAAG,IAAI,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;CACvC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACpC,eAAe,EAAE,MAAM,SAAS,MAAM,EAAE,EACxC,OAAO,GAAE,MAAM,MAAM,GAAG,IAAiB,GACvC,kBAAkB,CAsBpB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,IAAI,kBAAkB,CAKnD"}