livebook/lib/livebook_web/plugs/auth_plug.ex
Jonatan Kłosko e9766ed7a5
Introduce token auth and add basic cli configuration (#148)
* Add token authentication

* Restructure CLI

* Allow port configuration

* Further refactoring

* Make sure livebook node starts with unique name

* Improve startup error handling

* Further refactoring

* Add authentication tests

* Add authentication view for entering the token

* Fix auth tests

* Always use random Livebook name for distribution

* Don't enable ANSI on Windows

* Define CLI Task behaviour and move generic logic to the main module

* Generalize convertion from cli arguments to configuration

* Randomly generate secret key base

* Update test/livebook_web/plugs/auth_plug_test.exs

Co-authored-by: José Valim <jose.valim@dashbit.co>

* Override app config in persistent manner

* Update lib/litebook_cli.ex

Co-authored-by: José Valim <jose.valim@dashbit.co>

* Move auth error to ErrorView

* Unify node name configuration and allow it via CLI

* Set all applications configs at once

* Move token generation to application.ex to work outside CLI

* Clean up overriding configuration

* Store auth token in separate cookies

* Update lib/livebook_cli/server.ex

Co-authored-by: José Valim <jose.valim@dashbit.co>

* Update lib/livebook_web/endpoint.ex

Co-authored-by: José Valim <jose.valim@dashbit.co>

* Update lib/livebook_web/plugs/auth_plug.ex

Co-authored-by: José Valim <jose.valim@dashbit.co>

Co-authored-by: José Valim <jose.valim@dashbit.co>
2021-04-08 11:41:52 +02:00

52 lines
1.3 KiB
Elixir

defmodule LivebookWeb.InvalidTokenError do
defexception plug_status: 401, message: "invalid token"
end
defmodule LivebookWeb.AuthPlug do
@moduledoc false
@behaviour Plug
import Plug.Conn
import Phoenix.Controller
@cookie_opts [sign: true, max_age: 2_592_000]
@impl true
def init(opts), do: opts
@impl true
def call(conn, _otps) do
case Application.get_env(:livebook, :token) do
nil -> conn
token -> token_authentication(conn, token)
end
end
defp token_authentication(conn, token) do
# The user may run multiple Livebook instances on the same host
# on different ports, so we scope the cookie name under port
token_cookie = "#{conn.port}:token"
conn = fetch_cookies(conn, signed: [token_cookie])
param_token = Map.get(conn.query_params, "token")
cookie_token = conn.cookies[token_cookie]
cond do
is_binary(param_token) and Plug.Crypto.secure_compare(param_token, token) ->
conn
|> put_resp_cookie(token_cookie, param_token, @cookie_opts)
# Redirect to the same path without query params
|> redirect(to: conn.request_path)
|> halt()
is_binary(cookie_token) and Plug.Crypto.secure_compare(cookie_token, token) ->
conn
true ->
raise LivebookWeb.InvalidTokenError
end
end
end