@nuxtjs/better-auth 0.2.2 → 0.2.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.
package/dist/module.d.mts CHANGED
@@ -2,11 +2,15 @@ import * as _nuxt_schema from '@nuxt/schema';
2
2
  import { Nuxt } from '@nuxt/schema';
3
3
  import { BetterAuthModuleOptions } from '../dist/runtime/config.js';
4
4
  export { BetterAuthModuleOptions, defineClientAuth, defineServerAuth } from '../dist/runtime/config.js';
5
- import { BetterAuthOptions } from 'better-auth';
5
+ import { BetterAuthPlugin } from 'better-auth';
6
6
  export { AppSession, Auth, AuthActionError, AuthMeta, AuthMode, AuthRouteRules, AuthSession, AuthSocialProviderId, AuthUser, InferSession, InferUser, RequireSessionOptions, ServerAuthContext, UserMatch } from '../dist/runtime/types.js';
7
7
 
8
8
  type DbDialect = 'sqlite' | 'postgresql' | 'mysql';
9
9
 
10
+ interface BetterAuthPluginSources {
11
+ server?: string[];
12
+ client?: string[];
13
+ }
10
14
  interface BetterAuthDatabaseProviderBuildContext {
11
15
  hubDialect: DbDialect;
12
16
  usePlural: boolean;
@@ -28,12 +32,17 @@ interface BetterAuthDatabaseProviderDefinition {
28
32
  }
29
33
  declare module '@nuxt/schema' {
30
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>;
31
37
  /**
32
- * Extend better-auth config with additional plugins or options.
33
- * Called after user's auth.config.ts is loaded.
34
- * @param config - Partial config to merge into the auth options
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
35
42
  */
36
- 'better-auth:config:extend': (config: Partial<BetterAuthOptions>) => void | Promise<void>;
43
+ 'better-auth:config:extend': (config: {
44
+ plugins?: BetterAuthPlugin[];
45
+ }) => void | Promise<void>;
37
46
  /**
38
47
  * Register or override Better Auth database providers.
39
48
  * Providers are auto-selected via `isEnabled` + `priority`.
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuxtjs/better-auth",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "configKey": "auth",
5
5
  "compatibility": {
6
6
  "nuxt": ">=4.0.0"
package/dist/module.mjs CHANGED
@@ -2,7 +2,7 @@ import { existsSync, statSync, writeFileSync, readFileSync } from 'node:fs';
2
2
  import { mkdir, writeFile } from 'node:fs/promises';
3
3
  import { getLayerDirectories, updateTemplates, addServerImportsDir, addServerImports, addServerScanDir, addServerHandler, addImportsDir, addPlugin, addComponentsDir, extendPages, addTemplate, hasNuxtModule, addTypeTemplate, defineNuxtModule, createResolver } from '@nuxt/kit';
4
4
  import { consola as consola$1 } from 'consola';
5
- import { isAbsolute, join, relative, dirname } from 'pathe';
5
+ import { isAbsolute, resolve, join, relative, dirname } from 'pathe';
6
6
  import { defu } from 'defu';
7
7
  import { toRouteMatcher, createRouter } from 'radix3';
8
8
  import { generateDrizzleSchema as generateDrizzleSchema$1 } from 'auth/api';
@@ -10,10 +10,10 @@ import { randomBytes } from 'node:crypto';
10
10
  import { isCI, isTest } from 'std-env';
11
11
  export { defineClientAuth, defineServerAuth } from '../dist/runtime/config.js';
12
12
 
13
- const version = "0.2.2";
13
+ const version = "0.2.3";
14
14
 
15
15
  const CONFIG_EXTENSIONS = [".ts", ".js"];
16
- const CONFIG_EXTENSION_RE = /\.(?:ts|js)$/;
16
+ const CONFIG_EXTENSION_RE$1 = /\.(?:ts|js)$/;
17
17
  const DEFAULT_CONFIG_FILES = {
18
18
  server: "server/auth.config",
19
19
  client: "app/auth.config"
@@ -22,8 +22,12 @@ const OPTION_KEY_BY_KIND = {
22
22
  server: "serverConfig",
23
23
  client: "clientConfig"
24
24
  };
25
+ const PLUGIN_OPTION_KEY_BY_KIND = {
26
+ server: "serverPluginSources",
27
+ client: "clientPluginSources"
28
+ };
25
29
  function stripConfigExtension(path) {
26
- return path.replace(CONFIG_EXTENSION_RE, "");
30
+ return path.replace(CONFIG_EXTENSION_RE$1, "");
27
31
  }
28
32
  function defaultConfigExists(path) {
29
33
  return CONFIG_EXTENSIONS.some((ext) => existsSync(`${path}${ext}`));
@@ -125,6 +129,18 @@ function resolveAuthConfigDescriptors(nuxt, files = {}, dependencies = {}) {
125
129
  client: resolveAuthConfigDescriptor(nuxt, "client", files.client, dependencies)
126
130
  };
127
131
  }
132
+ function resolveAuthPluginSources(nuxt) {
133
+ const resolved = { server: [], client: [] };
134
+ for (const { directory, layer } of getLayerDirectoriesWithConfigs(nuxt)) {
135
+ for (const kind of ["server", "client"]) {
136
+ const sources = layer?.config?.auth?.[PLUGIN_OPTION_KEY_BY_KIND[kind]];
137
+ if (!Array.isArray(sources))
138
+ continue;
139
+ resolved[kind].push(...sources.map((source) => isAbsolute(source) ? source : resolve(directory.root, source)));
140
+ }
141
+ }
142
+ return resolved;
143
+ }
128
144
 
129
145
  function resolveNitroCompatibilityImports(nuxtVersion) {
130
146
  const major = Number.parseInt(nuxtVersion, 10);
@@ -311,6 +327,8 @@ function loadLocalEnv(rootDir) {
311
327
  return;
312
328
  process.loadEnvFile(envPath);
313
329
  }
330
+ const NO_DEFAULT_EXPORT_MESSAGE = "[@nuxtjs/better-auth] auth.config.ts does not export default. Expected: export default defineServerAuth(...)";
331
+ const SCHEMA_NOT_REGENERATED_MESSAGE = "The schema was not regenerated and any existing generated schema file was left unchanged.";
314
332
  async function loadUserAuthConfig(configPath, throwOnError = false, alias, runtimeConfig = {}, rootDir) {
315
333
  const { createJiti } = await import('jiti');
316
334
  const { defineServerAuth: runtimeDefineServerAuth } = await import('../dist/runtime/config.js');
@@ -331,17 +349,18 @@ async function loadUserAuthConfig(configPath, throwOnError = false, alias, runti
331
349
  if (typeof configFn === "function") {
332
350
  return configFn({ runtimeConfig, db: null });
333
351
  }
334
- consola$1.warn("[@nuxtjs/better-auth] auth.config.ts does not export default. Expected: export default defineServerAuth(...)");
335
352
  if (throwOnError) {
353
+ consola$1.warn(NO_DEFAULT_EXPORT_MESSAGE);
336
354
  throw new Error("auth.config.ts must export default defineServerAuth(...)");
337
355
  }
338
- return {};
356
+ consola$1.error(`${NO_DEFAULT_EXPORT_MESSAGE}. ${SCHEMA_NOT_REGENERATED_MESSAGE}`);
357
+ return null;
339
358
  } catch (error) {
340
359
  if (throwOnError) {
341
360
  throw new Error(`Failed to load auth config: ${error instanceof Error ? error.message : error}`);
342
361
  }
343
- consola$1.error("[@nuxtjs/better-auth] Failed to load auth config for schema generation. Schema may be incomplete:", error);
344
- return {};
362
+ consola$1.error(`[@nuxtjs/better-auth] Failed to load auth config for schema generation. ${SCHEMA_NOT_REGENERATED_MESSAGE}`, error);
363
+ return null;
345
364
  } finally {
346
365
  const sharedDefineServerAuth = schemaGlobals.__nuxtBetterAuthDefineServerAuth;
347
366
  if (sharedDefineServerAuth) {
@@ -372,6 +391,7 @@ function getHubCasing(hub) {
372
391
  }
373
392
 
374
393
  const NODE_MODULES_SEGMENT_RE = /[\\/]/;
394
+ const CONFIG_EXTENSION_RE = /\.[cm]?[jt]s$/;
375
395
  function resolveSchemaSecondaryStorageInjection(hubSecondaryStorage, userHasSecondaryStorage, isProduction) {
376
396
  if (hubSecondaryStorage === true)
377
397
  return { inject: false };
@@ -401,11 +421,13 @@ function resolveHubSchemaPath(buildDir, rootDir, dialect, exists = existsSync) {
401
421
  }
402
422
  async function loadAuthOptions(context) {
403
423
  const isProduction = !context.nuxt.options.dev;
404
- const configFile = `${context.serverConfigPath}.ts`;
424
+ const configFile = CONFIG_EXTENSION_RE.test(context.serverConfigPath) ? context.serverConfigPath : `${context.serverConfigPath}.ts`;
405
425
  const alias = Object.fromEntries(
406
426
  Object.entries(context.nuxt.options.alias).filter(([, value]) => typeof value === "string").map(([key, value]) => [key, value])
407
427
  );
408
428
  const userConfig = await loadUserAuthConfig(configFile, isProduction, alias, context.nuxt.options.runtimeConfig, context.nuxt.options.rootDir);
429
+ if (!userConfig)
430
+ return null;
409
431
  const extendedConfig = {};
410
432
  await context.nuxt.callHook("better-auth:config:extend", extendedConfig);
411
433
  const plugins = [...userConfig.plugins || [], ...extendedConfig.plugins || []];
@@ -419,8 +441,17 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola, h
419
441
  return;
420
442
  }
421
443
  const context = { nuxt, serverConfigPath };
444
+ const nuxtWithHubHooks = nuxt;
445
+ nuxtWithHubHooks.hook("hub:db:schema:extend", ({ paths, dialect: hookDialect }) => {
446
+ const schemaPath = resolveHubSchemaPath(nuxt.options.buildDir, nuxt.options.rootDir, hookDialect);
447
+ if (schemaPath)
448
+ paths.unshift(schemaPath);
449
+ });
422
450
  try {
423
- const { userConfig, plugins } = await loadAuthOptions(context);
451
+ const authConfig = await loadAuthOptions(context);
452
+ if (!authConfig)
453
+ return;
454
+ const { userConfig, plugins } = authConfig;
424
455
  const userHasSecondaryStorage = userConfig.secondaryStorage != null;
425
456
  const secondaryStorageResolution = resolveSchemaSecondaryStorageInjection(hubSecondaryStorage, userHasSecondaryStorage, !nuxt.options.dev);
426
457
  if (secondaryStorageResolution.error)
@@ -458,12 +489,6 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola, h
458
489
  addTemplate({ filename: `better-auth/schema.${dialect}.ts`, getContents: () => schemaCode, write: true });
459
490
  addTemplate({ filename: `better-auth/schema.${dialect}.mjs`, getContents: () => schemaCode, write: true });
460
491
  consola.info(`Generated ${dialect} schema (.ts + .mjs)`);
461
- const nuxtWithHubHooks = nuxt;
462
- nuxtWithHubHooks.hook("hub:db:schema:extend", ({ paths, dialect: hookDialect }) => {
463
- const schemaPath = resolveHubSchemaPath(nuxt.options.buildDir, nuxt.options.rootDir, hookDialect);
464
- if (schemaPath)
465
- paths.unshift(schemaPath);
466
- });
467
492
  } catch (error) {
468
493
  const isProduction = !nuxt.options.dev;
469
494
  if (isProduction)
@@ -563,21 +588,24 @@ function resolveDatabaseProvider(input) {
563
588
  }
564
589
 
565
590
  function resolveSecondaryStorage(input) {
566
- const { options, clientOnly, hasNuxtHub, hub } = input;
591
+ const { options, clientOnly, consola } = input;
567
592
  const opt = options.hubSecondaryStorage ?? false;
568
- const useHubKV = opt === true;
569
- const secondaryStorageEnabled = opt === true || opt === "custom";
593
+ if (opt === true) {
594
+ if (clientOnly)
595
+ consola.warn("[nuxt-better-auth] hubSecondaryStorage: true is ignored in clientOnly mode because this module does not run the auth server.");
596
+ else
597
+ consola.warn('[nuxt-better-auth] hubSecondaryStorage: true cannot use NuxtHub KV with Better Auth 1.7 because NuxtHub KV lacks atomic getAndDelete and increment operations. The module will ignore this option and continue without injecting secondary storage. Without a custom secondary store, sessions use the configured database when one exists and rate limiting defaults to process-local memory. Configure rateLimit.storage as "database" or provide rateLimit.customStorage for shared limits. Set auth.hubSecondaryStorage to false to silence this warning, or use "custom" with an atomic secondaryStorage in defineServerAuth(). Track NuxtHub support: https://github.com/nuxt-hub/core/pull/927');
598
+ return { hubSecondaryStorage: false, secondaryStorageEnabled: false };
599
+ }
600
+ const secondaryStorageEnabled = opt === "custom";
570
601
  if (secondaryStorageEnabled && clientOnly) {
571
602
  throw new Error("[nuxt-better-auth] hubSecondaryStorage is not available in clientOnly mode. Either disable clientOnly or remove auth.hubSecondaryStorage.");
572
603
  }
573
- if (useHubKV && (!hasNuxtHub || !hub?.kv)) {
574
- throw new Error("[nuxt-better-auth] hubSecondaryStorage: true requires @nuxthub/core with hub.kv: true. Either add hub.kv: true to your nuxt.config or remove auth.hubSecondaryStorage.");
575
- }
576
- return { useHubKV, secondaryStorageEnabled };
604
+ return { hubSecondaryStorage: opt, secondaryStorageEnabled };
577
605
  }
578
606
  function setupRuntimeConfig(input) {
579
607
  const { nuxt, options, clientOnly, databaseProvider, consola } = input;
580
- const { useHubKV, secondaryStorageEnabled } = resolveSecondaryStorage(input);
608
+ const { hubSecondaryStorage, secondaryStorageEnabled } = resolveSecondaryStorage(input);
581
609
  nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
582
610
  const configuredSiteUrl = nuxt.options.runtimeConfig.public.siteUrl;
583
611
  nuxt.options.runtimeConfig.public.siteUrl = configuredSiteUrl || process.env.NUXT_PUBLIC_SITE_URL || "";
@@ -599,27 +627,44 @@ function setupRuntimeConfig(input) {
599
627
  if (!siteUrl)
600
628
  consola.warn("clientOnly mode: set runtimeConfig.public.siteUrl (or NUXT_PUBLIC_SITE_URL) to your frontend URL");
601
629
  consola.info("clientOnly mode enabled - server utilities (serverAuth, getRequestSession, getUserSession, requireUserSession) are not available");
602
- return { useHubKV, secondaryStorageEnabled };
630
+ return { secondaryStorageEnabled };
603
631
  }
604
632
  const currentSecret = nuxt.options.runtimeConfig.betterAuthSecret;
605
633
  nuxt.options.runtimeConfig.betterAuthSecret = currentSecret || process.env.NUXT_BETTER_AUTH_SECRET || process.env.BETTER_AUTH_SECRET || "";
606
- nuxt.options.runtimeConfig.auth = defu(nuxt.options.runtimeConfig.auth, {
607
- hubSecondaryStorage: options.hubSecondaryStorage ?? false
634
+ const authRuntimeConfig = defu(nuxt.options.runtimeConfig.auth, {
635
+ hubSecondaryStorage
608
636
  });
609
- return { useHubKV, secondaryStorageEnabled };
637
+ authRuntimeConfig.hubSecondaryStorage = hubSecondaryStorage;
638
+ nuxt.options.runtimeConfig.auth = authRuntimeConfig;
639
+ return { secondaryStorageEnabled };
610
640
  }
611
641
 
612
- function buildSecondaryStorageCode(useHubKV) {
613
- if (!useHubKV)
614
- return "export function createSecondaryStorage() { return undefined }";
615
- return `import { kv } from '@nuxthub/kv'
616
- export function createSecondaryStorage() {
642
+ function buildImports(sources) {
617
643
  return {
618
- get: async (key) => kv.get(\`_auth:\${key}\`),
619
- set: async (key, value, ttl) => kv.set(\`_auth:\${key}\`, value, { ttl }),
620
- delete: async (key) => kv.del(\`_auth:\${key}\`),
621
- }
622
- }`;
644
+ imports: sources.map((source, index) => `import plugin${index} from ${JSON.stringify(source)}`).join("\n"),
645
+ names: sources.map((_, index) => `plugin${index}`).join(", ")
646
+ };
647
+ }
648
+ function buildExtendedServerAuthCode(configPath, sources) {
649
+ const plugins = buildImports(sources);
650
+ return `import createAppAuth from ${JSON.stringify(configPath)}
651
+ import { extendServerAuth } from '@nuxtjs/better-auth/config'
652
+ ${plugins.imports}
653
+
654
+ export default extendServerAuth(createAppAuth, [${plugins.names}])
655
+ `;
656
+ }
657
+ function buildExtendedClientAuthCode(configPath, sources) {
658
+ const plugins = buildImports(sources);
659
+ return `import createAppAuthClient from ${JSON.stringify(configPath)}
660
+ import { extendClientAuth } from '@nuxtjs/better-auth/config'
661
+ ${plugins.imports}
662
+
663
+ export default extendClientAuth(createAppAuthClient, [${plugins.names}])
664
+ `;
665
+ }
666
+ function buildSecondaryStorageCode() {
667
+ return "export function createSecondaryStorage() { return undefined }";
623
668
  }
624
669
  function buildDatabaseCode(input) {
625
670
  if (input.provider === "nuxthub") {
@@ -813,6 +858,9 @@ async function resolveAuthModuleSetup(input, dependencies = {}) {
813
858
  configExists: dependencies.configExists
814
859
  });
815
860
  assertConfigPresence(configs, clientOnly);
861
+ const pluginSources = resolveAuthPluginSources(nuxt);
862
+ pluginSources.server.push(...input.registeredPluginSources?.server || []);
863
+ pluginSources.client.push(...input.registeredPluginSources?.client || []);
816
864
  const aliases = {
817
865
  "#auth/server": clientOnly ? void 0 : configs.server.path,
818
866
  "#auth/client": configs.client.path
@@ -850,13 +898,8 @@ async function resolveAuthModuleSetup(input, dependencies = {}) {
850
898
  options,
851
899
  clientOnly,
852
900
  databaseProvider: providerId,
853
- hasNuxtHub,
854
- hub,
855
901
  consola
856
902
  });
857
- if (runtime.useHubKV && !nuxt.options.alias["hub:kv"]) {
858
- throw new Error("[nuxt-better-auth] hub:kv not found. Ensure @nuxthub/core is loaded before this module and hub.kv is enabled.");
859
- }
860
903
  const hasHubDb = providerId === "nuxthub";
861
904
  if (hasHubDb && !nuxt.options.alias["hub:db"]) {
862
905
  throw new Error("[nuxt-better-auth] hub:db not found. Ensure @nuxthub/core is loaded before this module and hub.db is configured.");
@@ -865,6 +908,7 @@ async function resolveAuthModuleSetup(input, dependencies = {}) {
865
908
  clientOnly,
866
909
  configs,
867
910
  aliases,
911
+ pluginSources,
868
912
  hub: {
869
913
  hasNuxtHub,
870
914
  options: hub,
@@ -912,12 +956,7 @@ function registerServerTypeTemplates(input) {
912
956
  filename: "types/auth-secondary-storage.d.ts",
913
957
  getContents: () => `
914
958
  declare module '#auth/secondary-storage' {
915
- interface SecondaryStorage {
916
- get: (key: string) => Promise<string | null>
917
- set: (key: string, value: unknown, ttl?: number) => Promise<void>
918
- delete: (key: string) => Promise<void>
919
- }
920
- export function createSecondaryStorage(): SecondaryStorage | undefined
959
+ export function createSecondaryStorage(): undefined
921
960
  }
922
961
  `
923
962
  }, { nitro: true });
@@ -1371,94 +1410,122 @@ const module$1 = defineNuxtModule({
1371
1410
  process.env.NUXT_BETTER_AUTH_SECRET = generatedSecret;
1372
1411
  await createDefaultAuthConfigFiles(nuxt);
1373
1412
  },
1374
- async setup(options, nuxt) {
1375
- const resolver = createResolver(import.meta.url);
1376
- const nitroImports = resolveNitroCompatibilityImports(nuxt._version);
1377
- nuxt.options.alias["#better-auth/nitro-compat"] = resolver.resolve(`./runtime/server/internal/${nitroImports.runtime}`);
1378
- const setup = await resolveAuthModuleSetup({
1379
- nuxt,
1380
- options,
1381
- runtimeTypesAugmentPath: resolver.resolve("./runtime/types/augment"),
1382
- consola
1383
- });
1384
- if (setup.aliases["#auth/server"])
1385
- nuxt.options.alias["#auth/server"] = setup.aliases["#auth/server"];
1386
- nuxt.options.alias["#auth/client"] = setup.aliases["#auth/client"];
1387
- if (!setup.clientOnly) {
1388
- const secondaryStorageTemplate = addTemplate({
1389
- filename: "better-auth/secondary-storage.mjs",
1390
- getContents: () => buildSecondaryStorageCode(setup.runtime.useHubKV),
1391
- write: true
1392
- });
1393
- nuxt.options.alias["#auth/secondary-storage"] = secondaryStorageTemplate.dst;
1394
- const databaseTemplate = addTemplate({
1395
- filename: "better-auth/database.mjs",
1396
- getContents: () => setup.database.providerDefinition.buildDatabaseCode(setup.database.buildContext),
1397
- write: true
1413
+ setup(options, nuxt) {
1414
+ nuxt.hook("modules:done", async () => {
1415
+ const resolver = createResolver(import.meta.url);
1416
+ const nitroImports = resolveNitroCompatibilityImports(nuxt._version);
1417
+ nuxt.options.alias["#better-auth/nitro-compat"] = resolver.resolve(`./runtime/server/internal/${nitroImports.runtime}`);
1418
+ const registeredPluginSources = {};
1419
+ await nuxt.callHook("better-auth:plugins:extend", registeredPluginSources);
1420
+ for (const source of [...registeredPluginSources.server || [], ...registeredPluginSources.client || []]) {
1421
+ if (!isAbsolute(source))
1422
+ throw new Error(`[nuxt-better-auth] Modules must register absolute plugin source paths. Received: ${source}`);
1423
+ }
1424
+ const setup = await resolveAuthModuleSetup({
1425
+ nuxt,
1426
+ options,
1427
+ runtimeTypesAugmentPath: resolver.resolve("./runtime/types/augment"),
1428
+ consola,
1429
+ registeredPluginSources
1398
1430
  });
1399
- nuxt.options.alias["#auth/database"] = databaseTemplate.dst;
1400
- const schemaTemplate = addTemplate({
1401
- filename: "better-auth/schema.mjs",
1402
- getContents: () => buildSchemaExportCode(setup.database.hasHubDb, setup.database.buildContext?.hubDialect ?? "sqlite"),
1431
+ let serverConfigPath = setup.aliases["#auth/server"];
1432
+ let clientConfigPath = setup.aliases["#auth/client"];
1433
+ if (serverConfigPath && setup.pluginSources.server.length) {
1434
+ const serverConfigCode = buildExtendedServerAuthCode(setup.configs.server.path, setup.pluginSources.server);
1435
+ serverConfigPath = addTemplate({
1436
+ filename: "better-auth/server.config.ts",
1437
+ getContents: () => serverConfigCode,
1438
+ write: true
1439
+ }).dst;
1440
+ await mkdir(dirname(serverConfigPath), { recursive: true });
1441
+ await writeFile(serverConfigPath, serverConfigCode);
1442
+ }
1443
+ if (setup.pluginSources.client.length) {
1444
+ clientConfigPath = addTemplate({
1445
+ filename: "better-auth/client.config.ts",
1446
+ getContents: () => buildExtendedClientAuthCode(setup.configs.client.path, setup.pluginSources.client),
1447
+ write: true
1448
+ }).dst;
1449
+ }
1450
+ if (serverConfigPath)
1451
+ nuxt.options.alias["#auth/server"] = serverConfigPath;
1452
+ nuxt.options.alias["#auth/client"] = clientConfigPath;
1453
+ if (!setup.clientOnly) {
1454
+ const secondaryStorageTemplate = addTemplate({
1455
+ filename: "better-auth/secondary-storage.mjs",
1456
+ getContents: () => buildSecondaryStorageCode(),
1457
+ write: true
1458
+ });
1459
+ nuxt.options.alias["#auth/secondary-storage"] = secondaryStorageTemplate.dst;
1460
+ const databaseTemplate = addTemplate({
1461
+ filename: "better-auth/database.mjs",
1462
+ getContents: () => setup.database.providerDefinition.buildDatabaseCode(setup.database.buildContext),
1463
+ write: true
1464
+ });
1465
+ nuxt.options.alias["#auth/database"] = databaseTemplate.dst;
1466
+ const schemaTemplate = addTemplate({
1467
+ filename: "better-auth/schema.mjs",
1468
+ getContents: () => buildSchemaExportCode(setup.database.hasHubDb, setup.database.buildContext?.hubDialect ?? "sqlite"),
1469
+ write: true
1470
+ });
1471
+ nuxt.options.alias["#auth/schema"] = schemaTemplate.dst;
1472
+ if (setup.schemaGeneration)
1473
+ await ensureSchemaBootstrap(schemaTemplate.dst, setup.database.buildContext?.hubDialect ?? "sqlite");
1474
+ }
1475
+ if (setup.prepareTypes) {
1476
+ registerPrepareTypesHook({
1477
+ nuxt,
1478
+ serverDir: setup.prepareTypes.serverDir,
1479
+ hasHubDb: setup.prepareTypes.hasHubDb
1480
+ });
1481
+ }
1482
+ if (setup.database.providerDefinition) {
1483
+ const setupCtx = {
1484
+ nuxt,
1485
+ options,
1486
+ clientOnly: setup.clientOnly
1487
+ };
1488
+ await setup.database.providerDefinition.setup?.(setupCtx);
1489
+ }
1490
+ const authRouteRulesTemplate = addTemplate({
1491
+ filename: "better-auth/route-rules.mjs",
1492
+ getContents: () => buildAuthRouteRulesCode(collectAuthRouteRules(nuxt)),
1403
1493
  write: true
1404
1494
  });
1405
- nuxt.options.alias["#auth/schema"] = schemaTemplate.dst;
1406
- if (setup.schemaGeneration)
1407
- await ensureSchemaBootstrap(schemaTemplate.dst, setup.database.buildContext?.hubDialect ?? "sqlite");
1408
- }
1409
- if (setup.prepareTypes) {
1410
- registerPrepareTypesHook({
1411
- nuxt,
1412
- serverDir: setup.prepareTypes.serverDir,
1413
- hasHubDb: setup.prepareTypes.hasHubDb
1414
- });
1415
- }
1416
- if (setup.database.providerDefinition) {
1417
- const setupCtx = {
1418
- nuxt,
1419
- options,
1420
- clientOnly: setup.clientOnly
1421
- };
1422
- await setup.database.providerDefinition.setup?.(setupCtx);
1423
- }
1424
- const authRouteRulesTemplate = addTemplate({
1425
- filename: "better-auth/route-rules.mjs",
1426
- getContents: () => buildAuthRouteRulesCode(collectAuthRouteRules(nuxt)),
1427
- write: true
1428
- });
1429
- nuxt.options.alias["#auth/route-rules"] = authRouteRulesTemplate.dst;
1430
- if (setup.serverTypes) {
1431
- registerServerTypeTemplates({
1432
- serverConfigPath: setup.serverTypes.serverConfigPath,
1433
- hasHubDb: setup.serverTypes.hasHubDb,
1495
+ nuxt.options.alias["#auth/route-rules"] = authRouteRulesTemplate.dst;
1496
+ if (setup.serverTypes) {
1497
+ registerServerTypeTemplates({
1498
+ serverConfigPath,
1499
+ hasHubDb: setup.serverTypes.hasHubDb,
1500
+ runtimeTypesPath: resolver.resolve("./runtime/types"),
1501
+ sharedServerConfigSafe: [setup.configs.server.path, ...setup.pluginSources.server].every(isServerConfigSharedTypeSafe),
1502
+ h3TypesPath: nitroImports.h3,
1503
+ nitroTypesPath: nitroImports.types
1504
+ });
1505
+ }
1506
+ if (setup.schemaGeneration) {
1507
+ if (setup.schemaGeneration.externalizeNuxtHubDatabase)
1508
+ registerNuxtHubDatabaseExternalHook(nuxt);
1509
+ await setupBetterAuthSchema(
1510
+ nuxt,
1511
+ serverConfigPath,
1512
+ options,
1513
+ consola,
1514
+ setup.schemaGeneration.hubSecondaryStorage
1515
+ );
1516
+ }
1517
+ registerSharedTypeTemplates({
1518
+ runtimeTypesAugmentPath: setup.sharedTypes.runtimeTypesAugmentPath,
1434
1519
  runtimeTypesPath: resolver.resolve("./runtime/types"),
1435
- sharedServerConfigSafe: isServerConfigSharedTypeSafe(setup.serverTypes.serverConfigPath),
1436
- h3TypesPath: nitroImports.h3,
1437
- nitroTypesPath: nitroImports.types
1520
+ clientConfigPath,
1521
+ h3TypesPath: nitroImports.h3
1438
1522
  });
1439
- }
1440
- if (setup.schemaGeneration) {
1441
- if (setup.schemaGeneration.externalizeNuxtHubDatabase)
1442
- registerNuxtHubDatabaseExternalHook(nuxt);
1443
- await setupBetterAuthSchema(
1444
- nuxt,
1445
- setup.schemaGeneration.serverConfigPath,
1446
- options,
1447
- consola,
1448
- setup.schemaGeneration.hubSecondaryStorage
1449
- );
1450
- }
1451
- registerSharedTypeTemplates({
1452
- runtimeTypesAugmentPath: setup.sharedTypes.runtimeTypesAugmentPath,
1453
- runtimeTypesPath: resolver.resolve("./runtime/types"),
1454
- clientConfigPath: setup.sharedTypes.clientConfigPath,
1455
- h3TypesPath: nitroImports.h3
1523
+ registerTemplateHmrHook(nuxt);
1524
+ registerServerRuntime({ clientOnly: setup.clientOnly, resolve: resolver.resolve });
1525
+ registerAuthMiddlewareHook(nuxt, resolver.resolve);
1526
+ await registerDevtools({ nuxt, clientOnly: setup.clientOnly, hasHubDb: setup.database.hasHubDb, resolve: resolver.resolve });
1527
+ registerRouteRulesMetaHook(nuxt);
1456
1528
  });
1457
- registerTemplateHmrHook(nuxt);
1458
- registerServerRuntime({ clientOnly: setup.clientOnly, resolve: resolver.resolve });
1459
- registerAuthMiddlewareHook(nuxt, resolver.resolve);
1460
- await registerDevtools({ nuxt, clientOnly: setup.clientOnly, hasHubDb: setup.database.hasHubDb, resolve: resolver.resolve });
1461
- registerRouteRulesMetaHook(nuxt);
1462
1529
  }
1463
1530
  });
1464
1531
 
@@ -1,5 +1,5 @@
1
1
  import type { BetterAuthOptions, BetterAuthPlugin } from 'better-auth';
2
- import type { BetterAuthClientOptions } from 'better-auth/client';
2
+ import type { BetterAuthClientOptions, BetterAuthClientPlugin } from 'better-auth/client';
3
3
  import type { ServerAuthContext as BaseServerAuthContext } from './types/augment.js';
4
4
  import { createAuthClient } from 'better-auth/vue';
5
5
  export interface ServerAuthContextExtension {
@@ -17,6 +17,23 @@ export type ClientAuthConfig = Omit<BetterAuthClientOptions, 'baseURL'> & {
17
17
  };
18
18
  export type ServerAuthConfigFn = (ctx: ServerAuthContext) => ServerAuthConfig;
19
19
  export type ClientAuthConfigFn = (ctx: ClientAuthContext) => ClientAuthConfig;
20
+ type ServerPlugins<T> = T extends {
21
+ plugins: infer P extends readonly BetterAuthPlugin[];
22
+ } ? P : [];
23
+ type ClientPlugins<T> = T extends {
24
+ plugins: infer P extends BetterAuthClientPlugin[];
25
+ } ? P : [];
26
+ type ExtendedServerAuthConfig<T, P extends readonly BetterAuthPlugin[]> = Omit<T, 'plugins'> & {
27
+ plugins: [...ServerPlugins<T>, ...P];
28
+ };
29
+ type ExtendedClientAuthConfig<T, P extends BetterAuthClientPlugin[]> = Omit<T, 'plugins'> & {
30
+ plugins: [...ClientPlugins<T>, ...P];
31
+ };
32
+ export type ClientAuthFactory<T extends ClientAuthConfig> = ((baseURL: string) => ReturnType<typeof createAuthClient<T>>) & {
33
+ resolveOptions: (baseURL: string) => T & {
34
+ baseURL: string;
35
+ };
36
+ };
20
37
  export type ModuleDatabaseProviderId = 'none' | 'nuxthub' | (string & {});
21
38
  export type EffectiveDatabaseProviderId = 'user' | ModuleDatabaseProviderId;
22
39
  export interface BetterAuthModuleOptions {
@@ -26,6 +43,10 @@ export interface BetterAuthModuleOptions {
26
43
  serverConfig?: string;
27
44
  /** Client config path. Relative paths resolve from the layer that declares them. Default: 'app/auth.config' */
28
45
  clientConfig?: string;
46
+ /** Server plugin modules. Relative paths resolve from the declaring layer. Sources are additive and are not deduplicated. */
47
+ serverPluginSources?: string[];
48
+ /** Client plugin modules. Relative paths resolve from the declaring layer. Sources are additive and are not deduplicated. */
49
+ clientPluginSources?: string[];
29
50
  redirects?: {
30
51
  /** Where to redirect unauthenticated users. Default: '/login' */
31
52
  login?: string;
@@ -50,9 +71,9 @@ export interface BetterAuthModuleOptions {
50
71
  */
51
72
  redirectQueryKey?: string;
52
73
  /**
53
- * Enable secondary storage for sessions.
54
- * - `true`: Use NuxtHub KV (requires hub.kv: true)
55
- * - `'custom'`: User provides own secondaryStorage in defineServerAuth()
74
+ * Configure secondary storage for sessions.
75
+ * - `true`: Temporarily falls back to database-backed sessions with a setup warning
76
+ * - `'custom'`: User provides an atomic secondaryStorage in defineServerAuth()
56
77
  * - `false` (default): No secondary storage from module
57
78
  */
58
79
  hubSecondaryStorage?: boolean | 'custom';
@@ -84,4 +105,7 @@ export interface AuthPrivateRuntimeConfig {
84
105
  }
85
106
  export declare function defineServerAuth<const R>(config: (ctx: ServerAuthContext) => R & ServerAuthConfig): (ctx: ServerAuthContext) => R;
86
107
  export declare function defineServerAuth<const R>(config: R & ServerAuthConfig): (ctx: ServerAuthContext) => R;
87
- export declare function defineClientAuth<T extends ClientAuthConfig>(config: T | ((ctx: ClientAuthContext) => T)): (baseURL: string) => ReturnType<typeof createAuthClient<T>>;
108
+ export declare function extendServerAuth<const R extends ServerAuthConfig, const P extends readonly BetterAuthPlugin[]>(createConfig: (ctx: ServerAuthContext) => R, plugins: P): (ctx: ServerAuthContext) => ExtendedServerAuthConfig<R, P>;
109
+ export declare function defineClientAuth<T extends ClientAuthConfig>(config: T | ((ctx: ClientAuthContext) => T)): ClientAuthFactory<T>;
110
+ export declare function extendClientAuth<T extends ClientAuthConfig, const P extends BetterAuthClientPlugin[]>(createClient: ClientAuthFactory<T>, plugins: P): ClientAuthFactory<ExtendedClientAuthConfig<T, P>>;
111
+ export {};
@@ -2,12 +2,30 @@ import { createAuthClient } from "better-auth/vue";
2
2
  export function defineServerAuth(config) {
3
3
  return typeof config === "function" ? config : () => config;
4
4
  }
5
+ export function extendServerAuth(createConfig, plugins) {
6
+ return (ctx) => {
7
+ const config = createConfig(ctx);
8
+ const basePlugins = config.plugins || [];
9
+ return { ...config, plugins: [...basePlugins, ...plugins] };
10
+ };
11
+ }
12
+ function createClientAuthFactory(resolveOptions) {
13
+ const factory = ((baseURL) => createAuthClient(resolveOptions(baseURL)));
14
+ factory.resolveOptions = resolveOptions;
15
+ return factory;
16
+ }
5
17
  export function defineClientAuth(config) {
6
- return (baseURL) => {
18
+ return createClientAuthFactory((baseURL) => {
7
19
  const ctx = { siteUrl: baseURL };
8
20
  const resolved = typeof config === "function" ? config(ctx) : config;
9
21
  const { baseURL: configuredBaseURL, ...resolvedOptions } = resolved;
10
- const clientOptions = { ...resolvedOptions, baseURL: configuredBaseURL ?? baseURL };
11
- return createAuthClient(clientOptions);
12
- };
22
+ return { ...resolvedOptions, baseURL: configuredBaseURL ?? baseURL };
23
+ });
24
+ }
25
+ export function extendClientAuth(createClient, plugins) {
26
+ return createClientAuthFactory((baseURL) => {
27
+ const config = createClient.resolveOptions(baseURL);
28
+ const basePlugins = config.plugins || [];
29
+ return { ...config, plugins: [...basePlugins, ...plugins] };
30
+ });
13
31
  }
@@ -25,7 +25,7 @@ export default defineEventHandler(async (event) => {
25
25
  },
26
26
  preserveRedirect: publicAuth?.preserveRedirect ?? true,
27
27
  redirectQueryKey: publicAuth?.redirectQueryKey ?? "redirect",
28
- hubSecondaryStorage: privateAuth?.hubSecondaryStorage ?? false,
28
+ hubSecondaryStorage: privateAuth?.hubSecondaryStorage === true ? false : privateAuth?.hubSecondaryStorage ?? false,
29
29
  useDatabase: publicAuth?.useDatabase ?? false,
30
30
  databaseProvider: publicAuth?.databaseProvider ?? "none"
31
31
  },
@@ -4,6 +4,7 @@ import { betterAuth } from 'better-auth';
4
4
  import createServerAuth from '#auth/server';
5
5
  type AuthOptions = ReturnType<typeof createServerAuth>;
6
6
  type UserAuthConfig = AuthOptions & {
7
+ rateLimit?: BetterAuthOptions['rateLimit'];
7
8
  secrets?: BetterAuthOptions['secrets'];
8
9
  trustedOrigins?: BetterAuthOptions['trustedOrigins'];
9
10
  secondaryStorage?: BetterAuthOptions['secondaryStorage'];
@@ -1,7 +1,6 @@
1
1
  import { betterAuth, env } from "better-auth";
2
2
  import { withoutProtocol } from "ufo";
3
3
  import { createDatabase, db } from "#auth/database";
4
- import { createSecondaryStorage } from "#auth/secondary-storage";
5
4
  import createServerAuth from "#auth/server";
6
5
  import { getRequestHost, getRequestProtocol, useRuntimeConfig } from "../internal/nitro-compat.js";
7
6
  import { resolveCustomSecondaryStorageRequirement } from "./custom-secondary-storage.js";
@@ -9,6 +8,8 @@ const _authCache = /* @__PURE__ */ new Map();
9
8
  const requestAuthKey = Symbol.for("nuxt-better-auth.requestAuth");
10
9
  let _baseURLInferenceLogged = false;
11
10
  let _customSecondaryStorageMisconfigWarned = false;
11
+ let _unsupportedHubSecondaryStorageWarned = false;
12
+ let _secondaryStorageRateLimitFallbackWarned = false;
12
13
  const fallbackRequestAuthContext = /* @__PURE__ */ new WeakMap();
13
14
  function getRequestAuthContext(event) {
14
15
  const eventWithContext = event;
@@ -214,6 +215,10 @@ export function serverAuth(event) {
214
215
  const database = createDatabase(event);
215
216
  const trustedOrigins = withDevTrustedOrigins(userConfig.trustedOrigins);
216
217
  const hubSecondaryStorage = runtimeConfig.auth?.hubSecondaryStorage;
218
+ if (hubSecondaryStorage === true && !_unsupportedHubSecondaryStorageWarned) {
219
+ _unsupportedHubSecondaryStorageWarned = true;
220
+ console.warn("[nuxt-better-auth] Runtime hubSecondaryStorage: true is unsupported with Better Auth 1.7 and will be ignored. The module will preserve secondaryStorage from defineServerAuth(). Remove NUXT_AUTH_HUB_SECONDARY_STORAGE or set it to false.");
221
+ }
217
222
  const customSecondaryStorage = resolveCustomSecondaryStorageRequirement(hubSecondaryStorage, userConfig.secondaryStorage != null, Boolean(import.meta.dev));
218
223
  if (customSecondaryStorage?.shouldThrow)
219
224
  throw new Error(customSecondaryStorage.message);
@@ -221,6 +226,12 @@ export function serverAuth(event) {
221
226
  _customSecondaryStorageMisconfigWarned = true;
222
227
  console.warn(customSecondaryStorage.message);
223
228
  }
229
+ const useMemoryRateLimit = userConfig.rateLimit?.storage === "secondary-storage" && !userConfig.rateLimit.customStorage && !userConfig.secondaryStorage;
230
+ if (useMemoryRateLimit && !_secondaryStorageRateLimitFallbackWarned) {
231
+ _secondaryStorageRateLimitFallbackWarned = true;
232
+ console.warn('[nuxt-better-auth] rateLimit.storage: "secondary-storage" requires secondaryStorage. Falling back to process-local memory. Set rateLimit.storage to "database" or provide rateLimit.customStorage for shared limits.');
233
+ }
234
+ const rateLimit = useMemoryRateLimit ? { ...userConfig.rateLimit, storage: "memory" } : userConfig.rateLimit;
224
235
  if (!database) {
225
236
  const cached = _authCache.get(cacheKey);
226
237
  if (cached) {
@@ -231,8 +242,8 @@ export function serverAuth(event) {
231
242
  }
232
243
  const authOptions = {
233
244
  ...userConfig,
245
+ ...rateLimit ? { rateLimit } : {},
234
246
  ...database ? { database } : {},
235
- ...hubSecondaryStorage === true ? { secondaryStorage: createSecondaryStorage() } : {},
236
247
  secret: betterAuthSecret,
237
248
  baseURL: siteUrl,
238
249
  trustedOrigins
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nuxtjs/better-auth",
3
3
  "type": "module",
4
- "version": "0.2.2",
4
+ "version": "0.2.3",
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",