@onmax/nuxt-better-auth 0.0.2-alpha.22 → 0.0.2-alpha.24
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 +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +75 -16
- package/dist/runtime/app/composables/useUserSession.js +31 -7
- package/dist/runtime/app/composables/useUserSignIn.d.ts +3 -0
- package/dist/runtime/app/composables/useUserSignIn.js +5 -0
- package/dist/runtime/app/composables/useUserSignUp.d.ts +3 -0
- package/dist/runtime/app/composables/useUserSignUp.js +5 -0
- package/dist/runtime/app/internal/auth-action-handles.d.ts +13 -0
- package/dist/runtime/app/internal/auth-action-handles.js +62 -0
- package/dist/runtime/app/middleware/auth.global.js +38 -2
- package/dist/runtime/app/pages/__better-auth-devtools.vue +1 -1
- package/dist/runtime/config.d.ts +17 -4
- package/dist/runtime/server/api/_better-auth/accounts.get.js +3 -2
- package/dist/runtime/server/api/_better-auth/config.get.d.ts +3 -1
- package/dist/runtime/server/api/_better-auth/config.get.js +2 -0
- package/dist/runtime/server/api/_better-auth/sessions.delete.js +3 -2
- package/dist/runtime/server/api/_better-auth/sessions.get.d.ts +3 -1
- package/dist/runtime/server/api/_better-auth/sessions.get.js +3 -2
- package/dist/runtime/server/api/_better-auth/users.get.js +3 -2
- package/dist/runtime/server/middleware/route-access.js +1 -0
- package/dist/runtime/server/tsconfig.json +9 -1
- package/dist/runtime/server/utils/session.d.ts +4 -8
- package/dist/runtime/server/utils/session.js +42 -4
- package/dist/runtime/server/virtual-modules.d.ts +22 -0
- package/dist/runtime/types/augment.d.ts +14 -0
- package/dist/runtime/types.d.ts +2 -12
- package/dist/types.d.mts +1 -1
- package/package.json +2 -1
package/dist/module.d.mts
CHANGED
|
@@ -3,7 +3,7 @@ import { Nuxt } from '@nuxt/schema';
|
|
|
3
3
|
import { BetterAuthModuleOptions } from '../dist/runtime/config.js';
|
|
4
4
|
export { BetterAuthModuleOptions, defineClientAuth, defineServerAuth } from '../dist/runtime/config.js';
|
|
5
5
|
import { BetterAuthOptions } from 'better-auth';
|
|
6
|
-
export { Auth, AuthMeta, AuthMode, AuthRouteRules, AuthSession, AuthUser, InferSession, InferUser, RequireSessionOptions, ServerAuthContext, UserMatch } from '../dist/runtime/types.js';
|
|
6
|
+
export { AppSession, Auth, AuthMeta, AuthMode, AuthRouteRules, AuthSession, AuthUser, InferSession, InferUser, RequireSessionOptions, ServerAuthContext, UserMatch } from '../dist/runtime/types.js';
|
|
7
7
|
|
|
8
8
|
interface DefineServerAuthFn {
|
|
9
9
|
(...args: unknown[]): unknown;
|
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.24";
|
|
14
14
|
|
|
15
15
|
function resolveDatabaseProvider(input) {
|
|
16
16
|
const enabledProviders = Object.entries(input.providers).filter(([_id, provider]) => provider.isEnabled?.(input.context) ?? true);
|
|
@@ -23,7 +23,8 @@ function resolveDatabaseProvider(input) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function setupDevTools(nuxt) {
|
|
26
|
-
|
|
26
|
+
const hookable = nuxt;
|
|
27
|
+
hookable.hook("devtools:customTabs", (tabs) => {
|
|
27
28
|
tabs.push({
|
|
28
29
|
category: "server",
|
|
29
30
|
name: "better-auth",
|
|
@@ -142,9 +143,10 @@ function setupRuntimeConfig(input) {
|
|
|
142
143
|
nuxt.options.runtimeConfig.public.siteUrl = process.env.NUXT_PUBLIC_SITE_URL;
|
|
143
144
|
nuxt.options.runtimeConfig.public.auth = defu(nuxt.options.runtimeConfig.public.auth, {
|
|
144
145
|
redirects: { login: options.redirects?.login ?? "/login", guest: options.redirects?.guest ?? "/" },
|
|
146
|
+
preserveRedirect: options.preserveRedirect ?? true,
|
|
147
|
+
redirectQueryKey: options.redirectQueryKey ?? "redirect",
|
|
145
148
|
useDatabase: databaseProvider !== "none",
|
|
146
149
|
databaseProvider,
|
|
147
|
-
databaseSource: "module",
|
|
148
150
|
clientOnly,
|
|
149
151
|
session: {
|
|
150
152
|
skipHydratedSsrGetSession: options.session?.skipHydratedSsrGetSession ?? false
|
|
@@ -154,7 +156,7 @@ function setupRuntimeConfig(input) {
|
|
|
154
156
|
const siteUrl = nuxt.options.runtimeConfig.public.siteUrl;
|
|
155
157
|
if (!siteUrl)
|
|
156
158
|
consola.warn("clientOnly mode: set runtimeConfig.public.siteUrl (or NUXT_PUBLIC_SITE_URL) to your frontend URL");
|
|
157
|
-
consola.info("clientOnly mode enabled - server utilities (serverAuth, getUserSession, requireUserSession) are not available");
|
|
159
|
+
consola.info("clientOnly mode enabled - server utilities (serverAuth, getAppSession, getUserSession, requireUserSession) are not available");
|
|
158
160
|
return { secondaryStorageEnabled };
|
|
159
161
|
}
|
|
160
162
|
const currentSecret = nuxt.options.runtimeConfig.betterAuthSecret;
|
|
@@ -235,6 +237,21 @@ async function loadUserAuthConfig(configPath, throwOnError = false) {
|
|
|
235
237
|
}
|
|
236
238
|
}
|
|
237
239
|
|
|
240
|
+
function isInsideNodeModules(path) {
|
|
241
|
+
return path.split(/[\\/]/).includes("node_modules");
|
|
242
|
+
}
|
|
243
|
+
function resolveHubSchemaPath(buildDir, rootDir, dialect, exists = existsSync) {
|
|
244
|
+
const rootTsPath = join(rootDir, ".nuxt", "better-auth", `schema.${dialect}.ts`);
|
|
245
|
+
if (isInsideNodeModules(buildDir) && exists(rootTsPath))
|
|
246
|
+
return rootTsPath;
|
|
247
|
+
const tsPath = join(buildDir, "better-auth", `schema.${dialect}.ts`);
|
|
248
|
+
if (exists(tsPath))
|
|
249
|
+
return tsPath;
|
|
250
|
+
const mjsPath = join(buildDir, "better-auth", `schema.${dialect}.mjs`);
|
|
251
|
+
if (exists(mjsPath))
|
|
252
|
+
return mjsPath;
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
238
255
|
async function loadAuthOptions(context) {
|
|
239
256
|
const isProduction = !context.nuxt.options.dev;
|
|
240
257
|
const configFile = `${context.serverConfigPath}.ts`;
|
|
@@ -270,25 +287,27 @@ async function setupBetterAuthSchema(nuxt, serverConfigPath, options, consola) {
|
|
|
270
287
|
await mkdir(schemaDir, { recursive: true });
|
|
271
288
|
await writeFile(schemaPathTs, schemaCode);
|
|
272
289
|
await writeFile(schemaPathMjs, schemaCode);
|
|
290
|
+
if (isInsideNodeModules(nuxt.options.buildDir)) {
|
|
291
|
+
const rootSchemaDir = join(nuxt.options.rootDir, ".nuxt", "better-auth");
|
|
292
|
+
const rootSchemaPathTs = join(rootSchemaDir, `schema.${dialect}.ts`);
|
|
293
|
+
await mkdir(rootSchemaDir, { recursive: true });
|
|
294
|
+
await writeFile(rootSchemaPathTs, schemaCode);
|
|
295
|
+
}
|
|
273
296
|
addTemplate({ filename: `better-auth/schema.${dialect}.ts`, getContents: () => schemaCode, write: true });
|
|
274
297
|
addTemplate({ filename: `better-auth/schema.${dialect}.mjs`, getContents: () => schemaCode, write: true });
|
|
275
298
|
consola.info(`Generated ${dialect} schema (.ts + .mjs)`);
|
|
276
299
|
const nuxtWithHubHooks = nuxt;
|
|
277
300
|
nuxtWithHubHooks.hook("hub:db:schema:extend", ({ paths, dialect: hookDialect }) => {
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
paths.unshift(tsPath);
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
if (existsSync(mjsPath))
|
|
285
|
-
paths.unshift(mjsPath);
|
|
301
|
+
const schemaPath = resolveHubSchemaPath(nuxt.options.buildDir, nuxt.options.rootDir, hookDialect);
|
|
302
|
+
if (schemaPath)
|
|
303
|
+
paths.unshift(schemaPath);
|
|
286
304
|
});
|
|
287
305
|
} catch (error) {
|
|
288
306
|
const isProduction = !nuxt.options.dev;
|
|
289
307
|
if (isProduction)
|
|
290
308
|
throw error;
|
|
291
309
|
consola.error("Failed to generate schema:", error);
|
|
310
|
+
throw error;
|
|
292
311
|
}
|
|
293
312
|
}
|
|
294
313
|
|
|
@@ -371,7 +390,8 @@ export function createSecondaryStorage() {
|
|
|
371
390
|
}
|
|
372
391
|
function buildDatabaseCode(input) {
|
|
373
392
|
if (input.provider === "nuxthub") {
|
|
374
|
-
return `import { db
|
|
393
|
+
return `import { db } from '@nuxthub/db'
|
|
394
|
+
import * as schema from './schema.${input.hubDialect}.mjs'
|
|
375
395
|
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
|
|
376
396
|
const rawDialect = '${input.hubDialect}'
|
|
377
397
|
const dialect = rawDialect === 'postgresql' ? 'pg' : rawDialect
|
|
@@ -405,6 +425,24 @@ declare module '#auth/database' {
|
|
|
405
425
|
export function createDatabase(): BetterAuthOptions['database']
|
|
406
426
|
export const db: ${hasHubDb ? `typeof import('@nuxthub/db')['db']` : "undefined"}
|
|
407
427
|
}
|
|
428
|
+
`
|
|
429
|
+
}, { nitro: true, node: true });
|
|
430
|
+
addTypeTemplate({
|
|
431
|
+
filename: "types/auth-schema.d.ts",
|
|
432
|
+
getContents: () => `
|
|
433
|
+
declare module '#auth/schema' {
|
|
434
|
+
export const user: any
|
|
435
|
+
export const session: any
|
|
436
|
+
export const account: any
|
|
437
|
+
export const verification: any
|
|
438
|
+
export const schema: {
|
|
439
|
+
user: any
|
|
440
|
+
session: any
|
|
441
|
+
account: any
|
|
442
|
+
verification: any
|
|
443
|
+
[key: string]: any
|
|
444
|
+
} | undefined
|
|
445
|
+
}
|
|
408
446
|
`
|
|
409
447
|
}, { nitro: true, node: true });
|
|
410
448
|
addTypeTemplate({
|
|
@@ -459,8 +497,8 @@ declare module '@onmax/nuxt-better-auth/config' {
|
|
|
459
497
|
type ServerAuthConfig = Omit<BetterAuthOptions, 'secret' | 'baseURL'> & {
|
|
460
498
|
plugins?: readonly BetterAuthPlugin[]
|
|
461
499
|
}
|
|
462
|
-
export function defineServerAuth<const R
|
|
463
|
-
export function defineServerAuth<const R
|
|
500
|
+
export function defineServerAuth<const R>(config: (ctx: _AugmentedServerAuthContext) => R & ServerAuthConfig): (ctx: _AugmentedServerAuthContext) => R
|
|
501
|
+
export function defineServerAuth<const R>(config: R & ServerAuthConfig): (ctx: _AugmentedServerAuthContext) => R
|
|
464
502
|
}
|
|
465
503
|
`
|
|
466
504
|
}, { nuxt: true, nitro: true, node: true });
|
|
@@ -491,8 +529,14 @@ function registerSharedTypeTemplates(input) {
|
|
|
491
529
|
addTypeTemplate({
|
|
492
530
|
filename: "types/nuxt-better-auth.d.ts",
|
|
493
531
|
getContents: () => `
|
|
532
|
+
import type { AppSession } from '${input.runtimeTypesAugmentPath}'
|
|
494
533
|
export * from '${input.runtimeTypesAugmentPath}'
|
|
495
|
-
export type { AuthMeta, AuthMode, AuthRouteRules,
|
|
534
|
+
export type { AuthMeta, AuthMode, AuthRouteRules, Auth, InferUser, InferSession } from '${input.runtimeTypesPath}'
|
|
535
|
+
declare module 'h3' {
|
|
536
|
+
interface H3EventContext {
|
|
537
|
+
appSession?: AppSession | null
|
|
538
|
+
}
|
|
539
|
+
}
|
|
496
540
|
`
|
|
497
541
|
});
|
|
498
542
|
addTypeTemplate({
|
|
@@ -545,6 +589,8 @@ const module$1 = defineNuxtModule({
|
|
|
545
589
|
serverConfig: "server/auth.config",
|
|
546
590
|
clientConfig: "app/auth.config",
|
|
547
591
|
redirects: { login: "/login", guest: "/" },
|
|
592
|
+
preserveRedirect: true,
|
|
593
|
+
redirectQueryKey: "redirect",
|
|
548
594
|
secondaryStorage: false
|
|
549
595
|
},
|
|
550
596
|
async onInstall(nuxt) {
|
|
@@ -652,6 +698,19 @@ const module$1 = defineNuxtModule({
|
|
|
652
698
|
write: true
|
|
653
699
|
});
|
|
654
700
|
nuxt.options.alias["#auth/database"] = databaseTemplate.dst;
|
|
701
|
+
const schemaTemplate = addTemplate({
|
|
702
|
+
filename: "better-auth/schema.mjs",
|
|
703
|
+
getContents: () => {
|
|
704
|
+
if (!hasHubDb)
|
|
705
|
+
return "export const schema = undefined\n";
|
|
706
|
+
return `export * from './schema.${hubDialect}.mjs'
|
|
707
|
+
import * as schema from './schema.${hubDialect}.mjs'
|
|
708
|
+
export { schema }
|
|
709
|
+
`;
|
|
710
|
+
},
|
|
711
|
+
write: true
|
|
712
|
+
});
|
|
713
|
+
nuxt.options.alias["#auth/schema"] = schemaTemplate.dst;
|
|
655
714
|
registerServerTypeTemplates({
|
|
656
715
|
serverConfigPath,
|
|
657
716
|
hasHubDb,
|
|
@@ -2,6 +2,9 @@ import createAppAuthClient from "#auth/client";
|
|
|
2
2
|
import { computed, nextTick, useNuxtApp, useRequestHeaders, useRequestURL, useRuntimeConfig, useState, watch } from "#imports";
|
|
3
3
|
let _sessionSignalListenerBound = false;
|
|
4
4
|
let _client = null;
|
|
5
|
+
function isRecord(value) {
|
|
6
|
+
return Boolean(value && typeof value === "object");
|
|
7
|
+
}
|
|
5
8
|
function getClient(baseURL) {
|
|
6
9
|
if (!_client)
|
|
7
10
|
_client = createAppAuthClient(baseURL);
|
|
@@ -17,10 +20,14 @@ function ensureSessionSignalListener(client, onSignal) {
|
|
|
17
20
|
if (_sessionSignalListenerBound)
|
|
18
21
|
return;
|
|
19
22
|
const store = client.$store;
|
|
20
|
-
if (!store
|
|
23
|
+
if (!isRecord(store))
|
|
24
|
+
return;
|
|
25
|
+
const listen = store.listen;
|
|
26
|
+
if (typeof listen !== "function")
|
|
21
27
|
return;
|
|
22
28
|
_sessionSignalListenerBound = true;
|
|
23
|
-
|
|
29
|
+
const listenFn = listen;
|
|
30
|
+
listenFn("$sessionSignal", async () => {
|
|
24
31
|
try {
|
|
25
32
|
await onSignal();
|
|
26
33
|
} catch {
|
|
@@ -130,12 +137,29 @@ export function useUserSession() {
|
|
|
130
137
|
}
|
|
131
138
|
function wrapAuthMethod(method) {
|
|
132
139
|
return (async (...args) => {
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
140
|
+
const data = args[0];
|
|
141
|
+
const options = args[1];
|
|
142
|
+
const dataRecord = isRecord(data) ? data : void 0;
|
|
143
|
+
const optionsRecord = isRecord(options) ? options : void 0;
|
|
144
|
+
const fetchOptions = isRecord(dataRecord?.fetchOptions) ? dataRecord.fetchOptions : void 0;
|
|
145
|
+
const nestedOnSuccess = fetchOptions?.onSuccess;
|
|
146
|
+
const topLevelOnSuccess = optionsRecord?.onSuccess;
|
|
147
|
+
if (typeof nestedOnSuccess === "function") {
|
|
148
|
+
const nextData = {
|
|
149
|
+
...dataRecord,
|
|
150
|
+
fetchOptions: {
|
|
151
|
+
...fetchOptions,
|
|
152
|
+
onSuccess: wrapOnSuccess(nestedOnSuccess)
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
return method(nextData, options);
|
|
136
156
|
}
|
|
137
|
-
if (
|
|
138
|
-
|
|
157
|
+
if (typeof topLevelOnSuccess === "function") {
|
|
158
|
+
const nextOptions = {
|
|
159
|
+
...optionsRecord,
|
|
160
|
+
onSuccess: wrapOnSuccess(topLevelOnSuccess)
|
|
161
|
+
};
|
|
162
|
+
return method(data, nextOptions);
|
|
139
163
|
}
|
|
140
164
|
return method(data, options);
|
|
141
165
|
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ComputedRef, Ref } from 'vue';
|
|
2
|
+
export type UserAuthActionStatus = 'idle' | 'pending' | 'success' | 'error';
|
|
3
|
+
export interface UserAuthActionHandle<TArgs extends unknown[], TResult> {
|
|
4
|
+
execute: (...args: TArgs) => Promise<TResult>;
|
|
5
|
+
status: Ref<UserAuthActionStatus>;
|
|
6
|
+
pending: ComputedRef<boolean>;
|
|
7
|
+
error: Ref<unknown | null>;
|
|
8
|
+
}
|
|
9
|
+
export type ActionHandleFor<T> = T extends (...args: infer A) => Promise<infer R> ? UserAuthActionHandle<A, R> : never;
|
|
10
|
+
export type ActionHandleMap<T> = {
|
|
11
|
+
[K in keyof T]: ActionHandleFor<T[K]>;
|
|
12
|
+
};
|
|
13
|
+
export declare function createActionHandles<T extends object>(getTarget: () => T, targetName: string): ActionHandleMap<T>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { computed, ref } from "#imports";
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return Boolean(value && typeof value === "object");
|
|
4
|
+
}
|
|
5
|
+
function isErrorResult(value) {
|
|
6
|
+
if (!isRecord(value))
|
|
7
|
+
return false;
|
|
8
|
+
if (!("error" in value))
|
|
9
|
+
return false;
|
|
10
|
+
return Boolean(value.error);
|
|
11
|
+
}
|
|
12
|
+
function createActionHandle(getMethod) {
|
|
13
|
+
const status = ref("idle");
|
|
14
|
+
const error = ref(null);
|
|
15
|
+
const pending = computed(() => status.value === "pending");
|
|
16
|
+
let latestCallId = 0;
|
|
17
|
+
const execute = (async (...args) => {
|
|
18
|
+
const callId = ++latestCallId;
|
|
19
|
+
status.value = "pending";
|
|
20
|
+
error.value = null;
|
|
21
|
+
try {
|
|
22
|
+
const result = await getMethod()(...args);
|
|
23
|
+
if (callId !== latestCallId)
|
|
24
|
+
return result;
|
|
25
|
+
if (isErrorResult(result)) {
|
|
26
|
+
status.value = "error";
|
|
27
|
+
error.value = result.error;
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
status.value = "success";
|
|
31
|
+
error.value = null;
|
|
32
|
+
return result;
|
|
33
|
+
} catch (err) {
|
|
34
|
+
if (callId === latestCallId) {
|
|
35
|
+
status.value = "error";
|
|
36
|
+
error.value = err;
|
|
37
|
+
}
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
return { execute, status, pending, error };
|
|
42
|
+
}
|
|
43
|
+
export function createActionHandles(getTarget, targetName) {
|
|
44
|
+
const handles = /* @__PURE__ */ new Map();
|
|
45
|
+
return new Proxy({}, {
|
|
46
|
+
get(_target, prop) {
|
|
47
|
+
if (prop === "then")
|
|
48
|
+
return void 0;
|
|
49
|
+
if (handles.has(prop))
|
|
50
|
+
return handles.get(prop);
|
|
51
|
+
const handle = createActionHandle(() => {
|
|
52
|
+
const target = getTarget();
|
|
53
|
+
const method = target[prop];
|
|
54
|
+
if (typeof method !== "function")
|
|
55
|
+
throw new TypeError(`${targetName}.${String(prop)}() is not a function`);
|
|
56
|
+
return method;
|
|
57
|
+
});
|
|
58
|
+
handles.set(prop, handle);
|
|
59
|
+
return handle;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -28,10 +28,46 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
|
|
28
28
|
return navigateTo(redirectTo ?? config?.redirects?.guest ?? "/");
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
|
-
if (!loggedIn.value)
|
|
32
|
-
|
|
31
|
+
if (!loggedIn.value) {
|
|
32
|
+
const resolved = resolveLoginRedirect({
|
|
33
|
+
route: to,
|
|
34
|
+
loginTarget: redirectTo ?? config?.redirects?.login ?? "/login",
|
|
35
|
+
config
|
|
36
|
+
});
|
|
37
|
+
return resolved.external ? navigateTo(resolved.to, { external: true }) : navigateTo(resolved.to);
|
|
38
|
+
}
|
|
33
39
|
if (typeof auth === "object" && auth.user) {
|
|
34
40
|
if (!user.value || !matchesUser(user.value, auth.user))
|
|
35
41
|
throw createError({ statusCode: 403, statusMessage: "Access denied" });
|
|
36
42
|
}
|
|
37
43
|
});
|
|
44
|
+
function resolveLoginRedirect(input) {
|
|
45
|
+
const { route, loginTarget, config } = input;
|
|
46
|
+
const preserveRedirect = config?.preserveRedirect ?? true;
|
|
47
|
+
const redirectQueryKey = config?.redirectQueryKey ?? "redirect";
|
|
48
|
+
if (!preserveRedirect)
|
|
49
|
+
return { to: loginTarget, external: false };
|
|
50
|
+
if (!loginTarget.startsWith("/") || loginTarget.startsWith("//"))
|
|
51
|
+
return { to: loginTarget, external: false };
|
|
52
|
+
const [beforeHash, hash = ""] = loginTarget.split("#", 2);
|
|
53
|
+
const [path, query = ""] = beforeHash.split("?", 2);
|
|
54
|
+
try {
|
|
55
|
+
const params2 = new URLSearchParams(query);
|
|
56
|
+
if (params2.has(redirectQueryKey))
|
|
57
|
+
return { to: loginTarget, external: false };
|
|
58
|
+
} catch {
|
|
59
|
+
return { to: loginTarget, external: false };
|
|
60
|
+
}
|
|
61
|
+
if (import.meta.server) {
|
|
62
|
+
const separator = query ? "&" : "";
|
|
63
|
+
const encodedRedirect = encodeURIComponent(route.fullPath);
|
|
64
|
+
const url = `${path}?${query}${separator}${redirectQueryKey}=${encodedRedirect}${hash ? `#${hash}` : ""}`;
|
|
65
|
+
return { to: url, external: true };
|
|
66
|
+
}
|
|
67
|
+
const params = new URLSearchParams(query);
|
|
68
|
+
const queryObj = {};
|
|
69
|
+
for (const [k, v] of params.entries())
|
|
70
|
+
queryObj[k] = v;
|
|
71
|
+
queryObj[redirectQueryKey] = route.fullPath;
|
|
72
|
+
return { to: { path, query: queryObj, ...hash ? { hash: `#${hash}` } : {} }, external: false };
|
|
73
|
+
}
|
|
@@ -381,7 +381,7 @@ function getAccountActions(row) {
|
|
|
381
381
|
</div>
|
|
382
382
|
<div class="config-row">
|
|
383
383
|
<span class="config-label">DB</span><UBadge :color="configData.config.module?.databaseProvider === 'none' ? 'neutral' : 'success'" variant="subtle" size="sm">
|
|
384
|
-
{{ configData.config.module?.databaseProvider === "nuxthub" ? "Hub" :
|
|
384
|
+
{{ configData.config.module?.databaseProvider === "nuxthub" ? "Hub" : "Off" }}
|
|
385
385
|
</UBadge>
|
|
386
386
|
</div>
|
|
387
387
|
<div class="config-row">
|
package/dist/runtime/config.d.ts
CHANGED
|
@@ -17,7 +17,6 @@ export type ServerAuthConfigFn = (ctx: ServerAuthContext) => ServerAuthConfig;
|
|
|
17
17
|
export type ClientAuthConfigFn = (ctx: ClientAuthContext) => ClientAuthConfig;
|
|
18
18
|
export type ModuleDatabaseProviderId = 'none' | 'nuxthub' | (string & {});
|
|
19
19
|
export type EffectiveDatabaseProviderId = 'user' | ModuleDatabaseProviderId;
|
|
20
|
-
export type DatabaseSource = 'module' | 'user';
|
|
21
20
|
export interface BetterAuthModuleOptions {
|
|
22
21
|
/** Client-only mode - skip server setup for external auth backends */
|
|
23
22
|
clientOnly?: boolean;
|
|
@@ -29,6 +28,19 @@ export interface BetterAuthModuleOptions {
|
|
|
29
28
|
login?: string;
|
|
30
29
|
guest?: string;
|
|
31
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* When redirecting unauthenticated users to the login route, append a query param
|
|
33
|
+
* containing the originally requested path (for safe "return-to" redirects).
|
|
34
|
+
*
|
|
35
|
+
* Default: true
|
|
36
|
+
*/
|
|
37
|
+
preserveRedirect?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Query param key used by preserveRedirect.
|
|
40
|
+
*
|
|
41
|
+
* Default: 'redirect'
|
|
42
|
+
*/
|
|
43
|
+
redirectQueryKey?: string;
|
|
32
44
|
session?: {
|
|
33
45
|
/**
|
|
34
46
|
* When enabled, and session/user are already hydrated from SSR, skip the initial
|
|
@@ -54,9 +66,10 @@ export interface AuthRuntimeConfig {
|
|
|
54
66
|
login: string;
|
|
55
67
|
guest: string;
|
|
56
68
|
};
|
|
69
|
+
preserveRedirect: boolean;
|
|
70
|
+
redirectQueryKey: string;
|
|
57
71
|
useDatabase: boolean;
|
|
58
72
|
databaseProvider: EffectiveDatabaseProviderId;
|
|
59
|
-
databaseSource: DatabaseSource;
|
|
60
73
|
clientOnly: boolean;
|
|
61
74
|
session: {
|
|
62
75
|
skipHydratedSsrGetSession: boolean;
|
|
@@ -65,6 +78,6 @@ export interface AuthRuntimeConfig {
|
|
|
65
78
|
export interface AuthPrivateRuntimeConfig {
|
|
66
79
|
secondaryStorage: boolean;
|
|
67
80
|
}
|
|
68
|
-
export declare function defineServerAuth<const R
|
|
69
|
-
export declare function defineServerAuth<const R
|
|
81
|
+
export declare function defineServerAuth<const R>(config: (ctx: ServerAuthContext) => R & ServerAuthConfig): (ctx: ServerAuthContext) => R;
|
|
82
|
+
export declare function defineServerAuth<const R>(config: R & ServerAuthConfig): (ctx: ServerAuthContext) => R;
|
|
70
83
|
export declare function defineClientAuth<T extends ClientAuthConfig>(config: T | ((ctx: ClientAuthContext) => T)): (baseURL: string) => ReturnType<typeof createAuthClient<T>>;
|
|
@@ -2,8 +2,9 @@ import { defineEventHandler, getQuery } from "h3";
|
|
|
2
2
|
import { paginationQuerySchema, sanitizeSearchPattern } from "./_schema.js";
|
|
3
3
|
export default defineEventHandler(async (event) => {
|
|
4
4
|
try {
|
|
5
|
-
const { db
|
|
6
|
-
|
|
5
|
+
const { db } = await import("@nuxthub/db");
|
|
6
|
+
const { schema } = await import("#auth/schema");
|
|
7
|
+
if (!schema?.account)
|
|
7
8
|
return { accounts: [], total: 0, error: "Account table not found" };
|
|
8
9
|
const query = paginationQuerySchema.parse(getQuery(event));
|
|
9
10
|
const { page, limit, search } = query;
|
|
@@ -5,9 +5,11 @@ declare const _default: import("h3").EventHandler<import("h3").EventHandlerReque
|
|
|
5
5
|
login?: string;
|
|
6
6
|
guest?: string;
|
|
7
7
|
};
|
|
8
|
+
preserveRedirect: boolean;
|
|
9
|
+
redirectQueryKey: string;
|
|
8
10
|
secondaryStorage: boolean;
|
|
9
11
|
useDatabase: boolean;
|
|
10
|
-
databaseProvider: "none" | "nuxthub"
|
|
12
|
+
databaseProvider: "none" | "nuxthub";
|
|
11
13
|
};
|
|
12
14
|
server: {
|
|
13
15
|
baseURL: any;
|
|
@@ -19,6 +19,8 @@ export default defineEventHandler(async (event) => {
|
|
|
19
19
|
// Module config (nuxt.config.ts)
|
|
20
20
|
module: {
|
|
21
21
|
redirects: publicAuth?.redirects || { login: "/login", guest: "/" },
|
|
22
|
+
preserveRedirect: publicAuth?.preserveRedirect ?? true,
|
|
23
|
+
redirectQueryKey: publicAuth?.redirectQueryKey ?? "redirect",
|
|
22
24
|
secondaryStorage: privateAuth?.secondaryStorage ?? false,
|
|
23
25
|
useDatabase: publicAuth?.useDatabase ?? false,
|
|
24
26
|
databaseProvider: publicAuth?.databaseProvider ?? "none"
|
|
@@ -6,8 +6,9 @@ const deleteSessionSchema = z.object({
|
|
|
6
6
|
export default defineEventHandler(async (event) => {
|
|
7
7
|
try {
|
|
8
8
|
const body = deleteSessionSchema.parse(await readBody(event));
|
|
9
|
-
const { db
|
|
10
|
-
|
|
9
|
+
const { db } = await import("@nuxthub/db");
|
|
10
|
+
const { schema } = await import("#auth/schema");
|
|
11
|
+
if (!schema?.session)
|
|
11
12
|
throw createError({ statusCode: 500, message: "Session table not found" });
|
|
12
13
|
const { eq } = await import("drizzle-orm");
|
|
13
14
|
await db.delete(schema.session).where(eq(schema.session.id, body.id));
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { Session } from 'better-auth/types';
|
|
2
|
+
type SafeSession = Pick<Session, 'id' | 'userId' | 'createdAt' | 'updatedAt' | 'expiresAt' | 'ipAddress' | 'userAgent'>;
|
|
1
3
|
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
|
|
2
4
|
sessions: never[];
|
|
3
5
|
total: number;
|
|
@@ -5,7 +7,7 @@ declare const _default: import("h3").EventHandler<import("h3").EventHandlerReque
|
|
|
5
7
|
page?: undefined;
|
|
6
8
|
limit?: undefined;
|
|
7
9
|
} | {
|
|
8
|
-
sessions:
|
|
10
|
+
sessions: SafeSession[];
|
|
9
11
|
total: any;
|
|
10
12
|
page: number;
|
|
11
13
|
limit: number;
|
|
@@ -2,8 +2,9 @@ import { defineEventHandler, getQuery } from "h3";
|
|
|
2
2
|
import { paginationQuerySchema, sanitizeSearchPattern } from "./_schema.js";
|
|
3
3
|
export default defineEventHandler(async (event) => {
|
|
4
4
|
try {
|
|
5
|
-
const { db
|
|
6
|
-
|
|
5
|
+
const { db } = await import("@nuxthub/db");
|
|
6
|
+
const { schema } = await import("#auth/schema");
|
|
7
|
+
if (!schema?.session)
|
|
7
8
|
return { sessions: [], total: 0, error: "Session table not found" };
|
|
8
9
|
const query = paginationQuerySchema.parse(getQuery(event));
|
|
9
10
|
const { page, limit, search } = query;
|
|
@@ -2,8 +2,9 @@ import { defineEventHandler, getQuery } from "h3";
|
|
|
2
2
|
import { paginationQuerySchema, sanitizeSearchPattern } from "./_schema.js";
|
|
3
3
|
export default defineEventHandler(async (event) => {
|
|
4
4
|
try {
|
|
5
|
-
const { db
|
|
6
|
-
|
|
5
|
+
const { db } = await import("@nuxthub/db");
|
|
6
|
+
const { schema } = await import("#auth/schema");
|
|
7
|
+
if (!schema?.user)
|
|
7
8
|
return { users: [], total: 0, error: "User table not found" };
|
|
8
9
|
const query = paginationQuerySchema.parse(getQuery(event));
|
|
9
10
|
const { page, limit, search } = query;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createError, defineEventHandler, getRequestURL } from "h3";
|
|
2
2
|
import { getRouteRules } from "nitropack/runtime";
|
|
3
3
|
import { matchesUser } from "../../utils/match-user.js";
|
|
4
|
+
import { getUserSession, requireUserSession } from "../utils/session.js";
|
|
4
5
|
export default defineEventHandler(async (event) => {
|
|
5
6
|
const path = getRequestURL(event).pathname;
|
|
6
7
|
if (!path.startsWith("/api/"))
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"extends": "
|
|
2
|
+
"extends": "../../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"baseUrl": ".",
|
|
5
|
+
"paths": {
|
|
6
|
+
"#nuxt-better-auth": ["../types/augment"]
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"include": ["./**/*.ts", "./**/*.d.ts"],
|
|
10
|
+
"exclude": ["node_modules"]
|
|
3
11
|
}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
+
import type { AppSession, RequireSessionOptions } from '#nuxt-better-auth';
|
|
1
2
|
import type { H3Event } from 'h3';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
session: AuthSession;
|
|
6
|
-
}
|
|
7
|
-
export declare function getUserSession(event: H3Event): Promise<FullSession | null>;
|
|
8
|
-
export declare function requireUserSession(event: H3Event, options?: RequireSessionOptions): Promise<FullSession>;
|
|
9
|
-
export {};
|
|
3
|
+
export declare function getAppSession(event: H3Event): Promise<AppSession | null>;
|
|
4
|
+
export declare function getUserSession(event: H3Event): Promise<AppSession | null>;
|
|
5
|
+
export declare function requireUserSession(event: H3Event, options?: RequireSessionOptions): Promise<AppSession>;
|
|
@@ -1,13 +1,51 @@
|
|
|
1
1
|
import { createError } from "h3";
|
|
2
2
|
import { matchesUser } from "../../utils/match-user.js";
|
|
3
3
|
import { serverAuth } from "./auth.js";
|
|
4
|
-
|
|
4
|
+
const appSessionLoadKey = Symbol.for("nuxt-better-auth.appSessionLoad");
|
|
5
|
+
const fallbackAppSessionContext = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
function getAppSessionContext(event) {
|
|
7
|
+
const eventWithContext = event;
|
|
8
|
+
if (eventWithContext.context && typeof eventWithContext.context === "object")
|
|
9
|
+
return eventWithContext.context;
|
|
10
|
+
let context = fallbackAppSessionContext.get(event);
|
|
11
|
+
if (!context) {
|
|
12
|
+
context = {};
|
|
13
|
+
fallbackAppSessionContext.set(event, context);
|
|
14
|
+
}
|
|
15
|
+
return context;
|
|
16
|
+
}
|
|
17
|
+
function loadSession(event) {
|
|
5
18
|
const auth = serverAuth(event);
|
|
6
|
-
|
|
7
|
-
|
|
19
|
+
return auth.api.getSession({ headers: event.headers });
|
|
20
|
+
}
|
|
21
|
+
export async function getAppSession(event) {
|
|
22
|
+
const context = getAppSessionContext(event);
|
|
23
|
+
if (context.appSession !== void 0)
|
|
24
|
+
return context.appSession;
|
|
25
|
+
const inFlight = context[appSessionLoadKey];
|
|
26
|
+
if (inFlight)
|
|
27
|
+
return inFlight;
|
|
28
|
+
const load = loadSession(event);
|
|
29
|
+
context[appSessionLoadKey] = load;
|
|
30
|
+
try {
|
|
31
|
+
const session = await load;
|
|
32
|
+
context.appSession = session;
|
|
33
|
+
return session;
|
|
34
|
+
} finally {
|
|
35
|
+
delete context[appSessionLoadKey];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function getUserSession(event) {
|
|
39
|
+
const context = getAppSessionContext(event);
|
|
40
|
+
if (context.appSession !== void 0)
|
|
41
|
+
return context.appSession;
|
|
42
|
+
const inFlight = context[appSessionLoadKey];
|
|
43
|
+
if (inFlight)
|
|
44
|
+
return inFlight;
|
|
45
|
+
return loadSession(event);
|
|
8
46
|
}
|
|
9
47
|
export async function requireUserSession(event, options) {
|
|
10
|
-
const session = await
|
|
48
|
+
const session = await getAppSession(event);
|
|
11
49
|
if (!session)
|
|
12
50
|
throw createError({ statusCode: 401, statusMessage: "Authentication required" });
|
|
13
51
|
if (options?.user) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
declare module '#auth/database' {
|
|
2
|
+
export const db: any
|
|
3
|
+
export function createDatabase(...args: any[]): any
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
declare module '#auth/secondary-storage' {
|
|
7
|
+
export function createSecondaryStorage(...args: any[]): any
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
declare module '#auth/schema' {
|
|
11
|
+
export const schema: any
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
declare module '#auth/server' {
|
|
15
|
+
const createServerAuth: any
|
|
16
|
+
export default createServerAuth
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
declare module '@nuxthub/db' {
|
|
20
|
+
export const db: any
|
|
21
|
+
export const schema: any
|
|
22
|
+
}
|
|
@@ -40,3 +40,17 @@ export interface UserSessionComposable {
|
|
|
40
40
|
}) => Promise<void>;
|
|
41
41
|
updateUser: (updates: Partial<AuthUser>) => Promise<void>;
|
|
42
42
|
}
|
|
43
|
+
export type UserMatch<T> = {
|
|
44
|
+
[K in keyof T]?: T[K] | T[K][];
|
|
45
|
+
};
|
|
46
|
+
export interface AppSession {
|
|
47
|
+
user: AuthUser;
|
|
48
|
+
session: AuthSession;
|
|
49
|
+
}
|
|
50
|
+
export interface RequireSessionOptions {
|
|
51
|
+
user?: UserMatch<AuthUser>;
|
|
52
|
+
rule?: (ctx: {
|
|
53
|
+
user: AuthUser;
|
|
54
|
+
session: AuthSession;
|
|
55
|
+
}) => boolean | Promise<boolean>;
|
|
56
|
+
}
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import type { NitroRouteRules } from 'nitropack/types';
|
|
2
|
-
import type {
|
|
3
|
-
export type { AuthSession, AuthUser, ServerAuthContext, UserSessionComposable } from './types/augment.js';
|
|
2
|
+
import type { AuthUser, UserMatch } from './types/augment.js';
|
|
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 type AuthMode = 'guest' | 'user';
|
|
6
|
-
export type UserMatch<T> = {
|
|
7
|
-
[K in keyof T]?: T[K] | T[K][];
|
|
8
|
-
};
|
|
9
6
|
export type AuthMeta = false | AuthMode | {
|
|
10
7
|
only?: AuthMode;
|
|
11
8
|
redirectTo?: string;
|
|
@@ -14,10 +11,3 @@ export type AuthMeta = false | AuthMode | {
|
|
|
14
11
|
export type AuthRouteRules = NitroRouteRules & {
|
|
15
12
|
auth?: AuthMeta;
|
|
16
13
|
};
|
|
17
|
-
export interface RequireSessionOptions {
|
|
18
|
-
user?: UserMatch<AuthUser>;
|
|
19
|
-
rule?: (ctx: {
|
|
20
|
-
user: AuthUser;
|
|
21
|
-
session: AuthSession;
|
|
22
|
-
}) => boolean | Promise<boolean>;
|
|
23
|
-
}
|
package/dist/types.d.mts
CHANGED
|
@@ -6,6 +6,6 @@ export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<
|
|
|
6
6
|
|
|
7
7
|
export { type BetterAuthModuleOptions, type defineClientAuth, type defineServerAuth } from '../dist/runtime/config.js'
|
|
8
8
|
|
|
9
|
-
export { type Auth, type AuthMeta, type AuthMode, type AuthRouteRules, type AuthSession, type AuthUser, type InferSession, type InferUser, type RequireSessionOptions, type ServerAuthContext, type UserMatch } from '../dist/runtime/types.js'
|
|
9
|
+
export { type AppSession, type Auth, type AuthMeta, type AuthMode, type AuthRouteRules, type AuthSession, type AuthUser, type InferSession, type InferUser, type RequireSessionOptions, type ServerAuthContext, type UserMatch } from '../dist/runtime/types.js'
|
|
10
10
|
|
|
11
11
|
export { default } from './module.mjs'
|
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.24",
|
|
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",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"lint": "eslint .",
|
|
56
56
|
"lint:fix": "eslint . --fix",
|
|
57
57
|
"typecheck": "vue-tsc --noEmit",
|
|
58
|
+
"typecheck:runtime-server": "tsc --noEmit --pretty false -p src/runtime/server/tsconfig.json",
|
|
58
59
|
"typecheck:playground": "cd playground && vue-tsc --noEmit",
|
|
59
60
|
"test": "vitest run",
|
|
60
61
|
"test:watch": "vitest watch"
|