Packages
livebook
0.14.2
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/live/hooks/app_auth_hook.ex
defmodule LivebookWeb.AppAuthHook do
import Phoenix.Component
import Phoenix.LiveView
use LivebookWeb, :verified_routes
# For apps with password, we want to store the hashed password
# (let's call it token) in the session, as we do for the main auth.
# However, the session uses cookies, which have a ~4kb size limit.
# We could use multiple cookies, however there are also limits on
# the number of cookies, and we don't want the browser to clear
# them all at some point. Additionally, accumulating cookies for
# all apps would imply larger payloads on every regular request.
#
# Since we don't have any persistence on the server side, the other
# option is to use the browser local storage and manage it through
# JavaScript. Therefore, the whole auth is built using LiveView.
# The flows are:
#
# * unauthenticated - the auth LiveView shows a regular form and
# validates the user-provided password. Once the password is
# correct, it pushes an event to the client to store the token.
# Once the token is stored it redirects to the app page.
#
# * authenticated - on dead render the app LiveView renders just
# a loading screen. On the client side, provided it's the app
# page, we read the token from local storage (if stored) and
# send it in mount connect params via the socket. Then on the
# server we use that token to authenticate.
#
# This module defines a hook that sets the following assigns:
#
# * `:app_authenticated?` - reflects the current authentication.
# For public apps (or in case the user has full access) it is
# set to `true` on both dead and live render
#
# * `:livebook_authenticated?` - if the user has full Livebook
# access
#
# * `:app_settings` - the current app settings
#
def on_mount(:default, %{"slug" => slug}, session, socket) do
livebook_authenticated? = livebook_authenticated?(session, socket)
socket = assign(socket, livebook_authenticated?: livebook_authenticated?)
case Livebook.Apps.fetch_settings(slug) do
{:ok, %{access_type: :public} = app_settings} ->
{:cont, assign(socket, app_authenticated?: true, app_settings: app_settings)}
{:ok, %{access_type: :protected} = app_settings} ->
app_authenticated? = livebook_authenticated? or has_valid_token?(socket, app_settings)
{:cont,
assign(socket, app_authenticated?: app_authenticated?, app_settings: app_settings)}
:error ->
{:halt, redirect(socket, to: ~p"/")}
end
end
# Skip auth for non-app-specific routes
def on_mount(:default, %{}, _session, socket) do
{:cont, socket}
end
defp livebook_authenticated?(session, socket) do
uri = get_connect_info(socket, :uri)
LivebookWeb.AuthPlug.authenticated?(session, uri.port)
end
defp has_valid_token?(socket, app_settings) do
connect_params = get_connect_params(socket) || %{}
if token = connect_params["app_auth_token"] do
valid_auth_token?(token, app_settings)
else
false
end
end
@doc """
Generates auth token that can be sent to the client.
"""
@spec get_auth_token(Livebook.Notebook.AppSettings.t()) :: String.t()
def get_auth_token(app_settings) do
:crypto.hash(:sha256, app_settings.password) |> Base.encode64()
end
@doc """
Checks the given token is valid.
"""
@spec valid_auth_token?(String.t(), Livebook.Notebook.AppSettings.t()) :: boolean()
def valid_auth_token?(token, app_settings) do
Plug.Crypto.secure_compare(token, get_auth_token(app_settings))
end
@doc """
Checks if the given password is valid.
"""
@spec valid_password?(String.t(), Livebook.Notebook.AppSettings.t()) :: boolean()
def valid_password?(password, app_settings) do
Plug.Crypto.secure_compare(password, app_settings.password)
end
end