@k2b/cloud 0.48.0 → 0.50.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 +3 -3
- package/src/_internal/app-access-resources.ts +2 -1
- package/src/_internal/app-capabilities.ts +73 -0
- package/src/_internal/app-event-catalog.ts +1 -1
- package/src/_internal/app-queries.ts +1 -1
- package/src/_internal/application-package.ts +2 -1
- package/src/_internal/capabilities.ts +17 -2
- package/src/_internal/define-app.ts +2 -0
- package/src/ai/app-skill-store.ts +2 -1
- package/src/ai/app-skills.ts +2 -1
- package/src/ai/approvals.ts +2 -1
- package/src/ai/chat-tasks.ts +2 -1
- package/src/ai/credentials.ts +2 -1
- package/src/ai/dictation-runtime.ts +1 -1
- package/src/ai/dictations.ts +1 -1
- package/src/ai/files-store.ts +2 -1
- package/src/ai/inference-calls.ts +2 -1
- package/src/ai/map-store.ts +1 -1
- package/src/ai/map-tool.ts +1 -1
- package/src/ai/memories.ts +1 -1
- package/src/ai/memory-learning-runs.ts +1 -1
- package/src/ai/memory-learning.ts +1 -1
- package/src/ai/memory-workflow-evidence.ts +1 -1
- package/src/ai/message-queue.ts +2 -1
- package/src/ai/migrate.ts +1 -1
- package/src/ai/model-access-migrate.ts +1 -1
- package/src/ai/model-access.ts +1 -1
- package/src/ai/model-catalog.ts +1 -1
- package/src/ai/model-delete.ts +1 -1
- package/src/ai/model-usage.ts +2 -1
- package/src/ai/open-questions.ts +1 -1
- package/src/ai/openai-api-slots.ts +2 -1
- package/src/ai/prefs.ts +2 -1
- package/src/ai/projects.ts +2 -1
- package/src/ai/provider-connections.ts +1 -1
- package/src/ai/quota-limits.ts +2 -1
- package/src/ai/quota-provider.ts +2 -1
- package/src/ai/quota-report.ts +2 -1
- package/src/ai/quotas-migrate.ts +2 -1
- package/src/ai/quotas.ts +2 -1
- package/src/ai/request-headers.ts +2 -1
- package/src/ai/review-content.ts +2 -1
- package/src/ai/skill-search.ts +2 -1
- package/src/ai/skills.ts +2 -1
- package/src/ai/store.ts +2 -1
- package/src/ai/structured-runs.ts +2 -1
- package/src/ai/tool-audit.ts +2 -1
- package/src/ai/turn-timing.ts +1 -1
- package/src/ai/usage.ts +2 -1
- package/src/ai/web-provenance.ts +2 -1
- package/src/api/admin-ai-quotas.ts +1 -1
- package/src/api/admin-ai.ts +1 -1
- package/src/api/admin-app-keys.ts +47 -3
- package/src/api/admin-core-settings.ts +1 -1
- package/src/api/admin-outgoing-mail.ts +1 -1
- package/src/api/app-bootstrap.ts +23 -1
- package/src/api/app-events.ts +1 -1
- package/src/api/auth.ts +1 -1
- package/src/api/authenticate-app-request.ts +129 -0
- package/src/api/capabilities.ts +79 -5
- package/src/api/index.ts +2 -0
- package/src/api/me.ts +1 -1
- package/src/api/search.bench.ts +1 -1
- package/src/app-pwa/service.ts +2 -1
- package/src/app-pwa/transaction.ts +4 -2
- package/src/capabilities/claims.ts +2 -1
- package/src/capabilities/executions.ts +8 -4
- package/src/capabilities/geo-client.ts +1 -1
- package/src/capabilities/migrate.ts +3 -1
- package/src/capabilities/resource-readers.ts +2 -2
- package/src/capabilities/server-invoke.ts +45 -18
- package/src/capabilities/server.ts +2 -1
- package/src/cli/admin/app-keys.ts +25 -3
- package/src/config/env.ts +4 -4
- package/src/contracts/capabilities.ts +15 -2
- package/src/contracts/outgoing-mail.ts +1 -1
- package/src/events/app-event-delivery.ts +1 -1
- package/src/events/live-delivery.ts +2 -1
- package/src/runtime/bootstrap-protocol.ts +11 -4
- package/src/runtime/clients.ts +6 -2
- package/src/server/index.ts +1 -0
- package/src/server/ratelimit.ts +1 -1
- package/src/server/services/access.ts +2 -1
- package/src/services/account-category-policy.ts +1 -1
- package/src/services/account-lifecycle/audit.ts +1 -1
- package/src/services/account-lifecycle/index.ts +1 -1
- package/src/services/account-lifecycle/ipa-backfill.ts +2 -1
- package/src/services/accounts/app.ts +1 -1
- package/src/services/accounts/avatar.ts +1 -1
- package/src/services/accounts/base-group.ts +1 -1
- package/src/services/accounts/directory-shares.ts +1 -1
- package/src/services/accounts/duplicate-emails.ts +1 -1
- package/src/services/accounts/entities.ts +2 -1
- package/src/services/accounts/group-sql.ts +2 -1
- package/src/services/accounts/groups.ts +1 -1
- package/src/services/accounts/identities.ts +13 -11
- package/src/services/accounts/ipa-data.ts +1 -1
- package/src/services/accounts/local-groups.ts +2 -1
- package/src/services/accounts/machine-group-management.ts +2 -1
- package/src/services/accounts/posix.ts +13 -12
- package/src/services/accounts/request-policy.ts +2 -1
- package/src/services/accounts/switching.ts +1 -1
- package/src/services/accounts/users.ts +2 -1
- package/src/services/announcements/index.ts +1 -1
- package/src/services/app-access-management.ts +2 -1
- package/src/services/app-access.ts +2 -1
- package/src/services/app-admin-facts.ts +11 -3
- package/src/services/app-approval.ts +34 -30
- package/src/services/app-bootstrap.ts +104 -21
- package/src/services/app-details.ts +3 -2
- package/src/services/app-events/accept.ts +5 -5
- package/src/services/app-events/admin.ts +15 -15
- package/src/services/app-events/declarations.ts +6 -3
- package/src/services/app-events/deliver.ts +6 -6
- package/src/services/app-events.ts +3 -2
- package/src/services/app-keys.ts +42 -11
- package/src/services/approvals/content.ts +1 -1
- package/src/services/approvals/index.ts +2 -1
- package/src/services/approvals/list.ts +1 -1
- package/src/services/approvals/migrate.ts +1 -1
- package/src/services/audit/index.ts +2 -1
- package/src/services/auth-flows/ipa.ts +1 -1
- package/src/services/auth-flows/magic-link.ts +1 -1
- package/src/services/auth-flows/password-reset.ts +1 -1
- package/src/services/auth-flows/proxy-return.ts +1 -1
- package/src/services/geo/admin.ts +13 -12
- package/src/services/geo/identity.ts +46 -0
- package/src/services/geo/ledger.ts +118 -106
- package/src/services/geo/migrate.ts +2 -1
- package/src/services/geo/route-chain.ts +3 -3
- package/src/services/geo/service.ts +4 -24
- package/src/services/geoip/index.ts +5 -5
- package/src/services/geoip/store.ts +24 -21
- package/src/services/help/index.ts +2 -1
- package/src/services/help/store.ts +2 -1
- package/src/services/identity/app-request.ts +44 -0
- package/src/services/identity/constants.ts +1 -0
- package/src/services/identity/index.ts +1 -0
- package/src/services/identity/invocation-actor.ts +2 -1
- package/src/services/identity/invocation-token.ts +112 -0
- package/src/services/identity/key-ring.ts +2 -2
- package/src/services/identity/resource-readers.ts +1 -1
- package/src/services/identity/routes.ts +11 -3
- package/src/services/identity/runtime-config.ts +14 -3
- package/src/services/identity/workload-auth.ts +2 -0
- package/src/services/identity/workload-runtime.ts +1 -1
- package/src/services/ipa/groups.ts +1 -1
- package/src/services/ipa/profile.ts +1 -1
- package/src/services/ipa/search.ts +1 -1
- package/src/services/ipa/sync.ts +1 -1
- package/src/services/ipa/users.ts +1 -1
- package/src/services/known-browsers.ts +2 -1
- package/src/services/legal-consent.ts +1 -1
- package/src/services/logging/index.ts +6 -3
- package/src/services/logging/trace.ts +1 -1
- package/src/services/logging/web-vitals.ts +1 -1
- package/src/services/mandates/index.ts +2 -1
- package/src/services/notifications/batches.ts +1 -1
- package/src/services/notifications/browser.ts +1 -1
- package/src/services/notifications/catalog.ts +1 -1
- package/src/services/notifications/dispatcher.ts +1 -1
- package/src/services/notifications/index.ts +1 -1
- package/src/services/notifications/observability.ts +2 -1
- package/src/services/notifications/platform.ts +1 -1
- package/src/services/notifications/quiet.ts +1 -1
- package/src/services/notifications/user.ts +1 -1
- package/src/services/oauth-tokens.ts +1 -1
- package/src/services/outbox.ts +15 -14
- package/src/services/outgoing-mail/admin.ts +1 -1
- package/src/services/outgoing-mail/dispatcher.ts +1 -1
- package/src/services/outgoing-mail/drain.ts +2 -1
- package/src/services/outgoing-mail/enqueue.ts +2 -1
- package/src/services/outgoing-mail/messages.ts +2 -1
- package/src/services/outgoing-mail/retention.ts +1 -1
- package/src/services/outgoing-mail/send.ts +1 -1
- package/src/services/outgoing-mail/store.ts +2 -1
- package/src/services/panel-context-consent.ts +32 -28
- package/src/services/providers/local/auth.ts +1 -1
- package/src/services/providers/local/users.ts +1 -1
- package/src/services/pwa-devices.ts +2 -1
- package/src/services/rail-preferences.ts +16 -12
- package/src/services/rail-shortcuts.ts +12 -10
- package/src/services/request-cache-redis.ts +3 -3
- package/src/services/service-account-credentials.ts +2 -1
- package/src/services/service-accounts.ts +2 -1
- package/src/services/session/index.ts +2 -1
- package/src/services/session/user.ts +2 -1
- package/src/services/settings/index.ts +1 -1
- package/src/services/settings/store.ts +1 -1
- package/src/services/settings/versions.ts +2 -1
- package/src/services/weather/forecast.ts +2 -1
- package/src/services/weather/locations.ts +1 -1
- package/src/services/weather/migrate.ts +2 -1
- package/src/services/weather/short-id.ts +2 -1
- package/src/services/webauthn.ts +1 -1
- package/src/shared-text/documents.ts +2 -2
- package/src/ssr/compact-shell.browser-harness.ts +2 -0
- package/src/workflows/ai/store.ts +2 -1
- package/src/workflows/store/budget.ts +2 -1
- package/src/workflows/store/definitions.ts +2 -1
- package/src/workflows/store/events.ts +2 -1
- package/src/workflows/store/observability.ts +2 -1
- package/src/workflows/store/retention.ts +2 -1
- package/src/workflows/store/runs.ts +2 -1
- package/src/workflows/store/transaction.ts +2 -1
- package/src/workflows/store/worker.ts +2 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { getProcessApplicationId } from "../_internal/process-identity";
|
|
2
3
|
import { refreshAppWorkloadCredential } from "../_internal/workload-credential";
|
|
3
4
|
import { dispatchCapability, loadCapabilityCatalogPage } from "../api/capabilities";
|
|
5
|
+
import { env } from "../config/env";
|
|
4
6
|
import {
|
|
5
7
|
CAPABILITY_FRAMEWORK_ERROR_CODES,
|
|
6
8
|
CAPABILITY_MAX_CATALOG_BYTES,
|
|
@@ -10,6 +12,7 @@ import {
|
|
|
10
12
|
capabilityResultSchema,
|
|
11
13
|
} from "../contracts/capabilities";
|
|
12
14
|
import type { RequestAuthority } from "../server";
|
|
15
|
+
import { sendAppRequest } from "../services/identity/app-request";
|
|
13
16
|
import { coreOrigin } from "../services/identity/core-origin";
|
|
14
17
|
import { capabilityMessages } from "../shared/capability-messages";
|
|
15
18
|
import { LOCALE_HEADER } from "../shared/locale";
|
|
@@ -39,12 +42,22 @@ export type CapabilityCaller = {
|
|
|
39
42
|
signal?: AbortSignal;
|
|
40
43
|
/** Request-local aggregate response budget; called before result validation for every received chunk. */
|
|
41
44
|
consumeResponseBytes?: (bytes: number) => void;
|
|
42
|
-
/** App-only query authority. The
|
|
45
|
+
/** App-only query authority. The platform authenticates each transmission to Core. */
|
|
43
46
|
workload?: { appId: string };
|
|
44
|
-
/**
|
|
47
|
+
/** App authority authenticated only by the configured Ed25519 app key. */
|
|
48
|
+
app?: { appId: string };
|
|
49
|
+
/** Durable background authority. The platform authenticates each transmission to Core. */
|
|
45
50
|
mandate?: { id: string; revision: number; callingAppId: string };
|
|
46
51
|
};
|
|
47
52
|
|
|
53
|
+
/** Call as the started application, without a person, service account token, or mandate. */
|
|
54
|
+
export const appCaller = (options: Pick<CapabilityCaller, "locale" | "requestId" | "signal"> = {}): CapabilityCaller => {
|
|
55
|
+
const appId = getProcessApplicationId();
|
|
56
|
+
if (!appId) throw new Error("Start the application before creating its capability caller");
|
|
57
|
+
if (!env.CLOUD_APP_KEY) throw new Error("App capability calls require CLOUD_APP_KEY");
|
|
58
|
+
return { ...options, app: { appId }, origin: "app" };
|
|
59
|
+
};
|
|
60
|
+
|
|
48
61
|
export { coreOrigin } from "../services/identity/core-origin";
|
|
49
62
|
|
|
50
63
|
export const callerRequest = async (
|
|
@@ -55,7 +68,7 @@ export const callerRequest = async (
|
|
|
55
68
|
): Promise<Request> => {
|
|
56
69
|
const headers = new Headers();
|
|
57
70
|
if (caller.authorization) headers.set("authorization", caller.authorization);
|
|
58
|
-
if (!caller.mandate && !caller.workload) {
|
|
71
|
+
if (!caller.mandate && !caller.workload && !caller.app) {
|
|
59
72
|
if (caller.cookie) headers.set("cookie", caller.cookie);
|
|
60
73
|
if (caller.requestId) headers.set("x-request-id", caller.requestId);
|
|
61
74
|
if (caller.traceparent) headers.set("traceparent", caller.traceparent);
|
|
@@ -63,14 +76,17 @@ export const callerRequest = async (
|
|
|
63
76
|
if (caller.locale) headers.set(LOCALE_HEADER, caller.locale);
|
|
64
77
|
if (idempotencyKey) headers.set("idempotency-key", idempotencyKey);
|
|
65
78
|
} else {
|
|
66
|
-
headers.set("x-cloud-app-id", caller.mandate?.callingAppId ?? caller.workload!.appId);
|
|
79
|
+
headers.set("x-cloud-app-id", caller.app?.appId ?? caller.mandate?.callingAppId ?? caller.workload!.appId);
|
|
67
80
|
}
|
|
68
81
|
const signal = combineCapabilitySignals(caller.signal, invocationSignal);
|
|
69
|
-
return new Request(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
82
|
+
return new Request(
|
|
83
|
+
new URL(path, await coreOrigin(caller.app !== undefined || caller.mandate !== undefined || caller.workload !== undefined)),
|
|
84
|
+
{
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers,
|
|
87
|
+
signal,
|
|
88
|
+
},
|
|
89
|
+
);
|
|
74
90
|
};
|
|
75
91
|
|
|
76
92
|
export const unavailable = (
|
|
@@ -106,15 +122,20 @@ const invokeCapabilityWithResultSchema = async <TDataSchema extends z.ZodType, T
|
|
|
106
122
|
caller: CapabilityCaller,
|
|
107
123
|
): Promise<CapabilityClientResult<z.output<TDataSchema>>> => {
|
|
108
124
|
try {
|
|
125
|
+
if (
|
|
126
|
+
caller.app &&
|
|
127
|
+
(!env.CLOUD_APP_KEY || caller.authority || caller.authorization || caller.cookie || caller.workload || caller.mandate)
|
|
128
|
+
)
|
|
129
|
+
throw new Error("App calls require one app-key authority source");
|
|
109
130
|
if (caller.workload && (caller.mandate || caller.cookie || invocation.kind !== "query"))
|
|
110
131
|
throw new Error("Workload authority permits only queries without a mandate or cookie");
|
|
111
132
|
const path =
|
|
112
|
-
caller.mandate || caller.workload
|
|
133
|
+
caller.app || caller.mandate || caller.workload
|
|
113
134
|
? "/api/_internal/identity/v1/invoke"
|
|
114
135
|
: `/api/capabilities/v1/${invocation.kind === "query" ? "queries" : "actions"}/${encodeURIComponent(invocation.appId)}/${encodeURIComponent(invocation.capabilityId)}`;
|
|
115
136
|
const request = await callerRequest(caller, path, invocation.idempotencyKey, invocation.signal);
|
|
116
137
|
if (caller.authority) {
|
|
117
|
-
if (caller.authorization || caller.cookie || caller.mandate || caller.workload || caller.transport)
|
|
138
|
+
if (caller.authorization || caller.cookie || caller.app || caller.mandate || caller.workload || caller.transport)
|
|
118
139
|
throw new Error("Use exactly one capability authority source");
|
|
119
140
|
let charged = false;
|
|
120
141
|
const charge = caller.consumeResponseBytes;
|
|
@@ -147,8 +168,9 @@ const invokeCapabilityWithResultSchema = async <TDataSchema extends z.ZodType, T
|
|
|
147
168
|
}
|
|
148
169
|
const brokerRequest = new Request(request, {
|
|
149
170
|
body: JSON.stringify(
|
|
150
|
-
caller.mandate || caller.workload
|
|
171
|
+
caller.app || caller.mandate || caller.workload
|
|
151
172
|
? {
|
|
173
|
+
...(caller.app ? { appCaller: true } : {}),
|
|
152
174
|
kind: invocation.kind,
|
|
153
175
|
targetApp: invocation.appId,
|
|
154
176
|
capabilityId: invocation.capabilityId,
|
|
@@ -169,13 +191,15 @@ const invokeCapabilityWithResultSchema = async <TDataSchema extends z.ZodType, T
|
|
|
169
191
|
: { input: invocation.input },
|
|
170
192
|
),
|
|
171
193
|
});
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
194
|
+
const workloadAppId = caller.app?.appId ?? caller.mandate?.callingAppId ?? caller.workload?.appId;
|
|
195
|
+
const send = (request: Request) =>
|
|
196
|
+
workloadAppId ? sendAppRequest(request, { appId: workloadAppId, transport: caller.transport }) : (caller.transport ?? fetch)(request);
|
|
197
|
+
// Only Core's pre-dispatch legacy rejection is safe to refresh and retry.
|
|
198
|
+
// A configured app key is never downgraded to a reusable credential.
|
|
175
199
|
const retryRequest = workloadAppId ? brokerRequest.clone() : undefined;
|
|
176
200
|
let response = await send(brokerRequest);
|
|
177
201
|
const credentialRejected = (reply: Response) => reply.status === 401 && reply.headers.get("x-cloud-workload-auth") === "required";
|
|
178
|
-
if (workloadAppId && retryRequest && credentialRejected(response)) {
|
|
202
|
+
if (!caller.app && workloadAppId && retryRequest && !env.CLOUD_APP_KEY && credentialRejected(response)) {
|
|
179
203
|
await response.body?.cancel();
|
|
180
204
|
let token: string | undefined;
|
|
181
205
|
try {
|
|
@@ -228,14 +252,17 @@ export const listCapabilityCatalog = async (
|
|
|
228
252
|
if (options.caller) {
|
|
229
253
|
const caller = options.caller;
|
|
230
254
|
if (options.authority) throw new Error("Use one catalog authority source");
|
|
231
|
-
if (
|
|
255
|
+
if (
|
|
256
|
+
caller.authority &&
|
|
257
|
+
(caller.authorization || caller.cookie || caller.app || caller.mandate || caller.workload || caller.transport)
|
|
258
|
+
)
|
|
232
259
|
throw new Error("Use exactly one capability authority source");
|
|
233
260
|
if (caller.authority)
|
|
234
261
|
return {
|
|
235
262
|
ok: true,
|
|
236
263
|
data: await loadCapabilityCatalogPage({ cursor: options.cursor, limit }, {}, caller.locale ?? options.locale, caller.authority),
|
|
237
264
|
};
|
|
238
|
-
if (caller.mandate || caller.workload) throw new Error("Calendar discovery requires request credentials");
|
|
265
|
+
if (caller.app || caller.mandate || caller.workload) throw new Error("Calendar discovery requires request credentials");
|
|
239
266
|
const query = new URLSearchParams({ limit: String(limit) });
|
|
240
267
|
if (options.cursor) query.set("cursor", options.cursor);
|
|
241
268
|
const request = new Request(await callerRequest(caller, `/api/capabilities/v1/catalog?${query}`), { method: "GET" });
|
|
@@ -19,6 +19,7 @@ import type { CapabilityCatalogAppClientResult, CapabilityInvocation, Capability
|
|
|
19
19
|
|
|
20
20
|
export type { CapabilityActionWording } from "../_internal/capability-sentences";
|
|
21
21
|
export {
|
|
22
|
+
appCaller,
|
|
22
23
|
type CapabilityCaller,
|
|
23
24
|
invokeCapability,
|
|
24
25
|
invokeCapabilityWithDataSchema,
|
|
@@ -89,7 +90,7 @@ export const reviewCapabilityAction = async <TInput = unknown>(
|
|
|
89
90
|
caller: CapabilityCaller,
|
|
90
91
|
): Promise<CapabilityReviewClientResult> => {
|
|
91
92
|
try {
|
|
92
|
-
if (caller.workload) throw new Error("App query authority cannot review Actions");
|
|
93
|
+
if (caller.app || caller.workload) throw new Error("App query authority cannot review Actions");
|
|
93
94
|
if (caller.authority) {
|
|
94
95
|
if (caller.authorization || caller.cookie || caller.mandate || caller.transport)
|
|
95
96
|
throw new Error("Use exactly one capability authority source");
|
|
@@ -78,7 +78,8 @@ export const enrollAppKey = async (input: {
|
|
|
78
78
|
format: AppKeyArtifactFormat;
|
|
79
79
|
uid?: number;
|
|
80
80
|
name?: string;
|
|
81
|
-
|
|
81
|
+
grants?: "oauth:issue"[];
|
|
82
|
+
register: (body: { publicKey: string; name?: string; grants?: "oauth:issue"[] }) => Promise<unknown>;
|
|
82
83
|
}) => {
|
|
83
84
|
validateAppKeyAppId(input.appId);
|
|
84
85
|
if (input.uid !== undefined && process.getuid?.() !== 0 && process.getuid?.() !== input.uid)
|
|
@@ -113,7 +114,11 @@ export const enrollAppKey = async (input: {
|
|
|
113
114
|
if ((await deriveAppPublicKey(state.seed)) !== state.publicKey) throw new Error("Enrollment public key does not match its seed");
|
|
114
115
|
await owner(provisional, input.uid);
|
|
115
116
|
const registered = registrationSchema.parse(
|
|
116
|
-
await input.register({
|
|
117
|
+
await input.register({
|
|
118
|
+
publicKey: state.publicKey,
|
|
119
|
+
...(input.name ? { name: input.name } : {}),
|
|
120
|
+
...(input.grants ? { grants: input.grants } : {}),
|
|
121
|
+
}),
|
|
117
122
|
);
|
|
118
123
|
if (registered.fingerprint !== (await appKeyFingerprint(state.publicKey)))
|
|
119
124
|
throw new Error("Core returned a different app-key fingerprint");
|
|
@@ -161,8 +166,11 @@ export const appKeyCommands = [
|
|
|
161
166
|
format: flag.enum(["file", "env", "compose", "k8s-secret"] as const, { default: "file" }),
|
|
162
167
|
uid: flag.int({ min: 0, description: "File owner UID; requires root or the current UID" }),
|
|
163
168
|
name: flag.string({ description: "Human-readable key name" }),
|
|
169
|
+
grant: flag.enum(["oauth:issue"] as const, { description: "Grant OAuth issuance to each new key; repeat on rotation" }),
|
|
170
|
+
noGrant: flag.boolean({ name: "no-grant", description: "Enroll new keys without grants (the default)" }),
|
|
164
171
|
},
|
|
165
172
|
async run({ ctx, args, flags }) {
|
|
173
|
+
if (flags.grant && flags.noGrant) throw new Error("Choose either --grant or --no-grant");
|
|
166
174
|
const apps = await resolveAppKeyApps(args.apps, flags.all, flags.apps);
|
|
167
175
|
const results = [];
|
|
168
176
|
for (const appId of apps)
|
|
@@ -174,6 +182,7 @@ export const appKeyCommands = [
|
|
|
174
182
|
format: flags.format ?? "file",
|
|
175
183
|
uid: flags.uid,
|
|
176
184
|
name: flags.name,
|
|
185
|
+
grants: flags.grant ? [flags.grant] : flags.noGrant ? [] : undefined,
|
|
177
186
|
register: (body) => apiJson(ctx, "POST", keyPath(appId), body),
|
|
178
187
|
}),
|
|
179
188
|
);
|
|
@@ -194,6 +203,14 @@ export const appKeyCommands = [
|
|
|
194
203
|
const result = z
|
|
195
204
|
.object({
|
|
196
205
|
installationId: z.uuid(),
|
|
206
|
+
lastRejectedStart: z
|
|
207
|
+
.object({
|
|
208
|
+
at: z.iso.datetime(),
|
|
209
|
+
reason: z.enum(["revoked", "expired", "disabled", "connections_not_permitted"]),
|
|
210
|
+
keyId: z.uuid().nullable(),
|
|
211
|
+
})
|
|
212
|
+
.nullable()
|
|
213
|
+
.optional(),
|
|
197
214
|
items: z.array(
|
|
198
215
|
z
|
|
199
216
|
.object({ keyId: z.string(), name: z.string(), fingerprint: z.string(), status: z.string(), accountStatus: z.string() })
|
|
@@ -202,7 +219,11 @@ export const appKeyCommands = [
|
|
|
202
219
|
})
|
|
203
220
|
.passthrough()
|
|
204
221
|
.parse(await apiGet(ctx, `${keyPath(args.app)}?page=${flags.page}&perPage=${flags.perPage}`));
|
|
205
|
-
if (!printStructured(ctx, result))
|
|
222
|
+
if (!printStructured(ctx, result)) {
|
|
223
|
+
if (result.lastRejectedStart) {
|
|
224
|
+
const rejection = result.lastRejectedStart;
|
|
225
|
+
ctx.print(`Last rejected start: ${rejection.at} · ${rejection.reason} · key ${rejection.keyId ?? "unknown"}`);
|
|
226
|
+
}
|
|
206
227
|
ctx.table(result.items, [
|
|
207
228
|
{ key: "keyId", label: "Key ID" },
|
|
208
229
|
{ key: "name", label: "Name" },
|
|
@@ -210,6 +231,7 @@ export const appKeyCommands = [
|
|
|
210
231
|
{ key: "status", label: "Status" },
|
|
211
232
|
{ key: "accountStatus", label: "Account" },
|
|
212
233
|
]);
|
|
234
|
+
}
|
|
213
235
|
},
|
|
214
236
|
}),
|
|
215
237
|
command("apps keys revoke", {
|
package/src/config/env.ts
CHANGED
|
@@ -164,13 +164,13 @@ const registry = defineEnv({
|
|
|
164
164
|
schema: z.string(),
|
|
165
165
|
scope: "secret",
|
|
166
166
|
secretFile: true,
|
|
167
|
-
doc: "Core and OAuth
|
|
167
|
+
doc: "Deprecated: Core and OAuth legacy 64-hex broker secret, used only when OAuth has no app key. Configure an OAuth app key instead; retained for this release.",
|
|
168
168
|
},
|
|
169
169
|
CLOUD_APP_KEY: {
|
|
170
170
|
schema: z.string(),
|
|
171
171
|
scope: "secret",
|
|
172
172
|
secretFile: true,
|
|
173
|
-
doc: "App Ed25519 private key for startup
|
|
173
|
+
doc: "App Ed25519 private key for startup bootstrap, background calls and OAuth issuance; alternatively mount CLOUD_APP_KEY_FILE. Requires the direct Core origin. Core and Gateway refuse app keys. Keep infrastructure env until the platform-client import migration is complete.",
|
|
174
174
|
},
|
|
175
175
|
CLOUD_CORE_INTERNAL_ORIGIN: {
|
|
176
176
|
schema: z.string(),
|
|
@@ -180,7 +180,7 @@ const registry = defineEnv({
|
|
|
180
180
|
schema: z.string(),
|
|
181
181
|
scope: "secret",
|
|
182
182
|
secretFile: true,
|
|
183
|
-
doc: "
|
|
183
|
+
doc: "Deprecated: host-run override for the automatically delivered workload credential when no app key is configured; use CLOUD_APP_KEY or CLOUD_APP_KEY_FILE instead. Retained for this release.",
|
|
184
184
|
},
|
|
185
185
|
CLOUD_IDENTITY_JWKS_ORIGIN: {
|
|
186
186
|
schema: z.string(),
|
|
@@ -188,7 +188,7 @@ const registry = defineEnv({
|
|
|
188
188
|
},
|
|
189
189
|
CLOUD_OAUTH_JWKS_ORIGIN: {
|
|
190
190
|
schema: z.string(),
|
|
191
|
-
doc: "
|
|
191
|
+
doc: "Deprecated: explicit legacy OAuth JWKS origin, honored for this release. Unset uses Core internal OAuth JWKS through CLOUD_CORE_INTERNAL_ORIGIN, then the public issuer origin.",
|
|
192
192
|
},
|
|
193
193
|
|
|
194
194
|
// Build and standalone development
|
|
@@ -12,6 +12,7 @@ import { z } from "zod";
|
|
|
12
12
|
import type { RequestAuthority } from "../server/middleware/auth";
|
|
13
13
|
import type { AppEventDefinition, AppEventSubscriptionDefinition } from "./app-events";
|
|
14
14
|
import { CommandPathSchema } from "./commands";
|
|
15
|
+
import { type PlatformPermission, PlatformPermissionSchema } from "./outgoing-mail";
|
|
15
16
|
import type { AccessSubject, RequestActor, User } from "./shared";
|
|
16
17
|
import { AuthenticatedPrincipalSchema } from "./shared";
|
|
17
18
|
|
|
@@ -955,12 +956,12 @@ export type CapabilityPresentationCatalog = {
|
|
|
955
956
|
|
|
956
957
|
/** App authority has no grant-bearing subject, user, nested exchange, or interactive credential. */
|
|
957
958
|
export type CapabilityAppExecutionContext = {
|
|
958
|
-
actor: { kind: "app"; appId: string };
|
|
959
|
+
actor: { kind: "app"; appId: string; keyId?: string };
|
|
959
960
|
accessSubject: { type: "none" };
|
|
960
961
|
user: null;
|
|
961
962
|
authority?: never;
|
|
962
963
|
query?: never;
|
|
963
|
-
idempotencyKey?:
|
|
964
|
+
idempotencyKey?: string;
|
|
964
965
|
locale: string;
|
|
965
966
|
requestId: string;
|
|
966
967
|
origin: CapabilityOrigin;
|
|
@@ -968,8 +969,18 @@ export type CapabilityAppExecutionContext = {
|
|
|
968
969
|
signal: AbortSignal;
|
|
969
970
|
};
|
|
970
971
|
|
|
972
|
+
/** Explicit provider entry for calls paid for and authorized as the calling application. */
|
|
973
|
+
export type CapabilityAppHandler<Input extends z.ZodType, Data extends z.ZodType> = {
|
|
974
|
+
permission: PlatformPermission;
|
|
975
|
+
run: (
|
|
976
|
+
input: z.output<Input>,
|
|
977
|
+
context: CapabilityAppExecutionContext,
|
|
978
|
+
) => CapabilityInvocationResult<z.output<Data>> | Promise<CapabilityInvocationResult<z.output<Data>>>;
|
|
979
|
+
};
|
|
980
|
+
|
|
971
981
|
export type CapabilityQueryDefinition<Input extends z.ZodType = z.ZodType<any>, Data extends z.ZodType = z.ZodType<any>> = {
|
|
972
982
|
appOnly?: never;
|
|
983
|
+
app?: CapabilityAppHandler<Input, Data>;
|
|
973
984
|
title: string;
|
|
974
985
|
description: string;
|
|
975
986
|
input: Input;
|
|
@@ -988,6 +999,7 @@ export type CapabilityActionApprovalPolicy = "rememberable" | "none";
|
|
|
988
999
|
|
|
989
1000
|
export type CapabilityActionDefinition<Input extends z.ZodType = z.ZodType<any>, Data extends z.ZodType = z.ZodType<any>> = {
|
|
990
1001
|
appOnly?: never;
|
|
1002
|
+
app?: CapabilityAppHandler<Input, Data>;
|
|
991
1003
|
title: string;
|
|
992
1004
|
description: string;
|
|
993
1005
|
input: Input;
|
|
@@ -1104,6 +1116,7 @@ const CapabilityOperationManifestBaseSchema = z
|
|
|
1104
1116
|
inputSchema: z.record(z.string(), z.unknown()),
|
|
1105
1117
|
dataSchema: z.record(z.string(), z.unknown()),
|
|
1106
1118
|
stream: CapabilityStreamPolicySchema.optional(),
|
|
1119
|
+
appPermission: PlatformPermissionSchema.optional(),
|
|
1107
1120
|
schemaHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
1108
1121
|
})
|
|
1109
1122
|
.strict();
|
|
@@ -2,7 +2,7 @@ import type { PageParams, Paginated, ServiceError } from "@k2b/stdlib";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { type RequestActor, ServiceAccountSchema, UserSchema } from "./shared";
|
|
4
4
|
|
|
5
|
-
export const PlatformPermissionSchema = z.enum(["mail:send", "mail:read", "resources:readers"]);
|
|
5
|
+
export const PlatformPermissionSchema = z.enum(["mail:send", "mail:read", "resources:readers", "geo:query"]);
|
|
6
6
|
export type PlatformPermission = z.infer<typeof PlatformPermissionSchema>;
|
|
7
7
|
export const MailRetentionSchema = z.object({ contentDays: z.int().min(1).max(36500), recordDays: z.int().min(1).max(36500) }).strict();
|
|
8
8
|
export type MailRetention = z.infer<typeof MailRetentionSchema> & {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Topic, TopicConfig } from "@k2b/sync";
|
|
2
|
-
import { sql } from "bun";
|
|
3
2
|
import { assertAppEventSize } from "../_internal/app-event-schemas";
|
|
4
3
|
import { lazySync } from "../_internal/process-sync";
|
|
5
4
|
import { type AppEventEnvelope, AppEventEnvelopeSchema } from "../contracts/app-events";
|
|
5
|
+
import { sql } from "../runtime/clients";
|
|
6
6
|
import { logger } from "../services/logging";
|
|
7
7
|
import { createPgOutbox } from "../services/outbox";
|
|
8
8
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Topic, TopicConfig } from "@k2b/sync";
|
|
2
|
-
import {
|
|
2
|
+
import type { SQL } from "bun";
|
|
3
3
|
import type { z } from "zod";
|
|
4
4
|
import { lazySync } from "../_internal/process-sync";
|
|
5
|
+
import { sql } from "../runtime/clients";
|
|
5
6
|
import { logger } from "../services/logging";
|
|
6
7
|
import { createPgOutbox } from "../services/outbox";
|
|
7
8
|
import type { LiveEnvelope } from "./live-engine";
|
|
@@ -42,12 +42,16 @@ export const bootstrapBodyDigest = async (body: Uint8Array<ArrayBuffer>): Promis
|
|
|
42
42
|
Buffer.from(await crypto.subtle.digest("SHA-256", body)).toString("base64url");
|
|
43
43
|
|
|
44
44
|
/** Call at the HTTP transmission seam, including every retry. */
|
|
45
|
-
export const
|
|
45
|
+
export const signAppProof = async (
|
|
46
|
+
key: AppKey,
|
|
47
|
+
request: { method: string; path: string; body: Uint8Array<ArrayBuffer> },
|
|
48
|
+
now = Date.now(),
|
|
49
|
+
): Promise<string> => {
|
|
46
50
|
const iat = Math.floor(now / 1000);
|
|
47
51
|
return new SignJWT({
|
|
48
|
-
htm:
|
|
49
|
-
htu:
|
|
50
|
-
bdh: await bootstrapBodyDigest(
|
|
52
|
+
htm: request.method,
|
|
53
|
+
htu: request.path,
|
|
54
|
+
bdh: await bootstrapBodyDigest(request.body),
|
|
51
55
|
})
|
|
52
56
|
.setProtectedHeader({ alg: "EdDSA", typ: BOOTSTRAP_TYPE, kid: key.keyId })
|
|
53
57
|
.setIssuer(key.appId)
|
|
@@ -58,3 +62,6 @@ export const signBootstrapProof = async (key: AppKey, now = Date.now()): Promise
|
|
|
58
62
|
.setJti(crypto.randomUUID())
|
|
59
63
|
.sign(await importAppKeySeed(key.seed));
|
|
60
64
|
};
|
|
65
|
+
|
|
66
|
+
export const signBootstrapProof = (key: AppKey, now = Date.now()): Promise<string> =>
|
|
67
|
+
signAppProof(key, { method: "POST", path: BOOTSTRAP_PATH, body: new TextEncoder().encode(BOOTSTRAP_BODY) }, now);
|
package/src/runtime/clients.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RedisClient, type RedisOptions, SQL } from "bun";
|
|
2
2
|
import { postgresApplicationOptions } from "../_internal/postgres-application-name";
|
|
3
|
-
import { connections
|
|
3
|
+
import { connections } from "./connections";
|
|
4
4
|
|
|
5
5
|
export type SqlPoolOptions = SQL.PostgresOrMySQLOptions;
|
|
6
6
|
|
|
@@ -28,6 +28,8 @@ const notStarted = <T>(name: string): T => {
|
|
|
28
28
|
|
|
29
29
|
export let sql: SQL = notStarted("sql");
|
|
30
30
|
export let redis: RedisClient = notStarted("redis");
|
|
31
|
+
// Whether `sql` and `redis` hold native clients; teardown must not touch the placeholders.
|
|
32
|
+
let live = false;
|
|
31
33
|
|
|
32
34
|
/** Own pool and lifecycle; defaults to the installation URL and application name. */
|
|
33
35
|
export const createSql = (options: SqlPoolOptions = {}): SQL => {
|
|
@@ -55,6 +57,7 @@ export const initializeClients = async (): Promise<void> => {
|
|
|
55
57
|
const valkey = createRedis();
|
|
56
58
|
sql = database;
|
|
57
59
|
redis = valkey;
|
|
60
|
+
live = true;
|
|
58
61
|
} catch {
|
|
59
62
|
await database.close();
|
|
60
63
|
throw new Error("Valkey client configuration is invalid; check REDIS_URL and native Redis options");
|
|
@@ -63,7 +66,8 @@ export const initializeClients = async (): Promise<void> => {
|
|
|
63
66
|
|
|
64
67
|
/** Internal test-target teardown, before a fixture selects a different database. */
|
|
65
68
|
export const closePlatformClients = async (): Promise<void> => {
|
|
66
|
-
if (
|
|
69
|
+
if (live) {
|
|
70
|
+
live = false;
|
|
67
71
|
await sql.close();
|
|
68
72
|
redis.close();
|
|
69
73
|
}
|
package/src/server/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { buildMetadata } from "../_internal/build-metadata";
|
|
2
2
|
export { loadPendingApprovals } from "../api/approvals-loader";
|
|
3
3
|
export { geoClient } from "../capabilities/geo-client";
|
|
4
|
+
export { appCaller } from "../capabilities/server-invoke";
|
|
4
5
|
export { expectUserBackedActor, getUserBackedActor, isAppSessionActor, userFromActor } from "./actor";
|
|
5
6
|
export type { ApiErrorBody, ApiErrorResponse, ApiErrorStatus } from "./api";
|
|
6
7
|
export { api, respond, respondMessage } from "./api";
|
package/src/server/ratelimit.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { err, fail, ok, type Result } from "@k2b/stdlib";
|
|
2
|
-
import {
|
|
2
|
+
import type { SQLQuery } from "bun";
|
|
3
3
|
import { type ManagerCandidate, removesLastManager } from "../../access/managers";
|
|
4
4
|
import { accessMessages } from "../../access/messages";
|
|
5
|
+
import { sql } from "../../runtime/clients";
|
|
5
6
|
import { recursiveGroupIdsSubquery, recursiveServiceAccountGroupIdsSubquery } from "../../services/accounts/group-sql";
|
|
6
7
|
import { toPgUuidArray } from "../../services/postgres";
|
|
7
8
|
import type { ServiceAccountKind } from "../../services/service-accounts";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { sql } from "bun";
|
|
2
1
|
import {
|
|
3
2
|
type AccountCategory,
|
|
4
3
|
type AccountCategoryPolicy,
|
|
5
4
|
accountCategory,
|
|
6
5
|
DEFAULT_ACCOUNT_CATEGORY_POLICY,
|
|
7
6
|
} from "../contracts/account-categories";
|
|
7
|
+
import { sql } from "../runtime/clients";
|
|
8
8
|
import { decryptValue } from "./settings/crypto";
|
|
9
9
|
|
|
10
10
|
const prefix = "user.category.";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { sql } from "bun";
|
|
2
1
|
import type { User } from "../../contracts/shared";
|
|
2
|
+
import { sql } from "../../runtime/clients";
|
|
3
3
|
import { err, fail, freeipa, ok, type Result } from "../../server/services";
|
|
4
4
|
import { dates } from "../../shared";
|
|
5
5
|
import { getConfiguredExpiryDays, parseIpaAccountTransitionPolicy } from "../account-model";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { JobContext, PumpConfig, Sync } from "@k2b/sync";
|
|
2
|
-
import { sql } from "
|
|
2
|
+
import { sql } from "../../runtime/clients";
|
|
3
|
+
|
|
3
4
|
import { freeipa } from "../../server/services/freeipa";
|
|
4
5
|
import { getConfiguredExpiryDays } from "../account-model";
|
|
5
6
|
import { getFreeIpaConfig } from "../freeipa-config";
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { sql } from "bun";
|
|
2
1
|
import { accountCategory } from "../../contracts/account-categories";
|
|
3
2
|
import type { PersonAvatar } from "../../contracts/avatar";
|
|
4
3
|
import type {
|
|
@@ -14,6 +13,7 @@ import type {
|
|
|
14
13
|
UserProfile,
|
|
15
14
|
UserProvider,
|
|
16
15
|
} from "../../contracts/shared";
|
|
16
|
+
import { sql } from "../../runtime/clients";
|
|
17
17
|
import type { ServiceAccountRequestActor } from "../../server/middleware/auth";
|
|
18
18
|
import {
|
|
19
19
|
err,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { sql } from "bun";
|
|
3
2
|
import {
|
|
4
3
|
type AvatarConfig,
|
|
5
4
|
type AvatarKind,
|
|
@@ -12,6 +11,7 @@ import {
|
|
|
12
11
|
} from "../../contracts/avatar";
|
|
13
12
|
import { MAX_AVATAR_BYTES, MAX_AVATAR_DATA_URL_LENGTH } from "../../contracts/profile";
|
|
14
13
|
import type { MutationResult } from "../../contracts/shared";
|
|
14
|
+
import { sql } from "../../runtime/clients";
|
|
15
15
|
import { AVATAR_RENDERER_REVISION, renderAvatarSvg } from "../../shared/avatar-art";
|
|
16
16
|
import { stripImageMetadata } from "../image-metadata";
|
|
17
17
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { err, fail, ok, type Result } from "@k2b/stdlib";
|
|
2
|
-
import { sql } from "bun";
|
|
3
2
|
import { z } from "zod";
|
|
4
3
|
import type { User } from "../../contracts/shared";
|
|
4
|
+
import { sql } from "../../runtime/clients";
|
|
5
5
|
import type { RequestActor } from "../../server/middleware/auth";
|
|
6
6
|
import { audit } from "../audit";
|
|
7
7
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { err, fail, ok, type PageParams, type Paginated, paginate, type Result } from "@k2b/stdlib";
|
|
2
|
-
import { sql } from "bun";
|
|
3
2
|
import type { User } from "../../contracts/shared";
|
|
3
|
+
import { sql } from "../../runtime/clients";
|
|
4
4
|
import { type AccountsActor, isAdminActor } from "./authz";
|
|
5
5
|
|
|
6
6
|
export type DuplicateEmailUser = Pick<
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { SQLQuery } from "bun";
|
|
2
2
|
import type { EntityKind, EntityListItem, UserProfile, UserProvider } from "../../contracts/shared";
|
|
3
|
+
import { sql } from "../../runtime/clients";
|
|
3
4
|
import { getFreeIpaConfig } from "../freeipa-config";
|
|
4
5
|
import { escapeLikePattern, toPgTextArray, toPgUuidArray } from "../postgres";
|
|
5
6
|
import type { ServiceAccountKind, ServiceAccountStatus } from "../service-accounts";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { sql } from "bun";
|
|
2
1
|
import type { BaseGroup, GroupMember, MutationResult, UserProvider } from "../../contracts/shared";
|
|
2
|
+
import { sql } from "../../runtime/clients";
|
|
3
3
|
import { freeipa } from "../../server/services";
|
|
4
4
|
import { getServiceIpaSession } from "../ipa/service-account";
|
|
5
5
|
import { toPgUuidArray } from "../postgres";
|