Validate tags

Validate tags

defmodule Thrift.Binary.Framed.ProtocolHandler do
  @moduledoc false

  @default_timeout 20_000
  @ssl_header_byte 0x16

  @typedoc "A module that implements the :ranch_transport behaviour"
  @type transport :: :ranch_tcp

  @typedoc "Transport-specific options"
  @type transport_opts :: :ranch_tcp.opts()

  alias Thrift.{
    Protocol.Binary,
    TApplicationException,
    Transport.SSL
  }

  require Logger

  defstruct [
    :transport,
    :socket,
    :server_module,
    :handler_module,
    :recv_timeout
  ]

  @spec start_link(reference, port, transport, {module, module, transport_opts, [SSL.option()]}) ::
          GenServer.on_start()
  def start_link(
        ref,
        socket,
        transport,
        {server_module, handler_module, transport_opts, ssl_opts}
      ) do
    pid =
      spawn_link(__MODULE__, :init, [
        ref,
        socket,
        transport,
        server_module,
        handler_module,
        transport_opts,
        ssl_opts
      ])

    {:ok, pid}
  end

  @dialyzer {:nowarn_function, init: 7}
  @spec init(reference, port, :ranch_tcp, module, module, :ranch_tcp.opts(), [SSL.option()]) ::
          :ok | no_return
  def init(ref, socket, :ranch_tcp, server_module, handler_module, tcp_opts, ssl_opts) do
    :ok = :ranch.accept_ack(ref)

    {recv_timeout, tcp_opts} = Keyword.pop(tcp_opts, :recv_timeout, @default_timeout)
    :ok = :inet.setopts(socket, Keyword.put(tcp_opts, :packet, 4))

    state = %__MODULE__{
      transport: :gen_tcp,
      socket: socket,
      server_module: server_module,
      handler_module: handler_module,
      recv_timeout: recv_timeout
    }

    case SSL.configuration(ssl_opts) do
      {:error, %_exception{} = err} ->
        Logger.error(fn ->
          format_log("SSL configuration error", Exception.format(:error, err, []), state)
        end)

        close(state)

      nil ->
        receive_message(state)

      {optional, ssl_opts} when optional in [:required, :optional] ->
        peek_first_byte(optional, ssl_opts, state)
    end
  end

  defp peek_first_byte(
         optional,
         ssl_opts,
         %__MODULE__{transport: :gen_tcp, socket: socket, recv_timeout: recv_timeout} = state
       ) do
    span = start_span(:peek_first_byte, state)

    with :ok <- :inet.setopts(socket, packet: :raw),
         {:ok, first_bytes} <- :gen_tcp.recv(socket, 4, recv_timeout),
         :ok <- :gen_tcp.unrecv(socket, first_bytes),
         :ok <- :inet.setopts(socket, packet: 4) do
      <<first_byte::8-unsigned, _::binary>> = first_bytes
      {:ok, first_byte}
    end
    |> case do
      {:ok, @ssl_header_byte} ->
        finish_span(span, result: "ssl")
        ssl_handshake(ssl_opts, state)

      {:ok, _non_ssl_header_byte} when optional == :required ->
        Logger.error(fn ->
          format_log("peek_first_byte", "SSL required", state)
        end)

        finish_span(span, result: "tcp_rejected")
        close(state)

      {:ok, _non_ssl_header_byte} when optional == :optional ->
        finish_span(span, result: "tcp")
        receive_message(state)

      {:error, closed} when closed in [:closed, :econnreset, :timeout] ->
        finish_span(span, result: to_string(closed))
        close(state)

      {:error, error} ->
        Logger.error(fn ->
          format_log("peek_first_byte", :ssl.format_error(error), state)
        end)

        finish_span(span, result: "error")
        close(state)
    end
  end

  defp ssl_handshake(
         ssl_opts,
         %__MODULE__{transport: :gen_tcp, socket: socket, recv_timeout: recv_timeout} = state
       ) do
    span = start_span(:ssl_handshake, state)

    # As of OTP 21.0, `:ssl.ssl_accept/3` is deprecated in favour of `:ssl.handshake/3`.
    # This check allows us to support both, depending on which OTP version is being used.
    if function_exported?(:ssl, :handshake, 3) do
      apply(:ssl, :handshake, [socket, ssl_opts, recv_timeout])
    else
      apply(:ssl, :ssl_accept, [socket, ssl_opts, recv_timeout])
    end
    |> case do
      {:ok, ssl_socket} ->
        finish_span(span, result: "success")
        state = %__MODULE__{state | transport: :ssl, socket: ssl_socket}
        receive_message(state)

      {:error, closed} when closed in [:closed, :econnreset, :timeout] ->
        finish_span(span, result: to_string(closed))
        close(state)

      {:error, error} ->
        Logger.error(fn ->
          format_log("ssl_handshake", :ssl.format_error(error), state)
        end)

        finish_span(span, result: "error")
        close(state)
    end
  end

  defp receive_message(
         %__MODULE__{transport: transport, socket: socket, recv_timeout: recv_timeout} = state
       ) do
    span = start_span(:receive_message, state)

    case transport.recv(socket, 0, recv_timeout) do
      {:ok, serialized_message} ->
        finish_span(span, result: "success")
        deserialize_message(serialized_message, state)

      {:error, closed} when closed in [:closed, :econnreset, :timeout] ->
        finish_span(span, result: to_string(closed))
        close(state)

      {:error, error} ->
        Logger.error(fn ->
          format_log("receive_message", :ssl.format_error(error), state)
        end)

        finish_span(span, result: "error")
        close(state)
    end
  end

  defp deserialize_message(serialized_message, state) do
    case Binary.deserialize(:message_begin, serialized_message) do
      {:ok, message} ->
        handle_message(message, state)

      {:error, reason} ->
        Logger.error(fn ->
          format_log("deserialize_message", inspect(reason), state)
        end)

        close(state)
    end
  end

  defp handle_message(
         {:call, sequence_id, method_name, args_binary},
         %__MODULE__{server_module: server_module, handler_module: handler_module} = state
       ) do
    request_size = IO.iodata_length(args_binary)
    gauge(:request_size, request_size, state, method: method_name)

    span = start_span(:call, state, method: method_name)

    case server_module.handle_thrift(method_name, args_binary, handler_module) do
      :noreply -> {:reply, <<0>>}
      other -> other
    end
    |> case do
      {:reply, serialized_response} ->
        finish_span(span, result: "success")

        serialized_message = Binary.serialize(:message_begin, {:reply, sequence_id, method_name})

        response_size = IO.iodata_length(serialized_message)
        gauge(:response_size, response_size, state, method: method_name, result: "success")

        send_reply([serialized_message | serialized_response], :continue, state)

      {:server_error, %TApplicationException{} = exc} ->
        finish_span(span, result: "error")

        serialized_message =
          Binary.serialize(:message_begin, {:exception, sequence_id, method_name})

        serialized_exception = Binary.serialize(:application_exception, exc)

        response_size = IO.iodata_length(serialized_exception)
        gauge(:response_size, response_size, state, method: method_name, result: "error")

        send_reply([serialized_message | serialized_exception], :stop, state)
    end
  end

  defp handle_message(
         {:oneway, _sequence_id, method_name, args_binary},
         %__MODULE__{server_module: server_module, handler_module: handler_module} = state
       ) do
    spawn(fn ->
      server_module.handle_thrift(method_name, args_binary, handler_module)
    end)

    send_reply(<<0>>, :continue, state)
  end

  defp send_reply(data, after_reply, %__MODULE__{transport: transport, socket: socket} = state) do
    span = start_span(:send_reply, state)

    case transport.send(socket, data) do
      :ok when after_reply == :continue ->
        finish_span(span, result: "success")
        receive_message(state)

      :ok when after_reply == :stop ->
        finish_span(span, result: "success")
        close(state)

      {:error, closed} when closed in [:closed, :econnreset, :timeout] ->
        finish_span(span, result: to_string(closed))
        close(state)

      {:error, error} ->
        Logger.error(fn ->
          format_log("send_reply", :ssl.format_error(error), state)
        end)

        finish_span(span, result: "error")
        close(state)
    end
  end

  defp close(%__MODULE__{transport: transport, socket: socket}) do
    transport.close(socket)
  end

  defp start_span(metric_name, %__MODULE__{handler_module: handler_module}, tags \\ %{}) do
    event_name = [Thrift, handler_module, metric_name]
    time = System.monotonic_time()
    metadata = tags_to_metadata(tags)
    {:span, event_name, metadata, time}
  end

  defp finish_span({:span, event_name, metadata, time}, tags) do
    duration = System.monotonic_time() - time
    metadata = tags_to_metadata(tags, metadata)
    :telemetry.execute(event_name, %{duration: duration}, metadata)
  end

  defp gauge(metric_name, value, %__MODULE__{handler_module: handler_module}, tags)
       when is_number(value) do
    event_name = [Thrift, handler_module, metric_name]
    metadata = tags_to_metadata(tags)
    :telemetry.execute(event_name, %{value: value}, metadata)
  end

  # Check that tags are simple and well-formed for use in systems like
  # OpenTSDB. This check is only done in test due to the performance cost.
  if Mix.env() == :test do
    defp validate_tag(tag) when is_atom(tag) do
      String.to_atom(validate_tag(Atom.to_string(tag)))
    end

    defp validate_tag(tag) when is_binary(tag) do
      false = tag =~ ~r{[^a-zA-Z_]}
      tag
    end
  else
    defmacrop validate_tag(tag) do
      tag
    end
  end

  defp tags_to_metadata(tags, metadata \\ %{}) do
    Enum.reduce(tags, metadata, fn
      {k, v}, metadata ->
        Map.put(metadata, validate_tag(k), validate_tag(v))
    end)
  end

  defp format_log(context, message, %__MODULE__{handler_module: handler_module}) do
    "#{inspect(handler_module)} (#{inspect(self())}) #{context}: #{message}"
  end
