Files

144 lines
3.6 KiB
Go
Raw Permalink Normal View History

package auth
import (
"context"
"crypto/sha256"
"encoding/hex"
"strings"
"time"
"img_infinite_canvas/internal/domain/design"
)
type CodeCache interface {
Get(ctx context.Context, key string, target any) (bool, error)
Set(ctx context.Context, key string, value any, ttl time.Duration) error
Delete(ctx context.Context, key string) error
}
type CacheCodeStore struct {
cache CodeCache
ttl time.Duration
prefix string
}
type codeIndex struct {
Key string `json:"key"`
}
func NewCacheCodeStore(cache CodeCache, ttl time.Duration) *CacheCodeStore {
if ttl <= 0 {
ttl = 10 * time.Minute
}
return &CacheCodeStore{
cache: cache,
ttl: ttl,
prefix: "auth:code",
}
}
func (s *CacheCodeStore) CreateVerificationCode(ctx context.Context, code VerificationCode) error {
if err := ctx.Err(); err != nil {
return err
}
if code.ExpiresAt.IsZero() {
code.ExpiresAt = time.Now().Add(s.ttl)
}
key := s.activeKey(code.Region, code.Channel, code.Target, code.Purpose)
ttl := s.remainingTTL(code)
if err := s.cache.Set(ctx, key, code, ttl); err != nil {
return err
}
return s.cache.Set(ctx, s.idKey(code.ID), codeIndex{Key: key}, ttl)
}
func (s *CacheCodeStore) FindActiveVerificationCode(ctx context.Context, region string, channel string, target string, purpose string) (VerificationCode, error) {
if err := ctx.Err(); err != nil {
return VerificationCode{}, err
}
key := s.activeKey(region, channel, target, purpose)
var code VerificationCode
found, err := s.cache.Get(ctx, key, &code)
if err != nil {
return VerificationCode{}, err
}
if !found || code.ID == "" {
return VerificationCode{}, design.ErrNotFound
}
if code.ConsumedAt != nil || time.Now().After(code.ExpiresAt) {
_ = s.cache.Delete(ctx, key)
_ = s.cache.Delete(ctx, s.idKey(code.ID))
return VerificationCode{}, design.ErrNotFound
}
return code, nil
}
func (s *CacheCodeStore) MarkVerificationCodeConsumed(ctx context.Context, id string, _ time.Time) error {
index, err := s.findIndex(ctx, id)
if err != nil {
return err
}
if err := s.cache.Delete(ctx, index.Key); err != nil {
return err
}
return s.cache.Delete(ctx, s.idKey(id))
}
func (s *CacheCodeStore) IncrementVerificationCodeAttempts(ctx context.Context, id string) error {
index, err := s.findIndex(ctx, id)
if err != nil {
return err
}
var code VerificationCode
found, err := s.cache.Get(ctx, index.Key, &code)
if err != nil {
return err
}
if !found || code.ID == "" {
_ = s.cache.Delete(ctx, s.idKey(id))
return design.ErrNotFound
}
code.Attempts++
return s.cache.Set(ctx, index.Key, code, s.remainingTTL(code))
}
func (s *CacheCodeStore) findIndex(ctx context.Context, id string) (codeIndex, error) {
if err := ctx.Err(); err != nil {
return codeIndex{}, err
}
var index codeIndex
found, err := s.cache.Get(ctx, s.idKey(id), &index)
if err != nil {
return codeIndex{}, err
}
if !found || index.Key == "" {
return codeIndex{}, design.ErrNotFound
}
return index, nil
}
func (s *CacheCodeStore) remainingTTL(code VerificationCode) time.Duration {
if !code.ExpiresAt.IsZero() {
ttl := time.Until(code.ExpiresAt)
if ttl > 0 {
return ttl
}
}
return s.ttl
}
func (s *CacheCodeStore) activeKey(region string, channel string, target string, purpose string) string {
raw := strings.Join([]string{
strings.TrimSpace(region),
strings.TrimSpace(channel),
strings.TrimSpace(target),
strings.TrimSpace(purpose),
}, "\x00")
sum := sha256.Sum256([]byte(raw))
return s.prefix + ":active:" + hex.EncodeToString(sum[:])
}
func (s *CacheCodeStore) idKey(id string) string {
return s.prefix + ":id:" + strings.TrimSpace(id)
}