Skip to content

Commit

Permalink
feat(auth): add webauthn key list edit button
Browse files Browse the repository at this point in the history
  • Loading branch information
kloenk committed Jul 22, 2023
1 parent 7a9c50a commit d4cb9ab
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ defmodule ExFleetYards.Repo.Account.User.U2fToken do
"""

use TypedEctoSchema
require Logger

alias ExFleetYards.Repo
alias ExFleetYards.Repo.Account.User
Expand Down Expand Up @@ -39,6 +40,17 @@ defmodule ExFleetYards.Repo.Account.User.U2fToken do

import Ecto.Query

def get_key(user, key) do
key_query(user, key)
|> Repo.one()
end

def edit(key, params) do
edit_changeset(key, params)
|> Repo.update()
|> broadcast_change([:edit])
end

def user_allow_credentials(%User{id: user_id}), do: user_allow_credentials(user_id)

def user_allow_credentials(user_id) when is_binary(user_id) do
Expand Down Expand Up @@ -86,9 +98,17 @@ defmodule ExFleetYards.Repo.Account.User.U2fToken do
token
|> cast(attrs, [:user_id, :credential_id, :cose_key, :name])
|> validate_required([:user_id, :credential_id, :cose_key])
|> validate_length(:name, min: 2, max: 30)
|> unique_constraint([:credential_id])
end

def edit_changeset(token, attrs) do
token
|> cast(attrs, [:name])
|> validate_length(:name, min: 2, max: 30)
|> validate_required([:name])
end

@doc """
Subscribe to updates to the user webauthn key list.
"""
Expand All @@ -111,5 +131,15 @@ defmodule ExFleetYards.Repo.Account.User.U2fToken do
v
end

defp broadcast_change(v, _event) do
Logger.debug("Could not send broadcast for #{inspect(v)}")
v
end

defp broadcast_change(v, _event, _user) do
Logger.debug("Could not send broadcast for #{inspect(v)}")
v
end

defp topic(user_id) when is_binary(user_id), do: Atom.to_string(__MODULE__) <> ":" <> user_id
end
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,43 @@ defmodule ExFleetYardsAuth.CoreComponents do
"""
attr :type, :string, default: nil
attr :class, :string, default: nil
attr :role, :string, default: "primary"
attr :rest, :global, include: ~w(disabled form name value)

slot :inner_block, required: true

def button(assigns) do
def button(%{role: "cancel"} = assigns) do
class_list = [
"bg-red-500 hover:bg-red-600 active:bg-red-700 focus:border-red-600 ring-red-300 text-white",
assigns["class"]
]

assigns
|> assign(role: nil)
|> assign(class: Enum.join(class_list, ","))
|> button
end

def button(%{role: "primary"} = assigns) do
class_list = [
"bg-indigo-400 hover:bg-indigo-500 active:bg-indigo-600",
"focus:border-indigo-500 ring-indigo-300 text-white",
assigns["class"]
]

assigns
|> assign(role: nil)
|> assign(class: Enum.join(class_list, ","))
|> button
end

