@cosmicdrift/kumiko-bundled-features 0.1.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 (333) hide show
  1. package/package.json +90 -0
  2. package/src/audit/__tests__/audit.integration.ts +328 -0
  3. package/src/audit/constants.ts +7 -0
  4. package/src/audit/feature.ts +23 -0
  5. package/src/audit/handlers/list.query.ts +98 -0
  6. package/src/audit/index.ts +2 -0
  7. package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.ts +149 -0
  8. package/src/auth-email-password/__tests__/account-lockout.integration.ts +308 -0
  9. package/src/auth-email-password/__tests__/auth-claims.integration.ts +512 -0
  10. package/src/auth-email-password/__tests__/auth.integration.ts +610 -0
  11. package/src/auth-email-password/__tests__/confirm-token-flow.test.ts +67 -0
  12. package/src/auth-email-password/__tests__/email-templates.test.ts +106 -0
  13. package/src/auth-email-password/__tests__/email-verification.integration.ts +327 -0
  14. package/src/auth-email-password/__tests__/identity-v3-hash.test.ts +174 -0
  15. package/src/auth-email-password/__tests__/identity-v3-login.integration.ts +150 -0
  16. package/src/auth-email-password/__tests__/invite-flow.integration.ts +458 -0
  17. package/src/auth-email-password/__tests__/multi-roles.integration.ts +256 -0
  18. package/src/auth-email-password/__tests__/password-reset.integration.ts +346 -0
  19. package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.ts +144 -0
  20. package/src/auth-email-password/__tests__/seed-admin.integration.ts +176 -0
  21. package/src/auth-email-password/__tests__/session-callbacks.integration.ts +310 -0
  22. package/src/auth-email-password/__tests__/session-strict-mode.integration.ts +101 -0
  23. package/src/auth-email-password/__tests__/signed-token.test.ts +78 -0
  24. package/src/auth-email-password/__tests__/signup-flow.integration.ts +259 -0
  25. package/src/auth-email-password/auth-user-row.ts +41 -0
  26. package/src/auth-email-password/constants.ts +101 -0
  27. package/src/auth-email-password/email-templates.ts +283 -0
  28. package/src/auth-email-password/feature.ts +140 -0
  29. package/src/auth-email-password/handlers/change-password.write.ts +58 -0
  30. package/src/auth-email-password/handlers/confirm-token-flow.ts +191 -0
  31. package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +203 -0
  32. package/src/auth-email-password/handlers/invite-accept.write.ts +189 -0
  33. package/src/auth-email-password/handlers/invite-create.write.ts +145 -0
  34. package/src/auth-email-password/handlers/invite-signup-complete.write.ts +192 -0
  35. package/src/auth-email-password/handlers/login.write.ts +208 -0
  36. package/src/auth-email-password/handlers/logout.write.ts +12 -0
  37. package/src/auth-email-password/handlers/request-email-verification.write.ts +29 -0
  38. package/src/auth-email-password/handlers/request-password-reset.write.ts +31 -0
  39. package/src/auth-email-password/handlers/reset-password.write.ts +61 -0
  40. package/src/auth-email-password/handlers/signup-confirm.write.ts +170 -0
  41. package/src/auth-email-password/handlers/signup-request.write.ts +104 -0
  42. package/src/auth-email-password/handlers/token-request-handler.ts +114 -0
  43. package/src/auth-email-password/handlers/verify-email.write.ts +62 -0
  44. package/src/auth-email-password/i18n.ts +211 -0
  45. package/src/auth-email-password/identity-v3-hash.ts +97 -0
  46. package/src/auth-email-password/index.ts +35 -0
  47. package/src/auth-email-password/invite-token-store.ts +92 -0
  48. package/src/auth-email-password/lockout-store.ts +118 -0
  49. package/src/auth-email-password/password-hashing.ts +43 -0
  50. package/src/auth-email-password/reset-token.ts +28 -0
  51. package/src/auth-email-password/seeding.ts +183 -0
  52. package/src/auth-email-password/signed-token.ts +85 -0
  53. package/src/auth-email-password/signup-token-store.ts +104 -0
  54. package/src/auth-email-password/stream-tenant.ts +31 -0
  55. package/src/auth-email-password/testing.ts +5 -0
  56. package/src/auth-email-password/token-burn-store.ts +57 -0
  57. package/src/auth-email-password/verification-token.ts +27 -0
  58. package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +51 -0
  59. package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +80 -0
  60. package/src/auth-email-password/web/__tests__/login-screen.test.tsx +94 -0
  61. package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +108 -0
  62. package/src/auth-email-password/web/__tests__/session-roles.test.ts +54 -0
  63. package/src/auth-email-password/web/__tests__/tenant-switcher.test.tsx +100 -0
  64. package/src/auth-email-password/web/__tests__/test-utils.tsx +73 -0
  65. package/src/auth-email-password/web/__tests__/user-menu.test.tsx +55 -0
  66. package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +59 -0
  67. package/src/auth-email-password/web/auth-client.ts +350 -0
  68. package/src/auth-email-password/web/auth-form-primitives.tsx +70 -0
  69. package/src/auth-email-password/web/auth-gate.tsx +33 -0
  70. package/src/auth-email-password/web/client-plugin.ts +48 -0
  71. package/src/auth-email-password/web/default-topbar-actions.tsx +47 -0
  72. package/src/auth-email-password/web/forgot-password-screen.tsx +110 -0
  73. package/src/auth-email-password/web/index.ts +56 -0
  74. package/src/auth-email-password/web/invite-accept-screen.tsx +220 -0
  75. package/src/auth-email-password/web/login-screen.tsx +150 -0
  76. package/src/auth-email-password/web/reset-password-screen.tsx +152 -0
  77. package/src/auth-email-password/web/session.tsx +171 -0
  78. package/src/auth-email-password/web/signup-complete-screen.tsx +150 -0
  79. package/src/auth-email-password/web/signup-screen.tsx +130 -0
  80. package/src/auth-email-password/web/tenant-switcher.tsx +116 -0
  81. package/src/auth-email-password/web/use-shell-user.ts +34 -0
  82. package/src/auth-email-password/web/user-menu.tsx +89 -0
  83. package/src/auth-email-password/web/verify-email-screen.tsx +102 -0
  84. package/src/billing-foundation/__tests__/billing-foundation.integration.ts +568 -0
  85. package/src/billing-foundation/__tests__/feature.test.ts +110 -0
  86. package/src/billing-foundation/__tests__/webhook-handler.test.ts +199 -0
  87. package/src/billing-foundation/aggregate-id.ts +21 -0
  88. package/src/billing-foundation/constants.ts +70 -0
  89. package/src/billing-foundation/entities.ts +50 -0
  90. package/src/billing-foundation/events.ts +71 -0
  91. package/src/billing-foundation/feature.ts +122 -0
  92. package/src/billing-foundation/get-subscription-for-tenant.ts +39 -0
  93. package/src/billing-foundation/handlers/create-checkout-session.write.ts +79 -0
  94. package/src/billing-foundation/handlers/create-portal-session.write.ts +73 -0
  95. package/src/billing-foundation/handlers/list-subscriptions.query.ts +20 -0
  96. package/src/billing-foundation/handlers/process-event.write.ts +160 -0
  97. package/src/billing-foundation/index.ts +42 -0
  98. package/src/billing-foundation/projection.ts +135 -0
  99. package/src/billing-foundation/types.ts +157 -0
  100. package/src/billing-foundation/webhook-handler.ts +184 -0
  101. package/src/cap-counter/__tests__/cap-counter.integration.ts +566 -0
  102. package/src/cap-counter/__tests__/enforce-cap.test.ts +422 -0
  103. package/src/cap-counter/__tests__/with-cap-enforcement.integration.ts +265 -0
  104. package/src/cap-counter/aggregate-id.ts +61 -0
  105. package/src/cap-counter/constants.ts +32 -0
  106. package/src/cap-counter/enforce-cap.ts +404 -0
  107. package/src/cap-counter/entity.ts +48 -0
  108. package/src/cap-counter/feature.ts +90 -0
  109. package/src/cap-counter/handlers/get-counter.query.ts +43 -0
  110. package/src/cap-counter/handlers/increment-rolling.write.ts +79 -0
  111. package/src/cap-counter/handlers/increment.write.ts +92 -0
  112. package/src/cap-counter/handlers/mark-soft-warned.write.ts +57 -0
  113. package/src/cap-counter/index.ts +34 -0
  114. package/src/cap-counter/with-cap-enforcement.ts +179 -0
  115. package/src/channel-email/email-channel.ts +48 -0
  116. package/src/channel-email/feature.ts +15 -0
  117. package/src/channel-email/index.ts +4 -0
  118. package/src/channel-email/smtp-transport.ts +65 -0
  119. package/src/channel-email/types.ts +34 -0
  120. package/src/channel-in-app/constants.ts +11 -0
  121. package/src/channel-in-app/feature.ts +30 -0
  122. package/src/channel-in-app/handlers/inbox.query.ts +28 -0
  123. package/src/channel-in-app/handlers/mark-all-read.write.ts +21 -0
  124. package/src/channel-in-app/handlers/mark-read.write.ts +32 -0
  125. package/src/channel-in-app/handlers/unread-count.query.ts +20 -0
  126. package/src/channel-in-app/in-app-channel.ts +44 -0
  127. package/src/channel-in-app/index.ts +4 -0
  128. package/src/channel-in-app/tables.ts +22 -0
  129. package/src/channel-push/feature.ts +15 -0
  130. package/src/channel-push/index.ts +3 -0
  131. package/src/channel-push/push-channel.ts +33 -0
  132. package/src/channel-push/types.ts +22 -0
  133. package/src/config/__tests__/app-overrides.test.ts +118 -0
  134. package/src/config/__tests__/config.integration.ts +1246 -0
  135. package/src/config/constants.ts +23 -0
  136. package/src/config/feature.ts +117 -0
  137. package/src/config/handlers/__tests__/prepare-config-write.test.ts +209 -0
  138. package/src/config/handlers/reset.write.ts +45 -0
  139. package/src/config/handlers/schema.query.ts +22 -0
  140. package/src/config/handlers/set.write.ts +93 -0
  141. package/src/config/handlers/values.query.ts +43 -0
  142. package/src/config/index.ts +15 -0
  143. package/src/config/resolver.ts +283 -0
  144. package/src/config/table.ts +35 -0
  145. package/src/config/write-helpers.ts +268 -0
  146. package/src/delivery/__tests__/delivery-events.integration.ts +166 -0
  147. package/src/delivery/__tests__/delivery.integration.ts +1405 -0
  148. package/src/delivery/constants.ts +33 -0
  149. package/src/delivery/delivery-service.ts +489 -0
  150. package/src/delivery/events.ts +18 -0
  151. package/src/delivery/feature.ts +70 -0
  152. package/src/delivery/handlers/log.query.ts +21 -0
  153. package/src/delivery/handlers/preferences.query.ts +18 -0
  154. package/src/delivery/handlers/set-preference.write.ts +28 -0
  155. package/src/delivery/index.ts +35 -0
  156. package/src/delivery/tables.ts +74 -0
  157. package/src/delivery/testing.ts +47 -0
  158. package/src/delivery/types.ts +71 -0
  159. package/src/delivery/unsubscribe.ts +99 -0
  160. package/src/delivery/upsert-preference.ts +145 -0
  161. package/src/feature-toggles/__tests__/feature-toggles.integration.ts +687 -0
  162. package/src/feature-toggles/constants.ts +20 -0
  163. package/src/feature-toggles/events.ts +18 -0
  164. package/src/feature-toggles/feature.ts +98 -0
  165. package/src/feature-toggles/global-feature-state-table.ts +28 -0
  166. package/src/feature-toggles/handlers/list.query.ts +26 -0
  167. package/src/feature-toggles/handlers/registered.query.ts +56 -0
  168. package/src/feature-toggles/handlers/set.write.ts +158 -0
  169. package/src/feature-toggles/index.ts +9 -0
  170. package/src/feature-toggles/toggle-runtime.ts +73 -0
  171. package/src/file-foundation/__tests__/feature.test.ts +35 -0
  172. package/src/file-foundation/__tests__/file-foundation.integration.ts +235 -0
  173. package/src/file-foundation/feature.ts +123 -0
  174. package/src/file-foundation/index.ts +7 -0
  175. package/src/file-provider-inmemory/__tests__/feature.test.ts +35 -0
  176. package/src/file-provider-inmemory/feature.ts +73 -0
  177. package/src/file-provider-inmemory/index.ts +3 -0
  178. package/src/file-provider-s3/__tests__/feature.test.ts +54 -0
  179. package/src/file-provider-s3/feature.ts +169 -0
  180. package/src/file-provider-s3/index.ts +3 -0
  181. package/src/files-provider-s3/__tests__/env-helper.test.ts +161 -0
  182. package/src/files-provider-s3/__tests__/s3-provider.integration.ts +134 -0
  183. package/src/files-provider-s3/__tests__/s3-provider.test.ts +36 -0
  184. package/src/files-provider-s3/env-helper.ts +49 -0
  185. package/src/files-provider-s3/index.ts +3 -0
  186. package/src/files-provider-s3/s3-provider.ts +114 -0
  187. package/src/foundation-shared/config-helpers.ts +67 -0
  188. package/src/foundation-shared/index.ts +4 -0
  189. package/src/jobs/__tests__/job-system-user.integration.ts +194 -0
  190. package/src/jobs/__tests__/jobs-events.integration.ts +143 -0
  191. package/src/jobs/__tests__/jobs-feature.integration.ts +342 -0
  192. package/src/jobs/constants.ts +21 -0
  193. package/src/jobs/events.ts +39 -0
  194. package/src/jobs/feature.ts +150 -0
  195. package/src/jobs/handlers/detail.query.ts +30 -0
  196. package/src/jobs/handlers/list.query.ts +36 -0
  197. package/src/jobs/handlers/retry.write.ts +69 -0
  198. package/src/jobs/handlers/trigger.write.ts +39 -0
  199. package/src/jobs/index.ts +5 -0
  200. package/src/jobs/job-run-logger.ts +213 -0
  201. package/src/jobs/job-run-table.ts +55 -0
  202. package/src/legal-pages/README.md +195 -0
  203. package/src/legal-pages/__tests__/legal-pages.integration.ts +361 -0
  204. package/src/legal-pages/constants.ts +36 -0
  205. package/src/legal-pages/feature.ts +187 -0
  206. package/src/legal-pages/index.ts +13 -0
  207. package/src/legal-pages/markdown.ts +69 -0
  208. package/src/mail-foundation/__tests__/feature.test.ts +46 -0
  209. package/src/mail-foundation/__tests__/mail-foundation.integration.ts +247 -0
  210. package/src/mail-foundation/feature.ts +160 -0
  211. package/src/mail-foundation/index.ts +14 -0
  212. package/src/mail-transport-inmemory/__tests__/feature.test.ts +37 -0
  213. package/src/mail-transport-inmemory/feature.ts +90 -0
  214. package/src/mail-transport-inmemory/index.ts +3 -0
  215. package/src/mail-transport-smtp/__tests__/feature.test.ts +61 -0
  216. package/src/mail-transport-smtp/feature.ts +182 -0
  217. package/src/mail-transport-smtp/index.ts +3 -0
  218. package/src/rate-limiting/__tests__/rate-limiting.integration.ts +84 -0
  219. package/src/rate-limiting/constants.ts +9 -0
  220. package/src/rate-limiting/feature.ts +16 -0
  221. package/src/rate-limiting/handlers/status.query.ts +52 -0
  222. package/src/rate-limiting/index.ts +2 -0
  223. package/src/renderer-simple/__tests__/simple-renderer.test.ts +97 -0
  224. package/src/renderer-simple/feature.ts +12 -0
  225. package/src/renderer-simple/index.ts +2 -0
  226. package/src/renderer-simple/simple-renderer.ts +72 -0
  227. package/src/secrets/__tests__/rotate.integration.ts +176 -0
  228. package/src/secrets/__tests__/secrets-events.integration.ts +125 -0
  229. package/src/secrets/__tests__/secrets.integration.ts +118 -0
  230. package/src/secrets/feature.ts +84 -0
  231. package/src/secrets/handlers/delete.write.ts +20 -0
  232. package/src/secrets/handlers/list.query.ts +38 -0
  233. package/src/secrets/handlers/rotate.job.ts +193 -0
  234. package/src/secrets/handlers/set.write.ts +50 -0
  235. package/src/secrets/index.ts +16 -0
  236. package/src/secrets/secrets-context.ts +296 -0
  237. package/src/secrets/table.ts +68 -0
  238. package/src/sessions/__tests__/cleanup.integration.ts +175 -0
  239. package/src/sessions/__tests__/password-auto-revoke.integration.ts +202 -0
  240. package/src/sessions/__tests__/sessions.integration.ts +472 -0
  241. package/src/sessions/__tests__/test-helpers.ts +66 -0
  242. package/src/sessions/constants.ts +43 -0
  243. package/src/sessions/feature.ts +84 -0
  244. package/src/sessions/handlers/cleanup.job.ts +109 -0
  245. package/src/sessions/handlers/list.query.ts +35 -0
  246. package/src/sessions/handlers/mine.query.ts +37 -0
  247. package/src/sessions/handlers/revoke-all-others.write.ts +42 -0
  248. package/src/sessions/handlers/revoke.write.ts +76 -0
  249. package/src/sessions/index.ts +17 -0
  250. package/src/sessions/schema/index.ts +5 -0
  251. package/src/sessions/schema/user-session.ts +67 -0
  252. package/src/sessions/session-callbacks.ts +110 -0
  253. package/src/sessions/testing.ts +42 -0
  254. package/src/subscription-mollie/__tests__/feature.test.ts +106 -0
  255. package/src/subscription-mollie/__tests__/mollie-foundation.integration.ts +421 -0
  256. package/src/subscription-mollie/__tests__/verify-webhook.test.ts +388 -0
  257. package/src/subscription-mollie/constants.ts +33 -0
  258. package/src/subscription-mollie/feature.ts +144 -0
  259. package/src/subscription-mollie/index.ts +13 -0
  260. package/src/subscription-mollie/plugin-methods.ts +79 -0
  261. package/src/subscription-mollie/verify-webhook.ts +244 -0
  262. package/src/subscription-stripe/__tests__/feature.test.ts +98 -0
  263. package/src/subscription-stripe/__tests__/plugin-methods.test.ts +161 -0
  264. package/src/subscription-stripe/__tests__/stripe-foundation.integration.ts +315 -0
  265. package/src/subscription-stripe/__tests__/verify-webhook.test.ts +306 -0
  266. package/src/subscription-stripe/constants.ts +20 -0
  267. package/src/subscription-stripe/feature.ts +120 -0
  268. package/src/subscription-stripe/index.ts +14 -0
  269. package/src/subscription-stripe/plugin-methods.ts +91 -0
  270. package/src/subscription-stripe/verify-webhook.ts +235 -0
  271. package/src/tenant/__tests__/multi-tenant.integration.ts +278 -0
  272. package/src/tenant/__tests__/seed-testing.integration.ts +229 -0
  273. package/src/tenant/__tests__/tenant.integration.ts +347 -0
  274. package/src/tenant/command-schemas.ts +37 -0
  275. package/src/tenant/constants.ts +37 -0
  276. package/src/tenant/feature.ts +109 -0
  277. package/src/tenant/handlers/active-tenant-ids.query.ts +19 -0
  278. package/src/tenant/handlers/add-member.write.ts +53 -0
  279. package/src/tenant/handlers/cancel-invitation.write.ts +87 -0
  280. package/src/tenant/handlers/create.write.ts +21 -0
  281. package/src/tenant/handlers/disable.write.ts +18 -0
  282. package/src/tenant/handlers/invitations.query.ts +31 -0
  283. package/src/tenant/handlers/list.query.ts +17 -0
  284. package/src/tenant/handlers/me.query.ts +17 -0
  285. package/src/tenant/handlers/members.query.ts +22 -0
  286. package/src/tenant/handlers/memberships.query.ts +24 -0
  287. package/src/tenant/handlers/remove-member.write.ts +40 -0
  288. package/src/tenant/handlers/resolve-user-ids.query.ts +43 -0
  289. package/src/tenant/handlers/update-member-roles.write.ts +54 -0
  290. package/src/tenant/handlers/update.write.ts +20 -0
  291. package/src/tenant/index.ts +12 -0
  292. package/src/tenant/invitation-table.ts +93 -0
  293. package/src/tenant/membership-table.ts +35 -0
  294. package/src/tenant/schema/index.ts +5 -0
  295. package/src/tenant/schema/tenant.ts +27 -0
  296. package/src/tenant/seeding.ts +155 -0
  297. package/src/tenant/testing.ts +8 -0
  298. package/src/text-content/README.md +190 -0
  299. package/src/text-content/__tests__/text-content.integration.ts +415 -0
  300. package/src/text-content/api.ts +92 -0
  301. package/src/text-content/constants.ts +19 -0
  302. package/src/text-content/feature.ts +29 -0
  303. package/src/text-content/handlers/by-slug.query.ts +55 -0
  304. package/src/text-content/handlers/set.write.ts +118 -0
  305. package/src/text-content/index.ts +14 -0
  306. package/src/text-content/seeding.ts +91 -0
  307. package/src/text-content/table.ts +45 -0
  308. package/src/tier-engine/__tests__/compose-app.test.ts +182 -0
  309. package/src/tier-engine/__tests__/drift.test.ts +42 -0
  310. package/src/tier-engine/__tests__/tier-engine.integration.ts +241 -0
  311. package/src/tier-engine/aggregate-id.ts +27 -0
  312. package/src/tier-engine/compose-app.ts +150 -0
  313. package/src/tier-engine/constants.ts +15 -0
  314. package/src/tier-engine/entity.ts +30 -0
  315. package/src/tier-engine/feature.ts +72 -0
  316. package/src/tier-engine/handlers/active-tier.query.ts +23 -0
  317. package/src/tier-engine/index.ts +22 -0
  318. package/src/user/__tests__/seed-testing.integration.ts +127 -0
  319. package/src/user/__tests__/user.integration.ts +198 -0
  320. package/src/user/command-schemas.ts +15 -0
  321. package/src/user/constants.ts +23 -0
  322. package/src/user/feature.ts +32 -0
  323. package/src/user/handlers/create.write.ts +54 -0
  324. package/src/user/handlers/detail.query.ts +9 -0
  325. package/src/user/handlers/find-for-auth.query.ts +38 -0
  326. package/src/user/handlers/list.query.ts +8 -0
  327. package/src/user/handlers/me.query.ts +15 -0
  328. package/src/user/handlers/update.write.ts +54 -0
  329. package/src/user/index.ts +4 -0
  330. package/src/user/schema/index.ts +5 -0
  331. package/src/user/schema/user.ts +69 -0
  332. package/src/user/seeding.ts +93 -0
  333. package/src/user/testing.ts +5 -0