end

defmodule Thrift.Binary.Framed.ProtocolHandler do
  @moduledoc false

  @default_timeout 20_000
  @ssl_header_byte 0x16

  @typedoc "A module that implements the :ranch_transport behaviour"
  @type transport :: :ranch_tcp

  @typedoc "Transport-specific options"
  @type transport_opts :: :ranch_tcp.opts()

  alias Thrift.{
    Protocol.Binary,
    TApplicationException,
    Transport.SSL
  }

  require Logger

  defstruct [
    :transport,
    :socket,
    :server_module,
    :handler_module,
    :recv_timeout
  ]

  @spec start_link(reference, port, transport, {module, module, transport_opts, [SSL.option()]}) ::
          GenServer.on_start()
  def start_link(
        ref,
        socket,
        transport,
        {server_module, handler_module, transport_opts, ssl_opts}
      ) do
    pid =
      spawn_link(__MODULE__, :init, [
        ref,
        socket,
        transport,
        server_module,
        handler_module,
        transport_opts,
        ssl_opts
      ])

    {:ok, pid}
  end

  @dialyzer {:nowarn_function, init: 7}
  @spec init(reference, port, :ranch_tcp, module, module, :ranch_tcp.opts(), [SSL.option()]) ::
          :ok | no_return
  def init(ref, socket, :ranch_tcp, server_module, handler_module, tcp_opts, ssl_opts) do
    :ok = :ranch.accept_ack(ref)

    {recv_timeout, tcp_opts} = Keyword.pop(tcp_opts, :recv_timeout, @default_timeout)
    :ok = :inet.setopts(socket, Keyword.put(tcp_opts, :packet, 4))

    state = %__MODULE__{
      transport: :gen_tcp,
      socket: socket,
      server_module: server_module,
      handler_module: handler_module,
      recv_timeout: recv_timeout
    }

    case SSL.configuration(ssl_opts) do
      {:error, %_exception{} = err} ->
        Logger.error(fn ->
          format_log("SSL configuration error", Exception.format(:error, err, []), state)
        end)

        close(state)

      nil ->
        receive_message(state)

      {optional, ssl_opts} when optional in [:required, :optional] ->
        peek_first_byte(optional, ssl_opts, state)
    end
  end

  defp peek_first_byte(
         optional,
         ssl_opts,
         %__MODULE__{transport: :gen_tcp, socket: socket, recv_timeout: recv_timeout} = state
       ) do
    span = start_span(:peek_first_byte, state)

    with :ok <- :inet.setopts(socket, packet: :raw),
         {:ok, first_bytes} <- :gen_tcp.recv(socket, 4, recv_timeout),
         :ok <- :gen_tcp.unrecv(socket, first_bytes),
         :ok <- :inet.setopts(socket, packet: 4) do
      <<first_byte::8-unsigned, _::binary>> = first_bytes
      {:ok, first_byte}
    end
    |> case do
      {:ok, @ssl_header_byte} ->
        finish_span(span, result: "ssl")
        ssl_handshake(ssl_opts, state)

      {:ok, _non_ssl_header_byte} when optional == :required ->
        Logger.error(fn ->
          format_log("peek_first_byte", "SSL required", state)
        end)

        finish_span(span, result: "tcp_rejected")
        close(state)

      {:ok, _non_ssl_header_byte} when optional == :optional ->
        finish_span(span, result: "tcp")
        receive_message(state)

      {:error, closed} when closed in [:closed, :econnreset, :timeout] ->
        finish_span(span, result: to_string(closed))
        close(state)

      {:error, error} ->
        Logger.error(fn ->
          format_log("peek_first_byte", :ssl.format_error(error), state)
        end)

        finish_span(span, result: "error")
        close(state)
    end
  end

  defp ssl_handshake(
         ssl_opts,
         %__MODULE__{transport: :gen_tcp, socket: socket, recv_timeout: recv_timeout} = state
       ) do
    span = start_span(:ssl_handshake, state)

    # As of OTP 21.0, `:ssl.ssl_accept/3` is deprecated in favour of `:ssl.handshake/3`.
    # This check allows us to support both, depending on which OTP version is being used.
    if function_exported?(:ssl, :handshake, 3) do
      apply(:ssl, :handshake, [socket, ssl_opts, recv_timeout])
    else
      apply(:ssl, :ssl_accept, [socket, ssl_opts, recv_timeout])
    end
    |> case do
      {:ok, ssl_socket} ->
        finish_span(span, result: "success")
        state = %__MODULE__{state | transport: :ssl, socket: ssl_socket}
        receive_message(state)

      {:error, closed} when closed in [:closed, :econnreset, :timeout] ->
        finish_span(span, result: to_string(closed))
        close(state)

      {:error, error} ->
        Logger.error(fn ->
          format_log("ssl_handshake", :ssl.format_error(error), state)
        end)

        finish_span(span, result: "error")
        close(state)
    end
  end

  defp receive_message(
         %__MODULE__{transport: transport, socket: socket, recv_timeout: recv_timeout} = state
       ) do
    span = start_span(:receive_message, state)

    case transport.recv(socket, 0, recv_timeout) do
      {:ok, serialized_message} ->
        finish_span(span, result: "success")
        deserialize_message(serialized_message, state)

      {:error, closed} when closed in [:closed, :econnreset, :timeout] ->
        finish_span(span, result: to_string(closed))
        close(state)

      {:error, error} ->
        Logger.error(fn ->
          format_log("receive_message", :ssl.format_error(error), state)
        end)

        finish_span(span, result: "error")
        close(state)
    end
  end

  defp deserialize_message(serialized_message, state) do
    case Binary.deserialize(:message_begin, serialized_message) do
      {:ok, message} ->
        handle_message(message, state)

      {:error, reason} ->
        Logger.error(fn ->
          format_log("deserialize_message", inspect(reason), state)
        end)

        close(state)
    end
  end

  defp handle_message(
         {:call, sequence_id, method_name, args_binary},
         %__MODULE__{server_module: server_module, handler_module: handler_module} = state
       ) do
    request_size = IO.iodata_length(args_binary)
    gauge(:request_size, request_size, state, method: method_name)

    span = start_span(:call, state, method: method_name)

    case server_module.handle_thrift(method_name, args_binary, handler_module) do
      :noreply -> {:reply, <<0>>}
      other -> other
    end
    |> case do
      {:reply, serialized_response} ->
        finish_span(span, result: "success")

        serialized_message = Binary.serialize(:message_begin, {:reply, sequence_id, method_name})

        response_size = IO.iodata_length(serialized_message)
        gauge(:response_size, response_size, state, method: method_name, result: "success")

        send_reply([serialized_message | serialized_response], :continue, state)

      {:server_error, %TApplicationException{} = exc} ->
        finish_span(span, result: "error")

        serialized_message =
          Binary.serialize(:message_begin, {:exception, sequence_id, method_name})

        serialized_exception = Binary.serialize(:application_exception, exc)

        response_size = IO.iodata_length(serialized_exception)
        gauge(:response_size, response_size, state, method: method_name, result: "error")

        send_reply([serialized_message | serialized_exception], :stop, state)
    end
  end

  defp handle_message(
         {:oneway, _sequence_id, method_name, args_binary},
         %__MODULE__{server_module: server_module, handler_module: handler_module} = state
       ) do
    spawn(fn ->
      server_module.handle_thrift(method_name, args_binary, handler_module)
    end)

    send_reply(<<0>>, :continue, state)
  end

  defp send_reply(data, after_reply, %__MODULE__{transport: transport, socket: socket} = state) do
    span = start_span(:send_reply, state)

    case transport.send(socket, data) do
      :ok when after_reply == :continue ->
        finish_span(span, result: "success")
        receive_message(state)

      :ok when after_reply == :stop ->
        finish_span(span, result: "success")
        close(state)

      {:error, closed} when closed in [:closed, :econnreset, :timeout] ->
        finish_span(span, result: to_string(closed))
        close(state)

      {:error, error} ->
        Logger.error(fn ->
          format_log("send_reply", :ssl.format_error(error), state)
        end)

        finish_span(span, result: "error")
        close(state)
    end
  end

  defp close(%__MODULE__{transport: transport, socket: socket}) do
    transport.close(socket)
  end

  defp start_span(metric_name, %__MODULE__{handler_module: handler_module}, tags \\ %{}) do
    event_name = [Thrift, handler_module, metric_name]
    time = System.monotonic_time()
    metadata = Map.new(tags)
    {:span, event_name, metadata, time}
  end

  defp finish_span({:span, event_name, metadata, time}, tags) do
    duration = System.monotonic_time() - time
    :telemetry.execute(event_name, %{duration: duration}, add_tags(metadata, tags))
  end

  defp gauge(metric_name, value, %__MODULE__{handler_module: handler_module}, tags)
       when is_number(value) do
    event_name = [Thrift, handler_module, metric_name]
    :telemetry.execute(event_name, %{value: value}, Map.new(tags))
  end

  defp add_tags(metadata, tags) do
    Enum.reduce(tags, metadata, fn
      {k, v}, metadata -> Map.put(metadata, k, v)
    end)
  end

  defp format_log(context, message, %__MODULE__{handler_module: handler_module}) do
    "#{inspect(handler_module)} (#{inspect(self())}) #{context}: #{message}"
  end
