lib/auth0_plug.ex

defmodule Auth0Plug do
  @moduledoc """
  Documentation for Auth0Plug.
  """
  alias Auth0Plug.Jwt
  alias Plug.Conn

  @realm Application.compile_env(:auth0_plug, :realm)
  @secret Application.compile_env(:auth0_plug, :secret)

  if @secret == nil do
    "Auth0Plug's secret is set to nil. Authentication will always fail."
    |> IO.warn()
  end

  def init(options) do
    options
  end

  def unauthorized_message do
    default = "Your credentials are invalid."
    message = Application.get_env(:auth0_plug, :unauthorized_message, default)
    Jason.encode!(%{"message" => message})
  end

  @doc """
  Whether the path is excluded.
  """
  def is_excluded?(conn) do
    Application.get_env(:auth0_plug, :exclude_from_401)
    |> List.insert_at(0, "/*_path")
    |> Enum.member?(elem(conn.private[:plug_route], 0))
  end

  @doc """
  Whether a 401 should be returned.

  401s are return only when return_401 is set to true and the path is not in
  exclude_from_401.
  """
  def is_401?(conn) do
    if Application.get_env(:auth0_plug, :return_401) do
      if Auth0Plug.is_excluded?(conn) do
        false
      else
        true
      end
    else
      false
    end
  end

  @doc """
  Sends a 401.

  This is a separate function so that it can be reused to send the same 401
  when is not directly sent by Auth0Plug, for example to send a 401 on an
  ignored route.
  """
  def send_401(conn) do
    conn
    |> Conn.put_resp_header(
      "www-authenticate",
      "Bearer realm=\"#{@realm}\", error=\"invalid_token\""
    )
    |> Conn.put_resp_content_type("application/json")
    |> Conn.send_resp(401, Auth0Plug.unauthorized_message())
  end

  @doc """
  Return a 401 response.
  """
  def unauthorized(conn) do
    if Auth0Plug.is_401?(conn) do
      conn
      |> Auth0Plug.send_401()
      |> Conn.halt()
    else
      conn
    end
  end

  def call(conn, _options) do
    token = Jwt.get(conn)

    case Jwt.verify(token, @secret) do
      {:ok, jwt} -> Jwt.put(conn, jwt)
      {:error, _jwt} -> Auth0Plug.unauthorized(conn)
    end
  end

  @doc """
  Exposes Jwt.get/1
  """
  def get_jwt(conn), do: Jwt.get(conn)
end