@agent-native/core 0.148.0 → 0.149.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/corpus/README.md +1 -1
- package/corpus/templates/analytics/changelog/2026-08-10-wide-tables-in-analytics-chat-now-scroll-horizontally-within.md +6 -0
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +13 -53
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -70
- package/corpus/templates/calendar/app/lib/event-layout.ts +122 -0
- package/corpus/templates/calendar/app/routes/_app.availability.tsx +1 -1
- package/corpus/templates/calendar/changelog/2026-08-10-opening-availability-settings-from-the-agent-lands-on-the-av.md +6 -0
- package/corpus/templates/calendar/vitest.config.ts +6 -1
- package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +3 -1
- package/corpus/templates/clips/app/components/player/video-player.tsx +44 -4
- package/corpus/templates/clips/changelog/2026-08-10-fixed-transcription-failures-for-recordings-without-captured.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-10-mobile-clips-can-now-enter-fullscreen-on-iphone-safari.md +6 -0
- package/corpus/templates/content/actions/_property-utils.ts +11 -1
- package/corpus/templates/content/actions/view-screen.ts +62 -12
- package/corpus/templates/content/server/plugins/agent-chat.ts +2 -0
- package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +22 -0
- package/corpus/templates/design/AGENTS.md +3 -0
- package/corpus/templates/design/actions/create-design-from-template.ts +19 -0
- package/corpus/templates/design/actions/get-design-snapshot.ts +18 -0
- package/corpus/templates/design/actions/get-design-template.ts +178 -0
- package/corpus/templates/design/actions/list-designs.ts +3 -1
- package/corpus/templates/design/actions/view-screen.ts +31 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +225 -214
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
- package/corpus/templates/design/app/i18n-data.ts +78 -0
- package/corpus/templates/design/app/lib/design-authors.ts +64 -0
- package/corpus/templates/design/app/pages/Index.tsx +114 -4
- package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +1 -0
- package/corpus/templates/design/changelog/2026-08-07-alt-dragging-an-element-to-copy-it-now-keeps-the-copy-visibl.md +6 -0
- package/corpus/templates/design/changelog/2026-08-07-design-cards-now-show-who-created-each-design-with-a-filter-.md +6 -0
- package/corpus/templates/design/changelog/2026-08-07-drawing-a-shape-on-the-overview-canvas-now-keeps-the-shape-s.md +6 -0
- package/corpus/templates/design/changelog/2026-08-07-hold-option-alt-while-dragging-a-frame-on-the-overview-canva.md +6 -0
- package/corpus/templates/design/server/lib/design-template-data.ts +143 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/forms/AGENTS.md +2 -2
- package/corpus/templates/forms/actions/create-form.ts +1 -1
- package/corpus/templates/forms/actions/update-form.ts +5 -5
- package/corpus/templates/forms/changelog/2026-08-10-forms-now-keep-agent-applied-field-changes-visible-after-an-.md +6 -0
- package/corpus/templates/forms/changelog/2026-08-10-published-form-links-always-use-the-working-public-form-url.md +6 -0
- package/corpus/templates/forms/server/plugins/agent-chat.ts +2 -2
- package/corpus/templates/slides/AGENTS.md +4 -3
- package/corpus/templates/slides/actions/import-google-slides-reference.ts +56 -10
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +12 -74
- package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +5 -1
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +15 -4
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +116 -55
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1 -0
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +143 -18
- package/corpus/templates/slides/app/i18n/en-US.ts +6 -0
- package/corpus/templates/slides/app/lib/generation-state.ts +7 -1
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +32 -14
- package/corpus/templates/slides/app/pages/Index.tsx +110 -128
- package/corpus/templates/slides/changelog/2026-08-10-imported-reference-decks-now-show-clear-success-and-stay-sel.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-pasted-google-slides-links-import-after-reconnecting-google-.md +6 -0
- package/corpus/templates/slides/server/handlers/google-docs-auth.ts +4 -0
- package/corpus/templates/slides/server/lib/google-docs-oauth.ts +13 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
- package/dist/agent/engine/registry.d.ts +8 -2
- package/dist/agent/engine/registry.js +63 -29
- package/dist/agent/production-agent.js +7 -0
- package/dist/agent/run-manager.d.ts +5 -4
- package/dist/agent/run-manager.js +6 -5
- package/dist/cli/claude-code-participant.d.ts +2 -1
- package/dist/cli/claude-code-participant.js +19 -0
- package/dist/cli/code-agent-executor.js +223 -0
- package/dist/cli/create-workspace.js +3 -3
- package/dist/cli/create.d.ts +3 -2
- package/dist/cli/create.js +20 -24
- package/dist/client/AgentPanel.js +8 -4
- package/dist/client/AssistantChat.js +34 -1
- package/dist/client/agent-chat-adapter.d.ts +1 -0
- package/dist/client/agent-chat-adapter.js +10 -2
- package/dist/client/blocks/library/diagram.js +1 -1
- package/dist/client/blocks/library/sanitize-html.js +66 -2
- package/dist/client/blocks/library/wireframe-kit.js +69 -0
- package/dist/client/chat/markdown-renderer.d.ts +1 -0
- package/dist/client/chat/markdown-renderer.js +4 -0
- package/dist/client/chat/message-components.d.ts +1 -0
- package/dist/client/chat/message-components.js +9 -1
- package/dist/client/chat/tool-call-display.d.ts +8 -0
- package/dist/client/chat/tool-call-display.js +30 -21
- package/dist/client/chat-first/app-pane.js +1 -1
- package/dist/client/chat-first/apps-rail.d.ts +1 -1
- package/dist/client/chat-first/apps-rail.js +12 -12
- package/dist/client/chat-first/chat-history.d.ts +1 -1
- package/dist/client/chat-first/chat-history.js +2 -0
- package/dist/client/chat-first/copy.js +1 -0
- package/dist/client/chat-first/primary-nav.js +1 -1
- package/dist/client/chat-first/types.d.ts +1 -0
- package/dist/client/resources/McpConnectionSuggestion.js +2 -2
- package/dist/client/settings/SettingsTabsPage.d.ts +3 -1
- package/dist/client/settings/SettingsTabsPage.js +2 -2
- package/dist/client/sse-event-processor.d.ts +8 -0
- package/dist/client/sse-event-processor.js +13 -6
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +5 -7
- package/dist/deploy/build.js +19 -12
- package/dist/localization/default-messages.js +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/provider-api/actions/provider-api.d.ts +2 -2
- package/dist/scripts/call-agent.js +3 -3
- package/dist/scripts/describe-workspace-apps.js +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/agent-capabilities.js +0 -0
- package/dist/server/agent-chat/context-tools.js +1 -1
- package/dist/server/agent-chat/prompt-resources.js +1 -1
- package/dist/server/agent-chat-plugin.js +1 -1
- package/dist/server/auth.js +190 -42
- package/dist/server/better-auth-migrations.js +53 -0
- package/dist/server/http-response-telemetry.js +7 -1
- package/dist/server/onboarding-html.js +119 -33
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/password-policy.d.ts +2 -1
- package/dist/shared/password-policy.js +2 -1
- package/dist/styles/agent-conversation.css +20 -3
- package/dist/styles/agent-native.css +14 -2
- package/dist/styles/blocks.css +132 -0
- package/dist/templates/default/app/i18n/ar-SA.ts +1 -1
- package/dist/templates/default/app/i18n/de-DE.ts +1 -1
- package/dist/templates/default/app/i18n/en-US.ts +1 -1
- package/dist/templates/default/app/i18n/es-ES.ts +1 -1
- package/dist/templates/default/app/i18n/fr-FR.ts +1 -1
- package/dist/templates/default/app/i18n/hi-IN.ts +1 -1
- package/dist/templates/default/app/i18n/ja-JP.ts +1 -1
- package/dist/templates/default/app/i18n/ko-KR.ts +1 -1
- package/dist/templates/default/app/i18n/pt-BR.ts +1 -1
- package/dist/templates/default/app/i18n/zh-CN.ts +1 -1
- package/dist/templates/default/app/i18n/zh-TW.ts +1 -1
- package/dist/templates/default/pnpm-workspace.yaml +1 -0
- package/dist/templates/workspace-root/pnpm-workspace.yaml +2 -0
- package/docs/content/actions.mdx +4 -1
- package/docs/content/agent-surfaces.mdx +8 -6
- package/docs/content/automations.mdx +1 -1
- package/docs/content/cloneable-saas.mdx +6 -6
- package/docs/content/database.mdx +6 -1
- package/docs/content/deployment.mdx +10 -8
- package/docs/content/docs-components.mdx +22 -14
- package/docs/content/drop-in-agent.mdx +1 -1
- package/docs/content/environment-variables.mdx +4 -3
- package/docs/content/getting-started-actions.mdx +5 -4
- package/docs/content/getting-started-pages.mdx +7 -6
- package/docs/content/getting-started.mdx +4 -3
- package/docs/content/notifications.mdx +6 -6
- package/docs/content/progress.mdx +5 -5
- package/docs/content/routing.mdx +3 -1
- package/docs/content/server-database.mdx +13 -4
- package/docs/content/server-overview.mdx +9 -9
- package/docs/content/server-plugins.mdx +10 -4
- package/docs/content/server-routes.mdx +7 -2
- package/docs/content/tracking.mdx +4 -4
- package/docs/content/what-is-agent-native.mdx +4 -4
- package/package.json +2 -2
- package/src/templates/default/app/i18n/ar-SA.ts +1 -1
- package/src/templates/default/app/i18n/de-DE.ts +1 -1
- package/src/templates/default/app/i18n/en-US.ts +1 -1
- package/src/templates/default/app/i18n/es-ES.ts +1 -1
- package/src/templates/default/app/i18n/fr-FR.ts +1 -1
- package/src/templates/default/app/i18n/hi-IN.ts +1 -1
- package/src/templates/default/app/i18n/ja-JP.ts +1 -1
- package/src/templates/default/app/i18n/ko-KR.ts +1 -1
- package/src/templates/default/app/i18n/pt-BR.ts +1 -1
- package/src/templates/default/app/i18n/zh-CN.ts +1 -1
- package/src/templates/default/app/i18n/zh-TW.ts +1 -1
- package/src/templates/default/pnpm-workspace.yaml +1 -0
- package/src/templates/workspace-root/pnpm-workspace.yaml +2 -0
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -37,17 +37,17 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
|
|
|
37
37
|
ok?: undefined;
|
|
38
38
|
status?: undefined;
|
|
39
39
|
} | {
|
|
40
|
-
error?: undefined;
|
|
41
40
|
ok: boolean;
|
|
42
41
|
status: string;
|
|
42
|
+
error?: undefined;
|
|
43
43
|
} | {
|
|
44
44
|
ok?: undefined;
|
|
45
45
|
error: string;
|
|
46
46
|
removed?: undefined;
|
|
47
47
|
} | {
|
|
48
|
-
error?: undefined;
|
|
49
48
|
ok: boolean;
|
|
50
49
|
removed: boolean;
|
|
50
|
+
error?: undefined;
|
|
51
51
|
}>>;
|
|
52
52
|
/**
|
|
53
53
|
* POST /_agent-native/secrets/:key/test — validate an optional candidate value
|
|
@@ -58,13 +58,13 @@ export declare function createTestSecretHandler(): import("h3").EventHandlerWith
|
|
|
58
58
|
error: string;
|
|
59
59
|
note?: undefined;
|
|
60
60
|
} | {
|
|
61
|
-
error?: undefined;
|
|
62
61
|
ok: boolean;
|
|
63
62
|
note?: undefined;
|
|
64
|
-
} | {
|
|
65
63
|
error?: undefined;
|
|
64
|
+
} | {
|
|
66
65
|
ok: boolean;
|
|
67
66
|
note: string;
|
|
67
|
+
error?: undefined;
|
|
68
68
|
} | {
|
|
69
69
|
note?: undefined;
|
|
70
70
|
ok: boolean;
|
|
@@ -95,11 +95,11 @@ export interface AdHocSecretPayload {
|
|
|
95
95
|
export declare function createAdHocSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<AdHocSecretPayload[] | {
|
|
96
96
|
error: string;
|
|
97
97
|
} | {
|
|
98
|
-
error?: undefined;
|
|
99
98
|
ok: boolean;
|
|
100
99
|
key: string;
|
|
101
|
-
} | {
|
|
102
100
|
error?: undefined;
|
|
101
|
+
} | {
|
|
103
102
|
ok: boolean;
|
|
104
103
|
removed: boolean;
|
|
104
|
+
error?: undefined;
|
|
105
105
|
}>>;
|
|
Binary file
|
|
@@ -138,7 +138,7 @@ Prefer \`web-request\` for simple API calls and static pages. Use browser automa
|
|
|
138
138
|
|
|
139
139
|
The \`call-agent\` tool sends a message to a DIFFERENT, separately-deployed app's agent (A2A protocol). It is **not** for calling actions within the current app.
|
|
140
140
|
|
|
141
|
-
Use a natural-language \`message\` by default. The receiving app owns interpretation and runs with its own instructions, skills, connected sources, data dictionary, and tools. Do not choose its provider, schema, query, joins, or SQL for it. A direct \`action\` + \`input\` call is only for an exact bounded read whose complete contract is already known; it is never a workaround for unreliable delegation.
|
|
141
|
+
Use a natural-language \`message\` by default. The receiving app owns interpretation and runs with its own instructions, skills, connected sources, data dictionary, and tools. Do not choose its provider, schema, query, joins, or SQL for it. A direct \`action\` + \`input\` call is only for an exact bounded read whose complete contract is already known; it is never for a create, update, delete, send, save, publish, or other side effect, and it is never a workaround for unreliable delegation.
|
|
142
142
|
|
|
143
143
|
**NEVER use \`call-agent\` to:**
|
|
144
144
|
- Call your own app by name
|
|
@@ -787,7 +787,7 @@ export async function loadResourcesForPrompt(owner, compact = false, selfAppId,
|
|
|
787
787
|
// Nothing else in the prompt or the initial tool surface tells the agent
|
|
788
788
|
// which peer apps exist, so this block is not droppable: without it the
|
|
789
789
|
// agent reports cross-app work as impossible instead of delegating.
|
|
790
|
-
addSection(`<available-apps>\nWorkspace apps available over A2A/call-agent:\n${lines.join("\n")}\n\nWhen another app owns the work or data, use \`call-agent\` with the app id and a natural-language message. The receiving specialist owns source selection, schema interpretation, queries, joins, and use of its local tools. Direct action invocation is
|
|
790
|
+
addSection(`<available-apps>\nWorkspace apps available over A2A/call-agent:\n${lines.join("\n")}\n\nWhen another app owns the work or data, use \`call-agent\` with the app id and a natural-language message. The receiving specialist owns source selection, schema interpretation, queries, joins, and use of its local tools. Direct action invocation is only for an explicitly read-only bounded action with a fully known schema. Never put creates, updates, deletes, sends, saves, publishes, or other side effects in direct action mode; put those objectives in the natural-language message.\n\nThese one-liners are the only cross-app detail in this prompt. Before building a capability another app may already own, before telling the user what is or is not possible across apps, and whenever the user asks which app to use, call \`describe-workspace-apps\` - it reads each peer's live agent card for current purpose and optional capability details. Never hand-maintain a list of workspace apps in code or docs; it goes stale silently.\n</available-apps>`, "required");
|
|
791
791
|
}
|
|
792
792
|
}
|
|
793
793
|
catch {
|
|
@@ -1081,7 +1081,7 @@ export function createAgentChatPlugin(options) {
|
|
|
1081
1081
|
if (!entry) {
|
|
1082
1082
|
return {
|
|
1083
1083
|
status: "failed",
|
|
1084
|
-
output: "Unknown or unavailable read-only action",
|
|
1084
|
+
output: "Unknown or unavailable read-only action. Direct cross-app action mode only accepts explicitly exposed read-only operations; retry creates, updates, deletes, sends, saves, publishes, and other side effects with a natural-language message.",
|
|
1085
1085
|
};
|
|
1086
1086
|
}
|
|
1087
1087
|
const result = await executeAgentToolCall({
|
package/dist/server/auth.js
CHANGED
|
@@ -48,7 +48,7 @@ import { readBody } from "../server/h3-helpers.js";
|
|
|
48
48
|
import { putSetting } from "../settings/store.js";
|
|
49
49
|
import { resolveSsrCacheHeaders } from "../shared/cache-control.js";
|
|
50
50
|
import { extractOAuthStateAppId } from "../shared/oauth-state.js";
|
|
51
|
-
import { PASSWORD_MIN_LENGTH, PASSWORD_MIN_LENGTH_MESSAGE, } from "../shared/password-policy.js";
|
|
51
|
+
import { PASSWORD_MIN_LENGTH, PASSWORD_MAX_LENGTH, PASSWORD_MAX_LENGTH_MESSAGE, PASSWORD_MIN_LENGTH_MESSAGE, } from "../shared/password-policy.js";
|
|
52
52
|
import { SIGN_IN_CONTINUATION_PARAM, SIGN_IN_ENTRY_PATH, SIGN_IN_LEGACY_ENTRY_PATH, SIGN_IN_LEGACY_RETURN_PARAM, decodeContinuation, normalizeAppPath, signInJourney, } from "../shared/sign-in-journey.js";
|
|
53
53
|
import { AGENT_NATIVE_SOCIAL_IMAGE_ALT, AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT, AGENT_NATIVE_SOCIAL_IMAGE_PATH, AGENT_NATIVE_SOCIAL_IMAGE_TYPE, AGENT_NATIVE_SOCIAL_IMAGE_WIDTH, withAgentNativeSocialImageCacheBuster, } from "../shared/social-meta.js";
|
|
54
54
|
import { normalizeWorkspaceAppAudience, workspaceAppAudienceFromEnv, workspaceAppRouteAccessFromEnv, } from "../shared/workspace-app-audience.js";
|
|
@@ -576,6 +576,18 @@ function verifyEmailRedirectHasError(location, requestUrl) {
|
|
|
576
576
|
return /[?&]error=/.test(location);
|
|
577
577
|
}
|
|
578
578
|
}
|
|
579
|
+
function sanitizeVerificationErrorRedirect(location, requestUrl) {
|
|
580
|
+
try {
|
|
581
|
+
const parsed = new URL(location, requestUrl);
|
|
582
|
+
parsed.searchParams.set("error", "verification_link_invalid");
|
|
583
|
+
return /^[a-z][a-z\d+.-]*:/i.test(location)
|
|
584
|
+
? parsed.toString()
|
|
585
|
+
: `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
586
|
+
}
|
|
587
|
+
catch {
|
|
588
|
+
return location;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
579
591
|
function appendVerifiedParamToLocation(location) {
|
|
580
592
|
const hashIndex = location.indexOf("#");
|
|
581
593
|
const beforeHash = hashIndex >= 0 ? location.slice(0, hashIndex) : location;
|
|
@@ -633,6 +645,41 @@ const EXPECTED_AUTH_FAILURE_PATTERNS = [
|
|
|
633
645
|
];
|
|
634
646
|
const VALID_AUTH_EMAIL_MESSAGE = "Enter a valid email address, like you@example.com.";
|
|
635
647
|
const AUTH_EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
648
|
+
const AUTH_CREDENTIALS_REQUIRED_MESSAGE = "Enter your email and password.";
|
|
649
|
+
const AUTH_EMAIL_NOT_VERIFIED_MESSAGE = "Your email isn't verified yet. Check your inbox for a verification link.";
|
|
650
|
+
const AUTH_ACCOUNT_EXISTS_MESSAGE = "An account with this email already exists. Sign in instead or reset your password.";
|
|
651
|
+
const AUTH_PASSWORD_REQUIRED_MESSAGE = "Enter a password.";
|
|
652
|
+
const AUTH_LOGIN_FALLBACK = "We couldn't sign you in right now. Please try again.";
|
|
653
|
+
const AUTH_MAGIC_LINK_FALLBACK = "We couldn't send a sign-in link right now. Please try again.";
|
|
654
|
+
const AUTH_SIGNUP_FALLBACK = "We couldn't create your account right now. Please try again.";
|
|
655
|
+
const AUTH_VERIFICATION_LINK_FALLBACK = "This verification link is invalid or expired. Request a new one.";
|
|
656
|
+
const AUTH_RESET_PASSWORD_FALLBACK = "We couldn't update your password. The link may have expired; request a new one.";
|
|
657
|
+
const AUTH_RESET_EMAIL_FALLBACK = "We couldn't send a password reset email. Check your email and try again.";
|
|
658
|
+
const AUTH_GENERIC_FALLBACK = "We couldn't complete that request right now. Please try again.";
|
|
659
|
+
const AUTH_GOOGLE_FALLBACK = "We couldn't sign you in with Google right now. Please try again.";
|
|
660
|
+
const AUTH_GOOGLE_CANCELLED = "Google sign-in was cancelled. Try again when you're ready.";
|
|
661
|
+
const AUTH_GOOGLE_START_FALLBACK = "We couldn't start Google sign-in. Please try again.";
|
|
662
|
+
function isTechnicalAuthErrorMessage(message) {
|
|
663
|
+
return /failed query|\bselect\b.*\bfrom\b|\binsert\b.*\binto\b|\bupdate\b.*\bset\b|\bdelete\b.*\bfrom\b|\bsql\b|database|relation .* does not exist|column .* does not exist|syntax error|constraint|connection refused|econn|timeout/i.test(message);
|
|
664
|
+
}
|
|
665
|
+
function isAuthPasswordTooShortMessage(message) {
|
|
666
|
+
return /password.*(?:at least|minimum|min(?:imum)?|too short)/i.test(message);
|
|
667
|
+
}
|
|
668
|
+
function isAuthPasswordTooLongMessage(message) {
|
|
669
|
+
return /password.*(?:at most|maximum|max(?:imum)?|too long)/i.test(message);
|
|
670
|
+
}
|
|
671
|
+
function isAuthPasswordRequiredMessage(message) {
|
|
672
|
+
return /password.*(?:required|missing)/i.test(message);
|
|
673
|
+
}
|
|
674
|
+
function isAuthEmailNotVerifiedMessage(message) {
|
|
675
|
+
return /(?:email|account).*(?:not verified|unverified)|not verified/i.test(message);
|
|
676
|
+
}
|
|
677
|
+
function isAuthAccountExistsMessage(message) {
|
|
678
|
+
return /(?:already exists|already registered|already in use|user exists|user already|duplicate key|unique constraint|unique.*(?:email|constraint))/i.test(message);
|
|
679
|
+
}
|
|
680
|
+
function isAuthInvalidCredentialsMessage(message) {
|
|
681
|
+
return /(?:invalid|incorrect|wrong).*?(?:email|password|credentials)|(?:email|password|credentials).*?(?:invalid|incorrect|wrong)/i.test(message);
|
|
682
|
+
}
|
|
636
683
|
function normalizeAuthEmail(value) {
|
|
637
684
|
if (typeof value !== "string")
|
|
638
685
|
return null;
|
|
@@ -640,16 +687,102 @@ function normalizeAuthEmail(value) {
|
|
|
640
687
|
return AUTH_EMAIL_PATTERN.test(email) ? email : null;
|
|
641
688
|
}
|
|
642
689
|
function publicAuthError(error, fallback) {
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
690
|
+
const authError = error;
|
|
691
|
+
const message = typeof authError?.message === "string" ? authError.message : "";
|
|
692
|
+
const code = typeof authError?.code === "string" ? authError.code : "";
|
|
693
|
+
const details = `${code} ${message}`.trim();
|
|
694
|
+
if (isAuthEmailValidationMessage(details) ||
|
|
695
|
+
/invalid[_ -]?email/i.test(code)) {
|
|
696
|
+
return { message: VALID_AUTH_EMAIL_MESSAGE, statusCode: 400 };
|
|
697
|
+
}
|
|
698
|
+
if (isAuthPasswordTooShortMessage(details) ||
|
|
699
|
+
/password[_ -]?(too[_ -]?short|minimum)/i.test(code)) {
|
|
700
|
+
return { message: PASSWORD_MIN_LENGTH_MESSAGE, statusCode: 400 };
|
|
701
|
+
}
|
|
702
|
+
if (isAuthPasswordTooLongMessage(details) ||
|
|
703
|
+
/password[_ -]?(too[_ -]?long|maximum)/i.test(code)) {
|
|
704
|
+
return { message: PASSWORD_MAX_LENGTH_MESSAGE, statusCode: 400 };
|
|
705
|
+
}
|
|
706
|
+
if (isAuthPasswordRequiredMessage(details)) {
|
|
707
|
+
return { message: AUTH_PASSWORD_REQUIRED_MESSAGE, statusCode: 400 };
|
|
708
|
+
}
|
|
709
|
+
if (isAuthEmailNotVerifiedMessage(details)) {
|
|
710
|
+
return { message: AUTH_EMAIL_NOT_VERIFIED_MESSAGE, statusCode: 403 };
|
|
711
|
+
}
|
|
712
|
+
if (isAuthAccountExistsMessage(details) ||
|
|
713
|
+
/user[_ -]?already[_ -]?exists/i.test(code)) {
|
|
714
|
+
return { message: AUTH_ACCOUNT_EXISTS_MESSAGE, statusCode: 409 };
|
|
715
|
+
}
|
|
716
|
+
if (isAuthInvalidCredentialsMessage(details)) {
|
|
717
|
+
return { message: "The email or password is incorrect.", statusCode: 401 };
|
|
718
|
+
}
|
|
719
|
+
// Better Auth adapters can include the underlying SQL statement in an
|
|
720
|
+
// Error.message. That detail is useful in server logs, but it is neither a
|
|
721
|
+
// recovery path nor a safe public response. Map it to the route-specific
|
|
722
|
+
// fallback and keep the diagnostic in captureAuthError() above. Account
|
|
723
|
+
// conflicts are classified above so a unique constraint error stays useful.
|
|
724
|
+
if (isTechnicalAuthErrorMessage(details)) {
|
|
725
|
+
return { message: fallback, statusCode: 500 };
|
|
650
726
|
}
|
|
651
727
|
return { message: fallback };
|
|
652
728
|
}
|
|
729
|
+
function publicAuthErrorFromPayload(payload, fallback) {
|
|
730
|
+
const payloadError = typeof payload.error === "string" ? payload.error : undefined;
|
|
731
|
+
const code = typeof payload.code === "string"
|
|
732
|
+
? payload.code
|
|
733
|
+
: typeof payload.errorCode === "string"
|
|
734
|
+
? payload.errorCode
|
|
735
|
+
: payloadError && /^[A-Z0-9_ -]+$/.test(payloadError)
|
|
736
|
+
? payloadError
|
|
737
|
+
: undefined;
|
|
738
|
+
const message = typeof payload.message === "string"
|
|
739
|
+
? payload.message
|
|
740
|
+
: payloadError && code !== payloadError
|
|
741
|
+
? payloadError
|
|
742
|
+
: undefined;
|
|
743
|
+
return publicAuthError({ code, message }, fallback);
|
|
744
|
+
}
|
|
745
|
+
function betterAuthErrorFallback(path) {
|
|
746
|
+
if (path.includes("reset-password"))
|
|
747
|
+
return AUTH_RESET_PASSWORD_FALLBACK;
|
|
748
|
+
if (path.includes("request-password-reset"))
|
|
749
|
+
return AUTH_RESET_EMAIL_FALLBACK;
|
|
750
|
+
if (path.includes("verify-email"))
|
|
751
|
+
return AUTH_VERIFICATION_LINK_FALLBACK;
|
|
752
|
+
if (path.includes("send-verification-email")) {
|
|
753
|
+
return "We couldn't resend the verification email. Please try again.";
|
|
754
|
+
}
|
|
755
|
+
if (path.includes("sign-in/magic-link"))
|
|
756
|
+
return AUTH_MAGIC_LINK_FALLBACK;
|
|
757
|
+
if (path.includes("sign-in/email")) {
|
|
758
|
+
return "The email or password is incorrect.";
|
|
759
|
+
}
|
|
760
|
+
if (path.includes("sign-up/email"))
|
|
761
|
+
return AUTH_SIGNUP_FALLBACK;
|
|
762
|
+
return AUTH_GENERIC_FALLBACK;
|
|
763
|
+
}
|
|
764
|
+
async function sanitizeBetterAuthErrorResponse(response, fallback) {
|
|
765
|
+
if (response.status < 400)
|
|
766
|
+
return response;
|
|
767
|
+
const payload = await response
|
|
768
|
+
.clone()
|
|
769
|
+
.json()
|
|
770
|
+
.catch(() => undefined);
|
|
771
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
772
|
+
return response;
|
|
773
|
+
}
|
|
774
|
+
const authError = publicAuthErrorFromPayload(payload, fallback);
|
|
775
|
+
const headers = new Headers(response.headers);
|
|
776
|
+
headers.delete("content-length");
|
|
777
|
+
headers.set("content-type", "application/json");
|
|
778
|
+
return new Response(JSON.stringify({ error: authError.message, message: authError.message }), {
|
|
779
|
+
status: authError.message === fallback
|
|
780
|
+
? response.status
|
|
781
|
+
: (authError.statusCode ?? response.status),
|
|
782
|
+
statusText: response.statusText,
|
|
783
|
+
headers,
|
|
784
|
+
});
|
|
785
|
+
}
|
|
653
786
|
function isAuthEmailValidationMessage(message) {
|
|
654
787
|
// Credential failures (e.g. Better Auth's "Invalid email or password") mention
|
|
655
788
|
// "email" + "invalid" but are NOT email-format errors — don't rewrite them to
|
|
@@ -2301,7 +2434,7 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
2301
2434
|
const redirectUri = resolveOAuthRedirectUri(event);
|
|
2302
2435
|
if (redirectUri === null) {
|
|
2303
2436
|
setResponseStatus(event, 400);
|
|
2304
|
-
return { error:
|
|
2437
|
+
return { error: AUTH_GOOGLE_START_FALLBACK };
|
|
2305
2438
|
}
|
|
2306
2439
|
const q = getQuery(event);
|
|
2307
2440
|
const desktop = isElectronRequest(event) || q.desktop === "1" || q.desktop === "true";
|
|
@@ -2398,22 +2531,22 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
2398
2531
|
query.error_description
|
|
2399
2532
|
? query.error_description
|
|
2400
2533
|
: undefined;
|
|
2401
|
-
const msg =
|
|
2402
|
-
|
|
2403
|
-
|
|
2534
|
+
const msg = providerError === "access_denied"
|
|
2535
|
+
? AUTH_GOOGLE_CANCELLED
|
|
2536
|
+
: AUTH_GOOGLE_FALLBACK;
|
|
2404
2537
|
if (flowId) {
|
|
2405
2538
|
setDesktopExchangeError(flowId, {
|
|
2406
|
-
message:
|
|
2539
|
+
message: msg,
|
|
2407
2540
|
code: providerError || "missing_authorization_code",
|
|
2408
2541
|
});
|
|
2409
2542
|
}
|
|
2410
2543
|
logGoogleOAuthDebug(event, "callback-error", {
|
|
2411
2544
|
flowId,
|
|
2412
2545
|
desktop,
|
|
2413
|
-
message: msg,
|
|
2546
|
+
message: providerDescription || providerError || msg,
|
|
2414
2547
|
code: providerError,
|
|
2415
2548
|
});
|
|
2416
|
-
return oauthErrorPage(
|
|
2549
|
+
return oauthErrorPage(msg);
|
|
2417
2550
|
}
|
|
2418
2551
|
// Defence in depth: the state is HMAC-signed, but if the signing
|
|
2419
2552
|
// key ever leaked an attacker could mint state with their own
|
|
@@ -2421,7 +2554,7 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
2421
2554
|
// auth-url time so the token exchange is always sent to a URI we
|
|
2422
2555
|
// own.
|
|
2423
2556
|
if (!isAllowedOAuthRedirectUri(redirectUri, event)) {
|
|
2424
|
-
const msg =
|
|
2557
|
+
const msg = AUTH_GOOGLE_START_FALLBACK;
|
|
2425
2558
|
if (flowId) {
|
|
2426
2559
|
setDesktopExchangeError(flowId, {
|
|
2427
2560
|
message: msg,
|
|
@@ -2433,7 +2566,7 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
2433
2566
|
desktop,
|
|
2434
2567
|
message: msg,
|
|
2435
2568
|
});
|
|
2436
|
-
return oauthErrorPage(
|
|
2569
|
+
return oauthErrorPage(msg);
|
|
2437
2570
|
}
|
|
2438
2571
|
const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
|
|
2439
2572
|
method: "POST",
|
|
@@ -2532,7 +2665,7 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
2532
2665
|
const msg = error.message || "Unknown error";
|
|
2533
2666
|
if (callbackFlowId) {
|
|
2534
2667
|
setDesktopExchangeError(callbackFlowId, {
|
|
2535
|
-
message:
|
|
2668
|
+
message: AUTH_GOOGLE_FALLBACK,
|
|
2536
2669
|
code: "callback_error",
|
|
2537
2670
|
});
|
|
2538
2671
|
}
|
|
@@ -2541,7 +2674,7 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
2541
2674
|
desktop: callbackDesktop,
|
|
2542
2675
|
message: msg,
|
|
2543
2676
|
});
|
|
2544
|
-
return oauthErrorPage(
|
|
2677
|
+
return oauthErrorPage(AUTH_GOOGLE_FALLBACK);
|
|
2545
2678
|
}
|
|
2546
2679
|
}));
|
|
2547
2680
|
}
|
|
@@ -2732,10 +2865,16 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
2732
2865
|
// validation error.
|
|
2733
2866
|
}
|
|
2734
2867
|
}
|
|
2735
|
-
|
|
2868
|
+
let response = await auth.handler(requestForAuth);
|
|
2736
2869
|
const isResponse = response != null &&
|
|
2737
2870
|
typeof response.status === "number" &&
|
|
2738
2871
|
typeof response.headers?.get === "function";
|
|
2872
|
+
if (isResponse && response.status >= 400) {
|
|
2873
|
+
// The direct Better Auth surface is also reachable from the browser,
|
|
2874
|
+
// so wrapper-route sanitization alone is not enough to keep adapter
|
|
2875
|
+
// SQL and provider internals out of the auth UI.
|
|
2876
|
+
response = await sanitizeBetterAuthErrorResponse(response, betterAuthErrorFallback(reqPath));
|
|
2877
|
+
}
|
|
2739
2878
|
// After email verification, add ?verified=1 to the redirect so the
|
|
2740
2879
|
// login page can show "Email verified!". MUTATE the response in
|
|
2741
2880
|
// place — `new Response(null, { headers: new Headers(response.headers) })`
|
|
@@ -2749,9 +2888,10 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
2749
2888
|
response.status >= 300 &&
|
|
2750
2889
|
response.status < 400) {
|
|
2751
2890
|
const loc = response.headers.get("location");
|
|
2752
|
-
if (loc &&
|
|
2753
|
-
|
|
2754
|
-
|
|
2891
|
+
if (loc && verifyEmailRedirectHasError(loc, authRequest.url)) {
|
|
2892
|
+
response.headers.set("location", sanitizeVerificationErrorRedirect(loc, authRequest.url));
|
|
2893
|
+
}
|
|
2894
|
+
else if (loc && !/[?&]verified=/.test(loc)) {
|
|
2755
2895
|
await ensureEmailVerifiedForRedirect(authRequest, response);
|
|
2756
2896
|
response.headers.set("location", appendVerifiedParamToLocation(loc));
|
|
2757
2897
|
}
|
|
@@ -2860,7 +3000,7 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
2860
3000
|
const password = body?.password;
|
|
2861
3001
|
if (!rawEmail.trim() || !password) {
|
|
2862
3002
|
setResponseStatus(event, 400);
|
|
2863
|
-
return { error:
|
|
3003
|
+
return { error: AUTH_CREDENTIALS_REQUIRED_MESSAGE };
|
|
2864
3004
|
}
|
|
2865
3005
|
if (!email) {
|
|
2866
3006
|
setResponseStatus(event, 400);
|
|
@@ -2890,15 +3030,13 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
2890
3030
|
// email isn't verified yet. Don't return { ok: true } without a
|
|
2891
3031
|
// session or the frontend will reload into a dead end.
|
|
2892
3032
|
setResponseStatus(event, 403);
|
|
2893
|
-
return {
|
|
2894
|
-
error: "Email not verified. Check your inbox for a verification link.",
|
|
2895
|
-
};
|
|
3033
|
+
return { error: AUTH_EMAIL_NOT_VERIFIED_MESSAGE };
|
|
2896
3034
|
}
|
|
2897
3035
|
catch (e) {
|
|
2898
3036
|
if (!isExpectedAuthFailure(e)) {
|
|
2899
3037
|
captureAuthError(e, { route: "login", email });
|
|
2900
3038
|
}
|
|
2901
|
-
const authError = publicAuthError(e,
|
|
3039
|
+
const authError = publicAuthError(e, AUTH_LOGIN_FALLBACK);
|
|
2902
3040
|
setResponseStatus(event, authError.statusCode ?? 401);
|
|
2903
3041
|
return { error: authError.message };
|
|
2904
3042
|
}
|
|
@@ -2938,7 +3076,7 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
2938
3076
|
if (!isExpectedAuthFailure(e)) {
|
|
2939
3077
|
captureAuthError(e, { route: "magic-link", email });
|
|
2940
3078
|
}
|
|
2941
|
-
const authError = publicAuthError(e,
|
|
3079
|
+
const authError = publicAuthError(e, AUTH_MAGIC_LINK_FALLBACK);
|
|
2942
3080
|
setResponseStatus(event, authError.statusCode ?? 400);
|
|
2943
3081
|
return { error: authError.message };
|
|
2944
3082
|
}
|
|
@@ -2960,12 +3098,18 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
2960
3098
|
setResponseStatus(event, 400);
|
|
2961
3099
|
return { error: VALID_AUTH_EMAIL_MESSAGE };
|
|
2962
3100
|
}
|
|
2963
|
-
if (!password ||
|
|
2964
|
-
|
|
2965
|
-
|
|
3101
|
+
if (!password || typeof password !== "string") {
|
|
3102
|
+
setResponseStatus(event, 400);
|
|
3103
|
+
return { error: AUTH_PASSWORD_REQUIRED_MESSAGE };
|
|
3104
|
+
}
|
|
3105
|
+
if (password.length < PASSWORD_MIN_LENGTH) {
|
|
2966
3106
|
setResponseStatus(event, 400);
|
|
2967
3107
|
return { error: PASSWORD_MIN_LENGTH_MESSAGE };
|
|
2968
3108
|
}
|
|
3109
|
+
if (password.length > PASSWORD_MAX_LENGTH) {
|
|
3110
|
+
setResponseStatus(event, 400);
|
|
3111
|
+
return { error: PASSWORD_MAX_LENGTH_MESSAGE };
|
|
3112
|
+
}
|
|
2969
3113
|
if (await isGoogleSignInRequiredForEmail(email)) {
|
|
2970
3114
|
setResponseStatus(event, 403);
|
|
2971
3115
|
return { error: GOOGLE_AUTH_REQUIRED_MESSAGE };
|
|
@@ -2982,7 +3126,7 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
2982
3126
|
if (!isExpectedAuthFailure(e)) {
|
|
2983
3127
|
captureAuthError(e, { route: "signup", email });
|
|
2984
3128
|
}
|
|
2985
|
-
const authError = publicAuthError(e,
|
|
3129
|
+
const authError = publicAuthError(e, AUTH_SIGNUP_FALLBACK);
|
|
2986
3130
|
setResponseStatus(event, authError.statusCode ?? 409);
|
|
2987
3131
|
return { error: authError.message };
|
|
2988
3132
|
}
|
|
@@ -3157,7 +3301,7 @@ function mountAuthFallbackRoutes(app) {
|
|
|
3157
3301
|
const password = body?.password;
|
|
3158
3302
|
if (!rawEmail.trim() || !password) {
|
|
3159
3303
|
setResponseStatus(event, 400);
|
|
3160
|
-
return { error:
|
|
3304
|
+
return { error: AUTH_CREDENTIALS_REQUIRED_MESSAGE };
|
|
3161
3305
|
}
|
|
3162
3306
|
if (!email) {
|
|
3163
3307
|
setResponseStatus(event, 400);
|
|
@@ -3185,15 +3329,13 @@ function mountAuthFallbackRoutes(app) {
|
|
|
3185
3329
|
return authLoginResponse(event, result.token, email);
|
|
3186
3330
|
}
|
|
3187
3331
|
setResponseStatus(event, 403);
|
|
3188
|
-
return {
|
|
3189
|
-
error: "Email not verified. Check your inbox for a verification link.",
|
|
3190
|
-
};
|
|
3332
|
+
return { error: AUTH_EMAIL_NOT_VERIFIED_MESSAGE };
|
|
3191
3333
|
}
|
|
3192
3334
|
catch (e) {
|
|
3193
3335
|
if (!isExpectedAuthFailure(e)) {
|
|
3194
3336
|
captureAuthError(e, { route: "login", email });
|
|
3195
3337
|
}
|
|
3196
|
-
const authError = publicAuthError(e,
|
|
3338
|
+
const authError = publicAuthError(e, AUTH_LOGIN_FALLBACK);
|
|
3197
3339
|
setResponseStatus(event, authError.statusCode ?? 401);
|
|
3198
3340
|
return { error: authError.message };
|
|
3199
3341
|
}
|
|
@@ -3211,12 +3353,18 @@ function mountAuthFallbackRoutes(app) {
|
|
|
3211
3353
|
setResponseStatus(event, 400);
|
|
3212
3354
|
return { error: VALID_AUTH_EMAIL_MESSAGE };
|
|
3213
3355
|
}
|
|
3214
|
-
if (!password ||
|
|
3215
|
-
|
|
3216
|
-
|
|
3356
|
+
if (!password || typeof password !== "string") {
|
|
3357
|
+
setResponseStatus(event, 400);
|
|
3358
|
+
return { error: AUTH_PASSWORD_REQUIRED_MESSAGE };
|
|
3359
|
+
}
|
|
3360
|
+
if (password.length < PASSWORD_MIN_LENGTH) {
|
|
3217
3361
|
setResponseStatus(event, 400);
|
|
3218
3362
|
return { error: PASSWORD_MIN_LENGTH_MESSAGE };
|
|
3219
3363
|
}
|
|
3364
|
+
if (password.length > PASSWORD_MAX_LENGTH) {
|
|
3365
|
+
setResponseStatus(event, 400);
|
|
3366
|
+
return { error: PASSWORD_MAX_LENGTH_MESSAGE };
|
|
3367
|
+
}
|
|
3220
3368
|
if (await isGoogleSignInRequiredForEmail(email)) {
|
|
3221
3369
|
setResponseStatus(event, 403);
|
|
3222
3370
|
return { error: GOOGLE_AUTH_REQUIRED_MESSAGE };
|
|
@@ -3234,7 +3382,7 @@ function mountAuthFallbackRoutes(app) {
|
|
|
3234
3382
|
if (!isExpectedAuthFailure(e)) {
|
|
3235
3383
|
captureAuthError(e, { route: "signup", email });
|
|
3236
3384
|
}
|
|
3237
|
-
const authError = publicAuthError(e,
|
|
3385
|
+
const authError = publicAuthError(e, AUTH_SIGNUP_FALLBACK);
|
|
3238
3386
|
setResponseStatus(event, authError.statusCode ?? 409);
|
|
3239
3387
|
return { error: authError.message };
|
|
3240
3388
|
}
|
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
import { runMigrations } from "../db/migrations.js";
|
|
2
|
+
async function assertBetterAuthUserIdentityColumns() {
|
|
3
|
+
const { getCloudflareD1Binding, getDbExec, getDialect, isPostgres } = await import("../db/client.js");
|
|
4
|
+
const dialect = getDialect();
|
|
5
|
+
let columnNames;
|
|
6
|
+
if (dialect === "d1") {
|
|
7
|
+
const d1 = getCloudflareD1Binding();
|
|
8
|
+
if (!d1) {
|
|
9
|
+
throw new Error('Cannot inspect the Better Auth "user" table because the D1 binding is unavailable.');
|
|
10
|
+
}
|
|
11
|
+
const result = await d1.prepare("PRAGMA table_info(user)").all();
|
|
12
|
+
columnNames = result.results.map((column) => {
|
|
13
|
+
const record = column;
|
|
14
|
+
return typeof record.name === "string" ? record.name : "";
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
else if (isPostgres()) {
|
|
18
|
+
const { rows } = await getDbExec().execute(`SELECT column_name FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'user' AND column_name IN ('id', 'email')`);
|
|
19
|
+
columnNames = rows.map((row) => String(row.column_name ?? row[0] ?? ""));
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
const { rows } = await getDbExec().execute("PRAGMA table_info(user)");
|
|
23
|
+
columnNames = rows.map((row) => String(row.name ?? row[1] ?? ""));
|
|
24
|
+
}
|
|
25
|
+
const missing = ["id", "email"].filter((column) => !columnNames.includes(column));
|
|
26
|
+
if (missing.length > 0) {
|
|
27
|
+
throw new Error(`Cannot repair the Better Auth "user" table because required identity column(s) are missing: ${missing.join(", ")}. Restore the existing user schema before deploying this migration.`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
2
30
|
/**
|
|
3
31
|
* Better Auth's framework-owned schema. This is deliberately a release
|
|
4
32
|
* migration, not a fallback inside `getBetterAuth()`: request functions must
|
|
@@ -171,6 +199,31 @@ export const BETTER_AUTH_MIGRATIONS = [
|
|
|
171
199
|
`,
|
|
172
200
|
},
|
|
173
201
|
},
|
|
202
|
+
{
|
|
203
|
+
version: 2,
|
|
204
|
+
name: "better-auth-repair-user-columns",
|
|
205
|
+
sql: {
|
|
206
|
+
// `CREATE TABLE IF NOT EXISTS` does not reconcile an older table that
|
|
207
|
+
// already has the Better Auth name but is missing newer columns. Keep
|
|
208
|
+
// this repair additive so existing user rows and legacy auth data stay
|
|
209
|
+
// intact while the adapter gets the columns it selects on signup.
|
|
210
|
+
postgres: `
|
|
211
|
+
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "name" TEXT NOT NULL DEFAULT '';
|
|
212
|
+
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "email_verified" BOOLEAN NOT NULL DEFAULT FALSE;
|
|
213
|
+
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "image" TEXT;
|
|
214
|
+
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
|
215
|
+
ALTER TABLE "user" ADD COLUMN IF NOT EXISTS "updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
216
|
+
`,
|
|
217
|
+
sqlite: `
|
|
218
|
+
ALTER TABLE user ADD COLUMN IF NOT EXISTS name TEXT NOT NULL DEFAULT '';
|
|
219
|
+
ALTER TABLE user ADD COLUMN IF NOT EXISTS email_verified INTEGER NOT NULL DEFAULT 0;
|
|
220
|
+
ALTER TABLE user ADD COLUMN IF NOT EXISTS image TEXT;
|
|
221
|
+
ALTER TABLE user ADD COLUMN IF NOT EXISTS created_at INTEGER NOT NULL DEFAULT 0;
|
|
222
|
+
ALTER TABLE user ADD COLUMN IF NOT EXISTS updated_at INTEGER NOT NULL DEFAULT 0
|
|
223
|
+
`,
|
|
224
|
+
},
|
|
225
|
+
run: assertBetterAuthUserIdentityColumns,
|
|
226
|
+
},
|
|
174
227
|
];
|
|
175
228
|
export async function runBetterAuthMigrations(nitroApp) {
|
|
176
229
|
await runMigrations(BETTER_AUTH_MIGRATIONS, {
|
|
@@ -7,6 +7,8 @@ import { track } from "../tracking/index.js";
|
|
|
7
7
|
import { getAppName } from "./app-name.js";
|
|
8
8
|
const TELEMETRY_EVENT_NAME = "http.response";
|
|
9
9
|
const REQUEST_ID_HEADER = "x-agent-native-request-id";
|
|
10
|
+
// Provider delivery posts back to these collectors. Recording the collector
|
|
11
|
+
// response would feed another `http.response` event into the same collector.
|
|
10
12
|
const TRACKING_INGEST_PATHS = new Set([
|
|
11
13
|
"/track",
|
|
12
14
|
"/api/analytics/track",
|
|
@@ -49,6 +51,10 @@ function sampleRate() {
|
|
|
49
51
|
function shouldDisableTelemetry() {
|
|
50
52
|
return boolEnv("AGENT_NATIVE_HTTP_TELEMETRY_DISABLED");
|
|
51
53
|
}
|
|
54
|
+
function isTrackingIngestPath(pathname) {
|
|
55
|
+
const normalized = pathname.replace(/\/+$/, "") || "/";
|
|
56
|
+
return TRACKING_INGEST_PATHS.has(normalized);
|
|
57
|
+
}
|
|
52
58
|
function requestPath(event) {
|
|
53
59
|
const raw = event.url?.pathname ??
|
|
54
60
|
String(event.node?.req?.url ?? event.path ?? "/").split("?")[0] ??
|
|
@@ -114,7 +120,7 @@ function organizationForHost(host) {
|
|
|
114
120
|
function shouldTrack(pathname, statusCode, state) {
|
|
115
121
|
if (shouldDisableTelemetry())
|
|
116
122
|
return false;
|
|
117
|
-
if (
|
|
123
|
+
if (isTrackingIngestPath(pathname))
|
|
118
124
|
return false;
|
|
119
125
|
if (pathname.startsWith("/api/analytics/replay"))
|
|
120
126
|
return false;
|