mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-16 15:17:14 +00:00
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:
@@ -0,0 +1,22 @@
|
||||
package totp
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/xlzd/gotp"
|
||||
)
|
||||
|
||||
// SkewWindows is how many 30s steps around now VerifyWithSkew accepts.
|
||||
// Standard TOTP clock-drift tolerance, see MHSanaei/3x-ui#6535.
|
||||
const SkewWindows = 1
|
||||
|
||||
// VerifyWithSkew accepts the code for the current step plus/minus SkewWindows.
|
||||
func VerifyWithSkew(secret, code string, now time.Time) bool {
|
||||
totp := gotp.NewDefaultTOTP(secret)
|
||||
for i := -SkewWindows; i <= SkewWindows; i++ {
|
||||
if totp.AtTime(now.Add(time.Duration(i*30)*time.Second)) == code {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package totp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/xlzd/gotp"
|
||||
)
|
||||
|
||||
func TestVerifyWithSkew(t *testing.T) {
|
||||
secret := "JBSWY3DPEHPK3PXP"
|
||||
totp := gotp.NewDefaultTOTP(secret)
|
||||
// Anchor mid-window so a step boundary can't fall between sampling and verify.
|
||||
now := time.Unix((time.Now().Unix()/30)*30+15, 0).UTC()
|
||||
|
||||
if !VerifyWithSkew(secret, totp.AtTime(now), now) {
|
||||
t.Fatal("current window code should verify")
|
||||
}
|
||||
if !VerifyWithSkew(secret, totp.AtTime(now.Add(-30*time.Second)), now) {
|
||||
t.Fatal("previous window code should verify (clock skew)")
|
||||
}
|
||||
if !VerifyWithSkew(secret, totp.AtTime(now.Add(30*time.Second)), now) {
|
||||
t.Fatal("next window code should verify (clock skew)")
|
||||
}
|
||||
if VerifyWithSkew(secret, totp.AtTime(now.Add(-60*time.Second)), now) {
|
||||
t.Fatal("code two windows old should not verify")
|
||||
}
|
||||
if VerifyWithSkew(secret, totp.AtTime(now.Add(60*time.Second)), now) {
|
||||
t.Fatal("code two windows ahead should not verify")
|
||||
}
|
||||
if VerifyWithSkew(secret, "000000", now) {
|
||||
t.Fatal("wrong code should not verify")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user