Skip to content
Draft
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
7 changes: 5 additions & 2 deletions lib/hex/api/auth.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ defmodule Hex.API.Auth do

alias Hex.API.Client

def get(domain, resource, auth) do
def get(domain, resource, auth \\ []) do
config = Client.config(auth)

params = %{
domain: to_string(domain),
resource: to_string(resource)
}

:mix_hex_api_auth.test(config, params)
Hex.Auth.with_api(:read, config, &:mix_hex_api_auth.test(&1, params),
auth_inline: false,
optional: true
)
end
end
5 changes: 2 additions & 3 deletions lib/hex/api/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ defmodule Hex.API.Client do
opts[:user] && opts[:pass] ->
# For basic auth, add it as an HTTP header
base64 = Base.encode64("#{opts[:user]}:#{opts[:pass]}")
headers = Map.get(config, :http_headers, %{})
headers = Map.put(headers, "authorization", "Basic #{base64}")
Map.put(config, :http_headers, headers)
token = "Basic #{base64}"
Map.put(config, :api_key, token)

true ->
config
Expand Down
84 changes: 37 additions & 47 deletions lib/hex/api/key.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,80 +3,70 @@ defmodule Hex.API.Key do

alias Hex.API.Client

def new(name, permissions, auth) do
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
config = Client.config(auth_with_otp)
def new(name, permissions, auth \\ []) do
config = Client.config(auth)

# Convert permissions to binary map format expected by hex_core
permissions =
Enum.map(permissions, fn perm ->
Map.new(perm, fn {k, v} -> {to_string(k), to_string(v)} end)
end)
# Convert permissions to binary map format expected by hex_core
permissions =
Enum.map(permissions, fn perm ->
Map.new(perm, fn {k, v} -> {to_string(k), to_string(v)} end)
end)

:mix_hex_api_key.add(config, to_string(name), permissions)
end)
Hex.Auth.with_api(:write, config, &:mix_hex_api_key.add(&1, to_string(name), permissions))
end

def get(auth) do
def get(auth \\ []) do
config = Client.config(auth)
:mix_hex_api_key.list(config)

Hex.Auth.with_api(:read, config, &:mix_hex_api_key.list(&1))
end

def delete(name, auth) do
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
config = Client.config(auth_with_otp)
:mix_hex_api_key.delete(config, to_string(name))
end)
def delete(name, auth \\ []) do
config = Client.config(auth)

Hex.Auth.with_api(:write, config, &:mix_hex_api_key.delete(&1, to_string(name)))
end

def delete_all(auth) do
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
config = Client.config(auth_with_otp)
:mix_hex_api_key.delete_all(config)
end)
def delete_all(auth \\ []) do
config = Client.config(auth)

Hex.Auth.with_api(:write, config, &:mix_hex_api_key.delete_all(&1))
end

defmodule Organization do
@moduledoc false

alias Hex.API.Client

def new(organization, name, permissions, auth) do
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
config =
Client.config(Keyword.put(auth_with_otp, :api_organization, to_string(organization)))
def new(organization, name, permissions, auth \\ []) do
config =
Client.config(Keyword.put(auth, :api_organization, to_string(organization)))

# Convert permissions to binary map format expected by hex_core
permissions =
Enum.map(permissions, fn perm ->
Map.new(perm, fn {k, v} -> {to_string(k), to_string(v)} end)
end)
# Convert permissions to binary map format expected by hex_core
permissions =
Enum.map(permissions, fn perm ->
Map.new(perm, fn {k, v} -> {to_string(k), to_string(v)} end)
end)

:mix_hex_api_key.add(config, to_string(name), permissions)
end)
Hex.Auth.with_api(:write, config, &:mix_hex_api_key.add(&1, to_string(name), permissions))
end

def get(organization, auth) do
def get(organization, auth \\ []) do
config = Client.config(Keyword.put(auth, :api_organization, to_string(organization)))
:mix_hex_api_key.list(config)
Hex.Auth.with_api(:read, config, &:mix_hex_api_key.list(&1))
end

