Elixir SDK
Idiomatic Elixir client for InferaDB with Phoenix integration.
Coming soon. The Elixir SDK is under active development. The API surface shown here is based on the Rust SDK and may change before release.
Fault-tolerant, process-based client for InferaDB’s authorization APIs. Requires Elixir 1.16+ / OTP 26+. Integrates with Phoenix and any Plug-based application.
Installation
# mix.exs
defp deps do
[
{:inferadb, "~> 0.1"}
]
end
mix deps.get
Configuration
# config/runtime.exs
config :inferadb,
url: System.fetch_env!("INFERADB_URL"),
credentials: [
client_id: System.fetch_env!("INFERADB_CLIENT_ID"),
private_key_path: System.fetch_env!("INFERADB_KEY_PATH"),
certificate_id: System.fetch_env!("INFERADB_CERT_ID")
],
organization: System.fetch_env!("INFERADB_ORG"),
vault: System.fetch_env!("INFERADB_VAULT")
Add the client to your supervision tree:
# lib/my_app/application.ex
children = [
{InferaDB, name: MyApp.InferaDB}
]
Authentication
Three authentication methods:
| Method | Use Case | Security |
|---|---|---|
| Client Credentials (Ed25519 JWT) | Service-to-service | High |
| Bearer Token | User sessions, OAuth | Medium |
| API Key | Testing, simple integrations | Basic |
Client Credentials (Recommended)
Via application config above, or explicitly:
{:ok, client} = InferaDB.start_link(
url: "https://engine.inferadb.com",
credentials: [
client_id: "my-client",
private_key_path: "client.pem",
certificate_id: "cert-id"
]
)
Bearer Token
{:ok, client} = InferaDB.start_link(
url: "https://engine.inferadb.com",
token: System.fetch_env!("INFERADB_TOKEN")
)
Permission Checks
vault =
InferaDB.organization(MyApp.InferaDB, "my-org")
|> InferaDB.vault("production")
# Simple check — returns {:ok, true} or {:ok, false}
{:ok, true} = InferaDB.check(vault, "user:alice", "can_edit", "document:readme")
With ABAC Context
{:ok, allowed} = InferaDB.check(vault, "user:alice", "can_view", "document:readme",
context: %{ip_address: "10.0.0.1"}
)
Require — Returns :ok or :error
case InferaDB.require(vault, "user:alice", "can_edit", "document:readme") do
:ok -> # permitted
{:error, :access_denied} -> # denied
{:error, reason} -> # other error
end
Require! — Raises on Deny
# Raises InferaDB.AccessDeniedError
InferaDB.require!(vault, "user:alice", "can_edit", "document:readme")
With Consistency Token
{:ok, allowed} = InferaDB.check(vault, "user:alice", "can_view", "document:readme",
at_least_as_fresh: revision_token
)
Batch Check
{:ok, results} = InferaDB.check_batch(vault, [
{"user:alice", "can_edit", "document:readme"},
{"user:bob", "can_view", "document:readme"},
])
InferaDB.all_allowed?(results) # => true/false
Relationships
Write
# Returns {:ok, revision_token}
{:ok, token} = InferaDB.write_relationship(vault,
resource: "document:readme",
relation: "editor",
subject: "user:alice"
)
Batch Write
{:ok, token} = InferaDB.write_relationships(vault, [
%{resource: "document:readme", relation: "editor", subject: "user:alice"},
%{resource: "document:readme", relation: "viewer", subject: "user:bob"},
])
List
{:ok, rels} = InferaDB.list_relationships(vault,
resource: "document:readme"
)
Delete
:ok = InferaDB.delete_relationships(vault,
resource: "document:readme",
relation: "viewer",
subject: "user:bob"
)
Lookups
# What resources can Alice view?
{:ok, resources} = InferaDB.list_resources(vault, "user:alice",
permission: "can_view",
resource_type: "document"
)
# Who can edit this document?
{:ok, subjects} = InferaDB.list_subjects(vault, "document:readme",
permission: "can_edit"
)
Testing
MockClient (Fastest)
alias InferaDB.Testing.MockClient
client = MockClient.new()
|> MockClient.on_check("user:alice", "can_edit", "document:readme", :allow)
|> MockClient.on_check("user:bob", "can_edit", "document:readme", :deny)
|> MockClient.on_check_any_subject("can_view", "document:readme", :allow)
|> MockClient.default_deny()
MockClient.verify!(client) # asserts all expectations were met
InMemoryClient (Full Policy Evaluation)
alias InferaDB.Testing.InMemoryClient
{:ok, client} = InMemoryClient.start_link(
schema: """
type document {
relation viewer
relation editor
relation can_view = viewer | editor
}
""",
data: [
%{resource: "document:readme", relation: "editor", subject: "user:alice"},
%{resource: "document:readme", relation: "viewer", subject: "user:bob"},
]
)
TestVault (Real Instance)
alias InferaDB.Testing.TestVault
{:ok, vault} = TestVault.create(org, schema: schema_ipl)
# vault auto-cleans up when the process stops
ExUnit Integration
defmodule MyApp.DocumentAuthorizationTest do
use ExUnit.Case, async: true
alias InferaDB.Testing.InMemoryClient
setup do
{:ok, client} = InMemoryClient.start_link(
schema: "...",
data: [%{resource: "document:readme", relation: "editor", subject: "user:alice"}]
)
vault =
InferaDB.organization(client, "test")
|> InferaDB.vault("test")
%{vault: vault}
end
test "Alice can edit", %{vault: vault} do
assert {:ok, true} = InferaDB.check(vault, "user:alice", "can_edit", "document:readme")
end
test "Bob cannot edit", %{vault: vault} do
assert {:ok, false} = InferaDB.check(vault, "user:bob", "can_edit", "document:readme")
end
end
Error Handling
Standard {:ok, result} / {:error, reason} tuples with bang variants that raise:
case InferaDB.check(vault, "user:alice", "can_edit", "document:readme") do
{:ok, true} -> :allowed
{:ok, false} -> :denied
{:error, %InferaDB.Error{kind: :rate_limited, retry_after: delay}} ->
Process.sleep(delay)
# retry
{:error, %InferaDB.Error{kind: kind, request_id: id}} ->
Logger.error("Authorization error: kind=#{kind} request_id=#{id}")
end
Error kinds: :unauthorized, :forbidden, :not_found, :rate_limited, :schema_violation, :unavailable, :timeout, :invalid_argument.
Framework Integrations
Phoenix Plug
# lib/my_app_web/plugs/inferadb_authorize.ex
defmodule MyAppWeb.Plugs.InferaDBAuthorize do
import Plug.Conn
def init(opts), do: opts
def call(conn, opts) do
permission = Keyword.fetch!(opts, :permission)
resource_fn = Keyword.fetch!(opts, :resource)
vault =
InferaDB.organization(MyApp.InferaDB, "my-org")
|> InferaDB.vault("production")
user_id = conn.assigns.current_user.id
resource = resource_fn.(conn)
case InferaDB.require(vault, "user:#{user_id}", permission, resource) do
:ok ->
conn
{:error, :access_denied} ->
conn
|> put_status(:forbidden)
|> Phoenix.Controller.json(%{error: "Forbidden"})
|> halt()
end
end
end
# lib/my_app_web/router.ex
pipeline :authorized_document do
plug MyAppWeb.Plugs.InferaDBAuthorize,
permission: "can_view",
resource: fn conn -> "document:#{conn.params["id"]}" end
end
scope "/documents", MyAppWeb do
pipe_through [:browser, :authenticated, :authorized_document]
get "/:id", DocumentController, :show
end
Phoenix LiveView
defmodule MyAppWeb.DocumentLive.Show do
use MyAppWeb, :live_view
@impl true
def mount(%{"id" => id}, _session, socket) do
vault =
InferaDB.organization(MyApp.InferaDB, "my-org")
|> InferaDB.vault("production")
user_id = socket.assigns.current_user.id
case InferaDB.require(vault, "user:#{user_id}", "can_view", "document:#{id}") do
:ok ->
{:ok, assign(socket, :document, Documents.get!(id))}
{:error, :access_denied} ->
{:ok, socket |> put_flash(:error, "Not authorized") |> redirect(to: ~p"/")}
end
end
end