118 lines
2.0 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|