Home

Awesome

Zachaeus

Zachaeus is a simple and easy to use licensing system for your Elixir application. It's inspired by JWT, PASETO and other security token systems, which are using asymmetric cryptography.

A generated Zachaeus license contains all relevant data, which is essential for a simple licensing system. Because of this nature, Zachaeus can be used without a database and integrates with Plug but can be used outside of it. If you're implementing something which needs a licensing system, Zachaeus can work for you.

Use cases

Features

Documentation

API documentation is available at https://hexdocs.pm/zachaeus

Installation

Required libraries

Installation of Zachaeus requires the following libraries to be installed:

Installation steps

The package can be installed as Hex package, just add Zachaeus to your application mix.exs

defp deps do
  [{:zachaeus, "~> 1.0.0"}]
end

Run mix deps.get to fetch and install the package.

To leverage Zachaeus, you will need to generate a public/secret key pair by running the mix zachaeus.gen.keys task.

After running this mix task, you need to add the generated key pair to your configuration config/config.exs.

config :zachaeus,
  public_key: "csKWI0t9mdPoyEWfXj4skhZpjaMp...",
  secret_key: "VkmBsZ5oklR8_MGk77AJUxDRpSqJL6449DTgK6y2f-hywpYjS32Z0..."

After adding the key pair to your configuration file, you are able to generate license(s) using the mix zachaeus.gen.license task.

$ mix zachaeus.gen.license --identifier user_1 --plan default_plan --valid-from 2020-01-01 --valid-until 2020-12-31

🎉🎉 Congratulations! 🎉🎉

You now have a working Zachaeus setup.

Basics

Once Zachaeus was set up correctly, you can issue licenses using the zachaeus.gen.license mix task (as shown above) or with your own code. For issuing/signing licenses, Zachaeus requires either the configured secret key in your config/config.exs or a directly specified secret key.

# Define a license with your specific license data
defined_license = %Zachaeus.License{
  identifier: "my_user_id_1",
  plan: "default",
  valid_from: ~U[2018-11-15 11:00:00Z],
  valid_until: ~U[2019-11-30 09:30:00Z]
}

# Sign the defined license using the configured secret key
signed_license = Zachaeus.sign(defined_license)

# Verify the signed license using the configured public key
{:ok, verified_license} = Zachaeus.verify(signed_license)

# Verify the signed license with the configured public key and validate the license in a single step
{:ok, 1123123} = Zachaeus.validate(signed_license)

# Get a boolean indicator, whether the license could be verified with the configured public key and is valid
Zachaeus.valid?(signed_license) # -> true

Use with your web framework

Zachaeus comes with a default EnsureAuthenticated plug, which can be used with your plug compatible web framework e.g. Phoenix.

defmodule MyAppWeb.Router do
  use Phoenix.Router

  pipeline :api do
    plug :accepts, ["json"]
    plug Zachaeus.Plug.EnsureAuthenticated
  end

  scope "/" do
    pipe_through :api
    # API related routes...
  end
end

If you need a custom behaviour, Zachaeus offers the ability to implement a fully customized plug on your own. Just use Zachaeus.Plug in your module and implement the default and the build_response callback.

defmodule CustomAuthentication do
  use Zachaeus.Plug

  def init(opts), do: opts

  def call(conn, _opts) do
    conn
    |> fetch_license()
    |> verify_license()
    |> validate_license()
    |> build_response()
  end

  def build_response({conn, {:ok, _license}}), do: conn
  def build_response({conn, {:error, %Error{message: message}}}) do
    conn
    |> put_resp_content_type("text/plain")
    |> send_resp(:unauthorized, "Dude, you don't have a valid license!")
    |> halt()
  end
end

Configuration

To keep Zachaeus configuration as simple as possible, it only needs a secret_key and/or a public_key (depending on your setup). All configuration values may be provided in two ways.

  1. Through your config file(s)
  2. Passed directly to the function

If you don't want to store the configuration in the configuration file e.g. juggle with multiple keys or just to use keys stored within a database, all relevant functions like sign/1, verify/1 etc. has a companion where the secret/public key can be specified directly.

# Using the configured secret_key
signed_license = Zachaeus.sign(license)

# Using a specific secret_key
custom_secret_key = "thisisyourcustomsecretkey"
signed_license    = Zachaeus.sign(license, custom_secrect_key)

Configuration values

The Zachaeus configuration is really simple, as it just has the following configuration values:

(The configuration values above are required for Zachaeus to work.)

Key security / Split configuration

Due to the nature of asymmetric cryptography, Zachaeus can be set up in a kind of split configuration. With this type of configuration, you can keep your secret_key in a controlled and secure environment e.g. on your local computer and you just need to store the public_key outside of this secure environment e.g. on your web server.

When you use this setup, you can generate licenses (using the secret_key) from within your secure environment, issue the licenses to your customers and verifying them (using the public_key) in an unsecure environment.

License issuing system

It's just required to set the secret_key configuration value e.g. in your config/config.exs, but it wouldn't hurt either to set the public_key configuration value.

config :zachaeus,
  public_key: "csKWI0t9mdPoyEWfXj4skhZpjaMp...",
  secret_key: "VkmBsZ5oklR8_MGk77AJUxDRpSqJL6449DTgK6y2f-hywpYjS32Z0..."

â–º Please keep in mind, that the verification of license only works, when the public_key configuration value is set!

License verifying system

It's strongly recommended to just set the public_key configuration value e.g. in your config/config.exs and not to set the secret_key configuration value.

config :zachaeus,
  public_key: "csKWI0t9mdPoyEWfXj4skhZpjaMp..."

â–º I recommend not to set the secret_key configuration value in a publicly available environment!

Errors

Zachaeus offers the ability to customize the returned error message through general error codes.

# Sample error
sample_error = %Zachaeus.Error{code: :license_expired, message: "The license has expired"}

# Match on the error code 'license_expired'
case sample_error do
  %Zachaeus.Error{code: :license_expired} ->
    {:error, "Hey dude, your license has expired!"}
  _any_other_error ->
    {:error, "Something unexpected happened"}
end

Here's the current list of errors returned by Zachaeus.

License

The MIT License (MIT). Please see License File for more information.