Files
galaxy-game/authsession/internal/ports/send_email_code_abuse.go
T
2026-04-08 16:23:07 +02:00

101 lines
3.4 KiB
Go

package ports
import (
"context"
"fmt"
"time"
"galaxy/authsession/internal/domain/challenge"
"galaxy/authsession/internal/domain/common"
)
// SendEmailCodeAbuseProtector decides whether one public send-email-code
// attempt may proceed immediately or must be throttled by the auth-side resend
// cooldown.
type SendEmailCodeAbuseProtector interface {
// CheckAndReserve validates input, checks the current resend cooldown
// decision for input.Email, and reserves a new cooldown window immediately
// when the outcome is allowed.
CheckAndReserve(ctx context.Context, input SendEmailCodeAbuseInput) (SendEmailCodeAbuseResult, error)
}
// SendEmailCodeAbuseInput describes one resend-throttle decision request for
// a normalized public send-email-code attempt.
type SendEmailCodeAbuseInput struct {
// Email identifies the normalized e-mail address addressed by the public
// request.
Email common.Email
// Now records when the send attempt is being evaluated.
Now time.Time
}
// Validate reports whether SendEmailCodeAbuseInput contains a complete resend
// cooldown decision request.
func (i SendEmailCodeAbuseInput) Validate() error {
if err := i.Email.Validate(); err != nil {
return fmt.Errorf("send email code abuse input email: %w", err)
}
if i.Now.IsZero() {
return fmt.Errorf("send email code abuse input now must not be zero")
}
return nil
}
// SendEmailCodeAbuseOutcome identifies the coarse resend-throttle decision for
// one public send-email-code attempt.
type SendEmailCodeAbuseOutcome string
const (
// SendEmailCodeAbuseOutcomeAllowed reports that the attempt may proceed and
// that the cooldown window has been reserved immediately.
SendEmailCodeAbuseOutcomeAllowed SendEmailCodeAbuseOutcome = "allowed"
// SendEmailCodeAbuseOutcomeThrottled reports that the cooldown window is
// still active and that the caller must not extend it.
SendEmailCodeAbuseOutcomeThrottled SendEmailCodeAbuseOutcome = "throttled"
)
// IsKnown reports whether SendEmailCodeAbuseOutcome belongs to the stable
// Stage-17 resend-throttle contract.
func (o SendEmailCodeAbuseOutcome) IsKnown() bool {
switch o {
case SendEmailCodeAbuseOutcomeAllowed, SendEmailCodeAbuseOutcomeThrottled:
return true
default:
return false
}
}
// SendEmailCodeAbuseResult describes one resend-throttle decision returned by
// SendEmailCodeAbuseProtector.
type SendEmailCodeAbuseResult struct {
// Outcome reports whether the current send attempt may proceed or must be
// throttled.
Outcome SendEmailCodeAbuseOutcome
}
// Validate reports whether SendEmailCodeAbuseResult satisfies the resend
// cooldown contract.
func (r SendEmailCodeAbuseResult) Validate() error {
if !r.Outcome.IsKnown() {
return fmt.Errorf("send email code abuse result outcome %q is unsupported", r.Outcome)
}
return nil
}
// SendEmailCodeThrottleStatusToChallengeStatus maps one resend-throttle
// outcome to the challenge lifecycle state used by sendemailcode.
func SendEmailCodeThrottleStatusToChallengeStatus(outcome SendEmailCodeAbuseOutcome) (challenge.Status, challenge.DeliveryState, error) {
switch outcome {
case SendEmailCodeAbuseOutcomeAllowed:
return challenge.StatusPendingSend, challenge.DeliveryPending, nil
case SendEmailCodeAbuseOutcomeThrottled:
return challenge.StatusDeliveryThrottled, challenge.DeliveryThrottled, nil
default:
return "", "", fmt.Errorf("map send email code abuse outcome %q: unsupported outcome", outcome)
}
}