@onmax/nuxt-better-auth 0.0.2-alpha.21 → 0.0.2-alpha.23
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/README.md +1 -1
- package/dist/module.d.mts +35 -0
- package/dist/module.json +2 -2
- package/dist/module.mjs +475 -432
- package/dist/runtime/app/composables/useUserSession.d.ts +1 -1
- package/dist/runtime/app/composables/useUserSession.js +70 -7
- package/dist/runtime/app/pages/__better-auth-devtools.vue +2 -2
- package/dist/runtime/config.d.ts +24 -3
- package/dist/runtime/server/api/_better-auth/config.get.d.ts +2 -0
- package/dist/runtime/server/api/_better-auth/config.get.js +7 -2
- package/dist/runtime/server/utils/auth.d.ts +1 -1
- package/dist/runtime/server/utils/auth.js +144 -23
- package/dist/runtime/server/utils/session.d.ts +10 -2
- package/dist/runtime/types/augment.d.ts +1 -1
- package/dist/runtime/types.d.ts +1 -1
- package/package.json +3 -13
- package/dist/runtime/adapters/convex.d.ts +0 -111
- package/dist/runtime/adapters/convex.js +0 -213
package/dist/module.mjs
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
|
-
import { randomBytes } from 'node:crypto';
|
|
2
1
|
import { existsSync, writeFileSync, readFileSync } from 'node:fs';
|
|
3
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
4
|
-
import {
|
|
3
|
+
import { updateTemplates, addServerImportsDir, addServerImports, addServerScanDir, addServerHandler, addImportsDir, addPlugin, addComponentsDir, hasNuxtModule, installModule, extendPages, addTemplate, addTypeTemplate, defineNuxtModule, createResolver } from '@nuxt/kit';
|
|
5
4
|
import { consola as consola$1 } from 'consola';
|
|
6
|
-
import { defu } from 'defu';
|
|
7
5
|
import { join, dirname } from 'pathe';
|
|
6
|
+
import { defu } from 'defu';
|
|
8
7
|
import { toRouteMatcher, createRouter } from 'radix3';
|
|
9
|
-
import { isCI, isTest } from 'std-env';
|
|
10
8
|
import { generateDrizzleSchema as generateDrizzleSchema$1 } from '@better-auth/cli/api';
|
|
11
|
-
import {
|
|
9
|
+
import { randomBytes } from 'node:crypto';
|
|
10
|
+
import { isCI, isTest } from 'std-env';
|
|
12
11
|
export { defineClientAuth, defineServerAuth } from '../dist/runtime/config.js';
|
|
13
12
|
|
|
14
|
-
const version = "0.0.2-alpha.
|
|
13
|
+
const version = "0.0.2-alpha.23";
|
|
14
|
+
|
|
15
|
+
function resolveDatabaseProvider(input) {
|
|
16
|
+
const enabledProviders = Object.entries(input.providers).filter(([_id, provider]) => provider.isEnabled?.(input.context) ?? true);
|
|
17
|
+
if (!enabledProviders.length) {
|
|
18
|
+
throw new Error("[nuxt-better-auth] No database provider is enabled. Register one with the better-auth:database:providers hook.");
|
|
19
|
+
}
|
|
20
|
+
enabledProviders.sort((a, b) => (b[1].priority ?? 0) - (a[1].priority ?? 0));
|
|
21
|
+
const [id, definition] = enabledProviders[0];
|
|
22
|
+
return { id, definition };
|
|
23
|
+
}
|
|
15
24
|
|
|
16
25
|
function setupDevTools(nuxt) {
|
|
17
26
|
nuxt.hook("devtools:customTabs", (tabs) => {
|
|
@@ -28,6 +37,141 @@ function setupDevTools(nuxt) {
|
|
|
28
37
|
});
|
|
29
38
|
}
|
|
30
39
|
|
|
40
|
+
function registerTemplateHmrHook(nuxt) {
|
|
41
|
+
nuxt.hook("builder:watch", async (_event, relativePath) => {
|
|
42
|
+
if (relativePath.includes("auth.config"))
|
|
43
|
+
await updateTemplates({ filter: (t) => t.filename.includes("nuxt-better-auth") });
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function registerServerRuntime(input) {
|
|
47
|
+
const { clientOnly, resolve } = input;
|
|
48
|
+
if (!clientOnly) {
|
|
49
|
+
addServerImportsDir(resolve("./runtime/server/utils"));
|
|
50
|
+
addServerImports([{ name: "defineServerAuth", from: resolve("./runtime/config") }]);
|
|
51
|
+
addServerScanDir(resolve("./runtime/server/middleware"));
|
|
52
|
+
addServerHandler({ route: "/api/auth/**", handler: resolve("./runtime/server/api/auth/[...all]") });
|
|
53
|
+
}
|
|
54
|
+
addImportsDir(resolve("./runtime/app/composables"));
|
|
55
|
+
addImportsDir(resolve("./runtime/utils"));
|
|
56
|
+
if (!clientOnly)
|
|
57
|
+
addPlugin({ src: resolve("./runtime/app/plugins/session.server"), mode: "server" });
|
|
58
|
+
addPlugin({ src: resolve("./runtime/app/plugins/session.client"), mode: "client" });
|
|
59
|
+
addComponentsDir({ path: resolve("./runtime/app/components") });
|
|
60
|
+
}
|
|
61
|
+
function registerAuthMiddlewareHook(nuxt, resolve) {
|
|
62
|
+
nuxt.hook("app:resolve", (app) => {
|
|
63
|
+
app.middleware.push({ name: "auth", path: resolve("./runtime/app/middleware/auth.global"), global: true });
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async function registerDevtools(input) {
|
|
67
|
+
const { nuxt, clientOnly, hasHubDb, resolve } = input;
|
|
68
|
+
const isProduction = process.env.NODE_ENV === "production" || !nuxt.options.dev;
|
|
69
|
+
if (isProduction || clientOnly)
|
|
70
|
+
return;
|
|
71
|
+
if (!hasNuxtModule("@nuxt/ui"))
|
|
72
|
+
await installModule("@nuxt/ui");
|
|
73
|
+
setupDevTools(nuxt);
|
|
74
|
+
addServerHandler({ route: "/api/_better-auth/config", method: "get", handler: resolve("./runtime/server/api/_better-auth/config.get") });
|
|
75
|
+
if (hasHubDb) {
|
|
76
|
+
const handlers = [
|
|
77
|
+
{ route: "/api/_better-auth/sessions", method: "get", handler: resolve("./runtime/server/api/_better-auth/sessions.get") },
|
|
78
|
+
{ route: "/api/_better-auth/sessions", method: "delete", handler: resolve("./runtime/server/api/_better-auth/sessions.delete") },
|
|
79
|
+
{ route: "/api/_better-auth/users", method: "get", handler: resolve("./runtime/server/api/_better-auth/users.get") },
|
|
80
|
+
{ route: "/api/_better-auth/accounts", method: "get", handler: resolve("./runtime/server/api/_better-auth/accounts.get") }
|
|
81
|
+
];
|
|
82
|
+
handlers.forEach((handler) => addServerHandler(handler));
|
|
83
|
+
}
|
|
84
|
+
extendPages((pages) => {
|
|
85
|
+
pages.push({ name: "better-auth-devtools", path: "/__better-auth-devtools", file: resolve("./runtime/app/pages/__better-auth-devtools.vue") });
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function registerRouteRulesMetaHook(nuxt) {
|
|
89
|
+
nuxt.hook("pages:extend", (pages) => {
|
|
90
|
+
const routeRules = nuxt.options.routeRules || {};
|
|
91
|
+
if (!Object.keys(routeRules).length)
|
|
92
|
+
return;
|
|
93
|
+
const matcher = toRouteMatcher(createRouter({ routes: routeRules }));
|
|
94
|
+
const applyMetaFromRules = (page) => {
|
|
95
|
+
const matches = matcher.matchAll(page.path);
|
|
96
|
+
if (!matches.length)
|
|
97
|
+
return;
|
|
98
|
+
const matchedRules = defu({}, ...matches.reverse());
|
|
99
|
+
if (matchedRules.auth !== void 0) {
|
|
100
|
+
page.meta = page.meta || {};
|
|
101
|
+
page.meta.auth = matchedRules.auth;
|
|
102
|
+
}
|
|
103
|
+
page.children?.forEach((child) => applyMetaFromRules(child));
|
|
104
|
+
};
|
|
105
|
+
pages.forEach((page) => applyMetaFromRules(page));
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getHubDialect(hub) {
|
|
110
|
+
if (!hub?.db)
|
|
111
|
+
return void 0;
|
|
112
|
+
if (typeof hub.db === "string")
|
|
113
|
+
return hub.db;
|
|
114
|
+
if (typeof hub.db === "object" && hub.db !== null)
|
|
115
|
+
return hub.db.dialect;
|
|
116
|
+
return void 0;
|
|
117
|
+
}
|
|
118
|
+
function getHubCasing(hub) {
|
|
119
|
+
if (!hub?.db || typeof hub.db !== "object" || hub.db === null)
|
|
120
|
+
return void 0;
|
|
121
|
+
return hub.db.casing;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function resolveSecondaryStorageEnabled(input) {
|
|
125
|
+
const { options, clientOnly, hasNuxtHub, hub, consola } = input;
|
|
126
|
+
let secondaryStorageEnabled = options.secondaryStorage ?? false;
|
|
127
|
+
if (secondaryStorageEnabled && clientOnly) {
|
|
128
|
+
consola.warn("secondaryStorage is not available in clientOnly mode. Disabling.");
|
|
129
|
+
secondaryStorageEnabled = false;
|
|
130
|
+
} else if (secondaryStorageEnabled && (!hasNuxtHub || !hub?.kv)) {
|
|
131
|
+
consola.warn("secondaryStorage requires @nuxthub/core with hub.kv: true. Disabling.");
|
|
132
|
+
secondaryStorageEnabled = false;
|
|
133
|
+
}
|
|
134
|
+
return secondaryStorageEnabled;
|
|
135
|
+
}
|
|
136
|
+
function setupRuntimeConfig(input) {
|
|
137
|
+
const { nuxt, options, clientOnly, databaseProvider, consola } = input;
|
|
138
|
+
const secondaryStorageEnabled = resolveSecondaryStorageEnabled(input);
|
|
139
|
+
nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
|
|
140
|
+
const configuredSiteUrl = nuxt.options.runtimeConfig.public.siteUrl;
|
|
141
|
+
if (!configuredSiteUrl && process.env.NUXT_PUBLIC_SITE_URL)
|
|
142
|
+
nuxt.options.runtimeConfig.public.siteUrl = process.env.NUXT_PUBLIC_SITE_URL;
|
|
143
|
+
nuxt.options.runtimeConfig.public.auth = defu(nuxt.options.runtimeConfig.public.auth, {
|
|
144
|
+
redirects: { login: options.redirects?.login ?? "/login", guest: options.redirects?.guest ?? "/" },
|
|
145
|
+
useDatabase: databaseProvider !== "none",
|
|
146
|
+
databaseProvider,
|
|
147
|
+
databaseSource: "module",
|
|
148
|
+
clientOnly,
|
|
149
|
+
session: {
|
|
150
|
+
skipHydratedSsrGetSession: options.session?.skipHydratedSsrGetSession ?? false
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
if (clientOnly) {
|
|
154
|
+
const siteUrl = nuxt.options.runtimeConfig.public.siteUrl;
|
|
155
|
+
if (!siteUrl)
|
|
156
|
+
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
|
+
return { secondaryStorageEnabled };
|
|
159
|
+
}
|
|
160
|
+
const currentSecret = nuxt.options.runtimeConfig.betterAuthSecret;
|
|
161
|
+
nuxt.options.runtimeConfig.betterAuthSecret = currentSecret || process.env.BETTER_AUTH_SECRET || "";
|
|
162
|
+
const betterAuthSecret = nuxt.options.runtimeConfig.betterAuthSecret;
|
|
163
|
+
if (!nuxt.options.dev && !nuxt.options._prepare && !betterAuthSecret) {
|
|
164
|
+
throw new Error("[nuxt-better-auth] BETTER_AUTH_SECRET is required in production. Set BETTER_AUTH_SECRET or NUXT_BETTER_AUTH_SECRET environment variable.");
|
|
165
|
+
}
|
|
166
|
+
if (betterAuthSecret && betterAuthSecret.length < 32) {
|
|
167
|
+
throw new Error("[nuxt-better-auth] BETTER_AUTH_SECRET must be at least 32 characters for security");
|
|
168
|
+
}
|
|
169
|
+
nuxt.options.runtimeConfig.auth = defu(nuxt.options.runtimeConfig.auth, {
|
|
170
|
+
secondaryStorage: secondaryStorageEnabled
|
|
171
|
+
});
|
|
172
|
+
return { secondaryStorageEnabled };
|
|
173
|
+
}
|
|
174
|
+
|
|
31
175
|
function dialectToProvider(dialect) {
|
|
32
176
|
return dialect === "postgresql" ? "pg" : dialect;
|
|
33
177
|
}
|
|
@@ -57,101 +201,6 @@ async function generateDrizzleSchema(authOptions, dialect, schemaOptions) {
|
|
|
57
201
|
}
|
|
58
202
|
return result.code;
|
|
59
203
|
}
|
|
60
|
-
const convexIndexFields = {
|
|
61
|
-
account: ["accountId", ["accountId", "providerId"], ["providerId", "userId"]],
|
|
62
|
-
rateLimit: ["key"],
|
|
63
|
-
session: ["expiresAt", ["expiresAt", "userId"]],
|
|
64
|
-
verification: ["expiresAt", "identifier"],
|
|
65
|
-
user: [["email", "name"], "name", "userId"],
|
|
66
|
-
passkey: ["credentialID"],
|
|
67
|
-
oauthConsent: [["clientId", "userId"]]
|
|
68
|
-
};
|
|
69
|
-
function getConvexSpecialFields(tables) {
|
|
70
|
-
return Object.fromEntries(
|
|
71
|
-
Object.entries(tables).map(([key, table]) => {
|
|
72
|
-
const fields = Object.fromEntries(
|
|
73
|
-
Object.entries(table.fields).map(([fieldKey, field]) => [
|
|
74
|
-
field.fieldName ?? fieldKey,
|
|
75
|
-
{
|
|
76
|
-
...field.sortable ? { sortable: true } : {},
|
|
77
|
-
...field.unique ? { unique: true } : {},
|
|
78
|
-
...field.references ? { references: field.references } : {}
|
|
79
|
-
}
|
|
80
|
-
]).filter(([_key, value]) => typeof value === "object" ? Object.keys(value).length > 0 : true)
|
|
81
|
-
);
|
|
82
|
-
return [key, fields];
|
|
83
|
-
}).filter(([_key, value]) => typeof value === "object" ? Object.keys(value).length > 0 : true)
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
function getMergedConvexIndexFields(tables) {
|
|
87
|
-
return Object.fromEntries(
|
|
88
|
-
Object.entries(tables).map(([key, table]) => {
|
|
89
|
-
const manualIndexes = convexIndexFields[key]?.map((index) => {
|
|
90
|
-
return typeof index === "string" ? table.fields[index]?.fieldName ?? index : index.map((i) => table.fields[i]?.fieldName ?? i);
|
|
91
|
-
}) || [];
|
|
92
|
-
const specialFieldsObj = getConvexSpecialFields(tables);
|
|
93
|
-
const specialFieldIndexes = Object.keys(specialFieldsObj[key] || {}).filter(
|
|
94
|
-
(index) => !manualIndexes.some((m) => Array.isArray(m) ? m[0] === index : m === index)
|
|
95
|
-
);
|
|
96
|
-
return [key, manualIndexes.concat(specialFieldIndexes)];
|
|
97
|
-
})
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
async function generateConvexSchema(authOptions) {
|
|
101
|
-
const tables = getAuthTables(authOptions);
|
|
102
|
-
let code = `/**
|
|
103
|
-
* Auto-generated Better Auth tables for Convex.
|
|
104
|
-
* Import these tables in your convex/schema.ts:
|
|
105
|
-
*
|
|
106
|
-
* import { defineSchema } from 'convex/server'
|
|
107
|
-
* import { authTables } from './_generated/auth-tables'
|
|
108
|
-
*
|
|
109
|
-
* export default defineSchema({ ...authTables, ...yourTables })
|
|
110
|
-
*/
|
|
111
|
-
|
|
112
|
-
import { defineTable } from 'convex/server'
|
|
113
|
-
import { v } from 'convex/values'
|
|
114
|
-
|
|
115
|
-
export const authTables = {
|
|
116
|
-
`;
|
|
117
|
-
const getType = (_name, field) => {
|
|
118
|
-
const type = field.type;
|
|
119
|
-
const typeMap = {
|
|
120
|
-
"string": "v.string()",
|
|
121
|
-
"boolean": "v.boolean()",
|
|
122
|
-
"number": "v.number()",
|
|
123
|
-
"date": "v.number()",
|
|
124
|
-
"json": "v.string()",
|
|
125
|
-
"number[]": "v.array(v.number())",
|
|
126
|
-
"string[]": "v.array(v.string())"
|
|
127
|
-
};
|
|
128
|
-
return typeMap[type];
|
|
129
|
-
};
|
|
130
|
-
for (const tableKey in tables) {
|
|
131
|
-
const table = tables[tableKey];
|
|
132
|
-
const modelName = table.modelName;
|
|
133
|
-
const fields = Object.fromEntries(Object.entries(table.fields).filter(([key]) => key !== "id"));
|
|
134
|
-
const indexes = getMergedConvexIndexFields(tables)[tableKey]?.map((index) => {
|
|
135
|
-
const indexArray = Array.isArray(index) ? index.sort() : [index];
|
|
136
|
-
const indexName = indexArray.join("_");
|
|
137
|
-
return `.index('${indexName}', ${JSON.stringify(indexArray)})`;
|
|
138
|
-
}) || [];
|
|
139
|
-
const schema = `${modelName}: defineTable({
|
|
140
|
-
${Object.keys(fields).map((field) => {
|
|
141
|
-
const attr = fields[field];
|
|
142
|
-
const type = getType(field, attr);
|
|
143
|
-
const optional = (fieldSchema) => attr.required ? fieldSchema : `v.optional(v.union(v.null(), ${fieldSchema}))`;
|
|
144
|
-
return ` ${attr.fieldName ?? field}: ${optional(type)},`;
|
|
145
|
-
}).join("\n")}
|
|
146
|
-
})${indexes.length > 0 ? `
|
|
147
|
-
${indexes.join("\n ")}` : ""},
|
|
148
|
-
`;
|
|
149
|
-
code += ` ${schema}`;
|
|
150
|
-
}
|
|
151
|
-
code += `}
|
|
152
|
-
`;
|
|
153
|
-
return code;
|
|
154
|
-
}
|
|
155
204
|
async function loadUserAuthConfig(configPath, throwOnError = false) {
|
|
156
205
|
const { createJiti } = await import('jiti');
|
|
157
206
|
const { defineServerAuth } = await import('../dist/runtime/config.js');
|
|
@@ -186,34 +235,79 @@ async function loadUserAuthConfig(configPath, throwOnError = false) {
|
|
|
186
235
|
}
|
|
187
236
|
}
|
|
188
237
|
|
|
189
|
-
function
|
|
190
|
-
|
|
191
|
-
return void 0;
|
|
192
|
-
if (typeof hub.db === "string")
|
|
193
|
-
return hub.db;
|
|
194
|
-
if (typeof hub.db === "object" && hub.db !== null)
|
|
195
|
-
return hub.db.dialect;
|
|
196
|
-
return void 0;
|
|
238
|
+
function isInsideNodeModules(path) {
|
|
239
|
+
return path.split(/[\\/]/).includes("node_modules");
|
|
197
240
|
}
|
|
198
|
-
function
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
241
|
+
function resolveHubSchemaPath(buildDir, rootDir, dialect, exists = existsSync) {
|
|
242
|
+
const rootTsPath = join(rootDir, ".nuxt", "better-auth", `schema.${dialect}.ts`);
|
|
243
|
+
if (isInsideNodeModules(buildDir) && exists(rootTsPath))
|
|
244
|
+
return rootTsPath;
|
|
245
|
+
const tsPath = join(buildDir, "better-auth", `schema.${dialect}.ts`);
|
|
246
|
+
if (exists(tsPath))
|
|
247
|
+
return tsPath;
|
|
248
|
+
const mjsPath = join(buildDir, "better-auth", `schema.${dialect}.mjs`);
|
|
249
|
+
if (exists(mjsPath))
|
|
250
|
+
return mjsPath;
|
|
251
|
+
return null;
|
|
202
252
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (process.env.CONVEX_URL)
|
|
212
|
-
return process.env.CONVEX_URL;
|
|
213
|
-
if (process.env.NUXT_PUBLIC_CONVEX_URL)
|
|
214
|
-
return process.env.NUXT_PUBLIC_CONVEX_URL;
|
|
215
|
-
return "";
|
|
253
|
+
async function loadAuthOptions(context) {
|
|
254
|
+
const isProduction = !context.nuxt.options.dev;
|
|
255
|
+
const configFile = `${context.serverConfigPath}.ts`;
|
|
256
|
+
const userConfig = await loadUserAuthConfig(configFile, isProduction);
|
|
257
|
+
const extendedConfig = {};
|
|
258
|
+
await context.nuxt.callHook("better-auth:config:extend", extendedConfig);
|
|
259
|
+
const plugins = [...userConfig.plugins || [], ...extendedConfig.plugins || []];
|
|
260
|
+
return { userConfig, plugins };
|
|
216
261
|
}
|
|
262
|
+
async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola) {
|
|
263
|
+
const hub = nuxt.options.hub;
|
|
264
|
+
const dialect = getHubDialect(hub);
|
|
265
|
+
if (!dialect || !["sqlite", "postgresql", "mysql"].includes(dialect)) {
|
|
266
|
+
consola.warn(`Unsupported database dialect: ${dialect}`);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const context = { nuxt, serverConfigPath };
|
|
270
|
+
try {
|
|
271
|
+
const { userConfig, plugins } = await loadAuthOptions(context);
|
|
272
|
+
const authOptions = {
|
|
273
|
+
...userConfig,
|
|
274
|
+
plugins,
|
|
275
|
+
secondaryStorage: options.secondaryStorage ? { get: async (_key) => null, set: async (_key, _value, _ttl) => {
|
|
276
|
+
}, delete: async (_key) => {
|
|
277
|
+
} } : void 0
|
|
278
|
+
};
|
|
279
|
+
const hubCasing = getHubCasing(hub);
|
|
280
|
+
const schemaOptions = { ...options.schema, useUuid: userConfig.advanced?.database?.generateId === "uuid", casing: options.schema?.casing ?? hubCasing };
|
|
281
|
+
const schemaCode = await generateDrizzleSchema(authOptions, dialect, schemaOptions);
|
|
282
|
+
const schemaDir = join(nuxt.options.buildDir, "better-auth");
|
|
283
|
+
const schemaPathTs = join(schemaDir, `schema.${dialect}.ts`);
|
|
284
|
+
const schemaPathMjs = join(schemaDir, `schema.${dialect}.mjs`);
|
|
285
|
+
await mkdir(schemaDir, { recursive: true });
|
|
286
|
+
await writeFile(schemaPathTs, schemaCode);
|
|
287
|
+
await writeFile(schemaPathMjs, schemaCode);
|
|
288
|
+
if (isInsideNodeModules(nuxt.options.buildDir)) {
|
|
289
|
+
const rootSchemaDir = join(nuxt.options.rootDir, ".nuxt", "better-auth");
|
|
290
|
+
const rootSchemaPathTs = join(rootSchemaDir, `schema.${dialect}.ts`);
|
|
291
|
+
await mkdir(rootSchemaDir, { recursive: true });
|
|
292
|
+
await writeFile(rootSchemaPathTs, schemaCode);
|
|
293
|
+
}
|
|
294
|
+
addTemplate({ filename: `better-auth/schema.${dialect}.ts`, getContents: () => schemaCode, write: true });
|
|
295
|
+
addTemplate({ filename: `better-auth/schema.${dialect}.mjs`, getContents: () => schemaCode, write: true });
|
|
296
|
+
consola.info(`Generated ${dialect} schema (.ts + .mjs)`);
|
|
297
|
+
const nuxtWithHubHooks = nuxt;
|
|
298
|
+
nuxtWithHubHooks.hook("hub:db:schema:extend", ({ paths, dialect: hookDialect }) => {
|
|
299
|
+
const schemaPath = resolveHubSchemaPath(nuxt.options.buildDir, nuxt.options.rootDir, hookDialect);
|
|
300
|
+
if (schemaPath)
|
|
301
|
+
paths.unshift(schemaPath);
|
|
302
|
+
});
|
|
303
|
+
} catch (error) {
|
|
304
|
+
const isProduction = !nuxt.options.dev;
|
|
305
|
+
if (isProduction)
|
|
306
|
+
throw error;
|
|
307
|
+
consola.error("Failed to generate schema:", error);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
217
311
|
const generateSecret = () => randomBytes(32).toString("hex");
|
|
218
312
|
function readEnvFile(rootDir) {
|
|
219
313
|
const envPath = join(rootDir, ".env");
|
|
@@ -232,7 +326,7 @@ function appendSecretToEnv(rootDir, secret) {
|
|
|
232
326
|
`;
|
|
233
327
|
writeFileSync(envPath, content, "utf-8");
|
|
234
328
|
}
|
|
235
|
-
async function promptForSecret(rootDir) {
|
|
329
|
+
async function promptForSecret(rootDir, consola) {
|
|
236
330
|
if (process.env.BETTER_AUTH_SECRET || hasEnvSecret(rootDir))
|
|
237
331
|
return void 0;
|
|
238
332
|
if (isCI || isTest) {
|
|
@@ -278,161 +372,37 @@ Proceed?`, { type: "confirm", initial: true, cancel: "null" });
|
|
|
278
372
|
consola.success("Added BETTER_AUTH_SECRET to .env");
|
|
279
373
|
return secret;
|
|
280
374
|
}
|
|
281
|
-
const module$1 = defineNuxtModule({
|
|
282
|
-
meta: { name: "@onmax/nuxt-better-auth", version, configKey: "auth", compatibility: { nuxt: ">=3.0.0" } },
|
|
283
|
-
defaults: {
|
|
284
|
-
clientOnly: false,
|
|
285
|
-
serverConfig: "server/auth.config",
|
|
286
|
-
clientConfig: "app/auth.config",
|
|
287
|
-
redirects: { login: "/login", guest: "/" },
|
|
288
|
-
secondaryStorage: false
|
|
289
|
-
},
|
|
290
|
-
async onInstall(nuxt) {
|
|
291
|
-
const generatedSecret = await promptForSecret(nuxt.options.rootDir);
|
|
292
|
-
if (generatedSecret)
|
|
293
|
-
process.env.BETTER_AUTH_SECRET = generatedSecret;
|
|
294
|
-
const serverPath = join(nuxt.options.rootDir, "server/auth.config.ts");
|
|
295
|
-
const clientPath = join(nuxt.options.srcDir, "auth.config.ts");
|
|
296
|
-
const serverTemplate = `import { defineServerAuth } from '@onmax/nuxt-better-auth/config'
|
|
297
375
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const clientTemplate = `import { defineClientAuth } from '@onmax/nuxt-better-auth/config'
|
|
303
|
-
|
|
304
|
-
export default defineClientAuth({})
|
|
305
|
-
`;
|
|
306
|
-
if (!existsSync(serverPath)) {
|
|
307
|
-
await mkdir(dirname(serverPath), { recursive: true });
|
|
308
|
-
await writeFile(serverPath, serverTemplate);
|
|
309
|
-
consola.success("Created server/auth.config.ts");
|
|
310
|
-
}
|
|
311
|
-
if (!existsSync(clientPath)) {
|
|
312
|
-
await mkdir(dirname(clientPath), { recursive: true });
|
|
313
|
-
await writeFile(clientPath, clientTemplate);
|
|
314
|
-
const relativePath = clientPath.replace(`${nuxt.options.rootDir}/`, "");
|
|
315
|
-
consola.success(`Created ${relativePath}`);
|
|
316
|
-
}
|
|
317
|
-
},
|
|
318
|
-
async setup(options, nuxt) {
|
|
319
|
-
const resolver = createResolver(import.meta.url);
|
|
320
|
-
if (options.clientConfig === "app/auth.config") {
|
|
321
|
-
const srcDirRelative = nuxt.options.srcDir.replace(`${nuxt.options.rootDir}/`, "");
|
|
322
|
-
options.clientConfig = srcDirRelative === nuxt.options.srcDir ? "auth.config" : `${srcDirRelative}/auth.config`;
|
|
323
|
-
}
|
|
324
|
-
const clientOnly = options.clientOnly;
|
|
325
|
-
const serverConfigFile = options.serverConfig;
|
|
326
|
-
const clientConfigFile = options.clientConfig;
|
|
327
|
-
const serverConfigPath = resolver.resolve(nuxt.options.rootDir, serverConfigFile);
|
|
328
|
-
const clientConfigPath = resolver.resolve(nuxt.options.rootDir, clientConfigFile);
|
|
329
|
-
const serverConfigExists = existsSync(`${serverConfigPath}.ts`) || existsSync(`${serverConfigPath}.js`);
|
|
330
|
-
const clientConfigExists = existsSync(`${clientConfigPath}.ts`) || existsSync(`${clientConfigPath}.js`);
|
|
331
|
-
if (!clientOnly && !serverConfigExists)
|
|
332
|
-
throw new Error(`[nuxt-better-auth] Missing ${serverConfigFile}.ts - export default defineServerAuth(...)`);
|
|
333
|
-
if (!clientConfigExists)
|
|
334
|
-
throw new Error(`[nuxt-better-auth] Missing ${clientConfigFile}.ts - export default defineClientAuth(...)`);
|
|
335
|
-
const hasNuxtHub = hasNuxtModule("@nuxthub/core", nuxt);
|
|
336
|
-
const hub = hasNuxtHub ? nuxt.options.hub : void 0;
|
|
337
|
-
const hasHubDb = !clientOnly && hasNuxtHub && !!hub?.db;
|
|
338
|
-
const hasConvex = hasNuxtModule("nuxt-convex", nuxt);
|
|
339
|
-
const convexUrl = resolveConvexUrl(nuxt);
|
|
340
|
-
const hasConvexDb = !clientOnly && hasConvex && !!convexUrl && !hasHubDb;
|
|
341
|
-
if (hasConvexDb) {
|
|
342
|
-
consola.info("Detected Convex - using Convex HTTP adapter for Better Auth database");
|
|
343
|
-
}
|
|
344
|
-
let secondaryStorageEnabled = options.secondaryStorage ?? false;
|
|
345
|
-
if (secondaryStorageEnabled && clientOnly) {
|
|
346
|
-
consola.warn("secondaryStorage is not available in clientOnly mode. Disabling.");
|
|
347
|
-
secondaryStorageEnabled = false;
|
|
348
|
-
} else if (secondaryStorageEnabled && (!hasNuxtHub || !hub?.kv)) {
|
|
349
|
-
consola.warn("secondaryStorage requires @nuxthub/core with hub.kv: true. Disabling.");
|
|
350
|
-
secondaryStorageEnabled = false;
|
|
351
|
-
}
|
|
352
|
-
nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
|
|
353
|
-
nuxt.options.runtimeConfig.public.auth = defu(nuxt.options.runtimeConfig.public.auth, {
|
|
354
|
-
redirects: { login: options.redirects?.login ?? "/login", guest: options.redirects?.guest ?? "/" },
|
|
355
|
-
useDatabase: hasHubDb || hasConvexDb,
|
|
356
|
-
clientOnly
|
|
357
|
-
});
|
|
358
|
-
if (clientOnly) {
|
|
359
|
-
const siteUrl = process.env.NUXT_PUBLIC_SITE_URL || nuxt.options.runtimeConfig.public.siteUrl;
|
|
360
|
-
if (!siteUrl) {
|
|
361
|
-
consola.warn("clientOnly mode: NUXT_PUBLIC_SITE_URL should be set to your frontend URL");
|
|
362
|
-
}
|
|
363
|
-
consola.info("clientOnly mode enabled - server utilities (serverAuth, getUserSession, requireUserSession) are not available");
|
|
364
|
-
}
|
|
365
|
-
if (!clientOnly) {
|
|
366
|
-
const currentSecret = nuxt.options.runtimeConfig.betterAuthSecret;
|
|
367
|
-
nuxt.options.runtimeConfig.betterAuthSecret = currentSecret || process.env.BETTER_AUTH_SECRET || "";
|
|
368
|
-
const betterAuthSecret = nuxt.options.runtimeConfig.betterAuthSecret;
|
|
369
|
-
if (!nuxt.options.dev && !nuxt.options._prepare && !betterAuthSecret) {
|
|
370
|
-
throw new Error("[nuxt-better-auth] BETTER_AUTH_SECRET is required in production. Set BETTER_AUTH_SECRET or NUXT_BETTER_AUTH_SECRET environment variable.");
|
|
371
|
-
}
|
|
372
|
-
if (betterAuthSecret && betterAuthSecret.length < 32) {
|
|
373
|
-
throw new Error("[nuxt-better-auth] BETTER_AUTH_SECRET must be at least 32 characters for security");
|
|
374
|
-
}
|
|
375
|
-
nuxt.options.runtimeConfig.auth = defu(nuxt.options.runtimeConfig.auth, {
|
|
376
|
-
secondaryStorage: secondaryStorageEnabled
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
nuxt.options.alias["#nuxt-better-auth"] = resolver.resolve("./runtime/types/augment");
|
|
380
|
-
if (!clientOnly)
|
|
381
|
-
nuxt.options.alias["#auth/server"] = serverConfigPath;
|
|
382
|
-
nuxt.options.alias["#auth/client"] = clientConfigPath;
|
|
383
|
-
if (!clientOnly) {
|
|
384
|
-
if (secondaryStorageEnabled && !nuxt.options.alias["hub:kv"]) {
|
|
385
|
-
throw new Error("[nuxt-better-auth] hub:kv not found. Ensure @nuxthub/core is loaded before this module and hub.kv is enabled.");
|
|
386
|
-
}
|
|
387
|
-
const secondaryStorageCode = secondaryStorageEnabled ? `import { kv } from '@nuxthub/kv'
|
|
376
|
+
function buildSecondaryStorageCode(enabled) {
|
|
377
|
+
if (!enabled)
|
|
378
|
+
return "export function createSecondaryStorage() { return undefined }";
|
|
379
|
+
return `import { kv } from '@nuxthub/kv'
|
|
388
380
|
export function createSecondaryStorage() {
|
|
389
381
|
return {
|
|
390
382
|
get: async (key) => kv.get(\`_auth:\${key}\`),
|
|
391
383
|
set: async (key, value, ttl) => kv.set(\`_auth:\${key}\`, value, { ttl }),
|
|
392
384
|
delete: async (key) => kv.del(\`_auth:\${key}\`),
|
|
393
385
|
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
const hubDialect = getHubDialect(hub) ?? "sqlite";
|
|
401
|
-
const usePlural = options.schema?.usePlural ?? false;
|
|
402
|
-
const camelCase = (options.schema?.casing ?? getHubCasing(hub)) !== "snake_case";
|
|
403
|
-
let databaseCode;
|
|
404
|
-
if (hasHubDb) {
|
|
405
|
-
databaseCode = `import { db, schema } from '@nuxthub/db'
|
|
386
|
+
}`;
|
|
387
|
+
}
|
|
388
|
+
function buildDatabaseCode(input) {
|
|
389
|
+
if (input.provider === "nuxthub") {
|
|
390
|
+
return `import { db, schema } from '@nuxthub/db'
|
|
406
391
|
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
|
|
407
|
-
const rawDialect = '${hubDialect}'
|
|
392
|
+
const rawDialect = '${input.hubDialect}'
|
|
408
393
|
const dialect = rawDialect === 'postgresql' ? 'pg' : rawDialect
|
|
409
|
-
export function createDatabase() { return drizzleAdapter(db, { provider: dialect, schema, usePlural: ${usePlural}, camelCase: ${camelCase} }) }
|
|
394
|
+
export function createDatabase() { return drizzleAdapter(db, { provider: dialect, schema, usePlural: ${input.usePlural}, camelCase: ${input.camelCase} }) }
|
|
410
395
|
export { db }`;
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
nuxt.options.runtimeConfig.betterAuth || {},
|
|
414
|
-
{ convexUrl }
|
|
415
|
-
);
|
|
416
|
-
databaseCode = `import { useRuntimeConfig } from '#imports'
|
|
417
|
-
import { createConvexHttpAdapter } from '@onmax/nuxt-better-auth/adapters/convex'
|
|
418
|
-
import { api } from '#convex/api'
|
|
419
|
-
|
|
420
|
-
export function createDatabase() {
|
|
421
|
-
const config = useRuntimeConfig()
|
|
422
|
-
const convexUrl = config.betterAuth?.convexUrl || config.public?.convex?.url
|
|
423
|
-
if (!convexUrl) throw new Error('[nuxt-better-auth] CONVEX_URL not configured')
|
|
424
|
-
return createConvexHttpAdapter({ url: convexUrl, api: api.auth })
|
|
425
|
-
}
|
|
426
|
-
export const db = undefined`;
|
|
427
|
-
} else {
|
|
428
|
-
databaseCode = `export function createDatabase() { return undefined }
|
|
396
|
+
}
|
|
397
|
+
return `export function createDatabase() { return undefined }
|
|
429
398
|
export const db = undefined`;
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function registerServerTypeTemplates(input) {
|
|
402
|
+
const { serverConfigPath, hasHubDb, runtimeTypesPath } = input;
|
|
403
|
+
addTypeTemplate({
|
|
404
|
+
filename: "types/auth-secondary-storage.d.ts",
|
|
405
|
+
getContents: () => `
|
|
436
406
|
declare module '#auth/secondary-storage' {
|
|
437
407
|
interface SecondaryStorage {
|
|
438
408
|
get: (key: string) => Promise<string | null>
|
|
@@ -442,214 +412,287 @@ declare module '#auth/secondary-storage' {
|
|
|
442
412
|
export function createSecondaryStorage(): SecondaryStorage | undefined
|
|
443
413
|
}
|
|
444
414
|
`
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
415
|
+
}, { nitro: true, node: true });
|
|
416
|
+
addTypeTemplate({
|
|
417
|
+
filename: "types/auth-database.d.ts",
|
|
418
|
+
getContents: () => `
|
|
449
419
|
declare module '#auth/database' {
|
|
450
|
-
import type {
|
|
451
|
-
export function createDatabase():
|
|
452
|
-
export const db:
|
|
420
|
+
import type { BetterAuthOptions } from 'better-auth'
|
|
421
|
+
export function createDatabase(): BetterAuthOptions['database']
|
|
422
|
+
export const db: ${hasHubDb ? `typeof import('@nuxthub/db')['db']` : "undefined"}
|
|
453
423
|
}
|
|
454
424
|
`
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
import type {
|
|
425
|
+
}, { nitro: true, node: true });
|
|
426
|
+
addTypeTemplate({
|
|
427
|
+
filename: "types/nuxt-better-auth-infer.d.ts",
|
|
428
|
+
getContents: () => `
|
|
429
|
+
import type { BetterAuthOptions, BetterAuthPlugin, InferPluginTypes, UnionToIntersection } from 'better-auth'
|
|
430
|
+
import type { InferFieldsOutput } from 'better-auth/db'
|
|
460
431
|
import type { RuntimeConfig } from 'nuxt/schema'
|
|
461
432
|
import type createServerAuth from '${serverConfigPath}'
|
|
462
433
|
|
|
463
|
-
type
|
|
434
|
+
type _RawConfig = ReturnType<typeof createServerAuth>
|
|
435
|
+
type _RawPlugins = _RawConfig extends { plugins: infer P } ? P : _RawConfig extends { plugins?: infer P } ? P : []
|
|
436
|
+
type _NormalizedPlugins = _RawPlugins extends readonly (infer T)[]
|
|
437
|
+
? Array<T & BetterAuthPlugin>
|
|
438
|
+
: _RawPlugins extends (infer T)[]
|
|
439
|
+
? Array<T & BetterAuthPlugin>
|
|
440
|
+
: BetterAuthPlugin[]
|
|
441
|
+
type _Config = Omit<BetterAuthOptions, 'plugins'> & Omit<_RawConfig, 'plugins'> & {
|
|
442
|
+
plugins?: _NormalizedPlugins
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
type _InferModelFieldsFromPlugins<P, M extends string> = P extends readonly (infer Plugin)[]
|
|
446
|
+
? UnionToIntersection<Plugin extends { schema: { [K in M]: { fields: infer F } } } ? InferFieldsOutput<F> : {}>
|
|
447
|
+
: P extends (infer Plugin)[]
|
|
448
|
+
? UnionToIntersection<Plugin extends { schema: { [K in M]: { fields: infer F } } } ? InferFieldsOutput<F> : {}>
|
|
449
|
+
: {}
|
|
450
|
+
|
|
451
|
+
type _InferModelFieldsFromOptions<C, M extends 'user' | 'session'> = C extends { [K in M]: { additionalFields: infer F } }
|
|
452
|
+
? InferFieldsOutput<F>
|
|
453
|
+
: {}
|
|
454
|
+
|
|
455
|
+
type _UserFallback = _InferModelFieldsFromPlugins<_RawPlugins, 'user'> & _InferModelFieldsFromOptions<_RawConfig, 'user'>
|
|
456
|
+
type _SessionFallback = _InferModelFieldsFromPlugins<_RawPlugins, 'session'> & _InferModelFieldsFromOptions<_RawConfig, 'session'>
|
|
464
457
|
|
|
465
458
|
declare module '#nuxt-better-auth' {
|
|
466
|
-
interface AuthUser extends
|
|
467
|
-
interface AuthSession extends
|
|
459
|
+
interface AuthUser extends _UserFallback {}
|
|
460
|
+
interface AuthSession extends _SessionFallback {}
|
|
468
461
|
interface ServerAuthContext {
|
|
469
462
|
runtimeConfig: RuntimeConfig
|
|
470
|
-
${hasHubDb ? `
|
|
463
|
+
db: ${hasHubDb ? `typeof import('@nuxthub/db')['db']` : "undefined"}
|
|
471
464
|
}
|
|
472
465
|
type PluginTypes = InferPluginTypes<_Config>
|
|
473
466
|
}
|
|
474
467
|
|
|
475
|
-
// Augment the config module to use the extended ServerAuthContext
|
|
476
468
|
interface _AugmentedServerAuthContext {
|
|
477
469
|
runtimeConfig: RuntimeConfig
|
|
478
|
-
${hasHubDb ? `
|
|
470
|
+
db: ${hasHubDb ? `typeof import('@nuxthub/db')['db']` : "undefined"}
|
|
479
471
|
}
|
|
480
472
|
|
|
481
473
|
declare module '@onmax/nuxt-better-auth/config' {
|
|
482
|
-
import type { BetterAuthOptions } from 'better-auth'
|
|
483
|
-
type ServerAuthConfig = Omit<BetterAuthOptions, '
|
|
484
|
-
|
|
474
|
+
import type { BetterAuthOptions, BetterAuthPlugin } from 'better-auth'
|
|
475
|
+
type ServerAuthConfig = Omit<BetterAuthOptions, 'secret' | 'baseURL'> & {
|
|
476
|
+
plugins?: readonly BetterAuthPlugin[]
|
|
477
|
+
}
|
|
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
|
|
485
480
|
}
|
|
486
481
|
`
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
482
|
+
}, { nuxt: true, nitro: true, node: true });
|
|
483
|
+
addTypeTemplate({
|
|
484
|
+
filename: "types/nuxt-better-auth-nitro.d.ts",
|
|
485
|
+
getContents: () => `
|
|
491
486
|
declare module 'nitropack' {
|
|
492
487
|
interface NitroRouteRules {
|
|
493
|
-
auth?: import('${
|
|
488
|
+
auth?: import('${runtimeTypesPath}').AuthMeta
|
|
494
489
|
}
|
|
495
490
|
interface NitroRouteConfig {
|
|
496
|
-
auth?: import('${
|
|
491
|
+
auth?: import('${runtimeTypesPath}').AuthMeta
|
|
497
492
|
}
|
|
498
493
|
}
|
|
499
494
|
declare module 'nitropack/types' {
|
|
500
495
|
interface NitroRouteRules {
|
|
501
|
-
auth?: import('${
|
|
496
|
+
auth?: import('${runtimeTypesPath}').AuthMeta
|
|
502
497
|
}
|
|
503
498
|
interface NitroRouteConfig {
|
|
504
|
-
auth?: import('${
|
|
499
|
+
auth?: import('${runtimeTypesPath}').AuthMeta
|
|
505
500
|
}
|
|
506
501
|
}
|
|
507
502
|
export {}
|
|
508
503
|
`
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
504
|
+
}, { nuxt: true, nitro: true, node: true });
|
|
505
|
+
}
|
|
506
|
+
function registerSharedTypeTemplates(input) {
|
|
507
|
+
addTypeTemplate({
|
|
508
|
+
filename: "types/nuxt-better-auth.d.ts",
|
|
509
|
+
getContents: () => `
|
|
510
|
+
import type { AuthSession, AuthUser } from '${input.runtimeTypesAugmentPath}'
|
|
511
|
+
import type { UserMatch } from '${input.runtimeTypesPath}'
|
|
512
|
+
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>
|
|
517
|
+
}
|
|
516
518
|
`
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
import type createAppAuthClient from '${clientConfigPath}'
|
|
519
|
+
});
|
|
520
|
+
addTypeTemplate({
|
|
521
|
+
filename: "types/nuxt-better-auth-client.d.ts",
|
|
522
|
+
getContents: () => `
|
|
523
|
+
import type createAppAuthClient from '${input.clientConfigPath}'
|
|
522
524
|
declare module '#nuxt-better-auth' {
|
|
523
525
|
export type AppAuthClient = ReturnType<typeof createAppAuthClient>
|
|
524
526
|
}
|
|
525
527
|
`
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const consola = consola$1.withTag("nuxt-better-auth");
|
|
532
|
+
function resolveDefaultClientConfig(options, rootDir, srcDir) {
|
|
533
|
+
if (options.clientConfig !== "app/auth.config")
|
|
534
|
+
return;
|
|
535
|
+
const srcDirRelative = srcDir.replace(`${rootDir}/`, "");
|
|
536
|
+
options.clientConfig = srcDirRelative === srcDir ? "auth.config" : `${srcDirRelative}/auth.config`;
|
|
537
|
+
}
|
|
538
|
+
async function createDefaultAuthConfigFiles(rootDir, srcDir) {
|
|
539
|
+
const serverPath = join(rootDir, "server/auth.config.ts");
|
|
540
|
+
const clientPath = join(srcDir, "auth.config.ts");
|
|
541
|
+
const serverTemplate = `import { defineServerAuth } from '@onmax/nuxt-better-auth/config'
|
|
542
|
+
|
|
543
|
+
export default defineServerAuth({
|
|
544
|
+
emailAndPassword: { enabled: true },
|
|
545
|
+
})
|
|
546
|
+
`;
|
|
547
|
+
const clientTemplate = `import { defineClientAuth } from '@onmax/nuxt-better-auth/config'
|
|
548
|
+
|
|
549
|
+
export default defineClientAuth({})
|
|
550
|
+
`;
|
|
551
|
+
if (!existsSync(serverPath)) {
|
|
552
|
+
await mkdir(dirname(serverPath), { recursive: true });
|
|
553
|
+
await writeFile(serverPath, serverTemplate);
|
|
554
|
+
consola.success("Created server/auth.config.ts");
|
|
555
|
+
}
|
|
556
|
+
if (!existsSync(clientPath)) {
|
|
557
|
+
await mkdir(dirname(clientPath), { recursive: true });
|
|
558
|
+
await writeFile(clientPath, clientTemplate);
|
|
559
|
+
const relativePath = clientPath.replace(`${rootDir}/`, "");
|
|
560
|
+
consola.success(`Created ${relativePath}`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
const module$1 = defineNuxtModule({
|
|
564
|
+
meta: { name: "@onmax/nuxt-better-auth", version, configKey: "auth", compatibility: { nuxt: ">=4.0.0" } },
|
|
565
|
+
defaults: {
|
|
566
|
+
clientOnly: false,
|
|
567
|
+
serverConfig: "server/auth.config",
|
|
568
|
+
clientConfig: "app/auth.config",
|
|
569
|
+
redirects: { login: "/login", guest: "/" },
|
|
570
|
+
secondaryStorage: false
|
|
571
|
+
},
|
|
572
|
+
async onInstall(nuxt) {
|
|
573
|
+
const generatedSecret = await promptForSecret(nuxt.options.rootDir, consola);
|
|
574
|
+
if (generatedSecret)
|
|
575
|
+
process.env.BETTER_AUTH_SECRET = generatedSecret;
|
|
576
|
+
await createDefaultAuthConfigFiles(nuxt.options.rootDir, nuxt.options.srcDir);
|
|
577
|
+
},
|
|
578
|
+
async setup(options, nuxt) {
|
|
579
|
+
const resolver = createResolver(import.meta.url);
|
|
580
|
+
resolveDefaultClientConfig(options, nuxt.options.rootDir, nuxt.options.srcDir);
|
|
581
|
+
const clientOnly = options.clientOnly;
|
|
582
|
+
const serverConfigFile = options.serverConfig;
|
|
583
|
+
const clientConfigFile = options.clientConfig;
|
|
584
|
+
const serverConfigPath = resolver.resolve(nuxt.options.rootDir, serverConfigFile);
|
|
585
|
+
const clientConfigPath = resolver.resolve(nuxt.options.rootDir, clientConfigFile);
|
|
586
|
+
const serverConfigExists = existsSync(`${serverConfigPath}.ts`) || existsSync(`${serverConfigPath}.js`);
|
|
587
|
+
const clientConfigExists = existsSync(`${clientConfigPath}.ts`) || existsSync(`${clientConfigPath}.js`);
|
|
588
|
+
if (!clientOnly && !serverConfigExists)
|
|
589
|
+
throw new Error(`[nuxt-better-auth] Missing ${serverConfigFile}.ts - export default defineServerAuth(...)`);
|
|
590
|
+
if (!clientConfigExists)
|
|
591
|
+
throw new Error(`[nuxt-better-auth] Missing ${clientConfigFile}.ts - export default defineClientAuth(...)`);
|
|
592
|
+
const hasNuxtHub = hasNuxtModule("@nuxthub/core", nuxt);
|
|
593
|
+
const hub = hasNuxtHub ? nuxt.options.hub : void 0;
|
|
594
|
+
const hasHubDbAvailable = !clientOnly && hasNuxtHub && !!hub?.db;
|
|
595
|
+
const deprecatedProvider = options.database?.provider;
|
|
596
|
+
if (deprecatedProvider) {
|
|
597
|
+
throw new Error(
|
|
598
|
+
`[nuxt-better-auth] auth.database.provider has been removed. Remove auth.database.provider="${deprecatedProvider}". To configure a database, either set "database" directly in server/auth.config.ts (defineServerAuth) or install a provider module that registers better-auth:database:providers.`
|
|
599
|
+
);
|
|
537
600
|
}
|
|
538
|
-
|
|
539
|
-
|
|
601
|
+
let databaseProvider = "none";
|
|
602
|
+
let hasHubDb = false;
|
|
603
|
+
nuxt.options.alias["#nuxt-better-auth"] = resolver.resolve("./runtime/types/augment");
|
|
540
604
|
if (!clientOnly)
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
}
|
|
553
|
-
const isProduction = process.env.NODE_ENV === "production" || !nuxt.options.dev;
|
|
554
|
-
if (!isProduction && !clientOnly) {
|
|
555
|
-
if (!hasNuxtModule("@nuxt/ui"))
|
|
556
|
-
await installModule("@nuxt/ui");
|
|
557
|
-
setupDevTools(nuxt);
|
|
558
|
-
addServerHandler({ route: "/api/_better-auth/config", method: "get", handler: resolver.resolve("./runtime/server/api/_better-auth/config.get") });
|
|
559
|
-
if (hasHubDb) {
|
|
560
|
-
addServerHandler({ route: "/api/_better-auth/sessions", method: "get", handler: resolver.resolve("./runtime/server/api/_better-auth/sessions.get") });
|
|
561
|
-
addServerHandler({ route: "/api/_better-auth/sessions", method: "delete", handler: resolver.resolve("./runtime/server/api/_better-auth/sessions.delete") });
|
|
562
|
-
addServerHandler({ route: "/api/_better-auth/users", method: "get", handler: resolver.resolve("./runtime/server/api/_better-auth/users.get") });
|
|
563
|
-
addServerHandler({ route: "/api/_better-auth/accounts", method: "get", handler: resolver.resolve("./runtime/server/api/_better-auth/accounts.get") });
|
|
564
|
-
}
|
|
565
|
-
extendPages((pages) => {
|
|
566
|
-
pages.push({ name: "better-auth-devtools", path: "/__better-auth-devtools", file: resolver.resolve("./runtime/app/pages/__better-auth-devtools.vue") });
|
|
605
|
+
nuxt.options.alias["#auth/server"] = serverConfigPath;
|
|
606
|
+
nuxt.options.alias["#auth/client"] = clientConfigPath;
|
|
607
|
+
if (clientOnly) {
|
|
608
|
+
setupRuntimeConfig({
|
|
609
|
+
nuxt,
|
|
610
|
+
options,
|
|
611
|
+
clientOnly,
|
|
612
|
+
databaseProvider,
|
|
613
|
+
hasNuxtHub,
|
|
614
|
+
hub,
|
|
615
|
+
consola
|
|
567
616
|
});
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
617
|
+
} else {
|
|
618
|
+
const hubDialect = getHubDialect(hub) ?? "sqlite";
|
|
619
|
+
const usePlural = options.schema?.usePlural ?? false;
|
|
620
|
+
const camelCase = (options.schema?.casing ?? getHubCasing(hub)) !== "snake_case";
|
|
621
|
+
const providers = {
|
|
622
|
+
nuxthub: {
|
|
623
|
+
priority: 100,
|
|
624
|
+
isEnabled: ({ hasHubDbAvailable: hasHubDbAvailable2 }) => hasHubDbAvailable2,
|
|
625
|
+
buildDatabaseCode: () => buildDatabaseCode({
|
|
626
|
+
provider: "nuxthub",
|
|
627
|
+
hubDialect,
|
|
628
|
+
usePlural,
|
|
629
|
+
camelCase
|
|
630
|
+
})
|
|
631
|
+
},
|
|
632
|
+
none: {
|
|
633
|
+
priority: 0,
|
|
634
|
+
buildDatabaseCode: () => buildDatabaseCode({
|
|
635
|
+
provider: "none",
|
|
636
|
+
hubDialect,
|
|
637
|
+
usePlural,
|
|
638
|
+
camelCase
|
|
639
|
+
})
|
|
582
640
|
}
|
|
583
|
-
page.children?.forEach((child) => applyMetaFromRules(child));
|
|
584
641
|
};
|
|
585
|
-
|
|
642
|
+
const enabledCtx = { nuxt, options, clientOnly, hasHubDbAvailable };
|
|
643
|
+
await nuxt.callHook("better-auth:database:providers", providers);
|
|
644
|
+
const resolvedProvider = resolveDatabaseProvider({ providers, context: enabledCtx });
|
|
645
|
+
databaseProvider = resolvedProvider.id;
|
|
646
|
+
hasHubDb = databaseProvider === "nuxthub";
|
|
647
|
+
const { secondaryStorageEnabled } = setupRuntimeConfig({
|
|
648
|
+
nuxt,
|
|
649
|
+
options,
|
|
650
|
+
clientOnly,
|
|
651
|
+
databaseProvider,
|
|
652
|
+
hasNuxtHub,
|
|
653
|
+
hub,
|
|
654
|
+
consola
|
|
655
|
+
});
|
|
656
|
+
if (secondaryStorageEnabled && !nuxt.options.alias["hub:kv"]) {
|
|
657
|
+
throw new Error("[nuxt-better-auth] hub:kv not found. Ensure @nuxthub/core is loaded before this module and hub.kv is enabled.");
|
|
658
|
+
}
|
|
659
|
+
const secondaryStorageTemplate = addTemplate({
|
|
660
|
+
filename: "better-auth/secondary-storage.mjs",
|
|
661
|
+
getContents: () => buildSecondaryStorageCode(secondaryStorageEnabled),
|
|
662
|
+
write: true
|
|
663
|
+
});
|
|
664
|
+
nuxt.options.alias["#auth/secondary-storage"] = secondaryStorageTemplate.dst;
|
|
665
|
+
if (hasHubDb && !nuxt.options.alias["hub:db"]) {
|
|
666
|
+
throw new Error("[nuxt-better-auth] hub:db not found. Ensure @nuxthub/core is loaded before this module and hub.db is configured.");
|
|
667
|
+
}
|
|
668
|
+
const setupCtx = { nuxt, options, clientOnly };
|
|
669
|
+
await resolvedProvider.definition.setup?.(setupCtx);
|
|
670
|
+
const buildCtx = { hubDialect, usePlural, camelCase };
|
|
671
|
+
const databaseTemplate = addTemplate({
|
|
672
|
+
filename: "better-auth/database.mjs",
|
|
673
|
+
getContents: () => resolvedProvider.definition.buildDatabaseCode(buildCtx),
|
|
674
|
+
write: true
|
|
675
|
+
});
|
|
676
|
+
nuxt.options.alias["#auth/database"] = databaseTemplate.dst;
|
|
677
|
+
registerServerTypeTemplates({
|
|
678
|
+
serverConfigPath,
|
|
679
|
+
hasHubDb,
|
|
680
|
+
runtimeTypesPath: resolver.resolve("./runtime/types")
|
|
681
|
+
});
|
|
682
|
+
if (hasHubDb)
|
|
683
|
+
await setupBetterAuthSchema(nuxt, serverConfigPath, options, consola);
|
|
684
|
+
}
|
|
685
|
+
registerSharedTypeTemplates({
|
|
686
|
+
runtimeTypesAugmentPath: resolver.resolve("./runtime/types/augment"),
|
|
687
|
+
runtimeTypesPath: resolver.resolve("./runtime/types"),
|
|
688
|
+
clientConfigPath
|
|
586
689
|
});
|
|
690
|
+
registerTemplateHmrHook(nuxt);
|
|
691
|
+
registerServerRuntime({ clientOnly, resolve: resolver.resolve });
|
|
692
|
+
registerAuthMiddlewareHook(nuxt, resolver.resolve);
|
|
693
|
+
await registerDevtools({ nuxt, clientOnly, hasHubDb, resolve: resolver.resolve });
|
|
694
|
+
registerRouteRulesMetaHook(nuxt);
|
|
587
695
|
}
|
|
588
696
|
});
|
|
589
|
-
async function setupBetterAuthSchema(nuxt, serverConfigPath, options) {
|
|
590
|
-
const hub = nuxt.options.hub;
|
|
591
|
-
const dialect = getHubDialect(hub);
|
|
592
|
-
if (!dialect || !["sqlite", "postgresql", "mysql"].includes(dialect)) {
|
|
593
|
-
consola.warn(`Unsupported database dialect: ${dialect}`);
|
|
594
|
-
return;
|
|
595
|
-
}
|
|
596
|
-
const isProduction = !nuxt.options.dev;
|
|
597
|
-
try {
|
|
598
|
-
const configFile = `${serverConfigPath}.ts`;
|
|
599
|
-
const userConfig = await loadUserAuthConfig(configFile, isProduction);
|
|
600
|
-
const extendedConfig = {};
|
|
601
|
-
await nuxt.callHook("better-auth:config:extend", extendedConfig);
|
|
602
|
-
const plugins = [...userConfig.plugins || [], ...extendedConfig.plugins || []];
|
|
603
|
-
const authOptions = {
|
|
604
|
-
...userConfig,
|
|
605
|
-
plugins,
|
|
606
|
-
secondaryStorage: options.secondaryStorage ? { get: async (_key) => null, set: async (_key, _value, _ttl) => {
|
|
607
|
-
}, delete: async (_key) => {
|
|
608
|
-
} } : void 0
|
|
609
|
-
};
|
|
610
|
-
const hubCasing = getHubCasing(hub);
|
|
611
|
-
const schemaOptions = { ...options.schema, useUuid: userConfig.advanced?.database?.generateId === "uuid", casing: options.schema?.casing ?? hubCasing };
|
|
612
|
-
const schemaCode = await generateDrizzleSchema(authOptions, dialect, schemaOptions);
|
|
613
|
-
const schemaDir = join(nuxt.options.buildDir, "better-auth");
|
|
614
|
-
const schemaPath = join(schemaDir, `schema.${dialect}.ts`);
|
|
615
|
-
await mkdir(schemaDir, { recursive: true });
|
|
616
|
-
await writeFile(schemaPath, schemaCode);
|
|
617
|
-
addTemplate({ filename: `better-auth/schema.${dialect}.ts`, getContents: () => schemaCode, write: true });
|
|
618
|
-
consola.info(`Generated ${dialect} schema`);
|
|
619
|
-
} catch (error) {
|
|
620
|
-
if (isProduction) {
|
|
621
|
-
throw error;
|
|
622
|
-
}
|
|
623
|
-
consola.error("Failed to generate schema:", error);
|
|
624
|
-
}
|
|
625
|
-
const nuxtWithHubHooks = nuxt;
|
|
626
|
-
nuxtWithHubHooks.hook("hub:db:schema:extend", ({ paths, dialect: hookDialect }) => {
|
|
627
|
-
const schemaPath = join(nuxt.options.buildDir, "better-auth", `schema.${hookDialect}.ts`);
|
|
628
|
-
if (existsSync(schemaPath)) {
|
|
629
|
-
paths.unshift(schemaPath);
|
|
630
|
-
}
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
async function setupConvexAuthSchema(nuxt, serverConfigPath) {
|
|
634
|
-
const isProduction = !nuxt.options.dev;
|
|
635
|
-
try {
|
|
636
|
-
const configFile = `${serverConfigPath}.ts`;
|
|
637
|
-
const userConfig = await loadUserAuthConfig(configFile, isProduction);
|
|
638
|
-
const authOptions = { ...userConfig };
|
|
639
|
-
const schemaCode = await generateConvexSchema(authOptions);
|
|
640
|
-
const schemaDir = join(nuxt.options.buildDir, "better-auth");
|
|
641
|
-
const schemaPath = join(schemaDir, "auth-tables.convex.ts");
|
|
642
|
-
await mkdir(schemaDir, { recursive: true });
|
|
643
|
-
await writeFile(schemaPath, schemaCode);
|
|
644
|
-
addTemplate({ filename: "better-auth/auth-tables.convex.ts", getContents: () => schemaCode, write: true });
|
|
645
|
-
nuxt.options.alias["#auth/convex-schema"] = schemaPath;
|
|
646
|
-
consola.info("Generated Convex auth schema at .nuxt/better-auth/auth-tables.convex.ts");
|
|
647
|
-
} catch (error) {
|
|
648
|
-
if (isProduction) {
|
|
649
|
-
throw error;
|
|
650
|
-
}
|
|
651
|
-
consola.error("Failed to generate Convex schema:", error);
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
697
|
|
|
655
698
|
export { module$1 as default };
|