BTCMarkets / API

API
119 stars 30 forks source link

Authentication failed using ruby #126

Closed vgunawan closed 6 years ago

vgunawan commented 6 years ago

Hi, I keep getting {"success"=>false, "errorCode"=>1, "errorMessage"=>"Authentication failed."} message while trying to use account balance API. I chose it because it seemed to be one of the basic one.

I also have followed the instruction in readme and I manage to get the same signature for both /account/balance and /order/history. Public/Private key is also correct. Am I missing something obvious? like maybe have to enable the API somehow? Please help

Here is the request/response log

I, [2018-03-11T11:06:37.515826 #60904]  INFO -- : GET https://api.btcmarkets.net/account/balance
D, [2018-03-11T11:06:37.515954 #60904] DEBUG -- : "Accept: application/json\nAccept-Charset: UTF-8\nContent-Type: application/json\napikey: 0f1903de-03d7-40fd-81ad-74749380c284\ntimestamp: 1520726797515\nsignature: g8+ITtdGbPGGEvnz+dPv2MszHxfG7UkjmNp+VchHod6scydTAzVSjGfkiRwr0kJglA8LgY2tBzWc6ZH2r20cYQ==\n\n"
I, [2018-03-11T11:06:37.621444 #60904]  INFO -- : HTTP 200
D, [2018-03-11T11:06:37.621556 #60904] DEBUG -- : "date: Sun, 11 Mar 2018 00:06:37 GMT\ncontent-type: application/json;charset=UTF-8\ntransfer-encoding: chunked\nconnection: close\nset-cookie: __cfduid=dcd6309ae4c4634b2c850fc473054c2a21520726797; expires=Mon, 11-Mar-19 00:06:37 GMT; path=/; domain=.btcmarkets.net; HttpOnly\nexpect-ct: max-age=604800, report-uri=\"https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\"\nserver: cloudflare\ncf-ray: 3f99c6b51d8451eb-MEL\n\n{\"success\":false,\"errorCode\":1,\"errorMessage\":\"Authentication failed.\"}"

Here is the ruby snippets

    ....
    BASE_URL = 'https://api.btcmarkets.net'.freeze
    ACCOUNT_BALANCE_PATH = "account/balance".freeze
    ACCOUNT_BALANCE_URL = "#{BASE_URL}/#{ACCOUNT_BALANCE_PATH}".freeze

    ....

    def account_balance
      timestamp_millis = DateTime.now.strftime('%Q') #returns time in millis as a string

      body = @connection.get do |req|
        req.url ACCOUNT_BALANCE_PATH

        req.headers = {
          "Accept" => "application/json",
          "Accept-Charset" => "UTF-8",
          "Content-Type" => "application/json",
          "apikey" => public_key,
          "timestamp" => timestamp_millis,
          "signature" => sign(ACCOUNT_BALANCE_PATH, timestamp_millis),
        }
      end.body

      body
    end

    private

    def sign(uri, timestamp_millis, post_data: nil)
      data = "#{uri}\n#{timestamp_millis}\n"
      data += post_data unless post_data.nil? || post_data.empty?

      hmac_sha_512_base64(data)
    end

    def hmac_sha_512_base64(data)
      @digest ||= OpenSSL::Digest.new('sha512')
      Base64.strict_encode64(OpenSSL::HMAC.digest(@digest, private_key, data))
    end

    def private_key
      @private_key ||= Base64.strict_decode64(File.read(PRIVATE_KEY_PATH))
    end

    def public_key
      @public_key ||= File.read(PUBLIC_KEY_PATH)
    end
justin-ngin commented 6 years ago

Hi @vgunawan ,

The most obvious thing I can see right now without fully debugging your code is that you are missing a / in the path. the string that you use to create your hmac needs the leading / in order to be formatted correctly. I'm not sure how you're replicating the example signatures without it. If correcting this (and ensuring that your machine is connected to a unix time server) doesn't get you past authentication, please let me know and I will dig deeper into your code.

Cheers, Justin

vgunawan commented 6 years ago

Wow yeah that's it! Spent good hour on it. Cheers @justin-ngin!

Also if I could suggest, create code example for various programming languages, sure if anyone could copy paste example, it will reduce the repetitive questions. I am happy to do for Ruby or Elixir.