From 4edafb0a923c147cc4d712113f3646672df46e1f Mon Sep 17 00:00:00 2001 From: Alexandre de Souza Date: Fri, 8 Aug 2025 17:47:48 -0300 Subject: [PATCH] Shows error message if deployment is unauthorized --- lib/livebook/teams/requests.ex | 24 +++++++-- test/livebook_teams/cli/deploy_test.exs | 52 +++++++++++++++++++ test/livebook_teams/web/session_live_test.exs | 36 +++++++++++++ 3 files changed, 108 insertions(+), 4 deletions(-) diff --git a/lib/livebook/teams/requests.ex b/lib/livebook/teams/requests.ex index 495117601..cd1f5d450 100644 --- a/lib/livebook/teams/requests.ex +++ b/lib/livebook/teams/requests.ex @@ -8,6 +8,7 @@ defmodule Livebook.Teams.Requests do @deploy_key_prefix Teams.Constants.deploy_key_prefix() @error_message "Something went wrong, try again later or please file a bug if it persists" @unauthorized_error_message "You are not authorized to perform this action, make sure you have the access and you are not in a Livebook App Server/Offline instance" + @unauthorized_app_deployment_error_message "You are not authorized to perform this action, make sure you have the access to deploy apps to this deployment group" @typep api_result :: {:ok, map()} | error_result() @typep error_result :: {:error, map() | String.t()} | {:transport_error, String.t()} @@ -300,6 +301,11 @@ defmodule Livebook.Teams.Requests do defp upload(path, content, params, team) do build_req(team) |> Req.Request.put_header("content-length", "#{byte_size(content)}") + |> Req.Request.append_response_steps( + livebook_put_private: fn {request, response} -> + {request, Req.Response.put_private(response, :livebook_app_deployment, true)} + end + ) |> Req.post(url: path, params: params, body: content) |> handle_response() |> dispatch_messages(team) @@ -337,10 +343,20 @@ defmodule Livebook.Teams.Requests do defp handle_response(response) do case response do - {:ok, %{status: status} = response} when status in 200..299 -> {:ok, response.body} - {:ok, %{status: status} = response} when status in [410, 422] -> return_error(response) - {:ok, %{status: 401}} -> {:transport_error, @unauthorized_error_message} - _otherwise -> {:transport_error, @error_message} + {:ok, %{status: status} = response} when status in 200..299 -> + {:ok, response.body} + + {:ok, %{status: status} = response} when status in [410, 422] -> + return_error(response) + + {:ok, %{status: 401, private: %{livebook_app_deployment: true}}} -> + {:transport_error, @unauthorized_app_deployment_error_message} + + {:ok, %{status: 401}} -> + {:transport_error, @unauthorized_error_message} + + _otherwise -> + {:transport_error, @error_message} end end diff --git a/test/livebook_teams/cli/deploy_test.exs b/test/livebook_teams/cli/deploy_test.exs index 17ac230d8..bd73f16e9 100644 --- a/test/livebook_teams/cli/deploy_test.exs +++ b/test/livebook_teams/cli/deploy_test.exs @@ -112,6 +112,58 @@ defmodule LivebookCLI.Integration.DeployTest do end end + test "fails with unauthorized deploy key", + %{team: team, node: node, org: org, tmp_dir: tmp_dir} do + title = "Test CLI Deploy App" + slug = Utils.random_short_id() + app_path = Path.join(tmp_dir, "#{slug}.livemd") + {key, _} = TeamsRPC.create_deploy_key(node, org: org) + + deployment_group = + TeamsRPC.create_deployment_group(node, org: org, url: @url, deploy_auth: true) + + hub_id = team.id + deployment_group_id = to_string(deployment_group.id) + + stamp_notebook(app_path, """ + + + # #{title} + + ## Test Section + + ```elixir + IO.puts("Hello from CLI deployed app!") + ``` + """) + + output = + ExUnit.CaptureIO.capture_io(fn -> + assert_raise(LivebookCLI.Error, "Some app deployments failed.", fn -> + assert deploy( + key, + team.teams_key, + deployment_group.id, + app_path + ) == :ok + end) + end) + + assert output =~ "* Preparing to deploy notebook #{slug}.livemd" + + assert output =~ + "* Test CLI Deploy App failed to deploy. Transport error: You are not authorized to perform this action, make sure you have the access to deploy apps to this deployment group" + + refute_receive {:app_deployment_started, + %{ + title: ^title, + slug: ^slug, + deployment_group_id: ^deployment_group_id, + hub_id: ^hub_id, + deployed_by: "CLI" + }} + end + test "fails with invalid deploy key", %{team: team, node: node, org: org, tmp_dir: tmp_dir} do slug = Utils.random_short_id() app_path = Path.join(tmp_dir, "#{slug}.livemd") diff --git a/test/livebook_teams/web/session_live_test.exs b/test/livebook_teams/web/session_live_test.exs index a998e752b..6e2d0f590 100644 --- a/test/livebook_teams/web/session_live_test.exs +++ b/test/livebook_teams/web/session_live_test.exs @@ -603,5 +603,41 @@ defmodule LivebookWeb.Integration.SessionLiveTest do assert render(view) =~ "Failed to pack files: the notebook and its attachments have exceeded the maximum size of 20MB" end + + test "shows an error when the deployment is unauthorized", + %{team: team, org: org, node: node, conn: conn, session: session} do + Session.set_notebook_hub(session.pid, team.id) + + slug = Livebook.Utils.random_short_id() + app_settings = %{Livebook.Notebook.AppSettings.new() | slug: slug} + Session.set_app_settings(session.pid, app_settings) + + deployment_group = + TeamsRPC.create_deployment_group(node, mode: :online, org: org, deploy_auth: true) + + id = to_string(deployment_group.id) + assert_receive {:deployment_group_created, %{id: ^id}} + + Session.set_notebook_deployment_group(session.pid, id) + assert_receive {:operation, {:set_notebook_deployment_group, _, ^id}} + + %{files_dir: files_dir} = session + image_file = FileSystem.File.resolve(files_dir, "image.jpg") + :ok = FileSystem.File.write(image_file, :crypto.strong_rand_bytes(1024 * 1024)) + Session.add_file_entries(session.pid, [%{type: :attachment, name: "image.jpg"}]) + + {:ok, view, _} = live(conn, ~p"/sessions/#{session.id}/app-teams") + + # From this point forward we are in a child LV + view = find_live_child(view, "app-teams") + assert render(view) =~ "App deployment with Livebook Teams" + + view + |> element("button", "Deploy") + |> render_click() + + assert render(view) =~ + "You are not authorized to perform this action, make sure you have the access to deploy apps to this deployment group" + end end end