Protect API behind a bearer token

This commit is contained in:
Claudio Ortolina
2024-12-22 18:32:04 +00:00
parent c18379801f
commit f1af38606a
6 changed files with 43 additions and 5 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ config :music_library,
ecto_repos: [MusicLibrary.Repo], ecto_repos: [MusicLibrary.Repo],
generators: [timestamp_type: :utc_datetime, binary_id: true] generators: [timestamp_type: :utc_datetime, binary_id: true]
config :music_library, MusicLibraryWeb, login_password: "change me" config :music_library, MusicLibraryWeb, login_password: "change me", api_token: "change me"
# Configures the endpoint # Configures the endpoint
config :music_library, MusicLibraryWeb.Endpoint, config :music_library, MusicLibraryWeb.Endpoint,
+7 -1
View File
@@ -69,10 +69,16 @@ if config_env() == :prod do
environment variable LOGIN_PASSWORD is missing. environment variable LOGIN_PASSWORD is missing.
""" """
api_token =
System.get_env("API_TOKEN") ||
raise """
environment variable API_TOKEN is missing.
"""
host = System.get_env("PHX_HOST") || "example.com" host = System.get_env("PHX_HOST") || "example.com"
port = String.to_integer(System.get_env("PORT") || "4000") port = String.to_integer(System.get_env("PORT") || "4000")
config :music_library, MusicLibraryWeb, login_password: login_password config :music_library, MusicLibraryWeb, login_password: login_password, api_token: api_token
config :music_library, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY") config :music_library, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY")
+17
View File
@@ -7,11 +7,28 @@ defmodule MusicLibraryWeb.Auth do
Plug.Crypto.secure_compare(login_password(), password) Plug.Crypto.secure_compare(login_password(), password)
end end
def require_api_token(conn, _opts) do
with ["Bearer " <> token] <- get_req_header(conn, "authorization"),
true <- Plug.Crypto.secure_compare(api_token(), token) do
conn
else
_ ->
conn
|> send_resp(:unauthorized, "Unauthorized API access")
|> halt()
end
end
defp login_password do defp login_password do
Application.get_env(:music_library, MusicLibraryWeb) Application.get_env(:music_library, MusicLibraryWeb)
|> Keyword.fetch!(:login_password) |> Keyword.fetch!(:login_password)
end end
defp api_token do
Application.get_env(:music_library, MusicLibraryWeb)
|> Keyword.fetch!(:api_token)
end
def require_logged_in(conn, _opts) do def require_logged_in(conn, _opts) do
if get_session(conn, :logged_in) do if get_session(conn, :logged_in) do
conn conn
+2 -1
View File
@@ -1,7 +1,7 @@
defmodule MusicLibraryWeb.Router do defmodule MusicLibraryWeb.Router do
use MusicLibraryWeb, :router use MusicLibraryWeb, :router
import MusicLibraryWeb.Auth, only: [require_logged_in: 2] import MusicLibraryWeb.Auth, only: [require_logged_in: 2, require_api_token: 2]
pipeline :browser do pipeline :browser do
plug :accepts, ["html"] plug :accepts, ["html"]
@@ -14,6 +14,7 @@ defmodule MusicLibraryWeb.Router do
pipeline :api do pipeline :api do
plug :accepts, ["json"] plug :accepts, ["json"]
plug :require_api_token
end end
pipeline :logged_in do pipeline :logged_in do
+1 -1
View File
@@ -335,7 +335,7 @@ msgstr ""
msgid "Wishlist" msgid "Wishlist"
msgstr "" msgstr ""
#: lib/music_library_web/auth.ex:20 #: lib/music_library_web/auth.ex:37
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "You must be logged in to access this page" msgid "You must be logged in to access this page"
msgstr "" msgstr ""
@@ -7,12 +7,26 @@ defmodule MusicLibraryWeb.CollectionControllerTest do
%{record: record_fixture_with_artist("Steven Wilson")} %{record: record_fixture_with_artist("Steven Wilson")}
end end
defp api_token do
Application.get_env(:music_library, MusicLibraryWeb)
|> Keyword.fetch!(:api_token)
end
describe "GET /api/collection/latest" do describe "GET /api/collection/latest" do
setup [:create_record] setup [:create_record]
test "it returns the latest record", %{conn: conn, record: record} do test "it requires authentication", %{conn: conn} do
conn = get(conn, ~p"/api/collection/latest") conn = get(conn, ~p"/api/collection/latest")
assert conn.status == 401
end
test "it returns the latest record", %{conn: conn, record: record} do
conn =
conn
|> put_req_header("authorization", "Bearer #{api_token()}")
|> get(~p"/api/collection/latest")
assert json_response(conn, 200) == %{ assert json_response(conn, 200) == %{
"artists" => ["Steven Wilson"], "artists" => ["Steven Wilson"],
"title" => record.title, "title" => record.title,