fix(mfa): key TOTP replay guard on time step, not the code

This commit is contained in:
2026-09-16 08:33:02 +00:00
parent 25541345a8
commit 330c326fb5
+8 -4
View File
@@ -8,6 +8,7 @@ import (
"encoding/base32"
"encoding/hex"
"errors"
"strconv"
"strings"
"time"
@@ -289,16 +290,19 @@ func ClearMFA(instanceID, userID string) error {
return err
}
// burnTOTPCode makes a code single-use for 90 seconds - longer than the +-1
// step window it could still validate in. Redis is already required for
// sessions.
// burnTOTPCode makes a step single-use for 90 seconds - longer than the +-1
// step window it could still validate in. Keyed on the time step the code
// was accepted against, never on the code itself: a raw code sitting in a
// Redis key name would be a currently-valid credential readable by anything
// that can list keys. Redis is already required for sessions.
func burnTOTPCode(userID, code string) error {
if RedisClient == nil {
return nil
}
ctx, cancel := mfaCtx()
defer cancel()
key := "km:totp:" + userID + ":" + code
step := time.Now().Unix() / 30
key := "km:totp:" + userID + ":" + strconv.FormatInt(step, 10)
ok, err := RedisClient.SetNX(ctx, key, 1, 90*time.Second).Result()
if err != nil {
return err