Current section
Files
Jump to
Current section
Files
lib/key_store.ex
defmodule ExFirebaseAuth.KeyStore do
@moduledoc """
The KeyStore handles fetching public keys from Google's servers to verify public keys with
## Warnings
By default ExFirebaseAuth.KeyStore will stop when the initial fetch failed. This behavior can be
changed by the following config key:
```
config :ex_firebase_auth, :key_store_fail_strategy, :silent
```
The following values are available
- `:stop`: Stops server after initial fetch fails.
- `:warn`: Logs a warning message with Logger. Continues retrying to fetch.
- `:silent`: Silently retries fetching new public keys, without warning when failing.
"""
use GenServer, restart: :transient
require Logger
def start_link(_) do
GenServer.start_link(__MODULE__, %{}, name: ExFirebaseAuth.KeyStore)
end
@spec key_store_fail_strategy :: :stop | :warn | :silent
@doc ~S"""
Returns the configured key_store_fail_strategy
## Examples
iex> ExFirebaseAuth.Token.key_store_fail_strategy()
:stop
"""
def key_store_fail_strategy,
do: Application.get_env(:ex_firebase_auth, :key_store_fail_strategy, :stop)
def init(_) do
find_or_create_ets_table()
case ExFirebaseAuth.KeySource.fetch_certificates() do
:error ->
case key_store_fail_strategy() do
:stop ->
{:stop,
"""
Initial certificate fetch failed
If you want to run ExFirebaseAuth offline during tests or development, add the following key to your config
```
config :ex_firebase_auth, :key_store_fail_strategy, :silent
```
"""}
:warn ->
unless key_store_fail_strategy() == :silent do
Logger.warn("Fetching firebase auth certificates failed. Retrying again shortly.")
end
schedule_refresh(10)
{:ok, %{}}
:silent ->
schedule_refresh(10)
{:ok, %{}}
end
{:ok, data} ->
store_data_to_ets(data)
Logger.debug("Fetched initial firebase auth certificates.")
schedule_refresh()
{:ok, %{}}
end
end
# When the refresh `info` is sent, we want to fetch the certificates
def handle_info(:refresh, state) do
case ExFirebaseAuth.KeySource.fetch_certificates() do
# keep trying with a lower interval, until then keep the old state
:error ->
Logger.warn("Fetching firebase auth certificates failed, using old state and retrying...")
schedule_refresh(10)
# if everything went okay, refresh at the regular interval and store the returned keys in state
{:ok, keys} ->
store_data_to_ets(keys)
Logger.debug("Fetched new firebase auth certificates.")
schedule_refresh()
end
{:noreply, state}
end
@doc false
def find_or_create_ets_table do
case :ets.whereis(ExFirebaseAuth.KeyStore) do
:undefined -> :ets.new(ExFirebaseAuth.KeyStore, [:set, :public, :named_table])
table -> table
end
end
defp store_data_to_ets(data) do
data
|> Enum.each(fn {key, value} ->
:ets.insert(ExFirebaseAuth.KeyStore, {key, value})
end)
end
defp schedule_refresh(after_s \\ 300) do
Process.send_after(self(), :refresh, after_s * 1000)
end
end