@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,644 @@
|
|
|
1
|
+
import { err, fail, ok, type Result } from "@k2b/stdlib";
|
|
2
|
+
import { type SQLQuery, sql } from "bun";
|
|
3
|
+
import { recursiveGroupIdsSubquery } from "../../services/accounts/group-sql";
|
|
4
|
+
import { toPgUuidArray } from "../../services/postgres";
|
|
5
|
+
|
|
6
|
+
export type AccessDb = typeof sql;
|
|
7
|
+
|
|
8
|
+
// ==========================
|
|
9
|
+
// Permission Levels
|
|
10
|
+
// ==========================
|
|
11
|
+
|
|
12
|
+
export const PERMISSION_LEVELS = ["none", "read", "write", "admin"] as const;
|
|
13
|
+
export type PermissionLevel = (typeof PERMISSION_LEVELS)[number];
|
|
14
|
+
|
|
15
|
+
/** Compare permission levels (returns true if a >= b) */
|
|
16
|
+
export const hasPermission = (userLevel: PermissionLevel, requiredLevel: PermissionLevel): boolean => {
|
|
17
|
+
const levels = PERMISSION_LEVELS;
|
|
18
|
+
return levels.indexOf(userLevel) >= levels.indexOf(requiredLevel);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// ==========================
|
|
22
|
+
// Principal Types
|
|
23
|
+
// ==========================
|
|
24
|
+
|
|
25
|
+
export type PrincipalType = "user" | "group" | "service_account" | "authenticated" | "public";
|
|
26
|
+
|
|
27
|
+
export type Principal =
|
|
28
|
+
| { type: "user"; userId: string }
|
|
29
|
+
| { type: "group"; groupId: string }
|
|
30
|
+
| { type: "service_account"; serviceAccountId: string }
|
|
31
|
+
| { type: "authenticated" }
|
|
32
|
+
| { type: "public" };
|
|
33
|
+
|
|
34
|
+
export type AccessSubject =
|
|
35
|
+
| { type: "user"; userId: string; delegatedByServiceAccountId?: string | null }
|
|
36
|
+
| { type: "service_account"; serviceAccountId: string };
|
|
37
|
+
|
|
38
|
+
export type EffectiveGroup = {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type AccessPrincipalColumns = {
|
|
44
|
+
userId: SQLQuery;
|
|
45
|
+
groupId: SQLQuery;
|
|
46
|
+
serviceAccountId: SQLQuery;
|
|
47
|
+
authenticatedOnly: SQLQuery;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type AccessPrincipalTierConditions = {
|
|
51
|
+
serviceAccount: SQLQuery;
|
|
52
|
+
user: SQLQuery;
|
|
53
|
+
group: SQLQuery;
|
|
54
|
+
authenticated: SQLQuery;
|
|
55
|
+
public: SQLQuery;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// ==========================
|
|
59
|
+
// Access Entry Types
|
|
60
|
+
// ==========================
|
|
61
|
+
|
|
62
|
+
export type AccessEntry = {
|
|
63
|
+
id: string;
|
|
64
|
+
principal: Principal;
|
|
65
|
+
permission: PermissionLevel;
|
|
66
|
+
createdAt: string;
|
|
67
|
+
// Resolved display info (populated by service)
|
|
68
|
+
displayName?: string;
|
|
69
|
+
avatarHash?: string | null;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type AccessUserSource =
|
|
73
|
+
| { type: "direct" }
|
|
74
|
+
| {
|
|
75
|
+
type: "group";
|
|
76
|
+
/** Top-level group from the access grant, not the nested membership group. */
|
|
77
|
+
groupId: string;
|
|
78
|
+
groupName: string;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export type AccessUser = {
|
|
82
|
+
id: string;
|
|
83
|
+
uid: string;
|
|
84
|
+
displayName: string;
|
|
85
|
+
avatarHash: string | null;
|
|
86
|
+
permission: Exclude<PermissionLevel, "none">;
|
|
87
|
+
source: AccessUserSource;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
type DbAccess = {
|
|
91
|
+
id: string;
|
|
92
|
+
user_id: string | null;
|
|
93
|
+
group_id: string | null;
|
|
94
|
+
service_account_id: string | null;
|
|
95
|
+
authenticated_only: boolean;
|
|
96
|
+
permission: PermissionLevel;
|
|
97
|
+
created_at: Date;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
type DbAccessUser = {
|
|
101
|
+
id: string;
|
|
102
|
+
uid: string;
|
|
103
|
+
display_name: string;
|
|
104
|
+
avatar_hash: string | null;
|
|
105
|
+
permission: Exclude<PermissionLevel, "none">;
|
|
106
|
+
direct: boolean;
|
|
107
|
+
source_group_id: string | null;
|
|
108
|
+
source_group_name: string | null;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// ==========================
|
|
112
|
+
// Helper Functions
|
|
113
|
+
// ==========================
|
|
114
|
+
|
|
115
|
+
const uniqueIds = (values: string[] | null | undefined): string[] => [...new Set((values ?? []).filter(Boolean))];
|
|
116
|
+
|
|
117
|
+
const escapeLikePattern = (value: string): string => value.replace(/[\\%_]/g, (match) => `\\${match}`);
|
|
118
|
+
|
|
119
|
+
const PERMISSION_RANK: Record<PermissionLevel, number> = {
|
|
120
|
+
none: 1,
|
|
121
|
+
read: 2,
|
|
122
|
+
write: 3,
|
|
123
|
+
admin: 4,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Builds a typed access principal from one database access row.
|
|
128
|
+
*/
|
|
129
|
+
const principalFromDb = (row: DbAccess): Principal => {
|
|
130
|
+
if (row.user_id) return { type: "user", userId: row.user_id };
|
|
131
|
+
if (row.group_id) return { type: "group", groupId: row.group_id };
|
|
132
|
+
if (row.service_account_id) return { type: "service_account", serviceAccountId: row.service_account_id };
|
|
133
|
+
if (row.authenticated_only) return { type: "authenticated" };
|
|
134
|
+
return { type: "public" };
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Maps raw access rows into the normalized AccessEntry shape used by app services.
|
|
139
|
+
*/
|
|
140
|
+
const mapToAccessEntry = (row: DbAccess): AccessEntry => ({
|
|
141
|
+
id: row.id,
|
|
142
|
+
principal: principalFromDb(row),
|
|
143
|
+
permission: row.permission,
|
|
144
|
+
createdAt: row.created_at.toISOString(),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// ==========================
|
|
148
|
+
// Core Access Service
|
|
149
|
+
// ==========================
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Create a new access entry.
|
|
153
|
+
* Returns the created entry ID.
|
|
154
|
+
*/
|
|
155
|
+
export const createAccess = async (
|
|
156
|
+
params: { principal: Principal; permission: PermissionLevel },
|
|
157
|
+
db: AccessDb = sql,
|
|
158
|
+
): Promise<Result<{ id: string }>> => {
|
|
159
|
+
const { principal, permission } = params;
|
|
160
|
+
|
|
161
|
+
let userId: string | null = null;
|
|
162
|
+
let groupId: string | null = null;
|
|
163
|
+
let serviceAccountId: string | null = null;
|
|
164
|
+
let authenticatedOnly = false;
|
|
165
|
+
|
|
166
|
+
if (principal.type === "user") {
|
|
167
|
+
userId = principal.userId;
|
|
168
|
+
// Verify user exists
|
|
169
|
+
const [user] = await db<{ id: string }[]>`
|
|
170
|
+
SELECT id FROM auth.users WHERE id = ${userId}::uuid
|
|
171
|
+
`;
|
|
172
|
+
if (!user) {
|
|
173
|
+
return fail(err.notFound("User"));
|
|
174
|
+
}
|
|
175
|
+
} else if (principal.type === "group") {
|
|
176
|
+
groupId = principal.groupId;
|
|
177
|
+
// Verify group exists
|
|
178
|
+
const [group] = await db<{ id: string }[]>`
|
|
179
|
+
SELECT id FROM auth.groups WHERE id = ${groupId}::uuid
|
|
180
|
+
`;
|
|
181
|
+
if (!group) {
|
|
182
|
+
return fail(err.notFound("Group"));
|
|
183
|
+
}
|
|
184
|
+
} else if (principal.type === "service_account") {
|
|
185
|
+
serviceAccountId = principal.serviceAccountId;
|
|
186
|
+
const [serviceAccount] = await db<{ id: string }[]>`
|
|
187
|
+
SELECT id FROM auth.service_accounts WHERE id = ${serviceAccountId}::uuid AND status = 'active'
|
|
188
|
+
`;
|
|
189
|
+
if (!serviceAccount) {
|
|
190
|
+
return fail(err.notFound("Service account"));
|
|
191
|
+
}
|
|
192
|
+
} else if (principal.type === "authenticated") {
|
|
193
|
+
authenticatedOnly = true;
|
|
194
|
+
}
|
|
195
|
+
// public: user/group null, authenticated_only false
|
|
196
|
+
|
|
197
|
+
const [row] = await db<{ id: string }[]>`
|
|
198
|
+
INSERT INTO auth.access (user_id, group_id, service_account_id, authenticated_only, permission)
|
|
199
|
+
VALUES (${userId}::uuid, ${groupId}::uuid, ${serviceAccountId}::uuid, ${authenticatedOnly}, ${permission}::auth.permission_level)
|
|
200
|
+
RETURNING id
|
|
201
|
+
`;
|
|
202
|
+
|
|
203
|
+
if (!row) {
|
|
204
|
+
return fail(err.internal("Failed to create access entry"));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return ok({ id: row.id });
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get an access entry by ID.
|
|
212
|
+
*/
|
|
213
|
+
export const getAccess = async (params: { id: string }, db: AccessDb = sql): Promise<AccessEntry | null> => {
|
|
214
|
+
const [row] = await db<DbAccess[]>`
|
|
215
|
+
SELECT id, user_id, group_id, service_account_id, authenticated_only, permission, created_at
|
|
216
|
+
FROM auth.access
|
|
217
|
+
WHERE id = ${params.id}::uuid
|
|
218
|
+
`;
|
|
219
|
+
return row ? mapToAccessEntry(row) : null;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Update an access entry's permission level.
|
|
224
|
+
*/
|
|
225
|
+
export const updateAccess = async (params: { id: string; permission: PermissionLevel }, db: AccessDb = sql): Promise<Result<void>> => {
|
|
226
|
+
const result = await db`
|
|
227
|
+
UPDATE auth.access
|
|
228
|
+
SET permission = ${params.permission}::auth.permission_level
|
|
229
|
+
WHERE id = ${params.id}::uuid
|
|
230
|
+
`;
|
|
231
|
+
|
|
232
|
+
if (result.count === 0) {
|
|
233
|
+
return fail(err.notFound("Access entry"));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return ok();
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
/** Resolve direct and nested group memberships from the authoritative database mirror. */
|
|
240
|
+
export const getEffectiveGroups = async (params: { userId: string | null }, db: AccessDb = sql): Promise<EffectiveGroup[]> => {
|
|
241
|
+
if (!params.userId) return [];
|
|
242
|
+
const rows = await db<{ id: string; name: string }[]>`
|
|
243
|
+
SELECT g.id, g.name
|
|
244
|
+
FROM auth.groups g
|
|
245
|
+
WHERE g.id IN (${recursiveGroupIdsSubquery(params.userId)})
|
|
246
|
+
ORDER BY g.name
|
|
247
|
+
`;
|
|
248
|
+
return rows;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
export const getEffectiveGroupIds = async (params: { userId: string | null }, db: AccessDb = sql): Promise<string[]> =>
|
|
252
|
+
(await getEffectiveGroups(params, db)).map((group) => group.id);
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Delete an access entry.
|
|
256
|
+
*/
|
|
257
|
+
export const deleteAccess = async (params: { id: string }, db: AccessDb = sql): Promise<Result<void>> => {
|
|
258
|
+
const result = await db`
|
|
259
|
+
DELETE FROM auth.access
|
|
260
|
+
WHERE id = ${params.id}::uuid
|
|
261
|
+
`;
|
|
262
|
+
|
|
263
|
+
if (result.count === 0) {
|
|
264
|
+
return fail(err.notFound("Access entry"));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return ok();
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// ==========================
|
|
271
|
+
// Resource Access Helpers
|
|
272
|
+
// ==========================
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Generic interface for resource access junction tables.
|
|
276
|
+
* Each app implements this to connect their resources to auth.access.
|
|
277
|
+
*/
|
|
278
|
+
export type ResourceAccessAdapter<TResourceId = string> = {
|
|
279
|
+
/** Get all access entries for a resource */
|
|
280
|
+
list: (resourceId: TResourceId) => Promise<AccessEntry[]>;
|
|
281
|
+
/** Add an access entry to a resource */
|
|
282
|
+
add: (resourceId: TResourceId, accessId: string) => Promise<Result<void>>;
|
|
283
|
+
/** Remove an access entry from a resource */
|
|
284
|
+
remove: (resourceId: TResourceId, accessId: string) => Promise<Result<void>>;
|
|
285
|
+
/** Count access entries for a resource */
|
|
286
|
+
count: (resourceId: TResourceId) => Promise<number>;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Build canonical principal predicates for one auth.access row, separated by
|
|
291
|
+
* precedence tier.
|
|
292
|
+
*
|
|
293
|
+
* Apps provide column fragments because access rows are commonly joined under
|
|
294
|
+
* different aliases. The subject is authoritative: group membership is always
|
|
295
|
+
* resolved recursively from its user id and never accepted from a caller.
|
|
296
|
+
* Resource binding, credential scopes, and permission precedence remain the
|
|
297
|
+
* responsibility of the app using the predicate.
|
|
298
|
+
*/
|
|
299
|
+
export const buildAccessPrincipalTierConditions = (params: {
|
|
300
|
+
subject: AccessSubject | null;
|
|
301
|
+
columns: AccessPrincipalColumns;
|
|
302
|
+
}): AccessPrincipalTierConditions => {
|
|
303
|
+
const userId = params.subject?.type === "user" ? params.subject.userId : null;
|
|
304
|
+
const serviceAccountId = params.subject?.type === "service_account" ? params.subject.serviceAccountId : null;
|
|
305
|
+
const authenticated = params.subject !== null;
|
|
306
|
+
const columns = params.columns;
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
serviceAccount: sql`${columns.serviceAccountId} = ${serviceAccountId}::uuid`,
|
|
310
|
+
user: sql`${columns.userId} = ${userId}::uuid`,
|
|
311
|
+
group: sql`(
|
|
312
|
+
${userId}::uuid IS NOT NULL
|
|
313
|
+
AND ${columns.groupId} IN (${userId ? recursiveGroupIdsSubquery(userId) : sql`SELECT NULL::uuid WHERE FALSE`})
|
|
314
|
+
)`,
|
|
315
|
+
authenticated: sql`${authenticated} AND ${columns.authenticatedOnly} = true`,
|
|
316
|
+
public: sql`(
|
|
317
|
+
${columns.userId} IS NULL
|
|
318
|
+
AND ${columns.groupId} IS NULL
|
|
319
|
+
AND ${columns.serviceAccountId} IS NULL
|
|
320
|
+
AND ${columns.authenticatedOnly} = false
|
|
321
|
+
)`,
|
|
322
|
+
};
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
/** Build one predicate that matches every principal tier for the subject. */
|
|
326
|
+
export const buildAccessPrincipalCondition = (params: { subject: AccessSubject | null; columns: AccessPrincipalColumns }): SQLQuery => {
|
|
327
|
+
const tiers = buildAccessPrincipalTierConditions(params);
|
|
328
|
+
return sql`(${tiers.serviceAccount} OR ${tiers.user} OR ${tiers.group} OR ${tiers.authenticated} OR ${tiers.public})`;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Get the effective permission level for an actor on a resource.
|
|
333
|
+
* Returns the highest permission from:
|
|
334
|
+
* - Direct user or service-account access
|
|
335
|
+
* - Direct and nested user group memberships resolved from the database
|
|
336
|
+
* - Authenticated access for every authenticated subject
|
|
337
|
+
* - Public access for every subject, including anonymous callers
|
|
338
|
+
*/
|
|
339
|
+
export const getEffectivePermission = async (params: {
|
|
340
|
+
accessIds: string[];
|
|
341
|
+
subject?: AccessSubject | null;
|
|
342
|
+
/** @deprecated Pass subject instead. */
|
|
343
|
+
userId?: string | null;
|
|
344
|
+
/** @deprecated Membership is resolved from userId and this value is intentionally ignored. */
|
|
345
|
+
userGroups?: string[];
|
|
346
|
+
/** @deprecated Pass subject instead. */
|
|
347
|
+
serviceAccountId?: string | null;
|
|
348
|
+
}): Promise<PermissionLevel> => {
|
|
349
|
+
const accessIds = params.accessIds ?? [];
|
|
350
|
+
const subject =
|
|
351
|
+
"subject" in params
|
|
352
|
+
? (params.subject ?? null)
|
|
353
|
+
: params.userId
|
|
354
|
+
? { type: "user" as const, userId: params.userId }
|
|
355
|
+
: params.serviceAccountId
|
|
356
|
+
? { type: "service_account" as const, serviceAccountId: params.serviceAccountId }
|
|
357
|
+
: null;
|
|
358
|
+
const legacyServiceAccountId = !("subject" in params) && !params.userId ? (params.serviceAccountId ?? null) : null;
|
|
359
|
+
|
|
360
|
+
if (accessIds.length === 0) return "none";
|
|
361
|
+
|
|
362
|
+
// Legacy callers did not provide resource binding or scope context. Keep
|
|
363
|
+
// their service-account lookup direct-only so the new authenticated/public
|
|
364
|
+
// semantics require an explicit AccessSubject migration.
|
|
365
|
+
const principalMatch = legacyServiceAccountId
|
|
366
|
+
? sql`a.service_account_id = ${legacyServiceAccountId}::uuid`
|
|
367
|
+
: buildAccessPrincipalCondition({
|
|
368
|
+
subject,
|
|
369
|
+
columns: {
|
|
370
|
+
userId: sql`a.user_id`,
|
|
371
|
+
groupId: sql`a.group_id`,
|
|
372
|
+
serviceAccountId: sql`a.service_account_id`,
|
|
373
|
+
authenticatedOnly: sql`a.authenticated_only`,
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
const rows = await sql<{ permission: PermissionLevel }[]>`
|
|
378
|
+
SELECT a.permission
|
|
379
|
+
FROM auth.access a
|
|
380
|
+
WHERE a.id = ANY(${toPgUuidArray(accessIds)}::uuid[])
|
|
381
|
+
AND ${principalMatch}
|
|
382
|
+
ORDER BY
|
|
383
|
+
CASE permission
|
|
384
|
+
WHEN 'admin' THEN 4
|
|
385
|
+
WHEN 'write' THEN 3
|
|
386
|
+
WHEN 'read' THEN 2
|
|
387
|
+
WHEN 'none' THEN 1
|
|
388
|
+
END DESC
|
|
389
|
+
LIMIT 1
|
|
390
|
+
`;
|
|
391
|
+
|
|
392
|
+
return rows[0]?.permission ?? "none";
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Lists concrete users reachable from auth.access entries.
|
|
397
|
+
*
|
|
398
|
+
* Apps stay responsible for collecting the relevant access entry IDs from their
|
|
399
|
+
* own junction tables. This helper expands direct user grants and recursive
|
|
400
|
+
* group grants only. It intentionally does not expand public or
|
|
401
|
+
* authenticated-only grants into "all users", because those scopes are not
|
|
402
|
+
* bounded, predictable assignee/member lists.
|
|
403
|
+
*/
|
|
404
|
+
export const listUsersWithAccess = async (params: {
|
|
405
|
+
accessIds: string[];
|
|
406
|
+
search?: string;
|
|
407
|
+
userIds?: string[];
|
|
408
|
+
excludeUserIds?: string[];
|
|
409
|
+
minimumPermission?: Exclude<PermissionLevel, "none">;
|
|
410
|
+
limit?: number;
|
|
411
|
+
db?: AccessDb;
|
|
412
|
+
}): Promise<AccessUser[]> => {
|
|
413
|
+
const accessIds = uniqueIds(params.accessIds);
|
|
414
|
+
if (accessIds.length === 0) return [];
|
|
415
|
+
|
|
416
|
+
const requestedUserIds = uniqueIds(params.userIds);
|
|
417
|
+
const excludeUserIds = uniqueIds(params.excludeUserIds);
|
|
418
|
+
const query = params.search?.trim().toLowerCase();
|
|
419
|
+
const pattern = query ? `%${escapeLikePattern(query)}%` : null;
|
|
420
|
+
const minimumRank = PERMISSION_RANK[params.minimumPermission ?? "read"];
|
|
421
|
+
const defaultLimit = requestedUserIds.length > 0 ? requestedUserIds.length : 20;
|
|
422
|
+
const limit = Math.min(Math.max(params.limit ?? defaultLimit, 1), 500);
|
|
423
|
+
const db = params.db ?? sql;
|
|
424
|
+
const userFilter = requestedUserIds.length > 0 ? db`AND id = ANY(${toPgUuidArray(requestedUserIds)}::uuid[])` : db``;
|
|
425
|
+
|
|
426
|
+
const rows = await db<DbAccessUser[]>`
|
|
427
|
+
WITH RECURSIVE
|
|
428
|
+
root_groups(root_group_id, root_group_name, group_id, group_ids, permission, permission_rank) AS (
|
|
429
|
+
SELECT
|
|
430
|
+
a.group_id,
|
|
431
|
+
COALESCE(NULLIF(g.name, ''), g.cn),
|
|
432
|
+
a.group_id,
|
|
433
|
+
ARRAY[a.group_id]::uuid[],
|
|
434
|
+
a.permission,
|
|
435
|
+
CASE a.permission
|
|
436
|
+
WHEN 'admin' THEN 4
|
|
437
|
+
WHEN 'write' THEN 3
|
|
438
|
+
WHEN 'read' THEN 2
|
|
439
|
+
ELSE 1
|
|
440
|
+
END
|
|
441
|
+
FROM auth.access a
|
|
442
|
+
JOIN auth.groups g ON g.id = a.group_id
|
|
443
|
+
WHERE a.id = ANY(${toPgUuidArray(accessIds)}::uuid[])
|
|
444
|
+
AND a.group_id IS NOT NULL
|
|
445
|
+
AND CASE a.permission
|
|
446
|
+
WHEN 'admin' THEN 4
|
|
447
|
+
WHEN 'write' THEN 3
|
|
448
|
+
WHEN 'read' THEN 2
|
|
449
|
+
ELSE 1
|
|
450
|
+
END >= ${minimumRank}
|
|
451
|
+
|
|
452
|
+
UNION ALL
|
|
453
|
+
|
|
454
|
+
SELECT
|
|
455
|
+
rg.root_group_id,
|
|
456
|
+
rg.root_group_name,
|
|
457
|
+
gg.child_group_id,
|
|
458
|
+
rg.group_ids || gg.child_group_id,
|
|
459
|
+
rg.permission,
|
|
460
|
+
rg.permission_rank
|
|
461
|
+
FROM auth.group_groups_v2 gg
|
|
462
|
+
JOIN root_groups rg ON rg.group_id = gg.parent_group_id
|
|
463
|
+
WHERE NOT gg.child_group_id = ANY(rg.group_ids)
|
|
464
|
+
),
|
|
465
|
+
candidate_users AS (
|
|
466
|
+
SELECT
|
|
467
|
+
u.id,
|
|
468
|
+
u.uid,
|
|
469
|
+
COALESCE(NULLIF(u.display_name, ''), u.uid, u.id::text) AS display_name,
|
|
470
|
+
u.avatar_hash,
|
|
471
|
+
TRUE AS direct,
|
|
472
|
+
NULL::uuid AS source_group_id,
|
|
473
|
+
NULL::text AS source_group_name,
|
|
474
|
+
a.permission,
|
|
475
|
+
CASE a.permission
|
|
476
|
+
WHEN 'admin' THEN 4
|
|
477
|
+
WHEN 'write' THEN 3
|
|
478
|
+
WHEN 'read' THEN 2
|
|
479
|
+
ELSE 1
|
|
480
|
+
END AS permission_rank
|
|
481
|
+
FROM auth.access a
|
|
482
|
+
JOIN auth.users u ON u.id = a.user_id
|
|
483
|
+
WHERE a.id = ANY(${toPgUuidArray(accessIds)}::uuid[])
|
|
484
|
+
AND a.user_id IS NOT NULL
|
|
485
|
+
AND CASE a.permission
|
|
486
|
+
WHEN 'admin' THEN 4
|
|
487
|
+
WHEN 'write' THEN 3
|
|
488
|
+
WHEN 'read' THEN 2
|
|
489
|
+
ELSE 1
|
|
490
|
+
END >= ${minimumRank}
|
|
491
|
+
|
|
492
|
+
UNION ALL
|
|
493
|
+
|
|
494
|
+
SELECT
|
|
495
|
+
u.id,
|
|
496
|
+
u.uid,
|
|
497
|
+
COALESCE(NULLIF(u.display_name, ''), u.uid, u.id::text) AS display_name,
|
|
498
|
+
u.avatar_hash,
|
|
499
|
+
FALSE AS direct,
|
|
500
|
+
rg.root_group_id AS source_group_id,
|
|
501
|
+
rg.root_group_name AS source_group_name,
|
|
502
|
+
rg.permission,
|
|
503
|
+
rg.permission_rank
|
|
504
|
+
FROM root_groups rg
|
|
505
|
+
JOIN auth.user_groups_v2 ug ON ug.group_id = rg.group_id
|
|
506
|
+
JOIN auth.users u ON u.id = ug.user_id
|
|
507
|
+
),
|
|
508
|
+
access_users AS (
|
|
509
|
+
SELECT
|
|
510
|
+
id,
|
|
511
|
+
uid,
|
|
512
|
+
display_name,
|
|
513
|
+
avatar_hash,
|
|
514
|
+
CASE MAX(permission_rank)
|
|
515
|
+
WHEN 4 THEN 'admin'
|
|
516
|
+
WHEN 3 THEN 'write'
|
|
517
|
+
ELSE 'read'
|
|
518
|
+
END AS permission,
|
|
519
|
+
BOOL_OR(direct) AS direct,
|
|
520
|
+
(
|
|
521
|
+
ARRAY_AGG(source_group_id ORDER BY permission_rank DESC, source_group_name)
|
|
522
|
+
FILTER (WHERE NOT direct AND source_group_id IS NOT NULL)
|
|
523
|
+
)[1] AS source_group_id,
|
|
524
|
+
(
|
|
525
|
+
ARRAY_AGG(source_group_name ORDER BY permission_rank DESC, source_group_name)
|
|
526
|
+
FILTER (WHERE NOT direct AND source_group_name IS NOT NULL)
|
|
527
|
+
)[1] AS source_group_name,
|
|
528
|
+
COALESCE(
|
|
529
|
+
STRING_AGG(source_group_name, ' ')
|
|
530
|
+
FILTER (WHERE NOT direct AND source_group_name IS NOT NULL),
|
|
531
|
+
''
|
|
532
|
+
) AS group_names
|
|
533
|
+
FROM candidate_users
|
|
534
|
+
GROUP BY id, uid, display_name, avatar_hash
|
|
535
|
+
)
|
|
536
|
+
SELECT id, uid, display_name, avatar_hash, permission, direct, source_group_id, source_group_name
|
|
537
|
+
FROM access_users
|
|
538
|
+
WHERE id <> ALL(${toPgUuidArray(excludeUserIds)}::uuid[])
|
|
539
|
+
AND (direct OR (source_group_id IS NOT NULL AND source_group_name IS NOT NULL))
|
|
540
|
+
${userFilter}
|
|
541
|
+
AND (
|
|
542
|
+
${pattern}::text IS NULL
|
|
543
|
+
OR LOWER(display_name) LIKE ${pattern} ESCAPE '\\'
|
|
544
|
+
OR LOWER(uid) LIKE ${pattern} ESCAPE '\\'
|
|
545
|
+
OR LOWER(group_names) LIKE ${pattern} ESCAPE '\\'
|
|
546
|
+
)
|
|
547
|
+
ORDER BY LOWER(display_name), id
|
|
548
|
+
LIMIT ${limit}
|
|
549
|
+
`;
|
|
550
|
+
|
|
551
|
+
return rows.map((row) => ({
|
|
552
|
+
id: row.id,
|
|
553
|
+
uid: row.uid,
|
|
554
|
+
displayName: row.display_name,
|
|
555
|
+
avatarHash: row.avatar_hash,
|
|
556
|
+
permission: row.permission,
|
|
557
|
+
source: row.direct
|
|
558
|
+
? { type: "direct" }
|
|
559
|
+
: {
|
|
560
|
+
type: "group",
|
|
561
|
+
groupId: row.source_group_id as string,
|
|
562
|
+
groupName: row.source_group_name as string,
|
|
563
|
+
},
|
|
564
|
+
}));
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Resolve display names for access entries.
|
|
569
|
+
* Populates the displayName field based on principal type.
|
|
570
|
+
*/
|
|
571
|
+
export const resolveDisplayNames = async (entries: AccessEntry[]): Promise<AccessEntry[]> => {
|
|
572
|
+
const userIds = entries.filter((e) => e.principal.type === "user").map((e) => (e.principal as { type: "user"; userId: string }).userId);
|
|
573
|
+
|
|
574
|
+
const groupIds = entries
|
|
575
|
+
.filter((e) => e.principal.type === "group")
|
|
576
|
+
.map((e) => (e.principal as { type: "group"; groupId: string }).groupId);
|
|
577
|
+
|
|
578
|
+
const serviceAccountIds = entries
|
|
579
|
+
.filter((e) => e.principal.type === "service_account")
|
|
580
|
+
.map((e) => (e.principal as { type: "service_account"; serviceAccountId: string }).serviceAccountId);
|
|
581
|
+
|
|
582
|
+
// Fetch user display names
|
|
583
|
+
const userNames = new Map<string, { displayName: string; avatarHash: string | null }>();
|
|
584
|
+
if (userIds.length > 0) {
|
|
585
|
+
const users = await sql<{ id: string; display_name: string; uid: string; avatar_hash: string | null }[]>`
|
|
586
|
+
SELECT id, display_name, uid, avatar_hash
|
|
587
|
+
FROM auth.users
|
|
588
|
+
WHERE id = ANY(${toPgUuidArray(userIds)}::uuid[])
|
|
589
|
+
`;
|
|
590
|
+
for (const u of users) {
|
|
591
|
+
userNames.set(u.id, { displayName: u.display_name || u.uid, avatarHash: u.avatar_hash });
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const groupNames = new Map<string, string>();
|
|
596
|
+
if (groupIds.length > 0) {
|
|
597
|
+
const groups = await sql<{ id: string; name: string }[]>`
|
|
598
|
+
SELECT id, name
|
|
599
|
+
FROM auth.groups
|
|
600
|
+
WHERE id = ANY(${toPgUuidArray(groupIds)}::uuid[])
|
|
601
|
+
`;
|
|
602
|
+
for (const group of groups) {
|
|
603
|
+
groupNames.set(group.id, group.name);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const serviceAccountNames = new Map<string, string>();
|
|
608
|
+
if (serviceAccountIds.length > 0) {
|
|
609
|
+
const serviceAccounts = await sql<{ id: string; name: string }[]>`
|
|
610
|
+
SELECT id, name
|
|
611
|
+
FROM auth.service_accounts
|
|
612
|
+
WHERE id = ANY(${toPgUuidArray(serviceAccountIds)}::uuid[])
|
|
613
|
+
`;
|
|
614
|
+
for (const serviceAccount of serviceAccounts) {
|
|
615
|
+
serviceAccountNames.set(serviceAccount.id, serviceAccount.name);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
return entries.map((entry) => {
|
|
620
|
+
let displayName: string;
|
|
621
|
+
let avatarHash: string | null | undefined;
|
|
622
|
+
switch (entry.principal.type) {
|
|
623
|
+
case "user": {
|
|
624
|
+
const user = userNames.get(entry.principal.userId);
|
|
625
|
+
displayName = user?.displayName ?? "Unknown User";
|
|
626
|
+
avatarHash = user?.avatarHash ?? null;
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
case "group":
|
|
630
|
+
displayName = groupNames.get(entry.principal.groupId) ?? "Unknown Group";
|
|
631
|
+
break;
|
|
632
|
+
case "service_account":
|
|
633
|
+
displayName = serviceAccountNames.get(entry.principal.serviceAccountId) ?? "Unknown Service Account";
|
|
634
|
+
break;
|
|
635
|
+
case "authenticated":
|
|
636
|
+
displayName = "All users (incl. guests)";
|
|
637
|
+
break;
|
|
638
|
+
case "public":
|
|
639
|
+
displayName = "Public";
|
|
640
|
+
break;
|
|
641
|
+
}
|
|
642
|
+
return { ...entry, displayName, avatarHash };
|
|
643
|
+
});
|
|
644
|
+
};
|