Current section
Files
Jump to
Current section
Files
lib/paladin_client.ex
defmodule PaladinClient do
@moduledoc """
Provides helper functions for working with the Paladin service
and a Behaviour for adapters
"""
use Application
def start(_type, _args) do
import Supervisor.Spec, warn: false
children = [worker(PaladinClient.TokenCache, [])]
opts = [strategy: :one_for_one, name: PaladinClient.Supervisor]
Supervisor.start_link(children, opts)
end
@type jwt :: String.t
@type app_id :: atom | String.t
@type assertion_token :: jwt
@type expiry :: non_neg_integer
@type reason :: atom | String.t
alias PaladinClient.TokenCache
@callback access_token(assertion_token) :: {:ok, jwt, expiry} |
{:error, reason}
@spec from_existing_token(jwt, app_id, Map.t) :: {:ok, jwt} | {:error, reason}
@doc """
When you have an existing Guardian JWT you may use it to
exchange via Paladin for a token of the application you wish to talk to
"""
def from_existing_token(token, app_id, opts \\ %{}) do
id = fetch_app_id(app_id)
TokenCache.find({token, id}, fn ->
claims = Guardian.peek_claims(token)
claims = Map.merge(claims, opts)
sub = claims["sub"]
{:ok, the_token} = new_assertion_token(id, sub, claims)
access_token!(the_token, token, id)
end)
end
@spec service_token(app_id, Map.t) :: {:ok, token} :: {:error, term}
@doc """
When there is no existing token.
This is useful for when there is no user you are acting on behalf of.
Primarily system to system
"""
def service_token(app_id, claims \\ %{}) do
id = fetch_app_id(app_id)
user = "Service:#{Guardian.issuer}"
TokenCache.find({user, id}, fn ->
case new_assertion_token(app_id, user, claims) do
{:ok, token} ->
access_token!(token, user, id)
error -> error
end
end)
end
@spec new_assertion_token(app_id, term, Map.t) :: {:ok, jwt} | {:error, term}
@doc """
Without an existing token, you may generate your own token
to make use as an exchange for Paladin
"""
def new_assertion_token(app_id, user, claims \\ %{}) do
id = fetch_app_id(app_id)
{:ok, sub} = Guardian.serializer.for_token(user)
claims = claims
|> Map.drop(["iat", "exp"])
|> Map.put("aud", id)
|> Map.put_new(:ttl, {1, :minute})
result = Guardian.encode_and_sign(sub, :assertion, claims)
case result do
{:ok, jwt, _full_claims} -> {:ok, jwt}
error -> error
end
end
@doc """
Fetch the paladin id from config via the name
In your configuration you should add a KWList of name: id
config :paladin_client, PaladinClient,
apps: [
app_one: "app-one-id",
app_two: "app-two-id",
]
PaladinClient.fetch_app_id(:app_one) == "app-one-id"
"""
def fetch_app_id(app_id) when is_atom(app_id) do
Application.get_env(:paladin_client, PaladinClient)[:apps][app_id]
end
def fetch_app_id(app_id), do: app_id
@doc """
When using Paladin, your issuer should be your application ID.
"""
def client_id, do: Application.get_env(:guardian, Guardian)[:issuer]
@doc """
Fetch the url of Paladin in this environment
"""
def endpoint, do: Application.get_env(:paladin_client, PaladinClient)[:url]
defp access_token!(the_token, original_token, app_id) do
adapter = Application.get_env(:paladin_client, __MODULE__)[:adapter]
case adapter.access_token(the_token) do
{:ok, access_token, exp} ->
TokenCache.store!({original_token, app_id}, access_token, exp)
{:ok, access_token}
error -> error
end
end
end