Packages

A Phoenix LiveView library for real-time API route management — discover, toggle, audit, and guard your routes from a dashboard

Current section

Files

Jump to
api_management_console lib api_management_console_v2 license.ex
Raw

lib/api_management_console_v2/license.ex

defmodule ApiManagementConsoleV2.License do
@moduledoc """
Offline license validation using signed JWT tokens.
The consumer sets `API_CONSOLE_LICENSE_KEY` in their environment.
The library validates the signature against an embedded public key
and extracts claims (tier, expiry, trial info).
## License claims
{
"tier": "paid" | "free",
"expires_at": "2026-12-31" (ISO8601, optional),
"trial": true | false,
"trial_start": "2026-01-01" (ISO8601),
"trial_days": 30
}
## Usage
License.get_tier() # => :paid | :free
License.expired?() # => true | false
License.in_trial?() # => true | false
"""
@public_key_file Path.join(:code.priv_dir(:api_management_console), "keys/public_key.pem")
@doc "Returns the current license tier: :paid or :free."
def get_tier do
case license_key() do
nil -> :free
key -> validate(key)
end
end
@doc "Returns true if the license has expired."
def expired? do
case license_key() do
nil -> false
key ->
case parse_claims(key) do
{:ok, claims} -> is_expired?(claims)
_ -> false
end
end
end
@doc "Returns true if currently in a trial period."
def in_trial? do
case license_key() do
nil -> false
key ->
case parse_claims(key) do
{:ok, claims} -> Map.get(claims, "trial", false)
_ -> false
end
end
end
@doc "Returns the expiry date if set, nil otherwise."
def expires_at do
case license_key() do
nil -> nil
key ->
case parse_claims(key) do
{:ok, %{"expires_at" => expires}} -> expires
_ -> nil
end
end
end
# --- Private ---
defp license_key do
System.get_env("API_CONSOLE_LICENSE_KEY") ||
Application.get_env(:api_management_console, :license_key)
end
defp validate(key) do
case parse_claims(key) do
{:ok, claims} ->
if is_expired?(claims) do
:free
else
String.to_existing_atom(claims["tier"] || "free")
end
_ ->
:free
end
end
defp parse_claims(key) do
public_key = read_public_key()
signer = Joken.Signer.create("RS256", %{"pem" => public_key})
case Joken.verify(key, signer) do
{:ok, claims} -> {:ok, claims}
{:error, reason} ->
IO.inspect(reason, label: "[License] verify error")
:error
end
rescue
e ->
IO.inspect(e, label: "[License] exception")
:error
end
defp is_expired?(claims) do
# Check explicit expiry
expires = Map.get(claims, "expires_at")
if expires do
case Date.from_iso8601(expires) do
{:ok, date} -> Date.compare(date, Date.utc_today()) == :lt
_ -> true
end
else
# Check trial expiry
if Map.get(claims, "trial", false) do
trial_start = Map.get(claims, "trial_start")
trial_days = Map.get(claims, "trial_days", 30)
if trial_start do
case Date.from_iso8601(trial_start) do
{:ok, start} ->
Date.diff(Date.utc_today(), start) > trial_days
_ ->
true
end
else
false
end
else
false
end
end
end
defp read_public_key do
File.read!(@public_key_file)
rescue
_ -> raise "Public key not found at #{@public_key_file}. Ensure priv/keys/public_key.pem is included in your release."
end
end