@nuxtjs/better-auth 0.3.10 → 0.3.12
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.d.mts +20 -17
- package/dist/module.json +1 -1
- package/dist/module.mjs +62 -5
- package/dist/runtime/app/composables/useUserSession.js +3 -3
- package/dist/runtime/app/internal/session-fetch.js +5 -4
- package/dist/runtime/app/internal/utils.d.ts +8 -0
- package/dist/runtime/app/internal/utils.js +16 -0
- package/dist/runtime/app/middleware/auth.global.js +5 -3
- package/dist/runtime/config.d.ts +6 -3
- package/dist/runtime/internal/auth-route-rules.d.ts +2 -2
- package/dist/runtime/internal/auth-route-rules.js +8 -5
- package/dist/runtime/server/internal/nitro2.d.ts +12 -2
- package/dist/runtime/server/internal/nitro2.js +98 -18
- package/dist/runtime/server/internal/portable.js +1 -1
- package/dist/runtime/server/middleware/route-access.js +20 -1
- package/dist/runtime/server/utils/auth.d.ts +7 -2
- package/dist/runtime/server/utils/auth.js +16 -2
- package/dist/runtime/types/augment.d.ts +2 -0
- package/package.json +15 -11
package/dist/module.d.mts
CHANGED
|
@@ -30,24 +30,27 @@ interface BetterAuthDatabaseProviderDefinition {
|
|
|
30
30
|
isEnabled?: (ctx: BetterAuthDatabaseProviderEnabledContext) => boolean;
|
|
31
31
|
priority?: number;
|
|
32
32
|
}
|
|
33
|
+
interface BetterAuthNuxtHooks {
|
|
34
|
+
/** Register absolute plugin module paths during module setup. Sources are additive and are not deduplicated. */
|
|
35
|
+
'better-auth:plugins:extend': (sources: BetterAuthPluginSources) => void | Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Add plugin schemas to generated Better Auth database tables.
|
|
38
|
+
* This build-time hook does not install plugins in the runtime auth instance.
|
|
39
|
+
* @param config - Plugins to include during schema generation
|
|
40
|
+
* @param config.plugins - Better Auth plugins whose schemas should be generated
|
|
41
|
+
*/
|
|
42
|
+
'better-auth:config:extend': (config: {
|
|
43
|
+
plugins?: BetterAuthPlugin[];
|
|
44
|
+
}) => void | Promise<void>;
|
|
45
|
+
/** Register or override Better Auth database providers. */
|
|
46
|
+
'better-auth:database:providers': (providers: Record<string, BetterAuthDatabaseProviderDefinition>) => void | Promise<void>;
|
|
47
|
+
}
|
|
33
48
|
declare module '@nuxt/schema' {
|
|
34
|
-
interface NuxtHooks {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
* This build-time hook does not install plugins in the runtime auth instance.
|
|
40
|
-
* @param config - Plugins to include during schema generation
|
|
41
|
-
* @param config.plugins - Better Auth plugins whose schemas should be generated
|
|
42
|
-
*/
|
|
43
|
-
'better-auth:config:extend': (config: {
|
|
44
|
-
plugins?: BetterAuthPlugin[];
|
|
45
|
-
}) => void | Promise<void>;
|
|
46
|
-
/**
|
|
47
|
-
* Register or override Better Auth database providers.
|
|
48
|
-
* Providers are auto-selected via `isEnabled` + `priority`.
|
|
49
|
-
*/
|
|
50
|
-
'better-auth:database:providers': (providers: Record<string, BetterAuthDatabaseProviderDefinition>) => void | Promise<void>;
|
|
49
|
+
interface NuxtHooks extends BetterAuthNuxtHooks {
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
declare module 'nuxt/schema' {
|
|
53
|
+
interface NuxtHooks extends BetterAuthNuxtHooks {
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
|
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, resolveServerVariant, hasNuxtCompatibility, hasNuxtModuleCompatibility, updateTemplates, addServerImports, addServerHandler, addNitroPlugin, addImports, addPlugin, addComponentsDir, addRouteMiddleware, extendPages, addTemplate, hasNuxtModule,
|
|
3
|
+
import { getLayerDirectories, resolveServerVariant, hasNuxtCompatibility, hasNuxtModuleCompatibility, addTypeTemplate, updateTemplates, addServerImports, addServerHandler, addNitroPlugin, addImports, addPlugin, addComponentsDir, addRouteMiddleware, extendPages, addTemplate, hasNuxtModule, 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.12";
|
|
17
17
|
|
|
18
18
|
const CONFIG_EXTENSIONS = [".ts", ".js", ".mts", ".cts", ".mjs", ".cjs"];
|
|
19
19
|
const CONFIG_EXTENSION_RE = /\.[cm]?[jt]s$/;
|
|
@@ -284,6 +284,14 @@ const diagnostics = defineDiagnostics({
|
|
|
284
284
|
why: (p) => `Failed to load auth config at ${p.configPath}: ${p.cause instanceof Error ? p.cause.message : String(p.cause)}`,
|
|
285
285
|
fix: "Check the config imports and required environment variables, then restart Nuxt to regenerate the schema."
|
|
286
286
|
},
|
|
287
|
+
NUXT_AUTH_CLI_MISSING: {
|
|
288
|
+
why: "NuxtHub schema generation needs the Better Auth CLI package (auth), which is not installed.",
|
|
289
|
+
fix: (p) => `Run ${p.command}. Keep auth on the same version as better-auth.`
|
|
290
|
+
},
|
|
291
|
+
NUXT_AUTH_CLI_VERSION_MISMATCH: {
|
|
292
|
+
why: (p) => `auth@${p.cliVersion} installs better-auth@${p.cliBetterAuthVersion}, but this app uses better-auth@${p.betterAuthVersion}. Two copies of Better Auth cause conflicting types.`,
|
|
293
|
+
fix: (p) => `Run ${p.command} so auth and better-auth use the same version.`
|
|
294
|
+
},
|
|
287
295
|
NUXT_AUTH_EMPTY_SCHEMA: {
|
|
288
296
|
why: (p) => `Schema generation returned empty result for ${p.dialect}`,
|
|
289
297
|
fix: "Check the Better Auth plugins and database options in defineServerAuth(), then regenerate the schema."
|
|
@@ -379,6 +387,14 @@ function registerAuthMiddleware(resolve) {
|
|
|
379
387
|
}
|
|
380
388
|
function registerPrepareTypesHook(input) {
|
|
381
389
|
const { nuxt, serverDir, hasHubDb, serverImportsTypesFile } = input;
|
|
390
|
+
addTypeTemplate({
|
|
391
|
+
filename: "types/auth-route-rules.d.ts",
|
|
392
|
+
getContents: () => `
|
|
393
|
+
declare module '#auth/route-rules' {
|
|
394
|
+
export const authRouteRules: Record<string, { auth?: unknown }>
|
|
395
|
+
}
|
|
396
|
+
`
|
|
397
|
+
}, { nitro: true, node: true });
|
|
382
398
|
nuxt.hook("prepare:types", ({ nodeTsConfig, nodeReferences, sharedReferences }) => {
|
|
383
399
|
nodeTsConfig.compilerOptions ||= {};
|
|
384
400
|
nodeTsConfig.compilerOptions.paths ||= {};
|
|
@@ -470,6 +486,40 @@ function registerRouteRulesMetaHook(nuxt) {
|
|
|
470
486
|
function dialectToProvider(dialect) {
|
|
471
487
|
return dialect === "postgresql" ? "pg" : dialect;
|
|
472
488
|
}
|
|
489
|
+
const EXACT_VERSION_RE = /^\d+\.\d+\.\d+(?:-[\w.]+)?$/;
|
|
490
|
+
const LOCKFILES = [["pnpm-lock.yaml", "pnpm"], ["yarn.lock", "yarn"], ["bun.lock", "bun"], ["bun.lockb", "bun"], ["package-lock.json", "npm"]];
|
|
491
|
+
function readInstalledPackage(name) {
|
|
492
|
+
let entry;
|
|
493
|
+
try {
|
|
494
|
+
entry = createRequire(import.meta.url).resolve(name === "auth" ? "auth/api" : name);
|
|
495
|
+
} catch {
|
|
496
|
+
return void 0;
|
|
497
|
+
}
|
|
498
|
+
for (let dir = dirname(entry); dir !== dirname(dir); dir = dirname(dir)) {
|
|
499
|
+
const file = join(dir, "package.json");
|
|
500
|
+
if (!existsSync(file))
|
|
501
|
+
continue;
|
|
502
|
+
const pkg = JSON.parse(readFileSync(file, "utf8"));
|
|
503
|
+
if (pkg.name === name)
|
|
504
|
+
return pkg;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
function devInstallCommand(spec, rootDir) {
|
|
508
|
+
const agent = process.env.npm_config_user_agent?.split("/")[0];
|
|
509
|
+
const manager = LOCKFILES.find(([file]) => existsSync(join(rootDir, file)))?.[1] ?? LOCKFILES.find(([, name]) => name === agent)?.[1] ?? "npm";
|
|
510
|
+
return manager === "npm" ? `npm install -D ${spec}` : `${manager} add -D ${spec}`;
|
|
511
|
+
}
|
|
512
|
+
function checkBetterAuthCli(rootDir, readPackage = readInstalledPackage) {
|
|
513
|
+
const betterAuthVersion = readPackage("better-auth")?.version;
|
|
514
|
+
const spec = betterAuthVersion ? `auth@${betterAuthVersion}` : "auth";
|
|
515
|
+
const cli = readPackage("auth");
|
|
516
|
+
if (!cli)
|
|
517
|
+
return { error: diagnostics.NUXT_AUTH_CLI_MISSING({ command: devInstallCommand(spec, rootDir) }) };
|
|
518
|
+
const cliBetterAuthVersion = cli.dependencies?.["better-auth"];
|
|
519
|
+
if (betterAuthVersion && cliBetterAuthVersion && EXACT_VERSION_RE.test(cliBetterAuthVersion) && cliBetterAuthVersion !== betterAuthVersion)
|
|
520
|
+
return { warn: diagnostics.NUXT_AUTH_CLI_VERSION_MISMATCH({ cliVersion: cli.version ?? cliBetterAuthVersion, cliBetterAuthVersion, betterAuthVersion, command: devInstallCommand(spec, rootDir) }) };
|
|
521
|
+
return {};
|
|
522
|
+
}
|
|
473
523
|
async function generateDrizzleSchema(authOptions, dialect, schemaOptions) {
|
|
474
524
|
const { generateDrizzleSchema: generateDrizzleSchema2 } = await import('auth/api');
|
|
475
525
|
const provider = dialectToProvider(dialect);
|
|
@@ -631,6 +681,11 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola, h
|
|
|
631
681
|
consola.warn(formatDiagnostic(diagnostics.NUXT_AUTH_UNSUPPORTED_DIALECT({ dialect })));
|
|
632
682
|
return;
|
|
633
683
|
}
|
|
684
|
+
const cli = checkBetterAuthCli(nuxt.options.rootDir);
|
|
685
|
+
if (cli.error)
|
|
686
|
+
throw cli.error;
|
|
687
|
+
if (cli.warn)
|
|
688
|
+
consola.warn(formatDiagnostic(cli.warn));
|
|
634
689
|
const context = { nuxt, serverConfigPath };
|
|
635
690
|
try {
|
|
636
691
|
const authConfig = await loadAuthOptions(context);
|
|
@@ -1092,10 +1147,10 @@ function getRuntimeRouteRules(nuxt) {
|
|
|
1092
1147
|
}
|
|
1093
1148
|
function registerAuthRouteRulesValidation(nuxt) {
|
|
1094
1149
|
nuxt.hook("nitro:init", (nitro) => {
|
|
1095
|
-
assertSafeAuthRouteRules(nitro.options.routeRules);
|
|
1150
|
+
assertSafeAuthRouteRules(nitro.options.routeRules, nuxt.options.app.baseURL);
|
|
1096
1151
|
});
|
|
1097
1152
|
}
|
|
1098
|
-
function assertSafeAuthRouteRules(routeRules) {
|
|
1153
|
+
function assertSafeAuthRouteRules(routeRules, baseURL = "/") {
|
|
1099
1154
|
if (!Object.keys(routeRules).length)
|
|
1100
1155
|
return;
|
|
1101
1156
|
const matcher = toRouteMatcher(createRouter({ routes: routeRules }));
|
|
@@ -1104,7 +1159,7 @@ function assertSafeAuthRouteRules(routeRules) {
|
|
|
1104
1159
|
for (const path of collectRouteRulePaths(patterns, ""))
|
|
1105
1160
|
paths.add(path);
|
|
1106
1161
|
const conflicts = [...paths].flatMap((path) => {
|
|
1107
|
-
if (isInternalAssetPath(path))
|
|
1162
|
+
if (isInternalAssetPath(path, baseURL))
|
|
1108
1163
|
return [];
|
|
1109
1164
|
const matches = matcher.matchAll(path);
|
|
1110
1165
|
const effectiveRule = defu({}, ...matches.reverse());
|
|
@@ -1573,6 +1628,8 @@ declare module '#nuxt-better-auth' {
|
|
|
1573
1628
|
emailVerified: boolean
|
|
1574
1629
|
name: string
|
|
1575
1630
|
image?: string | null
|
|
1631
|
+
/** Whether the user has enabled two-factor authentication. */
|
|
1632
|
+
twoFactorEnabled?: boolean
|
|
1576
1633
|
}
|
|
1577
1634
|
|
|
1578
1635
|
export interface AuthSession {
|
|
@@ -2,7 +2,7 @@ import { computed, navigateTo, nextTick, useNuxtApp, useRequestURL, useRuntimeCo
|
|
|
2
2
|
import { normalizeAuthActionError } from "../internal/auth-action-error.js";
|
|
3
3
|
import { resolvePostAuthSuccessRedirect, withFallbackSocialCallbackURL } from "../internal/redirect-helpers.js";
|
|
4
4
|
import { fetchSessionClient, fetchSessionServer, isExpectedSignedOutSessionError, stripToken } from "../internal/session-fetch.js";
|
|
5
|
-
import { isRecord } from "../internal/utils.js";
|
|
5
|
+
import { isRecord, setIfChanged } from "../internal/utils.js";
|
|
6
6
|
import { createVueSafeAuthFacade, isAuthProxyProbeKey } from "../internal/vue-safe-auth-proxy.js";
|
|
7
7
|
import { wrapAuthMethod } from "../internal/wrap-auth-method.js";
|
|
8
8
|
import { getAuthRuntimeFlags, useRawAuthClient } from "./useAuthClient.js";
|
|
@@ -152,8 +152,8 @@ export function useUserSession() {
|
|
|
152
152
|
if (newSession?.data?.session && newSession?.data?.user) {
|
|
153
153
|
sessionExpiredRedirecting = false;
|
|
154
154
|
hydrationSessionWasActive = false;
|
|
155
|
-
session
|
|
156
|
-
user
|
|
155
|
+
setIfChanged(session, stripToken(newSession.data.session));
|
|
156
|
+
setIfChanged(user, newSession.data.user);
|
|
157
157
|
} else if (!newSession?.isPending && !newSession?.isRefetching) {
|
|
158
158
|
const isHydrationEmptySnapshot = nuxtApp.isHydrating && nuxtApp.payload.serverRendered && Boolean(session.value && user.value) && !newSession?.data?.session && !newSession?.data?.user;
|
|
159
159
|
if (isHydrationEmptySnapshot) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseJSON } from "better-auth/client";
|
|
2
2
|
import { useRequestFetch, useRequestHeaders } from "#imports";
|
|
3
3
|
import { normalizeAuthActionError } from "./auth-action-error.js";
|
|
4
|
+
import { setIfChanged } from "./utils.js";
|
|
4
5
|
export function stripToken(session) {
|
|
5
6
|
const { token: _, ...safe } = session;
|
|
6
7
|
return safe;
|
|
@@ -35,8 +36,8 @@ export async function fetchSessionServer(session, user, authReady, options = {})
|
|
|
35
36
|
...options.force ? { query: { disableCookieCache: true } } : {}
|
|
36
37
|
});
|
|
37
38
|
if (data?.session && data?.user) {
|
|
38
|
-
session
|
|
39
|
-
user
|
|
39
|
+
setIfChanged(session, stripToken(data.session));
|
|
40
|
+
setIfChanged(user, data.user);
|
|
40
41
|
} else {
|
|
41
42
|
session.value = null;
|
|
42
43
|
user.value = null;
|
|
@@ -60,8 +61,8 @@ export async function fetchSessionClient(client, session, user, authReady, optio
|
|
|
60
61
|
}
|
|
61
62
|
const data = result.data;
|
|
62
63
|
if (data?.session && data?.user) {
|
|
63
|
-
session
|
|
64
|
-
user
|
|
64
|
+
setIfChanged(session, stripToken(data.session));
|
|
65
|
+
setIfChanged(user, data.user);
|
|
65
66
|
} else {
|
|
66
67
|
session.value = null;
|
|
67
68
|
user.value = null;
|
|
@@ -1 +1,9 @@
|
|
|
1
1
|
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
2
|
+
/**
|
|
3
|
+
* Better Auth refetches the session on window focus and returns fresh objects even
|
|
4
|
+
* when nothing changed. Keeping the current object avoids retriggering every
|
|
5
|
+
* computed and watcher that depends on the session or user.
|
|
6
|
+
*/
|
|
7
|
+
export declare function setIfChanged<T>(target: {
|
|
8
|
+
value: T;
|
|
9
|
+
}, value: T): void;
|
|
@@ -1,3 +1,19 @@
|
|
|
1
1
|
export function isRecord(value) {
|
|
2
2
|
return Boolean(value && typeof value === "object");
|
|
3
3
|
}
|
|
4
|
+
function isSameValue(a, b) {
|
|
5
|
+
if (Object.is(a, b))
|
|
6
|
+
return true;
|
|
7
|
+
if (a instanceof Date || b instanceof Date)
|
|
8
|
+
return a instanceof Date && b instanceof Date && a.getTime() === b.getTime();
|
|
9
|
+
if (Array.isArray(a) || Array.isArray(b))
|
|
10
|
+
return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((item, index) => isSameValue(item, b[index]));
|
|
11
|
+
if (!isRecord(a) || !isRecord(b))
|
|
12
|
+
return false;
|
|
13
|
+
const keys = Object.keys(a);
|
|
14
|
+
return keys.length === Object.keys(b).length && keys.every((key) => Object.hasOwn(b, key) && isSameValue(a[key], b[key]));
|
|
15
|
+
}
|
|
16
|
+
export function setIfChanged(target, value) {
|
|
17
|
+
if (!isSameValue(target.value, value))
|
|
18
|
+
target.value = value;
|
|
19
|
+
}
|
|
@@ -2,13 +2,15 @@ import { defu } from "defu";
|
|
|
2
2
|
import { createRouter, toRouteMatcher } from "radix3";
|
|
3
3
|
import { parsePath, parseQuery } from "ufo";
|
|
4
4
|
import { createError, defineNuxtRouteMiddleware, getRouteRules, navigateTo, useNuxtApp, useRequestHeaders, useRuntimeConfig } from "#imports";
|
|
5
|
-
import { shouldSkipAuthRouteRules } from "../../internal/auth-route-rules.js";
|
|
5
|
+
import { normalizeAuthRoutePath, shouldSkipAuthRouteRules } from "../../internal/auth-route-rules.js";
|
|
6
6
|
import { matchesUser } from "../../utils/match-user.js";
|
|
7
7
|
import { useUserSession } from "../composables/useUserSession.js";
|
|
8
8
|
let authRouteRulesPromise = null;
|
|
9
9
|
let routeRulesMatcherPromise = null;
|
|
10
10
|
export default defineNuxtRouteMiddleware(async (to) => {
|
|
11
|
-
|
|
11
|
+
const runtimeConfig = useRuntimeConfig();
|
|
12
|
+
const path = normalizeAuthRoutePath(to.path, runtimeConfig.app?.baseURL);
|
|
13
|
+
if (shouldSkipAuthRouteRules(to.path, runtimeConfig.app?.baseURL) || shouldSkipAuthRouteRules(path, runtimeConfig.app?.baseURL))
|
|
12
14
|
return;
|
|
13
15
|
if (to.matched?.length === 0)
|
|
14
16
|
return;
|
|
@@ -30,7 +32,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
|
|
30
32
|
const auth = to.meta.auth;
|
|
31
33
|
if (auth === void 0 || auth === false)
|
|
32
34
|
return;
|
|
33
|
-
const config =
|
|
35
|
+
const config = runtimeConfig.public.auth;
|
|
34
36
|
if (import.meta.server && config?.clientOnly)
|
|
35
37
|
return;
|
|
36
38
|
const { fetchSession, user, loggedIn } = useUserSession();
|
package/dist/runtime/config.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface ClientAuthContext {
|
|
|
12
12
|
export type ServerAuthConfig = Omit<BetterAuthOptions, 'secret' | 'baseURL' | 'basePath'> & {
|
|
13
13
|
/** The module registers its server handler at /api/auth. */
|
|
14
14
|
basePath?: '/api/auth';
|
|
15
|
-
plugins?: readonly
|
|
15
|
+
plugins?: readonly unknown[];
|
|
16
16
|
};
|
|
17
17
|
export type ClientAuthConfig = Omit<BetterAuthClientOptions, 'baseURL'> & {
|
|
18
18
|
baseURL?: string;
|
|
@@ -108,8 +108,11 @@ export interface AuthRuntimeConfig {
|
|
|
108
108
|
export interface AuthPrivateRuntimeConfig {
|
|
109
109
|
hubSecondaryStorage: boolean | 'custom';
|
|
110
110
|
}
|
|
111
|
-
export declare function defineServerAuth<const R>(config: (ctx: ServerAuthContext) => R
|
|
112
|
-
export declare function defineServerAuth<const R>(config:
|
|
111
|
+
export declare function defineServerAuth<const R extends ServerAuthConfig>(config: (ctx: ServerAuthContext) => R): (ctx: ServerAuthContext) => R;
|
|
112
|
+
export declare function defineServerAuth<const R>(config: (ctx: ServerAuthContext) => R & ServerAuthConfig & (R extends {
|
|
113
|
+
basePath: infer B;
|
|
114
|
+
} ? B extends '/api/auth' ? unknown : never : unknown)): (ctx: ServerAuthContext) => R;
|
|
115
|
+
export declare function defineServerAuth<const R extends ServerAuthConfig>(config: R & (R extends (...args: never[]) => unknown ? never : unknown)): (ctx: ServerAuthContext) => R;
|
|
113
116
|
export declare function extendServerAuth<const R extends ServerAuthConfig, const P extends readonly BetterAuthPlugin[]>(createConfig: (ctx: ServerAuthContext) => R, plugins: P): (ctx: ServerAuthContext) => ExtendedServerAuthConfig<R, P>;
|
|
114
117
|
export declare function defineClientAuth<T extends ClientAuthConfig>(config: T | ((ctx: ClientAuthContext) => T)): ClientAuthFactory<T>;
|
|
115
118
|
export declare function extendClientAuth<T extends ClientAuthConfig, const P extends BetterAuthClientPlugin[]>(createClient: ClientAuthFactory<T>, plugins: P): ClientAuthFactory<ExtendedClientAuthConfig<T, P>>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AuthRouteRules } from '../types.js';
|
|
2
|
-
export declare function isInternalAssetPath(path: string): boolean;
|
|
2
|
+
export declare function isInternalAssetPath(path: string, baseURL?: string): boolean;
|
|
3
3
|
export declare function normalizeAuthRoutePath(path: string, baseURL?: string): string;
|
|
4
4
|
export declare function normalizeAuthRouteRule(rule: unknown): AuthRouteRules['auth'];
|
|
5
|
-
export declare function shouldSkipAuthRouteRules(path: string): boolean;
|
|
5
|
+
export declare function shouldSkipAuthRouteRules(path: string, baseURL?: string): boolean;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { withoutBase } from "ufo";
|
|
2
|
-
const internalAssetPaths = ["/_fonts", "/_ipx", "/_nuxt", "/api/_nuxt_icon"];
|
|
3
|
-
export function isInternalAssetPath(path) {
|
|
2
|
+
const internalAssetPaths = ["/_fonts", "/_ipx", "/_nuxt", "/_scripts/assets", "/api/_nuxt_icon"];
|
|
3
|
+
export function isInternalAssetPath(path, baseURL = "/") {
|
|
4
4
|
const pathname = path.split(/[?#]/, 1)[0] || "/";
|
|
5
|
-
|
|
5
|
+
const normalizedPathname = withoutBase(pathname, baseURL) || "/";
|
|
6
|
+
return internalAssetPaths.some((prefix) => {
|
|
7
|
+
return normalizedPathname === prefix || normalizedPathname.startsWith(`${prefix}/`);
|
|
8
|
+
});
|
|
6
9
|
}
|
|
7
10
|
const internalRouteRuleAuthPaths = /* @__PURE__ */ new Set([
|
|
8
11
|
"/__better-auth-devtools",
|
|
@@ -27,7 +30,7 @@ export function normalizeAuthRouteRule(rule) {
|
|
|
27
30
|
return rule.options;
|
|
28
31
|
return rule;
|
|
29
32
|
}
|
|
30
|
-
export function shouldSkipAuthRouteRules(path) {
|
|
33
|
+
export function shouldSkipAuthRouteRules(path, baseURL = "/") {
|
|
31
34
|
const pathname = path.split(/[?#]/, 1)[0] || "/";
|
|
32
|
-
return isInternalAssetPath(pathname) || internalRouteRuleAuthPaths.has(pathname) || internalRouteRuleAuthPrefixes.some((prefix) => pathname.startsWith(prefix));
|
|
35
|
+
return isInternalAssetPath(pathname, baseURL) || internalRouteRuleAuthPaths.has(pathname) || internalRouteRuleAuthPrefixes.some((prefix) => pathname.startsWith(prefix));
|
|
33
36
|
}
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import type { H3Event } from 'h3';
|
|
2
2
|
import type { AuthRouteRules } from '../../types.js';
|
|
3
|
-
import { defineEventHandler
|
|
3
|
+
import { defineEventHandler } from 'h3';
|
|
4
4
|
import { useRuntimeConfig } from 'nitropack/runtime';
|
|
5
|
-
export { defineEventHandler,
|
|
5
|
+
export { defineEventHandler, useRuntimeConfig, };
|
|
6
6
|
export type ServerEvent = H3Event;
|
|
7
|
+
export declare function getRequestHost(event: ServerEvent, options?: {
|
|
8
|
+
xForwardedHost?: boolean;
|
|
9
|
+
}): string;
|
|
10
|
+
export declare function getRequestProtocol(event: ServerEvent, options?: {
|
|
11
|
+
xForwardedProto?: boolean;
|
|
12
|
+
}): string;
|
|
13
|
+
export declare function getRequestURL(event: ServerEvent): URL;
|
|
14
|
+
export declare function getQuery(event: ServerEvent): Record<string, string | string[]>;
|
|
15
|
+
export declare function toWebRequest(event: ServerEvent): Request;
|
|
16
|
+
export declare function readBody(event: ServerEvent): Promise<unknown>;
|
|
7
17
|
export declare function splitCookiesString(cookiesString: string | string[]): string[];
|
|
8
18
|
export declare function getAuthRouteRules(event: ServerEvent): AuthRouteRules;
|
|
9
19
|
export declare function createAuthError(status: number, statusText: string): Error;
|
|
@@ -1,31 +1,111 @@
|
|
|
1
1
|
import { splitSetCookieString } from "cookie-es";
|
|
2
|
-
import {
|
|
3
|
-
createError,
|
|
4
|
-
defineEventHandler,
|
|
5
|
-
getQuery,
|
|
6
|
-
getRequestHost,
|
|
7
|
-
getRequestProtocol,
|
|
8
|
-
getRequestURL,
|
|
9
|
-
readBody,
|
|
10
|
-
toWebRequest
|
|
11
|
-
} from "h3";
|
|
2
|
+
import { defineEventHandler } from "h3";
|
|
12
3
|
import { getRouteRules, useRuntimeConfig } from "nitropack/runtime";
|
|
13
4
|
export {
|
|
14
5
|
defineEventHandler,
|
|
15
|
-
getQuery,
|
|
16
|
-
getRequestHost,
|
|
17
|
-
getRequestProtocol,
|
|
18
|
-
getRequestURL,
|
|
19
|
-
readBody,
|
|
20
|
-
toWebRequest,
|
|
21
6
|
useRuntimeConfig
|
|
22
7
|
};
|
|
8
|
+
function createNitro2AuthError(status, statusText) {
|
|
9
|
+
const error = new Error(statusText);
|
|
10
|
+
const markerConstructor = function Nitro2ErrorConstructor() {
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(markerConstructor, "__h3_error__", { value: true });
|
|
13
|
+
Object.defineProperties(error, {
|
|
14
|
+
name: { value: "HTTPError", configurable: true, enumerable: false, writable: true },
|
|
15
|
+
constructor: { value: markerConstructor, configurable: true },
|
|
16
|
+
__h3_error__: { value: true, configurable: true, enumerable: true, writable: true },
|
|
17
|
+
status: { value: status, configurable: true, enumerable: true, writable: true },
|
|
18
|
+
statusText: { value: statusText, configurable: true, enumerable: true, writable: true },
|
|
19
|
+
statusCode: { value: status, configurable: true, enumerable: true, writable: true },
|
|
20
|
+
statusMessage: { value: statusText, configurable: true, enumerable: true, writable: true },
|
|
21
|
+
toJSON: {
|
|
22
|
+
value: () => ({
|
|
23
|
+
status,
|
|
24
|
+
statusText,
|
|
25
|
+
statusCode: status,
|
|
26
|
+
statusMessage: statusText,
|
|
27
|
+
message: statusText,
|
|
28
|
+
unhandled: false,
|
|
29
|
+
fatal: false
|
|
30
|
+
}),
|
|
31
|
+
configurable: true
|
|
32
|
+
},
|
|
33
|
+
unhandled: { value: false, configurable: true, enumerable: true, writable: true },
|
|
34
|
+
fatal: { value: false, configurable: true, enumerable: true, writable: true }
|
|
35
|
+
});
|
|
36
|
+
return error;
|
|
37
|
+
}
|
|
38
|
+
function requestHeaders(event) {
|
|
39
|
+
return event.node?.req?.headers ?? {};
|
|
40
|
+
}
|
|
41
|
+
export function getRequestHost(event, options = {}) {
|
|
42
|
+
const headers = requestHeaders(event);
|
|
43
|
+
if (options.xForwardedHost) {
|
|
44
|
+
const forwarded = headers["x-forwarded-host"];
|
|
45
|
+
const host2 = Array.isArray(forwarded) ? forwarded[0] : forwarded?.split(",")[0]?.trim();
|
|
46
|
+
if (host2)
|
|
47
|
+
return host2;
|
|
48
|
+
}
|
|
49
|
+
const host = headers.host;
|
|
50
|
+
return (Array.isArray(host) ? host[0] : host) || "localhost";
|
|
51
|
+
}
|
|
52
|
+
export function getRequestProtocol(event, options = {}) {
|
|
53
|
+
const headers = requestHeaders(event);
|
|
54
|
+
if (options.xForwardedProto !== false && headers["x-forwarded-proto"] === "https")
|
|
55
|
+
return "https";
|
|
56
|
+
return event.node?.req?.socket && event.node.req.socket["encrypted"] ? "https" : "http";
|
|
57
|
+
}
|
|
58
|
+
export function getRequestURL(event) {
|
|
59
|
+
const req = event.node?.req ?? {};
|
|
60
|
+
const path = req.originalUrl || req.url || event.path || "/";
|
|
61
|
+
return new URL(path, `${getRequestProtocol(event)}://${getRequestHost(event)}`);
|
|
62
|
+
}
|
|
63
|
+
export function getQuery(event) {
|
|
64
|
+
const query = {};
|
|
65
|
+
for (const [key, value] of getRequestURL(event).searchParams) {
|
|
66
|
+
const previous = query[key];
|
|
67
|
+
query[key] = previous === void 0 ? value : Array.isArray(previous) ? [...previous, value] : [previous, value];
|
|
68
|
+
}
|
|
69
|
+
return query;
|
|
70
|
+
}
|
|
71
|
+
export function toWebRequest(event) {
|
|
72
|
+
const req = event.node?.req;
|
|
73
|
+
const webRequest = event.web?.request;
|
|
74
|
+
if (webRequest)
|
|
75
|
+
return webRequest;
|
|
76
|
+
const init = {
|
|
77
|
+
method: req?.method,
|
|
78
|
+
headers: req?.headers ? Object.fromEntries(Object.entries(req.headers).flatMap(([key, value]) => value === void 0 ? [] : [[key, Array.isArray(value) ? value.join(", ") : value]])) : void 0,
|
|
79
|
+
duplex: "half"
|
|
80
|
+
};
|
|
81
|
+
if (req?.method !== "GET" && req?.method !== "HEAD")
|
|
82
|
+
init.body = req;
|
|
83
|
+
return new Request(getRequestURL(event), init);
|
|
84
|
+
}
|
|
85
|
+
export async function readBody(event) {
|
|
86
|
+
const request = toWebRequest(event);
|
|
87
|
+
const contentType = request.headers.get("content-type") || "";
|
|
88
|
+
if (contentType.includes("application/json"))
|
|
89
|
+
return request.json();
|
|
90
|
+
if (contentType.includes("application/x-www-form-urlencoded"))
|
|
91
|
+
return Object.fromEntries((await request.text()).split("&").filter(Boolean).map((pair) => pair.split("=").map(decodeURIComponent)));
|
|
92
|
+
return request.text();
|
|
93
|
+
}
|
|
23
94
|
export function splitCookiesString(cookiesString) {
|
|
24
95
|
return splitSetCookieString(cookiesString);
|
|
25
96
|
}
|
|
26
97
|
export function getAuthRouteRules(event) {
|
|
27
|
-
|
|
98
|
+
const eventWithContext = event;
|
|
99
|
+
if (!eventWithContext.context)
|
|
100
|
+
eventWithContext.context = {};
|
|
101
|
+
if (!eventWithContext.path)
|
|
102
|
+
eventWithContext.path = getRequestURL(event).pathname;
|
|
103
|
+
try {
|
|
104
|
+
return getRouteRules(event) || {};
|
|
105
|
+
} catch {
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
28
108
|
}
|
|
29
109
|
export function createAuthError(status, statusText) {
|
|
30
|
-
return
|
|
110
|
+
return createNitro2AuthError(status, statusText);
|
|
31
111
|
}
|
|
@@ -25,7 +25,7 @@ export function splitCookiesString(cookiesString) {
|
|
|
25
25
|
}
|
|
26
26
|
export function getAuthRouteRules(event) {
|
|
27
27
|
const rules = getRouteRules(event);
|
|
28
|
-
return { auth: normalizeAuthRouteRule(rules
|
|
28
|
+
return { auth: normalizeAuthRouteRule(rules?.auth) };
|
|
29
29
|
}
|
|
30
30
|
export function createAuthError(status, statusText) {
|
|
31
31
|
return createError({ status, statusText });
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { defu } from "defu";
|
|
2
|
+
import { createRouter, toRouteMatcher } from "radix3";
|
|
1
3
|
import { normalizeAuthRoutePath, shouldSkipAuthRouteRules } from "../../internal/auth-route-rules.js";
|
|
2
4
|
import { matchesUser } from "../../utils/match-user.js";
|
|
3
5
|
import { prepareAuthSecret } from "../internal/auth-secret.js";
|
|
4
6
|
import { createAuthError, defineEventHandler, getAuthRouteRules, getRequestURL, useRuntimeConfig } from "../internal/nitro-compat.js";
|
|
5
7
|
import { getUserSession, requireUserSession } from "../utils/session.js";
|
|
8
|
+
let authRouteRulesMatcherPromise = null;
|
|
6
9
|
export default defineEventHandler(async (event) => {
|
|
7
10
|
await prepareAuthSecret();
|
|
8
11
|
const path = normalizeAuthRoutePath(
|
|
@@ -13,7 +16,7 @@ export default defineEventHandler(async (event) => {
|
|
|
13
16
|
return;
|
|
14
17
|
if (shouldSkipAuthRouteRules(path))
|
|
15
18
|
return;
|
|
16
|
-
const rules =
|
|
19
|
+
const rules = await resolveAuthRouteRules(event, path);
|
|
17
20
|
if (!rules.auth)
|
|
18
21
|
return;
|
|
19
22
|
const auth = rules.auth;
|
|
@@ -32,3 +35,19 @@ export default defineEventHandler(async (event) => {
|
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
});
|
|
38
|
+
async function resolveAuthRouteRules(event, path) {
|
|
39
|
+
const rules = getAuthRouteRules(event);
|
|
40
|
+
if (rules?.auth !== void 0)
|
|
41
|
+
return rules;
|
|
42
|
+
if (!authRouteRulesMatcherPromise) {
|
|
43
|
+
authRouteRulesMatcherPromise = import("#auth/route-rules").then((mod) => {
|
|
44
|
+
const routeRules = mod.authRouteRules || {};
|
|
45
|
+
return Object.keys(routeRules).length ? toRouteMatcher(createRouter({ routes: routeRules })) : null;
|
|
46
|
+
}).catch(() => null);
|
|
47
|
+
}
|
|
48
|
+
const matcher = await authRouteRulesMatcherPromise;
|
|
49
|
+
const matches = matcher?.matchAll(path);
|
|
50
|
+
if (!matches?.length)
|
|
51
|
+
return rules || {};
|
|
52
|
+
return defu({}, ...matches.reverse());
|
|
53
|
+
}
|
|
@@ -9,13 +9,18 @@ type UserAuthConfig = AuthOptions & {
|
|
|
9
9
|
trustedOrigins?: BetterAuthOptions['trustedOrigins'];
|
|
10
10
|
secondaryStorage?: BetterAuthOptions['secondaryStorage'];
|
|
11
11
|
};
|
|
12
|
-
type ResolvedAuthOptions = Omit<UserAuthConfig, 'secret' | 'baseURL' | 'trustedOrigins' | 'database'> & {
|
|
12
|
+
type ResolvedAuthOptions = Omit<UserAuthConfig, 'secret' | 'baseURL' | 'trustedOrigins' | 'database' | 'plugins'> & {
|
|
13
|
+
plugins?: BetterAuthOptions['plugins'];
|
|
13
14
|
secret: string;
|
|
14
15
|
baseURL: string;
|
|
15
16
|
trustedOrigins?: BetterAuthOptions['trustedOrigins'];
|
|
16
17
|
database?: BetterAuthOptions['database'];
|
|
17
18
|
};
|
|
18
|
-
type AuthInstance = ReturnType<typeof betterAuth<ResolvedAuthOptions
|
|
19
|
+
type AuthInstance = ReturnType<typeof betterAuth<ResolvedAuthOptions>> & {
|
|
20
|
+
api: ReturnType<typeof betterAuth<ResolvedAuthOptions>>['api'] & {
|
|
21
|
+
signInUsername?: (...args: unknown[]) => unknown;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
19
24
|
/**
|
|
20
25
|
* Returns the Better Auth instance like `serverAuth()`, after waiting for the auth secret that
|
|
21
26
|
* Nuxt 4.6+ derives from `appSecret` (`NUXT_APP_SECRET`). Use it in code that can run before the
|
|
@@ -67,8 +67,21 @@ function resolveConfiguredSiteUrl(config) {
|
|
|
67
67
|
function resolveEventOrigin(event) {
|
|
68
68
|
if (!event)
|
|
69
69
|
return void 0;
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
let host;
|
|
71
|
+
let protocol;
|
|
72
|
+
try {
|
|
73
|
+
host = getRequestHost(event, { xForwardedHost: true });
|
|
74
|
+
protocol = getRequestProtocol(event, { xForwardedProto: true });
|
|
75
|
+
} catch {
|
|
76
|
+
const request = event.node?.req;
|
|
77
|
+
const headers = request?.headers ?? {};
|
|
78
|
+
const forwardedHost = headers["x-forwarded-host"];
|
|
79
|
+
const forwarded = Array.isArray(forwardedHost) ? forwardedHost[0] : forwardedHost?.split(",")[0]?.trim();
|
|
80
|
+
const rawHost = forwarded || headers.host;
|
|
81
|
+
host = (Array.isArray(rawHost) ? rawHost[0] : rawHost) || "";
|
|
82
|
+
const forwardedProto = headers["x-forwarded-proto"];
|
|
83
|
+
protocol = (Array.isArray(forwardedProto) ? forwardedProto[0] : forwardedProto) || (request?.socket?.encrypted ? "https" : "http");
|
|
84
|
+
}
|
|
72
85
|
if (!host || !protocol)
|
|
73
86
|
return void 0;
|
|
74
87
|
try {
|
|
@@ -274,6 +287,7 @@ export function serverAuth(event) {
|
|
|
274
287
|
}
|
|
275
288
|
const authOptions = {
|
|
276
289
|
...userConfig,
|
|
290
|
+
...userConfig.plugins ? { plugins: [...userConfig.plugins] } : {},
|
|
277
291
|
...rateLimit ? { rateLimit } : {},
|
|
278
292
|
...database ? { database } : {},
|
|
279
293
|
secret: betterAuthSecret,
|
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.12",
|
|
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",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"@nuxt/test-utils": "^4.1.0",
|
|
63
63
|
"@nuxthub/core": ">=0.10.5",
|
|
64
|
+
"auth": "1.7.12",
|
|
64
65
|
"better-auth": ">=1.7.3 <2"
|
|
65
66
|
},
|
|
66
67
|
"peerDependenciesMeta": {
|
|
@@ -69,18 +70,20 @@
|
|
|
69
70
|
},
|
|
70
71
|
"@nuxthub/core": {
|
|
71
72
|
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"auth": {
|
|
75
|
+
"optional": true
|
|
72
76
|
}
|
|
73
77
|
},
|
|
74
78
|
"dependencies": {
|
|
75
79
|
"@nuxt/devtools-kit": "^3.4.1",
|
|
76
80
|
"@nuxt/kit": "^4.6.0",
|
|
77
81
|
"@types/node": "^24.13.3",
|
|
78
|
-
"auth": "1.7.7",
|
|
79
82
|
"consola": "^3.4.2",
|
|
80
83
|
"cookie-es": "^3.1.1",
|
|
81
84
|
"defu": "^6.1.7",
|
|
82
85
|
"get-port-please": "^3.2.0",
|
|
83
|
-
"h3": "^
|
|
86
|
+
"h3": "^2.0.0",
|
|
84
87
|
"jiti": "^2.7.0",
|
|
85
88
|
"nostics": "^1.2.0",
|
|
86
89
|
"pathe": "^2.0.3",
|
|
@@ -94,7 +97,7 @@
|
|
|
94
97
|
"@libsql/client": "^0.18.0",
|
|
95
98
|
"@libsql/linux-x64-gnu": "0.5.29",
|
|
96
99
|
"@nuxt/devtools": "^3.4.1",
|
|
97
|
-
"@nuxt/fonts": "0.
|
|
100
|
+
"@nuxt/fonts": "1.0.1",
|
|
98
101
|
"@nuxt/module-builder": "^1.0.3",
|
|
99
102
|
"@nuxt/schema": "^4.6.0",
|
|
100
103
|
"@nuxt/test-utils": "^4.3.3",
|
|
@@ -102,25 +105,26 @@
|
|
|
102
105
|
"@pinia/nuxt": "^1.0.2",
|
|
103
106
|
"@types/better-sqlite3": "^9.6.0",
|
|
104
107
|
"@vue/test-utils": "^2.4.6",
|
|
105
|
-
"
|
|
106
|
-
"better-
|
|
108
|
+
"auth": "1.7.12",
|
|
109
|
+
"better-auth": "1.7.12",
|
|
110
|
+
"better-call": "1.4.4",
|
|
107
111
|
"better-sqlite3": "^13.0.0",
|
|
108
112
|
"drizzle-kit": "^0.31.10",
|
|
109
113
|
"drizzle-orm": "^0.45.2",
|
|
110
114
|
"eslint": "^10.8.1",
|
|
111
115
|
"happy-dom": "^21.0.0",
|
|
112
|
-
"nitropack": "2.13.
|
|
116
|
+
"nitropack": "2.13.5",
|
|
113
117
|
"nuxt": "^4.6.0",
|
|
114
118
|
"pinia": "^4.0.3",
|
|
115
|
-
"pkg-pr-new": "0.0.
|
|
116
|
-
"playwright-core": "1.
|
|
119
|
+
"pkg-pr-new": "0.0.91",
|
|
120
|
+
"playwright-core": "1.64.1",
|
|
117
121
|
"postgres": "^3.4.9",
|
|
118
122
|
"typescript": "~5.9.3",
|
|
119
123
|
"unstorage": "^1.17.5",
|
|
120
124
|
"vitest": "^5.0.0",
|
|
121
|
-
"vue": "3.5.
|
|
125
|
+
"vue": "3.5.44",
|
|
122
126
|
"vue-tsc": "^3.3.10",
|
|
123
|
-
"wrangler": "4.
|
|
127
|
+
"wrangler": "4.151.3",
|
|
124
128
|
"yaml": "^2.9.0"
|
|
125
129
|
},
|
|
126
130
|
"scripts": {
|