@k2b/cloud 0.42.0 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +13 -5
- package/scripts/app-favicon.ts +7 -6
- package/scripts/build.ts +3 -0
- package/scripts/installable-assets.ts +61 -0
- package/scripts/preload.ts +3 -0
- package/src/_internal/app-access-path.ts +12 -0
- package/src/_internal/app-access-request.ts +13 -0
- package/src/_internal/app-access-resources.ts +141 -0
- package/src/_internal/app-event-catalog.ts +40 -0
- package/src/_internal/app-event-schemas.ts +127 -0
- package/src/_internal/application-package.ts +40 -0
- package/src/_internal/built-in-app-ids.json +27 -0
- package/src/_internal/capabilities.ts +185 -12
- package/src/_internal/capability-origin.ts +4 -0
- package/src/_internal/capability-streams.ts +10 -5
- package/src/_internal/define-app.ts +159 -45
- package/src/_internal/installable-assets.ts +90 -0
- package/src/_internal/installable-head.ts +22 -0
- package/src/_internal/object-store-area.ts +9 -0
- package/src/_internal/object-store-config.ts +101 -0
- package/src/_internal/object-store-key.ts +14 -0
- package/src/_internal/page-responses.ts +34 -3
- package/src/_internal/process-identity.ts +7 -2
- package/src/_internal/registry-validation.ts +15 -1
- package/src/_internal/registry.ts +44 -1
- package/src/_internal/runtime-context.ts +9 -1
- package/src/_internal/runtime-watcher.ts +8 -0
- package/src/_internal/status-preserving-ssr.ts +12 -1
- package/src/access/AppAccessEditor.browser-fixture.ts +429 -0
- package/src/access/AppAccessEditor.browser-frame.tsx +68 -0
- package/src/access/AppAccessEditor.browser-harness.tsx +65 -0
- package/src/access/AppAccessEditor.tsx +380 -0
- package/src/access/PermissionEditor.browser-harness.tsx +22 -2
- package/src/access/PermissionEditor.tsx +386 -77
- package/src/access/PrincipalAvatar.tsx +63 -0
- package/src/access/PrincipalPicker.tsx +29 -8
- package/src/access/ResourceApiKeys.tsx +3 -4
- package/src/access/app-access-adapter.ts +51 -0
- package/src/access/app-access-messages.ts +172 -0
- package/src/access/messages.ts +14 -0
- package/src/access/ui.ts +4 -1
- package/src/account/Avatar.tsx +21 -3
- package/src/account/ProfilePictureDialog.tsx +699 -0
- package/src/account/profile-picture-messages.ts +269 -0
- package/src/account/ui.ts +8 -1
- package/src/ai/admin.ts +3 -0
- package/src/ai/app-frame.ts +12 -0
- package/src/ai/application-tasks.ts +101 -0
- package/src/ai/assistant-models.ts +126 -64
- package/src/ai/capabilities.ts +19 -4
- package/src/ai/capability-execution.ts +5 -2
- package/src/ai/chart-block.ts +63 -30
- package/src/ai/chat/chart-block.tsx +12 -3
- package/src/ai/chat/composer-adapter.ts +8 -7
- package/src/ai/chat/message-actions.tsx +1 -1
- package/src/ai/chat/message-utils.ts +2 -2
- package/src/ai/chat/presentation.tsx +22 -1
- package/src/ai/chat-quotas.ts +5 -4
- package/src/ai/chat-tasks.ts +2 -2
- package/src/ai/client/controller.ts +11 -10
- package/src/ai/client/projection.ts +3 -3
- package/src/ai/code-capability-routes.ts +1 -0
- package/src/ai/compaction.ts +3 -3
- package/src/ai/credentials.ts +13 -114
- package/src/ai/default-tool-contracts.ts +4 -3
- package/src/ai/dictation-runtime.ts +4 -4
- package/src/ai/enrich.ts +2 -2
- package/src/ai/executor.ts +38 -27
- package/src/ai/http.ts +17 -29
- package/src/ai/index.ts +11 -5
- package/src/ai/inference-calls.ts +6 -4
- package/src/ai/maintenance.ts +1 -1
- package/src/ai/memory-learning-runs.ts +5 -5
- package/src/ai/memory-learning.ts +3 -3
- package/src/ai/message-queue.ts +1 -2
- package/src/ai/migrate.ts +75 -42
- package/src/ai/model-access-migrate.ts +3 -17
- package/src/ai/model-access.ts +44 -85
- package/src/ai/model-card.tsx +134 -0
- package/src/ai/model-catalog.ts +359 -0
- package/src/ai/model-chooser-messages.ts +88 -0
- package/src/ai/model-chooser.tsx +577 -0
- package/src/ai/model-delete.ts +234 -0
- package/src/ai/model-fixture.ts +39 -0
- package/src/ai/model-icon.tsx +36 -0
- package/src/ai/model-overview.ts +53 -0
- package/src/ai/model-usage.ts +45 -0
- package/src/ai/openai-api-messages.ts +0 -4
- package/src/ai/openai-api-slots.ts +10 -2
- package/src/ai/openai-api.ts +83 -16
- package/src/ai/personal-agent.ts +1 -1
- package/src/ai/prefs.ts +12 -10
- package/src/ai/projects-routes.ts +2 -1
- package/src/ai/projects.ts +13 -12
- package/src/ai/protocol.ts +2 -2
- package/src/ai/provider-connections.ts +161 -0
- package/src/ai/provider-errors.ts +138 -0
- package/src/ai/provider-fetch.ts +4 -0
- package/src/ai/provider-retry.ts +1 -1
- package/src/ai/provider.ts +2 -1
- package/src/ai/quota-limits.ts +36 -0
- package/src/ai/quota-provider.ts +11 -2
- package/src/ai/quota-report.ts +7 -4
- package/src/ai/quotas-migrate.ts +1 -0
- package/src/ai/quotas.ts +19 -8
- package/src/ai/recent-resources.ts +6 -0
- package/src/ai/request-headers.ts +14 -87
- package/src/ai/resource-refs.ts +6 -2
- package/src/ai/routes.ts +17 -18
- package/src/ai/runtime.ts +50 -22
- package/src/ai/settings.ts +93 -580
- package/src/ai/store.ts +45 -23
- package/src/ai/stream.ts +1 -1
- package/src/ai/structured-runs.ts +2 -2
- package/src/ai/structured.ts +29 -46
- package/src/ai/task-contracts.ts +25 -5
- package/src/ai/task-execution.ts +15 -6
- package/src/ai/task-model.ts +9 -0
- package/src/ai/transcription.ts +37 -21
- package/src/ai/turn-failure.ts +24 -2
- package/src/ai/types.ts +50 -62
- package/src/ai/ui.tsx +13 -0
- package/src/ai/usage.ts +8 -8
- package/src/ai/validate.ts +8 -7
- package/src/ai/view-block.ts +2 -2
- package/src/api/accounts-entities.ts +40 -16
- package/src/api/admin-ai-quotas.ts +34 -56
- package/src/api/admin-ai-usage.ts +1 -1
- package/src/api/admin-ai.ts +173 -0
- package/src/api/admin-core-settings.ts +68 -244
- package/src/api/admin-directory-shares.ts +55 -0
- package/src/api/admin-identity.ts +81 -2
- package/src/api/app-access.ts +85 -0
- package/src/api/app-approval.ts +3 -1
- package/src/api/app-event-delivery.ts +51 -0
- package/src/api/app-events.ts +123 -0
- package/src/api/app-resource-access.ts +48 -0
- package/src/api/auth/account-mismatch.ts +24 -0
- package/src/api/auth.ts +26 -3
- package/src/api/capabilities.ts +127 -15
- package/src/api/capability-streams.ts +2 -5
- package/src/api/identity-child-invocation.ts +145 -0
- package/src/api/index.ts +11 -2
- package/src/api/mcp.ts +180 -5
- package/src/api/me.ts +38 -17
- package/src/api/pwa-phone.ts +87 -48
- package/src/api/pwa.ts +34 -8
- package/src/api/resource-links.ts +83 -6
- package/src/api/resource-resolve.ts +50 -0
- package/src/api/search/schemas.ts +3 -1
- package/src/api/search.ts +9 -7
- package/src/api/widgets.ts +1 -0
- package/src/app-pwa/authority.ts +75 -0
- package/src/app-pwa/contracts.ts +177 -0
- package/src/app-pwa/core-query-error.ts +20 -0
- package/src/app-pwa/index.ts +23 -0
- package/src/app-pwa/protocol.ts +27 -0
- package/src/app-pwa/schema.ts +59 -0
- package/src/app-pwa/service.ts +861 -0
- package/src/app-pwa/transaction.ts +338 -0
- package/src/app-pwa/transport-input.ts +57 -0
- package/src/app-pwa/transport.ts +235 -0
- package/src/approvals/messages.ts +2 -2
- package/src/browser/app-access-recovery.ts +44 -0
- package/src/browser/app-session.ts +14 -3
- package/src/browser/cloud-id.ts +86 -0
- package/src/browser/live-websocket.ts +5 -0
- package/src/browser/pwa-drafts.ts +57 -0
- package/src/browser/resource-clipboard.ts +4 -0
- package/src/browser/resource-links.ts +52 -0
- package/src/browser/resource-picker.tsx +3 -2
- package/src/capabilities/calendar-sources.ts +302 -0
- package/src/capabilities/executions.ts +10 -3
- package/src/capabilities/migrate.ts +4 -0
- package/src/capabilities/nested.ts +43 -0
- package/src/capabilities/server-invoke.ts +202 -0
- package/src/capabilities/server.ts +26 -187
- package/src/capabilities/testing.ts +5 -2
- package/src/cli/access.ts +6 -5
- package/src/cli/account.ts +57 -0
- package/src/cli/admin/agents.ts +66 -0
- package/src/cli/admin/ai-models.ts +199 -0
- package/src/cli/admin/ai-quotas.ts +6 -102
- package/src/cli/admin/ai-usage.ts +4 -4
- package/src/cli/admin/app-credentials.ts +5 -3
- package/src/cli/admin/index.ts +7 -3
- package/src/cli/app-access.ts +60 -0
- package/src/cli/apps.ts +89 -26
- package/src/cli/avatar.ts +64 -0
- package/src/cli/capabilities.ts +22 -1
- package/src/cli/events.ts +320 -0
- package/src/cli/index.ts +1 -1
- package/src/config/env.ts +40 -0
- package/src/contracts/app-access.ts +52 -5
- package/src/contracts/app-approval.ts +8 -1
- package/src/contracts/app-details.ts +89 -0
- package/src/contracts/app-events.ts +51 -0
- package/src/contracts/app.ts +24 -5
- package/src/contracts/calendar-source.ts +274 -0
- package/src/contracts/calendar-view.ts +35 -0
- package/src/contracts/capabilities.ts +164 -3
- package/src/contracts/geo-admin.ts +2 -0
- package/src/contracts/geo-route.ts +0 -11
- package/src/contracts/geo.ts +14 -2
- package/src/contracts/index.ts +6 -0
- package/src/contracts/installable.ts +40 -0
- package/src/contracts/notification-types.ts +36 -9
- package/src/contracts/profile.ts +32 -0
- package/src/contracts/pwa-builds.ts +14 -0
- package/src/contracts/pwa-location.ts +38 -0
- package/src/contracts/pwa.ts +24 -3
- package/src/contracts/registry.ts +8 -0
- package/src/contracts/resource-clipboard.ts +35 -0
- package/src/contracts/resource-pages.ts +140 -0
- package/src/contracts/resource-resolution.ts +13 -0
- package/src/contracts/shared.ts +4 -0
- package/src/contracts/user-notifications.ts +2 -0
- package/src/events/app-event-delivery.ts +100 -0
- package/src/events/app-events.testing.ts +65 -0
- package/src/events/app-events.ts +134 -0
- package/src/events/core-user-created.ts +24 -0
- package/src/events/index.ts +9 -0
- package/src/events/testing.ts +2 -0
- package/src/index.ts +2 -1
- package/src/server/api/respond.ts +8 -3
- package/src/server/index.ts +1 -0
- package/src/server/middleware/app-access.ts +7 -6
- package/src/server/middleware/auth.ts +168 -66
- package/src/server/middleware/index.ts +1 -0
- package/src/server/middleware/invocation.ts +4 -2
- package/src/server/middleware/workload.ts +3 -3
- package/src/server/oauth-scope.ts +10 -0
- package/src/server/public-storage.ts +106 -0
- package/src/server/services/access.ts +8 -0
- package/src/services/accounts/app.ts +35 -3
- package/src/services/accounts/avatar.ts +342 -35
- package/src/services/accounts/base-group.ts +4 -5
- package/src/services/accounts/directory-shares.ts +157 -0
- package/src/services/accounts/entities.ts +50 -6
- package/src/services/accounts/groups.ts +3 -1
- package/src/services/accounts/identities.ts +64 -12
- package/src/services/accounts/local-groups.ts +1 -1
- package/src/services/accounts/users.ts +23 -15
- package/src/services/app-access-management.ts +519 -0
- package/src/services/app-access.ts +25 -7
- package/src/services/app-approval.ts +15 -4
- package/src/services/app-details.ts +224 -0
- package/src/services/app-discovery.ts +2 -1
- package/src/services/app-event-shapes.ts +139 -0
- package/src/services/app-events/accept.ts +116 -0
- package/src/services/app-events/admin.ts +250 -0
- package/src/services/app-events/declarations.ts +187 -0
- package/src/services/app-events/deliver.ts +218 -0
- package/src/services/app-events.ts +92 -0
- package/src/services/config-conflict.ts +11 -0
- package/src/services/core-user-events.ts +1 -0
- package/src/services/freeipa-config.ts +9 -0
- package/src/services/geo/admin.ts +4 -1
- package/src/services/geo/ledger.ts +47 -14
- package/src/services/geo/route-chain.ts +40 -18
- package/src/services/geo/route-estimate.ts +19 -0
- package/src/services/geo/route-plan.ts +0 -2
- package/src/services/geo/route-service.ts +57 -39
- package/src/services/geo/service.ts +51 -25
- package/src/services/identity/core-origin.ts +12 -0
- package/src/services/identity/index.ts +2 -1
- package/src/services/identity/invocation-actor.ts +50 -2
- package/src/services/identity/invocation-authority.ts +38 -1
- package/src/services/identity/invocation-token.ts +255 -50
- package/src/services/identity/issue-invocation.ts +9 -1
- package/src/services/identity/models-only-auth.ts +13 -4
- package/src/services/identity/nested-invocation.ts +128 -0
- package/src/services/image-metadata.ts +65 -1
- package/src/services/index.ts +24 -1
- package/src/services/ipa/sync.ts +6 -2
- package/src/services/ipa/users.ts +4 -1
- package/src/services/mandates/policy.ts +19 -12
- package/src/services/notifications/browser.ts +4 -0
- package/src/services/notifications/catalog.ts +5 -2
- package/src/services/notifications/platform.ts +7 -2
- package/src/services/notifications/user.ts +18 -7
- package/src/services/oauth-tokens.ts +28 -2
- package/src/services/object-store.ts +217 -0
- package/src/services/providers/local/users.ts +5 -0
- package/src/services/pwa-devices.ts +147 -37
- package/src/services/service-account-credentials.ts +5 -2
- package/src/services/session/index.ts +14 -0
- package/src/services/settings/core-settings.ts +91 -47
- package/src/services/settings/index.ts +8 -0
- package/src/services/settings/versions.ts +26 -0
- package/src/shared/ai-models.ts +274 -0
- package/src/shared/ai-quotas.ts +2 -2
- package/src/shared/ai-usage.ts +2 -2
- package/src/shared/index.ts +2 -0
- package/src/shared-text/documents.ts +25 -8
- package/src/shared-text/log.ts +1 -1
- package/src/shared-text/text-diff.ts +75 -0
- package/src/ssr/AdminArea.tsx +131 -0
- package/src/ssr/AdminLayout.tsx +11 -3
- package/src/ssr/AdminSidebar.tsx +7 -7
- package/src/ssr/AppAccessDenied.tsx +114 -0
- package/src/ssr/AppBadge.tsx +14 -0
- package/src/ssr/AppLaunchpadPanel.tsx +1 -1
- package/src/ssr/AppPwaRuntime.island.tsx +82 -0
- package/src/ssr/CloudShell.island.tsx +25 -4
- package/src/ssr/Layout.tsx +15 -7
- package/src/ssr/PwaLayout.tsx +73 -8
- package/src/ssr/PwaPartCount.island.tsx +23 -0
- package/src/ssr/PwaRuntime.island.tsx +85 -6
- package/src/ssr/SettingsSaveOutcome.tsx +58 -0
- package/src/ssr/SettingsSaveStatus.island.tsx +16 -0
- package/src/ssr/ShellRuntime.island.tsx +5 -0
- package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +3 -3
- package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +1 -1
- package/src/ssr/admin-active-link.ts +16 -0
- package/src/ssr/admin-navigation.ts +26 -20
- package/src/ssr/app-badges.ts +31 -7
- package/src/ssr/app-pwa-document.tsx +16 -0
- package/src/ssr/app-pwa-messages.ts +17 -0
- package/src/ssr/index.ts +8 -0
- package/src/ssr/layout-context.ts +1 -1
- package/src/ssr/page-trail-runtime.ts +3 -1
- package/src/ssr/platform-messages.ts +9 -1
- package/src/ssr/pwa-messages.ts +10 -2
- package/src/ssr/rail-navigation.ts +5 -1
- package/src/ssr/settings-draft.ts +153 -0
- package/src/ssr/shell-data.tsx +2 -0
- package/src/styles/global.css +1 -0
- package/src/styles/profile-picture.css +270 -0
- package/src/styles/utilities-navigation.css +6 -0
- package/src/workflows/ai/policy.ts +49 -0
- package/src/workflows/ai/runtime.ts +131 -21
- package/src/workflows/ai/store.ts +283 -35
- package/src/workflows/ai/types.ts +8 -2
- package/src/workflows/ai-actions.ts +148 -120
- package/src/workflows/authoring.ts +31 -0
- package/src/workflows/contracts.ts +1 -0
- package/src/workflows/definition.ts +2 -0
- package/src/workflows/editor.ts +15 -0
- package/src/workflows/index.ts +2 -1
- package/src/workflows/language/compiler.ts +34 -3
- package/src/workflows/runtime/executor.ts +132 -33
- package/src/workflows/runtime/index.ts +2 -0
- package/src/workflows/runtime/ports.ts +5 -1
- package/src/workflows/store/actions.ts +1 -1
- package/src/workflows/store/definitions.ts +5 -0
- package/src/workflows/store/runs.ts +5 -3
- package/src/workflows/store/worker.ts +7 -2
- package/src/ai/model-pricing.ts +0 -43
- package/src/ai/model-request-settings.ts +0 -98
- package/src/ai/settings-messages.ts +0 -129
- package/src/shared/ai-model-request-settings.ts +0 -21
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { bodyLimit } from "hono/body-limit";
|
|
3
|
+
import { HTTPException } from "hono/http-exception";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { appAccessManagementPath } from "../_internal/app-access-path";
|
|
6
|
+
import { getApp } from "../_internal/registry";
|
|
7
|
+
import type { AppMeta } from "../contracts/app";
|
|
8
|
+
import type { AppAccessDeclaration } from "../contracts/app-access";
|
|
9
|
+
import { PrincipalSchema } from "../contracts/shared";
|
|
10
|
+
import { type AuthContext, auth } from "../server/middleware/auth";
|
|
11
|
+
import { v } from "../server/middleware/validator";
|
|
12
|
+
import { AppAccessManagementError, changeAppAccess, readAppAccess } from "../services/app-access-management";
|
|
13
|
+
|
|
14
|
+
// One UUID principal, level and action; the 4 KiB envelope also bounds malformed JSON.
|
|
15
|
+
export const APP_ACCESS_CHANGE_MAX_BYTES = 4096;
|
|
16
|
+
const repairQuery = v("query", z.object({ repair: z.literal("1").optional() }));
|
|
17
|
+
const permission = z.enum(["read", "admin"]);
|
|
18
|
+
export const AppAccessChangeSchema = z.discriminatedUnion("action", [
|
|
19
|
+
z.object({ action: z.literal("grant"), principal: PrincipalSchema, permission }).strict(),
|
|
20
|
+
z
|
|
21
|
+
.object({ action: z.literal("update"), accessId: z.union([z.string().uuid(), z.literal("inherited-authenticated")]), permission })
|
|
22
|
+
.strict(),
|
|
23
|
+
z.object({ action: z.literal("revoke"), accessId: z.union([z.string().uuid(), z.literal("inherited-authenticated")]) }).strict(),
|
|
24
|
+
]);
|
|
25
|
+
const base = () =>
|
|
26
|
+
new Hono<AuthContext>()
|
|
27
|
+
.use("*", async (c, next) => {
|
|
28
|
+
c.header("Cache-Control", "private, no-store");
|
|
29
|
+
await next();
|
|
30
|
+
})
|
|
31
|
+
.use("*", auth.requireRole("authenticated"))
|
|
32
|
+
.onError((error, c) => {
|
|
33
|
+
if (error instanceof AppAccessManagementError) return c.json({ code: error.code, message: error.message }, error.status);
|
|
34
|
+
if (error instanceof HTTPException) return error.getResponse();
|
|
35
|
+
throw error;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/** Mounted by the framework before the app-use gate; management still requires its own current grant. */
|
|
39
|
+
export const createOwnedAppAccessRoutes = (app: AppMeta, canManage?: AppAccessDeclaration["canManage"]) =>
|
|
40
|
+
base()
|
|
41
|
+
.get("/", async (c) => c.json(await readAppAccess({ app, authority: auth.getAuthority(c), canManage })))
|
|
42
|
+
.post("/", bodyLimit({ maxSize: APP_ACCESS_CHANGE_MAX_BYTES }), v("json", AppAccessChangeSchema), async (c) =>
|
|
43
|
+
c.json(
|
|
44
|
+
await changeAppAccess({
|
|
45
|
+
app,
|
|
46
|
+
authority: auth.getAuthority(c),
|
|
47
|
+
canManage,
|
|
48
|
+
change: c.req.valid("json"),
|
|
49
|
+
requestId: c.req.header("x-request-id"),
|
|
50
|
+
}),
|
|
51
|
+
),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
/** Stable Core URL. Managers continue at the owning app; administrators explicitly choose repair. */
|
|
55
|
+
export const createAppAccessRoutes = () =>
|
|
56
|
+
base()
|
|
57
|
+
.get("/:appId", repairQuery, async (c) => {
|
|
58
|
+
const appId = c.req.param("appId");
|
|
59
|
+
if (!appId) return c.json({ message: "App not found" }, 404);
|
|
60
|
+
const app = await getApp(appId);
|
|
61
|
+
if (!app) return c.json({ message: "App not found" }, 404);
|
|
62
|
+
if (app.access?.kind !== "grants")
|
|
63
|
+
throw new AppAccessManagementError("APP_ACCESS_FIXED", "This application uses a fixed access rule.", 409);
|
|
64
|
+
if (c.req.query("repair") !== "1") return c.redirect(appAccessManagementPath(app), 307);
|
|
65
|
+
return c.json(await readAppAccess({ app, authority: auth.getAuthority(c), repair: true }));
|
|
66
|
+
})
|
|
67
|
+
.post("/:appId", repairQuery, bodyLimit({ maxSize: APP_ACCESS_CHANGE_MAX_BYTES }), v("json", AppAccessChangeSchema), async (c) => {
|
|
68
|
+
const appId = c.req.param("appId");
|
|
69
|
+
if (!appId) return c.json({ message: "App not found" }, 404);
|
|
70
|
+
const app = await getApp(appId);
|
|
71
|
+
if (!app) return c.json({ message: "App not found" }, 404);
|
|
72
|
+
if (app.access?.kind !== "grants")
|
|
73
|
+
throw new AppAccessManagementError("APP_ACCESS_FIXED", "This application uses a fixed access rule.", 409);
|
|
74
|
+
if (c.req.query("repair") !== "1") return c.redirect(appAccessManagementPath(app), 307);
|
|
75
|
+
return c.json(
|
|
76
|
+
await changeAppAccess({
|
|
77
|
+
app,
|
|
78
|
+
authority: auth.getAuthority(c),
|
|
79
|
+
repair: true,
|
|
80
|
+
change: c.req.valid("json"),
|
|
81
|
+
requestId: c.req.header("x-request-id"),
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
export type AppAccessApi = ReturnType<typeof createAppAccessRoutes>;
|
package/src/api/app-approval.ts
CHANGED
|
@@ -27,9 +27,10 @@ import {
|
|
|
27
27
|
type createAppApprovalService,
|
|
28
28
|
} from "../services/app-approval";
|
|
29
29
|
import { type NewBrowserSignInNotifier, readBrowserCookie } from "../services/known-browsers";
|
|
30
|
-
import { SessionAppDeviceInactiveError, SessionEpochChangedError } from "../services/session";
|
|
30
|
+
import { SessionAccountMismatchError, SessionAppDeviceInactiveError, SessionEpochChangedError } from "../services/session";
|
|
31
31
|
import { appNameIn } from "../ssr/runtime";
|
|
32
32
|
import { createDeviceActions } from "./app-approval-actions";
|
|
33
|
+
import { accountMismatchResponse } from "./auth/account-mismatch";
|
|
33
34
|
|
|
34
35
|
const PairingIdSchema = z.object({ pairingId: z.string().uuid() }).strict();
|
|
35
36
|
const DevicePageSchema = z.object({ after: z.string().uuid().optional() }).strict();
|
|
@@ -56,6 +57,7 @@ export const createAppApprovalRoutes = (
|
|
|
56
57
|
.use("/device", noTransform())
|
|
57
58
|
.use("/device/*", noTransform())
|
|
58
59
|
.onError((error, c) => {
|
|
60
|
+
if (error instanceof SessionAccountMismatchError) return accountMismatchResponse(c);
|
|
59
61
|
if (error instanceof AppApprovalError) return c.json({ code: error.code, message: error.code }, error.status);
|
|
60
62
|
if (error instanceof HTTPException && error.status < 500) return c.json({ code: "INVALID_REQUEST" }, error.status);
|
|
61
63
|
// Never include SQL, pairing secrets, signature bodies or keys in errors.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { bodyLimit } from "hono/body-limit";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import type { CompiledCapabilities } from "../_internal/capabilities";
|
|
5
|
+
import { AppEventEnvelopeSchema, appEventSubscriptionId } from "../contracts/app-events";
|
|
6
|
+
import { deliverAppEvent } from "../events/app-events";
|
|
7
|
+
import { REQUEST_IDLE_TIMEOUT_SECONDS, setRequestTimeout } from "../server/request-timeout";
|
|
8
|
+
import { verifyAppEventInvocation } from "../services/identity/invocation-token";
|
|
9
|
+
|
|
10
|
+
const DeliveryInput = z
|
|
11
|
+
.object({ envelope: AppEventEnvelopeSchema, subscriptionId: z.string().max(400), attempt: z.number().int().min(1).max(8) })
|
|
12
|
+
.strict();
|
|
13
|
+
export const createAppEventDeliveryRoutes = (compiled: CompiledCapabilities, verify = verifyAppEventInvocation) =>
|
|
14
|
+
new Hono().use(bodyLimit({ maxSize: 8192 })).post("/deliver", async (c) => {
|
|
15
|
+
const payload: unknown = await c.req.json().catch(() => null);
|
|
16
|
+
const parsed = DeliveryInput.safeParse(payload);
|
|
17
|
+
if (!parsed.success) return c.json({ code: "schema_mismatch" }, 422);
|
|
18
|
+
const { envelope, subscriptionId, attempt } = parsed.data;
|
|
19
|
+
const token = c.req.header("authorization")?.match(/^Bearer (\S+)$/i)?.[1];
|
|
20
|
+
if (!token || !(await verify(token, { appId: compiled.manifest.appId, subscriptionId, eventId: envelope.id, payload })))
|
|
21
|
+
return c.json({ code: "unauthorized" }, 401);
|
|
22
|
+
const sub = compiled.subscribes.get(envelope.type);
|
|
23
|
+
if (
|
|
24
|
+
!sub ||
|
|
25
|
+
sub.manifest.version !== envelope.version ||
|
|
26
|
+
subscriptionId !== appEventSubscriptionId(compiled.manifest.appId, envelope.type, envelope.version)
|
|
27
|
+
)
|
|
28
|
+
return c.json({ code: "subscription_removed" }, 410);
|
|
29
|
+
if (envelope.place) return c.json({ code: "resource_events_unsupported" }, 410);
|
|
30
|
+
if (!sub.definition.schema.safeParse(envelope.data).success) return c.json({ code: "schema_mismatch" }, 422);
|
|
31
|
+
const signal = AbortSignal.any([c.req.raw.signal, AbortSignal.timeout(30000)]);
|
|
32
|
+
let onAbort: () => void = () => {};
|
|
33
|
+
const aborted = new Promise<never>((_, reject) => {
|
|
34
|
+
onAbort = () => reject(signal.reason);
|
|
35
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
36
|
+
if (signal.aborted) onAbort();
|
|
37
|
+
});
|
|
38
|
+
setRequestTimeout(c, 40);
|
|
39
|
+
try {
|
|
40
|
+
await Promise.race([
|
|
41
|
+
deliverAppEvent({ compiled, subscriptionId, envelope, context: { subscriptionId, eventId: envelope.id, attempt, signal } }),
|
|
42
|
+
aborted,
|
|
43
|
+
]);
|
|
44
|
+
return c.json({ done: true });
|
|
45
|
+
} catch {
|
|
46
|
+
return c.json({ code: signal.aborted && signal.reason?.name === "TimeoutError" ? "handler_timeout" : "handler_failed" }, 503);
|
|
47
|
+
} finally {
|
|
48
|
+
signal.removeEventListener("abort", onAbort);
|
|
49
|
+
setRequestTimeout(c, REQUEST_IDLE_TIMEOUT_SECONDS);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { sql } from "bun";
|
|
2
|
+
import { Hono, type MiddlewareHandler } from "hono";
|
|
3
|
+
import { describeRoute } from "hono-openapi";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { AppDetailsSchema } from "../contracts/app-details";
|
|
6
|
+
import { AppEventEnvelopeSchema } from "../contracts/app-events";
|
|
7
|
+
import { CapabilityQualifiedIdSchema } from "../contracts/capabilities";
|
|
8
|
+
import { type AuthContext, auth, expectUserBackedActor, getLocale, v } from "../server";
|
|
9
|
+
import { AppAccessManagementError } from "../services/app-access-management";
|
|
10
|
+
import { appDetails } from "../services/app-details";
|
|
11
|
+
import { appEventShape, appEventShapeId, recordedAppEventShape } from "../services/app-event-shapes";
|
|
12
|
+
import { appEventBroker } from "../services/app-events";
|
|
13
|
+
|
|
14
|
+
const App = z.string().regex(/^[a-z][a-z0-9-]{0,79}$/);
|
|
15
|
+
const Filter = z.object({
|
|
16
|
+
app: App.optional(),
|
|
17
|
+
state: z.enum(["pending", "retrying", "done", "failed", "cancelled"]).optional(),
|
|
18
|
+
eventType: CapabilityQualifiedIdSchema.optional(),
|
|
19
|
+
offset: z.coerce.number().int().min(0).default(0),
|
|
20
|
+
limit: z.coerce.number().int().min(1).max(100).default(50),
|
|
21
|
+
});
|
|
22
|
+
export const createAppEventRoutes = (
|
|
23
|
+
authenticate: MiddlewareHandler<AuthContext> = auth.requireRole("admin"),
|
|
24
|
+
service = appEventBroker,
|
|
25
|
+
details = appDetails,
|
|
26
|
+
) =>
|
|
27
|
+
new Hono<AuthContext>()
|
|
28
|
+
.use(authenticate)
|
|
29
|
+
.use(auth.requireOAuthScope("admin"))
|
|
30
|
+
.get(
|
|
31
|
+
"/apps/:appId",
|
|
32
|
+
describeRoute({ tags: ["App events"], summary: "Inspect one application's access, events and notifications" }),
|
|
33
|
+
v("param", z.object({ appId: App })),
|
|
34
|
+
async (c) => {
|
|
35
|
+
try {
|
|
36
|
+
const result = await details(c.req.valid("param").appId, { authority: auth.getAuthority(c), locale: getLocale(c) });
|
|
37
|
+
return result ? c.json(AppDetailsSchema.parse(result)) : c.json({ code: "app_not_found" }, 404);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error instanceof AppAccessManagementError) return c.json({ code: error.code, message: error.message }, error.status);
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
)
|
|
44
|
+
.get("/catalog", describeRoute({ tags: ["App events"], summary: "List declared event types and subscribers" }), async (c) =>
|
|
45
|
+
c.json(await service.catalog(getLocale(c))),
|
|
46
|
+
)
|
|
47
|
+
.get(
|
|
48
|
+
"/subscriptions",
|
|
49
|
+
describeRoute({ tags: ["App events"], summary: "Inspect declared subscriptions and delivery counts" }),
|
|
50
|
+
v("query", z.object({ app: App.optional() })),
|
|
51
|
+
async (c) => c.json(await service.subscriptions(c.req.valid("query").app)),
|
|
52
|
+
)
|
|
53
|
+
.get("/deliveries", describeRoute({ tags: ["App events"], summary: "List app event deliveries" }), v("query", Filter), async (c) =>
|
|
54
|
+
c.json(await service.deliveries(c.req.valid("query"))),
|
|
55
|
+
)
|
|
56
|
+
.post(
|
|
57
|
+
"/deliveries/:id/redeliver",
|
|
58
|
+
describeRoute({ tags: ["App events"], summary: "Retry one delivery preserving its event ID" }),
|
|
59
|
+
v("param", z.object({ id: z.uuid() })),
|
|
60
|
+
async (c) => {
|
|
61
|
+
const user = expectUserBackedActor(c);
|
|
62
|
+
const result = await service.redeliver(c.req.valid("param").id, {
|
|
63
|
+
userId: user.id,
|
|
64
|
+
uid: user.uid,
|
|
65
|
+
provider: user.provider,
|
|
66
|
+
roles: user.roles,
|
|
67
|
+
});
|
|
68
|
+
return result ? c.json(result) : c.json({ code: "not_redeliverable" }, 409);
|
|
69
|
+
},
|
|
70
|
+
)
|
|
71
|
+
.post("/status/acknowledge", describeRoute({ tags: ["App events"], summary: "Acknowledge a reconciled retention gap" }), async (c) => {
|
|
72
|
+
const user = expectUserBackedActor(c);
|
|
73
|
+
const result = await service.acknowledgeGap({ userId: user.id, uid: user.uid, provider: user.provider, roles: user.roles });
|
|
74
|
+
return result ? c.json(result) : c.json({ code: "no_gap_detected" }, 409);
|
|
75
|
+
})
|
|
76
|
+
.get(
|
|
77
|
+
"/status",
|
|
78
|
+
describeRoute({ tags: ["App events"], summary: "Read broker progress, retention gaps and rejected-event metadata" }),
|
|
79
|
+
async (c) => c.json(await service.status()),
|
|
80
|
+
)
|
|
81
|
+
.get(
|
|
82
|
+
"/record/:eventType",
|
|
83
|
+
describeRoute({ tags: ["App events"], summary: "Record schema-only JSONL fixtures; no stored event values are returned" }),
|
|
84
|
+
v("param", z.object({ eventType: CapabilityQualifiedIdSchema })),
|
|
85
|
+
v("query", z.object({ shapeOnly: z.literal("true"), limit: z.coerce.number().int().min(1).max(100).default(10) })),
|
|
86
|
+
async (c) => {
|
|
87
|
+
const { eventType } = c.req.valid("param");
|
|
88
|
+
const { limit } = c.req.valid("query");
|
|
89
|
+
const catalog = await service.catalog();
|
|
90
|
+
const rows = await sql<
|
|
91
|
+
{ version: number; envelope: { data: unknown } }[]
|
|
92
|
+
>`SELECT version, envelope FROM events.app_events WHERE type=${eventType} AND state='accepted' ORDER BY seq DESC LIMIT ${limit}`;
|
|
93
|
+
try {
|
|
94
|
+
const shapes = new Map<string, { version: number; shape: ReturnType<typeof recordedAppEventShape> }>();
|
|
95
|
+
for (const row of rows) {
|
|
96
|
+
const shape = recordedAppEventShape(row.envelope.data);
|
|
97
|
+
shapes.set(JSON.stringify([row.version, shape]), { version: row.version, shape });
|
|
98
|
+
}
|
|
99
|
+
const lines = [...shapes.values()].map((row) => {
|
|
100
|
+
const declaration = catalog.events.find((event) => event.type === eventType && event.version === row.version);
|
|
101
|
+
if (!declaration) throw new Error("shape_unavailable");
|
|
102
|
+
return JSON.stringify(
|
|
103
|
+
AppEventEnvelopeSchema.parse({
|
|
104
|
+
id: appEventShapeId(eventType, row.version, row.shape),
|
|
105
|
+
type: eventType,
|
|
106
|
+
version: row.version,
|
|
107
|
+
source: eventType.split(".")[0],
|
|
108
|
+
time: "2000-01-01T00:00:00.000Z",
|
|
109
|
+
subject: { type: declaration.subject, id: "00000000-0000-4000-8000-000000000001" },
|
|
110
|
+
...(declaration.place ? { place: { type: declaration.place, id: "00000000-0000-4000-8000-000000000001" } } : {}),
|
|
111
|
+
data: appEventShape(declaration.dataSchema, 0, row.shape),
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
return c.body(lines.length ? lines.join("\n") + "\n" : "", 200, {
|
|
116
|
+
"content-type": "application/x-ndjson",
|
|
117
|
+
"cache-control": "no-store",
|
|
118
|
+
});
|
|
119
|
+
} catch {
|
|
120
|
+
return c.json({ code: "shape_unavailable", message: "A deterministic fixture cannot be generated for this declaration." }, 422);
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type Context, Hono } from "hono";
|
|
2
|
+
import { bindAppAccessRequest } from "../_internal/app-access-request";
|
|
3
|
+
import { hasLocalResourceAppAccess } from "../_internal/app-access-resources";
|
|
4
|
+
import { getApp } from "../_internal/registry";
|
|
5
|
+
import type { AppMeta } from "../contracts/app";
|
|
6
|
+
import { type AuthContext, auth, type RequestAuthority } from "../server/middleware/auth";
|
|
7
|
+
import { rateLimit } from "../server/middleware/rate-limit";
|
|
8
|
+
import { canUseApp } from "../services/app-access";
|
|
9
|
+
import { resolveInvocationAuthority } from "../services/identity/invocation-actor";
|
|
10
|
+
import { isInvocationJwtCandidate, verifyAppAccessProbe } from "../services/identity/invocation-token";
|
|
11
|
+
|
|
12
|
+
/** Framework-only endpoint, mounted before the app gate to avoid recursive entry checks. */
|
|
13
|
+
export const createResourceAppAccessRoutes = (app: Pick<AppMeta, "id" | "access">) => {
|
|
14
|
+
const router = new Hono<AuthContext>();
|
|
15
|
+
if (app.access?.kind === "resources")
|
|
16
|
+
router.post("/check", auth.readOnly, async (c) => {
|
|
17
|
+
c.header("Cache-Control", "private, no-store");
|
|
18
|
+
const token = c.req.header("authorization")?.match(/^Bearer (\S+)$/i)?.[1];
|
|
19
|
+
if (!token || !isInvocationJwtCandidate(token)) return c.body(null, 401);
|
|
20
|
+
const claims = await verifyAppAccessProbe(token, app.id);
|
|
21
|
+
if (!claims) return c.body(null, 401);
|
|
22
|
+
if (!hasLocalResourceAppAccess(app.id)) return c.body(null, 403);
|
|
23
|
+
const authority = await resolveInvocationAuthority(claims);
|
|
24
|
+
if (!authority) return c.body(null, 401);
|
|
25
|
+
return c.body(null, (await canUseApp(bindAppAccessRequest(authority, c.req.raw), app)) ? 204 : 403);
|
|
26
|
+
});
|
|
27
|
+
if (app.id === "core")
|
|
28
|
+
router.post(
|
|
29
|
+
"/apps/:appId",
|
|
30
|
+
auth.readOnly,
|
|
31
|
+
async (c, next) => {
|
|
32
|
+
c.header("Cache-Control", "private, no-store");
|
|
33
|
+
const token = c.req.header("authorization")?.match(/^Bearer (\S+)$/i)?.[1];
|
|
34
|
+
if (token && isInvocationJwtCandidate(token)) return c.body(null, 401);
|
|
35
|
+
await next();
|
|
36
|
+
},
|
|
37
|
+
auth.requireRole("authenticated"),
|
|
38
|
+
rateLimit(),
|
|
39
|
+
(c) => answerCoreProbe(c, auth.getAuthority(c)),
|
|
40
|
+
);
|
|
41
|
+
return router;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const answerCoreProbe = async (c: Context<AuthContext>, authority: RequestAuthority): Promise<Response> => {
|
|
45
|
+
const target = await getApp(c.req.param("appId") ?? "");
|
|
46
|
+
if (!target || target.access?.kind !== "resources") return c.body(null, 404);
|
|
47
|
+
return c.body(null, (await canUseApp(bindAppAccessRequest(authority, c.req.raw), target)) ? 204 : 403);
|
|
48
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { i18n } from "@k2b/stdlib";
|
|
2
|
+
import type { Context } from "hono";
|
|
3
|
+
import { getLocale } from "../../server/locale";
|
|
4
|
+
|
|
5
|
+
const messages = i18n.define({
|
|
6
|
+
baseLocale: "en",
|
|
7
|
+
messages: {
|
|
8
|
+
en: {
|
|
9
|
+
recovery: "Use another sign-in method to confirm your identity. To recover an account, sign out first.",
|
|
10
|
+
mismatch: "Sign in with the account already open in this browser. If your session expired, open the sign-in page and start again.",
|
|
11
|
+
},
|
|
12
|
+
de: {
|
|
13
|
+
recovery: "Bestätige deine Identität mit einer anderen Anmeldemethode. Zur Kontowiederherstellung melde dich zuerst ab.",
|
|
14
|
+
mismatch:
|
|
15
|
+
"Melde dich mit dem Konto an, das in diesem Browser geöffnet ist. Falls deine Sitzung abgelaufen ist, öffne die Anmeldeseite und beginne erneut.",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const accountMismatchResponse = (c: Context) =>
|
|
21
|
+
c.json({ code: "ACCOUNT_MISMATCH", message: messages.resolve([getLocale(c)]).t.mismatch }, 403);
|
|
22
|
+
|
|
23
|
+
export const confirmationRecoveryResponse = (c: Context) =>
|
|
24
|
+
c.json({ code: "CONFIRMATION_METHOD_UNAVAILABLE", message: messages.resolve([getLocale(c)]).t.recovery }, 403);
|
package/src/api/auth.ts
CHANGED
|
@@ -4,15 +4,18 @@ import { describeRoute } from "hono-openapi";
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { env } from "../config";
|
|
6
6
|
import { ChangeExpiredPasswordSchema } from "../contracts";
|
|
7
|
+
import { coreUserEventPublisher } from "../events/core-user-created";
|
|
7
8
|
import { type AuthContext, auth, getLocale, jsonResponse, rateLimit, respond, v } from "../server";
|
|
8
9
|
import { accounts, audit, authFlows, getFreeIpaConfig, logger, webauthn } from "../services";
|
|
9
10
|
import { isAccountCategoryAllowed } from "../services/account-category-policy";
|
|
10
11
|
import type { AuthNotificationSender } from "../services/auth-flows/notification-sender";
|
|
11
12
|
import { legalConsent } from "../services/legal-consent";
|
|
12
|
-
import { SessionEpochChangedError } from "../services/session";
|
|
13
|
+
import { SessionAccountMismatchError, SessionEpochChangedError } from "../services/session";
|
|
13
14
|
import * as categorySettings from "../services/settings";
|
|
14
15
|
import { publicCloudOrigin } from "../shared/app-url";
|
|
15
16
|
|
|
17
|
+
import { accountMismatchResponse, confirmationRecoveryResponse } from "./auth/account-mismatch";
|
|
18
|
+
|
|
16
19
|
const log = logger("auth");
|
|
17
20
|
|
|
18
21
|
import { ErrorResponseSchema, MessageResponseSchema } from "../contracts";
|
|
@@ -33,7 +36,19 @@ const jsonError = (c: Context, message: string, status: 400 | 401 | 500 | 503) =
|
|
|
33
36
|
/** Authentication routes: login, logout. */
|
|
34
37
|
export const createAuthRoutes = (notificationSender: AuthNotificationSender) =>
|
|
35
38
|
new Hono<AuthContext>()
|
|
39
|
+
.onError((error, c) => {
|
|
40
|
+
if (error instanceof SessionAccountMismatchError) return accountMismatchResponse(c);
|
|
41
|
+
throw error;
|
|
42
|
+
})
|
|
36
43
|
.use(rateLimit())
|
|
44
|
+
.use(async (c, next) => {
|
|
45
|
+
if (
|
|
46
|
+
c.req.header("X-Cloud-Reauthenticate") === "1" &&
|
|
47
|
+
["/admin-login", "/change-expired-password", "/password-reset/complete"].some((path) => c.req.path.endsWith(path))
|
|
48
|
+
)
|
|
49
|
+
return confirmationRecoveryResponse(c);
|
|
50
|
+
return next();
|
|
51
|
+
})
|
|
37
52
|
.post(
|
|
38
53
|
"/legal-consent",
|
|
39
54
|
describeRoute({ tags: ["Auth"], summary: "Explicitly accept terms after sign-in; unlock the pending session" }),
|
|
@@ -301,7 +316,7 @@ export const createAuthRoutes = (notificationSender: AuthNotificationSender) =>
|
|
|
301
316
|
tags: ["Auth"],
|
|
302
317
|
summary: "Complete password reset",
|
|
303
318
|
description:
|
|
304
|
-
"Use a one-time reset token to first sign the account out everywhere, including unpairing Cloud Login and mobile app
|
|
319
|
+
"Use a one-time reset token to first sign the account out everywhere, including unpairing Cloud Login and mobile app devices, then set the password, sign out everywhere again and create the new browser session. If signing out everywhere fails before the change, the password stays unchanged; if it fails after the change, no session is issued. Both answer 500, and a new reset link is required. signOutOthersFailed is false whenever a session is issued.",
|
|
305
320
|
responses: {
|
|
306
321
|
200: jsonResponse(PasswordChangeAuthResponseSchema, "Password reset completed and session created"),
|
|
307
322
|
400: jsonResponse(ErrorResponseSchema, "Password reset failed"),
|
|
@@ -399,14 +414,22 @@ export const createAuthRoutes = (notificationSender: AuthNotificationSender) =>
|
|
|
399
414
|
// prior `DO NOTHING` could log into a pre-existing `uid='admin'` row that
|
|
400
415
|
// was locally demoted (non-admin local user, or even an IPA row named
|
|
401
416
|
// admin), silently bypassing the intent of the emergency endpoint.
|
|
402
|
-
await sql
|
|
417
|
+
await sql.begin(async (tx) => {
|
|
418
|
+
const [created] = await tx<{ id: string; inserted: boolean }[]>`
|
|
403
419
|
INSERT INTO auth.users (uid, provider, profile, admin, given_name, sn, display_name)
|
|
404
420
|
VALUES ('admin', 'local', 'user', true, 'Admin', '', 'Admin')
|
|
405
421
|
ON CONFLICT (uid) DO UPDATE SET
|
|
406
422
|
provider = 'local',
|
|
407
423
|
profile = 'user',
|
|
408
424
|
admin = true
|
|
425
|
+
RETURNING id, (xmax = 0) AS inserted
|
|
409
426
|
`;
|
|
427
|
+
if (created?.inserted)
|
|
428
|
+
await coreUserEventPublisher().publish(tx, "user.created", {
|
|
429
|
+
subject: created.id,
|
|
430
|
+
data: { userId: created.id, provider: "local" },
|
|
431
|
+
});
|
|
432
|
+
});
|
|
410
433
|
|
|
411
434
|
const user = await accounts.users.get({ uid: "admin" });
|
|
412
435
|
if (!user) return jsonError(c, "Failed to resolve admin user.", 500);
|