def delete(organization, name, auth) do
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
config =
Client.config(Keyword.put(auth_with_otp, :api_organization, to_string(organization)))
def delete(organization, name, auth \\ []) do
config =
Client.config(Keyword.put(auth, :api_organization, to_string(organization)))

:mix_hex_api_key.delete(config, to_string(name))
end)
Hex.Auth.with_api(:write, config, &:mix_hex_api_key.delete(&1, to_string(name)))
end

def delete_all(organization, auth) do
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
config =
Client.config(Keyword.put(auth_with_otp, :api_organization, to_string(organization)))
def delete_all(organization, auth \\ []) do
config = Client.config(Keyword.put(auth, :api_organization, to_string(organization)))

:mix_hex_api_key.delete_all(config)
end)
Hex.Auth.with_api(:write, config, &:mix_hex_api_key.delete_all(&1))
end
end
end
32 changes: 9 additions & 23 deletions lib/hex/api/oauth.ex
Original file line number Diff line number Diff line change
Expand Up @@ -66,36 +66,22 @@ defmodule Hex.API.OAuth do
end

@doc """
Exchanges an API key for a short-lived OAuth access token using the client credentials grant.
Runs the complete OAuth device authorization flow.

Optionally accepts a custom API URL for the OAuth exchange endpoint.
See `:mix_hex_api_oauth.device_auth_flow/5` for more details.

## Examples

iex> Hex.API.OAuth.exchange_api_key(api_key, "api")
{:ok, {200, _headers, %{
"access_token" => "...",
"token_type" => "bearer",
"expires_in" => 1800,
"scope" => "api"
}}}
iex> prompt_fn = fn uri, code -> IO.puts("Visit \#{uri} and enter: \#{code}") end
iex> Hex.API.OAuth.device_auth_flow("api", prompt_fn)
{:ok, %{access_token: "...", refresh_token: "...", expires_at: 1234567890}}

iex> Hex.API.OAuth.exchange_api_key(api_key, "api", nil, "https://custom.hex.pm")
{:ok, {200, _headers, %{...}}}
iex> Hex.API.OAuth.device_auth_flow("api", prompt_fn, open_browser: true)
{:ok, %{access_token: "...", refresh_token: "...", expires_at: 1234567890}}
"""
def exchange_api_key(api_key, scopes, name \\ nil, api_url \\ nil) do
def device_auth_flow(scopes, prompt_user, opts \\ []) do
config = Client.config()

config =
if api_url do
Map.put(config, :api_url, api_url)
else
config
end

scope_string = if is_list(scopes), do: Enum.join(scopes, " "), else: scopes
opts = if name, do: [name: name], else: []
:mix_hex_api_oauth.client_credentials_token(config, @client_id, api_key, scope_string, opts)
:mix_hex_api_oauth.device_auth_flow(config, @client_id, scopes, prompt_user, opts)
end

