@onmax/nuxt-better-auth 0.0.2-alpha.8 → 0.0.2

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.
Files changed (70) hide show
  1. package/README.md +57 -17
  2. package/dist/module.d.mts +28 -1
  3. package/dist/module.json +3 -3
  4. package/dist/module.mjs +1272 -339
  5. package/dist/runtime/app/components/BetterAuthState.d.vue.ts +4 -4
  6. package/dist/runtime/app/components/BetterAuthState.vue +1 -0
  7. package/dist/runtime/app/components/BetterAuthState.vue.d.ts +4 -4
  8. package/dist/runtime/app/composables/useAction.d.ts +2 -0
  9. package/dist/runtime/app/composables/useAction.js +6 -0
  10. package/dist/runtime/app/composables/useAuthAsyncData.d.ts +6 -0
  11. package/dist/runtime/app/composables/useAuthAsyncData.js +16 -0
  12. package/dist/runtime/app/composables/useAuthClient.d.ts +9 -0
  13. package/dist/runtime/app/composables/useAuthClient.js +34 -0
  14. package/dist/runtime/app/composables/useAuthClientAction.d.ts +3 -0
  15. package/dist/runtime/app/composables/useAuthClientAction.js +15 -0
  16. package/dist/runtime/app/composables/useAuthRequestFetch.d.ts +11 -0
  17. package/dist/runtime/app/composables/useAuthRequestFetch.js +4 -0
  18. package/dist/runtime/app/composables/useSignIn.d.ts +16 -0
  19. package/dist/runtime/app/composables/useSignIn.js +8 -0
  20. package/dist/runtime/app/composables/useSignUp.d.ts +5 -0
  21. package/dist/runtime/app/composables/useSignUp.js +8 -0
  22. package/dist/runtime/app/composables/useUserSession.d.ts +6 -5
  23. package/dist/runtime/app/composables/useUserSession.js +189 -100
  24. package/dist/runtime/app/composables/useUserSessionState.d.ts +3 -0
  25. package/dist/runtime/app/composables/useUserSessionState.js +4 -0
  26. package/dist/runtime/app/internal/auth-action-error.d.ts +3 -0
  27. package/dist/runtime/app/internal/auth-action-error.js +33 -0
  28. package/dist/runtime/app/internal/auth-action-handles.d.ts +18 -0
  29. package/dist/runtime/app/internal/auth-action-handles.js +105 -0
  30. package/dist/runtime/app/internal/redirect-helpers.d.ts +4 -0
  31. package/dist/runtime/app/internal/redirect-helpers.js +37 -0
  32. package/dist/runtime/app/internal/session-fetch.d.ts +12 -0
  33. package/dist/runtime/app/internal/session-fetch.js +56 -0
  34. package/dist/runtime/app/internal/utils.d.ts +1 -0
  35. package/dist/runtime/app/internal/utils.js +3 -0
  36. package/dist/runtime/app/internal/vue-safe-auth-proxy.d.ts +3 -0
  37. package/dist/runtime/app/internal/vue-safe-auth-proxy.js +68 -0
  38. package/dist/runtime/app/internal/wrap-auth-method.d.ts +15 -0
  39. package/dist/runtime/app/internal/wrap-auth-method.js +66 -0
  40. package/dist/runtime/app/middleware/auth.global.js +77 -15
  41. package/dist/runtime/app/pages/__better-auth-devtools.vue +4 -10
  42. package/dist/runtime/composables.d.ts +11 -0
  43. package/dist/runtime/composables.js +9 -0
  44. package/dist/runtime/config.d.ts +69 -15
  45. package/dist/runtime/config.js +9 -2
  46. package/dist/runtime/server/api/_better-auth/_schema.d.ts +1 -5
  47. package/dist/runtime/server/api/_better-auth/_schema.js +2 -1
  48. package/dist/runtime/server/api/_better-auth/accounts.get.d.ts +2 -2
  49. package/dist/runtime/server/api/_better-auth/accounts.get.js +3 -2
  50. package/dist/runtime/server/api/_better-auth/config.get.d.ts +9 -3
  51. package/dist/runtime/server/api/_better-auth/config.get.js +18 -6
  52. package/dist/runtime/server/api/_better-auth/sessions.delete.js +3 -2
  53. package/dist/runtime/server/api/_better-auth/sessions.get.d.ts +5 -3
  54. package/dist/runtime/server/api/_better-auth/sessions.get.js +3 -2
  55. package/dist/runtime/server/api/_better-auth/users.get.d.ts +2 -2
  56. package/dist/runtime/server/api/_better-auth/users.get.js +3 -2
  57. package/dist/runtime/server/api/auth/[...all].js +1 -1
  58. package/dist/runtime/server/middleware/route-access.js +2 -1
  59. package/dist/runtime/server/tsconfig.json +9 -1
  60. package/dist/runtime/server/utils/auth.d.ts +16 -8
  61. package/dist/runtime/server/utils/auth.js +229 -23
  62. package/dist/runtime/server/utils/custom-secondary-storage.d.ts +6 -0
  63. package/dist/runtime/server/utils/custom-secondary-storage.js +8 -0
  64. package/dist/runtime/server/utils/session.d.ts +6 -8
  65. package/dist/runtime/server/utils/session.js +216 -4
  66. package/dist/runtime/server/virtual-modules.d.ts +27 -0
  67. package/dist/runtime/types/augment.d.ts +18 -4
  68. package/dist/runtime/types.d.ts +12 -13
  69. package/dist/types.d.mts +1 -1
  70. package/package.json +51 -47
