@k2b/cloud 0.39.0 → 0.41.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/README.md +8 -0
- package/package.json +11 -4
- package/src/_internal/app-favicon.ts +4 -2
- package/src/_internal/capabilities.ts +72 -48
- package/src/_internal/capability-reviews.ts +81 -0
- package/src/_internal/capability-schemas.ts +29 -0
- package/src/_internal/capability-sentences.ts +217 -1
- package/src/_internal/capability-streams.ts +7 -0
- package/src/_internal/define-app.ts +49 -18
- package/src/_internal/font-preloads.ts +2 -1
- package/src/_internal/heartbeat.ts +203 -14
- package/src/_internal/registry.ts +28 -1
- package/src/_internal/runtime-watcher.ts +6 -0
- package/src/_internal/setup-lock.ts +103 -0
- package/src/_internal/static-assets.ts +52 -3
- package/src/_internal/status-preserving-ssr.ts +2 -0
- package/src/access/PermissionEditor.tsx +2 -2
- package/src/account/EntitySearch.tsx +1 -1
- package/src/ai/action-review.ts +101 -0
- package/src/ai/background-review.ts +72 -0
- package/src/ai/capabilities.ts +29 -11
- package/src/ai/capability-execution.ts +76 -43
- package/src/ai/capability-result.ts +95 -0
- package/src/ai/chat/action-card.ts +464 -0
- package/src/ai/chat/action-messages.ts +129 -0
- package/src/ai/chat/action-question.browser-harness.tsx +245 -0
- package/src/ai/chat/action-question.fixtures.ts +733 -0
- package/src/ai/chat/action-question.tsx +680 -0
- package/src/ai/chat/action-text.ts +288 -0
- package/src/ai/chat/blocks.tsx +19 -25
- package/src/ai/chat/message-actions.tsx +6 -0
- package/src/ai/chat/tool-action-words.ts +31 -0
- package/src/ai/chat/tool-action.tsx +456 -0
- package/src/ai/chat/turn-view.tsx +10 -160
- package/src/ai/chat/view-block.tsx +15 -5
- package/src/ai/chat-task-contracts.ts +4 -1
- package/src/ai/chat-task-reasons-migrate.ts +7 -0
- package/src/ai/chat-task-reasons.ts +1 -1
- package/src/ai/chat-tasks.ts +55 -25
- package/src/ai/client/controller.ts +59 -0
- package/src/ai/default-tools.ts +9 -1
- package/src/ai/executor.ts +198 -39
- package/src/ai/firecrawl-tools.ts +15 -2
- package/src/ai/index.ts +3 -0
- package/src/ai/migrate.ts +26 -2
- package/src/ai/open-questions.ts +172 -0
- package/src/ai/projects-routes.ts +3 -0
- package/src/ai/protocol.ts +14 -2
- package/src/ai/question-text.ts +8 -0
- package/src/ai/recent-resources.ts +240 -0
- package/src/ai/recent-sent-refs.ts +38 -0
- package/src/ai/routes.ts +195 -5
- package/src/ai/runtime.ts +1 -0
- package/src/ai/skills-routes.ts +3 -0
- package/src/ai/skills.ts +10 -1
- package/src/ai/store.ts +245 -24
- package/src/ai/testing.ts +36 -0
- package/src/ai/tool-audit.ts +8 -0
- package/src/ai/tool-sentences.ts +152 -34
- package/src/ai/tool-wording.ts +36 -8
- package/src/ai/tools.ts +8 -3
- package/src/ai/types.ts +43 -4
- package/src/api/accounts-entities.ts +2 -1
- package/src/api/admin-ai-projects.ts +2 -0
- package/src/api/admin-ai-skills.ts +2 -0
- package/src/api/admin-ai-usage.ts +2 -0
- package/src/api/admin-geoip.ts +80 -0
- package/src/api/admin-identity.ts +3 -0
- package/src/api/admin-lifecycle.ts +3 -0
- package/src/api/admin-outgoing-mail.ts +2 -0
- package/src/api/app-approval-actions.ts +31 -7
- package/src/api/app-approval.ts +46 -5
- package/src/api/approvals-loader.ts +23 -1
- package/src/api/approvals.ts +2 -0
- package/src/api/auth/schemas.ts +3 -0
- package/src/api/auth.ts +67 -12
- package/src/api/capabilities.ts +44 -11
- package/src/api/capability-streams.ts +12 -0
- package/src/api/help.ts +4 -28
- package/src/api/index.ts +6 -1
- package/src/api/lasting-access-messages.ts +17 -0
- package/src/api/mcp.ts +30 -3
- package/src/api/me-panel-consent.ts +71 -0
- package/src/api/me.ts +159 -36
- package/src/api/search.ts +3 -1
- package/src/api/sync-ops.ts +2 -0
- package/src/api/widgets.ts +3 -0
- package/src/approvals/Approvals.tsx +166 -262
- package/src/approvals/approval-card.ts +68 -0
- package/src/approvals/messages.ts +12 -24
- package/src/browser/CloudResourceSearch.browser-harness.tsx +10 -2
- package/src/browser/app-approval.ts +54 -5
- package/src/browser/commands.ts +22 -6
- package/src/browser/composer-catalog.ts +45 -3
- package/src/browser/composer-insert.ts +10 -0
- package/src/browser/composer-open-messages.ts +17 -0
- package/src/browser/composer-open.ts +110 -0
- package/src/browser/discoverable-command.ts +6 -0
- package/src/browser/hints.ts +6 -0
- package/src/browser/live-connection.ts +26 -2
- package/src/browser/navigation-apps.ts +7 -0
- package/src/browser/panel-context-bridge.ts +30 -0
- package/src/browser/panel-context-receiver.ts +53 -0
- package/src/browser/panel-menu.tsx +56 -0
- package/src/browser/panel-messages.ts +11 -0
- package/src/browser/panel-shortcuts.ts +31 -0
- package/src/browser/panel-slot.ts +412 -10
- package/src/browser/panel.ts +37 -0
- package/src/browser/search-commands.ts +6 -3
- package/src/capabilities/client.ts +8 -2
- package/src/capabilities/reviews.ts +205 -0
- package/src/capabilities/server.ts +10 -3
- package/src/capabilities/testing.ts +47 -0
- package/src/capabilities/types.ts +2 -2
- package/src/cli/account.ts +13 -3
- package/src/cli/admin/geoip.ts +115 -0
- package/src/cli/admin/index.ts +3 -0
- package/src/contracts/app-approval.ts +123 -16
- package/src/contracts/approvals.ts +2 -2
- package/src/contracts/capabilities.ts +410 -18
- package/src/contracts/geoip.ts +42 -0
- package/src/contracts/index.ts +3 -0
- package/src/contracts/panel-context-plain.ts +99 -0
- package/src/contracts/panel-context.ts +93 -0
- package/src/contracts/seen-hints.ts +35 -0
- package/src/contracts/shared.ts +3 -0
- package/src/server/client-ip.ts +5 -0
- package/src/server/help-payload.ts +38 -0
- package/src/server/middleware/auth.ts +13 -1
- package/src/server/middleware/middleware.ts +9 -0
- package/src/server/middleware/no-transform.ts +19 -0
- package/src/server/middleware/rate-limit.ts +4 -6
- package/src/server/request-context/address.ts +45 -0
- package/src/server/request-context/geoip.ts +380 -0
- package/src/server/request-context/index.ts +33 -0
- package/src/server/request-context/user-agent.ts +95 -0
- package/src/services/accounts/app.ts +23 -3
- package/src/services/accounts/users.ts +12 -1
- package/src/services/app-approval.ts +382 -59
- package/src/services/approvals/device.ts +49 -31
- package/src/services/approvals/index.ts +7 -5
- package/src/services/approvals/list.ts +27 -6
- package/src/services/approvals/question.ts +68 -0
- package/src/services/audit/index.ts +1 -0
- package/src/services/auth-flows/ipa.ts +39 -2
- package/src/services/auth-flows/notification-sender.ts +3 -0
- package/src/services/auth-flows/password-reset.ts +32 -6
- package/src/services/geoip/index.ts +164 -0
- package/src/services/geoip/store.ts +30 -0
- package/src/services/geoip/upload.ts +150 -0
- package/src/services/identity/routes.ts +5 -1
- package/src/services/index.ts +13 -0
- package/src/services/known-browsers.ts +312 -0
- package/src/services/login-address-store.ts +51 -0
- package/src/services/login-context.ts +118 -0
- package/src/services/mandates/index.ts +3 -0
- package/src/services/mandates/policy.ts +15 -3
- package/src/services/notifications/email-frame.ts +1 -1
- package/src/services/oauth-tokens.ts +16 -2
- package/src/services/panel-context-consent.ts +58 -0
- package/src/services/pdf/markdown.ts +160 -2
- package/src/services/service-account-credentials.ts +21 -0
- package/src/services/session/index.ts +296 -41
- package/src/services/session/recent.ts +13 -0
- package/src/services/webauthn.ts +12 -0
- package/src/shared/email-html.ts +2 -0
- package/src/shared/help.ts +3 -0
- package/src/shared-text/cursor.ts +22 -0
- package/src/shared-text/documents.ts +142 -0
- package/src/shared-text/events.ts +43 -0
- package/src/shared-text/index.ts +12 -0
- package/src/shared-text/log.ts +48 -0
- package/src/shared-text/text-diff.ts +23 -0
- package/src/shared-text/types.ts +41 -0
- package/src/ssr/AdminLayout.tsx +2 -0
- package/src/ssr/AppLaunchpadPanel.tsx +2 -2
- package/src/ssr/CloudShell.island.tsx +10 -2
- package/src/ssr/HelpPanelArticle.island.tsx +308 -0
- package/src/ssr/HelpPanelBack.island.tsx +32 -0
- package/src/ssr/HelpPanelMenu.island.tsx +133 -0
- package/src/ssr/HelpPanelSearch.island.tsx +123 -0
- package/src/ssr/HelpPanelShortcuts.island.tsx +22 -0
- package/src/ssr/HelpPanelView.island.tsx +27 -0
- package/src/ssr/Layout.tsx +25 -2
- package/src/ssr/LayoutHelp.browser-harness.tsx +2 -0
- package/src/ssr/LayoutHelp.tsx +222 -419
- package/src/ssr/LayoutHelpPanel.browser-harness.tsx +135 -0
- package/src/ssr/LayoutHelpPanel.server.browser-harness.tsx +131 -0
- package/src/ssr/LayoutHelpPanel.server.tsx +115 -0
- package/src/ssr/LayoutHelpTrigger.tsx +27 -2
- package/src/ssr/MobileNavigation.tsx +2 -2
- package/src/ssr/PanelLayout.tsx +9 -1
- package/src/ssr/PanelRuntime.island.tsx +7 -0
- package/src/ssr/PanelSlot.tsx +53 -3
- package/src/ssr/ShellRuntime.island.tsx +11 -2
- package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +29 -0
- package/src/ssr/app-color.ts +14 -9
- package/src/ssr/app-navigation.ts +16 -3
- package/src/ssr/compact-shell.browser-harness.ts +30 -22
- package/src/ssr/dialog-app-colors.browser-harness.tsx +43 -0
- package/src/ssr/help-messages.ts +4 -0
- package/src/ssr/help.ts +21 -0
- package/src/ssr/hints.ts +25 -0
- package/src/ssr/index.ts +3 -0
- package/src/ssr/layout-context.ts +2 -0
- package/src/ssr/layout-help-article.server.ts +50 -0
- package/src/ssr/layout-help-panel-controls.tsx +43 -0
- package/src/ssr/layout-help-panel-document.ts +67 -0
- package/src/ssr/layout-help-panel-lifecycle.ts +32 -0
- package/src/ssr/layout-help-panel-menu.tsx +113 -0
- package/src/ssr/layout-help-panel-modules.ts +7 -0
- package/src/ssr/layout-help-panel-reader.tsx +159 -0
- package/src/ssr/layout-help-panel-search.tsx +69 -0
- package/src/ssr/layout-help-panel-shortcuts.ts +7 -0
- package/src/ssr/layout-help-panel-state.ts +141 -0
- package/src/ssr/layout-help-panel-types.ts +19 -0
- package/src/ssr/layout-help-start.ts +33 -7
- package/src/ssr/layout-help-views.tsx +484 -0
- package/src/ssr/panel-consent.ts +29 -0
- package/src/ssr/panel-context.ts +10 -0
- package/src/ssr/panel-idle.ts +9 -0
- package/src/ssr/panel-slot.browser-harness.tsx +53 -4
- package/src/ssr/platform-messages.ts +18 -0
- package/src/ssr/shell-data.tsx +2 -1
- package/src/styles/approvals.css +6 -0
- package/src/styles/effects.css +213 -9
- package/src/styles/file-chooser.css +2 -1
- package/src/styles/global.css +49 -12
- package/src/styles/panel.css +47 -4
- package/src/styles/resource-search.css +6 -2
- package/src/styles/tokens.css +17 -11
- package/src/styles/utilities-buttons.css +3 -2
- package/src/styles/utilities-data.css +2 -1
- package/src/styles/utilities-feedback.css +2 -2
- package/src/styles/utilities-layout.css +3 -13
- package/src/styles/utilities-markdown-editor.css +6 -6
- package/src/styles/utilities-navigation.css +17 -22
package/src/api/me.ts
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* third-party management lives in the accounts admin app.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { err, fail, ok } from "@k2b/stdlib";
|
|
7
|
+
import { err, fail, ok, type Result } from "@k2b/stdlib";
|
|
8
|
+
import { sql } from "bun";
|
|
8
9
|
import { type Context, Hono, type MiddlewareHandler } from "hono";
|
|
10
|
+
import { deleteCookie } from "hono/cookie";
|
|
9
11
|
import { createMiddleware } from "hono/factory";
|
|
10
12
|
import { describeRoute } from "hono-openapi";
|
|
11
13
|
import { z } from "zod";
|
|
@@ -26,6 +28,7 @@ import {
|
|
|
26
28
|
NotificationQuietSettingsSchema,
|
|
27
29
|
RegisterBrowserNotificationEndpointSchema,
|
|
28
30
|
ServiceAccountCredentialSchema,
|
|
31
|
+
SignOutOthersResponseSchema,
|
|
29
32
|
UpdateAvatarResponseSchema,
|
|
30
33
|
UpdateAvatarSchema,
|
|
31
34
|
UpdateNotificationQuietSettingsSchema,
|
|
@@ -40,22 +43,29 @@ import {
|
|
|
40
43
|
import { CapabilityAppIdSchema } from "../contracts/capabilities";
|
|
41
44
|
import { type AuthContext, auth, getLocale, getTimeZone, jsonResponse, rateLimit, requiresAuth, respond, v } from "../server";
|
|
42
45
|
import { APP_SESSION_FORBIDDEN } from "../server/middleware/auth";
|
|
46
|
+
import { noTransform } from "../server/middleware/no-transform";
|
|
43
47
|
import {
|
|
44
48
|
accountLifecycle,
|
|
45
49
|
accountsAppService as accountsService,
|
|
46
50
|
audit,
|
|
47
51
|
browserNotifications,
|
|
52
|
+
logger,
|
|
48
53
|
mandates,
|
|
49
54
|
notifications,
|
|
50
55
|
serviceAccountCredentials,
|
|
51
56
|
webauthn,
|
|
52
57
|
} from "../services";
|
|
53
58
|
import { MandatePolicyV1Schema } from "../services/mandates";
|
|
59
|
+
import { ConfirmedBrowserSignInError, requireConfirmedBrowserSignIn } from "../services/session/recent";
|
|
54
60
|
import { coreSettings } from "../services/settings/api";
|
|
55
61
|
|
|
62
|
+
import { lastingAccessApiMessages } from "./lasting-access-messages";
|
|
63
|
+
import { createMePanelConsentRoutes } from "./me-panel-consent";
|
|
56
64
|
import { createMeRailRoutes } from "./me-rail";
|
|
57
65
|
import { createMeWebVitalsRoutes } from "./me-web-vitals";
|
|
58
66
|
|
|
67
|
+
const log = logger("api:me");
|
|
68
|
+
|
|
59
69
|
const toAccountsActor = (user: AuthContext["Variables"]["user"]) => ({
|
|
60
70
|
userId: user.id,
|
|
61
71
|
uid: user.uid,
|
|
@@ -67,19 +77,10 @@ const toAccountsActor = (user: AuthContext["Variables"]["user"]) => ({
|
|
|
67
77
|
const webOnly = (c: Context) => c.json(APP_SESSION_FORBIDDEN, 403);
|
|
68
78
|
|
|
69
79
|
/**
|
|
70
|
-
* Guards
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* its user, so it passes that check — and could then enrol a passkey, which is
|
|
75
|
-
* a full account takeover, or mint further keys that outlive the revocation of
|
|
76
|
-
* the one that was leaked. No scope in the vocabulary means "may add an
|
|
77
|
-
* authenticator", so a credential must not reach these endpoints at all.
|
|
78
|
-
*
|
|
79
|
-
* A credential may still *read* its account's passkeys and keys; only the
|
|
80
|
-
* mutations are gated. Browser sessions and user-issued OAuth access tokens
|
|
81
|
-
* both resolve to a `user` actor and are unaffected. The mobile app's session
|
|
82
|
-
* is rejected too: whatever it created would outlive removing the phone.
|
|
80
|
+
* Guards passkey and key removal, password changes, and account deletion.
|
|
81
|
+
* Browser sessions and user OAuth actors may perform these actions; personal
|
|
82
|
+
* API keys and mobile app sessions may not. Adding keys and sign-in methods
|
|
83
|
+
* requires a confirmed browser sign-in through the separate creation gate.
|
|
83
84
|
*/
|
|
84
85
|
const requireDirectUserActor = createMiddleware<AuthContext>(async (c, next) => {
|
|
85
86
|
if (c.get("actor")?.kind !== "user") {
|
|
@@ -95,6 +96,27 @@ const requireDirectUserActor = createMiddleware<AuthContext>(async (c, next) =>
|
|
|
95
96
|
return next();
|
|
96
97
|
});
|
|
97
98
|
|
|
99
|
+
const requireBrowserSignIn = createMiddleware<AuthContext>(async (c, next) => {
|
|
100
|
+
if (!(await auth.browserSignIn(c))) {
|
|
101
|
+
return c.json(
|
|
102
|
+
{ code: "BROWSER_SIGN_IN_REQUIRED", message: lastingAccessApiMessages.resolve([getLocale(c)]).t.browserSignInRequired },
|
|
103
|
+
403,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return next();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const lastingAccessMessage = (c: Context<AuthContext>, code: "REAUTHENTICATE" | "FORBIDDEN") => {
|
|
110
|
+
const { t } = lastingAccessApiMessages.resolve([getLocale(c)]);
|
|
111
|
+
return code === "REAUTHENTICATE" ? t.reauthenticate : t.forbidden;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/** The creation services fail with these codes only when the browser sign-in does not confirm the write. */
|
|
115
|
+
const localizeLastingAccessResult = <T>(c: Context<AuthContext>, result: Result<T>): Result<T> =>
|
|
116
|
+
!result.ok && (result.error.code === "REAUTHENTICATE" || result.error.code === "FORBIDDEN")
|
|
117
|
+
? fail({ ...result.error, message: lastingAccessMessage(c, result.error.code) })
|
|
118
|
+
: result;
|
|
119
|
+
|
|
98
120
|
const ExtendAccountResponseSchema = z.object({
|
|
99
121
|
message: z.string(),
|
|
100
122
|
newExpiry: z.string().datetime().optional(),
|
|
@@ -194,11 +216,13 @@ const NotificationHistoryQuerySchema = z.object({
|
|
|
194
216
|
});
|
|
195
217
|
const mandateRoutes = createMeMandateRoutes();
|
|
196
218
|
const app = new Hono<AuthContext>()
|
|
219
|
+
.use("/notifications/history/*", noTransform())
|
|
197
220
|
.use(rateLimit())
|
|
198
221
|
.use(auth.requireRole("authenticated"))
|
|
199
222
|
.use(auth.requireUser())
|
|
200
223
|
.route("/", mandateRoutes)
|
|
201
224
|
.route("/", createMeRailRoutes())
|
|
225
|
+
.route("/", createMePanelConsentRoutes())
|
|
202
226
|
.route("/", createMeWebVitalsRoutes())
|
|
203
227
|
|
|
204
228
|
.get(
|
|
@@ -540,7 +564,7 @@ const app = new Hono<AuthContext>()
|
|
|
540
564
|
|
|
541
565
|
.post(
|
|
542
566
|
"/passkeys/registration/start",
|
|
543
|
-
|
|
567
|
+
requireBrowserSignIn,
|
|
544
568
|
describeRoute({
|
|
545
569
|
tags: ["Me"],
|
|
546
570
|
summary: "Start passkey registration",
|
|
@@ -549,15 +573,28 @@ const app = new Hono<AuthContext>()
|
|
|
549
573
|
responses: {
|
|
550
574
|
200: jsonResponse(z.unknown(), "Passkey registration options"),
|
|
551
575
|
401: jsonResponse(ErrorResponseSchema, "Authentication required"),
|
|
552
|
-
403: jsonResponse(ErrorResponseSchema, "
|
|
576
|
+
403: jsonResponse(ErrorResponseSchema, "Confirmed browser sign-in required (BROWSER_SIGN_IN_REQUIRED or REAUTHENTICATE)"),
|
|
553
577
|
},
|
|
554
578
|
}),
|
|
555
|
-
async (c) =>
|
|
579
|
+
async (c) => {
|
|
580
|
+
const signIn = await auth.browserSignIn(c);
|
|
581
|
+
if (!signIn) throw new Error("Browser sign-in gate was not applied");
|
|
582
|
+
try {
|
|
583
|
+
await requireConfirmedBrowserSignIn(sql, signIn, (code) => {
|
|
584
|
+
throw new ConfirmedBrowserSignInError(code);
|
|
585
|
+
});
|
|
586
|
+
} catch (error) {
|
|
587
|
+
if (error instanceof ConfirmedBrowserSignInError)
|
|
588
|
+
return c.json({ code: error.code, message: lastingAccessMessage(c, error.code) }, 403);
|
|
589
|
+
throw error;
|
|
590
|
+
}
|
|
591
|
+
return respond(c, webauthn.beginRegistration({ user: c.get("user") }));
|
|
592
|
+
},
|
|
556
593
|
)
|
|
557
594
|
|
|
558
595
|
.post(
|
|
559
596
|
"/passkeys/registration/verify",
|
|
560
|
-
|
|
597
|
+
requireBrowserSignIn,
|
|
561
598
|
describeRoute({
|
|
562
599
|
tags: ["Me"],
|
|
563
600
|
summary: "Verify passkey registration",
|
|
@@ -567,20 +604,26 @@ const app = new Hono<AuthContext>()
|
|
|
567
604
|
201: jsonResponse(WebAuthnPasskeySchema, "Passkey created"),
|
|
568
605
|
400: jsonResponse(ErrorResponseSchema, "Passkey registration failed"),
|
|
569
606
|
401: jsonResponse(ErrorResponseSchema, "Authentication required"),
|
|
570
|
-
403: jsonResponse(ErrorResponseSchema, "
|
|
607
|
+
403: jsonResponse(ErrorResponseSchema, "Confirmed browser sign-in required (BROWSER_SIGN_IN_REQUIRED or REAUTHENTICATE)"),
|
|
571
608
|
},
|
|
572
609
|
}),
|
|
573
610
|
v("json", CreateWebAuthnPasskeySchema),
|
|
574
611
|
async (c) =>
|
|
575
612
|
respond(
|
|
576
613
|
c,
|
|
577
|
-
() => {
|
|
614
|
+
async () => {
|
|
578
615
|
const data = c.req.valid("json");
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
616
|
+
const browserSignIn = await auth.browserSignIn(c);
|
|
617
|
+
if (!browserSignIn) throw new Error("Browser sign-in gate was not applied");
|
|
618
|
+
return localizeLastingAccessResult(
|
|
619
|
+
c,
|
|
620
|
+
await webauthn.finishRegistration({
|
|
621
|
+
browserSignIn,
|
|
622
|
+
user: c.get("user"),
|
|
623
|
+
name: data.name,
|
|
624
|
+
response: data.response as never,
|
|
625
|
+
}),
|
|
626
|
+
);
|
|
584
627
|
},
|
|
585
628
|
201,
|
|
586
629
|
),
|
|
@@ -611,7 +654,7 @@ const app = new Hono<AuthContext>()
|
|
|
611
654
|
|
|
612
655
|
.post(
|
|
613
656
|
"/api-keys",
|
|
614
|
-
|
|
657
|
+
requireBrowserSignIn,
|
|
615
658
|
describeRoute({
|
|
616
659
|
tags: ["Me"],
|
|
617
660
|
summary: "Create current user API key",
|
|
@@ -622,7 +665,10 @@ const app = new Hono<AuthContext>()
|
|
|
622
665
|
201: jsonResponse(CreateUserApiKeyResponseSchema, "API key created"),
|
|
623
666
|
400: jsonResponse(ErrorResponseSchema, "Failed to create API key"),
|
|
624
667
|
401: jsonResponse(ErrorResponseSchema, "Authentication required"),
|
|
625
|
-
403: jsonResponse(
|
|
668
|
+
403: jsonResponse(
|
|
669
|
+
ErrorResponseSchema,
|
|
670
|
+
"Confirmed browser sign-in required (BROWSER_SIGN_IN_REQUIRED or REAUTHENTICATE), or models-only API turned off",
|
|
671
|
+
),
|
|
626
672
|
},
|
|
627
673
|
}),
|
|
628
674
|
v("json", CreateUserApiKeySchema),
|
|
@@ -638,12 +684,18 @@ const app = new Hono<AuthContext>()
|
|
|
638
684
|
"Models-only API keys are turned off. An administrator can turn on the OpenAI-compatible API in the AI settings.",
|
|
639
685
|
),
|
|
640
686
|
);
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
687
|
+
const browserSignIn = await auth.browserSignIn(c);
|
|
688
|
+
if (!browserSignIn) throw new Error("Browser sign-in gate was not applied");
|
|
689
|
+
return localizeLastingAccessResult(
|
|
690
|
+
c,
|
|
691
|
+
await serviceAccountCredentials.createUserApiToken({
|
|
692
|
+
browserSignIn,
|
|
693
|
+
access: data.access,
|
|
694
|
+
user,
|
|
695
|
+
name: data.name,
|
|
696
|
+
expiresAt: data.expiresAt ?? null,
|
|
697
|
+
}),
|
|
698
|
+
);
|
|
647
699
|
},
|
|
648
700
|
201,
|
|
649
701
|
),
|
|
@@ -700,25 +752,96 @@ const app = new Hono<AuthContext>()
|
|
|
700
752
|
describeRoute({
|
|
701
753
|
tags: ["Me"],
|
|
702
754
|
summary: "Change current user password",
|
|
703
|
-
description:
|
|
755
|
+
description:
|
|
756
|
+
"Change the authenticated user's password and end every other sign-in. Requires the current password. The current browser stays signed in with a new cookie and its original sign-in time. signInAgain is true when the caller must sign in again: this browser could not be kept, or the request did not come from a browser sign-in.",
|
|
704
757
|
...requiresAuth,
|
|
705
758
|
responses: {
|
|
706
|
-
200: jsonResponse(
|
|
759
|
+
200: jsonResponse(SignOutOthersResponseSchema, "Password changed"),
|
|
707
760
|
400: jsonResponse(ErrorResponseSchema, "Failed to change password"),
|
|
708
761
|
401: jsonResponse(ErrorResponseSchema, "Current password is incorrect"),
|
|
762
|
+
500: jsonResponse(
|
|
763
|
+
ErrorResponseSchema,
|
|
764
|
+
"Password changed, but signing out other sign-ins failed (SIGN_OUT_OTHERS_FAILED). Retry POST /api/me/sign-ins/sign-out-others.",
|
|
765
|
+
),
|
|
709
766
|
403: jsonResponse(ErrorResponseSchema, "Not permitted for API keys or other credentials"),
|
|
710
767
|
},
|
|
711
768
|
}),
|
|
712
769
|
v("json", ChangePasswordSchema),
|
|
713
770
|
async (c) =>
|
|
714
771
|
respond(c, async () => {
|
|
772
|
+
const keep = await auth.browserSignIn(c);
|
|
715
773
|
const result = await accountsService.user.changeOwnPassword({
|
|
774
|
+
keep: keep && { sid: keep.sid, authEpoch: keep.authEpoch },
|
|
716
775
|
user: c.get("user"),
|
|
717
776
|
currentPassword: c.req.valid("json").currentPassword,
|
|
718
777
|
newPassword: c.req.valid("json").newPassword,
|
|
719
778
|
});
|
|
720
779
|
if (!result.ok) return result;
|
|
721
|
-
|
|
780
|
+
if (result.data.kept) auth.session.setWebCookie(c, result.data.kept.token, result.data.kept.expiresAt);
|
|
781
|
+
const signInAgain = !result.data.kept;
|
|
782
|
+
if (keep && signInAgain) deleteCookie(c, "session_token", { path: "/" });
|
|
783
|
+
return ok({
|
|
784
|
+
message: signInAgain
|
|
785
|
+
? keep
|
|
786
|
+
? "Password changed. You are signed out everywhere, including this browser. Sign in again."
|
|
787
|
+
: "Password changed. You are signed out everywhere. Sign in again."
|
|
788
|
+
: "Password changed successfully.",
|
|
789
|
+
signInAgain,
|
|
790
|
+
});
|
|
791
|
+
}),
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
.post(
|
|
795
|
+
"/sign-ins/sign-out-others",
|
|
796
|
+
auth.rejectAppSession,
|
|
797
|
+
requireBrowserSignIn,
|
|
798
|
+
describeRoute({
|
|
799
|
+
tags: ["Me"],
|
|
800
|
+
summary: "Sign out every other sign-in",
|
|
801
|
+
description:
|
|
802
|
+
"End other sign-ins and rotate this browser's cookie while preserving its sign-in time and expiry; signInAgain is true if this browser could not be kept. Cloud Login phones stay paired. API keys, passkeys and mandates stay valid.",
|
|
803
|
+
...requiresAuth,
|
|
804
|
+
responses: {
|
|
805
|
+
200: jsonResponse(SignOutOthersResponseSchema, "Other sign-ins ended"),
|
|
806
|
+
401: jsonResponse(ErrorResponseSchema, "Authentication required"),
|
|
807
|
+
403: jsonResponse(
|
|
808
|
+
ErrorResponseSchema,
|
|
809
|
+
"Browser sign-in required (BROWSER_SIGN_IN_REQUIRED); mobile app sessions receive FORBIDDEN",
|
|
810
|
+
),
|
|
811
|
+
500: jsonResponse(ErrorResponseSchema, "Signing out other sign-ins failed (SIGN_OUT_OTHERS_FAILED)"),
|
|
812
|
+
},
|
|
813
|
+
}),
|
|
814
|
+
async (c) =>
|
|
815
|
+
respond(c, async () => {
|
|
816
|
+
const signIn = await auth.browserSignIn(c);
|
|
817
|
+
if (!signIn) throw new Error("Browser sign-in gate was not applied");
|
|
818
|
+
try {
|
|
819
|
+
const { kept } = await auth.session.endOtherSignIns(c.get("user").id, {
|
|
820
|
+
keep: { sid: signIn.sid, authEpoch: signIn.authEpoch },
|
|
821
|
+
reason: "sign_out_others",
|
|
822
|
+
});
|
|
823
|
+
const user = c.get("user");
|
|
824
|
+
try {
|
|
825
|
+
await audit.record({
|
|
826
|
+
action: "auth.session.sign_out_others",
|
|
827
|
+
outcome: "allowed",
|
|
828
|
+
actor: toAccountsActor(user),
|
|
829
|
+
target: { type: "user", id: user.id, label: user.uid, provider: user.provider },
|
|
830
|
+
metadata: { keptThisBrowser: !!kept },
|
|
831
|
+
});
|
|
832
|
+
} catch {
|
|
833
|
+
log.warn("Other sign-ins ended, but recording the audit event failed", { userId: user.id });
|
|
834
|
+
}
|
|
835
|
+
if (kept) auth.session.setWebCookie(c, kept.token, kept.expiresAt);
|
|
836
|
+
const signInAgain = !kept;
|
|
837
|
+
if (signInAgain) deleteCookie(c, "session_token", { path: "/" });
|
|
838
|
+
return ok({
|
|
839
|
+
message: signInAgain ? "Other sign-ins ended. Sign in again in this browser." : "Other sign-ins ended.",
|
|
840
|
+
signInAgain,
|
|
841
|
+
});
|
|
842
|
+
} catch {
|
|
843
|
+
return fail({ code: "SIGN_OUT_OTHERS_FAILED", status: 500, message: "Signing out your other sign-ins did not work. Try again." });
|
|
844
|
+
}
|
|
722
845
|
}),
|
|
723
846
|
)
|
|
724
847
|
|
package/src/api/search.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
UniversalSearchDataSchema,
|
|
13
13
|
} from "../contracts";
|
|
14
14
|
import { type AuthContext, auth, expectUserBackedActor, jsonResponse, preferredLocale, requiresAuth, v } from "../server";
|
|
15
|
+
import { noTransform } from "../server/middleware/no-transform";
|
|
15
16
|
import { logger } from "../services";
|
|
16
17
|
import { invocationAuthorityFromRequest } from "../services/identity/invocation-authority";
|
|
17
18
|
import { searchInvocationOperation } from "../services/identity/invocation-operations";
|
|
@@ -103,7 +104,8 @@ type AppOutcome = { appId: string; status: SearchProviderStatus; items: SearchIt
|
|
|
103
104
|
export const createSearchRoutes = (dependencies: SearchRouteDependencies = {}) => {
|
|
104
105
|
const registry = dependencies.listCapabilities ?? listCapabilities;
|
|
105
106
|
const fetchProvider = dependencies.fetch ?? globalThis.fetch;
|
|
106
|
-
|
|
107
|
+
const router = new Hono<AuthContext>().use("/search/*", noTransform());
|
|
108
|
+
return router.use(dependencies.authenticate ?? auth.requireRole("authenticated")).get(
|
|
107
109
|
"/search",
|
|
108
110
|
describeRoute({
|
|
109
111
|
tags: ["Search"],
|
package/src/api/sync-ops.ts
CHANGED
|
@@ -2,6 +2,7 @@ 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 { noTransform } from "../server/middleware/no-transform";
|
|
5
6
|
import { invocationAuthorityFromRequest } from "../services/identity/invocation-authority";
|
|
6
7
|
import { syncInvocationOperation } from "../services/identity/invocation-operations";
|
|
7
8
|
import { normalizeInvocationRequestId, signInvocationToken } from "../services/identity/invocation-token";
|
|
@@ -19,6 +20,7 @@ type Dependencies = {
|
|
|
19
20
|
/** Core owns the credential-bearing hop; targets receive only an operation-bound invocation. */
|
|
20
21
|
export const createSyncOpsProxyRoutes = (dependencies: Dependencies = {}) =>
|
|
21
22
|
new Hono<AuthContext>()
|
|
23
|
+
.use("*", noTransform())
|
|
22
24
|
.use(dependencies.authenticate ?? auth.requireRole("admin"))
|
|
23
25
|
.use(rejectReservedWorkloadCredential)
|
|
24
26
|
.use(auth.requireOAuthScope("admin"))
|
package/src/api/widgets.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
widgetKey,
|
|
21
21
|
} from "../contracts/widgets";
|
|
22
22
|
import { type AuthContext, auth, preferredLocale, rejectReservedWorkloadCredential } from "../server";
|
|
23
|
+
import { noTransform } from "../server/middleware/no-transform";
|
|
23
24
|
import { WIDGET_SETTINGS_PARAM, WIDGET_SETTINGS_PARAM_MAX_LENGTH, WIDGET_SIZE_PARAM } from "../server/widget-request";
|
|
24
25
|
import { CLOUD_INVOCATION_TOKEN_TTL_SECONDS } from "../services/identity/constants";
|
|
25
26
|
import { invocationAuthorityFromRequest } from "../services/identity/invocation-authority";
|
|
@@ -299,6 +300,8 @@ export const createWidgetRoutes = (dependencies: WidgetRouteDependencies = {}) =
|
|
|
299
300
|
|
|
300
301
|
return (
|
|
301
302
|
new Hono<AuthContext>()
|
|
303
|
+
.use("/widgets/v1", noTransform())
|
|
304
|
+
.use("/widgets/v1/*", noTransform())
|
|
302
305
|
.use(dependencies.authenticate ?? auth.requireRole("authenticated"))
|
|
303
306
|
.use(rejectReservedWorkloadCredential)
|
|
304
307
|
.use(auth.requireOAuthScope("read", "admin"))
|