Skip to content

Commit

Permalink
Added authentication sample for Elixir
Browse files Browse the repository at this point in the history
  • Loading branch information
Vladislav Kislitsyn committed Oct 22, 2024
1 parent 74a6e56 commit 1cafeb7
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 0 deletions.
1 change: 1 addition & 0 deletions authentication_examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ In this repo you will find Fireblocks API authentication mechanism implementatio
- [Go](https://github.com/fireblocks/developers-hub/tree/main/authentication_examples/go)
- [C#](https://github.com/fireblocks/developers-hub/tree/main/authentication_examples/cs)
- [Ruby](https://github.com/fireblocks/developers-hub/tree/main/authentication_examples/ruby)
- [Elixir](https://github.com/fireblocks/developers-hub/tree/main/authentication_examples/elixir)
The examples include the access token generation method (JWT) in addition to a GET and POST call examples.

⚠️ **Note**
Expand Down
26 changes: 26 additions & 0 deletions authentication_examples/elixir/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# The directory Mix will write compiled artifacts to.
/_build/

# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where third-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

# Ignore package tarball (built via "mix hex.build").
fireblocks_sdk_example-*.tar

# Temporary files, for example, from tests.
/tmp/
80 changes: 80 additions & 0 deletions authentication_examples/elixir/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
## Fireblocks SDK Example Application

This is a simple Elixir application demonstrating how to interact with the Fireblocks API. It shows how to authenticate using a private key and make HTTP requests to the Fireblocks API to retrieve vault accounts.

### Prerequisites

Before running this example, ensure you have the following prerequisites:

- **Elixir installed:** If you don't have Elixir installed, follow the official installation guide.
- **Fireblocks API credentials:**
- **API Key:** You'll need an API key from Fireblocks.
- **Private Key:** You need a `.pem` file containing your private key that is associated with your Fireblocks API key.

### Required dependencies:

The application uses the following dependencies:

- `httpoison` for making HTTP requests.
- `jason` for parsing JSON.
- `jose` for handling JWT token creation.

### Setup

1. **Clone the repository:**

```bash
cd developers-hub/authentication_examples/elixir
```

2. Then run the following command to install the dependencies:

```bash
mix deps.get
```

3. **Save your private key:**

Place your Fireblocks private key (in PEM format) in the location specified in the `@api_secret_path` in the code. The file should be similar to:

```vbnet
-----BEGIN PRIVATE KEY-----
YOUR_PRIVATE_KEY_HERE
-----END PRIVATE KEY-----
```

4. **Edit the API Key and Secret Path:**

In the `fireblocks_sdk_example.ex` file, ensure that your API key and the path to your private key are correctly set:

```elixir
@api_key "YOUR_API_KEY"
@api_secret_path "/path/to/your/private_key.pem"
```

### Running the Application

Once everything is set up, you can run the application using the Elixir interactive shell:

1. Start the interactive shell:

```bash
iex -S mix
```

2. **Run the example:**

In the interactive shell, run the following command to make the request to Fireblocks:

```elixir
Elixir.FireblocksSDKExample.run()
```

3. **Output:** If successful, the response from the Fireblocks API (vaults information) will be printed. If there is an error (e.g., an issue with authentication or network), an error message will be shown.

### Example Output

**Successful request:**

```elixir
%{"accounts" => [%{"assets" => [...]}] }
75 changes: 75 additions & 0 deletions authentication_examples/elixir/lib/fireblocks_http_client.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
defmodule FireblocksHttpClient do
@moduledoc """
Fireblocks HTTP Client for interacting with the Fireblocks API.
"""

defstruct [:api_key, :private_key, :base_url]

require Logger
alias Joken
alias HTTPoison

@headers [
{"Content-Type", "application/json; charset=utf-8"}
]

def request(client, method, path, data \\ nil) do
url = build_url(client.base_url, path)
jwt = sign_jwt(client, path, if(data, do: Jason.encode!(data), else: ""))

headers = build_headers(client.api_key, jwt)

case method do
:get -> HTTPoison.get(url, headers)
:post -> HTTPoison.post(url, Jason.encode!(data), headers)
end
|> handle_response()
end

def get(client, path), do: request(client, :get, path)

def post(client, path, data), do: request(client, :post, path, data)

defp build_url(base_url, path), do: "#{base_url}#{path}"

defp build_headers(api_key, jwt) do
[
{"X-API-Key", api_key},
{"Authorization", "Bearer " <> jwt} | @headers
]
end

defp sign_jwt(client, path, body) do
body_hash = hash_body(body)

claims = %{
"sub" => client.api_key,
"iat" => DateTime.utc_now() |> DateTime.to_unix(),
"exp" => DateTime.utc_now() |> DateTime.add(55) |> DateTime.to_unix(),
"nonce" => UUID.uuid4(),
"uri" => path,
"bodyHash" => body_hash
}

jwk = JOSE.JWK.from_pem(client.private_key)

{_, signed_token} =
jwk
|> JOSE.JWT.sign(%{"alg" => "RS256"}, claims)
|> JOSE.JWS.compact()

signed_token
end

defp hash_body(body) do
:crypto.hash(:sha256, body)
|> Base.encode16(case: :lower)
end

defp handle_response({:ok, %HTTPoison.Response{body: body}}), do: {:ok, Jason.decode!(body)}

defp handle_response({:error, %HTTPoison.Error{reason: reason}}) do
Logger.error("HTTP request failed: #{inspect(reason)}")
{:error, reason}
end
end
28 changes: 28 additions & 0 deletions authentication_examples/elixir/lib/fireblocks_sdk_example.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
defmodule FireblocksSDKExample do
@moduledoc """
Fireblocks SDK Example in Elixir.
"""

@api_key "API_KEY"
@base_url "https://api.fireblocks.io"
@api_secret_path "SECRET_KEY_PATH"

def run do
api_secret = read_private_key_file(@api_secret_path)

client = %FireblocksHttpClient{
api_key: @api_key,
private_key: api_secret,
base_url: @base_url
}

case FireblocksHttpClient.get(client, "/v1/vault/accounts_paged") do
{:ok, get_vaults_response} -> IO.inspect(get_vaults_response)
{:error, reason} -> IO.puts("Failed to fetch vaults: #{inspect(reason)}")
end
end

defp read_private_key_file(path) do
File.read!(path)
end
end
30 changes: 30 additions & 0 deletions authentication_examples/elixir/mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
defmodule FireblocksSdkExample.MixProject do
use Mix.Project

def project do
[
app: :fireblocks_sdk_example,
version: "0.1.0",
elixir: "~> 1.14",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end

# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end

# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:httpoison, "~> 1.8"},
{:jason, "~> 1.2"},
{:joken, "~> 2.5"},
{:uuid, "~> 1.1"}
]
end
end
15 changes: 15 additions & 0 deletions authentication_examples/elixir/mix.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
%{
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
"hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"joken": {:hex, :joken, "2.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"},
"jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
"uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"},
}

0 comments on commit 1cafeb7

Please sign in to comment.