def button(%{role: nil} = assigns) do
~H"""
<button
type={@type}
class={[
"inline-flex items-center px-4 py-2 border border-transparent rounded-md font-semibold text-xs text-white",
"uppercase tracking-widest bg-indigo-400 hover:bg-indigo-500 active:bg-indigo-600 focus:outline-none",
"focus:border-indigo-500 focus:ring ring-indigo-300 disabled:opacity-25 transition ease-in-out duration-150",
"inline-flex items-center px-4 py-2 border border-transparent rounded-md font-semibold text-xs uppercase",
"tracking-widest focus:outline-none focus:ring disabled:opacity-25 transition ease-in-out duration-150",
@class
]}
{@rest}
Expand Down Expand Up @@ -78,8 +103,11 @@ defmodule ExFleetYardsAuth.CoreComponents do
assigns
|> assign(field: nil, id: assigns.id || field.id)
|> assign(:errors, Enum.map(field.errors, &translate_error(&1)))
|> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end)
|> assign_new(:value, fn -> field.value end)
|> assign(
:name,
assigns.name || if(assigns.multiple, do: field.name <> "[]", else: field.name)
)
|> assign(:value, assigns.value || field.value)
|> input()
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ defmodule ExFleetYardsAuth.Auth.SessionController do

def otp_verify(conn, %{"otp_code" => code}) do
sub = get_session(conn, :user_id)
# FIXME: use session instead of sub

if Totp.valid?(sub, code) do
user = Account.get_user_by_sub(sub)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ defmodule ExFleetYardsAuth.Auth.WebAuthNController do
Logger.debug("authenticated with webauthn", user_id: user_id)

user = Account.get_user_by_sub(user_id)
# TODO: params
{conn, redir} = ExFleetYardsAuth.Auth.log_in_user(conn, user)

conn
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<li
class="py-4 flex items-center justify-between"
id={@token.id}
xmlns="http://www.w3.org/1999/html"
>
<.form
for={@form}
phx-change="validate_edit"
phx-submit="save_edit"
class="flex items-center justify-between w-full"
>
<div class="flex-grow">
<.input type="text" field={@form[:name]} minlength="2" maxlength="30" />
</div>
<div class="flex items-center">
<.button
type="submit"
class="mr-3 text-indigo-600 hover:text-indigo-800"
phx-disable-with="Saving..."
>
Save
</.button>
<.button
type="button"
role="cancel"
class="text-red-600 hover:text-red-800"
phx-click="cancel_edit"
phx-value-id={@token.id}
>
Cancel
</.button>
</div>
</.form>
</li>
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@
<% else %>
<ul>
<%= for token <- @webauthn_keys do %>
<.token token={token} />
<%= if @edit_key != nil and @edit_key.id == token.id do %>
<.edit_token token={token} form={@edit_form} />
<% else %>
<.token token={token} />
<% end %>
<% end %>
</ul>
<% end %>
Expand All @@ -43,13 +47,12 @@
<div class="rounded-md shadow-sm -space-y-px mt-4">
<div>
<label for="otp_code" class="sr-only">U2F Token Registration</label>
<input
id="webauthn_key_name"
<.input
name="key_name"
id="webauthn_key_name"
type="text"
required
class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-500 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="Enter WebAuthN key Name"
placeholder="Enter WebAuthN key name"
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,11 @@
<span><%= @token.name %></span>
</div>
<div class="flex items-center">
<button type="button" class="mr-3 text-indigo-600 hover:text-indigo-800">
<.button type="button" phx-click="edit_key" phx-value-id={@token.id}>
Rename
</button>
<button
type="button"
class="text-red-600 hover:text-red-800"
phx-click="delete_key"
phx-value-id={@token.id}
>
</.button>
<.button type="button" role="cancel" phx-click="delete_key" phx-value-id={@token.id}>
Delete
</button>
</.button>
</div>
</li>
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,6 @@ defmodule ExFleetYardsAuth.Auth.WebAuthNLive do
ExFleetYardsAuth.Auth.WebAuthNHTML.index(assigns)
end

def token(assigns) do
IO.inspect(assigns)

~H"""
"""
end

def mount(params, %{"user_token" => user_token}, socket) do
{:ok, token, user} = ExFleetYardsAuth.Auth.get_user_from_token(user_token)
User.U2fToken.subscribe(user)
Expand All @@ -32,6 +24,8 @@ defmodule ExFleetYardsAuth.Auth.WebAuthNLive do
|> assign(:current_user, user)
|> assign(:totp_tokens, totp_tokens)
|> assign(:webauthn_keys, webauthn_keys)
|> assign(:edit_key, nil)
|> assign(:edit_form, nil)

{:ok, socket}
end
Expand All @@ -44,6 +38,56 @@ defmodule ExFleetYardsAuth.Auth.WebAuthNLive do
{:noreply, socket}
end

def handle_event("edit_key", %{"id" => key_id}, socket) do
user = socket.assigns[:current_user]
key = User.U2fToken.get_key(user, key_id)

changeset =
User.U2fToken.edit_changeset(key, %{})
|> to_form

{:noreply,
socket
|> assign(:edit_key, key)
|> assign(:edit_form, changeset)}
end

def handle_event(
"validate_edit",
%{"u2f_token" => params},
%{assigns: %{edit_key: key}} = socket
) do
changeset =
User.U2fToken.edit_changeset(key, params)
|> to_form()

{:noreply,
socket
|> assign(:edit_form, changeset)}
end

def handle_event("save_edit", %{"u2f_token" => params}, %{assigns: %{edit_key: key}} = socket) do
User.U2fToken.edit(key, params)
|> case do
{:error, e} ->
form = to_form(e)

{:noreply,
socket
|> assign(:edit_form, form)}

{:ok, key} ->
{:noreply,
socket
|> assign(:edit_form, nil)
|> assign(:edit_key, nil)}
end
end

def handle_event("cancel_edit", %{"id" => key_id}, socket) do
{:noreply, socket |> assign(:edit_key, nil) |> assign(:edit_form, nil)}
end

def handle_info({User.U2fToken, [:delete], _}, socket) do
webauthn_keys = User.U2fToken.key_list(socket.assigns[:current_user])
{:noreply, assign(socket, webauthn_keys: webauthn_keys)}
Expand All @@ -53,4 +97,10 @@ defmodule ExFleetYardsAuth.Auth.WebAuthNLive do
webauthn_keys = User.U2fToken.key_list(socket.assigns[:current_user])
{:noreply, assign(socket, webauthn_keys: webauthn_keys)}
end

def handle_info({User.U2fToken, [:edit], _}, socket) do
# dont refresh full list?
webauthn_keys = User.U2fToken.key_list(socket.assigns[:current_user])
{:noreply, assign(socket, webauthn_keys: webauthn_keys)}
end
end

0 comments on commit d4cb9ab

Please sign in to comment.