Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 50 additions & 4 deletions lib/hex/registry/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ defmodule Hex.Registry.Server do
@timeout 60_000
@ets_version 3
@public_keys_html "https://hex.pm/docs/public_keys"
@dashboard_url "https://hex.pm/dashboard"

def start_link(opts \\ []) do
opts = Keyword.put_new(opts, :name, @name)
Expand Down Expand Up @@ -410,10 +411,7 @@ defmodule Hex.Registry.Server do
)

if missing_status?(result) do
Hex.Shell.error(
"This could be because the package does not exist, it was spelled " <>
"incorrectly or you don't have permissions to it"
)
print_missing_package_diagnostics(repo, package, result)
end

if not missing_status?(result) or Mix.debug?() do
Expand Down Expand Up @@ -442,6 +440,54 @@ defmodule Hex.Registry.Server do
end
end

@doc false
def print_missing_package_diagnostics(repo, package, result) do
{:ok, {status, _headers, _body}} = result
package_name = Hex.Utils.package_name(repo, package)

cond do
# Package does not exist
status == 404 ->
Hex.Shell.error(
"The package #{package_name} does not exist. Please verify the package name is spelled correctly."
)

# Permission issue
status == 403 ->
auth_status = if has_authentication?(), do: :authenticated, else: :unauthenticated
print_permission_error(package_name, auth_status)

true ->
Hex.Shell.error("Error encounted fetching registry entry for #{package_name}")
end
end

defp print_permission_error(package_name, :authenticated) do
Hex.Shell.error(
"You don't have permission to access #{package_name}. This could be because the package is private " <>
"and you don't have the required permissions. Contact the package owner to request access, or " <>
"check your permissions at: #{@dashboard_url}"
)
end

defp print_permission_error(package_name, :unauthenticated) do
Hex.Shell.error(
"You don't have permission to access #{package_name}. This could be because the package is private " <>
"and requires authentication, run 'mix hex.user auth' to authenticate."
)
end

defp has_authentication? do
# Check if user has OAuth token
has_oauth = Hex.OAuth.has_tokens?()

# Check for API keys
repos_key = Hex.State.get(:repos_key)
api_key = Hex.State.get(:api_key)

has_oauth || repos_key != nil || api_key != nil
end

defp missing_status?({:ok, {status, _, _}}), do: status in [403, 404]
defp missing_status?(_), do: false

Expand Down
90 changes: 90 additions & 0 deletions test/hex/registry/error_message_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
defmodule Hex.Registry.ErrorMessageTest do
use HexTest.Case

setup do
Application.ensure_all_started(:hex)

Hex.State.put(:repos, %{
"hexpm" => %{
url: "https://repo.hex.pm",
public_key: nil,
auth_key: nil
}
})

:ok
end

describe "404 errors (package not found)" do
test "displays helpful message for non-existent package" do
result = {:ok, {404, [], %{}}}

Hex.Registry.Server.print_missing_package_diagnostics(
"hexpm",
"nonexistent_package",
result
)

assert_received {:mix_shell, :error, [msg]}
assert msg =~ "The package nonexistent_package does not exist"
assert msg =~ "Please verify the package name is spelled correctly"
end
end

describe "403 errors (permission denied) - unauthenticated" do
setup do
# Clear any authentication
Hex.State.put(:oauth_token, nil)
Hex.State.put(:repos_key, nil)
Hex.State.put(:api_key, nil)
:ok
end

test "displays auth prompt for private package" do
package_name = "private_package"
result = {:ok, {403, [], %{}}}

Hex.Registry.Server.print_missing_package_diagnostics(
"hexpm",
package_name,
result
)

assert_received {:mix_shell, :error, [msg]}
assert msg =~ "You don't have permission to access #{package_name}"
assert msg =~ "the package is private and requires authentication"
assert msg =~ "run 'mix hex.user auth' to authenticate"
end
end

describe "403 errors (permission denied) - authenticated" do
setup do
# Simulate authenticated user with OAuth token
token_data = %{
"access_token" => "test_token",
"refresh_token" => "refresh_token",
"expires_at" => System.system_time(:second) + 3600
}

Hex.State.put(:oauth_token, token_data)
:ok
end

test "displays permission diagnostics for private package" do
package_name = "private_package"
result = {:ok, {403, [], %{}}}

Hex.Registry.Server.print_missing_package_diagnostics(
"hexpm",
package_name,
result
)

assert_received {:mix_shell, :error, [msg]}
assert msg =~ "You don't have permission to access #{package_name}"
assert msg =~ "the package is private and you don't have the required permissions"
assert msg =~ "Contact the package owner to request access"
assert msg =~ "https://hex.pm/dashboard"
end
end
end
Loading