Packages

Signed imgproxy URL generator with HMAC-SHA256 for Elixir. Built by Shiko (https://shiko.vet).

Current section

Files

Jump to
imgproxy_url lib imgproxy_url.ex
Raw

lib/imgproxy_url.ex

defmodule ImgproxyUrl do
@moduledoc """
Signed imgproxy URL generator with HMAC-SHA256.
By [Shiko](https://shiko.vet) — tech@shiko.vet
## Setup
{:imgproxy_url, "~> 0.1"}
config :imgproxy_url,
host: "https://img.example.com",
key: "your_hex_key",
salt: "your_hex_salt"
## Usage
ImgproxyUrl.build("s3://bucket/photo.jpg", width: 200, height: 200)
# => "https://img.example.com/<signature>/rs:fit:200:200/q:80/plain/s3://bucket/photo.jpg@webp"
Zero dependencies beyond Erlang stdlib `:crypto`.
"""
@doc """
Builds a signed imgproxy URL.
## Options
* `:width` - Target width (required)
* `:height` - Target height (required)
* `:quality` - JPEG/WebP quality 1-100 (default: 80)
* `:resize` - Resize type: "fit", "fill", "auto" (default: "fit")
* `:format` - Output format (default: "webp")
* `:host` - Override configured host
* `:key` - Override configured key
* `:salt` - Override configured salt
"""
@spec build(String.t(), keyword()) :: String.t()
def build(source_url, opts \\ []) do
width = Keyword.fetch!(opts, :width)
height = Keyword.fetch!(opts, :height)
quality = Keyword.get(opts, :quality, 80)
resize = Keyword.get(opts, :resize, "fit")
format = Keyword.get(opts, :format, "webp")
encoded_source = Base.url_encode64(source_url, padding: false)
processing_path = "/rs:#{resize}:#{width}:#{height}/q:#{quality}/#{encoded_source}.#{format}"
signature = sign(processing_path, opts)
"#{host(opts)}/#{signature}#{processing_path}"
end
@doc """
Builds a signed imgproxy URL using the `plain` source encoding.
Useful when the source URL should be human-readable.
"""
@spec build_plain(String.t(), keyword()) :: String.t()
def build_plain(source_url, opts \\ []) do
width = Keyword.fetch!(opts, :width)
height = Keyword.fetch!(opts, :height)
quality = Keyword.get(opts, :quality, 80)
resize = Keyword.get(opts, :resize, "fit")
format = Keyword.get(opts, :format, "webp")
processing_path = "/rs:#{resize}:#{width}:#{height}/q:#{quality}/plain/#{source_url}@#{format}"
signature = sign(processing_path, opts)
"#{host(opts)}/#{signature}#{processing_path}"
end
@doc """
Signs an imgproxy processing path with HMAC-SHA256.
"""
@spec sign(String.t(), keyword()) :: String.t()
def sign(path, opts \\ []) do
key = decode_hex(key(opts))
salt = decode_hex(salt(opts))
:crypto.mac(:hmac, :sha256, key, salt <> path)
|> Base.url_encode64(padding: false)
end
defp decode_hex(hex), do: Base.decode16!(hex, case: :mixed)
defp host(opts), do: opts[:host] || Application.get_env(:imgproxy_url, :host, "http://localhost:8080")
defp key(opts), do: opts[:key] || Application.get_env(:imgproxy_url, :key, "")
defp salt(opts), do: opts[:salt] || Application.get_env(:imgproxy_url, :salt, "")
end