Current section
Files
Jump to
Current section
Files
lib/couchdb_auth/environment.ex
defmodule CouchDB.Environment do
@moduledoc """
Runtime configuration helpers for the CouchDB adapter.
The module reads values either from the application environment
(`config/*.exs`) or from operating system variables prefixed with
`COUCHDB_`. Environment variables take precedence, which allows the
library to be configured without recompilation.
"""
@app :couchdb_auth
@env_prefix "COUCHDB_"
@typedoc "CouchDB connection credentials."
@type conn :: %{
protocol: String.t(),
hostname: String.t(),
database: String.t(),
port: String.t() | nil,
user: String.t() | nil,
password: String.t() | nil
}
@typedoc "CouchDB user role is a simple string; a list of strings represents user roles."
@type user_roles :: [String.t()]
@typedoc "HTTP headers are modeled as name/value pairs."
@type headers :: [{String.t(), String.t()}]
@typedoc "Username and password for basic authentication."
@type basic_auth :: %{user: String.t() | nil, password: String.t() | nil}
@typedoc "Authentication type configured for the adapter."
@type auth_type :: :admin_party | :basic | :cookie | :oauth | :bearer | :proxy
@typedoc "User information map."
@type user_info :: %{user: String.t() | nil, password: String.t() | nil}
@doc """
Returns the configured database properties.
"""
@spec dbprops :: %{
protocol: String.t() | nil,
hostname: String.t() | nil,
port: String.t() | nil,
backend_port: String.t() | nil,
database: String.t() | nil,
cluster: boolean(),
node_name: String.t() | nil
}
def dbprops do
%{
protocol: fetch_string(:protocol),
hostname: fetch_string(:hostname),
port: fetch_port(:port),
backend_port: fetch_port(:backend_port),
database: fetch_string(:database),
cluster: fetch_boolean(:cluster, false),
node_name: fetch_string(:node_name)
}
end
@doc """
Returns the configured OAuth credentials.
"""
@spec oauth_creds :: %{
optional(:consumer_key) => String.t() | nil,
optional(:consumer_secret) => String.t() | nil,
optional(:token) => String.t() | nil,
optional(:token_secret) => String.t() | nil
}
def oauth_creds do
%{
consumer_key: fetch_string(:consumer_key),
consumer_secret: fetch_string(:consumer_secret),
token: fetch_string(:token),
token_secret: fetch_string(:token_secret)
}
end
@doc """
Returns the base URL for the primary CouchDB interface.
"""
@spec base_url :: String.t()
def base_url do
props = dbprops()
build_url(props.protocol, props.hostname, props.port)
end
@doc """
Returns the base URL for the CouchDB backend (node management) interface.
"""
@spec backend_url :: String.t()
def backend_url do
props = dbprops()
build_url(props.protocol, props.hostname, props.backend_port)
end
@doc """
Returns the base URL for admin or cluster-aware endpoints.
"""
@spec admin_url :: String.t()
def admin_url do
backend_url()
|> Kernel.<>(admin_path())
|> ensure_trailing_cluster_path()
end
@doc """
Returns configured server admin credentials.
"""
@spec server_admin :: basic_auth()
def server_admin do
%{
user: fetch_string(:username),
password: fetch_string(:password)
}
end
@doc """
Returns the configured authentication type.
"""
@spec auth_type :: auth_type()
def auth_type do
:auth_type
|> fetch_config(:admin_party)
|> normalize_auth_type()
end
@doc """
Returns admin path depending on whether a cluster is configured.
"""
@spec admin_path :: String.t()
def admin_path do
props = dbprops()
if props.cluster do
"/_nodes/#{props.node_name || ""}"
else
""
end
end
@doc """
Returns the configured bearer (JWT) token, if present.
"""
@spec bearer_token :: String.t() | nil
def bearer_token do
fetch_string(:bearer_token)
end
@doc """
Returns proxy authentication configuration.
"""
@spec proxy_config :: %{
user: String.t() | nil,
roles: user_roles(),
secret: String.t() | nil,
token: String.t() | nil
}
def proxy_config do
%{
user: fetch_string(:proxy_user),
roles: fetch_roles(:proxy_roles),
secret: fetch_string(:proxy_secret),
token: fetch_string(:proxy_token)
}
end
defp build_url(nil, nil, _port), do: ""
defp build_url(protocol, hostname, nil) do
"#{protocol}://#{hostname}"
end
defp build_url(protocol, hostname, port) do
"#{protocol}://#{hostname}:#{port}"
end
defp fetch_port(key, default \\ nil) do
case fetch_config(key, default) do
nil -> nil
value -> to_string(value)
end
end
defp fetch_string(key, default \\ nil) do
case fetch_config(key, default) do
nil -> nil
value -> value |> to_string() |> String.trim()
end
end
defp fetch_boolean(key, default) do
case fetch_config(key, default) do
value when value in [true, true, "true", "TRUE", "1"] -> true
value when value in [false, false, "false", "FALSE", "0"] -> false
nil -> default
other -> !!other
end
end
defp fetch_roles(key, default \\ []) do
case fetch_config(key, default) do
nil ->
[]
value when is_list(value) ->
value
|> Enum.map(&to_string/1)
|> Enum.map(&String.trim/1)
|> Enum.reject(&(&1 == ""))
value when is_binary(value) ->
value
|> String.split([",", " "], trim: true)
|> Enum.map(&String.trim/1)
|> Enum.reject(&(&1 == ""))
other ->
[other |> to_string() |> String.trim()]
end
end
defp fetch_config(key, default) do
env_key = key |> to_string() |> String.upcase() |> then(&"#{@env_prefix}#{&1}")
case System.get_env(env_key) do
nil -> Application.get_env(@app, key, default)
value -> value
end
end
defp normalize_auth_type(value) when is_atom(value) do
case value do
:basic_auth -> :basic
:cookie_auth -> :cookie
:admin_party -> :admin_party
:basic -> :basic
:cookie -> :cookie
:oauth -> :oauth
:bearer -> :bearer
:jwt -> :bearer
:proxy -> :proxy
_ -> :admin_party
end
end
defp normalize_auth_type(value) when is_binary(value) do
value
|> String.trim()
|> String.downcase()
|> case do
"basic" -> :basic
"basic_auth" -> :basic
"cookie" -> :cookie
"cookie_auth" -> :cookie
"oauth" -> :oauth
"bearer" -> :bearer
"jwt" -> :bearer
"proxy" -> :proxy
"admin_party" -> :admin_party
_ -> :admin_party
end
end
defp normalize_auth_type(_), do: :admin_party
defp ensure_trailing_cluster_path(url) do
cond do
url == "" -> ""
String.ends_with?(url, "/") -> String.trim_trailing(url, "/")
true -> url
end
end
end