Skip to main content

lib/npm/registry/token.ex

defmodule NPM.Registry.Token do
  @moduledoc """
  Manages npm authentication tokens.

  Handles reading, validating, and masking auth tokens used
  for private registry access.
  """

  @doc """
  Reads the auth token from environment or .npmrc.
  """
  @spec read :: String.t() | nil
  def read do
    NPM.Config.auth_token() || read_from_npmrc()
  end

  @doc """
  Checks if a valid auth token is configured.
  """
  @spec configured? :: boolean()
  def configured?, do: read() != nil

  @doc """
  Masks a token for safe display.

  Shows only the first and last 4 characters.
  """
  @spec mask(String.t()) :: String.t()
  def mask(token) when byte_size(token) <= 8, do: "****"

  def mask(token) do
    first = String.slice(token, 0, 4)
    last = String.slice(token, -4, 4)
    "#{first}...#{last}"
  end

  @doc """
  Validates token format.

  npm tokens are typically UUIDs or base64-encoded strings.
  """
  @spec valid_format?(String.t()) :: boolean()
  def valid_format?(token) do
    byte_size(token) >= 8 and
      not String.contains?(token, " ") and
      not String.contains?(token, "\n")
  end

  @doc """
  Returns the auth header value for the given token.
  """
  @spec auth_header(String.t()) :: String.t()
  def auth_header(token), do: "Bearer #{token}"

  @doc """
  Reads token from an .npmrc file content.
  """
  @spec parse_npmrc(String.t()) :: String.t() | nil
  def parse_npmrc(content) do
    content
    |> String.split("\n")
    |> Enum.find_value(fn line ->
      case Regex.run(~r/_authToken=(.+)$/, String.trim(line)) do
        [_, token] -> String.trim(token)
        _ -> nil
      end
    end)
  end

  defp read_from_npmrc do
    npmrc_path = Path.join(System.user_home!(), ".npmrc")

    case File.read(npmrc_path) do
      {:ok, content} -> parse_npmrc(content)
      _ -> nil
    end
  end
end