@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,386 @@
|
|
|
1
|
+
import { sql } from "bun";
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_LINUX_IDENTITY_CONFIGURATION,
|
|
4
|
+
isPosixName,
|
|
5
|
+
LinuxIdentityConfigurationSchema,
|
|
6
|
+
type LinuxIdentityConfiguration,
|
|
7
|
+
type PosixIdentity,
|
|
8
|
+
PosixOverridesSchema,
|
|
9
|
+
} from "../../contracts/posix";
|
|
10
|
+
import { freeipa } from "../../server/services";
|
|
11
|
+
import { getFreeIpaConfig } from "../freeipa-config";
|
|
12
|
+
import { readCompleteIpaList } from "../ipa/sync-planning";
|
|
13
|
+
import { decryptValue } from "../settings/crypto";
|
|
14
|
+
import * as settings from "../settings";
|
|
15
|
+
import { audit, type AuditActor } from "../audit";
|
|
16
|
+
import type { MutationResult } from "../../contracts/shared";
|
|
17
|
+
|
|
18
|
+
const CONFIG_KEY = "linux.identity_config";
|
|
19
|
+
export const POSIX_PAGE_SIZE = 50;
|
|
20
|
+
type Actor = { id: string; roles: readonly string[] };
|
|
21
|
+
export class PosixError extends Error {
|
|
22
|
+
constructor(
|
|
23
|
+
public readonly code: string,
|
|
24
|
+
public readonly status: 400 | 403 | 404 | 409 = 409,
|
|
25
|
+
) {
|
|
26
|
+
super(code);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const requireAdmin = (actor: Actor): void => {
|
|
30
|
+
if (!actor.roles.includes("admin")) throw new PosixError("admin_required", 403);
|
|
31
|
+
};
|
|
32
|
+
export type IpaIdRange = { start: number; end: number };
|
|
33
|
+
|
|
34
|
+
/** A fresh directory inventory is mandatory before reserving a local namespace. */
|
|
35
|
+
export const readIpaIdRanges = async (): Promise<IpaIdRange[]> => {
|
|
36
|
+
const config = await getFreeIpaConfig();
|
|
37
|
+
if (!config.configured) {
|
|
38
|
+
if (config.enabled) throw new PosixError("ipa_inventory_unavailable");
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const ipaSession = await freeipa.session.getServiceSession({
|
|
43
|
+
url: config.url,
|
|
44
|
+
serviceUser: config.serviceUser,
|
|
45
|
+
servicePassword: config.servicePassword,
|
|
46
|
+
});
|
|
47
|
+
const response = await freeipa.client.call({
|
|
48
|
+
url: config.url,
|
|
49
|
+
ipaSession,
|
|
50
|
+
method: "idrange_find",
|
|
51
|
+
args: [],
|
|
52
|
+
options: { sizelimit: 0, all: true },
|
|
53
|
+
});
|
|
54
|
+
const records = readCompleteIpaList({ response, entity: "ID ranges" });
|
|
55
|
+
if (records.length === 0) throw new Error("Missing ranges");
|
|
56
|
+
return records.map((record) => {
|
|
57
|
+
const start = freeipa.util.num(record.ipabaseid);
|
|
58
|
+
const size = freeipa.util.num(record.ipaidrangesize);
|
|
59
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(size) || start === null || size === null || start < 1 || size < 1)
|
|
60
|
+
throw new Error("Invalid range");
|
|
61
|
+
return { start, end: start + size - 1 };
|
|
62
|
+
});
|
|
63
|
+
} catch {
|
|
64
|
+
throw new PosixError("ipa_inventory_unavailable");
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export type PosixCandidate = {
|
|
69
|
+
id: string;
|
|
70
|
+
uid: string;
|
|
71
|
+
displayName: string;
|
|
72
|
+
provider: "local" | "ipa";
|
|
73
|
+
profile: "user" | "guest";
|
|
74
|
+
identity: PosixIdentity | null;
|
|
75
|
+
state: "ready" | "prepared" | "guest" | "invalid_name" | "group_conflict" | "ipa_pending" | "provider_changed" | "identity_conflict";
|
|
76
|
+
};
|
|
77
|
+
type IdentityRow = {
|
|
78
|
+
id: string;
|
|
79
|
+
uid: string;
|
|
80
|
+
display_name: string;
|
|
81
|
+
provider: "local" | "ipa";
|
|
82
|
+
profile: "user" | "guest";
|
|
83
|
+
managed_by: "local" | "ipa" | null;
|
|
84
|
+
uid_number: number | null;
|
|
85
|
+
primary_gid_number: number | null;
|
|
86
|
+
home_directory: string | null;
|
|
87
|
+
login_shell: string | null;
|
|
88
|
+
group_conflict: boolean;
|
|
89
|
+
identity_conflict: boolean;
|
|
90
|
+
};
|
|
91
|
+
const candidate = (row: IdentityRow): PosixCandidate => {
|
|
92
|
+
const identity = row.managed_by
|
|
93
|
+
? {
|
|
94
|
+
userId: row.id,
|
|
95
|
+
managedBy: row.managed_by,
|
|
96
|
+
uidNumber: row.uid_number,
|
|
97
|
+
primaryGidNumber: row.primary_gid_number,
|
|
98
|
+
homeDirectory: row.home_directory,
|
|
99
|
+
loginShell: row.login_shell,
|
|
100
|
+
}
|
|
101
|
+
: null;
|
|
102
|
+
const state =
|
|
103
|
+
identity && identity.managedBy !== row.provider
|
|
104
|
+
? "provider_changed"
|
|
105
|
+
: row.identity_conflict ||
|
|
106
|
+
(identity &&
|
|
107
|
+
((identity.uidNumber !== null && identity.uidNumber <= 0) ||
|
|
108
|
+
(identity.primaryGidNumber !== null && identity.primaryGidNumber <= 0)))
|
|
109
|
+
? "identity_conflict"
|
|
110
|
+
: row.provider === "ipa" &&
|
|
111
|
+
(!identity ||
|
|
112
|
+
identity.uidNumber === null ||
|
|
113
|
+
identity.primaryGidNumber === null ||
|
|
114
|
+
!identity.homeDirectory ||
|
|
115
|
+
!identity.loginShell)
|
|
116
|
+
? "ipa_pending"
|
|
117
|
+
: row.provider === "local" && row.profile === "guest"
|
|
118
|
+
? "guest"
|
|
119
|
+
: identity
|
|
120
|
+
? "prepared"
|
|
121
|
+
: !isPosixName(row.uid)
|
|
122
|
+
? "invalid_name"
|
|
123
|
+
: row.group_conflict
|
|
124
|
+
? "group_conflict"
|
|
125
|
+
: "ready";
|
|
126
|
+
return { id: row.id, uid: row.uid, displayName: row.display_name, provider: row.provider, profile: row.profile, identity, state };
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Database injection is used by isolated integration tests; production uses the shared pool.
|
|
130
|
+
export const createPosixRuntime = (db: typeof sql = sql, readRanges = readIpaIdRanges) => {
|
|
131
|
+
const readConfig = async (connection = db): Promise<LinuxIdentityConfiguration> => {
|
|
132
|
+
// Provisioning must not use a stale cache after an operator disables or changes the range.
|
|
133
|
+
const [row] = await connection<{ value: string }[]>`SELECT value FROM settings.entries WHERE key = ${CONFIG_KEY}`;
|
|
134
|
+
const raw = row ? await decryptValue(row.value) : JSON.stringify(DEFAULT_LINUX_IDENTITY_CONFIGURATION);
|
|
135
|
+
try {
|
|
136
|
+
return LinuxIdentityConfigurationSchema.parse(typeof raw === "string" ? JSON.parse(raw) : raw);
|
|
137
|
+
} catch {
|
|
138
|
+
throw new PosixError("invalid_configuration", 400);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const lock = async (connection: typeof sql): Promise<void> => {
|
|
142
|
+
// Also serialize against existing provider writers, not just this allocator.
|
|
143
|
+
await connection`LOCK TABLE auth.users, auth.user_ipa_data, auth.groups, auth.user_posix, auth.posix_allocations IN SHARE ROW EXCLUSIVE MODE`;
|
|
144
|
+
await connection`LOCK TABLE settings.entries IN SHARE MODE`;
|
|
145
|
+
};
|
|
146
|
+
const checkRange = async (connection: typeof sql, config: LinuxIdentityConfiguration, ranges: IpaIdRange[]) => {
|
|
147
|
+
if (ranges.length === 0) {
|
|
148
|
+
const [ipaState] = await connection`SELECT 1 FROM auth.users WHERE provider = 'ipa'
|
|
149
|
+
UNION ALL SELECT 1 FROM auth.groups WHERE provider = 'ipa'
|
|
150
|
+
UNION ALL SELECT 1 FROM auth.user_posix WHERE managed_by = 'ipa' LIMIT 1`;
|
|
151
|
+
if (ipaState) throw new PosixError("ipa_inventory_unavailable");
|
|
152
|
+
}
|
|
153
|
+
if (ranges.some((range) => config.rangeStart <= range.end && config.rangeEnd >= range.start))
|
|
154
|
+
throw new PosixError("ipa_range_conflict");
|
|
155
|
+
const [conflict] = await connection`
|
|
156
|
+
SELECT 1 FROM auth.user_ipa_data WHERE uid_number BETWEEN ${config.rangeStart} AND ${config.rangeEnd}
|
|
157
|
+
UNION ALL SELECT 1 FROM auth.user_posix WHERE managed_by = 'ipa' AND
|
|
158
|
+
(uid_number BETWEEN ${config.rangeStart} AND ${config.rangeEnd} OR primary_gid_number BETWEEN ${config.rangeStart} AND ${config.rangeEnd})
|
|
159
|
+
UNION ALL SELECT 1 FROM auth.groups WHERE provider = 'ipa' AND gid_number BETWEEN ${config.rangeStart} AND ${config.rangeEnd}
|
|
160
|
+
LIMIT 1
|
|
161
|
+
`;
|
|
162
|
+
if (conflict) throw new PosixError("ipa_range_conflict");
|
|
163
|
+
};
|
|
164
|
+
const rows = (
|
|
165
|
+
connection: typeof sql,
|
|
166
|
+
id: string | null,
|
|
167
|
+
after: string | null,
|
|
168
|
+
filters: { search?: string; scope?: "ready" | "all" } = {},
|
|
169
|
+
) => connection<IdentityRow[]>`
|
|
170
|
+
WITH candidates AS (
|
|
171
|
+
SELECT u.id, u.uid, u.display_name, u.provider, u.profile,
|
|
172
|
+
COALESCE(p.managed_by, CASE WHEN u.provider = 'ipa' AND i.user_id IS NOT NULL THEN 'ipa' END) AS managed_by,
|
|
173
|
+
CASE WHEN p.user_id IS NOT NULL THEN p.uid_number WHEN u.provider = 'ipa' THEN i.uid_number END AS uid_number,
|
|
174
|
+
p.primary_gid_number, p.home_directory, p.login_shell,
|
|
175
|
+
EXISTS(SELECT 1 FROM auth.groups g WHERE lower(g.name) = lower(u.uid)) AS group_conflict,
|
|
176
|
+
(EXISTS(SELECT 1 FROM auth.users other WHERE other.id <> u.id AND lower(other.uid) = lower(u.uid))
|
|
177
|
+
OR EXISTS(SELECT 1 FROM auth.user_posix other WHERE other.user_id <> u.id AND other.uid_number = COALESCE(p.uid_number, i.uid_number))
|
|
178
|
+
OR EXISTS(SELECT 1 FROM auth.user_ipa_data other WHERE other.user_id <> u.id AND other.uid_number = COALESCE(p.uid_number, i.uid_number))
|
|
179
|
+
OR EXISTS(SELECT gid_number FROM auth.groups WHERE gid_number = p.primary_gid_number GROUP BY gid_number HAVING count(*) > 1)) AS identity_conflict
|
|
180
|
+
FROM auth.users u LEFT JOIN auth.user_posix p ON p.user_id = u.id
|
|
181
|
+
LEFT JOIN auth.user_ipa_data i ON i.user_id = u.id
|
|
182
|
+
WHERE (${id}::uuid IS NULL OR u.id = ${id}::uuid) AND (${after}::uuid IS NULL OR u.id > ${after}::uuid)
|
|
183
|
+
AND strpos(lower(u.uid), lower(${filters.search ?? ""})) > 0
|
|
184
|
+
)
|
|
185
|
+
SELECT * FROM candidates
|
|
186
|
+
WHERE (${filters.scope === "ready"} = false OR (
|
|
187
|
+
provider = 'local' AND profile = 'user' AND managed_by IS NULL
|
|
188
|
+
AND uid ~ '^[a-z_][a-z0-9_-]{0,31}$' AND NOT group_conflict AND NOT identity_conflict
|
|
189
|
+
))
|
|
190
|
+
ORDER BY id LIMIT ${id ? 1 : POSIX_PAGE_SIZE + 1}
|
|
191
|
+
`;
|
|
192
|
+
const allocate = async (
|
|
193
|
+
connection: typeof sql,
|
|
194
|
+
kind: "uid" | "gid",
|
|
195
|
+
ownerId: string,
|
|
196
|
+
config: LinuxIdentityConfiguration,
|
|
197
|
+
): Promise<number> => {
|
|
198
|
+
// The high-water mark survives deletion and range changes. No generate_series over a potentially huge range.
|
|
199
|
+
const [row] = await connection<{ number: number | null }[]>`
|
|
200
|
+
SELECT GREATEST(${config.rangeStart}::bigint, COALESCE(MAX(number)::bigint + 1, ${config.rangeStart}::bigint)) AS number
|
|
201
|
+
FROM auth.posix_allocations WHERE kind = ${kind} AND number BETWEEN ${config.rangeStart} AND ${config.rangeEnd}
|
|
202
|
+
`;
|
|
203
|
+
let number = Number(row?.number);
|
|
204
|
+
// Skip pre-existing identities without allocating/reusing their numbers.
|
|
205
|
+
const [occupied] =
|
|
206
|
+
kind === "uid"
|
|
207
|
+
? await connection<
|
|
208
|
+
{ number: number | null }[]
|
|
209
|
+
>`SELECT MAX(number) AS number FROM (SELECT uid_number AS number FROM auth.user_posix WHERE uid_number BETWEEN ${number} AND ${config.rangeEnd} UNION ALL SELECT uid_number FROM auth.user_ipa_data WHERE uid_number BETWEEN ${number} AND ${config.rangeEnd}) occupied`
|
|
210
|
+
: await connection<
|
|
211
|
+
{ number: number | null }[]
|
|
212
|
+
>`SELECT MAX(number) AS number FROM (SELECT gid_number AS number FROM auth.groups WHERE gid_number BETWEEN ${number} AND ${config.rangeEnd} UNION ALL SELECT primary_gid_number FROM auth.user_posix WHERE primary_gid_number BETWEEN ${number} AND ${config.rangeEnd}) occupied`;
|
|
213
|
+
if (occupied?.number != null) number = occupied.number + 1;
|
|
214
|
+
if (!Number.isSafeInteger(number) || number > config.rangeEnd) throw new PosixError("range_exhausted");
|
|
215
|
+
await connection`INSERT INTO auth.posix_allocations(kind, number, owner_id) VALUES (${kind}, ${number}, ${ownerId}::uuid)`;
|
|
216
|
+
return number;
|
|
217
|
+
};
|
|
218
|
+
const assign = async (tx: typeof sql, id: string, config: LinuxIdentityConfiguration, actor?: AuditActor) => {
|
|
219
|
+
const [row] = await rows(tx, id, null);
|
|
220
|
+
if (!row) throw new PosixError("user_not_found", 404);
|
|
221
|
+
const current = candidate(row);
|
|
222
|
+
if (current.state === "prepared" && row.provider === "local" && row.profile === "user") return current;
|
|
223
|
+
if (current.state !== "ready") throw new PosixError(current.state);
|
|
224
|
+
await checkRange(tx, config, await readRanges());
|
|
225
|
+
const paths = PosixOverridesSchema.safeParse({
|
|
226
|
+
homeDirectory: config.homeTemplate.replaceAll("{username}", row.uid),
|
|
227
|
+
loginShell: config.loginShell,
|
|
228
|
+
});
|
|
229
|
+
if (!paths.success) throw new PosixError("invalid_paths", 400);
|
|
230
|
+
const uidNumber = await allocate(tx, "uid", id, config);
|
|
231
|
+
const groupId = crypto.randomUUID();
|
|
232
|
+
const gidNumber = await allocate(tx, "gid", groupId, config);
|
|
233
|
+
await tx`INSERT INTO auth.groups(id, cn, provider, name, gid_number) VALUES (${groupId}::uuid, ${`local:${row.uid}`}, 'local', ${row.uid}, ${gidNumber})`;
|
|
234
|
+
await tx`INSERT INTO auth.user_groups_v2(user_id, group_id) VALUES (${id}::uuid, ${groupId}::uuid)`;
|
|
235
|
+
await tx`INSERT INTO auth.user_posix(user_id, managed_by, uid_number, primary_gid_number, primary_group_id, home_directory, login_shell)
|
|
236
|
+
VALUES (${id}::uuid, 'local', ${uidNumber}, ${gidNumber}, ${groupId}::uuid, ${paths.data.homeDirectory}, ${paths.data.loginShell})`;
|
|
237
|
+
await audit.record(
|
|
238
|
+
{
|
|
239
|
+
action: "accounts.linux.provision",
|
|
240
|
+
outcome: "allowed",
|
|
241
|
+
actor,
|
|
242
|
+
target: { type: "user", id },
|
|
243
|
+
metadata: { uidNumber, gidNumber, primaryGroupId: groupId },
|
|
244
|
+
},
|
|
245
|
+
tx,
|
|
246
|
+
);
|
|
247
|
+
const [created] = await rows(tx, id, null);
|
|
248
|
+
return candidate(created!);
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// Internal lifecycle boundary, not part of the public administrator service.
|
|
252
|
+
// Lock before the account write: upgrading a row-write lock afterwards can deadlock.
|
|
253
|
+
const writeLocalAccount = async (
|
|
254
|
+
write: (tx: typeof sql) => Promise<MutationResult<{ id: string; assignIdentity?: boolean }>>,
|
|
255
|
+
actor?: AuditActor,
|
|
256
|
+
): Promise<MutationResult<{ id: string }>> => {
|
|
257
|
+
try {
|
|
258
|
+
return await db.begin(async (tx) => {
|
|
259
|
+
await lock(tx);
|
|
260
|
+
const result = await write(tx);
|
|
261
|
+
if (!result.ok) return result;
|
|
262
|
+
const [user] = await tx<
|
|
263
|
+
{ provider: string; profile: string }[]
|
|
264
|
+
>`SELECT provider, profile FROM auth.users WHERE id = ${result.data.id}::uuid`;
|
|
265
|
+
if (result.data.assignIdentity !== false && user?.provider === "local" && user.profile === "user") {
|
|
266
|
+
const config = await readConfig(tx);
|
|
267
|
+
if (config.enabled) await assign(tx, result.data.id, config, actor);
|
|
268
|
+
}
|
|
269
|
+
return { ok: true, data: { id: result.data.id } };
|
|
270
|
+
});
|
|
271
|
+
} catch (error) {
|
|
272
|
+
if (error instanceof PosixError) return { ok: false, error: `Linux identity assignment failed: ${error.code}`, status: error.status };
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const service = {
|
|
278
|
+
async configuration(actor: Actor) {
|
|
279
|
+
requireAdmin(actor);
|
|
280
|
+
return readConfig();
|
|
281
|
+
},
|
|
282
|
+
async overview(actor: Actor, after: string | null = null, filters: { search?: string; scope?: "ready" | "all" } = {}) {
|
|
283
|
+
requireAdmin(actor);
|
|
284
|
+
const result = await rows(db, null, after, filters);
|
|
285
|
+
const items = result.slice(0, POSIX_PAGE_SIZE).map(candidate);
|
|
286
|
+
return { config: await readConfig(), items, nextCursor: result.length > POSIX_PAGE_SIZE ? items.at(-1)!.id : null };
|
|
287
|
+
},
|
|
288
|
+
async get(actor: Actor, id: string) {
|
|
289
|
+
requireAdmin(actor);
|
|
290
|
+
const [row] = await rows(db, id, null);
|
|
291
|
+
if (!row) throw new PosixError("user_not_found", 404);
|
|
292
|
+
return { config: await readConfig(), user: candidate(row) };
|
|
293
|
+
},
|
|
294
|
+
async configure(actor: Actor, input: LinuxIdentityConfiguration) {
|
|
295
|
+
requireAdmin(actor);
|
|
296
|
+
const parsed = LinuxIdentityConfigurationSchema.safeParse(input);
|
|
297
|
+
if (!parsed.success) throw new PosixError("invalid_configuration", 400);
|
|
298
|
+
const ranges = parsed.data.enabled ? await readRanges() : [];
|
|
299
|
+
await db.begin(async (tx) => {
|
|
300
|
+
// Same order as provisioning; settings write upgrades this lock in the same transaction.
|
|
301
|
+
await lock(tx);
|
|
302
|
+
if (parsed.data.enabled) await checkRange(tx, parsed.data, ranges);
|
|
303
|
+
await settings.set(CONFIG_KEY, JSON.stringify(parsed.data), tx);
|
|
304
|
+
await audit.record(
|
|
305
|
+
{
|
|
306
|
+
action: "accounts.linux.configure",
|
|
307
|
+
outcome: "allowed",
|
|
308
|
+
actor: { userId: actor.id, roles: actor.roles },
|
|
309
|
+
metadata: parsed.data,
|
|
310
|
+
},
|
|
311
|
+
tx,
|
|
312
|
+
);
|
|
313
|
+
});
|
|
314
|
+
await settings.invalidateSettingsCache([CONFIG_KEY]);
|
|
315
|
+
return parsed.data;
|
|
316
|
+
},
|
|
317
|
+
async provision(actor: Actor, id: string) {
|
|
318
|
+
requireAdmin(actor);
|
|
319
|
+
return db.begin(async (tx) => {
|
|
320
|
+
await lock(tx);
|
|
321
|
+
const config = await readConfig(tx);
|
|
322
|
+
if (!config.enabled) throw new PosixError("setup_disabled");
|
|
323
|
+
return assign(tx, id, config, { userId: actor.id, roles: actor.roles });
|
|
324
|
+
});
|
|
325
|
+
},
|
|
326
|
+
async update(actor: Actor, id: string, input: { homeDirectory: string; loginShell: string }) {
|
|
327
|
+
requireAdmin(actor);
|
|
328
|
+
const parsed = PosixOverridesSchema.safeParse(input);
|
|
329
|
+
if (!parsed.success) throw new PosixError("invalid_paths", 400);
|
|
330
|
+
return db.begin(async (tx) => {
|
|
331
|
+
await lock(tx);
|
|
332
|
+
const [row] = await rows(tx, id, null);
|
|
333
|
+
if (!row) throw new PosixError("user_not_found", 404);
|
|
334
|
+
if (row.provider !== "local" || row.managed_by !== "local" || row.profile !== "user")
|
|
335
|
+
throw new PosixError("identity_not_locally_managed");
|
|
336
|
+
await tx`UPDATE auth.user_posix SET home_directory = ${parsed.data.homeDirectory}, login_shell = ${parsed.data.loginShell} WHERE user_id = ${id}::uuid`;
|
|
337
|
+
await audit.record(
|
|
338
|
+
{
|
|
339
|
+
action: "accounts.linux.update",
|
|
340
|
+
outcome: "allowed",
|
|
341
|
+
actor: { userId: actor.id, roles: actor.roles },
|
|
342
|
+
target: { type: "user", id },
|
|
343
|
+
metadata: { before: { homeDirectory: row.home_directory, loginShell: row.login_shell }, after: parsed.data },
|
|
344
|
+
},
|
|
345
|
+
tx,
|
|
346
|
+
);
|
|
347
|
+
return { ...candidate(row), identity: { ...candidate(row).identity!, ...parsed.data } };
|
|
348
|
+
});
|
|
349
|
+
},
|
|
350
|
+
async provisionGroup(actor: Actor, id: string) {
|
|
351
|
+
requireAdmin(actor);
|
|
352
|
+
const ranges = await readRanges();
|
|
353
|
+
return db.begin(async (tx) => {
|
|
354
|
+
await lock(tx);
|
|
355
|
+
const config = await readConfig(tx);
|
|
356
|
+
if (!config.enabled) throw new PosixError("setup_disabled");
|
|
357
|
+
const [group] = await tx<
|
|
358
|
+
{ id: string; name: string; provider: string; gid_number: number | null }[]
|
|
359
|
+
>`SELECT id, name, provider, gid_number FROM auth.groups WHERE id = ${id}::uuid`;
|
|
360
|
+
if (!group) throw new PosixError("group_not_found", 404);
|
|
361
|
+
if (group.provider !== "local") throw new PosixError("identity_not_locally_managed");
|
|
362
|
+
if (group.gid_number !== null) return { gidNumber: group.gid_number };
|
|
363
|
+
if (!isPosixName(group.name)) throw new PosixError("invalid_name");
|
|
364
|
+
const [conflict] = await tx`SELECT 1 FROM auth.groups WHERE id <> ${id}::uuid AND lower(name) = lower(${group.name}) LIMIT 1`;
|
|
365
|
+
if (conflict) throw new PosixError("group_conflict");
|
|
366
|
+
await checkRange(tx, config, ranges);
|
|
367
|
+
const gidNumber = await allocate(tx, "gid", id, config);
|
|
368
|
+
await tx`UPDATE auth.groups SET gid_number = ${gidNumber} WHERE id = ${id}::uuid`;
|
|
369
|
+
await audit.record(
|
|
370
|
+
{
|
|
371
|
+
action: "accounts.linux.provision_group",
|
|
372
|
+
outcome: "allowed",
|
|
373
|
+
actor: { userId: actor.id, roles: actor.roles },
|
|
374
|
+
target: { type: "group", id },
|
|
375
|
+
metadata: { gidNumber },
|
|
376
|
+
},
|
|
377
|
+
tx,
|
|
378
|
+
);
|
|
379
|
+
return { gidNumber };
|
|
380
|
+
});
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
return { service, writeLocalAccount };
|
|
384
|
+
};
|
|
385
|
+
export const createPosixService = (db: typeof sql = sql, readRanges = readIpaIdRanges) => createPosixRuntime(db, readRanges).service;
|
|
386
|
+
export const { service: posix, writeLocalAccount } = createPosixRuntime();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { sql } from "bun";
|
|
2
|
+
import { decryptValue } from "../settings/crypto";
|
|
3
|
+
|
|
4
|
+
/** Read durable policy so disabling new requests is not delayed by the settings cache. */
|
|
5
|
+
export const accountRequestsEnabled = async (db: typeof sql = sql): Promise<boolean> => {
|
|
6
|
+
const [row] = await db<{ value: string }[]>`SELECT value FROM settings.entries WHERE key = 'user.account_requests.enabled'`;
|
|
7
|
+
if (!row) return false;
|
|
8
|
+
const value: unknown = await decryptValue(row.value);
|
|
9
|
+
if (typeof value !== "boolean") throw new Error("Invalid account request policy");
|
|
10
|
+
return value;
|
|
11
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { sql } from "bun";
|
|
2
|
+
import type { UserProfile, UserProvider } from "../../contracts/shared";
|
|
3
|
+
import * as settings from "../settings";
|
|
4
|
+
import type { IpaAccountTransitionPolicy } from "./model";
|
|
5
|
+
|
|
6
|
+
type SqlExecutor = typeof sql;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Remove provider-scoped group and manager relations for a user.
|
|
10
|
+
* Local group memberships are intentionally preserved when switching providers.
|
|
11
|
+
*/
|
|
12
|
+
export const clearUserRelationsForProvider = async (params: {
|
|
13
|
+
userId: string;
|
|
14
|
+
provider: UserProvider;
|
|
15
|
+
db?: SqlExecutor;
|
|
16
|
+
}): Promise<void> => {
|
|
17
|
+
const db = params.db ?? sql;
|
|
18
|
+
|
|
19
|
+
await db`
|
|
20
|
+
DELETE FROM auth.user_groups_v2 ug
|
|
21
|
+
USING auth.groups g
|
|
22
|
+
WHERE ug.group_id = g.id
|
|
23
|
+
AND ug.user_id = ${params.userId}
|
|
24
|
+
AND g.provider = ${params.provider}
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
await db`
|
|
28
|
+
DELETE FROM auth.group_manager_users_v2 gmu
|
|
29
|
+
USING auth.groups g
|
|
30
|
+
WHERE gmu.group_id = g.id
|
|
31
|
+
AND gmu.user_id = ${params.userId}
|
|
32
|
+
AND g.provider = ${params.provider}
|
|
33
|
+
`;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const getLocalExpiryDays = async (profile: UserProfile): Promise<number> => {
|
|
37
|
+
if (profile === "guest") {
|
|
38
|
+
const configured = await settings.get<number | null>("user.account.local_guest_expires_days");
|
|
39
|
+
return typeof configured === "number" ? configured : 365;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const configured = await settings.get<number | null>("user.account.local_user_expires_days");
|
|
43
|
+
return typeof configured === "number" ? configured : 0;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const resolveDefaultLocalAccountExpiry = async (profile: UserProfile): Promise<Date | null> => {
|
|
47
|
+
const days = await getLocalExpiryDays(profile);
|
|
48
|
+
if (days <= 0) return null;
|
|
49
|
+
return new Date(Date.now() + days * 24 * 60 * 60 * 1000);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const resolveIpaTransitionTarget = async (params: {
|
|
53
|
+
currentProfile: UserProfile;
|
|
54
|
+
policy: Exclude<IpaAccountTransitionPolicy, "delete">;
|
|
55
|
+
}): Promise<{ targetProfile: UserProfile; accountExpires: Date | null }> => {
|
|
56
|
+
const targetProfile = resolveIpaTransitionProfile(params);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
targetProfile,
|
|
60
|
+
accountExpires: await resolveDefaultLocalAccountExpiry(targetProfile),
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const resolveIpaTransitionProfile = (params: {
|
|
65
|
+
currentProfile: UserProfile;
|
|
66
|
+
policy: Exclude<IpaAccountTransitionPolicy, "delete">;
|
|
67
|
+
}): UserProfile => {
|
|
68
|
+
if (params.policy === "demote_to_local") return params.currentProfile;
|
|
69
|
+
if (params.policy === "demote_to_local_user") return "user";
|
|
70
|
+
return "guest";
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const transitionIpaUserToLocal = async (params: {
|
|
74
|
+
userId: string;
|
|
75
|
+
targetProfile: UserProfile;
|
|
76
|
+
accountExpires: Date | null;
|
|
77
|
+
db?: SqlExecutor;
|
|
78
|
+
}): Promise<void> => {
|
|
79
|
+
const db = params.db ?? sql;
|
|
80
|
+
|
|
81
|
+
await db`
|
|
82
|
+
UPDATE auth.users
|
|
83
|
+
SET provider = 'local',
|
|
84
|
+
profile = ${params.targetProfile},
|
|
85
|
+
admin = false,
|
|
86
|
+
account_expires = ${params.accountExpires}
|
|
87
|
+
WHERE id = ${params.userId}::uuid
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
await db`
|
|
91
|
+
DELETE FROM auth.user_ipa_data
|
|
92
|
+
WHERE user_id = ${params.userId}::uuid
|
|
93
|
+
`;
|
|
94
|
+
|
|
95
|
+
await db`
|
|
96
|
+
DELETE FROM auth.ipa_user_effective_groups
|
|
97
|
+
WHERE user_id = ${params.userId}::uuid
|
|
98
|
+
`;
|
|
99
|
+
|
|
100
|
+
await clearUserRelationsForProvider({
|
|
101
|
+
userId: params.userId,
|
|
102
|
+
provider: "ipa",
|
|
103
|
+
db,
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const applyIpaAccountTransitionPolicy = async (params: {
|
|
108
|
+
userId: string;
|
|
109
|
+
currentProfile: UserProfile;
|
|
110
|
+
policy: Exclude<IpaAccountTransitionPolicy, "delete">;
|
|
111
|
+
db?: SqlExecutor;
|
|
112
|
+
}): Promise<{ targetProfile: UserProfile; accountExpires: Date | null }> => {
|
|
113
|
+
const target = await resolveIpaTransitionTarget({
|
|
114
|
+
currentProfile: params.currentProfile,
|
|
115
|
+
policy: params.policy,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
await transitionIpaUserToLocal({
|
|
119
|
+
userId: params.userId,
|
|
120
|
+
targetProfile: target.targetProfile,
|
|
121
|
+
accountExpires: target.accountExpires,
|
|
122
|
+
db: params.db,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return target;
|
|
126
|
+
};
|