Packages
defdo_ddns
0.5.0
Cloudflare DDNS updater for A/AAAA/CNAME records with proxy-aware synchronization.
Current section
Files
Jump to
Current section
Files
lib/defdo/ddns/api/auth_config.ex
defmodule Defdo.DDNS.API.AuthConfig do
@moduledoc false
@type client :: %{
id: String.t(),
token: String.t(),
allowed_base_domains: [String.t()]
}
@type t :: %{
mode: :clients | :token | :none,
token: String.t() | nil,
clients: %{optional(String.t()) => client}
}
@spec from_app_env() :: {:ok, t()} | {:error, term()}
def from_app_env do
:defdo_ddns
|> Application.get_env(Defdo.DDNS.API, [])
|> from_config()
end
@spec from_config(keyword()) :: {:ok, t()} | {:error, term()}
def from_config(config) when is_list(config) do
token =
config
|> Keyword.get(:token)
|> normalize_token()
with {:ok, clients_map} <- parse_clients(Keyword.get(config, :clients, [])) do
mode =
cond do
map_size(clients_map) > 0 -> :clients
is_binary(token) -> :token
true -> :none
end
{:ok, %{mode: mode, token: token, clients: clients_map}}
end
end
@spec parse_clients(term()) :: {:ok, %{optional(String.t()) => client}} | {:error, term()}
def parse_clients(clients) do
normalize_clients(clients)
end
@spec normalize_base_domain(term()) :: String.t() | nil
def normalize_base_domain(value), do: normalize_domain(value)
@spec normalize_token(term()) :: String.t() | nil
def normalize_token(nil), do: nil
def normalize_token(value) when is_binary(value) do
case String.trim(value) do
"" -> nil
token -> token
end
end
def normalize_token(_), do: nil
defp normalize_clients(nil), do: {:ok, %{}}
defp normalize_clients([]), do: {:ok, %{}}
defp normalize_clients(clients) when is_list(clients) do
Enum.reduce_while(clients, {:ok, %{}}, fn entry, {:ok, acc} ->
with {:ok, normalized} <- normalize_client(entry),
:ok <- ensure_unique_client_id(acc, normalized.id) do
{:cont, {:ok, Map.put(acc, normalized.id, normalized)}}
else
{:error, reason} -> {:halt, {:error, reason}}
end
end)
end
defp normalize_clients(_), do: {:error, {:invalid_clients, "must be a JSON array"}}
defp normalize_client(entry) when is_map(entry) do
id =
entry
|> map_get("id", :id)
|> case do
nil -> map_get(entry, "client_id", :client_id)
value -> value
end
|> normalize_client_id()
token =
entry
|> map_get("token", :token)
|> normalize_token()
allowed_domains =
entry
|> map_get("allowed_base_domains", :allowed_base_domains)
|> normalize_allowed_domains()
cond do
not is_binary(id) ->
{:error, {:invalid_clients, "each client must include non-empty id"}}
not is_binary(token) ->
{:error, {:invalid_clients, "client #{id} must include non-empty token"}}
allowed_domains == [] ->
{:error, {:invalid_clients, "client #{id} must include non-empty allowed_base_domains"}}
true ->
{:ok, %{id: id, token: token, allowed_base_domains: allowed_domains}}
end
end
defp normalize_client(_),
do: {:error, {:invalid_clients, "each client entry must be an object"}}
defp ensure_unique_client_id(clients, id) do
if Map.has_key?(clients, id) do
{:error, {:invalid_clients, "duplicate client id #{id}"}}
else
:ok
end
end
defp normalize_client_id(value) when is_binary(value) do
case String.trim(value) do
"" -> nil
id -> id
end
end
defp normalize_client_id(_), do: nil
defp normalize_allowed_domains(value) when is_binary(value) do
value
|> String.split(~r/[,\s]+/, trim: true)
|> normalize_allowed_domains()
end
defp normalize_allowed_domains(value) when is_list(value) do
value
|> Enum.map(&normalize_domain/1)
|> Enum.reject(&is_nil/1)
|> Enum.uniq()
end
defp normalize_allowed_domains(_), do: []
defp normalize_domain(value) when is_binary(value) do
value
|> String.trim()
|> String.trim_trailing(".")
|> String.downcase()
|> case do
"" -> nil
domain -> domain
end
end
defp normalize_domain(_), do: nil
defp map_get(map, string_key, atom_key) do
cond do
Map.has_key?(map, string_key) ->
Map.get(map, string_key)
Map.has_key?(map, atom_key) ->
Map.get(map, atom_key)
true ->
nil
end
end
end