@agent-native/core 0.7.1 → 0.7.2
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/dist/action.d.ts +8 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +18 -0
- package/dist/action.js.map +1 -1
- package/dist/agent/production-agent.d.ts +9 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +148 -36
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +20 -1
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +14 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +63 -6
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/types.d.ts +2 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/cli/create.js +1 -1
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/setup-agents.d.ts.map +1 -1
- package/dist/cli/setup-agents.js +0 -2
- package/dist/cli/setup-agents.js.map +1 -1
- package/dist/cli/templates-meta.d.ts +52 -0
- package/dist/cli/templates-meta.d.ts.map +1 -0
- package/dist/cli/templates-meta.js +165 -0
- package/dist/cli/templates-meta.js.map +1 -0
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +279 -30
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +2 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +172 -40
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/ConnectBuilderCard.d.ts +21 -0
- package/dist/client/ConnectBuilderCard.d.ts.map +1 -0
- package/dist/client/ConnectBuilderCard.js +196 -0
- package/dist/client/ConnectBuilderCard.js.map +1 -0
- package/dist/client/FeedbackButton.d.ts +15 -0
- package/dist/client/FeedbackButton.d.ts.map +1 -0
- package/dist/client/FeedbackButton.js +72 -0
- package/dist/client/FeedbackButton.js.map +1 -0
- package/dist/client/IframeEmbed.d.ts +17 -0
- package/dist/client/IframeEmbed.d.ts.map +1 -0
- package/dist/client/IframeEmbed.js +108 -0
- package/dist/client/IframeEmbed.js.map +1 -0
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +34 -7
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +34 -15
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/agent-chat.d.ts +6 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +7 -0
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/composer/MentionPopover.d.ts.map +1 -1
- package/dist/client/composer/MentionPopover.js +27 -24
- package/dist/client/composer/MentionPopover.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +3 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +21 -4
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/embed.d.ts +28 -0
- package/dist/client/embed.d.ts.map +1 -0
- package/dist/client/embed.js +50 -0
- package/dist/client/embed.js.map +1 -0
- package/dist/client/index.d.ts +4 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +4 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/integrations/IntegrationsPanel.js +2 -2
- package/dist/client/integrations/IntegrationsPanel.js.map +1 -1
- package/dist/client/onboarding/OnboardingBanner.js +2 -2
- package/dist/client/onboarding/OnboardingBanner.js.map +1 -1
- package/dist/client/onboarding/OnboardingPanel.d.ts.map +1 -1
- package/dist/client/onboarding/OnboardingPanel.js +139 -52
- package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
- package/dist/client/onboarding/SetupButton.d.ts.map +1 -1
- package/dist/client/onboarding/SetupButton.js +13 -3
- package/dist/client/onboarding/SetupButton.js.map +1 -1
- package/dist/client/org/TeamPage.d.ts.map +1 -1
- package/dist/client/org/TeamPage.js +12 -7
- package/dist/client/org/TeamPage.js.map +1 -1
- package/dist/client/resources/ResourceEditor.d.ts.map +1 -1
- package/dist/client/resources/ResourceEditor.js +57 -2
- package/dist/client/resources/ResourceEditor.js.map +1 -1
- package/dist/client/resources/ResourceTree.d.ts +5 -1
- package/dist/client/resources/ResourceTree.d.ts.map +1 -1
- package/dist/client/resources/ResourceTree.js +17 -10
- 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 +12 -10
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/client/settings/AgentsSection.d.ts.map +1 -1
- package/dist/client/settings/AgentsSection.js +6 -3
- package/dist/client/settings/AgentsSection.js.map +1 -1
- package/dist/client/settings/ComingSoonSection.js +1 -1
- package/dist/client/settings/ComingSoonSection.js.map +1 -1
- package/dist/client/settings/LLMSection.d.ts.map +1 -1
- package/dist/client/settings/LLMSection.js +1 -1
- package/dist/client/settings/LLMSection.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +16 -23
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/SettingsSection.js +2 -2
- package/dist/client/settings/SettingsSection.js.map +1 -1
- package/dist/client/settings/UsageSection.d.ts +2 -0
- package/dist/client/settings/UsageSection.d.ts.map +1 -0
- package/dist/client/settings/UsageSection.js +70 -0
- package/dist/client/settings/UsageSection.js.map +1 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +67 -4
- package/dist/client/use-action.js.map +1 -1
- package/dist/client/use-db-sync.d.ts +25 -2
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +62 -1
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/client/use-dev-mode.d.ts.map +1 -1
- package/dist/client/use-dev-mode.js +16 -1
- package/dist/client/use-dev-mode.js.map +1 -1
- package/dist/db/client.d.ts +12 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +89 -2
- package/dist/db/client.js.map +1 -1
- package/dist/db/create-get-db.d.ts +11 -0
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +47 -3
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +62 -5
- package/dist/db/migrations.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/jobs/scheduler.d.ts.map +1 -1
- package/dist/jobs/scheduler.js +7 -2
- package/dist/jobs/scheduler.js.map +1 -1
- package/dist/oauth-tokens/google-refresh.d.ts +31 -0
- package/dist/oauth-tokens/google-refresh.d.ts.map +1 -0
- package/dist/oauth-tokens/google-refresh.js +115 -0
- package/dist/oauth-tokens/google-refresh.js.map +1 -0
- package/dist/oauth-tokens/index.d.ts +1 -0
- package/dist/oauth-tokens/index.d.ts.map +1 -1
- package/dist/oauth-tokens/index.js +1 -0
- package/dist/oauth-tokens/index.js.map +1 -1
- package/dist/onboarding/default-steps.d.ts.map +1 -1
- package/dist/onboarding/default-steps.js +57 -18
- package/dist/onboarding/default-steps.js.map +1 -1
- package/dist/org/context.js +1 -1
- package/dist/org/handlers.d.ts.map +1 -1
- package/dist/org/handlers.js +35 -10
- package/dist/org/handlers.js.map +1 -1
- package/dist/org/plugin.d.ts.map +1 -1
- package/dist/org/plugin.js +37 -22
- package/dist/org/plugin.js.map +1 -1
- package/dist/resources/handlers.js +1 -1
- package/dist/resources/handlers.js.map +1 -1
- package/dist/resources/metadata.d.ts.map +1 -1
- package/dist/resources/metadata.js +2 -2
- package/dist/resources/metadata.js.map +1 -1
- package/dist/resources/store.d.ts.map +1 -1
- package/dist/resources/store.js +27 -1
- package/dist/resources/store.js.map +1 -1
- package/dist/scripts/db/patch.d.ts.map +1 -1
- package/dist/scripts/db/patch.js +273 -11
- package/dist/scripts/db/patch.js.map +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +2 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +26 -0
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +12 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +495 -48
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-discovery.js +2 -2
- package/dist/server/agent-discovery.js.map +1 -1
- package/dist/server/agent-teams.d.ts.map +1 -1
- package/dist/server/agent-teams.js +21 -58
- package/dist/server/agent-teams.js.map +1 -1
- package/dist/server/auth.d.ts +6 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +284 -41
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +70 -4
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/builder-browser.d.ts +21 -0
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +67 -4
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +122 -4
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/desktop-sso.d.ts +30 -0
- package/dist/server/desktop-sso.d.ts.map +1 -0
- package/dist/server/desktop-sso.js +74 -0
- package/dist/server/desktop-sso.js.map +1 -0
- package/dist/server/email.d.ts +23 -0
- package/dist/server/email.d.ts.map +1 -0
- package/dist/server/email.js +105 -0
- package/dist/server/email.js.map +1 -0
- package/dist/server/framework-request-handler.d.ts.map +1 -1
- package/dist/server/framework-request-handler.js +29 -0
- package/dist/server/framework-request-handler.js.map +1 -1
- package/dist/server/google-oauth.d.ts.map +1 -1
- package/dist/server/google-oauth.js +19 -3
- package/dist/server/google-oauth.js.map +1 -1
- package/dist/server/local-migration.d.ts +9 -0
- package/dist/server/local-migration.d.ts.map +1 -1
- package/dist/server/local-migration.js +44 -14
- package/dist/server/local-migration.js.map +1 -1
- package/dist/server/oauth-helpers.d.ts +2 -0
- package/dist/server/oauth-helpers.d.ts.map +1 -1
- package/dist/server/oauth-helpers.js +2 -0
- package/dist/server/oauth-helpers.js.map +1 -1
- package/dist/server/onboarding-html.d.ts +6 -0
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +229 -25
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +48 -0
- package/dist/server/poll.js.map +1 -1
- package/dist/templates/default/.agents/skills/inline-embeds/SKILL.md +88 -0
- package/dist/usage/store.d.ts +74 -12
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +210 -44
- package/dist/usage/store.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +55 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/deployment.md +59 -2
- package/docs/content/resources.md +9 -9
- package/docs/content/workspace-management.md +2 -2
- package/package.json +3 -3
- package/src/templates/default/.agents/skills/inline-embeds/SKILL.md +88 -0
- /package/dist/templates/workspace-core/{skills → .agents/skills}/company-policies/SKILL.md +0 -0
- /package/src/templates/workspace-core/{skills → .agents/skills}/company-policies/SKILL.md +0 -0
package/dist/server/auth.js
CHANGED
|
@@ -14,20 +14,47 @@ import { defineEventHandler, getMethod, getQuery, setResponseHeader, setResponse
|
|
|
14
14
|
function toWebRequest(event) {
|
|
15
15
|
return event.req;
|
|
16
16
|
}
|
|
17
|
-
import { getDbExec, isPostgres, intType } from "../db/client.js";
|
|
17
|
+
import { getDbExec, isPostgres, intType, isLocalDatabase, } from "../db/client.js";
|
|
18
18
|
import { getBetterAuth, getBetterAuthSync } from "./better-auth-instance.js";
|
|
19
|
-
import { getOnboardingHtml } from "./onboarding-html.js";
|
|
19
|
+
import { getOnboardingHtml, getResetPasswordHtml } from "./onboarding-html.js";
|
|
20
20
|
import { migrateLocalUserData } from "./local-migration.js";
|
|
21
21
|
import { readBody } from "../server/h3-helpers.js";
|
|
22
|
+
import { readDesktopSso, writeDesktopSso, clearDesktopSso, } from "./desktop-sso.js";
|
|
23
|
+
import { isElectron as isElectronRequest } from "./google-oauth.js";
|
|
24
|
+
/**
|
|
25
|
+
* Get the configured session max age. Desktop SSO broker writes from
|
|
26
|
+
* OAuth flows read this so expiration stays consistent with the cookie.
|
|
27
|
+
*/
|
|
28
|
+
export function getSessionMaxAge() {
|
|
29
|
+
return sessionMaxAge;
|
|
30
|
+
}
|
|
22
31
|
// ---------------------------------------------------------------------------
|
|
23
32
|
// Constants
|
|
24
33
|
// ---------------------------------------------------------------------------
|
|
25
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Cookie name for the framework's session cookie.
|
|
36
|
+
*
|
|
37
|
+
* Browsers scope cookies by host (NOT host+port — RFC 6265), so two apps
|
|
38
|
+
* running on different localhost ports share one cookie jar. When multiple
|
|
39
|
+
* templates run side-by-side (`dev:all`, the desktop app, multi-template
|
|
40
|
+
* deploys on a shared domain), they would otherwise stomp on each other's
|
|
41
|
+
* `an_session` cookie and ping-pong each other into a logged-out state.
|
|
42
|
+
*
|
|
43
|
+
* When `APP_NAME` is set, suffix the cookie so each app gets its own slot.
|
|
44
|
+
*/
|
|
45
|
+
const APP_NAME_SLUG = (process.env.APP_NAME || "")
|
|
46
|
+
.toLowerCase()
|
|
47
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
48
|
+
.replace(/^_+|_+$/g, "");
|
|
49
|
+
export const COOKIE_NAME = APP_NAME_SLUG
|
|
50
|
+
? `an_session_${APP_NAME_SLUG}`
|
|
51
|
+
: "an_session";
|
|
26
52
|
const DEFAULT_MAX_AGE = 60 * 60 * 24 * 30; // 30 days
|
|
27
53
|
const LOCAL_MODE_MARKER_PATH = path.resolve(process.cwd(), ".agent-native", "auth-mode");
|
|
28
54
|
// ---------------------------------------------------------------------------
|
|
29
55
|
// AUTH_MODE detection
|
|
30
56
|
// ---------------------------------------------------------------------------
|
|
57
|
+
let _warnedRemoteLocalMode = false;
|
|
31
58
|
/**
|
|
32
59
|
* Check if the app is in local-only mode (no auth).
|
|
33
60
|
*
|
|
@@ -39,8 +66,20 @@ const LOCAL_MODE_MARKER_PATH = path.resolve(process.cwd(), ".agent-native", "aut
|
|
|
39
66
|
* no auth is used. In development, getSession() also falls back to
|
|
40
67
|
* local@localhost automatically if no other auth method succeeds, so
|
|
41
68
|
* apps are always usable without configuration in dev.
|
|
69
|
+
*
|
|
70
|
+
* Refuses to enable on any non-local database (Postgres, Turso, D1): local
|
|
71
|
+
* mode uses a single shared virtual user with no per-machine scoping, so on
|
|
72
|
+
* a shared DB every developer would land on the same account and collide.
|
|
42
73
|
*/
|
|
43
74
|
async function isLocalModeEnabled() {
|
|
75
|
+
if (!isLocalDatabase()) {
|
|
76
|
+
if (process.env.AUTH_MODE === "local" && !_warnedRemoteLocalMode) {
|
|
77
|
+
_warnedRemoteLocalMode = true;
|
|
78
|
+
console.warn("[agent-native] AUTH_MODE=local ignored: database is not local SQLite. " +
|
|
79
|
+
"local@localhost has no per-user scoping and would collide across developers on a shared DB.");
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
44
83
|
if (process.env.AUTH_MODE === "local")
|
|
45
84
|
return true;
|
|
46
85
|
try {
|
|
@@ -174,6 +213,15 @@ let _authGuardConfig = null;
|
|
|
174
213
|
* /_agent-native/* routes).
|
|
175
214
|
*/
|
|
176
215
|
let _authGuardFn = null;
|
|
216
|
+
/**
|
|
217
|
+
* The H3 app the auth routes + guard were last mounted on. Module-level
|
|
218
|
+
* state survives Vite HMR restarts, but each HMR cycle creates a fresh
|
|
219
|
+
* nitroApp/H3 instance whose middleware array is empty again. Tracking the
|
|
220
|
+
* app here lets autoMountAuth detect "same module state, new app" and
|
|
221
|
+
* re-mount routes instead of silently skipping them because `_authGuardFn`
|
|
222
|
+
* looks populated from a previous cycle.
|
|
223
|
+
*/
|
|
224
|
+
let _mountedApp = null;
|
|
177
225
|
/**
|
|
178
226
|
* Run the auth guard on an event. Returns a Response/object to block the
|
|
179
227
|
* request (login page or 401), or undefined to allow it through.
|
|
@@ -314,6 +362,14 @@ export async function getSession(event) {
|
|
|
314
362
|
const session = await customGetSession(event);
|
|
315
363
|
if (session)
|
|
316
364
|
return session;
|
|
365
|
+
// Desktop SSO broker: even with BYOA auth, fall back to the broker
|
|
366
|
+
// for Electron requests so cross-template SSO works for custom-auth
|
|
367
|
+
// templates too.
|
|
368
|
+
if (isElectronRequest(event)) {
|
|
369
|
+
const sso = await readDesktopSso();
|
|
370
|
+
if (sso?.email)
|
|
371
|
+
return { email: sso.email, token: sso.token };
|
|
372
|
+
}
|
|
317
373
|
// Fall through to mobile _session check
|
|
318
374
|
}
|
|
319
375
|
else {
|
|
@@ -325,6 +381,9 @@ export async function getSession(event) {
|
|
|
325
381
|
headers: event.headers,
|
|
326
382
|
});
|
|
327
383
|
if (baSession?.user?.email) {
|
|
384
|
+
// Successful real sign-in — clear the upgrade-pending marker so
|
|
385
|
+
// the dev fallback becomes reachable again for future local work.
|
|
386
|
+
clearUpgradePendingCookie(event);
|
|
328
387
|
return mapBetterAuthSession(baSession);
|
|
329
388
|
}
|
|
330
389
|
}
|
|
@@ -336,8 +395,24 @@ export async function getSession(event) {
|
|
|
336
395
|
const cookie = getCookie(event, COOKIE_NAME);
|
|
337
396
|
if (cookie) {
|
|
338
397
|
const email = await getSessionEmail(cookie);
|
|
339
|
-
if (email)
|
|
398
|
+
if (email) {
|
|
399
|
+
clearUpgradePendingCookie(event);
|
|
340
400
|
return { email, token: cookie };
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
// 5b. Desktop SSO broker fallback.
|
|
404
|
+
// Each template in the Electron desktop app has its own database, so
|
|
405
|
+
// a session token created by one template doesn't resolve in another.
|
|
406
|
+
// When an Electron request has no resolvable session, trust the
|
|
407
|
+
// home-dir SSO record written by whichever template the user signed
|
|
408
|
+
// into. Gated on Electron user-agent so no non-desktop code path
|
|
409
|
+
// consults the file.
|
|
410
|
+
if (isElectronRequest(event)) {
|
|
411
|
+
const sso = await readDesktopSso();
|
|
412
|
+
if (sso?.email) {
|
|
413
|
+
clearUpgradePendingCookie(event);
|
|
414
|
+
return { email: sso.email, token: sso.token };
|
|
415
|
+
}
|
|
341
416
|
}
|
|
342
417
|
}
|
|
343
418
|
// 6. Mobile WebView bridge — _session query param
|
|
@@ -347,8 +422,7 @@ export async function getSession(event) {
|
|
|
347
422
|
if (email) {
|
|
348
423
|
setCookie(event, COOKIE_NAME, qToken, {
|
|
349
424
|
httpOnly: true,
|
|
350
|
-
|
|
351
|
-
sameSite: "lax",
|
|
425
|
+
...crossSiteCookieAttrs(event),
|
|
352
426
|
path: "/",
|
|
353
427
|
maxAge: sessionMaxAge,
|
|
354
428
|
});
|
|
@@ -356,15 +430,113 @@ export async function getSession(event) {
|
|
|
356
430
|
return { email, token: qToken };
|
|
357
431
|
}
|
|
358
432
|
}
|
|
359
|
-
// 7. Dev-mode safety net — in development
|
|
360
|
-
// so the app is usable without any auth configuration.
|
|
361
|
-
// errors when Better Auth isn't configured, the marker file
|
|
362
|
-
// the user simply wants to play around locally.
|
|
363
|
-
|
|
433
|
+
// 7. Dev-mode safety net — in development on a local SQLite database, fall
|
|
434
|
+
// back to local@localhost so the app is usable without any auth configuration.
|
|
435
|
+
// This prevents 401 errors when Better Auth isn't configured, the marker file
|
|
436
|
+
// is missing, or the user simply wants to play around locally.
|
|
437
|
+
//
|
|
438
|
+
// Gated on isLocalDatabase() because local@localhost has no per-user scoping:
|
|
439
|
+
// on a shared DB (Postgres, Turso, D1) this fallback would land every
|
|
440
|
+
// developer on the same account and expose each other's data.
|
|
441
|
+
//
|
|
442
|
+
// EXCEPTION: if the user has explicitly exited local mode (clicked "Upgrade
|
|
443
|
+
// to real account"), they've signaled they want real auth. The upgrade
|
|
444
|
+
// cookie suppresses this fallback so the onboarding/sign-in page is served
|
|
445
|
+
// instead of silently re-authenticating them as local@localhost.
|
|
446
|
+
if (isDevEnvironment() &&
|
|
447
|
+
isLocalDatabase() &&
|
|
448
|
+
!isUpgradePending(event) &&
|
|
449
|
+
!hasSignInFlag(event)) {
|
|
364
450
|
return LOCAL_SESSION;
|
|
365
451
|
}
|
|
366
452
|
return null;
|
|
367
453
|
}
|
|
454
|
+
/**
|
|
455
|
+
* Cookie set by POST /_agent-native/auth/exit-local-mode so we know the user
|
|
456
|
+
* is in the middle of upgrading from local@localhost to a real account.
|
|
457
|
+
* While this cookie is present we skip the dev-mode "auto local session"
|
|
458
|
+
* fallback so the onboarding/sign-in page can actually render.
|
|
459
|
+
* Cleared on successful sign-in/sign-up.
|
|
460
|
+
*/
|
|
461
|
+
const UPGRADE_COOKIE = "an_upgrade_pending";
|
|
462
|
+
function isUpgradePending(event) {
|
|
463
|
+
try {
|
|
464
|
+
return getCookie(event, UPGRADE_COOKIE) === "1";
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function setUpgradePendingCookie(event) {
|
|
471
|
+
setCookie(event, UPGRADE_COOKIE, "1", {
|
|
472
|
+
httpOnly: true,
|
|
473
|
+
...crossSiteCookieAttrs(event),
|
|
474
|
+
path: "/",
|
|
475
|
+
maxAge: 60 * 60, // 1 hour — enough to complete sign-in
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* URL-flag fallback for third-party iframe contexts (e.g. the Builder.io
|
|
480
|
+
* editor) where SameSite=Lax cookies from an exit-local-mode POST are not
|
|
481
|
+
* delivered on the subsequent reload. TeamPage reloads with ?signin=1 so
|
|
482
|
+
* we can reliably suppress the dev-mode local fallback without a cookie.
|
|
483
|
+
*/
|
|
484
|
+
function hasSignInFlag(event) {
|
|
485
|
+
try {
|
|
486
|
+
return getQuery(event)?.signin === "1";
|
|
487
|
+
}
|
|
488
|
+
catch {
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Cookie attributes that work in both same-site and third-party iframe
|
|
494
|
+
* contexts. Over HTTPS we emit `SameSite=None; Secure` (required by browsers
|
|
495
|
+
* to ship the cookie back inside a cross-origin iframe); for plain HTTP dev
|
|
496
|
+
* we keep `SameSite=Lax` since `None` requires Secure.
|
|
497
|
+
*/
|
|
498
|
+
function crossSiteCookieAttrs(event) {
|
|
499
|
+
return isHttpsRequest(event)
|
|
500
|
+
? { sameSite: "none", secure: true }
|
|
501
|
+
: { sameSite: "lax", secure: false };
|
|
502
|
+
}
|
|
503
|
+
function isHttpsRequest(event) {
|
|
504
|
+
try {
|
|
505
|
+
const req = event.req ?? event.node?.req;
|
|
506
|
+
const headers = req?.headers;
|
|
507
|
+
const get = (k) => {
|
|
508
|
+
if (!headers)
|
|
509
|
+
return undefined;
|
|
510
|
+
if (typeof headers.get === "function") {
|
|
511
|
+
return headers.get(k) ?? undefined;
|
|
512
|
+
}
|
|
513
|
+
const v = headers[k];
|
|
514
|
+
return Array.isArray(v) ? v[0] : v;
|
|
515
|
+
};
|
|
516
|
+
const xfProto = get("x-forwarded-proto");
|
|
517
|
+
if (xfProto && String(xfProto).split(",")[0].trim() === "https") {
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
const url = req?.url;
|
|
521
|
+
if (typeof url === "string" && url.startsWith("https://"))
|
|
522
|
+
return true;
|
|
523
|
+
const appUrl = process.env.APP_URL || process.env.BETTER_AUTH_URL || "";
|
|
524
|
+
if (appUrl.startsWith("https://"))
|
|
525
|
+
return true;
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
// ignore
|
|
529
|
+
}
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
function clearUpgradePendingCookie(event) {
|
|
533
|
+
try {
|
|
534
|
+
deleteCookie(event, UPGRADE_COOKIE, { path: "/" });
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
// ignore
|
|
538
|
+
}
|
|
539
|
+
}
|
|
368
540
|
// ---------------------------------------------------------------------------
|
|
369
541
|
// Public path matching
|
|
370
542
|
// ---------------------------------------------------------------------------
|
|
@@ -489,6 +661,38 @@ async function removeAuthModeLocal() {
|
|
|
489
661
|
return false;
|
|
490
662
|
}
|
|
491
663
|
}
|
|
664
|
+
/**
|
|
665
|
+
* POST /_agent-native/auth/migrate-local-data handler. Exposed here (not
|
|
666
|
+
* inlined in a single mount function) because it must be registered from
|
|
667
|
+
* every auth-mount path — including local-mode and fallback — so the
|
|
668
|
+
* upgrade-from-local flow never 500s when BetterAuth init is skipped or
|
|
669
|
+
* failed. Previously this was only mounted inside mountBetterAuthRoutes()
|
|
670
|
+
* which meant that in local mode (or when BetterAuth failed to init) the
|
|
671
|
+
* request fell through to the Nitro SSR renderer and produced a 500.
|
|
672
|
+
*/
|
|
673
|
+
const migrateLocalDataHandler = defineEventHandler(async (event) => {
|
|
674
|
+
if (getMethod(event) !== "POST") {
|
|
675
|
+
setResponseStatus(event, 405);
|
|
676
|
+
return { error: "Method not allowed" };
|
|
677
|
+
}
|
|
678
|
+
const session = await getSession(event);
|
|
679
|
+
if (!session?.email || session.email === "local@localhost") {
|
|
680
|
+
setResponseStatus(event, 401);
|
|
681
|
+
return { error: "Not authenticated as a real account" };
|
|
682
|
+
}
|
|
683
|
+
try {
|
|
684
|
+
const result = await migrateLocalUserData(session.email);
|
|
685
|
+
return { ok: true, ...result };
|
|
686
|
+
}
|
|
687
|
+
catch (e) {
|
|
688
|
+
console.error("[migrate-local-data] Migration threw for", session.email, e);
|
|
689
|
+
setResponseStatus(event, 500);
|
|
690
|
+
return {
|
|
691
|
+
error: e?.message || "Migration failed",
|
|
692
|
+
stack: isDevEnvironment() ? e?.stack : undefined,
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
});
|
|
492
696
|
// ---------------------------------------------------------------------------
|
|
493
697
|
// mountBetterAuthRoutes — Better Auth powered auth with backward-compat routes
|
|
494
698
|
// ---------------------------------------------------------------------------
|
|
@@ -549,6 +753,9 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
549
753
|
setResponseStatus(event, 500);
|
|
550
754
|
return { error: "Failed to disable local mode" };
|
|
551
755
|
}
|
|
756
|
+
// Mark the browser so getSession's dev-mode fallback won't silently
|
|
757
|
+
// re-authenticate the user as local@localhost on the next request.
|
|
758
|
+
setUpgradePendingCookie(event);
|
|
552
759
|
return { ok: true };
|
|
553
760
|
}));
|
|
554
761
|
// Backward-compat: POST /_agent-native/auth/login
|
|
@@ -570,8 +777,7 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
570
777
|
await addSession(sessionToken, "user");
|
|
571
778
|
setCookie(event, COOKIE_NAME, sessionToken, {
|
|
572
779
|
httpOnly: true,
|
|
573
|
-
|
|
574
|
-
sameSite: "lax",
|
|
780
|
+
...crossSiteCookieAttrs(event),
|
|
575
781
|
path: "/",
|
|
576
782
|
maxAge: sessionMaxAge,
|
|
577
783
|
});
|
|
@@ -591,12 +797,18 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
591
797
|
if (result?.token) {
|
|
592
798
|
setCookie(event, COOKIE_NAME, result.token, {
|
|
593
799
|
httpOnly: true,
|
|
594
|
-
|
|
595
|
-
sameSite: "lax",
|
|
800
|
+
...crossSiteCookieAttrs(event),
|
|
596
801
|
path: "/",
|
|
597
802
|
maxAge: sessionMaxAge,
|
|
598
803
|
});
|
|
599
804
|
await addSession(result.token, email);
|
|
805
|
+
if (isElectronRequest(event)) {
|
|
806
|
+
await writeDesktopSso({
|
|
807
|
+
email,
|
|
808
|
+
token: result.token,
|
|
809
|
+
expiresAt: Date.now() + sessionMaxAge * 1000,
|
|
810
|
+
});
|
|
811
|
+
}
|
|
600
812
|
}
|
|
601
813
|
return { ok: true };
|
|
602
814
|
}
|
|
@@ -645,6 +857,8 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
645
857
|
catch {
|
|
646
858
|
// Ignore if no Better Auth session
|
|
647
859
|
}
|
|
860
|
+
if (isElectronRequest(event))
|
|
861
|
+
await clearDesktopSso();
|
|
648
862
|
return { ok: true };
|
|
649
863
|
}));
|
|
650
864
|
// GET /_agent-native/auth/session
|
|
@@ -659,24 +873,18 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
659
873
|
// POST /_agent-native/auth/migrate-local-data — move local-mode data to
|
|
660
874
|
// the currently signed-in account. Called by the UI after a user upgrades
|
|
661
875
|
// from local mode to a real account so they don't lose their data.
|
|
662
|
-
app.use("/_agent-native/auth/migrate-local-data",
|
|
663
|
-
|
|
876
|
+
app.use("/_agent-native/auth/migrate-local-data", migrateLocalDataHandler);
|
|
877
|
+
// GET /_agent-native/auth/reset — HTML page shown when a user clicks the
|
|
878
|
+
// reset link in their email. Reads ?token=... and POSTs to Better Auth's
|
|
879
|
+
// /reset-password endpoint on submit.
|
|
880
|
+
app.use("/_agent-native/auth/reset", defineEventHandler((event) => {
|
|
881
|
+
if (getMethod(event) !== "GET") {
|
|
664
882
|
setResponseStatus(event, 405);
|
|
665
883
|
return { error: "Method not allowed" };
|
|
666
884
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
return { error: "Not authenticated as a real account" };
|
|
671
|
-
}
|
|
672
|
-
try {
|
|
673
|
-
const result = await migrateLocalUserData(session.email);
|
|
674
|
-
return { ok: true, ...result };
|
|
675
|
-
}
|
|
676
|
-
catch (e) {
|
|
677
|
-
setResponseStatus(event, 500);
|
|
678
|
-
return { error: e?.message || "Migration failed" };
|
|
679
|
-
}
|
|
885
|
+
return new Response(getResetPasswordHtml(), {
|
|
886
|
+
headers: { "Content-Type": "text/html; charset=utf-8" },
|
|
887
|
+
});
|
|
680
888
|
}));
|
|
681
889
|
// Auth guard — stored both in framework middleware registry AND in
|
|
682
890
|
// _authGuardFn so the server middleware can enforce it on ALL routes.
|
|
@@ -706,8 +914,7 @@ function mountTokenOnlyRoutes(app, accessTokens, publicPaths = []) {
|
|
|
706
914
|
await addSession(sessionToken, "user");
|
|
707
915
|
setCookie(event, COOKIE_NAME, sessionToken, {
|
|
708
916
|
httpOnly: true,
|
|
709
|
-
|
|
710
|
-
sameSite: "lax",
|
|
917
|
+
...crossSiteCookieAttrs(event),
|
|
711
918
|
path: "/",
|
|
712
919
|
maxAge: sessionMaxAge,
|
|
713
920
|
});
|
|
@@ -718,6 +925,8 @@ function mountTokenOnlyRoutes(app, accessTokens, publicPaths = []) {
|
|
|
718
925
|
if (cookie)
|
|
719
926
|
await removeSession(cookie);
|
|
720
927
|
deleteCookie(event, COOKIE_NAME, { path: "/" });
|
|
928
|
+
if (isElectronRequest(event))
|
|
929
|
+
await clearDesktopSso();
|
|
721
930
|
return { ok: true };
|
|
722
931
|
}));
|
|
723
932
|
app.use("/_agent-native/auth/session", defineEventHandler(async (event) => {
|
|
@@ -728,6 +937,7 @@ function mountTokenOnlyRoutes(app, accessTokens, publicPaths = []) {
|
|
|
728
937
|
const session = await getSession(event);
|
|
729
938
|
return session ?? { error: "Not authenticated" };
|
|
730
939
|
}));
|
|
940
|
+
app.use("/_agent-native/auth/migrate-local-data", migrateLocalDataHandler);
|
|
731
941
|
_authGuardConfig = { loginHtml: TOKEN_LOGIN_HTML, publicPaths };
|
|
732
942
|
const guardFn = createAuthGuardFn();
|
|
733
943
|
_authGuardFn = guardFn;
|
|
@@ -757,8 +967,14 @@ function mountLocalModeRoutes(app) {
|
|
|
757
967
|
setResponseStatus(event, 500);
|
|
758
968
|
return { error: "Failed to disable local mode" };
|
|
759
969
|
}
|
|
970
|
+
// Mark the browser so getSession's dev-mode fallback won't silently
|
|
971
|
+
// re-authenticate the user as local@localhost on the next request.
|
|
972
|
+
setUpgradePendingCookie(event);
|
|
760
973
|
return { ok: true };
|
|
761
974
|
}));
|
|
975
|
+
// Upgrade path: migrate-local-data must be reachable from local mode
|
|
976
|
+
// because the user is still in local mode when they trigger the upgrade.
|
|
977
|
+
app.use("/_agent-native/auth/migrate-local-data", migrateLocalDataHandler);
|
|
762
978
|
}
|
|
763
979
|
// ---------------------------------------------------------------------------
|
|
764
980
|
// mountAuthFallbackRoutes — minimal auth endpoints when Better Auth init fails
|
|
@@ -784,12 +1000,18 @@ function mountAuthFallbackRoutes(app) {
|
|
|
784
1000
|
if (result?.token) {
|
|
785
1001
|
setCookie(event, COOKIE_NAME, result.token, {
|
|
786
1002
|
httpOnly: true,
|
|
787
|
-
|
|
788
|
-
sameSite: "lax",
|
|
1003
|
+
...crossSiteCookieAttrs(event),
|
|
789
1004
|
path: "/",
|
|
790
1005
|
maxAge: sessionMaxAge,
|
|
791
1006
|
});
|
|
792
1007
|
await addSession(result.token, email);
|
|
1008
|
+
if (isElectronRequest(event)) {
|
|
1009
|
+
await writeDesktopSso({
|
|
1010
|
+
email,
|
|
1011
|
+
token: result.token,
|
|
1012
|
+
expiresAt: Date.now() + sessionMaxAge * 1000,
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
793
1015
|
}
|
|
794
1016
|
return { ok: true };
|
|
795
1017
|
}
|
|
@@ -838,6 +1060,8 @@ function mountAuthFallbackRoutes(app) {
|
|
|
838
1060
|
catch {
|
|
839
1061
|
// Ignore if Better Auth is still unavailable
|
|
840
1062
|
}
|
|
1063
|
+
if (isElectronRequest(event))
|
|
1064
|
+
await clearDesktopSso();
|
|
841
1065
|
return { ok: true };
|
|
842
1066
|
}));
|
|
843
1067
|
app.use("/_agent-native/auth/local-mode", defineEventHandler(async (event) => {
|
|
@@ -868,6 +1092,9 @@ function mountAuthFallbackRoutes(app) {
|
|
|
868
1092
|
setResponseStatus(event, 500);
|
|
869
1093
|
return { error: "Failed to disable local mode" };
|
|
870
1094
|
}
|
|
1095
|
+
// Mark the browser so getSession's dev-mode fallback won't silently
|
|
1096
|
+
// re-authenticate the user as local@localhost on the next request.
|
|
1097
|
+
setUpgradePendingCookie(event);
|
|
871
1098
|
return { ok: true };
|
|
872
1099
|
}));
|
|
873
1100
|
app.use("/_agent-native/auth/session", defineEventHandler(async (event) => {
|
|
@@ -878,6 +1105,10 @@ function mountAuthFallbackRoutes(app) {
|
|
|
878
1105
|
const session = await getSession(event);
|
|
879
1106
|
return session ?? { error: "Not authenticated" };
|
|
880
1107
|
}));
|
|
1108
|
+
// Must be reachable from fallback mode too — otherwise a user who
|
|
1109
|
+
// upgrades-from-local on a server that couldn't init Better Auth gets a
|
|
1110
|
+
// 500 instead of a clear 401.
|
|
1111
|
+
app.use("/_agent-native/auth/migrate-local-data", migrateLocalDataHandler);
|
|
881
1112
|
}
|
|
882
1113
|
// ---------------------------------------------------------------------------
|
|
883
1114
|
// autoMountAuth — the recommended entry point
|
|
@@ -896,13 +1127,17 @@ function mountAuthFallbackRoutes(app) {
|
|
|
896
1127
|
* Returns true if auth was mounted, false if skipped.
|
|
897
1128
|
*/
|
|
898
1129
|
export async function autoMountAuth(app, options = {}) {
|
|
899
|
-
// If auth is already mounted (e.g., default plugin ran before
|
|
900
|
-
// don't re-mount routes — but DO
|
|
901
|
-
//
|
|
902
|
-
//
|
|
903
|
-
//
|
|
904
|
-
//
|
|
905
|
-
|
|
1130
|
+
// If auth is already mounted on THIS app (e.g., default plugin ran before
|
|
1131
|
+
// custom plugin in the same server boot), don't re-mount routes — but DO
|
|
1132
|
+
// update the live config if custom options like googleOnly or loginHtml
|
|
1133
|
+
// were provided. createAuthGuardFn() reads from _authGuardConfig on every
|
|
1134
|
+
// request, so updating it here takes effect immediately.
|
|
1135
|
+
//
|
|
1136
|
+
// We gate on `_mountedApp === app` because module-level state survives
|
|
1137
|
+
// Vite HMR — without this check, an HMR-restarted Nitro instance (fresh
|
|
1138
|
+
// H3 app, empty middleware) would short-circuit here and end up with no
|
|
1139
|
+
// auth routes mounted at all.
|
|
1140
|
+
if (_authGuardFn && _mountedApp === app) {
|
|
906
1141
|
if (_authGuardConfig) {
|
|
907
1142
|
if (options.googleOnly || options.loginHtml) {
|
|
908
1143
|
_authGuardConfig.loginHtml =
|
|
@@ -918,6 +1153,11 @@ export async function autoMountAuth(app, options = {}) {
|
|
|
918
1153
|
}
|
|
919
1154
|
return true;
|
|
920
1155
|
}
|
|
1156
|
+
// Fresh app (first boot, or HMR created a new Nitro instance) — reset
|
|
1157
|
+
// the guard so the mount path below installs it on the new app.
|
|
1158
|
+
_authGuardFn = null;
|
|
1159
|
+
_authGuardConfig = null;
|
|
1160
|
+
_mountedApp = app;
|
|
921
1161
|
if (!app) {
|
|
922
1162
|
if ((await isLocalModeEnabled()) || isDevEnvironment()) {
|
|
923
1163
|
authDisabledMode = false;
|
|
@@ -965,8 +1205,11 @@ export async function autoMountAuth(app, options = {}) {
|
|
|
965
1205
|
if (cookie)
|
|
966
1206
|
await removeSession(cookie);
|
|
967
1207
|
deleteCookie(event, COOKIE_NAME, { path: "/" });
|
|
1208
|
+
if (isElectronRequest(event))
|
|
1209
|
+
await clearDesktopSso();
|
|
968
1210
|
return { ok: true };
|
|
969
1211
|
}));
|
|
1212
|
+
app.use("/_agent-native/auth/migrate-local-data", migrateLocalDataHandler);
|
|
970
1213
|
const byoaLoginHtml = options.loginHtml ?? TOKEN_LOGIN_HTML;
|
|
971
1214
|
_authGuardConfig = { loginHtml: byoaLoginHtml, publicPaths };
|
|
972
1215
|
const guardFn = createAuthGuardFn();
|