Documentation
¶
Overview ¶
Package unsubscribe is the HMAC-signed token primitive that backs the one-click email unsubscribe endpoints from §5.1 of the notification-preferences paper.
Token shape (base64url, no padding):
body = user_id || "|" || category || "|" || expires_at_unix mac = HMAC-SHA256(NOTIFY_UNSUBSCRIBE_SECRET, body) tok = base64url(mac || body)
The first 32 bytes of the decoded token are the MAC; the rest is the plaintext payload. Verification recomputes the MAC over the payload and constant-time-compares against the prefix. A consumed-state check is layered on top via the notification_unsubscribe_tokens collection — Sign+Verify are pure (no DB).
Why not JWT: every byte counts in a URL that has to fit in mail clients' wrap rules and in the SMS-like List-Unsubscribe-Post space. HMAC + a 3-field tuple is the smallest thing that works.
Index ¶
Constants ¶
const Default30Days = 30 * 24 * time.Hour
Default30Days is the standard expiry window — CAN-SPAM requires processing within 10 business days, GDPR within 30; the paper §5.1 pins 30 days.
Variables ¶
var ErrExpired = errors.New("unsubscribe: token expired")
ErrExpired is returned for tokens whose ExpiresAt is in the past. Distinct from ErrInvalid so the route can render a "this link expired" page rather than a generic error.
var ErrInvalid = errors.New("unsubscribe: invalid token")
ErrInvalid is returned for any failed decode / HMAC mismatch / bad payload shape. Generic by design.
Functions ¶
This section is empty.
Types ¶
type Payload ¶
Payload is the verified inner state. Empty Category means "unsubscribe from all marketing"; the paper §5.1 + the consent-log schema use "" for the global-unsub case.
type Signer ¶
type Signer struct {
// contains filtered or unexported fields
}
Signer constructs and verifies tokens with a secret key. The key comes from NOTIFY_UNSUBSCRIBE_SECRET — fail-closed boot if missing in production mode.
func NewSigner ¶
NewSigner returns a Signer for the given secret. Empty secret is rejected — every caller should resolve this from env at boot.
func (*Signer) Sign ¶
Sign returns a base64url token for (userID, category, expiresAt). expiresAt is truncated to second precision because the token carries a unix timestamp.
func (*Signer) SignWithTTL ¶
func (s *Signer) SignWithTTL(userID, category string, now time.Time, ttl time.Duration) (string, error)
SignWithTTL is a convenience that signs a token expiring `ttl` from now. now is injectable for tests.