cloudflare-next-intl 0.6.24 → 0.6.26

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.
@@ -5,9 +5,9 @@ import { useRouter } from 'next/navigation';
5
5
  import usePathname from '../../client/hooks/use_path_name';
6
6
  import config from '@intl-config';
7
7
  import requireFirebaseAuthConfig from '../require_config';
8
- import { getFirebaseAuthClient, getFirebaseAuthModule } from './firebase_client';
8
+ import { getAppCheckToken, getFirebaseAuthClient, getFirebaseAuthModule } from './firebase_client';
9
9
  import { setAuthUserCache } from './auth_user_cache';
10
- import { defaultEmailVerifiedHintCookieName, defaultRefreshTokenCookieName, defaultSessionCookieName } from '../middleware/update_session';
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
13
  import setCookie from '../../client/functions/set_cookie';
@@ -39,9 +39,31 @@ function clearRefreshTokenCookie(refreshTokenCookieName) {
39
39
  function writeEmailVerifiedHintCookie(emailVerifiedHintCookieName, emailVerified, maxAge) {
40
40
  setCookie({ name: emailVerifiedHintCookieName, value: String(emailVerified), maxAge });
41
41
  }
42
- async function clearSession(sessionCookieName, refreshTokenCookieName, emailVerifiedHintCookieName, refreshTokenMaxAge) {
42
+ function clearAppCheckTokenCookie(appCheckTokenCookieName) {
43
+ setCookie({ name: appCheckTokenCookieName, value: '', maxAge: 0 });
44
+ }
45
+ // Mirrors the live App Check token into a client-readable cookie so
46
+ // `getAuthenticatedAppForUser` can forward it to `initializeServerApp` —
47
+ // required whenever App Check enforcement is on for Auth, or every
48
+ // server-side `getAuthUser()` call is rejected with
49
+ // `auth/firebase-app-check-token-is-invalid`. Best-effort: App Check may not
50
+ // be configured, or a token fetch can transiently fail (e.g. reCAPTCHA not
51
+ // yet ready) — either case just leaves the cookie unset rather than blocking
52
+ // session sync on it.
53
+ async function writeAppCheckTokenCookie(appCheckTokenCookieName, maxAge) {
54
+ try {
55
+ const token = await getAppCheckToken();
56
+ if (token)
57
+ setCookie({ name: appCheckTokenCookieName, value: token, maxAge });
58
+ }
59
+ catch (e) {
60
+ console.error('AuthUserProvider: App Check token cookie sync failed', e);
61
+ }
62
+ }
63
+ async function clearSession(sessionCookieName, refreshTokenCookieName, emailVerifiedHintCookieName, appCheckTokenCookieName, refreshTokenMaxAge) {
43
64
  clearSessionCookie(sessionCookieName);
44
65
  clearRefreshTokenCookie(refreshTokenCookieName);
66
+ clearAppCheckTokenCookie(appCheckTokenCookieName);
45
67
  // Signed-out is not "unknown" — it's a confirmed non-verified state, so
46
68
  // write 'false' explicitly rather than clearing (an absent hint means
47
69
  // "no signal yet", which forces the middleware to refresh unnecessarily
@@ -54,7 +76,7 @@ async function clearSession(sessionCookieName, refreshTokenCookieName, emailVeri
54
76
  console.error('AuthUserProvider: clearSessionAction failed', e);
55
77
  }
56
78
  }
57
- async function writeSession(user, sessionCookieName, maxAge, refreshTokenCookieName, refreshTokenMaxAge, emailVerifiedHintCookieName, idToken) {
79
+ async function writeSession(user, sessionCookieName, maxAge, refreshTokenCookieName, refreshTokenMaxAge, emailVerifiedHintCookieName, appCheckTokenCookieName, appCheckTokenMaxAge, idToken) {
58
80
  try {
59
81
  writeRefreshTokenCookie(refreshTokenCookieName, user, refreshTokenMaxAge);
60
82
  }
@@ -62,6 +84,7 @@ async function writeSession(user, sessionCookieName, maxAge, refreshTokenCookieN
62
84
  console.error('AuthUserProvider: refresh-token cookie sync failed', e);
63
85
  }
64
86
  writeEmailVerifiedHintCookie(emailVerifiedHintCookieName, user.emailVerified, refreshTokenMaxAge);
87
+ await writeAppCheckTokenCookie(appCheckTokenCookieName, appCheckTokenMaxAge);
65
88
  writeSessionCookie(sessionCookieName, idToken ?? await user.getIdToken(true), maxAge);
66
89
  }
67
90
  /**
@@ -94,6 +117,8 @@ export default function AuthUserProvider({ initialUser = null, children }) {
94
117
  const refreshTokenMaxAge = fa.refreshTokenCookieMaxAge ?? 60 * 60 * 24 * 365;
95
118
  const refreshTokenCookieName = fa.refreshTokenCookieName ?? defaultRefreshTokenCookieName;
96
119
  const emailVerifiedHintCookieName = fa.emailVerifiedHintCookieName ?? defaultEmailVerifiedHintCookieName;
120
+ const appCheckTokenCookieName = fa.appCheckTokenCookieName ?? defaultAppCheckTokenCookieName;
121
+ const appCheckTokenMaxAge = fa.appCheckTokenCookieMaxAge ?? 60 * 60;
97
122
  const [state, setState] = useState({
98
123
  user: initialUser,
99
124
  loading: initialUser === null,
@@ -164,10 +189,10 @@ export default function AuthUserProvider({ initialUser = null, children }) {
164
189
  const previous = syncedSignedIn.current;
165
190
  try {
166
191
  if (user) {
167
- await writeSession(user, sessionCookieName, maxAge, refreshTokenCookieName, refreshTokenMaxAge, emailVerifiedHintCookieName);
192
+ await writeSession(user, sessionCookieName, maxAge, refreshTokenCookieName, refreshTokenMaxAge, emailVerifiedHintCookieName, appCheckTokenCookieName, appCheckTokenMaxAge);
168
193
  }
169
194
  else {
170
- await clearSession(sessionCookieName, refreshTokenCookieName, emailVerifiedHintCookieName, refreshTokenMaxAge);
195
+ await clearSession(sessionCookieName, refreshTokenCookieName, emailVerifiedHintCookieName, appCheckTokenCookieName, refreshTokenMaxAge);
171
196
  }
172
197
  }
173
198
  catch (e) {
@@ -231,7 +256,7 @@ export default function AuthUserProvider({ initialUser = null, children }) {
231
256
  unsubscribe?.();
232
257
  };
233
258
  // eslint-disable-next-line react-hooks/exhaustive-deps
234
- }, [router, isAuthPage, maxAge, sessionCookieName, refreshTokenMaxAge, refreshTokenCookieName, emailVerifiedHintCookieName]);
259
+ }, [router, isAuthPage, maxAge, sessionCookieName, refreshTokenMaxAge, refreshTokenCookieName, emailVerifiedHintCookieName, appCheckTokenCookieName, appCheckTokenMaxAge]);
235
260
  const reloadUser = useCallback(async () => {
236
261
  const { auth } = await getFirebaseAuthClient();
237
262
  const user = auth.currentUser;
@@ -265,7 +290,7 @@ export default function AuthUserProvider({ initialUser = null, children }) {
265
290
  await sleep(500);
266
291
  }
267
292
  }
268
- await writeSession(user, sessionCookieName, maxAge, refreshTokenCookieName, refreshTokenMaxAge, emailVerifiedHintCookieName, confirmedToken);
293
+ await writeSession(user, sessionCookieName, maxAge, refreshTokenCookieName, refreshTokenMaxAge, emailVerifiedHintCookieName, appCheckTokenCookieName, appCheckTokenMaxAge, confirmedToken);
269
294
  if (!emailVerifiedRef.current && user.emailVerified) {
270
295
  emailVerifiedRef.current = true;
271
296
  try {
@@ -300,10 +325,10 @@ export default function AuthUserProvider({ initialUser = null, children }) {
300
325
  await signOut(auth);
301
326
  }
302
327
  finally {
303
- await clearSession(sessionCookieName, refreshTokenCookieName, emailVerifiedHintCookieName, refreshTokenMaxAge);
328
+ await clearSession(sessionCookieName, refreshTokenCookieName, emailVerifiedHintCookieName, appCheckTokenCookieName, refreshTokenMaxAge);
304
329
  router.push(fa.redirectAuthPath);
305
330
  }
306
331
  // eslint-disable-next-line react-hooks/exhaustive-deps
307
- }, [fa.redirectAuthPath, sessionCookieName, refreshTokenCookieName, emailVerifiedHintCookieName]);
332
+ }, [fa.redirectAuthPath, sessionCookieName, refreshTokenCookieName, emailVerifiedHintCookieName, appCheckTokenCookieName]);
308
333
  return _jsx(AuthUserContext.Provider, { value: { ...state, reloadUser, sendVerificationEmail, logout }, children: children });
309
334
  }
@@ -1,5 +1,13 @@
1
1
  import type { FirebaseApp } from 'firebase/app';
2
2
  import type { Auth } from 'firebase/auth';
3
+ /**
4
+ * Current App Check token, or `undefined` if `appCheck` isn't configured or
5
+ * hasn't initialized yet. Forces a refresh only when the cached token is
6
+ * expired/near-expiry — mirrors `getToken`'s own semantics, just exposed
7
+ * here so callers (e.g. `AuthUserProvider`'s session-cookie sync) don't need
8
+ * to import `firebase/app-check` themselves.
9
+ */
10
+ export declare function getAppCheckToken(): Promise<string | undefined>;
3
11
  /**
4
12
  * Lazily loads and initializes `firebase/app`/`firebase/auth` — a dynamic
5
13
  * import, not a static one, so consumers who never call a firebase_auth
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
  import config from '@intl-config';
3
3
  import requireFirebaseAuthConfig from '../require_config';
4
+ let cachedAppCheck;
4
5
  async function initializeFirebaseAppCheck(app, appCheckConfig) {
5
6
  const { initializeAppCheck, ReCaptchaV3Provider, ReCaptchaEnterpriseProvider } = await import('firebase/app-check');
6
7
  if (appCheckConfig.debugToken) {
@@ -10,11 +11,25 @@ async function initializeFirebaseAppCheck(app, appCheckConfig) {
10
11
  const provider = appCheckConfig.recaptchaEnterpriseSiteKey
11
12
  ? new ReCaptchaEnterpriseProvider(appCheckConfig.recaptchaEnterpriseSiteKey)
12
13
  : new ReCaptchaV3Provider(appCheckConfig.recaptchaV3SiteKey);
13
- initializeAppCheck(app, {
14
+ return initializeAppCheck(app, {
14
15
  provider,
15
16
  isTokenAutoRefreshEnabled: appCheckConfig.isTokenAutoRefreshEnabled ?? true,
16
17
  });
17
18
  }
19
+ /**
20
+ * Current App Check token, or `undefined` if `appCheck` isn't configured or
21
+ * hasn't initialized yet. Forces a refresh only when the cached token is
22
+ * expired/near-expiry — mirrors `getToken`'s own semantics, just exposed
23
+ * here so callers (e.g. `AuthUserProvider`'s session-cookie sync) don't need
24
+ * to import `firebase/app-check` themselves.
25
+ */
26
+ export async function getAppCheckToken() {
27
+ if (!cachedAppCheck)
28
+ return undefined;
29
+ const { getToken } = await import('firebase/app-check');
30
+ const result = await getToken(cachedAppCheck);
31
+ return result.token;
32
+ }
18
33
  let cached;
19
34
  let cachedPromise;
20
35
  /**
@@ -42,7 +57,7 @@ export async function getFirebaseAuthClient() {
42
57
  };
43
58
  const app = getApps().length ? getApp() : initializeApp(firebaseConfig);
44
59
  if (fa.appCheck) {
45
- await initializeFirebaseAppCheck(app, fa.appCheck);
60
+ cachedAppCheck = await initializeFirebaseAppCheck(app, fa.appCheck);
46
61
  }
47
62
  const auth = getAuth(app);
48
63
  cached = { app, auth };
@@ -2,6 +2,7 @@ import { NextResponse, type NextRequest } from 'next/server';
2
2
  export declare const defaultSessionCookieName = "__fa_session__";
3
3
  export declare const defaultRefreshTokenCookieName = "__fa_refresh_token__";
4
4
  export declare const defaultEmailVerifiedHintCookieName = "__fa_email_verified_hint__";
5
+ export declare const defaultAppCheckTokenCookieName = "__fa_app_check_token__";
5
6
  export declare const defaultResetPasswordPath = "/reset-password";
6
7
  /**
7
8
  * Layers Firebase session-cookie validation/refresh and auth redirects onto
@@ -11,6 +11,13 @@ export const defaultRefreshTokenCookieName = '__fa_refresh_token__';
11
11
  // — force one refresh). Readable client-side is fine: it carries no secret,
12
12
  // only a boolean mirror of a claim already inside the session JWT.
13
13
  export const defaultEmailVerifiedHintCookieName = '__fa_email_verified_hint__';
14
+ // Non-httpOnly: written by AuthUserProvider (client) whenever it mints a
15
+ // fresh App Check token, so the server can forward it to
16
+ // `initializeServerApp` — required whenever App Check enforcement is on for
17
+ // Auth, or every server-side `getAuthUser()` call is rejected with
18
+ // `auth/firebase-app-check-token-is-invalid`. Carries no secret beyond what
19
+ // the client already attaches to every Firebase SDK request itself.
20
+ export const defaultAppCheckTokenCookieName = '__fa_app_check_token__';
14
21
  export const defaultResetPasswordPath = '/reset-password';
15
22
  /**
16
23
  * Firebase's console exposes ONE project-wide action URL, so every email
@@ -2,8 +2,9 @@ import { cookies } from 'next/headers';
2
2
  import { cache } from 'react';
3
3
  import config from '@intl-config';
4
4
  import requireFirebaseAuthConfig from '../require_config';
5
- import { defaultSessionCookieName } from '../middleware/update_session';
5
+ import { defaultAppCheckTokenCookieName, defaultSessionCookieName } from '../middleware/update_session';
6
6
  import reportError from '../../error_handling/report_error';
7
+ import mintServerAppCheckToken from './mint_server_app_check_token';
7
8
  let baseApp;
8
9
  let firebaseAppModule;
9
10
  let firebaseAuthModule;
@@ -20,10 +21,31 @@ export const getAuthenticatedAppForUser = cache(async function getAuthenticatedA
20
21
  const fa = config.firebaseAuth;
21
22
  requireFirebaseAuthConfig(fa);
22
23
  const sessionCookieName = fa.sessionCookieName ?? defaultSessionCookieName;
23
- const authIdToken = (await cookies()).get(sessionCookieName)?.value;
24
+ const appCheckTokenCookieName = fa.appCheckTokenCookieName ?? defaultAppCheckTokenCookieName;
25
+ const cookieStore = await cookies();
26
+ const authIdToken = cookieStore.get(sessionCookieName)?.value;
24
27
  if (!authIdToken) {
25
28
  return { firebaseServerApp: null, currentUser: null };
26
29
  }
30
+ // Only meaningful when `appCheck` is configured — if App Check
31
+ // enforcement is on for Auth in the Firebase console, `initializeServerApp`
32
+ // rejects with `auth/firebase-app-check-token-is-invalid` unless this is
33
+ // supplied. Absent when App Check isn't configured, which is fine:
34
+ // `initializeServerApp` simply skips App Check validation in that case.
35
+ //
36
+ // The client-written cookie is the fast path (no round-trip) but can be
37
+ // missing on a cold navigation — a fresh tab/hard-refresh renders on the
38
+ // server BEFORE `AuthUserProvider` has had a chance to run and write it,
39
+ // even though `authIdToken` above proves this is a genuinely signed-in
40
+ // user. Falling straight through to `initializeServerApp` with no App
41
+ // Check token in that case would reject the whole lookup and render the
42
+ // page as signed-out. Minting one server-side (service-account-backed,
43
+ // see `mintServerAppCheckToken`) closes that gap; it's a no-op returning
44
+ // `undefined` if `appCheck.clientEmail`/`privateKey`/`appId` aren't set,
45
+ // so apps that never configure server-side minting keep today's exact
46
+ // behavior.
47
+ const appCheckToken = cookieStore.get(appCheckTokenCookieName)?.value
48
+ ?? await mintServerAppCheckToken(fa.projectId, fa.appCheck);
27
49
  try {
28
50
  if (!firebaseAppModule)
29
51
  firebaseAppModule = await import('firebase/app');
@@ -47,7 +69,7 @@ export const getAuthenticatedAppForUser = cache(async function getAuthenticatedA
47
69
  // registering a new named app in Firebase's global app registry.
48
70
  if (!baseApp)
49
71
  baseApp = initializeApp(firebaseConfig, 'firebase-auth-server-base');
50
- const firebaseServerApp = initializeServerApp(baseApp, { authIdToken });
72
+ const firebaseServerApp = initializeServerApp(baseApp, { authIdToken, appCheckToken });
51
73
  const auth = getAuth(firebaseServerApp);
52
74
  await auth.authStateReady();
53
75
  return { firebaseServerApp, currentUser: auth.currentUser };
@@ -0,0 +1,18 @@
1
+ import type { FirebaseAppCheckConfig } from '../../types/types';
2
+ /**
3
+ * Mints a fresh App Check token server-side via a service account, for use
4
+ * when the client-written App Check cookie (see `appCheckTokenCookieName`)
5
+ * is absent — e.g. a cold navigation before `AuthUserProvider` has run and
6
+ * had a chance to write it. Requires `clientEmail`/`privateKey`/`appId` on
7
+ * `firebaseAuth.appCheck`; returns `undefined` (never throws) if the
8
+ * exchange fails, so a caller can always fall back to "no App Check token"
9
+ * exactly as before this existed.
10
+ *
11
+ * Signs a short-lived custom JWT with the service account's private key
12
+ * (`jose`, Edge/WebCrypto-compatible — no `firebase-admin`), then exchanges
13
+ * it for an App Check token via `exchangeCustomToken`. Not cached beyond the
14
+ * caller's own request-scoped `cache()` wrapper — a fresh mint costs one
15
+ * signing operation plus one network round-trip, acceptable per-request but
16
+ * not worth doing more than once per request.
17
+ */
18
+ export default function mintServerAppCheckToken(projectId: string, appCheck: FirebaseAppCheckConfig | undefined): Promise<string | undefined>;
@@ -0,0 +1,55 @@
1
+ import config from '@intl-config';
2
+ import reportError from '../../error_handling/report_error';
3
+ const APP_CHECK_CUSTOM_TOKEN_AUDIENCE = 'https://firebaseappcheck.googleapis.com/google.firebase.appcheck.v1.FirebaseAppCheck';
4
+ const DEFAULT_CUSTOM_TOKEN_LIFETIME = '1h';
5
+ /**
6
+ * Mints a fresh App Check token server-side via a service account, for use
7
+ * when the client-written App Check cookie (see `appCheckTokenCookieName`)
8
+ * is absent — e.g. a cold navigation before `AuthUserProvider` has run and
9
+ * had a chance to write it. Requires `clientEmail`/`privateKey`/`appId` on
10
+ * `firebaseAuth.appCheck`; returns `undefined` (never throws) if the
11
+ * exchange fails, so a caller can always fall back to "no App Check token"
12
+ * exactly as before this existed.
13
+ *
14
+ * Signs a short-lived custom JWT with the service account's private key
15
+ * (`jose`, Edge/WebCrypto-compatible — no `firebase-admin`), then exchanges
16
+ * it for an App Check token via `exchangeCustomToken`. Not cached beyond the
17
+ * caller's own request-scoped `cache()` wrapper — a fresh mint costs one
18
+ * signing operation plus one network round-trip, acceptable per-request but
19
+ * not worth doing more than once per request.
20
+ */
21
+ export default async function mintServerAppCheckToken(projectId, appCheck) {
22
+ if (!appCheck?.clientEmail || !appCheck.privateKey || !appCheck.appId)
23
+ return undefined;
24
+ try {
25
+ const { SignJWT, importPKCS8 } = await import('jose');
26
+ const privateKey = await importPKCS8(appCheck.privateKey.replace(/\\n/g, '\n'), 'RS256');
27
+ const customToken = await new SignJWT({ app_id: appCheck.appId })
28
+ .setProtectedHeader({ alg: 'RS256', typ: 'JWT' })
29
+ .setIssuer(appCheck.clientEmail)
30
+ .setSubject(appCheck.clientEmail)
31
+ .setAudience(APP_CHECK_CUSTOM_TOKEN_AUDIENCE)
32
+ .setIssuedAt()
33
+ .setExpirationTime(appCheck.customTokenLifetime ?? DEFAULT_CUSTOM_TOKEN_LIFETIME)
34
+ .sign(privateKey);
35
+ const url = `https://firebaseappcheck.googleapis.com/v1/projects/${projectId}/apps/${appCheck.appId}:exchangeCustomToken`;
36
+ const res = await fetch(url, {
37
+ method: 'POST',
38
+ headers: { 'Content-Type': 'application/json' },
39
+ body: JSON.stringify({ customToken }),
40
+ });
41
+ if (!res.ok) {
42
+ await reportError(config, {
43
+ error: new Error(`exchangeCustomToken failed: ${res.status} ${await res.text()}`),
44
+ classOrMethodName: 'mintServerAppCheckToken',
45
+ });
46
+ return undefined;
47
+ }
48
+ const data = await res.json();
49
+ return data.token;
50
+ }
51
+ catch (error) {
52
+ await reportError(config, { error, classOrMethodName: 'mintServerAppCheckToken' });
53
+ return undefined;
54
+ }
55
+ }
@@ -509,6 +509,22 @@ export interface FirebaseAuthRoutingConfig {
509
509
  refreshTokenCookieName?: string;
510
510
  /** Email-verified hint cookie name. Defaults to `'__fa_email_verified_hint__'`. Client-written, non-httpOnly; lets the middleware avoid an unnecessary token refresh when its view already matches the client's. */
511
511
  emailVerifiedHintCookieName?: string;
512
+ /**
513
+ * App Check token cookie name. Defaults to `'__fa_app_check_token__'`.
514
+ * Client-written, non-httpOnly; carries the client's live App Check
515
+ * token to the server so `getAuthUser()`/`getAuthenticatedAppForUser`
516
+ * can pass it to `initializeServerApp`. Only relevant when `appCheck`
517
+ * is configured AND App Check enforcement is turned on for Auth in the
518
+ * Firebase console — otherwise `initializeServerApp` rejects every
519
+ * request with `auth/firebase-app-check-token-is-invalid`.
520
+ */
521
+ appCheckTokenCookieName?: string;
522
+ /**
523
+ * App Check token cookie max-age in seconds. Defaults to 1 hour (3600) —
524
+ * matches the App Check token's own default lifetime, so the cookie
525
+ * doesn't outlive the token it holds.
526
+ */
527
+ appCheckTokenCookieMaxAge?: number;
512
528
  /**
513
529
  * Called once, the moment `AuthUserProvider` observes a real sign-in
514
530
  * (a `null → user` transition) — never on a plain token refresh of an
@@ -555,6 +571,42 @@ export interface FirebaseAppCheckConfig {
555
571
  debugToken?: boolean | string;
556
572
  /** Forwarded to `initializeAppCheck`'s `isTokenAutoRefreshEnabled`. Defaults to `true`. */
557
573
  isTokenAutoRefreshEnabled?: boolean;
574
+ /**
575
+ * Service account client email, used ONLY server-side to mint an App
576
+ * Check token when the client-written App Check cookie is absent (e.g.
577
+ * a cold navigation before `AuthUserProvider` has run — see
578
+ * `appCheckTokenCookieName`). Required alongside `privateKey` and
579
+ * `appId` for server-side minting. Never sent to the client — read only
580
+ * by `firebase_server.ts`.
581
+ */
582
+ clientEmail: string;
583
+ /**
584
+ * Service account private key (PEM), paired with `clientEmail` for
585
+ * server-side App Check token minting. Same server-only, secret-bearing
586
+ * field as `clientEmail` — set from an untrusted-by-the-client env var
587
+ * (e.g. `process.env.FIREBASE_PRIVATE_KEY`), never exposed to the
588
+ * browser. Escaped `\n` sequences (common when stored in a single-line
589
+ * env var) are unescaped automatically before use.
590
+ */
591
+ privateKey: string;
592
+ /**
593
+ * Firebase App Check app ID (e.g. `"1:1234567890:web:abcdef123456"`),
594
+ * required alongside `clientEmail`/`privateKey` for server-side minting.
595
+ * Distinct from the Firebase Auth `appId` on `FirebaseAuthRoutingConfig`
596
+ * itself — App Check registers apps separately.
597
+ */
598
+ appId: string;
599
+ /**
600
+ * Lifetime of the custom JWT signed for the `exchangeCustomToken`
601
+ * server-side mint, as a `jose` `setExpirationTime` duration string
602
+ * (e.g. `'1h'`, `'30m'`, `'7d'`). Defaults to `'1h'`. This is the custom
603
+ * token's own lifetime, not the resulting App Check token's — Firebase
604
+ * controls that separately. Google's custom-token minting generally
605
+ * rejects lifetimes beyond 1 hour regardless of what's set here, so
606
+ * values longer than `'1h'` are unlikely to have any practical effect —
607
+ * kept configurable in case that constraint changes.
608
+ */
609
+ customTokenLifetime?: string;
558
610
  }
559
611
  export interface CookieAttributes {
560
612
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.6.24",
3
+ "version": "0.6.26",
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",
@@ -211,7 +211,8 @@
211
211
  "homepage": "https://github.com/demian-ilnytskyi/cloudflare-next-intl#readme",
212
212
  "dependencies": {
213
213
  "@microsoft/clarity": "^1.0.2",
214
- "firebase": "^12.17.0"
214
+ "firebase": "^12.17.0",
215
+ "jose": "^6.2.8"
215
216
  },
216
217
  "peerDependencies": {
217
218
  "next": ">=12.0.0",