@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.
- package/README.md +98 -0
- package/package.json +128 -0
- package/public/logo.svg +1 -0
- package/scripts/app-favicon.test.ts +49 -0
- package/scripts/app-favicon.ts +51 -0
- package/scripts/build.ts +205 -0
- package/scripts/preload.ts +164 -0
- package/scripts/sync-recovery-smoke.ts +200 -0
- package/src/_internal/app-favicon.ts +2 -0
- package/src/_internal/app-presentation.ts +61 -0
- package/src/_internal/app-workspace-state.ts +24 -0
- package/src/_internal/bounded-json.ts +47 -0
- package/src/_internal/build-metadata.ts +9 -0
- package/src/_internal/capabilities.ts +1118 -0
- package/src/_internal/define-app.ts +801 -0
- package/src/_internal/heartbeat.ts +167 -0
- package/src/_internal/help-catalog.ts +241 -0
- package/src/_internal/help.ts +95 -0
- package/src/_internal/nats-connection.ts +34 -0
- package/src/_internal/page-responses.ts +40 -0
- package/src/_internal/process-sync.ts +117 -0
- package/src/_internal/readiness.ts +3 -0
- package/src/_internal/registry-validation.ts +136 -0
- package/src/_internal/registry-watch.ts +52 -0
- package/src/_internal/registry.ts +338 -0
- package/src/_internal/runtime-compatibility.ts +64 -0
- package/src/_internal/runtime-context.ts +84 -0
- package/src/_internal/runtime-watcher.ts +78 -0
- package/src/_internal/static-assets.ts +86 -0
- package/src/_internal/status-preserving-ssr.ts +49 -0
- package/src/access/PermissionEditor.tsx +486 -0
- package/src/access/ResourceApiKeys.tsx +271 -0
- package/src/access/messages.ts +121 -0
- package/src/access/ui.ts +16 -0
- package/src/account/Avatar.tsx +28 -0
- package/src/account/EntitySearch.tsx +363 -0
- package/src/account/avatar-upload-dialog.tsx +197 -0
- package/src/account/avatar-upload.ts +149 -0
- package/src/account/ui.ts +3 -0
- package/src/ai/admin.ts +47 -0
- package/src/ai/approval-routes.ts +130 -0
- package/src/ai/approvals.ts +139 -0
- package/src/ai/assistant-models.ts +61 -0
- package/src/ai/attachments.ts +40 -0
- package/src/ai/browser.ts +92 -0
- package/src/ai/capabilities.ts +714 -0
- package/src/ai/capability-execution.ts +151 -0
- package/src/ai/chat/blocks.render.test.tsx +1108 -0
- package/src/ai/chat/blocks.tsx +730 -0
- package/src/ai/chat/builtin-tools.tsx +428 -0
- package/src/ai/chat/capability-table.tsx +87 -0
- package/src/ai/chat/composer-adapter.ts +169 -0
- package/src/ai/chat/file-tools.tsx +81 -0
- package/src/ai/chat/message-actions.tsx +370 -0
- package/src/ai/chat/message-utils.ts +479 -0
- package/src/ai/chat/messages.ts +43 -0
- package/src/ai/chat/presentation.tsx +395 -0
- package/src/ai/chat/primitives.tsx +3 -0
- package/src/ai/chat/tool-disclosure.tsx +41 -0
- package/src/ai/chat/turn-navigator-utils.ts +28 -0
- package/src/ai/chat/turn-navigator.tsx +164 -0
- package/src/ai/chat/user-message.tsx +149 -0
- package/src/ai/chat/visual-tools.tsx +435 -0
- package/src/ai/chat/web-tools.tsx +202 -0
- package/src/ai/chat-task-contracts.ts +113 -0
- package/src/ai/chat-tasks.ts +952 -0
- package/src/ai/client/controller.ts +1211 -0
- package/src/ai/client/file-source.ts +95 -0
- package/src/ai/client/live-connection.ts +140 -0
- package/src/ai/client/projection.ts +178 -0
- package/src/ai/client/transport.ts +111 -0
- package/src/ai/compaction.ts +182 -0
- package/src/ai/credentials.ts +124 -0
- package/src/ai/default-tool-contracts.ts +108 -0
- package/src/ai/default-tools.ts +127 -0
- package/src/ai/enrich.ts +280 -0
- package/src/ai/executor.ts +1624 -0
- package/src/ai/fetch-file-tool.ts +313 -0
- package/src/ai/file-context.ts +92 -0
- package/src/ai/file-media-type.ts +28 -0
- package/src/ai/file-mount.ts +16 -0
- package/src/ai/file-tools.ts +566 -0
- package/src/ai/files-store.ts +390 -0
- package/src/ai/firecrawl-tools.ts +305 -0
- package/src/ai/grids-skill.ts +126 -0
- package/src/ai/http.ts +265 -0
- package/src/ai/index.ts +337 -0
- package/src/ai/kit-skill.ts +22 -0
- package/src/ai/limits.ts +5 -0
- package/src/ai/live-events.ts +289 -0
- package/src/ai/live-messages.ts +45 -0
- package/src/ai/live-outbox.ts +102 -0
- package/src/ai/live-routes.ts +412 -0
- package/src/ai/live.ts +2 -0
- package/src/ai/maintenance.ts +302 -0
- package/src/ai/markdown-pdf-tool.ts +95 -0
- package/src/ai/memories.ts +605 -0
- package/src/ai/memory-learning-runs.ts +222 -0
- package/src/ai/memory-learning.ts +619 -0
- package/src/ai/memory-tool.ts +120 -0
- package/src/ai/memory-workflow-evidence.ts +158 -0
- package/src/ai/migrate.ts +2082 -0
- package/src/ai/model-access-migrate.ts +30 -0
- package/src/ai/model-access.ts +191 -0
- package/src/ai/personal-agent.ts +6 -0
- package/src/ai/prefs.ts +72 -0
- package/src/ai/project-tool.ts +94 -0
- package/src/ai/projects-routes.ts +277 -0
- package/src/ai/projects.ts +902 -0
- package/src/ai/protocol.ts +279 -0
- package/src/ai/provider.ts +73 -0
- package/src/ai/public-projection.ts +25 -0
- package/src/ai/resource-markers.ts +31 -0
- package/src/ai/resource-refs.ts +70 -0
- package/src/ai/routes.ts +1131 -0
- package/src/ai/runtime-tools.ts +39 -0
- package/src/ai/runtime.ts +544 -0
- package/src/ai/settings.ts +529 -0
- package/src/ai/short-id.ts +45 -0
- package/src/ai/skill-catalog.ts +73 -0
- package/src/ai/skill-format.ts +145 -0
- package/src/ai/skill-seeds.ts +430 -0
- package/src/ai/skill-tool.ts +81 -0
- package/src/ai/skills-routes.ts +143 -0
- package/src/ai/skills.ts +934 -0
- package/src/ai/solid.ts +27 -0
- package/src/ai/store.ts +3113 -0
- package/src/ai/stream.ts +308 -0
- package/src/ai/structured-runs.ts +66 -0
- package/src/ai/structured.ts +186 -0
- package/src/ai/system-prompt.ts +170 -0
- package/src/ai/task-prompt.ts +22 -0
- package/src/ai/timeline.ts +148 -0
- package/src/ai/tool-audit.ts +177 -0
- package/src/ai/tool-result-budget.ts +16 -0
- package/src/ai/tools.ts +155 -0
- package/src/ai/types.ts +967 -0
- package/src/ai/ui.tsx +39 -0
- package/src/ai/usage-migrate.ts +81 -0
- package/src/ai/usage.ts +315 -0
- package/src/ai/validate.ts +54 -0
- package/src/ai/vision-tool.ts +79 -0
- package/src/api/accounts-entities.ts +174 -0
- package/src/api/admin-ai-projects.ts +74 -0
- package/src/api/admin-ai-skills.ts +76 -0
- package/src/api/admin-ai-usage.ts +42 -0
- package/src/api/admin-core-settings.ts +508 -0
- package/src/api/admin-identity.ts +148 -0
- package/src/api/admin-lifecycle.ts +206 -0
- package/src/api/admin-linux-identities.ts +66 -0
- package/src/api/announcements.ts +131 -0
- package/src/api/app-approval.ts +129 -0
- package/src/api/apps.ts +51 -0
- package/src/api/auth/schemas.ts +54 -0
- package/src/api/auth.ts +370 -0
- package/src/api/capabilities.ts +658 -0
- package/src/api/help-messages.ts +19 -0
- package/src/api/help.ts +78 -0
- package/src/api/index.ts +108 -0
- package/src/api/mcp.ts +744 -0
- package/src/api/me-rail.ts +45 -0
- package/src/api/me.ts +753 -0
- package/src/api/search/schemas.ts +79 -0
- package/src/api/search.bench.ts +185 -0
- package/src/api/search.ts +407 -0
- package/src/api/sync-ops.ts +79 -0
- package/src/api/widgets.ts +143 -0
- package/src/browser/CloudResourceSearch.behavior.test.tsx +74 -0
- package/src/browser/CloudResourceSearch.tsx +791 -0
- package/src/browser/app-approval-vault.ts +305 -0
- package/src/browser/app-approval.ts +234 -0
- package/src/browser/live-websocket.ts +268 -0
- package/src/browser/locale-preference.ts +25 -0
- package/src/browser/notification-target.ts +27 -0
- package/src/browser/notification-timeout.ts +14 -0
- package/src/browser/notifications.ts +181 -0
- package/src/browser/resource-clipboard.ts +70 -0
- package/src/browser/resource-picker.tsx +39 -0
- package/src/browser/resource-search-messages.ts +55 -0
- package/src/browser/resource-search.ts +27 -0
- package/src/capabilities/client.ts +149 -0
- package/src/capabilities/response.ts +39 -0
- package/src/capabilities/server.ts +215 -0
- package/src/capabilities/signals.ts +4 -0
- package/src/capabilities/testing.ts +12 -0
- package/src/capabilities/types.ts +32 -0
- package/src/cli/access.ts +499 -0
- package/src/cli/account.ts +382 -0
- package/src/cli/admin/account-administration.ts +45 -0
- package/src/cli/admin/account-categories.ts +49 -0
- package/src/cli/admin/ai-usage.ts +118 -0
- package/src/cli/admin/app-sign-in.ts +49 -0
- package/src/cli/admin/data.ts +343 -0
- package/src/cli/admin/documentation.ts +34 -0
- package/src/cli/admin/gateway.ts +155 -0
- package/src/cli/admin/index.ts +61 -0
- package/src/cli/admin/instance.ts +250 -0
- package/src/cli/admin/jobs.ts +223 -0
- package/src/cli/admin/legal.ts +122 -0
- package/src/cli/admin/linux.ts +53 -0
- package/src/cli/admin/logs.ts +324 -0
- package/src/cli/admin/metrics.ts +142 -0
- package/src/cli/admin/nats.ts +167 -0
- package/src/cli/admin/notifications.ts +516 -0
- package/src/cli/admin/shared.ts +113 -0
- package/src/cli/admin/sync.ts +188 -0
- package/src/cli/admin/telemetry.ts +314 -0
- package/src/cli/admin/webhooks.ts +222 -0
- package/src/cli/admin/workflows.ts +367 -0
- package/src/cli/apps.ts +40 -0
- package/src/cli/capabilities.ts +239 -0
- package/src/cli/commands.ts +604 -0
- package/src/cli/index.ts +61 -0
- package/src/cli/locale.ts +31 -0
- package/src/cli/output.ts +58 -0
- package/src/clients/core.ts +20 -0
- package/src/config/env.ts +54 -0
- package/src/config/index.ts +6 -0
- package/src/contracts/account-categories.ts +62 -0
- package/src/contracts/announcements.ts +121 -0
- package/src/contracts/app-approval.ts +197 -0
- package/src/contracts/app.ts +162 -0
- package/src/contracts/capabilities.ts +484 -0
- package/src/contracts/index.ts +14 -0
- package/src/contracts/notification-live.ts +66 -0
- package/src/contracts/notification-types.ts +235 -0
- package/src/contracts/posix.ts +69 -0
- package/src/contracts/profile.ts +85 -0
- package/src/contracts/rail-preferences.ts +53 -0
- package/src/contracts/registry.ts +110 -0
- package/src/contracts/resource-clipboard.ts +33 -0
- package/src/contracts/settings-types.ts +98 -0
- package/src/contracts/shared.ts +408 -0
- package/src/contracts/user-notifications.ts +101 -0
- package/src/contracts/widgets.ts +183 -0
- package/src/desktop/index.ts +704 -0
- package/src/desktop/solid.tsx +938 -0
- package/src/index.ts +58 -0
- package/src/server/actor.ts +41 -0
- package/src/server/api/index.ts +2 -0
- package/src/server/api/respond.ts +96 -0
- package/src/server/api-client.ts +54 -0
- package/src/server/app-context.ts +39 -0
- package/src/server/help.ts +187 -0
- package/src/server/index.ts +89 -0
- package/src/server/locale.ts +55 -0
- package/src/server/middleware/auth.ts +386 -0
- package/src/server/middleware/index.ts +28 -0
- package/src/server/middleware/invocation.ts +55 -0
- package/src/server/middleware/middleware.ts +65 -0
- package/src/server/middleware/openapi.ts +126 -0
- package/src/server/middleware/rate-limit.ts +125 -0
- package/src/server/middleware/request-logger.ts +41 -0
- package/src/server/middleware/route-template.ts +46 -0
- package/src/server/middleware/runtime.ts +22 -0
- package/src/server/middleware/settings.ts +55 -0
- package/src/server/middleware/validator.ts +49 -0
- package/src/server/middleware/workload.ts +22 -0
- package/src/server/ratelimit.ts +151 -0
- package/src/server/request-cookies.ts +18 -0
- package/src/server/services/access.ts +644 -0
- package/src/server/services/freeipa/client.ts +137 -0
- package/src/server/services/freeipa/index.ts +9 -0
- package/src/server/services/freeipa/session.ts +123 -0
- package/src/server/services/freeipa/test-certificates.ts +44 -0
- package/src/server/services/freeipa/tls.ts +44 -0
- package/src/server/services/freeipa/transport.ts +116 -0
- package/src/server/services/freeipa/util.ts +59 -0
- package/src/server/services/geo.ts +198 -0
- package/src/server/services/index.ts +48 -0
- package/src/server/services/pagination.ts +22 -0
- package/src/server/services/services.ts +13 -0
- package/src/server/time.ts +33 -0
- package/src/services/account-category-policy.ts +51 -0
- package/src/services/account-lifecycle/audit.ts +41 -0
- package/src/services/account-lifecycle/index.ts +940 -0
- package/src/services/account-lifecycle/ipa-backfill.ts +152 -0
- package/src/services/account-lifecycle/notification-sender.ts +13 -0
- package/src/services/account-lifecycle/scheduler.ts +506 -0
- package/src/services/account-model.ts +22 -0
- package/src/services/accounts/app.ts +1671 -0
- package/src/services/accounts/authz.ts +44 -0
- package/src/services/accounts/avatar.ts +120 -0
- package/src/services/accounts/base-group.ts +11 -0
- package/src/services/accounts/base-user.ts +46 -0
- package/src/services/accounts/duplicate-emails.ts +82 -0
- package/src/services/accounts/entities.ts +622 -0
- package/src/services/accounts/group-sql.ts +106 -0
- package/src/services/accounts/groups.ts +272 -0
- package/src/services/accounts/index.ts +14 -0
- package/src/services/accounts/ipa-data.ts +66 -0
- package/src/services/accounts/lifecycle.ts +2 -0
- package/src/services/accounts/local-groups.ts +499 -0
- package/src/services/accounts/model.ts +129 -0
- package/src/services/accounts/notification-sender.ts +31 -0
- package/src/services/accounts/posix.ts +386 -0
- package/src/services/accounts/request-policy.ts +11 -0
- package/src/services/accounts/switching.ts +126 -0
- package/src/services/accounts/users.ts +745 -0
- package/src/services/announcements/index.ts +224 -0
- package/src/services/app-approval.ts +498 -0
- package/src/services/audit/index.ts +438 -0
- package/src/services/auth-flows/index.ts +15 -0
- package/src/services/auth-flows/ipa.ts +197 -0
- package/src/services/auth-flows/magic-link.ts +185 -0
- package/src/services/auth-flows/notification-sender.ts +10 -0
- package/src/services/auth-flows/password-reset.ts +268 -0
- package/src/services/auth-flows/proxy-return.ts +49 -0
- package/src/services/document-extraction.ts +148 -0
- package/src/services/freeipa-config.ts +179 -0
- package/src/services/gateway.ts +186 -0
- package/src/services/identity/constants.ts +18 -0
- package/src/services/identity/index.ts +52 -0
- package/src/services/identity/invocation-actor.ts +120 -0
- package/src/services/identity/invocation-authority.ts +53 -0
- package/src/services/identity/invocation-operations.ts +15 -0
- package/src/services/identity/invocation-token.ts +276 -0
- package/src/services/identity/key-config.ts +41 -0
- package/src/services/identity/key-ring.ts +530 -0
- package/src/services/identity/metrics.ts +29 -0
- package/src/services/identity/routes.ts +18 -0
- package/src/services/identity/runtime-config.ts +88 -0
- package/src/services/identity/session-token.ts +152 -0
- package/src/services/identity/signer-refresh.ts +44 -0
- package/src/services/identity/workload-auth.ts +64 -0
- package/src/services/index.ts +187 -0
- package/src/services/ipa/auth.ts +216 -0
- package/src/services/ipa/connection.ts +57 -0
- package/src/services/ipa/effective-groups.ts +70 -0
- package/src/services/ipa/groups.ts +722 -0
- package/src/services/ipa/guard.ts +17 -0
- package/src/services/ipa/index.ts +17 -0
- package/src/services/ipa/posix.ts +19 -0
- package/src/services/ipa/profile.ts +132 -0
- package/src/services/ipa/runtime.ts +28 -0
- package/src/services/ipa/search.ts +154 -0
- package/src/services/ipa/service-account.ts +15 -0
- package/src/services/ipa/sync-planning.ts +100 -0
- package/src/services/ipa/sync.ts +862 -0
- package/src/services/ipa/users.ts +806 -0
- package/src/services/legal-consent.ts +76 -0
- package/src/services/logging/index.ts +477 -0
- package/src/services/logging/redaction.ts +20 -0
- package/src/services/logging/trace.ts +1231 -0
- package/src/services/mandates/index.ts +855 -0
- package/src/services/mandates/policy.ts +85 -0
- package/src/services/network-security.ts +31 -0
- package/src/services/notifications/batches.ts +722 -0
- package/src/services/notifications/browser.ts +252 -0
- package/src/services/notifications/catalog.ts +113 -0
- package/src/services/notifications/channels.ts +104 -0
- package/src/services/notifications/dispatcher.ts +139 -0
- package/src/services/notifications/email.ts +118 -0
- package/src/services/notifications/index.ts +633 -0
- package/src/services/notifications/live.ts +50 -0
- package/src/services/notifications/observability.ts +407 -0
- package/src/services/notifications/platform.ts +421 -0
- package/src/services/notifications/runtime.ts +120 -0
- package/src/services/notifications/user.ts +248 -0
- package/src/services/notifications/web-push-transport.ts +135 -0
- package/src/services/oauth-tokens.ts +209 -0
- package/src/services/outbox.ts +187 -0
- package/src/services/pdf/gotenberg.ts +290 -0
- package/src/services/pdf/index.ts +43 -0
- package/src/services/pdf/markdown.ts +209 -0
- package/src/services/pdf/template-preview.ts +122 -0
- package/src/services/postgres.ts +76 -0
- package/src/services/providers/index.ts +27 -0
- package/src/services/providers/local/auth.ts +51 -0
- package/src/services/providers/local/index.ts +4 -0
- package/src/services/providers/local/users.ts +263 -0
- package/src/services/rail-preferences.ts +27 -0
- package/src/services/runtime-lifecycle.ts +191 -0
- package/src/services/secrets.ts +10 -0
- package/src/services/service-account-credentials.ts +891 -0
- package/src/services/service-accounts.ts +184 -0
- package/src/services/session/index.ts +236 -0
- package/src/services/session/test-fixture.ts +18 -0
- package/src/services/session/user.ts +144 -0
- package/src/services/settings/api.ts +61 -0
- package/src/services/settings/app.ts +86 -0
- package/src/services/settings/core-settings.ts +796 -0
- package/src/services/settings/crypto.ts +69 -0
- package/src/services/settings/defaults.ts +374 -0
- package/src/services/settings/index.ts +215 -0
- package/src/services/settings/namespace.ts +9 -0
- package/src/services/settings/snapshot.ts +49 -0
- package/src/services/settings/store.ts +241 -0
- package/src/services/settings/templates.ts +4 -0
- package/src/services/sync-ops.ts +403 -0
- package/src/services/topic-cursor.ts +22 -0
- package/src/services/weather/forecast.ts +323 -0
- package/src/services/weather/geo.ts +119 -0
- package/src/services/weather/index.ts +67 -0
- package/src/services/weather/location.ts +24 -0
- package/src/services/weather/locations.ts +133 -0
- package/src/services/weather/migrate.ts +45 -0
- package/src/services/weather/settings.ts +66 -0
- package/src/services/weather/short-id.ts +57 -0
- package/src/services/weather/types.ts +61 -0
- package/src/services/weather/ui.ts +50 -0
- package/src/services/webauthn.ts +385 -0
- package/src/shared/account-action-notice.ts +70 -0
- package/src/shared/account-display.ts +15 -0
- package/src/shared/account-session.ts +15 -0
- package/src/shared/ai-platform-prompt.ts +134 -0
- package/src/shared/ai-usage.ts +49 -0
- package/src/shared/app-presentation.ts +48 -0
- package/src/shared/app-url.ts +12 -0
- package/src/shared/branding.ts +14 -0
- package/src/shared/capability-messages.ts +70 -0
- package/src/shared/email-html.ts +54 -0
- package/src/shared/format.ts +91 -0
- package/src/shared/help.ts +68 -0
- package/src/shared/icons.ts +400 -0
- package/src/shared/index.ts +54 -0
- package/src/shared/locale.ts +48 -0
- package/src/shared/login-method.ts +44 -0
- package/src/shared/markdown/client.ts +130 -0
- package/src/shared/markdown/extensions/code.ts +105 -0
- package/src/shared/markdown/extensions/guided-help.ts +100 -0
- package/src/shared/markdown/extensions/images.ts +79 -0
- package/src/shared/markdown/extensions/info-blocks.ts +97 -0
- package/src/shared/markdown/extensions/katex.ts +120 -0
- package/src/shared/markdown/extensions/links.ts +43 -0
- package/src/shared/markdown/extensions/mark.ts +48 -0
- package/src/shared/markdown/extensions/sub-sup.ts +60 -0
- package/src/shared/markdown/extensions/tables.ts +109 -0
- package/src/shared/markdown/extensions/task-list.ts +53 -0
- package/src/shared/markdown/formula.ts +1187 -0
- package/src/shared/markdown/index.ts +193 -0
- package/src/shared/markdown/shared.ts +36 -0
- package/src/shared/mock-cover.ts +130 -0
- package/src/shared/network-address.ts +106 -0
- package/src/shared/redirect.ts +56 -0
- package/src/shared/template-rendering.ts +140 -0
- package/src/shared/theme.ts +68 -0
- package/src/shared/time.ts +13 -0
- package/src/ssr/AdminLayout.tsx +53 -0
- package/src/ssr/AdminSidebar.tsx +50 -0
- package/src/ssr/AppLaunchpad.island.tsx +236 -0
- package/src/ssr/AppWorkspaceController.island.tsx +21 -0
- package/src/ssr/BrowserNotifications.island.tsx +135 -0
- package/src/ssr/GlobalAnnouncements.island.tsx +142 -0
- package/src/ssr/GlobalSearchDialog.behavior.test.tsx +96 -0
- package/src/ssr/GlobalSearchDialog.tsx +35 -0
- package/src/ssr/GlobalSearchHelpDialog.tsx +104 -0
- package/src/ssr/GlobalSearchTrigger.island.tsx +55 -0
- package/src/ssr/HotkeysHelpRail.island.tsx +55 -0
- package/src/ssr/Layout.render.test.tsx +148 -0
- package/src/ssr/Layout.tsx +292 -0
- package/src/ssr/LayoutBreadcrumbs.island.tsx +44 -0
- package/src/ssr/LayoutFooter.tsx +22 -0
- package/src/ssr/LayoutHeader.tsx +81 -0
- package/src/ssr/LayoutHelp.tsx +869 -0
- package/src/ssr/LayoutPreferences.island.tsx +36 -0
- package/src/ssr/LayoutRail.tsx +58 -0
- package/src/ssr/MinimalLayout.tsx +46 -0
- package/src/ssr/PageError.tsx +29 -0
- package/src/ssr/ProfilePreferences.island.tsx +92 -0
- package/src/ssr/RailApps.island.tsx +61 -0
- package/src/ssr/RailEditor.tsx +293 -0
- package/src/ssr/RegisteredHelpDocuments.island.tsx +3 -0
- package/src/ssr/TimezoneCookie.island.tsx +23 -0
- package/src/ssr/admin-active-link.ts +47 -0
- package/src/ssr/admin-navigation.ts +78 -0
- package/src/ssr/app-appearance.ts +23 -0
- package/src/ssr/app-navigation.ts +41 -0
- package/src/ssr/help-messages.ts +104 -0
- package/src/ssr/index.ts +14 -0
- package/src/ssr/islands/SearchBar.island.tsx +80 -0
- package/src/ssr/islands/index.ts +16 -0
- package/src/ssr/layout-context.ts +17 -0
- package/src/ssr/layout-help-markdown.ts +9 -0
- package/src/ssr/layout-help-navigation.ts +24 -0
- package/src/ssr/layout-help-url.ts +5 -0
- package/src/ssr/layout-runtime.ts +22 -0
- package/src/ssr/page-error-messages.ts +25 -0
- package/src/ssr/platform-messages.ts +135 -0
- package/src/ssr/preference-controller.ts +44 -0
- package/src/ssr/profile-preferences-messages.ts +29 -0
- package/src/ssr/rail-context.ts +21 -0
- package/src/ssr/rail-messages.ts +66 -0
- package/src/ssr/rail-navigation.ts +60 -0
- package/src/ssr/runtime.ts +31 -0
- package/src/ssr/url-filter.ts +127 -0
- package/src/styles/base-popover.css +36 -0
- package/src/styles/effects.css +851 -0
- package/src/styles/global.css +268 -0
- package/src/styles/input.css +58 -0
- package/src/styles/tokens.css +230 -0
- package/src/styles/utilities-buttons.css +405 -0
- package/src/styles/utilities-data.css +33 -0
- package/src/styles/utilities-feedback.css +232 -0
- package/src/styles/utilities-layout.css +603 -0
- package/src/styles/utilities-markdown-editor.css +525 -0
- package/src/styles/utilities-markdown-table.css +234 -0
- package/src/styles/utilities-navigation.css +1054 -0
- package/src/types/ambient.d.ts +24 -0
- package/src/workflows/ai/index.ts +17 -0
- package/src/workflows/ai/runtime.ts +286 -0
- package/src/workflows/ai/store.ts +314 -0
- package/src/workflows/ai/types.ts +111 -0
- package/src/workflows/ai-actions.ts +269 -0
- package/src/workflows/authoring.ts +88 -0
- package/src/workflows/builtins.ts +193 -0
- package/src/workflows/contracts.ts +280 -0
- package/src/workflows/definition.ts +334 -0
- package/src/workflows/editor.ts +113 -0
- package/src/workflows/index.ts +71 -0
- package/src/workflows/language/binder.ts +45 -0
- package/src/workflows/language/canonical.ts +29 -0
- package/src/workflows/language/compiler.ts +396 -0
- package/src/workflows/language/expressions.ts +34 -0
- package/src/workflows/language/index.ts +20 -0
- package/src/workflows/language/references.ts +55 -0
- package/src/workflows/language/schema.ts +134 -0
- package/src/workflows/language/strict-yaml.ts +131 -0
- package/src/workflows/manifest.ts +24 -0
- package/src/workflows/module.ts +12 -0
- package/src/workflows/runtime/coordinator.ts +115 -0
- package/src/workflows/runtime/executor.ts +1003 -0
- package/src/workflows/runtime/index.ts +60 -0
- package/src/workflows/runtime/instant.ts +8 -0
- package/src/workflows/runtime/ports.ts +213 -0
- package/src/workflows/runtime/schedule.ts +203 -0
- package/src/workflows/runtime/trigger.ts +37 -0
- package/src/workflows/runtime/wake.ts +13 -0
- package/src/workflows/store/actions.ts +438 -0
- package/src/workflows/store/budget.ts +145 -0
- package/src/workflows/store/definitions.ts +345 -0
- package/src/workflows/store/events.ts +383 -0
- package/src/workflows/store/index.ts +107 -0
- package/src/workflows/store/observability.ts +594 -0
- package/src/workflows/store/runs.ts +975 -0
- package/src/workflows/store/transaction.ts +23 -0
- package/src/workflows/store/worker.ts +439 -0
- package/src/workflows/testing/conformance.ts +72 -0
- package/src/workflows/testing/index.ts +11 -0
- package/src/workflows/testing/process-fixtures.ts +275 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { err, fail, ok, type Result } from "@k2b/stdlib";
|
|
2
|
+
import { sql } from "bun";
|
|
3
|
+
import { isUniqueViolation } from "./postgres";
|
|
4
|
+
|
|
5
|
+
export type ServiceAccountKind = "user_delegated" | "resource_bound";
|
|
6
|
+
export type ServiceAccountStatus = "active" | "disabled";
|
|
7
|
+
|
|
8
|
+
export type ServiceAccount = {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
kind: ServiceAccountKind;
|
|
12
|
+
status: ServiceAccountStatus;
|
|
13
|
+
delegatedUserId: string | null;
|
|
14
|
+
appId: string | null;
|
|
15
|
+
resourceType: string | null;
|
|
16
|
+
resourceId: string | null;
|
|
17
|
+
createdBy: string | null;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type DbServiceAccount = {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
kind: ServiceAccountKind;
|
|
25
|
+
status: ServiceAccountStatus;
|
|
26
|
+
delegated_user_id: string | null;
|
|
27
|
+
app_id: string | null;
|
|
28
|
+
resource_type: string | null;
|
|
29
|
+
resource_id: string | null;
|
|
30
|
+
created_by: string | null;
|
|
31
|
+
created_at: Date;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const mapServiceAccount = (row: DbServiceAccount): ServiceAccount => ({
|
|
35
|
+
id: row.id,
|
|
36
|
+
name: row.name,
|
|
37
|
+
kind: row.kind,
|
|
38
|
+
status: row.status,
|
|
39
|
+
delegatedUserId: row.delegated_user_id,
|
|
40
|
+
appId: row.app_id,
|
|
41
|
+
resourceType: row.resource_type,
|
|
42
|
+
resourceId: row.resource_id,
|
|
43
|
+
createdBy: row.created_by,
|
|
44
|
+
createdAt: row.created_at.toISOString(),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const trimRequired = (value: string): string => value.trim();
|
|
48
|
+
|
|
49
|
+
const isForeignKeyViolation = (error: unknown): boolean => (error as { code?: string } | null)?.code === "23503";
|
|
50
|
+
const RESOURCE_BOUND_UNIQUE_CONSTRAINT = "uniq_service_accounts_resource_bound";
|
|
51
|
+
|
|
52
|
+
export const getByResource = async (params: {
|
|
53
|
+
appId: string;
|
|
54
|
+
resourceType: string;
|
|
55
|
+
resourceId: string;
|
|
56
|
+
}): Promise<ServiceAccount | null> => {
|
|
57
|
+
const [row] = await sql<DbServiceAccount[]>`
|
|
58
|
+
SELECT id, name, kind, status, delegated_user_id, app_id, resource_type, resource_id, created_by, created_at
|
|
59
|
+
FROM auth.service_accounts
|
|
60
|
+
WHERE kind = 'resource_bound'
|
|
61
|
+
AND app_id = ${params.appId}
|
|
62
|
+
AND resource_type = ${params.resourceType}
|
|
63
|
+
AND resource_id = ${params.resourceId}
|
|
64
|
+
ORDER BY created_at ASC
|
|
65
|
+
LIMIT 1
|
|
66
|
+
`;
|
|
67
|
+
return row ? mapServiceAccount(row) : null;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const get = async (params: { id: string }): Promise<ServiceAccount | null> => {
|
|
71
|
+
const [row] = await sql<DbServiceAccount[]>`
|
|
72
|
+
SELECT id, name, kind, status, delegated_user_id, app_id, resource_type, resource_id, created_by, created_at
|
|
73
|
+
FROM auth.service_accounts
|
|
74
|
+
WHERE id = ${params.id}::uuid
|
|
75
|
+
`;
|
|
76
|
+
return row ? mapServiceAccount(row) : null;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const createUserDelegated = async (params: {
|
|
80
|
+
name: string;
|
|
81
|
+
delegatedUserId: string;
|
|
82
|
+
createdBy?: string | null;
|
|
83
|
+
}): Promise<Result<ServiceAccount>> => {
|
|
84
|
+
const name = trimRequired(params.name);
|
|
85
|
+
if (!name) return fail(err.badInput("Service account name is required"));
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const [row] = await sql<DbServiceAccount[]>`
|
|
89
|
+
INSERT INTO auth.service_accounts (name, kind, delegated_user_id, created_by)
|
|
90
|
+
VALUES (${name}, 'user_delegated', ${params.delegatedUserId}::uuid, ${params.createdBy ?? null}::uuid)
|
|
91
|
+
RETURNING id, name, kind, status, delegated_user_id, app_id, resource_type, resource_id, created_by, created_at
|
|
92
|
+
`;
|
|
93
|
+
return row ? ok(mapServiceAccount(row)) : fail(err.internal("Failed to create service account"));
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (isForeignKeyViolation(error)) return fail(err.notFound("Delegated user"));
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const createResourceBound = async (params: {
|
|
101
|
+
name: string;
|
|
102
|
+
appId: string;
|
|
103
|
+
resourceType: string;
|
|
104
|
+
resourceId: string;
|
|
105
|
+
createdBy?: string | null;
|
|
106
|
+
}): Promise<Result<ServiceAccount>> => {
|
|
107
|
+
const name = trimRequired(params.name);
|
|
108
|
+
const appId = trimRequired(params.appId);
|
|
109
|
+
const resourceType = trimRequired(params.resourceType);
|
|
110
|
+
const resourceId = trimRequired(params.resourceId);
|
|
111
|
+
if (!name) return fail(err.badInput("Service account name is required"));
|
|
112
|
+
if (!appId || !resourceType || !resourceId) return fail(err.badInput("Resource binding is required"));
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const [row] = await sql<DbServiceAccount[]>`
|
|
116
|
+
INSERT INTO auth.service_accounts (name, kind, app_id, resource_type, resource_id, created_by)
|
|
117
|
+
VALUES (${name}, 'resource_bound', ${appId}, ${resourceType}, ${resourceId}, ${params.createdBy ?? null}::uuid)
|
|
118
|
+
RETURNING id, name, kind, status, delegated_user_id, app_id, resource_type, resource_id, created_by, created_at
|
|
119
|
+
`;
|
|
120
|
+
return row ? ok(mapServiceAccount(row)) : fail(err.internal("Failed to create service account"));
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (isForeignKeyViolation(error)) return fail(err.notFound("Creator"));
|
|
123
|
+
if (isUniqueViolation(error, RESOURCE_BOUND_UNIQUE_CONSTRAINT)) return fail(err.conflict("Resource service account"));
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export const getOrCreateResourceBound = async (params: {
|
|
129
|
+
name: string;
|
|
130
|
+
appId: string;
|
|
131
|
+
resourceType: string;
|
|
132
|
+
resourceId: string;
|
|
133
|
+
createdBy?: string | null;
|
|
134
|
+
}): Promise<Result<ServiceAccount>> => {
|
|
135
|
+
const existing = await getByResource(params);
|
|
136
|
+
if (existing) return ok(existing);
|
|
137
|
+
|
|
138
|
+
const created = await createResourceBound(params);
|
|
139
|
+
if (created.ok || created.error.code !== "CONFLICT") return created;
|
|
140
|
+
|
|
141
|
+
const raced = await getByResource(params);
|
|
142
|
+
return raced ? ok(raced) : fail(err.internal("Failed to load resource service account"));
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export const setStatus = async (params: { id: string; status: ServiceAccountStatus }): Promise<Result<void>> => {
|
|
146
|
+
const result = await sql`
|
|
147
|
+
UPDATE auth.service_accounts
|
|
148
|
+
SET status = ${params.status}
|
|
149
|
+
WHERE id = ${params.id}::uuid
|
|
150
|
+
`;
|
|
151
|
+
if (result.count === 0) return fail(err.notFound("Service account"));
|
|
152
|
+
return ok();
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export const delete_ = async (params: { id: string }): Promise<Result<void>> => {
|
|
156
|
+
const result = await sql`
|
|
157
|
+
DELETE FROM auth.service_accounts
|
|
158
|
+
WHERE id = ${params.id}::uuid
|
|
159
|
+
`;
|
|
160
|
+
if (result.count === 0) return fail(err.notFound("Service account"));
|
|
161
|
+
return ok();
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export const deleteForResource = async (params: { appId: string; resourceType: string; resourceId: string }): Promise<number> => {
|
|
165
|
+
const result = await sql`
|
|
166
|
+
DELETE FROM auth.service_accounts
|
|
167
|
+
WHERE kind = 'resource_bound'
|
|
168
|
+
AND app_id = ${params.appId}
|
|
169
|
+
AND resource_type = ${params.resourceType}
|
|
170
|
+
AND resource_id = ${params.resourceId}
|
|
171
|
+
`;
|
|
172
|
+
return result.count;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export const serviceAccounts = {
|
|
176
|
+
get,
|
|
177
|
+
createUserDelegated,
|
|
178
|
+
createResourceBound,
|
|
179
|
+
getByResource,
|
|
180
|
+
getOrCreateResourceBound,
|
|
181
|
+
setStatus,
|
|
182
|
+
delete: delete_,
|
|
183
|
+
deleteForResource,
|
|
184
|
+
};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { sql } from "bun";
|
|
2
|
+
import type { Context } from "hono";
|
|
3
|
+
import { deleteCookie, getCookie, setCookie } from "hono/cookie";
|
|
4
|
+
import { HTTPException } from "hono/http-exception";
|
|
5
|
+
import { env } from "../../config/env";
|
|
6
|
+
import type { User } from "../../contracts/shared";
|
|
7
|
+
import { isAccountCategoryAllowed } from "../account-category-policy";
|
|
8
|
+
import { isAccountExpired } from "../account-model";
|
|
9
|
+
import { IDENTITY_CLOCK_TOLERANCE_SECONDS, IDENTITY_ROLLOUT_MARGIN_MS } from "../identity/constants";
|
|
10
|
+
import { invalidateIdentitySignerCache, prepareIdentitySigner } from "../identity/key-ring";
|
|
11
|
+
import { getIdentityRuntimeConfig } from "../identity/runtime-config";
|
|
12
|
+
import { type CloudSessionClaims, isSessionJwtCandidate, signSessionToken, verifySessionToken } from "../identity/session-token";
|
|
13
|
+
import * as settings from "../settings";
|
|
14
|
+
import { loadJwtSessionUser } from "./user";
|
|
15
|
+
|
|
16
|
+
export type SessionData = {
|
|
17
|
+
userId: string;
|
|
18
|
+
sid: string;
|
|
19
|
+
authEpoch: number;
|
|
20
|
+
expiresAt: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type AuthenticatedSession = { data: SessionData; user: User };
|
|
24
|
+
|
|
25
|
+
const parseBearer = (header: string | undefined): string | null => {
|
|
26
|
+
if (!header) return null;
|
|
27
|
+
const match = header.match(/^Bearer\s+(\S+)$/i);
|
|
28
|
+
return match?.[1] ?? null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const isCloudApiToken = (token: string | null): boolean => Boolean(token?.startsWith("cld_"));
|
|
32
|
+
|
|
33
|
+
const verifyCredential = async (token: string): Promise<CloudSessionClaims | null> => {
|
|
34
|
+
if (!isSessionJwtCandidate(token)) return null;
|
|
35
|
+
return verifySessionToken(token);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const verificationByRequest = new WeakMap<Context, Map<string, Promise<CloudSessionClaims | null>>>();
|
|
39
|
+
const authenticationByRequest = new WeakMap<Context, Map<string, Promise<AuthenticatedSession | null>>>();
|
|
40
|
+
|
|
41
|
+
const requestCached = <T>(
|
|
42
|
+
cache: WeakMap<Context, Map<string, Promise<T>>>,
|
|
43
|
+
c: Context,
|
|
44
|
+
token: string,
|
|
45
|
+
load: () => Promise<T>,
|
|
46
|
+
): Promise<T> => {
|
|
47
|
+
let requests = cache.get(c);
|
|
48
|
+
if (!requests) {
|
|
49
|
+
requests = new Map();
|
|
50
|
+
cache.set(c, requests);
|
|
51
|
+
}
|
|
52
|
+
const existing = requests.get(token);
|
|
53
|
+
if (existing) return existing;
|
|
54
|
+
const pending = load();
|
|
55
|
+
requests.set(token, pending);
|
|
56
|
+
return pending;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const verifyForRequest = (c: Context, token: string): Promise<CloudSessionClaims | null> =>
|
|
60
|
+
requestCached(verificationByRequest, c, token, () => verifyCredential(token));
|
|
61
|
+
|
|
62
|
+
const authenticateVerified = async (credential: CloudSessionClaims): Promise<AuthenticatedSession | null> => {
|
|
63
|
+
const { groupsAdmin } = await getIdentityRuntimeConfig();
|
|
64
|
+
const user = await loadJwtSessionUser({
|
|
65
|
+
userId: credential.sub,
|
|
66
|
+
sid: credential.sid,
|
|
67
|
+
authEpoch: credential.auth_epoch,
|
|
68
|
+
groupsAdmin,
|
|
69
|
+
});
|
|
70
|
+
if (!user) return null;
|
|
71
|
+
if (!(await isAccountCategoryAllowed(user))) return null;
|
|
72
|
+
if (isAccountExpired(user.accountExpires)) {
|
|
73
|
+
await session.revokeAllForUser(user.id);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
user,
|
|
78
|
+
data: {
|
|
79
|
+
userId: user.id,
|
|
80
|
+
sid: credential.sid,
|
|
81
|
+
authEpoch: credential.auth_epoch,
|
|
82
|
+
expiresAt: new Date(credential.exp * 1_000).toISOString(),
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const authenticateToken = async (token: string): Promise<AuthenticatedSession | null> => {
|
|
88
|
+
const credential = await verifyCredential(token);
|
|
89
|
+
return credential ? authenticateVerified(credential) : null;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const revokeToken = async (token: string, requestId?: string | null): Promise<void> => {
|
|
93
|
+
if (isSessionJwtCandidate(token)) {
|
|
94
|
+
const claims = await verifySessionToken(token);
|
|
95
|
+
if (!claims) return;
|
|
96
|
+
await sql`
|
|
97
|
+
UPDATE auth.session_families
|
|
98
|
+
SET revoked_at = COALESCE(revoked_at, now()),
|
|
99
|
+
revocation_reason = COALESCE(revocation_reason, 'logout'),
|
|
100
|
+
revoked_request_id = COALESCE(revoked_request_id, ${requestId?.slice(0, 64) ?? null})
|
|
101
|
+
WHERE sid = ${claims.sid}::uuid AND user_id = ${claims.sub}::uuid
|
|
102
|
+
`;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const issueJwtSession = async (c: Context, userId: string, ttlSeconds: number, attempt = 0): Promise<string> => {
|
|
108
|
+
const issuedAt = new Date();
|
|
109
|
+
const expiresAt = new Date(issuedAt.getTime() + ttlSeconds * 1_000);
|
|
110
|
+
const sid = crypto.randomUUID();
|
|
111
|
+
const signer = await prepareIdentitySigner("session");
|
|
112
|
+
const requestId = c.req.header("x-request-id")?.slice(0, 64) ?? null;
|
|
113
|
+
const userAgent = c.req.header("user-agent")?.slice(0, 256) ?? null;
|
|
114
|
+
|
|
115
|
+
let authEpoch: number;
|
|
116
|
+
try {
|
|
117
|
+
authEpoch = await sql.begin(async (tx) => {
|
|
118
|
+
const [user] = await tx<Array<{ auth_epoch: string | number | bigint; provider: "local" | "ipa"; profile: "guest" | "user" }>>`
|
|
119
|
+
SELECT auth_epoch, provider, profile FROM auth.users WHERE id = ${userId}::uuid FOR UPDATE
|
|
120
|
+
`;
|
|
121
|
+
if (!user) throw new Error("Cannot create a session for an unknown user");
|
|
122
|
+
if (!(await isAccountCategoryAllowed(user, tx)))
|
|
123
|
+
throw new HTTPException(403, { message: "This account category is disabled. Contact an administrator." });
|
|
124
|
+
const epoch = Number(user.auth_epoch);
|
|
125
|
+
if (!Number.isSafeInteger(epoch) || epoch < 0) throw new Error("User auth_epoch is invalid");
|
|
126
|
+
const [activeKey] = await tx<Array<{ kid: string }>>`
|
|
127
|
+
UPDATE auth.signing_keys
|
|
128
|
+
SET verify_until = GREATEST(verify_until, ${new Date(
|
|
129
|
+
expiresAt.getTime() + IDENTITY_CLOCK_TOLERANCE_SECONDS * 1_000 + IDENTITY_ROLLOUT_MARGIN_MS,
|
|
130
|
+
)})
|
|
131
|
+
WHERE kid = ${signer.kid} AND purpose = 'session' AND state = 'active' AND sign_until > now()
|
|
132
|
+
RETURNING kid
|
|
133
|
+
`;
|
|
134
|
+
if (!activeKey) throw new Error("Prepared Cloud session signer is no longer active");
|
|
135
|
+
await tx`
|
|
136
|
+
INSERT INTO auth.session_families (
|
|
137
|
+
sid, user_id, auth_epoch, signing_kid, issued_at, expires_at, created_request_id, created_user_agent, legal_pending
|
|
138
|
+
) VALUES (${sid}, ${userId}, ${epoch}, ${signer.kid}, ${issuedAt}, ${expiresAt}, ${requestId}, ${userAgent},
|
|
139
|
+
NOT EXISTS (SELECT 1 FROM auth.legal_acceptances WHERE user_id = ${userId}::uuid))
|
|
140
|
+
`;
|
|
141
|
+
await tx`UPDATE auth.users SET last_login_local = ${issuedAt} WHERE id = ${userId}::uuid`;
|
|
142
|
+
return epoch;
|
|
143
|
+
});
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (attempt === 0 && error instanceof Error && error.message === "Prepared Cloud session signer is no longer active") {
|
|
146
|
+
invalidateIdentitySignerCache("session", signer.kid);
|
|
147
|
+
return issueJwtSession(c, userId, ttlSeconds, 1);
|
|
148
|
+
}
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let token: string;
|
|
153
|
+
try {
|
|
154
|
+
token = (await signSessionToken({ userId, sid, authEpoch, issuedAt, expiresAt, signer })).token;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
await sql`
|
|
157
|
+
UPDATE auth.session_families
|
|
158
|
+
SET revoked_at = now(), revocation_reason = 'issuance_failed'
|
|
159
|
+
WHERE sid = ${sid}::uuid AND revoked_at IS NULL
|
|
160
|
+
`;
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const [stillActive] = await sql<Array<{ active: boolean }>>`
|
|
165
|
+
SELECT EXISTS(
|
|
166
|
+
SELECT 1 FROM auth.signing_keys
|
|
167
|
+
WHERE kid = ${signer.kid} AND purpose = 'session' AND state = 'active'
|
|
168
|
+
) AS active
|
|
169
|
+
`;
|
|
170
|
+
if (stillActive?.active) return token;
|
|
171
|
+
|
|
172
|
+
await sql`
|
|
173
|
+
UPDATE auth.session_families
|
|
174
|
+
SET revoked_at = now(), revocation_reason = 'signer_revoked_during_issuance'
|
|
175
|
+
WHERE sid = ${sid}::uuid AND revoked_at IS NULL
|
|
176
|
+
`;
|
|
177
|
+
invalidateIdentitySignerCache("session", signer.kid);
|
|
178
|
+
if (attempt === 0) return issueJwtSession(c, userId, ttlSeconds, 1);
|
|
179
|
+
throw new Error("Cloud session signer changed repeatedly during issuance");
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export const session = {
|
|
183
|
+
getToken: (c: Context): string | null => {
|
|
184
|
+
const authorization = c.req.header("Authorization");
|
|
185
|
+
const bearer = parseBearer(authorization);
|
|
186
|
+
if (/^Bearer(?:\s|$)/i.test(authorization ?? "")) return isCloudApiToken(bearer) ? null : bearer;
|
|
187
|
+
return getCookie(c, "session_token") ?? null;
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
getBearerToken: (c: Context): string | null => parseBearer(c.req.header("Authorization")),
|
|
191
|
+
|
|
192
|
+
create: async (c: Context, userId: string): Promise<string> => {
|
|
193
|
+
const expiryHours = await settings.get<number>("user.session.expiry_hours");
|
|
194
|
+
if (!Number.isFinite(expiryHours) || expiryHours < 1) throw new Error("user.session.expiry_hours must be a positive number");
|
|
195
|
+
const ttlSeconds = Math.floor(expiryHours * 60 * 60);
|
|
196
|
+
const clientToken = await issueJwtSession(c, userId, ttlSeconds);
|
|
197
|
+
|
|
198
|
+
setCookie(c, "session_token", clientToken, {
|
|
199
|
+
httpOnly: true,
|
|
200
|
+
secure: !env.IS_DEVELOPMENT,
|
|
201
|
+
sameSite: "Lax",
|
|
202
|
+
maxAge: ttlSeconds,
|
|
203
|
+
path: "/",
|
|
204
|
+
});
|
|
205
|
+
return clientToken;
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
revoke: revokeToken,
|
|
209
|
+
|
|
210
|
+
delete: async (c: Context): Promise<void> => {
|
|
211
|
+
const token = session.getToken(c);
|
|
212
|
+
if (token) await revokeToken(token, c.req.header("x-request-id"));
|
|
213
|
+
deleteCookie(c, "session_token", { path: "/" });
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
revokeAllForUser: async (userId: string): Promise<void> => {
|
|
217
|
+
const [updated] = await sql<Array<{ id: string }>>`
|
|
218
|
+
UPDATE auth.users SET auth_epoch = auth_epoch + 1
|
|
219
|
+
WHERE id = ${userId}::uuid RETURNING id
|
|
220
|
+
`;
|
|
221
|
+
if (!updated) throw new Error("Cannot revoke sessions for an unknown user");
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
authenticate: authenticateToken,
|
|
225
|
+
|
|
226
|
+
authenticateRequest: (c: Context, token: string): Promise<AuthenticatedSession | null> =>
|
|
227
|
+
requestCached(authenticationByRequest, c, token, async () => {
|
|
228
|
+
const credential = await verifyForRequest(c, token);
|
|
229
|
+
return credential ? authenticateVerified(credential) : null;
|
|
230
|
+
}),
|
|
231
|
+
|
|
232
|
+
getRequestSubject: async (c: Context, token: string): Promise<string | null> => {
|
|
233
|
+
const credential = await verifyForRequest(c, token);
|
|
234
|
+
return credential?.sub ?? null;
|
|
235
|
+
},
|
|
236
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { sql } from "bun";
|
|
3
|
+
import { session } from "./index";
|
|
4
|
+
|
|
5
|
+
/** Integration fixture: run with an isolated Core authority and its public JWKS endpoint. */
|
|
6
|
+
export const createTestSession = async (userId: string): Promise<string> => {
|
|
7
|
+
// Most session fixtures represent established users, not first-use onboarding.
|
|
8
|
+
await sql`INSERT INTO auth.legal_acceptances (user_id, session_id, document_version, documents)
|
|
9
|
+
VALUES (${userId}::uuid, ${crypto.randomUUID()}::uuid, 'test-fixture', '[]'::jsonb) ON CONFLICT DO NOTHING`;
|
|
10
|
+
const app = new Hono().post("/login", async (c) => c.json({ token: await session.create(c, userId) }));
|
|
11
|
+
const response = await app.request("/login", { method: "POST" });
|
|
12
|
+
if (response.status !== 200) throw new Error(`Session fixture login failed: ${response.status}`);
|
|
13
|
+
const body: unknown = await response.json();
|
|
14
|
+
if (!body || typeof body !== "object" || !("token" in body) || typeof body.token !== "string") {
|
|
15
|
+
throw new Error("Session fixture did not return a token");
|
|
16
|
+
}
|
|
17
|
+
return body.token;
|
|
18
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { sql } from "bun";
|
|
2
|
+
import type { User } from "../../contracts/shared";
|
|
3
|
+
import { buildRoles } from "../accounts/authz";
|
|
4
|
+
import { resolveProviderProfile } from "../accounts/base-user";
|
|
5
|
+
import { managedGroupIdsSubquery, managedGroupsNamesSubquery } from "../accounts/group-sql";
|
|
6
|
+
import { buildIpaUserData, emptyIpaUserData, userIpaDataColumns, userIpaDataJoin } from "../accounts/ipa-data";
|
|
7
|
+
import { resolveAccountExpires } from "../accounts/model";
|
|
8
|
+
import { toPgTextArray } from "../postgres";
|
|
9
|
+
|
|
10
|
+
type DbRow = Record<string, unknown>;
|
|
11
|
+
|
|
12
|
+
const pgArrayLiteralToStrings = (value: string): string[] => {
|
|
13
|
+
if (value === "{}") return [];
|
|
14
|
+
if (!value.startsWith("{") || !value.endsWith("}")) return [];
|
|
15
|
+
const items: string[] = [];
|
|
16
|
+
let item = "";
|
|
17
|
+
let quoted = false;
|
|
18
|
+
let escaped = false;
|
|
19
|
+
let wasQuoted = false;
|
|
20
|
+
for (const char of value.slice(1, -1)) {
|
|
21
|
+
if (escaped) {
|
|
22
|
+
item += char;
|
|
23
|
+
escaped = false;
|
|
24
|
+
} else if (char === "\\") {
|
|
25
|
+
escaped = true;
|
|
26
|
+
} else if (char === '"') {
|
|
27
|
+
quoted = !quoted;
|
|
28
|
+
wasQuoted = true;
|
|
29
|
+
} else if (char === "," && !quoted) {
|
|
30
|
+
if (wasQuoted || (item !== "" && item !== "NULL")) items.push(item);
|
|
31
|
+
item = "";
|
|
32
|
+
wasQuoted = false;
|
|
33
|
+
} else {
|
|
34
|
+
item += char;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (wasQuoted || (item !== "" && item !== "NULL")) items.push(item);
|
|
38
|
+
return items;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const stringArray = (value: unknown): string[] => {
|
|
42
|
+
if (Array.isArray(value)) return value.filter((item): item is string => typeof item === "string");
|
|
43
|
+
if (typeof value === "string") return pgArrayLiteralToStrings(value);
|
|
44
|
+
return [];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const buildProjectedUser = (row: DbRow): User => {
|
|
48
|
+
const { provider, profile } = resolveProviderProfile(row);
|
|
49
|
+
const mail = (row.mail as string | null | undefined) ?? null;
|
|
50
|
+
const displayName = (row.display_name as string | null | undefined) ?? "";
|
|
51
|
+
const memberofGroup = stringArray(row.member_groups);
|
|
52
|
+
const memberofGroupIds = stringArray(row.member_group_ids);
|
|
53
|
+
const manages = stringArray(row.manages);
|
|
54
|
+
const managesGroupIds = stringArray(row.manages_group_ids);
|
|
55
|
+
const common = {
|
|
56
|
+
id: row.id as string,
|
|
57
|
+
uid: row.uid as string,
|
|
58
|
+
roles: buildRoles({ provider, profile, memberofGroup, manages, admin: Boolean(row.effective_admin) }),
|
|
59
|
+
profile,
|
|
60
|
+
givenname: (row.given_name as string | null | undefined) ?? "",
|
|
61
|
+
sn: (row.sn as string | null | undefined) ?? "",
|
|
62
|
+
displayName: displayName || (profile === "guest" && mail ? mail : ""),
|
|
63
|
+
mail,
|
|
64
|
+
avatarHash: (row.avatar_hash as string | null | undefined) ?? null,
|
|
65
|
+
accountExpires: resolveAccountExpires(row)?.toISOString() ?? null,
|
|
66
|
+
lastLoginLocal: row.last_login_local ? new Date(row.last_login_local as Date | string).toISOString() : null,
|
|
67
|
+
memberofGroup,
|
|
68
|
+
memberofGroupIds,
|
|
69
|
+
manages,
|
|
70
|
+
managesGroupIds,
|
|
71
|
+
};
|
|
72
|
+
return provider === "ipa"
|
|
73
|
+
? { ...common, provider: "ipa", ipa: buildIpaUserData(row) ?? emptyIpaUserData() }
|
|
74
|
+
: { ...common, provider: "local", ipa: null };
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const userProjectionSql = (groupsAdmin: string[]) => sql`
|
|
78
|
+
u.*,
|
|
79
|
+
${userIpaDataColumns},
|
|
80
|
+
CASE
|
|
81
|
+
WHEN u.provider = 'local' THEN u.admin
|
|
82
|
+
ELSE EXISTS(
|
|
83
|
+
SELECT 1 FROM auth.ipa_user_effective_groups eg
|
|
84
|
+
WHERE eg.user_id = u.id
|
|
85
|
+
AND eg.group_name = ANY(${toPgTextArray(groupsAdmin)}::text[])
|
|
86
|
+
)
|
|
87
|
+
END AS effective_admin,
|
|
88
|
+
COALESCE(ARRAY(
|
|
89
|
+
SELECT g.name
|
|
90
|
+
FROM auth.user_groups_v2 ug
|
|
91
|
+
JOIN auth.groups g ON g.id = ug.group_id
|
|
92
|
+
WHERE ug.user_id = u.id
|
|
93
|
+
ORDER BY g.name
|
|
94
|
+
), '{}') AS member_groups,
|
|
95
|
+
COALESCE(ARRAY(
|
|
96
|
+
SELECT ug.group_id
|
|
97
|
+
FROM auth.user_groups_v2 ug
|
|
98
|
+
JOIN auth.groups g ON g.id = ug.group_id
|
|
99
|
+
WHERE ug.user_id = u.id
|
|
100
|
+
ORDER BY g.name
|
|
101
|
+
), '{}') AS member_group_ids,
|
|
102
|
+
COALESCE(ARRAY(${managedGroupsNamesSubquery(sql`u.id`)}), '{}') AS manages,
|
|
103
|
+
COALESCE(ARRAY(${managedGroupIdsSubquery(sql`u.id`)}), '{}') AS manages_group_ids
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
export const loadJwtSessionUser = async (
|
|
107
|
+
params: {
|
|
108
|
+
userId: string;
|
|
109
|
+
sid: string;
|
|
110
|
+
authEpoch: number;
|
|
111
|
+
groupsAdmin: string[];
|
|
112
|
+
/** Core's consent screen only: still validates family, epoch and expiry. */
|
|
113
|
+
allowPendingLegalConsent?: boolean;
|
|
114
|
+
},
|
|
115
|
+
query: typeof sql = sql,
|
|
116
|
+
): Promise<User | null> => {
|
|
117
|
+
const rows = await query<DbRow[]>`
|
|
118
|
+
SELECT ${userProjectionSql(params.groupsAdmin)}
|
|
119
|
+
FROM auth.session_families sf
|
|
120
|
+
JOIN auth.users u ON u.id = sf.user_id
|
|
121
|
+
JOIN auth.signing_keys sk ON sk.kid = sf.signing_kid AND sk.state <> 'revoked'
|
|
122
|
+
${userIpaDataJoin}
|
|
123
|
+
WHERE sf.sid = ${params.sid}::uuid
|
|
124
|
+
AND sf.user_id = ${params.userId}::uuid
|
|
125
|
+
AND sf.auth_epoch = ${params.authEpoch}
|
|
126
|
+
AND u.auth_epoch = ${params.authEpoch}
|
|
127
|
+
AND sf.revoked_at IS NULL
|
|
128
|
+
AND sf.expires_at > now()
|
|
129
|
+
AND (${params.allowPendingLegalConsent === true} OR NOT sf.legal_pending OR EXISTS (
|
|
130
|
+
SELECT 1 FROM auth.legal_acceptances la WHERE la.user_id = u.id
|
|
131
|
+
))
|
|
132
|
+
`;
|
|
133
|
+
return rows[0] ? buildProjectedUser(rows[0]) : null;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const loadCurrentUser = async (params: { userId: string; groupsAdmin: string[] }, query: typeof sql = sql): Promise<User | null> => {
|
|
137
|
+
const rows = await query<DbRow[]>`
|
|
138
|
+
SELECT ${userProjectionSql(params.groupsAdmin)}
|
|
139
|
+
FROM auth.users u
|
|
140
|
+
${userIpaDataJoin}
|
|
141
|
+
WHERE u.id = ${params.userId}::uuid
|
|
142
|
+
`;
|
|
143
|
+
return rows[0] ? buildProjectedUser(rows[0]) : null;
|
|
144
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed async settings API.
|
|
3
|
+
*
|
|
4
|
+
* Two surfaces are exposed:
|
|
5
|
+
*
|
|
6
|
+
* 1. `app.settings` — created by `defineApp`, typed against that app's own
|
|
7
|
+
* declared settings (plus any core settings since core's snapshot is
|
|
8
|
+
* loaded into every container). Available on the AppDefinition return value.
|
|
9
|
+
*
|
|
10
|
+
* 2. `coreSettings` — for cloud-lib internal services that don't have an
|
|
11
|
+
* `app` reference (e.g. `services/notifications/email.ts`,
|
|
12
|
+
* `services/weather/forecast.ts`, OpenAPI middleware, cron callbacks).
|
|
13
|
+
* Loosely typed (caller provides the value type via the `T` generic).
|
|
14
|
+
*
|
|
15
|
+
* Backend: cache-aside via `store.ts` (Redis 5min TTL + DB fallback). All
|
|
16
|
+
* functions are async — sync render-time access uses the per-request snapshot
|
|
17
|
+
* exposed via `c.get("settings")`.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { AppSettingsMap, KindToType } from "../../contracts/settings-types";
|
|
21
|
+
import { deleteKey, readKey, writeKey } from "./store";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Per-app typed settings API. Keys constrained to those declared in the
|
|
25
|
+
* app's `defineApp({ settings: ... })` block.
|
|
26
|
+
*/
|
|
27
|
+
export interface SettingsAPI<F extends Record<string, unknown>> {
|
|
28
|
+
get<K extends keyof F & string>(key: K): Promise<F[K]>;
|
|
29
|
+
set<K extends keyof F & string>(key: K, value: F[K]): Promise<void>;
|
|
30
|
+
remove<K extends keyof F & string>(key: K): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Build a typed SettingsAPI for a given AppSettingsMap.
|
|
35
|
+
*
|
|
36
|
+
* The runtime is identical for every app (delegates to store.ts); the typing
|
|
37
|
+
* is purely a TS construct that constrains keys/values to what was declared.
|
|
38
|
+
*/
|
|
39
|
+
export const createSettingsAPI = <S extends AppSettingsMap>(): SettingsAPI<{
|
|
40
|
+
[K in keyof S]: KindToType<S[K]["kind"]>;
|
|
41
|
+
}> => ({
|
|
42
|
+
get: async (key) => readKey(key) as never,
|
|
43
|
+
set: async (key, value) => writeKey(key, value),
|
|
44
|
+
remove: async (key) => deleteKey(key),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* cloud-lib internal services and other apps use `coreSettings` for any
|
|
49
|
+
* setting access outside of a per-request context. Loose-typed (the caller
|
|
50
|
+
* specifies the expected value type via the `T` generic) — apps that need
|
|
51
|
+
* tight typing for their OWN settings use `app.settings.get/set` instead.
|
|
52
|
+
*
|
|
53
|
+
* Usage:
|
|
54
|
+
* await coreSettings.get<string>("app.name"); // string
|
|
55
|
+
* await coreSettings.set("freeipa.enable", true); // value type free
|
|
56
|
+
*/
|
|
57
|
+
export const coreSettings = {
|
|
58
|
+
get: <T = unknown>(key: string): Promise<T> => readKey(key) as Promise<T>,
|
|
59
|
+
set: (key: string, value: unknown): Promise<void> => writeKey(key, value),
|
|
60
|
+
remove: (key: string): Promise<void> => deleteKey(key),
|
|
61
|
+
};
|