@onmax/nuxt-better-auth 0.0.2-alpha.26 → 0.0.2-alpha.29
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 +3 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +77 -33
- package/dist/runtime/app/composables/useUserSession.js +55 -2
- package/dist/runtime/app/composables/useUserSignIn.d.ts +4 -2
- package/dist/runtime/app/composables/useUserSignIn.js +5 -2
- package/dist/runtime/app/composables/useUserSignUp.d.ts +4 -2
- package/dist/runtime/app/composables/useUserSignUp.js +5 -2
- package/dist/runtime/app/internal/auth-action-handles.d.ts +1 -3
- package/dist/runtime/app/internal/auth-action-handles.js +2 -6
- package/dist/runtime/app/middleware/auth.global.js +2 -2
- package/dist/runtime/app/pages/__better-auth-devtools.vue +2 -2
- package/dist/runtime/config.d.ts +24 -3
- package/dist/runtime/server/api/_better-auth/config.get.d.ts +16 -10
- package/dist/runtime/server/api/_better-auth/config.get.js +7 -1
- package/dist/runtime/server/utils/auth.d.ts +2 -3
- package/dist/runtime/server/utils/auth.js +11 -1
- package/dist/runtime/server/utils/custom-secondary-storage.d.ts +6 -0
- package/dist/runtime/server/utils/custom-secondary-storage.js +8 -0
- package/dist/runtime/types.d.ts +1 -1
- package/package.json +3 -4
package/dist/module.d.mts
CHANGED
|
@@ -5,12 +5,13 @@ export { BetterAuthModuleOptions, defineClientAuth, defineServerAuth } from '../
|
|
|
5
5
|
import { BetterAuthOptions } from 'better-auth';
|
|
6
6
|
export { AppSession, Auth, AuthActionError, AuthMeta, AuthMode, AuthRouteRules, AuthSession, AuthUser, InferSession, InferUser, RequireSessionOptions, ServerAuthContext, UserMatch } from '../dist/runtime/types.js';
|
|
7
7
|
|
|
8
|
-
interface
|
|
8
|
+
interface RuntimeDefineServerAuthFn {
|
|
9
9
|
(...args: unknown[]): unknown;
|
|
10
10
|
_count: number;
|
|
11
11
|
}
|
|
12
12
|
declare global {
|
|
13
|
-
var
|
|
13
|
+
var __nuxtBetterAuthDefineServerAuth: RuntimeDefineServerAuthFn | undefined;
|
|
14
|
+
var defineServerAuth: RuntimeDefineServerAuthFn | undefined;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
type DbDialect = 'sqlite' | 'postgresql' | 'mysql';
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { randomBytes } from 'node:crypto';
|
|
|
10
10
|
import { isCI, isTest } from 'std-env';
|
|
11
11
|
export { defineClientAuth, defineServerAuth } from '../dist/runtime/config.js';
|
|
12
12
|
|
|
13
|
-
const version = "0.0.2-alpha.
|
|
13
|
+
const version = "0.0.2-alpha.29";
|
|
14
14
|
|
|
15
15
|
function resolveDatabaseProvider(input) {
|
|
16
16
|
const enabledProviders = Object.entries(input.providers).filter(([_id, provider]) => provider.isEnabled?.(input.context) ?? true);
|
|
@@ -122,26 +122,33 @@ function getHubCasing(hub) {
|
|
|
122
122
|
return hub.db.casing;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
function
|
|
126
|
-
const { options, clientOnly, hasNuxtHub, hub
|
|
127
|
-
|
|
125
|
+
function resolveSecondaryStorage(input) {
|
|
126
|
+
const { options, clientOnly, hasNuxtHub, hub } = input;
|
|
127
|
+
const opt = options.hubSecondaryStorage ?? false;
|
|
128
|
+
const useHubKV = opt === true;
|
|
129
|
+
const secondaryStorageEnabled = opt === true || opt === "custom";
|
|
128
130
|
if (secondaryStorageEnabled && clientOnly) {
|
|
129
|
-
|
|
130
|
-
secondaryStorageEnabled = false;
|
|
131
|
-
} else if (secondaryStorageEnabled && (!hasNuxtHub || !hub?.kv)) {
|
|
132
|
-
consola.warn("secondaryStorage requires @nuxthub/core with hub.kv: true. Disabling.");
|
|
133
|
-
secondaryStorageEnabled = false;
|
|
131
|
+
throw new Error("[nuxt-better-auth] hubSecondaryStorage is not available in clientOnly mode. Either disable clientOnly or remove auth.hubSecondaryStorage.");
|
|
134
132
|
}
|
|
135
|
-
|
|
133
|
+
if (useHubKV && (!hasNuxtHub || !hub?.kv)) {
|
|
134
|
+
throw new Error("[nuxt-better-auth] hubSecondaryStorage: true requires @nuxthub/core with hub.kv: true. Either add hub.kv: true to your nuxt.config or remove auth.hubSecondaryStorage.");
|
|
135
|
+
}
|
|
136
|
+
return { useHubKV, secondaryStorageEnabled };
|
|
136
137
|
}
|
|
137
138
|
function setupRuntimeConfig(input) {
|
|
138
139
|
const { nuxt, options, clientOnly, databaseProvider, consola } = input;
|
|
139
|
-
const secondaryStorageEnabled =
|
|
140
|
+
const { useHubKV, secondaryStorageEnabled } = resolveSecondaryStorage(input);
|
|
140
141
|
nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
|
|
141
142
|
const configuredSiteUrl = nuxt.options.runtimeConfig.public.siteUrl;
|
|
142
143
|
if (!configuredSiteUrl && process.env.NUXT_PUBLIC_SITE_URL)
|
|
143
144
|
nuxt.options.runtimeConfig.public.siteUrl = process.env.NUXT_PUBLIC_SITE_URL;
|
|
144
145
|
nuxt.options.runtimeConfig.public.auth = defu(nuxt.options.runtimeConfig.public.auth, {
|
|
146
|
+
redirects: {
|
|
147
|
+
login: options.redirects?.login ?? "/login",
|
|
148
|
+
guest: options.redirects?.guest ?? "/",
|
|
149
|
+
authenticated: options.redirects?.authenticated,
|
|
150
|
+
logout: options.redirects?.logout
|
|
151
|
+
},
|
|
145
152
|
preserveRedirect: options.preserveRedirect ?? true,
|
|
146
153
|
redirectQueryKey: options.redirectQueryKey ?? "redirect",
|
|
147
154
|
useDatabase: databaseProvider !== "none",
|
|
@@ -156,7 +163,7 @@ function setupRuntimeConfig(input) {
|
|
|
156
163
|
if (!siteUrl)
|
|
157
164
|
consola.warn("clientOnly mode: set runtimeConfig.public.siteUrl (or NUXT_PUBLIC_SITE_URL) to your frontend URL");
|
|
158
165
|
consola.info("clientOnly mode enabled - server utilities (serverAuth, getAppSession, getUserSession, requireUserSession) are not available");
|
|
159
|
-
return { secondaryStorageEnabled };
|
|
166
|
+
return { useHubKV, secondaryStorageEnabled };
|
|
160
167
|
}
|
|
161
168
|
const currentSecret = nuxt.options.runtimeConfig.betterAuthSecret;
|
|
162
169
|
nuxt.options.runtimeConfig.betterAuthSecret = currentSecret || process.env.BETTER_AUTH_SECRET || "";
|
|
@@ -168,9 +175,9 @@ function setupRuntimeConfig(input) {
|
|
|
168
175
|
throw new Error("[nuxt-better-auth] BETTER_AUTH_SECRET must be at least 32 characters for security");
|
|
169
176
|
}
|
|
170
177
|
nuxt.options.runtimeConfig.auth = defu(nuxt.options.runtimeConfig.auth, {
|
|
171
|
-
|
|
178
|
+
hubSecondaryStorage: options.hubSecondaryStorage ?? false
|
|
172
179
|
});
|
|
173
|
-
return { secondaryStorageEnabled };
|
|
180
|
+
return { useHubKV, secondaryStorageEnabled };
|
|
174
181
|
}
|
|
175
182
|
|
|
176
183
|
function dialectToProvider(dialect) {
|
|
@@ -204,13 +211,16 @@ async function generateDrizzleSchema(authOptions, dialect, schemaOptions) {
|
|
|
204
211
|
}
|
|
205
212
|
async function loadUserAuthConfig(configPath, throwOnError = false) {
|
|
206
213
|
const { createJiti } = await import('jiti');
|
|
207
|
-
const { defineServerAuth } = await import('../dist/runtime/config.js');
|
|
214
|
+
const { defineServerAuth: runtimeDefineServerAuth } = await import('../dist/runtime/config.js');
|
|
208
215
|
const jiti = createJiti(import.meta.url, { interopDefault: true, moduleCache: false });
|
|
216
|
+
if (!globalThis.__nuxtBetterAuthDefineServerAuth) {
|
|
217
|
+
runtimeDefineServerAuth._count = 0;
|
|
218
|
+
globalThis.__nuxtBetterAuthDefineServerAuth = runtimeDefineServerAuth;
|
|
219
|
+
}
|
|
209
220
|
if (!globalThis.defineServerAuth) {
|
|
210
|
-
defineServerAuth
|
|
211
|
-
globalThis.defineServerAuth = defineServerAuth;
|
|
221
|
+
globalThis.defineServerAuth = globalThis.__nuxtBetterAuthDefineServerAuth;
|
|
212
222
|
}
|
|
213
|
-
globalThis.
|
|
223
|
+
globalThis.__nuxtBetterAuthDefineServerAuth._count++;
|
|
214
224
|
try {
|
|
215
225
|
const mod = await jiti.import(configPath);
|
|
216
226
|
const configFn = mod.default;
|
|
@@ -229,13 +239,31 @@ async function loadUserAuthConfig(configPath, throwOnError = false) {
|
|
|
229
239
|
consola$1.error("[@onmax/nuxt-better-auth] Failed to load auth config for schema generation. Schema may be incomplete:", error);
|
|
230
240
|
return {};
|
|
231
241
|
} finally {
|
|
232
|
-
globalThis.
|
|
233
|
-
if (
|
|
234
|
-
|
|
242
|
+
const sharedDefineServerAuth = globalThis.__nuxtBetterAuthDefineServerAuth;
|
|
243
|
+
if (sharedDefineServerAuth) {
|
|
244
|
+
sharedDefineServerAuth._count--;
|
|
245
|
+
if (!sharedDefineServerAuth._count) {
|
|
246
|
+
globalThis.__nuxtBetterAuthDefineServerAuth = void 0;
|
|
247
|
+
if (globalThis.defineServerAuth === sharedDefineServerAuth) {
|
|
248
|
+
globalThis.defineServerAuth = void 0;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
235
251
|
}
|
|
236
252
|
}
|
|
237
253
|
}
|
|
238
254
|
|
|
255
|
+
function resolveSchemaSecondaryStorageInjection(hubSecondaryStorage, userHasSecondaryStorage, isProduction) {
|
|
256
|
+
if (hubSecondaryStorage === true)
|
|
257
|
+
return { inject: true };
|
|
258
|
+
if (hubSecondaryStorage !== "custom")
|
|
259
|
+
return { inject: false };
|
|
260
|
+
if (userHasSecondaryStorage)
|
|
261
|
+
return { inject: true };
|
|
262
|
+
const message = '[nuxt-better-auth] hubSecondaryStorage: "custom" requires secondaryStorage in defineServerAuth() to omit the session table from the generated schema.';
|
|
263
|
+
if (isProduction)
|
|
264
|
+
return { inject: false, error: message };
|
|
265
|
+
return { inject: false, warn: message };
|
|
266
|
+
}
|
|
239
267
|
function isInsideNodeModules(path) {
|
|
240
268
|
return path.split(/[\\/]/).includes("node_modules");
|
|
241
269
|
}
|
|
@@ -260,7 +288,7 @@ async function loadAuthOptions(context) {
|
|
|
260
288
|
const plugins = [...userConfig.plugins || [], ...extendedConfig.plugins || []];
|
|
261
289
|
return { userConfig, plugins };
|
|
262
290
|
}
|
|
263
|
-
async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola) {
|
|
291
|
+
async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola, hubSecondaryStorage) {
|
|
264
292
|
const hub = nuxt.options.hub;
|
|
265
293
|
const dialect = getHubDialect(hub);
|
|
266
294
|
if (!dialect || !["sqlite", "postgresql", "mysql"].includes(dialect)) {
|
|
@@ -270,10 +298,16 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola) {
|
|
|
270
298
|
const context = { nuxt, serverConfigPath };
|
|
271
299
|
try {
|
|
272
300
|
const { userConfig, plugins } = await loadAuthOptions(context);
|
|
301
|
+
const userHasSecondaryStorage = userConfig.secondaryStorage != null;
|
|
302
|
+
const secondaryStorageResolution = resolveSchemaSecondaryStorageInjection(hubSecondaryStorage, userHasSecondaryStorage, !nuxt.options.dev);
|
|
303
|
+
if (secondaryStorageResolution.error)
|
|
304
|
+
throw new Error(secondaryStorageResolution.error);
|
|
305
|
+
if (secondaryStorageResolution.warn)
|
|
306
|
+
consola.warn(secondaryStorageResolution.warn);
|
|
273
307
|
const authOptions = {
|
|
274
308
|
...userConfig,
|
|
275
309
|
plugins,
|
|
276
|
-
secondaryStorage:
|
|
310
|
+
secondaryStorage: secondaryStorageResolution.inject ? { get: async (_key) => null, set: async (_key, _value, _ttl) => {
|
|
277
311
|
}, delete: async (_key) => {
|
|
278
312
|
} } : void 0
|
|
279
313
|
};
|
|
@@ -328,13 +362,21 @@ function appendSecretToEnv(rootDir, secret) {
|
|
|
328
362
|
`;
|
|
329
363
|
writeFileSync(envPath, content, "utf-8");
|
|
330
364
|
}
|
|
331
|
-
async function promptForSecret(rootDir, consola) {
|
|
365
|
+
async function promptForSecret(rootDir, consola, options = {}) {
|
|
366
|
+
const configuredSecret = options.configuredSecret?.trim();
|
|
367
|
+
if (configuredSecret)
|
|
368
|
+
return void 0;
|
|
332
369
|
if (process.env.BETTER_AUTH_SECRET || hasEnvSecret(rootDir))
|
|
333
370
|
return void 0;
|
|
371
|
+
const hasTty = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
372
|
+
if (options.prepare || !hasTty) {
|
|
373
|
+
consola.warn("[nuxt-better-auth] Skipping BETTER_AUTH_SECRET prompt (non-interactive). Set BETTER_AUTH_SECRET or NUXT_BETTER_AUTH_SECRET.");
|
|
374
|
+
return void 0;
|
|
375
|
+
}
|
|
334
376
|
if (isCI || isTest) {
|
|
335
377
|
const secret2 = generateSecret();
|
|
336
378
|
appendSecretToEnv(rootDir, secret2);
|
|
337
|
-
consola.info("Generated BETTER_AUTH_SECRET and added to .env (CI mode)");
|
|
379
|
+
consola.info("Generated BETTER_AUTH_SECRET and added to .env (CI/test mode)");
|
|
338
380
|
return secret2;
|
|
339
381
|
}
|
|
340
382
|
consola.box("BETTER_AUTH_SECRET is required for authentication.\nThis will be appended to your .env file.");
|
|
@@ -375,8 +417,8 @@ Proceed?`, { type: "confirm", initial: true, cancel: "null" });
|
|
|
375
417
|
return secret;
|
|
376
418
|
}
|
|
377
419
|
|
|
378
|
-
function buildSecondaryStorageCode(
|
|
379
|
-
if (!
|
|
420
|
+
function buildSecondaryStorageCode(useHubKV) {
|
|
421
|
+
if (!useHubKV)
|
|
380
422
|
return "export function createSecondaryStorage() { return undefined }";
|
|
381
423
|
return `import { kv } from '@nuxthub/kv'
|
|
382
424
|
export function createSecondaryStorage() {
|
|
@@ -587,12 +629,14 @@ const module$1 = defineNuxtModule({
|
|
|
587
629
|
clientOnly: false,
|
|
588
630
|
serverConfig: "server/auth.config",
|
|
589
631
|
clientConfig: "app/auth.config",
|
|
632
|
+
redirects: { login: "/login", guest: "/" },
|
|
590
633
|
preserveRedirect: true,
|
|
591
634
|
redirectQueryKey: "redirect",
|
|
592
|
-
|
|
635
|
+
hubSecondaryStorage: false
|
|
593
636
|
},
|
|
594
637
|
async onInstall(nuxt) {
|
|
595
|
-
const
|
|
638
|
+
const configuredSecret = nuxt.options.runtimeConfig?.betterAuthSecret;
|
|
639
|
+
const generatedSecret = await promptForSecret(nuxt.options.rootDir, consola, { configuredSecret, prepare: Boolean(nuxt.options._prepare) });
|
|
596
640
|
if (generatedSecret)
|
|
597
641
|
process.env.BETTER_AUTH_SECRET = generatedSecret;
|
|
598
642
|
await createDefaultAuthConfigFiles(nuxt.options.rootDir, nuxt.options.srcDir);
|
|
@@ -666,7 +710,7 @@ const module$1 = defineNuxtModule({
|
|
|
666
710
|
const resolvedProvider = resolveDatabaseProvider({ providers, context: enabledCtx });
|
|
667
711
|
databaseProvider = resolvedProvider.id;
|
|
668
712
|
hasHubDb = databaseProvider === "nuxthub";
|
|
669
|
-
const {
|
|
713
|
+
const { useHubKV } = setupRuntimeConfig({
|
|
670
714
|
nuxt,
|
|
671
715
|
options,
|
|
672
716
|
clientOnly,
|
|
@@ -675,12 +719,12 @@ const module$1 = defineNuxtModule({
|
|
|
675
719
|
hub,
|
|
676
720
|
consola
|
|
677
721
|
});
|
|
678
|
-
if (
|
|
722
|
+
if (useHubKV && !nuxt.options.alias["hub:kv"]) {
|
|
679
723
|
throw new Error("[nuxt-better-auth] hub:kv not found. Ensure @nuxthub/core is loaded before this module and hub.kv is enabled.");
|
|
680
724
|
}
|
|
681
725
|
const secondaryStorageTemplate = addTemplate({
|
|
682
726
|
filename: "better-auth/secondary-storage.mjs",
|
|
683
|
-
getContents: () => buildSecondaryStorageCode(
|
|
727
|
+
getContents: () => buildSecondaryStorageCode(useHubKV),
|
|
684
728
|
write: true
|
|
685
729
|
});
|
|
686
730
|
nuxt.options.alias["#auth/secondary-storage"] = secondaryStorageTemplate.dst;
|
|
@@ -715,7 +759,7 @@ export { schema }
|
|
|
715
759
|
runtimeTypesPath: resolver.resolve("./runtime/types")
|
|
716
760
|
});
|
|
717
761
|
if (hasHubDb)
|
|
718
|
-
await setupBetterAuthSchema(nuxt, serverConfigPath, options, consola);
|
|
762
|
+
await setupBetterAuthSchema(nuxt, serverConfigPath, options, consola, options.hubSecondaryStorage ?? false);
|
|
719
763
|
}
|
|
720
764
|
registerSharedTypeTemplates({
|
|
721
765
|
runtimeTypesAugmentPath: resolver.resolve("./runtime/types/augment"),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import createAppAuthClient from "#auth/client";
|
|
2
|
-
import { computed, nextTick, useNuxtApp, useRequestHeaders, useRequestURL, useRuntimeConfig, useState, watch } from "#imports";
|
|
2
|
+
import { computed, navigateTo, nextTick, useNuxtApp, useRequestHeaders, useRequestURL, useRuntimeConfig, useState, watch } from "#imports";
|
|
3
3
|
import { normalizeAuthActionError } from "../internal/auth-action-error.js";
|
|
4
4
|
let _sessionSignalListenerBound = false;
|
|
5
5
|
let _client = null;
|
|
@@ -128,6 +128,30 @@ export function useUserSession() {
|
|
|
128
128
|
}, 5e3);
|
|
129
129
|
});
|
|
130
130
|
}
|
|
131
|
+
function isSafeLocalRedirect(redirect) {
|
|
132
|
+
if (typeof redirect !== "string")
|
|
133
|
+
return;
|
|
134
|
+
if (!redirect.startsWith("/") || redirect.startsWith("//"))
|
|
135
|
+
return;
|
|
136
|
+
return redirect;
|
|
137
|
+
}
|
|
138
|
+
function resolvePostAuthRedirect() {
|
|
139
|
+
const authConfig = runtimeConfig.public.auth;
|
|
140
|
+
const redirectQueryKey = authConfig?.redirectQueryKey ?? "redirect";
|
|
141
|
+
const queryRedirect = requestURL.searchParams?.get(redirectQueryKey);
|
|
142
|
+
const safeQueryRedirect = isSafeLocalRedirect(queryRedirect);
|
|
143
|
+
if (safeQueryRedirect)
|
|
144
|
+
return safeQueryRedirect;
|
|
145
|
+
return isSafeLocalRedirect(authConfig?.redirects?.authenticated);
|
|
146
|
+
}
|
|
147
|
+
function resolvePostAuthSuccessRedirect() {
|
|
148
|
+
const target = resolvePostAuthRedirect();
|
|
149
|
+
if (!target)
|
|
150
|
+
return;
|
|
151
|
+
return async () => {
|
|
152
|
+
await navigateTo(target);
|
|
153
|
+
};
|
|
154
|
+
}
|
|
131
155
|
function wrapOnSuccess(cb) {
|
|
132
156
|
return async (ctx) => {
|
|
133
157
|
await fetchSession({ force: true });
|
|
@@ -146,6 +170,12 @@ export function useUserSession() {
|
|
|
146
170
|
const fetchOptions = isRecord(dataRecord?.fetchOptions) ? dataRecord.fetchOptions : void 0;
|
|
147
171
|
const nestedOnSuccess = fetchOptions?.onSuccess;
|
|
148
172
|
const topLevelOnSuccess = optionsRecord?.onSuccess;
|
|
173
|
+
const fallbackOnSuccess = resolvePostAuthSuccessRedirect();
|
|
174
|
+
const wrappedFallbackOnSuccess = fallbackOnSuccess && wrapOnSuccess(async () => {
|
|
175
|
+
if (!loggedIn.value)
|
|
176
|
+
return;
|
|
177
|
+
await fallbackOnSuccess();
|
|
178
|
+
});
|
|
149
179
|
if (typeof nestedOnSuccess === "function") {
|
|
150
180
|
const nextData = {
|
|
151
181
|
...dataRecord,
|
|
@@ -163,6 +193,23 @@ export function useUserSession() {
|
|
|
163
193
|
};
|
|
164
194
|
return method(data, nextOptions);
|
|
165
195
|
}
|
|
196
|
+
if (wrappedFallbackOnSuccess) {
|
|
197
|
+
if (fetchOptions) {
|
|
198
|
+
const nextData = {
|
|
199
|
+
...dataRecord,
|
|
200
|
+
fetchOptions: {
|
|
201
|
+
...fetchOptions,
|
|
202
|
+
onSuccess: wrappedFallbackOnSuccess
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
return method(nextData, options);
|
|
206
|
+
}
|
|
207
|
+
const nextOptions = {
|
|
208
|
+
...optionsRecord,
|
|
209
|
+
onSuccess: wrappedFallbackOnSuccess
|
|
210
|
+
};
|
|
211
|
+
return method(data, nextOptions);
|
|
212
|
+
}
|
|
166
213
|
return method(data, options);
|
|
167
214
|
});
|
|
168
215
|
}
|
|
@@ -229,8 +276,14 @@ export function useUserSession() {
|
|
|
229
276
|
throw new Error("signOut can only be called on client-side");
|
|
230
277
|
await client.signOut();
|
|
231
278
|
clearSession();
|
|
232
|
-
if (options?.onSuccess)
|
|
279
|
+
if (options?.onSuccess) {
|
|
233
280
|
await options.onSuccess();
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const authConfig = runtimeConfig.public.auth;
|
|
284
|
+
const logoutRedirect = authConfig?.redirects?.logout;
|
|
285
|
+
if (logoutRedirect)
|
|
286
|
+
await navigateTo(logoutRedirect);
|
|
234
287
|
}
|
|
235
288
|
return {
|
|
236
289
|
client,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { AppAuthClient } from '#nuxt-better-auth';
|
|
2
|
-
import type {
|
|
3
|
-
|
|
2
|
+
import type { ActionHandleFor } from '../internal/auth-action-handles.js';
|
|
3
|
+
type SignIn = NonNullable<AppAuthClient>['signIn'];
|
|
4
|
+
export declare function useUserSignIn<MethodKey extends keyof SignIn>(method: MethodKey): ActionHandleFor<SignIn[MethodKey]>;
|
|
5
|
+
export {};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { useUserSession } from "#imports";
|
|
2
2
|
import { createActionHandles } from "../internal/auth-action-handles.js";
|
|
3
|
-
export function useUserSignIn() {
|
|
4
|
-
|
|
3
|
+
export function useUserSignIn(method) {
|
|
4
|
+
if (method === void 0 || method === null)
|
|
5
|
+
throw new TypeError("useUserSignIn(method) requires a sign-in method key");
|
|
6
|
+
const handles = createActionHandles(() => useUserSession().signIn, "signIn");
|
|
7
|
+
return handles[method];
|
|
5
8
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { AppAuthClient } from '#nuxt-better-auth';
|
|
2
|
-
import type {
|
|
3
|
-
|
|
2
|
+
import type { ActionHandleFor } from '../internal/auth-action-handles.js';
|
|
3
|
+
type SignUp = NonNullable<AppAuthClient>['signUp'];
|
|
4
|
+
export declare function useUserSignUp<MethodKey extends keyof SignUp>(method: MethodKey): ActionHandleFor<SignUp[MethodKey]>;
|
|
5
|
+
export {};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { useUserSession } from "#imports";
|
|
2
2
|
import { createActionHandles } from "../internal/auth-action-handles.js";
|
|
3
|
-
export function useUserSignUp() {
|
|
4
|
-
|
|
3
|
+
export function useUserSignUp(method) {
|
|
4
|
+
if (method === void 0 || method === null)
|
|
5
|
+
throw new TypeError("useUserSignUp(method) requires a sign-up method key");
|
|
6
|
+
const handles = createActionHandles(() => useUserSession().signUp, "signUp");
|
|
7
|
+
return handles[method];
|
|
5
8
|
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Ref } from 'vue';
|
|
2
2
|
import type { AuthActionError } from '../../types.js';
|
|
3
3
|
export type UserAuthActionStatus = 'idle' | 'pending' | 'success' | 'error';
|
|
4
4
|
export interface UserAuthActionHandle<TArgs extends unknown[], TResult> {
|
|
5
5
|
execute: (...args: TArgs) => Promise<void>;
|
|
6
6
|
status: Ref<UserAuthActionStatus>;
|
|
7
|
-
pending: ComputedRef<boolean>;
|
|
8
7
|
data: Ref<TResult | null>;
|
|
9
8
|
error: Ref<AuthActionError | null>;
|
|
10
|
-
errorMessage: ComputedRef<string | null>;
|
|
11
9
|
}
|
|
12
10
|
export type ActionHandleFor<T> = T extends (...args: infer A) => Promise<infer R> ? UserAuthActionHandle<A, R> : never;
|
|
13
11
|
export type ActionHandleMap<T> = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ref } from "#imports";
|
|
2
2
|
import { normalizeAuthActionError } from "./auth-action-error.js";
|
|
3
3
|
function isRecord(value) {
|
|
4
4
|
return Boolean(value && typeof value === "object");
|
|
@@ -14,8 +14,6 @@ function createActionHandle(getMethod) {
|
|
|
14
14
|
const status = ref("idle");
|
|
15
15
|
const data = ref(null);
|
|
16
16
|
const error = ref(null);
|
|
17
|
-
const pending = computed(() => status.value === "pending");
|
|
18
|
-
const errorMessage = computed(() => error.value?.message ?? null);
|
|
19
17
|
let latestCallId = 0;
|
|
20
18
|
const run = async (...args) => {
|
|
21
19
|
const callId = ++latestCallId;
|
|
@@ -53,10 +51,8 @@ function createActionHandle(getMethod) {
|
|
|
53
51
|
return {
|
|
54
52
|
execute,
|
|
55
53
|
status,
|
|
56
|
-
pending,
|
|
57
54
|
data,
|
|
58
|
-
error
|
|
59
|
-
errorMessage
|
|
55
|
+
error
|
|
60
56
|
};
|
|
61
57
|
}
|
|
62
58
|
export function createActionHandles(getTarget, targetName) {
|
|
@@ -25,13 +25,13 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
|
|
25
25
|
const redirectTo = typeof auth === "object" ? auth.redirectTo : void 0;
|
|
26
26
|
if (mode === "guest") {
|
|
27
27
|
if (loggedIn.value)
|
|
28
|
-
return navigateTo(redirectTo ?? "/");
|
|
28
|
+
return navigateTo(redirectTo ?? config?.redirects?.guest ?? "/");
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
31
|
if (!loggedIn.value) {
|
|
32
32
|
const resolved = resolveLoginRedirect({
|
|
33
33
|
route: to,
|
|
34
|
-
loginTarget: redirectTo ?? "/login",
|
|
34
|
+
loginTarget: redirectTo ?? config?.redirects?.login ?? "/login",
|
|
35
35
|
config
|
|
36
36
|
});
|
|
37
37
|
return resolved.external ? navigateTo(resolved.to, { external: true }) : navigateTo(resolved.to);
|
|
@@ -379,8 +379,8 @@ function getAccountActions(row) {
|
|
|
379
379
|
</UBadge>
|
|
380
380
|
</div>
|
|
381
381
|
<div class="config-row">
|
|
382
|
-
<span class="config-label">KV</span><UBadge :color="configData.config.module?.
|
|
383
|
-
{{ configData.config.module?.
|
|
382
|
+
<span class="config-label">KV</span><UBadge :color="configData.config.module?.hubSecondaryStorage ? 'success' : 'neutral'" variant="subtle" size="sm">
|
|
383
|
+
{{ configData.config.module?.hubSecondaryStorage ? configData.config.module.hubSecondaryStorage === "custom" ? "Custom" : "On" : "Off" }}
|
|
384
384
|
</UBadge>
|
|
385
385
|
</div>
|
|
386
386
|
</div>
|
package/dist/runtime/config.d.ts
CHANGED
|
@@ -24,6 +24,16 @@ export interface BetterAuthModuleOptions {
|
|
|
24
24
|
serverConfig?: string;
|
|
25
25
|
/** Client config path relative to rootDir. Default: 'app/auth.config' */
|
|
26
26
|
clientConfig?: string;
|
|
27
|
+
redirects?: {
|
|
28
|
+
/** Where to redirect unauthenticated users. Default: '/login' */
|
|
29
|
+
login?: string;
|
|
30
|
+
/** Where to redirect authenticated users on guest-only routes. Default: '/' */
|
|
31
|
+
guest?: string;
|
|
32
|
+
/** Where to navigate after successful signIn/signUp when no onSuccess is provided. Default: no automatic navigation */
|
|
33
|
+
authenticated?: string;
|
|
34
|
+
/** Where to navigate after logout. Default: no automatic navigation */
|
|
35
|
+
logout?: string;
|
|
36
|
+
};
|
|
27
37
|
/**
|
|
28
38
|
* When redirecting unauthenticated users to the login route, append a query param
|
|
29
39
|
* containing the originally requested path (for safe "return-to" redirects).
|
|
@@ -47,8 +57,13 @@ export interface BetterAuthModuleOptions {
|
|
|
47
57
|
*/
|
|
48
58
|
skipHydratedSsrGetSession?: boolean;
|
|
49
59
|
};
|
|
50
|
-
/**
|
|
51
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Enable secondary storage for sessions.
|
|
62
|
+
* - `true`: Use NuxtHub KV (requires hub.kv: true)
|
|
63
|
+
* - `'custom'`: User provides own secondaryStorage in defineServerAuth()
|
|
64
|
+
* - `false` (default): No secondary storage from module
|
|
65
|
+
*/
|
|
66
|
+
hubSecondaryStorage?: boolean | 'custom';
|
|
52
67
|
/** Schema generation options. Must match drizzleAdapter config. */
|
|
53
68
|
schema?: {
|
|
54
69
|
/** Plural table names: user → users. Default: false */
|
|
@@ -58,6 +73,12 @@ export interface BetterAuthModuleOptions {
|
|
|
58
73
|
};
|
|
59
74
|
}
|
|
60
75
|
export interface AuthRuntimeConfig {
|
|
76
|
+
redirects: {
|
|
77
|
+
login: string;
|
|
78
|
+
guest: string;
|
|
79
|
+
authenticated?: string;
|
|
80
|
+
logout?: string;
|
|
81
|
+
};
|
|
61
82
|
preserveRedirect: boolean;
|
|
62
83
|
redirectQueryKey: string;
|
|
63
84
|
useDatabase: boolean;
|
|
@@ -68,7 +89,7 @@ export interface AuthRuntimeConfig {
|
|
|
68
89
|
};
|
|
69
90
|
}
|
|
70
91
|
export interface AuthPrivateRuntimeConfig {
|
|
71
|
-
|
|
92
|
+
hubSecondaryStorage: boolean | 'custom';
|
|
72
93
|
}
|
|
73
94
|
export declare function defineServerAuth<const R>(config: (ctx: ServerAuthContext) => R & ServerAuthConfig): (ctx: ServerAuthContext) => R;
|
|
74
95
|
export declare function defineServerAuth<const R>(config: R & ServerAuthConfig): (ctx: ServerAuthContext) => R;
|
|
@@ -1,29 +1,35 @@
|
|
|
1
1
|
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
|
|
2
2
|
config: {
|
|
3
3
|
module: {
|
|
4
|
+
redirects: {
|
|
5
|
+
login: string;
|
|
6
|
+
guest: string;
|
|
7
|
+
authenticated: string | undefined;
|
|
8
|
+
logout: string | undefined;
|
|
9
|
+
};
|
|
4
10
|
preserveRedirect: boolean;
|
|
5
11
|
redirectQueryKey: string;
|
|
6
|
-
|
|
12
|
+
hubSecondaryStorage: boolean | "custom";
|
|
7
13
|
useDatabase: boolean;
|
|
8
14
|
databaseProvider: "none" | "nuxthub";
|
|
9
15
|
};
|
|
10
16
|
server: {
|
|
11
|
-
baseURL:
|
|
12
|
-
basePath:
|
|
17
|
+
baseURL: string | undefined;
|
|
18
|
+
basePath: string;
|
|
13
19
|
socialProviders: string[];
|
|
14
|
-
plugins:
|
|
15
|
-
trustedOrigins:
|
|
16
|
-
configuredTrustedOrigins:
|
|
20
|
+
plugins: string[];
|
|
21
|
+
trustedOrigins: string[];
|
|
22
|
+
configuredTrustedOrigins: string[];
|
|
17
23
|
session: {
|
|
18
24
|
expiresIn: string;
|
|
19
25
|
updateAge: string;
|
|
20
|
-
cookieCache:
|
|
26
|
+
cookieCache: boolean;
|
|
21
27
|
};
|
|
22
28
|
emailAndPassword: boolean;
|
|
23
|
-
rateLimit:
|
|
29
|
+
rateLimit: boolean;
|
|
24
30
|
advanced: {
|
|
25
|
-
useSecureCookies:
|
|
26
|
-
disableCSRFCheck:
|
|
31
|
+
useSecureCookies: string | boolean;
|
|
32
|
+
disableCSRFCheck: boolean;
|
|
27
33
|
};
|
|
28
34
|
};
|
|
29
35
|
};
|
|
@@ -18,9 +18,15 @@ export default defineEventHandler(async (event) => {
|
|
|
18
18
|
config: {
|
|
19
19
|
// Module config (nuxt.config.ts)
|
|
20
20
|
module: {
|
|
21
|
+
redirects: {
|
|
22
|
+
login: publicAuth?.redirects?.login ?? "/login",
|
|
23
|
+
guest: publicAuth?.redirects?.guest ?? "/",
|
|
24
|
+
authenticated: publicAuth?.redirects?.authenticated,
|
|
25
|
+
logout: publicAuth?.redirects?.logout
|
|
26
|
+
},
|
|
21
27
|
preserveRedirect: publicAuth?.preserveRedirect ?? true,
|
|
22
28
|
redirectQueryKey: publicAuth?.redirectQueryKey ?? "redirect",
|
|
23
|
-
|
|
29
|
+
hubSecondaryStorage: privateAuth?.hubSecondaryStorage ?? false,
|
|
24
30
|
useDatabase: publicAuth?.useDatabase ?? false,
|
|
25
31
|
databaseProvider: publicAuth?.databaseProvider ?? "none"
|
|
26
32
|
},
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type { Auth } from 'better-auth';
|
|
2
1
|
import type { H3Event } from 'h3';
|
|
3
|
-
import
|
|
4
|
-
type AuthInstance =
|
|
2
|
+
import { betterAuth } from 'better-auth';
|
|
3
|
+
type AuthInstance = ReturnType<typeof betterAuth>;
|
|
5
4
|
/** Returns Better Auth instance. Caches per resolved host (or single instance when siteUrl is explicit). */
|
|
6
5
|
export declare function serverAuth(event?: H3Event): AuthInstance;
|
|
7
6
|
export {};
|
|
@@ -5,8 +5,10 @@ import { betterAuth } from "better-auth";
|
|
|
5
5
|
import { getRequestHost, getRequestProtocol } from "h3";
|
|
6
6
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
7
7
|
import { withoutProtocol } from "ufo";
|
|
8
|
+
import { resolveCustomSecondaryStorageRequirement } from "./custom-secondary-storage.js";
|
|
8
9
|
const _authCache = /* @__PURE__ */ new Map();
|
|
9
10
|
let _baseURLInferenceLogged = false;
|
|
11
|
+
let _customSecondaryStorageMisconfigWarned = false;
|
|
10
12
|
function normalizeLoopbackOrigin(origin) {
|
|
11
13
|
if (!import.meta.dev)
|
|
12
14
|
return origin;
|
|
@@ -189,10 +191,18 @@ export function serverAuth(event) {
|
|
|
189
191
|
const database = createDatabase();
|
|
190
192
|
const userConfig = createServerAuth({ runtimeConfig, db });
|
|
191
193
|
const trustedOrigins = withDevTrustedOrigins(userConfig.trustedOrigins, Boolean(hasExplicitSiteUrl));
|
|
194
|
+
const hubSecondaryStorage = runtimeConfig.auth?.hubSecondaryStorage;
|
|
195
|
+
const customSecondaryStorage = resolveCustomSecondaryStorageRequirement(hubSecondaryStorage, userConfig.secondaryStorage != null, Boolean(import.meta.dev));
|
|
196
|
+
if (customSecondaryStorage?.shouldThrow)
|
|
197
|
+
throw new Error(customSecondaryStorage.message);
|
|
198
|
+
if (customSecondaryStorage?.shouldWarn && !_customSecondaryStorageMisconfigWarned) {
|
|
199
|
+
_customSecondaryStorageMisconfigWarned = true;
|
|
200
|
+
console.warn(customSecondaryStorage.message);
|
|
201
|
+
}
|
|
192
202
|
const auth = betterAuth({
|
|
193
203
|
...userConfig,
|
|
194
204
|
...database && { database },
|
|
195
|
-
secondaryStorage: createSecondaryStorage(),
|
|
205
|
+
...hubSecondaryStorage === true && { secondaryStorage: createSecondaryStorage() },
|
|
196
206
|
secret: runtimeConfig.betterAuthSecret,
|
|
197
207
|
baseURL: siteUrl,
|
|
198
208
|
trustedOrigins
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type HubSecondaryStorageMode = boolean | 'custom' | undefined;
|
|
2
|
+
export declare function resolveCustomSecondaryStorageRequirement(hubSecondaryStorage: HubSecondaryStorageMode, userHasSecondaryStorage: boolean, isDev: boolean): {
|
|
3
|
+
shouldThrow: boolean;
|
|
4
|
+
shouldWarn: boolean;
|
|
5
|
+
message: string;
|
|
6
|
+
} | null;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function resolveCustomSecondaryStorageRequirement(hubSecondaryStorage, userHasSecondaryStorage, isDev) {
|
|
2
|
+
if (hubSecondaryStorage !== "custom")
|
|
3
|
+
return null;
|
|
4
|
+
if (userHasSecondaryStorage)
|
|
5
|
+
return null;
|
|
6
|
+
const message = '[nuxt-better-auth] hubSecondaryStorage: "custom" requires secondaryStorage in defineServerAuth().';
|
|
7
|
+
return { shouldThrow: !isDev, shouldWarn: isDev, message };
|
|
8
|
+
}
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import type { AuthUser, UserMatch } from '#nuxt-better-auth';
|
|
1
2
|
import type { NitroRouteRules } from 'nitropack/types';
|
|
2
|
-
import type { AuthUser, UserMatch } from './types/augment.js';
|
|
3
3
|
export type { AppSession, AuthSession, AuthUser, RequireSessionOptions, ServerAuthContext, UserMatch, UserSessionComposable } from './types/augment.js';
|
|
4
4
|
export type { Auth, InferPluginTypes, InferSessionFromClient as InferSession, InferUserFromClient as InferUser } from 'better-auth';
|
|
5
5
|
export interface AuthActionError {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onmax/nuxt-better-auth",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.2-alpha.
|
|
4
|
+
"version": "0.0.2-alpha.29",
|
|
5
5
|
"packageManager": "pnpm@10.15.1",
|
|
6
6
|
"description": "Nuxt module for Better Auth integration with NuxtHub, route protection, session management, and role-based access",
|
|
7
7
|
"author": "onmax",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"lint:fix": "eslint . --fix",
|
|
57
57
|
"typecheck": "vue-tsc --noEmit",
|
|
58
58
|
"typecheck:runtime-server": "tsc --noEmit --pretty false -p src/runtime/server/tsconfig.json",
|
|
59
|
-
"typecheck:playground": "
|
|
59
|
+
"typecheck:playground": "pnpm -C playground exec nuxi prepare && pnpm -C playground exec vue-tsc --noEmit -p .nuxt/tsconfig.app.json",
|
|
60
60
|
"test": "vitest run",
|
|
61
61
|
"test:watch": "vitest watch"
|
|
62
62
|
},
|
|
@@ -116,8 +116,7 @@
|
|
|
116
116
|
"workerd"
|
|
117
117
|
],
|
|
118
118
|
"patchedDependencies": {
|
|
119
|
-
"@peculiar/x509@1.14.2": "patches/@peculiar__x509@1.14.2.patch"
|
|
120
|
-
"unenv@2.0.0-rc.24": "patches/unenv@2.0.0-rc.24.patch"
|
|
119
|
+
"@peculiar/x509@1.14.2": "patches/@peculiar__x509@1.14.2.patch"
|
|
121
120
|
},
|
|
122
121
|
"overrides": {
|
|
123
122
|
"reka-ui": "^2.6.1"
|