Current section
Files
Jump to
Current section
Files
lib/nimble_zta.ex
defmodule NimbleZTA do
@moduledoc """
Enable Zero Trust Authentication within your Plug/Phoenix application,
mostly geared towards internal applications.
The following ZTA providers are supported:
* `NimbleZTA.Cloudflare`
* `NimbleZTA.GoogleIAP`
* `NimbleZTA.Tailscale`
We also support the following providers for dev/test/staging:
* `NimbleZTA.BasicAuth` - HTTP basic authentication with a single user-pass
* `NimbleZTA.PassThrough` - always succeeds with no metadata
## Usage
First you must add the ZTA provider of your choice to your supervision tree:
{NimbleZTA.GoogleIAP, name: :google_iap, identity_key: "foobar"}
where the `identity_key` is the identity provider specific key. See their
specific docs for more information.
Then you can use the provider's `c:authenticate/3` callback to authenticate
users on every request:
plug :zta
def zta(conn, _opts) do
case NimbleZTA.GoogleIAP.authenticate(conn, :google_iap) do
# The provider is redirecting somewhere for follow up
{%{halted: true} = conn, nil} ->
conn
# Authentication failed
{%{halted: false} = conn, nil} ->
send_resp(conn, 401, "Unauthorized")
# Authentication succeeded
{conn, metadata} ->
put_session(conn, :user_metadata, metadata)
end
end
Each provider may have specific options supported on `authenticate/3`.
"""
@type name :: atom()
@typedoc """
A metadata of keys returned by zero-trust authentication provider.
The following keys are supported:
* `:id` - a string that uniquely identifies the user
* `:name` - the user name
* `:email` - the user email
* `:avatar_url` - the user avatar
* `:access_type` - the user access type
* `:groups` - the user groups
* `:payload` - the provider payload
Note that none of the keys are required. The metadata returned depends
on the provider.
"""
@type metadata :: %{
optional(:id) => String.t(),
optional(:name) => String.t(),
optional(:email) => String.t(),
optional(:avatar_url) => String.t() | nil,
optional(:access_type) => Livebook.Users.User.access_type(),
optional(:groups) => list(map()),
optional(:payload) => map()
}
@doc """
Each provider must specify a child specification for its processes.
The `:name` and `:identity_key` keys are expected.
"""
@callback child_spec(name: name(), identity_key: String.t()) :: Supervisor.child_spec()
@doc """
Authenticates against the given name.
It will return one of:
* `{non_halted_conn, nil}` - the authentication failed and you must
halt the connection and render the appropriate report
* `{halted_conn, nil}` - the authentication failed and the connection
was modified accordingly to request the credentials
* `{non_halted_conn, metadata}` - the authentication succeed and the
following metadata about the user is available
"""
@callback authenticate(name(), Plug.Conn.t(), keyword()) :: {Plug.Conn.t(), metadata() | nil}
@doc false
def init do
:ets.new(__MODULE__, [:named_table, :public, :set, read_concurrency: true])
end
@doc """
Gets metadata about a ZTA of a given name.
This API is mostly used by NimbleZTA implementations.
"""
def get(name) do
:ets.lookup_element(__MODULE__, name, 2)
end
@doc """
Puts metadata about a ZTA of a given name.
This API is mostly used by NimbleZTA implementations.
"""
def put(name, value) do
:ets.insert(__MODULE__, [{name, value}])
end
end