ZTA revamp

* Rename SessionIdentity to PassThrough and make it part of ZTA

* Compute the ID at the Plug level, rather than ZTA level and
  avoid storing it twice

* Stop the user "avatar" from flashing on initial render

* Do not duplicate identity data inside user data, rather keep
  them distinct
This commit is contained in:
José Valim 2024-04-13 10:29:22 +02:00
parent 50f9bb2420
commit 29c5cb1904
21 changed files with 132 additions and 86 deletions

View file

@ -1,20 +1,17 @@
# Authentication # Authentication
## Introduction Livebook has three levels of authentication:
Livebook's authentication covers all pages for creating, writing, and managing notebooks. * Instance authentication: this authenticates the user on all routes of your Livebook instance, including deployed notebooks and the admin section. This is done via Zero Trust Authentication and typically used when deploying Livebook to production. See the "Deployment" section on the sidebar for more information.
Livebook's default authentication method is token authentication. A token is automatically generated at startup and printed to the logs. * Admin authentication: this authenticates access to Livebook admin interface, where users can create, write, and manage notebooks. Both password and token authentication are provided.
* Deployed notebook authentication: additionally, when deploying notebooks as applications, each application may be password protected with a unique password. Only users authenticated as admin or with the password will be able to access them.
## Admin authentication
Livebook's default admin authentication method is token authentication. A token is automatically generated at startup and printed to the logs.
You may optionally enable password-based authentication by setting the environment variable `LIVEBOOK_PASSWORD` on startup or deployment. It must be at least 12 characters. You may optionally enable password-based authentication by setting the environment variable `LIVEBOOK_PASSWORD` on startup or deployment. It must be at least 12 characters.
To disable authentication altogether, you may set the environment variable `LIVEBOOK_TOKEN_ENABLED` to `false`. To disable authentication altogether, you may set the environment variable `LIVEBOOK_TOKEN_ENABLED` to `false`.
## Securing deployed notebooks
When you deploy a notebook as an application, the deployed application is not covered by Livebook's token/password authentication. In such cases, you have two options:
* You can set a password when deploying your notebook
* You can enable proxy authentication when deploying inside a cloud infrastructure.
See the "Deployment" section on the sidebar for more information

View file

