(ctx context.Context, in *DomainParam)
| 249 | } |
| 250 | |
| 251 | func (s *Server) handleVerifyDomain(ctx context.Context, in *DomainParam) (*verifyDomainOutput, error) { |
| 252 | user, err := s.requireAccountUser(ctx) |
| 253 | if err != nil { |
| 254 | return nil, err |
| 255 | } |
| 256 | d, err := s.deps.LookupDomain(ctx, in.Domain, user.ID) |
| 257 | if err != nil || d == nil { |
| 258 | return nil, NewError(http.StatusNotFound, "not_found", "domain not found") |
| 259 | } |
| 260 | // Best-effort touch of last_checked_at — the probe runs regardless. |
| 261 | if s.deps.TouchDomainChecked != nil { |
| 262 | _ = s.deps.TouchDomainChecked(ctx, in.Domain, user.ID) |
| 263 | } |
| 264 | check := s.deps.VerifyProbe(in.Domain, d.VerificationToken, d.DKIMSelector, d.DKIMPublicKey) |
| 265 | |
| 266 | // Already verified: short-circuit but surface the latest diagnostic. |
| 267 | // Still (re-)enqueue sender provisioning so this endpoint doubles as the |
| 268 | // forced sending re-check for a domain whose sending_status is pending/failed. |
| 269 | if d.Verified { |
| 270 | s.enqueueSenderProvision(ctx, d.Domain) |
| 271 | return &verifyDomainOutput{Status: http.StatusOK, Body: VerifyDomainView{ |
| 272 | Domain: d.Domain, Verified: true, VerifiedAt: d.VerifiedAt, |
| 273 | MX: check.MX, SPF: check.SPF, DKIM: check.DKIM, |
| 274 | }}, nil |
| 275 | } |
| 276 | // Verification requires BOTH the ownership TXT and the inbound MX. The MX |
| 277 | // gate (added with the per-record status array) is what makes |
| 278 | // `inbound_mx.status: "verified"` honest: status is derived from the |
| 279 | // domain's `verified` flag, so `verified` must actually imply the MX is |
| 280 | // published — otherwise a TXT-only verify would claim a "verified" MX while |
| 281 | // inbound mail silently bounces. A domain can't receive mail without the MX, |
| 282 | // so requiring it for `verified` is also the correct inbound semantics. |
| 283 | // 412 with the diagnostic so callers see exactly which record is missing. |
| 284 | if !check.TXTFound || check.MX != "found" { |
| 285 | return &verifyDomainOutput{Status: http.StatusPreconditionFailed, Body: VerifyDomainView{ |
| 286 | Domain: d.Domain, Verified: false, MX: check.MX, SPF: check.SPF, DKIM: check.DKIM, |
| 287 | }}, nil |
| 288 | } |
| 289 | if err := s.deps.VerifyDomain(ctx, in.Domain, user.ID); err != nil { |
| 290 | return nil, NewError(http.StatusInternalServerError, "internal_error", "failed to verify domain") |
| 291 | } |
| 292 | // Newly verified (inbound ownership): kick off SES sending-identity |
| 293 | // provisioning so the domain can graduate to own-address From. |
| 294 | s.enqueueSenderProvision(ctx, in.Domain) |
| 295 | // Re-read for verified_at; fall back to the bare success shape. |
| 296 | updated, err := s.deps.LookupDomain(ctx, in.Domain, user.ID) |
| 297 | if err != nil || updated == nil { |
| 298 | return &verifyDomainOutput{Status: http.StatusOK, Body: VerifyDomainView{ |
| 299 | Domain: in.Domain, Verified: true, MX: check.MX, SPF: check.SPF, DKIM: check.DKIM, |
| 300 | }}, nil |
| 301 | } |
| 302 | return &verifyDomainOutput{Status: http.StatusOK, Body: VerifyDomainView{ |
| 303 | Domain: updated.Domain, Verified: true, VerifiedAt: updated.VerifiedAt, |
| 304 | MX: check.MX, SPF: check.SPF, DKIM: check.DKIM, |
| 305 | }}, nil |
| 306 | } |
| 307 | |
| 308 | func (s *Server) handleListDomains(ctx context.Context, _ *struct{}) (*listDomainsOutput, error) { |
nothing calls this directly
no test coverage detected