40 lines
933 B
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
|
|
}
|