Skip to content
Merged
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
11 changes: 5 additions & 6 deletions test/hex/api_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ defmodule Hex.APITest do

test "keys" do
permissions = [%{"domain" => "api"}]
auth = Hexpm.new_key("user", "keys_test")
sign_in = Hexpm.new_tfa_user("keys_user", "keys_user@mail.com", "hunter42")

assert {:ok, {201, _, %{"secret" => key_a}}} = Hex.API.Key.new("key_a", permissions, auth)
assert {:ok, {201, _, %{"secret" => key_b}}} = Hex.API.Key.new("key_b", permissions, auth)
assert {:ok, {201, _, %{"secret" => key_a}}} = Hex.API.Key.new("key_a", permissions, sign_in)
assert {:ok, {201, _, %{"secret" => key_b}}} = Hex.API.Key.new("key_b", permissions, sign_in)
assert is_binary(key_a) and byte_size(key_a) > 0
assert is_binary(key_b) and byte_size(key_b) > 0
auth = [key: key_a]
Expand All @@ -88,9 +88,8 @@ defmodule Hex.APITest do
assert {:ok, {401, _, _}} = Hex.API.Key.get(auth)

# Delete all keys
auth = Hexpm.new_key("user", "keys_test")
assert {:ok, {201, _, %{"secret" => key_c}}} = Hex.API.Key.new("key_c", permissions, auth)
assert {:ok, {201, _, %{"secret" => key_d}}} = Hex.API.Key.new("key_d", permissions, auth)
assert {:ok, {201, _, %{"secret" => key_c}}} = Hex.API.Key.new("key_c", permissions, sign_in)
assert {:ok, {201, _, %{"secret" => key_d}}} = Hex.API.Key.new("key_d", permissions, sign_in)
assert is_binary(key_c) and byte_size(key_c) > 0
assert is_binary(key_d) and byte_size(key_d) > 0
auth_c = [key: key_c]
Expand Down
15 changes: 1 addition & 14 deletions test/hex/organization_auth_integration_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ defmodule Hex.OrganizationAuthIntegrationTest do
"scope" => "api repositories"
})

auth = [key: response["access_token"], oauth: true, otp: totp(secret)]
auth = [key: response["access_token"], oauth: true, otp: Hexpm.totp(secret)]
assert {:ok, {204, _, _}} = Hexpm.new_repo(organization, auth)

assert {:ok, {204, _, _}} =
Expand All @@ -81,17 +81,4 @@ defmodule Hex.OrganizationAuthIntegrationTest do
assert decoded =~ "repository:#{organization}"
end)
end

defp totp(secret) do
counter = div(System.system_time(:second), 30) + 1
digest = :crypto.mac(:hmac, :sha, Base.decode32!(secret), <<counter::64>>)
offset = Bitwise.band(:binary.last(digest), 15)
<<code::32>> = :binary.part(digest, offset, 4)

