Packages
Bexio API Client for accessing the API described at https://docs.bexio.com.
Current section
Files
Jump to
Current section
Files
lib/bexio_api_client/req/access_token_refresher.ex
defmodule BexioApiClient.Req.AccessTokenRefresher do
@moduledoc """
Plugin to fetch a new access token for the given refresh token.
"""
require Logger
@expires_in_reserve 60
@ets_table_name :bexio_api_client_access_tokens
@spec attach(Req.Request.t()) :: Req.Request.t()
def attach(%Req.Request{} = request, options \\ []) do
request
|> Req.Request.register_options([
:refresh_token,
:client_id,
:client_secret
])
|> Req.Request.merge_options(options)
|> Req.Request.prepend_request_steps(get_access_token: &get_access_token/1)
end
def create_ets_table do
if :ets.whereis(@ets_table_name) == :undefined do
:ets.new(@ets_table_name, [:named_table, :set, :public])
end
end
@doc """
Remove all expired access tokens from the ETS table. Triggering this is part of the job of the caller process for now.
"""
def remove_expired_tokens do
if :ets.whereis(@ets_table_name) != :undefined do
:ets.match(@ets_table_name, {:"$0", {:_, :"$1"}})
|> Enum.filter(fn [_k, e] -> e < System.system_time(:second) end)
|> Enum.map(fn [k, _] -> k end)
|> Enum.each(fn k -> :ets.delete(:test, k) end)
end
end
defp get_access_token(%Req.Request{} = request) do
refresh_token = Req.Request.get_option(request, :refresh_token)
client_id = Req.Request.get_option(request, :client_id)
client_secret = Req.Request.get_option(request, :client_secret)
case get_access_token_from_ets(client_id, refresh_token) do
nil ->
Logger.debug("No access token found (or expired) in ETS, fetching a new one")
fetch_new_access_token_and_store(request, refresh_token, client_id, client_secret)
access_token ->
use_access_token(request, access_token)
end
end
defp fetch_new_access_token_and_store(
%Req.Request{} = req,
refresh_token,
client_id,
client_secret
) do
case BexioApiClient.access_token(refresh_token, client_id, client_secret) do
{:ok, access_token, expires_in} ->
Logger.debug("Received a token response: #{access_token}, #{expires_in}")
# Store the access token in ETS
store_access_token_in_ets(client_id, refresh_token, access_token, expires_in)
# Add the access token to the request
use_access_token(req, access_token)
{:error, reason} ->
Logger.error("Failed to fetch a new access token: #{inspect(reason)}")
req
end
end
defp store_access_token_in_ets(client_id, refresh_token, access_token, expires_in) do
:ets.insert(
@ets_table_name,
{{client_id, refresh_token},
{access_token, System.system_time(:second) + expires_in - @expires_in_reserve}}
)
end
defp get_access_token_from_ets(client_id, refresh_token) do
current_time = System.system_time(:second)
case :ets.lookup(@ets_table_name, {client_id, refresh_token}) do
[{_, {access_token, expires_at}}] when expires_at > current_time ->
access_token
_ ->
nil
end
end
defp use_access_token(request, access_token) do
Req.Request.merge_options(request, auth: {:bearer, access_token})
end
end