Packages
livebook
0.14.6
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook/zta/cloudflare.ex
defmodule Livebook.ZTA.Cloudflare do
@behaviour Livebook.ZTA
use GenServer
require Logger
import Plug.Conn
@assertion "cf-access-jwt-assertion"
@renew_afer 24 * 60 * 60 * 1000
@fields %{"user_uuid" => :id, "name" => :name, "email" => :email}
defstruct [:req_options, :identity, :name]
def start_link(opts) do
identity = opts[:custom_identity] || identity(opts[:identity_key])
name = Keyword.fetch!(opts, :name)
options = [req_options: [url: identity.certs], identity: identity, name: name]
GenServer.start_link(__MODULE__, options, name: name)
end
@impl true
def authenticate(name, conn, _opts) do
token = get_req_header(conn, @assertion)
{identity, keys} = Livebook.ZTA.get(name)
{conn, authenticate_user(token, identity, keys)}
end
@impl true
def init(options) do
state = struct!(__MODULE__, options)
{:ok, renew(state)}
end
@impl true
def handle_info(:renew, state) do
{:noreply, renew(state)}
end
defp renew(state) do
Logger.debug("[#{inspect(__MODULE__)}] requesting #{inspect(state.req_options)}")
keys = Req.request!(state.req_options).body["keys"]
Process.send_after(self(), :renew, @renew_afer)
Livebook.ZTA.put(state.name, {state.identity, keys})
state
end
defp authenticate_user(token, identity, keys) do
with [encoded_token] <- token,
{:ok, token} <- verify_token(encoded_token, keys),
:ok <- verify_iss(token, identity.iss),
{:ok, user} <- get_user_identity(encoded_token, identity.user_identity) do
for({k, v} <- user, new_k = @fields[k], do: {new_k, v}, into: %{payload: token.fields})
else
_ -> nil
end
end
defp verify_token(token, keys) do
Enum.find_value(keys, :error, fn key ->
case JOSE.JWT.verify(key, token) do
{true, token, _s} -> {:ok, token}
_ -> nil
end
end)
end
defp verify_iss(%{fields: %{"iss" => iss}}, iss), do: :ok
defp verify_iss(_, _), do: :error
defp get_user_identity(token, url) do
cookie = "CF_Authorization=#{token}"
resp = Req.request!(url: url, headers: [cookie: cookie])
if resp.status == 200, do: {:ok, resp.body}, else: :error
end
defp identity(key) do
%{
key: key,
key_type: "domain",
iss: "https://#{key}.cloudflareaccess.com",
certs: "https://#{key}.cloudflareaccess.com/cdn-cgi/access/certs",
assertion: "cf-access-jwt-assertion",
email: "cf-access-authenticated-user-email",
user_identity: "https://#{key}.cloudflareaccess.com/cdn-cgi/access/get-identity"
}
end
end