core/captcha/nonce.go

40 lines
933 B
Go

package captcha
import (
"sync"
"time"
)
// NonceStore tracks used challenge nonces for single-use enforcement. The
// default MemoryNonceStore is per-process (no DB) — sufficient for the 5a
// model. A durable cross-process store (5b) can implement this interface later.
type NonceStore interface {
// MarkUsed records nonce; returns true on first use, false if already seen.
MarkUsed(nonce string, ttl time.Duration) bool
}
type MemoryNonceStore struct {
mu sync.Mutex
used map[string]time.Time
}
func NewMemoryNonceStore() *MemoryNonceStore {
return &MemoryNonceStore{used: make(map[string]time.Time)}
}
func (m *MemoryNonceStore) MarkUsed(nonce string, ttl time.Duration) bool {
now := time.Now()
m.mu.Lock()
defer m.mu.Unlock()
for k, exp := range m.used {
if !exp.After(now) {
delete(m.used, k)
}
}
if _, seen := m.used[nonce]; seen {
return false
}
m.used[nonce] = now.Add(ttl)
return true
}