@k2b/cloud 0.41.0 → 0.42.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 +6 -5
- package/src/_internal/bounded-json.ts +14 -2
- package/src/_internal/capabilities.ts +87 -8
- package/src/_internal/capability-reviews.ts +6 -0
- package/src/_internal/capability-timeout.ts +71 -0
- package/src/_internal/define-app.ts +145 -89
- package/src/_internal/http-drain.ts +56 -0
- package/src/_internal/registry-validation.ts +3 -0
- package/src/_internal/runtime-context.ts +1 -0
- package/src/_internal/status-preserving-ssr.ts +2 -0
- package/src/access/PermissionEditor.tsx +16 -17
- package/src/access/PrincipalPicker.tsx +45 -10
- package/src/access/messages.ts +8 -0
- package/src/access/principal-label.ts +18 -0
- package/src/access/ui.ts +1 -0
- package/src/account/EntitySearch.tsx +5 -4
- package/src/ai/action-review.ts +2 -1
- package/src/ai/app-skill-store.ts +1 -1
- package/src/ai/capabilities.ts +21 -3
- package/src/ai/capability-execution.ts +14 -12
- package/src/ai/chat/action-card.ts +7 -1
- package/src/ai/chat/action-messages.ts +14 -0
- package/src/ai/chat/action-question.browser-harness.tsx +12 -0
- package/src/ai/chat/action-question.tsx +202 -22
- package/src/ai/chat/action-text.ts +1 -1
- package/src/ai/chat-tasks.ts +2 -1
- package/src/ai/client/controller.ts +150 -32
- package/src/ai/client/transport.ts +14 -2
- package/src/ai/code-capability-routes.ts +27 -23
- package/src/ai/code-runtime-tools.ts +14 -16
- package/src/ai/code-source-tools.ts +16 -18
- package/src/ai/executor.ts +56 -21
- package/src/ai/fetch-file-tool.ts +67 -76
- package/src/ai/http.ts +1 -0
- package/src/ai/migrate.ts +13 -0
- package/src/ai/open-questions.ts +4 -8
- package/src/ai/projects.ts +1 -2
- package/src/ai/protocol.ts +2 -2
- package/src/ai/recent-resources.ts +12 -5
- package/src/ai/review-content.ts +77 -0
- package/src/ai/routes.ts +69 -31
- package/src/ai/runtime.ts +1 -0
- package/src/ai/skill-tool.ts +17 -6
- package/src/ai/skills-routes.ts +72 -27
- package/src/ai/skills.ts +139 -60
- package/src/ai/store.ts +168 -33
- package/src/ai/types.ts +10 -3
- package/src/ai/usage.ts +2 -2
- package/src/api/admin-core-settings.ts +15 -2
- package/src/api/admin-geo.ts +54 -0
- package/src/api/admin-linux-identities.ts +6 -3
- package/src/api/admin-outgoing-mail.ts +29 -15
- package/src/api/approvals-loader.ts +3 -3
- package/src/api/approvals.ts +51 -0
- package/src/api/apps.ts +4 -2
- package/src/api/capabilities.ts +258 -181
- package/src/api/capability-streams.ts +21 -13
- package/src/api/cli-plugins.ts +4 -1
- package/src/api/geo-glyphs.ts +11 -0
- package/src/api/geo.ts +128 -0
- package/src/api/help.ts +12 -4
- package/src/api/index.ts +5 -0
- package/src/api/mcp.ts +46 -38
- package/src/api/me-panel-consent.ts +8 -5
- package/src/api/search.ts +21 -14
- package/src/api/sync-ops.ts +19 -13
- package/src/api/widgets.ts +42 -18
- package/src/approvals/Approvals.tsx +5 -3
- package/src/browser/CloudResourceSearch.browser-harness.tsx +53 -1
- package/src/browser/CloudResourceSearch.tsx +3 -13
- package/src/browser/PanelLeaveNotice.tsx +18 -0
- package/src/browser/navigation-search.ts +78 -25
- package/src/browser/panel-leave.ts +69 -0
- package/src/browser/panel-messages.ts +12 -0
- package/src/browser/panel-slot.ts +173 -24
- package/src/browser/panel.ts +3 -0
- package/src/browser/resource-search-messages.ts +2 -0
- package/src/capabilities/client.ts +28 -1
- package/src/capabilities/executions.ts +22 -3
- package/src/capabilities/geo-client.ts +57 -0
- package/src/capabilities/response.ts +2 -1
- package/src/capabilities/review-storage.ts +24 -0
- package/src/capabilities/reviews.ts +17 -3
- package/src/capabilities/server.ts +36 -16
- package/src/cli/access.ts +15 -23
- package/src/cli/admin/linux.ts +13 -2
- package/src/cli/admin/outgoing-mail.ts +26 -4
- package/src/cli/capabilities.ts +19 -0
- package/src/config/env.ts +0 -6
- package/src/contracts/app-access.ts +48 -0
- package/src/contracts/app.ts +4 -1
- package/src/contracts/approvals.ts +2 -2
- package/src/contracts/avatar.ts +186 -0
- package/src/contracts/capabilities.ts +148 -54
- package/src/contracts/geo-admin.ts +127 -0
- package/src/contracts/geo-map.ts +19 -0
- package/src/contracts/geo-route.ts +168 -0
- package/src/contracts/geo.ts +248 -0
- package/src/contracts/index.ts +4 -0
- package/src/contracts/outgoing-mail.ts +17 -4
- package/src/contracts/registry.ts +2 -0
- package/src/events/app-access.ts +68 -0
- package/src/events/live-delivery.ts +128 -0
- package/src/events/live.ts +14 -130
- package/src/geo/CloudMap.tsx +95 -0
- package/src/geo/map-messages.ts +17 -0
- package/src/geo/ui.ts +1 -0
- package/src/index.ts +2 -0
- package/src/server/client-ip.ts +36 -0
- package/src/server/index.ts +3 -4
- package/src/server/middleware/app-access.ts +312 -0
- package/src/server/middleware/auth.ts +1 -1
- package/src/server/middleware/invocation.ts +11 -0
- package/src/server/middleware/rate-limit.ts +4 -4
- package/src/server/services/access.ts +13 -9
- package/src/server/services/index.ts +0 -2
- package/src/server/services/services.ts +0 -2
- package/src/services/accounts/posix.ts +21 -7
- package/src/services/allowed-network-fetch.ts +16 -10
- package/src/services/app-access-metrics.ts +24 -0
- package/src/services/app-access.ts +123 -0
- package/src/services/app-discovery.ts +27 -0
- package/src/services/approvals/content.ts +40 -0
- package/src/services/approvals/index.ts +14 -6
- package/src/services/approvals/list.ts +6 -11
- package/src/services/approvals/question.ts +11 -7
- package/src/services/downstream-budget.ts +87 -0
- package/src/services/geo/admin.ts +262 -0
- package/src/services/geo/bucket.ts +67 -0
- package/src/services/geo/cache.ts +57 -0
- package/src/services/geo/costs.ts +5 -0
- package/src/services/geo/errors.ts +41 -0
- package/src/services/geo/fixtures/README.md +17 -0
- package/src/services/geo/fixtures/along.json +355 -0
- package/src/services/geo/fixtures/autocomplete.json +218 -0
- package/src/services/geo/fixtures/geocode.json +129 -0
- package/src/services/geo/fixtures/places.json +355 -0
- package/src/services/geo/fixtures/reverse.json +61 -0
- package/src/services/geo/fixtures/routing.json +57 -0
- package/src/services/geo/geoapify-route.ts +93 -0
- package/src/services/geo/geoapify.ts +281 -0
- package/src/services/geo/ledger.ts +122 -0
- package/src/services/geo/map.ts +160 -0
- package/src/services/geo/migrate.ts +25 -0
- package/src/services/geo/polyline.ts +184 -0
- package/src/services/geo/route-chain.ts +145 -0
- package/src/services/geo/route-costs.ts +9 -0
- package/src/services/geo/route-geometry.ts +39 -0
- package/src/services/geo/route-plan.ts +172 -0
- package/src/services/geo/route-service.ts +433 -0
- package/src/services/geo/service.ts +300 -0
- package/src/services/geo/settings.ts +49 -0
- package/src/services/help/index.ts +16 -7
- package/src/services/help/types.ts +6 -1
- package/src/services/identity/invocation-actor.ts +20 -2
- package/src/services/identity/issue-invocation.ts +67 -0
- package/src/services/identity/key-ring.ts +5 -3
- package/src/services/identity/runtime-config.ts +23 -0
- package/src/services/index.ts +9 -0
- package/src/services/mandates/index.ts +40 -3
- package/src/services/network-security.ts +6 -3
- package/src/services/network-transport.ts +99 -0
- package/src/services/notifications/catalog.ts +3 -2
- package/src/services/notifications/runtime.ts +1 -0
- package/src/services/notifications/web-push-transport.ts +45 -38
- package/src/services/outgoing-mail/dispatcher.ts +6 -1
- package/src/services/outgoing-mail/index.ts +55 -1
- package/src/services/outgoing-mail/messages.ts +82 -3
- package/src/services/outgoing-mail/resolve-host.ts +2 -2
- package/src/services/outgoing-mail/retention.ts +13 -1
- package/src/services/outgoing-mail/store.ts +36 -6
- package/src/services/outgoing-mail/test-send.ts +34 -4
- package/src/services/outgoing-mail/transport.ts +1 -1
- package/src/services/pdf/gotenberg.ts +11 -38
- package/src/services/pdf/template-preview.ts +1 -1
- package/src/services/public-http.ts +28 -40
- package/src/services/settings/app.ts +4 -1
- package/src/services/settings/core-settings.ts +88 -0
- package/src/services/settings/defaults.ts +8 -2
- package/src/services/settings/errors.ts +11 -0
- package/src/services/settings/geo-budget.ts +40 -0
- package/src/services/settings/index.ts +4 -3
- package/src/services/settings/store.ts +25 -17
- package/src/services/weather/geo.ts +29 -111
- package/src/services/weather/index.ts +4 -3
- package/src/services/weather/locations.ts +8 -5
- package/src/services/weather/migrate.ts +1 -0
- package/src/services/weather/settings.ts +0 -16
- package/src/shared/__snapshots__/avatar-art.test.ts.snap +5 -0
- package/src/shared/avatar-art.ts +400 -0
- package/src/shared/index.ts +2 -0
- package/src/shared/network-address.ts +22 -3
- package/src/shared/template-rendering.ts +36 -4
- package/src/shared-text/documents.ts +1 -1
- package/src/shared-text/index.ts +2 -0
- package/src/shared-text/log.ts +51 -3
- package/src/shared-text/snapshots.ts +340 -0
- package/src/ssr/AdminLayout.tsx +4 -2
- package/src/ssr/CloudShell.island.tsx +6 -0
- package/src/ssr/Layout.tsx +65 -15
- package/src/ssr/LayoutHelp.browser-harness.tsx +1 -1
- package/src/ssr/LayoutHelp.tsx +3 -2
- package/src/ssr/PanelRuntime.island.tsx +11 -7
- package/src/ssr/PwaLayout.tsx +2 -2
- package/src/ssr/RailApps.tsx +6 -0
- package/src/ssr/ShellRuntime.island.tsx +23 -7
- package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +151 -0
- package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +6 -6
- package/src/ssr/admin-navigation.ts +1 -0
- package/src/ssr/app-badges.ts +6 -1
- package/src/ssr/compact-shell.browser-harness.ts +44 -5
- package/src/ssr/help.ts +3 -2
- package/src/ssr/index.ts +1 -1
- package/src/ssr/layout-context.ts +2 -0
- package/src/ssr/mobile-menu-history.ts +4 -0
- package/src/ssr/page-swap-fixture/Column.island.tsx +15 -0
- package/src/ssr/page-swap-fixture/ProviderProbe.island.tsx +31 -0
- package/src/ssr/page-swap.browser-harness.tsx +177 -0
- package/src/ssr/page-swap.ts +28 -0
- package/src/ssr/page-trail-runtime.ts +32 -13
- package/src/ssr/panel-consent.ts +2 -2
- package/src/ssr/panel-slot.browser-harness.tsx +3 -0
- package/src/ssr/platform-messages.ts +8 -0
- package/src/ssr/runtime.ts +17 -0
- package/src/ssr/shell-data.tsx +10 -2
- package/src/styles/global.css +70 -6
- package/src/server/services/geo.ts +0 -215
|
@@ -15,9 +15,10 @@ import {
|
|
|
15
15
|
capabilityResultSchema,
|
|
16
16
|
} from "../contracts/capabilities";
|
|
17
17
|
import type { RequestAuthority } from "../server";
|
|
18
|
+
import { AppAccessDeniedError, appAccessDeniedBody } from "../server/middleware/app-access";
|
|
18
19
|
import { invocationAuthorityFromRequest } from "../services/identity/invocation-authority";
|
|
19
20
|
import { capabilityInvocationOperation } from "../services/identity/invocation-operations";
|
|
20
|
-
import { signInvocationToken } from "../services/identity/invocation
|
|
21
|
+
import { issueInvocationToken as signInvocationToken } from "../services/identity/issue-invocation";
|
|
21
22
|
import { withActiveIdentitySigner } from "../services/identity/key-ring";
|
|
22
23
|
import { logger } from "../services/logging";
|
|
23
24
|
import { decryptSecret, encryptSecret } from "../services/secrets";
|
|
@@ -142,17 +143,20 @@ export async function dispatchCapabilityStream(
|
|
|
142
143
|
destructive = "destructive" in operation && operation.destructive;
|
|
143
144
|
const signed = await (dependencies.withActiveSigner ?? withActiveIdentitySigner)(
|
|
144
145
|
"invocation",
|
|
145
|
-
(signer) =>
|
|
146
|
-
(dependencies.signInvocation ?? signInvocationToken)(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
146
|
+
(signer, db) =>
|
|
147
|
+
(dependencies.signInvocation ?? signInvocationToken)(
|
|
148
|
+
{
|
|
149
|
+
targetAppId: grant.appId,
|
|
150
|
+
callingAppId: "core",
|
|
151
|
+
operation: capabilityInvocationOperation(grant.kind, grant.capabilityId),
|
|
152
|
+
schemaHash: operation.schemaHash,
|
|
153
|
+
authority: invocationAuthorityFromRequest(authority),
|
|
154
|
+
requestId: grant.requestId,
|
|
155
|
+
signer,
|
|
156
|
+
issuer: signer.issuer,
|
|
157
|
+
},
|
|
158
|
+
{ db, groupsAdmin: signer.groupsAdmin },
|
|
159
|
+
),
|
|
156
160
|
{ signal, timeoutMs: 30_000 },
|
|
157
161
|
);
|
|
158
162
|
const headers = new Headers({
|
|
@@ -265,7 +269,11 @@ export async function dispatchCapabilityStream(
|
|
|
265
269
|
if (!error.success || response.status === 401) return denied(status, "STREAM_FAILED");
|
|
266
270
|
} else await record(response.status);
|
|
267
271
|
return Response.json(receipt.data, { status: response.status, headers: { "cache-control": "no-store" } });
|
|
268
|
-
} catch {
|
|
272
|
+
} catch (error) {
|
|
273
|
+
if (error instanceof AppAccessDeniedError) {
|
|
274
|
+
await record(403, error.code);
|
|
275
|
+
return Response.json(appAccessDeniedBody(error.app), { status: 403 });
|
|
276
|
+
}
|
|
269
277
|
const status = request.signal.aborted ? 499 : signal.aborted ? 504 : 502;
|
|
270
278
|
const code = status === 499 ? "REQUEST_CANCELLED" : status === 504 ? "DEADLINE_EXCEEDED" : "STREAM_FAILED";
|
|
271
279
|
await record(status, code);
|
package/src/api/cli-plugins.ts
CHANGED
|
@@ -4,13 +4,16 @@ import { listApps } from "../_internal/registry";
|
|
|
4
4
|
import type { CloudCliPluginSummary } from "../cli/plugin";
|
|
5
5
|
import type { AuthContext } from "../server/middleware/auth";
|
|
6
6
|
|
|
7
|
+
import { appAccessAuthority } from "../services/app-access";
|
|
8
|
+
import { discoverableApps } from "../services/app-discovery";
|
|
9
|
+
|
|
7
10
|
/**
|
|
8
11
|
* `GET /cli/plugins`: the `cld` plugins that live applications serve, under
|
|
9
12
|
* the same access rule as the plugin downloads. Core mounts this list; each
|
|
10
13
|
* application serves its own plugin files.
|
|
11
14
|
*/
|
|
12
15
|
export const cliPluginListRoutes = new Hono<AuthContext>().get("/", requireCliPluginAccess, async (c) => {
|
|
13
|
-
const plugins: CloudCliPluginSummary[] = (await listApps())
|
|
16
|
+
const plugins: CloudCliPluginSummary[] = (await discoverableApps(appAccessAuthority(c), await listApps()))
|
|
14
17
|
.flatMap((app) => (app.cliModules ?? []).map((name) => ({ name, app: app.id, version: app.runtime?.version ?? "unknown" })))
|
|
15
18
|
.sort((left, right) => (left.name < right.name ? -1 : left.name > right.name ? 1 : 0));
|
|
16
19
|
return c.json({ plugins }, 200, { "cache-control": "private, no-store" });
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { appAssetPath } from "../server/app-assets";
|
|
2
|
+
|
|
3
|
+
declare const __CLOUD_APP_ASSETS__: string | undefined;
|
|
4
|
+
|
|
5
|
+
/** The route validates the font and range before resolving a bundled asset. */
|
|
6
|
+
export const geoGlyphFile = (font: string, range: string) =>
|
|
7
|
+
Bun.file(
|
|
8
|
+
typeof __CLOUD_APP_ASSETS__ === "string"
|
|
9
|
+
? appAssetPath("map-glyphs", font, range)
|
|
10
|
+
: new URL(`./map-glyphs/${encodeURIComponent(font)}/${range}`, import.meta.resolve("@k2b/ui/package.json")),
|
|
11
|
+
);
|
package/src/api/geo.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { mapStyle } from "@k2b/ui";
|
|
2
|
+
import { Hono, type MiddlewareHandler } from "hono";
|
|
3
|
+
import { describeRoute } from "hono-openapi";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { GEO_MAP_MAX_ZOOM } from "../contracts/geo-map";
|
|
6
|
+
import { type AuthContext, auth, getLocale, requiresAuth, v } from "../server";
|
|
7
|
+
import { REQUEST_IDLE_TIMEOUT_SECONDS, setRequestTimeout } from "../server/request-timeout";
|
|
8
|
+
import { GEO_TIMEOUT_MS } from "../services/geo/bucket";
|
|
9
|
+
import { GeoError } from "../services/geo/errors";
|
|
10
|
+
import { coreGeoMap } from "../services/geo/map";
|
|
11
|
+
|
|
12
|
+
import { geoGlyphFile } from "./geo-glyphs";
|
|
13
|
+
|
|
14
|
+
const styleQuery = z.object({
|
|
15
|
+
theme: z.enum(["light", "dark"]).default("light"),
|
|
16
|
+
locale: z
|
|
17
|
+
.string()
|
|
18
|
+
.max(100)
|
|
19
|
+
.refine((locale) => {
|
|
20
|
+
try {
|
|
21
|
+
new Intl.Locale(locale);
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
.optional(),
|
|
28
|
+
});
|
|
29
|
+
const tileParams = z.object({
|
|
30
|
+
z: z.coerce.number().int().min(0).max(GEO_MAP_MAX_ZOOM),
|
|
31
|
+
x: z.coerce.number().int().nonnegative(),
|
|
32
|
+
y: z.string().regex(/^\d+\.pbf$/),
|
|
33
|
+
});
|
|
34
|
+
const glyphParams = z.object({ font: z.literal("IBM Plex Sans Medium"), range: z.string().regex(/^\d+-\d+\.pbf$/) });
|
|
35
|
+
|
|
36
|
+
export const createGeoRoutes = (
|
|
37
|
+
authenticate: MiddlewareHandler<AuthContext> = auth.requireRole("authenticated"),
|
|
38
|
+
service: typeof coreGeoMap = coreGeoMap,
|
|
39
|
+
) =>
|
|
40
|
+
new Hono<AuthContext>()
|
|
41
|
+
.use("*", authenticate)
|
|
42
|
+
.use("*", auth.requireOAuthScope("read", "admin"))
|
|
43
|
+
.onError((cause, c) => {
|
|
44
|
+
const error = cause instanceof GeoError ? cause : new GeoError("GEO_PROVIDER_FAILED");
|
|
45
|
+
c.header("Cache-Control", "private, no-store");
|
|
46
|
+
if (error.code === "GEO_BUSY") c.header("Retry-After", String(error.details.retryAfterSeconds ?? 1));
|
|
47
|
+
return Response.json(
|
|
48
|
+
{ code: error.code, details: error.details },
|
|
49
|
+
{
|
|
50
|
+
status: error.status,
|
|
51
|
+
headers: {
|
|
52
|
+
"Cache-Control": "private, no-store",
|
|
53
|
+
...(error.code === "GEO_BUSY" ? { "Retry-After": String(error.details.retryAfterSeconds ?? 1) } : {}),
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
})
|
|
58
|
+
.get(
|
|
59
|
+
"/style",
|
|
60
|
+
describeRoute({ ...requiresAuth, tags: ["Places and maps"], summary: "Get the Cloud map style with same-origin tiles and glyphs" }),
|
|
61
|
+
v("query", styleQuery),
|
|
62
|
+
async (c) => {
|
|
63
|
+
const query = c.req.valid("query");
|
|
64
|
+
const locale = query.locale ?? getLocale(c);
|
|
65
|
+
await service.style({ theme: query.theme }, { actor: c.get("actor"), accessSubject: c.get("accessSubject"), locale });
|
|
66
|
+
c.header("Cache-Control", "private, no-store");
|
|
67
|
+
return c.json(
|
|
68
|
+
mapStyle({
|
|
69
|
+
theme: query.theme,
|
|
70
|
+
locale,
|
|
71
|
+
tiles: "/api/geo/tiles/{z}/{x}/{y}.pbf",
|
|
72
|
+
glyphs: "/api/geo/glyphs/{fontstack}/{range}.pbf",
|
|
73
|
+
maxzoom: GEO_MAP_MAX_ZOOM,
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
},
|
|
77
|
+
)
|
|
78
|
+
.get(
|
|
79
|
+
"/tiles/:z/:x/:y",
|
|
80
|
+
describeRoute({ ...requiresAuth, tags: ["Places and maps"], summary: "Load one budgeted Geoapify EU vector tile" }),
|
|
81
|
+
v("param", tileParams),
|
|
82
|
+
async (c) => {
|
|
83
|
+
const params = c.req.valid("param");
|
|
84
|
+
setRequestTimeout(c, Math.ceil(GEO_TIMEOUT_MS.tiles / 1000) + REQUEST_IDLE_TIMEOUT_SECONDS);
|
|
85
|
+
try {
|
|
86
|
+
const bytes = await service.tile(
|
|
87
|
+
{ z: params.z, x: params.x, y: Number(params.y.slice(0, -4)) },
|
|
88
|
+
{
|
|
89
|
+
actor: c.get("actor"),
|
|
90
|
+
accessSubject: c.get("accessSubject"),
|
|
91
|
+
locale: getLocale(c),
|
|
92
|
+
signal: c.req.raw.signal,
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
return new Response(bytes, {
|
|
96
|
+
headers: {
|
|
97
|
+
"Content-Type": "application/vnd.mapbox-vector-tile",
|
|
98
|
+
"Cache-Control": "private, max-age=604800",
|
|
99
|
+
"X-Content-Type-Options": "nosniff",
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
} finally {
|
|
103
|
+
setRequestTimeout(c, REQUEST_IDLE_TIMEOUT_SECONDS);
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
.get(
|
|
108
|
+
"/glyphs/:font/:range",
|
|
109
|
+
describeRoute({ ...requiresAuth, tags: ["Places and maps"], summary: "Load locally bundled IBM Plex map labels" }),
|
|
110
|
+
v("param", glyphParams),
|
|
111
|
+
async (c) => {
|
|
112
|
+
const { font, range } = c.req.valid("param");
|
|
113
|
+
const [start, end] = range.slice(0, -4).split("-").map(Number);
|
|
114
|
+
if (start === undefined || end === undefined || start < 0 || end > 65535 || start % 256 !== 0 || end !== start + 255)
|
|
115
|
+
return c.notFound();
|
|
116
|
+
const file = geoGlyphFile(font, range);
|
|
117
|
+
if (!(await file.exists())) return c.notFound();
|
|
118
|
+
return new Response(file, {
|
|
119
|
+
headers: {
|
|
120
|
+
"Content-Type": "application/x-protobuf",
|
|
121
|
+
"Cache-Control": "private, max-age=604800",
|
|
122
|
+
"X-Content-Type-Options": "nosniff",
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
export default createGeoRoutes();
|
package/src/api/help.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Hono, type MiddlewareHandler } from "hono";
|
|
2
2
|
import { type AuthContext, auth, getLocale } from "../server";
|
|
3
3
|
import { buildHelpDocumentPayload } from "../server/help-payload";
|
|
4
|
+
import { appAccessAuthority } from "../services/app-access";
|
|
4
5
|
import { createHelpReader, type HelpReaderFactory } from "../services/help";
|
|
5
6
|
import type { HelpSearchPayload } from "../shared/help";
|
|
6
7
|
import type { renderHelpMarkdown } from "../shared/markdown";
|
|
@@ -24,16 +25,23 @@ export const createHelpRoutes = (dependencies: HelpRouteDependencies = {}) => {
|
|
|
24
25
|
const query = c.req.query("q")?.trim().slice(0, 200) ?? "";
|
|
25
26
|
const payload: HelpSearchPayload = {
|
|
26
27
|
locale,
|
|
27
|
-
ids: (
|
|
28
|
-
(
|
|
29
|
-
|
|
28
|
+
ids: (
|
|
29
|
+
await reader(locale, c.get("actor"), { authority: appAccessAuthority(c) }).search({
|
|
30
|
+
query,
|
|
31
|
+
appId: c.req.param("appId"),
|
|
32
|
+
limit: 25,
|
|
33
|
+
})
|
|
34
|
+
).map((document) => document.documentId),
|
|
30
35
|
};
|
|
31
36
|
return c.json(payload);
|
|
32
37
|
})
|
|
33
38
|
.get("/help/v1/:appId/documents/:documentId", async (c) => {
|
|
34
39
|
const locale = getLocale(c);
|
|
35
40
|
const { t } = helpApiMessages.resolve([locale]);
|
|
36
|
-
const document = await reader(locale, c.get("actor")
|
|
41
|
+
const document = await reader(locale, c.get("actor"), { authority: appAccessAuthority(c) }).read({
|
|
42
|
+
appId: c.req.param("appId"),
|
|
43
|
+
documentId: c.req.param("documentId"),
|
|
44
|
+
});
|
|
37
45
|
if (!document) return c.json({ code: "HELP_NOT_FOUND", message: t.notFound }, 404);
|
|
38
46
|
const payload = buildHelpDocumentPayload(document, c.req.header("User-Agent") ?? "", dependencies.renderMarkdown);
|
|
39
47
|
return c.json(payload);
|
package/src/api/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ import adminAiQuotaRoutes from "./admin-ai-quotas";
|
|
|
26
26
|
import adminAiSkillsRoutes from "./admin-ai-skills";
|
|
27
27
|
import adminAiUsageRoutes from "./admin-ai-usage";
|
|
28
28
|
import adminCoreSettingsRoutes from "./admin-core-settings";
|
|
29
|
+
import adminGeoRoutes from "./admin-geo";
|
|
29
30
|
import adminGeoIpRoutes from "./admin-geoip";
|
|
30
31
|
import adminIdentityRoutes from "./admin-identity";
|
|
31
32
|
import adminLifecycleRoutes from "./admin-lifecycle";
|
|
@@ -38,6 +39,7 @@ import { approvalRoutes } from "./approvals";
|
|
|
38
39
|
import { appDiscoveryRoutes } from "./apps";
|
|
39
40
|
import { createAuthRoutes } from "./auth";
|
|
40
41
|
import { createCapabilityRoutes } from "./capabilities";
|
|
42
|
+
import geoRoutes from "./geo";
|
|
41
43
|
import { createHelpRoutes } from "./help";
|
|
42
44
|
import { createMcpRoutes } from "./mcp";
|
|
43
45
|
import meRoutes from "./me";
|
|
@@ -46,6 +48,7 @@ import { createSearchRoutes } from "./search";
|
|
|
46
48
|
import { createSyncOpsProxyRoutes } from "./sync-ops";
|
|
47
49
|
import { createWidgetRoutes } from "./widgets";
|
|
48
50
|
|
|
51
|
+
export { withCapabilityRequestTimeout } from "../_internal/capability-timeout";
|
|
49
52
|
export { createAiOpenAiRoutes } from "../ai/openai-api";
|
|
50
53
|
export { type CapabilityDispatchDependencies, dispatchCapability } from "./capabilities";
|
|
51
54
|
export { cliPluginListRoutes } from "./cli-plugins";
|
|
@@ -78,6 +81,7 @@ const buildCoreApi = (options: CoreApiOptions) => {
|
|
|
78
81
|
.route("/auth", createAuthRoutes(options.notifications))
|
|
79
82
|
.route("/me", meRoutes)
|
|
80
83
|
.route("/accounts", accountsEntitiesRoutes)
|
|
84
|
+
.route("/geo", geoRoutes)
|
|
81
85
|
.route("/apps", appDiscoveryRoutes)
|
|
82
86
|
.route("/approvals", approvalRoutes)
|
|
83
87
|
.route("/announcements", announcementRoutes)
|
|
@@ -91,6 +95,7 @@ const buildCoreApi = (options: CoreApiOptions) => {
|
|
|
91
95
|
.route("/admin/core/rail", adminRailRoutes)
|
|
92
96
|
.route("/admin/core/linux-identities", adminLinuxIdentityRoutes)
|
|
93
97
|
.route("/admin/core/geoip", adminGeoIpRoutes)
|
|
98
|
+
.route("/admin/core/geo", adminGeoRoutes)
|
|
94
99
|
.route("/admin/identity", adminIdentityRoutes)
|
|
95
100
|
.route("/admin/sync", createSyncOpsProxyRoutes())
|
|
96
101
|
.route("/admin/lifecycle", adminLifecycleRoutes)
|
package/src/api/mcp.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { Hono, type MiddlewareHandler } from "hono";
|
|
|
16
16
|
import { z } from "zod";
|
|
17
17
|
import { readBoundedJson } from "../_internal/bounded-json";
|
|
18
18
|
import { capabilitySentenceText } from "../_internal/capability-sentences";
|
|
19
|
+
import { withCapabilityRequestTimeout } from "../_internal/capability-timeout";
|
|
19
20
|
import {
|
|
20
21
|
HELP_SEARCH_MAX_LIMIT,
|
|
21
22
|
helpAgentMarkdown,
|
|
@@ -46,6 +47,7 @@ import type { AppRegistryEntry, CapabilityRegistryEntry } from "../contracts/reg
|
|
|
46
47
|
import { type AuthContext, auth, type RequestAuthority, rateLimit, rejectReservedWorkloadCredential, resolveLocale } from "../server";
|
|
47
48
|
import { preferredLocale } from "../server/locale";
|
|
48
49
|
import { noTransform } from "../server/middleware/no-transform";
|
|
50
|
+
import { discoverableApps } from "../services/app-discovery";
|
|
49
51
|
import { createHelpReader, type HelpArticle, type HelpMetadata, type HelpReader, type HelpReaderFactory } from "../services/help";
|
|
50
52
|
import { normalizeInvocationRequestId } from "../services/identity/invocation-token";
|
|
51
53
|
import { logger } from "../services/logging";
|
|
@@ -493,7 +495,7 @@ const createMcpServer = (
|
|
|
493
495
|
oauthScopes: string[] | null,
|
|
494
496
|
authority?: RequestAuthority,
|
|
495
497
|
): Server => {
|
|
496
|
-
const registry = dependencies.listApps ?? listApps;
|
|
498
|
+
const registry = async () => discoverableApps(authority ?? null, await (dependencies.listApps ?? listApps)());
|
|
497
499
|
const capabilityLookup = dependencies.getCapability ?? getCapability;
|
|
498
500
|
let helpLocale: Promise<string> | undefined;
|
|
499
501
|
const resolveHelpLocale = () =>
|
|
@@ -501,9 +503,10 @@ const createMcpServer = (
|
|
|
501
503
|
resolveLocale(request.headers, operatorDefault),
|
|
502
504
|
));
|
|
503
505
|
const helpReader = async () =>
|
|
504
|
-
(dependencies.help ?? ((locale, viewer) => createHelpReader(locale, viewer, { listApps: registry })))(
|
|
506
|
+
(dependencies.help ?? ((locale, viewer) => createHelpReader(locale, viewer, { listApps: registry, authority: authority ?? null })))(
|
|
505
507
|
await resolveHelpLocale(),
|
|
506
508
|
authority?.actor,
|
|
509
|
+
{ authority: authority ?? null },
|
|
507
510
|
);
|
|
508
511
|
const hasScope = (scope: "read" | "write"): boolean =>
|
|
509
512
|
oauthScopes === null || oauthScopes.includes(scope) || oauthScopes.includes("admin");
|
|
@@ -781,42 +784,47 @@ export const createMcpRoutes = (dependencies: McpRouteDependencies = {}) =>
|
|
|
781
784
|
.use("/mcp/v1", dependencies.authenticate ?? mcpAuthentication(dependencies.getAppUrl ?? defaultAppUrl))
|
|
782
785
|
.use("/mcp/v1", rejectReservedWorkloadCredential)
|
|
783
786
|
.use("/mcp/v1", dependencies.limit ?? rateLimit())
|
|
784
|
-
.all("/mcp/v1", async (c) =>
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
787
|
+
.all("/mcp/v1", async (c) =>
|
|
788
|
+
withCapabilityRequestTimeout(c, "query", async () => {
|
|
789
|
+
if (c.req.method !== "POST") {
|
|
790
|
+
return new Response(null, { status: 405, headers: { Allow: "POST" } });
|
|
791
|
+
}
|
|
792
|
+
let request = c.req.raw;
|
|
793
|
+
const parsed = await readBoundedJson(request, CAPABILITY_MAX_REQUEST_BYTES);
|
|
794
|
+
if (!parsed.ok) {
|
|
795
|
+
return c.json(
|
|
796
|
+
{
|
|
797
|
+
jsonrpc: "2.0",
|
|
798
|
+
id: null,
|
|
799
|
+
error: {
|
|
800
|
+
code: parsed.reason === "too_large" ? -32600 : -32700,
|
|
801
|
+
message: parsed.reason === "too_large" ? "MCP request is too large" : "MCP request body must be JSON",
|
|
802
|
+
},
|
|
798
803
|
},
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
804
|
+
parsed.reason === "too_large" ? 413 : 400,
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
const headers = new Headers(request.headers);
|
|
808
|
+
headers.delete("content-length");
|
|
809
|
+
request = new Request(request.url, {
|
|
810
|
+
method: "POST",
|
|
811
|
+
headers,
|
|
812
|
+
body: JSON.stringify(parsed.data),
|
|
813
|
+
signal: request.signal,
|
|
814
|
+
});
|
|
815
|
+
const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true });
|
|
816
|
+
const server = createMcpServer(request, dependencies, c.get("oauthScopes") ?? null, auth.getAuthority(c));
|
|
817
|
+
try {
|
|
818
|
+
await server.connect(transport);
|
|
819
|
+
return await transport.handleRequest(request);
|
|
820
|
+
} finally {
|
|
821
|
+
try {
|
|
822
|
+
await transport.close();
|
|
823
|
+
} finally {
|
|
824
|
+
await server.close();
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}),
|
|
828
|
+
);
|
|
821
829
|
|
|
822
830
|
export type McpApiType = ReturnType<typeof createMcpRoutes>;
|
|
@@ -5,6 +5,8 @@ import { z } from "zod";
|
|
|
5
5
|
import { CapabilityAppIdSchema } from "../contracts/capabilities";
|
|
6
6
|
import { panelProviderNeedsConsent } from "../contracts/panel-context-plain";
|
|
7
7
|
import { type AuthContext, jsonResponse, requiresAuth, v } from "../server";
|
|
8
|
+
import { appAccessAuthority } from "../services/app-access";
|
|
9
|
+
import { discoverableApps } from "../services/app-discovery";
|
|
8
10
|
import { PanelConsentCancelled, panelContextConsent } from "../services/panel-context-consent";
|
|
9
11
|
import { getRuntimeContext } from "../ssr/runtime";
|
|
10
12
|
|
|
@@ -15,9 +17,9 @@ const SavedSchema = ProviderSchema.extend(AnswerSchema.shape).strict();
|
|
|
15
17
|
|
|
16
18
|
/** Mounted below /me's session and user guards. The live request registry owns provider membership. */
|
|
17
19
|
export const createMePanelConsentRoutes = (service = panelContextConsent) => {
|
|
18
|
-
const providers = (c:
|
|
19
|
-
getRuntimeContext(c)
|
|
20
|
-
.
|
|
20
|
+
const providers = async (c: import("hono").Context<AuthContext>) =>
|
|
21
|
+
(await discoverableApps(appAccessAuthority(c), getRuntimeContext(c).apps))
|
|
22
|
+
.filter((app) => app.panel && panelProviderNeedsConsent(app.id))
|
|
21
23
|
.map((app) => app.id);
|
|
22
24
|
return new Hono<AuthContext>()
|
|
23
25
|
.use("*", async (c, next) => {
|
|
@@ -33,7 +35,7 @@ export const createMePanelConsentRoutes = (service = panelContextConsent) => {
|
|
|
33
35
|
...requiresAuth,
|
|
34
36
|
responses: { 200: jsonResponse(AnswersSchema, "Answers for registered panel providers") },
|
|
35
37
|
}),
|
|
36
|
-
async (c) => c.json({ answers: await service.list(c.get("user").id, providers(c)) }),
|
|
38
|
+
async (c) => c.json({ answers: await service.list(c.get("user").id, await providers(c)) }),
|
|
37
39
|
)
|
|
38
40
|
.put(
|
|
39
41
|
"/panel-consent/:provider",
|
|
@@ -53,7 +55,8 @@ export const createMePanelConsentRoutes = (service = panelContextConsent) => {
|
|
|
53
55
|
v("json", AnswerSchema),
|
|
54
56
|
async (c) => {
|
|
55
57
|
const { provider } = c.req.valid("param");
|
|
56
|
-
if (!providers(c).includes(provider))
|
|
58
|
+
if (!(await providers(c)).includes(provider))
|
|
59
|
+
return c.json({ code: "PANEL_PROVIDER_NOT_FOUND", message: "Panel provider not found" }, 404);
|
|
57
60
|
const { allowed, revision } = c.req.valid("json");
|
|
58
61
|
let saved: Awaited<ReturnType<typeof service.set>>;
|
|
59
62
|
try {
|
package/src/api/search.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Hono, type MiddlewareHandler } from "hono";
|
|
|
2
2
|
import { describeRoute, resolver } from "hono-openapi";
|
|
3
3
|
import { readBoundedJson } from "../_internal/bounded-json";
|
|
4
4
|
import { resolveCapabilityManifestPresentation } from "../_internal/capabilities";
|
|
5
|
-
import { listCapabilities } from "../_internal/registry";
|
|
5
|
+
import { type listApps, listCapabilities } from "../_internal/registry";
|
|
6
6
|
import type { CapabilityRegistryEntry } from "../contracts";
|
|
7
7
|
import {
|
|
8
8
|
CAPABILITY_FRAMEWORK_ERROR_CODES,
|
|
@@ -14,9 +14,12 @@ import {
|
|
|
14
14
|
import { type AuthContext, auth, expectUserBackedActor, jsonResponse, preferredLocale, requiresAuth, v } from "../server";
|
|
15
15
|
import { noTransform } from "../server/middleware/no-transform";
|
|
16
16
|
import { logger } from "../services";
|
|
17
|
+
import { appAccessAuthority } from "../services/app-access";
|
|
18
|
+
import { discoverableAppEntries } from "../services/app-discovery";
|
|
17
19
|
import { invocationAuthorityFromRequest } from "../services/identity/invocation-authority";
|
|
18
20
|
import { searchInvocationOperation } from "../services/identity/invocation-operations";
|
|
19
|
-
import { normalizeInvocationRequestId
|
|
21
|
+
import { normalizeInvocationRequestId } from "../services/identity/invocation-token";
|
|
22
|
+
import { issueInvocationToken as signInvocationToken } from "../services/identity/issue-invocation";
|
|
20
23
|
import { withActiveIdentitySigner } from "../services/identity/key-ring";
|
|
21
24
|
import { LOCALE_HEADER } from "../shared/locale";
|
|
22
25
|
import { ndjsonLine, ndjsonStream, ndjsonStreamHeaders, startBounded, waitWithin } from "./fanout";
|
|
@@ -83,6 +86,7 @@ type SearchRouteDependencies = {
|
|
|
83
86
|
listCapabilities?: () => Promise<CapabilityRegistryEntry[]>;
|
|
84
87
|
fetch?: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
85
88
|
authenticate?: MiddlewareHandler<AuthContext>;
|
|
89
|
+
listApps?: typeof listApps;
|
|
86
90
|
signInvocation?: typeof signInvocationToken;
|
|
87
91
|
withActiveSigner?: typeof withActiveIdentitySigner;
|
|
88
92
|
};
|
|
@@ -137,7 +141,7 @@ export const createSearchRoutes = (dependencies: SearchRouteDependencies = {}) =
|
|
|
137
141
|
const invocationAuthority = invocationAuthorityFromRequest(auth.getAuthority(c));
|
|
138
142
|
let entries: CapabilityRegistryEntry[];
|
|
139
143
|
try {
|
|
140
|
-
entries = await registry();
|
|
144
|
+
entries = await discoverableAppEntries(appAccessAuthority(c), await registry(), dependencies.listApps);
|
|
141
145
|
} catch (error) {
|
|
142
146
|
log.warn("Search capability registry unavailable", { error: error instanceof Error ? error.message : String(error) });
|
|
143
147
|
return c.json(
|
|
@@ -237,22 +241,25 @@ export const createSearchRoutes = (dependencies: SearchRouteDependencies = {}) =
|
|
|
237
241
|
signedInvocations = await waitWithin(
|
|
238
242
|
(dependencies.withActiveSigner ?? withActiveIdentitySigner)(
|
|
239
243
|
"invocation",
|
|
240
|
-
(signer) =>
|
|
244
|
+
(signer, db) =>
|
|
241
245
|
Promise.all(
|
|
242
246
|
startBounded(
|
|
243
247
|
active,
|
|
244
248
|
PROVIDER_CONCURRENCY,
|
|
245
249
|
(provider) =>
|
|
246
|
-
(dependencies.signInvocation ?? signInvocationToken)(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
250
|
+
(dependencies.signInvocation ?? signInvocationToken)(
|
|
251
|
+
{
|
|
252
|
+
targetAppId: provider.appId,
|
|
253
|
+
callingAppId: "core",
|
|
254
|
+
operation: searchInvocationOperation,
|
|
255
|
+
schemaHash: provider.schemaHash,
|
|
256
|
+
authority: invocationAuthority,
|
|
257
|
+
requestId,
|
|
258
|
+
signer,
|
|
259
|
+
issuer: signer.issuer,
|
|
260
|
+
},
|
|
261
|
+
{ db, groupsAdmin: signer.groupsAdmin },
|
|
262
|
+
),
|
|
256
263
|
fanoutSignal,
|
|
257
264
|
),
|
|
258
265
|
),
|
package/src/api/sync-ops.ts
CHANGED
|
@@ -2,10 +2,12 @@ import { Hono, type MiddlewareHandler } from "hono";
|
|
|
2
2
|
import { readBoundedJson } from "../_internal/bounded-json";
|
|
3
3
|
import { getApp, listApps } from "../_internal/registry";
|
|
4
4
|
import { type AuthContext, auth, rejectReservedWorkloadCredential } from "../server";
|
|
5
|
+
import { AppAccessDeniedError, appAccessDeniedBody } from "../server/middleware/app-access";
|
|
5
6
|
import { noTransform } from "../server/middleware/no-transform";
|
|
6
7
|
import { invocationAuthorityFromRequest } from "../services/identity/invocation-authority";
|
|
7
8
|
import { syncInvocationOperation } from "../services/identity/invocation-operations";
|
|
8
|
-
import { normalizeInvocationRequestId
|
|
9
|
+
import { normalizeInvocationRequestId } from "../services/identity/invocation-token";
|
|
10
|
+
import { issueInvocationToken as signInvocationToken } from "../services/identity/issue-invocation";
|
|
9
11
|
import { withActiveIdentitySigner } from "../services/identity/key-ring";
|
|
10
12
|
|
|
11
13
|
type Dependencies = {
|
|
@@ -53,17 +55,20 @@ export const createSyncOpsProxyRoutes = (dependencies: Dependencies = {}) =>
|
|
|
53
55
|
try {
|
|
54
56
|
const signed = await (dependencies.withActiveSigner ?? withActiveIdentitySigner)(
|
|
55
57
|
"invocation",
|
|
56
|
-
(signer) =>
|
|
57
|
-
(dependencies.signInvocation ?? signInvocationToken)(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
58
|
+
(signer, db) =>
|
|
59
|
+
(dependencies.signInvocation ?? signInvocationToken)(
|
|
60
|
+
{
|
|
61
|
+
targetAppId: app.id,
|
|
62
|
+
callingAppId: "core",
|
|
63
|
+
operation,
|
|
64
|
+
schemaHash: null,
|
|
65
|
+
authority: invocationAuthorityFromRequest(auth.getAuthority(c)),
|
|
66
|
+
requestId,
|
|
67
|
+
signer,
|
|
68
|
+
issuer: signer.issuer,
|
|
69
|
+
},
|
|
70
|
+
{ db, groupsAdmin: signer.groupsAdmin },
|
|
71
|
+
),
|
|
67
72
|
{ signal, timeoutMs: 5_000 },
|
|
68
73
|
);
|
|
69
74
|
const headers = new Headers({ authorization: `Bearer ${signed.token}`, accept: "application/json" });
|
|
@@ -75,7 +80,8 @@ export const createSyncOpsProxyRoutes = (dependencies: Dependencies = {}) =>
|
|
|
75
80
|
const result = await readBoundedJson(response, 4 * 1024 * 1024);
|
|
76
81
|
if (!result.ok) return c.json({ message: "Invalid Sync operations response" }, 502);
|
|
77
82
|
return Response.json(result.data, { status: response.status, headers: { "cache-control": "no-store" } });
|
|
78
|
-
} catch {
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (error instanceof AppAccessDeniedError) return c.json(appAccessDeniedBody(error.app), 403);
|
|
79
85
|
return c.json({ message: "Sync operations unavailable" }, 502);
|
|
80
86
|
}
|
|
81
87
|
});
|