fix(panel): accept 2FA codes from adjacent TOTP windows (#6546)

* 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>
This commit is contained in:
sdhfsl
2026-09-15 20:42:30 +08:00
committed by GitHub
parent e790f46757
commit d440c2b932
5 changed files with 65 additions and 4 deletions
+3 -2
View File
@@ -2,8 +2,8 @@ package panel
import (
"errors"
"time"
"github.com/xlzd/gotp"
"gorm.io/gorm"
"github.com/mhsanaei/3x-ui/v3/internal/database"
@@ -11,6 +11,7 @@ import (
"github.com/mhsanaei/3x-ui/v3/internal/logger"
"github.com/mhsanaei/3x-ui/v3/internal/util/crypto"
ldaputil "github.com/mhsanaei/3x-ui/v3/internal/util/ldap"
"github.com/mhsanaei/3x-ui/v3/internal/util/totp"
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
)
@@ -97,7 +98,7 @@ func (s *UserService) CheckUser(username string, password string, twoFactorCode
return nil, err
}
if gotp.NewDefaultTOTP(twoFactorToken).Now() != twoFactorCode {
if !totp.VerifyWithSkew(twoFactorToken, twoFactorCode, time.Now()) {
return nil, errors.New("invalid 2fa code")
}
}