@k2b/cloud 0.42.0 → 0.44.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/package.json +13 -5
- package/scripts/app-favicon.ts +7 -6
- package/scripts/build.ts +3 -0
- package/scripts/installable-assets.ts +61 -0
- package/scripts/preload.ts +3 -0
- package/src/_internal/app-access-path.ts +12 -0
- package/src/_internal/app-access-request.ts +13 -0
- package/src/_internal/app-access-resources.ts +141 -0
- package/src/_internal/app-event-catalog.ts +40 -0
- package/src/_internal/app-event-schemas.ts +127 -0
- package/src/_internal/application-package.ts +40 -0
- package/src/_internal/built-in-app-ids.json +27 -0
- package/src/_internal/capabilities.ts +185 -12
- package/src/_internal/capability-origin.ts +4 -0
- package/src/_internal/capability-streams.ts +10 -5
- package/src/_internal/define-app.ts +159 -45
- package/src/_internal/installable-assets.ts +90 -0
- package/src/_internal/installable-head.ts +22 -0
- package/src/_internal/object-store-area.ts +9 -0
- package/src/_internal/object-store-config.ts +101 -0
- package/src/_internal/object-store-key.ts +14 -0
- package/src/_internal/page-responses.ts +34 -3
- package/src/_internal/process-identity.ts +7 -2
- package/src/_internal/registry-validation.ts +15 -1
- package/src/_internal/registry.ts +44 -1
- package/src/_internal/runtime-context.ts +9 -1
- package/src/_internal/runtime-watcher.ts +8 -0
- package/src/_internal/status-preserving-ssr.ts +12 -1
- package/src/access/AppAccessEditor.browser-fixture.ts +429 -0
- package/src/access/AppAccessEditor.browser-frame.tsx +68 -0
- package/src/access/AppAccessEditor.browser-harness.tsx +65 -0
- package/src/access/AppAccessEditor.tsx +380 -0
- package/src/access/PermissionEditor.browser-harness.tsx +22 -2
- package/src/access/PermissionEditor.tsx +386 -77
- package/src/access/PrincipalAvatar.tsx +63 -0
- package/src/access/PrincipalPicker.tsx +29 -8
- package/src/access/ResourceApiKeys.tsx +3 -4
- package/src/access/app-access-adapter.ts +51 -0
- package/src/access/app-access-messages.ts +172 -0
- package/src/access/messages.ts +14 -0
- package/src/access/ui.ts +4 -1
- package/src/account/Avatar.tsx +21 -3
- package/src/account/ProfilePictureDialog.tsx +699 -0
- package/src/account/profile-picture-messages.ts +269 -0
- package/src/account/ui.ts +8 -1
- package/src/ai/admin.ts +3 -0
- package/src/ai/app-frame.ts +12 -0
- package/src/ai/application-tasks.ts +101 -0
- package/src/ai/assistant-models.ts +126 -64
- package/src/ai/capabilities.ts +19 -4
- package/src/ai/capability-execution.ts +5 -2
- package/src/ai/chart-block.ts +63 -30
- package/src/ai/chat/chart-block.tsx +12 -3
- package/src/ai/chat/composer-adapter.ts +8 -7
- package/src/ai/chat/message-actions.tsx +1 -1
- package/src/ai/chat/message-utils.ts +2 -2
- package/src/ai/chat/presentation.tsx +22 -1
- package/src/ai/chat-quotas.ts +5 -4
- package/src/ai/chat-tasks.ts +2 -2
- package/src/ai/client/controller.ts +11 -10
- package/src/ai/client/projection.ts +3 -3
- package/src/ai/code-capability-routes.ts +1 -0
- package/src/ai/compaction.ts +3 -3
- package/src/ai/credentials.ts +13 -114
- package/src/ai/default-tool-contracts.ts +4 -3
- package/src/ai/dictation-runtime.ts +4 -4
- package/src/ai/enrich.ts +2 -2
- package/src/ai/executor.ts +38 -27
- package/src/ai/http.ts +17 -29
- package/src/ai/index.ts +11 -5
- package/src/ai/inference-calls.ts +6 -4
- package/src/ai/maintenance.ts +1 -1
- package/src/ai/memory-learning-runs.ts +5 -5
- package/src/ai/memory-learning.ts +3 -3
- package/src/ai/message-queue.ts +1 -2
- package/src/ai/migrate.ts +75 -42
- package/src/ai/model-access-migrate.ts +3 -17
- package/src/ai/model-access.ts +44 -85
- package/src/ai/model-card.tsx +134 -0
- package/src/ai/model-catalog.ts +359 -0
- package/src/ai/model-chooser-messages.ts +88 -0
- package/src/ai/model-chooser.tsx +577 -0
- package/src/ai/model-delete.ts +234 -0
- package/src/ai/model-fixture.ts +39 -0
- package/src/ai/model-icon.tsx +36 -0
- package/src/ai/model-overview.ts +53 -0
- package/src/ai/model-usage.ts +45 -0
- package/src/ai/openai-api-messages.ts +0 -4
- package/src/ai/openai-api-slots.ts +10 -2
- package/src/ai/openai-api.ts +83 -16
- package/src/ai/personal-agent.ts +1 -1
- package/src/ai/prefs.ts +12 -10
- package/src/ai/projects-routes.ts +2 -1
- package/src/ai/projects.ts +13 -12
- package/src/ai/protocol.ts +2 -2
- package/src/ai/provider-connections.ts +161 -0
- package/src/ai/provider-errors.ts +138 -0
- package/src/ai/provider-fetch.ts +4 -0
- package/src/ai/provider-retry.ts +1 -1
- package/src/ai/provider.ts +2 -1
- package/src/ai/quota-limits.ts +36 -0
- package/src/ai/quota-provider.ts +11 -2
- package/src/ai/quota-report.ts +7 -4
- package/src/ai/quotas-migrate.ts +1 -0
- package/src/ai/quotas.ts +19 -8
- package/src/ai/recent-resources.ts +6 -0
- package/src/ai/request-headers.ts +14 -87
- package/src/ai/resource-refs.ts +6 -2
- package/src/ai/routes.ts +17 -18
- package/src/ai/runtime.ts +50 -22
- package/src/ai/settings.ts +93 -580
- package/src/ai/store.ts +45 -23
- package/src/ai/stream.ts +1 -1
- package/src/ai/structured-runs.ts +2 -2
- package/src/ai/structured.ts +29 -46
- package/src/ai/task-contracts.ts +25 -5
- package/src/ai/task-execution.ts +15 -6
- package/src/ai/task-model.ts +9 -0
- package/src/ai/transcription.ts +37 -21
- package/src/ai/turn-failure.ts +24 -2
- package/src/ai/types.ts +50 -62
- package/src/ai/ui.tsx +13 -0
- package/src/ai/usage.ts +8 -8
- package/src/ai/validate.ts +8 -7
- package/src/ai/view-block.ts +2 -2
- package/src/api/accounts-entities.ts +40 -16
- package/src/api/admin-ai-quotas.ts +34 -56
- package/src/api/admin-ai-usage.ts +1 -1
- package/src/api/admin-ai.ts +173 -0
- package/src/api/admin-core-settings.ts +68 -244
- package/src/api/admin-directory-shares.ts +55 -0
- package/src/api/admin-identity.ts +81 -2
- package/src/api/app-access.ts +85 -0
- package/src/api/app-approval.ts +3 -1
- package/src/api/app-event-delivery.ts +51 -0
- package/src/api/app-events.ts +123 -0
- package/src/api/app-resource-access.ts +48 -0
- package/src/api/auth/account-mismatch.ts +24 -0
- package/src/api/auth.ts +26 -3
- package/src/api/capabilities.ts +127 -15
- package/src/api/capability-streams.ts +2 -5
- package/src/api/identity-child-invocation.ts +145 -0
- package/src/api/index.ts +11 -2
- package/src/api/mcp.ts +180 -5
- package/src/api/me.ts +38 -17
- package/src/api/pwa-phone.ts +87 -48
- package/src/api/pwa.ts +34 -8
- package/src/api/resource-links.ts +83 -6
- package/src/api/resource-resolve.ts +50 -0
- package/src/api/search/schemas.ts +3 -1
- package/src/api/search.ts +9 -7
- package/src/api/widgets.ts +1 -0
- package/src/app-pwa/authority.ts +75 -0
- package/src/app-pwa/contracts.ts +177 -0
- package/src/app-pwa/core-query-error.ts +20 -0
- package/src/app-pwa/index.ts +23 -0
- package/src/app-pwa/protocol.ts +27 -0
- package/src/app-pwa/schema.ts +59 -0
- package/src/app-pwa/service.ts +861 -0
- package/src/app-pwa/transaction.ts +338 -0
- package/src/app-pwa/transport-input.ts +57 -0
- package/src/app-pwa/transport.ts +235 -0
- package/src/approvals/messages.ts +2 -2
- package/src/browser/app-access-recovery.ts +44 -0
- package/src/browser/app-session.ts +14 -3
- package/src/browser/cloud-id.ts +86 -0
- package/src/browser/live-websocket.ts +5 -0
- package/src/browser/pwa-drafts.ts +57 -0
- package/src/browser/resource-clipboard.ts +4 -0
- package/src/browser/resource-links.ts +52 -0
- package/src/browser/resource-picker.tsx +3 -2
- package/src/capabilities/calendar-sources.ts +302 -0
- package/src/capabilities/executions.ts +10 -3
- package/src/capabilities/migrate.ts +4 -0
- package/src/capabilities/nested.ts +43 -0
- package/src/capabilities/server-invoke.ts +202 -0
- package/src/capabilities/server.ts +26 -187
- package/src/capabilities/testing.ts +5 -2
- package/src/cli/access.ts +6 -5
- package/src/cli/account.ts +57 -0
- package/src/cli/admin/agents.ts +66 -0
- package/src/cli/admin/ai-models.ts +199 -0
- package/src/cli/admin/ai-quotas.ts +6 -102
- package/src/cli/admin/ai-usage.ts +4 -4
- package/src/cli/admin/app-credentials.ts +5 -3
- package/src/cli/admin/index.ts +7 -3
- package/src/cli/app-access.ts +60 -0
- package/src/cli/apps.ts +89 -26
- package/src/cli/avatar.ts +64 -0
- package/src/cli/capabilities.ts +22 -1
- package/src/cli/events.ts +320 -0
- package/src/cli/index.ts +1 -1
- package/src/config/env.ts +40 -0
- package/src/contracts/app-access.ts +52 -5
- package/src/contracts/app-approval.ts +8 -1
- package/src/contracts/app-details.ts +89 -0
- package/src/contracts/app-events.ts +51 -0
- package/src/contracts/app.ts +24 -5
- package/src/contracts/calendar-source.ts +274 -0
- package/src/contracts/calendar-view.ts +35 -0
- package/src/contracts/capabilities.ts +164 -3
- package/src/contracts/geo-admin.ts +2 -0
- package/src/contracts/geo-route.ts +0 -11
- package/src/contracts/geo.ts +14 -2
- package/src/contracts/index.ts +6 -0
- package/src/contracts/installable.ts +40 -0
- package/src/contracts/notification-types.ts +36 -9
- package/src/contracts/profile.ts +32 -0
- package/src/contracts/pwa-builds.ts +14 -0
- package/src/contracts/pwa-location.ts +38 -0
- package/src/contracts/pwa.ts +24 -3
- package/src/contracts/registry.ts +8 -0
- package/src/contracts/resource-clipboard.ts +35 -0
- package/src/contracts/resource-pages.ts +140 -0
- package/src/contracts/resource-resolution.ts +13 -0
- package/src/contracts/shared.ts +4 -0
- package/src/contracts/user-notifications.ts +2 -0
- package/src/events/app-event-delivery.ts +100 -0
- package/src/events/app-events.testing.ts +65 -0
- package/src/events/app-events.ts +134 -0
- package/src/events/core-user-created.ts +24 -0
- package/src/events/index.ts +9 -0
- package/src/events/testing.ts +2 -0
- package/src/index.ts +2 -1
- package/src/server/api/respond.ts +8 -3
- package/src/server/index.ts +1 -0
- package/src/server/middleware/app-access.ts +7 -6
- package/src/server/middleware/auth.ts +168 -66
- package/src/server/middleware/index.ts +1 -0
- package/src/server/middleware/invocation.ts +4 -2
- package/src/server/middleware/workload.ts +3 -3
- package/src/server/oauth-scope.ts +10 -0
- package/src/server/public-storage.ts +106 -0
- package/src/server/services/access.ts +8 -0
- package/src/services/accounts/app.ts +35 -3
- package/src/services/accounts/avatar.ts +342 -35
- package/src/services/accounts/base-group.ts +4 -5
- package/src/services/accounts/directory-shares.ts +157 -0
- package/src/services/accounts/entities.ts +50 -6
- package/src/services/accounts/groups.ts +3 -1
- package/src/services/accounts/identities.ts +64 -12
- package/src/services/accounts/local-groups.ts +1 -1
- package/src/services/accounts/users.ts +23 -15
- package/src/services/app-access-management.ts +519 -0
- package/src/services/app-access.ts +25 -7
- package/src/services/app-approval.ts +15 -4
- package/src/services/app-details.ts +224 -0
- package/src/services/app-discovery.ts +2 -1
- package/src/services/app-event-shapes.ts +139 -0
- package/src/services/app-events/accept.ts +116 -0
- package/src/services/app-events/admin.ts +250 -0
- package/src/services/app-events/declarations.ts +187 -0
- package/src/services/app-events/deliver.ts +218 -0
- package/src/services/app-events.ts +92 -0
- package/src/services/config-conflict.ts +11 -0
- package/src/services/core-user-events.ts +1 -0
- package/src/services/freeipa-config.ts +9 -0
- package/src/services/geo/admin.ts +4 -1
- package/src/services/geo/ledger.ts +47 -14
- package/src/services/geo/route-chain.ts +40 -18
- package/src/services/geo/route-estimate.ts +19 -0
- package/src/services/geo/route-plan.ts +0 -2
- package/src/services/geo/route-service.ts +57 -39
- package/src/services/geo/service.ts +51 -25
- package/src/services/identity/core-origin.ts +12 -0
- package/src/services/identity/index.ts +2 -1
- package/src/services/identity/invocation-actor.ts +50 -2
- package/src/services/identity/invocation-authority.ts +38 -1
- package/src/services/identity/invocation-token.ts +255 -50
- package/src/services/identity/issue-invocation.ts +9 -1
- package/src/services/identity/models-only-auth.ts +13 -4
- package/src/services/identity/nested-invocation.ts +128 -0
- package/src/services/image-metadata.ts +65 -1
- package/src/services/index.ts +24 -1
- package/src/services/ipa/sync.ts +6 -2
- package/src/services/ipa/users.ts +4 -1
- package/src/services/mandates/policy.ts +19 -12
- package/src/services/notifications/browser.ts +4 -0
- package/src/services/notifications/catalog.ts +5 -2
- package/src/services/notifications/platform.ts +7 -2
- package/src/services/notifications/user.ts +18 -7
- package/src/services/oauth-tokens.ts +28 -2
- package/src/services/object-store.ts +217 -0
- package/src/services/providers/local/users.ts +5 -0
- package/src/services/pwa-devices.ts +147 -37
- package/src/services/service-account-credentials.ts +5 -2
- package/src/services/session/index.ts +14 -0
- package/src/services/settings/core-settings.ts +91 -47
- package/src/services/settings/index.ts +8 -0
- package/src/services/settings/versions.ts +26 -0
- package/src/shared/ai-models.ts +274 -0
- package/src/shared/ai-quotas.ts +2 -2
- package/src/shared/ai-usage.ts +2 -2
- package/src/shared/index.ts +2 -0
- package/src/shared-text/documents.ts +25 -8
- package/src/shared-text/log.ts +1 -1
- package/src/shared-text/text-diff.ts +75 -0
- package/src/ssr/AdminArea.tsx +131 -0
- package/src/ssr/AdminLayout.tsx +11 -3
- package/src/ssr/AdminSidebar.tsx +7 -7
- package/src/ssr/AppAccessDenied.tsx +114 -0
- package/src/ssr/AppBadge.tsx +14 -0
- package/src/ssr/AppLaunchpadPanel.tsx +1 -1
- package/src/ssr/AppPwaRuntime.island.tsx +82 -0
- package/src/ssr/CloudShell.island.tsx +25 -4
- package/src/ssr/Layout.tsx +15 -7
- package/src/ssr/PwaLayout.tsx +73 -8
- package/src/ssr/PwaPartCount.island.tsx +23 -0
- package/src/ssr/PwaRuntime.island.tsx +85 -6
- package/src/ssr/SettingsSaveOutcome.tsx +58 -0
- package/src/ssr/SettingsSaveStatus.island.tsx +16 -0
- package/src/ssr/ShellRuntime.island.tsx +5 -0
- package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +3 -3
- package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +1 -1
- package/src/ssr/admin-active-link.ts +16 -0
- package/src/ssr/admin-navigation.ts +26 -20
- package/src/ssr/app-badges.ts +31 -7
- package/src/ssr/app-pwa-document.tsx +16 -0
- package/src/ssr/app-pwa-messages.ts +17 -0
- package/src/ssr/index.ts +8 -0
- package/src/ssr/layout-context.ts +1 -1
- package/src/ssr/page-trail-runtime.ts +3 -1
- package/src/ssr/platform-messages.ts +9 -1
- package/src/ssr/pwa-messages.ts +10 -2
- package/src/ssr/rail-navigation.ts +5 -1
- package/src/ssr/settings-draft.ts +153 -0
- package/src/ssr/shell-data.tsx +2 -0
- package/src/styles/global.css +1 -0
- package/src/styles/profile-picture.css +270 -0
- package/src/styles/utilities-navigation.css +6 -0
- package/src/workflows/ai/policy.ts +49 -0
- package/src/workflows/ai/runtime.ts +131 -21
- package/src/workflows/ai/store.ts +283 -35
- package/src/workflows/ai/types.ts +8 -2
- package/src/workflows/ai-actions.ts +148 -120
- package/src/workflows/authoring.ts +31 -0
- package/src/workflows/contracts.ts +1 -0
- package/src/workflows/definition.ts +2 -0
- package/src/workflows/editor.ts +15 -0
- package/src/workflows/index.ts +2 -1
- package/src/workflows/language/compiler.ts +34 -3
- package/src/workflows/runtime/executor.ts +132 -33
- package/src/workflows/runtime/index.ts +2 -0
- package/src/workflows/runtime/ports.ts +5 -1
- package/src/workflows/store/actions.ts +1 -1
- package/src/workflows/store/definitions.ts +5 -0
- package/src/workflows/store/runs.ts +5 -3
- package/src/workflows/store/worker.ts +7 -2
- package/src/ai/model-pricing.ts +0 -43
- package/src/ai/model-request-settings.ts +0 -98
- package/src/ai/settings-messages.ts +0 -129
- package/src/shared/ai-model-request-settings.ts +0 -21
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { S3Client } from "bun";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { readStates } from "../services/settings/store";
|
|
4
|
+
|
|
5
|
+
export const objectStoreSettingKeys = [
|
|
6
|
+
"storage.endpoint",
|
|
7
|
+
"storage.region",
|
|
8
|
+
"storage.bucket",
|
|
9
|
+
"storage.access_key_id",
|
|
10
|
+
"storage.secret_access_key",
|
|
11
|
+
"storage.max_object_bytes",
|
|
12
|
+
] as const;
|
|
13
|
+
const bucket = z
|
|
14
|
+
.string()
|
|
15
|
+
.min(3)
|
|
16
|
+
.max(63)
|
|
17
|
+
.regex(/^[a-z0-9][a-z0-9.-]*[a-z0-9]$/);
|
|
18
|
+
const Config = z.object({
|
|
19
|
+
endpoint: z.string().min(1),
|
|
20
|
+
region: z.string().min(1),
|
|
21
|
+
bucket,
|
|
22
|
+
access_key_id: z.string().min(1),
|
|
23
|
+
secret_access_key: z.string().min(1),
|
|
24
|
+
max_object_bytes: z
|
|
25
|
+
.number()
|
|
26
|
+
.int()
|
|
27
|
+
.min(1)
|
|
28
|
+
.max(5 * 1024 ** 3),
|
|
29
|
+
});
|
|
30
|
+
export class ObjectStoreConfigurationError extends Error {}
|
|
31
|
+
export class ObjectStoreConnectionError extends Error {}
|
|
32
|
+
let cached: { signature: string; client: S3Client } | undefined;
|
|
33
|
+
|
|
34
|
+
export const objectStoreBackend = async () => {
|
|
35
|
+
// Read one coherent DB snapshot, including stored-state checks. No local TTL:
|
|
36
|
+
// changes and secret rotation apply to the next operation in every process.
|
|
37
|
+
const states = await readStates(objectStoreSettingKeys);
|
|
38
|
+
if (objectStoreSettingKeys.some((key) => states.get(key)?.stored === "unreadable")) {
|
|
39
|
+
throw new ObjectStoreConfigurationError(
|
|
40
|
+
"Storage settings cannot be decrypted. Check this process uses the installation APP_SECRET, then replace unreadable settings in Administration > Settings > Storage.",
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const parsed = Config.safeParse(
|
|
44
|
+
Object.fromEntries(objectStoreSettingKeys.map((key) => [key.slice("storage.".length), states.get(key)?.value])),
|
|
45
|
+
);
|
|
46
|
+
if (!parsed.success)
|
|
47
|
+
throw new ObjectStoreConfigurationError(
|
|
48
|
+
"Storage is not configured. Set the Garage endpoint, region, bucket and credentials in Administration > Settings > Storage.",
|
|
49
|
+
);
|
|
50
|
+
const config = parsed.data;
|
|
51
|
+
const endpoint = URL.parse(config.endpoint);
|
|
52
|
+
if (
|
|
53
|
+
!endpoint ||
|
|
54
|
+
!["http:", "https:"].includes(endpoint.protocol) ||
|
|
55
|
+
endpoint.username ||
|
|
56
|
+
endpoint.password ||
|
|
57
|
+
endpoint.search ||
|
|
58
|
+
endpoint.hash ||
|
|
59
|
+
endpoint.pathname !== "/"
|
|
60
|
+
) {
|
|
61
|
+
throw new ObjectStoreConfigurationError("Storage endpoint must be an HTTP(S) origin without credentials, path, query or fragment.");
|
|
62
|
+
}
|
|
63
|
+
const signature = JSON.stringify(config);
|
|
64
|
+
if (cached?.signature !== signature) {
|
|
65
|
+
const options = {
|
|
66
|
+
endpoint: endpoint.href,
|
|
67
|
+
accessKeyId: config.access_key_id,
|
|
68
|
+
secretAccessKey: config.secret_access_key,
|
|
69
|
+
region: config.region,
|
|
70
|
+
};
|
|
71
|
+
cached = { signature, client: new S3Client({ ...options, bucket: config.bucket }) };
|
|
72
|
+
}
|
|
73
|
+
return { client: cached.client, limit: config.max_object_bytes };
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Internal administrator check; never an application API or a credential response. */
|
|
77
|
+
export const testObjectStoreConnection = async (): Promise<void> => {
|
|
78
|
+
const backend = await objectStoreBackend();
|
|
79
|
+
const file = backend.client.file(`cloud-connection-test/${crypto.randomUUID()}`);
|
|
80
|
+
const content = "Cloud storage connection test";
|
|
81
|
+
let step = "write";
|
|
82
|
+
try {
|
|
83
|
+
await file.write(content, { type: "text/plain" });
|
|
84
|
+
step = "read";
|
|
85
|
+
if ((await file.text()) !== content) throw new Error("Unexpected content");
|
|
86
|
+
step = "delete";
|
|
87
|
+
await file.delete();
|
|
88
|
+
if (await file.exists()) throw new Error("Object still exists");
|
|
89
|
+
} catch {
|
|
90
|
+
try {
|
|
91
|
+
await file.delete();
|
|
92
|
+
} catch {
|
|
93
|
+
throw new ObjectStoreConnectionError(
|
|
94
|
+
`Storage test failed at ${step}; cleanup failed. Remove cloud-connection-test/ objects after fixing the connection.`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
throw new ObjectStoreConnectionError(
|
|
98
|
+
`Storage test failed at ${step}. Check the saved settings, Garage availability and bucket permissions, then retry.`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// S3's complete object key, including the area, is limited to 1,024 UTF-8 bytes.
|
|
2
|
+
// Reject percent signs altogether, including nested encodings, before Bun sees a key.
|
|
3
|
+
export const validateObjectKey = (key: string, area: string, allowPrefix = false): string => {
|
|
4
|
+
if (typeof key !== "string" || !key.isWellFormed() || key !== key.normalize("NFC") || /[\\%\p{Cc}\p{Cf}]/u.test(key)) {
|
|
5
|
+
throw new TypeError("Object keys must be NFC text without controls, backslashes, or percent escapes.");
|
|
6
|
+
}
|
|
7
|
+
if (allowPrefix && key === "") return key;
|
|
8
|
+
const path = allowPrefix && key.endsWith("/") ? key.slice(0, -1) : key;
|
|
9
|
+
if (!path || path.split("/").some((segment) => !segment || segment === "." || segment === "..")) {
|
|
10
|
+
throw new TypeError("Object keys must be relative paths without empty, dot, or parent segments.");
|
|
11
|
+
}
|
|
12
|
+
if (Buffer.byteLength(area + key) > 1024) throw new RangeError("Object key exceeds S3's 1,024-byte limit including its area.");
|
|
13
|
+
return key;
|
|
14
|
+
};
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import type { HtmlFn } from "@k2b/ssr";
|
|
2
2
|
import type { Context } from "hono";
|
|
3
3
|
import type { ClientErrorStatusCode, ServerErrorStatusCode } from "hono/utils/http-status";
|
|
4
|
+
import type { AppMeta } from "../contracts/app";
|
|
4
5
|
import { PWA_AUTH_PATH, PWA_SCOPE } from "../contracts/pwa";
|
|
5
6
|
import { getLocale } from "../server/locale";
|
|
6
|
-
import {
|
|
7
|
+
import { runtime } from "../server/middleware/runtime";
|
|
8
|
+
import { preloadLayoutAnnouncements, settings } from "../server/middleware/settings";
|
|
9
|
+
import { appAccessAuthority } from "../services/app-access";
|
|
10
|
+
import { appAccessManagers, canRepairAppAccess } from "../services/app-access-management";
|
|
11
|
+
import { logger } from "../services/logging";
|
|
12
|
+
import { resolveAppPresentation } from "../shared/app-presentation";
|
|
7
13
|
import { createLoginRedirectUrl } from "../shared/redirect";
|
|
8
14
|
import { readThemePreferenceFromCookieHeader } from "../shared/theme";
|
|
15
|
+
import { preloadDisplayApps } from "../ssr/runtime";
|
|
9
16
|
import type { PageOptions } from "./define-app";
|
|
10
17
|
|
|
11
18
|
export type PageErrorOptions = {
|
|
@@ -19,10 +26,12 @@ export type PageErrorOptions = {
|
|
|
19
26
|
export type PageErrorStatus = ClientErrorStatusCode | ServerErrorStatusCode;
|
|
20
27
|
|
|
21
28
|
/** Explicit document responses, bound to the owning application's SSR template. */
|
|
22
|
-
export const createPageResponses = (html: HtmlFn<
|
|
29
|
+
export const createPageResponses = <T extends PageOptions>(html: HtmlFn<T>, appId: string, requestOptions?: (c: Context) => Partial<T>) => {
|
|
23
30
|
const error = async (c: Context, status: PageErrorStatus, options: PageErrorOptions = {}): Promise<Response> => {
|
|
24
31
|
// Auth middleware may reject before ssr() initializes page metadata.
|
|
25
32
|
c.set("page", {
|
|
33
|
+
...requestOptions?.(c),
|
|
34
|
+
pwaOwner: appId,
|
|
26
35
|
lang: getLocale(c),
|
|
27
36
|
themePreference: readThemePreferenceFromCookieHeader(c.req.header("Cookie")),
|
|
28
37
|
});
|
|
@@ -58,5 +67,27 @@ export const createPageResponses = (html: HtmlFn<PageOptions>) => {
|
|
|
58
67
|
},
|
|
59
68
|
};
|
|
60
69
|
|
|
61
|
-
|
|
70
|
+
const appAccessDenied = async (c: Context, app: AppMeta): Promise<Response> => {
|
|
71
|
+
await runtime()(c, async () => {});
|
|
72
|
+
await settings()(c, async () => {});
|
|
73
|
+
c.set("page", { lang: getLocale(c), themePreference: readThemePreferenceFromCookieHeader(c.req.header("Cookie")) });
|
|
74
|
+
await preloadDisplayApps(c);
|
|
75
|
+
const managers =
|
|
76
|
+
app.access?.kind === "grants"
|
|
77
|
+
? await appAccessManagers(app.id).catch((error: unknown) => {
|
|
78
|
+
logger("app-access").error("Could not load application access contacts", { appId: app.id, error });
|
|
79
|
+
// Cloud administration remains the recovery contact when the list is unavailable.
|
|
80
|
+
return [];
|
|
81
|
+
})
|
|
82
|
+
: [];
|
|
83
|
+
const authority = appAccessAuthority(c);
|
|
84
|
+
const { renderAppAccessDenied } = await import("../ssr/AppAccessDenied");
|
|
85
|
+
const response = await html(
|
|
86
|
+
renderAppAccessDenied(c, resolveAppPresentation(app, getLocale(c)), managers, !!authority && canRepairAppAccess(authority)),
|
|
87
|
+
c.get("page"),
|
|
88
|
+
);
|
|
89
|
+
response.headers.set("Cache-Control", "private, no-store");
|
|
90
|
+
return new Response(response.body, { status: 403, headers: response.headers });
|
|
91
|
+
};
|
|
92
|
+
return { access, pwaAccess, error, appAccessDenied };
|
|
62
93
|
};
|
|
@@ -2,20 +2,25 @@ import type { PlatformPermission } from "../contracts/outgoing-mail";
|
|
|
2
2
|
|
|
3
3
|
/** Identity of the application started in this process, derived from its declaration. */
|
|
4
4
|
let applicationId: string | undefined;
|
|
5
|
+
let applicationPackage: string | undefined;
|
|
5
6
|
let platformPermissions: readonly PlatformPermission[] = [];
|
|
6
7
|
export const getProcessPlatformPermissions = (): readonly PlatformPermission[] => platformPermissions;
|
|
7
8
|
|
|
8
9
|
export const getProcessApplicationId = (): string | undefined => applicationId;
|
|
9
10
|
|
|
10
|
-
export const
|
|
11
|
-
|
|
11
|
+
export const getProcessApplicationPackage = (): string | undefined => applicationPackage;
|
|
12
|
+
|
|
13
|
+
export const bindProcessApplicationId = (id: string, permissions: readonly PlatformPermission[] = [], packageName = id): void => {
|
|
14
|
+
if (applicationId !== undefined && (applicationId !== id || applicationPackage !== packageName)) {
|
|
12
15
|
throw new Error(`This process already hosts application "${applicationId}"`);
|
|
13
16
|
}
|
|
14
17
|
applicationId = id;
|
|
18
|
+
applicationPackage = packageName;
|
|
15
19
|
platformPermissions = [...permissions];
|
|
16
20
|
};
|
|
17
21
|
|
|
18
22
|
export const clearProcessApplicationId = (): void => {
|
|
19
23
|
applicationId = undefined;
|
|
24
|
+
applicationPackage = undefined;
|
|
20
25
|
platformPermissions = [];
|
|
21
26
|
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { CLOUD_CLI_MODULE_NAME } from "../cli/plugin";
|
|
2
|
+
import { APP_ADMIN_AREAS } from "../contracts/app";
|
|
2
3
|
import { AppAccessRuleSchema } from "../contracts/app-access";
|
|
4
|
+
import { validateInstallable } from "../contracts/installable";
|
|
3
5
|
import { PlatformPermissionSchema } from "../contracts/outgoing-mail";
|
|
4
6
|
import type { AppRegistryEntry } from "../contracts/registry";
|
|
5
7
|
import { isOwnAppPath, SAME_ORIGIN_PATH } from "./app-path";
|
|
@@ -18,6 +20,9 @@ export const validateAppRegistryEntry = (value: unknown): string | null => {
|
|
|
18
20
|
for (const field of ["id", "name", "icon", "description", "baseUrl"] as const) {
|
|
19
21
|
if (!isString(value[field]) || value[field].length === 0) return invalid(field, "a non-empty string");
|
|
20
22
|
}
|
|
23
|
+
if (value.package !== undefined && (typeof value.package !== "string" || !/^[a-z][a-z0-9-]{0,79}$/.test(value.package))) {
|
|
24
|
+
return invalid("package", "a lowercase package name of 1–80 characters");
|
|
25
|
+
}
|
|
21
26
|
try {
|
|
22
27
|
const baseUrl = new URL(value.baseUrl as string);
|
|
23
28
|
if (!(["http:", "https:"] as const).includes(baseUrl.protocol as "http:" | "https:") || baseUrl.username || baseUrl.password) {
|
|
@@ -29,6 +34,9 @@ export const validateAppRegistryEntry = (value: unknown): string | null => {
|
|
|
29
34
|
if (!isStringArray(value.routes) || value.routes.some((route) => !route.startsWith("/"))) {
|
|
30
35
|
return invalid("routes", "an array of absolute paths");
|
|
31
36
|
}
|
|
37
|
+
if (value.installable !== undefined && !validateInstallable(String(value.id), value.routes, value.installable)) {
|
|
38
|
+
return invalid("installable", "a canonical standalone surface covered by the app's own declared routes");
|
|
39
|
+
}
|
|
32
40
|
if (value.access !== undefined && !AppAccessRuleSchema.safeParse(value.access).success)
|
|
33
41
|
return invalid("access", "a declarative app access rule");
|
|
34
42
|
if (value.startedAt !== undefined && !(typeof value.startedAt === "number" && Number.isFinite(value.startedAt))) {
|
|
@@ -96,7 +104,7 @@ export const validateAppRegistryEntry = (value: unknown): string | null => {
|
|
|
96
104
|
(group) =>
|
|
97
105
|
!isRecord(group) ||
|
|
98
106
|
(group.id !== undefined && !isString(group.id)) ||
|
|
99
|
-
(group.section !== undefined && group.section
|
|
107
|
+
(group.section !== undefined && !(APP_ADMIN_AREAS as readonly unknown[]).includes(group.section)) ||
|
|
100
108
|
!isString(group.label) ||
|
|
101
109
|
!Array.isArray(group.links) ||
|
|
102
110
|
group.links.some((link) => !isRecord(link) || !isString(link.label) || !isString(link.href) || !isString(link.icon)),
|
|
@@ -137,6 +145,12 @@ export const validateAppRegistryEntry = (value: unknown): string | null => {
|
|
|
137
145
|
if (value.pwa.requiresRoles !== undefined && !isStringArray(value.pwa.requiresRoles)) {
|
|
138
146
|
return invalid("pwa.requiresRoles", "an array of strings");
|
|
139
147
|
}
|
|
148
|
+
for (const field of ["badge", "summary"] as const) {
|
|
149
|
+
const path = value.pwa[field];
|
|
150
|
+
if (path !== undefined && (!isString(path) || !isOwnAppPath(path, value.routes))) {
|
|
151
|
+
return invalid(`pwa.${field}`, "a same-origin path below one of the app's routes");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
140
154
|
}
|
|
141
155
|
if (value.widgets !== undefined) {
|
|
142
156
|
if (
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import type { EphemeralConfig, MutexConfig } from "@k2b/sync";
|
|
3
3
|
import type { AppAppearanceColor } from "../contracts/app";
|
|
4
|
+
import { calendarSourceIssues } from "../contracts/calendar-source";
|
|
4
5
|
import type { CapabilityManifest, CapabilityPresentationCatalog } from "../contracts/capabilities";
|
|
5
6
|
import { fileProviderIssues } from "../contracts/file-provider";
|
|
6
7
|
import type { AppRegistryEntry, CapabilityRegistryEntry } from "../contracts/registry";
|
|
8
|
+
import { resourcePageIssue } from "../contracts/resource-pages";
|
|
7
9
|
import { type Role, RoleSchema } from "../contracts/shared";
|
|
8
10
|
import {
|
|
9
11
|
type DashboardWidgetPresentation,
|
|
@@ -233,6 +235,7 @@ const reportLeftOutEntries = (sent: unknown, manifest: CapabilityManifest): void
|
|
|
233
235
|
return typeof localId === "string" && !kept.has(localId) ? [`${group}/${localId}`] : [];
|
|
234
236
|
});
|
|
235
237
|
});
|
|
238
|
+
if (groups.calendarSource !== undefined && !manifest.calendarSource) leftOut.push("calendarSource");
|
|
236
239
|
if (groups.fileProvider !== undefined && !manifest.fileProvider) leftOut.push("fileProvider");
|
|
237
240
|
if (leftOut.length === 0) return;
|
|
238
241
|
console.warn(
|
|
@@ -283,6 +286,40 @@ const withValidFileProvider = (manifest: CapabilityManifest): CapabilityManifest
|
|
|
283
286
|
return rest;
|
|
284
287
|
};
|
|
285
288
|
|
|
289
|
+
const checkedCalendarSources = new Map<string, { manifestHash: string; valid: boolean }>();
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Ignores a calendar source that does not match the contract, logging once per manifest; the app's other
|
|
293
|
+
* capabilities stay available. The delivered `manifestHash` stays, because it identifies what the app registered.
|
|
294
|
+
*/
|
|
295
|
+
const withValidCalendarSource = (manifest: CapabilityManifest): CapabilityManifest => {
|
|
296
|
+
if (!manifest.calendarSource) return manifest;
|
|
297
|
+
const checked = checkedCalendarSources.get(manifest.appId);
|
|
298
|
+
let valid = checked?.manifestHash === manifest.manifestHash ? checked.valid : undefined;
|
|
299
|
+
if (valid === undefined) {
|
|
300
|
+
const issues = calendarSourceIssues(manifest);
|
|
301
|
+
valid = issues.length === 0;
|
|
302
|
+
checkedCalendarSources.set(manifest.appId, { manifestHash: manifest.manifestHash, valid });
|
|
303
|
+
if (!valid) {
|
|
304
|
+
console.error(
|
|
305
|
+
JSON.stringify({
|
|
306
|
+
level: "error",
|
|
307
|
+
source: "capability-registry",
|
|
308
|
+
message: "Ignored a calendar source that does not match the calendar-source contract",
|
|
309
|
+
appId: manifest.appId,
|
|
310
|
+
manifestHash: manifest.manifestHash,
|
|
311
|
+
issues: issues
|
|
312
|
+
.slice(0, 10)
|
|
313
|
+
.map(({ function: name, localId, code, path, message }) => ({ function: name, localId, code, path, message })),
|
|
314
|
+
}),
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (valid) return manifest;
|
|
319
|
+
const { calendarSource: _ignored, ...rest } = manifest;
|
|
320
|
+
return rest;
|
|
321
|
+
};
|
|
322
|
+
|
|
286
323
|
/** Ignores record fields from a newer release; the endpoint always comes from the live app registry. */
|
|
287
324
|
export const resolveLiveCapabilityRegistryEntry = (
|
|
288
325
|
key: string,
|
|
@@ -297,7 +334,13 @@ export const resolveLiveCapabilityRegistryEntry = (
|
|
|
297
334
|
if (!endpoint) return null;
|
|
298
335
|
try {
|
|
299
336
|
const read = parseCapabilityManifest(record.manifest, app.id);
|
|
300
|
-
const
|
|
337
|
+
const supported = withValidCalendarSource(withValidFileProvider(read));
|
|
338
|
+
const manifest = supported.resourcePages
|
|
339
|
+
? {
|
|
340
|
+
...supported,
|
|
341
|
+
resourcePages: supported.resourcePages.filter((page) => !resourcePageIssue(page.path, app.routes, app.id)),
|
|
342
|
+
}
|
|
343
|
+
: supported;
|
|
301
344
|
const presentation = compileCapabilityPresentation(manifest, record.presentation, "reader");
|
|
302
345
|
if (app.capabilities.protocolVersion !== manifest.protocolVersion || app.capabilities.manifestHash !== manifest.manifestHash) {
|
|
303
346
|
return null;
|
|
@@ -71,7 +71,15 @@ export const buildRuntimeFromRegistry = (entries: AppRegistryEntry[], capabiliti
|
|
|
71
71
|
),
|
|
72
72
|
legalLinks: e.legalLinks ? e.legalLinks.map((l) => ({ ...l })) : undefined,
|
|
73
73
|
searchLinks: e.searchLinks?.map((link) => ({ ...link, keywords: link.keywords ? [...link.keywords] : undefined })),
|
|
74
|
-
pwa: e.pwa
|
|
74
|
+
pwa: e.pwa
|
|
75
|
+
? {
|
|
76
|
+
href: e.pwa.href,
|
|
77
|
+
requiresRoles: e.pwa.requiresRoles ? ([...e.pwa.requiresRoles] as Role[]) : undefined,
|
|
78
|
+
...(e.pwa.badge ? { badge: e.pwa.badge } : {}),
|
|
79
|
+
...(e.pwa.summary ? { summary: e.pwa.summary } : {}),
|
|
80
|
+
}
|
|
81
|
+
: undefined,
|
|
82
|
+
installable: e.installable ? structuredClone(e.installable) : undefined,
|
|
75
83
|
openapi: e.openapi,
|
|
76
84
|
};
|
|
77
85
|
}),
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import type { CloudRuntime } from "../contracts/app";
|
|
14
|
+
import { pwaBuilds } from "../contracts/pwa-builds";
|
|
14
15
|
import { logger } from "../services/logging";
|
|
15
16
|
import { superviseRuntimeTask } from "../services/runtime-lifecycle";
|
|
16
17
|
import { listApps, listCapabilities, watchAppRegistry } from "./registry";
|
|
@@ -19,6 +20,11 @@ import { buildRuntimeFromRegistry } from "./runtime-context";
|
|
|
19
20
|
const log = logger("runtime-watcher");
|
|
20
21
|
|
|
21
22
|
let current: CloudRuntime | undefined;
|
|
23
|
+
let mobileBuilds: Record<string, string> = {};
|
|
24
|
+
|
|
25
|
+
/** Current registered release identities for the existing mobile session keepalive. */
|
|
26
|
+
export const registeredPwaBuilds = (): Record<string, string> => mobileBuilds;
|
|
27
|
+
|
|
22
28
|
let coreRelease: string | undefined;
|
|
23
29
|
let initPromise: Promise<void> | undefined;
|
|
24
30
|
let watcherTask: Promise<void> | undefined;
|
|
@@ -28,6 +34,7 @@ const refresh = async () => {
|
|
|
28
34
|
const apps = await listApps();
|
|
29
35
|
const capabilities = await listCapabilities(apps);
|
|
30
36
|
current = buildRuntimeFromRegistry(apps, capabilities);
|
|
37
|
+
mobileBuilds = pwaBuilds(apps);
|
|
31
38
|
coreRelease = apps.find((app) => app.id === "core")?.runtime?.release;
|
|
32
39
|
};
|
|
33
40
|
|
|
@@ -74,6 +81,7 @@ export const stopRuntimeWatcher = async (): Promise<void> => {
|
|
|
74
81
|
initPromise = undefined;
|
|
75
82
|
current = undefined;
|
|
76
83
|
coreRelease = undefined;
|
|
84
|
+
mobileBuilds = {};
|
|
77
85
|
};
|
|
78
86
|
|
|
79
87
|
export const getCurrentRuntime = (): CloudRuntime => {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { HtmlFn, RenderFn } from "@k2b/ssr";
|
|
2
2
|
import { createSSRHandler } from "@k2b/ssr/hono";
|
|
3
3
|
import type { Context, Env, MiddlewareHandler, TypedResponse } from "hono";
|
|
4
|
+
import { setCookie } from "hono/cookie";
|
|
4
5
|
import type { StatusCode } from "hono/utils/http-status";
|
|
5
6
|
import type { PanelConsentSnapshot } from "../contracts/panel-context";
|
|
7
|
+
import { PWA_COOKIES, PWA_LIMITS, PWA_SCOPE } from "../contracts/pwa";
|
|
6
8
|
import type { RailSnapshot } from "../contracts/rail-preferences";
|
|
7
9
|
import type { SettingsFlat, Unflatten } from "../contracts/settings-types";
|
|
8
10
|
import type { User } from "../contracts/shared";
|
|
@@ -12,7 +14,7 @@ import { measureServerPhase } from "./server-timing";
|
|
|
12
14
|
|
|
13
15
|
type PageEnv<T extends object> = {
|
|
14
16
|
Variables: {
|
|
15
|
-
page: Partial<T
|
|
17
|
+
page: Partial<T> & { pwaAddress?: string };
|
|
16
18
|
user?: User;
|
|
17
19
|
railPreferences?: RailSnapshot;
|
|
18
20
|
panelConsent?: PanelConsentSnapshot;
|
|
@@ -62,6 +64,15 @@ export const createStatusPreservingSsrHandler = <T extends object>(
|
|
|
62
64
|
response.headers.forEach((value, key) => {
|
|
63
65
|
headers[key] = value;
|
|
64
66
|
});
|
|
67
|
+
const address = context.get("page").pwaAddress;
|
|
68
|
+
if (address && status < 400) {
|
|
69
|
+
setCookie(context, PWA_COOKIES.lastAddress, address, {
|
|
70
|
+
path: PWA_SCOPE,
|
|
71
|
+
secure: true,
|
|
72
|
+
sameSite: "Lax",
|
|
73
|
+
maxAge: PWA_LIMITS.idleDays * 86_400,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
65
76
|
const document = context.newResponse(response.body, status as StatusCode, headers);
|
|
66
77
|
// A rendered document carries the signed-in user's state. Without an
|
|
67
78
|
// explicit policy, a browser may show it again on Back from its HTTP
|