@agent-native/core 0.66.1 → 0.66.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"app-url.js","sourceRoot":"","sources":["../../src/server/app-url.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Resolve the canonical URL of this app — used in transactional emails,\n * invite links, and anywhere we need an absolute URL that remains valid\n * outside the current request context.\n *\n * Resolution order:\n * 1. Explicit public URL env vars (`APP_URL`, workspace OAuth origin,\n * `BETTER_AUTH_URL`) — operator overrides\n * 2. Incoming request's origin (when an H3Event is available)\n * 3. First-party template `prodUrl` from the registry (matched by\n * package.json name) — lets deployed first-party apps (mail,\n * calendar, analytics, …) use e.g. `analytics.agent-native.com`\n * instead of their Netlify preview hostname.\n * 4. Platform-injected URL (Netlify `URL`, Vercel `VERCEL_URL`) —\n * automatically set by the hosting platform, so user-deployed apps\n * get a real hostname in emails without needing to set `APP_URL`.\n * 5. Public `WORKSPACE_GATEWAY_URL` — multi-app workspace gateway\n * 6. Local `WORKSPACE_GATEWAY_URL` — local multi-app workspace gateway\n * 7. `http://localhost:3000`\n *\n * Older versions preferred `WORKSPACE_GATEWAY_URL` before platform URLs.\n * That is fine for local development, but in hosted Builder Desktop sessions\n * the gateway can be `127.0.0.1`, which must not become Better Auth's\n * production base URL.\n */\nimport { getRequestURL, type H3Event } from \"h3\";\nimport path from \"node:path\";\nimport fs from \"node:fs\";\nimport { TEMPLATES } from \"../cli/templates-meta.js\";\nimport { isLocalDatabase } from \"../db/client.js\";\n\nlet cachedPkgName: string | undefined | null = null;\n\n/**\n * Read the app's package name, validated against the first-party template\n * registry. On serverless runtimes (Netlify Functions, Cloudflare Workers),\n * `process.cwd()` may point at a bundler-generated package.json with a\n * bogus name (e.g. Nitro's \"traced-node-modules\"). Only trust the name if\n * it matches a known template.\n */\nfunction readPackageName(): string | undefined {\n if (cachedPkgName !== null) return cachedPkgName ?? undefined;\n try {\n const pkgPath = path.join(process.cwd(), \"package.json\");\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf8\"));\n const name = typeof pkg?.name === \"string\" ? pkg.name : undefined;\n const isKnown = name && TEMPLATES.some((t) => t.name === name);\n cachedPkgName = isKnown ? name : undefined;\n } catch {\n cachedPkgName = undefined;\n }\n return cachedPkgName ?? undefined;\n}\n\n/** Strip trailing slashes for consistent URL concatenation. */\nfunction stripTrailingSlash(u: string): string {\n return u.replace(/\\/+$/, \"\");\n}\n\nfunction firstConfiguredUrl(keys: readonly string[]): string | undefined {\n for (const key of keys) {\n const value = process.env[key];\n if (value) return stripTrailingSlash(value);\n }\n return undefined;\n}\n\nfunction isLoopbackUrl(value: string | undefined): boolean {\n if (!value) return false;\n try {\n const hostname = new URL(value).hostname.toLowerCase();\n return (\n hostname === \"localhost\" || hostname === \"127.0.0.1\" || hostname === \"::1\"\n );\n } catch {\n return false;\n }\n}\n\nfunction workspaceGatewayUrl(options: {\n allowLoopback: boolean;\n}): string | undefined {\n const url = firstConfiguredUrl([\n \"WORKSPACE_GATEWAY_URL\",\n \"VITE_WORKSPACE_GATEWAY_URL\",\n ]);\n if (!url) return undefined;\n if (!options.allowLoopback && isLoopbackUrl(url)) return undefined;\n return url;\n}\n\n/**\n * Look up the first-party template `prodUrl` for the current app based on\n * its `package.json` name. Returns undefined if the app isn't a known\n * first-party template or the template has no `prodUrl`.\n */\nexport function getFirstPartyProdUrl(): string | undefined {\n const name = readPackageName();\n if (!name) return undefined;\n const t = TEMPLATES.find((t) => t.name === name);\n return t?.prodUrl;\n}\n\nexport function getAppProductionUrl(event?: H3Event): string {\n const envUrl = firstConfiguredUrl([\n \"APP_URL\",\n \"WORKSPACE_OAUTH_ORIGIN\",\n \"VITE_WORKSPACE_OAUTH_ORIGIN\",\n \"BETTER_AUTH_URL\",\n \"VITE_BETTER_AUTH_URL\",\n ]);\n if (envUrl) return envUrl;\n\n // Prefer the incoming request's origin when we have one — for local dev\n // this is `http://localhost:3000`, which keeps Better Auth from setting\n // `Secure` cookies on plain-HTTP dev servers.\n if (event) {\n try {\n const url = getRequestURL(event);\n return `${url.protocol}//${url.host}`;\n } catch {\n // fall through\n }\n }\n\n // Fall back to a first-party template's hard-coded prod URL when we're\n // running in production OR on a remote database (Neon/Postgres/Turso).\n // A remote DB means we're deployed even if NODE_ENV isn't explicitly\n // \"production\" (e.g. Netlify Functions). In local dev with SQLite, skip\n // this — the hard-coded URL breaks auth via Secure cookies on HTTP.\n if (process.env.NODE_ENV === \"production\" || !isLocalDatabase()) {\n const firstParty = getFirstPartyProdUrl();\n if (firstParty) return stripTrailingSlash(firstParty);\n\n // Netlify injects `URL` (main site URL, always https) and `DEPLOY_URL`\n // (deploy-specific URL). Prefer `URL` so emails always link to the\n // primary domain rather than a preview branch URL.\n const netlifyUrl = process.env.URL || process.env.DEPLOY_URL;\n if (netlifyUrl) return stripTrailingSlash(netlifyUrl);\n\n // Vercel injects `VERCEL_PROJECT_PRODUCTION_URL` (custom/primary domain,\n // no protocol) and `VERCEL_URL` (ephemeral deployment hostname). Prefer\n // the production URL so emails use the real domain, not *.vercel.app.\n const vercelUrl =\n process.env.VERCEL_PROJECT_PRODUCTION_URL || process.env.VERCEL_URL;\n if (vercelUrl) return `https://${stripTrailingSlash(vercelUrl)}`;\n\n const publicWorkspaceGateway = workspaceGatewayUrl({\n allowLoopback: false,\n });\n if (publicWorkspaceGateway) return publicWorkspaceGateway;\n }\n\n const localWorkspaceGateway = workspaceGatewayUrl({ allowLoopback: true });\n if (localWorkspaceGateway) return localWorkspaceGateway;\n\n return \"http://localhost:3000\";\n}\n"]}
1
+ {"version":3,"file":"app-url.js","sourceRoot":"","sources":["../../src/server/app-url.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Resolve the canonical URL of this app — used in transactional emails,\n * invite links, and anywhere we need an absolute URL that remains valid\n * outside the current request context.\n *\n * Resolution order:\n * 1. Explicit public URL env vars (`APP_URL`, workspace OAuth origin,\n * `BETTER_AUTH_URL`) — operator overrides\n * 2. Incoming request's origin (when an H3Event is available)\n * 3. First-party template `prodUrl` from the registry (matched by\n * package.json name) — lets deployed first-party apps (mail,\n * calendar, analytics, …) use e.g. `analytics.agent-native.com`\n * instead of their Netlify preview hostname.\n * 4. Platform-injected URL (Netlify `URL`, Vercel `VERCEL_URL`) —\n * automatically set by the hosting platform, so user-deployed apps\n * get a real hostname in emails without needing to set `APP_URL`.\n * 5. Public `WORKSPACE_GATEWAY_URL` — multi-app workspace gateway\n * 6. Local `WORKSPACE_GATEWAY_URL` — local multi-app workspace gateway\n * 7. `http://localhost:3000`\n *\n * Older versions preferred `WORKSPACE_GATEWAY_URL` before platform URLs.\n * That is fine for local development, but in hosted Builder Desktop sessions\n * the gateway can be `127.0.0.1`, which must not become Better Auth's\n * production base URL.\n */\nimport { getRequestURL, type H3Event } from \"h3\";\nimport path from \"node:path\";\nimport fs from \"node:fs\";\nimport { TEMPLATES } from \"../cli/templates-meta.js\";\nimport { isLocalDatabase } from \"../db/client.js\";\n\nlet cachedPkgName: string | undefined | null = null;\n\n/**\n * Read the app's package name, validated against the first-party template\n * registry. On serverless runtimes (Netlify Functions, Cloudflare Workers),\n * `process.cwd()` may point at a bundler-generated package.json with a\n * bogus name (e.g. Nitro's \"traced-node-modules\"). Only trust the name if\n * it matches a known template.\n */\nfunction readPackageName(): string | undefined {\n if (cachedPkgName !== null) return cachedPkgName ?? undefined;\n try {\n const pkgPath = path.join(process.cwd(), \"package.json\");\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf8\"));\n const name = typeof pkg?.name === \"string\" ? pkg.name : undefined;\n const isKnown = name && TEMPLATES.some((t) => t.name === name);\n cachedPkgName = isKnown ? name : undefined;\n } catch {\n cachedPkgName = undefined;\n }\n return cachedPkgName ?? undefined;\n}\n\n/** Strip trailing slashes for consistent URL concatenation. */\nfunction stripTrailingSlash(u: string): string {\n return u.replace(/\\/+$/, \"\");\n}\n\nfunction firstConfiguredUrl(keys: readonly string[]): string | undefined {\n for (const key of keys) {\n const value = process.env[key];\n if (value) return stripTrailingSlash(value);\n }\n return undefined;\n}\n\nfunction firstConfiguredPublicUrl(keys: readonly string[]): string | undefined {\n const allowLoopback = !isHostedRuntime();\n for (const key of keys) {\n const value = process.env[key];\n if (!value) continue;\n const url = stripTrailingSlash(value);\n if (!allowLoopback && isLoopbackUrl(url)) continue;\n return url;\n }\n return undefined;\n}\n\nfunction isLoopbackUrl(value: string | undefined): boolean {\n if (!value) return false;\n try {\n const hostname = new URL(value).hostname.toLowerCase();\n return (\n hostname === \"localhost\" || hostname === \"127.0.0.1\" || hostname === \"::1\"\n );\n } catch {\n return false;\n }\n}\n\nfunction isHostedRuntime(): boolean {\n return Boolean(\n process.env.NODE_ENV === \"production\" ||\n process.env.NETLIFY ||\n process.env.URL ||\n process.env.DEPLOY_URL ||\n process.env.VERCEL ||\n process.env.VERCEL_URL ||\n process.env.VERCEL_PROJECT_PRODUCTION_URL,\n );\n}\n\nfunction workspaceGatewayUrl(options: {\n allowLoopback: boolean;\n}): string | undefined {\n const url = firstConfiguredUrl([\n \"WORKSPACE_GATEWAY_URL\",\n \"VITE_WORKSPACE_GATEWAY_URL\",\n ]);\n if (!url) return undefined;\n if (!options.allowLoopback && isLoopbackUrl(url)) return undefined;\n return url;\n}\n\n/**\n * Look up the first-party template `prodUrl` for the current app based on\n * its `package.json` name. Returns undefined if the app isn't a known\n * first-party template or the template has no `prodUrl`.\n */\nexport function getFirstPartyProdUrl(): string | undefined {\n const name = readPackageName();\n if (!name) return undefined;\n const t = TEMPLATES.find((t) => t.name === name);\n return t?.prodUrl;\n}\n\nexport function getAppProductionUrl(event?: H3Event): string {\n const envUrl = firstConfiguredPublicUrl([\n \"APP_URL\",\n \"WORKSPACE_OAUTH_ORIGIN\",\n \"VITE_WORKSPACE_OAUTH_ORIGIN\",\n \"BETTER_AUTH_URL\",\n \"VITE_BETTER_AUTH_URL\",\n ]);\n if (envUrl) return envUrl;\n\n // Prefer the incoming request's origin when we have one — for local dev\n // this is `http://localhost:3000`, which keeps Better Auth from setting\n // `Secure` cookies on plain-HTTP dev servers.\n if (event) {\n try {\n const url = getRequestURL(event);\n return `${url.protocol}//${url.host}`;\n } catch {\n // fall through\n }\n }\n\n // Fall back to a first-party template's hard-coded prod URL when we're\n // running in production OR on a remote database (Neon/Postgres/Turso).\n // A remote DB means we're deployed even if NODE_ENV isn't explicitly\n // \"production\" (e.g. Netlify Functions). In local dev with SQLite, skip\n // this — the hard-coded URL breaks auth via Secure cookies on HTTP.\n if (process.env.NODE_ENV === \"production\" || !isLocalDatabase()) {\n const firstParty = getFirstPartyProdUrl();\n if (firstParty) return stripTrailingSlash(firstParty);\n\n // Netlify injects `URL` (main site URL, always https) and `DEPLOY_URL`\n // (deploy-specific URL). Prefer `URL` so emails always link to the\n // primary domain rather than a preview branch URL.\n const netlifyUrl = process.env.URL || process.env.DEPLOY_URL;\n if (netlifyUrl) return stripTrailingSlash(netlifyUrl);\n\n // Vercel injects `VERCEL_PROJECT_PRODUCTION_URL` (custom/primary domain,\n // no protocol) and `VERCEL_URL` (ephemeral deployment hostname). Prefer\n // the production URL so emails use the real domain, not *.vercel.app.\n const vercelUrl =\n process.env.VERCEL_PROJECT_PRODUCTION_URL || process.env.VERCEL_URL;\n if (vercelUrl) return `https://${stripTrailingSlash(vercelUrl)}`;\n\n const publicWorkspaceGateway = workspaceGatewayUrl({\n allowLoopback: false,\n });\n if (publicWorkspaceGateway) return publicWorkspaceGateway;\n }\n\n const localWorkspaceGateway = workspaceGatewayUrl({ allowLoopback: true });\n if (localWorkspaceGateway) return localWorkspaceGateway;\n\n return \"http://localhost:3000\";\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"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"}
@@ -542,12 +542,36 @@ function authLoginResponse(event, token, email) {
542
542
  */
543
543
  const EXPECTED_AUTH_FAILURE_PATTERNS = [
544
544
  /invalid\s+(email|password|credentials)/i,
545
+ /\[?body\.email\]?\s+invalid input/i,
545
546
  /password.*incorrect/i,
546
547
  /user\s+(not\s+found|already\s+exists)/i,
547
548
  /email\s+already/i,
548
549
  /already\s+(exists|registered|in\s+use)/i,
549
550
  /not\s+verified/i,
550
551
  ];
552
+ const VALID_AUTH_EMAIL_MESSAGE = "Enter a valid email address, like you@example.com.";
553
+ const AUTH_EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
554
+ function normalizeAuthEmail(value) {
555
+ if (typeof value !== "string")
556
+ return null;
557
+ const email = value.trim().toLowerCase();
558
+ return AUTH_EMAIL_PATTERN.test(email) ? email : null;
559
+ }
560
+ function publicAuthError(error, fallback) {
561
+ const message = error?.message;
562
+ if (typeof message === "string") {
563
+ if (isAuthEmailValidationMessage(message)) {
564
+ return { message: VALID_AUTH_EMAIL_MESSAGE, statusCode: 400 };
565
+ }
566
+ if (message.trim())
567
+ return { message };
568
+ }
569
+ return { message: fallback };
570
+ }
571
+ function isAuthEmailValidationMessage(message) {
572
+ return (/\bemail\b/i.test(message) &&
573
+ /(invalid|input|required|format)/i.test(message));
574
+ }
551
575
  export function isExpectedAuthFailure(error) {
552
576
  const msg = error?.message;
553
577
  if (typeof msg !== "string")
@@ -2394,12 +2418,17 @@ async function mountBetterAuthRoutes(app, options) {
2394
2418
  }
2395
2419
  const body = await readBody(event);
2396
2420
  // Email/password login via Better Auth
2397
- const email = body?.email?.trim?.()?.toLowerCase?.();
2421
+ const rawEmail = typeof body?.email === "string" ? body.email : "";
2422
+ const email = normalizeAuthEmail(rawEmail);
2398
2423
  const password = body?.password;
2399
- if (!email || !password) {
2424
+ if (!rawEmail.trim() || !password) {
2400
2425
  setResponseStatus(event, 400);
2401
2426
  return { error: "Email and password are required" };
2402
2427
  }
2428
+ if (!email) {
2429
+ setResponseStatus(event, 400);
2430
+ return { error: VALID_AUTH_EMAIL_MESSAGE };
2431
+ }
2403
2432
  try {
2404
2433
  const result = await auth.api.signInEmail({
2405
2434
  body: { email, password },
@@ -2428,8 +2457,9 @@ async function mountBetterAuthRoutes(app, options) {
2428
2457
  if (!isExpectedAuthFailure(e)) {
2429
2458
  captureAuthError(e, { route: "login", email });
2430
2459
  }
2431
- setResponseStatus(event, 401);
2432
- return { error: e?.message || "Invalid email or password" };
2460
+ const authError = publicAuthError(e, "Invalid email or password");
2461
+ setResponseStatus(event, authError.statusCode ?? 401);
2462
+ return { error: authError.message };
2433
2463
  }
2434
2464
  }));
2435
2465
  // Backward-compat: POST /_agent-native/auth/register
@@ -2439,14 +2469,15 @@ async function mountBetterAuthRoutes(app, options) {
2439
2469
  return { error: "Method not allowed" };
2440
2470
  }
2441
2471
  const body = await readBody(event);
2442
- const email = body?.email?.trim?.()?.toLowerCase?.();
2472
+ const rawEmail = typeof body?.email === "string" ? body.email : "";
2473
+ const email = normalizeAuthEmail(rawEmail);
2443
2474
  const password = body?.password;
2444
2475
  const callbackURL = typeof body?.callbackURL === "string"
2445
2476
  ? safeReturnPath(body.callbackURL)
2446
2477
  : "/";
2447
- if (!email || typeof email !== "string" || !email.includes("@")) {
2478
+ if (!email) {
2448
2479
  setResponseStatus(event, 400);
2449
- return { error: "Valid email is required" };
2480
+ return { error: VALID_AUTH_EMAIL_MESSAGE };
2450
2481
  }
2451
2482
  if (!password || typeof password !== "string" || password.length < 8) {
2452
2483
  setResponseStatus(event, 400);
@@ -2462,8 +2493,9 @@ async function mountBetterAuthRoutes(app, options) {
2462
2493
  if (!isExpectedAuthFailure(e)) {
2463
2494
  captureAuthError(e, { route: "signup", email });
2464
2495
  }
2465
- setResponseStatus(event, 409);
2466
- return { error: e?.message || "Registration failed" };
2496
+ const authError = publicAuthError(e, "Registration failed");
2497
+ setResponseStatus(event, authError.statusCode ?? 409);
2498
+ return { error: authError.message };
2467
2499
  }
2468
2500
  }));
2469
2501
  // Backward-compat: POST /_agent-native/auth/logout
@@ -2598,12 +2630,17 @@ function mountAuthFallbackRoutes(app) {
2598
2630
  return { error: "Method not allowed" };
2599
2631
  }
2600
2632
  const body = await readBody(event);
2601
- const email = body?.email?.trim?.()?.toLowerCase?.();
2633
+ const rawEmail = typeof body?.email === "string" ? body.email : "";
2634
+ const email = normalizeAuthEmail(rawEmail);
2602
2635
  const password = body?.password;
2603
- if (!email || !password) {
2636
+ if (!rawEmail.trim() || !password) {
2604
2637
  setResponseStatus(event, 400);
2605
2638
  return { error: "Email and password are required" };
2606
2639
  }
2640
+ if (!email) {
2641
+ setResponseStatus(event, 400);
2642
+ return { error: VALID_AUTH_EMAIL_MESSAGE };
2643
+ }
2607
2644
  try {
2608
2645
  const auth = await getBetterAuth();
2609
2646
  const result = await auth.api.signInEmail({
@@ -2630,8 +2667,9 @@ function mountAuthFallbackRoutes(app) {
2630
2667
  if (!isExpectedAuthFailure(e)) {
2631
2668
  captureAuthError(e, { route: "login", email });
2632
2669
  }
2633
- setResponseStatus(event, 401);
2634
- return { error: e?.message || "Invalid email or password" };
2670
+ const authError = publicAuthError(e, "Invalid email or password");
2671
+ setResponseStatus(event, authError.statusCode ?? 401);
2672
+ return { error: authError.message };
2635
2673
  }
2636
2674
  }));
2637
2675
  app.use("/_agent-native/auth/register", defineEventHandler(async (event) => {
@@ -2640,11 +2678,12 @@ function mountAuthFallbackRoutes(app) {
2640
2678
  return { error: "Method not allowed" };
2641
2679
  }
2642
2680
  const body = await readBody(event);
2643
- const email = body?.email?.trim?.()?.toLowerCase?.();
2681
+ const rawEmail = typeof body?.email === "string" ? body.email : "";
2682
+ const email = normalizeAuthEmail(rawEmail);
2644
2683
  const password = body?.password;
2645
- if (!email || typeof email !== "string" || !email.includes("@")) {
2684
+ if (!email) {
2646
2685
  setResponseStatus(event, 400);
2647
- return { error: "Valid email is required" };
2686
+ return { error: VALID_AUTH_EMAIL_MESSAGE };
2648
2687
  }
2649
2688
  if (!password || typeof password !== "string" || password.length < 8) {
2650
2689
  setResponseStatus(event, 400);
@@ -2661,8 +2700,9 @@ function mountAuthFallbackRoutes(app) {
2661
2700
  if (!isExpectedAuthFailure(e)) {
2662
2701
  captureAuthError(e, { route: "signup", email });
2663
2702
  }
2664
- setResponseStatus(event, 409);
2665
- return { error: e?.message || "Registration failed" };
2703
+ const authError = publicAuthError(e, "Registration failed");
2704
+ setResponseStatus(event, authError.statusCode ?? 409);
2705
+ return { error: authError.message };
2666
2706
  }
2667
2707
  }));
2668
2708
  app.use("/_agent-native/auth/logout", defineEventHandler(async (event) => {