@nuxtjs/better-auth 0.3.0 → 0.3.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 (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +12 -0
  3. package/dist/module.d.mts +2 -2
  4. package/dist/module.json +1 -1
  5. package/dist/module.mjs +297 -93
  6. package/dist/runtime/app/composables/runWithSessionRefresh.js +2 -1
  7. package/dist/runtime/app/composables/useAuthRequestFetch.d.ts +1 -1
  8. package/dist/runtime/app/composables/useAuthRequestFetch.js +22 -2
  9. package/dist/runtime/app/composables/useSignIn.d.ts +1 -4
  10. package/dist/runtime/app/composables/useUserSession.js +33 -8
  11. package/dist/runtime/app/internal/redirect-helpers.js +22 -1
  12. package/dist/runtime/app/internal/session-fetch.d.ts +1 -0
  13. package/dist/runtime/app/internal/session-fetch.js +22 -8
  14. package/dist/runtime/app/internal/wrap-auth-method.d.ts +3 -2
  15. package/dist/runtime/app/internal/wrap-auth-method.js +9 -7
  16. package/dist/runtime/app/plugins/session.client.js +2 -1
  17. package/dist/runtime/app/plugins/session.server.js +8 -3
  18. package/dist/runtime/composables.d.ts +2 -0
  19. package/dist/runtime/internal/auth-route-rules.d.ts +3 -0
  20. package/dist/runtime/internal/auth-route-rules.js +10 -0
  21. package/dist/runtime/server/internal/cookie-headers.d.ts +3 -0
  22. package/dist/runtime/server/internal/cookie-headers.js +30 -0
  23. package/dist/runtime/server/internal/nitro2.d.ts +4 -2
  24. package/dist/runtime/server/internal/nitro2.js +2 -2
  25. package/dist/runtime/server/internal/nitro3.d.ts +2 -1
  26. package/dist/runtime/server/internal/nitro3.js +5 -32
  27. package/dist/runtime/server/middleware/route-access.js +7 -4
  28. package/dist/runtime/server/utils/auth.js +11 -2
  29. package/dist/runtime/server/utils/session.js +34 -32
  30. package/dist/runtime/test-utils/e2e.d.ts +18 -0
  31. package/dist/runtime/test-utils/e2e.js +48 -0
  32. package/dist/runtime/test-utils/module.d.ts +6 -0
  33. package/dist/runtime/test-utils/module.js +18 -0
  34. package/dist/runtime/test-utils/runtime.d.ts +25 -0
  35. package/dist/runtime/test-utils/runtime.js +56 -0
  36. package/dist/runtime/test-utils/server/handler.d.ts +2 -0
  37. package/dist/runtime/test-utils/server/handler.js +34 -0
  38. package/dist/runtime/test-utils/server/plugin.d.ts +23 -0
  39. package/dist/runtime/test-utils/server/plugin.js +2 -0
  40. package/dist/runtime/types.d.ts +1 -1
  41. package/dist/types.d.mts +2 -8
  42. package/package.json +32 -7
@@ -5,8 +5,9 @@ export async function runWithSessionRefresh(runner) {
5
5
  if (typeof runner !== "function")
6
6
  throw new TypeError("runWithSessionRefresh(runner) requires an async function");
7
7
  const auth = useUserSession();
8
+ const wasLoggedIn = auth.loggedIn.value;
8
9
  const result = await runner();
9
10
  if (!isAuthActionErrorResult(result))
10
- await refreshSessionAfterAuthAction(auth.fetchSession, auth.loggedIn, auth.waitForSession);
11
+ await refreshSessionAfterAuthAction(auth.fetchSession, auth.loggedIn, auth.waitForSession, true, wasLoggedIn);
11
12
  return result;
12
13
  }
@@ -6,6 +6,6 @@ type AuthRequestFetchExtractedMethod<Options> = Options extends undefined ? 'get
6
6
  } ? Method : never, undefined>, string>> extends infer NormalizedMethod extends string ? NormalizedMethod : 'get';
7
7
  type AuthRequestFetchMethodFromOptions<Options> = RequestFetchOptions extends Options ? 'get' : AuthRequestFetchExtractedMethod<Options>;
8
8
  type AuthRequestFetchResolvedMethod<Path extends AuthApiEndpointPath, Options> = Extract<AuthRequestFetchMethodFromOptions<Options>, AuthApiEndpointMethod<Path>> extends infer Method extends string ? Method : never;