@@ -0,0 +1,105 @@
1
+ import { ref } from "#imports";
2
+ import { normalizeAuthActionError } from "./auth-action-error.js";
3
+ import { isRecord } from "./utils.js";
4
+ function isErrorResult(value) {
5
+ if (!isRecord(value))
6
+ return false;
7
+ if (!("error" in value))
8
+ return false;
9
+ return Boolean(value.error);
10
+ }
11
+ function isRedirectResult(value) {
12
+ if (!isRecord(value))
13
+ return false;
14
+ return value.redirect === true && typeof value.url === "string" && value.url.length > 0;
15
+ }
16
+ function getRedirectResult(value) {
17
+ if (isRedirectResult(value))
18
+ return value;
19
+ if (!isRecord(value))
20
+ return null;
21
+ const nested = value.data;
22
+ if (isRedirectResult(nested))
23
+ return nested;
24
+ return null;
25
+ }
26
+ const REDIRECT_PENDING_FALLBACK_MS = 1e4;
27
+ export function createActionHandle(getMethod, options = {}) {
28
+ const status = ref("idle");
29
+ const data = ref(null);
30
+ const error = ref(null);
31
+ let latestCallId = 0;
32
+ const run = async (...args) => {
33
+ const callId = ++latestCallId;
34
+ status.value = "pending";
35
+ data.value = null;
36
+ error.value = null;
37
+ try {
38
+ const result = await getMethod()(...args);
39
+ if (isErrorResult(result)) {
40
+ const normalizedError = normalizeAuthActionError(result.error);
41
+ if (callId === latestCallId) {
42
+ status.value = "error";
43
+ data.value = null;
44
+ error.value = normalizedError;
45
+ }
46
+ return;
47
+ }
48
+ if (callId === latestCallId) {
49
+ if (options.keepPendingOnRedirect !== false) {
50
+ const redirectResult = getRedirectResult(result);
51
+ if (redirectResult) {
52
+ status.value = "pending";
53
+ data.value = result;
54
+ error.value = null;
55
+ setTimeout(() => {
56
+ if (callId !== latestCallId || status.value !== "pending")
57
+ return;
58
+ status.value = "success";
59
+ }, REDIRECT_PENDING_FALLBACK_MS);
60
+ return;
61
+ }
62
+ }
63
+ status.value = "success";
64
+ data.value = result;
65
+ error.value = null;
66
+ }
67
+ } catch (thrown) {
68
+ const normalizedError = normalizeAuthActionError(thrown);
69
+ if (callId === latestCallId) {
70
+ status.value = "error";
71
+ data.value = null;
72
+ error.value = normalizedError;
73
+ }
74
+ }
75
+ };
76
+ const execute = (async (...args) => {
77
+ await run(...args);
78
+ });
79
+ return {
80
+ execute,
81
+ status,
82
+ data,
83
+ error
84
+ };
85
+ }
86
+ export function createActionHandles(getTarget, targetName) {
87
+ const handles = /* @__PURE__ */ new Map();
88
+ return new Proxy({}, {
89
+ get(_target, prop) {
90
+ if (prop === "then")
91
+ return void 0;
92
+ if (handles.has(prop))
93
+ return handles.get(prop);
94
+ const handle = createActionHandle(() => {
95
+ const target = getTarget();
96
+ const method = target[prop];
97
+ if (typeof method !== "function")
98
+ throw new TypeError(`${targetName}.${String(prop)}() is not a function`);
99
+ return method;
100
+ });
101
+ handles.set(prop, handle);
102
+ return handle;
103
+ }
104
+ });
105
+ }
@@ -0,0 +1,4 @@
1
+ export declare function isSafeLocalRedirect(redirect: unknown): string | undefined;
2
+ export declare function resolvePostAuthRedirect(requestURL: URL): string | undefined;
3
+ export declare function resolvePostAuthSuccessRedirect(requestURL: URL): (() => Promise<void>) | undefined;
4
+ export declare function withFallbackSocialCallbackURL(data: unknown, requestURL: URL): unknown;
@@ -0,0 +1,37 @@
1
+ import { navigateTo, useRuntimeConfig } from "#imports";
2
+ import { isRecord } from "./utils.js";
3
+ export function isSafeLocalRedirect(redirect) {
4
+ if (typeof redirect !== "string")
5
+ return;
6
+ if (!redirect.startsWith("/") || redirect.startsWith("//"))
7
+ return;
8
+ return redirect;
9
+ }
10
+ export function resolvePostAuthRedirect(requestURL) {
11
+ const runtimeConfig = useRuntimeConfig();
12
+ const authConfig = runtimeConfig.public.auth;
13
+ const redirectQueryKey = authConfig?.redirectQueryKey ?? "redirect";
14
+ const queryRedirect = requestURL.searchParams?.get(redirectQueryKey);
15
+ const safeQueryRedirect = isSafeLocalRedirect(queryRedirect);
16
+ if (safeQueryRedirect)
17
+ return safeQueryRedirect;
18
+ return isSafeLocalRedirect(authConfig?.redirects?.authenticated);
19
+ }
20
+ export function resolvePostAuthSuccessRedirect(requestURL) {
21
+ const target = resolvePostAuthRedirect(requestURL);
22
+ if (!target)
23
+ return;
24
+ return async () => {
25
+ await navigateTo(target);
26
+ };
27
+ }
28
+ export function withFallbackSocialCallbackURL(data, requestURL) {
29
+ const callbackURL = resolvePostAuthRedirect(requestURL);
30
+ if (!callbackURL)
31
+ return data;
32
+ if (!isRecord(data))
33
+ return { callbackURL };
34
+ if (typeof data.callbackURL === "string")
35
+ return data;
36
+ return { ...data, callbackURL };
37
+ }
@@ -0,0 +1,12 @@
1
+ import type { Ref } from 'vue';
2
+ import type { AppAuthClient, AuthSession, AuthUser } from '#nuxt-better-auth';
3
+ export declare function stripToken(session: AuthSession & {
4
+ token?: string;
5
+ }): AuthSession;
6
+ export declare function fetchSessionServer(session: Ref<AuthSession | null>, user: Ref<AuthUser | null>, authReady: Ref<boolean>, options?: {
7
+ headers?: HeadersInit;
8
+ }): Promise<void>;
9
+ export declare function fetchSessionClient(client: AppAuthClient, session: Ref<AuthSession | null>, user: Ref<AuthUser | null>, authReady: Ref<boolean>, options?: {
10
+ headers?: HeadersInit;
11
+ force?: boolean;
12
+ }): Promise<void>;
@@ -0,0 +1,56 @@
1
+ import { useRequestFetch, useRequestHeaders } from "#imports";
2
+ import { normalizeAuthActionError } from "./auth-action-error.js";
3
+ export function stripToken(session) {
4
+ const { token: _, ...safe } = session;
5
+ return safe;
6
+ }
7
+ function isExpectedSignedOutSessionError(error) {
8
+ const normalizedError = normalizeAuthActionError(error);
9
+ if (normalizedError.status === 401)
10
+ return true;
11
+ return normalizedError.code === "UNAUTHORIZED";
12
+ }
13
+ export async function fetchSessionServer(session, user, authReady, options = {}) {
14
+ try {
15
+ const headers = options.headers || useRequestHeaders(["cookie"]);
16
+ const requestFetch = useRequestFetch();
17
+ const data = await requestFetch("/api/auth/get-session", { headers });
18
+ if (data?.session && data?.user) {
19
+ session.value = stripToken(data.session);
20
+ user.value = data.user;
21
+ } else {
22
+ session.value = null;
23
+ user.value = null;
24
+ }
25
+ } catch {
26
+ session.value = null;
27
+ user.value = null;
28
+ } finally {
29
+ if (!authReady.value)
30
+ authReady.value = true;
31
+ }
32
+ }
33
+ export async function fetchSessionClient(client, session, user, authReady, options = {}) {
34
+ try {
35
+ const headers = options.headers || useRequestHeaders(["cookie"]);
36
+ const fetchOptions = headers ? { headers } : void 0;
37
+ const query = options.force ? { disableCookieCache: true } : void 0;
38
+ const result = await client.getSession({ query }, fetchOptions);
39
+ const data = result.data;
40
+ if (data?.session && data?.user) {
41
+ session.value = stripToken(data.session);
42
+ user.value = data.user;
43
+ } else {
44
+ session.value = null;
45
+ user.value = null;
46
+ }
47
+ } catch (error) {
48
+ session.value = null;
49
+ user.value = null;
50
+ if (!isExpectedSignedOutSessionError(error))
51
+ console.error("[nuxt-better-auth] Failed to fetch session:", error);
52
+ } finally {
53
+ if (!authReady.value)
54
+ authReady.value = true;
55
+ }
56
+ }
@@ -0,0 +1 @@
1
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
@@ -0,0 +1,3 @@
1
+ export function isRecord(value) {
2
+ return Boolean(value && typeof value === "object");
3
+ }
@@ -0,0 +1,3 @@
1
+ export declare function isAuthProxyProbeKey(prop: PropertyKey): boolean;
2
+ export declare function createVueSafeAuthFacade<T extends object>(resolve: (prop: PropertyKey, receiver: object) => unknown): T;
3
+ export declare function createVueSafeAuthProxy<T>(target: T): T;
@@ -0,0 +1,68 @@
1
+ const promiseProbeKeys = /* @__PURE__ */ new Set(["then", "catch", "finally"]);
2
+ export function isAuthProxyProbeKey(prop) {
3
+ return typeof prop !== "string" || promiseProbeKeys.has(prop) || prop.startsWith("__v");
4
+ }
5
+ function isObjectLike(value) {
6
+ return typeof value === "object" && value !== null || typeof value === "function";
7
+ }
8
+ export function createVueSafeAuthFacade(resolve) {
9
+ const propertyCache = /* @__PURE__ */ new Map();
10
+ const facadeTarget = {};
11
+ Object.defineProperty(facadeTarget, "__v_skip", {
12
+ value: true,
13
+ configurable: true
14
+ });
15
+ return new Proxy(facadeTarget, {
16
+ get(_target, prop, receiver) {
17
+ if (prop === "__v_skip")
18
+ return true;
19
+ if (isAuthProxyProbeKey(prop))
20
+ return void 0;
21
+ if (propertyCache.has(prop))
22
+ return propertyCache.get(prop);
23
+ const value = resolve(prop, receiver);
24
+ propertyCache.set(prop, value);
25
+ return value;
26
+ }
27
+ });
28
+ }
29
+ export function createVueSafeAuthProxy(target) {
30
+ if (!isObjectLike(target))
31
+ return target;
32
+ const cache = /* @__PURE__ */ new WeakMap();
33
+ const wrap = (value) => {
34
+ if (!isObjectLike(value))
35
+ return value;
36
+ const cached = cache.get(value);
37
+ if (cached)
38
+ return cached;
39
+ const propertyCache = /* @__PURE__ */ new Map();
40
+ const handler = {
41
+ get(target2, prop, receiver) {
42
+ if (prop === "__v_skip")
43
+ return true;
44
+ if (isAuthProxyProbeKey(prop))
45
+ return void 0;
46
+ if (propertyCache.has(prop))
47
+ return propertyCache.get(prop);
48
+ const wrapped = wrap(Reflect.get(target2, prop, receiver));
49
+ propertyCache.set(prop, wrapped);
50
+ return wrapped;
51
+ }
52
+ };
53
+ if (typeof value === "function") {
54
+ handler.apply = (target2, thisArg, args) => Reflect.apply(target2, thisArg, args);
55
+ }
56
+ const proxy = new Proxy(value, handler);
57
+ cache.set(value, proxy);
58
+ return proxy;
59
+ };
60
+ const createRootFacade = (value) => {
61
+ if (!isObjectLike(value))
62
+ return value;
63
+ const proxy = createVueSafeAuthFacade((prop, receiver) => wrap(Reflect.get(value, prop, receiver)));
64
+ cache.set(value, proxy);
65
+ return proxy;
66
+ };
67
+ return createRootFacade(target);
68
+ }
@@ -0,0 +1,15 @@
1
+ import type { ComputedRef } from 'vue';
2
+ export declare function wrapOnSuccess(fetchSession: (options?: {
3
+ force?: boolean;
4
+ }) => Promise<void>, loggedIn: ComputedRef<boolean>, waitForSession: () => Promise<void>, cb: (ctx: unknown) => void | Promise<void>): (ctx: unknown) => Promise<void>;
5
+ export declare function wrapAuthMethod<T extends (...args: unknown[]) => Promise<unknown>>(method: T, deps: {
6
+ fetchSession: (options?: {
7
+ force?: boolean;
8
+ }) => Promise<void>;
9
+ loggedIn: ComputedRef<boolean>;
10
+ waitForSession: () => Promise<void>;
11
+ resolvePostAuthSuccessRedirect: () => (() => Promise<void>) | undefined;
12
+ }, wrapOptions?: {
13
+ shouldSkipSessionSync?: (data: unknown, options: unknown) => boolean;
14
+ transformData?: (data: unknown, options: unknown) => unknown;
15
+ }): T;
@@ -0,0 +1,66 @@
1
+ import { nextTick } from "#imports";
2
+ import { isRecord } from "./utils.js";
3
+ export function wrapOnSuccess(fetchSession, loggedIn, waitForSession, cb) {
4
+ return async (ctx) => {
5
+ await fetchSession({ force: true });
6
+ if (!loggedIn.value)
7
+ await waitForSession();
8
+ await nextTick();
9
+ await cb(ctx);
10
+ };
11
+ }
12
+ export function wrapAuthMethod(method, deps, wrapOptions = {}) {
13
+ return (async (...args) => {
14
+ const originalData = args[0];
15
+ const options = args[1];
16
+ const data = wrapOptions.transformData?.(originalData, options) ?? originalData;
17
+ const dataRecord = isRecord(data) ? data : void 0;
18
+ const optionsRecord = isRecord(options) ? options : void 0;
19
+ if (wrapOptions.shouldSkipSessionSync?.(data, options))
20
+ return method(data, options);
21
+ const fetchOptions = isRecord(dataRecord?.fetchOptions) ? dataRecord.fetchOptions : void 0;
22
+ const nestedOnSuccess = fetchOptions?.onSuccess;
23
+ const topLevelOnSuccess = optionsRecord?.onSuccess;
24
+ const fallbackOnSuccess = deps.resolvePostAuthSuccessRedirect();
25
+ const wrappedFallbackOnSuccess = fallbackOnSuccess && wrapOnSuccess(deps.fetchSession, deps.loggedIn, deps.waitForSession, async () => {
26
+ if (!deps.loggedIn.value)
27
+ return;
28
+ await fallbackOnSuccess();
29
+ });
30
+ if (typeof nestedOnSuccess === "function") {
31
+ const nextData = {
32
+ ...dataRecord,
33
+ fetchOptions: {
34
+ ...fetchOptions,
35
+ onSuccess: wrapOnSuccess(deps.fetchSession, deps.loggedIn, deps.waitForSession, nestedOnSuccess)
36
+ }
37
+ };
38
+ return method(nextData, options);
39
+ }
40
+ if (typeof topLevelOnSuccess === "function") {
41
+ const nextOptions = {
42
+ ...optionsRecord,
43
+ onSuccess: wrapOnSuccess(deps.fetchSession, deps.loggedIn, deps.waitForSession, topLevelOnSuccess)
44
+ };
45
+ return method(data, nextOptions);
46
+ }
47
+ if (wrappedFallbackOnSuccess) {
48
+ if (fetchOptions) {
49
+ const nextData = {
50
+ ...dataRecord,
51
+ fetchOptions: {
52
+ ...fetchOptions,
53
+ onSuccess: wrappedFallbackOnSuccess
54
+ }
55
+ };
56
+ return method(nextData, options);
57
+ }
58
+ const nextOptions = {
59
+ ...optionsRecord,
60
+ onSuccess: wrappedFallbackOnSuccess
61
+ };
62
+ return method(data, nextOptions);
63
+ }
64
+ return method(data, options);
65
+ });
66
+ }
@@ -1,37 +1,99 @@
1
- import { createError, defineNuxtRouteMiddleware, getRouteRules, navigateTo, useNuxtApp, useRequestHeaders, useRuntimeConfig, useUserSession } from "#imports";
1
+ import { defu } from "defu";
2
+ import { createRouter, toRouteMatcher } from "radix3";
3
+ import { createError, defineNuxtRouteMiddleware, getRouteRules, navigateTo, useNuxtApp, useRequestHeaders, useRuntimeConfig } from "#imports";
2
4
  import { matchesUser } from "../../utils/match-user.js";
