Oasis.HMACToken behaviour (oasis v0.5.2)

View Source

Callback

There are two callback functions reserved for use in the generated modules when we use the hmac security scheme of the OpenAPI Specification.

  • crypto_config/3, provides a way to define the crypto-related key information for the high level usage, it required to return a Oasis.HMACToken.Crypto struct (or nil).
  • verify/3, an optional function to provide a way to custom the verification of the token, you may want to validate request datetime, HTTP body or other more rules to verify it.

Example

Here is an example to verify that HTTP request time does not exceed the current time by 1 minute.

defmodule Oasis.Gen.HMACAuth do
  @behaviour Oasis.HMACToken
  alias Oasis.HMACToken.Crypto

  # in seconds
  @max_diff 60

  @impl true
  def crypto_config(_conn, _opts, _credential) do
    %Crypto{
      credential: "...",
      secret: "..."
    }
  end

  @impl true
  def verify(conn, token, _opts) do
    with {:ok, _} <- Oasis.HMACToken.verify(conn, token, opts),
         {:ok, timestamp} <- conn |> get_header_date() |> parse_header_date() do
      timestamp_now = DateTime.utc_now() |> DateTime.to_unix()

      if abs(timestamp_now - timestamp) < @max_diff do
        {:ok, timestamp}
      else
        {:error, :expired}
      end
    end
  end

  defp get_header_date(conn) do
    conn
    |> Plug.Conn.get_req_header("x-oasis-date")
    |> case do
      [date] -> date
      _ -> nil
    end
  end

  defp parse_header_date(str) when is_binary(str) do
    with {:ok, datetime} <- Timex.parse(str, "%a, %d %b %Y %H:%M:%S GMT", :strftime),
         timestamp when is_integer(timestamp) <- Timex.to_unix(datetime) do
      {:ok, timestamp}
    end
  end

  defp parse_header_date(_otherwise), do: {:error, :expired}
end

Summary

Functions

Sign HTTP requests according to settings.

Default implementation of the callback verify, only verify the signature.

Types

opts()

@type opts() :: Plug.opts()

token()

@type token() :: %{
  credential: String.t(),
  signed_headers: String.t(),
  signature: String.t()
}

verify_error()

@type verify_error() ::
  {:error, :header_mismatch}
  | {:error, :invalid_credential}
  | {:error, :invalid_token}
  | {:error, :expired}

Callbacks

crypto_config(conn, opts, credential)

@callback crypto_config(
  conn :: Plug.Conn.t(),
  opts :: Keyword.t(),
  credential :: String.t()
) ::
  Oasis.HMACToken.Crypto.t() | nil

verify(conn, token, opts)

(optional)
@callback verify(conn :: Plug.Conn.t(), token :: token(), opts :: opts()) ::
  {:ok, term()} | verify_error()

Functions

sign(conn, signed_headers, secret, algorithm)

@spec sign(
  conn :: Plug.Conn.t(),
  signed_headers :: String.t(),
  secret :: String.t(),
  algorithm :: Oasis.Plug.HMACAuth.algorithm()
) :: String.t()

Sign HTTP requests according to settings.

verify(conn, token, opts)

@spec verify(
  conn :: Plug.Conn.t(),
  token :: token(),
  opts :: Oasis.Plug.HMACAuth.opts()
) :: {:ok, term()} | verify_error()

Default implementation of the callback verify, only verify the signature.