@k2b/cloud 0.43.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 +7 -2
- 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 +158 -12
- package/src/_internal/capability-origin.ts +4 -0
- package/src/_internal/capability-streams.ts +7 -5
- package/src/_internal/define-app.ts +57 -6
- 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/process-identity.ts +7 -2
- package/src/_internal/registry-validation.ts +11 -1
- package/src/_internal/registry.ts +8 -1
- package/src/_internal/runtime-context.ts +8 -1
- package/src/access/AppAccessEditor.tsx +2 -0
- package/src/access/PermissionEditor.tsx +9 -3
- package/src/access/PrincipalPicker.tsx +14 -4
- package/src/access/ResourceApiKeys.tsx +3 -4
- package/src/access/app-access-messages.ts +6 -0
- package/src/account/ProfilePictureDialog.tsx +699 -0
- package/src/account/profile-picture-messages.ts +269 -0
- package/src/account/ui.ts +7 -0
- 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/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/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 +31 -22
- package/src/ai/http.ts +17 -29
- package/src/ai/index.ts +11 -5
- package/src/ai/inference-calls.ts +5 -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.ts +26 -9
- 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 +55 -6
- package/src/ai/provider-fetch.ts +4 -0
- package/src/ai/provider.ts +1 -1
- package/src/ai/quota-limits.ts +36 -0
- package/src/ai/quota-provider.ts +4 -2
- package/src/ai/quota-report.ts +7 -4
- 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 +21 -44
- 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 +27 -22
- 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 +16 -0
- 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/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 +28 -4
- package/src/api/index.ts +7 -1
- package/src/api/mcp.ts +175 -2
- package/src/api/me.ts +6 -4
- package/src/api/pwa-phone.ts +75 -26
- 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/app-pwa/contracts.ts +4 -0
- package/src/app-pwa/core-query-error.ts +20 -0
- package/src/app-pwa/index.ts +1 -0
- package/src/app-pwa/service.ts +91 -0
- package/src/app-pwa/transport.ts +22 -0
- package/src/approvals/messages.ts +2 -2
- package/src/browser/cloud-id.ts +86 -0
- package/src/browser/resource-clipboard.ts +4 -0
- package/src/browser/resource-links.ts +52 -0
- package/src/capabilities/server-invoke.ts +2 -11
- package/src/capabilities/testing.ts +5 -2
- package/src/cli/access.ts +6 -5
- 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 +5 -2
- package/src/cli/apps.ts +89 -26
- package/src/cli/capabilities.ts +22 -1
- package/src/cli/events.ts +320 -0
- package/src/config/env.ts +40 -0
- package/src/contracts/app-access.ts +26 -2
- package/src/contracts/app-details.ts +89 -0
- package/src/contracts/app-events.ts +51 -0
- package/src/contracts/app.ts +21 -5
- package/src/contracts/capabilities.ts +143 -3
- package/src/contracts/index.ts +2 -0
- package/src/contracts/notification-types.ts +36 -9
- package/src/contracts/pwa.ts +17 -1
- package/src/contracts/registry.ts +6 -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 +1 -1
- package/src/server/api/respond.ts +8 -3
- package/src/server/index.ts +1 -0
- package/src/server/middleware/app-access.ts +1 -1
- package/src/server/middleware/auth.ts +21 -7
- package/src/server/middleware/index.ts +1 -0
- package/src/server/middleware/invocation.ts +2 -0
- package/src/server/public-storage.ts +106 -0
- package/src/server/services/access.ts +8 -0
- package/src/services/accounts/app.ts +6 -0
- package/src/services/accounts/entities.ts +14 -3
- package/src/services/accounts/groups.ts +3 -1
- package/src/services/accounts/identities.ts +62 -10
- package/src/services/accounts/users.ts +7 -6
- package/src/services/app-access.ts +25 -7
- 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/ledger.ts +28 -10
- package/src/services/geo/service.ts +43 -24
- package/src/services/identity/core-origin.ts +12 -0
- package/src/services/identity/invocation-actor.ts +20 -2
- package/src/services/identity/invocation-authority.ts +4 -0
- package/src/services/identity/invocation-token.ts +238 -56
- package/src/services/identity/issue-invocation.ts +6 -1
- package/src/services/image-metadata.ts +65 -1
- package/src/services/index.ts +13 -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/session/index.ts +14 -0
- package/src/services/settings/core-settings.ts +71 -43
- 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 +9 -3
- package/src/ssr/AdminSidebar.tsx +7 -7
- package/src/ssr/AppAccessDenied.tsx +2 -0
- package/src/ssr/AppBadge.tsx +14 -0
- package/src/ssr/AppLaunchpadPanel.tsx +1 -1
- package/src/ssr/CloudShell.island.tsx +25 -4
- package/src/ssr/Layout.tsx +8 -6
- package/src/ssr/PwaLayout.tsx +16 -1
- package/src/ssr/PwaPartCount.island.tsx +23 -0
- package/src/ssr/SettingsSaveOutcome.tsx +58 -0
- package/src/ssr/SettingsSaveStatus.island.tsx +16 -0
- package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +3 -3
- package/src/ssr/admin-active-link.ts +16 -0
- package/src/ssr/admin-navigation.ts +25 -20
- package/src/ssr/app-badges.ts +31 -7
- package/src/ssr/index.ts +8 -0
- package/src/ssr/page-trail-runtime.ts +3 -1
- package/src/ssr/platform-messages.ts +9 -1
- package/src/ssr/pwa-messages.ts +2 -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
|
+
};
|
|
@@ -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,4 +1,5 @@
|
|
|
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";
|
|
3
4
|
import { validateInstallable } from "../contracts/installable";
|
|
4
5
|
import { PlatformPermissionSchema } from "../contracts/outgoing-mail";
|
|
@@ -19,6 +20,9 @@ export const validateAppRegistryEntry = (value: unknown): string | null => {
|
|
|
19
20
|
for (const field of ["id", "name", "icon", "description", "baseUrl"] as const) {
|
|
20
21
|
if (!isString(value[field]) || value[field].length === 0) return invalid(field, "a non-empty string");
|
|
21
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
|
+
}
|
|
22
26
|
try {
|
|
23
27
|
const baseUrl = new URL(value.baseUrl as string);
|
|
24
28
|
if (!(["http:", "https:"] as const).includes(baseUrl.protocol as "http:" | "https:") || baseUrl.username || baseUrl.password) {
|
|
@@ -100,7 +104,7 @@ export const validateAppRegistryEntry = (value: unknown): string | null => {
|
|
|
100
104
|
(group) =>
|
|
101
105
|
!isRecord(group) ||
|
|
102
106
|
(group.id !== undefined && !isString(group.id)) ||
|
|
103
|
-
(group.section !== undefined && group.section
|
|
107
|
+
(group.section !== undefined && !(APP_ADMIN_AREAS as readonly unknown[]).includes(group.section)) ||
|
|
104
108
|
!isString(group.label) ||
|
|
105
109
|
!Array.isArray(group.links) ||
|
|
106
110
|
group.links.some((link) => !isRecord(link) || !isString(link.label) || !isString(link.href) || !isString(link.icon)),
|
|
@@ -141,6 +145,12 @@ export const validateAppRegistryEntry = (value: unknown): string | null => {
|
|
|
141
145
|
if (value.pwa.requiresRoles !== undefined && !isStringArray(value.pwa.requiresRoles)) {
|
|
142
146
|
return invalid("pwa.requiresRoles", "an array of strings");
|
|
143
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
|
+
}
|
|
144
154
|
}
|
|
145
155
|
if (value.widgets !== undefined) {
|
|
146
156
|
if (
|
|
@@ -5,6 +5,7 @@ import { calendarSourceIssues } from "../contracts/calendar-source";
|
|
|
5
5
|
import type { CapabilityManifest, CapabilityPresentationCatalog } from "../contracts/capabilities";
|
|
6
6
|
import { fileProviderIssues } from "../contracts/file-provider";
|
|
7
7
|
import type { AppRegistryEntry, CapabilityRegistryEntry } from "../contracts/registry";
|
|
8
|
+
import { resourcePageIssue } from "../contracts/resource-pages";
|
|
8
9
|
import { type Role, RoleSchema } from "../contracts/shared";
|
|
9
10
|
import {
|
|
10
11
|
type DashboardWidgetPresentation,
|
|
@@ -333,7 +334,13 @@ export const resolveLiveCapabilityRegistryEntry = (
|
|
|
333
334
|
if (!endpoint) return null;
|
|
334
335
|
try {
|
|
335
336
|
const read = parseCapabilityManifest(record.manifest, app.id);
|
|
336
|
-
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;
|
|
337
344
|
const presentation = compileCapabilityPresentation(manifest, record.presentation, "reader");
|
|
338
345
|
if (app.capabilities.protocolVersion !== manifest.protocolVersion || app.capabilities.manifestHash !== manifest.manifestHash) {
|
|
339
346
|
return null;
|
|
@@ -71,7 +71,14 @@ 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,
|
|
75
82
|
installable: e.installable ? structuredClone(e.installable) : undefined,
|
|
76
83
|
openapi: e.openapi,
|
|
77
84
|
};
|
|
@@ -217,6 +217,8 @@ export default function AppAccessEditor(props: AppAccessEditorProps) {
|
|
|
217
217
|
};
|
|
218
218
|
const fixedDescription = () => {
|
|
219
219
|
switch (props.app.access?.kind) {
|
|
220
|
+
case "resources":
|
|
221
|
+
return t().resources;
|
|
220
222
|
case "signedIn":
|
|
221
223
|
return t().fixedSignedIn;
|
|
222
224
|
case "noGuests":
|
|
@@ -3,7 +3,7 @@ import { announce, Button, IconButton, Placeholder, prompts, Select, SelectChip,
|
|
|
3
3
|
import { createEffect, createMemo, createSignal, For, on, onCleanup, Show } from "solid-js";
|
|
4
4
|
import { createStore, reconcile } from "solid-js/store";
|
|
5
5
|
import { CloudAvatar } from "../account/Avatar";
|
|
6
|
-
import type { AccessEntry, PermissionLevel, Principal, ServiceAccountKind } from "../contracts/shared";
|
|
6
|
+
import type { AccessEntry, PermissionLevel, Principal, ServiceAccountKind, ServiceAccountStatus } from "../contracts/shared";
|
|
7
7
|
import { createGroupCoverage, type GroupCoverage, GroupCoveragePanel, GroupCoverageToggle } from "./GroupCoverage";
|
|
8
8
|
import { isManagerEntry } from "./managers";
|
|
9
9
|
import { accessMessages } from "./messages";
|
|
@@ -81,9 +81,13 @@ type PermissionEditorProps = {
|
|
|
81
81
|
* true; resources with an explicit principal allowlist can disable it. */
|
|
82
82
|
allowAuthenticated?: boolean;
|
|
83
83
|
|
|
84
|
-
/** Allow granting service accounts.
|
|
85
|
-
*
|
|
84
|
+
/** Allow granting service accounts. Defaults to true, including agents.
|
|
85
|
+
* Disable only when the resource documents a user-backed identity requirement. */
|
|
86
86
|
allowServiceAccounts?: boolean;
|
|
87
|
+
/** Optional directory filters for the resource's accepted credential kinds. */
|
|
88
|
+
serviceAccountKinds?: ServiceAccountKind[];
|
|
89
|
+
/** Grant selection defaults to active accounts; existing entries are unaffected. */
|
|
90
|
+
serviceAccountStatus?: ServiceAccountStatus;
|
|
87
91
|
|
|
88
92
|
/** The principal search's placeholder, in the consumer's terms. Defaults to the principal kinds it offers. */
|
|
89
93
|
placeholder?: string;
|
|
@@ -563,6 +567,8 @@ export default function PermissionEditor(props: PermissionEditorProps) {
|
|
|
563
567
|
allowPublic={allowPublic()}
|
|
564
568
|
allowAuthenticated={allowAuthenticated()}
|
|
565
569
|
allowServiceAccounts={props.allowServiceAccounts}
|
|
570
|
+
serviceAccountKinds={props.serviceAccountKinds}
|
|
571
|
+
serviceAccountStatus={props.serviceAccountStatus ?? "active"}
|
|
566
572
|
placeholder={props.placeholder}
|
|
567
573
|
signedIn={props.signedIn}
|
|
568
574
|
readOnly={grantPending()}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Combobox, type ComboboxOption, useLocale } from "@k2b/ui";
|
|
2
|
-
import type { Principal, ServiceAccountKind } from "../contracts/shared";
|
|
2
|
+
import type { Principal, ServiceAccountKind, ServiceAccountStatus } from "../contracts/shared";
|
|
3
3
|
import { accessMessages } from "./messages";
|
|
4
4
|
import { principalLabel } from "./principal-label";
|
|
5
5
|
import { serviceAccountKindDisplay } from "./service-account-kind";
|
|
@@ -34,7 +34,11 @@ export default function PrincipalPicker(props: {
|
|
|
34
34
|
existing?: Principal[];
|
|
35
35
|
allowPublic?: boolean;
|
|
36
36
|
allowAuthenticated?: boolean;
|
|
37
|
+
/** Defaults to true; disable only for a documented user-backed workflow. */
|
|
37
38
|
allowServiceAccounts?: boolean;
|
|
39
|
+
/** Optional directory filters for the resource's accepted credential kinds. */
|
|
40
|
+
serviceAccountKinds?: ServiceAccountKind[];
|
|
41
|
+
serviceAccountStatus?: ServiceAccountStatus;
|
|
38
42
|
/** Offers only the service accounts this returns `true` for, such as active accounts the resource can accept. */
|
|
39
43
|
serviceAccountFilter?: (account: PrincipalPickerServiceAccount) => boolean;
|
|
40
44
|
disabled?: boolean;
|
|
@@ -78,7 +82,13 @@ export default function PrincipalPicker(props: {
|
|
|
78
82
|
if (search.length >= 2) {
|
|
79
83
|
const url = new URL("/api/accounts/entities", window.location.origin);
|
|
80
84
|
url.searchParams.set("search", search);
|
|
81
|
-
url.searchParams.set("kinds", props.allowServiceAccounts ? "user,group,service_account" : "user,group");
|
|
85
|
+
url.searchParams.set("kinds", props.allowServiceAccounts !== false ? "user,group,service_account" : "user,group");
|
|
86
|
+
if (props.allowServiceAccounts !== false && props.serviceAccountKinds?.length) {
|
|
87
|
+
url.searchParams.set("service_account_kinds", props.serviceAccountKinds.join(","));
|
|
88
|
+
}
|
|
89
|
+
if (props.allowServiceAccounts !== false && props.serviceAccountStatus) {
|
|
90
|
+
url.searchParams.set("service_account_status", props.serviceAccountStatus);
|
|
91
|
+
}
|
|
82
92
|
url.searchParams.set("per_page", "10");
|
|
83
93
|
const users = props.existing?.flatMap((p) => (p.type === "user" ? [p.userId] : [])) ?? [];
|
|
84
94
|
const groups = props.existing?.flatMap((p) => (p.type === "group" ? [p.groupId] : [])) ?? [];
|
|
@@ -145,10 +155,10 @@ export default function PrincipalPicker(props: {
|
|
|
145
155
|
placeholder={
|
|
146
156
|
props.placeholder ??
|
|
147
157
|
(props.allowAuthenticated !== false || props.allowPublic
|
|
148
|
-
? props.allowServiceAccounts
|
|
158
|
+
? props.allowServiceAccounts !== false
|
|
149
159
|
? t().addAll
|
|
150
160
|
: t().addAudience
|
|
151
|
-
: props.allowServiceAccounts
|
|
161
|
+
: props.allowServiceAccounts !== false
|
|
152
162
|
? t().addService
|
|
153
163
|
: t().addBasic)
|
|
154
164
|
}
|
|
@@ -217,12 +217,11 @@ export default function ResourceApiKeys(props: ResourceApiKeysProps) {
|
|
|
217
217
|
if (confirmed) await revokeMutation.mutate({ id: key.id, name: key.name });
|
|
218
218
|
};
|
|
219
219
|
|
|
220
|
-
// The heading is a detail panel section heading: in a
|
|
221
|
-
// and below a settings group title they read as one level down.
|
|
220
|
+
// The heading is a detail panel section heading without an icon: in a details panel the keys line up with the
|
|
221
|
+
// sections around them, and below a settings group title they read as one level down. Keys are flat rows.
|
|
222
222
|
return (
|
|
223
223
|
<DetailPanel.Section
|
|
224
224
|
title={props.title ?? t().apiKeys}
|
|
225
|
-
icon="ti ti-key"
|
|
226
225
|
description={props.description ?? t().apiKeysDescription}
|
|
227
226
|
actions={
|
|
228
227
|
<Button type="button" variant="secondary" size="sm" onClick={openCreate} disabled={createMutation.loading()}>
|
|
@@ -235,7 +234,7 @@ export default function ResourceApiKeys(props: ResourceApiKeysProps) {
|
|
|
235
234
|
<div class="flex flex-col gap-2">
|
|
236
235
|
<For each={keys()}>
|
|
237
236
|
{(key) => (
|
|
238
|
-
<div class="group/api-key flex items-center gap-3
|
|
237
|
+
<div class="group/api-key flex items-center gap-3 py-1.5">
|
|
239
238
|
<div class="min-w-0 flex-1">
|
|
240
239
|
{/* The name and its tags wrap instead of squeezing the name to a few letters in a narrow section. */}
|
|
241
240
|
<div class="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
|
|
@@ -55,12 +55,15 @@ export const appAccessMessages = i18n.define({
|
|
|
55
55
|
fixedNoGuests: "Signed-in people except guests. The app sets this rule; there is nothing to configure here.",
|
|
56
56
|
fixedAdmins: "Cloud administrators only. The app sets this rule; there is nothing to configure here.",
|
|
57
57
|
fixedRoles: "People with the Cloud roles required by the app. The app sets this rule.",
|
|
58
|
+
resources: "Accounts with an effective resource permission in this app. Manage permissions within the app.",
|
|
59
|
+
resourcesLabel: "Resource permissions",
|
|
58
60
|
legacy: "This app uses its existing access checks.",
|
|
59
61
|
deniedTitle: ({ name }: { name: string }) => `${name} is not available to you`,
|
|
60
62
|
revokedTitle: ({ name }: { name: string }) => `Your access to ${name} was removed`,
|
|
61
63
|
revokedDescription: "You had access until now. If this was a mistake, ask:",
|
|
62
64
|
deniedGrants: "Only people with access may use this app. Access is managed by:",
|
|
63
65
|
deniedFixed: "If you need this app, contact your Cloud administration.",
|
|
66
|
+
deniedResources: "Ask someone who manages resource permissions in this app for access.",
|
|
64
67
|
deniedAdmins: ({ name }: { name: string }) => `Only Cloud administrators use ${name}.`,
|
|
65
68
|
deniedNoGuests: ({ name }: { name: string }) => `Guests cannot use ${name}.`,
|
|
66
69
|
deniedRoles: ({ name }: { name: string }) => `Only people with specific Cloud roles use ${name}.`,
|
|
@@ -134,12 +137,15 @@ export const appAccessMessages = i18n.define({
|
|
|
134
137
|
fixedNoGuests: "Angemeldete Personen außer Gästen. Das legt die App fest; hier gibt es dazu nichts einzustellen.",
|
|
135
138
|
fixedAdmins: "Nur Cloud-Admins. Das legt die App fest; hier gibt es dazu nichts einzustellen.",
|
|
136
139
|
fixedRoles: "Personen mit den von der App verlangten Cloud-Rollen. Das legt die App fest.",
|
|
140
|
+
resources: "Konten mit einer wirksamen Ressourcenberechtigung in dieser App. Berechtigungen werden innerhalb der App verwaltet.",
|
|
141
|
+
resourcesLabel: "Ressourcenberechtigungen",
|
|
137
142
|
legacy: "Diese App verwendet ihre bisherigen Zugriffsprüfungen.",
|
|
138
143
|
deniedTitle: ({ name }: { name: string }) => `${name} ist für dich nicht freigegeben`,
|
|
139
144
|
revokedTitle: ({ name }: { name: string }) => `Dein Zugriff auf ${name} wurde entfernt`,
|
|
140
145
|
revokedDescription: "Du hattest bis eben Zugriff. Wenn das ein Versehen ist, frag:",
|
|
141
146
|
deniedGrants: "Diese App nutzen nur Personen mit Freigabe. Den Zugriff verwalten:",
|
|
142
147
|
deniedFixed: "Wenn du die App brauchst, wende dich an die Cloud-Verwaltung.",
|
|
148
|
+
deniedResources: "Bitte jemanden, der Ressourcenberechtigungen in dieser App verwaltet, um Zugriff.",
|
|
143
149
|
deniedAdmins: ({ name }: { name: string }) => `${name} nutzen nur Cloud-Admins.`,
|
|
144
150
|
deniedNoGuests: ({ name }: { name: string }) => `${name} nutzen nur angemeldete Personen außer Gästen.`,
|
|
145
151
|
deniedRoles: ({ name }: { name: string }) => `${name} nutzen nur Personen mit bestimmten Cloud-Rollen.`,
|