@payez/next-mvp 4.12.0 → 4.12.1
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.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog — @payez/next-mvp
|
|
2
2
|
|
|
3
|
+
## 4.12.1 (to be published under dist-tag `next` only, after 4.12.0; `latest` stays 4.8.9 until Jon says otherwise)
|
|
4
|
+
|
|
5
|
+
A patch cut from the 4.12.0 tree carrying ONE change: the PAY-2425 hygiene fix below (PR 1500).
|
|
6
|
+
|
|
7
|
+
- **CHANGE (PAY-2425, hygiene): the refresh no longer invents authentication claims for an OAuth (Google) session.** `lib/ensure-fresh-access-token` answered an OAuth session whose stored authentication methods were empty with `amr ['pwd','mfa']`, `two_factor_method 'oauth'` and an `acr` raised from '1' to '2' ("OAuth IS a form of multi-factor auth"), and echoed a stored `mfaVerified` as `two_factor_verified` whatever the session's methods said. OAuth is federated sign-in, not a second factor (BAPert 77404). The refresh body now carries only what the session records: empty stays empty, `acr` is the session's own level, no method is named from silence, and `two_factor_verified` is sent only when the session's recorded methods contain `mfa`, `sms`, `totp` or `email`. A session that really attests `mfa` still sends it. The IdP ignores these request fields and mints `amr`/`acr` from the stored refresh row, so this removes a false claim from the wire; it does not establish that any bypass existed or is closed. **What changes for a caller:** a Google user on a `require2FA` client is asked for a code, as before, because the IdP decides that; a Google-only client that wants no second factor sets `require2FA = false` explicitly. An earlier version of this fix on `nextpert/375639-amr-fabrication` (e6e3b6c3, e3b3bb12) was never merged; the refresh path has since moved into this helper. Rows: `test/next-mvp-pay-2425-no-fabricated-mfa.test.cjs` (R1/R2/R3, C1, N1 and four mutants).
|
|
8
|
+
|
|
3
9
|
## 4.12.0 (to be published under dist-tag `next` only, after 4.11.0; `latest` stays 4.8.9 until Jon says otherwise)
|
|
4
10
|
|
|
5
11
|
A minor: PII-in-logs groups 1, 2, 3a and 3b, the GOV-023 signout BREAKING change, the logout-401 FIX (PAY-2396), and the BREAKING site-logger signatures (3b: callers of `siteEvents.loginFailed` / `siteEvents.error` must change).
|
|
@@ -257,17 +257,18 @@ async function ensureFreshAccessToken(sessionToken, config, options = {}) {
|
|
|
257
257
|
}
|
|
258
258
|
catch { /* fall through */ }
|
|
259
259
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
260
|
+
// PAY-2425 (BAPert 77400, NightHawk 77401): THE CLIENT DOES NOT INFER AUTHENTICATION STRENGTH FROM SILENCE. This block used to answer an OAuth
|
|
261
|
+
// (Google) session whose stored methods were EMPTY with amr ['pwd','mfa'], a two-factor method of 'oauth' and an acr raised from '1' to '2' ("OAuth IS
|
|
262
|
+
// a form of multi-factor auth"). That is false: OAuth is federated sign-in, one factor typed at the provider. It fired exactly when the session
|
|
263
|
+
// could tell us nothing and answered with the strongest claim available; on prod (4.10.0) a Google user on a require2FA client was then counted
|
|
264
|
+
// 2FA-complete with no code (RigPert 77398). What the session does not attest, the refresh body does not assert: empty stays empty, the level stays
|
|
265
|
+
// the session's own, and no method is named unless one is recorded. The IdP computes the real amr/acr for the minted token (and mfaVerified is
|
|
266
|
+
// re-derived from THAT token below); a session that really attests 'mfa' still sends it. History: the fix existed on nextpert/375639-amr-fabrication
|
|
267
|
+
// (e6e3b6c3, e3b3bb12) and was never merged.
|
|
264
268
|
const twoFactorMethod = authMethods.find(m => ['sms', 'totp', 'email'].includes(m)) ||
|
|
265
269
|
session.mfaMethod ||
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (isOAuthSession && session.mfaVerified && acrValue === '1') {
|
|
269
|
-
acrValue = '2';
|
|
270
|
-
}
|
|
270
|
+
null;
|
|
271
|
+
const acrValue = String(session.authenticationLevel ?? '1');
|
|
271
272
|
// WO-2: send the POST-lock current refresh token, not the pre-lock snapshot.
|
|
272
273
|
// `session` was read before we acquired the lock; a concurrent refresh that
|
|
273
274
|
// completed while we waited rotates the token, so the pre-lock value is stale
|
|
@@ -300,7 +301,11 @@ async function ensureFreshAccessToken(sessionToken, config, options = {}) {
|
|
|
300
301
|
amr: authMethods,
|
|
301
302
|
acr: acrValue,
|
|
302
303
|
};
|
|
303
|
-
|
|
304
|
+
// PAY-2425 (NightHawk 77414): a STORED mfaVerified flag is only echoed when the session's own recorded methods back it. Sessions created while the
|
|
305
|
+
// bug was live carry mfaVerified:true that the bug itself wrote, with no 'mfa' (or sms/totp/email) in their methods; echoing that flag would carry the
|
|
306
|
+
// poison across the upgrade. A genuine second factor leaves 'mfa' and its method in the recorded methods, so it is still sent.
|
|
307
|
+
const methodsAttestSecondFactor = authMethods.some(m => m === 'mfa' || ['sms', 'totp', 'email'].includes(m));
|
|
308
|
+
if (session.mfaVerified && methodsAttestSecondFactor)
|
|
304
309
|
body.two_factor_verified = true;
|
|
305
310
|
if (twoFactorMethod)
|
|
306
311
|
body.two_factor_method = twoFactorMethod;
|
package/package.json
CHANGED
|
@@ -349,19 +349,20 @@ export async function ensureFreshAccessToken(
|
|
|
349
349
|
} else if (typeof session.authenticationMethods === 'string') {
|
|
350
350
|
try { authMethods = JSON.parse(session.authenticationMethods); } catch { /* fall through */ }
|
|
351
351
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
352
|
+
// PAY-2425 (BAPert 77400, NightHawk 77401): THE CLIENT DOES NOT INFER AUTHENTICATION STRENGTH FROM SILENCE. This block used to answer an OAuth
|
|
353
|
+
// (Google) session whose stored methods were EMPTY with amr ['pwd','mfa'], a two-factor method of 'oauth' and an acr raised from '1' to '2' ("OAuth IS
|
|
354
|
+
// a form of multi-factor auth"). That is false: OAuth is federated sign-in, one factor typed at the provider. It fired exactly when the session
|
|
355
|
+
// could tell us nothing and answered with the strongest claim available; on prod (4.10.0) a Google user on a require2FA client was then counted
|
|
356
|
+
// 2FA-complete with no code (RigPert 77398). What the session does not attest, the refresh body does not assert: empty stays empty, the level stays
|
|
357
|
+
// the session's own, and no method is named unless one is recorded. The IdP computes the real amr/acr for the minted token (and mfaVerified is
|
|
358
|
+
// re-derived from THAT token below); a session that really attests 'mfa' still sends it. History: the fix existed on nextpert/375639-amr-fabrication
|
|
359
|
+
// (e6e3b6c3, e3b3bb12) and was never merged.
|
|
356
360
|
const twoFactorMethod =
|
|
357
361
|
authMethods.find(m => ['sms', 'totp', 'email'].includes(m)) ||
|
|
358
362
|
session.mfaMethod ||
|
|
359
|
-
|
|
363
|
+
null;
|
|
360
364
|
|
|
361
|
-
|
|
362
|
-
if (isOAuthSession && session.mfaVerified && acrValue === '1') {
|
|
363
|
-
acrValue = '2';
|
|
364
|
-
}
|
|
365
|
+
const acrValue = String(session.authenticationLevel ?? '1');
|
|
365
366
|
|
|
366
367
|
// WO-2: send the POST-lock current refresh token, not the pre-lock snapshot.
|
|
367
368
|
// `session` was read before we acquired the lock; a concurrent refresh that
|
|
@@ -397,7 +398,11 @@ export async function ensureFreshAccessToken(
|
|
|
397
398
|
amr: authMethods,
|
|
398
399
|
acr: acrValue,
|
|
399
400
|
};
|
|
400
|
-
|
|
401
|
+
// PAY-2425 (NightHawk 77414): a STORED mfaVerified flag is only echoed when the session's own recorded methods back it. Sessions created while the
|
|
402
|
+
// bug was live carry mfaVerified:true that the bug itself wrote, with no 'mfa' (or sms/totp/email) in their methods; echoing that flag would carry the
|
|
403
|
+
// poison across the upgrade. A genuine second factor leaves 'mfa' and its method in the recorded methods, so it is still sent.
|
|
404
|
+
const methodsAttestSecondFactor = authMethods.some(m => m === 'mfa' || ['sms', 'totp', 'email'].includes(m));
|
|
405
|
+
if (session.mfaVerified && methodsAttestSecondFactor) body.two_factor_verified = true;
|
|
401
406
|
if (twoFactorMethod) body.two_factor_method = twoFactorMethod;
|
|
402
407
|
if (session.mfaCompletedAt) body.two_factor_completed_at = new Date(session.mfaCompletedAt).toISOString();
|
|
403
408
|
|