fix: P0-02 prevent login attempt counter race condition

Add atomic Increment method to cache layers:
- L2Cache interface: add Increment method signature
- RedisCache: implement using Redis INCRBY
- L1Cache: implement with mutex-protected counter
- CacheManager: add Increment that updates both L1 and L2

Update incrementFailAttempts to use atomic Increment instead
of Get-Increment-Set pattern, preventing TOCTOU race.
This commit is contained in:
2026-04-18 13:45:09 +08:00
parent 32a3d4c9e0
commit ca7ba5ccdf
4 changed files with 84 additions and 9 deletions

15
internal/cache/l2.go vendored
View File

@@ -17,6 +17,7 @@ type L2Cache interface {
Delete(ctx context.Context, key string) error
Exists(ctx context.Context, key string) (bool, error)
Clear(ctx context.Context) error
Increment(ctx context.Context, key string, delta int64, ttl time.Duration) (int64, error)
Close() error
}
@@ -127,6 +128,20 @@ func (c *RedisCache) Close() error {
return c.client.Close()
}
func (c *RedisCache) Increment(ctx context.Context, key string, delta int64, ttl time.Duration) (int64, error) {
if !c.enabled || c.client == nil {
return 0, errors.New("redis is not enabled")
}
result, err := c.client.IncrBy(ctx, key, delta).Result()
if err != nil {
return 0, err
}
if ttl > 0 {
c.client.Expire(ctx, key, ttl)
}
return result, nil
}
func decodeRedisValue(raw string) (interface{}, error) {
decoder := json.NewDecoder(strings.NewReader(raw))
decoder.UseNumber()