cloudflare-next-intl 0.6.30 → 0.6.31

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.
@@ -128,6 +128,7 @@ export default async function intlMiddleware(request, options) {
128
128
  }
129
129
  response.headers.set('Content-Language', effectiveLocaleForRequest);
130
130
  response.headers.set('x-pathname', pathWithoutLocale);
131
+ response.headers.set('x-search', search);
131
132
  // Auto-wires the firebase_auth submodule's redirect/session-refresh
132
133
  // logic when `firebaseAuth` is configured — dynamic import so this
133
134
  // file never pulls in firebase_auth/** (and transitively firebase/*)
@@ -10,6 +10,7 @@ import { setAuthUserCache } from './auth_user_cache';
10
10
  import { defaultAppCheckTokenCookieName, defaultEmailVerifiedHintCookieName, defaultRefreshTokenCookieName, defaultSessionCookieName } from '../middleware/update_session';
11
11
  import decodeJwtPayload from '../decode_jwt_payload';
12
12
  import isWhitelisted from '../is_whitelisted';
13
+ import withRedirectQuery from '../preserve_redirect_query';
13
14
  import setCookie from '../../client/functions/set_cookie';
14
15
  import getCookie from '../../client/functions/get_cookie';
15
16
  import clearSessionAction from '../server/clear_session_action';
@@ -155,14 +156,14 @@ export default function AuthUserProvider({ initialUser = null, children }) {
155
156
  // Signed-out on an auth page is where they're supposed to be —
156
157
  // only bounce a signed-out user away from a NON-auth page.
157
158
  if (!isAuthPage && confirmedSignedOut)
158
- router.replace(fa.redirectAuthPath);
159
+ router.replace(withRedirectQuery(fa.redirectAuthPath, window.location.search));
159
160
  }
160
161
  else if (fa.verifyEmailPath && !user.emailVerified && pathname !== fa.verifyEmailPath) {
161
162
  // Checked before the auth-page redirect below (mirrors
162
163
  // `update_session.ts`'s same ordering): an unverified signed-in
163
164
  // user must land on verifyEmailPath even if they navigated to
164
165
  // an auth page like /login — homePath isn't reachable yet either.
165
- router.replace(fa.verifyEmailPath);
166
+ router.replace(withRedirectQuery(fa.verifyEmailPath, window.location.search));
166
167
  }
167
168
  else if (isAuthPage || (fa.verifyEmailPath && user.emailVerified && pathname === fa.verifyEmailPath)) {
168
169
  // Mirrors the middleware's own signed-in-on-auth-page redirect
@@ -173,7 +174,7 @@ export default function AuthUserProvider({ initialUser = null, children }) {
173
174
  // there until a hard refresh. A verified user reaching
174
175
  // verifyEmailPath itself gets the same "go home" treatment —
175
176
  // they're done here, same as the auth-page case.
176
- router.replace(fa.homePath);
177
+ router.replace(withRedirectQuery(fa.homePath, window.location.search));
177
178
  }
178
179
  // eslint-disable-next-line react-hooks/exhaustive-deps
179
180
  }, [state, pathname, isAuthPage, isWhiteListed, confirmedSignedOut]);
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
2
2
  import config from '@intl-config';
3
3
  import decodeJwtPayload from '../decode_jwt_payload';
4
4
  import isWhitelisted from '../is_whitelisted';
5
+ import withRedirectQuery from '../preserve_redirect_query';
5
6
  export const defaultSessionCookieName = '__fa_session__';
6
7
  export const defaultRefreshTokenCookieName = '__fa_refresh_token__';
7
8
  // Non-httpOnly: written by AuthUserProvider (client) every time it observes
@@ -203,7 +204,7 @@ export default async function updateSession(request, baseResponse, locale, rebui
203
204
  return baseResponse;
204
205
  }
205
206
  const localePrefix = locale === config.defaultLocale ? '' : requestPrefix;
206
- const localeUrl = (target) => new URL(`${localePrefix}${target === '/' ? '' : target}` || '/', request.url);
207
+ const localeUrl = (target) => new URL(withRedirectQuery(`${localePrefix}${target === '/' ? '' : target}` || '/', request.nextUrl.search), request.url);
207
208
  // Emailed Firebase action links all arrive on the single project-wide
208
209
  // action URL carrying `?mode=<action>&oobCode=...`. Forward them to the
