lib/providers/aws_secrets_manager.ex

defmodule ExSecrets.Providers.AwsSecretsManager do
  use ExSecrets.Providers.Base

  @moduledoc """
  This module provides a provider for AWS Secrets Manager - https://aws.amazon.com/secrets-manager/
  Code for authenticating with AWS has been has been forked from ex_aws See https://github.com/ex-aws/ex_aws

  ## Configuration

  ```elixir
  config :ex_secrets, :providers, %{
        aws_secrets_manager: %{
          access_key_id: "taccess_key_id",
          secret_access_key: "secret_access_key"
        }
      }
  ```

  Its is recomended to create an access key and secret access key for the access key with only the required permissions.
  Limiting thye scope of the access key will help in reducing the risk of the access key being compromised.
  """

  @process_name :ex_secrets_aws_secrets_manager

  def reset() do
    :ok
  end

  def init(_) do
    {:ok, %{}}
  end

  def set(_, _) do
    {:error, "set is not supported for AWS Secrets Manager"}
  end

  def get(name) do
    name = name |> String.split("_") |> Enum.join("-")

    with process when not is_nil(process) <-
           GenServer.whereis(@process_name) do
      GenServer.call(@process_name, {:get, name})
    else
      nil ->
        case get_secret(name, %{}, get_current_epoch()) do
          {:ok, value, _} -> value
          _ -> nil
        end
    end
  end

  def get_secret(name, %{}, _) do
    with {:ok, secret} <- Utils.AwsRequest.call(name),
         value <- get_value(secret) do
      {:ok, value, %{}}
    else
      _err ->
        nil
    end
  end

  def handle_call({:get, name}, _from, state) do
    case get_secret(name, state, get_current_epoch()) do
      {:ok, secret, state} -> {:reply, secret, state}
      _ -> {:reply, nil, state}
    end
  end

  defp get_current_epoch() do
    DateTime.utc_now() |> DateTime.to_unix()
  end

  def process_name() do
    @process_name
  end

  defp get_value("{" <> _ = value) do
    with {:ok, data} <- Jason.decode(value),
         [key] <- Map.keys(data) do
      data[key]
    else
      _err ->
        value
    end
  end

  defp get_value(value), do: value
end