@onmax/nuxt-better-auth 0.0.2-alpha.23 → 0.0.2-alpha.25

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.
Files changed (29) hide show
  1. package/dist/module.d.mts +1 -1
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +50 -15
  4. package/dist/runtime/app/composables/useUserSession.js +31 -7
  5. package/dist/runtime/app/composables/useUserSignIn.d.ts +3 -0
  6. package/dist/runtime/app/composables/useUserSignIn.js +5 -0
  7. package/dist/runtime/app/composables/useUserSignUp.d.ts +3 -0
  8. package/dist/runtime/app/composables/useUserSignUp.js +5 -0
  9. package/dist/runtime/app/internal/auth-action-handles.d.ts +13 -0
  10. package/dist/runtime/app/internal/auth-action-handles.js +62 -0
  11. package/dist/runtime/app/middleware/auth.global.js +39 -3
  12. package/dist/runtime/app/pages/__better-auth-devtools.vue +1 -7
  13. package/dist/runtime/config.d.ts +17 -12
  14. package/dist/runtime/server/api/_better-auth/accounts.get.js +3 -2
  15. package/dist/runtime/server/api/_better-auth/config.get.d.ts +3 -5
  16. package/dist/runtime/server/api/_better-auth/config.get.js +2 -1
  17. package/dist/runtime/server/api/_better-auth/sessions.delete.js +3 -2
  18. package/dist/runtime/server/api/_better-auth/sessions.get.d.ts +3 -1
  19. package/dist/runtime/server/api/_better-auth/sessions.get.js +3 -2
  20. package/dist/runtime/server/api/_better-auth/users.get.js +3 -2
  21. package/dist/runtime/server/middleware/route-access.js +1 -0
  22. package/dist/runtime/server/tsconfig.json +9 -1
  23. package/dist/runtime/server/utils/session.d.ts +4 -16
  24. package/dist/runtime/server/utils/session.js +42 -4
  25. package/dist/runtime/server/virtual-modules.d.ts +22 -0
  26. package/dist/runtime/types/augment.d.ts +14 -0
  27. package/dist/runtime/types.d.ts +2 -12
  28. package/dist/types.d.mts +1 -1
  29. package/package.json +2 -1
package/dist/module.d.mts CHANGED
@@ -3,7 +3,7 @@ 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
5
  import { BetterAuthOptions } from 'better-auth';
6
- export { Auth, AuthMeta, AuthMode, AuthRouteRules, AuthSession, AuthUser, InferSession, InferUser, RequireSessionOptions, ServerAuthContext, UserMatch } from '../dist/runtime/types.js';
6
+ export { AppSession, Auth, AuthMeta, AuthMode, AuthRouteRules, AuthSession, AuthUser, InferSession, InferUser, RequireSessionOptions, ServerAuthContext, UserMatch } from '../dist/runtime/types.js';
7
7
 
