Reputation: 9823
I'm trying to generate Walmart's API WM_SEC.AUTH_SIGNATURE
header.
I found a lot of examples in Ruby and Python, but no examples in Elixir. I've been trying to create one and try it out.
Here's what I attempted.
# mix.exs
{:ex_crypto, git: "https://github.com/ntrepid8/ex_crypto.git", branch: "master"}
def scrape_store_item(store_item) do
consumer_id = "my-consumer-id"
private_key_version = "1"
private_key_password = "my-private-key-password"
private_key =
Application.app_dir(:my_app, "priv/keys/walmart/WM_IO_private_key.pem")
|> ExPublicKey.load!(private_key_password)
timestamp = DateTime.utc_now() |> DateTime.to_unix()
url = "https://developer.api.walmart.com/api-proxy/service/affil/product/v2/taxonomy"
message = "#{consumer_id}\n#{timestamp}\n#{private_key_version}\n"
encoded_message = Base.encode64(message)
{:ok, auth_signature} = ExPublicKey.sign(encoded_message, private_key)
auth_signature = Base.encode64(auth_signature)
middleware = [
{Tesla.Middleware.BaseUrl,
"https://developer.api.walmart.com/api-proxy/service/affil/product/v2"},
{Tesla.Middleware.Headers,
[
{"WM_CONSUMER.ID", consumer_id},
{"WM_CONSUMER.INTIMESTAMP", timestamp},
{"WM_SEC.KEY_VERSION", private_key_version},
{"WM_SEC.AUTH_SIGNATURE", auth_signature},
{"WM_SHOW_REASON_CODES", "ALL"},
{"Content-Type", "application/json"}
]}
]
client = Tesla.client(middleware)
{:ok, response} = Tesla.get(client, "/taxonomy") |> IO.inspect()
IO.inspect(response.body)
end
I get a 401 response with this code:
{:ok,
%Tesla.Env{
__client__: %Tesla.Client{
adapter: nil,
fun: nil,
post: [],
pre: [
{Tesla.Middleware.BaseUrl, :call,
["https://developer.api.walmart.com/api-proxy/service/affil/product/v2"]},
{Tesla.Middleware.Headers, :call,
[
[
{"WM_CONSUMER.ID", "my-consumer-id"},
{"WM_CONSUMER.INTIMESTAMP", 1654443660},
{"WM_SEC.KEY_VERSION", "1"},
{"WM_SEC.AUTH_SIGNATURE",
"kdXG+e6R/n+8pH1ha1WKnzLrAHbUqmJsZfN9nOIyOzp6gsHAH7/VrX0K477cdzAq/v7YLpNJXZug3Yt6WTZoP17sZhz6Dig1BK1gg+EZqVqRaF3VJdRwBKlVgBO31s634xL7M8kPhXK11CsMxG8/9xjTGn2cDKEZ9aLeq15ECIfYa5tVtCdTcjNS4u6a7npByU9PIFp9a7n3h1KbW9C/9EA05kTuC1N0oS8nBlnKbA2+C0UW9EAvN4MaIkG0SqOqf/uEHn9BteAv8hI0Ayyny9RpJQmfZEpZ0G3htA7t1pWTzwxUsIJrF/5D1gV+IIYR7OiwHUg2RsIrnPohbznPQw=="}
]
]}
]
},
__module__: Tesla,
body: <<31, 139, 8, 0, 0, 9, 110, 136, 0, 255, 68, 204, 65, 138, 131, 64, 16,
70, 225, 171, 20, 255, 122, 70, 156, 25, 199, 69, 175, 115, 132, 236, 67,
97, 151, 177, 192, 110, 155, 174, 210, 4, 196, 187, 135, ...>>,
headers: [
{"content-encoding", "gzip"},
{"content-type", "application/json;charset=utf-8"},
{"last-modified", "Sun, 05 Jun 2022 15:41:00 GMT"},
{"strict-transport-security", "max-age=86400"},
{"wm_svc.env", "prod"},
{"wm_svc.name", "affil-product"},
{"wm_svc.version", "2.0.0"},
{"x-lua-strict-transport-security", "max-age=86400"},
{"x-tb", "1"},
{"x-tb-optimization-total-bytes-saved", "0"},
{"date", "Sun, 05 Jun 2022 15:41:00 GMT"},
{"connection", "close"},
{"set-cookie",
"TS01a35e2a=01c5a4e2f95f0b472a3a7606aa7c7c33653874c13d636655443ecbca84d23369b19bc1de1973ac24c93ff1f24512e7af49264d46c6; Path=/; Secure"}
],
method: :get,
opts: [],
query: [],
status: 401,
url: "https://developer.api.walmart.com/api-proxy/service/affil/product/v2/taxonomy"
}}
Here's the example code someone shared for a working Ruby version.
version = 'YOUR VERSION'
consumer_id = "YOUR CONSUMER ID"
time_stamp = (Time.now.to_i * 1000).to_s
p_key = "YOUR PRIVATE KEY"
digest = OpenSSL::Digest.new('sha256')
data = consumer_id + "\n" + time_stamp + "\n" + version + "\n"
k = OpenSSL::PKey::RSA.new(p_key.to_s)
digest = OpenSSL::Digest::SHA256.new
signature = k.sign(digest,data)
signature = Base64.strict_encode64(signature)
headers = {
"WM_SEC.KEY_VERSION": version,
"WM_CONSUMER.ID": consumer_id,
"WM_CONSUMER.INTIMESTAMP": time_stamp,
"WM_SEC.AUTH_SIGNATURE": signature
}
puts HTTParty.get("https://developer.api.walmart.com/api-proxy/service/affil/product/v2/taxonomy", headers: headers).parsed_response
Upvotes: 1
Views: 168
Reputation: 23091
Here's a script that does it without ex_crypto
. Nice challenge, those Wallmart docs are terrible.
Mix.install([:tesla])
key_version = System.fetch_env!("WALLMART_KEY_VERSION")
consumer_id = System.fetch_env!("WALLMART_CONSUMER_ID")
private_key_pem = File.read!("WM_IO_private_key.pem")
[pem_entry] = :public_key.pem_decode(private_key_pem)
private_key = :public_key.pem_entry_decode(pem_entry)
timestamp = System.os_time(:millisecond)
auth_signature =
"#{consumer_id}\n#{timestamp}\n#{key_version}\n"
|> :public_key.sign(:sha256, private_key)
|> Base.encode64()
url = "https://developer.api.walmart.com/api-proxy/service/affil/product/v2/taxonomy"
headers = [
{"WM_CONSUMER.ID", consumer_id},
{"WM_CONSUMER.INTIMESTAMP", timestamp},
{"WM_SEC.KEY_VERSION", key_version},
{"WM_SEC.AUTH_SIGNATURE", auth_signature}
]
{:ok, %{body: body}} = Tesla.get(url, headers: headers)
IO.puts(body)
Output:
{"categories":[{"id":"0","name":"Home Page","path":"Home Page",...
Upvotes: 2
Reputation: 36743
Here's how you generate WM_SEC.AUTH_SIGNATURE in Elixir:
Make sure you have ex_crypto
package installed from master branch since the latest version has necessary changes but is not published.
{:ex_crypto, git: "https://github.com/ntrepid8/ex_crypto.git", branch: "master"}
Then here's the solution:
version = "1"
consumer_id = "my-consumer-id"
timestamp = DateTime.utc_now() |> DateTime.to_unix(:millisecond)
data = "#{consumer_id}\n#{timestamp}\n#{version}\n"
private_key =
Application.app_dir(:my_app, "priv/keys/walmart/WM_IO_private_key.pem")
|> ExPublicKey.load!()
{:ok, auth_signature} = ExPublicKey.sign(data, private_key)
auth_signature = Base.encode64(auth_signature)
middleware = [
{Tesla.Middleware.BaseUrl,
"https://developer.api.walmart.com/api-proxy/service/affil/product/v2"},
{Tesla.Middleware.Headers,
[
{"WM_CONSUMER.ID", consumer_id},
{"WM_CONSUMER.INTIMESTAMP", timestamp},
{"WM_SEC.KEY_VERSION", version},
{"WM_SEC.AUTH_SIGNATURE", auth_signature}
]}
]
client = Tesla.client(middleware)
Tesla.get(client, "/taxonomy")
Upvotes: 0