code
|> Bitwise.band(0x7FFFFFFF)
|> rem(1_000_000)
|> Integer.to_string()
|> String.pad_leading(6, "0")
end
end
37 changes: 18 additions & 19 deletions test/mix/tasks/hex.organization_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ defmodule Mix.Tasks.Hex.OrganizationTest do
test "auth" do
in_tmp(fn ->
set_home_cwd()
auth = Hexpm.new_user("orgauth", "orgauth@mail.com", "password", "key")
Hex.State.put(:api_key, auth[:key])
auth = Hexpm.new_tfa_user("orgauth", "orgauth@mail.com", "password")
Hexpm.sign_in(auth)
Hexpm.new_repo("myorgauth", auth)

send(self(), {:mix_shell_input, :yes?, true})
Expand All @@ -29,8 +29,8 @@ defmodule Mix.Tasks.Hex.OrganizationTest do
test "auth without --key warns about deprecation" do
in_tmp(fn ->
set_home_cwd()
auth = Hexpm.new_user("orgauthdeprecated", "orgauthdeprecated@mail.com", "password", "key")
Hex.State.put(:api_key, auth[:key])
auth = Hexpm.new_tfa_user("orgauthdeprecated", "orgauthdeprecated@mail.com", "password")
Hexpm.sign_in(auth)
Hexpm.new_repo("myorgauthdeprecated", auth)

send(self(), {:mix_shell_input, :yes?, true})
Expand All @@ -55,10 +55,8 @@ defmodule Mix.Tasks.Hex.OrganizationTest do
in_tmp(fn ->
set_home_cwd()

auth =
Hexpm.new_user("orgauthwithkeyname", "orgauthwithkeyname@mail.com", "password", "key")

Hex.State.put(:api_key, auth[:key])
auth = Hexpm.new_tfa_user("orgauthwithkeyname", "orgauthwithkeyname@mail.com", "password")
Hexpm.sign_in(auth)

Hexpm.new_repo("myorgauthwithkeyname", auth)

Expand Down Expand Up @@ -87,7 +85,7 @@ defmodule Mix.Tasks.Hex.OrganizationTest do
test "auth --key" do
in_tmp(fn ->
set_home_cwd()
auth = Hexpm.new_user("orgauthkey", "orgauthkey@mail.com", "password", "key")
auth = Hexpm.new_tfa_user("orgauthkey", "orgauthkey@mail.com", "password")
Hexpm.new_repo("myorgauthkey", auth)

parameters = [%{"domain" => "repository", "resource" => "myorgauthkey"}]
Expand Down Expand Up @@ -219,13 +217,14 @@ defmodule Mix.Tasks.Hex.OrganizationTest do
test "deauth" do
in_tmp(fn ->
set_home_cwd()
auth = Hexpm.new_user("orgdeauth", "orgdeauth@mail.com", "password", "key")
Hex.State.put(:api_key, auth[:key])
auth = Hexpm.new_tfa_user("orgdeauth", "orgdeauth@mail.com", "password")
Hexpm.sign_in(auth)
Hexpm.new_repo("myorgdeauth", auth)

send(self(), {:mix_shell_input, :yes?, true})
send(self(), {:mix_shell_input, :prompt, "password"})
Mix.Tasks.Hex.Organization.run(["auth", "myorgdeauth"])
assert Hex.Config.read_repos(Hex.Config.read())["hexpm:myorgdeauth"]

Mix.Tasks.Hex.Organization.run(["deauth", "myorgdeauth"])
refute Hex.Config.read_repos(Hex.Config.read())["hexpm:myorgdeauth"]
Expand All @@ -236,8 +235,8 @@ defmodule Mix.Tasks.Hex.OrganizationTest do
in_tmp(fn ->
set_home_cwd()

auth = Hexpm.new_user("orgkeygenuser", "orgkeygenuser@mail.com", "password", "key")
Hex.State.put(:api_key, auth[:key])
auth = Hexpm.new_tfa_user("orgkeygenuser", "orgkeygenuser@mail.com", "password")
Hexpm.sign_in(auth)

Hexpm.new_repo("orgkeygenrepo", auth)

Expand Down Expand Up @@ -265,8 +264,8 @@ defmodule Mix.Tasks.Hex.OrganizationTest do
in_tmp(fn ->
set_home_cwd()

auth = Hexpm.new_user("orgkeylistuser", "orgkeylistuser@mail.com", "password", "key")
Hex.State.put(:api_key, auth[:key])
auth = Hexpm.new_tfa_user("orgkeylistuser", "orgkeylistuser@mail.com", "password")
Hexpm.sign_in(auth)

Hexpm.new_repo("orgkeylistrepo", auth)

Expand All @@ -287,8 +286,8 @@ defmodule Mix.Tasks.Hex.OrganizationTest do
in_tmp(fn ->
set_home_cwd()

auth = Hexpm.new_user("orgkeyrevokeyuser", "orgkeyrevokeyuser@mail.com", "password", "key")
Hex.State.put(:api_key, auth[:key])
auth = Hexpm.new_tfa_user("orgkeyrevokeyuser", "orgkeyrevokeyuser@mail.com", "password")
Hexpm.sign_in(auth)

Hexpm.new_repo("orgkeyrevokerepo", auth)
Hexpm.new_organization_key("orgkeyrevokerepo", "orgkeyrevokerepo2", auth)
Expand All @@ -310,9 +309,9 @@ defmodule Mix.Tasks.Hex.OrganizationTest do
set_home_cwd()

auth =
Hexpm.new_user("orgkeyrevokealluser", "orgkeyrevokealluser@mail.com", "password", "key")
Hexpm.new_tfa_user("orgkeyrevokealluser", "orgkeyrevokealluser@mail.com", "password")

Hex.State.put(:api_key, auth[:key])
Hexpm.sign_in(auth)

Hexpm.new_repo("orgkeyrevokeallrepo", auth)
Hexpm.new_organization_key("orgkeyrevokeallrepo", "orgkeyrevokeallrepo2", auth)
Expand Down
56 changes: 56 additions & 0 deletions test/support/hexpm.ex
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,62 @@ defmodule HexTest.Hexpm do
[key: secret]
end

@doc """
Creates a user with two-factor authentication and an OAuth token for it, the
sign-in `mix hex.user auth` gives. The auth carries the user's current TOTP
code, which writes made with an OAuth token need.
"""
def new_tfa_user(username, email, password) do
secret = Base.encode32(:crypto.strong_rand_bytes(10))
config = Hex.API.Client.config()

{:ok, {201, _, _}} =
:mix_hex_api.post(config, ["user"], %{
"username" => to_string(username),
"email" => to_string(email),
"password" => to_string(password),
"tfa" => %{"secret" => secret}
})

{:ok, {200, _, response}} =
:mix_hex_api.post(config, ["oauth_token"], %{
"username" => to_string(username),
"scope" => "api repositories"
})

[
key: response["access_token"],
oauth: true,
otp: totp(secret),
"$oauth_token": %{
access_token: response["access_token"],
refresh_token: response["refresh_token"],
expires_at: System.system_time(:second) + response["expires_in"]
}
]
end

@doc """
Signs Hex in with an auth from `new_tfa_user/3`, as `mix hex.user auth` does.
"""
def sign_in(auth) do
Hex.OAuth.store_token(auth[:"$oauth_token"])
Hex.State.put(:api_otp, auth[:otp])
end

def totp(secret) do
counter = div(System.system_time(:second), 30) + 1
digest = :crypto.mac(:hmac, :sha, Base.decode32!(secret), <<counter::64>>)
offset = Bitwise.band(:binary.last(digest), 15)
<<code::32>> = :binary.part(digest, offset, 4)

code
|> Bitwise.band(0x7FFFFFFF)
|> rem(1_000_000)
|> Integer.to_string()
|> String.pad_leading(6, "0")
end

def new_organization_key(organization, key, auth) do
permissions = [%{"domain" => "api"}]

Expand Down
Loading