// Package internalhttp defines the frozen trusted internal HTTP contract used // by Mail Service. package internalhttp import ( "bytes" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "fmt" "io" "mime" "net/http" "strings" "galaxy/mail/internal/domain/common" ) const ( // LoginCodeDeliveriesPath is the dedicated trusted route used by // Auth / Session Service for auth login-code delivery intake. LoginCodeDeliveriesPath = "/api/v1/internal/login-code-deliveries" // IdempotencyKeyHeader is the required header that scopes auth-delivery // deduplication. IdempotencyKeyHeader = "Idempotency-Key" // ErrorCodeInvalidRequest identifies trusted validation failures. ErrorCodeInvalidRequest = "invalid_request" // ErrorCodeInternalError identifies trusted invariant failures. ErrorCodeInternalError = "internal_error" // ErrorCodeServiceUnavailable identifies trusted availability failures. ErrorCodeServiceUnavailable = "service_unavailable" // ErrorCodeConflict identifies conflicting idempotency replays. ErrorCodeConflict = "conflict" jsonMediaType = "application/json" ) // LoginCodeDeliveryRequest stores the strict JSON body accepted on the frozen // auth-delivery route before normalization. type LoginCodeDeliveryRequest struct { // Email stores the destination e-mail address. Email string `json:"email"` // Code stores the exact login code generated by Auth / Session Service. Code string `json:"code"` // Locale stores the caller-selected BCP 47 language tag. Locale string `json:"locale"` } // LoginCodeDeliveryCommand stores the normalized auth-delivery request shape // that later Mail Service handlers and services can consume directly. type LoginCodeDeliveryCommand struct { // IdempotencyKey stores the caller-owned stable deduplication key. IdempotencyKey common.IdempotencyKey // Email stores the normalized recipient address. Email common.Email // Code stores the exact login code after boundary validation. Code string // Locale stores the canonical BCP 47 language tag. Locale common.Locale } // Validate reports whether command satisfies the frozen auth-delivery // contract. func (command LoginCodeDeliveryCommand) Validate() error { if err := command.IdempotencyKey.Validate(); err != nil { return fmt.Errorf("idempotency key: %w", err) } if err := command.Email.Validate(); err != nil { return fmt.Errorf("email: %w", err) } if strings.TrimSpace(command.Code) == "" { return errors.New("code must not be empty") } if strings.TrimSpace(command.Code) != command.Code { return errors.New("code must not contain surrounding whitespace") } if err := command.Locale.Validate(); err != nil { return fmt.Errorf("locale: %w", err) } return nil } // Fingerprint returns the stable auth-delivery idempotency fingerprint of // command. func (command LoginCodeDeliveryCommand) Fingerprint() (string, error) { if err := command.Validate(); err != nil { return "", err } normalized := struct { IdempotencyKey string `json:"idempotency_key"` Email string `json:"email"` Code string `json:"code"` Locale string `json:"locale"` }{ IdempotencyKey: command.IdempotencyKey.String(), Email: command.Email.String(), Code: command.Code, Locale: command.Locale.String(), } payload, err := json.Marshal(normalized) if err != nil { return "", fmt.Errorf("marshal login code delivery fingerprint: %w", err) } sum := sha256.Sum256(payload) return "sha256:" + hex.EncodeToString(sum[:]), nil } // LoginCodeDeliveryOutcome identifies the stable successful auth-delivery // intake outcomes. type LoginCodeDeliveryOutcome string const ( // LoginCodeDeliveryOutcomeSent reports durable acceptance into the internal // mail-delivery pipeline. LoginCodeDeliveryOutcomeSent LoginCodeDeliveryOutcome = "sent" // LoginCodeDeliveryOutcomeSuppressed reports intentional outward delivery // suppression while keeping the auth flow success-shaped. LoginCodeDeliveryOutcomeSuppressed LoginCodeDeliveryOutcome = "suppressed" ) // IsKnown reports whether outcome belongs to the frozen auth success surface. func (outcome LoginCodeDeliveryOutcome) IsKnown() bool { switch outcome { case LoginCodeDeliveryOutcomeSent, LoginCodeDeliveryOutcomeSuppressed: return true default: return false } } // LoginCodeDeliveryResponse stores the stable successful auth-delivery // response body. type LoginCodeDeliveryResponse struct { // Outcome stores the stable coarse acceptance result. Outcome LoginCodeDeliveryOutcome `json:"outcome"` } // Validate reports whether response satisfies the frozen success contract. func (response LoginCodeDeliveryResponse) Validate() error { if !response.Outcome.IsKnown() { return fmt.Errorf("login code delivery outcome %q is unsupported", response.Outcome) } return nil } // ErrorResponse stores the stable trusted error envelope used by Mail Service. type ErrorResponse struct { // Error stores the stable trusted error body. Error ErrorBody `json:"error"` } // Validate reports whether response satisfies the frozen trusted error // envelope contract. func (response ErrorResponse) Validate() error { return response.Error.Validate() } // ErrorBody stores the stable trusted error shape returned by Mail Service. type ErrorBody struct { // Code stores the stable machine-readable error code. Code string `json:"code"` // Message stores the trusted human-readable error message. Message string `json:"message"` } // Validate reports whether body contains a complete trusted error payload. func (body ErrorBody) Validate() error { switch { case strings.TrimSpace(body.Code) == "": return errors.New("error code must not be empty") case strings.TrimSpace(body.Code) != body.Code: return errors.New("error code must not contain surrounding whitespace") case strings.TrimSpace(body.Message) == "": return errors.New("error message must not be empty") default: return nil } } // DecodeLoginCodeDeliveryCommand validates one trusted HTTP request and // returns the normalized auth-delivery command shape frozen by Stage 04. func DecodeLoginCodeDeliveryCommand(request *http.Request) (LoginCodeDeliveryCommand, error) { if request == nil { return LoginCodeDeliveryCommand{}, errors.New("login code delivery request must not be nil") } if err := validateJSONContentType(request.Header.Get("Content-Type")); err != nil { return LoginCodeDeliveryCommand{}, err } idempotencyKey, err := parseIdempotencyKey(request.Header.Get(IdempotencyKeyHeader)) if err != nil { return LoginCodeDeliveryCommand{}, err } body, err := decodeLoginCodeDeliveryRequest(request.Body) if err != nil { return LoginCodeDeliveryCommand{}, err } command := LoginCodeDeliveryCommand{ IdempotencyKey: idempotencyKey, Email: common.Email(strings.TrimSpace(body.Email)), Code: body.Code, } locale, err := common.ParseLocale(strings.TrimSpace(body.Locale)) if err != nil { return LoginCodeDeliveryCommand{}, fmt.Errorf("locale: %w", err) } command.Locale = locale if err := command.Validate(); err != nil { return LoginCodeDeliveryCommand{}, err } return command, nil } func decodeLoginCodeDeliveryRequest(body io.ReadCloser) (LoginCodeDeliveryRequest, error) { if body == nil { return LoginCodeDeliveryRequest{}, errors.New("request body must not be nil") } defer body.Close() payload, err := io.ReadAll(body) if err != nil { return LoginCodeDeliveryRequest{}, fmt.Errorf("read request body: %w", err) } decoder := json.NewDecoder(bytes.NewReader(payload)) decoder.DisallowUnknownFields() var request LoginCodeDeliveryRequest if err := decoder.Decode(&request); err != nil { return LoginCodeDeliveryRequest{}, fmt.Errorf("decode request body: %w", err) } if err := decoder.Decode(&struct{}{}); err != io.EOF { if err == nil { return LoginCodeDeliveryRequest{}, errors.New("decode request body: unexpected trailing JSON input") } return LoginCodeDeliveryRequest{}, fmt.Errorf("decode request body: %w", err) } return request, nil } func parseIdempotencyKey(value string) (common.IdempotencyKey, error) { switch { case strings.TrimSpace(value) == "": return "", errors.New("Idempotency-Key header must not be empty") case strings.TrimSpace(value) != value: return "", errors.New("Idempotency-Key header must not contain surrounding whitespace") default: key := common.IdempotencyKey(value) if err := key.Validate(); err != nil { return "", fmt.Errorf("idempotency key: %w", err) } return key, nil } } func validateJSONContentType(value string) error { mediaType, _, err := mime.ParseMediaType(value) if err != nil { return fmt.Errorf("Content-Type must be %s", jsonMediaType) } if mediaType != jsonMediaType { return fmt.Errorf("Content-Type must be %s", jsonMediaType) } return nil }