From 108097882db2ff229f2773f4cba693b0089cb464 Mon Sep 17 00:00:00 2001 From: Andrew Dryga Date: Mon, 7 Aug 2023 12:16:22 -0500 Subject: [PATCH] Add support for account slugs --- elixir/apps/domain/lib/domain/accounts.ex | 25 +++++++++ .../domain/lib/domain/accounts/account.ex | 1 + .../lib/domain/accounts/account/changeset.ex | 15 ++++- .../lib/domain/accounts/account/query.ex | 4 ++ .../20230807164429_add_account_slugs.exs | 11 ++++ .../apps/domain/test/domain/accounts_test.exs | 55 +++++++++++++++++++ elixir/apps/web/lib/web/auth.ex | 24 +++++--- .../lib/web/controllers/auth_controller.ex | 43 ++++++++------- elixir/apps/web/lib/web/live/auth/sign_in.ex | 4 +- .../identity_providers/saml/components.ex | 4 +- elixir/apps/web/lib/web/router.ex | 17 ++---- elixir/apps/web/test/web/auth_test.exs | 51 +++++++++++------ 12 files changed, 191 insertions(+), 63 deletions(-) create mode 100644 elixir/apps/domain/priv/repo/migrations/20230807164429_add_account_slugs.exs diff --git a/elixir/apps/domain/lib/domain/accounts.ex b/elixir/apps/domain/lib/domain/accounts.ex index b85c1fb37..e6fe30e39 100644 --- a/elixir/apps/domain/lib/domain/accounts.ex +++ b/elixir/apps/domain/lib/domain/accounts.ex @@ -15,6 +15,31 @@ defmodule Domain.Accounts do end end + def fetch_account_by_id_or_slug(id_or_slug, %Auth.Subject{} = subject) do + with :ok <- Auth.ensure_has_permissions(subject, Authorizer.view_accounts_permission()), + true <- not is_nil(id_or_slug) do + if Validator.valid_uuid?(id_or_slug) do + Account.Query.by_id(id_or_slug) + else + Account.Query.by_slug(id_or_slug) + end + |> Authorizer.for_subject(subject) + |> Repo.fetch() + else + false -> {:error, :not_found} + other -> other + end + end + + def fetch_account_by_id_or_slug(id_or_slug) do + if Validator.valid_uuid?(id_or_slug) do + Account.Query.by_id(id_or_slug) + else + Account.Query.by_slug(id_or_slug) + end + |> Repo.fetch() + end + def fetch_account_by_id(id) do if Validator.valid_uuid?(id) do Account.Query.by_id(id) diff --git a/elixir/apps/domain/lib/domain/accounts/account.ex b/elixir/apps/domain/lib/domain/accounts/account.ex index 416b23011..3490bf357 100644 --- a/elixir/apps/domain/lib/domain/accounts/account.ex +++ b/elixir/apps/domain/lib/domain/accounts/account.ex @@ -3,6 +3,7 @@ defmodule Domain.Accounts.Account do schema "accounts" do field :name, :string + field :slug, :string timestamps() end diff --git a/elixir/apps/domain/lib/domain/accounts/account/changeset.ex b/elixir/apps/domain/lib/domain/accounts/account/changeset.ex index ad8dc552d..68aacad53 100644 --- a/elixir/apps/domain/lib/domain/accounts/account/changeset.ex +++ b/elixir/apps/domain/lib/domain/accounts/account/changeset.ex @@ -4,7 +4,20 @@ defmodule Domain.Accounts.Account.Changeset do def create_changeset(attrs) do %Account{} - |> cast(attrs, [:name]) + |> cast(attrs, [:name, :slug]) |> validate_required([:name]) + |> validate_length(:name, min: 1, max: 255) + |> validate_slug() + |> validate_length(:slug, min: 3, max: 100) + end + + defp validate_slug(changeset) do + validate_change(changeset, :slug, fn field, slug -> + if valid_uuid?(slug) do + [{field, "must can not be a valid UUID"}] + else + [] + end + end) end end diff --git a/elixir/apps/domain/lib/domain/accounts/account/query.ex b/elixir/apps/domain/lib/domain/accounts/account/query.ex index ddb4185d9..8e308b5fa 100644 --- a/elixir/apps/domain/lib/domain/accounts/account/query.ex +++ b/elixir/apps/domain/lib/domain/accounts/account/query.ex @@ -9,4 +9,8 @@ defmodule Domain.Accounts.Account.Query do def by_id(queryable \\ all(), id) do where(queryable, [account: account], account.id == ^id) end + + def by_slug(queryable \\ all(), slug) do + where(queryable, [account: account], account.slug == ^slug) + end end diff --git a/elixir/apps/domain/priv/repo/migrations/20230807164429_add_account_slugs.exs b/elixir/apps/domain/priv/repo/migrations/20230807164429_add_account_slugs.exs new file mode 100644 index 000000000..af4cc884c --- /dev/null +++ b/elixir/apps/domain/priv/repo/migrations/20230807164429_add_account_slugs.exs @@ -0,0 +1,11 @@ +defmodule Domain.Repo.Migrations.AddAccountSlugs do + use Ecto.Migration + + def change do + alter table(:accounts) do + add(:slug, :string) + end + + create(unique_index(:accounts, [:slug], where: "deleted_at IS NULL") + end +end diff --git a/elixir/apps/domain/test/domain/accounts_test.exs b/elixir/apps/domain/test/domain/accounts_test.exs index 563d10247..1505caf43 100644 --- a/elixir/apps/domain/test/domain/accounts_test.exs +++ b/elixir/apps/domain/test/domain/accounts_test.exs @@ -42,6 +42,61 @@ defmodule Domain.AccountsTest do end end + describe "fetch_account_by_id_or_slug/2" do + setup do + account = + AccountsFixtures.create_account(slug: "follow_the_#{System.unique_integer([:positive])}") + + actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account) + identity = AuthFixtures.create_identity(account: account, actor: actor) + subject = AuthFixtures.create_subject(identity) + + %{ + account: account, + actor: actor, + identity: identity, + subject: subject + } + end + + test "returns error when account does not exist", %{subject: subject} do + assert fetch_account_by_id_or_slug(Ecto.UUID.generate(), subject) == {:error, :not_found} + assert fetch_account_by_id_or_slug("foo", subject) == {:error, :not_found} + end + + test "returns account when account exists", %{account: account, subject: subject} do + assert {:ok, fetched_account} = fetch_account_by_id_or_slug(account.id, subject) + assert fetched_account.id == account.id + end + + test "returns error when subject has no permission to view accounts", %{subject: subject} do + subject = AuthFixtures.remove_permissions(subject) + + assert fetch_account_by_id_or_slug(Ecto.UUID.generate(), subject) == + {:error, + {:unauthorized, + [missing_permissions: [Accounts.Authorizer.view_accounts_permission()]]}} + end + end + + describe "fetch_account_by_id_or_slug/1" do + test "returns error when account does not exist" do + assert fetch_account_by_id_or_slug(Ecto.UUID.generate()) == {:error, :not_found} + assert fetch_account_by_id_or_slug("foo") == {:error, :not_found} + end + + test "returns account when account exists" do + account = + AccountsFixtures.create_account(slug: "follow_the_#{System.unique_integer([:positive])}") + + assert {:ok, fetched_account} = fetch_account_by_id_or_slug(account.id) + assert fetched_account.id == account.id + + assert {:ok, fetched_account} = fetch_account_by_id_or_slug(account.slug) + assert fetched_account.id == account.id + end + end + describe "fetch_account_by_id/1" do test "returns error when account is not found" do assert fetch_account_by_id(Ecto.UUID.generate()) == {:error, :not_found} diff --git a/elixir/apps/web/lib/web/auth.ex b/elixir/apps/web/lib/web/auth.ex index b43436e5f..83e7f8d8f 100644 --- a/elixir/apps/web/lib/web/auth.ex +++ b/elixir/apps/web/lib/web/auth.ex @@ -97,12 +97,18 @@ defmodule Web.Auth do @doc """ Fetches the session token from the session and assigns the subject to the connection. """ - def fetch_subject(%Plug.Conn{} = conn, _opts) do + def fetch_subject_and_account(%Plug.Conn{} = conn, _opts) do with token when not is_nil(token) <- Plug.Conn.get_session(conn, :session_token), {:ok, subject} <- Domain.Auth.sign_in(token, conn.assigns.user_agent, conn.remote_ip), - true <- conn.path_params["account_id"] == subject.account.id do - Plug.Conn.assign(conn, :subject, subject) + {:ok, account} <- + Domain.Accounts.fetch_account_by_id_or_slug( + conn.path_params["account_id_or_slug"], + subject + ) do + conn + |> Plug.Conn.assign(:account, account) + |> Plug.Conn.assign(:subject, subject) else _ -> conn end @@ -133,7 +139,7 @@ defmodule Web.Auth do conn |> Phoenix.Controller.put_flash(:error, "You must log in to access this page.") |> maybe_store_return_to() - |> Phoenix.Controller.redirect(to: ~p"/#{conn.path_params["account_id"]}/sign_in") + |> Phoenix.Controller.redirect(to: ~p"/#{conn.path_params["account_id_or_slug"]}/sign_in") |> Plug.Conn.halt() end end @@ -246,14 +252,13 @@ defmodule Web.Auth do end end - defp mount_subject(socket, params, session) do + defp mount_subject(socket, _params, session) do Phoenix.Component.assign_new(socket, :subject, fn -> user_agent = Phoenix.LiveView.get_connect_info(socket, :user_agent) remote_ip = Phoenix.LiveView.get_connect_info(socket, :peer_data).address with token when not is_nil(token) <- session["session_token"], - {:ok, subject} <- Domain.Auth.sign_in(token, user_agent, remote_ip), - true <- params["account_id"] == subject.account.id do + {:ok, subject} <- Domain.Auth.sign_in(token, user_agent, remote_ip) do subject else _ -> nil @@ -263,11 +268,12 @@ defmodule Web.Auth do defp mount_account( %{assigns: %{subject: subject}} = socket, - %{"account_id" => account_id}, + %{"account_id_or_slug" => account_id_or_slug}, _session ) do Phoenix.Component.assign_new(socket, :account, fn -> - with {:ok, account} <- Domain.Accounts.fetch_account_by_id(account_id, subject) do + with {:ok, account} <- + Domain.Accounts.fetch_account_by_id_or_slug(account_id_or_slug, subject) do account else _ -> nil diff --git a/elixir/apps/web/lib/web/controllers/auth_controller.ex b/elixir/apps/web/lib/web/controllers/auth_controller.ex index 9b34f251f..74b1697e6 100644 --- a/elixir/apps/web/lib/web/controllers/auth_controller.ex +++ b/elixir/apps/web/lib/web/controllers/auth_controller.ex @@ -20,7 +20,7 @@ defmodule Web.AuthController do This is a callback for the UserPass provider which checks login and password to authenticate the user. """ def verify_credentials(conn, %{ - "account_id" => account_id, + "account_id_or_slug" => account_id_or_slug, "provider_id" => provider_id, "userpass" => %{ "provider_identifier" => provider_identifier, @@ -41,18 +41,18 @@ defmodule Web.AuthController do conn |> put_flash(:userpass_provider_identifier, String.slice(provider_identifier, 0, 160)) |> put_flash(:error, "You can not use this method to sign in.") - |> redirect(to: "/#{account_id}/sign_in") + |> redirect(to: "/#{account_id_or_slug}/sign_in") {:error, :invalid_actor_type} -> conn |> put_flash(:info, "Please use client application to access Firezone.") - |> redirect(to: ~p"/#{account_id}") + |> redirect(to: ~p"/#{account_id_or_slug}") {:error, _reason} -> conn |> put_flash(:userpass_provider_identifier, String.slice(provider_identifier, 0, 160)) |> put_flash(:error, "Invalid username or password.") - |> redirect(to: "/#{account_id}/sign_in") + |> redirect(to: "/#{account_id_or_slug}/sign_in") end end @@ -60,7 +60,7 @@ defmodule Web.AuthController do This is a callback for the Email provider which sends login link. """ def request_magic_link(conn, %{ - "account_id" => account_id, + "account_id_or_slug" => account_id_or_slug, "provider_id" => provider_id, "email" => %{ "provider_identifier" => provider_identifier @@ -75,7 +75,7 @@ defmodule Web.AuthController do |> Web.Mailer.deliver() end - redirect(conn, to: "/#{account_id}/sign_in/providers/email/#{provider_id}") + redirect(conn, to: "/#{account_id_or_slug}/sign_in/providers/email/#{provider_id}") end @doc """ @@ -83,7 +83,7 @@ defmodule Web.AuthController do to authenticate a user. """ def verify_sign_in_token(conn, %{ - "account_id" => account_id, + "account_id_or_slug" => account_id_or_slug, "provider_id" => provider_id, "identity_id" => identity_id, "secret" => secret @@ -101,17 +101,17 @@ defmodule Web.AuthController do {:error, :not_found} -> conn |> put_flash(:error, "You can not use this method to sign in.") - |> redirect(to: "/#{account_id}/sign_in") + |> redirect(to: "/#{account_id_or_slug}/sign_in") {:error, :invalid_actor_type} -> conn |> put_flash(:info, "Please use client application to access Firezone.") - |> redirect(to: ~p"/#{account_id}") + |> redirect(to: ~p"/#{account_id_or_slug}") {:error, _reason} -> conn |> put_flash(:error, "The sign in link is invalid or expired.") - |> redirect(to: "/#{account_id}/sign_in") + |> redirect(to: "/#{account_id_or_slug}/sign_in") end end @@ -119,7 +119,10 @@ defmodule Web.AuthController do This controller redirects user to IdP during sign in for authentication while persisting verification state to prevent various attacks on OpenID Connect. """ - def redirect_to_idp(conn, %{"account_id" => account_id, "provider_id" => provider_id}) do + def redirect_to_idp(conn, %{ + "account_id_or_slug" => account_id_or_slug, + "provider_id" => provider_id + }) do with {:ok, provider} <- Domain.Auth.fetch_active_provider_by_id(provider_id) do redirect_url = url(~p"/#{provider.account_id}/sign_in/providers/#{provider.id}/handle_callback") @@ -129,7 +132,7 @@ defmodule Web.AuthController do {:error, :not_found} -> conn |> put_flash(:error, "You can not use this method to sign in.") - |> redirect(to: "/#{account_id}/sign_in") + |> redirect(to: "/#{account_id_or_slug}/sign_in") end end @@ -149,14 +152,14 @@ defmodule Web.AuthController do This controller handles IdP redirect back to the Firezone when user signs in. """ def handle_idp_callback(conn, %{ - "account_id" => account_id, + "account_id_or_slug" => account_id_or_slug, "provider_id" => provider_id, "state" => state, "code" => code }) do with {:ok, code_verifier, conn} <- verify_state_and_fetch_verifier(conn, provider_id, state) do payload = { - url(~p"/#{account_id}/sign_in/providers/#{provider_id}/handle_callback"), + url(~p"/#{account_id_or_slug}/sign_in/providers/#{provider_id}/handle_callback"), code_verifier, code } @@ -174,23 +177,23 @@ defmodule Web.AuthController do {:error, :not_found} -> conn |> put_flash(:error, "You can not use this method to sign in.") - |> redirect(to: "/#{account_id}/sign_in") + |> redirect(to: "/#{account_id_or_slug}/sign_in") {:error, :invalid_actor_type} -> conn |> put_flash(:info, "Please use client application to access Firezone.") - |> redirect(to: ~p"/#{account_id}") + |> redirect(to: ~p"/#{account_id_or_slug}") {:error, _reason} -> conn |> put_flash(:error, "You can not authenticate to this account.") - |> redirect(to: "/#{account_id}/sign_in") + |> redirect(to: "/#{account_id_or_slug}/sign_in") end else {:error, :invalid_state, conn} -> conn |> put_flash(:error, "Your session has expired, please try again.") - |> redirect(to: "/#{account_id}/sign_in") + |> redirect(to: "/#{account_id_or_slug}/sign_in") end end @@ -211,9 +214,9 @@ defmodule Web.AuthController do @state_cookie_key_prefix <> provider_id end - def sign_out(conn, %{"account_id" => account_id}) do + def sign_out(conn, %{"account_id_or_slug" => account_id_or_slug}) do conn |> Auth.sign_out() - |> redirect(to: ~p"/#{account_id}/sign_in") + |> redirect(to: ~p"/#{account_id_or_slug}/sign_in") end end diff --git a/elixir/apps/web/lib/web/live/auth/sign_in.ex b/elixir/apps/web/lib/web/live/auth/sign_in.ex index a9d15a56a..8079f5272 100644 --- a/elixir/apps/web/lib/web/live/auth/sign_in.ex +++ b/elixir/apps/web/lib/web/live/auth/sign_in.ex @@ -2,8 +2,8 @@ defmodule Web.Auth.SignIn do use Web, {:live_view, layout: {Web.Layouts, :public}} alias Domain.{Auth, Accounts} - def mount(%{"account_id" => account_id}, _session, socket) do - with {:ok, account} <- Accounts.fetch_account_by_id(account_id), + def mount(%{"account_id_or_slug" => account_id_or_slug}, _session, socket) do + with {:ok, account} <- Accounts.fetch_account_by_id_or_slug(account_id_or_slug), {:ok, [_ | _] = providers} <- Auth.list_active_providers_for_account(account) do providers_by_adapter = providers diff --git a/elixir/apps/web/lib/web/live/settings/identity_providers/saml/components.ex b/elixir/apps/web/lib/web/live/settings/identity_providers/saml/components.ex index 9bb010766..95fce25b2 100644 --- a/elixir/apps/web/lib/web/live/settings/identity_providers/saml/components.ex +++ b/elixir/apps/web/lib/web/live/settings/identity_providers/saml/components.ex @@ -163,8 +163,8 @@ defmodule Web.Settings.IdentityProviders.SAML.Components do > <.icon name="hero-document-duplicate" class="w-5 h-5 mr-1" /> - - <%= url(~p"/#{@account}/scim/v2") %> + + <%= "/#{@account}/scim/v2" %> diff --git a/elixir/apps/web/lib/web/router.ex b/elixir/apps/web/lib/web/router.ex index 48b2e43e6..225c5f1f5 100644 --- a/elixir/apps/web/lib/web/router.ex +++ b/elixir/apps/web/lib/web/router.ex @@ -9,7 +9,7 @@ defmodule Web.Router do plug :fetch_live_flash plug :put_root_layout, {Web.Layouts, :root} plug :fetch_user_agent - plug :fetch_subject + plug :fetch_subject_and_account end pipeline :api do @@ -46,7 +46,7 @@ defmodule Web.Router do end end - scope "/:account_id/sign_in", Web do + scope "/:account_id_or_slug/sign_in", Web do pipe_through [:browser, :redirect_if_user_is_authenticated] live_session :redirect_if_user_is_authenticated, @@ -75,20 +75,13 @@ defmodule Web.Router do end end - scope "/:account_id/scim/v2", Web do - pipe_through [:api] - - get "/", ScimController, :index - # TODO: SCIM endpoints - end - - scope "/:account_id", Web do + scope "/:account_id_or_slug", Web do pipe_through [:browser] get "/sign_out", AuthController, :sign_out end - scope "/:account_id", Web do + scope "/:account_id_or_slug", Web do pipe_through [:browser, :ensure_authenticated_admin] live_session :ensure_authenticated, @@ -193,7 +186,7 @@ defmodule Web.Router do live_session :landing, on_mount: [Web.Sandbox] do - live "/:account_id/", Landing + live "/:account_id_or_slug/", Landing live "/", Landing end end diff --git a/elixir/apps/web/test/web/auth_test.exs b/elixir/apps/web/test/web/auth_test.exs index e56208ee6..cf7fcf806 100644 --- a/elixir/apps/web/test/web/auth_test.exs +++ b/elixir/apps/web/test/web/auth_test.exs @@ -105,7 +105,7 @@ defmodule Web.AuthTest do end end - describe "fetch_subject/2" do + describe "fetch_subject_and_account/2" do setup context do %{conn: assign(context.conn, :user_agent, context.admin_subject.context.user_agent)} end @@ -116,14 +116,15 @@ defmodule Web.AuthTest do conn = %{ conn - | path_params: %{"account_id" => subject.account.id}, + | path_params: %{"account_id_or_slug" => subject.account.id}, remote_ip: {100, 64, 100, 58} } |> put_session(:session_token, session_token) - |> fetch_subject([]) + |> fetch_subject_and_account([]) assert conn.assigns.subject.identity.id == subject.identity.id assert conn.assigns.subject.actor.id == subject.actor.id + assert conn.assigns.account.id == subject.account.id end test "does not authenticate to an incorrect account", %{conn: conn, admin_subject: subject} do @@ -132,19 +133,21 @@ defmodule Web.AuthTest do conn = %{ conn - | path_params: %{"account_id" => Ecto.UUID.generate()}, + | path_params: %{"account_id_or_slug" => Ecto.UUID.generate()}, remote_ip: {100, 64, 100, 58} } |> put_session(:session_token, session_token) - |> fetch_subject([]) + |> fetch_subject_and_account([]) refute Map.has_key?(conn.assigns, :subject) + refute Map.has_key?(conn.assigns, :account) end test "does not authenticate if data is missing", %{conn: conn} do - conn = fetch_subject(conn, []) + conn = fetch_subject_and_account(conn, []) refute get_session(conn, :session_token) refute Map.has_key?(conn.assigns, :subject) + refute Map.has_key?(conn.assigns, :account) end end @@ -168,7 +171,7 @@ defmodule Web.AuthTest do describe "ensure_authenticated/2" do setup context do - %{conn: %{context.conn | path_params: %{"account_id" => context.account.id}}} + %{conn: %{context.conn | path_params: %{"account_id_or_slug" => context.account.id}}} end test "redirects if user is not authenticated", %{account: account, conn: conn} do @@ -242,7 +245,7 @@ defmodule Web.AuthTest do } do {:ok, session_token} = Domain.Auth.create_session_token_from_subject(subject) session = conn |> put_session(:session_token, session_token) |> get_session() - params = %{"account_id" => subject.account.id} + params = %{"account_id_or_slug" => subject.account.id} assert {:cont, updated_socket} = on_mount(:mount_subject, params, session, socket) assert updated_socket.assigns.subject.identity.id == subject.identity.id @@ -255,7 +258,7 @@ defmodule Web.AuthTest do } do session_token = "invalid_token" session = conn |> put_session(:session_token, session_token) |> get_session() - params = %{"account_id" => subject.account.id} + params = %{"account_id_or_slug" => subject.account.id} assert {:cont, updated_socket} = on_mount(:mount_subject, params, session, socket) assert is_nil(updated_socket.assigns.subject) @@ -267,23 +270,37 @@ defmodule Web.AuthTest do admin_subject: subject } do session = conn |> get_session() - params = %{"account_id" => subject.account.id} + params = %{"account_id_or_slug" => subject.account.id} assert {:cont, updated_socket} = on_mount(:mount_subject, params, session, socket) assert is_nil(updated_socket.assigns.subject) end + end + describe "on_mount: assign_account" do test "assigns nil to subject assign if account_id doesn't match token", %{ conn: conn, - socket: socket, admin_subject: subject } do + socket = %LiveView.Socket{ + assigns: %{ + __changed__: %{}, + subject: subject + }, + private: %{ + connect_info: %{ + user_agent: subject.context.user_agent, + peer_data: %{address: {100, 64, 100, 58}} + } + } + } + {:ok, session_token} = Domain.Auth.create_session_token_from_subject(subject) session = conn |> put_session(:session_token, session_token) |> get_session() - params = %{"account_id" => Ecto.UUID.generate()} + params = %{"account_id_or_slug" => Ecto.UUID.generate()} - assert {:cont, updated_socket} = on_mount(:mount_subject, params, session, socket) - assert is_nil(updated_socket.assigns.subject) + assert {:cont, updated_socket} = on_mount(:mount_account, params, session, socket) + assert is_nil(updated_socket.assigns.account) end end @@ -311,7 +328,7 @@ defmodule Web.AuthTest do } do {:ok, session_token} = Domain.Auth.create_session_token_from_subject(subject) session = conn |> put_session(:session_token, session_token) |> get_session() - params = %{"account_id" => subject.account.id} + params = %{"account_id_or_slug" => subject.account.id} assert {:cont, updated_socket} = on_mount(:ensure_authenticated, params, session, socket) @@ -383,7 +400,7 @@ defmodule Web.AuthTest do |> put_session(:session_token, session_token) |> get_session() - params = %{"account_id" => subject.account.id} + params = %{"account_id_or_slug" => subject.account.id} assert {:halt, updated_socket} = on_mount(:redirect_if_user_is_authenticated, params, session, socket) @@ -398,7 +415,7 @@ defmodule Web.AuthTest do } do session = get_session(conn) - params = %{"account_id" => subject.account.id} + params = %{"account_id_or_slug" => subject.account.id} assert {:cont, updated_socket} = on_mount(:redirect_if_user_is_authenticated, params, session, socket)