209
210
  // page for that mode BEFORE any auth/whitelist check below: these links
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Whether the firebase_auth redirects should carry the request's query
3
+ * string over to their target. Defaults to `true` — see
4
+ * `FirebaseAuthRoutingConfig.preserveRedirectQuery`.
5
+ */
6
+ export declare function preserveRedirectQueryEnabled(): boolean;
7
+ /**
8
+ * Appends `search` to a redirect target path, honoring the
9
+ * `preserveRedirectQuery` setting. Shared by all three places that redirect
10
+ * to `redirectAuthPath`/`homePath`/`verifyEmailPath` — the middleware
11
+ * (`update_session`), the RSC pre-render redirect
12
+ * (`resolveAuthUserAndRedirect`), and the client `AuthUserProvider` effect —
13
+ * so they can't drift apart on whether a query string survives.
14
+ *
15
+ * @param search Leading-`?` query string (`''` when there is none), from
16
+ * `request.nextUrl.search`, the `x-search` header, or
17
+ * `window.location.search` depending on the caller's runtime.
18
+ */
19
+ export default function withRedirectQuery(target: string, search: string): string;
@@ -0,0 +1,26 @@
1
+ import config from '@intl-config';
2
+ /**
3
+ * Whether the firebase_auth redirects should carry the request's query
4
+ * string over to their target. Defaults to `true` — see
5
+ * `FirebaseAuthRoutingConfig.preserveRedirectQuery`.
6
+ */
7
+ export function preserveRedirectQueryEnabled() {
8
+ return config.firebaseAuth?.preserveRedirectQuery !== false;
9
+ }
10
+ /**
11
+ * Appends `search` to a redirect target path, honoring the
12
+ * `preserveRedirectQuery` setting. Shared by all three places that redirect
13
+ * to `redirectAuthPath`/`homePath`/`verifyEmailPath` — the middleware
14
+ * (`update_session`), the RSC pre-render redirect
15
+ * (`resolveAuthUserAndRedirect`), and the client `AuthUserProvider` effect —
16
+ * so they can't drift apart on whether a query string survives.
17
+ *
18
+ * @param search Leading-`?` query string (`''` when there is none), from
19
+ * `request.nextUrl.search`, the `x-search` header, or
20
+ * `window.location.search` depending on the caller's runtime.
21
+ */
22
+ export default function withRedirectQuery(target, search) {
23
+ if (!search || !preserveRedirectQueryEnabled())
24
+ return target;
25
+ return `${target}${search}`;
26
+ }
@@ -5,6 +5,7 @@ import { redirect } from 'next/navigation';
5
5
  import config from '@intl-config';
6
6
  import requireFirebaseAuthConfig from '../require_config';
7
7
  import { getAuthenticatedAppForUser } from './firebase_server';
8
+ import withRedirectQuery from '../preserve_redirect_query';
8
9
  const AuthUserProvider = dynamic(() => import('../client/auth_user_provider'));
9
10
  /**
10
11
  * Resolves the signed-in user from the session cookie and performs the
@@ -25,14 +26,19 @@ export async function resolveAuthUserAndRedirect() {
25
26
  const fa = config.firebaseAuth;
26
27
  requireFirebaseAuthConfig(fa);
27
28
  const { currentUser } = await getAuthenticatedAppForUser();
28
- const path = (await headers()).get('x-pathname') ?? '/';
29
+ const requestHeaders = await headers();
30
+ const path = requestHeaders.get('x-pathname') ?? '/';
29
31
  const isAuthPage = fa.isAuthPath(path);
30
32
  const isWhiteListed = fa.whiteListPaths?.includes(path) ?? false;
33
+ // `x-pathname` is path-only, so the query string comes from `x-search`
34
+ // (set alongside it by `intlMiddleware`) — `redirect()` takes a plain
35
+ // string, not a URL.
36
+ const search = requestHeaders.get('x-search') ?? '';
31
37
  if (!isWhiteListed) {
32
38
  if (!currentUser && !isAuthPage)
33
- redirect(fa.redirectAuthPath);
39
+ redirect(withRedirectQuery(fa.redirectAuthPath, search));
34
40
  if (currentUser && isAuthPage)
35
- redirect(fa.homePath);
41
+ redirect(withRedirectQuery(fa.homePath, search));
36
42
  }
37
43
  return currentUser && {
38
44
  uid: currentUser.uid,
@@ -495,6 +495,16 @@ export interface FirebaseAuthRoutingConfig {
495
495
  * auto-corrects a missing leading slash with a warning.
496
496
  */
497
497
  actionLinkPath?: string;
498
+ /**
499
+ * Whether the middleware's own redirects (`redirectAuthPath`, `homePath`,
500
+ * `verifyEmailPath`) carry over the original request's query string —
501
+ * e.g. `/login?ref=abc` stays `/login?ref=abc` after redirecting to
502
+ * `homePath` for a signed-in user, instead of dropping to `/`. Defaults
503
+ * to `true`. The emailed-action-link forward (see
504
+ * {@link resetPasswordPath}) always preserves its query string
505
+ * regardless of this setting, since `oobCode` must survive that hop.
506
+ */
507
+ preserveRedirectQuery?: boolean;
498
508
  /** Returns true if the given (locale-stripped) path is an auth page (login/signup/etc). */
499
509
  isAuthPath: (path: string) => boolean;
500
510
  /** Locale-stripped paths exempt from all auth redirects (e.g. public marketing pages). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.6.30",
3
+ "version": "0.6.31",
4
4
  "description": "Optimized Next Intl Package Special for App Router and Cloudflare",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",