8
8
  interface DefineServerAuthFn {
9
9
  (...args: unknown[]): unknown;
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onmax/nuxt-better-auth",
3
- "version": "0.0.2-alpha.23",
3
+ "version": "0.0.2-alpha.25",
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.23";
13
+ const version = "0.0.2-alpha.25";
14
14
 
15
15
  function resolveDatabaseProvider(input) {
16
16
  const enabledProviders = Object.entries(input.providers).filter(([_id, provider]) => provider.isEnabled?.(input.context) ?? true);
@@ -23,7 +23,8 @@ function resolveDatabaseProvider(input) {
23
23
  }
24
24
 
25
25
  function setupDevTools(nuxt) {
26
- nuxt.hook("devtools:customTabs", (tabs) => {
26
+ const hookable = nuxt;
27
+ hookable.hook("devtools:customTabs", (tabs) => {
27
28
  tabs.push({
28
29
  category: "server",
29
30
  name: "better-auth",
@@ -141,10 +142,10 @@ function setupRuntimeConfig(input) {
141
142
  if (!configuredSiteUrl && process.env.NUXT_PUBLIC_SITE_URL)
142
143
  nuxt.options.runtimeConfig.public.siteUrl = process.env.NUXT_PUBLIC_SITE_URL;
143
144
  nuxt.options.runtimeConfig.public.auth = defu(nuxt.options.runtimeConfig.public.auth, {
144
- redirects: { login: options.redirects?.login ?? "/login", guest: options.redirects?.guest ?? "/" },
145
+ preserveRedirect: options.preserveRedirect ?? true,
146
+ redirectQueryKey: options.redirectQueryKey ?? "redirect",
145
147
  useDatabase: databaseProvider !== "none",
146
148
  databaseProvider,
147
- databaseSource: "module",
148
149
  clientOnly,
149
150
  session: {
150
151
  skipHydratedSsrGetSession: options.session?.skipHydratedSsrGetSession ?? false
@@ -154,7 +155,7 @@ function setupRuntimeConfig(input) {
154
155
  const siteUrl = nuxt.options.runtimeConfig.public.siteUrl;
155
156
  if (!siteUrl)
156
157
  consola.warn("clientOnly mode: set runtimeConfig.public.siteUrl (or NUXT_PUBLIC_SITE_URL) to your frontend URL");
157
- consola.info("clientOnly mode enabled - server utilities (serverAuth, getUserSession, requireUserSession) are not available");
158
+ consola.info("clientOnly mode enabled - server utilities (serverAuth, getAppSession, getUserSession, requireUserSession) are not available");
158
159
  return { secondaryStorageEnabled };
159
160
  }
160
161
  const currentSecret = nuxt.options.runtimeConfig.betterAuthSecret;
@@ -305,6 +306,7 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola) {
305
306
  if (isProduction)
306
307
  throw error;
307
308
  consola.error("Failed to generate schema:", error);
309
+ throw error;
308
310
  }
309
311
  }
310
312
 
@@ -387,7 +389,8 @@ export function createSecondaryStorage() {
387
389
  }
388
390
  function buildDatabaseCode(input) {
389
391
  if (input.provider === "nuxthub") {
390
- return `import { db, schema } from '@nuxthub/db'
392
+ return `import { db } from '@nuxthub/db'
393
+ import * as schema from './schema.${input.hubDialect}.mjs'
391
394
  import { drizzleAdapter } from 'better-auth/adapters/drizzle'
392
395
  const rawDialect = '${input.hubDialect}'
393
396
  const dialect = rawDialect === 'postgresql' ? 'pg' : rawDialect
@@ -421,6 +424,24 @@ declare module '#auth/database' {
421
424
  export function createDatabase(): BetterAuthOptions['database']
422
425
  export const db: ${hasHubDb ? `typeof import('@nuxthub/db')['db']` : "undefined"}
423
426
  }
427
+ `
428
+ }, { nitro: true, node: true });
429
+ addTypeTemplate({
430
+ filename: "types/auth-schema.d.ts",
431
+ getContents: () => `
432
+ declare module '#auth/schema' {
433
+ export const user: any
434
+ export const session: any
435
+ export const account: any
436
+ export const verification: any
437
+ export const schema: {
438
+ user: any
439
+ session: any
440
+ account: any
441
+ verification: any
442
+ [key: string]: any
443
+ } | undefined
444
+ }
424
445
  `
425
446
  }, { nitro: true, node: true });
426
447
  addTypeTemplate({
@@ -475,8 +496,8 @@ declare module '@onmax/nuxt-better-auth/config' {
475
496
  type ServerAuthConfig = Omit<BetterAuthOptions, 'secret' | 'baseURL'> & {
476
497
  plugins?: readonly BetterAuthPlugin[]
477
498
  }
478
- export function defineServerAuth<const R extends ServerAuthConfig>(config: R): (ctx: _AugmentedServerAuthContext) => R
479
- export function defineServerAuth<const R extends ServerAuthConfig>(config: (ctx: _AugmentedServerAuthContext) => R): (ctx: _AugmentedServerAuthContext) => R
499
+ export function defineServerAuth<const R>(config: (ctx: _AugmentedServerAuthContext) => R & ServerAuthConfig): (ctx: _AugmentedServerAuthContext) => R
500
+ export function defineServerAuth<const R>(config: R & ServerAuthConfig): (ctx: _AugmentedServerAuthContext) => R
480
501
  }
481
502
  `
482
503
  }, { nuxt: true, nitro: true, node: true });
@@ -507,13 +528,13 @@ function registerSharedTypeTemplates(input) {
507
528
  addTypeTemplate({
508
529
  filename: "types/nuxt-better-auth.d.ts",
509
530
  getContents: () => `
510
- import type { AuthSession, AuthUser } from '${input.runtimeTypesAugmentPath}'
511
- import type { UserMatch } from '${input.runtimeTypesPath}'
531
+ import type { AppSession } from '${input.runtimeTypesAugmentPath}'
512
532
  export * from '${input.runtimeTypesAugmentPath}'
513
- export type { AuthMeta, AuthMode, AuthRouteRules, UserMatch, Auth, InferUser, InferSession } from '${input.runtimeTypesPath}'
514
- export interface RequireSessionOptions {
515
- user?: UserMatch<AuthUser>
516
- rule?: (ctx: { user: AuthUser, session: AuthSession }) => boolean | Promise<boolean>
533
+ export type { AuthMeta, AuthMode, AuthRouteRules, Auth, InferUser, InferSession } from '${input.runtimeTypesPath}'
534
+ declare module 'h3' {
535
+ interface H3EventContext {
536
+ appSession?: AppSession | null
537
+ }
517
538
  }
518
539
  `
519
540
  });
@@ -566,7 +587,8 @@ const module$1 = defineNuxtModule({
566
587
  clientOnly: false,
567
588
  serverConfig: "server/auth.config",
568
589
  clientConfig: "app/auth.config",
569
- redirects: { login: "/login", guest: "/" },
590
+ preserveRedirect: true,
591
+ redirectQueryKey: "redirect",
570
592
  secondaryStorage: false
571
593
  },
572
594
  async onInstall(nuxt) {
@@ -674,6 +696,19 @@ const module$1 = defineNuxtModule({
674
696
  write: true
675
697
  });
676
698
  nuxt.options.alias["#auth/database"] = databaseTemplate.dst;
699
+ const schemaTemplate = addTemplate({
700
+ filename: "better-auth/schema.mjs",
701
+ getContents: () => {
702
+ if (!hasHubDb)
703
+ return "export const schema = undefined\n";
704
+ return `export * from './schema.${hubDialect}.mjs'
705
+ import * as schema from './schema.${hubDialect}.mjs'
706
+ export { schema }
707
+ `;
708
+ },
709
+ write: true
710
+ });
711
+ nuxt.options.alias["#auth/schema"] = schemaTemplate.dst;
677
712
  registerServerTypeTemplates({
678
713
  serverConfigPath,
679
714
  hasHubDb,
@@ -2,6 +2,9 @@ import createAppAuthClient from "#auth/client";
2
2
  import { computed, nextTick, useNuxtApp, useRequestHeaders, useRequestURL, useRuntimeConfig, useState, watch } from "#imports";
3
3
  let _sessionSignalListenerBound = false;
4
4
  let _client = null;
5
+ function isRecord(value) {
6
+ return Boolean(value && typeof value === "object");
7
+ }
5
8
  function getClient(baseURL) {
6
9
  if (!_client)
7
10
  _client = createAppAuthClient(baseURL);
@@ -17,10 +20,14 @@ function ensureSessionSignalListener(client, onSignal) {
17
20
  if (_sessionSignalListenerBound)
18
21
  return;
19
22
  const store = client.$store;
20
- if (!store?.listen)
23
+ if (!isRecord(store))
24
+ return;
25
+ const listen = store.listen;
26
+ if (typeof listen !== "function")
21
27
  return;
22
28
  _sessionSignalListenerBound = true;
23
- store.listen("$sessionSignal", async () => {
29
+ const listenFn = listen;
30
+ listenFn("$sessionSignal", async () => {
24
31
  try {
25
32
  await onSignal();
26
33
  } catch {
@@ -130,12 +137,29 @@ export function useUserSession() {
130
137
  }
131
138
  function wrapAuthMethod(method) {
132
139
  return (async (...args) => {
133
- const [data, options] = args;
134
- if (data?.fetchOptions?.onSuccess) {
135
- return method({ ...data, fetchOptions: { ...data.fetchOptions, onSuccess: wrapOnSuccess(data.fetchOptions.onSuccess) } }, options);
140
+ const data = args[0];
141
+ const options = args[1];
142
+ const dataRecord = isRecord(data) ? data : void 0;
143
+ const optionsRecord = isRecord(options) ? options : void 0;
144
+ const fetchOptions = isRecord(dataRecord?.fetchOptions) ? dataRecord.fetchOptions : void 0;
145
+ const nestedOnSuccess = fetchOptions?.onSuccess;
146
+ const topLevelOnSuccess = optionsRecord?.onSuccess;
147
+ if (typeof nestedOnSuccess === "function") {
148
+ const nextData = {
149
+ ...dataRecord,
150
+ fetchOptions: {
151
+ ...fetchOptions,
152
+ onSuccess: wrapOnSuccess(nestedOnSuccess)
153
+ }
154
+ };
155
+ return method(nextData, options);
136
156
  }
137
- if (options?.onSuccess) {
138
- return method(data, { ...options, onSuccess: wrapOnSuccess(options.onSuccess) });
157
+ if (typeof topLevelOnSuccess === "function") {
158
+ const nextOptions = {
159
+ ...optionsRecord,
160
+ onSuccess: wrapOnSuccess(topLevelOnSuccess)
161
+ };
162
+ return method(data, nextOptions);
139
163
  }
140
164
  return method(data, options);
141
165
  });
@@ -0,0 +1,3 @@
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']>;
@@ -0,0 +1,5 @@
1
+ import { useUserSession } from "#imports";
2
+ import { createActionHandles } from "../internal/auth-action-handles.js";
3
+ export function useUserSignIn() {
4
+ return createActionHandles(() => useUserSession().signIn, "signIn");
5
+ }
@@ -0,0 +1,3 @@
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']>;
@@ -0,0 +1,5 @@
1
+ import { useUserSession } from "#imports";
2
+ import { createActionHandles } from "../internal/auth-action-handles.js";
3
+ export function useUserSignUp() {
4
+ return createActionHandles(() => useUserSession().signUp, "signUp");
5
+ }
@@ -0,0 +1,13 @@
1
+ import type { ComputedRef, Ref } from 'vue';
2
+ export type UserAuthActionStatus = 'idle' | 'pending' | 'success' | 'error';
3
+ export interface UserAuthActionHandle<TArgs extends unknown[], TResult> {
4
+ execute: (...args: TArgs) => Promise<TResult>;
5
+ status: Ref<UserAuthActionStatus>;
6
+ pending: ComputedRef<boolean>;
7
+ error: Ref<unknown | null>;
8
+ }
9
+ export type ActionHandleFor<T> = T extends (...args: infer A) => Promise<infer R> ? UserAuthActionHandle<A, R> : never;
10
+ export type ActionHandleMap<T> = {
11
+ [K in keyof T]: ActionHandleFor<T[K]>;
12
+ };
13
+ export declare function createActionHandles<T extends object>(getTarget: () => T, targetName: string): ActionHandleMap<T>;
@@ -0,0 +1,62 @@
1
+ import { computed, ref } from "#imports";
2
+ function isRecord(value) {
3
+ return Boolean(value && typeof value === "object");
4
+ }
5
+ function isErrorResult(value) {
6
+ if (!isRecord(value))
7
+ return false;
8
+ if (!("error" in value))
9
+ return false;
10
+ return Boolean(value.error);
11
+ }
12
+ function createActionHandle(getMethod) {
13
+ const status = ref("idle");
14
+ const error = ref(null);
15
+ const pending = computed(() => status.value === "pending");
16
+ let latestCallId = 0;
17
+ const execute = (async (...args) => {
18
+ const callId = ++latestCallId;
19
+ status.value = "pending";
20
+ error.value = null;
21
+ try {
22
+ const result = await getMethod()(...args);
23
+ if (callId !== latestCallId)
24
+ return result;
25
+ if (isErrorResult(result)) {
26
+ status.value = "error";
27
+ error.value = result.error;
28
+ return result;
29
+ }
30
+ status.value = "success";
31
+ error.value = null;
32
+ return result;
33
+ } catch (err) {
34
+ if (callId === latestCallId) {
35
+ status.value = "error";
36
+ error.value = err;
37
+ }
38
+ throw err;
39
+ }
40
+ });
41
+ return { execute, status, pending, error };
42
+ }
43
+ export function createActionHandles(getTarget, targetName) {
44
+ const handles = /* @__PURE__ */ new Map();
45
+ return new Proxy({}, {
46
+ get(_target, prop) {
47
+ if (prop === "then")
48
+ return void 0;
49
+ if (handles.has(prop))
50
+ return handles.get(prop);
51
+ const handle = createActionHandle(() => {
52
+ const target = getTarget();
53
+ const method = target[prop];
54
+ if (typeof method !== "function")
55
+ throw new TypeError(`${targetName}.${String(prop)}() is not a function`);
56
+ return method;
57
+ });
58
+ handles.set(prop, handle);
59
+ return handle;
60
+ }
61
+ });
62
+ }
@@ -25,13 +25,49 @@ 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 ?? config?.redirects?.guest ?? "/");
28
+ return navigateTo(redirectTo ?? "/");
29
29
  return;
30
30
  }
31
- if (!loggedIn.value)
32
- return navigateTo(redirectTo ?? config?.redirects?.login ?? "/login");
31
+ if (!loggedIn.value) {
32
+ const resolved = resolveLoginRedirect({
33
+ route: to,
34
+ loginTarget: redirectTo ?? "/login",
35
+ config
36
+ });
37
+ return resolved.external ? navigateTo(resolved.to, { external: true }) : navigateTo(resolved.to);
38
+ }
33
39
  if (typeof auth === "object" && auth.user) {
34
40
  if (!user.value || !matchesUser(user.value, auth.user))
35
41
  throw createError({ statusCode: 403, statusMessage: "Access denied" });
36
42
  }
37
43
  });
44
+ function resolveLoginRedirect(input) {
45
+ const { route, loginTarget, config } = input;
46
+ const preserveRedirect = config?.preserveRedirect ?? true;
47
+ const redirectQueryKey = config?.redirectQueryKey ?? "redirect";
48
+ if (!preserveRedirect)
49
+ return { to: loginTarget, external: false };
50
+ if (!loginTarget.startsWith("/") || loginTarget.startsWith("//"))
51
+ return { to: loginTarget, external: false };
52
+ const [beforeHash, hash = ""] = loginTarget.split("#", 2);
53
+ const [path, query = ""] = beforeHash.split("?", 2);
54
+ try {
55
+ const params2 = new URLSearchParams(query);
56
+ if (params2.has(redirectQueryKey))
57
+ return { to: loginTarget, external: false };
58
+ } catch {
59
+ return { to: loginTarget, external: false };
60
+ }
61
+ if (import.meta.server) {
62
+ const separator = query ? "&" : "";
63
+ const encodedRedirect = encodeURIComponent(route.fullPath);
64
+ const url = `${path}?${query}${separator}${redirectQueryKey}=${encodedRedirect}${hash ? `#${hash}` : ""}`;
65
+ return { to: url, external: true };
66
+ }
67
+ const params = new URLSearchParams(query);
68
+ const queryObj = {};
69
+ for (const [k, v] of params.entries())
70
+ queryObj[k] = v;
71
+ queryObj[redirectQueryKey] = route.fullPath;
72
+ return { to: { path, query: queryObj, ...hash ? { hash: `#${hash}` } : {} }, external: false };
73
+ }
@@ -373,15 +373,9 @@ function getAccountActions(row) {
373
373
  <div class="config-header">
374
374
  <UIcon name="i-lucide-settings-2" class="size-4" /><span>Module</span>
375
375
  </div>
376
- <div class="config-row">
377
- <span class="config-label">Login</span><span class="font-mono">{{ configData.config.module?.redirects?.login }}</span>
378
- </div>
379
- <div class="config-row">
380
- <span class="config-label">Guest</span><span class="font-mono">{{ configData.config.module?.redirects?.guest }}</span>
381
- </div>
382
376
  <div class="config-row">
383
377
  <span class="config-label">DB</span><UBadge :color="configData.config.module?.databaseProvider === 'none' ? 'neutral' : 'success'" variant="subtle" size="sm">
384
- {{ configData.config.module?.databaseProvider === "nuxthub" ? "Hub" : configData.config.module?.databaseProvider === "convex" ? "Convex" : "Off" }}
378
+ {{ configData.config.module?.databaseProvider === "nuxthub" ? "Hub" : "Off" }}
385
379
  </UBadge>
386
380
  </div>
387
381
  <div class="config-row">
@@ -17,7 +17,6 @@ export type ServerAuthConfigFn = (ctx: ServerAuthContext) => ServerAuthConfig;
17
17
  export type ClientAuthConfigFn = (ctx: ClientAuthContext) => ClientAuthConfig;
18
18
  export type ModuleDatabaseProviderId = 'none' | 'nuxthub' | (string & {});
19
19
  export type EffectiveDatabaseProviderId = 'user' | ModuleDatabaseProviderId;
20
- export type DatabaseSource = 'module' | 'user';
21
20
  export interface BetterAuthModuleOptions {
22
21
  /** Client-only mode - skip server setup for external auth backends */
23
22
  clientOnly?: boolean;
@@ -25,10 +24,19 @@ export interface BetterAuthModuleOptions {
25
24
  serverConfig?: string;
26
25
  /** Client config path relative to rootDir. Default: 'app/auth.config' */
27
26
  clientConfig?: string;
28
- redirects?: {
29
- login?: string;
30
- guest?: string;
31
- };
27
+ /**
28
+ * When redirecting unauthenticated users to the login route, append a query param
29
+ * containing the originally requested path (for safe "return-to" redirects).
30
+ *
31
+ * Default: true
32
+ */
33
+ preserveRedirect?: boolean;
34
+ /**
35
+ * Query param key used by preserveRedirect.
36
+ *
37
+ * Default: 'redirect'
38
+ */
39
+ redirectQueryKey?: string;
32
40
  session?: {
33
41
  /**
34
42
  * When enabled, and session/user are already hydrated from SSR, skip the initial
@@ -50,13 +58,10 @@ export interface BetterAuthModuleOptions {
50
58
  };
51
59
  }
52
60
  export interface AuthRuntimeConfig {
53
- redirects: {
54
- login: string;
55
- guest: string;
56
- };
61
+ preserveRedirect: boolean;
62
+ redirectQueryKey: string;
57
63
  useDatabase: boolean;
58
64
  databaseProvider: EffectiveDatabaseProviderId;
59
- databaseSource: DatabaseSource;
60
65
  clientOnly: boolean;
61
66
  session: {
62
67
  skipHydratedSsrGetSession: boolean;
@@ -65,6 +70,6 @@ export interface AuthRuntimeConfig {
65
70
  export interface AuthPrivateRuntimeConfig {
66
71
  secondaryStorage: boolean;
67
72
  }
68
- export declare function defineServerAuth<const R extends ServerAuthConfig>(config: R): (ctx: ServerAuthContext) => R;
69
- export declare function defineServerAuth<const R extends ServerAuthConfig>(config: (ctx: ServerAuthContext) => R): (ctx: ServerAuthContext) => R;
73
+ export declare function defineServerAuth<const R>(config: (ctx: ServerAuthContext) => R & ServerAuthConfig): (ctx: ServerAuthContext) => R;
74
+ export declare function defineServerAuth<const R>(config: R & ServerAuthConfig): (ctx: ServerAuthContext) => R;
70
75
  export declare function defineClientAuth<T extends ClientAuthConfig>(config: T | ((ctx: ClientAuthContext) => T)): (baseURL: string) => ReturnType<typeof createAuthClient<T>>;
@@ -2,8 +2,9 @@ import { defineEventHandler, getQuery } from "h3";
2
2
  import { paginationQuerySchema, sanitizeSearchPattern } from "./_schema.js";
3
3
  export default defineEventHandler(async (event) => {
4
4
  try {
5
- const { db, schema } = await import("@nuxthub/db");
6
- if (!schema.account)
5
+ const { db } = await import("@nuxthub/db");
6
+ const { schema } = await import("#auth/schema");
7
+ if (!schema?.account)
7
8
  return { accounts: [], total: 0, error: "Account table not found" };
8
9
  const query = paginationQuerySchema.parse(getQuery(event));
9
10
  const { page, limit, search } = query;
@@ -1,13 +1,11 @@
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
- };
4
+ preserveRedirect: boolean;
5
+ redirectQueryKey: string;
8
6
  secondaryStorage: boolean;
9
7
  useDatabase: boolean;
10
- databaseProvider: "none" | "nuxthub" | "convex";
8
+ databaseProvider: "none" | "nuxthub";
11
9
  };
12
10
  server: {
13
11
  baseURL: any;
@@ -18,7 +18,8 @@ export default defineEventHandler(async (event) => {
18
18
  config: {
19
19
  // Module config (nuxt.config.ts)
20
20
  module: {
21
- redirects: publicAuth?.redirects || { login: "/login", guest: "/" },
21
+ preserveRedirect: publicAuth?.preserveRedirect ?? true,
22
+ redirectQueryKey: publicAuth?.redirectQueryKey ?? "redirect",
22
23
  secondaryStorage: privateAuth?.secondaryStorage ?? false,
23
24
  useDatabase: publicAuth?.useDatabase ?? false,
24
25
  databaseProvider: publicAuth?.databaseProvider ?? "none"
@@ -6,8 +6,9 @@ const deleteSessionSchema = z.object({
6
6
  export default defineEventHandler(async (event) => {
7
7
  try {
8
8
  const body = deleteSessionSchema.parse(await readBody(event));
9
- const { db, schema } = await import("@nuxthub/db");
10
- if (!schema.session)
9
+ const { db } = await import("@nuxthub/db");
10
+ const { schema } = await import("#auth/schema");
11
+ if (!schema?.session)
11
12
  throw createError({ statusCode: 500, message: "Session table not found" });
12
13
  const { eq } = await import("drizzle-orm");
13
14
  await db.delete(schema.session).where(eq(schema.session.id, body.id));
@@ -1,3 +1,5 @@
1
+ import type { Session } from 'better-auth/types';
2
+ type SafeSession = Pick<Session, 'id' | 'userId' | 'createdAt' | 'updatedAt' | 'expiresAt' | 'ipAddress' | 'userAgent'>;
1
3
  declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
4
  sessions: never[];
3
5
  total: number;
@@ -5,7 +7,7 @@ declare const _default: import("h3").EventHandler<import("h3").EventHandlerReque
5
7
  page?: undefined;
6
8
  limit?: undefined;
7
9
  } | {
8
- sessions: any;
10
+ sessions: SafeSession[];
9
11
  total: any;
10
12
  page: number;
11
13
  limit: number;
@@ -2,8 +2,9 @@ import { defineEventHandler, getQuery } from "h3";
2
2
  import { paginationQuerySchema, sanitizeSearchPattern } from "./_schema.js";
3
3
  export default defineEventHandler(async (event) => {
4
4
  try {
5
- const { db, schema } = await import("@nuxthub/db");
6
- if (!schema.session)
5
+ const { db } = await import("@nuxthub/db");
6
+ const { schema } = await import("#auth/schema");
7
+ if (!schema?.session)
7
8
  return { sessions: [], total: 0, error: "Session table not found" };
8
9
  const query = paginationQuerySchema.parse(getQuery(event));
9
10
  const { page, limit, search } = query;
@@ -2,8 +2,9 @@ import { defineEventHandler, getQuery } from "h3";
2
2
  import { paginationQuerySchema, sanitizeSearchPattern } from "./_schema.js";
3
3
  export default defineEventHandler(async (event) => {
4
4
  try {
5
- const { db, schema } = await import("@nuxthub/db");
6
- if (!schema.user)
5
+ const { db } = await import("@nuxthub/db");
6
+ const { schema } = await import("#auth/schema");
7
+ if (!schema?.user)
7
8
  return { users: [], total: 0, error: "User table not found" };
8
9
  const query = paginationQuerySchema.parse(getQuery(event));
9
10
  const { page, limit, search } = query;
@@ -1,6 +1,7 @@
1
1
  import { createError, defineEventHandler, getRequestURL } from "h3";
2
2
  import { getRouteRules } from "nitropack/runtime";
3
3
  import { matchesUser } from "../../utils/match-user.js";
4
+ import { getUserSession, requireUserSession } from "../utils/session.js";
4
5
  export default defineEventHandler(async (event) => {
5
6
  const path = getRequestURL(event).pathname;
6
7
  if (!path.startsWith("/api/"))
@@ -1,3 +1,11 @@
1
1
  {
2
- "extends": "../../../.nuxt/tsconfig.server.json"
2
+ "extends": "../../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "baseUrl": ".",
5
+ "paths": {
6
+ "#nuxt-better-auth": ["../types/augment"]
7
+ }
8
+ },
9
+ "include": ["./**/*.ts", "./**/*.d.ts"],
10
+ "exclude": ["node_modules"]
3
11
  }
@@ -1,17 +1,5 @@
1
- import type { AuthSession, AuthUser } from '#nuxt-better-auth';
1
+ import type { AppSession, RequireSessionOptions } from '#nuxt-better-auth';
2
2
  import type { H3Event } from 'h3';
3
- import type { UserMatch } from '../../types.js';
4
- interface FullSession {
5
- user: AuthUser;
6
- session: AuthSession;
7
- }
8
- interface RequireUserSessionOptions {
9
- user?: UserMatch<AuthUser>;
10
- rule?: (ctx: {
11
- user: AuthUser;
12
- session: AuthSession;
13
- }) => boolean | Promise<boolean>;
14
- }
15
- export declare function getUserSession(event: H3Event): Promise<FullSession | null>;
16
- export declare function requireUserSession(event: H3Event, options?: RequireUserSessionOptions): Promise<FullSession>;
17
- export {};
3
+ export declare function getAppSession(event: H3Event): Promise<AppSession | null>;
4
+ export declare function getUserSession(event: H3Event): Promise<AppSession | null>;
5
+ export declare function requireUserSession(event: H3Event, options?: RequireSessionOptions): Promise<AppSession>;
@@ -1,13 +1,51 @@
1
1
  import { createError } from "h3";
2
2
  import { matchesUser } from "../../utils/match-user.js";
3
3
  import { serverAuth } from "./auth.js";
4
- export async function getUserSession(event) {
4
+ const appSessionLoadKey = Symbol.for("nuxt-better-auth.appSessionLoad");
5
+ const fallbackAppSessionContext = /* @__PURE__ */ new WeakMap();
6
+ function getAppSessionContext(event) {
7
+ const eventWithContext = event;
8
+ if (eventWithContext.context && typeof eventWithContext.context === "object")
9
+ return eventWithContext.context;
10
+ let context = fallbackAppSessionContext.get(event);
11
+ if (!context) {
12
+ context = {};
13
+ fallbackAppSessionContext.set(event, context);
14
+ }
15
+ return context;
16
+ }
17
+ function loadSession(event) {
5
18
  const auth = serverAuth(event);
6
- const session = await auth.api.getSession({ headers: event.headers });
7
- return session;
19
+ return auth.api.getSession({ headers: event.headers });
20
+ }
21
+ export async function getAppSession(event) {
22
+ const context = getAppSessionContext(event);
23
+ if (context.appSession !== void 0)
24
+ return context.appSession;
25
+ const inFlight = context[appSessionLoadKey];
26
+ if (inFlight)
27
+ return inFlight;
28
+ const load = loadSession(event);
29
+ context[appSessionLoadKey] = load;
30
+ try {
31
+ const session = await load;
32
+ context.appSession = session;
33
+ return session;
34
+ } finally {
35
+ delete context[appSessionLoadKey];
36
+ }
37
+ }
38
+ export async function getUserSession(event) {
39
+ const context = getAppSessionContext(event);
40
+ if (context.appSession !== void 0)
41
+ return context.appSession;
42
+ const inFlight = context[appSessionLoadKey];
43
+ if (inFlight)
44
+ return inFlight;
45
+ return loadSession(event);
8
46
  }
9
47
  export async function requireUserSession(event, options) {
10
- const session = await getUserSession(event);
48
+ const session = await getAppSession(event);
11
49
  if (!session)
12
50
  throw createError({ statusCode: 401, statusMessage: "Authentication required" });
13
51
  if (options?.user) {
@@ -0,0 +1,22 @@
1
+ declare module '#auth/database' {
2
+ export const db: any
3
+ export function createDatabase(...args: any[]): any
4
+ }
5
+
6
+ declare module '#auth/secondary-storage' {
7
+ export function createSecondaryStorage(...args: any[]): any
8
+ }
9
+
10
+ declare module '#auth/schema' {
11
+ export const schema: any
12
+ }
13
+
14
+ declare module '#auth/server' {
15
+ const createServerAuth: any
16
+ export default createServerAuth
17
+ }
18
+
19
+ declare module '@nuxthub/db' {
20
+ export const db: any
21
+ export const schema: any
22
+ }
@@ -40,3 +40,17 @@ export interface UserSessionComposable {
40
40
  }) => Promise<void>;
41
41
  updateUser: (updates: Partial<AuthUser>) => Promise<void>;
42
42
  }
43
+ export type UserMatch<T> = {
44
+ [K in keyof T]?: T[K] | T[K][];
45
+ };
46
+ export interface AppSession {
47
+ user: AuthUser;
48
+ session: AuthSession;
49
+ }
50
+ export interface RequireSessionOptions {
51
+ user?: UserMatch<AuthUser>;
52
+ rule?: (ctx: {
53
+ user: AuthUser;
54
+ session: AuthSession;
55
+ }) => boolean | Promise<boolean>;
56
+ }
@@ -1,11 +1,8 @@
1
1
  import type { NitroRouteRules } from 'nitropack/types';
2
- import type { AuthSession, AuthUser } from './types/augment.js';
3
- export type { AuthSession, AuthUser, ServerAuthContext, UserSessionComposable } from './types/augment.js';
2
+ import type { AuthUser, UserMatch } from './types/augment.js';
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 type AuthMode = 'guest' | 'user';
6
- export type UserMatch<T> = {
7
- [K in keyof T]?: T[K] | T[K][];
8
- };
9
6
  export type AuthMeta = false | AuthMode | {
10
7
  only?: AuthMode;
11
8
  redirectTo?: string;
@@ -14,10 +11,3 @@ export type AuthMeta = false | AuthMode | {
14
11
  export type AuthRouteRules = NitroRouteRules & {
15
12
  auth?: AuthMeta;
16
13
  };
17
- export interface RequireSessionOptions {
18
- user?: UserMatch<AuthUser>;
19
- rule?: (ctx: {
20
- user: AuthUser;
21
- session: AuthSession;
22
- }) => boolean | Promise<boolean>;
23
- }
package/dist/types.d.mts CHANGED
@@ -6,6 +6,6 @@ export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<
6
6
 
7
7
  export { type BetterAuthModuleOptions, type defineClientAuth, type defineServerAuth } from '../dist/runtime/config.js'
8
8
 
9
- export { type Auth, type AuthMeta, type AuthMode, type AuthRouteRules, type AuthSession, type AuthUser, type InferSession, type InferUser, type RequireSessionOptions, type ServerAuthContext, type UserMatch } from '../dist/runtime/types.js'
9
+ export { type AppSession, type Auth, type AuthMeta, type AuthMode, type AuthRouteRules, type AuthSession, type AuthUser, type InferSession, type InferUser, type RequireSessionOptions, type ServerAuthContext, type UserMatch } from '../dist/runtime/types.js'
10
10
 
11
11
  export { default } from './module.mjs'
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.23",
4
+ "version": "0.0.2-alpha.25",
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",
@@ -55,6 +55,7 @@
55
55
  "lint": "eslint .",
56
56
  "lint:fix": "eslint . --fix",
57
57
  "typecheck": "vue-tsc --noEmit",
58
+ "typecheck:runtime-server": "tsc --noEmit --pretty false -p src/runtime/server/tsconfig.json",
58
59
  "typecheck:playground": "cd playground && vue-tsc --noEmit",
59
60
  "test": "vitest run",
60
61
  "test:watch": "vitest watch"