mirror of
				https://github.com/livebook-dev/livebook.git
				synced 2025-11-01 00:06:04 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			72 lines
		
	
	
	
		
			1.7 KiB
		
	
	
	
		
			Elixir
		
	
	
	
	
	
			
		
		
	
	
			72 lines
		
	
	
	
		
			1.7 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
 | |
| 
 | |
|   @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.
 | |
|   """
 | |
|   def store(conn, mode, value) do
 | |
|     put_session(conn, key(conn, mode), hash(value))
 | |
|   end
 | |
| 
 | |
|   @doc """
 | |
|   Checks if given connection is already authenticated.
 | |
|   """
 | |
|   @spec authenticated?(Plug.Conn.t(), Livebook.Config.auth_mode()) :: boolean()
 | |
|   def authenticated?(conn, mode)
 | |
| 
 | |
|   def authenticated?(conn, mode) when mode in [:token, :password] do
 | |
|     secret = get_session(conn, key(conn, mode))
 | |
|     is_binary(secret) and Plug.Crypto.secure_compare(secret, expected(mode))
 | |
|   end
 | |
| 
 | |
|   def authenticated?(_conn, _mode) do
 | |
|     true
 | |
|   end
 | |
| 
 | |
|   defp authenticate(conn, :password) do
 | |
|     conn
 | |
|     |> redirect(to: "/authenticate")
 | |
|     |> halt()
 | |
|   end
 | |
| 
 | |
|   defp authenticate(conn, :token) do
 | |
|     token = Map.get(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: conn.request_path)
 | |
|       |> halt()
 | |
|     else
 | |
|       raise LivebookWeb.InvalidTokenError
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   defp key(conn, mode), do: "#{conn.port}:#{mode}"
 | |
|   defp expected(mode), do: hash(Application.fetch_env!(:livebook, mode))
 | |
|   defp hash(value), do: :crypto.hash(:sha256, value)
 | |
| end
 |