cloudflare-next-intl 0.8.27 → 0.8.29

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.
@@ -369,23 +369,35 @@ export default async function updateSession(request, baseResponse, locale, rebui
369
369
  }
370
370
  let unverifiedEmail = false;
371
371
  if (fa.verifyEmailPath && !isVerifyEmailPage && hasSession && decodeJwtPayload(token)?.email_verified === false) {
372
+ // Sending a user to `verifyEmailPath` is only safe when this claim
373
+ // reflects the LIVE account state, because that page resolves the
374
+ // same user through `getAuthUser()`/`initializeServerApp` — which
375
+ // reads the Auth service directly, not this frozen claim — and
376
+ // redirects home the moment it sees a verified user. Any disagreement
377
+ // between the two is therefore not a stale-data annoyance but an
378
+ // infinite redirect loop.
379
+ //
380
+ // The hint cookie cannot settle that: it is a client-written mirror,
381
+ // and after the user verifies in another tab (or the client simply
382
+ // never re-runs) it keeps asserting a `false` that is now wrong,
383
+ // while the Auth service already reports verified. Trusting a
384
+ // `'false'` hint as confirmation is exactly what pinned verified
385
+ // users in the loop. So the hint is only ever allowed to SKIP work
386
+ // when it agrees the user is verified — never to prove they aren't.
372
387
  const hint = request.cookies.get(emailVerifiedHintCookieName)?.value;
373
- const hintConfirms = hint === 'false';
374
- if (!hintConfirms && !refreshedToken) {
388
+ if (!refreshedToken) {
375
389
  const refreshToken = request.cookies.get(refreshTokenCookieName)?.value;
376
390
  if (refreshToken) {
377
- const result = await refreshIdToken(fa.apiKey, refreshToken);
391
+ // `skipCache`: the cached entry is what produced the very
392
+ // token whose claim is in question, so a cached refresh can
393
+ // hand back that same token and confirm nothing.
394
+ const result = await refreshIdToken(fa.apiKey, refreshToken, { skipCache: true });
378
395
  if (result.status === 'refreshed') {
379
396
  refreshedToken = { idToken: result.idToken, refreshToken: result.refreshToken };
380
397
  token = refreshedToken.idToken;
381
- // The hint can say `true` (client already confirmed
382
- // verification live) while this refreshed claim still
383
- // says `false` — token-claim propagation on Google's
384
- // backend can lag behind the account-info read `reload()`
385
- // used to set the hint. Trusting the stale claim here
386
- // would redirect to verifyEmailPath, which independently
387
- // resolves the user as verified via `getAuthUser()` and
388
- // redirects home — an infinite loop. Hint `true` wins.
398
+ // A `true` hint means the client already observed
399
+ // verification live; never redirect against it, even if
400
+ // this mint's claim hasn't caught up.
389
401
  unverifiedEmail = hint !== 'true' && decodeJwtPayload(token)?.email_verified === false;
390
402
  }
391
403
  else if (result.status === 'invalid') {
@@ -404,8 +416,7 @@ export default async function updateSession(request, baseResponse, locale, rebui
404
416
  }
405
417
  }
406
418
  else {
407
- // Hint confirms unverified no reason to refresh, trust the claim.
408
- unverifiedEmail = true;
419
+ unverifiedEmail = hint !== 'true' && decodeJwtPayload(token)?.email_verified === false;
409
420
  }
410
421
  }
411
422
  if (refreshWasTransientFailure) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.8.27",
3
+ "version": "0.8.29",
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",