Add secret key to personal hub edit form (#1776)

Co-authored-by: José Valim <jose.valim@dashbit.co>
This commit is contained in:
Jonatan Kłosko 2023-03-13 20:10:11 +01:00 committed by GitHub
parent 52f94a09e4
commit 9712554c14
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 112 additions and 40 deletions

View file

@ -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

View file

@ -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}
/>
</.with_password_toggle>
@ -310,21 +310,11 @@ defmodule LivebookWeb.FormComponents do
~H"""
<.field_wrapper id={@id} name={@name} label={@label} errors={@errors}>
<div class="flex border-[1px] bg-gray-50 rounded-lg space-x-4 items-center">
<div
id={"#{@id}-picker"}
class="grid grid-cols-1 md:grid-cols-3 w-full"
phx-hook="EmojiPicker"
>
<div class="place-content-start">
<div class="p-1 pl-3">
<div class="flex border bg-gray-50 rounded-lg space-x-4 items-center">
<div id={"#{@id}-picker"} class="flex w-full" phx-hook="EmojiPicker">
<div class="grow p-1 pl-3">
<span id={"#{@id}-preview"} data-emoji-preview><%= @value %></span>
</div>
</div>
<div />
<div class="flex items-center place-content-end">
<button
id={"#{@id}-button"}
type="button"
@ -333,7 +323,6 @@ defmodule LivebookWeb.FormComponents do
>
<.remix_icon icon="emotion-line" class="text-xl" />
</button>
</div>
<input
type="hidden"
name={@name}
@ -434,7 +423,7 @@ defmodule LivebookWeb.FormComponents do
def error(assigns) do
~H"""
<p class="text-red-600 text-sm hidden phx-form-error:block">
<p class="mt-0.5 text-red-600 text-sm hidden phx-form-error:block">
<%= render_slot(@inner_block) %>
</p>
"""

View file

@ -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
</button>
</div>
</.form>
@ -76,6 +76,61 @@ defmodule LivebookWeb.Hub.Edit.PersonalComponent do
target={@myself}
/>
</div>
<div class="flex flex-col space-y-4">
<h2 class="text-xl text-gray-800 font-medium pb-2 border-b border-gray-200">
Stamping
</h2>
<p class="text-gray-700">
Notebooks may be stamped using your <span class="font-medium text-gray-800">secret key</span>.
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.
</p>
<p class="text-gray-700">
If you change the <span class="font-medium text-gray-800">secret key</span>, you will need
to grant access to secrets once again in previously stamped notebooks.
</p>
<.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}
>
<div class="flex space-x-2">
<div class="grow">
<.password_field field={f[:secret_key]} label="Secret key" />
</div>
<div class="mt-6">
<span class="tooltip top" data-tooltip="Generate">
<button
class="button-base button-outlined-gray button-square-icon"
type="button"
phx-click="generate_secret_key"
phx-target={@myself}
>
<.remix_icon icon="refresh-line" class="text-xl" />
</button>
</span>
</div>
</div>
<div>
<button
class="button-base button-blue"
type="submit"
phx-disable-with="Updating..."
disable={not @stamp_changeset.valid?}
>
Save
</button>
</div>
</.form>
</div>
</div>
<.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