@agent-native/core 0.7.81 → 0.7.83
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 +4 -0
- package/dist/action.js.map +1 -1
- package/dist/agent/production-agent.d.ts +12 -2
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +58 -20
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +8 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +11 -12
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +13 -17
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/agent/types.d.ts +4 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/application-state/handlers.d.ts.map +1 -1
- package/dist/application-state/handlers.js +3 -8
- package/dist/application-state/handlers.js.map +1 -1
- package/dist/application-state/script-helpers.d.ts +2 -4
- package/dist/application-state/script-helpers.d.ts.map +1 -1
- package/dist/application-state/script-helpers.js +10 -47
- package/dist/application-state/script-helpers.js.map +1 -1
- package/dist/cli/create.d.ts +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +35 -10
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/workspace-dev.js +78 -15
- package/dist/cli/workspace-dev.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +6 -2
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +0 -15
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +69 -57
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/ConnectBuilderCard.d.ts +7 -1
- package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
- package/dist/client/ConnectBuilderCard.js +46 -5
- package/dist/client/ConnectBuilderCard.js.map +1 -1
- package/dist/client/ErrorBoundary.d.ts.map +1 -1
- package/dist/client/ErrorBoundary.js +20 -5
- package/dist/client/ErrorBoundary.js.map +1 -1
- package/dist/client/FeedbackButton.d.ts +3 -2
- package/dist/client/FeedbackButton.d.ts.map +1 -1
- package/dist/client/FeedbackButton.js +23 -15
- package/dist/client/FeedbackButton.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +303 -169
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/builder-frame.d.ts +36 -0
- package/dist/client/builder-frame.d.ts.map +1 -1
- package/dist/client/builder-frame.js +80 -9
- package/dist/client/builder-frame.js.map +1 -1
- package/dist/client/composer/ComposerPlusMenu.d.ts.map +1 -1
- package/dist/client/composer/ComposerPlusMenu.js +7 -2
- package/dist/client/composer/ComposerPlusMenu.js.map +1 -1
- package/dist/client/composer/PastedTextChip.d.ts +9 -0
- package/dist/client/composer/PastedTextChip.d.ts.map +1 -0
- package/dist/client/composer/PastedTextChip.js +47 -0
- package/dist/client/composer/PastedTextChip.js.map +1 -0
- package/dist/client/composer/PromptComposer.d.ts +4 -2
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +33 -5
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +13 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +61 -16
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/composer/VoiceButton.d.ts.map +1 -1
- package/dist/client/composer/VoiceButton.js +5 -1
- package/dist/client/composer/VoiceButton.js.map +1 -1
- package/dist/client/composer/pasted-text.d.ts +6 -0
- package/dist/client/composer/pasted-text.d.ts.map +1 -0
- package/dist/client/composer/pasted-text.js +49 -0
- package/dist/client/composer/pasted-text.js.map +1 -0
- package/dist/client/composer/useVoiceDictation.d.ts +1 -0
- package/dist/client/composer/useVoiceDictation.d.ts.map +1 -1
- package/dist/client/composer/useVoiceDictation.js +18 -0
- package/dist/client/composer/useVoiceDictation.js.map +1 -1
- package/dist/client/index.d.ts +0 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +0 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/integrations/IntegrationCard.d.ts.map +1 -1
- package/dist/client/integrations/IntegrationCard.js +14 -2
- package/dist/client/integrations/IntegrationCard.js.map +1 -1
- package/dist/client/integrations/IntegrationsPanel.d.ts.map +1 -1
- package/dist/client/integrations/IntegrationsPanel.js +23 -4
- package/dist/client/integrations/IntegrationsPanel.js.map +1 -1
- package/dist/client/notifications/NotificationsBell.d.ts.map +1 -1
- package/dist/client/notifications/NotificationsBell.js +4 -42
- package/dist/client/notifications/NotificationsBell.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts +4 -6
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +84 -74
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/org/TeamPage.d.ts.map +1 -1
- package/dist/client/org/TeamPage.js +3 -154
- package/dist/client/org/TeamPage.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +13 -35
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/client/settings/SettingsPanel.js +1 -1
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts +6 -0
- package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/client/settings/useBuilderStatus.js +3 -0
- package/dist/client/settings/useBuilderStatus.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +15 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +58 -54
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/tools/ToolEditor.d.ts.map +1 -1
- package/dist/client/tools/ToolEditor.js +34 -4
- package/dist/client/tools/ToolEditor.js.map +1 -1
- package/dist/client/tools/ToolViewer.d.ts.map +1 -1
- package/dist/client/tools/ToolViewer.js +20 -1
- package/dist/client/tools/ToolViewer.js.map +1 -1
- package/dist/client/tools/ToolsListPage.d.ts.map +1 -1
- package/dist/client/tools/ToolsListPage.js +2 -1
- package/dist/client/tools/ToolsListPage.js.map +1 -1
- package/dist/client/tools/ToolsSidebarSection.js +1 -1
- package/dist/client/tools/ToolsSidebarSection.js.map +1 -1
- package/dist/client/transcription/BuilderTranscriptionCta.js +1 -1
- package/dist/client/transcription/BuilderTranscriptionCta.js.map +1 -1
- package/dist/client/use-chat-threads.d.ts.map +1 -1
- package/dist/client/use-chat-threads.js +7 -2
- package/dist/client/use-chat-threads.js.map +1 -1
- package/dist/collab/client.d.ts.map +1 -1
- package/dist/collab/client.js +26 -7
- package/dist/collab/client.js.map +1 -1
- package/dist/jobs/scheduler.js +0 -4
- package/dist/jobs/scheduler.js.map +1 -1
- package/dist/oauth-tokens/store.d.ts +0 -4
- package/dist/oauth-tokens/store.d.ts.map +1 -1
- package/dist/oauth-tokens/store.js +3 -24
- package/dist/oauth-tokens/store.js.map +1 -1
- package/dist/observability/routes.d.ts.map +1 -1
- package/dist/observability/routes.js +1 -9
- package/dist/observability/routes.js.map +1 -1
- package/dist/onboarding/default-steps.js +1 -1
- package/dist/onboarding/default-steps.js.map +1 -1
- package/dist/onboarding/plugin.d.ts.map +1 -1
- package/dist/onboarding/plugin.js +1 -8
- package/dist/onboarding/plugin.js.map +1 -1
- package/dist/org/accept-pending.d.ts.map +1 -1
- package/dist/org/accept-pending.js +1 -2
- package/dist/org/accept-pending.js.map +1 -1
- package/dist/org/context.d.ts +0 -2
- package/dist/org/context.d.ts.map +1 -1
- package/dist/org/context.js +0 -5
- package/dist/org/context.js.map +1 -1
- package/dist/resources/script-helpers.d.ts +3 -4
- package/dist/resources/script-helpers.d.ts.map +1 -1
- package/dist/resources/script-helpers.js +8 -15
- package/dist/resources/script-helpers.js.map +1 -1
- package/dist/scripts/chat/search-chats.d.ts.map +1 -1
- package/dist/scripts/chat/search-chats.js +4 -4
- package/dist/scripts/chat/search-chats.js.map +1 -1
- package/dist/scripts/manage-agent-loop-settings.js +2 -2
- package/dist/scripts/manage-agent-loop-settings.js.map +1 -1
- package/dist/scripts/resources/delete-memory.d.ts.map +1 -1
- package/dist/scripts/resources/delete-memory.js +4 -2
- package/dist/scripts/resources/delete-memory.js.map +1 -1
- package/dist/scripts/resources/delete.d.ts.map +1 -1
- package/dist/scripts/resources/delete.js +11 -4
- package/dist/scripts/resources/delete.js.map +1 -1
- package/dist/scripts/resources/list.d.ts.map +1 -1
- package/dist/scripts/resources/list.js +5 -3
- package/dist/scripts/resources/list.js.map +1 -1
- package/dist/scripts/resources/migrate-learnings.d.ts.map +1 -1
- package/dist/scripts/resources/migrate-learnings.js +5 -2
- package/dist/scripts/resources/migrate-learnings.js.map +1 -1
- package/dist/scripts/resources/read.d.ts.map +1 -1
- package/dist/scripts/resources/read.js +4 -2
- package/dist/scripts/resources/read.js.map +1 -1
- package/dist/scripts/resources/save-memory.d.ts.map +1 -1
- package/dist/scripts/resources/save-memory.js +4 -2
- package/dist/scripts/resources/save-memory.js.map +1 -1
- package/dist/scripts/resources/write.d.ts.map +1 -1
- package/dist/scripts/resources/write.js +11 -4
- package/dist/scripts/resources/write.js.map +1 -1
- package/dist/secrets/onboarding.d.ts.map +1 -1
- package/dist/secrets/onboarding.js +1 -9
- package/dist/secrets/onboarding.js.map +1 -1
- package/dist/secrets/routes.d.ts.map +1 -1
- package/dist/secrets/routes.js +2 -7
- package/dist/secrets/routes.js.map +1 -1
- package/dist/server/action-discovery.d.ts +15 -0
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +49 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +5 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +81 -20
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-discovery.d.ts.map +1 -1
- package/dist/server/agent-discovery.js +5 -7
- package/dist/server/agent-discovery.js.map +1 -1
- package/dist/server/auth.d.ts +16 -20
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +115 -333
- package/dist/server/auth.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +23 -16
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +1 -2
- package/dist/server/credential-provider.js.map +1 -1
- package/dist/server/google-oauth.d.ts +14 -2
- package/dist/server/google-oauth.d.ts.map +1 -1
- package/dist/server/google-oauth.js +32 -10
- package/dist/server/google-oauth.js.map +1 -1
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/oauth-helpers.d.ts +2 -4
- package/dist/server/oauth-helpers.d.ts.map +1 -1
- package/dist/server/oauth-helpers.js +2 -4
- package/dist/server/oauth-helpers.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts.map +1 -1
- package/dist/server/transcribe-voice.js +2 -4
- package/dist/server/transcribe-voice.js.map +1 -1
- package/dist/triggers/dispatcher.d.ts.map +1 -1
- package/dist/triggers/dispatcher.js +0 -3
- package/dist/triggers/dispatcher.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +13 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/actions.md +1 -0
- package/docs/content/authentication.md +3 -20
- package/docs/content/creating-templates.md +1 -1
- package/docs/content/deployment.md +0 -1
- package/docs/content/security.md +0 -1
- package/docs/content/template-analytics.md +10 -0
- package/docs/content/template-calendar.md +10 -0
- package/docs/content/template-clips.md +10 -0
- package/docs/content/template-content.md +11 -1
- package/docs/content/template-dispatch.md +10 -0
- package/docs/content/template-forms.md +10 -0
- package/docs/content/template-mail.md +10 -0
- package/docs/content/template-slides.md +11 -1
- package/docs/content/template-starter.md +11 -1
- package/docs/content/template-video.md +10 -0
- package/package.json +1 -1
- package/dist/client/dev-mode.d.ts +0 -14
- package/dist/client/dev-mode.d.ts.map +0 -1
- package/dist/client/dev-mode.js +0 -14
- package/dist/client/dev-mode.js.map +0 -1
- package/dist/server/local-migration.d.ts +0 -41
- package/dist/server/local-migration.d.ts.map +0 -1
- package/dist/server/local-migration.js +0 -235
- package/dist/server/local-migration.js.map +0 -1
package/dist/server/auth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
-
import { defineEventHandler, getMethod, getQuery, getRequestIP, sendRedirect, setResponseHeader, setResponseStatus, getCookie, setCookie, deleteCookie, } from "h3";
|
|
2
|
+
import { defineEventHandler, getMethod, getQuery, getRequestIP, sendRedirect, setResponseHeader, setResponseStatus, getCookie, setCookie, deleteCookie, getHeader, } from "h3";
|
|
3
3
|
// In h3 v2, `event.req` IS the web Request — but in Nitro's dev server (srvx
|
|
4
4
|
// runtime), event.url and event.req share the same underlying URL object.
|
|
5
5
|
// When registerMiddleware strips the mount prefix from event.url.pathname, it
|
|
@@ -34,11 +34,10 @@ function toWebRequest(event) {
|
|
|
34
34
|
}
|
|
35
35
|
return req;
|
|
36
36
|
}
|
|
37
|
-
import { getDbExec, isPostgres, intType,
|
|
37
|
+
import { getDbExec, isPostgres, intType, retryOnDdlRace, } from "../db/client.js";
|
|
38
38
|
import { getBetterAuth, getBetterAuthSync } from "./better-auth-instance.js";
|
|
39
39
|
import { getAllowedCorsOrigin, readCorsAllowedOrigins, } from "./cors-origins.js";
|
|
40
40
|
import { getOnboardingHtml, getResetPasswordHtml } from "./onboarding-html.js";
|
|
41
|
-
import { migrateLocalUserData } from "./local-migration.js";
|
|
42
41
|
import { readBody } from "../server/h3-helpers.js";
|
|
43
42
|
import { readDesktopSso, writeDesktopSso, clearDesktopSso, } from "./desktop-sso.js";
|
|
44
43
|
import { isElectron as isElectronRequest, getAppBasePath, getAppUrl, encodeOAuthState, decodeOAuthState, createOAuthSession, oauthCallbackResponse, oauthErrorPage, resolveOAuthRedirectUri, isAllowedOAuthRedirectUri, } from "./google-oauth.js";
|
|
@@ -82,36 +81,8 @@ function getOAuthStateAppId() {
|
|
|
82
81
|
}
|
|
83
82
|
const DEFAULT_MAX_AGE = 60 * 60 * 24 * 30; // 30 days
|
|
84
83
|
// ---------------------------------------------------------------------------
|
|
85
|
-
//
|
|
84
|
+
// Environment helpers
|
|
86
85
|
// ---------------------------------------------------------------------------
|
|
87
|
-
let _warnedRemoteLocalMode = false;
|
|
88
|
-
/**
|
|
89
|
-
* Check if the app is in local-only mode (no auth).
|
|
90
|
-
*
|
|
91
|
-
* Returns true when AUTH_MODE=local is explicitly set.
|
|
92
|
-
*
|
|
93
|
-
* Local mode is an explicit escape hatch for when you want to guarantee
|
|
94
|
-
* no auth is used. In development, getSession() also falls back to
|
|
95
|
-
* local@localhost automatically if no other auth method succeeds, so
|
|
96
|
-
* apps are always usable without configuration in dev.
|
|
97
|
-
*
|
|
98
|
-
* Refuses to enable on any non-local database (Postgres, Turso, D1): local
|
|
99
|
-
* mode uses a single shared virtual user with no per-machine scoping, so on
|
|
100
|
-
* a shared DB every developer would land on the same account and collide.
|
|
101
|
-
*/
|
|
102
|
-
async function isLocalModeEnabled() {
|
|
103
|
-
if (process.env.AUTH_MODE !== "local")
|
|
104
|
-
return false;
|
|
105
|
-
if (!isLocalDatabase()) {
|
|
106
|
-
if (!_warnedRemoteLocalMode) {
|
|
107
|
-
_warnedRemoteLocalMode = true;
|
|
108
|
-
console.warn("[agent-native] AUTH_MODE=local ignored: database is not local SQLite. " +
|
|
109
|
-
"local@localhost has no per-user scoping and would collide across developers on a shared DB.");
|
|
110
|
-
}
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
86
|
/**
|
|
116
87
|
* Check if we're in a development/test environment.
|
|
117
88
|
* Used for cookie security settings, not for auth bypass.
|
|
@@ -245,6 +216,36 @@ function safeTokenMatch(input, tokens) {
|
|
|
245
216
|
}
|
|
246
217
|
return false;
|
|
247
218
|
}
|
|
219
|
+
function getBearerSessionToken(event) {
|
|
220
|
+
const auth = getHeader(event, "authorization");
|
|
221
|
+
if (!auth)
|
|
222
|
+
return undefined;
|
|
223
|
+
const match = /^Bearer\s+(.+)$/i.exec(auth.trim());
|
|
224
|
+
return match?.[1]?.trim() || undefined;
|
|
225
|
+
}
|
|
226
|
+
async function getBearerLegacySession(event) {
|
|
227
|
+
const bearerToken = getBearerSessionToken(event);
|
|
228
|
+
if (!bearerToken)
|
|
229
|
+
return null;
|
|
230
|
+
const email = await getSessionEmail(bearerToken);
|
|
231
|
+
return email ? { email, token: bearerToken } : null;
|
|
232
|
+
}
|
|
233
|
+
function shouldExposeSessionTokenInBody(event) {
|
|
234
|
+
const origin = getHeader(event, "origin");
|
|
235
|
+
if (origin && DESKTOP_AUTH_TOKEN_BODY_ORIGINS.has(origin))
|
|
236
|
+
return true;
|
|
237
|
+
// Some native WebViews do not consistently emit an Origin header for
|
|
238
|
+
// programmatic fetches. The desktop app marks same-server requests with
|
|
239
|
+
// X-Request-Source; browsers can only use that cross-origin after our CORS
|
|
240
|
+
// allowlist has approved the origin, and same-origin pages already receive
|
|
241
|
+
// an equivalent httpOnly session cookie on successful login.
|
|
242
|
+
return !origin && getHeader(event, "x-request-source") === "clips-desktop";
|
|
243
|
+
}
|
|
244
|
+
function authLoginResponse(event, token, email) {
|
|
245
|
+
if (!shouldExposeSessionTokenInBody(event))
|
|
246
|
+
return { ok: true };
|
|
247
|
+
return email ? { ok: true, token, email } : { ok: true, token };
|
|
248
|
+
}
|
|
248
249
|
// ---------------------------------------------------------------------------
|
|
249
250
|
// Legacy session store — kept for backward compat (addSession/getSessionEmail)
|
|
250
251
|
// Used by google-oauth.ts for mobile deep linking session creation.
|
|
@@ -348,7 +349,6 @@ export async function getSessionEmail(token) {
|
|
|
348
349
|
// getSession — the auth contract
|
|
349
350
|
// ---------------------------------------------------------------------------
|
|
350
351
|
let customGetSession = null;
|
|
351
|
-
let authDisabledMode = false;
|
|
352
352
|
let _authGuardConfig = null;
|
|
353
353
|
const _genericGoogleOAuthRoutesEnabled = new WeakMap();
|
|
354
354
|
function setGenericGoogleOAuthRoutesEnabled(app, enabled) {
|
|
@@ -361,6 +361,10 @@ function areGenericGoogleOAuthRoutesEnabled(app) {
|
|
|
361
361
|
}
|
|
362
362
|
const _desktopExchanges = new Map();
|
|
363
363
|
const DESKTOP_EXCHANGE_ERROR_PREFIX = "__error__::";
|
|
364
|
+
const DESKTOP_AUTH_TOKEN_BODY_ORIGINS = new Set([
|
|
365
|
+
"tauri://localhost",
|
|
366
|
+
"http://localhost:1420",
|
|
367
|
+
]);
|
|
364
368
|
// 5-minute TTL for exchange entries (short — single-use tokens).
|
|
365
369
|
const DESKTOP_EXCHANGE_TTL_MS = 5 * 60 * 1000;
|
|
366
370
|
export function setDesktopExchange(flowId, token, email) {
|
|
@@ -477,17 +481,6 @@ export async function runAuthGuard(event) {
|
|
|
477
481
|
return; // Auth not mounted (local mode, etc.)
|
|
478
482
|
return _authGuardFn(event);
|
|
479
483
|
}
|
|
480
|
-
/**
|
|
481
|
-
* The framework's dev-mode bypass identity. When `AUTH_MODE=local` (or
|
|
482
|
-
* dev-mode falls back), `getSession()` returns `{ email: DEV_MODE_USER_EMAIL }`.
|
|
483
|
-
* Production code that needs to check whether the current request is the
|
|
484
|
-
* dev-mode user (or filter it out of mailers, dashboards, etc.) should
|
|
485
|
-
* compare against this constant instead of inlining the literal —
|
|
486
|
-
* `guard-no-localhost-fallback.mjs` blocks the literal everywhere except
|
|
487
|
-
* `auth.ts` and a handful of dev-mode helpers.
|
|
488
|
-
*/
|
|
489
|
-
export const DEV_MODE_USER_EMAIL = "local@localhost";
|
|
490
|
-
const LOCAL_SESSION = { email: DEV_MODE_USER_EMAIL };
|
|
491
484
|
// ---------------------------------------------------------------------------
|
|
492
485
|
// Auth guard factory
|
|
493
486
|
// ---------------------------------------------------------------------------
|
|
@@ -506,9 +499,7 @@ function applyCorsHeaders(event) {
|
|
|
506
499
|
// response would be missing the Allow-Origin header and the browser
|
|
507
500
|
// blocks the response body (making it look like a network error
|
|
508
501
|
// rather than "unauthenticated").
|
|
509
|
-
const
|
|
510
|
-
const originRaw = reqHeaders["origin"];
|
|
511
|
-
const origin = Array.isArray(originRaw) ? originRaw[0] : originRaw;
|
|
502
|
+
const origin = getHeader(event, "origin");
|
|
512
503
|
if (!origin)
|
|
513
504
|
return { hasOrigin: false, allowed: true };
|
|
514
505
|
const allowedOrigin = getAllowedCorsOrigin(origin, {
|
|
@@ -524,6 +515,24 @@ function applyCorsHeaders(event) {
|
|
|
524
515
|
setResponseHeader(event, "Access-Control-Allow-Headers", "Content-Type,Authorization,X-Requested-With,X-Request-Source,X-Agent-Native-CSRF");
|
|
525
516
|
return { hasOrigin: true, allowed: true };
|
|
526
517
|
}
|
|
518
|
+
function createAuthCorsHandler() {
|
|
519
|
+
return defineEventHandler((event) => {
|
|
520
|
+
const cors = applyCorsHeaders(event);
|
|
521
|
+
if (getMethod(event) !== "OPTIONS")
|
|
522
|
+
return;
|
|
523
|
+
if (cors.hasOrigin && !cors.allowed) {
|
|
524
|
+
setResponseStatus(event, 403);
|
|
525
|
+
return "";
|
|
526
|
+
}
|
|
527
|
+
setResponseStatus(event, 204);
|
|
528
|
+
return "";
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
function mountAuthCorsMiddleware(app) {
|
|
532
|
+
const handler = createAuthCorsHandler();
|
|
533
|
+
app.use("/_agent-native/auth", handler);
|
|
534
|
+
app.use("/_agent-native/google", handler);
|
|
535
|
+
}
|
|
527
536
|
function createAuthGuardFn() {
|
|
528
537
|
return async (event) => {
|
|
529
538
|
const config = _authGuardConfig;
|
|
@@ -628,8 +637,8 @@ function createAuthGuardFn() {
|
|
|
628
637
|
});
|
|
629
638
|
}
|
|
630
639
|
// Auth entry pages are framework-owned pages, not app routes. When a user
|
|
631
|
-
// already has a session
|
|
632
|
-
//
|
|
640
|
+
// already has a session, redirect them back to the mounted app instead of
|
|
641
|
+
// letting React Router try to render /login.
|
|
633
642
|
if (p === "/login" || p === "/signup") {
|
|
634
643
|
const session = await getSession(event);
|
|
635
644
|
if (session) {
|
|
@@ -656,6 +665,15 @@ function createAuthGuardFn() {
|
|
|
656
665
|
p.endsWith(".woff")) {
|
|
657
666
|
return;
|
|
658
667
|
}
|
|
668
|
+
// React Router 7's lazy route discovery fetches `/__manifest?p=...` to
|
|
669
|
+
// resolve manifest patches for `<Link>`s the user might click. The
|
|
670
|
+
// auth fallback returning loginHtml here makes RR fail to parse the
|
|
671
|
+
// body as RSC, surfacing as a console error and (when the visitor
|
|
672
|
+
// already errored elsewhere) blocking the app from rendering. Let it
|
|
673
|
+
// through — it returns a tiny RSC-encoded manifest of the public
|
|
674
|
+
// route tree, no per-user data.
|
|
675
|
+
if (p === "/__manifest")
|
|
676
|
+
return;
|
|
659
677
|
if (isPublicPath(normalizedUrl, publicPaths))
|
|
660
678
|
return;
|
|
661
679
|
const session = await getSession(event);
|
|
@@ -687,51 +705,21 @@ function mapBetterAuthSession(baSession) {
|
|
|
687
705
|
* Get the current auth session for a request.
|
|
688
706
|
*
|
|
689
707
|
* Resolution chain:
|
|
690
|
-
* 1.
|
|
691
|
-
* 2.
|
|
692
|
-
* 3.
|
|
693
|
-
* 4.
|
|
694
|
-
* 5.
|
|
695
|
-
* 6.
|
|
708
|
+
* 1. ACCESS_TOKEN → check legacy cookie-based token sessions
|
|
709
|
+
* 2. BYOA custom getSession → delegate to template callback
|
|
710
|
+
* 3. Bearer legacy session → check Authorization: Bearer against sessions
|
|
711
|
+
* 4. Better Auth → check session via Better Auth API (cookie or Bearer)
|
|
712
|
+
* 5. Legacy cookie → check an_session cookie in legacy sessions table
|
|
713
|
+
* 6. Desktop SSO broker (Electron loopback only)
|
|
696
714
|
* 7. Mobile _session query param → promote to cookie
|
|
697
|
-
*
|
|
715
|
+
*
|
|
716
|
+
* Returns `null` for unauthenticated requests. There is no dev-mode bypass:
|
|
717
|
+
* local development uses the same Better Auth signup flow as production. The
|
|
718
|
+
* onboarding/sign-in page is served by `runAuthGuard` for any unauthenticated
|
|
719
|
+
* page load.
|
|
698
720
|
*/
|
|
699
721
|
export async function getSession(event) {
|
|
700
|
-
// 1.
|
|
701
|
-
if ((await isLocalModeEnabled()) || authDisabledMode) {
|
|
702
|
-
// Check for a real session cookie first (e.g. from Google OAuth)
|
|
703
|
-
try {
|
|
704
|
-
const cookie = getCookie(event, COOKIE_NAME);
|
|
705
|
-
if (cookie) {
|
|
706
|
-
const email = await getSessionEmail(cookie);
|
|
707
|
-
if (email)
|
|
708
|
-
return { email, token: cookie };
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
catch {
|
|
712
|
-
// DB not ready yet
|
|
713
|
-
}
|
|
714
|
-
// Also try Better Auth session (for users who created an account then went local)
|
|
715
|
-
try {
|
|
716
|
-
const ba = getBetterAuthSync();
|
|
717
|
-
if (ba) {
|
|
718
|
-
const baSession = await ba.api.getSession({
|
|
719
|
-
headers: event.headers,
|
|
720
|
-
});
|
|
721
|
-
if (baSession?.user?.email) {
|
|
722
|
-
return mapBetterAuthSession(baSession);
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
catch {
|
|
727
|
-
// Better Auth not initialized yet
|
|
728
|
-
}
|
|
729
|
-
const querySession = await promoteQuerySession(event);
|
|
730
|
-
if (querySession)
|
|
731
|
-
return querySession;
|
|
732
|
-
return LOCAL_SESSION;
|
|
733
|
-
}
|
|
734
|
-
// 2. ACCESS_TOKEN check (programmatic/agent access)
|
|
722
|
+
// 1. ACCESS_TOKEN check (programmatic/agent access)
|
|
735
723
|
const accessTokens = getAccessTokens();
|
|
736
724
|
if (accessTokens.length > 0) {
|
|
737
725
|
const cookie = getCookie(event, COOKIE_NAME);
|
|
@@ -741,11 +729,14 @@ export async function getSession(event) {
|
|
|
741
729
|
return { email, token: cookie };
|
|
742
730
|
}
|
|
743
731
|
}
|
|
744
|
-
//
|
|
732
|
+
// 2. BYOA custom getSession
|
|
745
733
|
if (customGetSession) {
|
|
746
734
|
const session = await customGetSession(event);
|
|
747
735
|
if (session)
|
|
748
736
|
return session;
|
|
737
|
+
const bearerSession = await getBearerLegacySession(event);
|
|
738
|
+
if (bearerSession)
|
|
739
|
+
return bearerSession;
|
|
749
740
|
// Desktop SSO broker: even with BYOA auth, fall back to the broker
|
|
750
741
|
// for Electron requests so cross-template SSO works for custom-auth
|
|
751
742
|
// templates too. Gated on `readDesktopSsoSafely` so a non-loopback
|
|
@@ -757,6 +748,11 @@ export async function getSession(event) {
|
|
|
757
748
|
// Fall through to mobile _session check
|
|
758
749
|
}
|
|
759
750
|
else {
|
|
751
|
+
// 3. Bearer legacy session. Desktop/native clients can persist a session
|
|
752
|
+
// token outside the WebView cookie jar and attach it to all app requests.
|
|
753
|
+
const bearerSession = await getBearerLegacySession(event);
|
|
754
|
+
if (bearerSession)
|
|
755
|
+
return bearerSession;
|
|
760
756
|
// 4. Better Auth session (cookie or Bearer token)
|
|
761
757
|
try {
|
|
762
758
|
const ba = getBetterAuthSync();
|
|
@@ -765,9 +761,6 @@ export async function getSession(event) {
|
|
|
765
761
|
headers: event.headers,
|
|
766
762
|
});
|
|
767
763
|
if (baSession?.user?.email) {
|
|
768
|
-
// Successful real sign-in — clear the upgrade-pending marker so
|
|
769
|
-
// the dev fallback becomes reachable again for future local work.
|
|
770
|
-
clearUpgradePendingCookie(event);
|
|
771
764
|
return mapBetterAuthSession(baSession);
|
|
772
765
|
}
|
|
773
766
|
}
|
|
@@ -780,11 +773,10 @@ export async function getSession(event) {
|
|
|
780
773
|
if (cookie) {
|
|
781
774
|
const email = await getSessionEmail(cookie);
|
|
782
775
|
if (email) {
|
|
783
|
-
clearUpgradePendingCookie(event);
|
|
784
776
|
return { email, token: cookie };
|
|
785
777
|
}
|
|
786
778
|
}
|
|
787
|
-
//
|
|
779
|
+
// 6. Desktop SSO broker fallback.
|
|
788
780
|
// Each template in the Electron desktop app has its own database, so
|
|
789
781
|
// a session token created by one template doesn't resolve in another.
|
|
790
782
|
// When an Electron request has no resolvable session, trust the
|
|
@@ -795,40 +787,13 @@ export async function getSession(event) {
|
|
|
795
787
|
// impersonate whichever email last signed into the desktop app.
|
|
796
788
|
const sso = await readDesktopSsoSafely(event);
|
|
797
789
|
if (sso?.email) {
|
|
798
|
-
clearUpgradePendingCookie(event);
|
|
799
790
|
return { email: sso.email, token: sso.token };
|
|
800
791
|
}
|
|
801
792
|
}
|
|
802
|
-
//
|
|
793
|
+
// 7. Mobile WebView bridge — _session query param
|
|
803
794
|
const querySession = await promoteQuerySession(event);
|
|
804
795
|
if (querySession)
|
|
805
796
|
return querySession;
|
|
806
|
-
// 7. Dev-mode safety net — in development on a local SQLite database, fall
|
|
807
|
-
// back to local@localhost so the app is usable without any auth configuration.
|
|
808
|
-
// This prevents 401 errors when Better Auth isn't configured or the user
|
|
809
|
-
// simply wants to play around locally.
|
|
810
|
-
//
|
|
811
|
-
// Gated on isLocalDatabase() because local@localhost has no per-user scoping:
|
|
812
|
-
// on a shared DB (Postgres, Turso, D1) this fallback would land every
|
|
813
|
-
// developer on the same account and expose each other's data.
|
|
814
|
-
//
|
|
815
|
-
// STRICT NODE_ENV check: this used to read `isDevEnvironment()` which
|
|
816
|
-
// also accepted `NODE_ENV=test`, meaning a misconfigured prod deploy
|
|
817
|
-
// started with `NODE_ENV=test` (or undefined NODE_ENV in some CI/build
|
|
818
|
-
// contexts) would silently bypass auth entirely. Limiting to the literal
|
|
819
|
-
// string "development" closes that footgun. Tests that need this branch
|
|
820
|
-
// to fire stub NODE_ENV explicitly to "development".
|
|
821
|
-
//
|
|
822
|
-
// EXCEPTION: if the user has explicitly exited local mode (clicked "Upgrade
|
|
823
|
-
// to real account"), they've signaled they want real auth. The upgrade
|
|
824
|
-
// cookie suppresses this fallback so the onboarding/sign-in page is served
|
|
825
|
-
// instead of silently re-authenticating them as local@localhost.
|
|
826
|
-
if (process.env.NODE_ENV === "development" &&
|
|
827
|
-
isLocalDatabase() &&
|
|
828
|
-
!isUpgradePending(event) &&
|
|
829
|
-
!hasSignInFlag(event)) {
|
|
830
|
-
return LOCAL_SESSION;
|
|
831
|
-
}
|
|
832
797
|
return null;
|
|
833
798
|
}
|
|
834
799
|
async function promoteQuerySession(event) {
|
|
@@ -842,44 +807,6 @@ async function promoteQuerySession(event) {
|
|
|
842
807
|
setResponseHeader(event, "Referrer-Policy", "no-referrer");
|
|
843
808
|
return { email, token: qToken };
|
|
844
809
|
}
|
|
845
|
-
/**
|
|
846
|
-
* Cookie set by POST /_agent-native/auth/exit-local-mode so we know the user
|
|
847
|
-
* is in the middle of upgrading from local@localhost to a real account.
|
|
848
|
-
* While this cookie is present we skip the dev-mode "auto local session"
|
|
849
|
-
* fallback so the onboarding/sign-in page can actually render.
|
|
850
|
-
* Cleared on successful sign-in/sign-up.
|
|
851
|
-
*/
|
|
852
|
-
const UPGRADE_COOKIE = "an_upgrade_pending";
|
|
853
|
-
function isUpgradePending(event) {
|
|
854
|
-
try {
|
|
855
|
-
return getCookie(event, UPGRADE_COOKIE) === "1";
|
|
856
|
-
}
|
|
857
|
-
catch {
|
|
858
|
-
return false;
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
function setUpgradePendingCookie(event) {
|
|
862
|
-
setCookie(event, UPGRADE_COOKIE, "1", {
|
|
863
|
-
httpOnly: true,
|
|
864
|
-
...crossSiteCookieAttrs(event),
|
|
865
|
-
path: "/",
|
|
866
|
-
maxAge: 60 * 60, // 1 hour — enough to complete sign-in
|
|
867
|
-
});
|
|
868
|
-
}
|
|
869
|
-
/**
|
|
870
|
-
* URL-flag fallback for third-party iframe contexts (e.g. the Builder.io
|
|
871
|
-
* editor) where SameSite=Lax cookies from an exit-local-mode POST are not
|
|
872
|
-
* delivered on the subsequent reload. TeamPage reloads with ?signin=1 so
|
|
873
|
-
* we can reliably suppress the dev-mode local fallback without a cookie.
|
|
874
|
-
*/
|
|
875
|
-
function hasSignInFlag(event) {
|
|
876
|
-
try {
|
|
877
|
-
return getQuery(event)?.signin === "1";
|
|
878
|
-
}
|
|
879
|
-
catch {
|
|
880
|
-
return false;
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
810
|
function isReadMethod(event) {
|
|
884
811
|
const method = getMethod(event);
|
|
885
812
|
return method === "GET" || method === "HEAD";
|
|
@@ -905,21 +832,11 @@ function setFrameworkSessionCookie(event, token) {
|
|
|
905
832
|
}
|
|
906
833
|
function isHttpsRequest(event) {
|
|
907
834
|
try {
|
|
908
|
-
const
|
|
909
|
-
const headers = req?.headers;
|
|
910
|
-
const get = (k) => {
|
|
911
|
-
if (!headers)
|
|
912
|
-
return undefined;
|
|
913
|
-
if (typeof headers.get === "function") {
|
|
914
|
-
return headers.get(k) ?? undefined;
|
|
915
|
-
}
|
|
916
|
-
const v = headers[k];
|
|
917
|
-
return Array.isArray(v) ? v[0] : v;
|
|
918
|
-
};
|
|
919
|
-
const xfProto = get("x-forwarded-proto");
|
|
835
|
+
const xfProto = getHeader(event, "x-forwarded-proto");
|
|
920
836
|
if (xfProto && String(xfProto).split(",")[0].trim() === "https") {
|
|
921
837
|
return true;
|
|
922
838
|
}
|
|
839
|
+
const req = event.req ?? event.node?.req;
|
|
923
840
|
const url = req?.url;
|
|
924
841
|
if (typeof url === "string" && url.startsWith("https://"))
|
|
925
842
|
return true;
|
|
@@ -932,14 +849,6 @@ function isHttpsRequest(event) {
|
|
|
932
849
|
}
|
|
933
850
|
return false;
|
|
934
851
|
}
|
|
935
|
-
function clearUpgradePendingCookie(event) {
|
|
936
|
-
try {
|
|
937
|
-
deleteCookie(event, UPGRADE_COOKIE, { path: "/" });
|
|
938
|
-
}
|
|
939
|
-
catch {
|
|
940
|
-
// ignore
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
852
|
// ---------------------------------------------------------------------------
|
|
944
853
|
// Public path matching
|
|
945
854
|
// ---------------------------------------------------------------------------
|
|
@@ -1053,57 +962,6 @@ const TOKEN_LOGIN_HTML = `<!DOCTYPE html>
|
|
|
1053
962
|
</body>
|
|
1054
963
|
</html>`;
|
|
1055
964
|
// ---------------------------------------------------------------------------
|
|
1056
|
-
// Local mode route helpers
|
|
1057
|
-
// ---------------------------------------------------------------------------
|
|
1058
|
-
function localModeMarkerDisabled(event) {
|
|
1059
|
-
setResponseStatus(event, 410);
|
|
1060
|
-
return {
|
|
1061
|
-
error: "The local-mode marker file has been removed. Set AUTH_MODE=local in your local shell or .env only for one-off local development.",
|
|
1062
|
-
};
|
|
1063
|
-
}
|
|
1064
|
-
function exitLocalMode(event) {
|
|
1065
|
-
// Mark the browser so getSession's dev-mode fallback won't silently
|
|
1066
|
-
// re-authenticate the user as local@localhost on the next request.
|
|
1067
|
-
setUpgradePendingCookie(event);
|
|
1068
|
-
return { ok: true };
|
|
1069
|
-
}
|
|
1070
|
-
/**
|
|
1071
|
-
* POST /_agent-native/auth/migrate-local-data handler. Exposed here (not
|
|
1072
|
-
* inlined in a single mount function) because it must be registered from
|
|
1073
|
-
* every auth-mount path — including local-mode and fallback — so the
|
|
1074
|
-
* upgrade-from-local flow never 500s when BetterAuth init is skipped or
|
|
1075
|
-
* failed. Previously this was only mounted inside mountBetterAuthRoutes()
|
|
1076
|
-
* which meant that in local mode (or when BetterAuth failed to init) the
|
|
1077
|
-
* request fell through to the Nitro SSR renderer and produced a 500.
|
|
1078
|
-
*/
|
|
1079
|
-
const migrateLocalDataHandler = defineEventHandler(async (event) => {
|
|
1080
|
-
if (getMethod(event) !== "POST") {
|
|
1081
|
-
setResponseStatus(event, 405);
|
|
1082
|
-
return { error: "Method not allowed" };
|
|
1083
|
-
}
|
|
1084
|
-
const session = await getSession(event);
|
|
1085
|
-
if (!session?.email || session.email === "local@localhost") {
|
|
1086
|
-
setResponseStatus(event, 401);
|
|
1087
|
-
return { error: "Not authenticated as a real account" };
|
|
1088
|
-
}
|
|
1089
|
-
try {
|
|
1090
|
-
const result = await migrateLocalUserData(session.email);
|
|
1091
|
-
return { ok: true, ...result };
|
|
1092
|
-
}
|
|
1093
|
-
catch (e) {
|
|
1094
|
-
console.error("[migrate-local-data] Migration threw for", session.email, e);
|
|
1095
|
-
setResponseStatus(event, 500);
|
|
1096
|
-
return {
|
|
1097
|
-
error: e?.message || "Migration failed",
|
|
1098
|
-
// Only surface the stack when explicitly enabled. `isDevEnvironment()`
|
|
1099
|
-
// returns true on preview deploys and Lambda contexts that forget
|
|
1100
|
-
// NODE_ENV=production, which leaked stack traces to clients. Use
|
|
1101
|
-
// AGENT_NATIVE_DEBUG_ERRORS=1 for opt-in debug visibility.
|
|
1102
|
-
stack: process.env.AGENT_NATIVE_DEBUG_ERRORS === "1" ? e?.stack : undefined,
|
|
1103
|
-
};
|
|
1104
|
-
}
|
|
1105
|
-
});
|
|
1106
|
-
// ---------------------------------------------------------------------------
|
|
1107
965
|
// mountBetterAuthRoutes — Better Auth powered auth with backward-compat routes
|
|
1108
966
|
// ---------------------------------------------------------------------------
|
|
1109
967
|
async function mountBetterAuthRoutes(app, options) {
|
|
@@ -1470,23 +1328,6 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
1470
1328
|
}
|
|
1471
1329
|
return response;
|
|
1472
1330
|
}));
|
|
1473
|
-
// POST /_agent-native/auth/local-mode — legacy endpoint kept so old clients
|
|
1474
|
-
// receive an explicit error instead of creating a persistent local marker.
|
|
1475
|
-
app.use("/_agent-native/auth/local-mode", defineEventHandler(async (event) => {
|
|
1476
|
-
if (getMethod(event) !== "POST") {
|
|
1477
|
-
setResponseStatus(event, 405);
|
|
1478
|
-
return { error: "Method not allowed" };
|
|
1479
|
-
}
|
|
1480
|
-
return localModeMarkerDisabled(event);
|
|
1481
|
-
}));
|
|
1482
|
-
// POST /_agent-native/auth/exit-local-mode — switch back to real auth
|
|
1483
|
-
app.use("/_agent-native/auth/exit-local-mode", defineEventHandler(async (event) => {
|
|
1484
|
-
if (getMethod(event) !== "POST") {
|
|
1485
|
-
setResponseStatus(event, 405);
|
|
1486
|
-
return { error: "Method not allowed" };
|
|
1487
|
-
}
|
|
1488
|
-
return exitLocalMode(event);
|
|
1489
|
-
}));
|
|
1490
1331
|
// Backward-compat: POST /_agent-native/auth/login
|
|
1491
1332
|
app.use("/_agent-native/auth/login", defineEventHandler(async (event) => {
|
|
1492
1333
|
if (getMethod(event) !== "POST") {
|
|
@@ -1510,7 +1351,7 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
1510
1351
|
path: "/",
|
|
1511
1352
|
maxAge: sessionMaxAge,
|
|
1512
1353
|
});
|
|
1513
|
-
return
|
|
1354
|
+
return authLoginResponse(event, sessionToken, "user");
|
|
1514
1355
|
}
|
|
1515
1356
|
// Email/password login via Better Auth
|
|
1516
1357
|
const email = body?.email?.trim?.()?.toLowerCase?.();
|
|
@@ -1538,7 +1379,7 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
1538
1379
|
expiresAt: Date.now() + sessionMaxAge * 1000,
|
|
1539
1380
|
});
|
|
1540
1381
|
}
|
|
1541
|
-
return
|
|
1382
|
+
return authLoginResponse(event, result.token, email);
|
|
1542
1383
|
}
|
|
1543
1384
|
// signInEmail succeeded but returned no token — typically means the
|
|
1544
1385
|
// email isn't verified yet. Don't return { ok: true } without a
|
|
@@ -1589,6 +1430,9 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
1589
1430
|
const cookie = getCookie(event, COOKIE_NAME);
|
|
1590
1431
|
if (cookie)
|
|
1591
1432
|
await removeSession(cookie);
|
|
1433
|
+
const bearerToken = getBearerSessionToken(event);
|
|
1434
|
+
if (bearerToken)
|
|
1435
|
+
await removeSession(bearerToken);
|
|
1592
1436
|
deleteCookie(event, COOKIE_NAME, { path: "/" });
|
|
1593
1437
|
try {
|
|
1594
1438
|
await auth.api.signOut({ headers: event.headers });
|
|
@@ -1677,10 +1521,6 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
1677
1521
|
const session = await getSession(event);
|
|
1678
1522
|
return session ?? { error: "Not authenticated" };
|
|
1679
1523
|
}));
|
|
1680
|
-
// POST /_agent-native/auth/migrate-local-data — move local-mode data to
|
|
1681
|
-
// the currently signed-in account. Called by the UI after a user upgrades
|
|
1682
|
-
// from local mode to a real account so they don't lose their data.
|
|
1683
|
-
app.use("/_agent-native/auth/migrate-local-data", migrateLocalDataHandler);
|
|
1684
1524
|
// GET /_agent-native/auth/reset — HTML page shown when a user clicks the
|
|
1685
1525
|
// reset link in their email. Reads ?token=... and POSTs to Better Auth's
|
|
1686
1526
|
// /reset-password endpoint on submit.
|
|
@@ -1729,12 +1569,15 @@ function mountTokenOnlyRoutes(app, accessTokens, publicPaths = []) {
|
|
|
1729
1569
|
path: "/",
|
|
1730
1570
|
maxAge: sessionMaxAge,
|
|
1731
1571
|
});
|
|
1732
|
-
return
|
|
1572
|
+
return authLoginResponse(event, sessionToken, "user");
|
|
1733
1573
|
}));
|
|
1734
1574
|
app.use("/_agent-native/auth/logout", defineEventHandler(async (event) => {
|
|
1735
1575
|
const cookie = getCookie(event, COOKIE_NAME);
|
|
1736
1576
|
if (cookie)
|
|
1737
1577
|
await removeSession(cookie);
|
|
1578
|
+
const bearerToken = getBearerSessionToken(event);
|
|
1579
|
+
if (bearerToken)
|
|
1580
|
+
await removeSession(bearerToken);
|
|
1738
1581
|
deleteCookie(event, COOKIE_NAME, { path: "/" });
|
|
1739
1582
|
if (isElectronRequest(event))
|
|
1740
1583
|
await clearDesktopSso();
|
|
@@ -1748,38 +1591,12 @@ function mountTokenOnlyRoutes(app, accessTokens, publicPaths = []) {
|
|
|
1748
1591
|
const session = await getSession(event);
|
|
1749
1592
|
return session ?? { error: "Not authenticated" };
|
|
1750
1593
|
}));
|
|
1751
|
-
app.use("/_agent-native/auth/migrate-local-data", migrateLocalDataHandler);
|
|
1752
1594
|
_authGuardConfig = { loginHtml: TOKEN_LOGIN_HTML, publicPaths };
|
|
1753
1595
|
const guardFn = createAuthGuardFn();
|
|
1754
1596
|
_authGuardFn = guardFn;
|
|
1755
1597
|
app.use(defineEventHandler(guardFn));
|
|
1756
1598
|
}
|
|
1757
1599
|
// ---------------------------------------------------------------------------
|
|
1758
|
-
// mountLocalModeRoutes — stub routes for AUTH_MODE=local
|
|
1759
|
-
// ---------------------------------------------------------------------------
|
|
1760
|
-
function mountLocalModeRoutes(app) {
|
|
1761
|
-
app.use("/_agent-native/auth/session", defineEventHandler(async (event) => {
|
|
1762
|
-
if (!isReadMethod(event)) {
|
|
1763
|
-
setResponseStatus(event, 405);
|
|
1764
|
-
return { error: "Method not allowed" };
|
|
1765
|
-
}
|
|
1766
|
-
return await getSession(event);
|
|
1767
|
-
}));
|
|
1768
|
-
app.use("/_agent-native/auth/login", defineEventHandler(() => ({ ok: true })));
|
|
1769
|
-
app.use("/_agent-native/auth/logout", defineEventHandler(() => ({ ok: true })));
|
|
1770
|
-
// Allow exiting local mode to switch to real auth
|
|
1771
|
-
app.use("/_agent-native/auth/exit-local-mode", defineEventHandler(async (event) => {
|
|
1772
|
-
if (getMethod(event) !== "POST") {
|
|
1773
|
-
setResponseStatus(event, 405);
|
|
1774
|
-
return { error: "Method not allowed" };
|
|
1775
|
-
}
|
|
1776
|
-
return exitLocalMode(event);
|
|
1777
|
-
}));
|
|
1778
|
-
// Upgrade path: migrate-local-data must be reachable from local mode
|
|
1779
|
-
// because the user is still in local mode when they trigger the upgrade.
|
|
1780
|
-
app.use("/_agent-native/auth/migrate-local-data", migrateLocalDataHandler);
|
|
1781
|
-
}
|
|
1782
|
-
// ---------------------------------------------------------------------------
|
|
1783
1600
|
// mountAuthFallbackRoutes — minimal auth endpoints when Better Auth init fails
|
|
1784
1601
|
// ---------------------------------------------------------------------------
|
|
1785
1602
|
function mountAuthFallbackRoutes(app) {
|
|
@@ -1815,7 +1632,7 @@ function mountAuthFallbackRoutes(app) {
|
|
|
1815
1632
|
expiresAt: Date.now() + sessionMaxAge * 1000,
|
|
1816
1633
|
});
|
|
1817
1634
|
}
|
|
1818
|
-
return
|
|
1635
|
+
return authLoginResponse(event, result.token, email);
|
|
1819
1636
|
}
|
|
1820
1637
|
setResponseStatus(event, 403);
|
|
1821
1638
|
return {
|
|
@@ -1859,6 +1676,9 @@ function mountAuthFallbackRoutes(app) {
|
|
|
1859
1676
|
const cookie = getCookie(event, COOKIE_NAME);
|
|
1860
1677
|
if (cookie)
|
|
1861
1678
|
await removeSession(cookie);
|
|
1679
|
+
const bearerToken = getBearerSessionToken(event);
|
|
1680
|
+
if (bearerToken)
|
|
1681
|
+
await removeSession(bearerToken);
|
|
1862
1682
|
deleteCookie(event, COOKIE_NAME, { path: "/" });
|
|
1863
1683
|
try {
|
|
1864
1684
|
const auth = await getBetterAuth();
|
|
@@ -1871,20 +1691,6 @@ function mountAuthFallbackRoutes(app) {
|
|
|
1871
1691
|
await clearDesktopSso();
|
|
1872
1692
|
return { ok: true };
|
|
1873
1693
|
}));
|
|
1874
|
-
app.use("/_agent-native/auth/local-mode", defineEventHandler(async (event) => {
|
|
1875
|
-
if (getMethod(event) !== "POST") {
|
|
1876
|
-
setResponseStatus(event, 405);
|
|
1877
|
-
return { error: "Method not allowed" };
|
|
1878
|
-
}
|
|
1879
|
-
return localModeMarkerDisabled(event);
|
|
1880
|
-
}));
|
|
1881
|
-
app.use("/_agent-native/auth/exit-local-mode", defineEventHandler(async (event) => {
|
|
1882
|
-
if (getMethod(event) !== "POST") {
|
|
1883
|
-
setResponseStatus(event, 405);
|
|
1884
|
-
return { error: "Method not allowed" };
|
|
1885
|
-
}
|
|
1886
|
-
return exitLocalMode(event);
|
|
1887
|
-
}));
|
|
1888
1694
|
app.use("/_agent-native/auth/session", defineEventHandler(async (event) => {
|
|
1889
1695
|
if (!isReadMethod(event)) {
|
|
1890
1696
|
setResponseStatus(event, 405);
|
|
@@ -1893,10 +1699,6 @@ function mountAuthFallbackRoutes(app) {
|
|
|
1893
1699
|
const session = await getSession(event);
|
|
1894
1700
|
return session ?? { error: "Not authenticated" };
|
|
1895
1701
|
}));
|
|
1896
|
-
// Must be reachable from fallback mode too — otherwise a user who
|
|
1897
|
-
// upgrades-from-local on a server that couldn't init Better Auth gets a
|
|
1898
|
-
// 500 instead of a clear 401.
|
|
1899
|
-
app.use("/_agent-native/auth/migrate-local-data", migrateLocalDataHandler);
|
|
1900
1702
|
}
|
|
1901
1703
|
// ---------------------------------------------------------------------------
|
|
1902
1704
|
// autoMountAuth — the recommended entry point
|
|
@@ -1904,14 +1706,16 @@ function mountAuthFallbackRoutes(app) {
|
|
|
1904
1706
|
/**
|
|
1905
1707
|
* Automatically configure auth based on environment and configuration:
|
|
1906
1708
|
*
|
|
1907
|
-
* - **AUTH_MODE=local**: Auth bypassed. `getSession()` returns `{ email: "local@localhost" }`.
|
|
1908
|
-
* This is the explicit escape hatch for solo local development.
|
|
1909
1709
|
* - **BYOA (custom getSession)**: Template-provided auth callback handles everything.
|
|
1910
|
-
* - **AUTH_DISABLED=true**: Auth bypassed (for infrastructure-level auth like Cloudflare Access).
|
|
1911
1710
|
* - **ACCESS_TOKEN/ACCESS_TOKENS**: Simple token-based auth.
|
|
1912
1711
|
* - **Default**: Better Auth with email/password, social providers, organizations, and JWT.
|
|
1913
1712
|
* Users see an onboarding page to create an account on first visit.
|
|
1914
1713
|
*
|
|
1714
|
+
* Local development uses the same Better Auth flow as production. Email
|
|
1715
|
+
* verification is automatically skipped in dev/test environments and when
|
|
1716
|
+
* no email provider is configured (see `shouldSkipEmailVerification`), so a
|
|
1717
|
+
* fresh local clone only needs an email + password to get started.
|
|
1718
|
+
*
|
|
1915
1719
|
* Returns true if auth was mounted, false if skipped.
|
|
1916
1720
|
*/
|
|
1917
1721
|
export async function autoMountAuth(app, options = {}) {
|
|
@@ -1959,8 +1763,7 @@ export async function autoMountAuth(app, options = {}) {
|
|
|
1959
1763
|
_authGuardConfig = null;
|
|
1960
1764
|
_mountedApp = app;
|
|
1961
1765
|
if (!app) {
|
|
1962
|
-
if (
|
|
1963
|
-
authDisabledMode = false;
|
|
1766
|
+
if (isDevEnvironment()) {
|
|
1964
1767
|
customGetSession = null;
|
|
1965
1768
|
return false;
|
|
1966
1769
|
}
|
|
@@ -1968,27 +1771,12 @@ export async function autoMountAuth(app, options = {}) {
|
|
|
1968
1771
|
}
|
|
1969
1772
|
// Reset globals
|
|
1970
1773
|
customGetSession = null;
|
|
1971
|
-
authDisabledMode = false;
|
|
1972
1774
|
sessionMaxAge = options.maxAge ?? DEFAULT_MAX_AGE;
|
|
1973
1775
|
const publicPaths = options.publicPaths ?? [];
|
|
1776
|
+
mountAuthCorsMiddleware(app);
|
|
1974
1777
|
if (options.getSession) {
|
|
1975
1778
|
customGetSession = options.getSession;
|
|
1976
1779
|
}
|
|
1977
|
-
// AUTH_MODE=local — explicit local-only mode (escape hatch)
|
|
1978
|
-
if (await isLocalModeEnabled()) {
|
|
1979
|
-
try {
|
|
1980
|
-
// Mount the standard auth endpoints and guard even in local mode so the
|
|
1981
|
-
// app can switch back to real auth immediately after AUTH_MODE is
|
|
1982
|
-
// cleared, without waiting for a server restart/remount.
|
|
1983
|
-
await mountBetterAuthRoutes(app, options);
|
|
1984
|
-
}
|
|
1985
|
-
catch (err) {
|
|
1986
|
-
console.error("[agent-native] Failed to initialize Better Auth in local mode:", err);
|
|
1987
|
-
mountLocalModeRoutes(app);
|
|
1988
|
-
}
|
|
1989
|
-
console.log("[agent-native] Auth mode: local (upgrade path enabled).");
|
|
1990
|
-
return false;
|
|
1991
|
-
}
|
|
1992
1780
|
// BYOA — custom getSession provider
|
|
1993
1781
|
if (customGetSession) {
|
|
1994
1782
|
app.use("/_agent-native/auth/session", defineEventHandler(async (event) => {
|
|
@@ -2004,12 +1792,14 @@ export async function autoMountAuth(app, options = {}) {
|
|
|
2004
1792
|
const cookie = getCookie(event, COOKIE_NAME);
|
|
2005
1793
|
if (cookie)
|
|
2006
1794
|
await removeSession(cookie);
|
|
1795
|
+
const bearerToken = getBearerSessionToken(event);
|
|
1796
|
+
if (bearerToken)
|
|
1797
|
+
await removeSession(bearerToken);
|
|
2007
1798
|
deleteCookie(event, COOKIE_NAME, { path: "/" });
|
|
2008
1799
|
if (isElectronRequest(event))
|
|
2009
1800
|
await clearDesktopSso();
|
|
2010
1801
|
return { ok: true };
|
|
2011
1802
|
}));
|
|
2012
|
-
app.use("/_agent-native/auth/migrate-local-data", migrateLocalDataHandler);
|
|
2013
1803
|
const byoaLoginHtml = options.loginHtml ?? TOKEN_LOGIN_HTML;
|
|
2014
1804
|
_authGuardConfig = { loginHtml: byoaLoginHtml, publicPaths };
|
|
2015
1805
|
const guardFn = createAuthGuardFn();
|
|
@@ -2019,14 +1809,6 @@ export async function autoMountAuth(app, options = {}) {
|
|
|
2019
1809
|
console.log("[agent-native] Auth enabled — custom getSession provider.");
|
|
2020
1810
|
return true;
|
|
2021
1811
|
}
|
|
2022
|
-
// AUTH_DISABLED — skip auth (infrastructure-level auth)
|
|
2023
|
-
if (process.env.AUTH_DISABLED === "true") {
|
|
2024
|
-
authDisabledMode = true;
|
|
2025
|
-
console.warn("[agent-native] AUTH_DISABLED=true — running without auth. " +
|
|
2026
|
-
"Ensure this app is behind infrastructure-level auth (Cloudflare Access, VPN, etc.).");
|
|
2027
|
-
mountLocalModeRoutes(app);
|
|
2028
|
-
return false;
|
|
2029
|
-
}
|
|
2030
1812
|
// ACCESS_TOKEN-only mode
|
|
2031
1813
|
const tokens = getAccessTokens();
|
|
2032
1814
|
if (tokens.length > 0) {
|