@aphexcms/cms-core 9.8.1 → 9.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/dist/api/organizations.d.ts +2 -1
  2. package/dist/api/organizations.d.ts.map +1 -1
  3. package/dist/api/organizations.js +2 -1
  4. package/dist/api/schemas/jobs.d.ts +15 -0
  5. package/dist/api/schemas/jobs.d.ts.map +1 -0
  6. package/dist/api/schemas/jobs.js +17 -0
  7. package/dist/api/schemas/user.d.ts.map +1 -1
  8. package/dist/api/schemas/user.js +6 -2
  9. package/dist/auth/account-deletion.d.ts +35 -0
  10. package/dist/auth/account-deletion.d.ts.map +1 -0
  11. package/dist/auth/account-deletion.js +65 -0
  12. package/dist/auth/bootstrap.d.ts +104 -0
  13. package/dist/auth/bootstrap.d.ts.map +1 -0
  14. package/dist/auth/bootstrap.js +164 -0
  15. package/dist/auth/instance-state.d.ts +13 -0
  16. package/dist/auth/instance-state.d.ts.map +1 -0
  17. package/dist/auth/instance-state.js +30 -0
  18. package/dist/auth/invitation-status.d.ts +25 -0
  19. package/dist/auth/invitation-status.d.ts.map +1 -0
  20. package/dist/auth/invitation-status.js +41 -0
  21. package/dist/auth/sign-up-policy.d.ts +62 -0
  22. package/dist/auth/sign-up-policy.d.ts.map +1 -0
  23. package/dist/auth/sign-up-policy.js +120 -0
  24. package/dist/components/admin/ActivityView.svelte +263 -13
  25. package/dist/components/admin/ActivityView.svelte.d.ts +10 -1
  26. package/dist/components/admin/ActivityView.svelte.d.ts.map +1 -1
  27. package/dist/db/interfaces/events.d.ts +29 -2
  28. package/dist/db/interfaces/events.d.ts.map +1 -1
  29. package/dist/db/interfaces/index.d.ts +1 -0
  30. package/dist/db/interfaces/index.d.ts.map +1 -1
  31. package/dist/db/interfaces/index.js +1 -0
  32. package/dist/db/interfaces/instance.d.ts +29 -0
  33. package/dist/db/interfaces/instance.d.ts.map +1 -1
  34. package/dist/db/interfaces/instance.js +8 -1
  35. package/dist/events/built-in-consumers.d.ts +20 -0
  36. package/dist/events/built-in-consumers.d.ts.map +1 -0
  37. package/dist/events/built-in-consumers.js +49 -0
  38. package/dist/events/catalog.d.ts +16 -0
  39. package/dist/events/catalog.d.ts.map +1 -1
  40. package/dist/events/catalog.js +17 -0
  41. package/dist/events/consumer.d.ts +14 -0
  42. package/dist/events/consumer.d.ts.map +1 -1
  43. package/dist/events/consumer.js +1 -0
  44. package/dist/events/emit.d.ts +23 -0
  45. package/dist/events/emit.d.ts.map +1 -1
  46. package/dist/events/emit.js +32 -1
  47. package/dist/events/index.d.ts +2 -0
  48. package/dist/events/index.d.ts.map +1 -1
  49. package/dist/events/index.js +2 -0
  50. package/dist/field-validation/utils.d.ts +27 -4
  51. package/dist/field-validation/utils.d.ts.map +1 -1
  52. package/dist/field-validation/utils.js +78 -10
  53. package/dist/index.d.ts +1 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +3 -0
  56. package/dist/jobs/run-batch.d.ts +7 -1
  57. package/dist/jobs/run-batch.d.ts.map +1 -1
  58. package/dist/jobs/run-batch.js +3 -1
  59. package/dist/lib/api/organizations.d.ts +2 -1
  60. package/dist/lib/api/organizations.d.ts.map +1 -1
  61. package/dist/lib/api/organizations.js +2 -1
  62. package/dist/lib/api/organizations.js.map +1 -1
  63. package/dist/lib/api/schemas/api-keys.d.ts +4 -4
  64. package/dist/lib/api/schemas/jobs.d.ts +15 -0
  65. package/dist/lib/api/schemas/jobs.d.ts.map +1 -0
  66. package/dist/lib/api/schemas/jobs.js +18 -0
  67. package/dist/lib/api/schemas/jobs.js.map +1 -0
  68. package/dist/lib/api/schemas/user.d.ts.map +1 -1
  69. package/dist/lib/api/schemas/user.js +6 -2
  70. package/dist/lib/api/schemas/user.js.map +1 -1
  71. package/dist/lib/auth/account-deletion.d.ts +35 -0
  72. package/dist/lib/auth/account-deletion.d.ts.map +1 -0
  73. package/dist/lib/auth/account-deletion.js +66 -0
  74. package/dist/lib/auth/account-deletion.js.map +1 -0
  75. package/dist/lib/auth/bootstrap.d.ts +104 -0
  76. package/dist/lib/auth/bootstrap.d.ts.map +1 -0
  77. package/dist/lib/auth/bootstrap.js +165 -0
  78. package/dist/lib/auth/bootstrap.js.map +1 -0
  79. package/dist/lib/auth/instance-state.d.ts +13 -0
  80. package/dist/lib/auth/instance-state.d.ts.map +1 -0
  81. package/dist/lib/auth/instance-state.js +31 -0
  82. package/dist/lib/auth/instance-state.js.map +1 -0
  83. package/dist/lib/auth/invitation-status.d.ts +25 -0
  84. package/dist/lib/auth/invitation-status.d.ts.map +1 -0
  85. package/dist/lib/auth/invitation-status.js +42 -0
  86. package/dist/lib/auth/invitation-status.js.map +1 -0
  87. package/dist/lib/auth/sign-up-policy.d.ts +62 -0
  88. package/dist/lib/auth/sign-up-policy.d.ts.map +1 -0
  89. package/dist/lib/auth/sign-up-policy.js +121 -0
  90. package/dist/lib/auth/sign-up-policy.js.map +1 -0
  91. package/dist/lib/db/interfaces/events.d.ts +29 -2
  92. package/dist/lib/db/interfaces/events.d.ts.map +1 -1
  93. package/dist/lib/db/interfaces/index.d.ts +1 -0
  94. package/dist/lib/db/interfaces/index.d.ts.map +1 -1
  95. package/dist/lib/db/interfaces/index.js +1 -0
  96. package/dist/lib/db/interfaces/index.js.map +1 -1
  97. package/dist/lib/db/interfaces/instance.d.ts +29 -0
  98. package/dist/lib/db/interfaces/instance.d.ts.map +1 -1
  99. package/dist/lib/db/interfaces/instance.js +8 -1
  100. package/dist/lib/db/interfaces/instance.js.map +1 -1
  101. package/dist/lib/events/built-in-consumers.d.ts +20 -0
  102. package/dist/lib/events/built-in-consumers.d.ts.map +1 -0
  103. package/dist/lib/events/built-in-consumers.js +50 -0
  104. package/dist/lib/events/built-in-consumers.js.map +1 -0
  105. package/dist/lib/events/catalog.d.ts +16 -0
  106. package/dist/lib/events/catalog.d.ts.map +1 -1
  107. package/dist/lib/events/catalog.js +17 -0
  108. package/dist/lib/events/catalog.js.map +1 -1
  109. package/dist/lib/events/consumer.d.ts +14 -0
  110. package/dist/lib/events/consumer.d.ts.map +1 -1
  111. package/dist/lib/events/consumer.js +1 -0
  112. package/dist/lib/events/consumer.js.map +1 -1
  113. package/dist/lib/events/emit.d.ts +23 -0
  114. package/dist/lib/events/emit.d.ts.map +1 -1
  115. package/dist/lib/events/emit.js +32 -1
  116. package/dist/lib/events/emit.js.map +1 -1
  117. package/dist/lib/events/index.d.ts +2 -0
  118. package/dist/lib/events/index.d.ts.map +1 -1
  119. package/dist/lib/events/index.js +2 -0
  120. package/dist/lib/events/index.js.map +1 -1
  121. package/dist/lib/field-validation/utils.d.ts +27 -4
  122. package/dist/lib/field-validation/utils.d.ts.map +1 -1
  123. package/dist/lib/field-validation/utils.js +78 -10
  124. package/dist/lib/field-validation/utils.js.map +1 -1
  125. package/dist/lib/index.d.ts +1 -0
  126. package/dist/lib/index.d.ts.map +1 -1
  127. package/dist/lib/index.js +3 -0
  128. package/dist/lib/index.js.map +1 -1
  129. package/dist/lib/jobs/run-batch.d.ts +7 -1
  130. package/dist/lib/jobs/run-batch.d.ts.map +1 -1
  131. package/dist/lib/jobs/run-batch.js +3 -1
  132. package/dist/lib/jobs/run-batch.js.map +1 -1
  133. package/dist/lib/local-api/collection-api.d.ts +42 -1
  134. package/dist/lib/local-api/collection-api.d.ts.map +1 -1
  135. package/dist/lib/local-api/collection-api.js +68 -8
  136. package/dist/lib/local-api/collection-api.js.map +1 -1
  137. package/dist/lib/local-api/index.d.ts +1 -1
  138. package/dist/lib/local-api/index.d.ts.map +1 -1
  139. package/dist/lib/local-api/index.js +1 -1
  140. package/dist/lib/local-api/index.js.map +1 -1
  141. package/dist/lib/plugins/resolver.d.ts.map +1 -1
  142. package/dist/lib/plugins/resolver.js +7 -1
  143. package/dist/lib/plugins/resolver.js.map +1 -1
  144. package/dist/lib/server/api/rate-limit.d.ts +38 -0
  145. package/dist/lib/server/api/rate-limit.d.ts.map +1 -0
  146. package/dist/lib/server/api/rate-limit.js +71 -0
  147. package/dist/lib/server/api/rate-limit.js.map +1 -0
  148. package/dist/lib/server/api/routes/documents-by-id.d.ts.map +1 -1
  149. package/dist/lib/server/api/routes/documents-by-id.js +10 -1
  150. package/dist/lib/server/api/routes/documents-by-id.js.map +1 -1
  151. package/dist/lib/server/api/routes/documents-publish.d.ts.map +1 -1
  152. package/dist/lib/server/api/routes/documents-publish.js +10 -0
  153. package/dist/lib/server/api/routes/documents-publish.js.map +1 -1
  154. package/dist/lib/server/api/routes/documents.d.ts.map +1 -1
  155. package/dist/lib/server/api/routes/documents.js +10 -0
  156. package/dist/lib/server/api/routes/documents.js.map +1 -1
  157. package/dist/lib/server/api/routes/jobs.d.ts.map +1 -1
  158. package/dist/lib/server/api/routes/jobs.js +174 -20
  159. package/dist/lib/server/api/routes/jobs.js.map +1 -1
  160. package/dist/lib/server/api/routes/organizations-by-id.d.ts.map +1 -1
  161. package/dist/lib/server/api/routes/organizations-by-id.js +61 -3
  162. package/dist/lib/server/api/routes/organizations-by-id.js.map +1 -1
  163. package/dist/lib/server/api/routes/organizations-invitations.d.ts.map +1 -1
  164. package/dist/lib/server/api/routes/organizations-invitations.js +31 -2
  165. package/dist/lib/server/api/routes/organizations-invitations.js.map +1 -1
  166. package/dist/lib/server/api/routes/resolve-organization-names.d.ts +17 -0
  167. package/dist/lib/server/api/routes/resolve-organization-names.d.ts.map +1 -0
  168. package/dist/lib/server/api/routes/resolve-organization-names.js +26 -0
  169. package/dist/lib/server/api/routes/resolve-organization-names.js.map +1 -0
  170. package/dist/lib/server/api/routes/user.d.ts +0 -8
  171. package/dist/lib/server/api/routes/user.d.ts.map +1 -1
  172. package/dist/lib/server/api/routes/user.js +144 -0
  173. package/dist/lib/server/api/routes/user.js.map +1 -1
  174. package/dist/lib/server/index.d.ts +6 -1
  175. package/dist/lib/server/index.d.ts.map +1 -1
  176. package/dist/lib/server/index.js +9 -1
  177. package/dist/lib/server/index.js.map +1 -1
  178. package/dist/lib/types/capabilities.d.ts +14 -0
  179. package/dist/lib/types/capabilities.d.ts.map +1 -1
  180. package/dist/lib/types/capabilities.js +37 -18
  181. package/dist/lib/types/capabilities.js.map +1 -1
  182. package/dist/lib/types/events.d.ts +27 -4
  183. package/dist/lib/types/events.d.ts.map +1 -1
  184. package/dist/lib/utils/fetch-remote-file.d.ts.map +1 -1
  185. package/dist/lib/utils/fetch-remote-file.js +32 -3
  186. package/dist/lib/utils/fetch-remote-file.js.map +1 -1
  187. package/dist/local-api/collection-api.d.ts +42 -1
  188. package/dist/local-api/collection-api.d.ts.map +1 -1
  189. package/dist/local-api/collection-api.js +68 -8
  190. package/dist/local-api/index.d.ts +1 -1
  191. package/dist/local-api/index.d.ts.map +1 -1
  192. package/dist/local-api/index.js +1 -1
  193. package/dist/plugins/resolver.d.ts.map +1 -1
  194. package/dist/plugins/resolver.js +7 -1
  195. package/dist/server/api/rate-limit.d.ts +38 -0
  196. package/dist/server/api/rate-limit.d.ts.map +1 -0
  197. package/dist/server/api/rate-limit.js +70 -0
  198. package/dist/server/api/routes/documents-by-id.d.ts.map +1 -1
  199. package/dist/server/api/routes/documents-by-id.js +10 -1
  200. package/dist/server/api/routes/documents-publish.d.ts.map +1 -1
  201. package/dist/server/api/routes/documents-publish.js +10 -0
  202. package/dist/server/api/routes/documents.d.ts.map +1 -1
  203. package/dist/server/api/routes/documents.js +10 -0
  204. package/dist/server/api/routes/jobs.d.ts.map +1 -1
  205. package/dist/server/api/routes/jobs.js +174 -20
  206. package/dist/server/api/routes/organizations-by-id.d.ts.map +1 -1
  207. package/dist/server/api/routes/organizations-by-id.js +61 -3
  208. package/dist/server/api/routes/organizations-invitations.d.ts.map +1 -1
  209. package/dist/server/api/routes/organizations-invitations.js +31 -2
  210. package/dist/server/api/routes/resolve-organization-names.d.ts +17 -0
  211. package/dist/server/api/routes/resolve-organization-names.d.ts.map +1 -0
  212. package/dist/server/api/routes/resolve-organization-names.js +25 -0
  213. package/dist/server/api/routes/user.d.ts +0 -8
  214. package/dist/server/api/routes/user.d.ts.map +1 -1
  215. package/dist/server/api/routes/user.js +144 -0
  216. package/dist/server/index.d.ts +6 -1
  217. package/dist/server/index.d.ts.map +1 -1
  218. package/dist/server/index.js +9 -1
  219. package/dist/types/capabilities.d.ts +14 -0
  220. package/dist/types/capabilities.d.ts.map +1 -1
  221. package/dist/types/capabilities.js +37 -18
  222. package/dist/types/events.d.ts +27 -4
  223. package/dist/types/events.d.ts.map +1 -1
  224. package/dist/utils/fetch-remote-file.d.ts.map +1 -1
  225. package/dist/utils/fetch-remote-file.js +32 -3
  226. package/package.json +6 -6
