@k2b/cloud 0.6.0-rc.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 (540) hide show
  1. package/README.md +98 -0
  2. package/package.json +128 -0
  3. package/public/logo.svg +1 -0
  4. package/scripts/app-favicon.test.ts +49 -0
  5. package/scripts/app-favicon.ts +51 -0
  6. package/scripts/build.ts +205 -0
  7. package/scripts/preload.ts +164 -0
  8. package/scripts/sync-recovery-smoke.ts +200 -0
  9. package/src/_internal/app-favicon.ts +2 -0
  10. package/src/_internal/app-presentation.ts +61 -0
  11. package/src/_internal/app-workspace-state.ts +24 -0
  12. package/src/_internal/bounded-json.ts +47 -0
  13. package/src/_internal/build-metadata.ts +9 -0
  14. package/src/_internal/capabilities.ts +1118 -0
  15. package/src/_internal/define-app.ts +801 -0
  16. package/src/_internal/heartbeat.ts +167 -0
  17. package/src/_internal/help-catalog.ts +241 -0
  18. package/src/_internal/help.ts +95 -0
  19. package/src/_internal/nats-connection.ts +34 -0
  20. package/src/_internal/page-responses.ts +40 -0
  21. package/src/_internal/process-sync.ts +117 -0
  22. package/src/_internal/readiness.ts +3 -0
  23. package/src/_internal/registry-validation.ts +136 -0
  24. package/src/_internal/registry-watch.ts +52 -0
  25. package/src/_internal/registry.ts +338 -0
  26. package/src/_internal/runtime-compatibility.ts +64 -0
  27. package/src/_internal/runtime-context.ts +84 -0
  28. package/src/_internal/runtime-watcher.ts +78 -0
  29. package/src/_internal/static-assets.ts +86 -0
  30. package/src/_internal/status-preserving-ssr.ts +49 -0
  31. package/src/access/PermissionEditor.tsx +486 -0
  32. package/src/access/ResourceApiKeys.tsx +271 -0
  33. package/src/access/messages.ts +121 -0
  34. package/src/access/ui.ts +16 -0
  35. package/src/account/Avatar.tsx +28 -0
  36. package/src/account/EntitySearch.tsx +363 -0
  37. package/src/account/avatar-upload-dialog.tsx +197 -0
  38. package/src/account/avatar-upload.ts +149 -0
  39. package/src/account/ui.ts +3 -0
  40. package/src/ai/admin.ts +47 -0
  41. package/src/ai/approval-routes.ts +130 -0
  42. package/src/ai/approvals.ts +139 -0
  43. package/src/ai/assistant-models.ts +61 -0
  44. package/src/ai/attachments.ts +40 -0
  45. package/src/ai/browser.ts +92 -0
  46. package/src/ai/capabilities.ts +714 -0
  47. package/src/ai/capability-execution.ts +151 -0
  48. package/src/ai/chat/blocks.render.test.tsx +1108 -0
  49. package/src/ai/chat/blocks.tsx +730 -0
  50. package/src/ai/chat/builtin-tools.tsx +428 -0
  51. package/src/ai/chat/capability-table.tsx +87 -0
  52. package/src/ai/chat/composer-adapter.ts +169 -0
  53. package/src/ai/chat/file-tools.tsx +81 -0
  54. package/src/ai/chat/message-actions.tsx +370 -0
  55. package/src/ai/chat/message-utils.ts +479 -0
  56. package/src/ai/chat/messages.ts +43 -0
  57. package/src/ai/chat/presentation.tsx +395 -0
  58. package/src/ai/chat/primitives.tsx +3 -0
  59. package/src/ai/chat/tool-disclosure.tsx +41 -0
  60. package/src/ai/chat/turn-navigator-utils.ts +28 -0
  61. package/src/ai/chat/turn-navigator.tsx +164 -0
  62. package/src/ai/chat/user-message.tsx +149 -0
  63. package/src/ai/chat/visual-tools.tsx +435 -0
  64. package/src/ai/chat/web-tools.tsx +202 -0
  65. package/src/ai/chat-task-contracts.ts +113 -0
  66. package/src/ai/chat-tasks.ts +952 -0
  67. package/src/ai/client/controller.ts +1211 -0
  68. package/src/ai/client/file-source.ts +95 -0
  69. package/src/ai/client/live-connection.ts +140 -0
  70. package/src/ai/client/projection.ts +178 -0
  71. package/src/ai/client/transport.ts +111 -0
  72. package/src/ai/compaction.ts +182 -0
  73. package/src/ai/credentials.ts +124 -0
  74. package/src/ai/default-tool-contracts.ts +108 -0
  75. package/src/ai/default-tools.ts +127 -0
  76. package/src/ai/enrich.ts +280 -0
  77. package/src/ai/executor.ts +1624 -0
  78. package/src/ai/fetch-file-tool.ts +313 -0
  79. package/src/ai/file-context.ts +92 -0
  80. package/src/ai/file-media-type.ts +28 -0
  81. package/src/ai/file-mount.ts +16 -0
  82. package/src/ai/file-tools.ts +566 -0
  83. package/src/ai/files-store.ts +390 -0
  84. package/src/ai/firecrawl-tools.ts +305 -0
  85. package/src/ai/grids-skill.ts +126 -0
  86. package/src/ai/http.ts +265 -0
  87. package/src/ai/index.ts +337 -0
  88. package/src/ai/kit-skill.ts +22 -0
  89. package/src/ai/limits.ts +5 -0
  90. package/src/ai/live-events.ts +289 -0
  91. package/src/ai/live-messages.ts +45 -0
  92. package/src/ai/live-outbox.ts +102 -0
  93. package/src/ai/live-routes.ts +412 -0
  94. package/src/ai/live.ts +2 -0
  95. package/src/ai/maintenance.ts +302 -0
  96. package/src/ai/markdown-pdf-tool.ts +95 -0
  97. package/src/ai/memories.ts +605 -0
  98. package/src/ai/memory-learning-runs.ts +222 -0
  99. package/src/ai/memory-learning.ts +619 -0
  100. package/src/ai/memory-tool.ts +120 -0
  101. package/src/ai/memory-workflow-evidence.ts +158 -0
  102. package/src/ai/migrate.ts +2082 -0
  103. package/src/ai/model-access-migrate.ts +30 -0
  104. package/src/ai/model-access.ts +191 -0
  105. package/src/ai/personal-agent.ts +6 -0
  106. package/src/ai/prefs.ts +72 -0
  107. package/src/ai/project-tool.ts +94 -0
  108. package/src/ai/projects-routes.ts +277 -0
  109. package/src/ai/projects.ts +902 -0
  110. package/src/ai/protocol.ts +279 -0
  111. package/src/ai/provider.ts +73 -0
  112. package/src/ai/public-projection.ts +25 -0
  113. package/src/ai/resource-markers.ts +31 -0
  114. package/src/ai/resource-refs.ts +70 -0
  115. package/src/ai/routes.ts +1131 -0
  116. package/src/ai/runtime-tools.ts +39 -0
  117. package/src/ai/runtime.ts +544 -0
  118. package/src/ai/settings.ts +529 -0
  119. package/src/ai/short-id.ts +45 -0
  120. package/src/ai/skill-catalog.ts +73 -0
  121. package/src/ai/skill-format.ts +145 -0
  122. package/src/ai/skill-seeds.ts +430 -0
  123. package/src/ai/skill-tool.ts +81 -0
  124. package/src/ai/skills-routes.ts +143 -0
  125. package/src/ai/skills.ts +934 -0
  126. package/src/ai/solid.ts +27 -0
  127. package/src/ai/store.ts +3113 -0
  128. package/src/ai/stream.ts +308 -0
  129. package/src/ai/structured-runs.ts +66 -0
  130. package/src/ai/structured.ts +186 -0
  131. package/src/ai/system-prompt.ts +170 -0
  132. package/src/ai/task-prompt.ts +22 -0
  133. package/src/ai/timeline.ts +148 -0
  134. package/src/ai/tool-audit.ts +177 -0
  135. package/src/ai/tool-result-budget.ts +16 -0
  136. package/src/ai/tools.ts +155 -0
  137. package/src/ai/types.ts +967 -0
  138. package/src/ai/ui.tsx +39 -0
  139. package/src/ai/usage-migrate.ts +81 -0
  140. package/src/ai/usage.ts +315 -0
  141. package/src/ai/validate.ts +54 -0
  142. package/src/ai/vision-tool.ts +79 -0
  143. package/src/api/accounts-entities.ts +174 -0
  144. package/src/api/admin-ai-projects.ts +74 -0
  145. package/src/api/admin-ai-skills.ts +76 -0
  146. package/src/api/admin-ai-usage.ts +42 -0
  147. package/src/api/admin-core-settings.ts +508 -0
  148. package/src/api/admin-identity.ts +148 -0
  149. package/src/api/admin-lifecycle.ts +206 -0
  150. package/src/api/admin-linux-identities.ts +66 -0
  151. package/src/api/announcements.ts +131 -0
  152. package/src/api/app-approval.ts +129 -0
  153. package/src/api/apps.ts +51 -0
  154. package/src/api/auth/schemas.ts +54 -0
  155. package/src/api/auth.ts +370 -0
  156. package/src/api/capabilities.ts +658 -0
  157. package/src/api/help-messages.ts +19 -0
  158. package/src/api/help.ts +78 -0
  159. package/src/api/index.ts +108 -0
  160. package/src/api/mcp.ts +744 -0
  161. package/src/api/me-rail.ts +45 -0
  162. package/src/api/me.ts +753 -0
  163. package/src/api/search/schemas.ts +79 -0
  164. package/src/api/search.bench.ts +185 -0
  165. package/src/api/search.ts +407 -0
  166. package/src/api/sync-ops.ts +79 -0
  167. package/src/api/widgets.ts +143 -0
  168. package/src/browser/CloudResourceSearch.behavior.test.tsx +74 -0
  169. package/src/browser/CloudResourceSearch.tsx +791 -0
  170. package/src/browser/app-approval-vault.ts +305 -0
  171. package/src/browser/app-approval.ts +234 -0
  172. package/src/browser/live-websocket.ts +268 -0
  173. package/src/browser/locale-preference.ts +25 -0
  174. package/src/browser/notification-target.ts +27 -0
  175. package/src/browser/notification-timeout.ts +14 -0
  176. package/src/browser/notifications.ts +181 -0
  177. package/src/browser/resource-clipboard.ts +70 -0
  178. package/src/browser/resource-picker.tsx +39 -0
  179. package/src/browser/resource-search-messages.ts +55 -0
  180. package/src/browser/resource-search.ts +27 -0
  181. package/src/capabilities/client.ts +149 -0
  182. package/src/capabilities/response.ts +39 -0
  183. package/src/capabilities/server.ts +215 -0
  184. package/src/capabilities/signals.ts +4 -0
  185. package/src/capabilities/testing.ts +12 -0
  186. package/src/capabilities/types.ts +32 -0
  187. package/src/cli/access.ts +499 -0
  188. package/src/cli/account.ts +382 -0
  189. package/src/cli/admin/account-administration.ts +45 -0
  190. package/src/cli/admin/account-categories.ts +49 -0
  191. package/src/cli/admin/ai-usage.ts +118 -0
  192. package/src/cli/admin/app-sign-in.ts +49 -0
  193. package/src/cli/admin/data.ts +343 -0
  194. package/src/cli/admin/documentation.ts +34 -0
  195. package/src/cli/admin/gateway.ts +155 -0
  196. package/src/cli/admin/index.ts +61 -0
  197. package/src/cli/admin/instance.ts +250 -0
  198. package/src/cli/admin/jobs.ts +223 -0
  199. package/src/cli/admin/legal.ts +122 -0
  200. package/src/cli/admin/linux.ts +53 -0
  201. package/src/cli/admin/logs.ts +324 -0
  202. package/src/cli/admin/metrics.ts +142 -0
  203. package/src/cli/admin/nats.ts +167 -0
  204. package/src/cli/admin/notifications.ts +516 -0
  205. package/src/cli/admin/shared.ts +113 -0
  206. package/src/cli/admin/sync.ts +188 -0
  207. package/src/cli/admin/telemetry.ts +314 -0
  208. package/src/cli/admin/webhooks.ts +222 -0
  209. package/src/cli/admin/workflows.ts +367 -0
  210. package/src/cli/apps.ts +40 -0
  211. package/src/cli/capabilities.ts +239 -0
  212. package/src/cli/commands.ts +604 -0
  213. package/src/cli/index.ts +61 -0
  214. package/src/cli/locale.ts +31 -0
  215. package/src/cli/output.ts +58 -0
  216. package/src/clients/core.ts +20 -0
  217. package/src/config/env.ts +54 -0
  218. package/src/config/index.ts +6 -0
  219. package/src/contracts/account-categories.ts +62 -0
  220. package/src/contracts/announcements.ts +121 -0
  221. package/src/contracts/app-approval.ts +197 -0
  222. package/src/contracts/app.ts +162 -0
  223. package/src/contracts/capabilities.ts +484 -0
  224. package/src/contracts/index.ts +14 -0
  225. package/src/contracts/notification-live.ts +66 -0
  226. package/src/contracts/notification-types.ts +235 -0
  227. package/src/contracts/posix.ts +69 -0
  228. package/src/contracts/profile.ts +85 -0
  229. package/src/contracts/rail-preferences.ts +53 -0
  230. package/src/contracts/registry.ts +110 -0
  231. package/src/contracts/resource-clipboard.ts +33 -0
  232. package/src/contracts/settings-types.ts +98 -0
  233. package/src/contracts/shared.ts +408 -0
  234. package/src/contracts/user-notifications.ts +101 -0
  235. package/src/contracts/widgets.ts +183 -0
  236. package/src/desktop/index.ts +704 -0
  237. package/src/desktop/solid.tsx +938 -0
  238. package/src/index.ts +58 -0
  239. package/src/server/actor.ts +41 -0
  240. package/src/server/api/index.ts +2 -0
  241. package/src/server/api/respond.ts +96 -0
  242. package/src/server/api-client.ts +54 -0
  243. package/src/server/app-context.ts +39 -0
  244. package/src/server/help.ts +187 -0
  245. package/src/server/index.ts +89 -0
  246. package/src/server/locale.ts +55 -0
  247. package/src/server/middleware/auth.ts +386 -0
  248. package/src/server/middleware/index.ts +28 -0
  249. package/src/server/middleware/invocation.ts +55 -0
  250. package/src/server/middleware/middleware.ts +65 -0
  251. package/src/server/middleware/openapi.ts +126 -0
  252. package/src/server/middleware/rate-limit.ts +125 -0
  253. package/src/server/middleware/request-logger.ts +41 -0
  254. package/src/server/middleware/route-template.ts +46 -0
  255. package/src/server/middleware/runtime.ts +22 -0
  256. package/src/server/middleware/settings.ts +55 -0
  257. package/src/server/middleware/validator.ts +49 -0
  258. package/src/server/middleware/workload.ts +22 -0
  259. package/src/server/ratelimit.ts +151 -0
  260. package/src/server/request-cookies.ts +18 -0
  261. package/src/server/services/access.ts +644 -0
  262. package/src/server/services/freeipa/client.ts +137 -0
  263. package/src/server/services/freeipa/index.ts +9 -0
  264. package/src/server/services/freeipa/session.ts +123 -0
  265. package/src/server/services/freeipa/test-certificates.ts +44 -0
  266. package/src/server/services/freeipa/tls.ts +44 -0
  267. package/src/server/services/freeipa/transport.ts +116 -0
  268. package/src/server/services/freeipa/util.ts +59 -0
  269. package/src/server/services/geo.ts +198 -0
  270. package/src/server/services/index.ts +48 -0
  271. package/src/server/services/pagination.ts +22 -0
  272. package/src/server/services/services.ts +13 -0
  273. package/src/server/time.ts +33 -0
  274. package/src/services/account-category-policy.ts +51 -0
  275. package/src/services/account-lifecycle/audit.ts +41 -0
  276. package/src/services/account-lifecycle/index.ts +940 -0
  277. package/src/services/account-lifecycle/ipa-backfill.ts +152 -0
  278. package/src/services/account-lifecycle/notification-sender.ts +13 -0
  279. package/src/services/account-lifecycle/scheduler.ts +506 -0
  280. package/src/services/account-model.ts +22 -0
  281. package/src/services/accounts/app.ts +1671 -0
  282. package/src/services/accounts/authz.ts +44 -0
  283. package/src/services/accounts/avatar.ts +120 -0
  284. package/src/services/accounts/base-group.ts +11 -0
  285. package/src/services/accounts/base-user.ts +46 -0
  286. package/src/services/accounts/duplicate-emails.ts +82 -0
  287. package/src/services/accounts/entities.ts +622 -0
  288. package/src/services/accounts/group-sql.ts +106 -0
  289. package/src/services/accounts/groups.ts +272 -0
  290. package/src/services/accounts/index.ts +14 -0
  291. package/src/services/accounts/ipa-data.ts +66 -0
  292. package/src/services/accounts/lifecycle.ts +2 -0
  293. package/src/services/accounts/local-groups.ts +499 -0
  294. package/src/services/accounts/model.ts +129 -0
  295. package/src/services/accounts/notification-sender.ts +31 -0
  296. package/src/services/accounts/posix.ts +386 -0
  297. package/src/services/accounts/request-policy.ts +11 -0
  298. package/src/services/accounts/switching.ts +126 -0
  299. package/src/services/accounts/users.ts +745 -0
  300. package/src/services/announcements/index.ts +224 -0
  301. package/src/services/app-approval.ts +498 -0
  302. package/src/services/audit/index.ts +438 -0
  303. package/src/services/auth-flows/index.ts +15 -0
  304. package/src/services/auth-flows/ipa.ts +197 -0
  305. package/src/services/auth-flows/magic-link.ts +185 -0
  306. package/src/services/auth-flows/notification-sender.ts +10 -0
  307. package/src/services/auth-flows/password-reset.ts +268 -0
  308. package/src/services/auth-flows/proxy-return.ts +49 -0
  309. package/src/services/document-extraction.ts +148 -0
  310. package/src/services/freeipa-config.ts +179 -0
  311. package/src/services/gateway.ts +186 -0
  312. package/src/services/identity/constants.ts +18 -0
  313. package/src/services/identity/index.ts +52 -0
  314. package/src/services/identity/invocation-actor.ts +120 -0
  315. package/src/services/identity/invocation-authority.ts +53 -0
  316. package/src/services/identity/invocation-operations.ts +15 -0
  317. package/src/services/identity/invocation-token.ts +276 -0
  318. package/src/services/identity/key-config.ts +41 -0
  319. package/src/services/identity/key-ring.ts +530 -0
  320. package/src/services/identity/metrics.ts +29 -0
  321. package/src/services/identity/routes.ts +18 -0
  322. package/src/services/identity/runtime-config.ts +88 -0
  323. package/src/services/identity/session-token.ts +152 -0
  324. package/src/services/identity/signer-refresh.ts +44 -0
  325. package/src/services/identity/workload-auth.ts +64 -0
  326. package/src/services/index.ts +187 -0
  327. package/src/services/ipa/auth.ts +216 -0
  328. package/src/services/ipa/connection.ts +57 -0
  329. package/src/services/ipa/effective-groups.ts +70 -0
  330. package/src/services/ipa/groups.ts +722 -0
  331. package/src/services/ipa/guard.ts +17 -0
  332. package/src/services/ipa/index.ts +17 -0
  333. package/src/services/ipa/posix.ts +19 -0
  334. package/src/services/ipa/profile.ts +132 -0
  335. package/src/services/ipa/runtime.ts +28 -0
  336. package/src/services/ipa/search.ts +154 -0
  337. package/src/services/ipa/service-account.ts +15 -0
  338. package/src/services/ipa/sync-planning.ts +100 -0
  339. package/src/services/ipa/sync.ts +862 -0
  340. package/src/services/ipa/users.ts +806 -0
  341. package/src/services/legal-consent.ts +76 -0
  342. package/src/services/logging/index.ts +477 -0
  343. package/src/services/logging/redaction.ts +20 -0
  344. package/src/services/logging/trace.ts +1231 -0
  345. package/src/services/mandates/index.ts +855 -0
  346. package/src/services/mandates/policy.ts +85 -0
  347. package/src/services/network-security.ts +31 -0
  348. package/src/services/notifications/batches.ts +722 -0
  349. package/src/services/notifications/browser.ts +252 -0
  350. package/src/services/notifications/catalog.ts +113 -0
  351. package/src/services/notifications/channels.ts +104 -0
  352. package/src/services/notifications/dispatcher.ts +139 -0
  353. package/src/services/notifications/email.ts +118 -0
  354. package/src/services/notifications/index.ts +633 -0
  355. package/src/services/notifications/live.ts +50 -0
  356. package/src/services/notifications/observability.ts +407 -0
  357. package/src/services/notifications/platform.ts +421 -0
  358. package/src/services/notifications/runtime.ts +120 -0
  359. package/src/services/notifications/user.ts +248 -0
  360. package/src/services/notifications/web-push-transport.ts +135 -0
  361. package/src/services/oauth-tokens.ts +209 -0
  362. package/src/services/outbox.ts +187 -0
  363. package/src/services/pdf/gotenberg.ts +290 -0
  364. package/src/services/pdf/index.ts +43 -0
  365. package/src/services/pdf/markdown.ts +209 -0
  366. package/src/services/pdf/template-preview.ts +122 -0
  367. package/src/services/postgres.ts +76 -0
  368. package/src/services/providers/index.ts +27 -0
  369. package/src/services/providers/local/auth.ts +51 -0
  370. package/src/services/providers/local/index.ts +4 -0
  371. package/src/services/providers/local/users.ts +263 -0
  372. package/src/services/rail-preferences.ts +27 -0
  373. package/src/services/runtime-lifecycle.ts +191 -0
  374. package/src/services/secrets.ts +10 -0
  375. package/src/services/service-account-credentials.ts +891 -0
  376. package/src/services/service-accounts.ts +184 -0
  377. package/src/services/session/index.ts +236 -0
  378. package/src/services/session/test-fixture.ts +18 -0
  379. package/src/services/session/user.ts +144 -0
  380. package/src/services/settings/api.ts +61 -0
  381. package/src/services/settings/app.ts +86 -0
  382. package/src/services/settings/core-settings.ts +796 -0
  383. package/src/services/settings/crypto.ts +69 -0
  384. package/src/services/settings/defaults.ts +374 -0
  385. package/src/services/settings/index.ts +215 -0
  386. package/src/services/settings/namespace.ts +9 -0
  387. package/src/services/settings/snapshot.ts +49 -0
  388. package/src/services/settings/store.ts +241 -0
  389. package/src/services/settings/templates.ts +4 -0
  390. package/src/services/sync-ops.ts +403 -0
  391. package/src/services/topic-cursor.ts +22 -0
  392. package/src/services/weather/forecast.ts +323 -0
  393. package/src/services/weather/geo.ts +119 -0
  394. package/src/services/weather/index.ts +67 -0
  395. package/src/services/weather/location.ts +24 -0
  396. package/src/services/weather/locations.ts +133 -0
  397. package/src/services/weather/migrate.ts +45 -0
  398. package/src/services/weather/settings.ts +66 -0
  399. package/src/services/weather/short-id.ts +57 -0
  400. package/src/services/weather/types.ts +61 -0
  401. package/src/services/weather/ui.ts +50 -0
  402. package/src/services/webauthn.ts +385 -0
  403. package/src/shared/account-action-notice.ts +70 -0
  404. package/src/shared/account-display.ts +15 -0
  405. package/src/shared/account-session.ts +15 -0
  406. package/src/shared/ai-platform-prompt.ts +134 -0
  407. package/src/shared/ai-usage.ts +49 -0
  408. package/src/shared/app-presentation.ts +48 -0
  409. package/src/shared/app-url.ts +12 -0
  410. package/src/shared/branding.ts +14 -0
  411. package/src/shared/capability-messages.ts +70 -0
  412. package/src/shared/email-html.ts +54 -0
  413. package/src/shared/format.ts +91 -0
  414. package/src/shared/help.ts +68 -0
  415. package/src/shared/icons.ts +400 -0
  416. package/src/shared/index.ts +54 -0
  417. package/src/shared/locale.ts +48 -0
  418. package/src/shared/login-method.ts +44 -0
  419. package/src/shared/markdown/client.ts +130 -0
  420. package/src/shared/markdown/extensions/code.ts +105 -0
  421. package/src/shared/markdown/extensions/guided-help.ts +100 -0
  422. package/src/shared/markdown/extensions/images.ts +79 -0
  423. package/src/shared/markdown/extensions/info-blocks.ts +97 -0
  424. package/src/shared/markdown/extensions/katex.ts +120 -0
  425. package/src/shared/markdown/extensions/links.ts +43 -0
  426. package/src/shared/markdown/extensions/mark.ts +48 -0
  427. package/src/shared/markdown/extensions/sub-sup.ts +60 -0
  428. package/src/shared/markdown/extensions/tables.ts +109 -0
  429. package/src/shared/markdown/extensions/task-list.ts +53 -0
  430. package/src/shared/markdown/formula.ts +1187 -0
  431. package/src/shared/markdown/index.ts +193 -0
  432. package/src/shared/markdown/shared.ts +36 -0
  433. package/src/shared/mock-cover.ts +130 -0
  434. package/src/shared/network-address.ts +106 -0
  435. package/src/shared/redirect.ts +56 -0
  436. package/src/shared/template-rendering.ts +140 -0
  437. package/src/shared/theme.ts +68 -0
  438. package/src/shared/time.ts +13 -0
  439. package/src/ssr/AdminLayout.tsx +53 -0
  440. package/src/ssr/AdminSidebar.tsx +50 -0
  441. package/src/ssr/AppLaunchpad.island.tsx +236 -0
  442. package/src/ssr/AppWorkspaceController.island.tsx +21 -0
  443. package/src/ssr/BrowserNotifications.island.tsx +135 -0
  444. package/src/ssr/GlobalAnnouncements.island.tsx +142 -0
  445. package/src/ssr/GlobalSearchDialog.behavior.test.tsx +96 -0
  446. package/src/ssr/GlobalSearchDialog.tsx +35 -0
  447. package/src/ssr/GlobalSearchHelpDialog.tsx +104 -0
  448. package/src/ssr/GlobalSearchTrigger.island.tsx +55 -0
  449. package/src/ssr/HotkeysHelpRail.island.tsx +55 -0
  450. package/src/ssr/Layout.render.test.tsx +148 -0
  451. package/src/ssr/Layout.tsx +292 -0
  452. package/src/ssr/LayoutBreadcrumbs.island.tsx +44 -0
  453. package/src/ssr/LayoutFooter.tsx +22 -0
  454. package/src/ssr/LayoutHeader.tsx +81 -0
  455. package/src/ssr/LayoutHelp.tsx +869 -0
  456. package/src/ssr/LayoutPreferences.island.tsx +36 -0
  457. package/src/ssr/LayoutRail.tsx +58 -0
  458. package/src/ssr/MinimalLayout.tsx +46 -0
  459. package/src/ssr/PageError.tsx +29 -0
  460. package/src/ssr/ProfilePreferences.island.tsx +92 -0
  461. package/src/ssr/RailApps.island.tsx +61 -0
  462. package/src/ssr/RailEditor.tsx +293 -0
  463. package/src/ssr/RegisteredHelpDocuments.island.tsx +3 -0
  464. package/src/ssr/TimezoneCookie.island.tsx +23 -0
  465. package/src/ssr/admin-active-link.ts +47 -0
  466. package/src/ssr/admin-navigation.ts +78 -0
  467. package/src/ssr/app-appearance.ts +23 -0
  468. package/src/ssr/app-navigation.ts +41 -0
  469. package/src/ssr/help-messages.ts +104 -0
  470. package/src/ssr/index.ts +14 -0
  471. package/src/ssr/islands/SearchBar.island.tsx +80 -0
  472. package/src/ssr/islands/index.ts +16 -0
  473. package/src/ssr/layout-context.ts +17 -0
  474. package/src/ssr/layout-help-markdown.ts +9 -0
  475. package/src/ssr/layout-help-navigation.ts +24 -0
  476. package/src/ssr/layout-help-url.ts +5 -0
  477. package/src/ssr/layout-runtime.ts +22 -0
  478. package/src/ssr/page-error-messages.ts +25 -0
  479. package/src/ssr/platform-messages.ts +135 -0
  480. package/src/ssr/preference-controller.ts +44 -0
  481. package/src/ssr/profile-preferences-messages.ts +29 -0
  482. package/src/ssr/rail-context.ts +21 -0
  483. package/src/ssr/rail-messages.ts +66 -0
  484. package/src/ssr/rail-navigation.ts +60 -0
  485. package/src/ssr/runtime.ts +31 -0
  486. package/src/ssr/url-filter.ts +127 -0
  487. package/src/styles/base-popover.css +36 -0
  488. package/src/styles/effects.css +851 -0
  489. package/src/styles/global.css +268 -0
  490. package/src/styles/input.css +58 -0
  491. package/src/styles/tokens.css +230 -0
  492. package/src/styles/utilities-buttons.css +405 -0
  493. package/src/styles/utilities-data.css +33 -0
  494. package/src/styles/utilities-feedback.css +232 -0
  495. package/src/styles/utilities-layout.css +603 -0
  496. package/src/styles/utilities-markdown-editor.css +525 -0
  497. package/src/styles/utilities-markdown-table.css +234 -0
  498. package/src/styles/utilities-navigation.css +1054 -0
  499. package/src/types/ambient.d.ts +24 -0
  500. package/src/workflows/ai/index.ts +17 -0
  501. package/src/workflows/ai/runtime.ts +286 -0
  502. package/src/workflows/ai/store.ts +314 -0
  503. package/src/workflows/ai/types.ts +111 -0
  504. package/src/workflows/ai-actions.ts +269 -0
  505. package/src/workflows/authoring.ts +88 -0
  506. package/src/workflows/builtins.ts +193 -0
  507. package/src/workflows/contracts.ts +280 -0
  508. package/src/workflows/definition.ts +334 -0
  509. package/src/workflows/editor.ts +113 -0
  510. package/src/workflows/index.ts +71 -0
  511. package/src/workflows/language/binder.ts +45 -0
  512. package/src/workflows/language/canonical.ts +29 -0
  513. package/src/workflows/language/compiler.ts +396 -0
  514. package/src/workflows/language/expressions.ts +34 -0
  515. package/src/workflows/language/index.ts +20 -0
  516. package/src/workflows/language/references.ts +55 -0
  517. package/src/workflows/language/schema.ts +134 -0
  518. package/src/workflows/language/strict-yaml.ts +131 -0
  519. package/src/workflows/manifest.ts +24 -0
  520. package/src/workflows/module.ts +12 -0
  521. package/src/workflows/runtime/coordinator.ts +115 -0
  522. package/src/workflows/runtime/executor.ts +1003 -0
  523. package/src/workflows/runtime/index.ts +60 -0
  524. package/src/workflows/runtime/instant.ts +8 -0
  525. package/src/workflows/runtime/ports.ts +213 -0
  526. package/src/workflows/runtime/schedule.ts +203 -0
  527. package/src/workflows/runtime/trigger.ts +37 -0
  528. package/src/workflows/runtime/wake.ts +13 -0
  529. package/src/workflows/store/actions.ts +438 -0
  530. package/src/workflows/store/budget.ts +145 -0
  531. package/src/workflows/store/definitions.ts +345 -0
  532. package/src/workflows/store/events.ts +383 -0
  533. package/src/workflows/store/index.ts +107 -0
  534. package/src/workflows/store/observability.ts +594 -0
  535. package/src/workflows/store/runs.ts +975 -0
  536. package/src/workflows/store/transaction.ts +23 -0
  537. package/src/workflows/store/worker.ts +439 -0
  538. package/src/workflows/testing/conformance.ts +72 -0
  539. package/src/workflows/testing/index.ts +11 -0
  540. package/src/workflows/testing/process-fixtures.ts +275 -0
