cloudflare-next-intl 0.6.26 → 0.6.28

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.
@@ -45,7 +45,7 @@ export const getAuthenticatedAppForUser = cache(async function getAuthenticatedA
45
45
  // so apps that never configure server-side minting keep today's exact
46
46
  // behavior.
47
47
  const appCheckToken = cookieStore.get(appCheckTokenCookieName)?.value
48
- ?? await mintServerAppCheckToken(fa.projectId, fa.appCheck);
48
+ ?? await mintServerAppCheckToken(fa.projectId, fa.apiKey, fa.appCheck);
49
49
  try {
50
50
  if (!firebaseAppModule)
51
51
  firebaseAppModule = await import('firebase/app');
@@ -10,9 +10,13 @@ import type { FirebaseAppCheckConfig } from '../../types/types';
10
10
  *
11
11
  * Signs a short-lived custom JWT with the service account's private key
12
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.
13
+ * it for an App Check token via `exchangeCustomToken`, authenticated with
14
+ * the project's Web API key (`?key=`) — `exchangeCustomToken` otherwise
15
+ * rejects the call outright as an unregistered/unidentified caller
16
+ * (403 `PERMISSION_DENIED`), before the custom token itself is even
17
+ * evaluated. Not cached beyond the caller's own request-scoped `cache()`
18
+ * wrapper — a fresh mint costs one signing operation plus one network
19
+ * round-trip, acceptable per-request but not worth doing more than once per
20
+ * request.
17
21
  */
18
- export default function mintServerAppCheckToken(projectId: string, appCheck: FirebaseAppCheckConfig | undefined): Promise<string | undefined>;
22
+ export default function mintServerAppCheckToken(projectId: string, apiKey: string, appCheck: FirebaseAppCheckConfig | undefined): Promise<string | undefined>;
@@ -1,7 +1,22 @@
1
1
  import config from '@intl-config';
2
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';
3
+ // Matches `firebase-admin`'s own `AppCheckTokenGenerator.createCustomToken`
4
+ // exactly (`token-generator.js`) — this specific audience (the App Check
5
+ // TOKEN EXCHANGE service, not the App Check API resource name itself) is
6
+ // REQUIRED. The wrong-but-plausible-looking
7
+ // `.../google.firebase.appcheck.v1.FirebaseAppCheck` audience gets rejected
8
+ // by `exchangeCustomToken` with an opaque `403 App attestation failed`, with
9
+ // no indication the audience is the problem.
10
+ const APP_CHECK_CUSTOM_TOKEN_AUDIENCE = 'https://firebaseappcheck.googleapis.com/google.firebase.appcheck.v1.TokenExchangeService';
11
+ // `firebase-admin` hardcodes this custom token's own lifetime to 5 minutes
12
+ // and does not expose it as configurable — it's a short-lived credential
13
+ // whose only job is to be immediately exchanged for the actual App Check
14
+ // token (whose own lifetime is controlled by Firebase, separately). Mirrored
15
+ // here as a fixed value rather than the configurable
16
+ // `customTokenLifetime` this used to be: Google's real backend rejects
17
+ // longer lifetimes outright, so making it configurable only offered a
18
+ // footgun with no working range above this default.
19
+ const CUSTOM_TOKEN_LIFETIME = '5m';
5
20
  /**
6
21
  * Mints a fresh App Check token server-side via a service account, for use
7
22
  * when the client-written App Check cookie (see `appCheckTokenCookieName`)
@@ -13,12 +28,16 @@ const DEFAULT_CUSTOM_TOKEN_LIFETIME = '1h';
13
28
  *
14
29
  * Signs a short-lived custom JWT with the service account's private key
15
30
  * (`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.
31
+ * it for an App Check token via `exchangeCustomToken`, authenticated with
32
+ * the project's Web API key (`?key=`) — `exchangeCustomToken` otherwise
33
+ * rejects the call outright as an unregistered/unidentified caller
34
+ * (403 `PERMISSION_DENIED`), before the custom token itself is even
35
+ * evaluated. Not cached beyond the caller's own request-scoped `cache()`
36
+ * wrapper — a fresh mint costs one signing operation plus one network
37
+ * round-trip, acceptable per-request but not worth doing more than once per
38
+ * request.
20
39
  */
21
- export default async function mintServerAppCheckToken(projectId, appCheck) {
40
+ export default async function mintServerAppCheckToken(projectId, apiKey, appCheck) {
22
41
  if (!appCheck?.clientEmail || !appCheck.privateKey || !appCheck.appId)
23
42
  return undefined;
24
43
  try {
@@ -30,9 +49,9 @@ export default async function mintServerAppCheckToken(projectId, appCheck) {
30
49
  .setSubject(appCheck.clientEmail)
31
50
  .setAudience(APP_CHECK_CUSTOM_TOKEN_AUDIENCE)
32
51
  .setIssuedAt()
33
- .setExpirationTime(appCheck.customTokenLifetime ?? DEFAULT_CUSTOM_TOKEN_LIFETIME)
52
+ .setExpirationTime(CUSTOM_TOKEN_LIFETIME)
34
53
  .sign(privateKey);
35
- const url = `https://firebaseappcheck.googleapis.com/v1/projects/${projectId}/apps/${appCheck.appId}:exchangeCustomToken`;
54
+ const url = `https://firebaseappcheck.googleapis.com/v1/projects/${projectId}/apps/${appCheck.appId}:exchangeCustomToken?key=${apiKey}`;
36
55
  const res = await fetch(url, {
37
56
  method: 'POST',
38
57
  headers: { 'Content-Type': 'application/json' },
@@ -596,17 +596,6 @@ export interface FirebaseAppCheckConfig {
596
596
  * itself — App Check registers apps separately.
597
597
  */
598
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;
610
599
  }
611
600
  export interface CookieAttributes {
612
601
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.6.26",
3
+ "version": "0.6.28",
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",