Skip to content

Commit c0a14cd

Browse files
committed
Add 2FA for write operations
1 parent 773dfa5 commit c0a14cd

39 files changed

+461
-168
lines changed

lib/hex/api/client.ex

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ defmodule Hex.API.Client do
1111

1212
config
1313
|> maybe_put_api_key(opts)
14+
|> maybe_put_otp(opts)
1415
|> maybe_put_organization(opts)
1516
|> maybe_put_repository(opts)
1617
end
@@ -34,6 +35,14 @@ defmodule Hex.API.Client do
3435
end
3536
end
3637

38+
defp maybe_put_otp(config, opts) do
39+
if otp = opts[:otp] do
40+
Map.put(config, :api_otp, otp)
41+
else
42+
config
43+
end
44+
end
45+
3746
defp maybe_put_organization(config, opts) do
3847
if org = opts[:organization] || opts[:api_organization] do
3948
Map.put(config, :api_organization, to_string(org))

lib/hex/api/key.ex

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ defmodule Hex.API.Key do
44
alias Hex.API.Client
55

66
def new(name, permissions, auth) do
7-
config = Client.config(auth)
7+
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
8+
config = Client.config(auth_with_otp)
89

9-
# Convert permissions to binary map format expected by hex_core
10-
permissions =
11-
Enum.map(permissions, fn perm ->
12-
Map.new(perm, fn {k, v} -> {to_string(k), to_string(v)} end)
13-
end)
10+
# Convert permissions to binary map format expected by hex_core
11+
permissions =
12+
Enum.map(permissions, fn perm ->
13+
Map.new(perm, fn {k, v} -> {to_string(k), to_string(v)} end)
14+
end)
1415

15-
:mix_hex_api_key.add(config, to_string(name), permissions)
16+
:mix_hex_api_key.add(config, to_string(name), permissions)
17+
end)
1618
end
1719

1820
def get(auth) do
@@ -21,13 +23,17 @@ defmodule Hex.API.Key do
2123
end
2224

2325
def delete(name, auth) do
24-
config = Client.config(auth)
25-
:mix_hex_api_key.delete(config, to_string(name))
26+
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
27+
config = Client.config(auth_with_otp)
28+
:mix_hex_api_key.delete(config, to_string(name))
29+
end)
2630
end
2731

2832
def delete_all(auth) do
29-
config = Client.config(auth)
30-
:mix_hex_api_key.delete_all(config)
33+
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
34+
config = Client.config(auth_with_otp)
35+
:mix_hex_api_key.delete_all(config)
36+
end)
3137
end
3238

3339
defmodule Organization do
@@ -36,15 +42,17 @@ defmodule Hex.API.Key do
3642
alias Hex.API.Client
3743

3844
def new(organization, name, permissions, auth) do
39-
config = Client.config(Keyword.put(auth, :api_organization, to_string(organization)))
45+
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
46+
config = Client.config(Keyword.put(auth_with_otp, :api_organization, to_string(organization)))
4047

41-
# Convert permissions to binary map format expected by hex_core
42-
permissions =
43-
Enum.map(permissions, fn perm ->
44-
Map.new(perm, fn {k, v} -> {to_string(k), to_string(v)} end)
45-
end)
48+
# Convert permissions to binary map format expected by hex_core
49+
permissions =
50+
Enum.map(permissions, fn perm ->
51+
Map.new(perm, fn {k, v} -> {to_string(k), to_string(v)} end)
52+
end)
4653

47-
:mix_hex_api_key.add(config, to_string(name), permissions)
54+
:mix_hex_api_key.add(config, to_string(name), permissions)
55+
end)
4856
end
4957

5058
def get(organization, auth) do
@@ -53,13 +61,17 @@ defmodule Hex.API.Key do
5361
end
5462

5563
def delete(organization, name, auth) do
56-
config = Client.config(Keyword.put(auth, :api_organization, to_string(organization)))
57-
:mix_hex_api_key.delete(config, to_string(name))
64+
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
65+
config = Client.config(Keyword.put(auth_with_otp, :api_organization, to_string(organization)))
66+
:mix_hex_api_key.delete(config, to_string(name))
67+
end)
5868
end
5969

6070
def delete_all(organization, auth) do
61-
config = Client.config(Keyword.put(auth, :api_organization, to_string(organization)))
62-
:mix_hex_api_key.delete_all(config)
71+
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
72+
config = Client.config(Keyword.put(auth_with_otp, :api_organization, to_string(organization)))
73+
:mix_hex_api_key.delete_all(config)
74+
end)
6375
end
6476
end
6577
end

lib/hex/api/package.ex

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,29 @@ defmodule Hex.API.Package do
2121
alias Hex.API.Client
2222

2323
def add(repo, package, owner, level, transfer, auth) when package != "" do
24-
config = Client.build_config(repo, auth)
25-
26-
:mix_hex_api_package_owner.add(
27-
config,
28-
to_string(package),
29-
to_string(owner),
30-
to_string(level),
31-
transfer
32-
)
24+
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
25+
config = Client.build_config(repo, auth_with_otp)
26+
27+
:mix_hex_api_package_owner.add(
28+
config,
29+
to_string(package),
30+
to_string(owner),
31+
to_string(level),
32+
transfer
33+
)
34+
end)
3335
end
3436

3537
def delete(repo, package, owner, auth) when package != "" do
36-
config = Client.build_config(repo, auth)
37-
38-
:mix_hex_api_package_owner.delete(
39-
config,
40-
to_string(package),
41-
to_string(owner)
42-
)
38+
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
39+
config = Client.build_config(repo, auth_with_otp)
40+
41+
:mix_hex_api_package_owner.delete(
42+
config,
43+
to_string(package),
44+
to_string(owner)
45+
)
46+
end)
4347
end
4448

