88 lines
2.1 KiB
Go
88 lines
2.1 KiB
Go
package memory
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"sync"
|
|
"time"
|
|
|
|
"git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/domain"
|
|
"git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/port"
|
|
)
|
|
|
|
// Compile-time interface check.
|
|
var _ port.AuthCodeRepository = (*AuthCodeRepository)(nil)
|
|
|
|
// AuthCodeRepository is an in-memory auth code store for standalone development.
|
|
type AuthCodeRepository struct {
|
|
mu sync.RWMutex
|
|
codes map[string]*domain.AuthCode
|
|
}
|
|
|
|
// NewAuthCodeRepository creates a new in-memory auth code repository.
|
|
func NewAuthCodeRepository() *AuthCodeRepository {
|
|
return &AuthCodeRepository{
|
|
codes: make(map[string]*domain.AuthCode),
|
|
}
|
|
}
|
|
|
|
func (r *AuthCodeRepository) Create(_ context.Context, code *domain.AuthCode) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
cp := *code
|
|
r.codes[code.ID] = &cp
|
|
|
|
// In standalone dev mode the code lives only in memory and is lost on restart.
|
|
// Always log it so the developer can copy-paste the code from the terminal
|
|
// even when NOTIFY_URL is set and an email is also being delivered.
|
|
slog.Warn("[DEV] auth code created — use this code to log in",
|
|
"email", code.Email,
|
|
"purpose", code.Purpose,
|
|
"code", code.Code,
|
|
"expires_at", code.ExpiresAt.Format("15:04:05"),
|
|
)
|
|
return nil
|
|
}
|
|
|
|
func (r *AuthCodeRepository) FindValid(_ context.Context, email string, code string, purpose domain.AuthCodePurpose) (*domain.AuthCode, error) {
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
|
|
for _, c := range r.codes {
|
|
if c.Email == email && c.Code == code && c.Purpose == purpose && c.IsValid() {
|
|
cp := *c
|
|
return &cp, nil
|
|
}
|
|
}
|
|
return nil, domain.ErrInvalidAuthCode
|
|
}
|
|
|
|
func (r *AuthCodeRepository) MarkUsed(_ context.Context, id string) error {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
c, ok := r.codes[id]
|
|
if !ok {
|
|
return domain.ErrInvalidAuthCode
|
|
}
|
|
now := time.Now()
|
|
c.UsedAt = &now
|
|
return nil
|
|
}
|
|
|
|
func (r *AuthCodeRepository) DeleteExpired(_ context.Context) (int, error) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
deleted := 0
|
|
for id, c := range r.codes {
|
|
if now.After(c.ExpiresAt) {
|
|
delete(r.codes, id)
|
|
deleted++
|
|
}
|
|
}
|
|
return deleted, nil
|
|
}
|