@doc """
Expand Down
57 changes: 41 additions & 16 deletions lib/hex/api/package.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,50 +5,75 @@ defmodule Hex.API.Package do

def get(repo, name, auth \\ []) when name != "" do
config = Client.build_config(repo, auth)
:mix_hex_api_package.get(config, to_string(name))

Hex.Auth.with_api(
:read,
config,
&:mix_hex_api_package.get(&1, to_string(name)),
auth_inline: false,
optional: true
)
end

def search(repo, search, auth \\ []) do
config = Client.build_config(repo, auth)
search_params = [{:sort, "downloads"}]

:mix_hex_api_package.search(config, to_string(search), search_params)
Hex.Auth.with_api(
:read,
config,
&:mix_hex_api_package.search(&1, to_string(search), search_params),
auth_inline: false,
optional: true
)
end

defmodule Owner do
@moduledoc false

alias Hex.API.Client

def add(repo, package, owner, level, transfer, auth) when package != "" do
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
config = Client.build_config(repo, auth_with_otp)
def add(repo, package, owner, level, transfer, auth \\ []) when package != "" do
config =
Client.build_config(repo, auth)

:mix_hex_api_package_owner.add(
config,
Hex.Auth.with_api(
:write,
config,
&:mix_hex_api_package_owner.add(
&1,
to_string(package),
to_string(owner),
to_string(level),
transfer
)
end)
)
end

def delete(repo, package, owner, auth) when package != "" do
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
config = Client.build_config(repo, auth_with_otp)
def delete(repo, package, owner, auth \\ []) when package != "" do
config = Client.build_config(repo, auth)

:mix_hex_api_package_owner.delete(
config,
Hex.Auth.with_api(
:write,
config,
&:mix_hex_api_package_owner.delete(
&1,
to_string(package),
to_string(owner)
)
end)
)
end

def get(repo, package, auth) when package != "" do
def get(repo, package, auth \\ []) when package != "" do
config = Client.build_config(repo, auth)
:mix_hex_api_package_owner.list(config, to_string(package))

Hex.Auth.with_api(
:read,
config,
&:mix_hex_api_package_owner.list(&1, to_string(package)),
auth_inline: false,
optional: true
)
end
end
end
62 changes: 37 additions & 25 deletions lib/hex/api/release.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,59 @@ defmodule Hex.API.Release do
def get(repo, name, version, auth \\ []) do
config = Client.build_config(repo, auth)

:mix_hex_api_release.get(config, to_string(name), to_string(version))
Hex.Auth.with_api(
:read,
config,
&:mix_hex_api_release.get(&1, to_string(name), to_string(version)),
auth_inline: false,
optional: true
)
end

def publish(repo, tar, auth, progress \\ fn _ -> nil end, replace \\ false)
def publish(repo, tar, auth \\ [], progress \\ fn _ -> nil end, replace \\ false)

def publish(repo, tar, auth, progress, replace?) do
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
config = Client.build_config(repo, auth_with_otp)
config = Client.build_config(repo, auth)
# Pass progress callback through adapter config
adapter_config = %{progress_callback: progress}

# Pass progress callback through adapter config
adapter_config = %{progress_callback: progress}
Hex.Auth.with_api(:write, config, fn config ->
config = Map.put(config, :http_adapter, {Hex.HTTP, adapter_config})

params = [{:replace, replace?}]
:mix_hex_api_release.publish(config, tar, params)
:mix_hex_api_release.publish(config, tar, replace: replace?)
end)
end

def delete(repo, name, version, auth) do
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
config = Client.build_config(repo, auth_with_otp)
def delete(repo, name, version, auth \\ []) do
config = Client.build_config(repo, auth)

:mix_hex_api_release.delete(config, to_string(name), to_string(version))
end)
Hex.Auth.with_api(
:write,
config,
&:mix_hex_api_release.delete(&1, to_string(name), to_string(version))
)
end

def retire(repo, name, version, body, auth) do
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
config = Client.build_config(repo, auth_with_otp)
# Convert body to binary map for hex_core
params = Map.new(body, fn {k, v} -> {to_string(k), to_string(v)} end)
def retire(repo, name, version, body, auth \\ []) do
config = Client.build_config(repo, auth)

# Convert body to binary map for hex_core
params = Map.new(body, fn {k, v} -> {to_string(k), to_string(v)} end)

:mix_hex_api_release.retire(config, to_string(name), to_string(version), params)
end)
Hex.Auth.with_api(
:write,
config,
&:mix_hex_api_release.retire(&1, to_string(name), to_string(version), params)
)
end

def unretire(repo, name, version, auth) do
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
config = Client.build_config(repo, auth_with_otp)
def unretire(repo, name, version, auth \\ []) do
config = Client.build_config(repo, auth)

:mix_hex_api_release.unretire(config, to_string(name), to_string(version))
end)
Hex.Auth.with_api(
:write,
config,
&:mix_hex_api_release.unretire(&1, to_string(name), to_string(version))
)
end
end
Loading
Loading