@onmax/nuxt-better-auth 0.0.2-alpha.1 → 0.0.2-alpha.11
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 +1 -1
- package/dist/module.mjs +146 -33
- package/dist/runtime/app/composables/useUserSession.d.ts +13 -11
- package/dist/runtime/app/composables/useUserSession.js +16 -12
- package/dist/runtime/app/middleware/auth.global.js +9 -3
- package/dist/runtime/app/plugins/session.server.js +2 -1
- package/dist/runtime/config.d.ts +8 -0
- package/dist/runtime/server/api/_better-auth/config.get.d.ts +9 -9
- package/dist/runtime/server/api/_better-auth/config.get.js +4 -3
- package/dist/runtime/server/api/_better-auth/sessions.get.js +10 -1
- package/dist/runtime/server/api/auth/[...all].js +1 -1
- package/dist/runtime/server/utils/auth.d.ts +4 -9
- package/dist/runtime/server/utils/auth.js +10 -18
- package/dist/runtime/server/utils/session.js +2 -1
- package/dist/runtime/types/augment.d.ts +1 -1
- package/package.json +7 -3
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { defineNuxtModule, createResolver, hasNuxtModule, addTemplate, addTypeTemplate, updateTemplates, addServerImportsDir, addServerScanDir, addServerHandler, addImportsDir, addPlugin, addComponentsDir, extendPages } from '@nuxt/kit';
|
|
3
|
+
import { defineNuxtModule, createResolver, hasNuxtModule, addTemplate, addTypeTemplate, updateTemplates, addServerImportsDir, addServerImports, addServerScanDir, addServerHandler, addImportsDir, addPlugin, addComponentsDir, extendPages } from '@nuxt/kit';
|
|
4
4
|
import { consola as consola$1 } from 'consola';
|
|
5
5
|
import { defu } from 'defu';
|
|
6
6
|
import { join } from 'pathe';
|
|
7
7
|
import { toRouteMatcher, createRouter } from 'radix3';
|
|
8
|
+
import pluralize from 'pluralize';
|
|
8
9
|
export { defineClientAuth, defineServerAuth } from '../dist/runtime/config.js';
|
|
9
10
|
|
|
10
11
|
function setupDevTools(nuxt) {
|
|
@@ -22,46 +23,65 @@ function setupDevTools(nuxt) {
|
|
|
22
23
|
});
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
function toSnakeCase(str) {
|
|
27
|
+
return str.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
function generateDrizzleSchema(tables, dialect, options) {
|
|
30
|
+
const typedTables = tables;
|
|
31
|
+
const imports = getImports(dialect, options);
|
|
32
|
+
const tableDefinitions = Object.entries(typedTables).map(([tableName, table]) => generateTable(tableName, table, dialect, typedTables, options)).join("\n\n");
|
|
28
33
|
return `${imports}
|
|
29
34
|
|
|
30
35
|
${tableDefinitions}
|
|
31
36
|
`;
|
|
32
37
|
}
|
|
33
|
-
function getImports(dialect) {
|
|
38
|
+
function getImports(dialect, options) {
|
|
34
39
|
switch (dialect) {
|
|
35
40
|
case "sqlite":
|
|
36
41
|
return `import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'`;
|
|
37
42
|
case "postgresql":
|
|
38
|
-
return `import { boolean, pgTable, text, timestamp, integer } from 'drizzle-orm/pg-core'`;
|
|
43
|
+
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
44
|
case "mysql":
|
|
40
45
|
return `import { boolean, int, mysqlTable, text, timestamp, varchar } from 'drizzle-orm/mysql-core'`;
|
|
41
46
|
}
|
|
42
47
|
}
|
|
43
|
-
function generateTable(tableName, table, dialect, allTables) {
|
|
48
|
+
function generateTable(tableName, table, dialect, allTables, options) {
|
|
44
49
|
const tableFunc = dialect === "sqlite" ? "sqliteTable" : dialect === "postgresql" ? "pgTable" : "mysqlTable";
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
const hasCustomModelName = table.modelName && table.modelName !== tableName;
|
|
51
|
+
let dbTableName = hasCustomModelName ? table.modelName : tableName;
|
|
52
|
+
if (options?.casing === "snake_case" && !hasCustomModelName)
|
|
53
|
+
dbTableName = toSnakeCase(dbTableName);
|
|
54
|
+
if (options?.usePlural && !hasCustomModelName)
|
|
55
|
+
dbTableName = pluralize(dbTableName);
|
|
56
|
+
const fields = Object.entries(table.fields).map(([fieldName, field]) => generateField(fieldName, field, dialect, allTables, options)).join(",\n ");
|
|
57
|
+
const idField = generateIdField(dialect, options);
|
|
48
58
|
return `export const ${tableName} = ${tableFunc}('${dbTableName}', {
|
|
49
59
|
${idField},
|
|
50
60
|
${fields}
|
|
51
61
|
})`;
|
|
52
62
|
}
|
|
53
|
-
function generateIdField(dialect) {
|
|
63
|
+
function generateIdField(dialect, options) {
|
|
54
64
|
switch (dialect) {
|
|
55
65
|
case "sqlite":
|
|
56
|
-
|
|
66
|
+
if (options?.useUuid)
|
|
67
|
+
consola$1.warn("[@onmax/nuxt-better-auth] useUuid ignored for SQLite (no native uuid type). Using text.");
|
|
57
68
|
return `id: text('id').primaryKey()`;
|
|
69
|
+
case "postgresql":
|
|
70
|
+
return options?.useUuid ? `id: uuid('id').defaultRandom().primaryKey()` : `id: text('id').primaryKey()`;
|
|
58
71
|
case "mysql":
|
|
59
72
|
return `id: varchar('id', { length: 36 }).primaryKey()`;
|
|
60
73
|
}
|
|
61
74
|
}
|
|
62
|
-
function generateField(fieldName, field, dialect, allTables) {
|
|
63
|
-
const dbFieldName = fieldName;
|
|
64
|
-
|
|
75
|
+
function generateField(fieldName, field, dialect, allTables, options) {
|
|
76
|
+
const dbFieldName = options?.casing === "snake_case" ? toSnakeCase(fieldName) : fieldName;
|
|
77
|
+
const isFkToId = options?.useUuid && field.references?.field === "id";
|
|
78
|
+
let fieldDef;
|
|
79
|
+
if (isFkToId && dialect === "postgresql")
|
|
80
|
+
fieldDef = `uuid('${dbFieldName}')`;
|
|
81
|
+
else if (isFkToId && dialect === "mysql")
|
|
82
|
+
fieldDef = `varchar('${dbFieldName}', { length: 36 })`;
|
|
83
|
+
else
|
|
84
|
+
fieldDef = getFieldType(field.type, dialect, dbFieldName);
|
|
65
85
|
if (field.required && field.defaultValue === void 0)
|
|
66
86
|
fieldDef += ".notNull()";
|
|
67
87
|
if (field.unique)
|
|
@@ -71,11 +91,15 @@ function generateField(fieldName, field, dialect, allTables) {
|
|
|
71
91
|
fieldDef += `.default(${field.defaultValue})`;
|
|
72
92
|
else if (typeof field.defaultValue === "string")
|
|
73
93
|
fieldDef += `.default('${field.defaultValue}')`;
|
|
94
|
+
else if (typeof field.defaultValue === "function")
|
|
95
|
+
fieldDef += `.$defaultFn(${field.defaultValue})`;
|
|
74
96
|
else
|
|
75
97
|
fieldDef += `.default(${field.defaultValue})`;
|
|
76
98
|
if (field.required)
|
|
77
99
|
fieldDef += ".notNull()";
|
|
78
100
|
}
|
|
101
|
+
if (typeof field.onUpdate === "function" && field.type === "date")
|
|
102
|
+
fieldDef += `.$onUpdate(${field.onUpdate})`;
|
|
79
103
|
if (field.references) {
|
|
80
104
|
const refTable = field.references.model;
|
|
81
105
|
if (allTables[refTable])
|
|
@@ -136,22 +160,55 @@ function getMysqlType(type, fieldName) {
|
|
|
136
160
|
return `text('${fieldName}')`;
|
|
137
161
|
}
|
|
138
162
|
}
|
|
139
|
-
async function loadUserAuthConfig(configPath) {
|
|
163
|
+
async function loadUserAuthConfig(configPath, throwOnError = false) {
|
|
140
164
|
const { createJiti } = await import('jiti');
|
|
141
|
-
const
|
|
165
|
+
const { defineServerAuth } = await import('../dist/runtime/config.js');
|
|
166
|
+
const jiti = createJiti(import.meta.url, { interopDefault: true, moduleCache: false });
|
|
167
|
+
const key = "defineServerAuth";
|
|
168
|
+
const g = globalThis;
|
|
169
|
+
if (!g[key]) {
|
|
170
|
+
defineServerAuth._count = 0;
|
|
171
|
+
g[key] = defineServerAuth;
|
|
172
|
+
}
|
|
173
|
+
g[key]._count++;
|
|
142
174
|
try {
|
|
143
175
|
const mod = await jiti.import(configPath);
|
|
144
|
-
const configFn = mod.default
|
|
176
|
+
const configFn = typeof mod === "object" && mod !== null && "default" in mod ? mod.default : mod;
|
|
145
177
|
if (typeof configFn === "function") {
|
|
146
178
|
return configFn({ runtimeConfig: {}, db: null });
|
|
147
179
|
}
|
|
180
|
+
if (throwOnError) {
|
|
181
|
+
throw new Error("auth.config.ts must export default defineServerAuth(...)");
|
|
182
|
+
}
|
|
148
183
|
return {};
|
|
149
184
|
} catch (error) {
|
|
185
|
+
if (throwOnError) {
|
|
186
|
+
throw new Error(`Failed to load auth config: ${error instanceof Error ? error.message : error}`);
|
|
187
|
+
}
|
|
150
188
|
consola$1.error("[@onmax/nuxt-better-auth] Failed to load auth config for schema generation. Schema may be incomplete:", error);
|
|
151
189
|
return {};
|
|
190
|
+
} finally {
|
|
191
|
+
g[key]._count--;
|
|
192
|
+
if (!g[key]._count) {
|
|
193
|
+
delete g[key];
|
|
194
|
+
}
|
|
152
195
|
}
|
|
153
196
|
}
|
|
154
197
|
|
|
198
|
+
function getHubDialect(hub) {
|
|
199
|
+
if (!hub?.db)
|
|
200
|
+
return void 0;
|
|
201
|
+
if (typeof hub.db === "string")
|
|
202
|
+
return hub.db;
|
|
203
|
+
if (typeof hub.db === "object" && hub.db !== null)
|
|
204
|
+
return hub.db.dialect;
|
|
205
|
+
return void 0;
|
|
206
|
+
}
|
|
207
|
+
function getHubCasing(hub) {
|
|
208
|
+
if (!hub?.db || typeof hub.db !== "object" || hub.db === null)
|
|
209
|
+
return void 0;
|
|
210
|
+
return hub.db.casing;
|
|
211
|
+
}
|
|
155
212
|
const consola = consola$1.withTag("nuxt-better-auth");
|
|
156
213
|
const module$1 = defineNuxtModule({
|
|
157
214
|
meta: { name: "@onmax/nuxt-better-auth", configKey: "auth", compatibility: { nuxt: ">=3.0.0" } },
|
|
@@ -186,14 +243,24 @@ const module$1 = defineNuxtModule({
|
|
|
186
243
|
redirects: { login: options.redirects?.login ?? "/login", guest: options.redirects?.guest ?? "/" },
|
|
187
244
|
useDatabase: hasHubDb
|
|
188
245
|
});
|
|
189
|
-
|
|
246
|
+
const betterAuthSecret = process.env.BETTER_AUTH_SECRET || process.env.NUXT_BETTER_AUTH_SECRET || nuxt.options.runtimeConfig.betterAuthSecret || "";
|
|
247
|
+
if (!nuxt.options.dev && !betterAuthSecret) {
|
|
248
|
+
throw new Error("[nuxt-better-auth] BETTER_AUTH_SECRET is required in production. Set BETTER_AUTH_SECRET or NUXT_BETTER_AUTH_SECRET environment variable.");
|
|
249
|
+
}
|
|
250
|
+
if (betterAuthSecret && betterAuthSecret.length < 32) {
|
|
251
|
+
throw new Error("[nuxt-better-auth] BETTER_AUTH_SECRET must be at least 32 characters for security");
|
|
252
|
+
}
|
|
253
|
+
nuxt.options.runtimeConfig.betterAuthSecret = betterAuthSecret;
|
|
190
254
|
nuxt.options.runtimeConfig.auth = defu(nuxt.options.runtimeConfig.auth, {
|
|
191
255
|
secondaryStorage: secondaryStorageEnabled
|
|
192
256
|
});
|
|
193
257
|
nuxt.options.alias["#nuxt-better-auth"] = resolver.resolve("./runtime/types/augment");
|
|
194
258
|
nuxt.options.alias["#auth/server"] = serverConfigPath;
|
|
195
259
|
nuxt.options.alias["#auth/client"] = clientConfigPath;
|
|
196
|
-
|
|
260
|
+
if (secondaryStorageEnabled && !nuxt.options.alias["hub:kv"]) {
|
|
261
|
+
throw new Error("[nuxt-better-auth] hub:kv not found. Ensure @nuxthub/core is loaded before this module and hub.kv is enabled.");
|
|
262
|
+
}
|
|
263
|
+
const secondaryStorageCode = secondaryStorageEnabled ? `import { kv } from '../hub/kv.mjs'
|
|
197
264
|
export function createSecondaryStorage() {
|
|
198
265
|
return {
|
|
199
266
|
get: async (key) => kv.get(\`_auth:\${key}\`),
|
|
@@ -203,10 +270,13 @@ export function createSecondaryStorage() {
|
|
|
203
270
|
}` : `export function createSecondaryStorage() { return undefined }`;
|
|
204
271
|
const secondaryStorageTemplate = addTemplate({ filename: "better-auth/secondary-storage.mjs", getContents: () => secondaryStorageCode, write: true });
|
|
205
272
|
nuxt.options.alias["#auth/secondary-storage"] = secondaryStorageTemplate.dst;
|
|
206
|
-
|
|
207
|
-
|
|
273
|
+
if (hasHubDb && !nuxt.options.alias["hub:db"]) {
|
|
274
|
+
throw new Error("[nuxt-better-auth] hub:db not found. Ensure @nuxthub/core is loaded before this module and hub.db is configured.");
|
|
275
|
+
}
|
|
276
|
+
const hubDialect = getHubDialect(hub) ?? "sqlite";
|
|
277
|
+
const databaseCode = hasHubDb ? `import { db, schema } from '../hub/db.mjs'
|
|
208
278
|
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
|
|
209
|
-
const rawDialect = '${
|
|
279
|
+
const rawDialect = '${hubDialect}'
|
|
210
280
|
const dialect = rawDialect === 'postgresql' ? 'pg' : rawDialect
|
|
211
281
|
export function createDatabase() { return drizzleAdapter(db, { provider: dialect, schema }) }
|
|
212
282
|
export { db }` : `export function createDatabase() { return undefined }
|
|
@@ -260,6 +330,18 @@ declare module '#nuxt-better-auth' {
|
|
|
260
330
|
${hasHubDb ? `db: typeof import('hub:db')['db']` : ""}
|
|
261
331
|
}
|
|
262
332
|
}
|
|
333
|
+
|
|
334
|
+
// Augment the config module to use the extended ServerAuthContext
|
|
335
|
+
interface _AugmentedServerAuthContext {
|
|
336
|
+
runtimeConfig: RuntimeConfig
|
|
337
|
+
${hasHubDb ? `db: typeof import('hub:db')['db']` : "db: unknown"}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
declare module '@onmax/nuxt-better-auth/config' {
|
|
341
|
+
import type { BetterAuthOptions } from 'better-auth'
|
|
342
|
+
type ServerAuthConfig = Omit<BetterAuthOptions, 'database' | 'secret' | 'baseURL'>
|
|
343
|
+
export function defineServerAuth<T extends ServerAuthConfig>(config: (ctx: _AugmentedServerAuthContext) => T): (ctx: _AugmentedServerAuthContext) => T
|
|
344
|
+
}
|
|
263
345
|
`
|
|
264
346
|
});
|
|
265
347
|
addTypeTemplate({
|
|
@@ -274,19 +356,32 @@ declare module '#nuxt-better-auth' {
|
|
|
274
356
|
addTypeTemplate({
|
|
275
357
|
filename: "types/nuxt-better-auth-nitro.d.ts",
|
|
276
358
|
getContents: () => `
|
|
359
|
+
declare module 'nitropack' {
|
|
360
|
+
interface NitroRouteRules {
|
|
361
|
+
auth?: import('${resolver.resolve("./runtime/types")}').AuthMeta
|
|
362
|
+
}
|
|
363
|
+
interface NitroRouteConfig {
|
|
364
|
+
auth?: import('${resolver.resolve("./runtime/types")}').AuthMeta
|
|
365
|
+
}
|
|
366
|
+
}
|
|
277
367
|
declare module 'nitropack/types' {
|
|
278
368
|
interface NitroRouteRules {
|
|
279
369
|
auth?: import('${resolver.resolve("./runtime/types")}').AuthMeta
|
|
280
370
|
}
|
|
371
|
+
interface NitroRouteConfig {
|
|
372
|
+
auth?: import('${resolver.resolve("./runtime/types")}').AuthMeta
|
|
373
|
+
}
|
|
281
374
|
}
|
|
375
|
+
export {}
|
|
282
376
|
`
|
|
283
|
-
});
|
|
377
|
+
}, { nuxt: true, nitro: true, node: true });
|
|
284
378
|
nuxt.hook("builder:watch", async (_event, relativePath) => {
|
|
285
379
|
if (relativePath.includes("auth.config")) {
|
|
286
380
|
await updateTemplates({ filter: (t) => t.filename.includes("nuxt-better-auth") });
|
|
287
381
|
}
|
|
288
382
|
});
|
|
289
383
|
addServerImportsDir(resolver.resolve("./runtime/server/utils"));
|
|
384
|
+
addServerImports([{ name: "defineServerAuth", from: resolver.resolve("./runtime/config") }]);
|
|
290
385
|
addServerScanDir(resolver.resolve("./runtime/server/middleware"));
|
|
291
386
|
addServerHandler({ route: "/api/auth/**", handler: resolver.resolve("./runtime/server/api/auth/[...all]") });
|
|
292
387
|
addImportsDir(resolver.resolve("./runtime/app/composables"));
|
|
@@ -298,9 +393,10 @@ declare module 'nitropack/types' {
|
|
|
298
393
|
app.middleware.push({ name: "auth", path: resolver.resolve("./runtime/app/middleware/auth.global"), global: true });
|
|
299
394
|
});
|
|
300
395
|
if (hasHubDb) {
|
|
301
|
-
await setupBetterAuthSchema(nuxt, serverConfigPath);
|
|
396
|
+
await setupBetterAuthSchema(nuxt, serverConfigPath, options);
|
|
302
397
|
}
|
|
303
|
-
|
|
398
|
+
const isProduction = process.env.NODE_ENV === "production" || !nuxt.options.dev;
|
|
399
|
+
if (!isProduction) {
|
|
304
400
|
setupDevTools(nuxt);
|
|
305
401
|
addServerHandler({ route: "/api/_better-auth/config", method: "get", handler: resolver.resolve("./runtime/server/api/_better-auth/config.get") });
|
|
306
402
|
if (hasHubDb) {
|
|
@@ -333,22 +429,35 @@ declare module 'nitropack/types' {
|
|
|
333
429
|
});
|
|
334
430
|
}
|
|
335
431
|
});
|
|
336
|
-
async function setupBetterAuthSchema(nuxt, serverConfigPath) {
|
|
432
|
+
async function setupBetterAuthSchema(nuxt, serverConfigPath, options) {
|
|
337
433
|
const hub = nuxt.options.hub;
|
|
338
|
-
const dialect =
|
|
434
|
+
const dialect = getHubDialect(hub);
|
|
339
435
|
if (!dialect || !["sqlite", "postgresql", "mysql"].includes(dialect)) {
|
|
340
436
|
consola.warn(`Unsupported database dialect: ${dialect}`);
|
|
341
437
|
return;
|
|
342
438
|
}
|
|
439
|
+
const isProduction = !nuxt.options.dev;
|
|
343
440
|
try {
|
|
344
441
|
const configFile = `${serverConfigPath}.ts`;
|
|
345
|
-
const userConfig = await loadUserAuthConfig(configFile);
|
|
442
|
+
const userConfig = await loadUserAuthConfig(configFile, isProduction);
|
|
346
443
|
const extendedConfig = {};
|
|
347
444
|
await nuxt.callHook("better-auth:config:extend", extendedConfig);
|
|
348
445
|
const plugins = [...userConfig.plugins || [], ...extendedConfig.plugins || []];
|
|
349
446
|
const { getAuthTables } = await import('better-auth/db');
|
|
350
|
-
const tables = getAuthTables({
|
|
351
|
-
|
|
447
|
+
const tables = getAuthTables({
|
|
448
|
+
plugins,
|
|
449
|
+
secondaryStorage: options.secondaryStorage ? {
|
|
450
|
+
get: async (_key) => null,
|
|
451
|
+
set: async (_key, _value, _ttl) => {
|
|
452
|
+
},
|
|
453
|
+
delete: async (_key) => {
|
|
454
|
+
}
|
|
455
|
+
} : void 0
|
|
456
|
+
});
|
|
457
|
+
const useUuid = userConfig.advanced?.database?.generateId === "uuid";
|
|
458
|
+
const hubCasing = getHubCasing(hub);
|
|
459
|
+
const schemaOptions = { ...options.schema, useUuid, casing: options.schema?.casing ?? hubCasing };
|
|
460
|
+
const schemaCode = generateDrizzleSchema(tables, dialect, schemaOptions);
|
|
352
461
|
const schemaDir = join(nuxt.options.buildDir, "better-auth");
|
|
353
462
|
const schemaPath = join(schemaDir, `schema.${dialect}.ts`);
|
|
354
463
|
await mkdir(schemaDir, { recursive: true });
|
|
@@ -356,12 +465,16 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath) {
|
|
|
356
465
|
addTemplate({ filename: `better-auth/schema.${dialect}.ts`, getContents: () => schemaCode, write: true });
|
|
357
466
|
consola.info(`Generated ${dialect} schema with ${Object.keys(tables).length} tables`);
|
|
358
467
|
} catch (error) {
|
|
468
|
+
if (isProduction) {
|
|
469
|
+
throw error;
|
|
470
|
+
}
|
|
359
471
|
consola.error("Failed to generate schema:", error);
|
|
360
472
|
}
|
|
361
|
-
|
|
473
|
+
const nuxtWithHubHooks = nuxt;
|
|
474
|
+
nuxtWithHubHooks.hook("hub:db:schema:extend", ({ paths, dialect: hookDialect }) => {
|
|
362
475
|
const schemaPath = join(nuxt.options.buildDir, "better-auth", `schema.${hookDialect}.ts`);
|
|
363
476
|
if (existsSync(schemaPath)) {
|
|
364
|
-
paths.
|
|
477
|
+
paths.unshift(schemaPath);
|
|
365
478
|
}
|
|
366
479
|
});
|
|
367
480
|
}
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import type { AuthUser } from '#nuxt-better-auth';
|
|
1
|
+
import type { AppAuthClient, AuthSession, AuthUser } from '#nuxt-better-auth';
|
|
2
|
+
import type { ComputedRef, Ref } from 'vue';
|
|
2
3
|
export interface SignOutOptions {
|
|
3
4
|
onSuccess?: () => void | Promise<void>;
|
|
4
5
|
}
|
|
5
|
-
export
|
|
6
|
-
client:
|
|
7
|
-
session:
|
|
8
|
-
user:
|
|
9
|
-
loggedIn:
|
|
10
|
-
ready:
|
|
11
|
-
signIn:
|
|
12
|
-
signUp:
|
|
13
|
-
signOut: (options?: SignOutOptions) => Promise<
|
|
6
|
+
export interface UseUserSessionReturn {
|
|
7
|
+
client: AppAuthClient | null;
|
|
8
|
+
session: Ref<AuthSession | null>;
|
|
9
|
+
user: Ref<AuthUser | null>;
|
|
10
|
+
loggedIn: ComputedRef<boolean>;
|
|
11
|
+
ready: ComputedRef<boolean>;
|
|
12
|
+
signIn: NonNullable<AppAuthClient>['signIn'];
|
|
13
|
+
signUp: NonNullable<AppAuthClient>['signUp'];
|
|
14
|
+
signOut: (options?: SignOutOptions) => Promise<void>;
|
|
14
15
|
waitForSession: () => Promise<void>;
|
|
15
16
|
fetchSession: (options?: {
|
|
16
17
|
headers?: HeadersInit;
|
|
17
18
|
force?: boolean;
|
|
18
19
|
}) => Promise<void>;
|
|
19
20
|
updateUser: (updates: Partial<AuthUser>) => void;
|
|
20
|
-
}
|
|
21
|
+
}
|
|
22
|
+
export declare function useUserSession(): UseUserSessionReturn;
|
|
@@ -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
|
-
|
|
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
|
|
85
|
+
const targetRecord = target;
|
|
86
|
+
const method = targetRecord[prop];
|
|
83
87
|
if (typeof method !== "function")
|
|
84
88
|
return method;
|
|
85
|
-
return wrapAuthMethod((...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
|
|
98
|
+
const targetRecord = target;
|
|
99
|
+
const method = targetRecord[prop];
|
|
95
100
|
if (typeof method !== "function")
|
|
96
101
|
return method;
|
|
97
|
-
return wrapAuthMethod((...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
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -133,11 +138,10 @@ export function useUserSession() {
|
|
|
133
138
|
async function signOut(options) {
|
|
134
139
|
if (!client)
|
|
135
140
|
throw new Error("signOut can only be called on client-side");
|
|
136
|
-
|
|
141
|
+
await client.signOut();
|
|
137
142
|
clearSession();
|
|
138
143
|
if (options?.onSuccess)
|
|
139
144
|
await options.onSuccess();
|
|
140
|
-
return response;
|
|
141
145
|
}
|
|
142
146
|
return {
|
|
143
147
|
client,
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { createError, defineNuxtRouteMiddleware, getRouteRules, navigateTo, useRequestHeaders, useRuntimeConfig } from "#imports";
|
|
1
|
+
import { createError, defineNuxtRouteMiddleware, getRouteRules, navigateTo, useNuxtApp, useRequestHeaders, useRuntimeConfig, useUserSession } 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
|
|
14
|
-
if (!loggedIn.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
|
-
|
|
15
|
+
const { token: _, ...safeSession } = data.session;
|
|
16
|
+
session.value = safeSession;
|
|
16
17
|
user.value = data.user;
|
|
17
18
|
}
|
|
18
19
|
} catch {
|
package/dist/runtime/config.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BetterAuthOptions } from 'better-auth';
|
|
2
2
|
import type { ClientOptions } from 'better-auth/client';
|
|
3
|
+
import type { CasingOption } from '../schema-generator.js';
|
|
3
4
|
import type { ServerAuthContext } from './types/augment.js';
|
|
4
5
|
export type { ServerAuthContext };
|
|
5
6
|
export interface ClientAuthContext {
|
|
@@ -22,6 +23,13 @@ export interface BetterAuthModuleOptions {
|
|
|
22
23
|
};
|
|
23
24
|
/** Enable KV secondary storage for sessions. Requires hub.kv: true */
|
|
24
25
|
secondaryStorage?: boolean;
|
|
26
|
+
/** Schema generation options. Must match drizzleAdapter config. */
|
|
27
|
+
schema?: {
|
|
28
|
+
/** Plural table names: user → users. Default: false */
|
|
29
|
+
usePlural?: boolean;
|
|
30
|
+
/** Column/table name casing. Explicit value takes precedence over hub.db.casing. */
|
|
31
|
+
casing?: CasingOption;
|
|
32
|
+
};
|
|
25
33
|
}
|
|
26
34
|
export interface AuthRuntimeConfig {
|
|
27
35
|
redirects: {
|
|
@@ -9,27 +9,27 @@ declare const _default: import("h3").EventHandler<import("h3").EventHandlerReque
|
|
|
9
9
|
useDatabase: boolean;
|
|
10
10
|
};
|
|
11
11
|
server: {
|
|
12
|
-
baseURL:
|
|
13
|
-
basePath:
|
|
12
|
+
baseURL: any;
|
|
13
|
+
basePath: any;
|
|
14
14
|
socialProviders: string[];
|
|
15
|
-
plugins: any
|
|
16
|
-
trustedOrigins:
|
|
15
|
+
plugins: any;
|
|
16
|
+
trustedOrigins: any;
|
|
17
17
|
session: {
|
|
18
18
|
expiresIn: string;
|
|
19
19
|
updateAge: string;
|
|
20
|
-
cookieCache:
|
|
20
|
+
cookieCache: any;
|
|
21
21
|
};
|
|
22
22
|
emailAndPassword: boolean;
|
|
23
|
-
rateLimit:
|
|
23
|
+
rateLimit: any;
|
|
24
24
|
advanced: {
|
|
25
|
-
useSecureCookies:
|
|
26
|
-
disableCSRFCheck:
|
|
25
|
+
useSecureCookies: any;
|
|
26
|
+
disableCSRFCheck: any;
|
|
27
27
|
};
|
|
28
28
|
};
|
|
29
29
|
};
|
|
30
30
|
error?: undefined;
|
|
31
31
|
} | {
|
|
32
32
|
config: null;
|
|
33
|
-
error:
|
|
33
|
+
error: string;
|
|
34
34
|
}>>;
|
|
35
35
|
export default _default;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { defineEventHandler } from "h3";
|
|
2
2
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
3
|
import { serverAuth } from "../../utils/auth.js";
|
|
4
|
-
export default defineEventHandler(async (
|
|
4
|
+
export default defineEventHandler(async () => {
|
|
5
5
|
try {
|
|
6
|
-
const auth =
|
|
6
|
+
const auth = serverAuth();
|
|
7
7
|
const options = auth.options;
|
|
8
8
|
const runtimeConfig = useRuntimeConfig();
|
|
9
9
|
const publicAuth = runtimeConfig.public?.auth;
|
|
@@ -41,6 +41,7 @@ export default defineEventHandler(async (event) => {
|
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
} catch (error) {
|
|
44
|
-
|
|
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
|
-
|
|
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" };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineEventHandler, toWebRequest } from "h3";
|
|
2
2
|
import { serverAuth } from "../../utils/auth.js";
|
|
3
3
|
export default defineEventHandler(async (event) => {
|
|
4
|
-
const auth =
|
|
4
|
+
const auth = serverAuth();
|
|
5
5
|
return auth.handler(toWebRequest(event));
|
|
6
6
|
});
|
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import
|
|
3
|
-
type AuthInstance = ReturnType<typeof
|
|
4
|
-
declare
|
|
5
|
-
interface H3EventContext {
|
|
6
|
-
_betterAuth?: AuthInstance;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
export declare function serverAuth(event: H3Event): Promise<AuthInstance>;
|
|
1
|
+
import type { Auth } from 'better-auth';
|
|
2
|
+
import createServerAuth from '#auth/server';
|
|
3
|
+
type AuthInstance = Auth<ReturnType<typeof createServerAuth>>;
|
|
4
|
+
export declare function serverAuth(): AuthInstance;
|
|
10
5
|
export {};
|
|
@@ -2,31 +2,23 @@ import { createDatabase, db } from "#auth/database";
|
|
|
2
2
|
import { createSecondaryStorage } from "#auth/secondary-storage";
|
|
3
3
|
import createServerAuth from "#auth/server";
|
|
4
4
|
import { betterAuth } from "better-auth";
|
|
5
|
-
import { consola } from "consola";
|
|
6
|
-
import { getRequestURL } from "h3";
|
|
7
5
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
8
|
-
|
|
9
|
-
function
|
|
10
|
-
if (
|
|
11
|
-
return
|
|
12
|
-
const origin = getRequestURL(event).origin;
|
|
13
|
-
if (process.env.NODE_ENV === "production")
|
|
14
|
-
throw new Error("siteUrl must be configured in production. Set NUXT_PUBLIC_SITE_URL or configure in nuxt.config.");
|
|
15
|
-
logger.warn("siteUrl not set, auto-detected:", origin);
|
|
16
|
-
return origin;
|
|
17
|
-
}
|
|
18
|
-
export async function serverAuth(event) {
|
|
19
|
-
if (event.context._betterAuth)
|
|
20
|
-
return event.context._betterAuth;
|
|
6
|
+
let _auth = null;
|
|
7
|
+
export function serverAuth() {
|
|
8
|
+
if (_auth)
|
|
9
|
+
return _auth;
|
|
21
10
|
const runtimeConfig = useRuntimeConfig();
|
|
11
|
+
const siteUrl = runtimeConfig.public.siteUrl;
|
|
12
|
+
if (!siteUrl)
|
|
13
|
+
throw new Error("siteUrl must be configured. Set NUXT_PUBLIC_SITE_URL or configure in nuxt.config.");
|
|
22
14
|
const database = createDatabase();
|
|
23
15
|
const userConfig = createServerAuth({ runtimeConfig, db });
|
|
24
|
-
|
|
16
|
+
_auth = betterAuth({
|
|
25
17
|
...userConfig,
|
|
26
18
|
...database && { database },
|
|
27
19
|
secondaryStorage: createSecondaryStorage(),
|
|
28
20
|
secret: runtimeConfig.betterAuthSecret,
|
|
29
|
-
baseURL:
|
|
21
|
+
baseURL: siteUrl
|
|
30
22
|
});
|
|
31
|
-
return
|
|
23
|
+
return _auth;
|
|
32
24
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createError } from "h3";
|
|
2
2
|
import { matchesUser } from "../../utils/match-user.js";
|
|
3
|
+
import { serverAuth } from "./auth.js";
|
|
3
4
|
export async function getUserSession(event) {
|
|
4
|
-
const auth =
|
|
5
|
+
const auth = serverAuth();
|
|
5
6
|
const session = await auth.api.getSession({ headers: event.headers });
|
|
6
7
|
return session;
|
|
7
8
|
}
|
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.
|
|
4
|
+
"version": "0.0.2-alpha.11",
|
|
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",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"dev:prepare": "nuxt-module-build build --stub && nuxi prepare playground",
|
|
42
42
|
"dev:docs": "nuxi dev docs",
|
|
43
43
|
"build:docs": "nuxi build docs",
|
|
44
|
-
"release": "bumpp
|
|
44
|
+
"release": "bumpp --push",
|
|
45
45
|
"lint": "eslint .",
|
|
46
46
|
"lint:fix": "eslint . --fix",
|
|
47
47
|
"typecheck": "vue-tsc --noEmit",
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"defu": "^6.1.4",
|
|
64
64
|
"jiti": "^2.4.2",
|
|
65
65
|
"pathe": "^2.0.3",
|
|
66
|
+
"pluralize": "^8.0.0",
|
|
66
67
|
"radix3": "^1.1.2"
|
|
67
68
|
},
|
|
68
69
|
"devDependencies": {
|
|
@@ -77,6 +78,7 @@
|
|
|
77
78
|
"@nuxthub/core": "^0.10.3",
|
|
78
79
|
"@types/better-sqlite3": "^7.6.13",
|
|
79
80
|
"@types/node": "latest",
|
|
81
|
+
"@types/pluralize": "^0.0.33",
|
|
80
82
|
"better-auth": "^1.4.7",
|
|
81
83
|
"better-sqlite3": "^11.9.1",
|
|
82
84
|
"bumpp": "^10.3.2",
|
|
@@ -95,9 +97,11 @@
|
|
|
95
97
|
},
|
|
96
98
|
"pnpm": {
|
|
97
99
|
"onlyBuiltDependencies": [
|
|
100
|
+
"@parcel/watcher",
|
|
98
101
|
"better-sqlite3",
|
|
99
102
|
"esbuild",
|
|
100
|
-
"
|
|
103
|
+
"sharp",
|
|
104
|
+
"workerd"
|
|
101
105
|
],
|
|
102
106
|
"patchedDependencies": {
|
|
103
107
|
"@peculiar/x509@1.14.2": "patches/@peculiar__x509@1.14.2.patch",
|