101 lines
3.4 KiB
Go
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)
|
|
}
|
|
}
|