// Package antiabuse provides runtime in-process adapters for auth-specific // public abuse controls. package antiabuse import ( "context" "fmt" "sync" "time" "galaxy/authsession/internal/domain/challenge" "galaxy/authsession/internal/domain/common" "galaxy/authsession/internal/ports" ) // SendEmailCodeProtector is a concurrency-safe in-process resend-throttle // adapter for public send-email-code attempts. type SendEmailCodeProtector struct { mu sync.Mutex reservedUntil map[common.Email]time.Time } // CheckAndReserve applies the fixed Stage-17 resend cooldown using input.Now // as the authoritative decision timestamp. func (p *SendEmailCodeProtector) CheckAndReserve(ctx context.Context, input ports.SendEmailCodeAbuseInput) (ports.SendEmailCodeAbuseResult, error) { if ctx == nil { return ports.SendEmailCodeAbuseResult{}, fmt.Errorf("check and reserve send email code abuse: nil context") } if err := ctx.Err(); err != nil { return ports.SendEmailCodeAbuseResult{}, err } if err := input.Validate(); err != nil { return ports.SendEmailCodeAbuseResult{}, fmt.Errorf("check and reserve send email code abuse: %w", err) } p.mu.Lock() defer p.mu.Unlock() if p.reservedUntil == nil { p.reservedUntil = make(map[common.Email]time.Time) } reservedUntil, exists := p.reservedUntil[input.Email] if exists && input.Now.Before(reservedUntil) { return ports.SendEmailCodeAbuseResult{ Outcome: ports.SendEmailCodeAbuseOutcomeThrottled, }, nil } p.reservedUntil[input.Email] = input.Now.UTC().Add(challenge.ResendThrottleCooldown) return ports.SendEmailCodeAbuseResult{ Outcome: ports.SendEmailCodeAbuseOutcomeAllowed, }, nil } var _ ports.SendEmailCodeAbuseProtector = (*SendEmailCodeProtector)(nil)