cloudflare-next-intl 0.8.28 → 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,33 +369,36 @@ 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
391
  // `skipCache`: the cached entry is what produced the very
378
- // token whose claim is in question, so a normal refresh can
379
- // hand back that same token and "confirm" nothing. Only a
380
- // genuinely newly-minted token carries information here.
381
- const previousToken = token;
392
+ // token whose claim is in question, so a cached refresh can
393
+ // hand back that same token and confirm nothing.
382
394
  const result = await refreshIdToken(fa.apiKey, refreshToken, { skipCache: true });
383
395
  if (result.status === 'refreshed') {
384
396
  refreshedToken = { idToken: result.idToken, refreshToken: result.refreshToken };
385
397
  token = refreshedToken.idToken;
386
- // Redirecting to verifyEmailPath on a claim this refresh
387
- // did not actually re-confirm is what caused an infinite
388
- // loop: the destination page resolves the same user as
389
- // VERIFIED via `getAuthUser()` (`initializeServerApp`
390
- // reads live Auth-service state, not this frozen claim)
391
- // and redirects straight back home. So only trust the
392
- // claim when this mint genuinely re-stated it — the token
393
- // changed — and never against a hint that already
394
- // observed verification live.
395
- const reconfirmed = result.idToken !== previousToken;
396
- unverifiedEmail = hint !== 'true'
397
- && reconfirmed
398
- && decodeJwtPayload(token)?.email_verified === false;
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.
401
+ unverifiedEmail = hint !== 'true' && decodeJwtPayload(token)?.email_verified === false;
399
402
  }
400
403
  else if (result.status === 'invalid') {
401
404
  clearInvalidSession = true;
@@ -413,8 +416,7 @@ export default async function updateSession(request, baseResponse, locale, rebui
413
416
  }
414
417
  }
415
418
  else {
416
- // Hint confirms unverified no reason to refresh, trust the claim.
417
- unverifiedEmail = true;
419
+ unverifiedEmail = hint !== 'true' && decodeJwtPayload(token)?.email_verified === false;
418
420
  }
419
421
  }
420
422
  if (refreshWasTransientFailure) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.8.28",
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",