@ -1,6 +1,6 @@
# Authentication with Basic Auth # Authentication with Basic Auth
Setting up Basic Authentication will protect all routes of your notebook. It is particularly useful for adding authentication to deployed notebooks. Basic Authentication is provided in addition to [Livebook's authentication](../authentication.md) for authoring notebooks. Setting up Basic Authentication is a simple mechanism for protecting all routes of your Livebook instance with a single username-password combo. However, because this password is shared across all users, this authentication mechanism cannot be used to identity users and more robust authentication methods provided by Livebook should be preferred. Basic Authentication occurs in addition to [Livebook's authentication](../authentication.md) for deployed notebooks and admins.
## How to ## How to
@ -15,7 +15,7 @@ livebook server
## Livebook Teams ## Livebook Teams
[Livebook Teams](https://livebook.dev/teams/) users have access to airgapped notebook deployment via Docker, with pre-configured Zero Trust Authentication, shared team secrets, and file storages. [Livebook Teams](https://livebook.dev/teams/) users can deploy notebooks with the click of a button with pre-configured Zero Trust Authentication, shared team secrets, and file storages. Both online and airgapped deployment mechanisms are supported.
Furthermore, if you are deploying multi-session apps via [Livebook Teams](https://livebook.dev/teams/), you can programmatically access data from the authenticated user by calling [`Kino.Hub.app_info/0`](https://hexdocs.pm/kino/Kino.Hub.html#app_info/0). Furthermore, if you are deploying multi-session apps via [Livebook Teams](https://livebook.dev/teams/), you can programmatically access data from the authenticated user by calling [`Kino.Hub.app_info/0`](https://hexdocs.pm/kino/Kino.Hub.html#app_info/0).

View file

@ -1,6 +1,6 @@
# Authentication with Cloudflare # Authentication with Cloudflare
Setting up Cloudflare authentication will protect all routes of your notebook. It is particularly useful for adding authentication to deployed notebooks. Cloudflare authentication is provided in addition to [Livebook's authentication](../authentication.md) for authoring notebooks. Setting up Cloudflare authentication will protect all routes of your Livebook instance. It is particularly useful for adding authentication to Livebook instances with deployed notebooks. Cloudflare authentication occurs in addition to [Livebook's authentication](../authentication.md) for deployed notebooks and admins.
Once Cloudflare is enabled, we recommend leaving the "/public" route of your instances still public. This route is used for integration with the [Livebook Badge](https://livebook.dev/badge/) and other conveniences. Once Cloudflare is enabled, we recommend leaving the "/public" route of your instances still public. This route is used for integration with the [Livebook Badge](https://livebook.dev/badge/) and other conveniences.
@ -17,7 +17,7 @@ https://developers.cloudflare.com/cloudflare-one/.
## Livebook Teams ## Livebook Teams
[Livebook Teams](https://livebook.dev/teams/) users have access to airgapped notebook deployment via Docker, with pre-configured Zero Trust Authentication, shared team secrets, and file storages. [Livebook Teams](https://livebook.dev/teams/) users can deploy notebooks with the click of a button with pre-configured Zero Trust Authentication, shared team secrets, and file storages. Both online and airgapped deployment mechanisms are supported.
Furthermore, if you are deploying multi-session apps via [Livebook Teams](https://livebook.dev/teams/), you can programmatically access data from the authenticated user by calling [`Kino.Hub.app_info/0`](https://hexdocs.pm/kino/Kino.Hub.html#app_info/0). Furthermore, if you are deploying multi-session apps via [Livebook Teams](https://livebook.dev/teams/), you can programmatically access data from the authenticated user by calling [`Kino.Hub.app_info/0`](https://hexdocs.pm/kino/Kino.Hub.html#app_info/0).

View file

@ -1,6 +1,6 @@
# Authentication with Google IAP # Authentication with Google IAP
Setting up Google IAP authentication will protect all routes of your notebook. It is particularly useful for adding authentication to deployed notebooks. Google IAP authentication is provided in addition to [Livebook's authentication](../authentication.md) for authoring notebooks. Setting up Google IAP authentication will protect all routes of your Livebook instance. It is particularly useful for adding authentication to Livebook instances with deployed notebooks. Google IAP authentication occurs in addition to [Livebook's authentication](../authentication.md) for deployed notebooks and admins.
Once Google IAP is enabled, we recommend leaving the "/public" route of your instances still public. This route is used for integration with the [Livebook Badge](https://livebook.dev/badge/) and other conveniences. Once Google IAP is enabled, we recommend leaving the "/public" route of your instances still public. This route is used for integration with the [Livebook Badge](https://livebook.dev/badge/) and other conveniences.
@ -17,7 +17,7 @@ For more details about how to find your JWT audience, see https://cloud.google.c
## Livebook Teams ## Livebook Teams
[Livebook Teams](https://livebook.dev/teams/) users have access to airgapped notebook deployment via Docker, with pre-configured Zero Trust Authentication, shared team secrets, and file storages. [Livebook Teams](https://livebook.dev/teams/) users can deploy notebooks with the click of a button with pre-configured Zero Trust Authentication, shared team secrets, and file storages. Both online and airgapped deployment mechanisms are supported.
Furthermore, if you are deploying multi-session apps via [Livebook Teams](https://livebook.dev/teams/), you can programmatically access data from the authenticated user by calling [`Kino.Hub.app_info/0`](https://hexdocs.pm/kino/Kino.Hub.html#app_info/0). Furthermore, if you are deploying multi-session apps via [Livebook Teams](https://livebook.dev/teams/), you can programmatically access data from the authenticated user by calling [`Kino.Hub.app_info/0`](https://hexdocs.pm/kino/Kino.Hub.html#app_info/0).

View file

@ -1,6 +1,6 @@
# Authentication with Tailscale # Authentication with Tailscale
Setting up Tailscale authentication will protect all routes of your notebook. It is particularly useful for adding authentication to deployed notebooks. Tailscale authentication is provided in addition to [Livebook's authentication](../authentication.md) for authoring notebooks. Setting up Tailscale authentication will protect all routes of your Livebook instance. It is particularly useful for adding authentication to Livebook instances with deployed notebooks. Tailscale authentication occurs in addition to [Livebook's authentication](../authentication.md) for deployed notebooks and admins.
Once Tailscale is enabled, we recommend leaving the "/public" route of your instances still public. This route is used for integration with the [Livebook Badge](https://livebook.dev/badge/) and other conveniences. Once Tailscale is enabled, we recommend leaving the "/public" route of your instances still public. This route is used for integration with the [Livebook Badge](https://livebook.dev/badge/) and other conveniences.
@ -42,7 +42,7 @@ livebook server
## Livebook Teams ## Livebook Teams
[Livebook Teams](https://livebook.dev/teams/) users have access to airgapped notebook deployment via Docker, with pre-configured Zero Trust Authentication, shared team secrets, and file storages. [Livebook Teams](https://livebook.dev/teams/) users can deploy notebooks with the click of a button with pre-configured Zero Trust Authentication, shared team secrets, and file storages. Both online and airgapped deployment mechanisms are supported.
Furthermore, if you are deploying multi-session apps via [Livebook Teams](https://livebook.dev/teams/), you can programmatically access data from the authenticated user by calling [`Kino.Hub.app_info/0`](https://hexdocs.pm/kino/Kino.Hub.html#app_info/0). Furthermore, if you are deploying multi-session apps via [Livebook Teams](https://livebook.dev/teams/), you can programmatically access data from the authenticated user by calling [`Kino.Hub.app_info/0`](https://hexdocs.pm/kino/Kino.Hub.html#app_info/0).

View file

@ -1,6 +1,6 @@
# Authentication with Teleport # Authentication with Teleport
Setting up Teleport authentication will protect all routes of your notebook. It is particularly useful for adding authentication to deployed notebooks. Teleport authentication is provided in addition to [Livebook's authentication](../authentication.md) for authoring notebooks. Setting up Teleport authentication will protect all routes of your Livebook instance. It is particularly useful for adding authentication to Livebook instances with deployed notebooks. Teleport authentication occurs in addition to [Livebook's authentication](../authentication.md) for deployed notebooks and admins.
## How to ## How to
@ -17,7 +17,7 @@ on how Teleport authentication works.
## Livebook Teams ## Livebook Teams
[Livebook Teams](https://livebook.dev/teams/) users have access to airgapped notebook deployment via Docker, with pre-configured Zero Trust Authentication, shared team secrets, and file storages. [Livebook Teams](https://livebook.dev/teams/) users can deploy notebooks with the click of a button with pre-configured Zero Trust Authentication, shared team secrets, and file storages. Both online and airgapped deployment mechanisms are supported.
Furthermore, if you are deploying multi-session apps via [Livebook Teams](https://livebook.dev/teams/), you can programmatically access data from the authenticated user by calling [`Kino.Hub.app_info/0`](https://hexdocs.pm/kino/Kino.Hub.html#app_info/0). Furthermore, if you are deploying multi-session apps via [Livebook Teams](https://livebook.dev/teams/), you can programmatically access data from the authenticated user by calling [`Kino.Hub.app_info/0`](https://hexdocs.pm/kino/Kino.Hub.html#app_info/0).

View file

@ -45,6 +45,8 @@ defmodule Livebook.Config do
} }
] ]
@identity_provider_no_id [Livebook.ZTA.BasicAuth, Livebook.ZTA.PassThrough]
@identity_provider_type_to_module Map.new(@identity_providers, fn provider -> @identity_provider_type_to_module Map.new(@identity_providers, fn provider ->
{Atom.to_string(provider.type), provider.module} {Atom.to_string(provider.type), provider.module}
end) end)
@ -296,8 +298,8 @@ defmodule Livebook.Config do
""" """
@spec identity_provider_read_only?() :: boolean() @spec identity_provider_read_only?() :: boolean()
def identity_provider_read_only?() do def identity_provider_read_only?() do
{type, _module, _key} = Livebook.Config.identity_provider() {_type, module, _key} = Livebook.Config.identity_provider()
Map.has_key?(identity_provider_type_to_module(), type) module not in @identity_provider_no_id
end end
@doc """ @doc """
@ -703,7 +705,7 @@ defmodule Livebook.Config do
def identity_provider!(env) do def identity_provider!(env) do
case System.get_env(env) do case System.get_env(env) do
nil -> nil ->
{:session, LivebookWeb.ZTA.SessionIdentity, :unused} {:session, Livebook.ZTA.PassThrough, :unused}
"custom:" <> module_key -> "custom:" <> module_key ->
destructure [module, key], String.split(module_key, ":", parts: 2) destructure [module, key], String.split(module_key, ":", parts: 2)

View file

@ -45,7 +45,7 @@ defmodule Livebook.Users.User do
def changeset(user, attrs \\ %{}) do def changeset(user, attrs \\ %{}) do
user user
|> cast(attrs, [:id, :name, :email, :hex_color]) |> cast(attrs, [:name, :email, :hex_color])
|> validate_required([:id, :name, :hex_color]) |> validate_required([:hex_color])
end end
end end

View file

@ -1,4 +1,50 @@
defmodule Livebook.ZTA do defmodule Livebook.ZTA do
@type name :: atom()
@typedoc """
A metadata of keys returned by zero-trust authentication provider.
The following keys are supported:
* `:id` - a string that uniquely identifies the user
* `:name` - the user name
* `:email` - the user email
* `:payload` - the provider payload
Note that none of the keys are required. The metadata returned depends
on the provider.
"""
@type metadata :: %{
optional(:id) => String.t(),
optional(:name) => String.t(),
optional(:email) => String.t(),
optional(:payload) => map()
}
@doc """
Each provider must specify a child specification for its processes.
The `:name` and `:identity_key` keys are expected.
"""
@callback child_spec(name: name(), identity_key: String.t()) :: Supervisor.child_spec()
@doc """
Authenticates against the given name.
It will return one of:
* `{non_halted_conn, nil}` - the authentication failed and you must
halt the connection and render the appropriate report
* `{halted_conn, nil}` - the authentication failed and the connection
was modified accordingly to request the credentials
* `{non_halted_conn, metadata}` - the authentication succeed and the
following metadata about the user is available
"""
@callback authenticate(name(), Plug.Conn.t(), keyword()) :: {Plug.Conn.t(), metadata() | nil}
@doc false @doc false
def init do def init do
:ets.new(__MODULE__, [:named_table, :public, :set, read_concurrency: true]) :ets.new(__MODULE__, [:named_table, :public, :set, read_concurrency: true])

View file

@ -1,4 +1,7 @@
defmodule Livebook.ZTA.BasicAuth do defmodule Livebook.ZTA.BasicAuth do
@behaviour Livebook.ZTA
@impl true
def child_spec(opts) do def child_spec(opts) do
%{id: __MODULE__, start: {__MODULE__, :start_link, [opts]}} %{id: __MODULE__, start: {__MODULE__, :start_link, [opts]}}
end end
@ -12,6 +15,7 @@ defmodule Livebook.ZTA.BasicAuth do
:ignore :ignore
end end
@impl true
def authenticate(name, conn, _options) do def authenticate(name, conn, _options) do
{username, password} = Livebook.ZTA.get(name) {username, password} = Livebook.ZTA.get(name)
conn = Plug.BasicAuth.basic_auth(conn, username: username, password: password) conn = Plug.BasicAuth.basic_auth(conn, username: username, password: password)
@ -19,7 +23,7 @@ defmodule Livebook.ZTA.BasicAuth do
if conn.halted do if conn.halted do
{conn, nil} {conn, nil}
else else
{conn, %{payload: %{}}} {conn, %{}}
end end
end end
end end

View file

@ -1,4 +1,6 @@
defmodule Livebook.ZTA.Cloudflare do defmodule Livebook.ZTA.Cloudflare do
@behaviour Livebook.ZTA
use GenServer use GenServer
require Logger require Logger
import Plug.Conn import Plug.Conn
@ -16,6 +18,7 @@ defmodule Livebook.ZTA.Cloudflare do
GenServer.start_link(__MODULE__, options, name: name) GenServer.start_link(__MODULE__, options, name: name)
end end
@impl true
def authenticate(name, conn, _opts) do def authenticate(name, conn, _opts) do
token = get_req_header(conn, @assertion) token = get_req_header(conn, @assertion)
{identity, keys} = Livebook.ZTA.get(name) {identity, keys} = Livebook.ZTA.get(name)

View file

@ -1,4 +1,6 @@
defmodule Livebook.ZTA.GoogleIAP do defmodule Livebook.ZTA.GoogleIAP do
@behaviour Livebook.ZTA
use GenServer use GenServer
require Logger require Logger
import Plug.Conn import Plug.Conn
@ -16,6 +18,7 @@ defmodule Livebook.ZTA.GoogleIAP do
GenServer.start_link(__MODULE__, options, name: name) GenServer.start_link(__MODULE__, options, name: name)
end end
@impl true
def authenticate(name, conn, _opts) do def authenticate(name, conn, _opts) do
token = get_req_header(conn, @assertion) token = get_req_header(conn, @assertion)
{identity, keys} = Livebook.ZTA.get(name) {identity, keys} = Livebook.ZTA.get(name)

View file

@ -0,0 +1,13 @@
defmodule Livebook.ZTA.PassThrough do
@behaviour Livebook.ZTA
@impl true
def child_spec(_opts) do
%{id: __MODULE__, start: {Function, :identity, [:ignore]}}
end
@impl true
def authenticate(_, conn, _) do
{conn, %{}}
end
end

View file

@ -1,6 +1,8 @@
defmodule Livebook.ZTA.Tailscale do defmodule Livebook.ZTA.Tailscale do
@behaviour Livebook.ZTA
require Logger require Logger
@impl true
def child_spec(opts) do def child_spec(opts) do
%{id: __MODULE__, start: {__MODULE__, :start_link, [opts]}} %{id: __MODULE__, start: {__MODULE__, :start_link, [opts]}}
end end
@ -19,6 +21,7 @@ defmodule Livebook.ZTA.Tailscale do
:ignore :ignore
end end
@impl true
def authenticate(name, conn, _opts) do def authenticate(name, conn, _opts) do
remote_ip = to_string(:inet_parse.ntoa(conn.remote_ip)) remote_ip = to_string(:inet_parse.ntoa(conn.remote_ip))
tailscale_address = Livebook.ZTA.get(name) tailscale_address = Livebook.ZTA.get(name)

View file

@ -1,4 +1,6 @@
defmodule Livebook.ZTA.Teleport do defmodule Livebook.ZTA.Teleport do
@behaviour Livebook.ZTA
use GenServer use GenServer
require Logger require Logger
@ -21,6 +23,7 @@ defmodule Livebook.ZTA.Teleport do
GenServer.start_link(__MODULE__, options, name: name) GenServer.start_link(__MODULE__, options, name: name)
end end
@impl true
def authenticate(name, conn, _opts) do def authenticate(name, conn, _opts) do
token = Plug.Conn.get_req_header(conn, @assertion) token = Plug.Conn.get_req_header(conn, @assertion)
jwks = Livebook.ZTA.get(name) jwks = Livebook.ZTA.get(name)

View file

@ -16,8 +16,6 @@ defmodule LivebookWeb.UserPlug do
import Plug.Conn import Plug.Conn
import Phoenix.Controller import Phoenix.Controller
alias Livebook.Users.User
@impl true @impl true
def init(opts), do: opts def init(opts), do: opts
@ -31,17 +29,26 @@ defmodule LivebookWeb.UserPlug do
defp ensure_user_identity(conn) do defp ensure_user_identity(conn) do
{_type, module, _key} = Livebook.Config.identity_provider() {_type, module, _key} = Livebook.Config.identity_provider()
{conn, identity_data} = module.authenticate(LivebookWeb.ZTA, conn, []) {conn, identity_data} = module.authenticate(LivebookWeb.ZTA, conn, [])
if identity_data do cond do
put_session(conn, :identity_data, identity_data) identity_data ->
else # Ensure we have a unique ID to identify this user/session.
conn id =
|> put_status(:forbidden) identity_data[:id] || get_session(conn, :identity_data)[:id] ||
|> put_view(LivebookWeb.ErrorHTML) Livebook.Utils.random_long_id()
|> render("403.html", %{status: 403})
|> halt() put_session(conn, :identity_data, Map.put(identity_data, :id, id))
conn.halted ->
conn
true ->
conn
|> put_status(:forbidden)
|> put_view(LivebookWeb.ErrorHTML)
|> render("403.html", %{status: 403})
|> halt()
end end
end end
@ -51,9 +58,10 @@ defmodule LivebookWeb.UserPlug do
if Map.has_key?(conn.req_cookies, "lb_user_data") do if Map.has_key?(conn.req_cookies, "lb_user_data") do
conn conn
else else
identity_data = get_session(conn, :identity_data) encoded =
user_data = User.new() |> client_user_data() |> Map.merge(identity_data) %{"name" => nil, "hex_color" => Livebook.EctoTypes.HexColor.random()}
encoded = user_data |> Jason.encode!() |> Base.encode64() |> Jason.encode!()
|> Base.encode64()
# We disable HttpOnly, so that it can be accessed on the client # We disable HttpOnly, so that it can be accessed on the client
# and set expiration to 5 years # and set expiration to 5 years
@ -62,13 +70,6 @@ defmodule LivebookWeb.UserPlug do
end end
end end
defp client_user_data(user) do
user
|> Map.from_struct()
|> Map.delete(:id)
|> Map.delete(:payload)
end
# Copies user_data from cookie to session, so that it's # Copies user_data from cookie to session, so that it's
# accessible to LiveViews # accessible to LiveViews
defp mirror_user_data_in_session(conn) when conn.halted, do: conn defp mirror_user_data_in_session(conn) when conn.halted, do: conn

View file

@ -1,18 +0,0 @@
defmodule LivebookWeb.ZTA.SessionIdentity do
# This module implements the ZTA contract specific to Livebook cookies
import Plug.Conn
def authenticate(_, conn, _) do
if id = get_session(conn, :current_user_id) do
{conn, %{id: id}}
else
user_id = Livebook.Utils.random_long_id()
{put_session(conn, :current_user_id, user_id), %{id: user_id}}
end
end
def child_spec(_opts) do
%{id: __MODULE__, start: {Function, :identity, [:ignore]}}
end
end

View file

@ -39,9 +39,9 @@ defmodule Livebook.ConfigTest do
assert Config.identity_provider!("TEST_IDENTITY_PROVIDER") == {:custom, Module, nil} assert Config.identity_provider!("TEST_IDENTITY_PROVIDER") == {:custom, Module, nil}
end) end)
with_env([TEST_IDENTITY_PROVIDER: "custom:LivebookWeb.ZTA.SessionIdentity:extra"], fn -> with_env([TEST_IDENTITY_PROVIDER: "custom:Livebook.ZTA.PassThrough:extra"], fn ->
assert Config.identity_provider!("TEST_IDENTITY_PROVIDER") == assert Config.identity_provider!("TEST_IDENTITY_PROVIDER") ==
{:custom, LivebookWeb.ZTA.SessionIdentity, "extra"} {:custom, Livebook.ZTA.PassThrough, "extra"}
end) end)
with_env([TEST_IDENTITY_PROVIDER: "cloudflare:123"], fn -> with_env([TEST_IDENTITY_PROVIDER: "cloudflare:123"], fn ->

View file

@ -14,15 +14,6 @@ defmodule Livebook.Users.UserTest do
assert get_field(changeset, :hex_color) == "#000000" assert get_field(changeset, :hex_color) == "#000000"
end end
test "given empty name returns an error" do
user = build(:user)
attrs = %{"name" => ""}
changeset = User.changeset(user, attrs)
refute changeset.valid?
assert "can't be blank" in errors_on(changeset).name
end
test "given invalid color returns an error" do test "given invalid color returns an error" do
user = build(:user) user = build(:user)
attrs = %{"hex_color" => "#invalid"} attrs = %{"hex_color" => "#invalid"}

View file

@ -21,7 +21,7 @@ defmodule Livebook.ZTA.BasicAuthTest do
conn = put_req_header(context.conn, "authorization", authorization) conn = put_req_header(context.conn, "authorization", authorization)
start_supervised!({BasicAuth, context.options}) start_supervised!({BasicAuth, context.options})
assert {_conn, %{payload: %{}}} = BasicAuth.authenticate(@name, conn, []) assert {_conn, %{}} = BasicAuth.authenticate(@name, conn, [])
end end
test "returns nil when the username is invalid", context do test "returns nil when the username is invalid", context do

View file

@ -13,17 +13,17 @@ defmodule LivebookWeb.UserPlugTest do
|> fetch_cookies() |> fetch_cookies()
|> call() |> call()
assert get_session(conn, :current_user_id) != nil assert get_session(conn, :identity_data)[:id] != nil
end end
test "keeps user id in the session if present" do test "keeps user id in the session if present" do
conn = conn =
conn(:get, "/") conn(:get, "/")
|> init_test_session(%{current_user_id: "valid_user_id"}) |> init_test_session(%{identity_data: %{id: "valid_user_id"}})
|> fetch_cookies() |> fetch_cookies()
|> call() |> call()
assert get_session(conn, :current_user_id) != nil assert get_session(conn, :identity_data)[:id] != nil
end end
test "given no user_data cookie, generates and stores new data" do test "given no user_data cookie, generates and stores new data" do
@ -34,10 +34,8 @@ defmodule LivebookWeb.UserPlugTest do
|> call() |> call()
assert %{ assert %{
"email" => nil, "name" => nil,
"hex_color" => <<_::binary>>, "hex_color" => <<_::binary>>
"id" => <<_::binary>>,
"name" => nil
} = conn.cookies["lb_user_data"] |> Base.decode64!() |> Jason.decode!() } = conn.cookies["lb_user_data"] |> Base.decode64!() |> Jason.decode!()
end end