fix anwser token and database issus

This commit is contained in:
tamakyi
2026-09-15 11:06:09 +08:00
parent e683e798aa
commit adcc42cc8c
33 changed files with 1011 additions and 802 deletions
+6 -14
View File
@@ -27,10 +27,8 @@ import (
)
const (
// Session key prefix for storing math captcha answers
sessionKeyPrefix = "math_captcha_"
// TTL for each captcha in session
captchaTTL = 5 * time.Minute
captchaTTL = 5 * time.Minute
// maxCaptchaAttempts 同一条验证码被尝试(含模态框预校验)的最大次数,
// 超过即销毁该验证码,迫使刷新,防止对答案做无限轮询爆破。
maxCaptchaAttempts = 5
@@ -52,7 +50,6 @@ type StoredAnswer struct {
// GenerateMath creates a new arithmetic captcha problem and stores the answer in session.
func GenerateMath(s session.Session) (*MathProblem, error) {
// Generate random operands
op1, err := rand.Int(rand.Reader, big.NewInt(90))
if err != nil {
return nil, errors.Wrap(err, "generate operand 1")
@@ -64,7 +61,6 @@ func GenerateMath(s session.Session) (*MathProblem, error) {
a := int(op1.Int64()) + 1 // 1-90
b := int(op2.Int64()) + 1 // 1-90
// Pick a random operator (+, -, *)
opIdx, err := rand.Int(rand.Reader, big.NewInt(3))
if err != nil {
return nil, errors.Wrap(err, "generate operator")
@@ -90,18 +86,16 @@ func GenerateMath(s session.Session) (*MathProblem, error) {
answer = a * b
}
// 生成唯一 ID
idBytes := make([]byte, 8)
if _, err := rand.Read(idBytes); err != nil {
return nil, errors.Wrap(err, "generate captcha id")
}
id := hex.EncodeToString(idBytes)
// 清理策略说明:flamego session 接口不支持遍历,无法主动清理过期条目。
// 泄漏可控:验证码一次性消费(VerifyMath 校验后立即删除),
// 且每个用户同一时刻只生成 1 个新 key;session 本身过期后存储层整体清理。
// 清理策略:flamego session 不支持遍历,无法主动清理过期条目。但验证码一次性消费
// (校验后立即删除)、每用户同时刻只生成 1 个新 key,session 过期后存储层整体清理,
// 泄漏可控。
// Store answer in session
key := sessionKeyPrefix + id
s.Set(key, StoredAnswer{
Answer: answer,
@@ -136,14 +130,12 @@ func VerifyMath(s session.Session, token string, lang string) (bool, error) {
return false, errors.New(i18n.T(lang, "captcha.errAnswerFormat"))
}
// Retrieve stored answer
key := sessionKeyPrefix + id
stored, ok := s.Get(key).(StoredAnswer)
if !ok {
return false, errors.New(i18n.T(lang, "captcha.errExpired"))
}
// Check expiry
if time.Since(stored.CreatedAt) > captchaTTL {
s.Delete(key)
return false, errors.New(i18n.T(lang, "captcha.errExpired"))
@@ -186,8 +178,8 @@ func VerifyOnly(s session.Session, id string, answer int) bool {
return subtle.ConstantTimeCompare([]byte(strconv.Itoa(answer)), []byte(strconv.Itoa(stored.Answer))) == 1
}
// sessionLang 从会话读取匿名用户显式选择的语言(与 Contexter 一致,语言偏好现托管于
// session 而非独立 lang cookie,避免被 session 中间件的 Set-Cookie 覆盖);未设置时回退默认。
// sessionLang 从会话读取匿名用户显式选择的语言(语言偏好托管于 session 而非独立 lang
// cookie,避免被 session 中间件的 Set-Cookie 覆盖);未设置时回退默认。
func sessionLang(s session.Session) string {
if v := s.Get("lang"); v != nil {
if str, ok := v.(string); ok && str != "" {