# `Mailglass.Tracking.Token`
[🔗](https://github.com/szTheory/mailglass/blob/v1.0.0/lib/mailglass/tracking/token.ex#L1)

Phoenix.Token-signed tokens for open pixel + click redirect URLs (TRACK-03, D-33..D-35).

## Token shape (D-34 + D-35)

Open pixel payload: `{:open, delivery_id, tenant_id}`
Click redirect payload: `{:click, delivery_id, tenant_id, target_url}`

## Open-redirect prevention (D-35 pattern a)

Target URL lives INSIDE the signed token, NEVER as a query parameter.
The class of CVE that Mailchimp shipped in 2019 + 2022
(open-redirect via weak parameter validation) is structurally
unreachable — there is no parameter to tamper with. A tampered
token fails Phoenix.Token's HMAC check → `:error`.

Target URL scheme is validated at SIGN time (not just verify time) —
`http` or `https` only. Attempting to sign a `javascript:` or `ftp:`
URL raises `%Mailglass.ConfigError{type: :invalid}`.

## Salts rotation (D-33)

`config :mailglass, :tracking, salts: ["q2-2026", "q1-2026"]`. The
HEAD of the list signs; ALL salts in the list verify (iterate with
early return). Rotating = prepending a new salt; old salts verify
until removed from the list. Token max_age default: 2 years
(archived-email pixel loads still work).

## `tenant_id` in payload, not URL (D-39)

Decoded `tenant_id` comes from the SIGNED PAYLOAD, not from URL
path/query. Phase 3 Plug uses it to call `Tenancy.put_current/1`.
URL path + query leak to referrer headers, shared-link screenshots,
corporate proxy logs; the signed payload is the only privacy-preserving
option.

# `sign_click`
*since 0.1.0* 

```elixir
@spec sign_click(
  endpoint :: Phoenix.Token.context(),
  String.t(),
  String.t(),
  String.t()
) :: binary()
```

Signs a click-redirect token. Payload:
`{:click, delivery_id, tenant_id, target_url}`.

Raises `%Mailglass.ConfigError{type: :invalid}` if `target_url`
scheme is not `http` or `https`.

# `sign_open`
*since 0.1.0* 

```elixir
@spec sign_open(
  endpoint :: Phoenix.Token.context(),
  delivery_id :: String.t(),
  tenant_id :: String.t()
) :: binary()
```

Signs an open-pixel token. Payload: `{:open, delivery_id, tenant_id}`.

Uses the HEAD of `config :mailglass, :tracking, salts:` to sign.
Raises `%Mailglass.ConfigError{type: :missing}` if no salts are configured.

# `verify_click`
*since 0.1.0* 

```elixir
@spec verify_click(endpoint :: Phoenix.Token.context(), binary()) ::
  {:ok,
   %{delivery_id: String.t(), tenant_id: String.t(), target_url: String.t()}}
  | :error
```

Verifies a click-redirect token. Returns
`{:ok, %{delivery_id, tenant_id, target_url}}` or `:error`.

Verified target_url is ALWAYS scheme ∈ ["http", "https"] (scheme was
validated at sign time; tampered tokens fail HMAC first; defense-in-depth
re-check at verify time per T-3-07-10).

# `verify_open`
*since 0.1.0* 

```elixir
@spec verify_open(endpoint :: Phoenix.Token.context(), binary()) ::
  {:ok, %{delivery_id: String.t(), tenant_id: String.t()}} | :error
```

Verifies an open-pixel token. Returns `{:ok, %{delivery_id, tenant_id}}`
on success or `:error` on any failure (expired, tampered, unknown salt).

Iterates over ALL configured salts to support rotation windows.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
