diff --git a/lib/openid_connect/worker.ex b/lib/openid_connect/worker.ex index 1b73484..2ba02f6 100644 --- a/lib/openid_connect/worker.ex +++ b/lib/openid_connect/worker.ex @@ -9,19 +9,29 @@ defmodule OpenIDConnect.Worker do @refresh_time 60 * 60 * 1000 - def start_link(provider_configs, name \\ :openid_connect) do - GenServer.start_link(__MODULE__, provider_configs, name: name) + require Logger + + def start_link(options, name \\ :openid_connect) + + def start_link(config, name) do + GenServer.start_link(__MODULE__, config, name: name) end def init(:ignore) do :ignore end + def init({provider_configs, pid}) when is_pid(pid) do + {:ok, state} = init(provider_configs) + Process.send_after(pid, :ready, 1) + {:ok, state} + end + def init(provider_configs) do state = Enum.into(provider_configs, %{}, fn {provider, config} -> - documents = update_documents(provider, config) - {provider, %{config: config, documents: documents}} + Process.send_after(self(), {:update_documents, provider}, 0) + {provider, %{config: config, documents: nil}} end) {:ok, state} @@ -43,23 +53,34 @@ defmodule OpenIDConnect.Worker do end def handle_info({:update_documents, provider}, state) do - config = get_in(state, [provider, :config]) - documents = update_documents(provider, config) - - state = put_in(state, [provider, :documents], documents) - - {:noreply, state} + with config <- get_in(state, [provider, :config]), + {:ok, documents} <- update_documents(provider, config) do + {:noreply, put_in(state, [provider, :documents], documents)} + else + _ -> {:noreply, state} + end end defp update_documents(provider, config) do - {:ok, %{remaining_lifetime: remaining_lifetime}} = - {:ok, documents} = OpenIDConnect.update_documents(config) - - refresh_time = time_until_next_refresh(remaining_lifetime) - - Process.send_after(self(), {:update_documents, provider}, refresh_time) + with {:ok, documents} <- OpenIDConnect.update_documents(config), + remaining_lifetime <- Map.get(documents, :remaining_lifetime), + refresh_time <- time_until_next_refresh(remaining_lifetime) do + Process.send_after(self(), {:update_documents, provider}, refresh_time) + {:ok, documents} + else + {:error, :update_documents, reason} = error -> + Logger.warn("Failed to update documents for provider #{provider}: #{message(reason)}") + Process.send_after(self(), {:update_documents, provider}, @refresh_time) + error + end + end - documents + defp message(reason) do + if Exception.exception?(reason) do + Exception.message(reason) + else + "#{inspect(reason)}" + end end defp time_until_next_refresh(nil), do: @refresh_time diff --git a/test/openid_connect/worker_test.exs b/test/openid_connect/worker_test.exs index 281d3a9..abf8ade 100644 --- a/test/openid_connect/worker_test.exs +++ b/test/openid_connect/worker_test.exs @@ -19,7 +19,7 @@ defmodule OpenIDConnect.WorkerTest do config = Application.get_env(:openid_connect, :providers) - {:ok, pid} = start_supervised({OpenIDConnect.Worker, config}) + {:ok, pid} = start_worker(config) state = :sys.get_state(pid) @@ -43,10 +43,9 @@ defmodule OpenIDConnect.WorkerTest do test "worker can respond to a call for the config" do mock_http_requests() - config = Application.get_env(:openid_connect, :providers) - {:ok, pid} = start_supervised({OpenIDConnect.Worker, config}) + {:ok, pid} = start_worker(config) google_config = GenServer.call(pid, {:config, :google}) @@ -58,8 +57,7 @@ defmodule OpenIDConnect.WorkerTest do config = Application.get_env(:openid_connect, :providers) - {:ok, pid} = start_supervised({OpenIDConnect.Worker, config}) - + {:ok, pid} = start_worker(config) discovery_document = GenServer.call(pid, {:discovery_document, :google}) expected_document = @@ -77,7 +75,7 @@ defmodule OpenIDConnect.WorkerTest do config = Application.get_env(:openid_connect, :providers) - {:ok, pid} = start_supervised({OpenIDConnect.Worker, config}) + {:ok, pid} = start_worker(config) jwk = GenServer.call(pid, {:jwk, :google}) @@ -91,6 +89,14 @@ defmodule OpenIDConnect.WorkerTest do assert expected_jwk == jwk end + test "worker doesn't die if dns fails" do + mock_nxdomain_error() + + config = Application.get_env(:openid_connect, :providers) + + assert {:ok, _} = start_worker(config) + end + defp mock_http_requests do HTTPClientMock |> expect(:get, fn "https://accounts.google.com/.well-known/openid-configuration", _headers, _opts -> @@ -98,4 +104,15 @@ defmodule OpenIDConnect.WorkerTest do end) |> expect(:get, fn "https://www.googleapis.com/oauth2/v3/certs", _headers, _opts -> @google_certs end) end + + defp mock_nxdomain_error do + HTTPClientMock + |> expect(:get, fn _, _, _ -> {:error, %HTTPoison.Error{id: nil, reason: :nxdomain}} end) + end + + defp start_worker(config) do + {:ok, pid} = start_supervised({OpenIDConnect.Worker, {config, self()}}) + assert_receive :ready + {:ok, pid} + end end