diff --git a/config/config.exs b/config/config.exs
index 1ba90df37..acffd98b6 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -5,7 +5,7 @@
# is restricted to this project.
# General application configuration
-use Mix.Config
+import Config
config :arrow,
ecto_repos: [Arrow.Repo],
@@ -20,7 +20,10 @@ config :arrow,
# map cognito groups to roles
"arrow-admin" => "admin"
},
+ ueberauth_provider: :cognito,
+ api_login_module: ArrowWeb.TryApiTokenAuth.Cognito,
required_roles: %{
+ view_disruption: ["read-only", "admin"],
create_disruption: ["admin"],
update_disruption: ["admin"],
delete_disruption: ["admin"],
@@ -58,7 +61,10 @@ config :arrow, ArrowWeb.AuthManager, issuer: "arrow"
config :ueberauth, Ueberauth,
providers: [
- cognito: {Ueberauth.Strategy.Cognito, []}
+ cognito: {Ueberauth.Strategy.Cognito, []},
+ keycloak:
+ {Ueberauth.Strategy.Oidcc,
+ issuer: :keycloak_issuer, userinfo: true, uid_field: "email", scopes: ~w"openid email"}
]
config :ueberauth, Ueberauth.Strategy.Cognito,
diff --git a/config/dev.exs b/config/dev.exs
index 5b883fcd0..56de819a0 100644
--- a/config/dev.exs
+++ b/config/dev.exs
@@ -1,4 +1,4 @@
-use Mix.Config
+import Config
# Configure your database
config :arrow, Arrow.Repo,
diff --git a/config/prod.exs b/config/prod.exs
index 6c3ce3e0d..618230061 100644
--- a/config/prod.exs
+++ b/config/prod.exs
@@ -1,4 +1,4 @@
-use Mix.Config
+import Config
# For production, don't forget to configure the url host
# to something meaningful, Phoenix uses this information
diff --git a/config/runtime.exs b/config/runtime.exs
index f03a4d3ec..052196dce 100644
--- a/config/runtime.exs
+++ b/config/runtime.exs
@@ -1,5 +1,50 @@
import Config
+is_test? = config_env() == :test
+
+keycloak_issuer =
+ case System.get_env() do
+ %{"KEYCLOAK_ISSUER" => issuer} when issuer != "" ->
+ issuer
+
+ %{"KEYCLOAK_DISCOVERY_URI" => well_known} when well_known != "" ->
+ String.replace_trailing(well_known, "/.well-known/openid-configuration", "")
+
+ _ ->
+ nil
+ end
+
+if is_binary(keycloak_issuer) and not is_test? do
+ config :arrow,
+ ueberauth_provider: :keycloak,
+ api_login_module: ArrowWeb.TryApiTokenAuth.Keycloak,
+ keycloak_client_uuid: System.fetch_env!("KEYCLOAK_CLIENT_UUID"),
+ keycloak_api_base: System.fetch_env!("KEYCLOAK_API_BASE")
+
+ keycloak_opts = [
+ client_id: System.fetch_env!("KEYCLOAK_CLIENT_ID"),
+ client_secret: System.fetch_env!("KEYCLOAK_CLIENT_SECRET")
+ ]
+
+ keycloak_opts =
+ if keycloak_idp = System.get_env("KEYCLOAK_IDP_HINT") do
+ Keyword.put(keycloak_opts, :authorization_params, %{kc_idp_hint: keycloak_idp})
+ else
+ keycloak_opts
+ end
+
+ config :ueberauth_oidcc,
+ issuers: [
+ %{
+ name: :keycloak_issuer,
+ issuer: keycloak_issuer
+ }
+ ],
+ strategies: [
+ keycloak: keycloak_opts
+ ]
+end
+
if config_env() == :prod do
sentry_env = System.get_env("SENTRY_ENV")
diff --git a/config/test.exs b/config/test.exs
index cdf479ef7..791957c21 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -1,4 +1,4 @@
-use Mix.Config
+import Config
# Configure your database
config :arrow, Arrow.Repo,
@@ -25,7 +25,7 @@ config :arrow,
http_client: Arrow.HTTPMock
# Print only warnings and errors during test
-config :logger, level: :warn
+config :logger, level: :warning
config :arrow, env: :test
diff --git a/lib/arrow/adjustment_fetcher.ex b/lib/arrow/adjustment_fetcher.ex
index 313915271..f740d627b 100644
--- a/lib/arrow/adjustment_fetcher.ex
+++ b/lib/arrow/adjustment_fetcher.ex
@@ -31,7 +31,7 @@ defmodule Arrow.AdjustmentFetcher do
_ =
case fetch() do
:ok -> Logger.debug("adjustment_fetch_complete")
- {:error, reason} -> Logger.warn("adjustment_fetch_failed: #{inspect(reason)}")
+ {:error, reason} -> Logger.warning("adjustment_fetch_failed: #{inspect(reason)}")
end
Process.send_after(self(), :fetch, interval)
diff --git a/lib/arrow/permissions.ex b/lib/arrow/permissions.ex
index ef29e69de..b1ff6ec3b 100644
--- a/lib/arrow/permissions.ex
+++ b/lib/arrow/permissions.ex
@@ -7,7 +7,8 @@ defmodule Arrow.Permissions do
@required_roles Application.compile_env!(:arrow, :required_roles)
@type action() ::
- :create_disruption
+ :view_disruption
+ | :create_disruption
| :update_disruption
| :delete_disruption
| :use_api
diff --git a/lib/arrow_web/auth_manager/error_handler.ex b/lib/arrow_web/auth_manager/error_handler.ex
index 1209542d8..4ec6b5bcf 100644
--- a/lib/arrow_web/auth_manager/error_handler.ex
+++ b/lib/arrow_web/auth_manager/error_handler.ex
@@ -10,6 +10,7 @@ defmodule ArrowWeb.AuthManager.ErrorHandler do
@impl Guardian.Plug.ErrorHandler
def auth_error(conn, {_type, _reason}, _opts) do
- Controller.redirect(conn, to: Routes.auth_path(conn, :request, "cognito"))
+ provider = Application.get_env(:arrow, :ueberauth_provider)
+ Controller.redirect(conn, to: Routes.auth_path(conn, :request, "#{provider}"))
end
end
diff --git a/lib/arrow_web/controllers/auth_controller.ex b/lib/arrow_web/controllers/auth_controller.ex
index 7d95254fb..0a041c424 100644
--- a/lib/arrow_web/controllers/auth_controller.ex
+++ b/lib/arrow_web/controllers/auth_controller.ex
@@ -1,18 +1,28 @@
defmodule ArrowWeb.AuthController do
use ArrowWeb, :controller
- plug Ueberauth
+ plug(Ueberauth)
+
+ @spec logout(Plug.Conn.t(), map()) :: Plug.Conn.t()
+ def logout(conn, _params) do
+ logout_url = Map.get(Guardian.Plug.current_claims(conn), "logout_url")
+ conn = clear_session(conn)
+
+ if logout_url do
+ redirect(conn, external: logout_url)
+ else
+ redirect(conn, to: "/")
+ end
+ end
@cognito_groups Application.compile_env!(:arrow, :cognito_groups)
@spec callback(Plug.Conn.t(), map()) :: Plug.Conn.t()
- def callback(%{assigns: %{ueberauth_auth: auth}} = conn, _params) do
+ def callback(%{assigns: %{ueberauth_auth: %{provider: :cognito} = auth}} = conn, _params) do
username = auth.uid
expiration = auth.credentials.expires_at
- credentials = conn.assigns.ueberauth_auth.credentials
-
current_time = System.system_time(:second)
- groups = credentials.other[:groups] || []
+ groups = Map.get(auth.credentials.other, :groups, [])
roles =
Enum.flat_map(groups, fn group ->
@@ -26,10 +36,43 @@ defmodule ArrowWeb.AuthController do
|> Guardian.Plug.sign_in(
ArrowWeb.AuthManager,
username,
- %{roles: roles},
+ %{
+ # all cognito users have read-only access
+ roles: roles ++ ["read-only"]
+ },
+ ttl: {expiration - current_time, :seconds}
+ )
+ |> redirect(to: Routes.disruption_path(conn, :index))
+ end
+
+ def callback(%{assigns: %{ueberauth_auth: %{provider: :keycloak} = auth}} = conn, _params) do
+ username = auth.uid
+ expiration = auth.credentials.expires_at
+ current_time = System.system_time(:second)
+
+ roles = auth.extra.raw_info.userinfo["roles"] || []
+
+ logout_url =
+ case UeberauthOidcc.initiate_logout_url(auth, %{
+ post_logout_redirect_uri: "https://www.mbta.com/"
+ }) do
+ {:ok, url} ->
+ url
+
+ _ ->
+ nil
+ end
+
+ conn
+ |> Guardian.Plug.sign_in(
+ ArrowWeb.AuthManager,
+ username,
+ %{
+ roles: roles,
+ logout_url: logout_url
+ },
ttl: {expiration - current_time, :seconds}
)
- |> put_session(:arrow_username, username)
|> redirect(to: Routes.disruption_path(conn, :index))
end
diff --git a/lib/arrow_web/controllers/disruption_controller.ex b/lib/arrow_web/controllers/disruption_controller.ex
index fd8c7ff05..811680add 100644
--- a/lib/arrow_web/controllers/disruption_controller.ex
+++ b/lib/arrow_web/controllers/disruption_controller.ex
@@ -8,6 +8,7 @@ defmodule ArrowWeb.DisruptionController do
alias Ecto.Changeset
alias Plug.Conn
+ plug(Authorize, :view_disruption when action in [:index, :show])
plug(Authorize, :create_disruption when action in [:new, :create])
plug(Authorize, :update_disruption when action in [:edit, :update, :update_row_status])
plug(Authorize, :delete_disruption when action in [:delete])
diff --git a/lib/arrow_web/controllers/my_token_controller.ex b/lib/arrow_web/controllers/my_token_controller.ex
index edbba5960..3e059ab7b 100644
--- a/lib/arrow_web/controllers/my_token_controller.ex
+++ b/lib/arrow_web/controllers/my_token_controller.ex
@@ -6,7 +6,7 @@ defmodule ArrowWeb.MyTokenController do
@spec show(Plug.Conn.t(), Plug.Conn.params()) :: Plug.Conn.t()
def show(conn, _params) do
- token = conn |> get_session(:arrow_username) |> AuthToken.get_or_create_token_for_user()
+ token = conn |> Guardian.Plug.current_resource() |> AuthToken.get_or_create_token_for_user()
render(conn, "index.html", token: token)
end
diff --git a/lib/arrow_web/controllers/unauthorized_controller.ex b/lib/arrow_web/controllers/unauthorized_controller.ex
index f8e46487a..846a9343f 100644
--- a/lib/arrow_web/controllers/unauthorized_controller.ex
+++ b/lib/arrow_web/controllers/unauthorized_controller.ex
@@ -3,7 +3,10 @@ defmodule ArrowWeb.UnauthorizedController do
@spec index(Plug.Conn.t(), map()) :: Plug.Conn.t()
def index(conn, _params) do
+ roles = Guardian.Plug.current_claims(conn)["roles"] || []
+
conn
+ |> assign(:roles, roles)
|> put_status(403)
|> render("index.html")
end
diff --git a/lib/arrow_web/router.ex b/lib/arrow_web/router.ex
index 195ce9f3e..34e39a506 100644
--- a/lib/arrow_web/router.ex
+++ b/lib/arrow_web/router.ex
@@ -11,7 +11,6 @@ defmodule ArrowWeb.Router do
pipeline :json_api do
plug(:accepts, ["json-api"])
- plug(:fetch_session)
plug(JaSerializer.ContentTypeNegotiation)
end
@@ -42,6 +41,7 @@ defmodule ArrowWeb.Router do
scope "/", ArrowWeb do
pipe_through([:redirect_prod_http, :browser, :authenticate])
+ get("/logout", AuthController, :logout)
get("/unauthorized", UnauthorizedController, :index)
get("/feed", FeedController, :index)
get("/mytoken", MyTokenController, :show)
diff --git a/lib/arrow_web/templates/layout/app.html.heex b/lib/arrow_web/templates/layout/app.html.heex
index 71a619263..e189b5c33 100644
--- a/lib/arrow_web/templates/layout/app.html.heex
+++ b/lib/arrow_web/templates/layout/app.html.heex
@@ -27,6 +27,11 @@
diff --git a/lib/arrow_web/templates/unauthorized/index.html.heex b/lib/arrow_web/templates/unauthorized/index.html.heex
index 5a25e3abc..ac0926f14 100644
--- a/lib/arrow_web/templates/unauthorized/index.html.heex
+++ b/lib/arrow_web/templates/unauthorized/index.html.heex
@@ -1,8 +1,10 @@
Whoops! You are not authorized to access this page.
+ <%= if "read-only" in @roles or "admin" in @roles do %>
Were you looking for a <%= link("list of disruptions", to: Routes.disruption_path(@conn, :index)) %>
or a <%= link("calendar schedule", to: Routes.disruption_path(@conn, :index, view: "calendar")) %>?
+ <% end %>
To request access to this page, please contact <%= link("trc@mbta.com", to: "mailto:trc@mbta.com") %>.
diff --git a/lib/arrow_web/try_api_token_auth.ex b/lib/arrow_web/try_api_token_auth.ex
index 765f1ad46..29a872bcc 100644
--- a/lib/arrow_web/try_api_token_auth.ex
+++ b/lib/arrow_web/try_api_token_auth.ex
@@ -6,72 +6,26 @@ defmodule ArrowWeb.TryApiTokenAuth do
import Plug.Conn
require Logger
- @aws_cognito_target "AWSCognitoIdentityProviderService"
- @cognito_groups Application.compile_env!(:arrow, :cognito_groups)
-
def init(options), do: options
def call(conn, _opts) do
api_key_values = get_req_header(conn, "x-api-key")
- if api_key_values == [] do
+ with [token | _] <- api_key_values,
+ token = String.downcase(token),
+ auth_token = %Arrow.AuthToken{} <-
+ Arrow.Repo.get_by(Arrow.AuthToken, token: token),
+ api_login_module = Application.get_env(:arrow, :api_login_module),
+ conn = api_login_module.sign_in(conn, auth_token),
+ true <- Guardian.Plug.authenticated?(conn) do
conn
else
- [token | _] = api_key_values
- token = String.downcase(token)
-
- auth_token = Arrow.Repo.get_by(Arrow.AuthToken, token: token)
+ [] ->
+ # no API key present, pass on through
+ conn
- if is_nil(auth_token) do
+ _ ->
conn |> send_resp(401, "unauthenticated") |> halt()
- else
- user_pool_id =
- :ueberauth
- |> Application.get_env(Ueberauth.Strategy.Cognito)
- |> Keyword.get(:user_pool_id)
- |> config_value
-
- data = %{
- "Username" => auth_token.username,
- "UserPoolId" => user_pool_id
- }
-
- headers = [
- {"x-amz-target", "#{@aws_cognito_target}.AdminListGroupsForUser"},
- {"content-type", "application/x-amz-json-1.1"}
- ]
-
- operation = ExAws.Operation.JSON.new(:"cognito-idp", data: data, headers: headers)
-
- {module, function} = Application.get_env(:arrow, :ex_aws_requester)
-
- roles =
- case apply(module, function, [operation]) do
- {:ok, %{"Groups" => groups}} ->
- Enum.flat_map(groups, fn %{"GroupName" => group} ->
- case @cognito_groups[group] do
- role when is_binary(role) -> [role]
- _ -> []
- end
- end)
-
- response ->
- :ok = Logger.warn("unexpected_aws_api_response: #{inspect(response)}")
- []
- end
-
- conn
- |> Guardian.Plug.sign_in(
- ArrowWeb.AuthManager,
- auth_token.username,
- %{roles: roles}
- )
- |> put_session(:arrow_username, auth_token.username)
- end
end
end
-
- @spec config_value(binary() | {module(), atom(), [any()]}) :: any()
- defp config_value(value) when is_binary(value), do: value
- defp config_value({m, f, a}), do: apply(m, f, a)
end
diff --git a/lib/arrow_web/try_api_token_auth/cognito.ex b/lib/arrow_web/try_api_token_auth/cognito.ex
new file mode 100644
index 000000000..73188dfd0
--- /dev/null
+++ b/lib/arrow_web/try_api_token_auth/cognito.ex
@@ -0,0 +1,57 @@
+defmodule ArrowWeb.TryApiTokenAuth.Cognito do
+ @moduledoc """
+ Signs in an API client via Cognito.
+ """
+
+ require Logger
+
+ @aws_cognito_target "AWSCognitoIdentityProviderService"
+ @cognito_groups Application.compile_env!(:arrow, :cognito_groups)
+
+ def sign_in(conn, auth_token) do
+ user_pool_id =
+ :ueberauth
+ |> Application.get_env(Ueberauth.Strategy.Cognito)
+ |> Keyword.get(:user_pool_id)
+ |> config_value
+
+ data = %{
+ "Username" => auth_token.username,
+ "UserPoolId" => user_pool_id
+ }
+
+ headers = [
+ {"x-amz-target", "#{@aws_cognito_target}.AdminListGroupsForUser"},
+ {"content-type", "application/x-amz-json-1.1"}
+ ]
+
+ operation = ExAws.Operation.JSON.new(:"cognito-idp", data: data, headers: headers)
+
+ {module, function} = Application.get_env(:arrow, :ex_aws_requester)
+
+ roles =
+ case apply(module, function, [operation]) do
+ {:ok, %{"Groups" => groups}} ->
+ for %{"GroupName" => group} <- groups,
+ {:ok, role} <- [Map.fetch(@cognito_groups, group)] do
+ role
+ end
+
+ response ->
+ :ok = Logger.warn("unexpected_aws_api_response: #{inspect(response)}")
+ []
+ end
+
+ conn
+ |> Guardian.Plug.sign_in(
+ ArrowWeb.AuthManager,
+ auth_token.username,
+ %{roles: roles},
+ ttl: {0, :second}
+ )
+ end
+
+ @spec config_value(binary() | {module(), atom(), [any()]}) :: any()
+ defp config_value(value) when is_binary(value), do: value
+ defp config_value({m, f, a}), do: apply(m, f, a)
+end
diff --git a/lib/arrow_web/try_api_token_auth/keycloak.ex b/lib/arrow_web/try_api_token_auth/keycloak.ex
new file mode 100644
index 000000000..2189b0de2
--- /dev/null
+++ b/lib/arrow_web/try_api_token_auth/keycloak.ex
@@ -0,0 +1,96 @@
+defmodule ArrowWeb.TryApiTokenAuth.Keycloak do
+ @moduledoc """
+ Signs in an API client via Keycloak.
+ """
+
+ require Logger
+
+ def sign_in(conn, auth_token) do
+ with {:ok, user_id} <- lookup_user_id(auth_token.username),
+ {:ok, roles} <- lookup_user_roles(user_id) do
+ conn
+ |> Guardian.Plug.sign_in(
+ ArrowWeb.AuthManager,
+ auth_token.username,
+ %{roles: roles},
+ ttl: {0, :second}
+ )
+ else
+ other ->
+ Logger.warn(
+ "unexpected response when logging #{auth_token.username} in via Keycloak API: #{inspect(other)}"
+ )
+
+ conn
+ end
+ end
+
+ defp lookup_user_id(email) do
+ case keycloak_api("/users", %{
+ max: 1,
+ email: String.downcase(email),
+ exact: true,
+ briefRepresentation: true
+ }) do
+ {:ok, [%{"id" => user_id}]} ->
+ {:ok, user_id}
+
+ {:ok, []} ->
+ {:error, :no_users}
+
+ {:ok, [_, _ | _]} ->
+ {:error, :multiple_users}
+
+ e ->
+ e
+ end
+ end
+
+ defp lookup_user_roles(user_id) do
+ client_uuid = Application.get_env(:arrow, :keycloak_client_uuid)
+ url = "/users/#{user_id}/role-mappings/clients/#{client_uuid}/composite"
+
+ case keycloak_api(url) do
+ {:ok, response} ->
+ roles = for r <- response, do: r["name"]
+ {:ok, roles}
+
+ e ->
+ e
+ end
+ end
+
+ defp keycloak_api(url, params \\ %{}) do
+ base_url = Application.get_env(:arrow, :keycloak_api_base)
+ {_, base_opts} = Application.get_env(:ueberauth, Ueberauth)[:providers][:keycloak]
+ runtime_opts = Application.get_env(:ueberauth_oidcc, :strategies)[:keycloak]
+
+ opts =
+ base_opts
+ |> Keyword.merge(runtime_opts)
+ |> Map.new()
+
+ with {:ok, token} <-
+ Oidcc.client_credentials_token(opts.issuer, opts.client_id, opts.client_secret, %{}),
+ headers = [{"authorization", "Bearer #{token.access.token}"}],
+ {:ok, %{status_code: 200} = response} <-
+ HTTPoison.get("#{base_url}#{url}", headers,
+ params: params,
+ hackney: [
+ ssl_options: [
+ verify: :verify_peer,
+ cacerts: :public_key.cacerts_get(),
+ versions: [:"tlsv1.2"],
+ customize_hostname_check: [
+ match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
+ ]
+ ]
+ ]
+ ) do
+ Jason.decode(response.body)
+ else
+ {:ok, %{status_code: _} = response} -> {:error, response}
+ e -> e
+ end
+ end
+end
diff --git a/mix.exs b/mix.exs
index 802f7e89b..bcc1211d2 100644
--- a/mix.exs
+++ b/mix.exs
@@ -63,21 +63,21 @@ defmodule Arrow.MixProject do
{:phoenix_ecto, "~> 4.0"},
# override for react_phoenix, pending
# https://github.com/geolessel/react-phoenix/pull/58
- {:phoenix_html, "~> 3.0", override: true},
+ {:phoenix_html, "~> 3.2.0", override: true},
{:phoenix_live_reload, "~> 1.2", only: :dev},
- {:phoenix_live_view, "~> 0.20.1"},
+ {:phoenix_live_view, "~> 0.16.4"},
{:phoenix_pubsub, "~> 2.0"},
{:phoenix, "~> 1.6.0"},
{:plug_cowboy, "~> 2.1"},
+ {:telemetry, "~> 1.2", override: true},
{:postgrex, ">= 0.0.0"},
# If react_phoenix changes, check assets/src/ReactPhoenix.js, too
{:react_phoenix, "1.2.0"},
- {:telemetry_metrics, "~> 0.6"},
- {:telemetry_poller, "~> 0.5"},
{:tzdata, "~> 1.1"},
{:ueberauth_cognito, "0.4.0"},
+ {:ueberauth_oidcc, "~> 0.1.0-rc"},
{:ueberauth, "~> 0.9"},
- {:wallaby, "~> 0.28.1", runtime: false, only: :test},
+ {:wallaby, "~> 0.30.6", runtime: false, only: :test},
{:sentry, "~> 8.0"}
]
end
diff --git a/mix.lock b/mix.lock
index aa2b641ba..f96e6ea5c 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,10 +1,10 @@
%{
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
- "castore": {:hex, :castore, "0.1.17", "ba672681de4e51ed8ec1f74ed624d104c0db72742ea1a5e74edbc770c815182f", [:mix], [], "hexpm", "d9844227ed52d26e7519224525cb6868650c272d4a3d327ce3ca5570c12163f9"},
- "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
+ "castore": {:hex, :castore, "1.0.4", "ff4d0fb2e6411c0479b1d965a814ea6d00e51eb2f58697446e9c41a97d940b28", [:mix], [], "hexpm", "9418c1b8144e11656f0be99943db4caf04612e3eaecefb5dae9a2a87565584f8"},
+ "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"},
- "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"},
+ "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"},
"credo": {:hex, :credo, "1.6.4", "ddd474afb6e8c240313f3a7b0d025cc3213f0d171879429bf8535d7021d9ad78", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "c28f910b61e1ff829bffa056ef7293a8db50e87f2c57a9b5c3f57eee124536b7"},
"db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"},
@@ -20,30 +20,37 @@
"ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
"expo": {:hex, :expo, "0.1.0", "d4e932bdad052c374118e312e35280f1919ac13881cb3ac07a209a54d0c81dd8", [:mix], [], "hexpm", "c22c536021c56de058aaeedeabb4744eb5d48137bacf8c29f04d25b6c6bbbf45"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
+ "finch": {:hex, :finch, "0.16.0", "40733f02c89f94a112518071c0a91fe86069560f5dbdb39f9150042f44dcfb1a", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f660174c4d519e5fec629016054d60edd822cdfe2b7270836739ac2f97735ec5"},
"gettext": {:hex, :gettext, "0.21.0", "15bbceb20b317b706a8041061a08e858b5a189654128618b53746bf36c84352b", [:mix], [{:expo, "~> 0.1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "04a66db4103b6d1d18f92240bb2c73167b517229316b7bef84e4eebbfb2f14f6"},
"guardian": {:hex, :guardian, "2.3.1", "2b2d78dc399a7df182d739ddc0e566d88723299bfac20be36255e2d052fd215d", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bbe241f9ca1b09fad916ad42d6049d2600bbc688aba5b3c4a6c82592a54274c3"},
"guardian_phoenix": {:hex, :guardian_phoenix, "2.0.1", "89a817265af09a6ddf7cb1e77f17ffca90cea2db10ff888375ef34502b2731b1", [:mix], [{:guardian, "~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "21f439246715192b231f228680465d1ed5fbdf01555a4a3b17165532f5f9a08c"},
- "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
- "httpoison": {:hex, :httpoison, "1.8.1", "df030d96de89dad2e9983f92b0c506a642d4b1f4a819c96ff77d12796189c63e", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "35156a6d678d6d516b9229e208942c405cf21232edd632327ecfaf4fd03e79e0"},
+ "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
+ "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
+ "httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"inflex": {:hex, :inflex, "1.10.0", "8366a7696e70e1813aca102e61274addf85d99f4a072b2f9c7984054ea1b9d29", [:mix], [], "hexpm", "7b5ccb9b720c26516f5962dc4565fc26f083ca107b0f6c167048506a125d2df3"},
"ja_serializer": {:git, "https://github.com/mbta/ja_serializer.git", "efb1d4489809e31e4b54b4af9e85f0b3ceeb650b", [branch: "master"]},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
- "jose": {:hex, :jose, "1.11.5", "3bc2d75ffa5e2c941ca93e5696b54978323191988eb8d225c2e663ddfefd515e", [:mix, :rebar3], [], "hexpm", "dcd3b215bafe02ea7c5b23dafd3eb8062a5cd8f2d904fd9caa323d37034ab384"},
+ "jose": {:hex, :jose, "1.11.6", "613fda82552128aa6fb804682e3a616f4bc15565a048dabd05b1ebd5827ed965", [:mix, :rebar3], [], "hexpm", "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738"},
"lcov_ex": {:hex, :lcov_ex, "0.2.2", "2dd1ef86ed510ac9c3fe3f11b82cbfd09c92b91b7d6565298932c560f72bd584", [:mix], [], "hexpm", "5e412eddb6a384d2e66cff94eec048ba5c9ed30d6cd9fb5a331456b210de0801"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
- "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
+ "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
+ "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"},
"mox": {:hex, :mox, "1.0.1", "b651bf0113265cda0ba3a827fcb691f848b683c373b77e7d7439910a8d754d6e", [:mix], [], "hexpm", "35bc0dea5499d18db4ef7fe4360067a59b06c74376eb6ab3bd67e6295b133469"},
- "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
- "phoenix": {:hex, :phoenix, "1.6.16", "e5bdd18c7a06da5852a25c7befb72246de4ddc289182285f8685a40b7b5f5451", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e15989ff34f670a96b95ef6d1d25bad0d9c50df5df40b671d8f4a669e050ac39"},
- "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
- "phoenix_html": {:hex, :phoenix_html, "3.3.3", "380b8fb45912b5638d2f1d925a3771b4516b9a78587249cabe394e0a5d579dc9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c"},
+ "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"},
+ "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"},
+ "oauth2": {:hex, :oauth2, "2.0.1", "70729503e05378697b958919bb2d65b002ba6b28c8112328063648a9348aaa3f", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "c64e20d4d105bcdbcbe03170fb530d0eddc3a3e6b135a87528a22c8aecf74c52"},
+ "oidcc": {:hex, :oidcc, "3.1.0-beta.2", "3a3f4d9cf9026392579684adfdd903e76b2cc7634a0c443fa477b940f31c3b83", [:mix, :rebar3], [{:jose, "~> 1.11", [hex: :jose, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_registry, "~> 0.3.1", [hex: :telemetry_registry, repo: "hexpm", optional: false]}], "hexpm", "65f12afb4d0afa9f121ba419dcc88251163dd0553309b2e13b8edf217acda5c4"},
+ "openid_connect": {:git, "https://github.com/firezone/openid_connect.git", "13320ed8b0d347330d07e1375a9661f3089b9c03", [ref: "13320ed8b0d347330d07e1375a9661f3089b9c03"]},
+ "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
+ "phoenix": {:hex, :phoenix, "1.6.9", "648e660040cdc758c5401972e0f592ce622d4ce9cd16d2d9c33dda32d0c9f7fa", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "be2fe497597d6bf297dcbf9f4416b4929dbfbdcc25edc1acf6d4dcaecbe898a6"},
+ "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.3", "86e9878f833829c3f66da03d75254c155d91d72a201eb56ae83482328dc7ca93", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d36c401206f3011fefd63d04e8ef626ec8791975d9d107f9a0817d426f61ac07"},
+ "phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"},
- "phoenix_live_view": {:hex, :phoenix_live_view, "0.20.1", "92a37acf07afca67ac98bd326532ba8f44ad7d4bdf3e4361b03f7f02594e5ae9", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "be494fd1215052729298b0e97d5c2ce8e719c00854b82cd8cf15c1cd7fcf6294"},
- "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
- "phoenix_template": {:hex, :phoenix_template, "1.0.3", "32de561eefcefa951aead30a1f94f1b5f0379bc9e340bb5c667f65f1edfa4326", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "16f4b6588a4152f3cc057b9d0c0ba7e82ee23afa65543da535313ad8d25d8e2c"},
- "phoenix_view": {:hex, :phoenix_view, "2.0.3", "4d32c4817fce933693741deeb99ef1392619f942633dde834a5163124813aad3", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "cd34049af41be2c627df99cd4eaa71fc52a328c0c3d8e7d4aa28f880c30e7f64"},
+ "phoenix_live_view": {:hex, :phoenix_live_view, "0.16.4", "5692edd0bac247a9a816eee7394e32e7a764959c7d0cf9190662fc8b0cd24c97", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.9 or ~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "754ba49aa2e8601afd4f151492c93eb72df69b0b9856bab17711b8397e43bba0"},
+ "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
+ "phoenix_view": {:hex, :phoenix_view, "1.1.2", "1b82764a065fb41051637872c7bd07ed2fdb6f5c3bd89684d4dca6e10115c95a", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "7ae90ad27b09091266f6adbb61e1d2516a7c3d7062c6789d46a7554ec40f3a56"},
"plug": {:hex, :plug, "1.15.2", "94cf1fa375526f30ff8770837cb804798e0045fd97185f0bb9e5fcd858c792a3", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02731fa0c2dcb03d8d21a1d941bdbbe99c2946c0db098eee31008e04c6283615"},
"plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
"plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
@@ -52,15 +59,19 @@
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
"react_phoenix": {:hex, :react_phoenix, "1.2.0", "42f4f6a7d1006b50f89f2209fc1402e8d6b5cca34ca0fbfcdc0c43619db1ad4a", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.11", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "aab3a7ba35e68776da5d52817b1044b130e13740b72db9faa8a919dfce68be66"},
"sentry": {:hex, :sentry, "8.0.6", "c8de1bf0523bc120ec37d596c55260901029ecb0994e7075b0973328779ceef7", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 2.3", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "051a2d0472162f3137787c7c9d6e6e4ef239de9329c8c45b1f1bf1e9379e1883"},
- "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
- "telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"},
+ "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
+ "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
"telemetry_poller": {:hex, :telemetry_poller, "0.5.1", "21071cc2e536810bac5628b935521ff3e28f0303e770951158c73eaaa01e962a", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4cab72069210bc6e7a080cec9afffad1b33370149ed5d379b81c7c5f0c663fd4"},
- "tesla": {:hex, :tesla, "1.3.3", "26ae98627af5c406584aa6755ab5fc96315d70d69a24dd7f8369cfcb75094a45", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2648f1c276102f9250299e0b7b57f3071c67827349d9173f34c281756a1b124c"},
+ "telemetry_registry": {:hex, :telemetry_registry, "0.3.1", "14a3319a7d9027bdbff7ebcacf1a438f5f5c903057b93aee484cca26f05bdcba", [:mix, :rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6d0ca77b691cf854ed074b459a93b87f4c7f5512f8f7743c635ca83da81f939e"},
+ "tesla": {:hex, :tesla, "1.8.0", "d511a4f5c5e42538d97eef7c40ec4f3e44effdc5068206f42ed859e09e51d1fd", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "10501f360cd926a309501287470372af1a6e1cbed0f43949203a4c13300bc79f"},
"tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"},
- "ueberauth": {:hex, :ueberauth, "0.9.0", "9f2dc8f6158fc09d048da0c1a548a4b2f9326bf01a35acdcaa94f4bc5b936c9a", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "6d6e0c6f7191b8d25153ae3596b3d98b5c06f9bb887d1e2d7b98b74eff3d189b"},
+ "ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"},
"ueberauth_cognito": {:hex, :ueberauth_cognito, "0.4.0", "62daa3f675298c2b03002d2e1b7e5a30cbc513400e5732a264864a26847e71ac", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:jose, "~> 1.0", [hex: :jose, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "62378f4f34c8569cd95cc4e7463c56e9981c8afc83fdc516922065f0e1302a35"},
+ "ueberauth_keycloak_strategy": {:hex, :ueberauth_keycloak_strategy, "0.4.0", "51e975874564ef4a6eb0044b9f0c6a08be4ba6086e62e41d385e7dd52fe9568b", [:mix], [{:oauth2, "~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "c03027937bddcbd9ff499e457f9bb05f79018fa321abf79ebcfed2af0007211b"},
+ "ueberauth_oidc": {:git, "https://github.com/mbta/ueberauth_oidc.git", "6216cb2a93bf075b76bcd97db89579233c44314f", []},
+ "ueberauth_oidcc": {:hex, :ueberauth_oidcc, "0.1.0-rc.0", "6bf1404e35cf919f27e774ca990255725fdc4ea6198021b752d0244e4df74ef2", [:mix], [{:oidcc, "~> 3.1.0-beta", [hex: :oidcc, repo: "hexpm", optional: false]}, {:plug, "~> 1.11", [hex: :plug, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.10.5", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "319cd35580373e146b9f707ebbd6b86785b40e5136f7427e0af64abe5183e111"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
- "wallaby": {:hex, :wallaby, "0.28.1", "0487ac4e76a5ffcc9b0ac3ddc35b931b0c2f4cac87b30b029a0f4e7e5ee20ff3", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.1.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "618538448e21dc8b0a02f6810472eb48a05badf14db4a0441dc562a1eac896e9"},
- "web_driver_client": {:hex, :web_driver_client, "0.1.0", "19466a989c76b7ec803c796cec0fec4611a64f445fd5120ce50c9e3817e09c2c", [:mix], [{:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.3.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "c9c031ca915e8fc75b5e24ac93503244f3cc406dd7f53047087a45aa62d60e9e"},
+ "wallaby": {:hex, :wallaby, "0.30.6", "7dc4c1213f3b52c4152581d126632bc7e06892336d3a0f582853efeeabd45a71", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.2.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "50950c1d968549b54c20e16175c68c7fc0824138e2bb93feb11ef6add8eb23d4"},
+ "web_driver_client": {:hex, :web_driver_client, "0.2.0", "63b76cd9eb3b0716ec5467a0f8bead73d3d9612e63f7560d21357f03ad86e31a", [:mix], [{:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.3", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "83cc6092bc3e74926d1c8455f0ce927d5d1d36707b74d9a65e38c084aab0350f"},
}
diff --git a/test/arrow_web/auth_manager/error_handler_test.exs b/test/arrow_web/auth_manager/error_handler_test.exs
index 31dd3504c..bc63fb93e 100644
--- a/test/arrow_web/auth_manager/error_handler_test.exs
+++ b/test/arrow_web/auth_manager/error_handler_test.exs
@@ -2,13 +2,15 @@ defmodule ArrowWeb.AuthManager.ErrorHandlerTest do
use ArrowWeb.ConnCase
describe "auth_error/3" do
- test "redirects to Cognito login if there's no refresh key", %{conn: conn} do
+ test "redirects to login if there's no refresh key", %{conn: conn} do
+ provider = Application.get_env(:arrow, :ueberauth_provider)
+
conn =
conn
|> init_test_session(%{})
|> ArrowWeb.AuthManager.ErrorHandler.auth_error({:some_type, :reason}, [])
- assert html_response(conn, 302) =~ "\"/auth/cognito\""
+ assert html_response(conn, 302) =~ "\"/auth/#{provider}\""
end
end
end
diff --git a/test/arrow_web/controllers/auth_controller_test.exs b/test/arrow_web/controllers/auth_controller_test.exs
index 735a13342..b5ad22007 100644
--- a/test/arrow_web/controllers/auth_controller_test.exs
+++ b/test/arrow_web/controllers/auth_controller_test.exs
@@ -2,11 +2,12 @@ defmodule ArrowWeb.Controllers.AuthControllerTest do
use ArrowWeb.ConnCase
describe "callback" do
- test "redirects on success", %{conn: conn} do
+ test "redirects on success (cognito)", %{conn: conn} do
current_time = System.system_time(:second)
auth = %Ueberauth.Auth{
uid: "foo@mbta.com",
+ provider: :cognito,
credentials: %Ueberauth.Auth.Credentials{
expires_at: current_time + 1_000,
other: %{groups: ["arrow-admin"]}
@@ -20,9 +21,69 @@ defmodule ArrowWeb.Controllers.AuthControllerTest do
response = html_response(conn, 302)
+ assert response =~ Routes.disruption_path(conn, :index)
+ assert Enum.sort(Guardian.Plug.current_claims(conn)["roles"]) == ["admin", "read-only"]
+ assert Guardian.Plug.current_resource(conn) == "foo@mbta.com"
+ end
+
+ test "redirects on success (keycloak)", %{conn: conn} do
+ current_time = System.system_time(:second)
+
+ auth = %Ueberauth.Auth{
+ uid: "foo@mbta.com",
+ provider: :keycloak,
+ credentials: %Ueberauth.Auth.Credentials{
+ expires_at: current_time + 1_000,
+ other: %{id_token: "id_token"}
+ },
+ extra: %{
+ raw_info: %{
+ userinfo: %{
+ "roles" => ["admin"]
+ }
+ }
+ }
+ }
+
+ conn =
+ conn
+ |> assign(:ueberauth_auth, auth)
+ |> get(Routes.auth_path(conn, :callback, "keycloak"))
+
+ response = html_response(conn, 302)
+
assert response =~ Routes.disruption_path(conn, :index)
assert Guardian.Plug.current_claims(conn)["roles"] == ["admin"]
- assert get_session(conn, :arrow_username) == "foo@mbta.com"
+ assert Guardian.Plug.current_resource(conn) == "foo@mbta.com"
+ end
+
+ test "handles missing roles (keycloak)", %{conn: conn} do
+ current_time = System.system_time(:second)
+
+ auth = %Ueberauth.Auth{
+ uid: "foo@mbta.com",
+ provider: :keycloak,
+ credentials: %Ueberauth.Auth.Credentials{
+ expires_at: current_time + 1_000,
+ other: %{id_token: "id_token"}
+ },
+ extra: %{
+ raw_info: %{
+ userinfo: %{}
+ }
+ }
+ }
+
+ conn =
+ conn
+ |> assign(:ueberauth_auth, auth)
+ |> get(Routes.auth_path(conn, :callback, "keycloak"))
+
+ response = html_response(conn, 302)
+
+ assert response =~ Routes.disruption_path(conn, :index)
+ assert Guardian.Plug.current_claims(conn)["roles"] == []
+ assert Guardian.Plug.current_resource(conn) == "foo@mbta.com"
end
test "handles generic failure", %{conn: conn} do
diff --git a/test/arrow_web/controllers/unauthorized_controller_test.exs b/test/arrow_web/controllers/unauthorized_controller_test.exs
index e66b9301e..96deebe67 100644
--- a/test/arrow_web/controllers/unauthorized_controller_test.exs
+++ b/test/arrow_web/controllers/unauthorized_controller_test.exs
@@ -5,8 +5,17 @@ defmodule ArrowWeb.UnauthorizedControllerTest do
@tag :authenticated
test "renders response", %{conn: conn} do
conn = get(conn, Routes.unauthorized_path(conn, :index))
+ response = html_response(conn, 403)
+ assert response =~ "not authorized"
+ assert response =~ "calendar schedule"
+ end
- assert html_response(conn, 403) =~ "not authorized"
+ @tag :authenticated_empty
+ test "does not offer a calendar if the user has no roles", %{conn: conn} do
+ conn = get(conn, Routes.unauthorized_path(conn, :index))
+ response = html_response(conn, 403)
+ assert response =~ "not authorized"
+ refute response =~ "calendar schedule"
end
end
end
diff --git a/test/arrow_web/plug/assign_user_test.exs b/test/arrow_web/plug/assign_user_test.exs
index 20dfe37a0..53b49f92a 100644
--- a/test/arrow_web/plug/assign_user_test.exs
+++ b/test/arrow_web/plug/assign_user_test.exs
@@ -18,9 +18,9 @@ defmodule ArrowWeb.Plug.AssignUserTest do
end
@tag :authenticated
- test "loads a non-admin into the connection when user has no roles", %{conn: conn} do
+ test "loads a non-admin into the connection when user is not an admin", %{conn: conn} do
assert AssignUser.call(conn, []).assigns == %{
- current_user: %User{id: "test_user", roles: MapSet.new()}
+ current_user: %User{id: "test_user", roles: MapSet.new(["read-only"])}
}
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 6a77ca0e8..2ce1bef58 100644
--- a/test/arrow_web/try_api_token_auth_test.exs
+++ b/test/arrow_web/try_api_token_auth_test.exs
@@ -53,7 +53,7 @@ defmodule ArrowWeb.TryApiTokenAuthTest do
assert claims["sub"] == "foo@mbta.com"
assert claims["typ"] == "access"
assert claims["roles"] == ["admin"]
- assert get_session(conn, :arrow_username) == "foo@mbta.com"
+ assert Guardian.Plug.current_resource(conn) == "foo@mbta.com"
end
test "handles unexpected response from Cognito API", %{conn: conn} do
diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex
index 08608ad04..dfc625684 100644
--- a/test/support/conn_case.ex
+++ b/test/support/conn_case.ex
@@ -39,11 +39,14 @@ defmodule ArrowWeb.ConnCase do
cond do
tags[:authenticated] ->
- {:ok, conn: build_conn("test_user")}
+ {:ok, conn: build_conn("test_user", ["read-only"])}
tags[:authenticated_admin] ->
{:ok, conn: build_conn("test_user", ["admin"])}
+ tags[:authenticated_empty] ->
+ {:ok, conn: build_conn("test_user", [])}
+
true ->
{:ok,
conn:
@@ -53,10 +56,10 @@ defmodule ArrowWeb.ConnCase do
end
@spec build_conn(String.t(), [String.t()] | []) :: Plug.Conn.t()
- defp build_conn(user, roles \\ []) do
+ defp build_conn(user, roles) do
Phoenix.ConnTest.build_conn()
|> Plug.Conn.put_req_header("x-forwarded-proto", "https")
- |> init_test_session(%{arrow_username: user})
+ |> init_test_session(%{})
|> Guardian.Plug.sign_in(ArrowWeb.AuthManager, user, %{roles: roles})
end
end