@@ -0,0 +1,185 @@
1
+ import { redis, sql } from "bun";
2
+ import { accountCategory } from "../../contracts/account-categories";
3
+ import type { User } from "../../contracts/shared";
4
+ import { createAuthLoginUrl } from "../../shared/redirect";
5
+ import { isAccountCategoryAllowed } from "../account-category-policy";
6
+ import { accounts } from "../accounts";
7
+ import { logger } from "../logging";
8
+ import { providers } from "../providers";
9
+ import * as settings from "../settings";
10
+ import type { AuthNotificationSender } from "./notification-sender";
11
+
12
+ const log = logger("auth:magic-link");
13
+ const IPA_HINT_COOLDOWN_SECONDS = 300;
14
+
15
+ const normalizeEmail = (email: string): string => email.trim().toLowerCase();
16
+ const ipaHintCooldownKey = (email: string): string => `ipa-email-login-hint-cooldown:${email}`;
17
+
18
+ const getAppUrl = async (): Promise<string> => {
19
+ const rawAppUrl = await settings.get<string>("app.url");
20
+ return rawAppUrl.startsWith("http") ? rawAppUrl : `https://${rawAppUrl}`;
21
+ };
22
+
23
+ const hasIpaAccountForEmail = async (email: string): Promise<boolean> => {
24
+ const rows = await sql<{ exists: boolean }[]>`
25
+ SELECT EXISTS (
26
+ SELECT 1
27
+ FROM auth.users
28
+ WHERE provider = 'ipa'
29
+ AND lower(btrim(mail)) = ${email}
30
+ ) AS exists
31
+ `;
32
+ return Boolean(rows[0]?.exists);
33
+ };
34
+
35
+ const claimIpaHintCooldown = async (email: string): Promise<boolean> => {
36
+ const result = await redis.send("SET", [ipaHintCooldownKey(email), "1", "EX", String(IPA_HINT_COOLDOWN_SECONDS), "NX"]);
37
+ return result === "OK";
38
+ };
39
+
40
+ const sendIpaEmailLoginHint = async (
41
+ params: { email: string; redirectTo?: string; locale?: string },
42
+ notificationSender: AuthNotificationSender,
43
+ ): Promise<void> => {
44
+ const appUrl = await getAppUrl();
45
+ const loginUrl = createAuthLoginUrl(appUrl, {
46
+ method: "ipa",
47
+ redirectTo: params.redirectTo,
48
+ });
49
+ const result = await notificationSender.sendIpaLoginHint({ email: params.email, loginUrl, locale: params.locale });
50
+ if (result.status === "error") log.error("FreeIPA login hint delivery failed", { notificationId: result.id });
51
+ };
52
+
53
+ export const request = async (
54
+ params: { email: string; redirectTo?: string; locale?: string; category?: "guest" | "login" },
55
+ notificationSender: AuthNotificationSender,
56
+ ): Promise<{ ok: true } | { ok: false; status: 400; message: string }> => {
57
+ const email = normalizeEmail(params.email);
58
+ const hasIpaUser = await hasIpaAccountForEmail(email);
59
+ const userRows = hasIpaUser
60
+ ? []
61
+ : await sql<
62
+ { provider: "local" | "ipa"; profile: "guest" | "user" }[]
63
+ >`SELECT provider, profile FROM auth.users WHERE lower(btrim(mail)) = ${email}`;
64
+ const localUser = userRows.find((row) => row.provider === "local");
65
+ const allowSelfRegistration = await settings.get<boolean>("user.allow_self_registration");
66
+
67
+ if (hasIpaUser) {
68
+ if (!(await isAccountCategoryAllowed({ provider: "ipa", profile: "user" }))) return { ok: true };
69
+ if (await claimIpaHintCooldown(email)) {
70
+ void sendIpaEmailLoginHint({ email, redirectTo: params.redirectTo, locale: params.locale }, notificationSender).catch((error) => {
71
+ log.warn("Failed to send FreeIPA email-login hint", {
72
+ email,
73
+ error: error instanceof Error ? error.message : String(error),
74
+ });
75
+ });
76
+ }
77
+ return { ok: true };
78
+ }
79
+
80
+ if (localUser && ((params.category && accountCategory(localUser) !== params.category) || !(await isAccountCategoryAllowed(localUser))))
81
+ return { ok: true };
82
+ if (
83
+ !localUser &&
84
+ (!allowSelfRegistration || params.category === "login" || !(await isAccountCategoryAllowed({ provider: "local", profile: "guest" })))
85
+ ) {
86
+ return { ok: true };
87
+ }
88
+
89
+ const token = await providers.local.auth.createMagicLinkToken({ email, category: params.category, ttlSeconds: 300 });
90
+ const appUrl = await getAppUrl();
91
+ const magicLink = createAuthLoginUrl(appUrl, { token, redirectTo: params.redirectTo });
92
+
93
+ try {
94
+ const result = await notificationSender.sendMagicLink({ email, token, magicLink, locale: params.locale });
95
+ if (result.status === "error") log.error("Magic link delivery failed", { notificationId: result.id });
96
+ } catch (error) {
97
+ // Keep the response generic to prevent account enumeration. The durable
98
+ // sender records accepted delivery failures; pre-persistence failures land here.
99
+ log.error("Magic link notification could not be accepted", {
100
+ error: error instanceof Error ? error.message : String(error),
101
+ });
102
+ }
103
+
104
+ return { ok: true };
105
+ };
106
+
107
+ export const verify = async (params: {
108
+ token: string;
109
+ }): Promise<
110
+ | { ok: true; userId: string; user: User; email: string; createdGuest: boolean }
111
+ | { ok: false; status: 401; message: string }
112
+ | { ok: false; status: number; message: string }
113
+ > => {
114
+ const payload = await providers.local.auth.consumeMagicLinkToken(params.token);
115
+ if (!payload) {
116
+ return { ok: false, status: 401, message: "Invalid or expired token" };
117
+ }
118
+
119
+ const { email, category } = payload;
120
+ const normalizedEmail = normalizeEmail(email);
121
+ if (await hasIpaAccountForEmail(normalizedEmail)) {
122
+ return {
123
+ ok: false,
124
+ status: 401,
125
+ message: "This email address belongs to a FreeIPA-managed account. Sign in with FreeIPA.",
126
+ };
127
+ }
128
+
129
+ // Reject expired accounts at login time, not just during cleanup. Without
130
+ // this, an expired local user / guest could still authenticate in the
131
+ // window between expiry and the next lifecycle run.
132
+ const userRows = await sql`
133
+ SELECT id, account_expires
134
+ FROM auth.users
135
+ WHERE lower(btrim(mail)) = ${normalizedEmail} AND provider = 'local'
136
+ AND (account_expires IS NULL OR account_expires > now())
137
+ ORDER BY profile = 'user' DESC
138
+ LIMIT 1
139
+ `;
140
+
141
+ let userId: string;
142
+ let createdGuest = false;
143
+ if (userRows.length > 0) {
144
+ userId = userRows[0]!.id as string;
145
+ } else {
146
+ // Distinguish "no account" from "account expired" for a better error.
147
+ const expiredRows = await sql`
148
+ SELECT id
149
+ FROM auth.users
150
+ WHERE lower(btrim(mail)) = ${normalizedEmail} AND provider = 'local'
151
+ AND account_expires IS NOT NULL AND account_expires <= now()
152
+ LIMIT 1
153
+ `;
154
+ if (expiredRows.length > 0) {
155
+ return {
156
+ ok: false,
157
+ status: 403,
158
+ message: "Your account has expired. Contact an administrator.",
159
+ };
160
+ }
161
+ const allowSelfRegistration = await settings.get<boolean>("user.allow_self_registration");
162
+ if (!allowSelfRegistration || category === "login" || !(await isAccountCategoryAllowed({ provider: "local", profile: "guest" }))) {
163
+ return {
164
+ ok: false,
165
+ status: 401,
166
+ message: "Only existing local accounts can sign in with email. Contact an administrator if you need access.",
167
+ };
168
+ }
169
+ const guest = await providers.local.users.createGuest({ email });
170
+ if (!guest.ok) {
171
+ return { ok: false, status: guest.status, message: guest.error };
172
+ }
173
+ userId = guest.data.id;
174
+ createdGuest = true;
175
+ }
176
+
177
+ const user = await accounts.users.get({ id: userId });
178
+ if (!user) {
179
+ return { ok: false, status: 401, message: "User not found" };
180
+ }
181
+ if (!(await isAccountCategoryAllowed(user)) || (category && accountCategory(user) !== category))
182
+ return { ok: false, status: 403, message: "This account cannot sign in through this category. Contact an administrator." };
183
+
184
+ return { ok: true, userId, user, email, createdGuest };
185
+ };
@@ -0,0 +1,10 @@
1
+ export type AuthNotificationDeliveryResult = {
2
+ id: string;
3
+ status: "queued" | "delivered" | "suppressed" | "error";
4
+ };
5
+
6
+ export type AuthNotificationSender = {
7
+ sendMagicLink: (input: { email: string; token: string; magicLink: string; locale?: string }) => Promise<AuthNotificationDeliveryResult>;
8
+ sendIpaLoginHint: (input: { email: string; loginUrl: string; locale?: string }) => Promise<AuthNotificationDeliveryResult>;
9
+ sendPasswordReset: (input: { email: string; resetLink: string; locale?: string }) => Promise<AuthNotificationDeliveryResult>;
10
+ };
@@ -0,0 +1,268 @@
1
+ import { redis, sql } from "bun";
2
+ import type { User } from "../../contracts/shared";
3
+ import { createAuthPasswordResetUrl } from "../../shared/redirect";
4
+ import { isAccountCategoryAllowed } from "../account-category-policy";
5
+ import { getFreeIpaConfig } from "../freeipa-config";
6
+ import { getServiceIpaSession } from "../ipa/service-account";
7
+ import { logger } from "../logging";
8
+ import { providers } from "../providers";
9
+ import { session } from "../session";
10
+ import * as settings from "../settings";
11
+ import * as ipaFlow from "./ipa";
12
+ import type { AuthNotificationDeliveryResult, AuthNotificationSender } from "./notification-sender";
13
+
14
+ const log = logger("auth:password-reset");
15
+
16
+ const REQUEST_TTL_SECONDS = 900;
17
+ const REQUEST_COOLDOWN_SECONDS = 60;
18
+ const GENERIC_MESSAGE = "If this account can reset a password, a reset link has been sent.";
19
+
20
+ type ResetTarget = {
21
+ userId: string;
22
+ uid: string;
23
+ email: string;
24
+ };
25
+
26
+ type ResetAttemptSuccess = {
27
+ ok: true;
28
+ userId: string;
29
+ user: User;
30
+ };
31
+
32
+ type ResetAttemptFailure =
33
+ | {
34
+ ok: false;
35
+ status: 400;
36
+ reason: "policy_failed";
37
+ message: string;
38
+ }
39
+ | {
40
+ ok: false;
41
+ status: 400 | 401;
42
+ reason: "invalid_or_expired";
43
+ message: string;
44
+ }
45
+ | {
46
+ ok: false;
47
+ status: number;
48
+ reason: "reset_failed" | "login_failed";
49
+ message: string;
50
+ };
51
+
52
+ const normalizeEmail = (email: string): string => email.trim().toLowerCase();
53
+ const cooldownKey = (email: string) => `password-reset-cooldown:${email}`;
54
+
55
+ const isInCooldown = async (email: string): Promise<boolean> => {
56
+ if (await redis.get(cooldownKey(email))) return true;
57
+ await redis.set(cooldownKey(email), "1", "EX", REQUEST_COOLDOWN_SECONDS);
58
+ return false;
59
+ };
60
+
61
+ const buildTarget = (row: { id: string; uid: string; mail: string }): ResetTarget => ({
62
+ userId: row.id,
63
+ uid: row.uid,
64
+ email: row.mail,
65
+ });
66
+
67
+ const resolveResetTarget = async (email: string): Promise<ResetTarget | null> => {
68
+ const rows = await sql<{ id: string; uid: string; mail: string }[]>`
69
+ SELECT id, uid, btrim(mail) AS mail
70
+ FROM auth.users
71
+ WHERE provider = 'ipa'
72
+ AND profile = 'user'
73
+ AND lower(btrim(mail)) = ${email}
74
+ AND (account_expires IS NULL OR account_expires > now())
75
+ `;
76
+
77
+ if (rows.length !== 1) {
78
+ if (rows.length > 1) {
79
+ log.warn("Password reset skipped: ambiguous IPA email", {
80
+ email,
81
+ matches: rows.length,
82
+ });
83
+ }
84
+ return null;
85
+ }
86
+
87
+ return buildTarget(rows[0]!);
88
+ };
89
+
90
+ const resolveResetTargetForToken = async (params: { userId: string; email: string }): Promise<ResetTarget | null> => {
91
+ const rows = await sql<{ id: string; uid: string; mail: string }[]>`
92
+ SELECT id, uid, btrim(mail) AS mail
93
+ FROM auth.users
94
+ WHERE id = ${params.userId}
95
+ AND provider = 'ipa'
96
+ AND profile = 'user'
97
+ AND lower(btrim(mail)) = ${params.email}
98
+ AND (account_expires IS NULL OR account_expires > now())
99
+ LIMIT 1
100
+ `;
101
+
102
+ return rows.length === 1 ? buildTarget(rows[0]!) : null;
103
+ };
104
+
105
+ const sendResetEmail = async (
106
+ params: ResetTarget & { redirectTo?: string; locale?: string },
107
+ notificationSender: AuthNotificationSender,
108
+ ): Promise<AuthNotificationDeliveryResult> => {
109
+ const token = await providers.local.auth.createPasswordResetToken({
110
+ userId: params.userId,
111
+ uid: params.uid,
112
+ email: params.email,
113
+ ttlSeconds: REQUEST_TTL_SECONDS,
114
+ });
115
+ const rawAppUrl = await settings.get<string>("app.url");
116
+ const appUrl = rawAppUrl.startsWith("http") ? rawAppUrl : `https://${rawAppUrl}`;
117
+ const resetLink = createAuthPasswordResetUrl(appUrl, {
118
+ token,
119
+ redirectTo: params.redirectTo,
120
+ });
121
+ return notificationSender.sendPasswordReset({ email: params.email, resetLink, locale: params.locale });
122
+ };
123
+
124
+ const changeTemporaryPassword = async (params: {
125
+ userId: string;
126
+ uid: string;
127
+ email: string;
128
+ temporaryPassword: string;
129
+ newPassword: string;
130
+ }): Promise<ResetAttemptSuccess | ResetAttemptFailure> => {
131
+ const changeResult = await ipaFlow.changeExpiredPassword({
132
+ username: params.uid,
133
+ currentPassword: params.temporaryPassword,
134
+ newPassword: params.newPassword,
135
+ });
136
+
137
+ if (!changeResult.ok) {
138
+ if (changeResult.reason === "change_failed") {
139
+ return {
140
+ ok: false,
141
+ status: 400,
142
+ reason: "policy_failed",
143
+ message: `${changeResult.message} Request a new reset link and choose a stronger password.`,
144
+ };
145
+ }
146
+
147
+ return {
148
+ ok: false,
149
+ status: changeResult.status,
150
+ reason: "login_failed",
151
+ message: changeResult.message,
152
+ };
153
+ }
154
+
155
+ await session.revokeAllForUser(changeResult.userId);
156
+
157
+ return {
158
+ ok: true,
159
+ userId: changeResult.userId,
160
+ user: changeResult.user,
161
+ };
162
+ };
163
+
164
+ export const request = async (
165
+ params: { email: string; redirectTo?: string; locale?: string },
166
+ notificationSender: AuthNotificationSender,
167
+ ): Promise<{ ok: true; message: string }> => {
168
+ if (!(await isAccountCategoryAllowed({ provider: "ipa", profile: "user" }))) return { ok: true, message: GENERIC_MESSAGE };
169
+ const email = normalizeEmail(params.email);
170
+ if (await isInCooldown(email)) {
171
+ log.info("Password reset request ignored during cooldown");
172
+ return { ok: true, message: GENERIC_MESSAGE };
173
+ }
174
+
175
+ const freeIpaConfig = await getFreeIpaConfig();
176
+ if (!freeIpaConfig.enabled || !freeIpaConfig.configured) {
177
+ log.info("Password reset request accepted while FreeIPA is unavailable");
178
+ return { ok: true, message: GENERIC_MESSAGE };
179
+ }
180
+
181
+ const target = await resolveResetTarget(email);
182
+ if (!target) {
183
+ log.info("Password reset request accepted without eligible target");
184
+ return { ok: true, message: GENERIC_MESSAGE };
185
+ }
186
+
187
+ try {
188
+ const result = await sendResetEmail({ ...target, redirectTo: params.redirectTo, locale: params.locale }, notificationSender);
189
+ if (result.status === "error") log.error("Password reset delivery failed", { notificationId: result.id, uid: target.uid });
190
+ else log.info("Password reset notification accepted", { notificationId: result.id, uid: target.uid, status: result.status });
191
+ } catch (error) {
192
+ log.error("Password reset notification could not be accepted", {
193
+ uid: target.uid,
194
+ error: error instanceof Error ? error.message : String(error),
195
+ });
196
+ }
197
+ return { ok: true, message: GENERIC_MESSAGE };
198
+ };
199
+
200
+ export const complete = async (params: { token?: string; newPassword: string }): Promise<ResetAttemptSuccess | ResetAttemptFailure> => {
201
+ if (!(await isAccountCategoryAllowed({ provider: "ipa", profile: "user" })))
202
+ return { ok: false, status: 403, reason: "reset_failed", message: "FreeIPA accounts are disabled. Contact an administrator." };
203
+ if (!params.token) {
204
+ return {
205
+ ok: false,
206
+ status: 400,
207
+ reason: "invalid_or_expired",
208
+ message: "Missing password reset token.",
209
+ };
210
+ }
211
+
212
+ const payload = await providers.local.auth.consumePasswordResetToken(params.token);
213
+ if (!payload) {
214
+ return {
215
+ ok: false,
216
+ status: 401,
217
+ reason: "invalid_or_expired",
218
+ message: "This password reset link has expired. Request a new reset link.",
219
+ };
220
+ }
221
+
222
+ const target = await resolveResetTargetForToken({
223
+ userId: payload.userId,
224
+ email: payload.email,
225
+ });
226
+ if (!target) {
227
+ return {
228
+ ok: false,
229
+ status: 401,
230
+ reason: "invalid_or_expired",
231
+ message: "This password reset link has expired. Request a new reset link.",
232
+ };
233
+ }
234
+
235
+ const serviceSession = await getServiceIpaSession();
236
+ if (!serviceSession.ok) {
237
+ return {
238
+ ok: false,
239
+ status: serviceSession.status,
240
+ reason: "reset_failed",
241
+ message: serviceSession.error,
242
+ };
243
+ }
244
+
245
+ const resetResult = await providers.ipa.users.resetPassword({
246
+ ipaSession: serviceSession.data,
247
+ id: target.userId,
248
+ });
249
+ if (!resetResult.ok) {
250
+ return {
251
+ ok: false,
252
+ status: resetResult.status,
253
+ reason: "reset_failed",
254
+ message: resetResult.error,
255
+ };
256
+ }
257
+
258
+ return changeTemporaryPassword({
259
+ ...target,
260
+ temporaryPassword: resetResult.data.password,
261
+ newPassword: params.newPassword,
262
+ });
263
+ };
264
+
265
+ export const passwordReset = {
266
+ request,
267
+ complete,
268
+ } as const;
@@ -0,0 +1,49 @@
1
+ import { redis } from "bun";
2
+
3
+ const KEY_PREFIX = "auth:proxy-return:";
4
+ const DEFAULT_TTL_SECONDS = 300;
5
+
6
+ type ProxyReturnPayload = {
7
+ clientId: string;
8
+ url: string;
9
+ };
10
+
11
+ const key = (token: string) => `${KEY_PREFIX}${token}`;
12
+
13
+ const normalizeReturnUrl = (value: string): string | null => {
14
+ try {
15
+ const url = new URL(value);
16
+ if (url.protocol !== "https:" && url.protocol !== "http:") return null;
17
+ return url.toString();
18
+ } catch {
19
+ return null;
20
+ }
21
+ };
22
+
23
+ export const create = async (params: { clientId: string; url: string; ttlSeconds?: number }): Promise<string | null> => {
24
+ const url = normalizeReturnUrl(params.url);
25
+ if (!url) return null;
26
+
27
+ const token = crypto.randomUUID();
28
+ const payload: ProxyReturnPayload = {
29
+ clientId: params.clientId,
30
+ url,
31
+ };
32
+ await redis.set(key(token), JSON.stringify(payload), "EX", params.ttlSeconds ?? DEFAULT_TTL_SECONDS);
33
+ return token;
34
+ };
35
+
36
+ export const consume = async (params: { token: string }): Promise<ProxyReturnPayload | null> => {
37
+ const raw = await redis.getdel(key(params.token));
38
+ if (!raw) return null;
39
+
40
+ try {
41
+ const payload = JSON.parse(raw) as Partial<ProxyReturnPayload>;
42
+ if (typeof payload.clientId !== "string" || typeof payload.url !== "string") return null;
43
+ const url = normalizeReturnUrl(payload.url);
44
+ if (!url) return null;
45
+ return { clientId: payload.clientId, url };
46
+ } catch {
47
+ return null;
48
+ }
49
+ };
@@ -0,0 +1,148 @@
1
+ import type { Format as AnyDocFormat } from "@firecrawl/anydoc";
2
+ import {
3
+ formatFromBytes as anydocFormatFromBytes,
4
+ formatFromExtension as anydocFormatFromExtension,
5
+ toMarkdownBytes,
6
+ } from "@firecrawl/anydoc";
7
+
8
+ export const DOCUMENT_EXTRACTION_MAX_INPUT_BYTES = 20 * 1024 * 1024;
9
+ export const DOCUMENT_EXTRACTION_MAX_OUTPUT_BYTES = 1024 * 1024;
10
+
11
+ export type DocumentFormat = "doc" | "docx" | "odt" | "pdf" | "ppt" | "pptx" | "rtf" | "epub" | "xlsx" | "ods" | "odp" | "csv";
12
+
13
+ export type DocumentExtractionErrorCode =
14
+ | "cancelled"
15
+ | "encrypted"
16
+ | "internal"
17
+ | "input_too_large"
18
+ | "malformed"
19
+ | "ocr_required"
20
+ | "resource_limit"
21
+ | "unsupported";
22
+
23
+ export class DocumentExtractionError extends Error {
24
+ constructor(
25
+ readonly code: DocumentExtractionErrorCode,
26
+ message: string,
27
+ ) {
28
+ super(message);
29
+ this.name = "DocumentExtractionError";
30
+ }
31
+ }
32
+
33
+ export type ExtractDocumentMarkdownInput = {
34
+ bytes: Uint8Array;
35
+ filename?: string | null;
36
+ signal?: AbortSignal;
37
+ };
38
+
39
+ export type ExtractDocumentMarkdownResult = {
40
+ format: DocumentFormat;
41
+ markdown: string;
42
+ inputBytes: number;
43
+ outputBytes: number;
44
+ truncated: boolean;
45
+ };
46
+
47
+ const anydocErrorCode = (error: unknown): string | null => {
48
+ if (!error || typeof error !== "object" || !("code" in error)) return null;
49
+ return typeof error.code === "string" ? error.code : null;
50
+ };
51
+
52
+ const cancelled = (): DocumentExtractionError => new DocumentExtractionError("cancelled", "Document extraction was cancelled.");
53
+
54
+ const assertNotCancelled = (signal: AbortSignal | undefined): void => {
55
+ if (signal?.aborted) throw cancelled();
56
+ };
57
+
58
+ const filenameExtension = (filename: string | null | undefined): string | null => {
59
+ const name = filename?.trim();
60
+ if (!name) return null;
61
+ const basename = name.split(/[\\/]/u).at(-1) ?? "";
62
+ const dot = basename.lastIndexOf(".");
63
+ return dot > 0 && dot < basename.length - 1 ? basename.slice(dot + 1) : null;
64
+ };
65
+
66
+ export const documentFormatFromFilename = (filename: string | null | undefined): DocumentFormat | null => {
67
+ const extension = filenameExtension(filename);
68
+ return extension ? (anydocFormatFromExtension(extension) as DocumentFormat | null) : null;
69
+ };
70
+
71
+ export const documentFormatFromBytes = (bytes: Uint8Array): DocumentFormat | null => anydocFormatFromBytes(bytes) as DocumentFormat | null;
72
+
73
+ const resolveFormat = (bytes: Uint8Array, filename: string | null | undefined): AnyDocFormat | null => {
74
+ const detected = anydocFormatFromBytes(bytes);
75
+ if (detected) return detected;
76
+ const extension = filenameExtension(filename);
77
+ const fromExtension = extension ? anydocFormatFromExtension(extension) : null;
78
+ return fromExtension === "csv" ? fromExtension : null;
79
+ };
80
+
81
+ const truncateUtf8 = (value: string, limit: number): { value: string; bytes: number; truncated: boolean } => {
82
+ const encoded = new TextEncoder().encode(value);
83
+ if (encoded.byteLength <= limit) return { value, bytes: encoded.byteLength, truncated: false };
84
+
85
+ for (let trim = 0; trim <= 3; trim += 1) {
86
+ try {
87
+ const slice = encoded.slice(0, limit - trim);
88
+ return {
89
+ value: new TextDecoder("utf-8", { fatal: true }).decode(slice),
90
+ bytes: slice.byteLength,
91
+ truncated: true,
92
+ };
93
+ } catch {
94
+ // A byte limit can split one UTF-8 code point by at most three bytes.
95
+ }
96
+ }
97
+ throw new DocumentExtractionError("malformed", "Extracted document text is not valid UTF-8.");
98
+ };
99
+
100
+ const mapConversionError = (error: unknown, format: DocumentFormat): DocumentExtractionError => {
101
+ switch (anydocErrorCode(error)) {
102
+ case "encrypted":
103
+ return new DocumentExtractionError("encrypted", "The document is encrypted or password-protected.");
104
+ case "resourceLimit":
105
+ return new DocumentExtractionError("resource_limit", "The document exceeds safe extraction limits.");
106
+ case "malformed":
107
+ case "missingPart":
108
+ return new DocumentExtractionError("malformed", "The document is malformed or incomplete.");
109
+ case "unsupported":
110
+ return format === "pdf"
111
+ ? new DocumentExtractionError("ocr_required", "The PDF has no readable text and requires OCR.")
112
+ : new DocumentExtractionError("unsupported", "The document format is not supported.");
113
+ default:
114
+ return new DocumentExtractionError("internal", "The document could not be extracted.");
115
+ }
116
+ };
117
+
118
+ export const extractDocumentMarkdown = async (input: ExtractDocumentMarkdownInput): Promise<ExtractDocumentMarkdownResult> => {
119
+ assertNotCancelled(input.signal);
120
+ if (input.bytes.byteLength === 0) throw new DocumentExtractionError("malformed", "The document is empty.");
121
+ if (input.bytes.byteLength > DOCUMENT_EXTRACTION_MAX_INPUT_BYTES) {
122
+ throw new DocumentExtractionError(
123
+ "input_too_large",
124
+ `The document exceeds the ${DOCUMENT_EXTRACTION_MAX_INPUT_BYTES}-byte extraction limit.`,
125
+ );
126
+ }
127
+
128
+ const format = resolveFormat(input.bytes, input.filename);
129
+ if (!format) throw new DocumentExtractionError("unsupported", "The document format is not supported.");
130
+ const publicFormat = format as DocumentFormat;
131
+
132
+ let markdown: string;
133
+ try {
134
+ markdown = await toMarkdownBytes(input.bytes, format);
135
+ } catch (error) {
136
+ throw mapConversionError(error, publicFormat);
137
+ }
138
+ assertNotCancelled(input.signal);
139
+
140
+ const bounded = truncateUtf8(markdown, DOCUMENT_EXTRACTION_MAX_OUTPUT_BYTES);
141
+ return {
142
+ format: publicFormat,
143
+ markdown: bounded.value,
144
+ inputBytes: input.bytes.byteLength,
145
+ outputBytes: bounded.bytes,
146
+ truncated: bounded.truncated,
147
+ };
148
+ };