# `Image.Plug.Provider.Cloudinary.Signing`
[🔗](https://github.com/elixir-image/image_plug/blob/v0.1.0/lib/image/plug/provider/cloudinary/signing.ex#L1)

Cloudinary-flavoured URL signing.

Per [Cloudinary's docs](https://cloudinary.com/documentation/control_access_to_media#signed_delivery_urls):

* SHA-256 (modern, default) over the canonical-string `<transforms>/<public-id><api-secret>`.
* Signature appears as a path segment `s--<base64url-truncated>--`
  inserted between the delivery type (`upload`) and the first
  transform stage.
* Cloudinary truncates the SHA-256 digest to 32 url-safe-base64
  characters by default. We follow that convention; longer
  signatures are rejected as invalid even if the prefix matches.

v0.1 ships SHA-256 only. Cloudinary's older SHA-1 scheme (8-char
signature, no `--` delimiters around the segment) is not
supported — open an issue if you need it.

Sign and verify share the same wire format. The module mirrors
`Image.Plug.Signing`'s public API so it's a drop-in alternative
when configuring the Cloudinary provider.

# `sign`

```elixir
@spec sign(String.t(), [String.t(), ...], keyword()) :: String.t()
```

Signs a Cloudinary path with the first key in `keys`.

### Arguments

* `path` is the path-and-query of a Cloudinary URL **without** an
  `s--<sig>--` segment. The signer inserts the segment between
  `<delivery>` (e.g. `upload`) and the first transform stage.

* `keys` is a non-empty list of API secrets.

### Options

* `:expires_at` — currently unused. Cloudinary's signed-URL flow
  relies on path-bound signatures rather than a per-URL expiry
  parameter; expirations live on the API-secret rotation cycle.

### Returns

The signed path.

# `split_at_delivery`

```elixir
@spec split_at_delivery(String.t()) :: {String.t(), String.t()}
```

Splits a Cloudinary path into `{prefix, transforms_and_source}`
where `prefix` is `/<account>/<resource-type>/<delivery>` and the
remainder is the rest of the path with no leading `/`.

Used by both signing and verification to canonicalise.

# `verify`

```elixir
@spec verify(String.t(), [String.t(), ...], keyword()) ::
  :ok | {:error, Image.Plug.Error.t()}
```

Verifies the signature on a Cloudinary path.

Returns `:ok` or an `Image.Plug.Error` with one of
`:signature_required`, `:invalid_signature`.

### Options

* `:required?` — when `true`, missing `s--<sig>--` segment produces
  `:signature_required`. Default `false`.

---

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