9
- type AuthRequestFetch = <Path extends AuthApiEndpointPath, Options extends RequestFetchOptions = RequestFetchOptions>(request: Path, opts?: Options) => Promise<AuthApiEndpointResponse<Path, Extract<AuthRequestFetchResolvedMethod<Path, Options>, AuthApiEndpointMethod<Path>>>>;
9
+ type AuthRequestFetch = <Path extends AuthApiEndpointPath & ('/api/auth' | `/api/auth/${string}`), Options extends RequestFetchOptions = RequestFetchOptions>(request: Path, opts?: Options) => Promise<AuthApiEndpointResponse<Path, Extract<AuthRequestFetchResolvedMethod<Path, Options>, AuthApiEndpointMethod<Path>>>>;
10
10
  export declare function useAuthRequestFetch(): AuthRequestFetch & ReturnType<typeof useRequestFetch>;
11
11
  export {};
@@ -1,4 +1,24 @@
1
- import { useRequestFetch } from "#imports";
1
+ import createAppAuthClient from "#auth/client";
2
+ import { joinURL } from "ufo";
3
+ import { useRequestFetch, useRuntimeConfig } from "#imports";
2
4
  export function useAuthRequestFetch() {
3
- return useRequestFetch();
5
+ const requestFetch = useRequestFetch();
6
+ const runtimeConfig = useRuntimeConfig();
7
+ const authRuntimeConfig = runtimeConfig.public.auth;
8
+ if (!authRuntimeConfig?.clientOnly)
9
+ return requestFetch;
10
+ const siteUrl = runtimeConfig.public.siteUrl;
11
+ const clientOptions = createAppAuthClient.resolveOptions(siteUrl);
12
+ const configuredBaseURL = clientOptions.baseURL ?? siteUrl;
13
+ const baseURL = configuredBaseURL && new URL(configuredBaseURL).pathname.replace(/\/+$/, "") ? configuredBaseURL : joinURL(configuredBaseURL || "/", clientOptions.basePath ?? "/api/auth");
14
+ const externalRequestFetch = requestFetch;
15
+ return ((request, opts) => {
16
+ if (typeof request !== "string" || !/^\/api\/auth(?=\/|$)/.test(request))
17
+ return externalRequestFetch(request, opts);
18
+ return externalRequestFetch(request.slice("/api/auth".length), {
19
+ ...opts,
20
+ baseURL,
21
+ credentials: "include"
22
+ });
23
+ });
4
24
  }
@@ -1,9 +1,6 @@
1
- import type { AppAuthClient, AuthSocialProviderRegistry } from '#nuxt-better-auth';
1
+ import type { AppAuthClient, AuthSocialProviderId } from '#nuxt-better-auth';
2
2
  import type { ActionHandleFor } from '../internal/auth-action-handles.js';
3
3
  type SignIn = NonNullable<AppAuthClient>['signIn'];
4
- type AuthSocialProviderId = AuthSocialProviderRegistry extends {
5
- ids: infer T;
6
- } ? Extract<T, string> : never;
7
4
  type TypedSocialMethod<Method> = Method extends (data: infer Data, ...rest: infer Rest) => Promise<infer Result> ? (data: Omit<Extract<Data, Record<string, unknown>>, 'provider'> & {
8
5
  provider: AuthSocialProviderId;
9
6
  }, ...rest: Rest) => Promise<Result> : Method;
@@ -1,7 +1,7 @@
1
1
  import { computed, navigateTo, nextTick, useNuxtApp, useRequestURL, useRuntimeConfig, useState, watch } from "#imports";
2
2
  import { normalizeAuthActionError } from "../internal/auth-action-error.js";
3
3
  import { resolvePostAuthSuccessRedirect, withFallbackSocialCallbackURL } from "../internal/redirect-helpers.js";
4
- import { fetchSessionClient, fetchSessionServer, stripToken } from "../internal/session-fetch.js";
4
+ import { fetchSessionClient, fetchSessionServer, isExpectedSignedOutSessionError, stripToken } from "../internal/session-fetch.js";
5
5
  import { isRecord } from "../internal/utils.js";
6
6
  import { createVueSafeAuthFacade, isAuthProxyProbeKey } from "../internal/vue-safe-auth-proxy.js";
7
7
  import { wrapAuthMethod } from "../internal/wrap-auth-method.js";
@@ -22,6 +22,11 @@ function createServerOnlyActionNamespace(path) {
22
22
  }
23
23
  const _signInServerOnly = createServerOnlyActionNamespace("signIn");
24
24
  const _signUpServerOnly = createServerOnlyActionNamespace("signUp");
25
+ function shouldWaitForSignUpSession(context) {
26
+ if (!isRecord(context) || !isRecord(context.data))
27
+ return true;
28
+ return context.data.token !== null;
29
+ }
25
30
  export function useUserSession() {
26
31
  const runtimeFlags = getAuthRuntimeFlags();
27
32
  const runtimeConfig = useRuntimeConfig();
@@ -88,14 +93,28 @@ export function useUserSession() {
88
93
  throw error;
89
94
  }
90
95
  }