@@ -65,7 +65,8 @@ export declare class OrganizationsApi {
65
65
  success: boolean;
66
66
  }>>;
67
67
  /**
68
- * Delete an organization (super_admin only)
68
+ * Delete an organization, its media, and every membership in it. Owners only
69
+ * enforced by the route, not here.
69
70
  */
70
71
  static remove(id: string): Promise<ApiResponse<{
71
72
  success: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"organizations.d.ts","sourceRoot":"","sources":["../../src/lib/api/organizations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAChG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,KAAK,EACX,yBAAyB,EACzB,yBAAyB,EACzB,yBAAyB,EACzB,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,EACnB,uBAAuB,EACvB,MAAM,yBAAyB,CAAC;AAIjC,MAAM,MAAM,sBAAsB,GAAG,yBAAyB,CAAC;AAC/D,MAAM,MAAM,sBAAsB,GAAG,yBAAyB,CAAC;AAC/D,MAAM,MAAM,sBAAsB,GAAG,yBAAyB,CAAC;AAC/D,MAAM,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AACnD,MAAM,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAC3D,MAAM,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AACnD,MAAM,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAE3D,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACzD,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,IAAI,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,gBAAgB;IAC5B;;OAEG;WACU,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC,oBAAoB,EAAE,CAAC,CAAC;IAIjE;;OAEG;WACU,MAAM,CAAC,IAAI,EAAE,yBAAyB,GAAG,OAAO,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;IAIxF;;OAEG;WACU,MAAM,CAAC,IAAI,EAAE,yBAAyB,GAAG,OAAO,CAAC,WAAW,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAIhG;;OAEG;WACU,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;IAIpE;;OAEG;WACU,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;IAY5D;;OAEG;WACU,UAAU,IAAI,OAAO,CAAC,WAAW,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAIrE;;OAEG;WACU,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC;IAI9F;;OAEG;WACU,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,WAAW,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAIhG;;OAEG;WACU,gBAAgB,CAC5B,IAAI,EAAE,uBAAuB,GAC3B,OAAO,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC;IAI3C;;OAEG;WACU,MAAM,CAClB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,yBAAyB,GAC7B,OAAO,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;IAIrC;;OAEG;WACU,gBAAgB,CAC5B,IAAI,EAAE,uBAAuB,GAC3B,OAAO,CAAC,WAAW,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAI7C;;OAEG;WACU,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAG3E;AAGD,eAAO,MAAM,aAAa;;;;;;;;;;;;;CAazB,CAAC"}
1
+ {"version":3,"file":"organizations.d.ts","sourceRoot":"","sources":["../../src/lib/api/organizations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAChG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,KAAK,EACX,yBAAyB,EACzB,yBAAyB,EACzB,yBAAyB,EACzB,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,EACnB,uBAAuB,EACvB,MAAM,yBAAyB,CAAC;AAIjC,MAAM,MAAM,sBAAsB,GAAG,yBAAyB,CAAC;AAC/D,MAAM,MAAM,sBAAsB,GAAG,yBAAyB,CAAC;AAC/D,MAAM,MAAM,sBAAsB,GAAG,yBAAyB,CAAC;AAC/D,MAAM,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AACnD,MAAM,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAC3D,MAAM,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AACnD,MAAM,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAE3D,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACzD,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,IAAI,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,gBAAgB;IAC5B;;OAEG;WACU,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC,oBAAoB,EAAE,CAAC,CAAC;IAIjE;;OAEG;WACU,MAAM,CAAC,IAAI,EAAE,yBAAyB,GAAG,OAAO,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;IAIxF;;OAEG;WACU,MAAM,CAAC,IAAI,EAAE,yBAAyB,GAAG,OAAO,CAAC,WAAW,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAIhG;;OAEG;WACU,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;IAIpE;;OAEG;WACU,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;IAY5D;;OAEG;WACU,UAAU,IAAI,OAAO,CAAC,WAAW,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAIrE;;OAEG;WACU,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC;IAI9F;;OAEG;WACU,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,WAAW,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAIhG;;OAEG;WACU,gBAAgB,CAC5B,IAAI,EAAE,uBAAuB,GAC3B,OAAO,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC;IAI3C;;OAEG;WACU,MAAM,CAClB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,yBAAyB,GAC7B,OAAO,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;IAIrC;;OAEG;WACU,gBAAgB,CAC5B,IAAI,EAAE,uBAAuB,GAC3B,OAAO,CAAC,WAAW,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAI7C;;;OAGG;WACU,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAG3E;AAGD,eAAO,MAAM,aAAa;;;;;;;;;;;;;CAazB,CAAC"}
@@ -76,7 +76,8 @@ export class OrganizationsApi {
76
76
  return apiClient.delete('/organizations/invitations', data);
77
77
  }
78
78
  /**
79
- * Delete an organization (super_admin only)
79
+ * Delete an organization, its media, and every membership in it. Owners only
80
+ * enforced by the route, not here.
80
81
  */
81
82
  static async remove(id) {
82
83
  return apiClient.delete(`/organizations/${id}`);
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Body for both operator actions on a job.
4
+ *
5
+ * `organizationId` exists solely for the instance-wide view: a super admin looking at every
6
+ * tenant's queue needs to act on a job that isn't in their *active* org, and the id alone
7
+ * doesn't say where it lives. Omit it and the action targets the caller's active organization
8
+ * — which is the only thing a non-super-admin may ever do (the route rejects a mismatch with
9
+ * 403 rather than trusting the body).
10
+ */
11
+ export declare const jobActionRequestSchema: z.ZodObject<{
12
+ organizationId: z.ZodOptional<z.ZodString>;
13
+ }, z.core.$strip>;
14
+ export type JobActionRequest = z.infer<typeof jobActionRequestSchema>;
15
+ //# sourceMappingURL=jobs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jobs.d.ts","sourceRoot":"","sources":["../../../src/lib/api/schemas/jobs.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB;;iBAEjC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC"}
@@ -0,0 +1,17 @@
1
+ // Request contracts for the job queue's operator actions (POST /api/jobs/:id/retry|cancel).
2
+ // Single source of truth: the route validates with these, the client infers its input types
3
+ // from them. The read endpoints (GET /jobs, /events, /jobs/health) keep their query schemas
4
+ // in the route handler — per convention, GET-only shapes don't earn a shared module.
5
+ import { z } from 'zod';
6
+ /**
7
+ * Body for both operator actions on a job.
8
+ *
9
+ * `organizationId` exists solely for the instance-wide view: a super admin looking at every
10
+ * tenant's queue needs to act on a job that isn't in their *active* org, and the id alone
11
+ * doesn't say where it lives. Omit it and the action targets the caller's active organization
12
+ * — which is the only thing a non-super-admin may ever do (the route rejects a mismatch with
13
+ * 403 rather than trusting the body).
14
+ */
15
+ export const jobActionRequestSchema = z.object({
16
+ organizationId: z.string().min(1).optional()
17
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../../src/lib/api/schemas/user.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,iBAAiB;;;iBAO3B,CAAC;AAIJ,eAAO,MAAM,4BAA4B;;kBAI/B,CAAC;AAIX,eAAO,MAAM,2BAA2B;;;iBAGtC,CAAC;AAIH,eAAO,MAAM,oBAAoB;;;iBAG/B,CAAC;AAIH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAClE,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AACxF,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AACtF,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
1
+ {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../../src/lib/api/schemas/user.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,iBAAiB;;;iBAW3B,CAAC;AAIJ,eAAO,MAAM,4BAA4B;;kBAI/B,CAAC;AAIX,eAAO,MAAM,2BAA2B;;;iBAGtC,CAAC;AAIH,eAAO,MAAM,oBAAoB;;;iBAG/B,CAAC;AAIH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAClE,MAAM,MAAM,4BAA4B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AACxF,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AACtF,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
@@ -2,8 +2,12 @@ import { z } from 'zod';
2
2
  // ---------- PATCH /user ----------
3
3
  export const updateUserRequest = z
4
4
  .object({
5
- name: z.string().min(1).optional(),
6
- image: z.string().min(1).nullable().optional()
5
+ // Bounded so a display name can't be pasted in at essay length — it renders
6
+ // in sidebars, member lists, and audit trails that all assume it's short.
7
+ name: z.string().min(1).max(80).optional(),
8
+ // Generous, but finite: this holds a `/media/<id>/<filename>` path or an
9
+ // external provider's avatar URL, never free text.
10
+ image: z.string().min(1).max(2048).nullable().optional()
7
11
  })
8
12
  .refine((v) => v.name !== undefined || v.image !== undefined, {
9
13
  message: 'At least one field (name, image) is required'
@@ -0,0 +1,35 @@
1
+ import type { DatabaseAdapter } from '../db/interfaces/index.js';
2
+ /**
3
+ * Thrown when an account may not be deleted yet. Providers translate it into their own
4
+ * transport error; the message is written to be shown to the user as-is.
5
+ */
6
+ export declare class AccountDeletionBlockedError extends Error {
7
+ readonly code = "ACCOUNT_DELETION_BLOCKED";
8
+ constructor(message: string);
9
+ }
10
+ /**
11
+ * Throws unless this user can leave without stranding an organization.
12
+ *
13
+ * The rule is narrow on purpose: an organization whose only owner deletes their account has
14
+ * nobody left who can add members, change roles, or delete it — the org becomes unreachable
15
+ * to everyone including its remaining members, and no in-product path can recover it. Being
16
+ * the sole *member* is fine, because deleting the organization first is a thing the user can
17
+ * actually do; being the sole *owner* of an org with other people in it is the trap.
18
+ *
19
+ * Enforced server-side rather than in the confirmation dialog: the endpoint is reachable
20
+ * without the UI, and this is the check that decides whether an organization survives.
21
+ */
22
+ export declare function assertAccountDeletable(db: DatabaseAdapter, userId: string): Promise<void>;
23
+ /**
24
+ * Detach a deleted user from every organization.
25
+ *
26
+ * `cms_organization_members.user_id` has no foreign key to the profile table — memberships
27
+ * reference the auth layer's user, which cms-core doesn't own — so nothing cascades and a
28
+ * deleted account would otherwise keep appearing in member lists indefinitely, still holding
29
+ * whatever role it had.
30
+ *
31
+ * Call inside the same transaction as the profile delete, after the erasure events are
32
+ * emitted (those read the memberships to know which organizations to fan out to).
33
+ */
34
+ export declare function detachUserFromOrganizations(tx: DatabaseAdapter, userId: string, organizationIds: readonly string[]): Promise<void>;
35
+ //# sourceMappingURL=account-deletion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-deletion.d.ts","sourceRoot":"","sources":["../../src/lib/auth/account-deletion.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;;GAGG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;IACrD,QAAQ,CAAC,IAAI,8BAA8B;gBAC/B,OAAO,EAAE,MAAM;CAI3B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,sBAAsB,CAAC,EAAE,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB/F;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,2BAA2B,CAChD,EAAE,EAAE,eAAe,EACnB,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,SAAS,MAAM,EAAE,GAChC,OAAO,CAAC,IAAI,CAAC,CAUf"}
@@ -0,0 +1,65 @@
1
+ // What has to be true before an account may be deleted, and what has to happen after.
2
+ //
3
+ // Lives in cms-core rather than in `@aphexcms/auth` for the same reason the sign-up gate
4
+ // does: "can this account go?" is a CMS question about organizations and ownership, and it
5
+ // must mean the same thing whichever provider authenticated the user. A provider supplies
6
+ // the lifecycle hook; it doesn't get to re-derive the rule.
7
+ import { cmsLogger } from '../utils/logger.js';
8
+ /**
9
+ * Thrown when an account may not be deleted yet. Providers translate it into their own
10
+ * transport error; the message is written to be shown to the user as-is.
11
+ */
12
+ export class AccountDeletionBlockedError extends Error {
13
+ code = 'ACCOUNT_DELETION_BLOCKED';
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = 'AccountDeletionBlockedError';
17
+ }
18
+ }
19
+ /**
20
+ * Throws unless this user can leave without stranding an organization.
21
+ *
22
+ * The rule is narrow on purpose: an organization whose only owner deletes their account has
23
+ * nobody left who can add members, change roles, or delete it — the org becomes unreachable
24
+ * to everyone including its remaining members, and no in-product path can recover it. Being
25
+ * the sole *member* is fine, because deleting the organization first is a thing the user can
26
+ * actually do; being the sole *owner* of an org with other people in it is the trap.
27
+ *
28
+ * Enforced server-side rather than in the confirmation dialog: the endpoint is reachable
29
+ * without the UI, and this is the check that decides whether an organization survives.
30
+ */
31
+ export async function assertAccountDeletable(db, userId) {
32
+ const memberships = await db.findUserOrganizations(userId);
33
+ const stranded = [];
34
+ for (const { organization } of memberships) {
35
+ const members = await db.findOrganizationMembers(organization.id);
36
+ const owners = members.filter((m) => m.role === 'owner');
37
+ // Sole owner, and somebody else is still here.
38
+ if (owners.length === 1 && owners[0]?.userId === userId && members.length > 1) {
39
+ stranded.push(organization.name);
40
+ }
41
+ }
42
+ if (stranded.length > 0) {
43
+ throw new AccountDeletionBlockedError(`You are the only owner of ${stranded.join(', ')}. Transfer ownership or remove the other ` +
44
+ `members before deleting your account.`);
45
+ }
46
+ }
47
+ /**
48
+ * Detach a deleted user from every organization.
49
+ *
50
+ * `cms_organization_members.user_id` has no foreign key to the profile table — memberships
51
+ * reference the auth layer's user, which cms-core doesn't own — so nothing cascades and a
52
+ * deleted account would otherwise keep appearing in member lists indefinitely, still holding
53
+ * whatever role it had.
54
+ *
55
+ * Call inside the same transaction as the profile delete, after the erasure events are
56
+ * emitted (those read the memberships to know which organizations to fan out to).
57
+ */
58
+ export async function detachUserFromOrganizations(tx, userId, organizationIds) {
59
+ for (const organizationId of organizationIds) {
60
+ const removed = await tx.removeMember(organizationId, userId);
61
+ if (!removed) {
62
+ cmsLogger.warn('[AccountDeletion]', `No membership row for ${userId} in ${organizationId} — nothing to detach.`);
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,104 @@
1
+ import type { DatabaseAdapter } from '../db/interfaces/index.js';
2
+ /** Instance-level role a bootstrap policy may grant. */
3
+ export type InstanceRole = 'super_admin' | 'admin' | 'editor' | 'viewer';
4
+ export interface BootstrapContext {
5
+ user: {
6
+ id: string;
7
+ email: string;
8
+ emailVerified: boolean;
9
+ };
10
+ /**
11
+ * True only when the system is *provably* empty.
12
+ *
13
+ * Never inferred from a missing capability: an adapter that can't answer the
14
+ * question yields `false`, so an unanswerable check can't be mistaken for
15
+ * "nobody is here yet, promote this person".
16
+ */
17
+ isFirstUser: boolean;
18
+ /** The request that triggered profile creation, when there is one. */
19
+ request?: Request;
20
+ db: DatabaseAdapter;
21
+ }
22
+ /**
23
+ * Decides what instance role a brand-new user profile gets.
24
+ *
25
+ * Bootstrapping is a policy decision, not a library one — how you claim a fresh
26
+ * instance depends on how you deploy it. Return `null` for "no promotion"; the
27
+ * caller falls back to the ordinary `editor` role.
28
+ */
29
+ export interface BootstrapPolicy {
30
+ (ctx: BootstrapContext): Promise<InstanceRole | null>;
31
+ /**
32
+ * Optional one-off startup step, run before any request is served.
33
+ *
34
+ * Lets a recipe own its own setup instead of making the app wire it: only
35
+ * `claimCode()` needs one (to generate and log its code), and an app that
36
+ * swaps recipes shouldn't have to remember to delete a hook. Callers invoke
37
+ * it as `policy.prepare?.(db)` — recipes without setup simply omit it.
38
+ */
39
+ prepare?: (db: DatabaseAdapter) => Promise<void>;
40
+ }
41
+ /**
42
+ * Ensure an unclaimed instance has a pending claim code, and log it.
43
+ *
44
+ * Only the **hash** is persisted: a leaked database dump or backup shouldn't be
45
+ * enough to claim the instance. Called at startup by `claimCode()` consumers.
46
+ */
47
+ export declare function ensureClaimCode(db: DatabaseAdapter): Promise<void>;
48
+ /**
49
+ * True when nobody has claimed this instance yet and a code is waiting to be
50
+ * used. Drives the sign-up form's claim-code field: without this the code has
51
+ * nowhere to go but a hand-set cookie, which is not a flow anyone can follow.
52
+ *
53
+ * Deliberately narrow. It reveals only that an instance is unclaimed — never the
54
+ * code or its hash — so it is safe to hand to an unauthenticated page. That fact
55
+ * is already obvious to anyone who can reach a CMS with no users in it.
56
+ */
57
+ export declare function isInstanceUnclaimed(db: DatabaseAdapter): Promise<boolean>;
58
+ /**
59
+ * The default. Keeps the familiar first-run wizard, but promotion requires a
60
+ * code printed to the server log at startup — so arriving first isn't enough,
61
+ * you also have to control the deployment. Same shape as Jupyter's `?token=`
62
+ * and GitLab's generated root password.
63
+ *
64
+ * Clearing the hash here is not what makes the code single-use — two concurrent
65
+ * claims can both read it before either clears it. Mutual exclusion comes from
66
+ * `tryClaimBootstrap`, which `createUserProfileWithBootstrap` takes before
67
+ * granting any instance role: the loser is demoted to an ordinary profile even
68
+ * though this returned `super_admin`. Clearing the hash still matters, just for
69
+ * the sequential case — it stops the code being reused later.
70
+ *
71
+ * One residual: a claim can be spent by a request whose profile insert then
72
+ * fails, which costs a code rather than granting anything. Recover by clearing
73
+ * the key from instance settings and restarting for a fresh one.
74
+ */
75
+ export declare function claimCode(options?: {
76
+ readCode?: (request?: Request) => string | undefined;
77
+ }): BootstrapPolicy;
78
+ /**
79
+ * First user whose address is on the allowlist becomes super admin — Discourse's
80
+ * `DISCOURSE_DEVELOPER_EMAILS`. Good when you know the owner's address at deploy
81
+ * time and would rather not read logs.
82
+ *
83
+ * This recipe is only as strong as the address is trustworthy, so pair it with
84
+ * `requireEmailVerification`. That's enforced at the auth layer — better-auth
85
+ * refuses to complete sign-in for an unconfirmed address, so an unverified user
86
+ * never reaches profile creation at all. Re-checking it here would be dead code
87
+ * when verification is on, and would brick a fresh install when it's off.
88
+ */
89
+ export declare function allowlistEmail(emails: string | readonly string[] | undefined): BootstrapPolicy;
90
+ /**
91
+ * Whoever signs up first becomes super admin, with nothing else required.
92
+ *
93
+ * This is what WordPress, Ghost, Strapi and Payload do, and it's fine when you
94
+ * install immediately after deploying. It is **not** fine for an instance that
95
+ * sits reachable before anyone signs in: the first stranger to find the URL owns
96
+ * it. Opt in deliberately.
97
+ */
98
+ export declare function openFirstUser(): BootstrapPolicy;
99
+ /**
100
+ * Never promote anyone. Provision the first administrator out of band — a seed
101
+ * script, a migration, or a direct row — the way Directus and Keycloak do.
102
+ */
103
+ export declare function never(): BootstrapPolicy;
104
+ //# sourceMappingURL=bootstrap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../src/lib/auth/bootstrap.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,wDAAwD;AACxD,MAAM,MAAM,YAAY,GAAG,aAAa,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAKzE,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,OAAO,CAAA;KAAE,CAAC;IAC5D;;;;;;OAMG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,EAAE,EAAE,eAAe,CAAC;CACpB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC/B,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IACtD;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACjD;AAcD;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBxE;AAED;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAK/E;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CACxB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAA;CAAO,GACpE,eAAe,CAqDjB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,GAAG,eAAe,CAsB9F;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,IAAI,eAAe,CAE/C;AAED;;;GAGG;AACH,wBAAgB,KAAK,IAAI,eAAe,CAEvC"}
@@ -0,0 +1,164 @@
1
+ import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
2
+ import { cmsLogger } from '../utils/logger.js';
3
+ import { isInstanceEmpty } from './instance-state.js';
4
+ /** Where the claim-code hash lives inside the instance-settings blob. */
5
+ const CLAIM_CODE_KEY = 'bootstrapClaimCodeHash';
6
+ function hashCode(code) {
7
+ return createHash('sha256').update(code).digest('hex');
8
+ }
9
+ /** Constant-time compare of two hex digests of equal length. */
10
+ function digestsMatch(a, b) {
11
+ const left = Buffer.from(a, 'hex');
12
+ const right = Buffer.from(b, 'hex');
13
+ if (left.length !== right.length || left.length === 0)
14
+ return false;
15
+ return timingSafeEqual(left, right);
16
+ }
17
+ /**
18
+ * Ensure an unclaimed instance has a pending claim code, and log it.
19
+ *
20
+ * Only the **hash** is persisted: a leaked database dump or backup shouldn't be
21
+ * enough to claim the instance. Called at startup by `claimCode()` consumers.
22
+ */
23
+ export async function ensureClaimCode(db) {
24
+ if (!(await isInstanceEmpty(db)))
25
+ return;
26
+ const settings = await db.getInstanceSettings();
27
+ if (typeof settings[CLAIM_CODE_KEY] === 'string')
28
+ return;
29
+ const code = randomBytes(24).toString('base64url');
30
+ await db.updateInstanceSettings({ [CLAIM_CODE_KEY]: hashCode(code) });
31
+ cmsLogger.info('[Bootstrap]', `\n\n This instance has no administrator yet.\n` +
32
+ ` Sign up at /admin and enter this claim code to become the super admin:\n\n` +
33
+ ` ${code}\n\n` +
34
+ ` It is single-use and is not stored in recoverable form. If you lose it,\n` +
35
+ ` clear "${CLAIM_CODE_KEY}" from instance settings and restart to get a new one.\n`);
36
+ }
37
+ /**
38
+ * True when nobody has claimed this instance yet and a code is waiting to be
39
+ * used. Drives the sign-up form's claim-code field: without this the code has
40
+ * nowhere to go but a hand-set cookie, which is not a flow anyone can follow.
41
+ *
42
+ * Deliberately narrow. It reveals only that an instance is unclaimed — never the
43
+ * code or its hash — so it is safe to hand to an unauthenticated page. That fact
44
+ * is already obvious to anyone who can reach a CMS with no users in it.
45
+ */
46
+ export async function isInstanceUnclaimed(db) {
47
+ if (!(await isInstanceEmpty(db)))
48
+ return false;
49
+ const settings = await db.getInstanceSettings();
50
+ return typeof settings[CLAIM_CODE_KEY] === 'string';
51
+ }
52
+ /**
53
+ * The default. Keeps the familiar first-run wizard, but promotion requires a
54
+ * code printed to the server log at startup — so arriving first isn't enough,
55
+ * you also have to control the deployment. Same shape as Jupyter's `?token=`
56
+ * and GitLab's generated root password.
57
+ *
58
+ * Clearing the hash here is not what makes the code single-use — two concurrent
59
+ * claims can both read it before either clears it. Mutual exclusion comes from
60
+ * `tryClaimBootstrap`, which `createUserProfileWithBootstrap` takes before
61
+ * granting any instance role: the loser is demoted to an ordinary profile even
62
+ * though this returned `super_admin`. Clearing the hash still matters, just for
63
+ * the sequential case — it stops the code being reused later.
64
+ *
65
+ * One residual: a claim can be spent by a request whose profile insert then
66
+ * fails, which costs a code rather than granting anything. Recover by clearing
67
+ * the key from instance settings and restarting for a fresh one.
68
+ */
69
+ export function claimCode(options = {}) {
70
+ /**
71
+ * Header first, then an `aphex_bootstrap_code` cookie. The profile is created
72
+ * on the first authenticated request after sign-up, which may be a redirect
73
+ * the signup form doesn't control, so a header alone would be unreachable from
74
+ * a plain HTML flow. Override `readCode` to accept it somewhere else.
75
+ *
76
+ * Deliberately *not* a `?claim=` query parameter, which this used to accept.
77
+ * A URL carrying the code lands in browser history, server and proxy access
78
+ * logs, and any `Referer` sent to a third party — persisting a credential in
79
+ * several places nobody thinks to clear, to save one hop that the cookie
80
+ * already covers.
81
+ */
82
+ const readCode = options.readCode ??
83
+ ((request) => {
84
+ if (!request)
85
+ return undefined;
86
+ const header = request.headers.get('x-aphex-bootstrap-code');
87
+ if (header)
88
+ return header;
89
+ const cookie = request.headers.get('cookie');
90
+ const raw = cookie?.match(/(?:^|;\s*)aphex_bootstrap_code=([^;]+)/)?.[1];
91
+ // The sign-up form percent-encodes the value. A generated base64url code
92
+ // has nothing to decode, but a custom one may.
93
+ return raw === undefined ? undefined : decodeURIComponent(raw);
94
+ });
95
+ const policy = async ({ isFirstUser, request, db }) => {
96
+ if (!isFirstUser)
97
+ return null;
98
+ const supplied = readCode(request)?.trim();
99
+ if (!supplied)
100
+ return null;
101
+ const settings = await db.getInstanceSettings();
102
+ const expected = settings[CLAIM_CODE_KEY];
103
+ if (typeof expected !== 'string')
104
+ return null;
105
+ if (!digestsMatch(hashCode(supplied), expected)) {
106
+ cmsLogger.warn('[Bootstrap]', 'Rejected claim attempt — code did not match');
107
+ return null;
108
+ }
109
+ // Single-use: clearing the hash is what makes a second claimant lose.
110
+ await db.updateInstanceSettings({ [CLAIM_CODE_KEY]: null });
111
+ cmsLogger.info('[Bootstrap]', 'Instance claimed — super admin created');
112
+ return 'super_admin';
113
+ };
114
+ // The code only exists because this recipe was chosen, so this recipe is what
115
+ // generates it. Swapping to another recipe takes the startup step with it.
116
+ policy.prepare = ensureClaimCode;
117
+ return policy;
118
+ }
119
+ /**
120
+ * First user whose address is on the allowlist becomes super admin — Discourse's
121
+ * `DISCOURSE_DEVELOPER_EMAILS`. Good when you know the owner's address at deploy
122
+ * time and would rather not read logs.
123
+ *
124
+ * This recipe is only as strong as the address is trustworthy, so pair it with
125
+ * `requireEmailVerification`. That's enforced at the auth layer — better-auth
126
+ * refuses to complete sign-in for an unconfirmed address, so an unverified user
127
+ * never reaches profile creation at all. Re-checking it here would be dead code
128
+ * when verification is on, and would brick a fresh install when it's off.
129
+ */
130
+ export function allowlistEmail(emails) {
131
+ const allowed = new Set((typeof emails === 'string' ? emails.split(',') : (emails ?? []))
132
+ .map((email) => email.trim().toLowerCase())
133
+ .filter(Boolean));
134
+ return async ({ isFirstUser, user }) => {
135
+ if (!isFirstUser || allowed.size === 0)
136
+ return null;
137
+ if (!allowed.has(user.email.trim().toLowerCase()))
138
+ return null;
139
+ if (!user.emailVerified) {
140
+ // Advice, not enforcement — the decision belongs to the auth layer.
141
+ cmsLogger.warn('[Bootstrap]', `Promoting ${user.email} on an unverified address. Set ` +
142
+ `AUTH_REQUIRE_EMAIL_VERIFICATION=true so the address has to be proven.`);
143
+ }
144
+ return 'super_admin';
145
+ };
146
+ }
147
+ /**
148
+ * Whoever signs up first becomes super admin, with nothing else required.
149
+ *
150
+ * This is what WordPress, Ghost, Strapi and Payload do, and it's fine when you
151
+ * install immediately after deploying. It is **not** fine for an instance that
152
+ * sits reachable before anyone signs in: the first stranger to find the URL owns
153
+ * it. Opt in deliberately.
154
+ */
155
+ export function openFirstUser() {
156
+ return async ({ isFirstUser }) => (isFirstUser ? 'super_admin' : null);
157
+ }
158
+ /**
159
+ * Never promote anyone. Provision the first administrator out of band — a seed
160
+ * script, a migration, or a direct row — the way Directus and Keycloak do.
161
+ */
162
+ export function never() {
163
+ return async () => null;
164
+ }
@@ -0,0 +1,13 @@
1
+ import type { DatabaseAdapter } from '../db/interfaces/index.js';
2
+ /**
3
+ * True only when the instance is *provably* empty — the adapter can answer the
4
+ * question, and the answer is "no user profiles exist".
5
+ *
6
+ * Returns `false` when the adapter doesn't implement `hasAnyUserProfiles()`,
7
+ * which is the safe direction: bootstrap promotion is skipped and the invite
8
+ * gate stays shut rather than swinging open.
9
+ */
10
+ export declare function isInstanceEmpty(db: DatabaseAdapter): Promise<boolean>;
11
+ /** Whether the adapter can answer `isInstanceEmpty` at all — for warning about a skipped bootstrap. */
12
+ export declare function canDetermineInstanceEmptiness(db: DatabaseAdapter): boolean;
13
+ //# sourceMappingURL=instance-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instance-state.d.ts","sourceRoot":"","sources":["../../src/lib/auth/instance-state.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAI3E;AAED,uGAAuG;AACvG,wBAAgB,6BAA6B,CAAC,EAAE,EAAE,eAAe,GAAG,OAAO,CAE1E"}
@@ -0,0 +1,30 @@
1
+ // The single definition of "has this instance been claimed yet?".
2
+ //
3
+ // Two features hang off this one fact — the bootstrap policy (who gets promoted
4
+ // to super admin) and the invite gate (who may sign up at all) — and they must
5
+ // agree, or the disagreement is a bypass rather than a bug.
6
+ //
7
+ // It also has one non-obvious rule that every caller has to get right, which is
8
+ // why it lives in one place instead of six: **proof of emptiness, never absence
9
+ // of proof.** `hasAnyUserProfiles` is optional on the adapter interface, and an
10
+ // adapter that cannot answer must NOT be read as "nobody is here yet". Getting
11
+ // that backwards is exactly how a missing implementation once promoted every
12
+ // single sign-up to super admin.
13
+ /**
14
+ * True only when the instance is *provably* empty — the adapter can answer the
15
+ * question, and the answer is "no user profiles exist".
16
+ *
17
+ * Returns `false` when the adapter doesn't implement `hasAnyUserProfiles()`,
18
+ * which is the safe direction: bootstrap promotion is skipped and the invite
19
+ * gate stays shut rather than swinging open.
20
+ */
21
+ export async function isInstanceEmpty(db) {
22
+ const countUsers = db.hasAnyUserProfiles?.bind(db);
23
+ if (!countUsers)
24
+ return false;
25
+ return !(await countUsers());
26
+ }
27
+ /** Whether the adapter can answer `isInstanceEmpty` at all — for warning about a skipped bootstrap. */
28
+ export function canDetermineInstanceEmptiness(db) {
29
+ return typeof db.hasAnyUserProfiles === 'function';
30
+ }
@@ -0,0 +1,25 @@
1
+ import type { Invitation } from '../types/organization.js';
2
+ /** The minimum shape these predicates need — so callers can pass rows from any layer. */
3
+ export type InvitationState = Pick<Invitation, 'expiresAt' | 'acceptedAt'>;
4
+ /** Default lifetime for a new invitation. */
5
+ export declare const INVITATION_TTL_MS: number;
6
+ /** When a freshly-created invitation should expire. */
7
+ export declare function invitationExpiryFrom(now?: Date): Date;
8
+ export declare function isAccepted(invitation: InvitationState): boolean;
9
+ export declare function isExpired(invitation: InvitationState, now?: Date): boolean;
10
+ /**
11
+ * Redeemable right now: not yet accepted and not yet expired.
12
+ *
13
+ * The only question worth asking in most places — whether a sign-up may proceed,
14
+ * whether to show it in the members list, whether a re-invite is redundant.
15
+ */
16
+ export declare function isPendingInvitation(invitation: InvitationState, now?: Date): boolean;
17
+ /**
18
+ * Lapsed without being used, so it is safe to clear and replace.
19
+ *
20
+ * Deliberately distinct from `!isPendingInvitation(...)`: an *accepted*
21
+ * invitation is also "not pending", but deleting it would erase the record that
22
+ * someone joined by invitation.
23
+ */
24
+ export declare function isStaleInvitation(invitation: InvitationState, now?: Date): boolean;
25
+ //# sourceMappingURL=invitation-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invitation-status.d.ts","sourceRoot":"","sources":["../../src/lib/auth/invitation-status.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,yFAAyF;AACzF,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,YAAY,CAAC,CAAC;AAE3E,6CAA6C;AAC7C,eAAO,MAAM,iBAAiB,QAA0B,CAAC;AAEzD,uDAAuD;AACvD,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,IAAiB,GAAG,IAAI,CAEjE;AAED,wBAAgB,UAAU,CAAC,UAAU,EAAE,eAAe,GAAG,OAAO,CAE/D;AAED,wBAAgB,SAAS,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO,CAEtF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO,CAEhG;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO,CAE9F"}
@@ -0,0 +1,41 @@
1
+ // The single definition of what an invitation's state means.
2
+ //
3
+ // This exists because three call sites independently decided what "pending"
4
+ // meant and two of them drifted: the sign-up gate required unaccepted *and*
5
+ // unexpired, the members list did the same, but the re-invite check tested only
6
+ // `acceptedAt === null`. An expired invitation therefore blocked re-inviting
7
+ // while being unusable and invisible — a permanent deadlock for that address.
8
+ //
9
+ // Anything that asks a question about invitation state imports from here. If a
10
+ // rule changes (grace periods, revocation, resend windows), it changes once.
11
+ /** Default lifetime for a new invitation. */
12
+ export const INVITATION_TTL_MS = 7 * 24 * 60 * 60 * 1000;
13
+ /** When a freshly-created invitation should expire. */
14
+ export function invitationExpiryFrom(now = new Date()) {
15
+ return new Date(now.getTime() + INVITATION_TTL_MS);
16
+ }
17
+ export function isAccepted(invitation) {
18
+ return invitation.acceptedAt !== null;
19
+ }
20
+ export function isExpired(invitation, now = new Date()) {
21
+ return new Date(invitation.expiresAt).getTime() <= now.getTime();
22
+ }
23
+ /**
24
+ * Redeemable right now: not yet accepted and not yet expired.
25
+ *
26
+ * The only question worth asking in most places — whether a sign-up may proceed,
27
+ * whether to show it in the members list, whether a re-invite is redundant.
28
+ */
29
+ export function isPendingInvitation(invitation, now = new Date()) {
30
+ return !isAccepted(invitation) && !isExpired(invitation, now);
31
+ }
32
+ /**
33
+ * Lapsed without being used, so it is safe to clear and replace.
34
+ *
35
+ * Deliberately distinct from `!isPendingInvitation(...)`: an *accepted*
36
+ * invitation is also "not pending", but deleting it would erase the record that
37
+ * someone joined by invitation.
38
+ */
39
+ export function isStaleInvitation(invitation, now = new Date()) {
40
+ return !isAccepted(invitation) && isExpired(invitation, now);
41
+ }