Files
projectsend/tests/Feature/Identity
ignacionelson 90009b7029 Merge pull request #1738 from denkfabrik-li/fix/totp-replay-claim-atomically
TwoFactorService::verify() asked Cache::has(), verified the code, then Cache::put(). Between the read and the write the key is free, so two requests carrying the same code could both be told yes -- which is precisely what the replay guard exists to prevent, and the window an intercepted code has is the whole of its validity either side.

Cache::add() writes only if the key is absent, so of two requests carrying the same valid code exactly one gets true back, and has() is gone: a failed claim is "already used". Verification still runs first, so a wrong code never touches the cache and cannot burn the window for the code the person is about to type correctly. The 90-second claim, the key's shape, and the recovery codes are all unchanged.

Verified before merging: 11 passed on the trial-merge, 1 failed / 10 passed with app/ reset. The existing "a totp code cannot be replayed" test is green either way, because it covers the sequential case, which was never the problem. Being on the authentication path, the wider suites were run too: tests/Feature/Identity and tests/Feature/Auth together, 393 passed.

Cache::add() is only as atomic as the store under it, so every store an installation could realistically run was checked in the vendored framework rather than assumed: database (the default when CACHE_STORE is unset) decides on insertOrIgnore(...) > 0 against the cache table's primary key; redis and memcached have native atomic adds; and the file store takes an exclusive flock before it reads and writes.

Reported and fixed by @denkfabrik-li.
2026-08-28 17:24:28 -03:00
..
2026-08-14 01:38:12 -03:00
2026-08-14 01:38:12 -03:00
2026-08-14 01:38:12 -03:00
2026-08-14 01:38:12 -03:00
2026-08-14 01:38:12 -03:00
2026-08-14 01:38:12 -03:00
2026-08-14 01:38:12 -03:00
2026-08-14 01:38:12 -03:00
2026-08-14 01:38:12 -03:00