@onmax/nuxt-better-auth 0.0.2-alpha.26 → 0.0.2-alpha.29

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
@@ -5,12 +5,13 @@ export { BetterAuthModuleOptions, defineClientAuth, defineServerAuth } from '../
5
5
  import { BetterAuthOptions } from 'better-auth';
6
6
  export { AppSession, Auth, AuthActionError, AuthMeta, AuthMode, AuthRouteRules, AuthSession, AuthUser, InferSession, InferUser, RequireSessionOptions, ServerAuthContext, UserMatch } from '../dist/runtime/types.js';
7
7
 
8
- interface DefineServerAuthFn {
8
+ interface RuntimeDefineServerAuthFn {
9
9
  (...args: unknown[]): unknown;
10
10
  _count: number;
11
11
  }
12
12
  declare global {
13
- var defineServerAuth: DefineServerAuthFn | undefined;
13
+ var __nuxtBetterAuthDefineServerAuth: RuntimeDefineServerAuthFn | undefined;
14
+ var defineServerAuth: RuntimeDefineServerAuthFn | undefined;
14
15
  }
15
16
 
16
17
  type DbDialect = 'sqlite' | 'postgresql' | 'mysql';
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onmax/nuxt-better-auth",
3
- "version": "0.0.2-alpha.26",
3
+ "version": "0.0.2-alpha.29",
4
4
  "configKey": "auth",
5
5
  "compatibility": {
6
6
  "nuxt": ">=4.0.0"
package/dist/module.mjs CHANGED
@@ -10,7 +10,7 @@ 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.0.2-alpha.26";
13
+ const version = "0.0.2-alpha.29";
14
14
 
15
15
  function resolveDatabaseProvider(input) {
16
16
  const enabledProviders = Object.entries(input.providers).filter(([_id, provider]) => provider.isEnabled?.(input.context) ?? true);
@@ -122,26 +122,33 @@ function getHubCasing(hub) {
122
122
  return hub.db.casing;
123
123
  }
124
124
 
125
- function resolveSecondaryStorageEnabled(input) {
126
- const { options, clientOnly, hasNuxtHub, hub, consola } = input;
127
- let secondaryStorageEnabled = options.secondaryStorage ?? false;
125
+ function resolveSecondaryStorage(input) {
126
+ const { options, clientOnly, hasNuxtHub, hub } = input;
127
+ const opt = options.hubSecondaryStorage ?? false;
128
+ const useHubKV = opt === true;
129
+ const secondaryStorageEnabled = opt === true || opt === "custom";
128
130
  if (secondaryStorageEnabled && clientOnly) {
129
- consola.warn("secondaryStorage is not available in clientOnly mode. Disabling.");
130
- secondaryStorageEnabled = false;
131
- } else if (secondaryStorageEnabled && (!hasNuxtHub || !hub?.kv)) {
132
- consola.warn("secondaryStorage requires @nuxthub/core with hub.kv: true. Disabling.");
133
- secondaryStorageEnabled = false;
131
+ throw new Error("[nuxt-better-auth] hubSecondaryStorage is not available in clientOnly mode. Either disable clientOnly or remove auth.hubSecondaryStorage.");
134
132
  }
135
- return secondaryStorageEnabled;
133
+ if (useHubKV && (!hasNuxtHub || !hub?.kv)) {
134
+ 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.");
135
+ }
136
+ return { useHubKV, secondaryStorageEnabled };
136
137
  }
137
138
  function setupRuntimeConfig(input) {
138
139
  const { nuxt, options, clientOnly, databaseProvider, consola } = input;
139
- const secondaryStorageEnabled = resolveSecondaryStorageEnabled(input);
140
+ const { useHubKV, secondaryStorageEnabled } = resolveSecondaryStorage(input);
140
141
  nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
141
142
  const configuredSiteUrl = nuxt.options.runtimeConfig.public.siteUrl;
142
143
  if (!configuredSiteUrl && process.env.NUXT_PUBLIC_SITE_URL)
143
144
  nuxt.options.runtimeConfig.public.siteUrl = process.env.NUXT_PUBLIC_SITE_URL;
144
145
  nuxt.options.runtimeConfig.public.auth = defu(nuxt.options.runtimeConfig.public.auth, {
146
+ redirects: {
147
+ login: options.redirects?.login ?? "/login",
148
+ guest: options.redirects?.guest ?? "/",
149
+ authenticated: options.redirects?.authenticated,
150
+ logout: options.redirects?.logout
151
+ },
145
152
  preserveRedirect: options.preserveRedirect ?? true,
146
153
  redirectQueryKey: options.redirectQueryKey ?? "redirect",
147
154
  useDatabase: databaseProvider !== "none",
@@ -156,7 +163,7 @@ function setupRuntimeConfig(input) {
156
163
  if (!siteUrl)
157
164
  consola.warn("clientOnly mode: set runtimeConfig.public.siteUrl (or NUXT_PUBLIC_SITE_URL) to your frontend URL");
158
165
  consola.info("clientOnly mode enabled - server utilities (serverAuth, getAppSession, getUserSession, requireUserSession) are not available");
159
- return { secondaryStorageEnabled };
166
+ return { useHubKV, secondaryStorageEnabled };
160
167
  }
161
168
  const currentSecret = nuxt.options.runtimeConfig.betterAuthSecret;
162
169
  nuxt.options.runtimeConfig.betterAuthSecret = currentSecret || process.env.BETTER_AUTH_SECRET || "";
@@ -168,9 +175,9 @@ function setupRuntimeConfig(input) {
168
175
  throw new Error("[nuxt-better-auth] BETTER_AUTH_SECRET must be at least 32 characters for security");
169
176
  }
170
177
  nuxt.options.runtimeConfig.auth = defu(nuxt.options.runtimeConfig.auth, {
171
- secondaryStorage: secondaryStorageEnabled
178
+ hubSecondaryStorage: options.hubSecondaryStorage ?? false
172
179
  });
173
- return { secondaryStorageEnabled };
180
+ return { useHubKV, secondaryStorageEnabled };
174
181
  }
175
182
 
176
183
  function dialectToProvider(dialect) {
@@ -204,13 +211,16 @@ async function generateDrizzleSchema(authOptions, dialect, schemaOptions) {
204
211
  }
205
212
  async function loadUserAuthConfig(configPath, throwOnError = false) {
206
213
  const { createJiti } = await import('jiti');
207
- const { defineServerAuth } = await import('../dist/runtime/config.js');
214
+ const { defineServerAuth: runtimeDefineServerAuth } = await import('../dist/runtime/config.js');
208
215
  const jiti = createJiti(import.meta.url, { interopDefault: true, moduleCache: false });
216
+ if (!globalThis.__nuxtBetterAuthDefineServerAuth) {
217
+ runtimeDefineServerAuth._count = 0;
218
+ globalThis.__nuxtBetterAuthDefineServerAuth = runtimeDefineServerAuth;
219
+ }
209
220
  if (!globalThis.defineServerAuth) {
210
- defineServerAuth._count = 0;
211
- globalThis.defineServerAuth = defineServerAuth;
221
+ globalThis.defineServerAuth = globalThis.__nuxtBetterAuthDefineServerAuth;
212
222
  }
213
- globalThis.defineServerAuth._count++;
223
+ globalThis.__nuxtBetterAuthDefineServerAuth._count++;
214
224
  try {
215
225
  const mod = await jiti.import(configPath);
216
226
  const configFn = mod.default;
@@ -229,13 +239,31 @@ async function loadUserAuthConfig(configPath, throwOnError = false) {
229
239
  consola$1.error("[@onmax/nuxt-better-auth] Failed to load auth config for schema generation. Schema may be incomplete:", error);
230
240
  return {};
231
241
  } finally {
232
- globalThis.defineServerAuth._count--;
233
- if (!globalThis.defineServerAuth._count) {
234
- globalThis.defineServerAuth = void 0;
242
+ const sharedDefineServerAuth = globalThis.__nuxtBetterAuthDefineServerAuth;
243
+ if (sharedDefineServerAuth) {
244
+ sharedDefineServerAuth._count--;
245
+ if (!sharedDefineServerAuth._count) {
246
+ globalThis.__nuxtBetterAuthDefineServerAuth = void 0;
247
+ if (globalThis.defineServerAuth === sharedDefineServerAuth) {
248
+ globalThis.defineServerAuth = void 0;
249
+ }
250
+ }
235
251
  }
236
252
  }
237
253
  }
238
254
 
255
+ function resolveSchemaSecondaryStorageInjection(hubSecondaryStorage, userHasSecondaryStorage, isProduction) {
256
+ if (hubSecondaryStorage === true)
257
+ return { inject: true };
258
+ if (hubSecondaryStorage !== "custom")
259
+ return { inject: false };
260
+ if (userHasSecondaryStorage)
261
+ return { inject: true };
262
+ const message = '[nuxt-better-auth] hubSecondaryStorage: "custom" requires secondaryStorage in defineServerAuth() to omit the session table from the generated schema.';
263
+ if (isProduction)
264
+ return { inject: false, error: message };
265
+ return { inject: false, warn: message };
266
+ }
239
267
  function isInsideNodeModules(path) {
240
268
  return path.split(/[\\/]/).includes("node_modules");
241
269
  }
@@ -260,7 +288,7 @@ async function loadAuthOptions(context) {
260
288
  const plugins = [...userConfig.plugins || [], ...extendedConfig.plugins || []];
261
289
  return { userConfig, plugins };
262
290
  }
263
- async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola) {
291
+ async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola, hubSecondaryStorage) {
264
292
  const hub = nuxt.options.hub;
265
293
  const dialect = getHubDialect(hub);
266
294
  if (!dialect || !["sqlite", "postgresql", "mysql"].includes(dialect)) {
@@ -270,10 +298,16 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola) {
270
298
  const context = { nuxt, serverConfigPath };
271
299
  try {
272
300
  const { userConfig, plugins } = await loadAuthOptions(context);
301
+ const userHasSecondaryStorage = userConfig.secondaryStorage != null;
302
+ const secondaryStorageResolution = resolveSchemaSecondaryStorageInjection(hubSecondaryStorage, userHasSecondaryStorage, !nuxt.options.dev);
303
+ if (secondaryStorageResolution.error)
304
+ throw new Error(secondaryStorageResolution.error);
305
+ if (secondaryStorageResolution.warn)
306
+ consola.warn(secondaryStorageResolution.warn);
273
307
  const authOptions = {
274
308
  ...userConfig,
275
309
  plugins,
276
- secondaryStorage: options.secondaryStorage ? { get: async (_key) => null, set: async (_key, _value, _ttl) => {
310
+ secondaryStorage: secondaryStorageResolution.inject ? { get: async (_key) => null, set: async (_key, _value, _ttl) => {
277
311
  }, delete: async (_key) => {
278
312
  } } : void 0
279
313
  };
@@ -328,13 +362,21 @@ function appendSecretToEnv(rootDir, secret) {
328
362
  `;
329
363
  writeFileSync(envPath, content, "utf-8");
330
364
  }
331
- async function promptForSecret(rootDir, consola) {
365
+ async function promptForSecret(rootDir, consola, options = {}) {
366
+ const configuredSecret = options.configuredSecret?.trim();
367
+ if (configuredSecret)
368
+ return void 0;
332
369
  if (process.env.BETTER_AUTH_SECRET || hasEnvSecret(rootDir))
333
370
  return void 0;
371
+ const hasTty = Boolean(process.stdin.isTTY && process.stdout.isTTY);
372
+ if (options.prepare || !hasTty) {
373
+ consola.warn("[nuxt-better-auth] Skipping BETTER_AUTH_SECRET prompt (non-interactive). Set BETTER_AUTH_SECRET or NUXT_BETTER_AUTH_SECRET.");
374
+ return void 0;
375
+ }
334
376
  if (isCI || isTest) {
335
377
  const secret2 = generateSecret();
336
378
  appendSecretToEnv(rootDir, secret2);
337
- consola.info("Generated BETTER_AUTH_SECRET and added to .env (CI mode)");
379
+ consola.info("Generated BETTER_AUTH_SECRET and added to .env (CI/test mode)");
338
380
  return secret2;
339
381
  }
340
382
  consola.box("BETTER_AUTH_SECRET is required for authentication.\nThis will be appended to your .env file.");
@@ -375,8 +417,8 @@ Proceed?`, { type: "confirm", initial: true, cancel: "null" });
375
417
  return secret;
376
418
  }
377
419
 
378
- function buildSecondaryStorageCode(enabled) {
379
- if (!enabled)
420
+ function buildSecondaryStorageCode(useHubKV) {
421
+ if (!useHubKV)
380
422
  return "export function createSecondaryStorage() { return undefined }";
381
423
  return `import { kv } from '@nuxthub/kv'
382
424
  export function createSecondaryStorage() {
@@ -587,12 +629,14 @@ const module$1 = defineNuxtModule({
587
629
  clientOnly: false,
588
630
  serverConfig: "server/auth.config",
589
631
  clientConfig: "app/auth.config",
632
+ redirects: { login: "/login", guest: "/" },
590
633
  preserveRedirect: true,
591
634
  redirectQueryKey: "redirect",
592
- secondaryStorage: false
635
+ hubSecondaryStorage: false
593
636
  },
594
637
  async onInstall(nuxt) {
595
- const generatedSecret = await promptForSecret(nuxt.options.rootDir, consola);
638
+ const configuredSecret = nuxt.options.runtimeConfig?.betterAuthSecret;
639
+ const generatedSecret = await promptForSecret(nuxt.options.rootDir, consola, { configuredSecret, prepare: Boolean(nuxt.options._prepare) });
596
640
  if (generatedSecret)
597
641
  process.env.BETTER_AUTH_SECRET = generatedSecret;
598
642
  await createDefaultAuthConfigFiles(nuxt.options.rootDir, nuxt.options.srcDir);
@@ -666,7 +710,7 @@ const module$1 = defineNuxtModule({
666
710
  const resolvedProvider = resolveDatabaseProvider({ providers, context: enabledCtx });
667
711
  databaseProvider = resolvedProvider.id;
668
712
  hasHubDb = databaseProvider === "nuxthub";
669
- const { secondaryStorageEnabled } = setupRuntimeConfig({
713
+ const { useHubKV } = setupRuntimeConfig({
670
714
  nuxt,
671
715
  options,
672
716
  clientOnly,
@@ -675,12 +719,12 @@ const module$1 = defineNuxtModule({
675
719
  hub,
676
720
  consola
677
721
  });
678
- if (secondaryStorageEnabled && !nuxt.options.alias["hub:kv"]) {
722
+ if (useHubKV && !nuxt.options.alias["hub:kv"]) {
679
723
  throw new Error("[nuxt-better-auth] hub:kv not found. Ensure @nuxthub/core is loaded before this module and hub.kv is enabled.");
680
724
  }
681
725
  const secondaryStorageTemplate = addTemplate({
682
726
  filename: "better-auth/secondary-storage.mjs",
683
- getContents: () => buildSecondaryStorageCode(secondaryStorageEnabled),
727
+ getContents: () => buildSecondaryStorageCode(useHubKV),
684
728
  write: true
685
729
  });
686
730
  nuxt.options.alias["#auth/secondary-storage"] = secondaryStorageTemplate.dst;
@@ -715,7 +759,7 @@ export { schema }
715
759
  runtimeTypesPath: resolver.resolve("./runtime/types")
716
760
  });
717
761
  if (hasHubDb)
718
- await setupBetterAuthSchema(nuxt, serverConfigPath, options, consola);
762
+ await setupBetterAuthSchema(nuxt, serverConfigPath, options, consola, options.hubSecondaryStorage ?? false);
719
763
  }
720
764
  registerSharedTypeTemplates({
721
765
  runtimeTypesAugmentPath: resolver.resolve("./runtime/types/augment"),
@@ -1,5 +1,5 @@
1
1
  import createAppAuthClient from "#auth/client";
2
- import { computed, nextTick, useNuxtApp, useRequestHeaders, useRequestURL, useRuntimeConfig, useState, watch } from "#imports";
2
+ import { computed, navigateTo, nextTick, useNuxtApp, useRequestHeaders, useRequestURL, useRuntimeConfig, useState, watch } from "#imports";
3
3
  import { normalizeAuthActionError } from "../internal/auth-action-error.js";
4
4
  let _sessionSignalListenerBound = false;
5
5
  let _client = null;
@@ -128,6 +128,30 @@ export function useUserSession() {
128
128
  }, 5e3);
129
129
  });
130
130
  }
131
+ function isSafeLocalRedirect(redirect) {
132
+ if (typeof redirect !== "string")
133
+ return;
134
+ if (!redirect.startsWith("/") || redirect.startsWith("//"))
135
+ return;
136
+ return redirect;
137
+ }
138
+ function resolvePostAuthRedirect() {
139
+ const authConfig = runtimeConfig.public.auth;
140
+ const redirectQueryKey = authConfig?.redirectQueryKey ?? "redirect";
141
+ const queryRedirect = requestURL.searchParams?.get(redirectQueryKey);
142
+ const safeQueryRedirect = isSafeLocalRedirect(queryRedirect);
143
+ if (safeQueryRedirect)
144
+ return safeQueryRedirect;
145
+ return isSafeLocalRedirect(authConfig?.redirects?.authenticated);
146
+ }
147
+ function resolvePostAuthSuccessRedirect() {
148
+ const target = resolvePostAuthRedirect();
149
+ if (!target)
150
+ return;
151
+ return async () => {
152
+ await navigateTo(target);
153
+ };
154
+ }
131
155
  function wrapOnSuccess(cb) {
132
156
  return async (ctx) => {
133
157
  await fetchSession({ force: true });
@@ -146,6 +170,12 @@ export function useUserSession() {
146
170
  const fetchOptions = isRecord(dataRecord?.fetchOptions) ? dataRecord.fetchOptions : void 0;
147
171
  const nestedOnSuccess = fetchOptions?.onSuccess;
148
172
  const topLevelOnSuccess = optionsRecord?.onSuccess;
173
+ const fallbackOnSuccess = resolvePostAuthSuccessRedirect();
174
+ const wrappedFallbackOnSuccess = fallbackOnSuccess && wrapOnSuccess(async () => {
175
+ if (!loggedIn.value)
176
+ return;
177
+ await fallbackOnSuccess();
178
+ });
149
179
  if (typeof nestedOnSuccess === "function") {
150
180
  const nextData = {
151
181
  ...dataRecord,
@@ -163,6 +193,23 @@ export function useUserSession() {
163
193
  };
164
194
  return method(data, nextOptions);
165
195
  }
196
+ if (wrappedFallbackOnSuccess) {
197
+ if (fetchOptions) {
198
+ const nextData = {
199
+ ...dataRecord,
200
+ fetchOptions: {
201
+ ...fetchOptions,
202
+ onSuccess: wrappedFallbackOnSuccess
203
+ }
204
+ };
205
+ return method(nextData, options);
206
+ }
207
+ const nextOptions = {
208
+ ...optionsRecord,
209
+ onSuccess: wrappedFallbackOnSuccess
210
+ };
211
+ return method(data, nextOptions);
212
+ }
166
213
  return method(data, options);
167
214
  });
168
215
  }
@@ -229,8 +276,14 @@ export function useUserSession() {
229
276
  throw new Error("signOut can only be called on client-side");
230
277
  await client.signOut();
231
278
  clearSession();
232
- if (options?.onSuccess)
279
+ if (options?.onSuccess) {
233
280
  await options.onSuccess();
281
+ return;
282
+ }
283
+ const authConfig = runtimeConfig.public.auth;
284
+ const logoutRedirect = authConfig?.redirects?.logout;
285
+ if (logoutRedirect)
286
+ await navigateTo(logoutRedirect);
234
287
  }
235
288
  return {
236
289
  client,
@@ -1,3 +1,5 @@
1
1
  import type { AppAuthClient } from '#nuxt-better-auth';
2
- import type { ActionHandleMap } from '../internal/auth-action-handles.js';
3
- export declare function useUserSignIn(): ActionHandleMap<NonNullable<AppAuthClient>['signIn']>;
2
+ import type { ActionHandleFor } from '../internal/auth-action-handles.js';
3
+ type SignIn = NonNullable<AppAuthClient>['signIn'];
4
+ export declare function useUserSignIn<MethodKey extends keyof SignIn>(method: MethodKey): ActionHandleFor<SignIn[MethodKey]>;
5
+ export {};
@@ -1,5 +1,8 @@
1
1
  import { useUserSession } from "#imports";
2
2
  import { createActionHandles } from "../internal/auth-action-handles.js";
3
- export function useUserSignIn() {
4
- return createActionHandles(() => useUserSession().signIn, "signIn");
3
+ export function useUserSignIn(method) {
4
+ if (method === void 0 || method === null)
5
+ throw new TypeError("useUserSignIn(method) requires a sign-in method key");
6
+ const handles = createActionHandles(() => useUserSession().signIn, "signIn");
7
+ return handles[method];
5
8
  }
@@ -1,3 +1,5 @@
1
1
  import type { AppAuthClient } from '#nuxt-better-auth';
2
- import type { ActionHandleMap } from '../internal/auth-action-handles.js';
3
- export declare function useUserSignUp(): ActionHandleMap<NonNullable<AppAuthClient>['signUp']>;
2
+ import type { ActionHandleFor } from '../internal/auth-action-handles.js';
3
+ type SignUp = NonNullable<AppAuthClient>['signUp'];
4
+ export declare function useUserSignUp<MethodKey extends keyof SignUp>(method: MethodKey): ActionHandleFor<SignUp[MethodKey]>;
5
+ export {};
@@ -1,5 +1,8 @@
1
1
  import { useUserSession } from "#imports";
2
2
  import { createActionHandles } from "../internal/auth-action-handles.js";
3
- export function useUserSignUp() {
4
- return createActionHandles(() => useUserSession().signUp, "signUp");
3
+ export function useUserSignUp(method) {
4
+ if (method === void 0 || method === null)
5
+ throw new TypeError("useUserSignUp(method) requires a sign-up method key");
6
+ const handles = createActionHandles(() => useUserSession().signUp, "signUp");
7
+ return handles[method];
5
8
  }
@@ -1,13 +1,11 @@
1
- import type { ComputedRef, Ref } from 'vue';
1
+ import type { Ref } from 'vue';
2
2
  import type { AuthActionError } from '../../types.js';
3
3
  export type UserAuthActionStatus = 'idle' | 'pending' | 'success' | 'error';
4
4
  export interface UserAuthActionHandle<TArgs extends unknown[], TResult> {
5
5
  execute: (...args: TArgs) => Promise<void>;
6
6
  status: Ref<UserAuthActionStatus>;
7
- pending: ComputedRef<boolean>;
8
7
  data: Ref<TResult | null>;
9
8
  error: Ref<AuthActionError | null>;
10
- errorMessage: ComputedRef<string | null>;
11
9
  }
12
10
  export type ActionHandleFor<T> = T extends (...args: infer A) => Promise<infer R> ? UserAuthActionHandle<A, R> : never;
13
11
  export type ActionHandleMap<T> = {
@@ -1,4 +1,4 @@
1
- import { computed, ref } from "#imports";
1
+ import { ref } from "#imports";
2
2
  import { normalizeAuthActionError } from "./auth-action-error.js";
3
3
  function isRecord(value) {
4
4
  return Boolean(value && typeof value === "object");
@@ -14,8 +14,6 @@ function createActionHandle(getMethod) {
14
14
  const status = ref("idle");
15
15
  const data = ref(null);
16
16
  const error = ref(null);
17
- const pending = computed(() => status.value === "pending");
18
- const errorMessage = computed(() => error.value?.message ?? null);
19
17
  let latestCallId = 0;
20
18
  const run = async (...args) => {
21
19
  const callId = ++latestCallId;
@@ -53,10 +51,8 @@ function createActionHandle(getMethod) {
53
51
  return {
54
52
  execute,
55
53
  status,
56
- pending,
57
54
  data,
58
- error,
59
- errorMessage
55
+ error
60
56
  };
61
57
  }
62
58
  export function createActionHandles(getTarget, targetName) {
@@ -25,13 +25,13 @@ export default defineNuxtRouteMiddleware(async (to) => {
25
25
  const redirectTo = typeof auth === "object" ? auth.redirectTo : void 0;
26
26
  if (mode === "guest") {
27
27
  if (loggedIn.value)
28
- return navigateTo(redirectTo ?? "/");
28
+ return navigateTo(redirectTo ?? config?.redirects?.guest ?? "/");
29
29
  return;
30
30
  }
31
31
  if (!loggedIn.value) {
32
32
  const resolved = resolveLoginRedirect({
33
33
  route: to,
34
- loginTarget: redirectTo ?? "/login",
34
+ loginTarget: redirectTo ?? config?.redirects?.login ?? "/login",
35
35
  config
36
36
  });
37
37
  return resolved.external ? navigateTo(resolved.to, { external: true }) : navigateTo(resolved.to);
@@ -379,8 +379,8 @@ function getAccountActions(row) {
379
379
  </UBadge>
380
380
  </div>
381
381
  <div class="config-row">
382
- <span class="config-label">KV</span><UBadge :color="configData.config.module?.secondaryStorage ? 'success' : 'neutral'" variant="subtle" size="sm">
383
- {{ configData.config.module?.secondaryStorage ? "On" : "Off" }}
382
+ <span class="config-label">KV</span><UBadge :color="configData.config.module?.hubSecondaryStorage ? 'success' : 'neutral'" variant="subtle" size="sm">
383
+ {{ configData.config.module?.hubSecondaryStorage ? configData.config.module.hubSecondaryStorage === "custom" ? "Custom" : "On" : "Off" }}
384
384
  </UBadge>
385
385
  </div>
386
386
  </div>
@@ -24,6 +24,16 @@ export interface BetterAuthModuleOptions {
24
24
  serverConfig?: string;
25
25
  /** Client config path relative to rootDir. Default: 'app/auth.config' */
26
26
  clientConfig?: string;
27
+ redirects?: {
28
+ /** Where to redirect unauthenticated users. Default: '/login' */
29
+ login?: string;
30
+ /** Where to redirect authenticated users on guest-only routes. Default: '/' */
31
+ guest?: string;
32
+ /** Where to navigate after successful signIn/signUp when no onSuccess is provided. Default: no automatic navigation */
33
+ authenticated?: string;
34
+ /** Where to navigate after logout. Default: no automatic navigation */
35
+ logout?: string;
36
+ };
27
37
  /**
28
38
  * When redirecting unauthenticated users to the login route, append a query param
29
39
  * containing the originally requested path (for safe "return-to" redirects).
@@ -47,8 +57,13 @@ export interface BetterAuthModuleOptions {
47
57
  */
48
58
  skipHydratedSsrGetSession?: boolean;
49
59
  };
50
- /** Enable KV secondary storage for sessions. Requires hub.kv: true */
51
- secondaryStorage?: boolean;
60
+ /**
61
+ * Enable secondary storage for sessions.
62
+ * - `true`: Use NuxtHub KV (requires hub.kv: true)
63
+ * - `'custom'`: User provides own secondaryStorage in defineServerAuth()
64
+ * - `false` (default): No secondary storage from module
65
+ */
66
+ hubSecondaryStorage?: boolean | 'custom';
52
67
  /** Schema generation options. Must match drizzleAdapter config. */
53
68
  schema?: {
54
69
  /** Plural table names: user → users. Default: false */
@@ -58,6 +73,12 @@ export interface BetterAuthModuleOptions {
58
73
  };
59
74
  }
60
75
  export interface AuthRuntimeConfig {
76
+ redirects: {
77
+ login: string;
78
+ guest: string;
79
+ authenticated?: string;
80
+ logout?: string;
81
+ };
61
82
  preserveRedirect: boolean;
62
83
  redirectQueryKey: string;
63
84
  useDatabase: boolean;
@@ -68,7 +89,7 @@ export interface AuthRuntimeConfig {
68
89
  };
69
90
  }
70
91
  export interface AuthPrivateRuntimeConfig {
71
- secondaryStorage: boolean;
92
+ hubSecondaryStorage: boolean | 'custom';
72
93
  }
73
94
  export declare function defineServerAuth<const R>(config: (ctx: ServerAuthContext) => R & ServerAuthConfig): (ctx: ServerAuthContext) => R;
74
95
  export declare function defineServerAuth<const R>(config: R & ServerAuthConfig): (ctx: ServerAuthContext) => R;
@@ -1,29 +1,35 @@
1
1
  declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
2
  config: {
3
3
  module: {
4
+ redirects: {
5
+ login: string;
6
+ guest: string;
7
+ authenticated: string | undefined;
8
+ logout: string | undefined;
9
+ };
4
10
  preserveRedirect: boolean;
5
11
  redirectQueryKey: string;
6
- secondaryStorage: boolean;
12
+ hubSecondaryStorage: boolean | "custom";
7
13
  useDatabase: boolean;
8
14
  databaseProvider: "none" | "nuxthub";
9
15
  };
10
16
  server: {
11
- baseURL: any;
12
- basePath: any;
17
+ baseURL: string | undefined;
18
+ basePath: string;
13
19
  socialProviders: string[];
14
- plugins: any;
15
- trustedOrigins: any;
16
- configuredTrustedOrigins: any;
20
+ plugins: string[];
21
+ trustedOrigins: string[];
22
+ configuredTrustedOrigins: string[];
17
23
  session: {
18
24
  expiresIn: string;
19
25
  updateAge: string;
20
- cookieCache: any;
26
+ cookieCache: boolean;
21
27
  };
22
28
  emailAndPassword: boolean;
23
- rateLimit: any;
29
+ rateLimit: boolean;
24
30
  advanced: {
25
- useSecureCookies: any;
26
- disableCSRFCheck: any;
31
+ useSecureCookies: string | boolean;
32
+ disableCSRFCheck: boolean;
27
33
  };
28
34
  };
29
35
  };
@@ -18,9 +18,15 @@ export default defineEventHandler(async (event) => {
18
18
  config: {
19
19
  // Module config (nuxt.config.ts)
20
20
  module: {
21
+ redirects: {
22
+ login: publicAuth?.redirects?.login ?? "/login",
23
+ guest: publicAuth?.redirects?.guest ?? "/",
24
+ authenticated: publicAuth?.redirects?.authenticated,
25
+ logout: publicAuth?.redirects?.logout
26
+ },
21
27
  preserveRedirect: publicAuth?.preserveRedirect ?? true,
22
28
  redirectQueryKey: publicAuth?.redirectQueryKey ?? "redirect",
23
- secondaryStorage: privateAuth?.secondaryStorage ?? false,
29
+ hubSecondaryStorage: privateAuth?.hubSecondaryStorage ?? false,
24
30
  useDatabase: publicAuth?.useDatabase ?? false,
25
31
  databaseProvider: publicAuth?.databaseProvider ?? "none"
26
32
  },
@@ -1,7 +1,6 @@
1
- import type { Auth } from 'better-auth';
2
1
  import type { H3Event } from 'h3';
3
- import createServerAuth from '#auth/server';
4
- type AuthInstance = Auth<ReturnType<typeof createServerAuth>>;
2
+ import { betterAuth } from 'better-auth';
3
+ type AuthInstance = ReturnType<typeof betterAuth>;
5
4
  /** Returns Better Auth instance. Caches per resolved host (or single instance when siteUrl is explicit). */
6
5
  export declare function serverAuth(event?: H3Event): AuthInstance;
7
6
  export {};
@@ -5,8 +5,10 @@ import { betterAuth } from "better-auth";
5
5
  import { getRequestHost, getRequestProtocol } from "h3";
6
6
  import { useRuntimeConfig } from "nitropack/runtime";
7
7
  import { withoutProtocol } from "ufo";
8
+ import { resolveCustomSecondaryStorageRequirement } from "./custom-secondary-storage.js";
8
9
  const _authCache = /* @__PURE__ */ new Map();
9
10
  let _baseURLInferenceLogged = false;
11
+ let _customSecondaryStorageMisconfigWarned = false;
10
12
  function normalizeLoopbackOrigin(origin) {
11
13
  if (!import.meta.dev)
12
14
  return origin;
@@ -189,10 +191,18 @@ export function serverAuth(event) {
189
191
  const database = createDatabase();
190
192
  const userConfig = createServerAuth({ runtimeConfig, db });
191
193
  const trustedOrigins = withDevTrustedOrigins(userConfig.trustedOrigins, Boolean(hasExplicitSiteUrl));
194
+ const hubSecondaryStorage = runtimeConfig.auth?.hubSecondaryStorage;
195
+ const customSecondaryStorage = resolveCustomSecondaryStorageRequirement(hubSecondaryStorage, userConfig.secondaryStorage != null, Boolean(import.meta.dev));
196
+ if (customSecondaryStorage?.shouldThrow)
197
+ throw new Error(customSecondaryStorage.message);
198
+ if (customSecondaryStorage?.shouldWarn && !_customSecondaryStorageMisconfigWarned) {
199
+ _customSecondaryStorageMisconfigWarned = true;
200
+ console.warn(customSecondaryStorage.message);
201
+ }
192
202
  const auth = betterAuth({
193
203
  ...userConfig,
194
204
  ...database && { database },
195
- secondaryStorage: createSecondaryStorage(),
205
+ ...hubSecondaryStorage === true && { secondaryStorage: createSecondaryStorage() },
196
206
  secret: runtimeConfig.betterAuthSecret,
197
207
  baseURL: siteUrl,
198
208
  trustedOrigins
@@ -0,0 +1,6 @@
1
+ export type HubSecondaryStorageMode = boolean | 'custom' | undefined;
2
+ export declare function resolveCustomSecondaryStorageRequirement(hubSecondaryStorage: HubSecondaryStorageMode, userHasSecondaryStorage: boolean, isDev: boolean): {
3
+ shouldThrow: boolean;
4
+ shouldWarn: boolean;
5
+ message: string;
6
+ } | null;
@@ -0,0 +1,8 @@
1
+ export function resolveCustomSecondaryStorageRequirement(hubSecondaryStorage, userHasSecondaryStorage, isDev) {
2
+ if (hubSecondaryStorage !== "custom")
3
+ return null;
4
+ if (userHasSecondaryStorage)
5
+ return null;
6
+ const message = '[nuxt-better-auth] hubSecondaryStorage: "custom" requires secondaryStorage in defineServerAuth().';
7
+ return { shouldThrow: !isDev, shouldWarn: isDev, message };
8
+ }
@@ -1,5 +1,5 @@
1
+ import type { AuthUser, UserMatch } from '#nuxt-better-auth';
1
2
  import type { NitroRouteRules } from 'nitropack/types';
2
- import type { AuthUser, UserMatch } from './types/augment.js';
3
3
  export type { AppSession, AuthSession, AuthUser, RequireSessionOptions, ServerAuthContext, UserMatch, UserSessionComposable } from './types/augment.js';
4
4
  export type { Auth, InferPluginTypes, InferSessionFromClient as InferSession, InferUserFromClient as InferUser } from 'better-auth';
5
5
  export interface AuthActionError {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@onmax/nuxt-better-auth",
3
3
  "type": "module",
4
- "version": "0.0.2-alpha.26",
4
+ "version": "0.0.2-alpha.29",
5
5
  "packageManager": "pnpm@10.15.1",
6
6
  "description": "Nuxt module for Better Auth integration with NuxtHub, route protection, session management, and role-based access",
7
7
  "author": "onmax",
@@ -56,7 +56,7 @@
56
56
  "lint:fix": "eslint . --fix",
57
57
  "typecheck": "vue-tsc --noEmit",
58
58
  "typecheck:runtime-server": "tsc --noEmit --pretty false -p src/runtime/server/tsconfig.json",
59
- "typecheck:playground": "cd playground && vue-tsc --noEmit",
59
+ "typecheck:playground": "pnpm -C playground exec nuxi prepare && pnpm -C playground exec vue-tsc --noEmit -p .nuxt/tsconfig.app.json",
60
60
  "test": "vitest run",
61
61
  "test:watch": "vitest watch"
62
62
  },
@@ -116,8 +116,7 @@
116
116
  "workerd"
117
117
  ],
118
118
  "patchedDependencies": {
119
- "@peculiar/x509@1.14.2": "patches/@peculiar__x509@1.14.2.patch",
120
- "unenv@2.0.0-rc.24": "patches/unenv@2.0.0-rc.24.patch"
119
+ "@peculiar/x509@1.14.2": "patches/@peculiar__x509@1.14.2.patch"
121
120
  },
122
121
  "overrides": {
123
122
  "reka-ui": "^2.6.1"