Packages
livebook
0.14.0-rc.1
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_web/plugs/auth_plug.ex
defmodule LivebookWeb.AuthPlug do
@behaviour Plug
import Plug.Conn
import Phoenix.Controller
use LivebookWeb, :verified_routes
@impl true
def init(opts), do: opts
@impl true
def call(conn, _opts) do
if authenticated?(conn) do
conn
else
authenticate(conn)
end
end
@doc """
Stores in the session the secret for the given mode.
"""
@spec store(Plug.Conn.t(), Livebook.Config.authentication_mode(), String.t()) :: Plug.Conn.t()
def store(conn, mode, value) do
conn
|> put_session(key(conn.port, mode), hash(value))
|> configure_session(renew: true)
end
@doc """
Checks if given connection is already authenticated.
"""
@spec authenticated?(Plug.Conn.t()) :: boolean()
def authenticated?(conn) do
authenticated?(get_session(conn), conn.port)
end
@doc """
Checks if the given session is authenticated.
"""
@spec authenticated?(map(), non_neg_integer()) :: boolean()
def authenticated?(session, port) do
case authentication(session) do
%{mode: :disabled} ->
true
%{mode: mode, secret: secret} when mode in [:token, :password] ->
secret_hash = session[key(port, mode)]
is_binary(secret_hash) and matches_secret?(secret_hash, secret)
end
end
defp authenticate(conn) do
case authentication(conn) do
%{mode: :password} ->
redirect_to_authenticate(conn)
%{mode: :token, secret: secret} ->
{token, query_params} = Map.pop(conn.query_params, "token")
if is_binary(token) and matches_secret?(hash(token), secret) do
# Redirect to the same path without query params
conn
|> store(:token, token)
|> redirect(to: path_with_query(conn.request_path, query_params))
|> halt()
else
redirect_to_authenticate(conn)
end
end
end
defp matches_secret?(hash, secret) do
Plug.Crypto.secure_compare(hash, hash(secret))
end
defp redirect_to_authenticate(%{path_info: []} = conn) do
path =
if Livebook.Apps.list_apps() != [] or Livebook.Config.apps_path() != nil or
Livebook.Config.teams_auth?() do
~p"/apps"
else
~p"/authenticate"
end
conn
|> redirect(to: path)
|> halt()
end
defp redirect_to_authenticate(conn) do
conn
|> then(fn
%{method: "GET"} -> put_session(conn, :redirect_to, current_path(conn))
conn -> conn
end)
|> redirect(to: ~p"/authenticate")
|> halt()
end
defp path_with_query(path, params) when params == %{}, do: path
defp path_with_query(path, params), do: path <> "?" <> URI.encode_query(params)
defp key(port, mode), do: "#{port}:#{mode}"
defp hash(value), do: :crypto.hash(:sha256, value)
@doc """
Returns the authentication configuration for the given `conn` or
`session`.
This mirrors `Livebook.Config.authentication/0`, except the it can
be overridden in tests, for each connection.
"""
if Mix.env() == :test do
def authentication(%Plug.Conn{} = conn), do: authentication(get_session(conn))
def authentication(%{} = session) do
session["authentication_test_override"] || Livebook.Config.authentication()
end
else
def authentication(_), do: Livebook.Config.authentication()
end
end