lib/openagents/scv/codex_credential_store/file.ex

58e6347eeb72 · 1 KB

defmodule OpenAgents.SCV.CodexCredentialStore.File do
  @moduledoc "Private file credential store for local development and protocol tests."

  @behaviour OpenAgents.SCV.CodexCredentialStore

  alias OpenAgents.SCV.DriverAccount

  @impl true
  def put(%DriverAccount{} = account, auth_json) when is_binary(auth_json) do
    with {:ok, path} <- path(account),
         :ok <- File.mkdir_p(Path.dirname(path)),
         :ok <- File.chmod(Path.dirname(path), 0o700),
         :ok <- atomic_write(path, auth_json) do
      {:ok, System.system_time(:millisecond)}
    else
      _error -> {:error, :credential_store_failed}
    end
  end

  @impl true
  def fetch(%DriverAccount{} = account) do
    with {:ok, path} <- path(account),
         {:ok, auth_json} <- File.read(path) do
      {:ok, auth_json}
    else
      _error -> {:error, :credential_not_found}
    end
  end

  defp path(%DriverAccount{secret_ref: "file:" <> slot}) do
    if Regex.match?(~r/\A[a-zA-Z0-9_-]{1,80}\z/, slot) do
      {:ok, Path.join(root(), slot <> ".json")}
    else
      {:error, :credential_reference_invalid}
    end
  end

  defp path(%DriverAccount{}), do: {:error, :credential_reference_invalid}

  defp atomic_write(path, contents) do
    temporary = path <> ".tmp-" <> Integer.to_string(System.unique_integer([:positive]))

    with :ok <- File.write(temporary, contents, [:binary, :exclusive]),
         :ok <- File.chmod(temporary, 0o600),
         :ok <- File.rename(temporary, path) do
      :ok
    else
      error ->
        _ = File.rm(temporary)
        error
    end
  end

  defp root do
    :openagents
    |> Application.fetch_env!(:scv_codex)
    |> Keyword.fetch!(:file_root)
  end
end