-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added authentication sample for Elixir
- Loading branch information
Vladislav Kislitsyn
committed
Oct 22, 2024
1 parent
74a6e56
commit 1cafeb7
Showing
7 changed files
with
255 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
75
authentication_examples/elixir/lib/fireblocks_http_client.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
28
authentication_examples/elixir/lib/fireblocks_sdk_example.ex
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"}, | ||
} |