end


Allow cross-origin socket connections for now

defmodule Neoboard.UserSocket do
  use Phoenix.Socket

  ## Channels
  channel "*", Neoboard.BoardChannel

  ## Transports
  transport :websocket, Phoenix.Transports.WebSocket,
    check_origin: false
  transport :longpoll, Phoenix.Transports.LongPoll

  # Socket params are passed from the client and can
  # be used to verify and authenticate a user. After
  # verification, you can put default assigns into
  # the socket that will be set for all channels, ie
  #
  #     {:ok, assign(socket, :user_id, verified_user_id)}
  #
  #  To deny connection, return `:error`.
  def connect(_params, socket) do
    {:ok, socket}
  end

  # Socket id's are topics that allow you to identify all sockets for a given user:
  #
  #     def id(socket), do: "users_socket:#{socket.assigns.user_id}"
  #
  # Would allow you to broadcast a "disconnect" event and terminate
  # all active sockets and channels for a given user:
  #
  #     MyApp.Endpoint.broadcast("users_socket:" <> user.id, "disconnect", %{})
  #
  # Returning `nil` makes this socket anonymous.
  def id(_socket), do: nil
end

defmodule Neoboard.UserSocket do
  use Phoenix.Socket

  ## Channels
  channel "*", Neoboard.BoardChannel

  ## Transports
  transport :websocket, Phoenix.Transports.WebSocket
  transport :longpoll, Phoenix.Transports.LongPoll

  # Socket params are passed from the client and can
  # be used to verify and authenticate a user. After
  # verification, you can put default assigns into
  # the socket that will be set for all channels, ie
  #
  #     {:ok, assign(socket, :user_id, verified_user_id)}
  #
  #  To deny connection, return `:error`.
  def connect(_params, socket) do
    {:ok, socket}
  end

  # Socket id's are topics that allow you to identify all sockets for a given user:
  #
  #     def id(socket), do: "users_socket:#{socket.assigns.user_id}"
  #
  # Would allow you to broadcast a "disconnect" event and terminate
  # all active sockets and channels for a given user:
  #
  #     MyApp.Endpoint.broadcast("users_socket:" <> user.id, "disconnect", %{})
  #
  # Returning `nil` makes this socket anonymous.
  def id(_socket), do: nil
