gotp decodes the stored secret with strict base32 and panics when that fails,
so a 2FA secret saved through the settings API in lower case, grouped by
spaces, or not base32 at all turned every login and every attempt to turn 2FA
off into an HTTP 500. An empty secret had the opposite problem: the login
check accepted the code of an empty HMAC key, which anyone can compute.
VerifyWithSkew now normalizes the secret and verifies nothing for an empty or
non-base32 one, so secrets already stored in a display form work again.
Turning 2FA on through the settings update stores the normalized secret and
refuses one no authenticator could match.
* fix(panel): accept 2FA codes from adjacent TOTP windows
CheckUser compared only gotp.Now(), so a code submitted at the end of
its 30s window (or with slight client/server clock drift) failed with
'invalid 2fa code', while the immediate retry in the next window
succeeded. Accept current +/-1 window, the standard TOTP skew
tolerance.
Fixes MHSanaei/3x-ui#6535
* fix(panel): share TOTP skew tolerance with VerifyTwoFactorCode
Move the +/-1 window helper to internal/util/totp so both 2FA
acceptance points use it: login (CheckUser) and disable/rebind plus
username/password changes (VerifyTwoFactorCode). Also shrink comments
to the 2-line house rule and anchor the unit test mid-window to avoid
a step-boundary flake.
Addresses review on #6546 (MEDIUM + 2 LOWs).
---------
Co-authored-by: sdhfsl <sdhfsl@users.noreply.github.com>