@@ -0,0 +1,56 @@
1
+ // @runtime client
2
+ // Public exports für die Browser-Seite des auth-email-password Features.
3
+ // Wird über den Sub-Path-Export `@cosmicdrift/kumiko-bundled-features/auth-email-
4
+ // password/web` konsumiert — die Server-Seite (defineFeature) lebt in
5
+ // `@cosmicdrift/kumiko-bundled-features/auth-email-password` und hat keine
6
+ // React-/DOM-Deps. Trennung bleibt sauber so wie renderer vs renderer-web.
7
+
8
+ export { defaultTranslations } from "../i18n";
9
+ export type {
10
+ AuthTokenFailure,
11
+ CurrentUserProfile,
12
+ LoginFailure,
13
+ LoginRequest,
14
+ ResetPasswordFailure,
15
+ SignupConfirmSuccess,
16
+ TenantSummary,
17
+ VerifyEmailFailure,
18
+ } from "./auth-client";
19
+ export {
20
+ confirmSignup,
21
+ requestEmailVerification,
22
+ requestPasswordReset,
23
+ requestSignup,
24
+ resetPassword,
25
+ verifyEmail,
26
+ } from "./auth-client";
27
+ export { makeAuthGate } from "./auth-gate";
28
+ export type {
29
+ EmailPasswordClientFeature,
30
+ EmailPasswordClientOptions,
31
+ } from "./client-plugin";
32
+ export { emailPasswordClient } from "./client-plugin";
33
+ export type { DefaultTopbarActionsProps } from "./default-topbar-actions";
34
+ export { DefaultTopbarActions } from "./default-topbar-actions";
35
+ export type { ForgotPasswordScreenProps } from "./forgot-password-screen";
36
+ export { ForgotPasswordScreen } from "./forgot-password-screen";
37
+ export type { InviteAcceptScreenProps } from "./invite-accept-screen";
38
+ export { InviteAcceptScreen } from "./invite-accept-screen";
39
+ export type { LoginScreenProps } from "./login-screen";
40
+ export { LoginScreen } from "./login-screen";
41
+ export type { ResetPasswordScreenProps } from "./reset-password-screen";
42
+ export { ResetPasswordScreen } from "./reset-password-screen";
43
+ export type { SessionApi, SessionState, SessionStatus } from "./session";
44
+ export { SessionContext, SessionProvider, useSession } from "./session";
45
+ export type { SignupCompleteScreenProps } from "./signup-complete-screen";
46
+ export { SignupCompleteScreen } from "./signup-complete-screen";
47
+ export type { SignupScreenProps } from "./signup-screen";
48
+ export { SignupScreen } from "./signup-screen";
49
+ export type { TenantSwitcherProps } from "./tenant-switcher";
50
+ export { TenantSwitcher } from "./tenant-switcher";
51
+ export type { ShellUser } from "./use-shell-user";
52
+ export { useShellUser } from "./use-shell-user";
53
+ export type { UserMenuProps } from "./user-menu";
54
+ export { UserMenu } from "./user-menu";
55
+ export type { VerifyEmailScreenProps } from "./verify-email-screen";
56
+ export { VerifyEmailScreen } from "./verify-email-screen";
@@ -0,0 +1,220 @@
1
+ // @runtime client
2
+ // InviteAcceptScreen — Magic-Link-Accept für Tenant-Invitations.
3
+ //
4
+ // Liest `?token=...` aus der URL und branched intern je nach session-state:
5
+ // - Logged-in + Email-Match: 1-click accept (Branch 1, dispatcher
6
+ // invite-accept). Bei Email-Mismatch zeigen wir die fehlermeldung
7
+ // und einen "Mit anderem Account anmelden"-Link.
8
+ // - Anonymous: Email + Password-Form. Toggle "Schon einen Account?"
9
+ // entscheidet Branch 2 (existing user, login + accept) vs Branch 3
10
+ // (neuer user, signup + accept).
11
+ //
12
+ // Anti-enumeration: invalidInviteToken collapsed alle Token/User/
13
+ // Password-Failures auf einen Code (gleicher Trade-off wie reset).
14
+ //
15
+ // Auto-Login: Branch 2+3 setzen Cookies via Server, Frontend redirected
16
+ // zu loggedInHref. Branch 1 hat schon eine Session — Frontend redirected
17
+ // auch zu loggedInHref damit der invitee in seinem neuen Tenant landet.
18
+
19
+ import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
20
+ import { type FormEvent, type ReactNode, useState } from "react";
21
+ import { csrfHeader } from "./auth-client";
22
+ import { AuthCard, authMutedLinkClass, parseUrlToken } from "./auth-form-primitives";
23
+ import { useSession } from "./session";
24
+
25
+ export type InviteAcceptScreenProps = {
26
+ readonly title?: string;
27
+ readonly token?: string;
28
+ /** Where to redirect on success. Default "/" — Apps mit Multi-Tenant-
29
+ * Routing können `(data) => "/${data.tenantId}/"` setzen. */
30
+ readonly loggedInHref?: string | ((args: { tenantId: string }) => string);
31
+ /** Login-Href für "Mit anderem Account anmelden". Default "/login". */
32
+ readonly loginHref?: string;
33
+ };
34
+
35
+ type Mode = "loggedin" | "anon-existing" | "anon-new";
36
+
37
+ export function InviteAcceptScreen({
38
+ title,
39
+ token: tokenProp,
40
+ loggedInHref = "/",
41
+ loginHref = "/login",
42
+ }: InviteAcceptScreenProps): ReactNode {
43
+ const t = useTranslation();
44
+ const { Form, Field, Input, Button, Banner } = usePrimitives();
45
+ const session = useSession();
46
+ const [token] = useState(() => tokenProp ?? parseUrlToken());
47
+ const [mode, setMode] = useState<Mode>(() =>
48
+ session.status === "authenticated" ? "loggedin" : "anon-existing",
49
+ );
50
+ const [email, setEmail] = useState("");
51
+ const [password, setPassword] = useState("");
52
+ const [submitting, setSubmitting] = useState(false);
53
+ const [error, setError] = useState<string | null>(null);
54
+
55
+ const effectiveTitle = title ?? t("auth.inviteAccept.title");
56
+
57
+ const acceptLoggedIn = async (): Promise<void> => {
58
+ setSubmitting(true);
59
+ setError(null);
60
+ const res = await fetch("/api/auth/invite-accept", {
61
+ method: "POST",
62
+ credentials: "same-origin",
63
+ headers: { "Content-Type": "application/json", ...csrfHeader() },
64
+ body: JSON.stringify({ token }),
65
+ });
66
+ setSubmitting(false);
67
+ if (res.ok) {
68
+ const data = (await res.json()) as { tenantId: string };
69
+ const target =
70
+ typeof loggedInHref === "function"
71
+ ? loggedInHref({ tenantId: data.tenantId })
72
+ : loggedInHref;
73
+ window.location.assign(target);
74
+ return;
75
+ }
76
+ setError(t("auth.errors.invalidInviteToken"));
77
+ };
78
+
79
+ const acceptAnonExisting = async (): Promise<void> => {
80
+ setSubmitting(true);
81
+ setError(null);
82
+ const res = await fetch("/api/auth/invite-accept-with-login", {
83
+ method: "POST",
84
+ credentials: "same-origin",
85
+ headers: { "Content-Type": "application/json", ...csrfHeader() },
86
+ body: JSON.stringify({ token, email, password }),
87
+ });
88
+ setSubmitting(false);
89
+ if (res.ok) {
90
+ const data = (await res.json()) as { tenantId: string };
91
+ const target =
92
+ typeof loggedInHref === "function"
93
+ ? loggedInHref({ tenantId: data.tenantId })
94
+ : loggedInHref;
95
+ window.location.assign(target);
96
+ return;
97
+ }
98
+ setError(t("auth.errors.invalidInviteToken"));
99
+ };
100
+
101
+ const acceptAnonNew = async (): Promise<void> => {
102
+ setSubmitting(true);
103
+ setError(null);
104
+ const res = await fetch("/api/auth/invite-signup-complete", {
105
+ method: "POST",
106
+ credentials: "same-origin",
107
+ headers: { "Content-Type": "application/json", ...csrfHeader() },
108
+ body: JSON.stringify({ token, password }),
109
+ });
110
+ setSubmitting(false);
111
+ if (res.ok) {
112
+ const data = (await res.json()) as { tenantId: string };
113
+ const target =
114
+ typeof loggedInHref === "function"
115
+ ? loggedInHref({ tenantId: data.tenantId })
116
+ : loggedInHref;
117
+ window.location.assign(target);
118
+ return;
119
+ }
120
+ setError(t("auth.errors.invalidInviteToken"));
121
+ };
122
+
123
+ const onSubmit = (e?: FormEvent): void => {
124
+ e?.preventDefault();
125
+ if (mode === "loggedin") {
126
+ void acceptLoggedIn();
127
+ return;
128
+ }
129
+ if (mode === "anon-existing") {
130
+ void acceptAnonExisting();
131
+ return;
132
+ }
133
+ void acceptAnonNew();
134
+ };
135
+
136
+ if (token === "") {
137
+ return (
138
+ <AuthCard title={effectiveTitle}>
139
+ <div className="p-6 pt-0 flex flex-col gap-4">
140
+ <p className="text-sm text-muted-foreground">{t("auth.inviteAccept.missingToken")}</p>
141
+ <a href={loginHref} className={authMutedLinkClass}>
142
+ {t("auth.inviteAccept.goToLogin")}
143
+ </a>
144
+ </div>
145
+ </AuthCard>
146
+ );
147
+ }
148
+
149
+ return (
150
+ <AuthCard title={effectiveTitle}>
151
+ <div className="p-6 pt-0 flex flex-col gap-4">
152
+ <p className="text-sm text-muted-foreground">{t("auth.inviteAccept.intro")}</p>
153
+
154
+ {mode === "loggedin" ? (
155
+ <Form onSubmit={onSubmit}>
156
+ <p className="text-sm">{t("auth.inviteAccept.loggedInAs")}</p>
157
+ {error !== null && <Banner variant="error">{error}</Banner>}
158
+ <Button type="submit" loading={submitting} disabled={submitting}>
159
+ {submitting ? t("auth.inviteAccept.submitting") : t("auth.inviteAccept.acceptButton")}
160
+ </Button>
161
+ <Button
162
+ type="button"
163
+ variant="secondary"
164
+ onClick={() => {
165
+ setMode("anon-existing");
166
+ }}
167
+ >
168
+ {t("auth.inviteAccept.useOtherAccount")}
169
+ </Button>
170
+ </Form>
171
+ ) : (
172
+ <Form onSubmit={onSubmit}>
173
+ {mode === "anon-existing" && (
174
+ <Field id="invite-email" label={t("auth.inviteAccept.email")} required>
175
+ <Input
176
+ kind="email"
177
+ id="invite-email"
178
+ name="invite-email"
179
+ value={email}
180
+ onChange={setEmail}
181
+ disabled={submitting}
182
+ required
183
+ autoComplete="email"
184
+ />
185
+ </Field>
186
+ )}
187
+ <Field id="invite-password" label={t("auth.inviteAccept.password")} required>
188
+ <Input
189
+ kind="password"
190
+ id="invite-password"
191
+ name="invite-password"
192
+ value={password}
193
+ onChange={setPassword}
194
+ disabled={submitting}
195
+ required
196
+ autoComplete={mode === "anon-existing" ? "current-password" : "new-password"}
197
+ />
198
+ </Field>
199
+ {error !== null && <Banner variant="error">{error}</Banner>}
200
+ <Button type="submit" loading={submitting} disabled={submitting}>
201
+ {submitting ? t("auth.inviteAccept.submitting") : t("auth.inviteAccept.submit")}
202
+ </Button>
203
+ <Button
204
+ type="button"
205
+ variant="secondary"
206
+ onClick={() => {
207
+ setMode(mode === "anon-existing" ? "anon-new" : "anon-existing");
208
+ setError(null);
209
+ }}
210
+ >
211
+ {mode === "anon-existing"
212
+ ? t("auth.inviteAccept.toggleNew")
213
+ : t("auth.inviteAccept.toggleExisting")}
214
+ </Button>
215
+ </Form>
216
+ )}
217
+ </div>
218
+ </AuthCard>
219
+ );
220
+ }
@@ -0,0 +1,150 @@
1
+ // @runtime client
2
+ // Default-LoginScreen. Reine E-Mail-+-Passwort-Form, zentriert, Card-
3
+ // Layout. Texte kommen aus `useTranslation()` — die Default-Bundles
4
+ // (de+en) liefert das Feature selber mit (translations.ts); Apps die
5
+ // was anderes wollen, setzen entweder einen eigenen LocaleResolver auf
6
+ // App-Level oder reichen Key-Overrides an `emailPasswordClient()`.
7
+ //
8
+ // Die Reason-Codes aus dem Login-Handler werden 1:1 auf i18n-Keys
9
+ // gemappt (reasonToI18nKey) — neue Reason-Codes brauchen nur eine
10
+ // neue Zeile im Bundle + hier im Mapping.
11
+
12
+ import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
13
+ import { type FormEvent, type ReactNode, useState } from "react";
14
+ import type { LoginFailure } from "./auth-client";
15
+ import { AuthCard, authMutedLinkClass } from "./auth-form-primitives";
16
+ import { useSession } from "./session";
17
+
18
+ export type LoginScreenProps = {
19
+ /** Overridet den `auth.login.title`-i18n-Key. Nur setzen wenn der
20
+ * Titel stark app-branded ist und keine Translation braucht. */
21
+ readonly title?: string;
22
+ readonly subtitle?: ReactNode;
23
+ readonly submitLabel?: string;
24
+ /** Optional href zum ForgotPasswordScreen. Wenn gesetzt rendert die
25
+ * LoginScreen unter dem Submit-Button einen "Passwort vergessen?"-
26
+ * Link. Apps die den Reset-Flow NICHT anbieten (z.B. nur Magic-Link),
27
+ * setzen das einfach nicht — Login bleibt minimalistisch. */
28
+ readonly forgotPasswordHref?: string;
29
+ /** Optional href zum SignupScreen. Wenn gesetzt rendert die LoginScreen
30
+ * einen "Account erstellen"-Link. Apps die kein Self-Signup wollen
31
+ * (closed-invite-only) setzen das einfach nicht. */
32
+ readonly signupHref?: string;
33
+ };
34
+
35
+ // Map vom Reason-Code des Login-Handlers auf einen i18n-Key plus
36
+ // optional extrahierte Interpolations-Parameter. Ungekannte Codes
37
+ // fallen auf `auth.errors.loginFailed` zurück.
38
+ function reasonToKey(failure: LoginFailure): {
39
+ readonly key: string;
40
+ readonly params?: Readonly<Record<string, unknown>>;
41
+ } {
42
+ switch (failure.reason) {
43
+ case "invalid_credentials":
44
+ return { key: "auth.errors.invalidCredentials" };
45
+ case "no_membership":
46
+ return { key: "auth.errors.noMembership" };
47
+ case "account_locked":
48
+ if (failure.retryAfterSeconds !== undefined) {
49
+ return {
50
+ key: "auth.errors.accountLockedRetry",
51
+ params: { minutes: Math.ceil(failure.retryAfterSeconds / 60) },
52
+ };
53
+ }
54
+ return { key: "auth.errors.accountLocked" };
55
+ case "email_not_verified":
56
+ return { key: "auth.errors.emailNotVerified" };
57
+ case "rate_limited":
58
+ return { key: "auth.errors.rateLimited" };
59
+ case "invalid_body":
60
+ return { key: "auth.errors.invalidBody" };
61
+ default:
62
+ return { key: "auth.errors.loginFailed" };
63
+ }
64
+ }
65
+
66
+ export function LoginScreen({
67
+ title,
68
+ subtitle,
69
+ submitLabel,
70
+ forgotPasswordHref,
71
+ signupHref,
72
+ }: LoginScreenProps): ReactNode {
73
+ const t = useTranslation();
74
+ const { Form, Field, Input, Button, Banner } = usePrimitives();
75
+ const session = useSession();
76
+ const [email, setEmail] = useState("");
77
+ const [password, setPassword] = useState("");
78
+ const [submitting, setSubmitting] = useState(false);
79
+ const [error, setError] = useState<LoginFailure | null>(null);
80
+
81
+ const doSubmit = async (): Promise<void> => {
82
+ setSubmitting(true);
83
+ setError(null);
84
+ const res = await session.login({ email, password });
85
+ setSubmitting(false);
86
+ if (!res.ok) setError(res.error);
87
+ };
88
+
89
+ const onSubmit = (e?: FormEvent): void => {
90
+ e?.preventDefault();
91
+ void doSubmit();
92
+ };
93
+
94
+ const effectiveTitle = title ?? t("auth.login.title");
95
+ const effectiveSubmit = submitLabel ?? t("auth.login.submit");
96
+
97
+ return (
98
+ <AuthCard title={effectiveTitle} subtitle={subtitle}>
99
+ <div className="p-6 pt-0 flex flex-col gap-4">
100
+ <Form onSubmit={onSubmit}>
101
+ <Field id="login-email" label={t("auth.login.email")} required>
102
+ <Input
103
+ kind="email"
104
+ id="login-email"
105
+ name="login-email"
106
+ value={email}
107
+ onChange={setEmail}
108
+ disabled={submitting}
109
+ required
110
+ autoComplete="email"
111
+ />
112
+ </Field>
113
+ <Field id="login-password" label={t("auth.login.password")} required>
114
+ <Input
115
+ kind="password"
116
+ id="login-password"
117
+ name="login-password"
118
+ value={password}
119
+ onChange={setPassword}
120
+ disabled={submitting}
121
+ required
122
+ autoComplete="current-password"
123
+ />
124
+ </Field>
125
+ {error !== null && (
126
+ <Banner variant="error">
127
+ {(() => {
128
+ const { key, params } = reasonToKey(error);
129
+ return t(key, params);
130
+ })()}
131
+ </Banner>
132
+ )}
133
+ <Button type="submit" loading={submitting} disabled={submitting}>
134
+ {submitting ? t("auth.login.submitting") : effectiveSubmit}
135
+ </Button>
136
+ </Form>
137
+ {forgotPasswordHref !== undefined && (
138
+ <a href={forgotPasswordHref} className={`${authMutedLinkClass} self-center`}>
139
+ {t("auth.login.forgotPassword")}
140
+ </a>
141
+ )}
142
+ {signupHref !== undefined && (
143
+ <a href={signupHref} className={`${authMutedLinkClass} self-center`}>
144
+ {t("auth.signup.title")}
145
+ </a>
146
+ )}
147
+ </div>
148
+ </AuthCard>
149
+ );
150
+ }
@@ -0,0 +1,152 @@
1
+ // @runtime client
2
+ // ResetPasswordScreen — liest `?token=...` aus der URL, zeigt Form mit
3
+ // new + confirm-password. Submit triggert /api/auth/reset-password mit
4
+ // dem Token. Server collapses alle Token-Verify-Failures auf einen
5
+ // Code (anti-enumeration); UI zeigt unified "Link ungültig oder
6
+ // abgelaufen"-message.
7
+ //
8
+ // Token-Quelle ist read-once: wir lesen via parseUrlToken() einmalig
9
+ // im useState-Initializer. Apps die das anders brauchen (server-
10
+ // injected Token-Prop, andere Parameter-Namen) reichen einen
11
+ // expliziten `token` als Prop durch.
12
+
13
+ import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
14
+ import { type FormEvent, type ReactNode, useState } from "react";
15
+ import { resetPassword } from "./auth-client";
16
+ import {
17
+ AuthCard,
18
+ authButtonClass,
19
+ authMutedLinkClass,
20
+ parseUrlToken,
21
+ } from "./auth-form-primitives";
22
+
23
+ export type ResetPasswordScreenProps = {
24
+ readonly title?: string;
25
+ /** Override für den Token aus der URL — Apps die per server-side-
26
+ * Render einen Token reinreichen, brauchen das. Default: parsed aus
27
+ * `?token=...` in der URL. */
28
+ readonly token?: string;
29
+ /** href für "Zum Login"-Link nach Success. Default "/login". */
30
+ readonly loginHref?: string;
31
+ };
32
+
33
+ export function ResetPasswordScreen({
34
+ title,
35
+ token: tokenProp,
36
+ loginHref = "/login",
37
+ }: ResetPasswordScreenProps): ReactNode {
38
+ const t = useTranslation();
39
+ const { Form, Field, Input, Button, Banner } = usePrimitives();
40
+ const [token] = useState(() => tokenProp ?? parseUrlToken());
41
+ const [newPassword, setNewPassword] = useState("");
42
+ const [confirmPassword, setConfirmPassword] = useState("");
43
+ const [submitting, setSubmitting] = useState(false);
44
+ const [done, setDone] = useState(false);
45
+ const [error, setError] = useState<string | null>(null);
46
+
47
+ const doSubmit = async (): Promise<void> => {
48
+ setError(null);
49
+ if (newPassword.length < 8) {
50
+ setError(t("auth.resetPassword.tooShort"));
51
+ return;
52
+ }
53
+ if (newPassword !== confirmPassword) {
54
+ setError(t("auth.resetPassword.mismatch"));
55
+ return;
56
+ }
57
+ setSubmitting(true);
58
+ const res = await resetPassword(token, newPassword);
59
+ setSubmitting(false);
60
+ if (res.ok) {
61
+ setDone(true);
62
+ return;
63
+ }
64
+ if (res.error.reason === "invalid_reset_token") {
65
+ setError(t("auth.errors.invalidResetToken"));
66
+ return;
67
+ }
68
+ if (res.error.reason === "rate_limited") {
69
+ setError(t("auth.errors.rateLimited"));
70
+ return;
71
+ }
72
+ setError(t("auth.errors.unknownError"));
73
+ };
74
+
75
+ const onSubmit = (e?: FormEvent): void => {
76
+ e?.preventDefault();
77
+ void doSubmit();
78
+ };
79
+
80
+ const effectiveTitle = title ?? t("auth.resetPassword.title");
81
+
82
+ // Kein Token in der URL → User soll den Link aus seiner Mail nochmal
83
+ // klicken oder einen neuen Reset anfordern. Form ohne Token zu
84
+ // submitten würde nur den invalidResetToken-Error zeigen — das ist
85
+ // verwirrend. Lieber upfront eine klare Message.
86
+ if (token === "") {
87
+ return (
88
+ <AuthCard title={effectiveTitle}>
89
+ <div className="p-6 pt-0 flex flex-col gap-4">
90
+ <p className="text-sm text-muted-foreground">{t("auth.resetPassword.missingToken")}</p>
91
+ <a href={loginHref} className={authMutedLinkClass}>
92
+ {t("auth.resetPassword.goToLogin")}
93
+ </a>
94
+ </div>
95
+ </AuthCard>
96
+ );
97
+ }
98
+
99
+ return (
100
+ <AuthCard title={effectiveTitle}>
101
+ {done ? (
102
+ <div className="p-6 pt-0 flex flex-col gap-4">
103
+ <Banner variant="info">
104
+ <p className="font-medium text-foreground">{t("auth.resetPassword.successTitle")}</p>
105
+ <p className="mt-1">{t("auth.resetPassword.successBody")}</p>
106
+ </Banner>
107
+ <a href={loginHref} className={authButtonClass}>
108
+ {t("auth.resetPassword.goToLogin")}
109
+ </a>
110
+ </div>
111
+ ) : (
112
+ <div className="p-6 pt-0 flex flex-col gap-4">
113
+ <p className="text-sm text-muted-foreground">{t("auth.resetPassword.intro")}</p>
114
+ <Form onSubmit={onSubmit}>
115
+ <Field id="reset-new-password" label={t("auth.resetPassword.newPassword")} required>
116
+ <Input
117
+ kind="password"
118
+ id="reset-new-password"
119
+ name="reset-new-password"
120
+ value={newPassword}
121
+ onChange={setNewPassword}
122
+ disabled={submitting}
123
+ required
124
+ autoComplete="new-password"
125
+ />
126
+ </Field>
127
+ <Field
128
+ id="reset-confirm-password"
129
+ label={t("auth.resetPassword.confirmPassword")}
130
+ required
131
+ >
132
+ <Input
133
+ kind="password"
134
+ id="reset-confirm-password"
135
+ name="reset-confirm-password"
136
+ value={confirmPassword}
137
+ onChange={setConfirmPassword}
138
+ disabled={submitting}
139
+ required
140
+ autoComplete="new-password"
141
+ />
142
+ </Field>
143
+ {error !== null && <Banner variant="error">{error}</Banner>}
144
+ <Button type="submit" loading={submitting} disabled={submitting}>
145
+ {submitting ? t("auth.resetPassword.submitting") : t("auth.resetPassword.submit")}
146
+ </Button>
147
+ </Form>
148
+ </div>
149
+ )}
150
+ </AuthCard>
151
+ );
152
+ }