end


Make all actions in /users require authentication

defmodule Condorest.Web.UserController do
  use Condorest.Web, :controller
  plug :authenticate

  alias Condorest.Accounts

  def index(conn, _params) do
    users = Accounts.list_users()
    render(conn, "index.html", users: users)
  end

  def new(conn, _params) do
    changeset = Accounts.change_user(%Condorest.Accounts.User{})
    render(conn, "new.html", changeset: changeset)
  end

  def create(conn, %{"user" => user_params}) do
    case Accounts.create_user(user_params) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "User created successfully.")
        |> redirect(to: user_path(conn, :show, user))
      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end

  def show(conn, %{"id" => id}) do
    user = Accounts.get_user!(id)
    render(conn, "show.html", user: user)
  end

  def edit(conn, %{"id" => id}) do
    user = Accounts.get_user!(id)
    changeset = Accounts.change_user(user)
    render(conn, "edit.html", user: user, changeset: changeset)
  end

  def update(conn, %{"id" => id, "user" => user_params}) do
    user = Accounts.get_user!(id)

    case Accounts.update_user(user, user_params) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "User updated successfully.")
        |> redirect(to: user_path(conn, :show, user))
      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "edit.html", user: user, changeset: changeset)
    end
  end

  def delete(conn, %{"id" => id}) do
    user = Accounts.get_user!(id)
    {:ok, _user} = Accounts.delete_user(user)

    conn
    |> put_flash(:info, "User deleted successfully.")
    |> redirect(to: user_path(conn, :index))
  end

  defp authenticate(conn, _opts) do
    if conn.assigns.current_user do
      conn
    else
      conn
      |> put_flash(:error, "You must be logged in to access that page")
      |> redirect(to: page_path(conn, :index))
      |> halt()
    end
  end
