From 0dbcd86c881442c916a1c8c5caaef2e89f9baa95 Mon Sep 17 00:00:00 2001 From: reichert621 Date: Mon, 16 Nov 2020 23:09:06 -0500 Subject: [PATCH] Start playing around with setting up a public v1 API --- lib/chat_api/users.ex | 6 ++ lib/chat_api_web/api_auth_plug.ex | 1 + lib/chat_api_web/public_api_auth_plug.ex | 85 ++++++++++++++++++++++++ lib/chat_api_web/router.ex | 12 ++++ 4 files changed, 104 insertions(+) create mode 100644 lib/chat_api_web/public_api_auth_plug.ex diff --git a/lib/chat_api/users.ex b/lib/chat_api/users.ex index bc7805204..d9faaa44d 100644 --- a/lib/chat_api/users.ex +++ b/lib/chat_api/users.ex @@ -40,6 +40,12 @@ defmodule ChatApi.Users do User |> where(password_reset_token: ^token) |> Repo.one() end + @spec find_by_api_key(binary()) :: User.t() | nil + def find_by_api_key(_api_key) do + # TODO: implement me! + nil + end + @spec send_password_reset_email(User.t()) :: ChatApi.Emails.deliver_result() | {:error, Ecto.Changeset.t()} def send_password_reset_email(user) do diff --git a/lib/chat_api_web/api_auth_plug.ex b/lib/chat_api_web/api_auth_plug.ex index a3902ab21..ec8ab5189 100644 --- a/lib/chat_api_web/api_auth_plug.ex +++ b/lib/chat_api_web/api_auth_plug.ex @@ -93,6 +93,7 @@ defmodule ChatApiWeb.APIAuthPlug do defp signing_salt(), do: Atom.to_string(__MODULE__) defp fetch_auth_token(conn, config) do + # TODO: if token isn't verified, check personal API keys? with [token | _rest] <- Conn.get_req_header(conn, "authorization"), {:ok, token} <- Plug.verify_token(conn, signing_salt(), token, config) do token diff --git a/lib/chat_api_web/public_api_auth_plug.ex b/lib/chat_api_web/public_api_auth_plug.ex new file mode 100644 index 000000000..5f203a49f --- /dev/null +++ b/lib/chat_api_web/public_api_auth_plug.ex @@ -0,0 +1,85 @@ +defmodule ChatApiWeb.PublicAPIAuthPlug do + @moduledoc false + use Pow.Plug.Base + + alias Plug.Conn + alias Pow.{Config, Store.CredentialsCache} + + @impl true + @spec fetch(Conn.t(), Config.t()) :: {Conn.t(), map() | nil} + def fetch(conn, config) do + conn + |> fetch_auth_token(config) + |> fetch_user(conn, config) + end + + defp fetch_user(nil, conn, _config), do: {conn, nil} + + defp fetch_user(token, conn, config) do + case fetch_from_store(token, config) do + nil -> fetch_and_cache_user(token, conn, config) + user -> {conn, user} + end + end + + defp fetch_and_cache_user(token, conn, config) do + case ChatApi.Users.find_by_api_key(token) do + nil -> + {conn, nil} + + user -> + config + |> store_config() + |> CredentialsCache.put(token, {user, []}) + + {conn, user} + end + end + + defp fetch_from_store(token, config) do + config + |> store_config() + |> CredentialsCache.get(token) + |> case do + :not_found -> nil + {user, _metadata} -> user + end + end + + @impl true + @spec create(Conn.t(), map(), Config.t()) :: {Conn.t(), map()} + def create(conn, user, _config) do + {conn, user} + end + + @impl true + @spec delete(Conn.t(), Config.t()) :: Conn.t() + def delete(conn, config) do + case fetch_auth_token(conn, config) do + nil -> + :ok + + token -> + config + |> store_config() + |> CredentialsCache.delete(token) + end + + conn + end + + defp fetch_auth_token(conn, _config) do + with [token | _rest] <- Conn.get_req_header(conn, "authorization"), + "bearer " <> token <- String.downcase(token) do + token + else + _any -> nil + end + end + + defp store_config(config) do + backend = Config.get(config, :cache_store_backend, Pow.Store.Backend.EtsCache) + + [backend: backend] + end +end diff --git a/lib/chat_api_web/router.ex b/lib/chat_api_web/router.ex index 04aabc9e6..53c42d52f 100644 --- a/lib/chat_api_web/router.ex +++ b/lib/chat_api_web/router.ex @@ -20,6 +20,12 @@ defmodule ChatApiWeb.Router do plug(ChatApiWeb.EnsureUserEnabledPlug) end + pipeline :public_api do + plug(ChatApiWeb.IPAddressPlug) + plug(:accepts, ["json"]) + plug(ChatApiWeb.PublicAPIAuthPlug, otp_app: :chat_api) + end + # Swagger scope "/api/swagger" do forward "/", PhoenixSwagger.Plug.SwaggerUI, otp_app: :chat_api, swagger_file: "swagger.json" @@ -103,6 +109,12 @@ defmodule ChatApiWeb.Router do post("/event_subscriptions/verify", EventSubscriptionController, :verify) end + scope "/api/v1", ChatApiWeb do + pipe_through([:public_api]) + + get("/me", SessionController, :me) + end + # Enables LiveDashboard only for development # # If you want to use the LiveDashboard in production, you should put