lib/tencent_cloud_core.ex

defmodule TencentCloud.Core do
  @moduledoc """
  Common functions for TencentCloud.
  """

  @doc """
  Gen Common Headers

  ## Examples

      iex> TencentCloud.Core.common_headers(
      ...>   %{
      ...>     host: "nlp.tencentcloudapi.com",
      ...>     action: "ChatBot",
      ...>     version: "2019-04-08",
      ...>     region: "ap-guangzhou",
      ...>     date: "2021-09-27",
      ...>     timestamp: 1632706407,
      ...>     access_key_id: "GebjUWWJJVnL1en1gtgb52CmNJN8CqdKC0ne",
      ...>     access_key_secret: "QtXXc6HftX/LEwrSi+MR4OyVEnEslwyg"
      ...>   },
      ...>   ~s({"Query":"你好,红领巾,我叫白胡子"})
      ...> )
      [
        {"X-TC-Action", "ChatBot"},
        {"X-TC-Region", "ap-guangzhou"},
        {"X-TC-Timestamp", "1632706407"},
        {"X-TC-Version", "2019-04-08"},
        {"Authorization", "TC3-HMAC-SHA256 Credential=GebjUWWJJVnL1en1gtgb52CmNJN8CqdKC0ne/2021-09-27/nlp/tc3_request, SignedHeaders=content-type;host, Signature=3ec0d092a50635e6bdfcf051750d55dca5bc39e270348d8e46f928d50d3ed833"},
        {"Content-Type", "application/json; charset=utf-8"},
        {"Host", "nlp.tencentcloudapi.com"}
      ]

  """
  @spec common_headers(common :: map, body :: binary) :: list
  def common_headers(
        %{
          host: host,
          action: action,
          version: version,
          region: region,
          access_key_id: secret_id,
          access_key_secret: secret_key
        } = common,
        body
      ) do
    content_type = Map.get(common, :content_type, "application/json; charset=utf-8")
    timestamp = Map.get_lazy(common, :timestamp, fn -> System.system_time(:second) end) |> to_string()

    date =
      Map.get_lazy(common, :date, fn -> :erlang.date() |> Date.from_erl!() |> Date.to_string() end)

    service = Map.get_lazy(common, :service, fn -> host |> String.split(".") |> hd() end)
    credential_scope = "#{date}/#{service}/tc3_request"

    signature =
      sign(host, body, content_type, timestamp, date, service, credential_scope, secret_key)

    authorization =
      "TC3-HMAC-SHA256 Credential=#{secret_id}/#{credential_scope}, SignedHeaders=content-type;host, Signature=#{signature}"

    [
      {"X-TC-Action", action},
      {"X-TC-Region", region},
      {"X-TC-Timestamp", timestamp},
      {"X-TC-Version", version},
      {"Authorization", authorization},
      {"Content-Type", content_type},
      {"Host", host}
    ]
  end

  def sign(host, body, content_type, timestamp, date, service, credential_scope, secret_key) do
    canonical_request =
      "POST\n/\n\ncontent-type:#{content_type}\nhost:#{host}\n\ncontent-type;host\n#{hash(body)}"

    string_to_sign =
      "TC3-HMAC-SHA256\n#{timestamp}\n#{credential_scope}\n#{hash(canonical_request)}"

    hmac_sha("TC3" <> secret_key, date)
    |> hmac_sha(service)
    |> hmac_sha("tc3_request")
    |> hmac_sha(string_to_sign)
    |> Base.encode16(case: :lower)
  end

  @compile {:inline, hash: 1, hmac_sha: 2}
  defp hash(data), do: :crypto.hash(:sha256, data) |> Base.encode16(case: :lower)
  defp hmac_sha(key, data), do: :crypto.mac(:hmac, :sha256, key, data)
end