end

defmodule Condorest.Web.UserController do
  use Condorest.Web, :controller
  plug :authenticate when action in [:index, :show]

  alias Condorest.Accounts

  def index(conn, _params) do
    users = Accounts.list_users()
    render(conn, "index.html", users: users)
  end

  def new(conn, _params) do
    changeset = Accounts.change_user(%Condorest.Accounts.User{})
    render(conn, "new.html", changeset: changeset)
  end

  def create(conn, %{"user" => user_params}) do
    case Accounts.create_user(user_params) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "User created successfully.")
        |> redirect(to: user_path(conn, :show, user))
      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end

  def show(conn, %{"id" => id}) do
    user = Accounts.get_user!(id)
    render(conn, "show.html", user: user)
  end

  def edit(conn, %{"id" => id}) do
    user = Accounts.get_user!(id)
    changeset = Accounts.change_user(user)
    render(conn, "edit.html", user: user, changeset: changeset)
  end

  def update(conn, %{"id" => id, "user" => user_params}) do
    user = Accounts.get_user!(id)

    case Accounts.update_user(user, user_params) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "User updated successfully.")
        |> redirect(to: user_path(conn, :show, user))
      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "edit.html", user: user, changeset: changeset)
    end
  end

  def delete(conn, %{"id" => id}) do
    user = Accounts.get_user!(id)
    {:ok, _user} = Accounts.delete_user(user)

    conn
    |> put_flash(:info, "User deleted successfully.")
    |> redirect(to: user_path(conn, :index))
  end

  defp authenticate(conn, _opts) do
    if conn.assigns.current_user do
      conn
    else
      conn
      |> put_flash(:error, "You must be logged in to access that page")
      |> redirect(to: page_path(conn, :index))
      |> halt()
    end
  end
