From 601e93ae76726e4ee873da7ddb614f7c8afbe5e6 Mon Sep 17 00:00:00 2001 From: Alexandre de Souza Date: Wed, 8 Jan 2025 15:55:44 -0300 Subject: [PATCH] Implement the logout button (#2906) --- lib/livebook/config.ex | 17 ++++-- lib/livebook/teams/requests.ex | 9 ++++ lib/livebook/zta/basic_auth.ex | 2 +- lib/livebook/zta/livebook_teams.ex | 11 ++++ lib/livebook/zta/pass_through.ex | 2 +- .../components/layout_components.ex | 28 ++++++++-- .../controllers/auth_controller.ex | 11 ++++ .../controllers/auth_html/logout.html.heex | 18 +++++++ lib/livebook_web/live/hooks/sidebar_hook.ex | 27 ++++++++++ lib/livebook_web/plugs/user_plug.ex | 4 +- lib/livebook_web/router.ex | 5 ++ .../zta/livebook_teams_test.exs | 54 +++++++++++++++++++ 12 files changed, 177 insertions(+), 11 deletions(-) create mode 100644 lib/livebook_web/controllers/auth_html/logout.html.heex diff --git a/lib/livebook/config.ex b/lib/livebook/config.ex index a5c8e392a..367e8569e 100644 --- a/lib/livebook/config.ex +++ b/lib/livebook/config.ex @@ -1,8 +1,6 @@ defmodule Livebook.Config do alias Livebook.FileSystem - @type authentication_mode :: :token | :password | :disabled - @type authentication :: %{mode: :password, secret: String.t()} | %{mode: :token, secret: String.t()} @@ -68,7 +66,7 @@ defmodule Livebook.Config do @doc """ Returns the authentication configuration. """ - @spec authentication() :: authentication_mode() + @spec authentication() :: authentication() def authentication() do case Application.fetch_env!(:livebook, :authentication) do {:password, password} -> %{mode: :password, secret: password} @@ -270,6 +268,19 @@ defmodule Livebook.Config do module not in @identity_provider_no_id end + @doc """ + Returns if the identity provider supports logout. + """ + @spec logout_enabled?() :: boolean() + def logout_enabled?() do + {_type, module, _key} = Livebook.Config.identity_provider() + + identity_logout? = + Code.ensure_loaded?(module) and function_exported?(module, :logout, 2) + + authentication().mode != :disabled or identity_logout? + end + @doc """ Returns whether the application is running inside an iframe. """ diff --git a/lib/livebook/teams/requests.ex b/lib/livebook/teams/requests.ex index 125a2e52b..1b3cdb435 100644 --- a/lib/livebook/teams/requests.ex +++ b/lib/livebook/teams/requests.ex @@ -238,6 +238,15 @@ defmodule Livebook.Teams.Requests do get("/api/v1/org/identity", %{access_token: access_token}, team) end + @doc """ + Send a request to Livebook Team API to revoke session from given access token. + """ + @spec logout_identity_provider(Team.t(), String.t()) :: + {:ok, String.t()} | {:error, map()} | {:transport_error, String.t()} + def logout_identity_provider(team, access_token) do + post("/api/v1/org/identity/revoke", %{access_token: access_token}, team) + end + @doc """ Normalizes errors map into errors for the given schema. """ diff --git a/lib/livebook/zta/basic_auth.ex b/lib/livebook/zta/basic_auth.ex index 606ed2d11..a07e59f23 100644 --- a/lib/livebook/zta/basic_auth.ex +++ b/lib/livebook/zta/basic_auth.ex @@ -16,7 +16,7 @@ defmodule Livebook.ZTA.BasicAuth do end @impl true - def authenticate(name, conn, _options) do + def authenticate(name, conn, _opts) do {username, password} = Livebook.ZTA.get(name) conn = Plug.BasicAuth.basic_auth(conn, username: username, password: password) diff --git a/lib/livebook/zta/livebook_teams.ex b/lib/livebook/zta/livebook_teams.ex index 95ae2e0b3..69411b3de 100644 --- a/lib/livebook/zta/livebook_teams.ex +++ b/lib/livebook/zta/livebook_teams.ex @@ -35,6 +35,17 @@ defmodule Livebook.ZTA.LivebookTeams do end end + # Our extension to Livebook.ZTA to deal with logouts + def logout(name, %{assigns: %{current_user: %{payload: %{"access_token" => token}}}}) do + team = Livebook.ZTA.get(name) + + case Teams.Requests.logout_identity_provider(team, token) do + {:ok, _no_content} -> :ok + {:error, %{}} -> {:error, "You are already logged out."} + {:transport_error, reason} -> {:error, reason} + end + end + defp handle_request(conn, team, %{"teams_identity" => _, "code" => code}) do with {:ok, access_token} <- retrieve_access_token(team, code), {:ok, metadata} <- get_user_info(team, access_token) do diff --git a/lib/livebook/zta/pass_through.ex b/lib/livebook/zta/pass_through.ex index 77a6a9cc4..4e610410d 100644 --- a/lib/livebook/zta/pass_through.ex +++ b/lib/livebook/zta/pass_through.ex @@ -7,7 +7,7 @@ defmodule Livebook.ZTA.PassThrough do end @impl true - def authenticate(_, conn, _) do + def authenticate(_name, conn, _opts) do {conn, %{}} end end diff --git a/lib/livebook_web/components/layout_components.ex b/lib/livebook_web/components/layout_components.ex index 557ece315..e2805b9d6 100644 --- a/lib/livebook_web/components/layout_components.ex +++ b/lib/livebook_web/components/layout_components.ex @@ -111,21 +111,39 @@ defmodule LivebookWeb.LayoutComponents do to={~p"/settings"} current={@current_page} /> + <.hub_section hubs={@saved_hubs} current_page={@current_page} />
+