96
+ let mountedReconciliationStarted = false;
91
97
  function queueHydrationReconciliation() {
92
98
  if (hydrationReconcileQueued.value)
93
99
  return;
94
100
  hydrationReconcileQueued.value = true;
95
- nuxtApp.hook("app:mounted", async () => {
96
- await fetchSession({ force: true });
97
- hydrationReconcileQueued.value = false;
98
- });
101
+ const reconcile = async () => {
102
+ try {
103
+ await fetchSession({ force: true });
104
+ } catch (error) {
105
+ console.error("[nuxt-better-auth] Failed to fetch session during hydration reconciliation:", error);
106
+ } finally {
107
+ hydrationReconcileQueued.value = false;
108
+ }
109
+ };
110
+ if (mountedReconciliationStarted) {
111
+ void reconcile();
112
+ } else {
113
+ nuxtApp.hook("app:mounted", () => {
114
+ mountedReconciliationStarted = true;
115
+ return reconcile();
116
+ });
117
+ }
99
118
  }
100
119
  if (runtimeFlags.client && rawClient && !_sessionSyncApps.has(nuxtApp)) {
101
120
  const clientSession = rawClient.useSession();
@@ -118,7 +137,8 @@ export function useUserSession() {
118
137
  queueHydrationReconciliation();
119
138
  return;
120
139
  }
121
- clearSession();
140
+ if (!newSession?.error || isExpectedSignedOutSessionError(newSession.error))
141
+ clearSession();
122
142
  }
123
143
  if (!authReady.value && !newSession?.isPending && !newSession?.isRefetching)
124
144
  authReady.value = true;
@@ -203,8 +223,9 @@ export function useAuthActionNamespaces() {
203
223
  wrapDeps,
204
224
  isRedirectOAuthSignIn ? {
205
225
  shouldSkipSessionSync: (data) => !isRecord(data) || data.disableRedirect !== true,
226
+ shouldWaitForSession: () => true,
206
227
  transformData: (data) => withFallbackSocialCallbackURL(data, requestURL)
207
- } : {}
228
+ } : { shouldWaitForSession: () => true }
208
229
  );
209
230
  }) : _signInServerOnly;
210
231
  const signUp = rawClient?.signUp ? createVueSafeAuthFacade((prop) => {
@@ -212,7 +233,11 @@ export function useAuthActionNamespaces() {
212
233
  const method = targetRecord[prop];
213
234
  if (typeof method !== "function")
214
235
  return method;
215
- return wrapAuthMethod((...args) => targetRecord[prop](...args), wrapDeps);
236
+ return wrapAuthMethod(
237
+ (...args) => targetRecord[prop](...args),
238
+ wrapDeps,
239
+ { shouldWaitForSession: shouldWaitForSignUpSession }
240
+ );
216
241
  }) : _signUpServerOnly;
217
242
  return { signIn, signUp };
218
243
  }
@@ -1,11 +1,32 @@
1
1
  import { navigateTo, useRuntimeConfig } from "#imports";
2
2
  import { isRecord } from "./utils.js";
3
+ const ENCODED_PATH_SEPARATOR_RE = /%(?:2f|5c)/gi;
4
+ const REDIRECT_VALIDATION_ORIGIN = "https://nuxt-better-auth.invalid";
5
+ function containsControlCharacter(value) {
6
+ return Array.from(value).some((character) => {
7
+ const code = character.charCodeAt(0);
8
+ return code <= 31 || code === 127;
9
+ });
10
+ }
3
11
  export function isSafeLocalRedirect(redirect) {
4
12
  if (typeof redirect !== "string")
5
13
  return;
6
14
  if (!redirect.startsWith("/") || redirect.startsWith("//"))
7
15
  return;
8
- return redirect;
16
+ if (redirect.includes("\\") || containsControlCharacter(redirect))
17
+ return;
18
+ try {
19
+ const parsed = new URL(redirect, REDIRECT_VALIDATION_ORIGIN);
20
+ if (parsed.origin !== REDIRECT_VALIDATION_ORIGIN || parsed.pathname.startsWith("//"))
21
+ return;
22
+ const normalizedPath = parsed.pathname.replace(ENCODED_PATH_SEPARATOR_RE, "/");
23
+ const normalized = new URL(normalizedPath, REDIRECT_VALIDATION_ORIGIN);
24
+ if (normalized.origin !== REDIRECT_VALIDATION_ORIGIN || normalized.pathname.startsWith("//"))
25
+ return;
26
+ return redirect;
27
+ } catch {
28
+ return void 0;
29
+ }
9
30
  }
