@nuxtjs/better-auth 0.3.10 → 0.3.12

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/module.d.mts CHANGED
@@ -30,24 +30,27 @@ interface BetterAuthDatabaseProviderDefinition {
30
30
  isEnabled?: (ctx: BetterAuthDatabaseProviderEnabledContext) => boolean;
31
31
  priority?: number;
32
32
  }
33
+ interface BetterAuthNuxtHooks {
34
+ /** Register absolute plugin module paths during module setup. Sources are additive and are not deduplicated. */
35
+ 'better-auth:plugins:extend': (sources: BetterAuthPluginSources) => void | Promise<void>;
36
+ /**
37
+ * Add plugin schemas to generated Better Auth database tables.
38
+ * This build-time hook does not install plugins in the runtime auth instance.
39
+ * @param config - Plugins to include during schema generation
40
+ * @param config.plugins - Better Auth plugins whose schemas should be generated
41
+ */
42
+ 'better-auth:config:extend': (config: {
43
+ plugins?: BetterAuthPlugin[];
44
+ }) => void | Promise<void>;
45
+ /** Register or override Better Auth database providers. */
46
+ 'better-auth:database:providers': (providers: Record<string, BetterAuthDatabaseProviderDefinition>) => void | Promise<void>;
47
+ }
33
48
  declare module '@nuxt/schema' {
34
- interface NuxtHooks {
35
- /** Register absolute plugin module paths during module setup. Sources are additive and are not deduplicated. */
36
- 'better-auth:plugins:extend': (sources: BetterAuthPluginSources) => void | Promise<void>;
37
- /**
38
- * Add plugin schemas to generated Better Auth database tables.
39
- * This build-time hook does not install plugins in the runtime auth instance.
40
- * @param config - Plugins to include during schema generation
41
- * @param config.plugins - Better Auth plugins whose schemas should be generated
42
- */
43
- 'better-auth:config:extend': (config: {
44
- plugins?: BetterAuthPlugin[];
45
- }) => void | Promise<void>;
46
- /**
47
- * Register or override Better Auth database providers.
48
- * Providers are auto-selected via `isEnabled` + `priority`.
49
- */
50
- 'better-auth:database:providers': (providers: Record<string, BetterAuthDatabaseProviderDefinition>) => void | Promise<void>;
49
+ interface NuxtHooks extends BetterAuthNuxtHooks {
50
+ }
51
+ }
52
+ declare module 'nuxt/schema' {
53
+ interface NuxtHooks extends BetterAuthNuxtHooks {
51
54
  }
52
55
  }
53
56
 
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuxtjs/better-auth",
3
- "version": "0.3.10",
3
+ "version": "0.3.12",
4
4
  "configKey": "auth",
5
5
  "compatibility": {
6
6
  "nuxt": ">=4.0.0"
package/dist/module.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
2
  import { mkdir, writeFile } from 'node:fs/promises';
3
- import { getLayerDirectories, resolveServerVariant, hasNuxtCompatibility, hasNuxtModuleCompatibility, updateTemplates, addServerImports, addServerHandler, addNitroPlugin, addImports, addPlugin, addComponentsDir, addRouteMiddleware, extendPages, addTemplate, hasNuxtModule, addTypeTemplate, defineNuxtModule, createResolver, getNitroVersion } from '@nuxt/kit';
3
+ import { getLayerDirectories, resolveServerVariant, hasNuxtCompatibility, hasNuxtModuleCompatibility, addTypeTemplate, updateTemplates, addServerImports, addServerHandler, addNitroPlugin, addImports, addPlugin, addComponentsDir, addRouteMiddleware, extendPages, addTemplate, hasNuxtModule, defineNuxtModule, createResolver, getNitroVersion } from '@nuxt/kit';
4
4
  import { consola as consola$1 } from 'consola';
5
5
  import { defineDiagnostics, Diagnostic, formatDiagnostic } from 'nostics';
6
6
  import { isAbsolute, join, resolve, relative, dirname } from 'pathe';
@@ -13,7 +13,7 @@ import { isCI, isTest } from 'std-env';
13
13
  import { isInternalAssetPath } from '../dist/runtime/internal/auth-route-rules.js';
14
14
  export { defineClientAuth, defineServerAuth } from '../dist/runtime/config.js';
15
15
 
16
- const version = "0.3.10";
16
+ const version = "0.3.12";
17
17
 
18
18
  const CONFIG_EXTENSIONS = [".ts", ".js", ".mts", ".cts", ".mjs", ".cjs"];
19
19
  const CONFIG_EXTENSION_RE = /\.[cm]?[jt]s$/;
@@ -284,6 +284,14 @@ const diagnostics = defineDiagnostics({
284
284
  why: (p) => `Failed to load auth config at ${p.configPath}: ${p.cause instanceof Error ? p.cause.message : String(p.cause)}`,
285
285
  fix: "Check the config imports and required environment variables, then restart Nuxt to regenerate the schema."
286
286
  },
287
+ NUXT_AUTH_CLI_MISSING: {
288
+ why: "NuxtHub schema generation needs the Better Auth CLI package (auth), which is not installed.",
289
+ fix: (p) => `Run ${p.command}. Keep auth on the same version as better-auth.`
290
+ },
291
+ NUXT_AUTH_CLI_VERSION_MISMATCH: {
292
+ why: (p) => `auth@${p.cliVersion} installs better-auth@${p.cliBetterAuthVersion}, but this app uses better-auth@${p.betterAuthVersion}. Two copies of Better Auth cause conflicting types.`,
293
+ fix: (p) => `Run ${p.command} so auth and better-auth use the same version.`
294
+ },
287
295
  NUXT_AUTH_EMPTY_SCHEMA: {
288
296
  why: (p) => `Schema generation returned empty result for ${p.dialect}`,
289
297
  fix: "Check the Better Auth plugins and database options in defineServerAuth(), then regenerate the schema."
@@ -379,6 +387,14 @@ function registerAuthMiddleware(resolve) {
379
387
  }
380
388
  function registerPrepareTypesHook(input) {
381
389
  const { nuxt, serverDir, hasHubDb, serverImportsTypesFile } = input;
390
+ addTypeTemplate({
391
+ filename: "types/auth-route-rules.d.ts",
392
+ getContents: () => `
393
+ declare module '#auth/route-rules' {
394
+ export const authRouteRules: Record<string, { auth?: unknown }>
395
+ }
396
+ `
397
+ }, { nitro: true, node: true });
382
398
  nuxt.hook("prepare:types", ({ nodeTsConfig, nodeReferences, sharedReferences }) => {
383
399
  nodeTsConfig.compilerOptions ||= {};
384
400
  nodeTsConfig.compilerOptions.paths ||= {};
@@ -470,6 +486,40 @@ function registerRouteRulesMetaHook(nuxt) {
470
486
  function dialectToProvider(dialect) {
471
487
  return dialect === "postgresql" ? "pg" : dialect;
472
488
  }
489
+ const EXACT_VERSION_RE = /^\d+\.\d+\.\d+(?:-[\w.]+)?$/;
490
+ const LOCKFILES = [["pnpm-lock.yaml", "pnpm"], ["yarn.lock", "yarn"], ["bun.lock", "bun"], ["bun.lockb", "bun"], ["package-lock.json", "npm"]];
491
+ function readInstalledPackage(name) {
492
+ let entry;
493
+ try {
494
+ entry = createRequire(import.meta.url).resolve(name === "auth" ? "auth/api" : name);
495
+ } catch {
496
+ return void 0;
497
+ }
498
+ for (let dir = dirname(entry); dir !== dirname(dir); dir = dirname(dir)) {
499
+ const file = join(dir, "package.json");
500
+ if (!existsSync(file))
501
+ continue;
502
+ const pkg = JSON.parse(readFileSync(file, "utf8"));
503
+ if (pkg.name === name)
504
+ return pkg;
505
+ }
506
+ }
507
+ function devInstallCommand(spec, rootDir) {
508
+ const agent = process.env.npm_config_user_agent?.split("/")[0];
509
+ const manager = LOCKFILES.find(([file]) => existsSync(join(rootDir, file)))?.[1] ?? LOCKFILES.find(([, name]) => name === agent)?.[1] ?? "npm";
510
+ return manager === "npm" ? `npm install -D ${spec}` : `${manager} add -D ${spec}`;
511
+ }
512
+ function checkBetterAuthCli(rootDir, readPackage = readInstalledPackage) {
513
+ const betterAuthVersion = readPackage("better-auth")?.version;
514
+ const spec = betterAuthVersion ? `auth@${betterAuthVersion}` : "auth";
515
+ const cli = readPackage("auth");
516
+ if (!cli)
517
+ return { error: diagnostics.NUXT_AUTH_CLI_MISSING({ command: devInstallCommand(spec, rootDir) }) };
518
+ const cliBetterAuthVersion = cli.dependencies?.["better-auth"];
519
+ if (betterAuthVersion && cliBetterAuthVersion && EXACT_VERSION_RE.test(cliBetterAuthVersion) && cliBetterAuthVersion !== betterAuthVersion)
520
+ return { warn: diagnostics.NUXT_AUTH_CLI_VERSION_MISMATCH({ cliVersion: cli.version ?? cliBetterAuthVersion, cliBetterAuthVersion, betterAuthVersion, command: devInstallCommand(spec, rootDir) }) };
521
+ return {};
522
+ }
473
523
  async function generateDrizzleSchema(authOptions, dialect, schemaOptions) {
474
524
  const { generateDrizzleSchema: generateDrizzleSchema2 } = await import('auth/api');
475
525
  const provider = dialectToProvider(dialect);
@@ -631,6 +681,11 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola, h
631
681
  consola.warn(formatDiagnostic(diagnostics.NUXT_AUTH_UNSUPPORTED_DIALECT({ dialect })));
632
682
  return;
633
683
  }
684
+ const cli = checkBetterAuthCli(nuxt.options.rootDir);
685
+ if (cli.error)
686
+ throw cli.error;
687
+ if (cli.warn)
688
+ consola.warn(formatDiagnostic(cli.warn));
634
689
  const context = { nuxt, serverConfigPath };
635
690
  try {
636
691
  const authConfig = await loadAuthOptions(context);
@@ -1092,10 +1147,10 @@ function getRuntimeRouteRules(nuxt) {
1092
1147
  }
1093
1148
  function registerAuthRouteRulesValidation(nuxt) {
1094
1149
  nuxt.hook("nitro:init", (nitro) => {
1095
- assertSafeAuthRouteRules(nitro.options.routeRules);
1150
+ assertSafeAuthRouteRules(nitro.options.routeRules, nuxt.options.app.baseURL);
1096
1151
  });
1097
1152
  }
1098
- function assertSafeAuthRouteRules(routeRules) {
1153
+ function assertSafeAuthRouteRules(routeRules, baseURL = "/") {
1099
1154
  if (!Object.keys(routeRules).length)
1100
1155
  return;
1101
1156
  const matcher = toRouteMatcher(createRouter({ routes: routeRules }));
@@ -1104,7 +1159,7 @@ function assertSafeAuthRouteRules(routeRules) {
1104
1159
  for (const path of collectRouteRulePaths(patterns, ""))
1105
1160
  paths.add(path);
1106
1161
  const conflicts = [...paths].flatMap((path) => {
1107
- if (isInternalAssetPath(path))
1162
+ if (isInternalAssetPath(path, baseURL))
1108
1163
  return [];
1109
1164
  const matches = matcher.matchAll(path);
1110
1165
  const effectiveRule = defu({}, ...matches.reverse());
@@ -1573,6 +1628,8 @@ declare module '#nuxt-better-auth' {
1573
1628
  emailVerified: boolean
1574
1629
  name: string
1575
1630
  image?: string | null
1631
+ /** Whether the user has enabled two-factor authentication. */
1632
+ twoFactorEnabled?: boolean
1576
1633
  }
1577
1634
 
1578
1635
  export interface AuthSession {
@@ -2,7 +2,7 @@ import { computed, navigateTo, nextTick, useNuxtApp, useRequestURL, useRuntimeCo
2
2
  import { normalizeAuthActionError } from "../internal/auth-action-error.js";
3
3
  import { resolvePostAuthSuccessRedirect, withFallbackSocialCallbackURL } from "../internal/redirect-helpers.js";
4
4
  import { fetchSessionClient, fetchSessionServer, isExpectedSignedOutSessionError, stripToken } from "../internal/session-fetch.js";
5
- import { isRecord } from "../internal/utils.js";
5
+ import { isRecord, setIfChanged } from "../internal/utils.js";
6
6
  import { createVueSafeAuthFacade, isAuthProxyProbeKey } from "../internal/vue-safe-auth-proxy.js";
7
7
  import { wrapAuthMethod } from "../internal/wrap-auth-method.js";
8
8
  import { getAuthRuntimeFlags, useRawAuthClient } from "./useAuthClient.js";
@@ -152,8 +152,8 @@ export function useUserSession() {
152
152
  if (newSession?.data?.session && newSession?.data?.user) {
153
153
  sessionExpiredRedirecting = false;
154
154
  hydrationSessionWasActive = false;
155
- session.value = stripToken(newSession.data.session);
156
- user.value = newSession.data.user;
155
+ setIfChanged(session, stripToken(newSession.data.session));
156
+ setIfChanged(user, newSession.data.user);
157
157
  } else if (!newSession?.isPending && !newSession?.isRefetching) {
158
158
  const isHydrationEmptySnapshot = nuxtApp.isHydrating && nuxtApp.payload.serverRendered && Boolean(session.value && user.value) && !newSession?.data?.session && !newSession?.data?.user;
159
159
  if (isHydrationEmptySnapshot) {
@@ -1,6 +1,7 @@
1
1
  import { parseJSON } from "better-auth/client";
2
2
  import { useRequestFetch, useRequestHeaders } from "#imports";
3
3
  import { normalizeAuthActionError } from "./auth-action-error.js";
4
+ import { setIfChanged } from "./utils.js";
4
5
  export function stripToken(session) {
5
6
  const { token: _, ...safe } = session;
6
7
  return safe;
@@ -35,8 +36,8 @@ export async function fetchSessionServer(session, user, authReady, options = {})
35
36
  ...options.force ? { query: { disableCookieCache: true } } : {}
36
37
  });
37
38
  if (data?.session && data?.user) {
38
- session.value = stripToken(data.session);
39
- user.value = data.user;
39
+ setIfChanged(session, stripToken(data.session));
40
+ setIfChanged(user, data.user);
40
41
  } else {
41
42
  session.value = null;
42
43
  user.value = null;
@@ -60,8 +61,8 @@ export async function fetchSessionClient(client, session, user, authReady, optio
60
61
  }
61
62
  const data = result.data;
62
63
  if (data?.session && data?.user) {
63
- session.value = stripToken(data.session);
64
- user.value = data.user;
64
+ setIfChanged(session, stripToken(data.session));
65
+ setIfChanged(user, data.user);
65
66
  } else {
66
67
  session.value = null;
67
68
  user.value = null;
@@ -1 +1,9 @@
1
1
  export declare function isRecord(value: unknown): value is Record<string, unknown>;
2
+ /**
3
+ * Better Auth refetches the session on window focus and returns fresh objects even
4
+ * when nothing changed. Keeping the current object avoids retriggering every
5
+ * computed and watcher that depends on the session or user.
6
+ */
7
+ export declare function setIfChanged<T>(target: {
8
+ value: T;
9
+ }, value: T): void;
@@ -1,3 +1,19 @@
1
1
  export function isRecord(value) {
2
2
  return Boolean(value && typeof value === "object");
3
3
  }
4
+ function isSameValue(a, b) {
5
+ if (Object.is(a, b))
6
+ return true;
7
+ if (a instanceof Date || b instanceof Date)
8
+ return a instanceof Date && b instanceof Date && a.getTime() === b.getTime();
9
+ if (Array.isArray(a) || Array.isArray(b))
10
+ return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((item, index) => isSameValue(item, b[index]));
11
+ if (!isRecord(a) || !isRecord(b))
12
+ return false;
13
+ const keys = Object.keys(a);
14
+ return keys.length === Object.keys(b).length && keys.every((key) => Object.hasOwn(b, key) && isSameValue(a[key], b[key]));
15
+ }
16
+ export function setIfChanged(target, value) {
17
+ if (!isSameValue(target.value, value))
18
+ target.value = value;
19
+ }
@@ -2,13 +2,15 @@ import { defu } from "defu";
2
2
  import { createRouter, toRouteMatcher } from "radix3";
3
3
  import { parsePath, parseQuery } from "ufo";
4
4
  import { createError, defineNuxtRouteMiddleware, getRouteRules, navigateTo, useNuxtApp, useRequestHeaders, useRuntimeConfig } from "#imports";
5
- import { shouldSkipAuthRouteRules } from "../../internal/auth-route-rules.js";
5
+ import { normalizeAuthRoutePath, shouldSkipAuthRouteRules } from "../../internal/auth-route-rules.js";
6
6
  import { matchesUser } from "../../utils/match-user.js";
7
7
  import { useUserSession } from "../composables/useUserSession.js";
8
8
  let authRouteRulesPromise = null;
9
9
  let routeRulesMatcherPromise = null;
10
10
  export default defineNuxtRouteMiddleware(async (to) => {
11
- if (shouldSkipAuthRouteRules(to.path))
11
+ const runtimeConfig = useRuntimeConfig();
12
+ const path = normalizeAuthRoutePath(to.path, runtimeConfig.app?.baseURL);
13
+ if (shouldSkipAuthRouteRules(to.path, runtimeConfig.app?.baseURL) || shouldSkipAuthRouteRules(path, runtimeConfig.app?.baseURL))
12
14
  return;
13
15
  if (to.matched?.length === 0)
14
16
  return;
@@ -30,7 +32,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
30
32
  const auth = to.meta.auth;
31
33
  if (auth === void 0 || auth === false)
32
34
  return;
33
- const config = useRuntimeConfig().public.auth;
35
+ const config = runtimeConfig.public.auth;
34
36
  if (import.meta.server && config?.clientOnly)
35
37
  return;
36
38
  const { fetchSession, user, loggedIn } = useUserSession();
@@ -12,7 +12,7 @@ export interface ClientAuthContext {
12
12
  export type ServerAuthConfig = Omit<BetterAuthOptions, 'secret' | 'baseURL' | 'basePath'> & {
13
13
  /** The module registers its server handler at /api/auth. */
14
14
  basePath?: '/api/auth';
15
- plugins?: readonly BetterAuthPlugin[];
15
+ plugins?: readonly unknown[];
16
16
  };
17
17
  export type ClientAuthConfig = Omit<BetterAuthClientOptions, 'baseURL'> & {
18
18
  baseURL?: string;
@@ -108,8 +108,11 @@ export interface AuthRuntimeConfig {
108
108
  export interface AuthPrivateRuntimeConfig {
109
109
  hubSecondaryStorage: boolean | 'custom';
110
110
  }
111
- export declare function defineServerAuth<const R>(config: (ctx: ServerAuthContext) => R & ServerAuthConfig): (ctx: ServerAuthContext) => R;
112
- export declare function defineServerAuth<const R>(config: R & ServerAuthConfig & (R extends (...args: never[]) => unknown ? never : unknown)): (ctx: ServerAuthContext) => R;
111
+ export declare function defineServerAuth<const R extends ServerAuthConfig>(config: (ctx: ServerAuthContext) => R): (ctx: ServerAuthContext) => R;
112
+ export declare function defineServerAuth<const R>(config: (ctx: ServerAuthContext) => R & ServerAuthConfig & (R extends {
113
+ basePath: infer B;
114
+ } ? B extends '/api/auth' ? unknown : never : unknown)): (ctx: ServerAuthContext) => R;
115
+ export declare function defineServerAuth<const R extends ServerAuthConfig>(config: R & (R extends (...args: never[]) => unknown ? never : unknown)): (ctx: ServerAuthContext) => R;
113
116
  export declare function extendServerAuth<const R extends ServerAuthConfig, const P extends readonly BetterAuthPlugin[]>(createConfig: (ctx: ServerAuthContext) => R, plugins: P): (ctx: ServerAuthContext) => ExtendedServerAuthConfig<R, P>;
114
117
  export declare function defineClientAuth<T extends ClientAuthConfig>(config: T | ((ctx: ClientAuthContext) => T)): ClientAuthFactory<T>;
115
118
  export declare function extendClientAuth<T extends ClientAuthConfig, const P extends BetterAuthClientPlugin[]>(createClient: ClientAuthFactory<T>, plugins: P): ClientAuthFactory<ExtendedClientAuthConfig<T, P>>;
@@ -1,5 +1,5 @@
1
1
  import type { AuthRouteRules } from '../types.js';
2
- export declare function isInternalAssetPath(path: string): boolean;
2
+ export declare function isInternalAssetPath(path: string, baseURL?: string): boolean;
3
3
  export declare function normalizeAuthRoutePath(path: string, baseURL?: string): string;
4
4
  export declare function normalizeAuthRouteRule(rule: unknown): AuthRouteRules['auth'];
5
- export declare function shouldSkipAuthRouteRules(path: string): boolean;
5
+ export declare function shouldSkipAuthRouteRules(path: string, baseURL?: string): boolean;
@@ -1,8 +1,11 @@
1
1
  import { withoutBase } from "ufo";
2
- const internalAssetPaths = ["/_fonts", "/_ipx", "/_nuxt", "/api/_nuxt_icon"];
3
- export function isInternalAssetPath(path) {
2
+ const internalAssetPaths = ["/_fonts", "/_ipx", "/_nuxt", "/_scripts/assets", "/api/_nuxt_icon"];
3
+ export function isInternalAssetPath(path, baseURL = "/") {
4
4
  const pathname = path.split(/[?#]/, 1)[0] || "/";
5
- return internalAssetPaths.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`));
5
+ const normalizedPathname = withoutBase(pathname, baseURL) || "/";
6
+ return internalAssetPaths.some((prefix) => {
7
+ return normalizedPathname === prefix || normalizedPathname.startsWith(`${prefix}/`);
8
+ });
6
9
  }
7
10
  const internalRouteRuleAuthPaths = /* @__PURE__ */ new Set([
8
11
  "/__better-auth-devtools",
@@ -27,7 +30,7 @@ export function normalizeAuthRouteRule(rule) {
27
30
  return rule.options;
28
31
  return rule;
29
32
  }
30
- export function shouldSkipAuthRouteRules(path) {
33
+ export function shouldSkipAuthRouteRules(path, baseURL = "/") {
31
34
  const pathname = path.split(/[?#]/, 1)[0] || "/";
32
- return isInternalAssetPath(pathname) || internalRouteRuleAuthPaths.has(pathname) || internalRouteRuleAuthPrefixes.some((prefix) => pathname.startsWith(prefix));
35
+ return isInternalAssetPath(pathname, baseURL) || internalRouteRuleAuthPaths.has(pathname) || internalRouteRuleAuthPrefixes.some((prefix) => pathname.startsWith(prefix));
33
36
  }
@@ -1,9 +1,19 @@
1
1
  import type { H3Event } from 'h3';
2
2
  import type { AuthRouteRules } from '../../types.js';
3
- import { defineEventHandler, getQuery, getRequestHost, getRequestProtocol, getRequestURL, readBody, toWebRequest } from 'h3';
3
+ import { defineEventHandler } from 'h3';
4
4
  import { useRuntimeConfig } from 'nitropack/runtime';
5
- export { defineEventHandler, getQuery, getRequestHost, getRequestProtocol, getRequestURL, readBody, toWebRequest, useRuntimeConfig, };
5
+ export { defineEventHandler, useRuntimeConfig, };
6
6
  export type ServerEvent = H3Event;
7
+ export declare function getRequestHost(event: ServerEvent, options?: {
8
+ xForwardedHost?: boolean;
9
+ }): string;
10
+ export declare function getRequestProtocol(event: ServerEvent, options?: {
11
+ xForwardedProto?: boolean;
12
+ }): string;
13
+ export declare function getRequestURL(event: ServerEvent): URL;
14
+ export declare function getQuery(event: ServerEvent): Record<string, string | string[]>;
15
+ export declare function toWebRequest(event: ServerEvent): Request;
16
+ export declare function readBody(event: ServerEvent): Promise<unknown>;
7
17
  export declare function splitCookiesString(cookiesString: string | string[]): string[];
8
18
  export declare function getAuthRouteRules(event: ServerEvent): AuthRouteRules;
9
19
  export declare function createAuthError(status: number, statusText: string): Error;
@@ -1,31 +1,111 @@
1
1
  import { splitSetCookieString } from "cookie-es";
2
- import {
3
- createError,
4
- defineEventHandler,
5
- getQuery,
6
- getRequestHost,
7
- getRequestProtocol,
8
- getRequestURL,
9
- readBody,
10
- toWebRequest
11
- } from "h3";
2
+ import { defineEventHandler } from "h3";
12
3
  import { getRouteRules, useRuntimeConfig } from "nitropack/runtime";
13
4
  export {
14
5
  defineEventHandler,
15
- getQuery,
16
- getRequestHost,
17
- getRequestProtocol,
18
- getRequestURL,
19
- readBody,
20
- toWebRequest,
21
6
  useRuntimeConfig
22
7
  };
8
+ function createNitro2AuthError(status, statusText) {
9
+ const error = new Error(statusText);
10
+ const markerConstructor = function Nitro2ErrorConstructor() {
11
+ };
12
+ Object.defineProperty(markerConstructor, "__h3_error__", { value: true });
13
+ Object.defineProperties(error, {
14
+ name: { value: "HTTPError", configurable: true, enumerable: false, writable: true },
15
+ constructor: { value: markerConstructor, configurable: true },
16
+ __h3_error__: { value: true, configurable: true, enumerable: true, writable: true },
17
+ status: { value: status, configurable: true, enumerable: true, writable: true },
18
+ statusText: { value: statusText, configurable: true, enumerable: true, writable: true },
19
+ statusCode: { value: status, configurable: true, enumerable: true, writable: true },
20
+ statusMessage: { value: statusText, configurable: true, enumerable: true, writable: true },
21
+ toJSON: {
22
+ value: () => ({
23
+ status,
24
+ statusText,
25
+ statusCode: status,
26
+ statusMessage: statusText,
27
+ message: statusText,
28
+ unhandled: false,
29
+ fatal: false
30
+ }),
31
+ configurable: true
32
+ },
33
+ unhandled: { value: false, configurable: true, enumerable: true, writable: true },
34
+ fatal: { value: false, configurable: true, enumerable: true, writable: true }
35
+ });
36
+ return error;
37
+ }
38
+ function requestHeaders(event) {
39
+ return event.node?.req?.headers ?? {};
40
+ }
41
+ export function getRequestHost(event, options = {}) {
42
+ const headers = requestHeaders(event);
43
+ if (options.xForwardedHost) {
44
+ const forwarded = headers["x-forwarded-host"];
45
+ const host2 = Array.isArray(forwarded) ? forwarded[0] : forwarded?.split(",")[0]?.trim();
46
+ if (host2)
47
+ return host2;
48
+ }
49
+ const host = headers.host;
50
+ return (Array.isArray(host) ? host[0] : host) || "localhost";
51
+ }
52
+ export function getRequestProtocol(event, options = {}) {
53
+ const headers = requestHeaders(event);
54
+ if (options.xForwardedProto !== false && headers["x-forwarded-proto"] === "https")
55
+ return "https";
56
+ return event.node?.req?.socket && event.node.req.socket["encrypted"] ? "https" : "http";
57
+ }
58
+ export function getRequestURL(event) {
59
+ const req = event.node?.req ?? {};
60
+ const path = req.originalUrl || req.url || event.path || "/";
61
+ return new URL(path, `${getRequestProtocol(event)}://${getRequestHost(event)}`);
62
+ }
63
+ export function getQuery(event) {
64
+ const query = {};
65
+ for (const [key, value] of getRequestURL(event).searchParams) {
66
+ const previous = query[key];
67
+ query[key] = previous === void 0 ? value : Array.isArray(previous) ? [...previous, value] : [previous, value];
68
+ }
69
+ return query;
70
+ }
71
+ export function toWebRequest(event) {
72
+ const req = event.node?.req;
73
+ const webRequest = event.web?.request;
74
+ if (webRequest)
75
+ return webRequest;
76
+ const init = {
77
+ method: req?.method,
78
+ headers: req?.headers ? Object.fromEntries(Object.entries(req.headers).flatMap(([key, value]) => value === void 0 ? [] : [[key, Array.isArray(value) ? value.join(", ") : value]])) : void 0,
79
+ duplex: "half"
80
+ };
81
+ if (req?.method !== "GET" && req?.method !== "HEAD")
82
+ init.body = req;
83
+ return new Request(getRequestURL(event), init);
84
+ }
85
+ export async function readBody(event) {
86
+ const request = toWebRequest(event);
87
+ const contentType = request.headers.get("content-type") || "";
88
+ if (contentType.includes("application/json"))
89
+ return request.json();
90
+ if (contentType.includes("application/x-www-form-urlencoded"))
91
+ return Object.fromEntries((await request.text()).split("&").filter(Boolean).map((pair) => pair.split("=").map(decodeURIComponent)));
92
+ return request.text();
93
+ }
23
94
  export function splitCookiesString(cookiesString) {
24
95
  return splitSetCookieString(cookiesString);
25
96
  }
26
97
  export function getAuthRouteRules(event) {
27
- return getRouteRules(event);
98
+ const eventWithContext = event;
99
+ if (!eventWithContext.context)
100
+ eventWithContext.context = {};
101
+ if (!eventWithContext.path)
102
+ eventWithContext.path = getRequestURL(event).pathname;
103
+ try {
104
+ return getRouteRules(event) || {};
105
+ } catch {
106
+ return {};
107
+ }
28
108
  }
29
109
  export function createAuthError(status, statusText) {
30
- return createError({ statusCode: status, statusMessage: statusText });
110
+ return createNitro2AuthError(status, statusText);
31
111
  }
@@ -25,7 +25,7 @@ export function splitCookiesString(cookiesString) {
25
25
  }
26
26
  export function getAuthRouteRules(event) {
27
27
  const rules = getRouteRules(event);
28
- return { auth: normalizeAuthRouteRule(rules.auth) };
28
+ return { auth: normalizeAuthRouteRule(rules?.auth) };
29
29
  }
30
30
  export function createAuthError(status, statusText) {
31
31
  return createError({ status, statusText });
@@ -1,8 +1,11 @@
1
+ import { defu } from "defu";
2
+ import { createRouter, toRouteMatcher } from "radix3";
1
3
  import { normalizeAuthRoutePath, shouldSkipAuthRouteRules } from "../../internal/auth-route-rules.js";
2
4
  import { matchesUser } from "../../utils/match-user.js";
3
5
  import { prepareAuthSecret } from "../internal/auth-secret.js";
4
6
  import { createAuthError, defineEventHandler, getAuthRouteRules, getRequestURL, useRuntimeConfig } from "../internal/nitro-compat.js";
5
7
  import { getUserSession, requireUserSession } from "../utils/session.js";
8
+ let authRouteRulesMatcherPromise = null;
6
9
  export default defineEventHandler(async (event) => {
7
10
  await prepareAuthSecret();
8
11
  const path = normalizeAuthRoutePath(
@@ -13,7 +16,7 @@ export default defineEventHandler(async (event) => {
13
16
  return;
14
17
  if (shouldSkipAuthRouteRules(path))
15
18
  return;
16
- const rules = getAuthRouteRules(event);
19
+ const rules = await resolveAuthRouteRules(event, path);
17
20
  if (!rules.auth)
18
21
  return;
19
22
  const auth = rules.auth;
@@ -32,3 +35,19 @@ export default defineEventHandler(async (event) => {
32
35
  }
33
36
  }
34
37
  });
38
+ async function resolveAuthRouteRules(event, path) {
39
+ const rules = getAuthRouteRules(event);
40
+ if (rules?.auth !== void 0)
41
+ return rules;
42
+ if (!authRouteRulesMatcherPromise) {
43
+ authRouteRulesMatcherPromise = import("#auth/route-rules").then((mod) => {
44
+ const routeRules = mod.authRouteRules || {};
45
+ return Object.keys(routeRules).length ? toRouteMatcher(createRouter({ routes: routeRules })) : null;
46
+ }).catch(() => null);
47
+ }
48
+ const matcher = await authRouteRulesMatcherPromise;
49
+ const matches = matcher?.matchAll(path);
50
+ if (!matches?.length)
51
+ return rules || {};
52
+ return defu({}, ...matches.reverse());
53
+ }
@@ -9,13 +9,18 @@ type UserAuthConfig = AuthOptions & {
9
9
  trustedOrigins?: BetterAuthOptions['trustedOrigins'];
10
10
  secondaryStorage?: BetterAuthOptions['secondaryStorage'];
11
11
  };
12
- type ResolvedAuthOptions = Omit<UserAuthConfig, 'secret' | 'baseURL' | 'trustedOrigins' | 'database'> & {
12
+ type ResolvedAuthOptions = Omit<UserAuthConfig, 'secret' | 'baseURL' | 'trustedOrigins' | 'database' | 'plugins'> & {
13
+ plugins?: BetterAuthOptions['plugins'];
13
14
  secret: string;
14
15
  baseURL: string;
15
16
  trustedOrigins?: BetterAuthOptions['trustedOrigins'];
16
17
  database?: BetterAuthOptions['database'];
17
18
  };
18
- type AuthInstance = ReturnType<typeof betterAuth<ResolvedAuthOptions>>;
19
+ type AuthInstance = ReturnType<typeof betterAuth<ResolvedAuthOptions>> & {
20
+ api: ReturnType<typeof betterAuth<ResolvedAuthOptions>>['api'] & {
21
+ signInUsername?: (...args: unknown[]) => unknown;
22
+ };
23
+ };
19
24
  /**
20
25
  * Returns the Better Auth instance like `serverAuth()`, after waiting for the auth secret that
21
26
  * Nuxt 4.6+ derives from `appSecret` (`NUXT_APP_SECRET`). Use it in code that can run before the
@@ -67,8 +67,21 @@ function resolveConfiguredSiteUrl(config) {
67
67
  function resolveEventOrigin(event) {
68
68
  if (!event)
69
69
  return void 0;
70
- const host = getRequestHost(event, { xForwardedHost: true });
71
- const protocol = getRequestProtocol(event, { xForwardedProto: true });
70
+ let host;
71
+ let protocol;
72
+ try {
73
+ host = getRequestHost(event, { xForwardedHost: true });
74
+ protocol = getRequestProtocol(event, { xForwardedProto: true });
75
+ } catch {
76
+ const request = event.node?.req;
77
+ const headers = request?.headers ?? {};
78
+ const forwardedHost = headers["x-forwarded-host"];
79
+ const forwarded = Array.isArray(forwardedHost) ? forwardedHost[0] : forwardedHost?.split(",")[0]?.trim();
80
+ const rawHost = forwarded || headers.host;
81
+ host = (Array.isArray(rawHost) ? rawHost[0] : rawHost) || "";
82
+ const forwardedProto = headers["x-forwarded-proto"];
83
+ protocol = (Array.isArray(forwardedProto) ? forwardedProto[0] : forwardedProto) || (request?.socket?.encrypted ? "https" : "http");
84
+ }
72
85
  if (!host || !protocol)
73
86
  return void 0;
74
87
  try {
@@ -274,6 +287,7 @@ export function serverAuth(event) {
274
287
  }
275
288
  const authOptions = {
276
289
  ...userConfig,
290
+ ...userConfig.plugins ? { plugins: [...userConfig.plugins] } : {},
277
291
  ...rateLimit ? { rateLimit } : {},
278
292
  ...database ? { database } : {},
279
293
  secret: betterAuthSecret,
@@ -7,6 +7,8 @@ export interface AuthUser {
7
7
  emailVerified: boolean;
8
8
  name: string;
9
9
  image?: string | null;
10
+ /** Whether the user has enabled two-factor authentication. */
11
+ twoFactorEnabled?: boolean;
10
12
  }
11
13
  export interface AuthSession {
12
14
  id: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nuxtjs/better-auth",
3
3
  "type": "module",
4
- "version": "0.3.10",
4
+ "version": "0.3.12",
5
5
  "description": "Nuxt module for Better Auth integration with NuxtHub, route protection, session management, and role-based access",
6
6
  "author": "onmax",
7
7
  "license": "MIT",
@@ -61,6 +61,7 @@
61
61
  "peerDependencies": {
62
62
  "@nuxt/test-utils": "^4.1.0",
63
63
  "@nuxthub/core": ">=0.10.5",
64
+ "auth": "1.7.12",
64
65
  "better-auth": ">=1.7.3 <2"
65
66
  },
66
67
  "peerDependenciesMeta": {
@@ -69,18 +70,20 @@
69
70
  },
70
71
  "@nuxthub/core": {
71
72
  "optional": true
73
+ },
74
+ "auth": {
75
+ "optional": true
72
76
  }
73
77
  },
74
78
  "dependencies": {
75
79
  "@nuxt/devtools-kit": "^3.4.1",
76
80
  "@nuxt/kit": "^4.6.0",
77
81
  "@types/node": "^24.13.3",
78
- "auth": "1.7.7",
79
82
  "consola": "^3.4.2",
80
83
  "cookie-es": "^3.1.1",
81
84
  "defu": "^6.1.7",
82
85
  "get-port-please": "^3.2.0",
83
- "h3": "^1.15.11",
86
+ "h3": "^2.0.0",
84
87
  "jiti": "^2.7.0",
85
88
  "nostics": "^1.2.0",
86
89
  "pathe": "^2.0.3",
@@ -94,7 +97,7 @@
94
97
  "@libsql/client": "^0.18.0",
95
98
  "@libsql/linux-x64-gnu": "0.5.29",
96
99
  "@nuxt/devtools": "^3.4.1",
97
- "@nuxt/fonts": "0.14.0",
100
+ "@nuxt/fonts": "1.0.1",
98
101
  "@nuxt/module-builder": "^1.0.3",
99
102
  "@nuxt/schema": "^4.6.0",
100
103
  "@nuxt/test-utils": "^4.3.3",
@@ -102,25 +105,26 @@
102
105
  "@pinia/nuxt": "^1.0.2",
103
106
  "@types/better-sqlite3": "^9.6.0",
104
107
  "@vue/test-utils": "^2.4.6",
105
- "better-auth": "1.7.7",
106
- "better-call": "1.4.0",
108
+ "auth": "1.7.12",
109
+ "better-auth": "1.7.12",
110
+ "better-call": "1.4.4",
107
111
  "better-sqlite3": "^13.0.0",
108
112
  "drizzle-kit": "^0.31.10",
109
113
  "drizzle-orm": "^0.45.2",
110
114
  "eslint": "^10.8.1",
111
115
  "happy-dom": "^21.0.0",
112
- "nitropack": "2.13.4",
116
+ "nitropack": "2.13.5",
113
117
  "nuxt": "^4.6.0",
114
118
  "pinia": "^4.0.3",
115
- "pkg-pr-new": "0.0.88",
116
- "playwright-core": "1.63.0",
119
+ "pkg-pr-new": "0.0.91",
120
+ "playwright-core": "1.64.1",
117
121
  "postgres": "^3.4.9",
118
122
  "typescript": "~5.9.3",
119
123
  "unstorage": "^1.17.5",
120
124
  "vitest": "^5.0.0",
121
- "vue": "3.5.43",
125
+ "vue": "3.5.44",
122
126
  "vue-tsc": "^3.3.10",
123
- "wrangler": "4.147.0",
127
+ "wrangler": "4.151.3",
124
128
  "yaml": "^2.9.0"
125
129
  },
126
130
  "scripts": {