@onmax/nuxt-better-auth 0.0.2-alpha.1 → 0.0.2-alpha.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.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.0.0"
6
6
  },
7
- "version": "0.0.2-alpha.1",
7
+ "version": "0.0.2-alpha.3",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -22,46 +22,60 @@ function setupDevTools(nuxt) {
22
22
  });
23
23
  }
24
24
 
25
- function generateDrizzleSchema(tables, dialect) {
26
- const imports = getImports(dialect);
27
- const tableDefinitions = Object.entries(tables).map(([tableName, table]) => generateTable(tableName, table, dialect, tables)).join("\n\n");
25
+ function generateDrizzleSchema(tables, dialect, options) {
26
+ const typedTables = tables;
27
+ const imports = getImports(dialect, options);
28
+ const tableDefinitions = Object.entries(typedTables).map(([tableName, table]) => generateTable(tableName, table, dialect, typedTables, options)).join("\n\n");
28
29
  return `${imports}
29
30
 
30
31
  ${tableDefinitions}
31
32
  `;
32
33
  }
33
- function getImports(dialect) {
34
+ function getImports(dialect, options) {
34
35
  switch (dialect) {
35
36
  case "sqlite":
36
37
  return `import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'`;
37
38
  case "postgresql":
38
- return `import { boolean, pgTable, text, timestamp, integer } from 'drizzle-orm/pg-core'`;
39
+ return options?.useUuid ? `import { boolean, integer, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'` : `import { boolean, integer, pgTable, text, timestamp } from 'drizzle-orm/pg-core'`;
39
40
  case "mysql":
40
41
  return `import { boolean, int, mysqlTable, text, timestamp, varchar } from 'drizzle-orm/mysql-core'`;
41
42
  }
42
43
  }
43
- function generateTable(tableName, table, dialect, allTables) {
44
+ function generateTable(tableName, table, dialect, allTables, options) {
44
45
  const tableFunc = dialect === "sqlite" ? "sqliteTable" : dialect === "postgresql" ? "pgTable" : "mysqlTable";
45
- const dbTableName = table.modelName || tableName;
46
- const fields = Object.entries(table.fields).map(([fieldName, field]) => generateField(fieldName, field, dialect, allTables)).join(",\n ");
47
- const idField = generateIdField(dialect);
46
+ let dbTableName = table.modelName || tableName;
47
+ if (options?.usePlural && !table.modelName) {
48
+ dbTableName = `${tableName}s`;
49
+ }
50
+ const fields = Object.entries(table.fields).map(([fieldName, field]) => generateField(fieldName, field, dialect, allTables, options)).join(",\n ");
51
+ const idField = generateIdField(dialect, options);
48
52
  return `export const ${tableName} = ${tableFunc}('${dbTableName}', {
49
53
  ${idField},
50
54
  ${fields}
51
55
  })`;
52
56
  }
53
- function generateIdField(dialect) {
57
+ function generateIdField(dialect, options) {
54
58
  switch (dialect) {
55
59
  case "sqlite":
56
- case "postgresql":
60
+ if (options?.useUuid)
61
+ consola$1.warn("[@onmax/nuxt-better-auth] useUuid ignored for SQLite (no native uuid type). Using text.");
57
62
  return `id: text('id').primaryKey()`;
63
+ case "postgresql":
64
+ return options?.useUuid ? `id: uuid('id').defaultRandom().primaryKey()` : `id: text('id').primaryKey()`;
58
65
  case "mysql":
59
66
  return `id: varchar('id', { length: 36 }).primaryKey()`;
60
67
  }
61
68
  }
62
- function generateField(fieldName, field, dialect, allTables) {
69
+ function generateField(fieldName, field, dialect, allTables, options) {
63
70
  const dbFieldName = fieldName;
64
- let fieldDef = getFieldType(field.type, dialect, dbFieldName);
71
+ const isFkToId = options?.useUuid && field.references?.field === "id";
72
+ let fieldDef;
73
+ if (isFkToId && dialect === "postgresql")
74
+ fieldDef = `uuid('${dbFieldName}')`;
75
+ else if (isFkToId && dialect === "mysql")
76
+ fieldDef = `varchar('${dbFieldName}', { length: 36 })`;
77
+ else
78
+ fieldDef = getFieldType(field.type, dialect, dbFieldName);
65
79
  if (field.required && field.defaultValue === void 0)
66
80
  fieldDef += ".notNull()";
67
81
  if (field.unique)
@@ -136,17 +150,23 @@ function getMysqlType(type, fieldName) {
136
150
  return `text('${fieldName}')`;
137
151
  }
138
152
  }
139
- async function loadUserAuthConfig(configPath) {
153
+ async function loadUserAuthConfig(configPath, throwOnError = false) {
140
154
  const { createJiti } = await import('jiti');
141
155
  const jiti = createJiti(import.meta.url, { interopDefault: true });
142
156
  try {
143
157
  const mod = await jiti.import(configPath);
144
- const configFn = mod.default || mod;
158
+ const configFn = typeof mod === "object" && mod !== null && "default" in mod ? mod.default : mod;
145
159
  if (typeof configFn === "function") {
146
160
  return configFn({ runtimeConfig: {}, db: null });
147
161
  }
162
+ if (throwOnError) {
163
+ throw new Error("auth.config.ts must export default defineServerAuth(...)");
164
+ }
148
165
  return {};
149
166
  } catch (error) {
167
+ if (throwOnError) {
168
+ throw new Error(`Failed to load auth config: ${error instanceof Error ? error.message : error}`);
169
+ }
150
170
  consola$1.error("[@onmax/nuxt-better-auth] Failed to load auth config for schema generation. Schema may be incomplete:", error);
151
171
  return {};
152
172
  }
@@ -186,7 +206,14 @@ const module$1 = defineNuxtModule({
186
206
  redirects: { login: options.redirects?.login ?? "/login", guest: options.redirects?.guest ?? "/" },
187
207
  useDatabase: hasHubDb
188
208
  });
189
- nuxt.options.runtimeConfig.betterAuthSecret ||= process.env.BETTER_AUTH_SECRET || process.env.NUXT_BETTER_AUTH_SECRET || "";
209
+ const betterAuthSecret = process.env.BETTER_AUTH_SECRET || process.env.NUXT_BETTER_AUTH_SECRET || nuxt.options.runtimeConfig.betterAuthSecret || "";
210
+ if (!nuxt.options.dev && !betterAuthSecret) {
211
+ throw new Error("[nuxt-better-auth] BETTER_AUTH_SECRET is required in production. Set BETTER_AUTH_SECRET or NUXT_BETTER_AUTH_SECRET environment variable.");
212
+ }
213
+ if (betterAuthSecret && betterAuthSecret.length < 32) {
214
+ throw new Error("[nuxt-better-auth] BETTER_AUTH_SECRET must be at least 32 characters for security");
215
+ }
216
+ nuxt.options.runtimeConfig.betterAuthSecret = betterAuthSecret;
190
217
  nuxt.options.runtimeConfig.auth = defu(nuxt.options.runtimeConfig.auth, {
191
218
  secondaryStorage: secondaryStorageEnabled
192
219
  });
@@ -204,9 +231,13 @@ export function createSecondaryStorage() {
204
231
  const secondaryStorageTemplate = addTemplate({ filename: "better-auth/secondary-storage.mjs", getContents: () => secondaryStorageCode, write: true });
205
232
  nuxt.options.alias["#auth/secondary-storage"] = secondaryStorageTemplate.dst;
206
233
  const hubDbPath = nuxt.options.alias["hub:db"];
234
+ if (hasHubDb && !hubDbPath) {
235
+ throw new Error("[nuxt-better-auth] hub:db alias not found. Ensure @nuxthub/core is loaded before this module.");
236
+ }
237
+ const hubDialect = typeof hub?.db === "string" ? hub.db : (typeof hub?.db === "object" ? hub.db.dialect : void 0) ?? "sqlite";
207
238
  const databaseCode = hasHubDb && hubDbPath ? `import { db, schema } from '${hubDbPath}'
208
239
  import { drizzleAdapter } from 'better-auth/adapters/drizzle'
209
- const rawDialect = '${hub?.db?.dialect ?? "sqlite"}'
240
+ const rawDialect = '${hubDialect}'
210
241
  const dialect = rawDialect === 'postgresql' ? 'pg' : rawDialect
211
242
  export function createDatabase() { return drizzleAdapter(db, { provider: dialect, schema }) }
212
243
  export { db }` : `export function createDatabase() { return undefined }
@@ -298,9 +329,10 @@ declare module 'nitropack/types' {
298
329
  app.middleware.push({ name: "auth", path: resolver.resolve("./runtime/app/middleware/auth.global"), global: true });
299
330
  });
300
331
  if (hasHubDb) {
301
- await setupBetterAuthSchema(nuxt, serverConfigPath);
332
+ await setupBetterAuthSchema(nuxt, serverConfigPath, options);
302
333
  }
303
- if (nuxt.options.dev && process.env.NODE_ENV !== "production") {
334
+ const isProduction = process.env.NODE_ENV === "production" || !nuxt.options.dev;
335
+ if (!isProduction) {
304
336
  setupDevTools(nuxt);
305
337
  addServerHandler({ route: "/api/_better-auth/config", method: "get", handler: resolver.resolve("./runtime/server/api/_better-auth/config.get") });
306
338
  if (hasHubDb) {
@@ -333,22 +365,25 @@ declare module 'nitropack/types' {
333
365
  });
334
366
  }
335
367
  });
336
- async function setupBetterAuthSchema(nuxt, serverConfigPath) {
368
+ async function setupBetterAuthSchema(nuxt, serverConfigPath, options) {
337
369
  const hub = nuxt.options.hub;
338
- const dialect = typeof hub.db === "string" ? hub.db : hub.db?.dialect;
370
+ const dialect = typeof hub?.db === "string" ? hub.db : typeof hub?.db === "object" ? hub.db.dialect : void 0;
339
371
  if (!dialect || !["sqlite", "postgresql", "mysql"].includes(dialect)) {
340
372
  consola.warn(`Unsupported database dialect: ${dialect}`);
341
373
  return;
342
374
  }
375
+ const isProduction = !nuxt.options.dev;
343
376
  try {
344
377
  const configFile = `${serverConfigPath}.ts`;
345
- const userConfig = await loadUserAuthConfig(configFile);
378
+ const userConfig = await loadUserAuthConfig(configFile, isProduction);
346
379
  const extendedConfig = {};
347
380
  await nuxt.callHook("better-auth:config:extend", extendedConfig);
348
381
  const plugins = [...userConfig.plugins || [], ...extendedConfig.plugins || []];
349
382
  const { getAuthTables } = await import('better-auth/db');
350
383
  const tables = getAuthTables({ plugins });
351
- const schemaCode = generateDrizzleSchema(tables, dialect);
384
+ const useUuid = userConfig.advanced?.database?.generateId === "uuid";
385
+ const schemaOptions = { ...options.schema, useUuid };
386
+ const schemaCode = generateDrizzleSchema(tables, dialect, schemaOptions);
352
387
  const schemaDir = join(nuxt.options.buildDir, "better-auth");
353
388
  const schemaPath = join(schemaDir, `schema.${dialect}.ts`);
354
389
  await mkdir(schemaDir, { recursive: true });
@@ -356,12 +391,16 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath) {
356
391
  addTemplate({ filename: `better-auth/schema.${dialect}.ts`, getContents: () => schemaCode, write: true });
357
392
  consola.info(`Generated ${dialect} schema with ${Object.keys(tables).length} tables`);
358
393
  } catch (error) {
394
+ if (isProduction) {
395
+ throw error;
396
+ }
359
397
  consola.error("Failed to generate schema:", error);
360
398
  }
361
- nuxt.hook("hub:db:schema:extend", ({ paths, dialect: hookDialect }) => {
399
+ const nuxtWithHubHooks = nuxt;
400
+ nuxtWithHubHooks.hook("hub:db:schema:extend", ({ paths, dialect: hookDialect }) => {
362
401
  const schemaPath = join(nuxt.options.buildDir, "better-auth", `schema.${hookDialect}.ts`);
363
402
  if (existsSync(schemaPath)) {
364
- paths.push(schemaPath);
403
+ paths.unshift(schemaPath);
365
404
  }
366
405
  });
367
406
  }
@@ -1,6 +1,5 @@
1
1
  import { createAppAuthClient } from "#auth/client";
2
- import { computed, useRequestHeaders, useRequestURL, useRuntimeConfig, useState, watch } from "#imports";
3
- import { consola } from "consola";
2
+ import { computed, nextTick, useRequestHeaders, useRequestURL, useRuntimeConfig, useState, watch } from "#imports";
4
3
  let _client = null;
5
4
  function getClient(baseURL) {
6
5
  if (!_client)
@@ -30,7 +29,8 @@ export function useUserSession() {
30
29
  () => clientSession.value,
31
30
  (newSession) => {
32
31
  if (newSession?.data?.session && newSession?.data?.user) {
33
- session.value = newSession.data.session;
32
+ const { token: _, ...safeSession } = newSession.data.session;
33
+ session.value = safeSession;
34
34
  user.value = newSession.data.user;
35
35
  } else if (!newSession?.isPending) {
36
36
  clearSession();
@@ -62,6 +62,9 @@ export function useUserSession() {
62
62
  function wrapOnSuccess(cb) {
63
63
  return async (ctx) => {
64
64
  await fetchSession({ force: true });
65
+ if (!loggedIn.value)
66
+ await waitForSession();
67
+ await nextTick();
65
68
  await cb(ctx);
66
69
  };
67
70
  }
@@ -79,10 +82,11 @@ export function useUserSession() {
79
82
  }
80
83
  const signIn = client?.signIn ? new Proxy(client.signIn, {
81
84
  get(target, prop) {
82
- const method = target[prop];
85
+ const targetRecord = target;
86
+ const method = targetRecord[prop];
83
87
  if (typeof method !== "function")
84
88
  return method;
85
- return wrapAuthMethod((...args) => target[prop](...args));
89
+ return wrapAuthMethod((...args) => targetRecord[prop](...args));
86
90
  }
87
91
  }) : new Proxy({}, {
88
92
  get: (_, prop) => {
@@ -91,10 +95,11 @@ export function useUserSession() {
91
95
  });
92
96
  const signUp = client?.signUp ? new Proxy(client.signUp, {
93
97
  get(target, prop) {
94
- const method = target[prop];
98
+ const targetRecord = target;
99
+ const method = targetRecord[prop];
95
100
  if (typeof method !== "function")
96
101
  return method;
97
- return wrapAuthMethod((...args) => target[prop](...args));
102
+ return wrapAuthMethod((...args) => targetRecord[prop](...args));
98
103
  }
99
104
  }) : new Proxy({}, {
100
105
  get: (_, prop) => {
@@ -115,15 +120,15 @@ export function useUserSession() {
115
120
  const result = await client.getSession({ query }, fetchOptions);
116
121
  const data = result.data;
117
122
  if (data?.session && data?.user) {
118
- session.value = data.session;
123
+ const { token: _, ...safeSession } = data.session;
124
+ session.value = safeSession;
119
125
  user.value = data.user;
120
126
  } else {
121
127
  clearSession();
122
128
  }
123
129
  } catch (error) {
124
130
  clearSession();
125
- if (import.meta.dev)
126
- consola.error("Failed to fetch auth session:", error);
131
+ console.error("[nuxt-better-auth] Failed to fetch session:", error);
127
132
  } finally {
128
133
  if (!authReady.value)
129
134
  authReady.value = true;
@@ -1,6 +1,12 @@
1
- import { createError, defineNuxtRouteMiddleware, getRouteRules, navigateTo, useRequestHeaders, useRuntimeConfig } from "#imports";
1
+ import { createError, defineNuxtRouteMiddleware, getRouteRules, navigateTo, useNuxtApp, useRequestHeaders, useRuntimeConfig } from "#imports";
2
2
  import { matchesUser } from "../../utils/match-user.js";
3
3
  export default defineNuxtRouteMiddleware(async (to) => {
4
+ const nuxtApp = useNuxtApp();
5
+ if (import.meta.client) {
6
+ const isPrerendered = nuxtApp.payload.prerenderedAt || nuxtApp.payload.isCached;
7
+ if (isPrerendered && nuxtApp.isHydrating)
8
+ return;
9
+ }
4
10
  if (to.meta.auth === void 0) {
5
11
  const rules = await getRouteRules({ path: to.path });
6
12
  if (rules.auth !== void 0)
@@ -10,8 +16,8 @@ export default defineNuxtRouteMiddleware(async (to) => {
10
16
  if (auth === void 0 || auth === false)
11
17
  return;
12
18
  const config = useRuntimeConfig().public.auth;
13
- const { fetchSession, user, loggedIn, ready } = useUserSession();
14
- if (!loggedIn.value && !ready.value) {
19
+ const { fetchSession, user, loggedIn } = useUserSession();
20
+ if (!loggedIn.value) {
15
21
  const headers = import.meta.server ? useRequestHeaders(["cookie"]) : void 0;
16
22
  await fetchSession({ headers });
17
23
  }
@@ -12,7 +12,8 @@ export default defineNuxtPlugin({
12
12
  const headers = useRequestHeaders(["cookie"]);
13
13
  const data = await $fetch("/api/auth/get-session", { headers });
14
14
  if (data?.session && data?.user) {
15
- session.value = data.session;
15
+ const { token: _, ...safeSession } = data.session;
16
+ session.value = safeSession;
16
17
  user.value = data.user;
17
18
  }
18
19
  } catch {
@@ -22,6 +22,11 @@ export interface BetterAuthModuleOptions {
22
22
  };
23
23
  /** Enable KV secondary storage for sessions. Requires hub.kv: true */
24
24
  secondaryStorage?: boolean;
25
+ /** Schema generation options. Must match drizzleAdapter config. */
26
+ schema?: {
27
+ /** Plural table names: user → users. Default: false */
28
+ usePlural?: boolean;
29
+ };
25
30
  }
26
31
  export interface AuthRuntimeConfig {
27
32
  redirects: {
@@ -12,7 +12,7 @@ declare const _default: import("h3").EventHandler<import("h3").EventHandlerReque
12
12
  baseURL: string | undefined;
13
13
  basePath: string;
14
14
  socialProviders: string[];
15
- plugins: any[];
15
+ plugins: string[];
16
16
  trustedOrigins: string[] | ((request: Request) => string[] | Promise<string[]>);
17
17
  session: {
18
18
  expiresIn: string;
@@ -30,6 +30,6 @@ declare const _default: import("h3").EventHandler<import("h3").EventHandlerReque
30
30
  error?: undefined;
31
31
  } | {
32
32
  config: null;
33
- error: any;
33
+ error: string;
34
34
  }>>;
35
35
  export default _default;
@@ -41,6 +41,7 @@ export default defineEventHandler(async (event) => {
41
41
  }
42
42
  };
43
43
  } catch (error) {
44
- return { config: null, error: error.message || "Failed to fetch config" };
44
+ console.error("[DevTools] Config fetch failed:", error);
45
+ return { config: null, error: "Failed to fetch configuration" };
45
46
  }
46
47
  });
@@ -26,7 +26,16 @@ export default defineEventHandler(async (event) => {
26
26
  dbQuery.orderBy(desc(schema.session.createdAt)).limit(limit).offset(offset),
27
27
  countQuery
28
28
  ]);
29
- return { sessions, total: totalResult[0]?.count ?? 0, page, limit };
29
+ const safeSessions = sessions.map((s) => ({
30
+ id: s.id,
31
+ userId: s.userId,
32
+ createdAt: s.createdAt,
33
+ updatedAt: s.updatedAt,
34
+ expiresAt: s.expiresAt,
35
+ ipAddress: s.ipAddress,
36
+ userAgent: s.userAgent
37
+ }));
38
+ return { sessions: safeSessions, total: totalResult[0]?.count ?? 0, page, limit };
30
39
  } catch (error) {
31
40
  console.error("[DevTools] Fetch sessions failed:", error);
32
41
  return { sessions: [], total: 0, error: "Failed to fetch sessions" };
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.1",
4
+ "version": "0.0.2-alpha.3",
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",
@@ -95,9 +95,11 @@
95
95
  },
96
96
  "pnpm": {
97
97
  "onlyBuiltDependencies": [
98
+ "@parcel/watcher",
98
99
  "better-sqlite3",
99
100
  "esbuild",
100
- "@parcel/watcher"
101
+ "sharp",
102
+ "workerd"
101
103
  ],
102
104
  "patchedDependencies": {
103
105
  "@peculiar/x509@1.14.2": "patches/@peculiar__x509@1.14.2.patch",