10
31
  export function resolvePostAuthRedirect(requestURL) {
11
32
  const runtimeConfig = useRuntimeConfig();
@@ -3,6 +3,7 @@ import type { AppAuthClient, AuthSession, AuthUser, ClientAuthSession } from '#n
3
3
  export declare function stripToken(session: AuthSession & {
4
4
  token?: string;
5
5
  }): ClientAuthSession;
6
+ export declare function isExpectedSignedOutSessionError(error: unknown): boolean;
6
7
  export declare function fetchSessionServer(session: Ref<ClientAuthSession | null>, user: Ref<AuthUser | null>, authReady: Ref<boolean>, options?: {
7
8
  headers?: HeadersInit;
8
9
  force?: boolean;
@@ -5,12 +5,26 @@ export function stripToken(session) {
5
5
  const { token: _, ...safe } = session;
6
6
  return safe;
7
7
  }
8
- function isExpectedSignedOutSessionError(error) {
8
+ export function isExpectedSignedOutSessionError(error) {
9
9
  const normalizedError = normalizeAuthActionError(error);
10
10
  if (normalizedError.status === 401)
11
11
  return true;
12
12
  return normalizedError.code === "UNAUTHORIZED";
13
13
  }
14
+ function handleSessionFetchError(error, session, user) {
15
+ if (isExpectedSignedOutSessionError(error)) {
16
+ session.value = null;
17
+ user.value = null;
18
+ return;
19
+ }
20
+ if (error instanceof Error)
21
+ throw error;
22
+ const normalizedError = normalizeAuthActionError(error);
23
+ throw new Error(
24
+ `[nuxt-better-auth] Failed to fetch session: ${normalizedError.message}`,
25
+ { cause: error }
26
+ );
27
+ }
14
28
  export async function fetchSessionServer(session, user, authReady, options = {}) {
15
29
  try {
16
30
  const headers = options.headers || useRequestHeaders(["cookie"]);
@@ -27,9 +41,8 @@ export async function fetchSessionServer(session, user, authReady, options = {})
27
41
  session.value = null;
28
42
  user.value = null;
29
43
  }
30
- } catch {
31
- session.value = null;
32
- user.value = null;
44
+ } catch (error) {
45
+ handleSessionFetchError(error, session, user);
33
46
  } finally {
34
47
  if (!authReady.value)
35
48
  authReady.value = true;
@@ -41,6 +54,10 @@ export async function fetchSessionClient(client, session, user, authReady, optio
41
54
  const fetchOptions = { ...headers ? { headers } : {}, throw: false };
42
55
  const query = options.force ? { disableCookieCache: true } : void 0;
43
56
  const result = await client.getSession({ query }, fetchOptions);
57
+ if (result.error) {
58
+ handleSessionFetchError(result.error, session, user);
59
+ return;
60
+ }
44
61
  const data = result.data;
45
62
  if (data?.session && data?.user) {
46
63
  session.value = stripToken(data.session);
@@ -50,10 +67,7 @@ export async function fetchSessionClient(client, session, user, authReady, optio
50
67
  user.value = null;
51
68
  }
52
69
  } catch (error) {
53
- session.value = null;
54
- user.value = null;
55
- if (!isExpectedSignedOutSessionError(error))
56
- console.error("[nuxt-better-auth] Failed to fetch session:", error);
70
+ handleSessionFetchError(error, session, user);
57
71
  } finally {
58
72
  if (!authReady.value)
59
73
  authReady.value = true;
@@ -1,10 +1,10 @@
1
1
  import type { ComputedRef } from 'vue';
2
2
  export declare function refreshSessionAfterAuthAction(fetchSession: (options?: {
3
3
  force?: boolean;
4
- }) => Promise<void>, loggedIn: ComputedRef<boolean>, waitForSession: () => Promise<void>): Promise<void>;
4
+ }) => Promise<void>, loggedIn: ComputedRef<boolean>, waitForSession: () => Promise<void>, shouldWaitForSession?: boolean, wasLoggedIn?: boolean): Promise<void>;
5
5
  export declare function wrapOnSuccess(fetchSession: (options?: {
6
6
  force?: boolean;
7
- }) => Promise<void>, loggedIn: ComputedRef<boolean>, waitForSession: () => Promise<void>, cb: (ctx: unknown) => void | Promise<void>): (ctx: unknown) => Promise<void>;
7
+ }) => Promise<void>, loggedIn: ComputedRef<boolean>, waitForSession: () => Promise<void>, cb: (ctx: unknown) => void | Promise<void>, shouldWaitForSession: (ctx: unknown) => boolean, wasLoggedIn: boolean): (ctx: unknown) => Promise<void>;
8
8
  export declare function wrapAuthMethod<T extends (...args: unknown[]) => Promise<unknown>>(method: T, deps: {
9
9
  fetchSession: (options?: {
10
10
  force?: boolean;
@@ -14,5 +14,6 @@ export declare function wrapAuthMethod<T extends (...args: unknown[]) => Promise
14
14
  resolvePostAuthSuccessRedirect: () => (() => Promise<void>) | undefined;
15
15
  }, wrapOptions?: {
16
16
  shouldSkipSessionSync?: (data: unknown, options: unknown) => boolean;
17
+ shouldWaitForSession?: (ctx: unknown) => boolean;
17
18
  transformData?: (data: unknown, options: unknown) => unknown;
18
19
  }): T;
@@ -1,19 +1,20 @@
1
1
  import { nextTick } from "#imports";
2
2
  import { isRecord } from "./utils.js";
3
- export async function refreshSessionAfterAuthAction(fetchSession, loggedIn, waitForSession) {
3
+ export async function refreshSessionAfterAuthAction(fetchSession, loggedIn, waitForSession, shouldWaitForSession = true, wasLoggedIn = loggedIn.value) {
4
4
  await fetchSession({ force: true });
5
- if (!loggedIn.value)
5
+ if (shouldWaitForSession && !wasLoggedIn && !loggedIn.value)
6
6
  await waitForSession();
7
7
  await nextTick();
8
8
  }
9
- export function wrapOnSuccess(fetchSession, loggedIn, waitForSession, cb) {
9
+ export function wrapOnSuccess(fetchSession, loggedIn, waitForSession, cb, shouldWaitForSession, wasLoggedIn) {
10
10
  return async (ctx) => {
11
- await refreshSessionAfterAuthAction(fetchSession, loggedIn, waitForSession);
11
+ await refreshSessionAfterAuthAction(fetchSession, loggedIn, waitForSession, shouldWaitForSession(ctx), wasLoggedIn);
12
12
  await cb(ctx);
13
13
  };
14
14
  }
15
15
  export function wrapAuthMethod(method, deps, wrapOptions = {}) {
16
16
  return (async (...args) => {
17
+ const wasLoggedIn = deps.loggedIn.value;
17
18
  const originalData = args[0];
18
19
  const options = args[1];
19
20
  const data = wrapOptions.transformData?.(originalData, options) ?? originalData;
@@ -24,18 +25,19 @@ export function wrapAuthMethod(method, deps, wrapOptions = {}) {
24
25
  const fetchOptions = isRecord(dataRecord?.fetchOptions) ? dataRecord.fetchOptions : void 0;
25
26
  const nestedOnSuccess = fetchOptions?.onSuccess;
26
27
  const topLevelOnSuccess = optionsRecord?.onSuccess;
28
+ const shouldWaitForSession = wrapOptions.shouldWaitForSession ?? (() => false);
27
29
  const fallbackOnSuccess = deps.resolvePostAuthSuccessRedirect();
28
30
  const wrappedFallbackOnSuccess = fallbackOnSuccess && wrapOnSuccess(deps.fetchSession, deps.loggedIn, deps.waitForSession, async () => {
29
31
  if (!deps.loggedIn.value)
30
32
  return;
31
33
  await fallbackOnSuccess();
32
- });
34
+ }, shouldWaitForSession, wasLoggedIn);
33
35
  if (typeof nestedOnSuccess === "function") {
34
36
  const nextData = {
35
37
  ...dataRecord,
36
38
  fetchOptions: {
37
39
  ...fetchOptions,
38
- onSuccess: wrapOnSuccess(deps.fetchSession, deps.loggedIn, deps.waitForSession, nestedOnSuccess)
40
+ onSuccess: wrapOnSuccess(deps.fetchSession, deps.loggedIn, deps.waitForSession, nestedOnSuccess, shouldWaitForSession, wasLoggedIn)
39
41
  }
40
42
  };
41
43
  return method(nextData, options);
@@ -43,7 +45,7 @@ export function wrapAuthMethod(method, deps, wrapOptions = {}) {
43
45
  if (typeof topLevelOnSuccess === "function") {
44
46
  const nextOptions = {
45
47
  ...optionsRecord,
46
- onSuccess: wrapOnSuccess(deps.fetchSession, deps.loggedIn, deps.waitForSession, topLevelOnSuccess)
48
+ onSuccess: wrapOnSuccess(deps.fetchSession, deps.loggedIn, deps.waitForSession, topLevelOnSuccess, shouldWaitForSession, wasLoggedIn)
47
49
  };
48
50
  return method(data, nextOptions);
49
51
  }
@@ -5,7 +5,8 @@ export default defineNuxtPlugin(async (nuxtApp) => {
5
5
  const safeFetch = async () => {
6
6
  try {
7
7
  await fetchSession();
8
- } catch {
8
+ } catch (error) {
9
+ console.error("[nuxt-better-auth] Failed to fetch session during app startup:", error);
9
10
  }
10
11
  };
11
12
  if (!nuxtApp.payload.serverRendered) {
@@ -1,5 +1,6 @@
1
1
  import { parseJSON } from "better-auth/client";
2
- import { defineNuxtPlugin, useRequestEvent, useRequestHeaders, useState } from "#imports";
2
+ import { defineNuxtPlugin, useRequestEvent, useRequestFetch, useState } from "#imports";
3
+ import { appendSetCookieHeaders } from "../../server/internal/cookie-headers.js";
3
4
  export default defineNuxtPlugin({
4
5
  name: "auth:session-init",
5
6
  enforce: "pre",
@@ -10,8 +11,12 @@ export default defineNuxtPlugin({
10
11
  const event = useRequestEvent();
11
12
  if (event) {
12
13
  try {
13
- const headers = useRequestHeaders(["cookie"]);
14
- const data = await $fetch("/api/auth/get-session", { headers, parseResponse: parseJSON });
14
+ const data = await useRequestFetch()("/api/auth/get-session", {
15
+ parseResponse: parseJSON,
16
+ onResponse({ response }) {
17
+ appendSetCookieHeaders(event, response.headers);
18
+ }
19
+ });
15
20
  if (data?.session && data?.user) {
16
21
  const { token: _, ...safeSession } = data.session;
17
22
  session.value = safeSession;
@@ -1,6 +1,7 @@
1
1
  export { runWithSessionRefresh } from './app/composables/runWithSessionRefresh.js';
2
2
  export { useAction } from './app/composables/useAction.js';
3
3
  export { useAuthAsyncData } from './app/composables/useAuthAsyncData.js';
4
+ export type { UseAuthAsyncDataOptions } from './app/composables/useAuthAsyncData.js';
4
5
  export { useAuthClient } from './app/composables/useAuthClient.js';
5
6
  export { useAuthClientAction } from './app/composables/useAuthClientAction.js';
6
7
  export { useAuthRequestFetch } from './app/composables/useAuthRequestFetch.js';
@@ -11,3 +12,4 @@ export { useUserSession } from './app/composables/useUserSession.js';
11
12
  export type { SignOutOptions, UseUserSessionReturn } from './app/composables/useUserSession.js';
12
13
  export { useUserSessionState } from './app/composables/useUserSessionState.js';
13
14
  export type { UseUserSessionStateReturn } from './app/composables/useUserSessionState.js';
15
+ export type { ActionHandleFor, UserAuthActionHandle, UserAuthActionStatus } from './app/internal/auth-action-handles.js';
@@ -1 +1,4 @@
1
+ import type { AuthRouteRules } from '../types.js';
2
+ export declare function normalizeAuthRoutePath(path: string, baseURL?: string): string;
3
+ export declare function normalizeAuthRouteRule(rule: unknown): AuthRouteRules['auth'];
1
4
  export declare function shouldSkipAuthRouteRules(path: string): boolean;
@@ -1,3 +1,4 @@
1
+ import { withoutBase } from "ufo";
1
2
  const internalRouteRuleAuthPaths = /* @__PURE__ */ new Set([
2
3
  "/_ipx",
3
4
  "/_nuxt",
@@ -18,6 +19,15 @@ const internalRouteRuleAuthPrefixes = [
18
19
  "/api/_nuxt_icon/",
19
20
  "/api/auth/"
20
21
  ];
22
+ export function normalizeAuthRoutePath(path, baseURL = "/") {
23
+ const pathname = path.split(/[?#]/, 1)[0] || "/";
24
+ return withoutBase(pathname, baseURL) || "/";
25
+ }
26
+ export function normalizeAuthRouteRule(rule) {
27
+ if (rule && typeof rule === "object" && Object.hasOwn(rule, "options"))
28
+ return rule.options;
29
+ return rule;
30
+ }
21
31
  export function shouldSkipAuthRouteRules(path) {
22
32
  const pathname = path.split(/[?#]/, 1)[0] || "/";
23
33
  return internalRouteRuleAuthPaths.has(pathname) || internalRouteRuleAuthPrefixes.some((prefix) => pathname.startsWith(prefix));
@@ -0,0 +1,3 @@
1
+ import type { ServerEvent } from './nitro-compat.js';
2
+ export declare function appendCookieHeader(event: ServerEvent, header: string): void;
3
+ export declare function appendSetCookieHeaders(event: ServerEvent, headers: Headers): void;
@@ -0,0 +1,30 @@
1
+ export function appendCookieHeader(event, header) {
2
+ const nodeResponse = event.node?.res;
3
+ if (nodeResponse?.setHeader) {
4
+ const current = nodeResponse.getHeader?.("set-cookie");
5
+ if (Array.isArray(current))
6
+ nodeResponse.setHeader("set-cookie", [...current, header]);
7
+ else if (typeof current === "string")
8
+ nodeResponse.setHeader("set-cookie", [current, header]);
9
+ else
10
+ nodeResponse.setHeader("set-cookie", [header]);
11
+ return;
12
+ }
13
+ const eventWithResponse = event;
14
+ const responseHeaders = eventWithResponse.res?.headers ?? eventWithResponse.response?.headers;
15
+ responseHeaders?.append("set-cookie", header);
16
+ }
17
+ function getSetCookieHeaders(headers) {
18
+ const cookieHeaders = headers;
19
+ const getSetCookie = cookieHeaders.getSetCookie;
20
+ if (getSetCookie)
21
+ return getSetCookie.call(headers);
22
+ if (cookieHeaders.getAll)
23
+ return cookieHeaders.getAll.call(headers, "set-cookie");
24
+ const header = headers.get("set-cookie");
25
+ return header ? [header] : [];
26
+ }
27
+ export function appendSetCookieHeaders(event, headers) {
28
+ for (const header of getSetCookieHeaders(headers))
29
+ appendCookieHeader(event, header);
30
+ }
@@ -1,8 +1,10 @@
1
1
  import type { H3Event } from 'h3';
2
2
  import type { AuthRouteRules } from '../../types.js';
3
- import { defineEventHandler, getQuery, getRequestHost, getRequestProtocol, getRequestURL, readBody, splitCookiesString, toWebRequest } from 'h3';
3
+ import { splitSetCookieString } from 'cookie-es';
4
+ import { defineEventHandler, getQuery, getRequestHost, getRequestProtocol, getRequestURL, readBody, toWebRequest } from 'h3';
4
5
  import { useRuntimeConfig } from 'nitropack/runtime';
5
- export { defineEventHandler, getQuery, getRequestHost, getRequestProtocol, getRequestURL, readBody, splitCookiesString, toWebRequest, useRuntimeConfig, };
6
+ export { defineEventHandler, getQuery, getRequestHost, getRequestProtocol, getRequestURL, readBody, toWebRequest, useRuntimeConfig, };
6
7
  export type ServerEvent = H3Event;
8
+ export { splitSetCookieString as splitCookiesString };
7
9
  export declare function getAuthRouteRules(event: ServerEvent): AuthRouteRules;
8
10
  export declare function createAuthError(status: number, statusText: string): Error;
@@ -1,3 +1,4 @@
1
+ import { splitSetCookieString } from "cookie-es";
1
2
  import {
2
3
  createError,
3
4
  defineEventHandler,
@@ -6,7 +7,6 @@ import {
6
7
  getRequestProtocol,
7
8
  getRequestURL,
8
9
  readBody,
9
- splitCookiesString,
10
10
  toWebRequest
11
11
  } from "h3";
12
12
  import { getRouteRules, useRuntimeConfig } from "nitropack/runtime";
@@ -17,10 +17,10 @@ export {
17
17
  getRequestProtocol,
18
18
  getRequestURL,
19
19
  readBody,
20
- splitCookiesString,
21
20
  toWebRequest,
22
21
  useRuntimeConfig
23
22
  };
23
+ export { splitSetCookieString as splitCookiesString };
24
24
  export function getAuthRouteRules(event) {
25
25
  return getRouteRules(event);
26
26
  }
@@ -1,10 +1,11 @@
1
1
  import type { H3Event } from 'nitro/h3';
2
2
  import type { AuthRouteRules } from '../../types.js';
3
+ import { splitSetCookieString } from 'cookie-es';
3
4
  import { defineEventHandler, getQuery, getRequestHost, getRequestProtocol, getRequestURL, readBody } from 'nitro/h3';
4
5
  import { useRuntimeConfig } from 'nitro/runtime-config';
5
6
  export { defineEventHandler, getQuery, getRequestHost, getRequestProtocol, getRequestURL, readBody, useRuntimeConfig, };
6
7
  export type ServerEvent = H3Event;
8
+ export { splitSetCookieString as splitCookiesString };
7
9
  export declare function getAuthRouteRules(event: ServerEvent): AuthRouteRules;
8
10
  export declare function createAuthError(status: number, statusText: string): Error;
9
11
  export declare function toWebRequest(event: ServerEvent): Request;
10
- export declare function splitCookiesString(header: string): string[];
@@ -1,3 +1,4 @@
1
+ import { splitSetCookieString } from "cookie-es";
1
2
  import { getRouteRules } from "nitro/app";
2
3
  import {
3
4
  defineEventHandler,
@@ -9,6 +10,7 @@ import {
9
10
  readBody
10
11
  } from "nitro/h3";
11
12
  import { useRuntimeConfig } from "nitro/runtime-config";
13
+ import { normalizeAuthRouteRule } from "../../internal/auth-route-rules.js";
12
14
  export {
13
15
  defineEventHandler,
14
16
  getQuery,
@@ -18,9 +20,10 @@ export {
18
20
  readBody,
19
21
  useRuntimeConfig
20
22
  };
23
+ export { splitSetCookieString as splitCookiesString };
21
24
  export function getAuthRouteRules(event) {
22
- const auth = getRouteRules(event.req.method, getRequestURL(event).pathname).routeRules.auth?.options;
23
- return { auth };
25
+ const auth = getRouteRules(event.req.method, getRequestURL(event).pathname).routeRules?.auth;
26
+ return { auth: normalizeAuthRouteRule(auth) };
24
27
  }
25
28
  export function createAuthError(status, statusText) {
26
29
  return HTTPError.status(status, statusText);
@@ -28,33 +31,3 @@ export function createAuthError(status, statusText) {
28
31
  export function toWebRequest(event) {
29
32
  return event.req;
30
33
  }
31
- export function splitCookiesString(header) {
32
- const cookies = [];
33
- let position = 0;
34
- while (position < header.length) {
35
- const start = position;
36
- let separator;
37
- while (position < header.length) {
38
- if (header[position] !== ",") {
39
- position += 1;
40
- continue;
41
- }
42
- separator = position;
43
- position += 1;
44
- while (position < header.length && /\s/.test(header[position]))
45
- position += 1;
46
- const nextStart = position;
47
- while (position < header.length && !["=", ";", ","].includes(header[position]))
48
- position += 1;
49
- if (header[position] === "=") {
50
- cookies.push(header.slice(start, separator));
51
- position = nextStart;
52
- break;
53
- }
54
- position = separator + 1;
55
- }
56
- if (separator === void 0 || position >= header.length)
57
- cookies.push(header.slice(start));
58
- }
59
- return cookies;
60
- }
@@ -1,10 +1,13 @@
1
- import { shouldSkipAuthRouteRules } from "../../internal/auth-route-rules.js";
1
+ import { normalizeAuthRoutePath, shouldSkipAuthRouteRules } from "../../internal/auth-route-rules.js";
2
2
  import { matchesUser } from "../../utils/match-user.js";
3
- import { createAuthError, defineEventHandler, getAuthRouteRules, getRequestURL } from "../internal/nitro-compat.js";
3
+ import { createAuthError, defineEventHandler, getAuthRouteRules, getRequestURL, useRuntimeConfig } from "../internal/nitro-compat.js";
4
4
  import { getUserSession, requireUserSession } from "../utils/session.js";
5
5
  export default defineEventHandler(async (event) => {
6
- const path = getRequestURL(event).pathname;
7
- if (!path.startsWith("/api/"))
6
+ const path = normalizeAuthRoutePath(
7
+ getRequestURL(event).pathname,
8
+ useRuntimeConfig().app?.baseURL
9
+ );
10
+ if (path !== "/api" && !path.startsWith("/api/"))
8
11
  return;
9
12
  if (shouldSkipAuthRouteRules(path))
10
13
  return;
@@ -43,9 +43,18 @@ function logInferredBaseURL(baseURL, source) {
43
43
  }
44
44
  function validateURL(url) {
45
45
  try {
46
- return normalizeLoopbackOrigin(new URL(url).origin);
46
+ const firstCode = url.charCodeAt(0);
47
+ if (firstCode <= 32 || firstCode === 127)
48
+ throw new Error("leading whitespace and control characters are not allowed");
49
+ const parsed = new URL(url);
50
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
51
+ throw new Error("unsupported protocol");
52
+ const hasUserinfo = /^https?:[/\\]*[^/\\?#]*@/i.test(url.trim().replace(/[\t\n\r]/g, ""));
53
+ if (parsed.username || parsed.password || hasUserinfo)
54
+ throw new Error("credentials are not allowed");
55
+ return normalizeLoopbackOrigin(parsed.origin);
47
56
  } catch {
48
- throw new Error(`Invalid siteUrl: "${url}". Must be a valid URL.`);
57
+ throw new Error("Invalid siteUrl. Must be a valid HTTP(S) URL without credentials.");
49
58
  }
50
59
  }
51
60
  function resolveConfiguredSiteUrl(config) {