end


Fix warnings

Fix warnings

defmodule Noegle.SessionTest do
  use Noegle.ConnCase

  alias Noegle.Session

  defmodule MockRepo do
    def get(MockUser, id) do
      if id == 42 do
        %{}
      else
        nil
      end
    end
  end

  test "authenticate sets user_id on the session" do
    conn = Session.authenticate(conn(), %{id: 42})

    assert Plug.Conn.get_session(conn, :user_id) == 42
  end

  test "current_user fetches the current user if user_id is set on session" do
    Mix.Config.persist([noegle: %{repo: MockRepo, user: MockUser}])

    conn = Plug.Conn.put_session(conn(), :user_id, 42)

    assert Session.current_user(conn)
  end

  test "current_user returns nil if no user_id is set on session" do
    Mix.Config.persist([noegle: %{repo: MockRepo, user: MockUser}])

    assert Plug.Conn.get_session(conn(), :user_id) == nil
    refute Session.current_user(conn())
  end

  test "logged_in returns true if conn.assigns.current_user is set" do
    conn = Plug.Conn.assign(conn(), :current_user, %{id: 42})
    assert Session.logged_in?(conn)
  end

  test "logged_in returns false if conn.assigns.current_user isn't set" do
    refute conn().assigns[:current_user]
    refute Session.logged_in?(conn())
  end
