From 4fdf2cdec059bd946c26a0a0e95832d82d7d6921 Mon Sep 17 00:00:00 2001 From: BADRI NARAYANAN Date: Sun, 6 Sep 2026 16:42:32 -0700 Subject: [PATCH 1/2] chore(repo): gitignore Playwright MCP cache and drop stray artifacts .playwright-mcp/ (Playwright MCP's own session cache -- console logs and page-snapshot YAML, regenerated on every browser-automation run) had no .gitignore entry and was committed straight into the tracked tree in bed804c "Testing Plugin". Untrack all 11 files and add the entry so it can't happen again. Also add .DS_Store preventively. frontend/vite.config.js had its dev proxy target repointed from port 5000 to 5001 in 2852ec2 "Vite config" -- a single developer's local workaround for macOS AirPlay occupying port 5000 on their own machine, never meant to be shared. Reverted to the shared default; the override stays uncommitted locally, as this session has kept it throughout. Also removes navbar-fixed.png, a stray verification screenshot that was never project content. Co-Authored-By: Claude Opus 5 --- .gitignore | 7 +++ .../console-2026-09-06T16-25-54-975Z.log | 4 -- .../console-2026-09-06T16-26-36-860Z.log | 1 - .../console-2026-09-06T16-29-13-176Z.log | 3 -- .../console-2026-09-06T16-30-37-945Z.log | 3 -- .../console-2026-09-06T16-44-21-999Z.log | 1 - .../page-2026-09-06T16-25-56-687Z.yml | 26 ---------- .../page-2026-09-06T16-26-11-791Z.yml | 21 -------- .../page-2026-09-06T16-26-24-613Z.yml | 29 ----------- .../page-2026-09-06T16-26-37-243Z.yml | 2 - .../page-2026-09-06T16-27-25-675Z.yml | 33 ------------ .../page-2026-09-06T16-27-43-136Z.yml | 18 ------- .../page-2026-09-06T16-29-17-030Z.yml | 2 - .../page-2026-09-06T16-30-40-682Z.yml | 2 - .../page-2026-09-06T16-31-08-410Z.yml | 47 ----------------- .../page-2026-09-06T16-44-22-809Z.yml | 2 - .../page-2026-09-06T16-44-32-784Z.yml | 50 ------------------- frontend/vite.config.js | 4 +- 18 files changed, 9 insertions(+), 246 deletions(-) delete mode 100644 .playwright-mcp/console-2026-09-06T16-25-54-975Z.log delete mode 100644 .playwright-mcp/console-2026-09-06T16-26-36-860Z.log delete mode 100644 .playwright-mcp/console-2026-09-06T16-29-13-176Z.log delete mode 100644 .playwright-mcp/console-2026-09-06T16-30-37-945Z.log delete mode 100644 .playwright-mcp/console-2026-09-06T16-44-21-999Z.log delete mode 100644 .playwright-mcp/page-2026-09-06T16-25-56-687Z.yml delete mode 100644 .playwright-mcp/page-2026-09-06T16-26-11-791Z.yml delete mode 100644 .playwright-mcp/page-2026-09-06T16-26-24-613Z.yml delete mode 100644 .playwright-mcp/page-2026-09-06T16-26-37-243Z.yml delete mode 100644 .playwright-mcp/page-2026-09-06T16-27-25-675Z.yml delete mode 100644 .playwright-mcp/page-2026-09-06T16-27-43-136Z.yml delete mode 100644 .playwright-mcp/page-2026-09-06T16-29-17-030Z.yml delete mode 100644 .playwright-mcp/page-2026-09-06T16-30-40-682Z.yml delete mode 100644 .playwright-mcp/page-2026-09-06T16-31-08-410Z.yml delete mode 100644 .playwright-mcp/page-2026-09-06T16-44-22-809Z.yml delete mode 100644 .playwright-mcp/page-2026-09-06T16-44-32-784Z.yml diff --git a/.gitignore b/.gitignore index bc5d5e6..66aea94 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,10 @@ frontend/dist # NOTE: `frontend/src` was previously listed here, which silently excluded the entire frontend # source tree from new commits. The existing files stayed tracked only because they were added # before the rule. Removed deliberately — see CHANGELOG. + +# Playwright MCP session cache -- console logs + page snapshots regenerated on every +# browser-automation run, never project content. +.playwright-mcp/ + +# macOS Finder metadata. +.DS_Store diff --git a/.playwright-mcp/console-2026-09-06T16-25-54-975Z.log b/.playwright-mcp/console-2026-09-06T16-25-54-975Z.log deleted file mode 100644 index 5cd64ef..0000000 --- a/.playwright-mcp/console-2026-09-06T16-25-54-975Z.log +++ /dev/null @@ -1,4 +0,0 @@ -[ 1167ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=b492bb20:14335 -[ 1372ms] [ERROR] Failed to load resource: the server responded with a status of 401 (UNAUTHORIZED) @ http://localhost:5173/api/v1/auth/session:0 -[ 1375ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:5173/favicon.ico:0 -[ 1530ms] [ERROR] Failed to load resource: the server responded with a status of 401 (UNAUTHORIZED) @ http://localhost:5173/api/v1/auth/session:0 diff --git a/.playwright-mcp/console-2026-09-06T16-26-36-860Z.log b/.playwright-mcp/console-2026-09-06T16-26-36-860Z.log deleted file mode 100644 index 0b3f8c8..0000000 --- a/.playwright-mcp/console-2026-09-06T16-26-36-860Z.log +++ /dev/null @@ -1 +0,0 @@ -[ 288ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=b492bb20:14335 diff --git a/.playwright-mcp/console-2026-09-06T16-29-13-176Z.log b/.playwright-mcp/console-2026-09-06T16-29-13-176Z.log deleted file mode 100644 index b0b0866..0000000 --- a/.playwright-mcp/console-2026-09-06T16-29-13-176Z.log +++ /dev/null @@ -1,3 +0,0 @@ -[ 3057ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=b492bb20:14335 -[ 3897ms] [ERROR] Failed to load resource: the server responded with a status of 401 (UNAUTHORIZED) @ http://localhost:5173/api/v1/auth/session:0 -[ 3985ms] [ERROR] Failed to load resource: the server responded with a status of 401 (UNAUTHORIZED) @ http://localhost:5173/api/v1/auth/session:0 diff --git a/.playwright-mcp/console-2026-09-06T16-30-37-945Z.log b/.playwright-mcp/console-2026-09-06T16-30-37-945Z.log deleted file mode 100644 index cf77473..0000000 --- a/.playwright-mcp/console-2026-09-06T16-30-37-945Z.log +++ /dev/null @@ -1,3 +0,0 @@ -[ 2327ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=b492bb20:14335 -[ 2843ms] [ERROR] Failed to load resource: the server responded with a status of 401 (UNAUTHORIZED) @ http://localhost:5173/api/v1/auth/session:0 -[ 2852ms] [ERROR] Failed to load resource: the server responded with a status of 401 (UNAUTHORIZED) @ http://localhost:5173/api/v1/auth/session:0 diff --git a/.playwright-mcp/console-2026-09-06T16-44-21-999Z.log b/.playwright-mcp/console-2026-09-06T16-44-21-999Z.log deleted file mode 100644 index 07d51ae..0000000 --- a/.playwright-mcp/console-2026-09-06T16-44-21-999Z.log +++ /dev/null @@ -1 +0,0 @@ -[ 568ms] [INFO] %cDownload the React DevTools for a better development experience: https://react.dev/link/react-devtools font-weight:bold @ http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=b492bb20:14335 diff --git a/.playwright-mcp/page-2026-09-06T16-25-56-687Z.yml b/.playwright-mcp/page-2026-09-06T16-25-56-687Z.yml deleted file mode 100644 index 79b929e..0000000 --- a/.playwright-mcp/page-2026-09-06T16-25-56-687Z.yml +++ /dev/null @@ -1,26 +0,0 @@ -- generic [ref=e2]: - - generic [ref=e4]: - - heading "Create your account" [level=1] [ref=e5] - - generic [ref=e6]: - - generic [ref=e7]: Email - - textbox "Email" [ref=e8] - - generic [ref=e9]: - - generic [ref=e10]: - - generic [ref=e11]: Password - - textbox "Password" [ref=e12] - - list [ref=e13]: - - listitem [ref=e14]: - - text: At least 8 characters - - generic [ref=e17]: — not met - - listitem [ref=e18]: - - text: "12+ characters, or a mix of 3+ of: lowercase, uppercase, number, symbol" - - generic [ref=e21]: — not met - - generic [ref=e22]: - - generic [ref=e23]: Confirm password - - textbox "Confirm password" [ref=e24] - - button "Create account" [ref=e25] [cursor=pointer] - - paragraph [ref=e27]: - - text: Already have an account? - - link "Log in" [ref=e28] [cursor=pointer]: - - /url: /login - - region "Notifications" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-09-06T16-26-11-791Z.yml b/.playwright-mcp/page-2026-09-06T16-26-11-791Z.yml deleted file mode 100644 index 6fee254..0000000 --- a/.playwright-mcp/page-2026-09-06T16-26-11-791Z.yml +++ /dev/null @@ -1,21 +0,0 @@ -- generic [ref=e2]: - - generic [ref=e30]: - - heading "Log in" [level=1] [ref=e31] - - generic [ref=e32]: - - generic [ref=e33]: Email - - textbox "Email" [ref=e34] - - generic [ref=e35]: - - generic [ref=e36]: Password - - textbox "Password" [ref=e37] - - generic [ref=e38]: - - checkbox "Remember me" [ref=e39] - - text: Remember me - - button "Log in" [ref=e40] [cursor=pointer] - - paragraph [ref=e42]: - - text: Don't have an account? - - link "Register" [ref=e43] [cursor=pointer]: - - /url: /register - - region "Notifications" [ref=e44]: - - status [ref=e45]: - - generic [ref=e46]: Account created — log in to continue. - - button "Dismiss notification" [ref=e47] [cursor=pointer]: ✕ \ No newline at end of file diff --git a/.playwright-mcp/page-2026-09-06T16-26-24-613Z.yml b/.playwright-mcp/page-2026-09-06T16-26-24-613Z.yml deleted file mode 100644 index 394c96f..0000000 --- a/.playwright-mcp/page-2026-09-06T16-26-24-613Z.yml +++ /dev/null @@ -1,29 +0,0 @@ -- generic [ref=e2]: - - generic [ref=e48]: - - banner [ref=e49]: - - generic [ref=e50]: - - link "Trueup, go to home" [ref=e51] [cursor=pointer]: - - /url: /dashboard - - text: Trueup - - navigation "Primary" [ref=e52]: - - link "Onboarding" [ref=e54] [cursor=pointer]: - - /url: /onboarding - - button "Account menu, signed in as uidiag001@example.com" [ref=e57] [cursor=pointer]: - - generic [aria-hidden] [ref=e58]: UI - - main [ref=e61]: - - generic [ref=e62]: - - generic [ref=e63]: - - heading "Identity and funding status" [level=1] [ref=e64] - - button "Refresh status" [ref=e65] [cursor=pointer] - - generic [ref=e68]: - - generic [ref=e69]: - - generic [ref=e70]: Identity verification - - generic [ref=e71]: Pending - - generic [ref=e73]: - - generic [ref=e74]: Account approval - - generic [ref=e75]: Pending - - generic [ref=e78]: - - heading "Verify your identity" [level=2] [ref=e79] - - paragraph [ref=e80]: We use Stripe Identity to confirm who you are — you'll need a government-issued photo ID. - - button "Start identity verification" [ref=e81] [cursor=pointer] - - region "Notifications" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-09-06T16-26-37-243Z.yml b/.playwright-mcp/page-2026-09-06T16-26-37-243Z.yml deleted file mode 100644 index 25d2704..0000000 --- a/.playwright-mcp/page-2026-09-06T16-26-37-243Z.yml +++ /dev/null @@ -1,2 +0,0 @@ -- generic [ref=f3e2]: - - region "Notifications" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-09-06T16-27-25-675Z.yml b/.playwright-mcp/page-2026-09-06T16-27-25-675Z.yml deleted file mode 100644 index cd0aea3..0000000 --- a/.playwright-mcp/page-2026-09-06T16-27-25-675Z.yml +++ /dev/null @@ -1,33 +0,0 @@ -- generic [ref=f3e2]: - - generic [ref=f3e3]: - - banner [ref=f3e4]: - - generic [ref=f3e5]: - - link "Trueup, go to home" [ref=f3e6] [cursor=pointer]: - - /url: /dashboard - - text: Trueup - - navigation "Primary" [ref=f3e7]: - - link "Onboarding" [ref=f3e9] [cursor=pointer]: - - /url: /onboarding - - generic [ref=f3e11]: - - button "Account menu, signed in as uidiag001@example.com" [expanded] [ref=f3e12] [cursor=pointer]: - - generic [aria-hidden] [ref=f3e13]: UI - - generic [ref=f3e38]: - - paragraph [ref=f3e39]: uidiag001@example.com - - button "Log out" [active] [ref=f3e40] [cursor=pointer] - - main [ref=f3e16]: - - generic [ref=f3e17]: - - generic [ref=f3e18]: - - heading "Identity and funding status" [level=1] [ref=f3e19] - - button "Refresh status" [ref=f3e20] [cursor=pointer] - - generic [ref=f3e23]: - - generic [ref=f3e24]: - - generic [ref=f3e25]: Identity verification - - generic [ref=f3e26]: Pending - - generic [ref=f3e28]: - - generic [ref=f3e29]: Account approval - - generic [ref=f3e30]: Pending - - generic [ref=f3e33]: - - heading "Verify your identity" [level=2] [ref=f3e34] - - paragraph [ref=f3e35]: We use Stripe Identity to confirm who you are — you'll need a government-issued photo ID. - - button "Start identity verification" [ref=f3e36] [cursor=pointer] - - region "Notifications" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-09-06T16-27-43-136Z.yml b/.playwright-mcp/page-2026-09-06T16-27-43-136Z.yml deleted file mode 100644 index 7bb35c9..0000000 --- a/.playwright-mcp/page-2026-09-06T16-27-43-136Z.yml +++ /dev/null @@ -1,18 +0,0 @@ -- generic [ref=f3e2]: - - generic [ref=f3e43]: - - heading "Log in" [level=1] [ref=f3e44] - - generic [ref=f3e45]: - - generic [ref=f3e46]: Email - - textbox "Email" [ref=f3e47] - - generic [ref=f3e48]: - - generic [ref=f3e49]: Password - - textbox "Password" [ref=f3e50] - - generic [ref=f3e51]: - - checkbox "Remember me" [ref=f3e52] - - text: Remember me - - button "Log in" [ref=f3e53] [cursor=pointer] - - paragraph [ref=f3e55]: - - text: Don't have an account? - - link "Register" [ref=f3e56] [cursor=pointer]: - - /url: /register - - region "Notifications" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-09-06T16-29-17-030Z.yml b/.playwright-mcp/page-2026-09-06T16-29-17-030Z.yml deleted file mode 100644 index e99458a..0000000 --- a/.playwright-mcp/page-2026-09-06T16-29-17-030Z.yml +++ /dev/null @@ -1,2 +0,0 @@ -- generic [ref=f6e2]: - - region "Notifications" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-09-06T16-30-40-682Z.yml b/.playwright-mcp/page-2026-09-06T16-30-40-682Z.yml deleted file mode 100644 index 26d3f68..0000000 --- a/.playwright-mcp/page-2026-09-06T16-30-40-682Z.yml +++ /dev/null @@ -1,2 +0,0 @@ -- generic [ref=e2]: - - region "Notifications" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-09-06T16-31-08-410Z.yml b/.playwright-mcp/page-2026-09-06T16-31-08-410Z.yml deleted file mode 100644 index 54d25a3..0000000 --- a/.playwright-mcp/page-2026-09-06T16-31-08-410Z.yml +++ /dev/null @@ -1,47 +0,0 @@ -- generic [ref=e2]: - - generic [ref=e3]: - - banner [ref=e4]: - - generic [ref=e5]: - - link "Trueup, go to home" [ref=e6] [cursor=pointer]: - - /url: /dashboard - - text: Trueup - - navigation "Primary" [ref=e7]: - - generic [ref=e8]: - - link "Dashboard" [ref=e9] [cursor=pointer]: - - /url: /dashboard - - link "Portfolio" [ref=e10] [cursor=pointer]: - - /url: /portfolio - - link "Orders" [ref=e11] [cursor=pointer]: - - /url: /orders - - generic [ref=e13]: - - link "Transactions" [ref=e14] [cursor=pointer]: - - /url: /transactions - - link "Funding" [ref=e15] [cursor=pointer]: - - /url: /funding - - link "Tax lots" [ref=e16] [cursor=pointer]: - - /url: /lots - - link "Statements" [ref=e17] [cursor=pointer]: - - /url: /statements - - link "Fees" [ref=e18] [cursor=pointer]: - - /url: /fees - - link "Ask Trueup" [ref=e21] [cursor=pointer]: - - /url: /chat - - button "Account menu, signed in as uidiag001@example.com" [ref=e24] [cursor=pointer]: - - generic [aria-hidden] [ref=e25]: UI - - main [ref=e28]: - - generic [ref=e29]: - - heading "Dashboard" [level=1] [ref=e30] - - generic [ref=e31]: - - generic [ref=e32]: - - generic [ref=e33]: Account balance - - generic [ref=e34]: $0.00 - - generic [ref=e35]: As of Sep 6, 2026 - - generic [ref=e36]: - - generic [ref=e37]: Month-to-date return - - generic [ref=e38]: +0.00% - - generic [ref=e39]: - - heading "Target allocation" [level=2] [ref=e40] - - generic [ref=e41]: - - paragraph [ref=e45]: No model assigned yet - - paragraph [ref=e46]: Choose a model on the Portfolio page to see your target allocation here. - - region "Notifications" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-09-06T16-44-22-809Z.yml b/.playwright-mcp/page-2026-09-06T16-44-22-809Z.yml deleted file mode 100644 index 26d3f68..0000000 --- a/.playwright-mcp/page-2026-09-06T16-44-22-809Z.yml +++ /dev/null @@ -1,2 +0,0 @@ -- generic [ref=e2]: - - region "Notifications" \ No newline at end of file diff --git a/.playwright-mcp/page-2026-09-06T16-44-32-784Z.yml b/.playwright-mcp/page-2026-09-06T16-44-32-784Z.yml deleted file mode 100644 index 005b8d2..0000000 --- a/.playwright-mcp/page-2026-09-06T16-44-32-784Z.yml +++ /dev/null @@ -1,50 +0,0 @@ -- generic [ref=e2]: - - generic [ref=e3]: - - banner [ref=e4]: - - generic [ref=e5]: - - link "Trueup, go to home" [ref=e6] [cursor=pointer]: - - /url: /dashboard - - text: Trueup - - navigation "Primary" [ref=e7]: - - generic [ref=e8]: - - link "Dashboard" [ref=e9] [cursor=pointer]: - - /url: /dashboard - - link "Portfolio" [ref=e10] [cursor=pointer]: - - /url: /portfolio - - link "Orders" [ref=e11] [cursor=pointer]: - - /url: /orders - - generic [ref=e13]: - - link "Transactions" [ref=e14] [cursor=pointer]: - - /url: /transactions - - link "Funding" [ref=e15] [cursor=pointer]: - - /url: /funding - - link "Tax lots" [ref=e16] [cursor=pointer]: - - /url: /lots - - link "Statements" [ref=e17] [cursor=pointer]: - - /url: /statements - - link "Fees" [ref=e18] [cursor=pointer]: - - /url: /fees - - generic [ref=e20]: - - link "Ask Trueup" [ref=e21] [cursor=pointer]: - - /url: /chat - - link "Onboarding" [ref=e22] [cursor=pointer]: - - /url: /onboarding - - button "Account menu, signed in as ui-capture-1788711863@example.com" [ref=e25] [cursor=pointer]: - - generic [aria-hidden] [ref=e26]: UC - - main [ref=e29]: - - generic [ref=e30]: - - generic [ref=e31]: - - heading "Identity and funding status" [level=1] [ref=e32] - - button "Refresh status" [ref=e33] [cursor=pointer] - - generic [ref=e36]: - - generic [ref=e37]: - - generic [ref=e38]: Identity verification - - generic [ref=e39]: Pending - - generic [ref=e41]: - - generic [ref=e42]: Account approval - - generic [ref=e43]: Pending - - generic [ref=e46]: - - heading "Verify your identity" [level=2] [ref=e47] - - paragraph [ref=e48]: We use Stripe Identity to confirm who you are — you'll need a government-issued photo ID. - - button "Start identity verification" [ref=e49] [cursor=pointer] - - region "Notifications" \ No newline at end of file diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 8207e71..8e81c77 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -7,11 +7,11 @@ export default defineConfig({ server: { proxy: { '/api': { - target: 'http://localhost:5001', + target: 'http://localhost:5000', changeOrigin: true, }, '/health': { - target: 'http://localhost:5001', + target: 'http://localhost:5000', changeOrigin: true, }, }, From 961889f07c5dbc3a11f69f32ed76e40511266b2a Mon Sep 17 00:00:00 2001 From: BADRI NARAYANAN Date: Sun, 6 Sep 2026 16:42:41 -0700 Subject: [PATCH 2/2] fix(auth): tolerate TOTP clock/network drift with a validation window CI failed on main at bed804c: Backend / tests + coverage floor, test_mfa_enroll_reset_rejects_the_wrong_password - KeyError: 'csrf_token'. The traceback shows the failure at the test's own /mfa/verify call -- the server rejected a TOTP code the test itself generated as correct. Root cause: verify_totp() called pyotp.TOTP(secret).verify(code) with the library's default valid_window=0 -- zero tolerance on either side of the current 30-second window. The test computes a code, then makes two sequential HTTP round trips (login, then verify); under CI's load that's enough latency for the code to roll into a new window before the server checks it, rejecting a genuinely correct code nondeterministically. Confirmed as a flake, not a regression: bd02376's CI run (identical backend source -- bed804c changed zero backend files) passed all 798 tests, this one included. Fixed at the source: valid_window=1 accepts the current window plus one either side (+-30s), RFC 6238's recommended tolerance for clock drift and network latency. This is a real production fix, not just a test workaround -- without it, a real staff member whose request is delayed by ordinary network jitter can have a correct code rejected. Verified: tests/api/test_auth.py and the full backend suite, each run twice -- 798 passed both times, no regressions. Co-Authored-By: Claude Opus 5 --- backend/app/services/identity/auth.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/app/services/identity/auth.py b/backend/app/services/identity/auth.py index 73e5da2..6431efa 100644 --- a/backend/app/services/identity/auth.py +++ b/backend/app/services/identity/auth.py @@ -70,4 +70,11 @@ def totp_provisioning_uri(secret: str, email: str) -> str: def verify_totp(secret: str, code: str) -> bool: - return pyotp.TOTP(secret).verify(code) + """`valid_window=1` accepts the current 30-second window plus one on either side (RFC 6238's + recommended tolerance for clock drift and network latency) -- the library's own default, + `valid_window=0`, has zero tolerance and rejects a genuinely correct code whenever the + login-then-verify round trip crosses a window boundary. Real network jitter can trigger that, + which is what surfaced as a nondeterministic CI failure in + `test_mfa_enroll_reset_rejects_the_wrong_password` (passed on the identical code an hour + earlier).""" + return pyotp.TOTP(secret).verify(code, valid_window=1)