diff --git a/lib/arrow_web/try_api_token_auth.ex b/lib/arrow_web/try_api_token_auth.ex index 55af18122..9311b603c 100644 --- a/lib/arrow_web/try_api_token_auth.ex +++ b/lib/arrow_web/try_api_token_auth.ex @@ -40,6 +40,10 @@ defmodule ArrowWeb.TryApiTokenAuth do ArrowWeb.TryApiTokenAuth.Cognito end + defp api_login_module_for_token(%Arrow.AuthToken{username: "gtfs_creator_ci@mbta.com"}) do + ArrowWeb.TryApiTokenAuth.Local + end + defp api_login_module_for_token(_token) do Application.get_env(:arrow, :api_login_module) end diff --git a/lib/arrow_web/try_api_token_auth/local.ex b/lib/arrow_web/try_api_token_auth/local.ex new file mode 100644 index 000000000..833688bdb --- /dev/null +++ b/lib/arrow_web/try_api_token_auth/local.ex @@ -0,0 +1,19 @@ +defmodule ArrowWeb.TryApiTokenAuth.Local do + @moduledoc """ + Signs in an API client from a database token for read-only access. + """ + + alias Plug.Conn + require Logger + + @spec sign_in(Conn.t(), Arrow.AuthToken.t()) :: Conn.t() + def sign_in(%Conn{} = conn, %Arrow.AuthToken{} = auth_token) do + conn + |> Guardian.Plug.sign_in( + ArrowWeb.AuthManager, + auth_token.username, + %{roles: ["read-only"]}, + ttl: {0, :second} + ) + end +end diff --git a/priv/repo/migrations/20240207224211_add_ci_readonly_user_token.exs b/priv/repo/migrations/20240207224211_add_ci_readonly_user_token.exs new file mode 100644 index 000000000..a6d5a4162 --- /dev/null +++ b/priv/repo/migrations/20240207224211_add_ci_readonly_user_token.exs @@ -0,0 +1,7 @@ +defmodule Arrow.Repo.Migrations.AddCiReadonlyUserToken do + use Ecto.Migration + + def change do + Arrow.AuthToken.get_or_create_token_for_user("gtfs_creator_ci@mbta.com") + end +end diff --git a/priv/repo/structure.sql b/priv/repo/structure.sql index 81f913da9..e7faf141a 100644 --- a/priv/repo/structure.sql +++ b/priv/repo/structure.sql @@ -636,3 +636,4 @@ INSERT INTO public."schema_migrations" (version) VALUES (20210922191945); INSERT INTO public."schema_migrations" (version) VALUES (20210924180538); INSERT INTO public."schema_migrations" (version) VALUES (20211209185029); INSERT INTO public."schema_migrations" (version) VALUES (20220105203850); +INSERT INTO public."schema_migrations" (version) VALUES (20240207224211); diff --git a/test/arrow_web/try_api_token_auth/local_test.exs b/test/arrow_web/try_api_token_auth/local_test.exs new file mode 100644 index 000000000..c18aa53c8 --- /dev/null +++ b/test/arrow_web/try_api_token_auth/local_test.exs @@ -0,0 +1,37 @@ +defmodule ArrowWeb.TryApiTokenAuth.LocalTest do + use ArrowWeb.ConnCase + import ExUnit.CaptureLog + + alias ArrowWeb.TryApiTokenAuth.Local + + describe "sign_in/2" do + test "signs in a read-only token user", %{conn: conn} do + local_token_email = "gtfs_creator_ci@mbta.com" + auth_token = auth_token_for(local_token_email) + + conn = Local.sign_in(conn, auth_token) + + assert Guardian.Plug.authenticated?(conn) + claims = Guardian.Plug.current_claims(conn) + + assert claims["sub"] == local_token_email + assert claims["typ"] == "access" + assert claims["roles"] == ["read-only"] + assert Guardian.Plug.current_resource(conn) == local_token_email + end + + test "does not sign in a token user who does not exist", %{conn: conn} do + auth_token = auth_token_for("arrow-missing@mbta.com") + + assert_raise FunctionClauseError, fn -> + Local.sign_in(conn, auth_token) + end + + refute Guardian.Plug.authenticated?(conn) + end + end + + defp auth_token_for(username) do + Arrow.Repo.get_by(Arrow.AuthToken, username: username) + end +end diff --git a/test/arrow_web/try_api_token_auth_test.exs b/test/arrow_web/try_api_token_auth_test.exs index 56442a812..dcfcbb61c 100644 --- a/test/arrow_web/try_api_token_auth_test.exs +++ b/test/arrow_web/try_api_token_auth_test.exs @@ -55,6 +55,20 @@ defmodule ArrowWeb.TryApiTokenAuthTest do assert Guardian.Plug.current_resource(conn) == "foo@mbta.com" end + test "handles API token with local database token from gtfs_creator user", %{conn: conn} do + token = Arrow.AuthToken.get_or_create_token_for_user("gtfs_creator_ci@mbta.com") + + conn = + conn + |> put_req_header("x-api-key", token) + |> ArrowWeb.TryApiTokenAuth.call([]) + + claims = Guardian.Plug.current_claims(conn) + + assert claims["roles"] == ["read-only"] + assert Guardian.Plug.current_resource(conn) == "gtfs_creator_ci@mbta.com" + end + test "handles unexpected response from Cognito API", %{conn: conn} do reassign_env(:ex_aws_requester, {Fake.ExAws, :unexpected_response})