Auto-suggest name for new Passkeys
- Use plain client data json everywhere - Expose AAGUID and backed_up on Credentials - Make attestation verifiers configurable - Auto-suggest names for passkeys and show icons
This commit is contained in:
@@ -1,11 +1,10 @@
|
||||
<tr style="view-transition-name: <%= dom_id(credential) %>">
|
||||
<td><strong><%= credential.name.presence || "Passkey" %></strong></td>
|
||||
<td>
|
||||
<%= button_to user_credential_path(Current.user, credential), method: :delete,
|
||||
class: "btn btn--circle txt-negative txt-xx-small borderless",
|
||||
data: { turbo_confirm: "Are you sure you want to remove this passkey?" } do %>
|
||||
<%= icon_tag "trash" %>
|
||||
<span class="for-screen-reader">Remove this passkey</span>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<li class="list-style-none margin-none" style="view-transition-name: <%= dom_id(credential) %>">
|
||||
<%= link_to edit_user_credential_path(Current.user, credential), class: "txt-ink flex align-center gap txt-medium full-width" do %>
|
||||
<% authenticator = credential.authenticator %>
|
||||
<%= image_tag authenticator&.icon&.[](:light) || "passkeys/generic_light.svg", size: 24, class: "flex-item-no-shrink passkey-icon passkey-icon--light", aria: { hidden: true } %>
|
||||
<%= image_tag authenticator&.icon&.[](:dark) || "passkeys/generic_dark.svg", size: 24, class: "flex-item-no-shrink passkey-icon passkey-icon--dark", aria: { hidden: true } %>
|
||||
<strong class="overflow-ellipsis min-width"><%= credential.name.presence || "Passkey" %></strong>
|
||||
<hr class="separator--horizontal flex-item-grow" style="--border-color: var(--color-ink-medium); --border-style: dashed" aria-hidden="true">
|
||||
<%= icon_tag "arrow-right", class: "txt-subtle txt-xx-small flex-item-no-shrink" %>
|
||||
<% end %>
|
||||
</li>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<% @page_title = "Name your passkey" %>
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= back_link_to "Passkeys", user_credentials_path(Current.user), "keydown.left@document->hotkey#click keydown.esc@document->hotkey#click" %>
|
||||
</div>
|
||||
|
||||
<h1 class="header__title" data-bridge--title-target="header"><%= @page_title %></h1>
|
||||
<% end %>
|
||||
|
||||
<section class="panel panel--wide shadow center txt-align-start flex flex-column gap">
|
||||
<% if params[:created] %>
|
||||
<p class="margin-none">Your passkey has been registered. Give it a name so you can identify it later.</p>
|
||||
<% end %>
|
||||
|
||||
<%= form_with model: @credential, scope: :credential, url: user_credential_path(Current.user, @credential), html: { class: "flex flex-column gap" } do |form| %>
|
||||
<div class="flex flex-column gap-half">
|
||||
<strong><%= form.label :name %></strong>
|
||||
<%= form.text_field :name, autofocus: true, class: "input", placeholder: "e.g. MacBook Pro, iPhone", data: { "1p-ignore": "" }, autocomplete: "off" %>
|
||||
</div>
|
||||
|
||||
<%= form.submit "Save", class: "btn btn--primary" %>
|
||||
<% end %>
|
||||
|
||||
<div class="txt-align-center">
|
||||
<%= button_to user_credential_path(Current.user, @credential), method: :delete,
|
||||
class: "btn txt-negative borderless txt-small",
|
||||
data: { turbo_confirm: "Are you sure you want to remove this passkey?" } do %>
|
||||
<%= icon_tag "trash" %>
|
||||
<span>Remove this passkey</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
@@ -8,25 +8,33 @@
|
||||
<h1 class="header__title" data-bridge--title-target="header"><%= @page_title %></h1>
|
||||
<% end %>
|
||||
|
||||
<section class="panel panel--wide shadow center txt-align-start">
|
||||
<p class="margin-none-block-start">Passkeys let you sign in using your device’s biometrics, PIN, or security key—no email code needed.</p>
|
||||
<section class="panel panel--wide shadow center"
|
||||
data-controller="credential"
|
||||
data-credential-public-key-value="<%= @creation_options.as_json.to_json %>"
|
||||
data-credential-register-url-value="<%= user_credentials_path(Current.user) %>">
|
||||
|
||||
<p class="margin-none-block-start"><strong>Passkeys let you sign in securely without a password or email code.</strong></p>
|
||||
|
||||
<% if @credentials.any? %>
|
||||
<table class="access-tokens">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Passkey</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%= render partial: "users/credentials/credential", collection: @credentials %>
|
||||
</tbody>
|
||||
</table>
|
||||
<ul class="flex flex-column gap margin-none-block-start unpad full-width">
|
||||
<%= render partial: "users/credentials/credential", collection: @credentials %>
|
||||
</ul>
|
||||
<% end %>
|
||||
|
||||
<%= link_to new_user_credential_path(Current.user), class: "btn btn--link" do %>
|
||||
<p class="txt-small pad-inline-double pad-block-half">Your browser will prompt you to create a passkey
|
||||
using your device's biometrics, PIN, or security key</p>
|
||||
|
||||
<button type="button" data-action="credential#create" data-credential-target="button" class="btn btn--link center txt-medium">
|
||||
<%= icon_tag "add" %>
|
||||
<span>Add a passkey</span>
|
||||
<% end %>
|
||||
<span>Register passkey</span>
|
||||
</button>
|
||||
|
||||
<p data-credential-target="error" class="txt-negative" hidden>
|
||||
Something went wrong while registering your passkey.
|
||||
</p>
|
||||
|
||||
<p data-credential-target="cancelled" class="txt-subtle" hidden>
|
||||
Passkey registration was cancelled.
|
||||
Try again when you are ready.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
<% @page_title = "Add a passkey" %>
|
||||
|
||||
<% content_for :header do %>
|
||||
<div class="header__actions header__actions--start">
|
||||
<%= back_link_to "Passkeys", user_credentials_path(Current.user), "keydown.left@document->hotkey#click keydown.esc@document->hotkey#click" %>
|
||||
</div>
|
||||
|
||||
<h1 class="header__title" data-bridge--title-target="header"><%= @page_title %></h1>
|
||||
<% end %>
|
||||
|
||||
<section class="panel panel--wide shadow center txt-align-start">
|
||||
<%= form_with url: user_credentials_path(Current.user),
|
||||
data: { controller: "credential", credential_public_key_value: @creation_options.as_json },
|
||||
html: { class: "flex flex-column gap" } do |form| %>
|
||||
|
||||
<div class="flex flex-column gap-half">
|
||||
<strong><label for="credential_name">Passkey name</label></strong>
|
||||
<input type="text" id="credential_name" name="credential[name]" required autofocus class="input" placeholder="e.g. MacBook Pro, iPhone">
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="credential[client_data_json]" data-credential-target="clientDataJSON">
|
||||
<input type="hidden" name="credential[attestation_object]" data-credential-target="attestationObject">
|
||||
|
||||
<button type="button" data-action="credential#create" class="btn btn--link center txt-medium">
|
||||
<%= icon_tag "add" %>
|
||||
<span>Register passkey</span>
|
||||
</button>
|
||||
<% end %>
|
||||
|
||||
<%= link_to "Cancel and go back", user_credentials_path(Current.user), hidden: true %>
|
||||
</section>
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="flex flex-column gap position-relative">
|
||||
<% if Current.user == @user %>
|
||||
<%= link_to user_credentials_path(@user), class: "user-edit-link btn", style: "inset: 0 auto auto 0", data: { controller: "tooltip" } do %>
|
||||
<%= icon_tag "key" %>
|
||||
<%= icon_tag "authentication" %>
|
||||
<span class="for-screen-reader">Manage passkeys</span>
|
||||
<% end %>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user