2026-04-06 00:20:51 -05:00

118 lines
2.0 KiB
Go

package token
import (
"crypto/rand"
"encoding/hex"
"fmt"
"sync"
"time"
)
type otpEntry struct {
otpType string
data interface{}
createdAt time.Time
}
type OTPManager struct {
mutex sync.Mutex
entries map[string]*otpEntry
ttl time.Duration
tokenBytes int
done chan struct{}
}
func NewOTPManager(ttl time.Duration, cleanupInterval time.Duration) *OTPManager {
s := &OTPManager{
entries: make(map[string]*otpEntry),
ttl: ttl,
tokenBytes: 32,
done: make(chan struct{}),
}
go s.cleanupLoop(cleanupInterval)
return s
}
func (s *OTPManager) generateToken() (string, error) {
b := make([]byte, s.tokenBytes)
if _, err := rand.Read(b); err != nil {
return "", fmt.Errorf("otp: failed to generate token: %w", err)
}
return hex.EncodeToString(b), nil
}
func (s *OTPManager) Create(otpType string, data interface{}) (string, error) {
token, err := s.generateToken()
if err != nil {
return "", err
}
s.mutex.Lock()
s.entries[token] = &otpEntry{
otpType: otpType,
data: data,
createdAt: time.Now(),
}
s.mutex.Unlock()
return token, nil
}
func (s *OTPManager) Validate(token string) (string, interface{}, bool) {
s.mutex.Lock()
defer s.mutex.Unlock()
entry, exists := s.entries[token]
if !exists {
return "", nil, false
}
delete(s.entries, token)
if time.Since(entry.createdAt) > s.ttl {
return "", nil, false
}
return entry.otpType, entry.data, true
}
func (s *OTPManager) Len() int {
s.mutex.Lock()
defer s.mutex.Unlock()
return len(s.entries)
}
func (s *OTPManager) Stop() {
select {
case <-s.done:
default:
close(s.done)
}
}
func (s *OTPManager) cleanupLoop(interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-s.done:
return
case <-ticker.C:
s.purgeExpired()
}
}
}
func (s *OTPManager) purgeExpired() {
now := time.Now()
s.mutex.Lock()
defer s.mutex.Unlock()
for token, entry := range s.entries {
if now.Sub(entry.createdAt) > s.ttl {
delete(s.entries, token)
}
}
}