5
+ import { useUserSession } from "../composables/useUserSession.js";
6
+ let authRouteRulesPromise = null;
7
+ let routeRulesMatcherPromise = null;
3
8
  export default defineNuxtRouteMiddleware(async (to) => {
4
9
  const nuxtApp = useNuxtApp();
5
- if (import.meta.client) {
6
- const isPrerendered = nuxtApp.payload.prerenderedAt || nuxtApp.payload.isCached;
7
- if (isPrerendered && nuxtApp.isHydrating)
8
- return;
9
- }
10
10
  if (to.meta.auth === void 0) {
11
- const rules = await getRouteRules({ path: to.path });
12
- if (rules.auth !== void 0)
13
- to.meta.auth = rules.auth;
11
+ const routeRulesMatcher = await getRouteRulesMatcher();
12
+ const matches = routeRulesMatcher?.matchAll(to.path);
13
+ if (matches?.length) {
14
+ const merged = defu({}, ...matches.reverse());
15
+ if (merged.auth !== void 0)
16
+ to.meta.auth = merged.auth;
17
+ }
18
+ if (to.meta.auth === void 0) {
19
+ const rules = await getRouteRules({ path: to.path });
20
+ if (rules.auth !== void 0)
21
+ to.meta.auth = rules.auth;
22
+ }
14
23
  }
15
24
  const auth = to.meta.auth;
16
25
  if (auth === void 0 || auth === false)
17
26
  return;
18
27
  const config = useRuntimeConfig().public.auth;
19
28
  const { fetchSession, user, loggedIn } = useUserSession();
20
- if (!loggedIn.value) {
21
- const headers = import.meta.server ? useRequestHeaders(["cookie"]) : void 0;
22
- await fetchSession({ headers });
23
- }
24
29
  const mode = typeof auth === "string" ? auth : auth?.only ?? "user";
25
30
  const redirectTo = typeof auth === "object" ? auth.redirectTo : void 0;
31
+ if (!loggedIn.value || mode === "guest") {
32
+ const headers = import.meta.server ? useRequestHeaders(["cookie"]) : void 0;
33
+ const isHydratedPrerenderPayload = (import.meta.client || !import.meta.server) && nuxtApp.isHydrating && Boolean(nuxtApp.payload.prerenderedAt || nuxtApp.payload.isCached);
34
+ await fetchSession({ headers, ...isHydratedPrerenderPayload ? { force: true } : {} });
35
+ }
26
36
  if (mode === "guest") {
27
37
  if (loggedIn.value)
28
38
  return navigateTo(redirectTo ?? config?.redirects?.guest ?? "/");
29
39
  return;
30
40
  }
31
- if (!loggedIn.value)
32
- return navigateTo(redirectTo ?? config?.redirects?.login ?? "/login");
41
+ if (!loggedIn.value) {
42
+ const resolved = resolveLoginRedirect({
43
+ route: to,
44
+ loginTarget: redirectTo ?? config?.redirects?.login ?? "/login",
45
+ config
46
+ });
47
+ return resolved.external ? navigateTo(resolved.to, { external: true }) : navigateTo(resolved.to);
48
+ }
33
49
  if (typeof auth === "object" && auth.user) {
34
50
  if (!user.value || !matchesUser(user.value, auth.user))
35
51
  throw createError({ statusCode: 403, statusMessage: "Access denied" });
36
52
  }
37
53
  });
