Improve MFA verification tolerance and docs (#368)

This commit is contained in:
shenlan 2025-10-02 15:16:38 +08:00 committed by GitHub
parent 48550554c8
commit 2037143bae
5 changed files with 34 additions and 6 deletions

View File

@ -578,7 +578,17 @@ func (h *handler) verifyTOTP(c *gin.Context) {
return
}
if !totp.Validate(code, user.MFATOTPSecret) {
valid, err := totp.ValidateCustom(code, user.MFATOTPSecret, time.Now().UTC(), totp.ValidateOpts{
Period: 30,
Skew: 1,
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1,
})
if err != nil {
respondError(c, http.StatusInternalServerError, "invalid_mfa_code", "invalid totp code")
return
}
if !valid {
respondError(c, http.StatusUnauthorized, "invalid_mfa_code", "invalid totp code")
return
}

View File

@ -34,6 +34,19 @@ func decodeResponse(t *testing.T, rr *httptest.ResponseRecorder) apiResponse {
return resp
}
func waitForStableTOTPWindow(t *testing.T) {
t.Helper()
const period int64 = 30
remainder := time.Now().Unix() % period
const buffer int64 = 10
if remainder > period-buffer {
sleep := (period - remainder) + 2
if sleep > 0 {
time.Sleep(time.Duration(sleep) * time.Second)
}
}
}
func TestRegisterEndpoint(t *testing.T) {
gin.SetMode(gin.TestMode)
@ -178,7 +191,8 @@ func TestMFATOTPFlow(t *testing.T) {
return code
}
code := generateCode(0)
waitForStableTOTPWindow(t)
code := generateCode(-30 * time.Second)
verifyPayload := map[string]string{
"token": resp.MFAToken,
@ -239,8 +253,8 @@ func TestMFATOTPFlow(t *testing.T) {
return recorder
}
time.Sleep(1 * time.Second)
totpCode := generateCode(0)
waitForStableTOTPWindow(t)
totpCode := generateCode(-30 * time.Second)
if ok, _ := totp.ValidateCustom(totpCode, secret, time.Now().UTC(), totp.ValidateOpts{
Period: 30,
Skew: 1,
@ -259,7 +273,7 @@ func TestMFATOTPFlow(t *testing.T) {
t.Fatalf("expected mfa login success, got %d: %s", rr.Code, rr.Body.String())
}
time.Sleep(1 * time.Second)
waitForStableTOTPWindow(t)
totpCode = generateCode(0)
if ok, _ := totp.ValidateCustom(totpCode, secret, time.Now().UTC(), totp.ValidateOpts{
Period: 30,

View File

@ -44,7 +44,7 @@ session:
**TLS 提示**:当 `certFile``keyFile` 都非空时,`accountsvc` 会调用 `ListenAndServeTLS` 启动 HTTPS。如果同时希望保留 80 端口,可将 `redirectHttp` 置为 `true`,服务会开启一个额外的明文监听,将请求 301 重定向到 HTTPS。
**MFA 相关接口**:账号服务在 `/api/auth/mfa/*` 下提供 MFA 绑定与验证接口,默认无需额外配置即可使用,但生产环境建议将 `server.tls` 打开,确保 MFA 秘钥与 TOTP 码在传输过程中被加密。
**MFA 相关接口**:账号服务在 `/api/auth/mfa/*` 下提供 MFA 绑定与验证接口,默认无需额外配置即可使用,但生产环境建议将 `server.tls` 打开,确保 MFA 秘钥与 TOTP 码在传输过程中被加密。MFA 挑战 token 默认 10 分钟过期,服务器会接受 ±1 个 30 秒窗口的 TOTP 漂移,因此务必启用 NTP 等时间同步手段,避免合法验证码因时钟偏差被拒绝。
## 3. 配置示例

View File

@ -69,6 +69,8 @@
若需要重新绑定 MFA可再次发起登录以获取新的 `mfaToken`,然后重复 `provision``verify` 流程;如需彻底重置,可在数据库中清理相关 MFA 字段后重新执行上述步骤。
> 时间同步提示TOTP 验证允许 ±1 个 30 秒时间片的偏移,但依赖服务器与客户端时钟保持一致。请在部署环境中启用 NTP/Chrony 等服务,并注意 `mfaToken` 默认 10 分钟后失效。
## 3. 启用 HTTPS/TLS
账号服务内置 TLS 支持,只要在配置文件中提供证书即可:

View File

@ -25,6 +25,7 @@ The standalone account service exposes user registration, MFA provisioning, and
- `token` MFA challenge token obtained from a prior `/api/auth/login` attempt.
- `issuer` Optional override for the TOTP issuer label.
- `account` Optional override for the account label in authenticator apps.
- **Notes:** Challenge tokens expire after 10 minutes. If the token is lost or expired, re-run the login flow to obtain a fresh value.
- **Test:**
```bash
curl -X POST http://localhost:8080/api/auth/mfa/totp/provision \
@ -37,6 +38,7 @@ The standalone account service exposes user registration, MFA provisioning, and
- **Body Parameters (JSON):**
- `token` MFA challenge token used during provisioning.
- `code` 6-digit TOTP from Google Authenticator/oathtool.
- **Notes:** Codes are calculated in 30-second windows with ±1 window skew. Ensure server and client clocks stay synchronized (e.g., via NTP) to prevent false negatives.
- **Test:**
```bash
curl -X POST http://localhost:8080/api/auth/mfa/totp/verify \