@agent-native/core 0.18.0 → 0.19.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 +1 -11
- package/dist/a2a/client.d.ts +7 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +3 -0
- package/dist/a2a/client.js.map +1 -1
- package/dist/cli/connect.d.ts +94 -0
- package/dist/cli/connect.d.ts.map +1 -0
- package/dist/cli/connect.js +443 -0
- package/dist/cli/connect.js.map +1 -0
- package/dist/cli/index.js +16 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-config-writers.d.ts +71 -0
- package/dist/cli/mcp-config-writers.d.ts.map +1 -0
- package/dist/cli/mcp-config-writers.js +210 -0
- package/dist/cli/mcp-config-writers.js.map +1 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +11 -63
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts +6 -1
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +5 -4
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +6 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +20 -10
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/conversation/AgentConversation.d.ts +18 -0
- package/dist/client/conversation/AgentConversation.d.ts.map +1 -0
- package/dist/client/conversation/AgentConversation.js +94 -0
- package/dist/client/conversation/AgentConversation.js.map +1 -0
- package/dist/client/conversation/AgentConversation.spec.d.ts +2 -0
- package/dist/client/conversation/AgentConversation.spec.d.ts.map +1 -0
- package/dist/client/conversation/AgentConversation.spec.js +69 -0
- package/dist/client/conversation/AgentConversation.spec.js.map +1 -0
- package/dist/client/conversation/index.d.ts +4 -0
- package/dist/client/conversation/index.d.ts.map +1 -0
- package/dist/client/conversation/index.js +3 -0
- package/dist/client/conversation/index.js.map +1 -0
- package/dist/client/conversation/types.d.ts +54 -0
- package/dist/client/conversation/types.d.ts.map +1 -0
- package/dist/client/conversation/types.js +2 -0
- package/dist/client/conversation/types.js.map +1 -0
- package/dist/client/conversation/use-near-bottom-autoscroll.d.ts +15 -0
- package/dist/client/conversation/use-near-bottom-autoscroll.d.ts.map +1 -0
- package/dist/client/conversation/use-near-bottom-autoscroll.js +66 -0
- package/dist/client/conversation/use-near-bottom-autoscroll.js.map +1 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/resources/ResourceTree.d.ts.map +1 -1
- package/dist/client/resources/ResourceTree.js +2 -2
- package/dist/client/resources/ResourceTree.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +4 -28
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/code-agents/index.d.ts +1 -0
- package/dist/code-agents/index.d.ts.map +1 -1
- package/dist/code-agents/index.js +1 -0
- package/dist/code-agents/index.js.map +1 -1
- package/dist/code-agents/transcript-normalizer.d.ts +50 -0
- package/dist/code-agents/transcript-normalizer.d.ts.map +1 -0
- package/dist/code-agents/transcript-normalizer.js +356 -0
- package/dist/code-agents/transcript-normalizer.js.map +1 -0
- package/dist/extensions/schema.d.ts +1 -1
- package/dist/mcp/build-server.d.ts +20 -3
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +120 -15
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/builtin-tools.d.ts +8 -1
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +115 -13
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/mcp/connect-route.d.ts +43 -0
- package/dist/mcp/connect-route.d.ts.map +1 -0
- package/dist/mcp/connect-route.js +638 -0
- package/dist/mcp/connect-route.js.map +1 -0
- package/dist/mcp/connect-store.d.ts +132 -0
- package/dist/mcp/connect-store.d.ts.map +1 -0
- package/dist/mcp/connect-store.js +434 -0
- package/dist/mcp/connect-store.js.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +23 -4
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/stdio.d.ts.map +1 -1
- package/dist/mcp/stdio.js +1 -0
- package/dist/mcp/stdio.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +1 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth.d.ts +17 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +192 -49
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts +43 -0
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +25 -0
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +12 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +42 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/identity-sso-store.d.ts +86 -0
- package/dist/server/identity-sso-store.d.ts.map +1 -0
- package/dist/server/identity-sso-store.js +243 -0
- package/dist/server/identity-sso-store.js.map +1 -0
- package/dist/server/identity-sso.d.ts +78 -0
- package/dist/server/identity-sso.d.ts.map +1 -0
- package/dist/server/identity-sso.js +425 -0
- package/dist/server/identity-sso.js.map +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +2 -1
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/server/open-route.d.ts.map +1 -1
- package/dist/server/open-route.js +36 -5
- package/dist/server/open-route.js.map +1 -1
- package/dist/server/request-context.d.ts +8 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js.map +1 -1
- package/dist/sharing/schema.d.ts +1 -1
- package/docs/content/code-agents-ui.md +14 -3
- package/docs/content/cross-app-sso.md +118 -0
- package/docs/content/external-agents.md +130 -51
- package/docs/content/migration-workbench.md +1 -1
- package/package.json +2 -1
package/dist/server/auth.d.ts
CHANGED
|
@@ -178,6 +178,23 @@ export declare function safeReturnPath(raw: string | null | undefined): string;
|
|
|
178
178
|
* proceeds. Mirrors the rawPath/getLoginHtml resolution in the auth guard.
|
|
179
179
|
*/
|
|
180
180
|
export declare function getConfiguredLoginHtml(event: H3Event): string | null;
|
|
181
|
+
/**
|
|
182
|
+
* True only when the request originates from the local machine — the raw
|
|
183
|
+
* socket peer is `127.0.0.0/8`, `::1`, or the IPv4-mapped `::ffff:127.0.0.1`
|
|
184
|
+
* (an optional IPv6 zone id like `fe80::1%en0` is stripped first).
|
|
185
|
+
*
|
|
186
|
+
* `getRequestIP(event)` is called WITHOUT `{ xForwardedFor: true }`, so it
|
|
187
|
+
* returns the real connection peer and never an attacker-controlled
|
|
188
|
+
* `X-Forwarded-For` value — a remote client cannot spoof its way past this.
|
|
189
|
+
* Used to scope local-only conveniences (the desktop SSO broker and the dev
|
|
190
|
+
* auto-account) so a directly network-reachable dev server never exposes
|
|
191
|
+
* them to a remote visitor. NOTE: a reverse proxy / tunnel that connects to
|
|
192
|
+
* the dev server over localhost still appears as loopback, so this is a
|
|
193
|
+
* necessary but not sufficient gate — callers pair it with NODE_ENV and,
|
|
194
|
+
* for the dev account, a throwaway per-DB password.
|
|
195
|
+
*/
|
|
196
|
+
export declare function isLoopbackAddress(ip: string | undefined): boolean;
|
|
197
|
+
export declare function isExpectedAuthFailure(error: unknown): boolean;
|
|
181
198
|
/**
|
|
182
199
|
* Create a new session in the legacy sessions table.
|
|
183
200
|
* Used by google-oauth.ts for mobile deep linking.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAsChE,KAAK,KAAK,GAAG,SAAS,CAAC;AAQvB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAMlE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAwB5D,OAAO,EAIL,KAAK,oBAAoB,EAC1B,MAAM,qCAAqC,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/server/auth.js
CHANGED
|
@@ -47,6 +47,10 @@ import { extractOAuthStateAppId } from "../shared/oauth-state.js";
|
|
|
47
47
|
import { isValidWorkspaceAppIdFormat } from "../shared/workspace-app-id.js";
|
|
48
48
|
import { normalizeWorkspaceAppAudience, workspaceAppAudienceFromEnv, workspaceAppRouteAccessFromEnv, } from "../shared/workspace-app-audience.js";
|
|
49
49
|
import { BUILDER_CONNECT_OWNER_COOKIE, BUILDER_CONNECT_PARAM, BUILDER_STATE_PARAM, verifyBuilderCallbackStateAndGetOwner, verifyBuilderConnectTokenAndGetOwner, } from "./builder-browser.js";
|
|
50
|
+
// Pure env-read feature switch from a leaf module (no dependency back on
|
|
51
|
+
// auth.ts), so the guard and the SSO route handler share one validator and
|
|
52
|
+
// can never disagree about whether federated SSO is enabled.
|
|
53
|
+
import { isIdentitySsoEnabled } from "./identity-sso-store.js";
|
|
50
54
|
/**
|
|
51
55
|
* Get the configured session max age. Desktop SSO broker writes from
|
|
52
56
|
* OAuth flows read this so expiration stays consistent with the cookie.
|
|
@@ -310,6 +314,39 @@ export function getConfiguredLoginHtml(event) {
|
|
|
310
314
|
const rawPath = queryStart >= 0 ? url.slice(0, queryStart) : url;
|
|
311
315
|
return config.getLoginHtml?.(event, rawPath) ?? config.loginHtml ?? null;
|
|
312
316
|
}
|
|
317
|
+
/**
|
|
318
|
+
* True only when the request originates from the local machine — the raw
|
|
319
|
+
* socket peer is `127.0.0.0/8`, `::1`, or the IPv4-mapped `::ffff:127.0.0.1`
|
|
320
|
+
* (an optional IPv6 zone id like `fe80::1%en0` is stripped first).
|
|
321
|
+
*
|
|
322
|
+
* `getRequestIP(event)` is called WITHOUT `{ xForwardedFor: true }`, so it
|
|
323
|
+
* returns the real connection peer and never an attacker-controlled
|
|
324
|
+
* `X-Forwarded-For` value — a remote client cannot spoof its way past this.
|
|
325
|
+
* Used to scope local-only conveniences (the desktop SSO broker and the dev
|
|
326
|
+
* auto-account) so a directly network-reachable dev server never exposes
|
|
327
|
+
* them to a remote visitor. NOTE: a reverse proxy / tunnel that connects to
|
|
328
|
+
* the dev server over localhost still appears as loopback, so this is a
|
|
329
|
+
* necessary but not sufficient gate — callers pair it with NODE_ENV and,
|
|
330
|
+
* for the dev account, a throwaway per-DB password.
|
|
331
|
+
*/
|
|
332
|
+
export function isLoopbackAddress(ip) {
|
|
333
|
+
// Strip an optional IPv6 zone id (e.g. "fe80::1%en0") before comparing.
|
|
334
|
+
const normalised = (ip ?? "").split("%")[0];
|
|
335
|
+
return (normalised === "127.0.0.1" ||
|
|
336
|
+
normalised === "::1" ||
|
|
337
|
+
normalised === "::ffff:127.0.0.1" ||
|
|
338
|
+
normalised.startsWith("127."));
|
|
339
|
+
}
|
|
340
|
+
function isLoopbackRequest(event) {
|
|
341
|
+
let ip;
|
|
342
|
+
try {
|
|
343
|
+
ip = getRequestIP(event) ?? undefined;
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
ip = undefined;
|
|
347
|
+
}
|
|
348
|
+
return isLoopbackAddress(ip);
|
|
349
|
+
}
|
|
313
350
|
/**
|
|
314
351
|
* Read the desktop-SSO broker file, but only if the request is plausibly
|
|
315
352
|
* from the Electron desktop app *and* coming from the local machine.
|
|
@@ -329,21 +366,7 @@ async function readDesktopSsoSafely(event) {
|
|
|
329
366
|
return null;
|
|
330
367
|
if (!isElectronRequest(event))
|
|
331
368
|
return null;
|
|
332
|
-
|
|
333
|
-
let ip;
|
|
334
|
-
try {
|
|
335
|
-
ip = getRequestIP(event) ?? undefined;
|
|
336
|
-
}
|
|
337
|
-
catch {
|
|
338
|
-
ip = undefined;
|
|
339
|
-
}
|
|
340
|
-
// Strip an optional zone id (e.g. "fe80::1%en0") before comparing.
|
|
341
|
-
const normalised = (ip ?? "").split("%")[0];
|
|
342
|
-
const isLoopback = normalised === "127.0.0.1" ||
|
|
343
|
-
normalised === "::1" ||
|
|
344
|
-
normalised === "::ffff:127.0.0.1" ||
|
|
345
|
-
normalised.startsWith("127.");
|
|
346
|
-
if (!isLoopback)
|
|
369
|
+
if (!isLoopbackRequest(event))
|
|
347
370
|
return null;
|
|
348
371
|
return await readDesktopSso();
|
|
349
372
|
}
|
|
@@ -452,7 +475,7 @@ const EXPECTED_AUTH_FAILURE_PATTERNS = [
|
|
|
452
475
|
/already\s+(exists|registered|in\s+use)/i,
|
|
453
476
|
/not\s+verified/i,
|
|
454
477
|
];
|
|
455
|
-
function isExpectedAuthFailure(error) {
|
|
478
|
+
export function isExpectedAuthFailure(error) {
|
|
456
479
|
const msg = error?.message;
|
|
457
480
|
if (typeof msg !== "string")
|
|
458
481
|
return false;
|
|
@@ -901,6 +924,54 @@ function createAuthGuardFn() {
|
|
|
901
924
|
if (p === "/_agent-native/a2a") {
|
|
902
925
|
return;
|
|
903
926
|
}
|
|
927
|
+
// MCP protocol endpoint. `mountMCP` runs its own `verifyAuth` (Bearer
|
|
928
|
+
// ACCESS_TOKEN/ACCESS_TOKENS or A2A_SECRET JWT, open in dev) and is the
|
|
929
|
+
// authoritative gate — exactly like A2A above. Without this bypass the
|
|
930
|
+
// guard's blanket 401-for-/_agent-native/* below shadows that check, so
|
|
931
|
+
// an external coding agent (Claude Code / Codex / Cowork) connecting via
|
|
932
|
+
// the stdio proxy or HTTP can never reach it. Exact path only: the MCP
|
|
933
|
+
// handler returns early for `/_agent-native/mcp/*` management subroutes,
|
|
934
|
+
// which keep their normal session auth.
|
|
935
|
+
if (p === "/_agent-native/mcp") {
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
// MCP connect — frictionless external-agent connection. Like /open
|
|
939
|
+
// above, the connect *page* resolves the browser session itself and
|
|
940
|
+
// serves its own login form when unauthenticated (so the post-login
|
|
941
|
+
// reload returns to the same URL, carrying the device user_code in the
|
|
942
|
+
// query). The two unauthenticated device endpoints below are the CLI's
|
|
943
|
+
// OAuth-style polling pair: `device/start` (mint a device+user code) and
|
|
944
|
+
// `device/poll` (exchange an approved code for the token) — both must be
|
|
945
|
+
// reachable without a browser session because the CLI has none. They are
|
|
946
|
+
// protected by short-TTL, single-use, crypto-random codes + a creation
|
|
947
|
+
// rate-limit, not cookies.
|
|
948
|
+
//
|
|
949
|
+
// Everything that MINTS or MUTATES on behalf of the user — `/token`,
|
|
950
|
+
// `/device/authorize`, `/tokens`, `/tokens/revoke` — is intentionally
|
|
951
|
+
// NOT bypassed: the guard's default 401-for-/_agent-native/* is the
|
|
952
|
+
// correct gate for them. Those are POSTed by the in-page fetch, which
|
|
953
|
+
// carries the session cookie, so the guard (which only 401s when there
|
|
954
|
+
// is no session) lets the authenticated same-origin request through and
|
|
955
|
+
// the handler then re-checks the session itself (defense in depth).
|
|
956
|
+
if (p === "/_agent-native/mcp/connect" ||
|
|
957
|
+
p === "/_agent-native/mcp/connect/device/start" ||
|
|
958
|
+
p === "/_agent-native/mcp/connect/device/poll") {
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
// Cross-app SSO ("Sign in with Agent-Native") — CLIENT side. Both the
|
|
962
|
+
// `/login` entry point and the `/callback` (hit by a user who is, by
|
|
963
|
+
// definition, NOT yet signed in to THIS app) must bypass the blanket
|
|
964
|
+
// 401-for-/_agent-native/*: they resolve / mint the browser session
|
|
965
|
+
// themselves and verify a signature-bound, single-use, CSRF-stated
|
|
966
|
+
// hub token — not a cookie. This bypass is GATED on the opt-in env var
|
|
967
|
+
// so an unset `AGENT_NATIVE_IDENTITY_HUB_URL` is a true no-op (the
|
|
968
|
+
// guard's behaviour is byte-for-byte unchanged when SSO is off). The
|
|
969
|
+
// handler itself 404s when disabled as defence in depth.
|
|
970
|
+
if (isIdentitySsoEnabled() &&
|
|
971
|
+
(p === "/_agent-native/identity/login" ||
|
|
972
|
+
p === "/_agent-native/identity/callback")) {
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
904
975
|
// Internal processor endpoint for the A2A async-mode fanout. Mirrors the
|
|
905
976
|
// integration webhook fanout: when `message/send` is called with
|
|
906
977
|
// `async: true`, the JSON-RPC handler enqueues to a2a_tasks and self-
|
|
@@ -1000,8 +1071,8 @@ function createAuthGuardFn() {
|
|
|
1000
1071
|
return { error: "Unauthorized" };
|
|
1001
1072
|
}
|
|
1002
1073
|
// Local-dev convenience: on the first page GET of a freshly-scaffolded
|
|
1003
|
-
// app, transparently create + sign in `dev@local` instead of
|
|
1004
|
-
// sign-up form. Gated on NODE_ENV=development AND no real users in the
|
|
1074
|
+
// app, transparently create + sign in `dev@local.test` instead of
|
|
1075
|
+
// showing the sign-up form. Gated on NODE_ENV=development AND no real users in the
|
|
1005
1076
|
// DB, so production and any app that has ever had a real signup are
|
|
1006
1077
|
// unaffected. See maybeAutoCreateDevSession for full conditions.
|
|
1007
1078
|
if (getMethod(event) === "GET") {
|
|
@@ -1015,69 +1086,105 @@ function createAuthGuardFn() {
|
|
|
1015
1086
|
});
|
|
1016
1087
|
};
|
|
1017
1088
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1089
|
+
// `.test` is an RFC 6761 reserved TLD that never resolves, so this stays a
|
|
1090
|
+
// safe local-only address while still passing better-auth's `z.email()`
|
|
1091
|
+
// validator (a bare `dev@local` has no TLD and is rejected as INVALID_EMAIL,
|
|
1092
|
+
// which silently broke the zero-setup auto-sign-in on every fresh dev DB).
|
|
1093
|
+
const AUTO_DEV_ACCOUNT_EMAIL = "dev@local.test";
|
|
1094
|
+
// No fixed password: maybeAutoCreateDevSession mints a random one per DB
|
|
1095
|
+
// and prints it to the console once (see there).
|
|
1096
|
+
// Pre-fix local dev DBs may already contain a `dev@local` user. Treat that
|
|
1097
|
+
// legacy address as the dev account too, so the "any real users?" check
|
|
1098
|
+
// below doesn't mistake the old auto-account for a real signup (which would
|
|
1099
|
+
// permanently disable auto-create) and the post-logout guard still fires.
|
|
1100
|
+
const LEGACY_AUTO_DEV_ACCOUNT_EMAIL = "dev@local";
|
|
1020
1101
|
/**
|
|
1021
1102
|
* Local-dev convenience: skip the sign-up wall on first run.
|
|
1022
1103
|
*
|
|
1023
1104
|
* When NODE_ENV=development AND the `user` table has no rows for any
|
|
1024
|
-
* email other than `dev@local`,
|
|
1105
|
+
* email other than the dev account (`dev@local.test`, or the legacy
|
|
1106
|
+
* `dev@local` on pre-fix DBs), transparently sign up (or sign back in
|
|
1025
1107
|
* to) the auto-managed dev account and return a 302 to the original URL
|
|
1026
1108
|
* with a session cookie set. A developer who just ran `pnpm dev` lands
|
|
1027
1109
|
* in the app immediately instead of being asked to fill in name + email
|
|
1028
1110
|
* + password to try the framework.
|
|
1029
1111
|
*
|
|
1030
|
-
* Auto-create fires exactly once per local DB: as soon as
|
|
1031
|
-
* (or any real user) exists in the `user` table, the helper
|
|
1032
|
-
* null and the normal login flow takes over. Signing out then
|
|
1033
|
-
* the user on the regular sign-in form; without this guard the
|
|
1112
|
+
* Auto-create fires exactly once per local DB: as soon as the dev
|
|
1113
|
+
* account (or any real user) exists in the `user` table, the helper
|
|
1114
|
+
* returns null and the normal login flow takes over. Signing out then
|
|
1115
|
+
* leaves the user on the regular sign-in form; without this guard the
|
|
1034
1116
|
* post-logout reload would silently re-create the session.
|
|
1035
1117
|
*
|
|
1036
|
-
*
|
|
1037
|
-
*
|
|
1038
|
-
*
|
|
1039
|
-
*
|
|
1040
|
-
*
|
|
1041
|
-
*
|
|
1042
|
-
*
|
|
1118
|
+
* Hardening (this is a convenience, not an auth bypass — it uses the
|
|
1119
|
+
* real Better Auth sign-up/sign-in, but a known-credential local account
|
|
1120
|
+
* is still worth not shipping):
|
|
1121
|
+
* - **Loopback only.** Gated on `isLoopbackRequest`, so a tunnelled /
|
|
1122
|
+
* reverse-proxied / misconfigured-non-prod dev server never auto-signs
|
|
1123
|
+
* in a directly-remote visitor (mirrors the desktop SSO broker).
|
|
1124
|
+
* - **Random per-DB password.** The account password is freshly
|
|
1125
|
+
* generated on creation and printed to the server console exactly
|
|
1126
|
+
* once — there is no source-code-known credential. After logout the
|
|
1127
|
+
* auto-flow won't refire (dev row exists), so signing back in uses
|
|
1128
|
+
* that printed password; lost it ⇒ drop the row or wipe the local DB.
|
|
1129
|
+
* - **NODE_ENV.** Still gated on development/test.
|
|
1130
|
+
*
|
|
1131
|
+
* Set `AGENT_NATIVE_DISABLE_AUTO_DEV_ACCOUNT=1` to opt out entirely
|
|
1132
|
+
* (useful for tests that exercise the unauthenticated branch).
|
|
1043
1133
|
*/
|
|
1044
1134
|
async function maybeAutoCreateDevSession(event, redirectTo) {
|
|
1045
1135
|
if (!isDevEnvironment())
|
|
1046
1136
|
return null;
|
|
1047
1137
|
if (process.env.AGENT_NATIVE_DISABLE_AUTO_DEV_ACCOUNT === "1")
|
|
1048
1138
|
return null;
|
|
1139
|
+
// Local machine only: never auto-sign-in a remote visitor, even if a
|
|
1140
|
+
// dev server is exposed (tunnel, reverse proxy, misconfigured NODE_ENV).
|
|
1141
|
+
if (!isLoopbackRequest(event))
|
|
1142
|
+
return null;
|
|
1049
1143
|
try {
|
|
1050
1144
|
const db = getDbExec();
|
|
1145
|
+
// Exclude BOTH the current and the legacy dev-account email so a
|
|
1146
|
+
// pre-fix local DB that still holds a `dev@local` row isn't treated
|
|
1147
|
+
// as having a "real user" (which would permanently disable
|
|
1148
|
+
// auto-create on that DB).
|
|
1051
1149
|
const { rows: realUsers } = await db.execute({
|
|
1052
|
-
sql: 'SELECT 1 FROM "user" WHERE email
|
|
1053
|
-
args: [AUTO_DEV_ACCOUNT_EMAIL],
|
|
1150
|
+
sql: 'SELECT 1 FROM "user" WHERE email NOT IN (?, ?) LIMIT 1',
|
|
1151
|
+
args: [AUTO_DEV_ACCOUNT_EMAIL, LEGACY_AUTO_DEV_ACCOUNT_EMAIL],
|
|
1054
1152
|
});
|
|
1055
1153
|
if (realUsers.length > 0)
|
|
1056
1154
|
return null;
|
|
1057
|
-
// If
|
|
1155
|
+
// If the dev account already exists, this is not a freshly-scaffolded
|
|
1058
1156
|
// app — the user has been through the auto-create flow at least
|
|
1059
1157
|
// once. Skip auto-create so signing out actually works: without
|
|
1060
1158
|
// this guard, the post-logout reload immediately re-creates the
|
|
1061
|
-
// session and the user is stuck in dev
|
|
1062
|
-
// set AGENT_NATIVE_DISABLE_AUTO_DEV_ACCOUNT=1). To get the demo
|
|
1063
|
-
// experience back, drop the row or wipe the local DB.
|
|
1159
|
+
// session and the user is stuck in the dev account forever (or has
|
|
1160
|
+
// to set AGENT_NATIVE_DISABLE_AUTO_DEV_ACCOUNT=1). To get the demo
|
|
1161
|
+
// experience back, drop the row or wipe the local DB. The legacy
|
|
1162
|
+
// `dev@local` address is matched too so pre-fix DBs still suppress
|
|
1163
|
+
// re-create after logout.
|
|
1064
1164
|
const { rows: devUsers } = await db.execute({
|
|
1065
|
-
sql: 'SELECT 1 FROM "user" WHERE email
|
|
1066
|
-
args: [AUTO_DEV_ACCOUNT_EMAIL],
|
|
1165
|
+
sql: 'SELECT 1 FROM "user" WHERE email IN (?, ?) LIMIT 1',
|
|
1166
|
+
args: [AUTO_DEV_ACCOUNT_EMAIL, LEGACY_AUTO_DEV_ACCOUNT_EMAIL],
|
|
1067
1167
|
});
|
|
1068
1168
|
if (devUsers.length > 0)
|
|
1069
1169
|
return null;
|
|
1070
1170
|
const auth = await getBetterAuth();
|
|
1071
1171
|
if (!auth)
|
|
1072
1172
|
return null;
|
|
1073
|
-
//
|
|
1074
|
-
//
|
|
1075
|
-
//
|
|
1173
|
+
// Random per-DB password — there is no source-code-known credential
|
|
1174
|
+
// for this account. Printed once below so the developer can sign back
|
|
1175
|
+
// in after logout (the auto-flow won't refire while the dev row
|
|
1176
|
+
// exists).
|
|
1177
|
+
const devPassword = crypto.randomBytes(18).toString("base64url");
|
|
1178
|
+
// The dev account does not exist at this point (the devUsers check
|
|
1179
|
+
// above returned early otherwise). The "already exists" swallow only
|
|
1180
|
+
// matters under a rare concurrent first-hit race — in that case the
|
|
1181
|
+
// sign-in below fails the password check and we return null, leaving
|
|
1182
|
+
// the racing request that already won to keep the session.
|
|
1076
1183
|
try {
|
|
1077
1184
|
await auth.api.signUpEmail({
|
|
1078
1185
|
body: {
|
|
1079
1186
|
email: AUTO_DEV_ACCOUNT_EMAIL,
|
|
1080
|
-
password:
|
|
1187
|
+
password: devPassword,
|
|
1081
1188
|
name: "Dev",
|
|
1082
1189
|
},
|
|
1083
1190
|
});
|
|
@@ -1089,17 +1196,26 @@ async function maybeAutoCreateDevSession(event, redirectTo) {
|
|
|
1089
1196
|
const result = await auth.api.signInEmail({
|
|
1090
1197
|
body: {
|
|
1091
1198
|
email: AUTO_DEV_ACCOUNT_EMAIL,
|
|
1092
|
-
password:
|
|
1199
|
+
password: devPassword,
|
|
1093
1200
|
},
|
|
1094
1201
|
});
|
|
1095
1202
|
if (!result?.token)
|
|
1096
1203
|
return null;
|
|
1097
1204
|
setFrameworkSessionCookie(event, result.token);
|
|
1098
1205
|
await addSession(result.token, AUTO_DEV_ACCOUNT_EMAIL);
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1206
|
+
// Print the throwaway credential exactly once so the developer can
|
|
1207
|
+
// sign back in manually after logout (auto-flow won't refire once the
|
|
1208
|
+
// dev row exists). Local console only — never Sentry.
|
|
1209
|
+
console.log(`\n[agent-native] Local dev auto-login ready.\n` +
|
|
1210
|
+
` email: ${AUTO_DEV_ACCOUNT_EMAIL}\n` +
|
|
1211
|
+
` password: ${devPassword}\n` +
|
|
1212
|
+
` (random, this DB only — needed to sign back in after logout.\n` +
|
|
1213
|
+
` Set AGENT_NATIVE_DISABLE_AUTO_DEV_ACCOUNT=1 to disable.)\n`);
|
|
1214
|
+
// Emit the session cookie ON the 302 itself. Returning a bare
|
|
1215
|
+
// `new Response(...)` here drops the cookie staged on event.node.res
|
|
1216
|
+
// (see redirectWithStagedCookies), so the developer would 302 to the
|
|
1217
|
+
// app and immediately bounce back to the login form.
|
|
1218
|
+
return redirectWithStagedCookies(event, redirectTo);
|
|
1103
1219
|
}
|
|
1104
1220
|
catch (e) {
|
|
1105
1221
|
// Local-dev only — log to console for debugging, but don't surface
|
|
@@ -1252,6 +1368,33 @@ export function setFrameworkSessionCookie(event, token) {
|
|
|
1252
1368
|
maxAge: sessionMaxAge,
|
|
1253
1369
|
});
|
|
1254
1370
|
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Build a redirect `Response` that carries whatever `Set-Cookie` headers were
|
|
1373
|
+
* just staged on the event (e.g. by `setFrameworkSessionCookie`).
|
|
1374
|
+
*
|
|
1375
|
+
* h3 v2's `setCookie` appends the cookie onto `event.res.headers`. When a
|
|
1376
|
+
* handler returns a plain object/string, h3's `prepareResponse` merges those
|
|
1377
|
+
* staged headers into the synthesized response, so the cookie survives. But
|
|
1378
|
+
* when a handler returns a web `Response`, `prepareResponse` only merges the
|
|
1379
|
+
* staged headers if the Response is 2xx — its `!val.ok` early-return hands a
|
|
1380
|
+
* non-2xx Response (like a 302) straight back WITHOUT merging. A bare
|
|
1381
|
+
* `new Response("", { status: 302, headers: { Location } })` therefore 302s
|
|
1382
|
+
* the browser with no session cookie, so the zero-setup dev auto-sign-in
|
|
1383
|
+
* bounces straight back to the login form.
|
|
1384
|
+
*
|
|
1385
|
+
* Mirroring the staged cookies onto the redirect Response's own headers makes
|
|
1386
|
+
* them part of the Response that's returned as-is, so the 302 actually
|
|
1387
|
+
* carries the session cookie. (`event.res.headers` is also left intact for
|
|
1388
|
+
* any non-Response continuation path; h3 only skips the merge for the
|
|
1389
|
+
* Response branch, so there's no double-emit.)
|
|
1390
|
+
*/
|
|
1391
|
+
function redirectWithStagedCookies(event, location, status = 302) {
|
|
1392
|
+
const headers = new Headers({ Location: location });
|
|
1393
|
+
const staged = event.res?.headers?.getSetCookie?.() ?? [];
|
|
1394
|
+
for (const cookie of staged)
|
|
1395
|
+
headers.append("set-cookie", cookie);
|
|
1396
|
+
return new Response("", { status, headers });
|
|
1397
|
+
}
|
|
1255
1398
|
function isHttpsRequest(event) {
|
|
1256
1399
|
try {
|
|
1257
1400
|
const xfProto = getHeader(event, "x-forwarded-proto");
|