end

defmodule Noegle.SessionTest do
  use Noegle.ConnCase

  alias Noegle.Session

  defmodule MockRepo do
    def get(MockUser, id) do
      if id == 42 do
        %{}
      else
        nil
      end
    end
  end

  test "authenticate sets user_id on the session" do
    conn = Session.authenticate(conn(), %{id: 42})

    assert Plug.Conn.get_session(conn, :user_id) == 42
  end

  test "current_user fetches the current user if user_id is set on session" do
    Mix.Config.persist([noegle: %{repo: MockRepo, user: MockUser}])

    conn = Plug.Conn.put_session(conn(), :user_id, 42)

    assert Session.current_user(conn)
  end

  test "current_user returns nil if no user_id is set on session" do
    Mix.Config.persist([noegle: %{repo: MockRepo, user: MockUser}])

    assert Plug.Conn.get_session(conn(), :user_id) == nil
    refute Session.current_user(conn())
  end

  test "logged_in returns true if conn.assigns.current_user is set" do
    conn = Plug.Conn.assign(conn, :current_user, %{id: 42})
    assert Session.logged_in?(conn)
  end

  test "logged_in returns false if conn.assigns.current_user isn't set" do
    refute conn.assigns[:current_user]
    refute Session.logged_in?(conn)
  end
end