@nuxtjs/better-auth 0.3.6 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.json +1 -1
- package/dist/module.mjs +104 -44
- package/dist/runtime/app/composables/useAuthRequestFetch.js +25 -9
- package/dist/runtime/app/composables/useUserSession.js +3 -1
- package/dist/runtime/app/middleware/auth.global.js +9 -10
- package/dist/runtime/server/api/_better-auth/accounts.get.js +8 -1
- package/dist/runtime/server/api/auth/[...all].js +2 -0
- package/dist/runtime/server/internal/app-secret-compat.d.ts +1 -0
- package/dist/runtime/server/internal/app-secret-compat.js +1 -0
- package/dist/runtime/server/internal/app-secret-unavailable.d.ts +2 -0
- package/dist/runtime/server/internal/app-secret-unavailable.js +1 -0
- package/dist/runtime/server/internal/app-secret.d.ts +4 -0
- package/dist/runtime/server/internal/app-secret.js +3 -0
- package/dist/runtime/server/internal/auth-secret.d.ts +25 -0
- package/dist/runtime/server/internal/auth-secret.js +40 -0
- package/dist/runtime/server/internal/cookie-headers.d.ts +1 -0
- package/dist/runtime/server/internal/cookie-headers.js +1 -1
- package/dist/runtime/server/internal/{nitro3.d.ts → portable.d.ts} +3 -4
- package/dist/runtime/server/internal/{nitro3.js → portable.js} +8 -8
- package/dist/runtime/server/middleware/route-access.js +2 -0
- package/dist/runtime/server/plugins/auth-secret.d.ts +7 -0
- package/dist/runtime/server/plugins/auth-secret.js +4 -0
- package/dist/runtime/server/tsconfig.json +1 -0
- package/dist/runtime/server/utils/auth.d.ts +6 -0
- package/dist/runtime/server/utils/auth.js +51 -29
- package/dist/runtime/server/utils/session.js +40 -2
- package/dist/runtime/test-utils/e2e.js +4 -2
- package/dist/runtime/test-utils/server/handler.js +1 -1
- package/package.json +13 -13
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { getLayerDirectories, updateTemplates, addServerImports, addServerHandler, addImports, addPlugin, addComponentsDir, addRouteMiddleware, extendPages, addTemplate, hasNuxtModule, addTypeTemplate, defineNuxtModule, createResolver } from '@nuxt/kit';
|
|
3
|
+
import { getLayerDirectories, resolveServerVariant, hasNuxtCompatibility, updateTemplates, addServerImports, addServerHandler, addNitroPlugin, addImports, addPlugin, addComponentsDir, addRouteMiddleware, extendPages, addTemplate, hasNuxtModule, addTypeTemplate, defineNuxtModule, createResolver, getNitroVersion } from '@nuxt/kit';
|
|
4
4
|
import { consola as consola$1 } from 'consola';
|
|
5
5
|
import { defineDiagnostics, Diagnostic, formatDiagnostic } from 'nostics';
|
|
6
6
|
import { isAbsolute, join, resolve, relative, dirname } from 'pathe';
|
|
@@ -13,7 +13,7 @@ import { isCI, isTest } from 'std-env';
|
|
|
13
13
|
import { isInternalAssetPath } from '../dist/runtime/internal/auth-route-rules.js';
|
|
14
14
|
export { defineClientAuth, defineServerAuth } from '../dist/runtime/config.js';
|
|
15
15
|
|
|
16
|
-
const version = "0.3.
|
|
16
|
+
const version = "0.3.8";
|
|
17
17
|
|
|
18
18
|
const CONFIG_EXTENSIONS = [".ts", ".js", ".mts", ".cts", ".mjs", ".cjs"];
|
|
19
19
|
const CONFIG_EXTENSION_RE = /\.[cm]?[jt]s$/;
|
|
@@ -198,13 +198,26 @@ function resolveNitro3RouteRulesTarget(nuxtRootDir) {
|
|
|
198
198
|
return fallback;
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
|
-
function
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
201
|
+
function resolveServerRuntimeShim(resolve) {
|
|
202
|
+
const nitro2 = resolve("./runtime/server/internal/nitro2");
|
|
203
|
+
return resolveServerVariant({
|
|
204
|
+
nuxt: resolve("./runtime/server/internal/portable"),
|
|
205
|
+
nitro2
|
|
206
|
+
}) ?? nitro2;
|
|
207
|
+
}
|
|
208
|
+
function resolveH3TypesPath(nitroMajor) {
|
|
209
|
+
return nitroMajor === 3 ? "nitro/h3" : "h3";
|
|
210
|
+
}
|
|
211
|
+
async function resolveAppSecretShim(resolve) {
|
|
212
|
+
const canDeriveAppSecret = await hasNuxtCompatibility({ nuxt: ">=4.6.0-0" });
|
|
213
|
+
return resolve(`./runtime/server/internal/${canDeriveAppSecret ? "app-secret" : "app-secret-unavailable"}`);
|
|
214
|
+
}
|
|
215
|
+
async function resolveAuthSecretPlugin(resolve) {
|
|
216
|
+
if (!await hasNuxtCompatibility({ nuxt: ">=4.6.0-0" }))
|
|
217
|
+
return;
|
|
218
|
+
if (!resolveServerVariant({ nitro2: true, nitro3: true, nuxt: false }))
|
|
219
|
+
return;
|
|
220
|
+
return resolve("./runtime/server/plugins/auth-secret");
|
|
208
221
|
}
|
|
209
222
|
|
|
210
223
|
function configFilename(file) {
|
|
@@ -286,15 +299,18 @@ function registerTemplateHmrHook(nuxt) {
|
|
|
286
299
|
});
|
|
287
300
|
}
|
|
288
301
|
function registerServerRuntime(input) {
|
|
289
|
-
const { clientOnly, resolve } = input;
|
|
302
|
+
const { authSecretPlugin, clientOnly, resolve } = input;
|
|
290
303
|
if (!clientOnly) {
|
|
291
304
|
addServerImports([
|
|
292
305
|
{ name: "defineServerAuth", from: resolve("./runtime/config") },
|
|
293
306
|
{ name: "serverAuth", from: resolve("./runtime/server/utils/auth") },
|
|
307
|
+
{ name: "ensureServerAuth", from: resolve("./runtime/server/utils/auth") },
|
|
294
308
|
...["getRequestSession", "getUserSession", "setRequestSession", "refreshSessionCookieCache", "setSessionCookie", "createSession", "requireUserSession"].map((name) => ({ name, from: resolve("./runtime/server/utils/session") }))
|
|
295
309
|
]);
|
|
296
310
|
addServerHandler({ middleware: true, handler: resolve("./runtime/server/middleware/route-access") });
|
|
297
311
|
addServerHandler({ route: "/api/auth/**", handler: resolve("./runtime/server/api/auth/[...all]") });
|
|
312
|
+
if (authSecretPlugin)
|
|
313
|
+
addNitroPlugin({ nitro2: authSecretPlugin, nitro3: authSecretPlugin });
|
|
298
314
|
}
|
|
299
315
|
addImports([
|
|
300
316
|
"runWithSessionRefresh",
|
|
@@ -607,6 +623,18 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola, h
|
|
|
607
623
|
const hubCasing = getHubCasing(hub);
|
|
608
624
|
const schemaOptions = { ...options.schema, useUuid: userConfig.advanced?.database?.generateId === "uuid", casing: options.schema?.casing ?? hubCasing };
|
|
609
625
|
const schemaCode = await generateDrizzleSchema(authOptions, dialect, schemaOptions);
|
|
626
|
+
const { getAuthTables } = await import('better-auth/db');
|
|
627
|
+
const { initGetModelName } = await import('better-auth/adapters');
|
|
628
|
+
const authTables = getAuthTables(authOptions);
|
|
629
|
+
const getModelName = initGetModelName({
|
|
630
|
+
schema: authTables,
|
|
631
|
+
usePlural: schemaOptions.usePlural ?? false
|
|
632
|
+
});
|
|
633
|
+
const modelNames = {};
|
|
634
|
+
for (const name of ["user", "session", "account", "verification"]) {
|
|
635
|
+
if (authTables[name])
|
|
636
|
+
modelNames[name] = getModelName(name);
|
|
637
|
+
}
|
|
610
638
|
const schemaDir = join(nuxt.options.buildDir, "better-auth");
|
|
611
639
|
const schemaPathTs = join(schemaDir, `schema.${dialect}.ts`);
|
|
612
640
|
const schemaPathMjs = join(schemaDir, `schema.${dialect}.mjs`);
|
|
@@ -622,6 +650,7 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola, h
|
|
|
622
650
|
addTemplate({ filename: `better-auth/schema.${dialect}.ts`, getContents: () => schemaCode, write: true });
|
|
623
651
|
addTemplate({ filename: `better-auth/schema.${dialect}.mjs`, getContents: () => schemaCode, write: true });
|
|
624
652
|
consola.info(`Generated ${dialect} schema (.ts + .mjs)`);
|
|
653
|
+
return modelNames;
|
|
625
654
|
} catch (error) {
|
|
626
655
|
throw error instanceof Diagnostic ? error : diagnostics.NUXT_AUTH_SCHEMA_GENERATION_FAILED({ cause: error });
|
|
627
656
|
}
|
|
@@ -630,45 +659,62 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola, h
|
|
|
630
659
|
const DEFAULT_SECRET_ENV = "NUXT_BETTER_AUTH_SECRET";
|
|
631
660
|
const FALLBACK_SECRET_ENV = "BETTER_AUTH_SECRET";
|
|
632
661
|
const VERSIONED_SECRET_ENV = "BETTER_AUTH_SECRETS";
|
|
662
|
+
const APP_SECRET_ENV = "NUXT_APP_SECRET";
|
|
633
663
|
const generateSecret = () => randomBytes(32).toString("hex");
|
|
634
664
|
function readEnvFile(rootDir) {
|
|
635
665
|
const envPath = join(rootDir, ".env");
|
|
636
666
|
return existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
|
|
637
667
|
}
|
|
638
|
-
function hasEnvSecret(rootDir) {
|
|
668
|
+
function hasEnvSecret(rootDir, names) {
|
|
639
669
|
const envFile = readEnvFile(rootDir);
|
|
640
|
-
return
|
|
670
|
+
return names.some((name) => {
|
|
641
671
|
const match = envFile.match(new RegExp(`^${name}=(.+)$`, "m"));
|
|
642
672
|
return !!match && !!match[1] && match[1].trim().length > 0;
|
|
643
673
|
});
|
|
644
674
|
}
|
|
645
|
-
function appendSecretToEnv(rootDir, secret) {
|
|
675
|
+
function appendSecretToEnv(rootDir, name, secret) {
|
|
646
676
|
const envPath = join(rootDir, ".env");
|
|
647
677
|
let content = readEnvFile(rootDir);
|
|
648
678
|
if (content.length > 0 && !content.endsWith("\n"))
|
|
649
679
|
content += "\n";
|
|
650
|
-
content += `${
|
|
680
|
+
content += `${name}=${secret}
|
|
651
681
|
`;
|
|
652
682
|
writeFileSync(envPath, content, "utf-8");
|
|
653
683
|
}
|
|
684
|
+
function getSecretEnvName(appSecret) {
|
|
685
|
+
return appSecret ? APP_SECRET_ENV : DEFAULT_SECRET_ENV;
|
|
686
|
+
}
|
|
687
|
+
function applyPromptedAppSecret(secret) {
|
|
688
|
+
process.env[APP_SECRET_ENV] = secret;
|
|
689
|
+
delete process.env.NUXT_APP_SECRET_GENERATED;
|
|
690
|
+
}
|
|
654
691
|
async function promptForSecret(rootDir, consola, options = {}) {
|
|
655
692
|
const configuredSecret = options.configuredSecret?.trim();
|
|
656
693
|
if (configuredSecret)
|
|
657
694
|
return void 0;
|
|
658
|
-
if (process.env.NUXT_BETTER_AUTH_SECRET || process.env.BETTER_AUTH_SECRET || process.env.BETTER_AUTH_SECRETS
|
|
695
|
+
if (process.env.NUXT_BETTER_AUTH_SECRET || process.env.BETTER_AUTH_SECRET || process.env.BETTER_AUTH_SECRETS)
|
|
696
|
+
return void 0;
|
|
697
|
+
const secretEnvNames = [DEFAULT_SECRET_ENV, FALLBACK_SECRET_ENV, VERSIONED_SECRET_ENV];
|
|
698
|
+
if (options.appSecret)
|
|
699
|
+
secretEnvNames.push(APP_SECRET_ENV);
|
|
700
|
+
if (hasEnvSecret(rootDir, secretEnvNames))
|
|
701
|
+
return void 0;
|
|
702
|
+
const generatedAppSecret = process.env.NUXT_APP_SECRET_GENERATED === "1";
|
|
703
|
+
if (options.appSecret && !generatedAppSecret && (process.env.NUXT_APP_SECRET || options.configuredAppSecret?.trim()))
|
|
659
704
|
return void 0;
|
|
660
705
|
const hasTty = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
661
706
|
if (options.prepare || !hasTty) {
|
|
662
|
-
consola.warn("[nuxt-better-auth] Skipping auth secret prompt (non-interactive). Set NUXT_BETTER_AUTH_SECRET, BETTER_AUTH_SECRET, or BETTER_AUTH_SECRETS.");
|
|
707
|
+
consola.warn(options.appSecret ? "[nuxt-better-auth] Skipping auth secret prompt (non-interactive). Set NUXT_APP_SECRET, or NUXT_BETTER_AUTH_SECRET, BETTER_AUTH_SECRET, or BETTER_AUTH_SECRETS." : "[nuxt-better-auth] Skipping auth secret prompt (non-interactive). Set NUXT_BETTER_AUTH_SECRET, BETTER_AUTH_SECRET, or BETTER_AUTH_SECRETS.");
|
|
663
708
|
return void 0;
|
|
664
709
|
}
|
|
710
|
+
const envName = getSecretEnvName(options.appSecret);
|
|
665
711
|
if (isCI || isTest) {
|
|
666
712
|
const secret2 = generateSecret();
|
|
667
|
-
appendSecretToEnv(rootDir, secret2);
|
|
668
|
-
consola.info(
|
|
713
|
+
appendSecretToEnv(rootDir, envName, secret2);
|
|
714
|
+
consola.info(`Generated ${envName} and added to .env (CI/test mode)`);
|
|
669
715
|
return secret2;
|
|
670
716
|
}
|
|
671
|
-
consola.box("An auth secret is required for authentication.\nThis will add NUXT_BETTER_AUTH_SECRET to your .env file.\nBETTER_AUTH_SECRET and BETTER_AUTH_SECRETS are also supported.");
|
|
717
|
+
consola.box(options.appSecret ? "An auth secret is required for authentication.\nThis will add NUXT_APP_SECRET to your .env file. The auth secret is derived from it.\nNUXT_BETTER_AUTH_SECRET, BETTER_AUTH_SECRET and BETTER_AUTH_SECRETS are also supported." : "An auth secret is required for authentication.\nThis will add NUXT_BETTER_AUTH_SECRET to your .env file.\nBETTER_AUTH_SECRET and BETTER_AUTH_SECRETS are also supported.");
|
|
672
718
|
const choice = await consola.prompt("How do you want to set it?", {
|
|
673
719
|
type: "select",
|
|
674
720
|
options: [
|
|
@@ -695,14 +741,14 @@ async function promptForSecret(rootDir, consola, options = {}) {
|
|
|
695
741
|
}
|
|
696
742
|
const preview = `${secret.slice(0, 8)}...${secret.slice(-4)}`;
|
|
697
743
|
const confirm = await consola.prompt(`Add to .env:
|
|
698
|
-
${
|
|
744
|
+
${envName}=${preview}
|
|
699
745
|
Proceed?`, { type: "confirm", initial: true, cancel: "null" });
|
|
700
746
|
if (typeof confirm === "symbol" || !confirm) {
|
|
701
747
|
consola.info("Cancelled. Secret not written.");
|
|
702
748
|
return void 0;
|
|
703
749
|
}
|
|
704
|
-
appendSecretToEnv(rootDir, secret);
|
|
705
|
-
consola.success(
|
|
750
|
+
appendSecretToEnv(rootDir, envName, secret);
|
|
751
|
+
consola.success(`Added ${envName} to .env`);
|
|
706
752
|
return secret;
|
|
707
753
|
}
|
|
708
754
|
|
|
@@ -755,7 +801,7 @@ function setupRuntimeConfig(input) {
|
|
|
755
801
|
if (clientOnly) {
|
|
756
802
|
const siteUrl = nuxt.options.runtimeConfig.public.siteUrl;
|
|
757
803
|
if (!siteUrl)
|
|
758
|
-
consola.warn("clientOnly mode: set runtimeConfig.public.siteUrl (or NUXT_PUBLIC_SITE_URL) to your
|
|
804
|
+
consola.warn("clientOnly mode: set runtimeConfig.public.siteUrl (or NUXT_PUBLIC_SITE_URL) to your auth backend URL, or set baseURL in defineClientAuth()");
|
|
759
805
|
consola.info("clientOnly mode enabled - server utilities (serverAuth, getRequestSession, getUserSession, requireUserSession) are not available");
|
|
760
806
|
return { secondaryStorageEnabled };
|
|
761
807
|
}
|
|
@@ -914,7 +960,7 @@ export { db }`;
|
|
|
914
960
|
return `export function createDatabase() { return undefined }
|
|
915
961
|
export const db = undefined`;
|
|
916
962
|
}
|
|
917
|
-
function buildSchemaExportCode(hasHubDb, hubDialect) {
|
|
963
|
+
function buildSchemaExportCode(hasHubDb, hubDialect, modelNames = {}) {
|
|
918
964
|
if (!hasHubDb) {
|
|
919
965
|
return `export const user = undefined
|
|
920
966
|
export const session = undefined
|
|
@@ -927,14 +973,14 @@ export const schema = undefined
|
|
|
927
973
|
import * as generatedSchema from './schema.${hubDialect}.mjs'
|
|
928
974
|
|
|
929
975
|
const getGeneratedTable = name => generatedSchema[name] ?? generatedSchema[\`\${name}s\`]
|
|
930
|
-
export const user = getGeneratedTable(
|
|
931
|
-
export const session = getGeneratedTable(
|
|
932
|
-
export const account = getGeneratedTable(
|
|
933
|
-
export const verification = getGeneratedTable(
|
|
976
|
+
export const user = getGeneratedTable(${JSON.stringify(modelNames.user ?? "user")})
|
|
977
|
+
export const session = getGeneratedTable(${JSON.stringify(modelNames.session ?? "session")})
|
|
978
|
+
export const account = getGeneratedTable(${JSON.stringify(modelNames.account ?? "account")})
|
|
979
|
+
export const verification = getGeneratedTable(${JSON.stringify(modelNames.verification ?? "verification")})
|
|
934
980
|
export const schema = { ...generatedSchema, user, session, account, verification }
|
|
935
981
|
`;
|
|
936
982
|
}
|
|
937
|
-
function buildSchemaExportTypes(hasHubDb, hubDialect) {
|
|
983
|
+
function buildSchemaExportTypes(hasHubDb, hubDialect, modelNames = {}) {
|
|
938
984
|
if (!hasHubDb) {
|
|
939
985
|
return `export declare const user: undefined
|
|
940
986
|
export declare const session: undefined
|
|
@@ -952,10 +998,10 @@ type GeneratedTable<Name extends string> = Name extends keyof GeneratedSchema
|
|
|
952
998
|
? GeneratedSchema[\`\${Name}s\`]
|
|
953
999
|
: undefined
|
|
954
1000
|
|
|
955
|
-
export declare const user: GeneratedTable
|
|
956
|
-
export declare const session: GeneratedTable
|
|
957
|
-
export declare const account: GeneratedTable
|
|
958
|
-
export declare const verification: GeneratedTable
|
|
1001
|
+
export declare const user: GeneratedTable<${JSON.stringify(modelNames.user ?? "user")}>
|
|
1002
|
+
export declare const session: GeneratedTable<${JSON.stringify(modelNames.session ?? "session")}>
|
|
1003
|
+
export declare const account: GeneratedTable<${JSON.stringify(modelNames.account ?? "account")}>
|
|
1004
|
+
export declare const verification: GeneratedTable<${JSON.stringify(modelNames.verification ?? "verification")}>
|
|
959
1005
|
export declare const schema: GeneratedSchema & {
|
|
960
1006
|
user: typeof user
|
|
961
1007
|
session: typeof session
|
|
@@ -1632,16 +1678,25 @@ const module$1 = defineNuxtModule({
|
|
|
1632
1678
|
},
|
|
1633
1679
|
async onInstall(nuxt) {
|
|
1634
1680
|
const configuredSecret = nuxt.options.runtimeConfig?.betterAuthSecret;
|
|
1635
|
-
const
|
|
1636
|
-
|
|
1681
|
+
const appSecret = await hasNuxtCompatibility({ nuxt: ">=4.6.0-0" }, nuxt);
|
|
1682
|
+
const generatedSecret = await promptForSecret(nuxt.options.rootDir, consola, {
|
|
1683
|
+
configuredSecret,
|
|
1684
|
+
prepare: Boolean(nuxt.options._prepare),
|
|
1685
|
+
appSecret,
|
|
1686
|
+
configuredAppSecret: nuxt.options.runtimeConfig?.appSecret
|
|
1687
|
+
});
|
|
1688
|
+
if (generatedSecret && appSecret)
|
|
1689
|
+
applyPromptedAppSecret(generatedSecret);
|
|
1690
|
+
else if (generatedSecret)
|
|
1637
1691
|
process.env.NUXT_BETTER_AUTH_SECRET = generatedSecret;
|
|
1638
1692
|
await createDefaultAuthConfigFiles(nuxt);
|
|
1639
1693
|
},
|
|
1640
1694
|
setup(options, nuxt) {
|
|
1641
1695
|
const finishSetup = async () => {
|
|
1642
1696
|
const resolver = createResolver(import.meta.url);
|
|
1643
|
-
const
|
|
1644
|
-
nuxt.options.alias["#better-auth/nitro-compat"] = resolver.resolve
|
|
1697
|
+
const h3TypesPath = resolveH3TypesPath(getNitroVersion(nuxt));
|
|
1698
|
+
nuxt.options.alias["#better-auth/nitro-compat"] = resolveServerRuntimeShim(resolver.resolve);
|
|
1699
|
+
nuxt.options.alias["#better-auth/app-secret"] = await resolveAppSecretShim(resolver.resolve);
|
|
1645
1700
|
const registeredPluginSources = {};
|
|
1646
1701
|
await nuxt.callHook("better-auth:plugins:extend", registeredPluginSources);
|
|
1647
1702
|
for (const source of [...registeredPluginSources.server || [], ...registeredPluginSources.client || []]) {
|
|
@@ -1657,6 +1712,7 @@ const module$1 = defineNuxtModule({
|
|
|
1657
1712
|
});
|
|
1658
1713
|
let serverConfigPath = setup.aliases["#auth/server"];
|
|
1659
1714
|
let clientConfigPath = setup.aliases["#auth/client"];
|
|
1715
|
+
let schemaModelNames;
|
|
1660
1716
|
if (serverConfigPath && setup.pluginSources.server.length) {
|
|
1661
1717
|
const serverConfigCode = buildExtendedServerAuthCode(setup.configs.server.path, setup.pluginSources.server);
|
|
1662
1718
|
serverConfigPath = addTemplate({
|
|
@@ -1686,13 +1742,13 @@ const module$1 = defineNuxtModule({
|
|
|
1686
1742
|
nuxt.options.alias["#auth/database"] = databaseTemplate.dst;
|
|
1687
1743
|
const schemaTemplate = addTemplate({
|
|
1688
1744
|
filename: "better-auth/schema.mjs",
|
|
1689
|
-
getContents: () => buildSchemaExportCode(setup.database.hasHubDb, setup.database.buildContext?.hubDialect ?? "sqlite"),
|
|
1745
|
+
getContents: () => buildSchemaExportCode(setup.database.hasHubDb, setup.database.buildContext?.hubDialect ?? "sqlite", schemaModelNames),
|
|
1690
1746
|
write: true
|
|
1691
1747
|
});
|
|
1692
1748
|
nuxt.options.alias["#auth/schema"] = schemaTemplate.dst;
|
|
1693
1749
|
addTemplate({
|
|
1694
1750
|
filename: "better-auth/schema.d.ts",
|
|
1695
|
-
getContents: () => buildSchemaExportTypes(setup.database.hasHubDb, setup.database.buildContext?.hubDialect ?? "sqlite"),
|
|
1751
|
+
getContents: () => buildSchemaExportTypes(setup.database.hasHubDb, setup.database.buildContext?.hubDialect ?? "sqlite", schemaModelNames),
|
|
1696
1752
|
write: true
|
|
1697
1753
|
});
|
|
1698
1754
|
if (setup.schemaGeneration)
|
|
@@ -1726,14 +1782,14 @@ const module$1 = defineNuxtModule({
|
|
|
1726
1782
|
hasHubDb: setup.serverTypes.hasHubDb,
|
|
1727
1783
|
runtimeTypesPath: resolver.resolve("./runtime/types"),
|
|
1728
1784
|
sharedServerConfigSafe: [setup.configs.server.path, ...setup.pluginSources.server].every(isServerConfigSharedTypeSafe),
|
|
1729
|
-
h3TypesPath
|
|
1730
|
-
nitro3RouteRulesTarget:
|
|
1785
|
+
h3TypesPath,
|
|
1786
|
+
nitro3RouteRulesTarget: h3TypesPath === "nitro/h3" ? resolveNitro3RouteRulesTarget(nuxt.options.rootDir) : void 0
|
|
1731
1787
|
});
|
|
1732
1788
|
}
|
|
1733
1789
|
if (setup.schemaGeneration) {
|
|
1734
1790
|
if (setup.schemaGeneration.externalizeNuxtHubDatabase)
|
|
1735
1791
|
registerNuxtHubDatabaseExternalHook(nuxt);
|
|
1736
|
-
await setupBetterAuthSchema(
|
|
1792
|
+
schemaModelNames = await setupBetterAuthSchema(
|
|
1737
1793
|
nuxt,
|
|
1738
1794
|
serverConfigPath,
|
|
1739
1795
|
options,
|
|
@@ -1745,11 +1801,15 @@ const module$1 = defineNuxtModule({
|
|
|
1745
1801
|
runtimeTypesAugmentPath: setup.sharedTypes.runtimeTypesAugmentPath,
|
|
1746
1802
|
runtimeTypesPath: resolver.resolve("./runtime/types"),
|
|
1747
1803
|
clientConfigPath,
|
|
1748
|
-
h3TypesPath
|
|
1804
|
+
h3TypesPath,
|
|
1749
1805
|
clientOnly: setup.clientOnly
|
|
1750
1806
|
});
|
|
1751
1807
|
registerTemplateHmrHook(nuxt);
|
|
1752
|
-
registerServerRuntime({
|
|
1808
|
+
registerServerRuntime({
|
|
1809
|
+
clientOnly: setup.clientOnly,
|
|
1810
|
+
resolve: resolver.resolve,
|
|
1811
|
+
authSecretPlugin: setup.clientOnly ? void 0 : await resolveAuthSecretPlugin(resolver.resolve)
|
|
1812
|
+
});
|
|
1753
1813
|
registerAuthMiddleware(resolver.resolve);
|
|
1754
1814
|
await registerDevtools({ nuxt, clientOnly: setup.clientOnly, hasHubDb: setup.database.hasHubDb, resolve: resolver.resolve });
|
|
1755
1815
|
registerRouteRulesMetaHook(nuxt);
|
|
@@ -11,14 +11,30 @@ export function useAuthRequestFetch() {
|
|
|
11
11
|
const clientOptions = createAppAuthClient.resolveOptions(siteUrl);
|
|
12
12
|
const configuredBaseURL = clientOptions.baseURL ?? siteUrl;
|
|
13
13
|
const baseURL = configuredBaseURL && new URL(configuredBaseURL).pathname.replace(/\/+$/, "") ? configuredBaseURL : joinURL(configuredBaseURL || "/", clientOptions.basePath ?? "/api/auth");
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
function wrapRequestFetch(fetch) {
|
|
15
|
+
const methods = /* @__PURE__ */ new Map();
|
|
16
|
+
return new Proxy(fetch, {
|
|
17
|
+
apply(target, thisArg, args) {
|
|
18
|
+
const [request, opts] = args;
|
|
19
|
+
if (typeof request !== "string" || !/^\/api\/auth(?=\/|$)/.test(request))
|
|
20
|
+
return Reflect.apply(target, thisArg, args);
|
|
21
|
+
return Reflect.apply(target, thisArg, [request.slice("/api/auth".length), {
|
|
22
|
+
...opts,
|
|
23
|
+
baseURL,
|
|
24
|
+
credentials: "include"
|
|
25
|
+
}]);
|
|
26
|
+
},
|
|
27
|
+
get(target, prop, receiver) {
|
|
28
|
+
if (methods.has(prop))
|
|
29
|
+
return methods.get(prop);
|
|
30
|
+
const value = Reflect.get(target, prop, receiver);
|
|
31
|
+
if (typeof value !== "function" || prop !== "raw" && prop !== "create")
|
|
32
|
+
return value;
|
|
33
|
+
const method = prop === "raw" ? wrapRequestFetch(value) : (...args) => wrapRequestFetch(Reflect.apply(value, target, args));
|
|
34
|
+
methods.set(prop, method);
|
|
35
|
+
return method;
|
|
36
|
+
}
|
|
22
37
|
});
|
|
23
|
-
}
|
|
38
|
+
}
|
|
39
|
+
return wrapRequestFetch(requestFetch);
|
|
24
40
|
}
|
|
@@ -74,6 +74,7 @@ export function useUserSession() {
|
|
|
74
74
|
return;
|
|
75
75
|
const previousUser = user.value;
|
|
76
76
|
user.value = { ...user.value, ...updates };
|
|
77
|
+
const optimisticUser = user.value;
|
|
77
78
|
if (!rawClient)
|
|
78
79
|
return;
|
|
79
80
|
try {
|
|
@@ -86,7 +87,8 @@ export function useUserSession() {
|
|
|
86
87
|
throw new Error(normalizedError.message);
|
|
87
88
|
}
|
|
88
89
|
} catch (error) {
|
|
89
|
-
user.value
|
|
90
|
+
if (user.value === optimisticUser)
|
|
91
|
+
user.value = previousUser;
|
|
90
92
|
if (!(error instanceof Error)) {
|
|
91
93
|
const normalizedError = normalizeAuthActionError(error);
|
|
92
94
|
throw new Error(normalizedError.message);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defu } from "defu";
|
|
2
2
|
import { createRouter, toRouteMatcher } from "radix3";
|
|
3
|
+
import { parsePath, parseQuery } from "ufo";
|
|
3
4
|
import { createError, defineNuxtRouteMiddleware, getRouteRules, navigateTo, useNuxtApp, useRequestHeaders, useRuntimeConfig } from "#imports";
|
|
4
5
|
import { shouldSkipAuthRouteRules } from "../../internal/auth-route-rules.js";
|
|
5
6
|
import { matchesUser } from "../../utils/match-user.js";
|
|
@@ -64,11 +65,11 @@ function resolveLoginRedirect(input) {
|
|
|
64
65
|
return { to: loginTarget, external: false };
|
|
65
66
|
if (!loginTarget.startsWith("/") || loginTarget.startsWith("//"))
|
|
66
67
|
return { to: loginTarget, external: false };
|
|
67
|
-
const
|
|
68
|
-
const
|
|
68
|
+
const { pathname: path, search, hash } = parsePath(loginTarget);
|
|
69
|
+
const query = search.slice(1);
|
|
69
70
|
try {
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
71
|
+
const params = new URLSearchParams(query);
|
|
72
|
+
if (params.has(redirectQueryKey))
|
|
72
73
|
return { to: loginTarget, external: false };
|
|
73
74
|
} catch {
|
|
74
75
|
return { to: loginTarget, external: false };
|
|
@@ -76,15 +77,13 @@ function resolveLoginRedirect(input) {
|
|
|
76
77
|
if (import.meta.server) {
|
|
77
78
|
const separator = query ? "&" : "";
|
|
78
79
|
const encodedRedirect = encodeURIComponent(route.fullPath);
|
|
79
|
-
const
|
|
80
|
+
const encodedKey = encodeURIComponent(redirectQueryKey);
|
|
81
|
+
const url = `${path}?${query}${separator}${encodedKey}=${encodedRedirect}${hash}`;
|
|
80
82
|
return { to: url, external: true };
|
|
81
83
|
}
|
|
82
|
-
const
|
|
83
|
-
const queryObj = {};
|
|
84
|
-
for (const [k, v] of params.entries())
|
|
85
|
-
queryObj[k] = v;
|
|
84
|
+
const queryObj = parseQuery(query);
|
|
86
85
|
queryObj[redirectQueryKey] = route.fullPath;
|
|
87
|
-
return { to: { path, query: queryObj, ...hash ? { hash
|
|
86
|
+
return { to: { path, query: queryObj, ...hash ? { hash } : {} }, external: false };
|
|
88
87
|
}
|
|
89
88
|
async function getAuthRouteRules() {
|
|
90
89
|
if (!authRouteRulesPromise) {
|
|
@@ -21,7 +21,14 @@ export default defineEventHandler(async (event) => {
|
|
|
21
21
|
dbQuery.orderBy(desc(schema.account.createdAt)).limit(limit).offset(offset),
|
|
22
22
|
countQuery
|
|
23
23
|
]);
|
|
24
|
-
|
|
24
|
+
const safeAccounts = accounts.map((account) => ({
|
|
25
|
+
id: account.id,
|
|
26
|
+
userId: account.userId,
|
|
27
|
+
providerId: account.providerId,
|
|
28
|
+
accountId: account.accountId,
|
|
29
|
+
createdAt: account.createdAt
|
|
30
|
+
}));
|
|
31
|
+
return { accounts: safeAccounts, total: totalResult[0]?.count ?? 0, page, limit };
|
|
25
32
|
} catch (error) {
|
|
26
33
|
console.error("[DevTools] Fetch accounts failed:", error);
|
|
27
34
|
return { accounts: [], total: 0, error: "Failed to fetch accounts" };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { prepareAuthSecret } from "../../internal/auth-secret.js";
|
|
1
2
|
import { defineEventHandler, toWebRequest } from "../../internal/nitro-compat.js";
|
|
2
3
|
import { serverAuth } from "../../utils/auth.js";
|
|
3
4
|
export default defineEventHandler(async (event) => {
|
|
5
|
+
await prepareAuthSecret();
|
|
4
6
|
const auth = serverAuth(event);
|
|
5
7
|
return auth.handler(toWebRequest(event));
|
|
6
8
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '#better-auth/app-secret';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "#better-auth/app-secret";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const deriveAuthSecret = void 0;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** The `deriveSecret()` purpose for the Better Auth secret. Changing it rotates every derived secret. */
|
|
2
|
+
export declare const AUTH_SECRET_PURPOSE = "better-auth:secret";
|
|
3
|
+
/** Derives the Better Auth secret from Nuxt's `appSecret` (`NUXT_APP_SECRET`), on Nuxt 4.6+. */
|
|
4
|
+
export declare const deriveAuthSecret: (() => Promise<string>) | undefined;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** The singular secret configured for the module: runtime config (`NUXT_BETTER_AUTH_SECRET`) or `BETTER_AUTH_SECRET`. */
|
|
2
|
+
export declare function getConfiguredAuthSecret(runtimeConfig: object): string;
|
|
3
|
+
/** Whether a secret source that predates `appSecret` derivation is configured. These always win. */
|
|
4
|
+
export declare function hasExplicitAuthSecret(runtimeConfig: object): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Whether the auth secret is derived from `appSecret`: Nuxt provides `deriveSecret()` (4.6+) and
|
|
7
|
+
* the `appSecret` is the user's. In development Nuxt generates an `appSecret` when none is set
|
|
8
|
+
* (`NUXT_APP_SECRET_GENERATED`); deriving from that would replace Better Auth's development
|
|
9
|
+
* default secret and sign out existing development sessions, so the default is kept.
|
|
10
|
+
*/
|
|
11
|
+
export declare function canDeriveAuthSecret(): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Derives the Better Auth secret from Nuxt's `appSecret` once per server process, when no auth
|
|
14
|
+
* secret is configured and the `appSecret` is the user's (see `canDeriveAuthSecret`).
|
|
15
|
+
* `serverAuth()` is synchronous, so request entry points and the startup plugin start this, and
|
|
16
|
+
* the request entry points await it before they create the auth instance.
|
|
17
|
+
*/
|
|
18
|
+
export declare function prepareAuthSecret(): Promise<void> | undefined;
|
|
19
|
+
export interface DerivedAuthSecretState {
|
|
20
|
+
/** The secret is derived from the user's `appSecret` (Nuxt 4.6+). */
|
|
21
|
+
available: boolean;
|
|
22
|
+
secret?: string;
|
|
23
|
+
error?: unknown;
|
|
24
|
+
}
|
|
25
|
+
export declare function getDerivedAuthSecret(): DerivedAuthSecretState;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { env } from "better-auth";
|
|
2
|
+
import { deriveAuthSecret } from "./app-secret-compat.js";
|
|
3
|
+
import { useRuntimeConfig } from "./nitro-compat.js";
|
|
4
|
+
let derivedSecret;
|
|
5
|
+
let derivedSecretPromise;
|
|
6
|
+
let derivedSecretError;
|
|
7
|
+
export function getConfiguredAuthSecret(runtimeConfig) {
|
|
8
|
+
const secret = runtimeConfig.betterAuthSecret;
|
|
9
|
+
return typeof secret === "string" && secret || env.BETTER_AUTH_SECRET || "";
|
|
10
|
+
}
|
|
11
|
+
export function hasExplicitAuthSecret(runtimeConfig) {
|
|
12
|
+
return Boolean(getConfiguredAuthSecret(runtimeConfig) || env.BETTER_AUTH_SECRETS || env.AUTH_SECRET);
|
|
13
|
+
}
|
|
14
|
+
export function canDeriveAuthSecret() {
|
|
15
|
+
if (!deriveAuthSecret)
|
|
16
|
+
return false;
|
|
17
|
+
return !(import.meta.dev && globalThis.process?.env?.NUXT_APP_SECRET_GENERATED === "1");
|
|
18
|
+
}
|
|
19
|
+
export function prepareAuthSecret() {
|
|
20
|
+
if (derivedSecret !== void 0 || derivedSecretError !== void 0 || !canDeriveAuthSecret())
|
|
21
|
+
return;
|
|
22
|
+
if (hasExplicitAuthSecret(useRuntimeConfig()))
|
|
23
|
+
return;
|
|
24
|
+
derivedSecretPromise ||= deriveAuthSecret().then(
|
|
25
|
+
(secret) => {
|
|
26
|
+
derivedSecret = secret;
|
|
27
|
+
},
|
|
28
|
+
(error) => {
|
|
29
|
+
derivedSecretError = error ?? new Error("deriveSecret failed");
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
return derivedSecretPromise;
|
|
33
|
+
}
|
|
34
|
+
export function getDerivedAuthSecret() {
|
|
35
|
+
return {
|
|
36
|
+
available: canDeriveAuthSecret(),
|
|
37
|
+
secret: derivedSecret,
|
|
38
|
+
error: derivedSecretError
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { ServerEvent } from './nitro-compat.js';
|
|
2
2
|
export declare function appendCookieHeader(event: ServerEvent, header: string): void;
|
|
3
|
+
export declare function getSetCookieHeaders(headers: Headers): string[];
|
|
3
4
|
export declare function appendSetCookieHeaders(event: ServerEvent, headers: Headers): void;
|
|
@@ -14,7 +14,7 @@ export function appendCookieHeader(event, header) {
|
|
|
14
14
|
const responseHeaders = eventWithResponse.res?.headers ?? eventWithResponse.response?.headers;
|
|
15
15
|
responseHeaders?.append("set-cookie", header);
|
|
16
16
|
}
|
|
17
|
-
function getSetCookieHeaders(headers) {
|
|
17
|
+
export function getSetCookieHeaders(headers) {
|
|
18
18
|
const cookieHeaders = headers;
|
|
19
19
|
const getSetCookie = cookieHeaders.getSetCookie;
|
|
20
20
|
if (getSetCookie)
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RequestEvent } from 'nuxt/server';
|
|
2
2
|
import type { AuthRouteRules } from '../../types.js';
|
|
3
3
|
import { splitSetCookieString } from 'cookie-es';
|
|
4
|
-
import { defineEventHandler, getQuery, getRequestHost, getRequestProtocol, getRequestURL, readBody } from '
|
|
5
|
-
import { useRuntimeConfig } from 'nitro/runtime-config';
|
|
4
|
+
import { defineEventHandler, getQuery, getRequestHost, getRequestProtocol, getRequestURL, readBody, useRuntimeConfig } from 'nuxt/server';
|
|
6
5
|
export { defineEventHandler, getQuery, getRequestHost, getRequestProtocol, getRequestURL, readBody, useRuntimeConfig, };
|
|
7
|
-
export type ServerEvent =
|
|
6
|
+
export type ServerEvent = RequestEvent;
|
|
8
7
|
export { splitSetCookieString as splitCookiesString };
|
|
9
8
|
export declare function getAuthRouteRules(event: ServerEvent): AuthRouteRules;
|
|
10
9
|
export declare function createAuthError(status: number, statusText: string): Error;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { splitSetCookieString } from "cookie-es";
|
|
2
|
-
import { getRouteRules } from "nitro/app";
|
|
3
2
|
import {
|
|
3
|
+
createError,
|
|
4
4
|
defineEventHandler,
|
|
5
5
|
getQuery,
|
|
6
6
|
getRequestHost,
|
|
7
7
|
getRequestProtocol,
|
|
8
8
|
getRequestURL,
|
|
9
|
-
|
|
10
|
-
readBody
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
getRouteRules,
|
|
10
|
+
readBody,
|
|
11
|
+
useRuntimeConfig
|
|
12
|
+
} from "nuxt/server";
|
|
13
13
|
import { normalizeAuthRouteRule } from "../../internal/auth-route-rules.js";
|
|
14
14
|
export {
|
|
15
15
|
defineEventHandler,
|
|
@@ -22,11 +22,11 @@ export {
|
|
|
22
22
|
};
|
|
23
23
|
export { splitSetCookieString as splitCookiesString };
|
|
24
24
|
export function getAuthRouteRules(event) {
|
|
25
|
-
const
|
|
26
|
-
return { auth: normalizeAuthRouteRule(auth) };
|
|
25
|
+
const rules = getRouteRules(event);
|
|
26
|
+
return { auth: normalizeAuthRouteRule(rules.auth) };
|
|
27
27
|
}
|
|
28
28
|
export function createAuthError(status, statusText) {
|
|
29
|
-
return
|
|
29
|
+
return createError({ status, statusText });
|
|
30
30
|
}
|
|
31
31
|
export function toWebRequest(event) {
|
|
32
32
|
return event.req;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { normalizeAuthRoutePath, shouldSkipAuthRouteRules } from "../../internal/auth-route-rules.js";
|
|
2
2
|
import { matchesUser } from "../../utils/match-user.js";
|
|
3
|
+
import { prepareAuthSecret } from "../internal/auth-secret.js";
|
|
3
4
|
import { createAuthError, defineEventHandler, getAuthRouteRules, getRequestURL, useRuntimeConfig } from "../internal/nitro-compat.js";
|
|
4
5
|
import { getUserSession, requireUserSession } from "../utils/session.js";
|
|
5
6
|
export default defineEventHandler(async (event) => {
|
|
7
|
+
await prepareAuthSecret();
|
|
6
8
|
const path = normalizeAuthRoutePath(
|
|
7
9
|
getRequestURL(event).pathname,
|
|
8
10
|
useRuntimeConfig().app?.baseURL
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starts deriving the auth secret from Nuxt's `appSecret` when the server starts, so a
|
|
3
|
+
* `serverAuth()` call made before the first request, such as from a task, can use it.
|
|
4
|
+
* Nitro does not await plugins: startup code that needs the secret right away awaits
|
|
5
|
+
* `ensureServerAuth()` instead.
|
|
6
|
+
*/
|
|
7
|
+
export default function authSecretPlugin(): void;
|
|
@@ -16,6 +16,12 @@ type ResolvedAuthOptions = Omit<UserAuthConfig, 'secret' | 'baseURL' | 'trustedO
|
|
|
16
16
|
database?: BetterAuthOptions['database'];
|
|
17
17
|
};
|
|
18
18
|
type AuthInstance = ReturnType<typeof betterAuth<ResolvedAuthOptions>>;
|
|
19
|
+
/**
|
|
20
|
+
* Returns the Better Auth instance like `serverAuth()`, after waiting for the auth secret that
|
|
21
|
+
* Nuxt 4.6+ derives from `appSecret` (`NUXT_APP_SECRET`). Use it in code that can run before the
|
|
22
|
+
* first request, such as a Nitro plugin or task at server startup.
|
|
23
|
+
*/
|
|
24
|
+
export declare function ensureServerAuth(event?: ServerEvent): Promise<AuthInstance>;
|
|
19
25
|
/** Returns Better Auth instance. Caches per resolved host (or single instance when siteUrl is explicit). */
|
|
20
26
|
export declare function serverAuth(event?: ServerEvent): AuthInstance;
|
|
21
27
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { betterAuth, env } from "better-auth";
|
|
2
|
-
import { withoutProtocol } from "ufo";
|
|
3
2
|
import { createDatabase, db } from "#auth/database";
|
|
4
3
|
import createServerAuth from "#auth/server";
|
|
4
|
+
import { getConfiguredAuthSecret, getDerivedAuthSecret, hasExplicitAuthSecret, prepareAuthSecret } from "../internal/auth-secret.js";
|
|
5
5
|
import { getRequestHost, getRequestProtocol, useRuntimeConfig } from "../internal/nitro-compat.js";
|
|
6
6
|
import { resolveAuthBaseURL } from "../../internal/auth-base-url.js";
|
|
7
7
|
import { resolveCustomSecondaryStorageRequirement } from "./custom-secondary-storage.js";
|
|
@@ -11,6 +11,7 @@ let _baseURLInferenceLogged = false;
|
|
|
11
11
|
let _customSecondaryStorageMisconfigWarned = false;
|
|
12
12
|
let _unsupportedHubSecondaryStorageWarned = false;
|
|
13
13
|
let _secondaryStorageRateLimitFallbackWarned = false;
|
|
14
|
+
let _derivedSecretFallbackWarned = false;
|
|
14
15
|
const fallbackRequestAuthContext = /* @__PURE__ */ new WeakMap();
|
|
15
16
|
function getRequestAuthContext(event) {
|
|
16
17
|
const eventWithContext = event;
|
|
@@ -83,33 +84,25 @@ function getNitroOrigin() {
|
|
|
83
84
|
let port;
|
|
84
85
|
if (import.meta.dev)
|
|
85
86
|
port = process.env.NITRO_PORT || process.env.PORT || "3000";
|
|
86
|
-
|
|
87
|
+
const protocol = cert && key || !import.meta.dev ? "https" : "http";
|
|
87
88
|
try {
|
|
88
89
|
if ((import.meta.dev || import.meta.prerender) && process.env.__NUXT_DEV__) {
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
const url = new URL(JSON.parse(process.env.__NUXT_DEV__).proxy.url);
|
|
91
|
+
if (url.protocol === "http:" || url.protocol === "https:")
|
|
92
|
+
return url.origin;
|
|
92
93
|
} else if ((import.meta.dev || import.meta.prerender) && process.env.NUXT_VITE_NODE_OPTIONS) {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
const url = new URL(JSON.parse(process.env.NUXT_VITE_NODE_OPTIONS).baseURL);
|
|
95
|
+
if (url.protocol === "http:" || url.protocol === "https:")
|
|
96
|
+
return url.origin;
|
|
96
97
|
}
|
|
97
98
|
} catch {
|
|
98
99
|
}
|
|
99
100
|
if (!host)
|
|
100
101
|
return void 0;
|
|
101
|
-
if (host.startsWith("[") && host.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (extractedPort)
|
|
106
|
-
port = extractedPort;
|
|
107
|
-
} else if (host.includes(":") && !host.startsWith("[")) {
|
|
108
|
-
const hostParts = host.split(":");
|
|
109
|
-
port = hostParts.pop();
|
|
110
|
-
host = hostParts.join(":");
|
|
111
|
-
}
|
|
112
|
-
const portSuffix = port ? `:${port}` : "";
|
|
102
|
+
if (!host.startsWith("[") && host.indexOf(":") !== host.lastIndexOf(":"))
|
|
103
|
+
host = `[${host}]`;
|
|
104
|
+
const hasExplicitPort = host.startsWith("[") ? host.includes("]:") : host.includes(":");
|
|
105
|
+
const portSuffix = !hasExplicitPort && port ? `:${port}` : "";
|
|
113
106
|
return `${protocol}://${host}${portSuffix}`;
|
|
114
107
|
}
|
|
115
108
|
function resolveEnvironmentOrigin() {
|
|
@@ -162,8 +155,7 @@ function getDevTrustedOrigins() {
|
|
|
162
155
|
try {
|
|
163
156
|
const url = new URL(nitroOrigin);
|
|
164
157
|
const protocol = url.protocol === "https:" ? "https" : "http";
|
|
165
|
-
const
|
|
166
|
-
const localhostOrigin = `${protocol}://localhost:${port}`;
|
|
158
|
+
const localhostOrigin = `${protocol}://localhost${url.port ? `:${url.port}` : ""}`;
|
|
167
159
|
return dedupeOrigins([localhostOrigin, url.origin]);
|
|
168
160
|
} catch {
|
|
169
161
|
return [fallbackOrigin];
|
|
@@ -203,17 +195,39 @@ function withDevTrustedOrigins(trustedOrigins) {
|
|
|
203
195
|
return mergeOrigins([], request);
|
|
204
196
|
};
|
|
205
197
|
}
|
|
198
|
+
function missingAuthSecretMessage(derived) {
|
|
199
|
+
const message = "[nuxt-better-auth] An auth secret is required in production. Set NUXT_BETTER_AUTH_SECRET, BETTER_AUTH_SECRET, BETTER_AUTH_SECRETS, or defineServerAuth({ secrets })";
|
|
200
|
+
if (!derived.available)
|
|
201
|
+
return `${message}.`;
|
|
202
|
+
if (derived.error !== void 0)
|
|
203
|
+
return `${message}, or set NUXT_APP_SECRET (at least 32 characters) to derive one from Nuxt's appSecret.`;
|
|
204
|
+
return `${message}. The secret derived from NUXT_APP_SECRET is resolved asynchronously, and serverAuth() was called before it was ready, outside a request. Use \`await ensureServerAuth()\` in code that runs at server startup.`;
|
|
205
|
+
}
|
|
206
|
+
function warnDerivedSecretFallback(derived) {
|
|
207
|
+
if (_derivedSecretFallbackWarned)
|
|
208
|
+
return;
|
|
209
|
+
_derivedSecretFallbackWarned = true;
|
|
210
|
+
const reason = derived.error !== void 0 ? "could not be derived from NUXT_APP_SECRET. Set NUXT_APP_SECRET (at least 32 characters) or NUXT_BETTER_AUTH_SECRET." : "derived from NUXT_APP_SECRET was not ready yet. Use `await ensureServerAuth()` in code that runs at server startup.";
|
|
211
|
+
console.warn(`[nuxt-better-auth] Better Auth's development default secret is used for this call: the auth secret ${reason}`);
|
|
212
|
+
}
|
|
213
|
+
export async function ensureServerAuth(event) {
|
|
214
|
+
await prepareAuthSecret();
|
|
215
|
+
return serverAuth(event);
|
|
216
|
+
}
|
|
206
217
|
export function serverAuth(event) {
|
|
207
218
|
const runtimeConfig = useRuntimeConfig();
|
|
208
|
-
const
|
|
209
|
-
if (
|
|
219
|
+
const configuredSecret = getConfiguredAuthSecret(runtimeConfig);
|
|
220
|
+
if (configuredSecret && configuredSecret.length < 32)
|
|
210
221
|
throw new Error("[nuxt-better-auth] Singular auth secret must be at least 32 characters for security");
|
|
222
|
+
const derived = getDerivedAuthSecret();
|
|
211
223
|
const requestOrigin = resolveEventOrigin(event);
|
|
212
224
|
let userConfig;
|
|
213
|
-
if (!import.meta.dev && !
|
|
225
|
+
if (!import.meta.dev && !configuredSecret && !env.BETTER_AUTH_SECRETS && !derived.secret) {
|
|
214
226
|
userConfig = createServerAuth({ runtimeConfig, db, requestOrigin });
|
|
215
|
-
if (userConfig.secrets === void 0)
|
|
216
|
-
|
|
227
|
+
if (userConfig.secrets === void 0) {
|
|
228
|
+
void prepareAuthSecret();
|
|
229
|
+
throw new Error(missingAuthSecretMessage(derived));
|
|
230
|
+
}
|
|
217
231
|
}
|
|
218
232
|
const siteUrl = getBaseURL(event);
|
|
219
233
|
const hasExplicitSiteUrl = runtimeConfig.public.siteUrl && typeof runtimeConfig.public.siteUrl === "string";
|
|
@@ -222,6 +236,13 @@ export function serverAuth(event) {
|
|
|
222
236
|
if (requestContext?.[requestAuthKey])
|
|
223
237
|
return requestContext[requestAuthKey];
|
|
224
238
|
userConfig ??= createServerAuth({ runtimeConfig, db, requestOrigin });
|
|
239
|
+
const usesDerivedSecret = derived.available && userConfig.secrets === void 0 && !hasExplicitAuthSecret(runtimeConfig);
|
|
240
|
+
const derivedSecretMissing = usesDerivedSecret && !derived.secret;
|
|
241
|
+
if (derivedSecretMissing) {
|
|
242
|
+
void prepareAuthSecret();
|
|
243
|
+
warnDerivedSecretFallback(derived);
|
|
244
|
+
}
|
|
245
|
+
const betterAuthSecret = configuredSecret || (usesDerivedSecret ? derived.secret ?? "" : "");
|
|
225
246
|
const database = createDatabase(event);
|
|
226
247
|
const trustedOrigins = withDevTrustedOrigins(userConfig.trustedOrigins);
|
|
227
248
|
const hubSecondaryStorage = runtimeConfig.auth?.hubSecondaryStorage;
|
|
@@ -242,7 +263,8 @@ export function serverAuth(event) {
|
|
|
242
263
|
console.warn('[nuxt-better-auth] rateLimit.storage: "secondary-storage" requires secondaryStorage. Falling back to process-local memory. Set rateLimit.storage to "database" or provide rateLimit.customStorage for shared limits.');
|
|
243
264
|
}
|
|
244
265
|
const rateLimit = useMemoryRateLimit ? { ...userConfig.rateLimit, storage: "memory" } : userConfig.rateLimit;
|
|
245
|
-
|
|
266
|
+
const cacheable = !database && !derivedSecretMissing;
|
|
267
|
+
if (cacheable) {
|
|
246
268
|
const cached = _authCache.get(cacheKey);
|
|
247
269
|
if (cached) {
|
|
248
270
|
if (requestContext)
|
|
@@ -261,7 +283,7 @@ export function serverAuth(event) {
|
|
|
261
283
|
const auth = betterAuth(authOptions);
|
|
262
284
|
if (requestContext)
|
|
263
285
|
requestContext[requestAuthKey] = auth;
|
|
264
|
-
if (
|
|
286
|
+
if (cacheable)
|
|
265
287
|
_authCache.set(cacheKey, auth);
|
|
266
288
|
return auth;
|
|
267
289
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { matchesUser } from "../../utils/match-user.js";
|
|
2
2
|
import { createAuthError } from "../internal/nitro-compat.js";
|
|
3
|
-
import {
|
|
3
|
+
import { prepareAuthSecret } from "../internal/auth-secret.js";
|
|
4
|
+
import { appendCookieHeader, appendSetCookieHeaders, getSetCookieHeaders } from "../internal/cookie-headers.js";
|
|
4
5
|
import { serverAuth } from "./auth.js";
|
|
5
6
|
const requestSessionLoadKey = Symbol.for("nuxt-better-auth.requestSessionLoad");
|
|
6
7
|
const requestSessionMutationKey = Symbol.for("nuxt-better-auth.requestSessionMutation");
|
|
@@ -26,6 +27,9 @@ function getRequestHeaders(event) {
|
|
|
26
27
|
return getRequestSessionContext(event).requestHeaders ?? getIncomingRequestHeaders(event);
|
|
27
28
|
}
|
|
28
29
|
async function loadSession(event) {
|
|
30
|
+
const pendingSecret = prepareAuthSecret();
|
|
31
|
+
if (pendingSecret)
|
|
32
|
+
await pendingSecret;
|
|
29
33
|
const auth = serverAuth(event);
|
|
30
34
|
const result = await auth.api.getSession({
|
|
31
35
|
headers: getRequestHeaders(event),
|
|
@@ -36,6 +40,9 @@ async function loadSession(event) {
|
|
|
36
40
|
return { headers: new Headers(), response: result };
|
|
37
41
|
}
|
|
38
42
|
function loadFreshSession(event) {
|
|
43
|
+
const pendingSecret = prepareAuthSecret();
|
|
44
|
+
if (pendingSecret)
|
|
45
|
+
return pendingSecret.then(() => loadFreshSession(event));
|
|
39
46
|
const auth = serverAuth(event);
|
|
40
47
|
return auth.api.getSession({
|
|
41
48
|
headers: getRequestHeaders(event),
|
|
@@ -44,6 +51,9 @@ function loadFreshSession(event) {
|
|
|
44
51
|
});
|
|
45
52
|
}
|
|
46
53
|
function getServerAuthContext(event) {
|
|
54
|
+
const pendingSecret = prepareAuthSecret();
|
|
55
|
+
if (pendingSecret)
|
|
56
|
+
return pendingSecret.then(() => getServerAuthContext(event));
|
|
47
57
|
const auth = serverAuth(event);
|
|
48
58
|
return auth.$context;
|
|
49
59
|
}
|
|
@@ -162,6 +172,30 @@ function expireCookies(event, cookie) {
|
|
|
162
172
|
for (const cookieName of getChunkedCookieNames(event, cookie.name))
|
|
163
173
|
expireCookie(event, cookieName, cookie.attributes);
|
|
164
174
|
}
|
|
175
|
+
function isCookieOrChunk(name, cookieName) {
|
|
176
|
+
if (name === cookieName)
|
|
177
|
+
return true;
|
|
178
|
+
if (!name.startsWith(`${cookieName}.`))
|
|
179
|
+
return false;
|
|
180
|
+
const suffix = name.slice(cookieName.length + 1);
|
|
181
|
+
const index = Number(suffix);
|
|
182
|
+
return Number.isSafeInteger(index) && index >= 0 && String(index) === suffix;
|
|
183
|
+
}
|
|
184
|
+
function expireUnrefreshedSessionCookieCache(event, headers, cookie) {
|
|
185
|
+
const cookieName = getCookieName(cookie.name, cookie.attributes.prefix);
|
|
186
|
+
if (!cookieName)
|
|
187
|
+
return;
|
|
188
|
+
const responseHandlesCache = getSetCookieHeaders(headers).some((header) => {
|
|
189
|
+
const name = header.slice(0, header.indexOf("="));
|
|
190
|
+
return isCookieOrChunk(name, cookieName);
|
|
191
|
+
});
|
|
192
|
+
if (responseHandlesCache)
|
|
193
|
+
return;
|
|
194
|
+
for (const name of parseRequestCookies(getRequestHeaders(event).get("cookie")).keys()) {
|
|
195
|
+
if (isCookieOrChunk(name, cookieName))
|
|
196
|
+
expireCookie(event, name, cookie.attributes);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
165
199
|
function updateRequestHeaders(event, sessionCookie, clearedCookieNames) {
|
|
166
200
|
const requestContext = getRequestSessionContext(event);
|
|
167
201
|
const incomingHeaders = getIncomingRequestHeaders(event);
|
|
@@ -227,10 +261,14 @@ export async function refreshSessionCookieCache(event) {
|
|
|
227
261
|
if (context[requestSessionLoadKey] !== load)
|
|
228
262
|
return context.requestSession ?? null;
|
|
229
263
|
delete context.requestSession;
|
|
230
|
-
const { headers, response } = await
|
|
264
|
+
const [{ headers, response }, authContext] = await Promise.all([
|
|
265
|
+
loadFreshSession(event),
|
|
266
|
+
getServerAuthContext(event)
|
|
267
|
+
]);
|
|
231
268
|
if (context[requestSessionLoadKey] !== load)
|
|
232
269
|
return context.requestSession ?? null;
|
|
233
270
|
appendSetCookieHeaders(event, headers);
|
|
271
|
+
expireUnrefreshedSessionCookieCache(event, headers, authContext.authCookies.sessionData);
|
|
234
272
|
context.requestSession = response;
|
|
235
273
|
return response;
|
|
236
274
|
});
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { url } from "@nuxt/test-utils/e2e";
|
|
3
|
+
import { url, useTestContext } from "@nuxt/test-utils/e2e";
|
|
4
4
|
import { parseJSON } from "better-auth/client";
|
|
5
5
|
import { getRandomPort } from "get-port-please";
|
|
6
6
|
import { defu } from "defu";
|
|
7
|
+
import { joinURL } from "ufo";
|
|
7
8
|
export async function createAuthTestContext(options = {}) {
|
|
8
9
|
if (options.host || options.dev || options.build === false || options.server === false)
|
|
9
10
|
throw new Error("[nuxt-better-auth] Auth E2E helpers require a local test build. Use setup() with build and server enabled, without dev or host.");
|
|
@@ -22,7 +23,8 @@ export async function createAuthTestContext(options = {}) {
|
|
|
22
23
|
}
|
|
23
24
|
}, options);
|
|
24
25
|
async function call(action, data = {}) {
|
|
25
|
-
const
|
|
26
|
+
const baseURL = useTestContext().nuxt?.options.app.baseURL ?? "/";
|
|
27
|
+
const response = await fetch(url(joinURL(baseURL, endpoint)), {
|
|
26
28
|
method: "POST",
|
|
27
29
|
headers: { "content-type": "application/json", "x-nuxt-auth-test": token },
|
|
28
30
|
body: JSON.stringify({ action, data })
|
|
@@ -9,7 +9,7 @@ const requestSchema = z.discriminatedUnion("action", [
|
|
|
9
9
|
]);
|
|
10
10
|
const ownedUsers = /* @__PURE__ */ new Set();
|
|
11
11
|
export default defineEventHandler(async (event) => {
|
|
12
|
-
const token = useRuntimeConfig(
|
|
12
|
+
const token = useRuntimeConfig().betterAuthTestToken;
|
|
13
13
|
if (!token || toWebRequest(event).headers.get("x-nuxt-auth-test") !== token)
|
|
14
14
|
throw createAuthError(404, "Not found");
|
|
15
15
|
const body = requestSchema.parse(parseJSON(await toWebRequest(event).text()));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuxtjs/better-auth",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.8",
|
|
5
5
|
"description": "Nuxt module for Better Auth integration with NuxtHub, route protection, session management, and role-based access",
|
|
6
6
|
"author": "onmax",
|
|
7
7
|
"license": "MIT",
|
|
@@ -73,9 +73,9 @@
|
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
75
|
"@nuxt/devtools-kit": "^3.4.1",
|
|
76
|
-
"@nuxt/kit": "^4.
|
|
76
|
+
"@nuxt/kit": "^4.6.0",
|
|
77
77
|
"@types/node": "^24.13.3",
|
|
78
|
-
"auth": "1.7.
|
|
78
|
+
"auth": "1.7.7",
|
|
79
79
|
"consola": "^3.4.2",
|
|
80
80
|
"cookie-es": "^3.1.1",
|
|
81
81
|
"defu": "^6.1.7",
|
|
@@ -91,36 +91,36 @@
|
|
|
91
91
|
},
|
|
92
92
|
"devDependencies": {
|
|
93
93
|
"@antfu/eslint-config": "^9.3.0",
|
|
94
|
-
"@libsql/client": "^0.
|
|
94
|
+
"@libsql/client": "^0.18.0",
|
|
95
95
|
"@libsql/linux-x64-gnu": "0.5.29",
|
|
96
96
|
"@nuxt/devtools": "^3.4.1",
|
|
97
97
|
"@nuxt/fonts": "0.14.0",
|
|
98
98
|
"@nuxt/module-builder": "^1.0.3",
|
|
99
|
-
"@nuxt/schema": "^4.
|
|
100
|
-
"@nuxt/test-utils": "^4.
|
|
99
|
+
"@nuxt/schema": "^4.6.0",
|
|
100
|
+
"@nuxt/test-utils": "^4.3.3",
|
|
101
101
|
"@nuxthub/core": "^0.10.8",
|
|
102
102
|
"@pinia/nuxt": "^1.0.2",
|
|
103
103
|
"@types/better-sqlite3": "^9.6.0",
|
|
104
104
|
"@vue/test-utils": "^2.4.6",
|
|
105
|
-
"better-auth": "1.7.
|
|
105
|
+
"better-auth": "1.7.7",
|
|
106
106
|
"better-call": "1.4.0",
|
|
107
|
-
"better-sqlite3": "^
|
|
107
|
+
"better-sqlite3": "^13.0.0",
|
|
108
108
|
"drizzle-kit": "^0.31.10",
|
|
109
109
|
"drizzle-orm": "^0.45.2",
|
|
110
110
|
"eslint": "^10.8.1",
|
|
111
111
|
"happy-dom": "^20.8.9",
|
|
112
112
|
"nitropack": "2.13.4",
|
|
113
|
-
"nuxt": "^4.
|
|
113
|
+
"nuxt": "^4.6.0",
|
|
114
114
|
"pinia": "^4.0.3",
|
|
115
115
|
"pkg-pr-new": "0.0.88",
|
|
116
|
-
"playwright-core": "1.
|
|
116
|
+
"playwright-core": "1.63.0",
|
|
117
117
|
"postgres": "^3.4.9",
|
|
118
118
|
"typescript": "~5.9.3",
|
|
119
119
|
"unstorage": "^1.17.5",
|
|
120
|
-
"vitest": "^
|
|
121
|
-
"vue": "3.5.
|
|
120
|
+
"vitest": "^5.0.0",
|
|
121
|
+
"vue": "3.5.43",
|
|
122
122
|
"vue-tsc": "^3.3.10",
|
|
123
|
-
"wrangler": "4.
|
|
123
|
+
"wrangler": "4.147.0",
|
|
124
124
|
"yaml": "^2.9.0"
|
|
125
125
|
},
|
|
126
126
|
"scripts": {
|