diff --git a/lib/livebook/hubs/personal.ex b/lib/livebook/hubs/personal.ex index 8504c6112..84f744e60 100644 --- a/lib/livebook/hubs/personal.ex +++ b/lib/livebook/hubs/personal.ex @@ -6,6 +6,8 @@ defmodule Livebook.Hubs.Personal do alias Livebook.Hubs + @secret_key_size 64 + @type t :: %__MODULE__{ id: String.t() | nil, hub_name: String.t() | nil, @@ -65,6 +67,12 @@ defmodule Livebook.Hubs.Personal do personal |> cast(attrs, @fields) |> validate_required(@fields) + |> validate_change(:secret_key, fn :secret_key, secret_key -> + case Base.url_decode64(secret_key, padding: false) do + {:ok, binary} when byte_size(binary) == @secret_key_size -> [] + _ -> [secret_key: "must be #{@secret_key_size} bytes in Base 64 URL alphabet"] + end + end) |> put_change(:id, id()) end @@ -73,7 +81,7 @@ defmodule Livebook.Hubs.Personal do """ @spec generate_secret_key() :: String.t() def generate_secret_key() do - :crypto.strong_rand_bytes(64) |> Base.url_encode64(padding: false) + :crypto.strong_rand_bytes(@secret_key_size) |> Base.url_encode64(padding: false) end end diff --git a/lib/livebook_web/components/form_components.ex b/lib/livebook_web/components/form_components.ex index 560809c72..faa170b70 100644 --- a/lib/livebook_web/components/form_components.ex +++ b/lib/livebook_web/components/form_components.ex @@ -105,7 +105,7 @@ defmodule LivebookWeb.FormComponents do name={@name} id={@id || @name} value={Phoenix.HTML.Form.normalize_value("text", @value)} - class="input" + class="input pr-8" {@rest} /> @@ -310,30 +310,19 @@ defmodule LivebookWeb.FormComponents do ~H""" <.field_wrapper id={@id} name={@name} label={@label} errors={@errors}> -
-
-
-
- <%= @value %> -
-
- -
- -
- +
+
+
+ <%= @value %>
+ + """ diff --git a/lib/livebook_web/live/hub/edit/personal_component.ex b/lib/livebook_web/live/hub/edit/personal_component.ex index 2c01624d3..e0bc59b70 100644 --- a/lib/livebook_web/live/hub/edit/personal_component.ex +++ b/lib/livebook_web/live/hub/edit/personal_component.ex @@ -17,7 +17,7 @@ defmodule LivebookWeb.Hub.Edit.PersonalComponent do {:ok, socket |> assign(assigns) - |> assign(changeset: changeset, secret_value: secret_value)} + |> assign(changeset: changeset, stamp_changeset: changeset, secret_value: secret_value)} end @impl true @@ -58,7 +58,7 @@ defmodule LivebookWeb.Hub.Edit.PersonalComponent do phx-disable-with="Updating..." disable={not @changeset.valid?} > - Update Hub + Save
@@ -76,6 +76,61 @@ defmodule LivebookWeb.Hub.Edit.PersonalComponent do target={@myself} />
+ +
+

+ Stamping +

+ +

+ Notebooks may be stamped using your secret key. + A stamp allows to securely store information such as the names of the secrets that you granted access to. + You must not share your secret key with others. But you may copy the secret key between + different machines you own. +

+

+ If you change the secret key, you will need + to grant access to secrets once again in previously stamped notebooks. +

+ + <.form + :let={f} + id={"#{@id}-stamp"} + class="flex flex-col mt-4 space-y-4" + for={@stamp_changeset} + phx-submit="stamp_save" + phx-change="stamp_validate" + phx-target={@myself} + > +
+
+ <.password_field field={f[:secret_key]} label="Secret key" /> +
+
+ + + +
+
+
+ +
+ +
<.modal @@ -183,20 +238,24 @@ defmodule LivebookWeb.Hub.Edit.PersonalComponent do @impl true def handle_event("save", %{"personal" => params}, socket) do - case Personal.update_hub(socket.assigns.hub, params) do - {:ok, hub} -> - {:noreply, - socket - |> put_flash(:success, "Hub updated successfully") - |> push_navigate(to: ~p"/hub/#{hub.id}")} - - {:error, changeset} -> - {:noreply, assign(socket, changeset: changeset)} - end + {:noreply, save(params, :changeset, socket)} end - def handle_event("validate", %{"personal" => attrs}, socket) do - {:noreply, assign(socket, changeset: Personal.validate_hub(socket.assigns.hub, attrs))} + def handle_event("validate", %{"personal" => params}, socket) do + {:noreply, validate(params, :changeset, socket)} + end + + def handle_event("stamp_save", %{"personal" => params}, socket) do + {:noreply, save(params, :stamp_changeset, socket)} + end + + def handle_event("stamp_validate", %{"personal" => params}, socket) do + {:noreply, validate(params, :stamp_changeset, socket)} + end + + def handle_event("generate_secret_key", %{}, socket) do + params = %{"secret_key" => Personal.generate_secret_key()} + {:noreply, validate(params, :stamp_changeset, socket)} end def handle_event("delete_hub_secret", attrs, socket) do @@ -205,4 +264,20 @@ defmodule LivebookWeb.Hub.Edit.PersonalComponent do {:noreply, socket} end + + defp save(params, changeset_name, socket) do + case Personal.update_hub(socket.assigns.hub, params) do + {:ok, hub} -> + socket + |> put_flash(:success, "Hub updated successfully") + |> push_navigate(to: ~p"/hub/#{hub.id}") + + {:error, changeset} -> + assign(socket, changeset_name, changeset) + end + end + + defp validate(params, changeset_name, socket) do + assign(socket, changeset_name, Personal.validate_hub(socket.assigns.hub, params)) + end end