54
+ function resolveLoginRedirect(input) {
55
+ const { route, loginTarget, config } = input;
56
+ const preserveRedirect = config?.preserveRedirect ?? true;
57
+ const redirectQueryKey = config?.redirectQueryKey ?? "redirect";
58
+ if (!preserveRedirect)
59
+ return { to: loginTarget, external: false };
60
+ if (!loginTarget.startsWith("/") || loginTarget.startsWith("//"))
61
+ return { to: loginTarget, external: false };
62
+ const [beforeHash, hash = ""] = loginTarget.split("#", 2);
63
+ const [path, query = ""] = beforeHash.split("?", 2);
64
+ try {
65
+ const params2 = new URLSearchParams(query);
66
+ if (params2.has(redirectQueryKey))
67
+ return { to: loginTarget, external: false };
68
+ } catch {
69
+ return { to: loginTarget, external: false };
70
+ }
71
+ if (import.meta.server) {
72
+ const separator = query ? "&" : "";
73
+ const encodedRedirect = encodeURIComponent(route.fullPath);
74
+ const url = `${path}?${query}${separator}${redirectQueryKey}=${encodedRedirect}${hash ? `#${hash}` : ""}`;
75
+ return { to: url, external: true };
76
+ }
77
+ const params = new URLSearchParams(query);
78
+ const queryObj = {};
79
+ for (const [k, v] of params.entries())
80
+ queryObj[k] = v;
81
+ queryObj[redirectQueryKey] = route.fullPath;
82
+ return { to: { path, query: queryObj, ...hash ? { hash: `#${hash}` } : {} }, external: false };
83
+ }
84
+ async function getAuthRouteRules() {
85
+ if (!authRouteRulesPromise) {
86
+ authRouteRulesPromise = import("#auth/route-rules").then((mod) => mod.authRouteRules || {}).catch(() => ({}));
87
+ }
88
+ return await authRouteRulesPromise;
89
+ }
90
+ async function getRouteRulesMatcher() {
91
+ if (!routeRulesMatcherPromise) {
92
+ routeRulesMatcherPromise = getAuthRouteRules().then((authRouteRules) => {
93
+ if (!Object.keys(authRouteRules).length)
94
+ return null;
95
+ return toRouteMatcher(createRouter({ routes: authRouteRules }));
96
+ });
97
+ }
98
+ return await routeRulesMatcherPromise;
99
+ }
@@ -374,19 +374,13 @@ function getAccountActions(row) {
374
374
  <UIcon name="i-lucide-settings-2" class="size-4" /><span>Module</span>
375
375
  </div>
376
376
  <div class="config-row">
377
- <span class="config-label">Login</span><span class="font-mono">{{ configData.config.module?.redirects?.login }}</span>
378
- </div>
379
- <div class="config-row">
380
- <span class="config-label">Guest</span><span class="font-mono">{{ configData.config.module?.redirects?.guest }}</span>
381
- </div>
382
- <div class="config-row">
383
- <span class="config-label">DB</span><UBadge :color="configData.config.module?.useDatabase ? 'success' : 'neutral'" variant="subtle" size="sm">
384
- {{ configData.config.module?.useDatabase ? "Hub" : "Off" }}
377
+ <span class="config-label">DB</span><UBadge :color="configData.config.module?.databaseProvider === 'none' ? 'neutral' : 'success'" variant="subtle" size="sm">
378
+ {{ configData.config.module?.databaseProvider === "nuxthub" ? "Hub" : "Off" }}
385
379
  </UBadge>
386
380
  </div>
387
381
  <div class="config-row">
388
- <span class="config-label">KV</span><UBadge :color="configData.config.module?.secondaryStorage ? 'success' : 'neutral'" variant="subtle" size="sm">
389
- {{ configData.config.module?.secondaryStorage ? "On" : "Off" }}
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" }}
390
384
  </UBadge>
391
385
  </div>
392
386
  </div>
@@ -0,0 +1,11 @@
1
+ export { useAction } from './app/composables/useAction.js';
2
+ export { useAuthAsyncData } from './app/composables/useAuthAsyncData.js';
3
+ export { useAuthClient } from './app/composables/useAuthClient.js';
4
+ export { useAuthClientAction } from './app/composables/useAuthClientAction.js';
5
+ export { useAuthRequestFetch } from './app/composables/useAuthRequestFetch.js';
6
+ export { useSignIn } from './app/composables/useSignIn.js';
7
+ export { useSignUp } from './app/composables/useSignUp.js';
8
+ export { useUserSession } from './app/composables/useUserSession.js';
9
+ export type { SignOutOptions, UseUserSessionReturn } from './app/composables/useUserSession.js';
10
+ export { useUserSessionState } from './app/composables/useUserSessionState.js';
11
+ export type { UseUserSessionStateReturn } from './app/composables/useUserSessionState.js';
@@ -0,0 +1,9 @@
1
+ export { useAction } from "./app/composables/useAction.js";
2
+ export { useAuthAsyncData } from "./app/composables/useAuthAsyncData.js";
3
+ export { useAuthClient } from "./app/composables/useAuthClient.js";
4
+ export { useAuthClientAction } from "./app/composables/useAuthClientAction.js";
5
+ export { useAuthRequestFetch } from "./app/composables/useAuthRequestFetch.js";
6
+ export { useSignIn } from "./app/composables/useSignIn.js";
7
+ export { useSignUp } from "./app/composables/useSignUp.js";
8
+ export { useUserSession } from "./app/composables/useUserSession.js";
9
+ export { useUserSessionState } from "./app/composables/useUserSessionState.js";
@@ -1,44 +1,98 @@
1
- import type { BetterAuthOptions } from 'better-auth';
2
- import type { ClientOptions } from 'better-auth/client';
3
- import type { CasingOption } from '../schema-generator.js';
4
- import type { ServerAuthContext } from './types/augment.js';
5
- export type { ServerAuthContext };
1
+ import type { BetterAuthOptions, BetterAuthPlugin } from 'better-auth';
2
+ import type { BetterAuthClientOptions } from 'better-auth/client';
3
+ import type { Casing } from 'drizzle-orm/utils';
4
+ import type { ServerAuthContext as BaseServerAuthContext } from './types/augment.js';
5
+ import { createAuthClient } from 'better-auth/vue';
6
+ export interface ServerAuthContextExtension {
7
+ }
8
+ export type ServerAuthContext = BaseServerAuthContext & ServerAuthContextExtension;
6
9
  export interface ClientAuthContext {
7
10
  siteUrl: string;
8
11
  }
9
- type ServerAuthConfig = Omit<BetterAuthOptions, 'database' | 'secret' | 'baseURL'>;
10
- type ClientAuthConfig = Omit<ClientOptions, 'baseURL'> & {
12
+ export type ServerAuthConfig = Omit<BetterAuthOptions, 'secret' | 'baseURL'> & {
13
+ plugins?: readonly BetterAuthPlugin[];
14
+ };
15
+ export type ClientAuthConfig = Omit<BetterAuthClientOptions, 'baseURL'> & {
11
16
  baseURL?: string;
12
17
  };
13
18
  export type ServerAuthConfigFn = (ctx: ServerAuthContext) => ServerAuthConfig;
14
19
  export type ClientAuthConfigFn = (ctx: ClientAuthContext) => ClientAuthConfig;
20
+ export type ModuleDatabaseProviderId = 'none' | 'nuxthub' | (string & {});
21
+ export type EffectiveDatabaseProviderId = 'user' | ModuleDatabaseProviderId;
15
22
  export interface BetterAuthModuleOptions {
16
- /** Server config path relative to rootDir. Default: 'server/auth.config' */
23
+ /** Client-only mode - skip server setup for external auth backends */
24
+ clientOnly?: boolean;
25
+ /** Server config path. Relative paths resolve from the layer that declares them. Default: 'server/auth.config' */
17
26
  serverConfig?: string;
18
- /** Client config path relative to rootDir. Default: 'app/auth.config' */
27
+ /** Client config path. Relative paths resolve from the layer that declares them. Default: 'app/auth.config' */
19
28
  clientConfig?: string;
20
29
  redirects?: {
30
+ /** Where to redirect unauthenticated users. Default: '/login' */
21
31
  login?: string;
32
+ /** Where to redirect authenticated users on guest-only routes. Default: '/' */
22
33
  guest?: string;
34
+ /** Where to navigate after successful signIn/signUp when no onSuccess is provided. Default: no automatic navigation */
35
+ authenticated?: string;
36
+ /** Where to navigate after logout. Default: no automatic navigation */
37
+ logout?: string;
23
38
  };
24
- /** Enable KV secondary storage for sessions. Requires hub.kv: true */
25
- secondaryStorage?: boolean;
39
+ /**
40
+ * When redirecting unauthenticated users to the login route, append a query param
41
+ * containing the originally requested path (for safe "return-to" redirects).
42
+ *
43
+ * Default: true
44
+ */
45
+ preserveRedirect?: boolean;
46
+ /**
47
+ * Query param key used by preserveRedirect.
48
+ *
49
+ * Default: 'redirect'
50
+ */
51
+ redirectQueryKey?: string;
52
+ session?: {
53
+ /**
54
+ * When enabled, and session/user are already hydrated from SSR, skip the initial
55
+ * client `/api/auth/get-session` bootstrap request. This also skips Better Auth's
56
+ * session refresh manager on those pages.
57
+ *
58
+ * Default: false
59
+ */
60
+ skipHydratedSsrGetSession?: boolean;
61
+ };
62
+ /**
63
+ * Enable secondary storage for sessions.
64
+ * - `true`: Use NuxtHub KV (requires hub.kv: true)
65
+ * - `'custom'`: User provides own secondaryStorage in defineServerAuth()
66
+ * - `false` (default): No secondary storage from module
67
+ */
68
+ hubSecondaryStorage?: boolean | 'custom';
26
69
  /** Schema generation options. Must match drizzleAdapter config. */
27
70
  schema?: {
28
71
  /** Plural table names: user → users. Default: false */
29
72
  usePlural?: boolean;
30
73
  /** Column/table name casing. Explicit value takes precedence over hub.db.casing. */
31
- casing?: CasingOption;
74
+ casing?: Casing;
32
75
  };
33
76
  }
34
77
  export interface AuthRuntimeConfig {
35
78
  redirects: {
36
79
  login: string;
37
80
  guest: string;
81
+ authenticated?: string;
82
+ logout?: string;
83
+ };
84
+ preserveRedirect: boolean;
85
+ redirectQueryKey: string;
86
+ useDatabase: boolean;
87
+ databaseProvider: EffectiveDatabaseProviderId;
88
+ clientOnly: boolean;
89
+ session: {
90
+ skipHydratedSsrGetSession: boolean;
38
91
  };
39
92
  }
40
93
  export interface AuthPrivateRuntimeConfig {
41
- secondaryStorage: boolean;
94
+ hubSecondaryStorage: boolean | 'custom';
42
95
  }
43
- export declare function defineServerAuth<T extends ServerAuthConfig>(config: (ctx: ServerAuthContext) => T): (ctx: ServerAuthContext) => T;
44
- export declare function defineClientAuth(config: ClientAuthConfigFn): ClientAuthConfigFn;
96
+ export declare function defineServerAuth<const R>(config: (ctx: ServerAuthContext) => R & ServerAuthConfig): (ctx: ServerAuthContext) => R;
97
+ export declare function defineServerAuth<const R>(config: R & ServerAuthConfig): (ctx: ServerAuthContext) => R;
98
+ export declare function defineClientAuth<T extends ClientAuthConfig>(config: T | ((ctx: ClientAuthContext) => T)): (baseURL: string) => ReturnType<typeof createAuthClient<T>>;