@nuxtjs/better-auth 0.3.7 → 0.3.9
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 +76 -30
- package/dist/runtime/app/middleware/auth.global.js +2 -0
- 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/{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 +39 -7
- package/dist/runtime/server/utils/session.js +10 -0
- package/dist/runtime/test-utils/server/handler.js +1 -1
- package/package.json +11 -11
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.9";
|
|
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",
|
|
@@ -643,45 +659,62 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola, h
|
|
|
643
659
|
const DEFAULT_SECRET_ENV = "NUXT_BETTER_AUTH_SECRET";
|
|
644
660
|
const FALLBACK_SECRET_ENV = "BETTER_AUTH_SECRET";
|
|
645
661
|
const VERSIONED_SECRET_ENV = "BETTER_AUTH_SECRETS";
|
|
662
|
+
const APP_SECRET_ENV = "NUXT_APP_SECRET";
|
|
646
663
|
const generateSecret = () => randomBytes(32).toString("hex");
|
|
647
664
|
function readEnvFile(rootDir) {
|
|
648
665
|
const envPath = join(rootDir, ".env");
|
|
649
666
|
return existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
|
|
650
667
|
}
|
|
651
|
-
function hasEnvSecret(rootDir) {
|
|
668
|
+
function hasEnvSecret(rootDir, names) {
|
|
652
669
|
const envFile = readEnvFile(rootDir);
|
|
653
|
-
return
|
|
670
|
+
return names.some((name) => {
|
|
654
671
|
const match = envFile.match(new RegExp(`^${name}=(.+)$`, "m"));
|
|
655
672
|
return !!match && !!match[1] && match[1].trim().length > 0;
|
|
656
673
|
});
|
|
657
674
|
}
|
|
658
|
-
function appendSecretToEnv(rootDir, secret) {
|
|
675
|
+
function appendSecretToEnv(rootDir, name, secret) {
|
|
659
676
|
const envPath = join(rootDir, ".env");
|
|
660
677
|
let content = readEnvFile(rootDir);
|
|
661
678
|
if (content.length > 0 && !content.endsWith("\n"))
|
|
662
679
|
content += "\n";
|
|
663
|
-
content += `${
|
|
680
|
+
content += `${name}=${secret}
|
|
664
681
|
`;
|
|
665
682
|
writeFileSync(envPath, content, "utf-8");
|
|
666
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
|
+
}
|
|
667
691
|
async function promptForSecret(rootDir, consola, options = {}) {
|
|
668
692
|
const configuredSecret = options.configuredSecret?.trim();
|
|
669
693
|
if (configuredSecret)
|
|
670
694
|
return void 0;
|
|
671
|
-
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()))
|
|
672
704
|
return void 0;
|
|
673
705
|
const hasTty = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
674
706
|
if (options.prepare || !hasTty) {
|
|
675
|
-
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.");
|
|
676
708
|
return void 0;
|
|
677
709
|
}
|
|
710
|
+
const envName = getSecretEnvName(options.appSecret);
|
|
678
711
|
if (isCI || isTest) {
|
|
679
712
|
const secret2 = generateSecret();
|
|
680
|
-
appendSecretToEnv(rootDir, secret2);
|
|
681
|
-
consola.info(
|
|
713
|
+
appendSecretToEnv(rootDir, envName, secret2);
|
|
714
|
+
consola.info(`Generated ${envName} and added to .env (CI/test mode)`);
|
|
682
715
|
return secret2;
|
|
683
716
|
}
|
|
684
|
-
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.");
|
|
685
718
|
const choice = await consola.prompt("How do you want to set it?", {
|
|
686
719
|
type: "select",
|
|
687
720
|
options: [
|
|
@@ -708,14 +741,14 @@ async function promptForSecret(rootDir, consola, options = {}) {
|
|
|
708
741
|
}
|
|
709
742
|
const preview = `${secret.slice(0, 8)}...${secret.slice(-4)}`;
|
|
710
743
|
const confirm = await consola.prompt(`Add to .env:
|
|
711
|
-
${
|
|
744
|
+
${envName}=${preview}
|
|
712
745
|
Proceed?`, { type: "confirm", initial: true, cancel: "null" });
|
|
713
746
|
if (typeof confirm === "symbol" || !confirm) {
|
|
714
747
|
consola.info("Cancelled. Secret not written.");
|
|
715
748
|
return void 0;
|
|
716
749
|
}
|
|
717
|
-
appendSecretToEnv(rootDir, secret);
|
|
718
|
-
consola.success(
|
|
750
|
+
appendSecretToEnv(rootDir, envName, secret);
|
|
751
|
+
consola.success(`Added ${envName} to .env`);
|
|
719
752
|
return secret;
|
|
720
753
|
}
|
|
721
754
|
|
|
@@ -1645,16 +1678,25 @@ const module$1 = defineNuxtModule({
|
|
|
1645
1678
|
},
|
|
1646
1679
|
async onInstall(nuxt) {
|
|
1647
1680
|
const configuredSecret = nuxt.options.runtimeConfig?.betterAuthSecret;
|
|
1648
|
-
const
|
|
1649
|
-
|
|
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)
|
|
1650
1691
|
process.env.NUXT_BETTER_AUTH_SECRET = generatedSecret;
|
|
1651
1692
|
await createDefaultAuthConfigFiles(nuxt);
|
|
1652
1693
|
},
|
|
1653
1694
|
setup(options, nuxt) {
|
|
1654
1695
|
const finishSetup = async () => {
|
|
1655
1696
|
const resolver = createResolver(import.meta.url);
|
|
1656
|
-
const
|
|
1657
|
-
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);
|
|
1658
1700
|
const registeredPluginSources = {};
|
|
1659
1701
|
await nuxt.callHook("better-auth:plugins:extend", registeredPluginSources);
|
|
1660
1702
|
for (const source of [...registeredPluginSources.server || [], ...registeredPluginSources.client || []]) {
|
|
@@ -1740,8 +1782,8 @@ const module$1 = defineNuxtModule({
|
|
|
1740
1782
|
hasHubDb: setup.serverTypes.hasHubDb,
|
|
1741
1783
|
runtimeTypesPath: resolver.resolve("./runtime/types"),
|
|
1742
1784
|
sharedServerConfigSafe: [setup.configs.server.path, ...setup.pluginSources.server].every(isServerConfigSharedTypeSafe),
|
|
1743
|
-
h3TypesPath
|
|
1744
|
-
nitro3RouteRulesTarget:
|
|
1785
|
+
h3TypesPath,
|
|
1786
|
+
nitro3RouteRulesTarget: h3TypesPath === "nitro/h3" ? resolveNitro3RouteRulesTarget(nuxt.options.rootDir) : void 0
|
|
1745
1787
|
});
|
|
1746
1788
|
}
|
|
1747
1789
|
if (setup.schemaGeneration) {
|
|
@@ -1759,11 +1801,15 @@ const module$1 = defineNuxtModule({
|
|
|
1759
1801
|
runtimeTypesAugmentPath: setup.sharedTypes.runtimeTypesAugmentPath,
|
|
1760
1802
|
runtimeTypesPath: resolver.resolve("./runtime/types"),
|
|
1761
1803
|
clientConfigPath,
|
|
1762
|
-
h3TypesPath
|
|
1804
|
+
h3TypesPath,
|
|
1763
1805
|
clientOnly: setup.clientOnly
|
|
1764
1806
|
});
|
|
1765
1807
|
registerTemplateHmrHook(nuxt);
|
|
1766
|
-
registerServerRuntime({
|
|
1808
|
+
registerServerRuntime({
|
|
1809
|
+
clientOnly: setup.clientOnly,
|
|
1810
|
+
resolve: resolver.resolve,
|
|
1811
|
+
authSecretPlugin: setup.clientOnly ? void 0 : await resolveAuthSecretPlugin(resolver.resolve)
|
|
1812
|
+
});
|
|
1767
1813
|
registerAuthMiddleware(resolver.resolve);
|
|
1768
1814
|
await registerDevtools({ nuxt, clientOnly: setup.clientOnly, hasHubDb: setup.database.hasHubDb, resolve: resolver.resolve });
|
|
1769
1815
|
registerRouteRulesMetaHook(nuxt);
|
|
@@ -31,6 +31,8 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
|
|
31
31
|
if (auth === void 0 || auth === false)
|
|
32
32
|
return;
|
|
33
33
|
const config = useRuntimeConfig().public.auth;
|
|
34
|
+
if (import.meta.server && config?.clientOnly)
|
|
35
|
+
return;
|
|
34
36
|
const { fetchSession, user, loggedIn } = useUserSession();
|
|
35
37
|
const mode = typeof auth === "string" ? auth : auth?.only ?? "user";
|
|
36
38
|
const redirectTo = typeof auth === "object" ? auth.redirectTo : void 0;
|
|
@@ -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,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,6 +1,7 @@
|
|
|
1
1
|
import { betterAuth, env } from "better-auth";
|
|
2
2
|
import { createDatabase, db } from "#auth/database";
|
|
3
3
|
import createServerAuth from "#auth/server";
|
|
4
|
+
import { getConfiguredAuthSecret, getDerivedAuthSecret, hasExplicitAuthSecret, prepareAuthSecret } from "../internal/auth-secret.js";
|
|
4
5
|
import { getRequestHost, getRequestProtocol, useRuntimeConfig } from "../internal/nitro-compat.js";
|
|
5
6
|
import { resolveAuthBaseURL } from "../../internal/auth-base-url.js";
|
|
6
7
|
import { resolveCustomSecondaryStorageRequirement } from "./custom-secondary-storage.js";
|
|
@@ -10,6 +11,7 @@ let _baseURLInferenceLogged = false;
|
|
|
10
11
|
let _customSecondaryStorageMisconfigWarned = false;
|
|
11
12
|
let _unsupportedHubSecondaryStorageWarned = false;
|
|
12
13
|
let _secondaryStorageRateLimitFallbackWarned = false;
|
|
14
|
+
let _derivedSecretFallbackWarned = false;
|
|
13
15
|
const fallbackRequestAuthContext = /* @__PURE__ */ new WeakMap();
|
|
14
16
|
function getRequestAuthContext(event) {
|
|
15
17
|
const eventWithContext = event;
|
|
@@ -193,17 +195,39 @@ function withDevTrustedOrigins(trustedOrigins) {
|
|
|
193
195
|
return mergeOrigins([], request);
|
|
194
196
|
};
|
|
195
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
|
+
}
|
|
196
217
|
export function serverAuth(event) {
|
|
197
218
|
const runtimeConfig = useRuntimeConfig();
|
|
198
|
-
const
|
|
199
|
-
if (
|
|
219
|
+
const configuredSecret = getConfiguredAuthSecret(runtimeConfig);
|
|
220
|
+
if (configuredSecret && configuredSecret.length < 32)
|
|
200
221
|
throw new Error("[nuxt-better-auth] Singular auth secret must be at least 32 characters for security");
|
|
222
|
+
const derived = getDerivedAuthSecret();
|
|
201
223
|
const requestOrigin = resolveEventOrigin(event);
|
|
202
224
|
let userConfig;
|
|
203
|
-
if (!import.meta.dev && !
|
|
225
|
+
if (!import.meta.dev && !configuredSecret && !env.BETTER_AUTH_SECRETS && !derived.secret) {
|
|
204
226
|
userConfig = createServerAuth({ runtimeConfig, db, requestOrigin });
|
|
205
|
-
if (userConfig.secrets === void 0)
|
|
206
|
-
|
|
227
|
+
if (userConfig.secrets === void 0) {
|
|
228
|
+
void prepareAuthSecret();
|
|
229
|
+
throw new Error(missingAuthSecretMessage(derived));
|
|
230
|
+
}
|
|
207
231
|
}
|
|
208
232
|
const siteUrl = getBaseURL(event);
|
|
209
233
|
const hasExplicitSiteUrl = runtimeConfig.public.siteUrl && typeof runtimeConfig.public.siteUrl === "string";
|
|
@@ -212,6 +236,13 @@ export function serverAuth(event) {
|
|
|
212
236
|
if (requestContext?.[requestAuthKey])
|
|
213
237
|
return requestContext[requestAuthKey];
|
|
214
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 ?? "" : "");
|
|
215
246
|
const database = createDatabase(event);
|
|
216
247
|
const trustedOrigins = withDevTrustedOrigins(userConfig.trustedOrigins);
|
|
217
248
|
const hubSecondaryStorage = runtimeConfig.auth?.hubSecondaryStorage;
|
|
@@ -232,7 +263,8 @@ export function serverAuth(event) {
|
|
|
232
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.');
|
|
233
264
|
}
|
|
234
265
|
const rateLimit = useMemoryRateLimit ? { ...userConfig.rateLimit, storage: "memory" } : userConfig.rateLimit;
|
|
235
|
-
|
|
266
|
+
const cacheable = !database && !derivedSecretMissing;
|
|
267
|
+
if (cacheable) {
|
|
236
268
|
const cached = _authCache.get(cacheKey);
|
|
237
269
|
if (cached) {
|
|
238
270
|
if (requestContext)
|
|
@@ -251,7 +283,7 @@ export function serverAuth(event) {
|
|
|
251
283
|
const auth = betterAuth(authOptions);
|
|
252
284
|
if (requestContext)
|
|
253
285
|
requestContext[requestAuthKey] = auth;
|
|
254
|
-
if (
|
|
286
|
+
if (cacheable)
|
|
255
287
|
_authCache.set(cacheKey, auth);
|
|
256
288
|
return auth;
|
|
257
289
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { matchesUser } from "../../utils/match-user.js";
|
|
2
2
|
import { createAuthError } from "../internal/nitro-compat.js";
|
|
3
|
+
import { prepareAuthSecret } from "../internal/auth-secret.js";
|
|
3
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");
|
|
@@ -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
|
}
|
|
@@ -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.9",
|
|
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,7 +73,7 @@
|
|
|
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
78
|
"auth": "1.7.7",
|
|
79
79
|
"consola": "^3.4.2",
|
|
@@ -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
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": {
|