Oasis.HMACToken behaviour (oasis v0.7.0)

Copy Markdown 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, 0} <- DateTime.from_iso8601(str) do
      {:ok, DateTime.to_unix(datetime)}
    end
  end

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

Summary

Types

Structured verification error returned by verify/3.

String status code used in verify_error().

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, verify_error_status()}

Structured verification error returned by verify/3.

The second tuple element is a fixed string status code rather than an atom.

verify_error_status()

@type verify_error_status() :: String.t()

String status code used in verify_error().

Allowed values are:

  • "header_mismatch"
  • "invalid_credential"
  • "invalid_token"
  • "expired"

Elixir typespecs cannot enumerate string literals directly, so this remains String.t() at the type level and the fixed set is documented here.

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.

Error statuses are normalized to strings for Oasis's public callback contract.