mirror of
https://github.com/livebook-dev/livebook.git
synced 2024-11-15 12:25:31 +08:00
4334e8a58e
Co-authored-by: José Valim <jose.valim@dashbit.co>
94 lines
2.5 KiB
Elixir
94 lines
2.5 KiB
Elixir
defmodule LivebookWeb.AuthPlug do
|
|
@moduledoc false
|
|
|
|
@behaviour Plug
|
|
|
|
import Plug.Conn
|
|
import Phoenix.Controller
|
|
|
|
alias LivebookWeb.Router.Helpers, as: Routes
|
|
|
|
@impl true
|
|
def init(opts), do: opts
|
|
|
|
@impl true
|
|
def call(conn, _opts) do
|
|
mode = Livebook.Config.auth_mode()
|
|
|
|
if authenticated?(conn, mode) do
|
|
conn
|
|
else
|
|
authenticate(conn, mode)
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Stores in the session the secret for the given mode.
|
|
"""
|
|
@spec store(Plug.Conn.t(), Livebook.Config.auth_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(), Livebook.Config.auth_mode()) :: boolean()
|
|
def authenticated?(conn, mode) do
|
|
authenticated?(get_session(conn), conn.port, mode)
|
|
end
|
|
|
|
@doc """
|
|
Checks if the given session is authenticated.
|
|
"""
|
|
@spec authenticated?(map(), non_neg_integer(), Livebook.Config.auth_mode()) :: boolean()
|
|
def authenticated?(session, port, mode)
|
|
|
|
def authenticated?(_session, _port, :disabled) do
|
|
true
|
|
end
|
|
|
|
def authenticated?(session, port, mode) when mode in [:token, :password] do
|
|
secret = session[key(port, mode)]
|
|
|
|
is_binary(secret) and mode == Livebook.Config.auth_mode() and
|
|
Plug.Crypto.secure_compare(secret, expected(mode))
|
|
end
|
|
|
|
defp authenticate(conn, :password) do
|
|
redirect_to_authenticate(conn)
|
|
end
|
|
|
|
defp authenticate(conn, :token) do
|
|
{token, query_params} = Map.pop(conn.query_params, "token")
|
|
|
|
if is_binary(token) and Plug.Crypto.secure_compare(hash(token), expected(:token)) 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
|
|
|
|
defp redirect_to_authenticate(conn) do
|
|
conn
|
|
|> then(fn
|
|
%{method: "GET"} -> put_session(conn, :redirect_to, current_path(conn))
|
|
conn -> conn
|
|
end)
|
|
|> redirect(to: Routes.path(conn, "/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 expected(mode), do: hash(Application.fetch_env!(:livebook, mode))
|
|
defp hash(value), do: :crypto.hash(:sha256, value)
|
|
end
|