4549
def get(repo, package, auth) when package != "" do

lib/hex/api/release.ex

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,41 @@ defmodule Hex.API.Release do
1212
def publish(repo, tar, auth, progress \\ fn _ -> nil end, replace \\ false)
1313

1414
def publish(repo, tar, auth, progress, replace?) do
15-
config = Client.build_config(repo, auth)
15+
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
16+
config = Client.build_config(repo, auth_with_otp)
1617

17-
# Pass progress callback through adapter config
18-
adapter_config = %{progress_callback: progress}
19-
config = Map.put(config, :http_adapter, {Hex.HTTP, adapter_config})
18+
# Pass progress callback through adapter config
19+
adapter_config = %{progress_callback: progress}
20+
config = Map.put(config, :http_adapter, {Hex.HTTP, adapter_config})
2021

21-
params = [{:replace, replace?}]
22-
:mix_hex_api_release.publish(config, tar, params)
22+
params = [{:replace, replace?}]
23+
:mix_hex_api_release.publish(config, tar, params)
24+
end)
2325
end
2426

2527
def delete(repo, name, version, auth) do
26-
config = Client.build_config(repo, auth)
28+
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
29+
config = Client.build_config(repo, auth_with_otp)
2730

28-
:mix_hex_api_release.delete(config, to_string(name), to_string(version))
31+
:mix_hex_api_release.delete(config, to_string(name), to_string(version))
32+
end)
2933
end
3034

3135
def retire(repo, name, version, body, auth) do
32-
config = Client.build_config(repo, auth)
33-
# Convert body to binary map for hex_core
34-
params = Map.new(body, fn {k, v} -> {to_string(k), to_string(v)} end)
36+
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
37+
config = Client.build_config(repo, auth_with_otp)
38+
# Convert body to binary map for hex_core
39+
params = Map.new(body, fn {k, v} -> {to_string(k), to_string(v)} end)
3540

36-
:mix_hex_api_release.retire(config, to_string(name), to_string(version), params)
41+
:mix_hex_api_release.retire(config, to_string(name), to_string(version), params)
42+
end)
3743
end
3844

3945
def unretire(repo, name, version, auth) do
40-
config = Client.build_config(repo, auth)
46+
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
47+
config = Client.build_config(repo, auth_with_otp)
4148

42-
:mix_hex_api_release.unretire(config, to_string(name), to_string(version))
49+
:mix_hex_api_release.unretire(config, to_string(name), to_string(version))
50+
end)
4351
end
4452
end

lib/hex/api/release_docs.ex

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,38 +19,42 @@ defmodule Hex.API.ReleaseDocs do
1919
end
2020

2121
def publish(repo, name, version, tar, auth, progress \\ fn _ -> nil end) do
22-
config = Client.build_config(repo, auth)
23-
24-
# Pass progress callback through adapter config
25-
adapter_config = %{progress_callback: progress}
26-
config = Map.put(config, :http_adapter, {Hex.HTTP, adapter_config})
27-
28-
path =
29-
:mix_hex_api.build_repository_path(config, [
30-
"packages",
31-
to_string(name),
32-
"releases",
33-
to_string(version),
34-
"docs"
35-
])
36-
37-
body = {"application/octet-stream", tar}
38-
39-
:mix_hex_api.post(config, path, body)
22+
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
23+
config = Client.build_config(repo, auth_with_otp)
24+
25+
# Pass progress callback through adapter config
26+
adapter_config = %{progress_callback: progress}
27+
config = Map.put(config, :http_adapter, {Hex.HTTP, adapter_config})
28+
29+
path =
30+
:mix_hex_api.build_repository_path(config, [
31+
"packages",
32+
to_string(name),
33+
"releases",
34+
to_string(version),
35+
"docs"
36+
])
37+
38+
body = {"application/octet-stream", tar}
39+
40+
:mix_hex_api.post(config, path, body)
41+
end)
4042
end
4143

4244
def delete(repo, name, version, auth) do
43-
config = Client.build_config(repo, auth)
44-
45-
path =
46-
:mix_hex_api.build_repository_path(config, [
47-
"packages",
48-
to_string(name),
49-
"releases",
50-
to_string(version),
51-
"docs"
52-
])
53-
54-
:mix_hex_api.delete(config, path)
45+
Mix.Tasks.Hex.with_otp_retry(auth, fn auth_with_otp ->
46+
config = Client.build_config(repo, auth_with_otp)
47+
48+
path =
49+
:mix_hex_api.build_repository_path(config, [
50+
"packages",
51+
to_string(name),
52+
"releases",
53+
to_string(version),
54+
"docs"
55+
])
56+
57+
:mix_hex_api.delete(config, path)
58+
end)
5559
end
5660
end

lib/hex/api/user.ex

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ defmodule Hex.API.User do
1313
:mix_hex_api_user.get(config, to_string(username))
1414
end
1515

16+
# NOTE: Only used for testing
1617
def new(username, email, password) do
1718
config = Client.config()
1819

@@ -23,9 +24,4 @@ defmodule Hex.API.User do
2324
to_string(email)
2425
)
2526
end
26-
27-
def password_reset(name) do
28-
config = Client.config()
29-
:mix_hex_api_user.reset_password(config, to_string(name))
30-
end
3127
end

lib/hex/state.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ defmodule Hex.State do
1212
env: ["HEX_API_KEY"],
1313
config: [:api_key]
1414
},
15+
api_otp: %{
16+
env: ["HEX_OTP"]
17+
},
1518
oauth_tokens: %{
1619
config: [:"$oauth_tokens"]
1720
},

0 commit comments

Comments
 (0)