livebook/lib/livebook_web/plugs/auth_plug.ex

96 lines
2.1 KiB
Elixir
Raw Normal View History

defmodule LivebookWeb.InvalidTokenError do
defexception plug_status: 401, message: "invalid token"
end
defmodule LivebookWeb.AuthPlug do
@moduledoc false
@behaviour Plug
2021-04-15 20:15:56 +08:00
alias LivebookWeb.Helpers
import Plug.Conn
import Phoenix.Controller
@impl true
def init(opts), do: opts
@impl true
2021-04-15 20:15:56 +08:00
def call(conn, _opts) do
case auth_mode() do
:password ->
password_authentication(conn)
:token ->
token_authentication(conn)
:disabled ->
conn
end
end
2021-04-15 20:15:56 +08:00
@doc """
Checks if given connection is already authenticated.
"""
@spec authenticated?(Plug.Conn.t()) :: boolean()
def authenticated?(conn, mode \\ auth_mode())
2021-04-15 20:15:56 +08:00
def authenticated?(conn, mode) when mode in [:token, :password] do
secret = prepare_secret(mode)
2021-04-15 20:15:56 +08:00
key = Helpers.auth_cookie_key(conn, mode)
conn = fetch_cookies(conn, signed: [key])
cookie = conn.cookies[key]
is_binary(cookie) and Plug.Crypto.secure_compare(cookie, secret)
end
def authenticated?(_conn, _mode) do
true
end
defp password_authentication(conn) do
if authenticated?(conn) do
conn
else
conn
|> redirect(to: "/authenticate")
|> halt()
end
end
defp token_authentication(conn) do
token = prepare_secret(:token)
cookie_key = Helpers.auth_cookie_key(conn, :token)
token_param = Map.get(conn.query_params, "token")
cond do
2021-04-15 20:15:56 +08:00
is_binary(token_param) and Plug.Crypto.secure_compare(token_param, token) ->
conn
2021-04-15 20:15:56 +08:00
|> put_resp_cookie(cookie_key, token_param, Helpers.auth_cookie_opts())
# Redirect to the same path without query params
|> redirect(to: conn.request_path)
|> halt()
2021-04-15 20:15:56 +08:00
authenticated?(conn) ->
conn
true ->
raise LivebookWeb.InvalidTokenError
end
end
2021-04-15 20:15:56 +08:00
defp auth_mode() do
Application.fetch_env!(:livebook, :authentication_mode)
end
defp prepare_secret(mode) do
secret = Application.fetch_env!(:livebook, mode)
case mode do
:token -> secret
:password -> :crypto.hash(:sha256, secret) |> Base.encode16()
end
end
end