Files
shankar0123 21aeed4f4e legal: addlicense headers + normalize legacy variants (Phase 0 RED-4)
Phase 0 closure (Path B2, post-rewrite):

addlicense sweep — adds the canonical certctl LLC copyright + BUSL-1.1
SPDX header to every production Go file. Template:

  // Copyright 2026 certctl LLC. All rights reserved.
  // SPDX-License-Identifier: BUSL-1.1

Coverage: 338 / 338 production Go files (cmd/ + internal/, excluding
*_test.go and **/testdata/**). Pre-sweep coverage was 22 / 338 (6.5%);
post-sweep is 338 / 338 (100%).

Normalized 22 pre-existing legacy headers (`// Copyright (c) certctl`
+ `// SPDX-License-Identifier: BSL-1.1`) and 1 file using a
`Certctl Contributors` attribution. The legacy SPDX ID `BSL-1.1`
is non-standard; the official SPDX identifier for Business Source
License 1.1 is `BUSL-1.1` (capital U). All 338 files now share the
canonical form.

Generated via:
  addlicense -c "certctl LLC" -y 2026 \
    -f cowork/legal/copyright-header.tpl \
    -ignore '**/testdata/**' -ignore '**/*_test.go' \
    cmd/ internal/

Verification:
  find cmd internal -name '*.go' -not -name '*_test.go' \
    -not -path '*/testdata/*' \
    -exec grep -L '^// Copyright 2026 certctl LLC' {} \; | wc -l

  Returns: 0

gofmt clean. Header additions are comments only, no compile impact.

Closes: cowork/certctl-architecture-diligence-audit.html#fix-RED-4
2026-05-13 21:23:35 +00:00

83 lines
3.6 KiB
Go

// Copyright 2026 certctl LLC. All rights reserved.
// SPDX-License-Identifier: BUSL-1.1
package acme
import (
"github.com/certctl-io/certctl/internal/domain"
)
// AccountResponseJSON is the wire shape RFC 8555 §7.1.2 mandates for
// account-resource responses (new-account success, account update,
// per-account GET POST-as-GET).
//
// The orders URL is mandatory per RFC 8555 §7.1.2.1; it points at the
// per-account orders list endpoint that Phase 2 implements. Phase 1b
// emits it as an empty placeholder ("orders not yet implemented") so
// the directory + new-account flow round-trips against ACME clients
// that expect the field present.
type AccountResponseJSON struct {
Status string `json:"status"`
Contact []string `json:"contact,omitempty"`
Orders string `json:"orders"`
}
// MarshalAccount renders an ACMEAccount in RFC 8555 §7.1.2 wire shape.
// `ordersURL` is the per-account orders list URL the handler computes
// from the inbound request (scheme + host + profile path + account
// id); Phase 1b's handler passes it but Phase 2 wires the actual
// /acme/profile/<id>/account/<acc-id>/orders endpoint.
func MarshalAccount(acct *domain.ACMEAccount, ordersURL string) AccountResponseJSON {
contact := acct.Contact
if contact == nil {
// RFC 8555 doesn't require contact be present, but cert-manager
// + lego both expect a stable shape. Emit [] rather than null.
contact = []string{}
}
return AccountResponseJSON{
Status: string(acct.Status),
Contact: contact,
Orders: ordersURL,
}
}
// NewAccountRequest is the payload shape RFC 8555 §7.3 mandates for
// new-account requests. The handler json.Unmarshals VerifiedRequest.Payload
// into this struct after JWS verify succeeds.
type NewAccountRequest struct {
// Contact is a list of mailto: / tel: URIs. Optional per RFC 8555
// but operators typically supply at least one mailto:.
Contact []string `json:"contact,omitempty"`
// TermsOfServiceAgreed signals client consent to the operator's
// ToS document (advertised via meta.termsOfService). Phase 1b
// records the value but does NOT enforce — the meta field is
// informational only at this stage.
TermsOfServiceAgreed bool `json:"termsOfServiceAgreed,omitempty"`
// OnlyReturnExisting, when true, asks the server to return the
// existing account row for this JWK (RFC 8555 §7.3.1). When
// true and no account exists, the server MUST return 400 +
// urn:ietf:params:acme:error:accountDoesNotExist.
OnlyReturnExisting bool `json:"onlyReturnExisting,omitempty"`
// ExternalAccountBinding (EAB) is RFC 8555 §7.3.4. Phase 1b
// accepts the field but does NOT validate — EAB enforcement is
// a deliberate out-of-scope per the master prompt and lands as a
// follow-up if there's demand. Storing the raw envelope means a
// future phase can backfill validation against historical accounts.
ExternalAccountBinding map[string]interface{} `json:"externalAccountBinding,omitempty"`
}
// AccountUpdateRequest is the payload shape for the account-update
// endpoint POST /acme/profile/<id>/account/<acc-id> (RFC 8555 §7.3.2 +
// §7.3.6). Only `contact` and `status` are mutable per the spec.
type AccountUpdateRequest struct {
// Contact, when non-nil, replaces the account's contact list.
// nil means "leave unchanged" (distinct from empty []string{}
// which means "clear contacts" — cert-manager doesn't issue
// either, but the spec permits both).
Contact []string `json:"contact,omitempty"`
// Status, when set to "deactivated", retires the account per
// RFC 8555 §7.3.6. Other values are rejected — the operator
// path for revoked is via certctl's API, not via ACME.
Status string `json:"status,omitempty"`
}