@onmax/nuxt-better-auth 0.0.2-alpha.13 → 0.0.2-alpha.15
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 +163 -172
- package/dist/runtime/app/composables/useUserSession.js +1 -1
- package/dist/runtime/config.d.ts +5 -4
- package/dist/runtime/config.js +7 -2
- package/dist/runtime/server/utils/auth.js +38 -3
- package/package.json +18 -8
- package/skills/nuxt-better-auth/SKILL.md +92 -0
- package/skills/nuxt-better-auth/references/client-auth.md +153 -0
- package/skills/nuxt-better-auth/references/client-only.md +87 -0
- package/skills/nuxt-better-auth/references/database.md +115 -0
- package/skills/nuxt-better-auth/references/installation.md +143 -0
- package/skills/nuxt-better-auth/references/plugins.md +139 -0
- package/skills/nuxt-better-auth/references/route-protection.md +105 -0
- package/skills/nuxt-better-auth/references/server-auth.md +135 -0
- package/skills/nuxt-better-auth/references/types.md +144 -0
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { existsSync, writeFileSync, readFileSync } from 'node:fs';
|
|
2
3
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { defineNuxtModule, createResolver, hasNuxtModule, addTemplate, addTypeTemplate, updateTemplates, addServerImportsDir, addServerImports, addServerScanDir, addServerHandler, addImportsDir, addPlugin, addComponentsDir, extendPages } from '@nuxt/kit';
|
|
4
|
+
import { defineNuxtModule, createResolver, hasNuxtModule, addTemplate, addTypeTemplate, updateTemplates, addServerImportsDir, addServerImports, addServerScanDir, addServerHandler, addImportsDir, addPlugin, addComponentsDir, installModule, extendPages } from '@nuxt/kit';
|
|
4
5
|
import { consola as consola$1 } from 'consola';
|
|
5
6
|
import { defu } from 'defu';
|
|
6
|
-
import { join } from 'pathe';
|
|
7
|
+
import { join, dirname } from 'pathe';
|
|
7
8
|
import { toRouteMatcher, createRouter } from 'radix3';
|
|
8
|
-
import
|
|
9
|
+
import { isCI, isTest } from 'std-env';
|
|
10
|
+
import { generateDrizzleSchema as generateDrizzleSchema$1 } from '@better-auth/cli/api';
|
|
9
11
|
export { defineClientAuth, defineServerAuth } from '../dist/runtime/config.js';
|
|
10
12
|
|
|
13
|
+
const version = "0.0.2-alpha.15";
|
|
14
|
+
|
|
11
15
|
function setupDevTools(nuxt) {
|
|
12
16
|
nuxt.hook("devtools:customTabs", (tabs) => {
|
|
13
17
|
tabs.push({
|
|
@@ -23,160 +27,51 @@ function setupDevTools(nuxt) {
|
|
|
23
27
|
});
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
function
|
|
27
|
-
return
|
|
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");
|
|
33
|
-
return `${imports}
|
|
34
|
-
|
|
35
|
-
${tableDefinitions}
|
|
36
|
-
`;
|
|
37
|
-
}
|
|
38
|
-
function getImports(dialect, options) {
|
|
39
|
-
switch (dialect) {
|
|
40
|
-
case "sqlite":
|
|
41
|
-
return `import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'`;
|
|
42
|
-
case "postgresql":
|
|
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'`;
|
|
44
|
-
case "mysql":
|
|
45
|
-
return `import { boolean, int, mysqlTable, text, timestamp, varchar } from 'drizzle-orm/mysql-core'`;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function generateTable(tableName, table, dialect, allTables, options) {
|
|
49
|
-
const tableFunc = dialect === "sqlite" ? "sqliteTable" : dialect === "postgresql" ? "pgTable" : "mysqlTable";
|
|
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);
|
|
58
|
-
return `export const ${tableName} = ${tableFunc}('${dbTableName}', {
|
|
59
|
-
${idField},
|
|
60
|
-
${fields}
|
|
61
|
-
})`;
|
|
62
|
-
}
|
|
63
|
-
function generateIdField(dialect, options) {
|
|
64
|
-
switch (dialect) {
|
|
65
|
-
case "sqlite":
|
|
66
|
-
if (options?.useUuid)
|
|
67
|
-
consola$1.warn("[@onmax/nuxt-better-auth] useUuid ignored for SQLite (no native uuid type). Using text.");
|
|
68
|
-
return `id: text('id').primaryKey()`;
|
|
69
|
-
case "postgresql":
|
|
70
|
-
return options?.useUuid ? `id: uuid('id').defaultRandom().primaryKey()` : `id: text('id').primaryKey()`;
|
|
71
|
-
case "mysql":
|
|
72
|
-
return `id: varchar('id', { length: 36 }).primaryKey()`;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
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);
|
|
85
|
-
if (field.required && field.defaultValue === void 0)
|
|
86
|
-
fieldDef += ".notNull()";
|
|
87
|
-
if (field.unique)
|
|
88
|
-
fieldDef += ".unique()";
|
|
89
|
-
if (field.defaultValue !== void 0) {
|
|
90
|
-
if (typeof field.defaultValue === "boolean")
|
|
91
|
-
fieldDef += `.default(${field.defaultValue})`;
|
|
92
|
-
else if (typeof field.defaultValue === "string")
|
|
93
|
-
fieldDef += `.default('${field.defaultValue}')`;
|
|
94
|
-
else if (typeof field.defaultValue === "function")
|
|
95
|
-
fieldDef += `.$defaultFn(${field.defaultValue})`;
|
|
96
|
-
else
|
|
97
|
-
fieldDef += `.default(${field.defaultValue})`;
|
|
98
|
-
if (field.required)
|
|
99
|
-
fieldDef += ".notNull()";
|
|
100
|
-
}
|
|
101
|
-
if (typeof field.onUpdate === "function" && field.type === "date")
|
|
102
|
-
fieldDef += `.$onUpdate(${field.onUpdate})`;
|
|
103
|
-
if (field.references) {
|
|
104
|
-
const refTable = field.references.model;
|
|
105
|
-
if (allTables[refTable])
|
|
106
|
-
fieldDef += `.references(() => ${refTable}.${field.references.field})`;
|
|
107
|
-
}
|
|
108
|
-
return `${fieldName}: ${fieldDef}`;
|
|
30
|
+
function dialectToProvider(dialect) {
|
|
31
|
+
return dialect === "postgresql" ? "pg" : dialect;
|
|
109
32
|
}
|
|
110
|
-
function
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
function getPostgresType(type, fieldName) {
|
|
136
|
-
switch (type) {
|
|
137
|
-
case "string":
|
|
138
|
-
return `text('${fieldName}')`;
|
|
139
|
-
case "boolean":
|
|
140
|
-
return `boolean('${fieldName}')`;
|
|
141
|
-
case "date":
|
|
142
|
-
return `timestamp('${fieldName}')`;
|
|
143
|
-
case "number":
|
|
144
|
-
return `integer('${fieldName}')`;
|
|
145
|
-
default:
|
|
146
|
-
return `text('${fieldName}')`;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
function getMysqlType(type, fieldName) {
|
|
150
|
-
switch (type) {
|
|
151
|
-
case "string":
|
|
152
|
-
return `text('${fieldName}')`;
|
|
153
|
-
case "boolean":
|
|
154
|
-
return `boolean('${fieldName}')`;
|
|
155
|
-
case "date":
|
|
156
|
-
return `timestamp('${fieldName}')`;
|
|
157
|
-
case "number":
|
|
158
|
-
return `int('${fieldName}')`;
|
|
159
|
-
default:
|
|
160
|
-
return `text('${fieldName}')`;
|
|
33
|
+
async function generateDrizzleSchema(authOptions, dialect, schemaOptions) {
|
|
34
|
+
const provider = dialectToProvider(dialect);
|
|
35
|
+
const options = {
|
|
36
|
+
...authOptions,
|
|
37
|
+
advanced: {
|
|
38
|
+
...authOptions.advanced,
|
|
39
|
+
database: {
|
|
40
|
+
...authOptions.advanced?.database,
|
|
41
|
+
...schemaOptions?.useUuid && { generateId: "uuid" }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const adapter = {
|
|
46
|
+
id: "drizzle",
|
|
47
|
+
options: {
|
|
48
|
+
provider,
|
|
49
|
+
camelCase: schemaOptions?.casing !== "snake_case",
|
|
50
|
+
adapterConfig: { usePlural: schemaOptions?.usePlural ?? false }
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const result = await generateDrizzleSchema$1({ adapter, options });
|
|
54
|
+
if (!result.code) {
|
|
55
|
+
throw new Error(`Schema generation returned empty result for ${dialect}`);
|
|
161
56
|
}
|
|
57
|
+
return result.code;
|
|
162
58
|
}
|
|
163
59
|
async function loadUserAuthConfig(configPath, throwOnError = false) {
|
|
164
60
|
const { createJiti } = await import('jiti');
|
|
165
61
|
const { defineServerAuth } = await import('../dist/runtime/config.js');
|
|
166
62
|
const jiti = createJiti(import.meta.url, { interopDefault: true, moduleCache: false });
|
|
167
|
-
|
|
168
|
-
const g = globalThis;
|
|
169
|
-
if (!g[key]) {
|
|
63
|
+
if (!globalThis.defineServerAuth) {
|
|
170
64
|
defineServerAuth._count = 0;
|
|
171
|
-
|
|
65
|
+
globalThis.defineServerAuth = defineServerAuth;
|
|
172
66
|
}
|
|
173
|
-
|
|
67
|
+
globalThis.defineServerAuth._count++;
|
|
174
68
|
try {
|
|
175
69
|
const mod = await jiti.import(configPath);
|
|
176
|
-
const configFn =
|
|
70
|
+
const configFn = mod.default;
|
|
177
71
|
if (typeof configFn === "function") {
|
|
178
72
|
return configFn({ runtimeConfig: {}, db: null });
|
|
179
73
|
}
|
|
74
|
+
consola$1.warn("[@onmax/nuxt-better-auth] auth.config.ts does not export default. Expected: export default defineServerAuth(...)");
|
|
180
75
|
if (throwOnError) {
|
|
181
76
|
throw new Error("auth.config.ts must export default defineServerAuth(...)");
|
|
182
77
|
}
|
|
@@ -188,9 +83,9 @@ async function loadUserAuthConfig(configPath, throwOnError = false) {
|
|
|
188
83
|
consola$1.error("[@onmax/nuxt-better-auth] Failed to load auth config for schema generation. Schema may be incomplete:", error);
|
|
189
84
|
return {};
|
|
190
85
|
} finally {
|
|
191
|
-
|
|
192
|
-
if (!
|
|
193
|
-
|
|
86
|
+
globalThis.defineServerAuth._count--;
|
|
87
|
+
if (!globalThis.defineServerAuth._count) {
|
|
88
|
+
globalThis.defineServerAuth = void 0;
|
|
194
89
|
}
|
|
195
90
|
}
|
|
196
91
|
}
|
|
@@ -210,8 +105,72 @@ function getHubCasing(hub) {
|
|
|
210
105
|
return hub.db.casing;
|
|
211
106
|
}
|
|
212
107
|
const consola = consola$1.withTag("nuxt-better-auth");
|
|
108
|
+
const generateSecret = () => randomBytes(32).toString("hex");
|
|
109
|
+
function readEnvFile(rootDir) {
|
|
110
|
+
const envPath = join(rootDir, ".env");
|
|
111
|
+
return existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
|
|
112
|
+
}
|
|
113
|
+
function hasEnvSecret(rootDir) {
|
|
114
|
+
const match = readEnvFile(rootDir).match(/^BETTER_AUTH_SECRET=(.+)$/m);
|
|
115
|
+
return !!match && !!match[1] && match[1].trim().length > 0;
|
|
116
|
+
}
|
|
117
|
+
function appendSecretToEnv(rootDir, secret) {
|
|
118
|
+
const envPath = join(rootDir, ".env");
|
|
119
|
+
let content = readEnvFile(rootDir);
|
|
120
|
+
if (content.length > 0 && !content.endsWith("\n"))
|
|
121
|
+
content += "\n";
|
|
122
|
+
content += `BETTER_AUTH_SECRET=${secret}
|
|
123
|
+
`;
|
|
124
|
+
writeFileSync(envPath, content, "utf-8");
|
|
125
|
+
}
|
|
126
|
+
async function promptForSecret(rootDir) {
|
|
127
|
+
if (process.env.BETTER_AUTH_SECRET || hasEnvSecret(rootDir))
|
|
128
|
+
return void 0;
|
|
129
|
+
if (isCI || isTest) {
|
|
130
|
+
const secret2 = generateSecret();
|
|
131
|
+
appendSecretToEnv(rootDir, secret2);
|
|
132
|
+
consola.info("Generated BETTER_AUTH_SECRET and added to .env (CI mode)");
|
|
133
|
+
return secret2;
|
|
134
|
+
}
|
|
135
|
+
consola.box("BETTER_AUTH_SECRET is required for authentication.\nThis will be appended to your .env file.");
|
|
136
|
+
const choice = await consola.prompt("How do you want to set it?", {
|
|
137
|
+
type: "select",
|
|
138
|
+
options: [
|
|
139
|
+
{ label: "Generate for me", value: "generate", hint: "uses crypto.randomBytes(32)" },
|
|
140
|
+
{ label: "Enter manually", value: "paste" },
|
|
141
|
+
{ label: "Skip", value: "skip", hint: "will fail in production" }
|
|
142
|
+
],
|
|
143
|
+
cancel: "null"
|
|
144
|
+
});
|
|
145
|
+
if (typeof choice === "symbol" || choice === "skip") {
|
|
146
|
+
consola.warn("Skipping BETTER_AUTH_SECRET. Auth will fail without it in production.");
|
|
147
|
+
return void 0;
|
|
148
|
+
}
|
|
149
|
+
let secret;
|
|
150
|
+
if (choice === "generate") {
|
|
151
|
+
secret = generateSecret();
|
|
152
|
+
} else {
|
|
153
|
+
const input = await consola.prompt("Paste your secret (min 32 chars):", { type: "text", cancel: "null" });
|
|
154
|
+
if (typeof input === "symbol" || !input || input.length < 32) {
|
|
155
|
+
consola.warn("Invalid secret. Skipping.");
|
|
156
|
+
return void 0;
|
|
157
|
+
}
|
|
158
|
+
secret = input;
|
|
159
|
+
}
|
|
160
|
+
const preview = `${secret.slice(0, 8)}...${secret.slice(-4)}`;
|
|
161
|
+
const confirm = await consola.prompt(`Add to .env:
|
|
162
|
+
BETTER_AUTH_SECRET=${preview}
|
|
163
|
+
Proceed?`, { type: "confirm", initial: true, cancel: "null" });
|
|
164
|
+
if (typeof confirm === "symbol" || !confirm) {
|
|
165
|
+
consola.info("Cancelled. Secret not written.");
|
|
166
|
+
return void 0;
|
|
167
|
+
}
|
|
168
|
+
appendSecretToEnv(rootDir, secret);
|
|
169
|
+
consola.success("Added BETTER_AUTH_SECRET to .env");
|
|
170
|
+
return secret;
|
|
171
|
+
}
|
|
213
172
|
const module$1 = defineNuxtModule({
|
|
214
|
-
meta: { name: "@onmax/nuxt-better-auth", configKey: "auth", compatibility: { nuxt: ">=3.0.0" } },
|
|
173
|
+
meta: { name: "@onmax/nuxt-better-auth", version, configKey: "auth", compatibility: { nuxt: ">=3.0.0" } },
|
|
215
174
|
defaults: {
|
|
216
175
|
clientOnly: false,
|
|
217
176
|
serverConfig: "server/auth.config",
|
|
@@ -219,8 +178,40 @@ const module$1 = defineNuxtModule({
|
|
|
219
178
|
redirects: { login: "/login", guest: "/" },
|
|
220
179
|
secondaryStorage: false
|
|
221
180
|
},
|
|
181
|
+
async onInstall(nuxt) {
|
|
182
|
+
const generatedSecret = await promptForSecret(nuxt.options.rootDir);
|
|
183
|
+
if (generatedSecret)
|
|
184
|
+
process.env.BETTER_AUTH_SECRET = generatedSecret;
|
|
185
|
+
const serverPath = join(nuxt.options.rootDir, "server/auth.config.ts");
|
|
186
|
+
const clientPath = join(nuxt.options.srcDir, "auth.config.ts");
|
|
187
|
+
const serverTemplate = `import { defineServerAuth } from '@onmax/nuxt-better-auth/config'
|
|
188
|
+
|
|
189
|
+
export default defineServerAuth({
|
|
190
|
+
emailAndPassword: { enabled: true },
|
|
191
|
+
})
|
|
192
|
+
`;
|
|
193
|
+
const clientTemplate = `import { defineClientAuth } from '@onmax/nuxt-better-auth/config'
|
|
194
|
+
|
|
195
|
+
export default defineClientAuth({})
|
|
196
|
+
`;
|
|
197
|
+
if (!existsSync(serverPath)) {
|
|
198
|
+
await mkdir(dirname(serverPath), { recursive: true });
|
|
199
|
+
await writeFile(serverPath, serverTemplate);
|
|
200
|
+
consola.success("Created server/auth.config.ts");
|
|
201
|
+
}
|
|
202
|
+
if (!existsSync(clientPath)) {
|
|
203
|
+
await mkdir(dirname(clientPath), { recursive: true });
|
|
204
|
+
await writeFile(clientPath, clientTemplate);
|
|
205
|
+
const relativePath = clientPath.replace(`${nuxt.options.rootDir}/`, "");
|
|
206
|
+
consola.success(`Created ${relativePath}`);
|
|
207
|
+
}
|
|
208
|
+
},
|
|
222
209
|
async setup(options, nuxt) {
|
|
223
210
|
const resolver = createResolver(import.meta.url);
|
|
211
|
+
if (options.clientConfig === "app/auth.config") {
|
|
212
|
+
const srcDirRelative = nuxt.options.srcDir.replace(`${nuxt.options.rootDir}/`, "");
|
|
213
|
+
options.clientConfig = srcDirRelative === nuxt.options.srcDir ? "auth.config" : `${srcDirRelative}/auth.config`;
|
|
214
|
+
}
|
|
224
215
|
const clientOnly = options.clientOnly;
|
|
225
216
|
const serverConfigFile = options.serverConfig;
|
|
226
217
|
const clientConfigFile = options.clientConfig;
|
|
@@ -229,9 +220,9 @@ const module$1 = defineNuxtModule({
|
|
|
229
220
|
const serverConfigExists = existsSync(`${serverConfigPath}.ts`) || existsSync(`${serverConfigPath}.js`);
|
|
230
221
|
const clientConfigExists = existsSync(`${clientConfigPath}.ts`) || existsSync(`${clientConfigPath}.js`);
|
|
231
222
|
if (!clientOnly && !serverConfigExists)
|
|
232
|
-
throw new Error(`[nuxt-better-auth] Missing ${serverConfigFile}.ts -
|
|
223
|
+
throw new Error(`[nuxt-better-auth] Missing ${serverConfigFile}.ts - export default defineServerAuth(...)`);
|
|
233
224
|
if (!clientConfigExists)
|
|
234
|
-
throw new Error(`[nuxt-better-auth] Missing ${clientConfigFile}.ts - export
|
|
225
|
+
throw new Error(`[nuxt-better-auth] Missing ${clientConfigFile}.ts - export default defineClientAuth(...)`);
|
|
235
226
|
const hasNuxtHub = hasNuxtModule("@nuxthub/core", nuxt);
|
|
236
227
|
const hub = hasNuxtHub ? nuxt.options.hub : void 0;
|
|
237
228
|
const hasHubDb = !clientOnly && hasNuxtHub && !!hub?.db;
|
|
@@ -257,14 +248,15 @@ const module$1 = defineNuxtModule({
|
|
|
257
248
|
consola.info("clientOnly mode enabled - server utilities (serverAuth, getUserSession, requireUserSession) are not available");
|
|
258
249
|
}
|
|
259
250
|
if (!clientOnly) {
|
|
260
|
-
const
|
|
261
|
-
|
|
251
|
+
const currentSecret = nuxt.options.runtimeConfig.betterAuthSecret;
|
|
252
|
+
nuxt.options.runtimeConfig.betterAuthSecret = currentSecret || process.env.BETTER_AUTH_SECRET || "";
|
|
253
|
+
const betterAuthSecret = nuxt.options.runtimeConfig.betterAuthSecret;
|
|
254
|
+
if (!nuxt.options.dev && !nuxt.options._prepare && !betterAuthSecret) {
|
|
262
255
|
throw new Error("[nuxt-better-auth] BETTER_AUTH_SECRET is required in production. Set BETTER_AUTH_SECRET or NUXT_BETTER_AUTH_SECRET environment variable.");
|
|
263
256
|
}
|
|
264
257
|
if (betterAuthSecret && betterAuthSecret.length < 32) {
|
|
265
258
|
throw new Error("[nuxt-better-auth] BETTER_AUTH_SECRET must be at least 32 characters for security");
|
|
266
259
|
}
|
|
267
|
-
nuxt.options.runtimeConfig.betterAuthSecret = betterAuthSecret;
|
|
268
260
|
nuxt.options.runtimeConfig.auth = defu(nuxt.options.runtimeConfig.auth, {
|
|
269
261
|
secondaryStorage: secondaryStorageEnabled
|
|
270
262
|
});
|
|
@@ -291,11 +283,13 @@ export function createSecondaryStorage() {
|
|
|
291
283
|
throw new Error("[nuxt-better-auth] hub:db not found. Ensure @nuxthub/core is loaded before this module and hub.db is configured.");
|
|
292
284
|
}
|
|
293
285
|
const hubDialect = getHubDialect(hub) ?? "sqlite";
|
|
286
|
+
const usePlural = options.schema?.usePlural ?? false;
|
|
287
|
+
const camelCase = (options.schema?.casing ?? getHubCasing(hub)) !== "snake_case";
|
|
294
288
|
const databaseCode = hasHubDb ? `import { db, schema } from '../hub/db.mjs'
|
|
295
289
|
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
|
|
296
290
|
const rawDialect = '${hubDialect}'
|
|
297
291
|
const dialect = rawDialect === 'postgresql' ? 'pg' : rawDialect
|
|
298
|
-
export function createDatabase() { return drizzleAdapter(db, { provider: dialect, schema }) }
|
|
292
|
+
export function createDatabase() { return drizzleAdapter(db, { provider: dialect, schema, usePlural: ${usePlural}, camelCase: ${camelCase} }) }
|
|
299
293
|
export { db }` : `export function createDatabase() { return undefined }
|
|
300
294
|
export const db = undefined`;
|
|
301
295
|
const databaseTemplate = addTemplate({ filename: "better-auth/database.mjs", getContents: () => databaseCode, write: true });
|
|
@@ -328,9 +322,9 @@ declare module '#auth/database' {
|
|
|
328
322
|
getContents: () => `
|
|
329
323
|
import type { InferUser, InferSession, InferPluginTypes } from 'better-auth'
|
|
330
324
|
import type { RuntimeConfig } from 'nuxt/schema'
|
|
331
|
-
import type
|
|
325
|
+
import type createServerAuth from '${serverConfigPath}'
|
|
332
326
|
|
|
333
|
-
type _Config = ReturnType<typeof
|
|
327
|
+
type _Config = ReturnType<typeof createServerAuth>
|
|
334
328
|
|
|
335
329
|
declare module '#nuxt-better-auth' {
|
|
336
330
|
interface AuthUser extends InferUser<_Config> {}
|
|
@@ -351,7 +345,7 @@ interface _AugmentedServerAuthContext {
|
|
|
351
345
|
declare module '@onmax/nuxt-better-auth/config' {
|
|
352
346
|
import type { BetterAuthOptions } from 'better-auth'
|
|
353
347
|
type ServerAuthConfig = Omit<BetterAuthOptions, 'database' | 'secret' | 'baseURL'>
|
|
354
|
-
export function defineServerAuth<T extends ServerAuthConfig>(config: (ctx: _AugmentedServerAuthContext) => T): (ctx: _AugmentedServerAuthContext) => T
|
|
348
|
+
export function defineServerAuth<T extends ServerAuthConfig>(config: T | ((ctx: _AugmentedServerAuthContext) => T)): (ctx: _AugmentedServerAuthContext) => T
|
|
355
349
|
}
|
|
356
350
|
`
|
|
357
351
|
}, { nuxt: true, nitro: true, node: true });
|
|
@@ -388,7 +382,7 @@ export type { AuthMeta, AuthMode, AuthRouteRules, UserMatch, RequireSessionOptio
|
|
|
388
382
|
addTypeTemplate({
|
|
389
383
|
filename: "types/nuxt-better-auth-client.d.ts",
|
|
390
384
|
getContents: () => `
|
|
391
|
-
import type
|
|
385
|
+
import type createAppAuthClient from '${clientConfigPath}'
|
|
392
386
|
declare module '#nuxt-better-auth' {
|
|
393
387
|
export type AppAuthClient = ReturnType<typeof createAppAuthClient>
|
|
394
388
|
}
|
|
@@ -419,6 +413,8 @@ declare module '#nuxt-better-auth' {
|
|
|
419
413
|
}
|
|
420
414
|
const isProduction = process.env.NODE_ENV === "production" || !nuxt.options.dev;
|
|
421
415
|
if (!isProduction && !clientOnly) {
|
|
416
|
+
if (!hasNuxtModule("@nuxt/ui"))
|
|
417
|
+
await installModule("@nuxt/ui");
|
|
422
418
|
setupDevTools(nuxt);
|
|
423
419
|
addServerHandler({ route: "/api/_better-auth/config", method: "get", handler: resolver.resolve("./runtime/server/api/_better-auth/config.get") });
|
|
424
420
|
if (hasHubDb) {
|
|
@@ -465,27 +461,22 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options) {
|
|
|
465
461
|
const extendedConfig = {};
|
|
466
462
|
await nuxt.callHook("better-auth:config:extend", extendedConfig);
|
|
467
463
|
const plugins = [...userConfig.plugins || [], ...extendedConfig.plugins || []];
|
|
468
|
-
const
|
|
469
|
-
|
|
464
|
+
const authOptions = {
|
|
465
|
+
...userConfig,
|
|
470
466
|
plugins,
|
|
471
|
-
secondaryStorage: options.secondaryStorage ? {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
delete: async (_key) => {
|
|
476
|
-
}
|
|
477
|
-
} : void 0
|
|
478
|
-
});
|
|
479
|
-
const useUuid = userConfig.advanced?.database?.generateId === "uuid";
|
|
467
|
+
secondaryStorage: options.secondaryStorage ? { get: async (_key) => null, set: async (_key, _value, _ttl) => {
|
|
468
|
+
}, delete: async (_key) => {
|
|
469
|
+
} } : void 0
|
|
470
|
+
};
|
|
480
471
|
const hubCasing = getHubCasing(hub);
|
|
481
|
-
const schemaOptions = { ...options.schema, useUuid, casing: options.schema?.casing ?? hubCasing };
|
|
482
|
-
const schemaCode = generateDrizzleSchema(
|
|
472
|
+
const schemaOptions = { ...options.schema, useUuid: userConfig.advanced?.database?.generateId === "uuid", casing: options.schema?.casing ?? hubCasing };
|
|
473
|
+
const schemaCode = await generateDrizzleSchema(authOptions, dialect, schemaOptions);
|
|
483
474
|
const schemaDir = join(nuxt.options.buildDir, "better-auth");
|
|
484
475
|
const schemaPath = join(schemaDir, `schema.${dialect}.ts`);
|
|
485
476
|
await mkdir(schemaDir, { recursive: true });
|
|
486
477
|
await writeFile(schemaPath, schemaCode);
|
|
487
478
|
addTemplate({ filename: `better-auth/schema.${dialect}.ts`, getContents: () => schemaCode, write: true });
|
|
488
|
-
consola.info(`Generated ${dialect} schema
|
|
479
|
+
consola.info(`Generated ${dialect} schema`);
|
|
489
480
|
} catch (error) {
|
|
490
481
|
if (isProduction) {
|
|
491
482
|
throw error;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import createAppAuthClient from "#auth/client";
|
|
2
2
|
import { computed, nextTick, useRequestHeaders, useRequestURL, useRuntimeConfig, useState, watch } from "#imports";
|
|
3
3
|
let _client = null;
|
|
4
4
|
function getClient(baseURL) {
|
package/dist/runtime/config.d.ts
CHANGED
|
@@ -2,12 +2,13 @@ import type { BetterAuthOptions } from 'better-auth';
|
|
|
2
2
|
import type { ClientOptions } from 'better-auth/client';
|
|
3
3
|
import type { CasingOption } from '../schema-generator.js';
|
|
4
4
|
import type { ServerAuthContext } from './types/augment.js';
|
|
5
|
+
import { createAuthClient } from 'better-auth/vue';
|
|
5
6
|
export type { ServerAuthContext };
|
|
6
7
|
export interface ClientAuthContext {
|
|
7
8
|
siteUrl: string;
|
|
8
9
|
}
|
|
9
|
-
type ServerAuthConfig = Omit<BetterAuthOptions, 'database' | 'secret' | 'baseURL'>;
|
|
10
|
-
type ClientAuthConfig = Omit<ClientOptions, 'baseURL'> & {
|
|
10
|
+
export type ServerAuthConfig = Omit<BetterAuthOptions, 'database' | 'secret' | 'baseURL'>;
|
|
11
|
+
export type ClientAuthConfig = Omit<ClientOptions, 'baseURL'> & {
|
|
11
12
|
baseURL?: string;
|
|
12
13
|
};
|
|
13
14
|
export type ServerAuthConfigFn = (ctx: ServerAuthContext) => ServerAuthConfig;
|
|
@@ -44,5 +45,5 @@ export interface AuthRuntimeConfig {
|
|
|
44
45
|
export interface AuthPrivateRuntimeConfig {
|
|
45
46
|
secondaryStorage: boolean;
|
|
46
47
|
}
|
|
47
|
-
export declare function defineServerAuth<T extends ServerAuthConfig>(config: (ctx: ServerAuthContext) => T): (ctx: ServerAuthContext) => T;
|
|
48
|
-
export declare function defineClientAuth(config:
|
|
48
|
+
export declare function defineServerAuth<T extends ServerAuthConfig>(config: T | ((ctx: ServerAuthContext) => T)): (ctx: ServerAuthContext) => T;
|
|
49
|
+
export declare function defineClientAuth<T extends ClientAuthConfig>(config: T | ((ctx: ClientAuthContext) => T)): (baseURL: string) => ReturnType<typeof createAuthClient<T>>;
|
package/dist/runtime/config.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import { createAuthClient } from "better-auth/vue";
|
|
1
2
|
export function defineServerAuth(config) {
|
|
2
|
-
return config;
|
|
3
|
+
return typeof config === "function" ? config : () => config;
|
|
3
4
|
}
|
|
4
5
|
export function defineClientAuth(config) {
|
|
5
|
-
return
|
|
6
|
+
return (baseURL) => {
|
|
7
|
+
const ctx = { siteUrl: baseURL };
|
|
8
|
+
const resolved = typeof config === "function" ? config(ctx) : config;
|
|
9
|
+
return createAuthClient({ ...resolved, baseURL });
|
|
10
|
+
};
|
|
6
11
|
}
|
|
@@ -2,8 +2,9 @@ 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 {
|
|
5
|
+
import { getRequestHost, getRequestProtocol } from "h3";
|
|
6
6
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
7
|
+
import { withoutProtocol } from "ufo";
|
|
7
8
|
let _auth = null;
|
|
8
9
|
function validateURL(url) {
|
|
9
10
|
try {
|
|
@@ -12,12 +13,46 @@ function validateURL(url) {
|
|
|
12
13
|
throw new Error(`Invalid siteUrl: "${url}". Must be a valid URL.`);
|
|
13
14
|
}
|
|
14
15
|
}
|
|
16
|
+
function getNitroOrigin(e) {
|
|
17
|
+
const cert = process.env.NITRO_SSL_CERT;
|
|
18
|
+
const key = process.env.NITRO_SSL_KEY;
|
|
19
|
+
let host = process.env.NITRO_HOST || process.env.HOST;
|
|
20
|
+
let port;
|
|
21
|
+
if (import.meta.dev)
|
|
22
|
+
port = process.env.NITRO_PORT || process.env.PORT || "3000";
|
|
23
|
+
let protocol = cert && key || !import.meta.dev ? "https" : "http";
|
|
24
|
+
try {
|
|
25
|
+
if ((import.meta.dev || import.meta.prerender) && process.env.__NUXT_DEV__) {
|
|
26
|
+
const origin = JSON.parse(process.env.__NUXT_DEV__).proxy.url;
|
|
27
|
+
host = withoutProtocol(origin);
|
|
28
|
+
protocol = origin.includes("https") ? "https" : "http";
|
|
29
|
+
} else if ((import.meta.dev || import.meta.prerender) && process.env.NUXT_VITE_NODE_OPTIONS) {
|
|
30
|
+
const origin = JSON.parse(process.env.NUXT_VITE_NODE_OPTIONS).baseURL.replace("/__nuxt_vite_node__", "");
|
|
31
|
+
host = withoutProtocol(origin);
|
|
32
|
+
protocol = origin.includes("https") ? "https" : "http";
|
|
33
|
+
} else if (e) {
|
|
34
|
+
host = getRequestHost(e, { xForwardedHost: true }) || host;
|
|
35
|
+
protocol = getRequestProtocol(e, { xForwardedProto: true }) || protocol;
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
if (!host)
|
|
40
|
+
return void 0;
|
|
41
|
+
if (host.includes(":") && !host.startsWith("[")) {
|
|
42
|
+
const hostParts = host.split(":");
|
|
43
|
+
port = hostParts.pop();
|
|
44
|
+
host = hostParts.join(":");
|
|
45
|
+
}
|
|
46
|
+
const portSuffix = port ? `:${port}` : "";
|
|
47
|
+
return `${protocol}://${host}${portSuffix}`;
|
|
48
|
+
}
|
|
15
49
|
function getBaseURL(event) {
|
|
16
50
|
const config = useRuntimeConfig();
|
|
17
51
|
if (config.public.siteUrl && typeof config.public.siteUrl === "string")
|
|
18
52
|
return validateURL(config.public.siteUrl);
|
|
19
|
-
|
|
20
|
-
|
|
53
|
+
const nitroOrigin = getNitroOrigin(event);
|
|
54
|
+
if (nitroOrigin)
|
|
55
|
+
return validateURL(nitroOrigin);
|
|
21
56
|
if (process.env.VERCEL_URL)
|
|
22
57
|
return validateURL(`https://${process.env.VERCEL_URL}`);
|
|
23
58
|
if (process.env.CF_PAGES_URL)
|
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.15",
|
|
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",
|
|
@@ -10,6 +10,14 @@
|
|
|
10
10
|
"type": "git",
|
|
11
11
|
"url": "https://github.com/onmax/nuxt-better-auth"
|
|
12
12
|
},
|
|
13
|
+
"agents": {
|
|
14
|
+
"skills": [
|
|
15
|
+
{
|
|
16
|
+
"name": "nuxt-better-auth",
|
|
17
|
+
"path": "./skills/nuxt-better-auth"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
},
|
|
13
21
|
"exports": {
|
|
14
22
|
".": {
|
|
15
23
|
"types": "./dist/types.d.mts",
|
|
@@ -32,7 +40,8 @@
|
|
|
32
40
|
}
|
|
33
41
|
},
|
|
34
42
|
"files": [
|
|
35
|
-
"dist"
|
|
43
|
+
"dist",
|
|
44
|
+
"skills"
|
|
36
45
|
],
|
|
37
46
|
"scripts": {
|
|
38
47
|
"prepack": "nuxt-module-build build",
|
|
@@ -41,7 +50,7 @@
|
|
|
41
50
|
"dev:prepare": "nuxt-module-build build --stub && nuxi prepare playground",
|
|
42
51
|
"dev:docs": "nuxi dev docs",
|
|
43
52
|
"build:docs": "nuxi build docs",
|
|
44
|
-
"release": "bumpp --push",
|
|
53
|
+
"release": "bumpp --push --no-push-all",
|
|
45
54
|
"lint": "eslint .",
|
|
46
55
|
"lint:fix": "eslint . --fix",
|
|
47
56
|
"typecheck": "vue-tsc --noEmit",
|
|
@@ -59,16 +68,17 @@
|
|
|
59
68
|
}
|
|
60
69
|
},
|
|
61
70
|
"dependencies": {
|
|
71
|
+
"@better-auth/cli": "^1.5.0-beta.3",
|
|
62
72
|
"@nuxt/kit": "^4.2.2",
|
|
73
|
+
"@nuxt/ui": "^4.2.1",
|
|
63
74
|
"defu": "^6.1.4",
|
|
64
75
|
"jiti": "^2.4.2",
|
|
65
76
|
"pathe": "^2.0.3",
|
|
66
|
-
"
|
|
67
|
-
"
|
|
77
|
+
"radix3": "^1.1.2",
|
|
78
|
+
"std-env": "^3.10.0"
|
|
68
79
|
},
|
|
69
80
|
"devDependencies": {
|
|
70
81
|
"@antfu/eslint-config": "^4.12.0",
|
|
71
|
-
"@better-auth/cli": "^1.4.6",
|
|
72
82
|
"@libsql/client": "^0.15.15",
|
|
73
83
|
"@nuxt/devtools": "^3.1.1",
|
|
74
84
|
"@nuxt/devtools-kit": "^3.1.1",
|
|
@@ -78,8 +88,7 @@
|
|
|
78
88
|
"@nuxthub/core": "^0.10.3",
|
|
79
89
|
"@types/better-sqlite3": "^7.6.13",
|
|
80
90
|
"@types/node": "latest",
|
|
81
|
-
"
|
|
82
|
-
"better-auth": "^1.4.7",
|
|
91
|
+
"better-auth": "^1.5.0-beta.3",
|
|
83
92
|
"better-sqlite3": "^11.9.1",
|
|
84
93
|
"bumpp": "^10.3.2",
|
|
85
94
|
"changelogen": "^0.6.2",
|
|
@@ -87,6 +96,7 @@
|
|
|
87
96
|
"drizzle-kit": "^0.31.8",
|
|
88
97
|
"drizzle-orm": "^0.38.4",
|
|
89
98
|
"eslint": "^9.39.1",
|
|
99
|
+
"npm-agentskills": "https://pkg.pr.new/onmax/npm-agentskills@394499e",
|
|
90
100
|
"nuxt": "^4.2.2",
|
|
91
101
|
"tinyexec": "^1.0.2",
|
|
92
102
|
"typescript": "~5.9.3",
|