@onmax/nuxt-better-auth 0.0.2-alpha.21 → 0.0.2-alpha.22
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 +454 -433
- 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/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.22";
|
|
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,63 @@ async function loadUserAuthConfig(configPath, throwOnError = false) {
|
|
|
186
235
|
}
|
|
187
236
|
}
|
|
188
237
|
|
|
189
|
-
function
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return
|
|
197
|
-
}
|
|
198
|
-
function getHubCasing(hub) {
|
|
199
|
-
if (!hub?.db || typeof hub.db !== "object" || hub.db === null)
|
|
200
|
-
return void 0;
|
|
201
|
-
return hub.db.casing;
|
|
238
|
+
async function loadAuthOptions(context) {
|
|
239
|
+
const isProduction = !context.nuxt.options.dev;
|
|
240
|
+
const configFile = `${context.serverConfigPath}.ts`;
|
|
241
|
+
const userConfig = await loadUserAuthConfig(configFile, isProduction);
|
|
242
|
+
const extendedConfig = {};
|
|
243
|
+
await context.nuxt.callHook("better-auth:config:extend", extendedConfig);
|
|
244
|
+
const plugins = [...userConfig.plugins || [], ...extendedConfig.plugins || []];
|
|
245
|
+
return { userConfig, plugins };
|
|
202
246
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
247
|
+
async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola) {
|
|
248
|
+
const hub = nuxt.options.hub;
|
|
249
|
+
const dialect = getHubDialect(hub);
|
|
250
|
+
if (!dialect || !["sqlite", "postgresql", "mysql"].includes(dialect)) {
|
|
251
|
+
consola.warn(`Unsupported database dialect: ${dialect}`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const context = { nuxt, serverConfigPath };
|
|
255
|
+
try {
|
|
256
|
+
const { userConfig, plugins } = await loadAuthOptions(context);
|
|
257
|
+
const authOptions = {
|
|
258
|
+
...userConfig,
|
|
259
|
+
plugins,
|
|
260
|
+
secondaryStorage: options.secondaryStorage ? { get: async (_key) => null, set: async (_key, _value, _ttl) => {
|
|
261
|
+
}, delete: async (_key) => {
|
|
262
|
+
} } : void 0
|
|
263
|
+
};
|
|
264
|
+
const hubCasing = getHubCasing(hub);
|
|
265
|
+
const schemaOptions = { ...options.schema, useUuid: userConfig.advanced?.database?.generateId === "uuid", casing: options.schema?.casing ?? hubCasing };
|
|
266
|
+
const schemaCode = await generateDrizzleSchema(authOptions, dialect, schemaOptions);
|
|
267
|
+
const schemaDir = join(nuxt.options.buildDir, "better-auth");
|
|
268
|
+
const schemaPathTs = join(schemaDir, `schema.${dialect}.ts`);
|
|
269
|
+
const schemaPathMjs = join(schemaDir, `schema.${dialect}.mjs`);
|
|
270
|
+
await mkdir(schemaDir, { recursive: true });
|
|
271
|
+
await writeFile(schemaPathTs, schemaCode);
|
|
272
|
+
await writeFile(schemaPathMjs, schemaCode);
|
|
273
|
+
addTemplate({ filename: `better-auth/schema.${dialect}.ts`, getContents: () => schemaCode, write: true });
|
|
274
|
+
addTemplate({ filename: `better-auth/schema.${dialect}.mjs`, getContents: () => schemaCode, write: true });
|
|
275
|
+
consola.info(`Generated ${dialect} schema (.ts + .mjs)`);
|
|
276
|
+
const nuxtWithHubHooks = nuxt;
|
|
277
|
+
nuxtWithHubHooks.hook("hub:db:schema:extend", ({ paths, dialect: hookDialect }) => {
|
|
278
|
+
const tsPath = join(nuxt.options.buildDir, "better-auth", `schema.${hookDialect}.ts`);
|
|
279
|
+
const mjsPath = join(nuxt.options.buildDir, "better-auth", `schema.${hookDialect}.mjs`);
|
|
280
|
+
if (existsSync(tsPath)) {
|
|
281
|
+
paths.unshift(tsPath);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (existsSync(mjsPath))
|
|
285
|
+
paths.unshift(mjsPath);
|
|
286
|
+
});
|
|
287
|
+
} catch (error) {
|
|
288
|
+
const isProduction = !nuxt.options.dev;
|
|
289
|
+
if (isProduction)
|
|
290
|
+
throw error;
|
|
291
|
+
consola.error("Failed to generate schema:", error);
|
|
292
|
+
}
|
|
216
293
|
}
|
|
294
|
+
|
|
217
295
|
const generateSecret = () => randomBytes(32).toString("hex");
|
|
218
296
|
function readEnvFile(rootDir) {
|
|
219
297
|
const envPath = join(rootDir, ".env");
|
|
@@ -232,7 +310,7 @@ function appendSecretToEnv(rootDir, secret) {
|
|
|
232
310
|
`;
|
|
233
311
|
writeFileSync(envPath, content, "utf-8");
|
|
234
312
|
}
|
|
235
|
-
async function promptForSecret(rootDir) {
|
|
313
|
+
async function promptForSecret(rootDir, consola) {
|
|
236
314
|
if (process.env.BETTER_AUTH_SECRET || hasEnvSecret(rootDir))
|
|
237
315
|
return void 0;
|
|
238
316
|
if (isCI || isTest) {
|
|
@@ -278,161 +356,37 @@ Proceed?`, { type: "confirm", initial: true, cancel: "null" });
|
|
|
278
356
|
consola.success("Added BETTER_AUTH_SECRET to .env");
|
|
279
357
|
return secret;
|
|
280
358
|
}
|
|
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
359
|
|
|
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'
|
|
360
|
+
function buildSecondaryStorageCode(enabled) {
|
|
361
|
+
if (!enabled)
|
|
362
|
+
return "export function createSecondaryStorage() { return undefined }";
|
|
363
|
+
return `import { kv } from '@nuxthub/kv'
|
|
388
364
|
export function createSecondaryStorage() {
|
|
389
365
|
return {
|
|
390
366
|
get: async (key) => kv.get(\`_auth:\${key}\`),
|
|
391
367
|
set: async (key, value, ttl) => kv.set(\`_auth:\${key}\`, value, { ttl }),
|
|
392
368
|
delete: async (key) => kv.del(\`_auth:\${key}\`),
|
|
393
369
|
}
|
|
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'
|
|
370
|
+
}`;
|
|
371
|
+
}
|
|
372
|
+
function buildDatabaseCode(input) {
|
|
373
|
+
if (input.provider === "nuxthub") {
|
|
374
|
+
return `import { db, schema } from '@nuxthub/db'
|
|
406
375
|
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
|
|
407
|
-
const rawDialect = '${hubDialect}'
|
|
376
|
+
const rawDialect = '${input.hubDialect}'
|
|
408
377
|
const dialect = rawDialect === 'postgresql' ? 'pg' : rawDialect
|
|
409
|
-
export function createDatabase() { return drizzleAdapter(db, { provider: dialect, schema, usePlural: ${usePlural}, camelCase: ${camelCase} }) }
|
|
378
|
+
export function createDatabase() { return drizzleAdapter(db, { provider: dialect, schema, usePlural: ${input.usePlural}, camelCase: ${input.camelCase} }) }
|
|
410
379
|
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 }
|
|
380
|
+
}
|
|
381
|
+
return `export function createDatabase() { return undefined }
|
|
429
382
|
export const db = undefined`;
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function registerServerTypeTemplates(input) {
|
|
386
|
+
const { serverConfigPath, hasHubDb, runtimeTypesPath } = input;
|
|
387
|
+
addTypeTemplate({
|
|
388
|
+
filename: "types/auth-secondary-storage.d.ts",
|
|
389
|
+
getContents: () => `
|
|
436
390
|
declare module '#auth/secondary-storage' {
|
|
437
391
|
interface SecondaryStorage {
|
|
438
392
|
get: (key: string) => Promise<string | null>
|
|
@@ -442,214 +396,281 @@ declare module '#auth/secondary-storage' {
|
|
|
442
396
|
export function createSecondaryStorage(): SecondaryStorage | undefined
|
|
443
397
|
}
|
|
444
398
|
`
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
399
|
+
}, { nitro: true, node: true });
|
|
400
|
+
addTypeTemplate({
|
|
401
|
+
filename: "types/auth-database.d.ts",
|
|
402
|
+
getContents: () => `
|
|
449
403
|
declare module '#auth/database' {
|
|
450
|
-
import type {
|
|
451
|
-
export function createDatabase():
|
|
452
|
-
export const db:
|
|
404
|
+
import type { BetterAuthOptions } from 'better-auth'
|
|
405
|
+
export function createDatabase(): BetterAuthOptions['database']
|
|
406
|
+
export const db: ${hasHubDb ? `typeof import('@nuxthub/db')['db']` : "undefined"}
|
|
453
407
|
}
|
|
454
408
|
`
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
import type {
|
|
409
|
+
}, { nitro: true, node: true });
|
|
410
|
+
addTypeTemplate({
|
|
411
|
+
filename: "types/nuxt-better-auth-infer.d.ts",
|
|
412
|
+
getContents: () => `
|
|
413
|
+
import type { BetterAuthOptions, BetterAuthPlugin, InferPluginTypes, UnionToIntersection } from 'better-auth'
|
|
414
|
+
import type { InferFieldsOutput } from 'better-auth/db'
|
|
460
415
|
import type { RuntimeConfig } from 'nuxt/schema'
|
|
461
416
|
import type createServerAuth from '${serverConfigPath}'
|
|
462
417
|
|
|
463
|
-
type
|
|
418
|
+
type _RawConfig = ReturnType<typeof createServerAuth>
|
|
419
|
+
type _RawPlugins = _RawConfig extends { plugins: infer P } ? P : _RawConfig extends { plugins?: infer P } ? P : []
|
|
420
|
+
type _NormalizedPlugins = _RawPlugins extends readonly (infer T)[]
|
|
421
|
+
? Array<T & BetterAuthPlugin>
|
|
422
|
+
: _RawPlugins extends (infer T)[]
|
|
423
|
+
? Array<T & BetterAuthPlugin>
|
|
424
|
+
: BetterAuthPlugin[]
|
|
425
|
+
type _Config = Omit<BetterAuthOptions, 'plugins'> & Omit<_RawConfig, 'plugins'> & {
|
|
426
|
+
plugins?: _NormalizedPlugins
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
type _InferModelFieldsFromPlugins<P, M extends string> = P extends readonly (infer Plugin)[]
|
|
430
|
+
? UnionToIntersection<Plugin extends { schema: { [K in M]: { fields: infer F } } } ? InferFieldsOutput<F> : {}>
|
|
431
|
+
: P extends (infer Plugin)[]
|
|
432
|
+
? UnionToIntersection<Plugin extends { schema: { [K in M]: { fields: infer F } } } ? InferFieldsOutput<F> : {}>
|
|
433
|
+
: {}
|
|
434
|
+
|
|
435
|
+
type _InferModelFieldsFromOptions<C, M extends 'user' | 'session'> = C extends { [K in M]: { additionalFields: infer F } }
|
|
436
|
+
? InferFieldsOutput<F>
|
|
437
|
+
: {}
|
|
438
|
+
|
|
439
|
+
type _UserFallback = _InferModelFieldsFromPlugins<_RawPlugins, 'user'> & _InferModelFieldsFromOptions<_RawConfig, 'user'>
|
|
440
|
+
type _SessionFallback = _InferModelFieldsFromPlugins<_RawPlugins, 'session'> & _InferModelFieldsFromOptions<_RawConfig, 'session'>
|
|
464
441
|
|
|
465
442
|
declare module '#nuxt-better-auth' {
|
|
466
|
-
interface AuthUser extends
|
|
467
|
-
interface AuthSession extends
|
|
443
|
+
interface AuthUser extends _UserFallback {}
|
|
444
|
+
interface AuthSession extends _SessionFallback {}
|
|
468
445
|
interface ServerAuthContext {
|
|
469
446
|
runtimeConfig: RuntimeConfig
|
|
470
|
-
${hasHubDb ? `
|
|
447
|
+
db: ${hasHubDb ? `typeof import('@nuxthub/db')['db']` : "undefined"}
|
|
471
448
|
}
|
|
472
449
|
type PluginTypes = InferPluginTypes<_Config>
|
|
473
450
|
}
|
|
474
451
|
|
|
475
|
-
// Augment the config module to use the extended ServerAuthContext
|
|
476
452
|
interface _AugmentedServerAuthContext {
|
|
477
453
|
runtimeConfig: RuntimeConfig
|
|
478
|
-
${hasHubDb ? `
|
|
454
|
+
db: ${hasHubDb ? `typeof import('@nuxthub/db')['db']` : "undefined"}
|
|
479
455
|
}
|
|
480
456
|
|
|
481
457
|
declare module '@onmax/nuxt-better-auth/config' {
|
|
482
|
-
import type { BetterAuthOptions } from 'better-auth'
|
|
483
|
-
type ServerAuthConfig = Omit<BetterAuthOptions, '
|
|
484
|
-
|
|
458
|
+
import type { BetterAuthOptions, BetterAuthPlugin } from 'better-auth'
|
|
459
|
+
type ServerAuthConfig = Omit<BetterAuthOptions, 'secret' | 'baseURL'> & {
|
|
460
|
+
plugins?: readonly BetterAuthPlugin[]
|
|
461
|
+
}
|
|
462
|
+
export function defineServerAuth<const R extends ServerAuthConfig>(config: R): (ctx: _AugmentedServerAuthContext) => R
|
|
463
|
+
export function defineServerAuth<const R extends ServerAuthConfig>(config: (ctx: _AugmentedServerAuthContext) => R): (ctx: _AugmentedServerAuthContext) => R
|
|
485
464
|
}
|
|
486
465
|
`
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
466
|
+
}, { nuxt: true, nitro: true, node: true });
|
|
467
|
+
addTypeTemplate({
|
|
468
|
+
filename: "types/nuxt-better-auth-nitro.d.ts",
|
|
469
|
+
getContents: () => `
|
|
491
470
|
declare module 'nitropack' {
|
|
492
471
|
interface NitroRouteRules {
|
|
493
|
-
auth?: import('${
|
|
472
|
+
auth?: import('${runtimeTypesPath}').AuthMeta
|
|
494
473
|
}
|
|
495
474
|
interface NitroRouteConfig {
|
|
496
|
-
auth?: import('${
|
|
475
|
+
auth?: import('${runtimeTypesPath}').AuthMeta
|
|
497
476
|
}
|
|
498
477
|
}
|
|
499
478
|
declare module 'nitropack/types' {
|
|
500
479
|
interface NitroRouteRules {
|
|
501
|
-
auth?: import('${
|
|
480
|
+
auth?: import('${runtimeTypesPath}').AuthMeta
|
|
502
481
|
}
|
|
503
482
|
interface NitroRouteConfig {
|
|
504
|
-
auth?: import('${
|
|
483
|
+
auth?: import('${runtimeTypesPath}').AuthMeta
|
|
505
484
|
}
|
|
506
485
|
}
|
|
507
486
|
export {}
|
|
508
487
|
`
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
export
|
|
488
|
+
}, { nuxt: true, nitro: true, node: true });
|
|
489
|
+
}
|
|
490
|
+
function registerSharedTypeTemplates(input) {
|
|
491
|
+
addTypeTemplate({
|
|
492
|
+
filename: "types/nuxt-better-auth.d.ts",
|
|
493
|
+
getContents: () => `
|
|
494
|
+
export * from '${input.runtimeTypesAugmentPath}'
|
|
495
|
+
export type { AuthMeta, AuthMode, AuthRouteRules, UserMatch, RequireSessionOptions, Auth, InferUser, InferSession } from '${input.runtimeTypesPath}'
|
|
516
496
|
`
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
import type createAppAuthClient from '${clientConfigPath}'
|
|
497
|
+
});
|
|
498
|
+
addTypeTemplate({
|
|
499
|
+
filename: "types/nuxt-better-auth-client.d.ts",
|
|
500
|
+
getContents: () => `
|
|
501
|
+
import type createAppAuthClient from '${input.clientConfigPath}'
|
|
522
502
|
declare module '#nuxt-better-auth' {
|
|
523
503
|
export type AppAuthClient = ReturnType<typeof createAppAuthClient>
|
|
524
504
|
}
|
|
525
505
|
`
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const consola = consola$1.withTag("nuxt-better-auth");
|
|
510
|
+
function resolveDefaultClientConfig(options, rootDir, srcDir) {
|
|
511
|
+
if (options.clientConfig !== "app/auth.config")
|
|
512
|
+
return;
|
|
513
|
+
const srcDirRelative = srcDir.replace(`${rootDir}/`, "");
|
|
514
|
+
options.clientConfig = srcDirRelative === srcDir ? "auth.config" : `${srcDirRelative}/auth.config`;
|
|
515
|
+
}
|
|
516
|
+
async function createDefaultAuthConfigFiles(rootDir, srcDir) {
|
|
517
|
+
const serverPath = join(rootDir, "server/auth.config.ts");
|
|
518
|
+
const clientPath = join(srcDir, "auth.config.ts");
|
|
519
|
+
const serverTemplate = `import { defineServerAuth } from '@onmax/nuxt-better-auth/config'
|
|
520
|
+
|
|
521
|
+
export default defineServerAuth({
|
|
522
|
+
emailAndPassword: { enabled: true },
|
|
523
|
+
})
|
|
524
|
+
`;
|
|
525
|
+
const clientTemplate = `import { defineClientAuth } from '@onmax/nuxt-better-auth/config'
|
|
526
|
+
|
|
527
|
+
export default defineClientAuth({})
|
|
528
|
+
`;
|
|
529
|
+
if (!existsSync(serverPath)) {
|
|
530
|
+
await mkdir(dirname(serverPath), { recursive: true });
|
|
531
|
+
await writeFile(serverPath, serverTemplate);
|
|
532
|
+
consola.success("Created server/auth.config.ts");
|
|
533
|
+
}
|
|
534
|
+
if (!existsSync(clientPath)) {
|
|
535
|
+
await mkdir(dirname(clientPath), { recursive: true });
|
|
536
|
+
await writeFile(clientPath, clientTemplate);
|
|
537
|
+
const relativePath = clientPath.replace(`${rootDir}/`, "");
|
|
538
|
+
consola.success(`Created ${relativePath}`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
const module$1 = defineNuxtModule({
|
|
542
|
+
meta: { name: "@onmax/nuxt-better-auth", version, configKey: "auth", compatibility: { nuxt: ">=4.0.0" } },
|
|
543
|
+
defaults: {
|
|
544
|
+
clientOnly: false,
|
|
545
|
+
serverConfig: "server/auth.config",
|
|
546
|
+
clientConfig: "app/auth.config",
|
|
547
|
+
redirects: { login: "/login", guest: "/" },
|
|
548
|
+
secondaryStorage: false
|
|
549
|
+
},
|
|
550
|
+
async onInstall(nuxt) {
|
|
551
|
+
const generatedSecret = await promptForSecret(nuxt.options.rootDir, consola);
|
|
552
|
+
if (generatedSecret)
|
|
553
|
+
process.env.BETTER_AUTH_SECRET = generatedSecret;
|
|
554
|
+
await createDefaultAuthConfigFiles(nuxt.options.rootDir, nuxt.options.srcDir);
|
|
555
|
+
},
|
|
556
|
+
async setup(options, nuxt) {
|
|
557
|
+
const resolver = createResolver(import.meta.url);
|
|
558
|
+
resolveDefaultClientConfig(options, nuxt.options.rootDir, nuxt.options.srcDir);
|
|
559
|
+
const clientOnly = options.clientOnly;
|
|
560
|
+
const serverConfigFile = options.serverConfig;
|
|
561
|
+
const clientConfigFile = options.clientConfig;
|
|
562
|
+
const serverConfigPath = resolver.resolve(nuxt.options.rootDir, serverConfigFile);
|
|
563
|
+
const clientConfigPath = resolver.resolve(nuxt.options.rootDir, clientConfigFile);
|
|
564
|
+
const serverConfigExists = existsSync(`${serverConfigPath}.ts`) || existsSync(`${serverConfigPath}.js`);
|
|
565
|
+
const clientConfigExists = existsSync(`${clientConfigPath}.ts`) || existsSync(`${clientConfigPath}.js`);
|
|
566
|
+
if (!clientOnly && !serverConfigExists)
|
|
567
|
+
throw new Error(`[nuxt-better-auth] Missing ${serverConfigFile}.ts - export default defineServerAuth(...)`);
|
|
568
|
+
if (!clientConfigExists)
|
|
569
|
+
throw new Error(`[nuxt-better-auth] Missing ${clientConfigFile}.ts - export default defineClientAuth(...)`);
|
|
570
|
+
const hasNuxtHub = hasNuxtModule("@nuxthub/core", nuxt);
|
|
571
|
+
const hub = hasNuxtHub ? nuxt.options.hub : void 0;
|
|
572
|
+
const hasHubDbAvailable = !clientOnly && hasNuxtHub && !!hub?.db;
|
|
573
|
+
const deprecatedProvider = options.database?.provider;
|
|
574
|
+
if (deprecatedProvider) {
|
|
575
|
+
throw new Error(
|
|
576
|
+
`[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.`
|
|
577
|
+
);
|
|
537
578
|
}
|
|
538
|
-
|
|
539
|
-
|
|
579
|
+
let databaseProvider = "none";
|
|
580
|
+
let hasHubDb = false;
|
|
581
|
+
nuxt.options.alias["#nuxt-better-auth"] = resolver.resolve("./runtime/types/augment");
|
|
540
582
|
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") });
|
|
583
|
+
nuxt.options.alias["#auth/server"] = serverConfigPath;
|
|
584
|
+
nuxt.options.alias["#auth/client"] = clientConfigPath;
|
|
585
|
+
if (clientOnly) {
|
|
586
|
+
setupRuntimeConfig({
|
|
587
|
+
nuxt,
|
|
588
|
+
options,
|
|
589
|
+
clientOnly,
|
|
590
|
+
databaseProvider,
|
|
591
|
+
hasNuxtHub,
|
|
592
|
+
hub,
|
|
593
|
+
consola
|
|
567
594
|
});
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
595
|
+
} else {
|
|
596
|
+
const hubDialect = getHubDialect(hub) ?? "sqlite";
|
|
597
|
+
const usePlural = options.schema?.usePlural ?? false;
|
|
598
|
+
const camelCase = (options.schema?.casing ?? getHubCasing(hub)) !== "snake_case";
|
|
599
|
+
const providers = {
|
|
600
|
+
nuxthub: {
|
|
601
|
+
priority: 100,
|
|
602
|
+
isEnabled: ({ hasHubDbAvailable: hasHubDbAvailable2 }) => hasHubDbAvailable2,
|
|
603
|
+
buildDatabaseCode: () => buildDatabaseCode({
|
|
604
|
+
provider: "nuxthub",
|
|
605
|
+
hubDialect,
|
|
606
|
+
usePlural,
|
|
607
|
+
camelCase
|
|
608
|
+
})
|
|
609
|
+
},
|
|
610
|
+
none: {
|
|
611
|
+
priority: 0,
|
|
612
|
+
buildDatabaseCode: () => buildDatabaseCode({
|
|
613
|
+
provider: "none",
|
|
614
|
+
hubDialect,
|
|
615
|
+
usePlural,
|
|
616
|
+
camelCase
|
|
617
|
+
})
|
|
582
618
|
}
|
|
583
|
-
page.children?.forEach((child) => applyMetaFromRules(child));
|
|
584
619
|
};
|
|
585
|
-
|
|
620
|
+
const enabledCtx = { nuxt, options, clientOnly, hasHubDbAvailable };
|
|
621
|
+
await nuxt.callHook("better-auth:database:providers", providers);
|
|
622
|
+
const resolvedProvider = resolveDatabaseProvider({ providers, context: enabledCtx });
|
|
623
|
+
databaseProvider = resolvedProvider.id;
|
|
624
|
+
hasHubDb = databaseProvider === "nuxthub";
|
|
625
|
+
const { secondaryStorageEnabled } = setupRuntimeConfig({
|
|
626
|
+
nuxt,
|
|
627
|
+
options,
|
|
628
|
+
clientOnly,
|
|
629
|
+
databaseProvider,
|
|
630
|
+
hasNuxtHub,
|
|
631
|
+
hub,
|
|
632
|
+
consola
|
|
633
|
+
});
|
|
634
|
+
if (secondaryStorageEnabled && !nuxt.options.alias["hub:kv"]) {
|
|
635
|
+
throw new Error("[nuxt-better-auth] hub:kv not found. Ensure @nuxthub/core is loaded before this module and hub.kv is enabled.");
|
|
636
|
+
}
|
|
637
|
+
const secondaryStorageTemplate = addTemplate({
|
|
638
|
+
filename: "better-auth/secondary-storage.mjs",
|
|
639
|
+
getContents: () => buildSecondaryStorageCode(secondaryStorageEnabled),
|
|
640
|
+
write: true
|
|
641
|
+
});
|
|
642
|
+
nuxt.options.alias["#auth/secondary-storage"] = secondaryStorageTemplate.dst;
|
|
643
|
+
if (hasHubDb && !nuxt.options.alias["hub:db"]) {
|
|
644
|
+
throw new Error("[nuxt-better-auth] hub:db not found. Ensure @nuxthub/core is loaded before this module and hub.db is configured.");
|
|
645
|
+
}
|
|
646
|
+
const setupCtx = { nuxt, options, clientOnly };
|
|
647
|
+
await resolvedProvider.definition.setup?.(setupCtx);
|
|
648
|
+
const buildCtx = { hubDialect, usePlural, camelCase };
|
|
649
|
+
const databaseTemplate = addTemplate({
|
|
650
|
+
filename: "better-auth/database.mjs",
|
|
651
|
+
getContents: () => resolvedProvider.definition.buildDatabaseCode(buildCtx),
|
|
652
|
+
write: true
|
|
653
|
+
});
|
|
654
|
+
nuxt.options.alias["#auth/database"] = databaseTemplate.dst;
|
|
655
|
+
registerServerTypeTemplates({
|
|
656
|
+
serverConfigPath,
|
|
657
|
+
hasHubDb,
|
|
658
|
+
runtimeTypesPath: resolver.resolve("./runtime/types")
|
|
659
|
+
});
|
|
660
|
+
if (hasHubDb)
|
|
661
|
+
await setupBetterAuthSchema(nuxt, serverConfigPath, options, consola);
|
|
662
|
+
}
|
|
663
|
+
registerSharedTypeTemplates({
|
|
664
|
+
runtimeTypesAugmentPath: resolver.resolve("./runtime/types/augment"),
|
|
665
|
+
runtimeTypesPath: resolver.resolve("./runtime/types"),
|
|
666
|
+
clientConfigPath
|
|
586
667
|
});
|
|
668
|
+
registerTemplateHmrHook(nuxt);
|
|
669
|
+
registerServerRuntime({ clientOnly, resolve: resolver.resolve });
|
|
670
|
+
registerAuthMiddlewareHook(nuxt, resolver.resolve);
|
|
671
|
+
await registerDevtools({ nuxt, clientOnly, hasHubDb, resolve: resolver.resolve });
|
|
672
|
+
registerRouteRulesMetaHook(nuxt);
|
|
587
673
|
}
|
|
588
674
|
});
|
|
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
675
|
|
|
655
676
|
export { module$1 as default };
|