diff --git a/lib/tesla/client.ex b/lib/tesla/client.ex index a120075c..13227541 100644 --- a/lib/tesla/client.ex +++ b/lib/tesla/client.ex @@ -47,4 +47,46 @@ defmodule Tesla.Client do defp unruntime({module, :call, [[]]}) when is_atom(module), do: module defp unruntime({module, :call, [opts]}) when is_atom(module), do: {module, opts} defp unruntime({:fn, fun}) when is_function(fun), do: fun + + defimpl Inspect do + @filtered "[FILTERED]" + + @sensitive_opts %{ + Tesla.Middleware.BasicAuth => [:username, :password], + Tesla.Middleware.BearerAuth => [:token], + Tesla.Middleware.DigestAuth => [:username, :password] + } + + def inspect(%Tesla.Client{} = client, opts) do + client + |> Map.update!(:pre, &filter_sensitive_opts/1) + |> Inspect.Any.inspect(opts) + end + + defp filter_sensitive_opts([]), do: [] + + defp filter_sensitive_opts([{middleware, :call, [opts]} | rest]) do + sensitive_opts = Map.get(@sensitive_opts, middleware, []) + filtered_opts = Enum.reduce(sensitive_opts, opts, &maybe_redact(&2, &1)) + + [{middleware, :call, filtered_opts} | filter_sensitive_opts(rest)] + end + + defp filter_sensitive_opts([middleware | rest]) do + [middleware | filter_sensitive_opts(rest)] + end + + defp maybe_redact(opts, key) do + cond do + is_map(opts) and is_map_key(opts, key) -> + Map.put(opts, key, @filtered) + + is_list(opts) and Keyword.has_key?(opts, key) -> + Keyword.put(opts, key, @filtered) + + true -> + opts + end + end + end end diff --git a/test/tesla/client_test.exs b/test/tesla/client_test.exs index 75ae161f..5ef19833 100644 --- a/test/tesla/client_test.exs +++ b/test/tesla/client_test.exs @@ -49,4 +49,19 @@ defmodule Tesla.ClientTest do assert middlewares == Tesla.Client.middleware(client) end end + + describe "Inspect.Tesla.Client" do + test "ensures that no secrets are leaked in logs" do + middlewares = [ + {Tesla.Middleware.BasicAuth, username: "secret", password: "secret", other: "OK"}, + {Tesla.Middleware.BearerAuth, token: "secret", other: "OK"}, + {Tesla.Middleware.DigestAuth, username: "secret", password: "secret", other: "OK"} + ] + + inspected = middlewares |> Tesla.client() |> inspect() + + refute String.contains?(inspected, "secret") + assert String.contains?(inspected, "OK") + end + end end