From 5a57f82790a617d67666282858fd764eec104d20 Mon Sep 17 00:00:00 2001 From: Franz Bettag Date: Tue, 31 Oct 2023 18:23:29 +0100 Subject: [PATCH 1/7] enables the use of per-request api-key/secret-key passing while keeping backward compatibility. --- lib/binance.ex | 6 ++++-- lib/binance/rest/http_client.ex | 16 ++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/binance.ex b/lib/binance.ex index f6ba311..58ca848 100644 --- a/lib/binance.ex +++ b/lib/binance.ex @@ -154,7 +154,9 @@ docs binding = binding() # merge all passed args together, so opts + passed - all_passed_args = Keyword.merge(binding, opts) |> Keyword.drop([:opts]) + all_passed_args = Keyword.merge(binding, opts) |> Keyword.drop([:opts, :api_key, :secret_key]) + api_key = Keyword.get(opts, :api_key) || Application.get_env(:binance, :api_key, "") + secret_key = Keyword.get(opts, :secret_key) || Application.get_env(:binance, :secret_key, "") # if the call requires a timestamp, we add it adjusted_args = @@ -185,7 +187,7 @@ docs case unquote(needs_auth) do true -> - case HTTPClient.signed_request_binance(unquote(url), adjusted_args, unquote(method)) do + case HTTPClient.signed_request_binance(api_key, secret_key, unquote(url), adjusted_args, unquote(method)) do {:ok, %{"code" => _code, "msg" => _msg} = err} -> {:error, Binance.Helper.format_error(err)} diff --git a/lib/binance/rest/http_client.ex b/lib/binance/rest/http_client.ex index 9a07bda..6e53d72 100644 --- a/lib/binance/rest/http_client.ex +++ b/lib/binance/rest/http_client.ex @@ -33,7 +33,7 @@ defmodule Binance.Rest.HTTPClient do end end - defp request_binance(url, body, method) do + defp request_binance(api_key, url, body, method) do url = URI.parse("#{endpoint()}#{url}") encoded_url = @@ -48,7 +48,7 @@ defmodule Binance.Rest.HTTPClient do HTTPoison.get( URI.to_string(encoded_url), [ - {"X-MBX-APIKEY", Application.get_env(:binance, :api_key)} + {"X-MBX-APIKEY", api_key}, ] ) @@ -56,7 +56,7 @@ defmodule Binance.Rest.HTTPClient do HTTPoison.delete( URI.to_string(encoded_url), [ - {"X-MBX-APIKEY", Application.get_env(:binance, :api_key)} + {"X-MBX-APIKEY", api_key}, ] ) @@ -65,7 +65,7 @@ defmodule Binance.Rest.HTTPClient do URI.to_string(encoded_url), "", [ - {"X-MBX-APIKEY", Application.get_env(:binance, :api_key)} + {"X-MBX-APIKEY", api_key}, ] ]) end @@ -81,7 +81,7 @@ defmodule Binance.Rest.HTTPClient do end end - def signed_request_binance(url, params, method) do + def signed_request_binance(api_key, secret_key, url, params, method) do argument_string = params |> prepare_query_params() @@ -90,14 +90,14 @@ defmodule Binance.Rest.HTTPClient do signature = generate_signature( :sha256, - Application.get_env(:binance, :secret_key), + secret_key, argument_string ) |> Base.encode16() body = "#{argument_string}&signature=#{signature}" - request_binance(url, body, method) + request_binance(api_key, url, body, method) end @doc """ @@ -110,7 +110,7 @@ defmodule Binance.Rest.HTTPClient do data |> prepare_query_params() - request_binance(url, argument_string, method) + request_binance("", url, argument_string, method) end defp validate_credentials(nil, nil), From 8c96c68d685b9b2f4434bda7f7c10fbb0c2432d3 Mon Sep 17 00:00:00 2001 From: David Mohl Date: Fri, 3 Nov 2023 12:55:47 +0900 Subject: [PATCH 2/7] Refactor fx signature for api_key, secret_key --- lib/binance.ex | 23 ++++++++++++++++---- lib/binance/rest/http_client.ex | 37 +++++++++++++++++++++++---------- 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/lib/binance.ex b/lib/binance.ex index 58ca848..db11734 100644 --- a/lib/binance.ex +++ b/lib/binance.ex @@ -78,6 +78,10 @@ docs |> Enum.map(fn param -> %{name: param.name, description: param.description} end) + |> Kernel.++([ + %{name: "api_key", description: "Binance API key, will overwrite env key"}, + %{name: "api_secret", description: "Binance API secret, will overwrite env secret"} + ]) optional_params = case needs_timestamp do @@ -154,9 +158,14 @@ docs binding = binding() # merge all passed args together, so opts + passed - all_passed_args = Keyword.merge(binding, opts) |> Keyword.drop([:opts, :api_key, :secret_key]) - api_key = Keyword.get(opts, :api_key) || Application.get_env(:binance, :api_key, "") - secret_key = Keyword.get(opts, :secret_key) || Application.get_env(:binance, :secret_key, "") + all_passed_args = + Keyword.merge(binding, opts) |> Keyword.drop([:opts, :api_key, :secret_key]) + + api_key = + Keyword.get(opts, :api_key) || Application.get_env(:binance, :api_key, "") + + secret_key = + Keyword.get(opts, :secret_key) || Application.get_env(:binance, :secret_key, "") # if the call requires a timestamp, we add it adjusted_args = @@ -187,7 +196,13 @@ docs case unquote(needs_auth) do true -> - case HTTPClient.signed_request_binance(api_key, secret_key, unquote(url), adjusted_args, unquote(method)) do + case HTTPClient.signed_request_binance( + unquote(url), + api_key, + secret_key, + adjusted_args, + unquote(method) + ) do {:ok, %{"code" => _code, "msg" => _msg} = err} -> {:error, Binance.Helper.format_error(err)} diff --git a/lib/binance/rest/http_client.ex b/lib/binance/rest/http_client.ex index 6e53d72..a149db5 100644 --- a/lib/binance/rest/http_client.ex +++ b/lib/binance/rest/http_client.ex @@ -33,7 +33,7 @@ defmodule Binance.Rest.HTTPClient do end end - defp request_binance(api_key, url, body, method) do + defp request_binance(url, api_key, body, method) do url = URI.parse("#{endpoint()}#{url}") encoded_url = @@ -48,7 +48,7 @@ defmodule Binance.Rest.HTTPClient do HTTPoison.get( URI.to_string(encoded_url), [ - {"X-MBX-APIKEY", api_key}, + {"X-MBX-APIKEY", api_key} ] ) @@ -56,7 +56,7 @@ defmodule Binance.Rest.HTTPClient do HTTPoison.delete( URI.to_string(encoded_url), [ - {"X-MBX-APIKEY", api_key}, + {"X-MBX-APIKEY", api_key} ] ) @@ -65,7 +65,7 @@ defmodule Binance.Rest.HTTPClient do URI.to_string(encoded_url), "", [ - {"X-MBX-APIKEY", api_key}, + {"X-MBX-APIKEY", api_key} ] ]) end @@ -81,10 +81,12 @@ defmodule Binance.Rest.HTTPClient do end end - def signed_request_binance(api_key, secret_key, url, params, method) do - argument_string = - params - |> prepare_query_params() + def signed_request_binance(url, api_key, secret_key, params, method) do + case validate_credentials(secret_key, api_key) do + :ok -> + argument_string = + params + |> prepare_query_params() # generate signature signature = @@ -95,9 +97,13 @@ defmodule Binance.Rest.HTTPClient do ) |> Base.encode16() - body = "#{argument_string}&signature=#{signature}" + body = "#{argument_string}&signature=#{signature}" - request_binance(api_key, url, body, method) + request_binance(url, api_key, body, method) + + e -> + e + end end @doc """ @@ -110,18 +116,27 @@ defmodule Binance.Rest.HTTPClient do data |> prepare_query_params() - request_binance("", url, argument_string, method) + request_binance(url, "", argument_string, method) end defp validate_credentials(nil, nil), do: {:error, {:config_missing, "Secret and API key missing"}} + defp validate_credentials("", ""), + do: {:error, {:config_missing, "Secret and API key missing"}} + defp validate_credentials(nil, _api_key), do: {:error, {:config_missing, "Secret key missing"}} + defp validate_credentials("", _api_key), + do: {:error, {:config_missing, "Secret key missing"}} + defp validate_credentials(_secret_key, nil), do: {:error, {:config_missing, "API key missing"}} + defp validate_credentials(_secret_key, ""), + do: {:error, {:config_missing, "API key missing"}} + defp validate_credentials(_secret_key, _api_key), do: :ok From 751abb02de16bd9258542246671799f69e97a3a6 Mon Sep 17 00:00:00 2001 From: David Mohl Date: Fri, 3 Nov 2023 12:55:59 +0900 Subject: [PATCH 3/7] Add test for when no params are passed --- fixture/vcr_cassettes/api_key_missing.json | 1 + test/binance_test.exs | 26 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 fixture/vcr_cassettes/api_key_missing.json diff --git a/fixture/vcr_cassettes/api_key_missing.json b/fixture/vcr_cassettes/api_key_missing.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/fixture/vcr_cassettes/api_key_missing.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/binance_test.exs b/test/binance_test.exs index fc489ae..bbadbf1 100644 --- a/test/binance_test.exs +++ b/test/binance_test.exs @@ -7,12 +7,38 @@ defmodule BinanceTest do HTTPoison.start() end + setup do + Application.put_env(:binance, :api_key, "api_key") + Application.put_env(:binance, :secret_key, "api_key") + end + test "ping returns an empty map" do use_cassette "ping_ok" do assert Binance.Market.get_ping() == {:ok, %{}} end end + test "return err when no key or secret" do + use_cassette "api_key_missing" do + Application.put_env(:binance, :api_key, "") + Application.put_env(:binance, :secret_key, "") + + assert Binance.Trade.post_order("LTCBTC", "BUY", "LIMIT", quantity: 0.1, price: 0.01) == + {:error, {:config_missing, "Secret and API key missing"}} + + Application.put_env(:binance, :secret_key, "secret") + + assert Binance.Trade.post_order("LTCBTC", "BUY", "LIMIT", quantity: 0.1, price: 0.01) == + {:error, {:config_missing, "API key missing"}} + + Application.put_env(:binance, :secret_key, "") + Application.put_env(:binance, :api_key, "apikey") + + assert Binance.Trade.post_order("LTCBTC", "BUY", "LIMIT", quantity: 0.1, price: 0.01) == + {:error, {:config_missing, "Secret key missing"}} + end + end + test "get_server_time success return an ok, time tuple" do use_cassette "get_server_time_ok" do assert Binance.Market.get_time() == {:ok, 1_616_592_268_319} From 1f7691baeca602eba145c3a6790b48ac3a772a2e Mon Sep 17 00:00:00 2001 From: David Mohl Date: Fri, 3 Nov 2023 13:24:55 +0900 Subject: [PATCH 4/7] Add a test to see if api_key is correctly propagated --- ...pen_orders_for_api_key_and_secret_key.json | 44 +++++++++++++++++++ test/binance_test.exs | 13 ++++++ 2 files changed, 57 insertions(+) create mode 100644 fixture/vcr_cassettes/get_open_orders_for_api_key_and_secret_key.json diff --git a/fixture/vcr_cassettes/get_open_orders_for_api_key_and_secret_key.json b/fixture/vcr_cassettes/get_open_orders_for_api_key_and_secret_key.json new file mode 100644 index 0000000..bb89acb --- /dev/null +++ b/fixture/vcr_cassettes/get_open_orders_for_api_key_and_secret_key.json @@ -0,0 +1,44 @@ +[ + { + "request": { + "body": "", + "headers": { + "X-MBX-APIKEY": "dummy_api_key" + }, + "method": "get", + "options": [], + "request_body": "", + "url": "https://api.binance.com/api/v3/openOrders?timestamp=1698985036203&signature=***" + }, + "response": { + "binary": false, + "body": "{\"code\":-2014,\"msg\":\"API-key format invalid.\"}", + "headers": { + "Content-Type": "application/json;charset=UTF-8", + "Content-Length": "46", + "Connection": "keep-alive", + "Date": "Fri, 03 Nov 2023 04:17:16 GMT", + "Server": "nginx", + "x-mbx-uuid": "b2c45e59-9b6d-470a-9c78-962dbfa17d9d", + "x-mbx-used-weight": "80", + "x-mbx-used-weight-1m": "80", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains", + "X-Frame-Options": "SAMEORIGIN", + "X-Xss-Protection": "1; mode=block", + "X-Content-Type-Options": "nosniff", + "Content-Security-Policy": "default-src 'self'", + "X-Content-Security-Policy": "default-src 'self'", + "X-WebKit-CSP": "default-src 'self'", + "Cache-Control": "no-cache, no-store, must-revalidate", + "Pragma": "no-cache", + "Expires": "0", + "X-Cache": "Error from cloudfront", + "Via": "1.1 12632bbc89afe55228d7f1ab9e5993a6.cloudfront.net (CloudFront)", + "X-Amz-Cf-Pop": "NRT57-C3", + "X-Amz-Cf-Id": "vAUtPe6jUC-CzKpGocdbeFjnnILu7o6w9tNsBybIiHQwNO4xvETT_g==" + }, + "status_code": 401, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/test/binance_test.exs b/test/binance_test.exs index bbadbf1..af68f6a 100644 --- a/test/binance_test.exs +++ b/test/binance_test.exs @@ -59,6 +59,19 @@ defmodule BinanceTest do end end + test "api_key and secret_key are correctly passed to request" do + existing_setting = ExVCR.Setting.get(:filter_request_headers) + ExVCR.Config.filter_request_headers(nil) + + use_cassette "get_open_orders_for_api_key_and_secret_key", + match_requests_on: [:headers, :request_body] do + assert {:error, {:binance_error, %{code: -2014, msg: "API-key format invalid."}}} = + Binance.Trade.get_open_orders(api_key: "dummy_api_key", api_secret: "hoge") + end + + ExVCR.Setting.set(:filter_request_headers, existing_setting) + end + test "get_exchange_info success returns the trading rules and symbol information" do use_cassette "get_exchange_info_ok" do assert {:ok, %Binance.Structs.ExchangeInfo{} = info} = Binance.Market.get_exchange_info() From d818353f4df083efb61338b5b6d2cb59d1edd817 Mon Sep 17 00:00:00 2001 From: David Mohl Date: Fri, 3 Nov 2023 13:26:05 +0900 Subject: [PATCH 5/7] Add test for env propagation --- test/binance_test.exs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/binance_test.exs b/test/binance_test.exs index af68f6a..53f2f40 100644 --- a/test/binance_test.exs +++ b/test/binance_test.exs @@ -67,6 +67,11 @@ defmodule BinanceTest do match_requests_on: [:headers, :request_body] do assert {:error, {:binance_error, %{code: -2014, msg: "API-key format invalid."}}} = Binance.Trade.get_open_orders(api_key: "dummy_api_key", api_secret: "hoge") + + Application.put_env(:binance, :api_key, "dummy_api_key") + + assert {:error, {:binance_error, %{code: -2014, msg: "API-key format invalid."}}} = + Binance.Trade.get_open_orders() end ExVCR.Setting.set(:filter_request_headers, existing_setting) From 285ae325ad1474690e726525d577df0e34b75b57 Mon Sep 17 00:00:00 2001 From: David Mohl Date: Fri, 3 Nov 2023 13:29:05 +0900 Subject: [PATCH 6/7] Rename api_secret -> secret_key for consistency --- lib/binance.ex | 2 +- test/binance_test.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/binance.ex b/lib/binance.ex index db11734..4e477ab 100644 --- a/lib/binance.ex +++ b/lib/binance.ex @@ -80,7 +80,7 @@ docs end) |> Kernel.++([ %{name: "api_key", description: "Binance API key, will overwrite env key"}, - %{name: "api_secret", description: "Binance API secret, will overwrite env secret"} + %{name: "secret_key", description: "Binance API secret, will overwrite env secret"} ]) optional_params = diff --git a/test/binance_test.exs b/test/binance_test.exs index 53f2f40..4c1d627 100644 --- a/test/binance_test.exs +++ b/test/binance_test.exs @@ -66,7 +66,7 @@ defmodule BinanceTest do use_cassette "get_open_orders_for_api_key_and_secret_key", match_requests_on: [:headers, :request_body] do assert {:error, {:binance_error, %{code: -2014, msg: "API-key format invalid."}}} = - Binance.Trade.get_open_orders(api_key: "dummy_api_key", api_secret: "hoge") + Binance.Trade.get_open_orders(api_key: "dummy_api_key", secret_key: "hoge") Application.put_env(:binance, :api_key, "dummy_api_key") From 3254b38c97e3df08f730ca4d045add8f89ed48e1 Mon Sep 17 00:00:00 2001 From: David Mohl Date: Fri, 3 Nov 2023 13:32:59 +0900 Subject: [PATCH 7/7] Remove unused file --- fixture/vcr_cassettes/api_key_missing.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 fixture/vcr_cassettes/api_key_missing.json diff --git a/fixture/vcr_cassettes/api_key_missing.json b/fixture/vcr_cassettes/api_key_missing.json deleted file mode 100644 index 0637a08..0000000 --- a/fixture/vcr_cassettes/api_key_missing.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file