diff --git a/app/assets/javascripts/koi/controllers/webauthn_registration_controller.js b/app/assets/javascripts/koi/controllers/webauthn_registration_controller.js index b0ff5b92..25883639 100644 --- a/app/assets/javascripts/koi/controllers/webauthn_registration_controller.js +++ b/app/assets/javascripts/koi/controllers/webauthn_registration_controller.js @@ -6,8 +6,11 @@ import { } from "@github/webauthn-json/browser-ponyfill"; export default class WebauthnRegistrationController extends Controller { - static values = { options: Object }; - static targets = ["response"]; + static values = { + options: Object, + response: String, + }; + static targets = ["intro", "nickname", "response"]; submit(e) { if (this.responseTarget.value === "") { @@ -16,12 +19,17 @@ export default class WebauthnRegistrationController extends Controller { } } - createCredential() { - create(this.options).then((response) => { - this.responseTarget.value = JSON.stringify(response); + async createCredential() { + const response = await create(this.options); - this.element.requestSubmit(); - }); + this.responseValue = JSON.stringify(response); + this.responseTarget.value = JSON.stringify(response); + } + + responseValueChanged(response) { + const responsePresent = response !== ""; + this.introTarget.toggleAttribute("hidden", responsePresent); + this.nicknameTarget.toggleAttribute("hidden", !responsePresent); } get options() { diff --git a/app/assets/stylesheets/koi/base/_flow.scss b/app/assets/stylesheets/koi/base/_flow.scss new file mode 100644 index 00000000..7630019b --- /dev/null +++ b/app/assets/stylesheets/koi/base/_flow.scss @@ -0,0 +1,8 @@ +/* +FLOW COMPOSITION +Like the Every Layout stack: https://every-layout.dev/layouts/stack/ +Info about this implementation: https://piccalil.li/quick-tip/flow-utility/ +*/ +.flow > * + * { + margin-top: var(--flow-space, 1em); +} diff --git a/app/assets/stylesheets/koi/base/_index.scss b/app/assets/stylesheets/koi/base/_index.scss index eae4b74d..ef550c0f 100644 --- a/app/assets/stylesheets/koi/base/_index.scss +++ b/app/assets/stylesheets/koi/base/_index.scss @@ -1,8 +1,10 @@ @use "button"; @use "icon"; @use "input"; +@use "flow"; @use "link"; @use "list"; +@use "repel"; @use "tables"; @use "typography"; diff --git a/app/assets/stylesheets/koi/base/_repel.scss b/app/assets/stylesheets/koi/base/_repel.scss new file mode 100644 index 00000000..78179a7e --- /dev/null +++ b/app/assets/stylesheets/koi/base/_repel.scss @@ -0,0 +1,23 @@ +/* +REPEL +A little layout that pushes items away from each other where +there is space in the viewport and stacks on small viewports + +CUSTOM PROPERTIES AND CONFIGURATION +--gutter (var(--space-s-m)): This defines the space +between each item. + +--repel-vertical-alignment How items should align vertically. +Can be any acceptable flexbox alignment value. +*/ +.repel { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: var(--repel-vertical-alignment, center); + gap: var(--gutter, var(--space-s-m)); +} + +.repel[data-nowrap] { + flex-wrap: nowrap; +} diff --git a/app/controllers/concerns/koi/controller/has_webauthn.rb b/app/controllers/concerns/koi/controller/has_webauthn.rb index c18326a6..2b7346b3 100644 --- a/app/controllers/concerns/koi/controller/has_webauthn.rb +++ b/app/controllers/concerns/koi/controller/has_webauthn.rb @@ -36,7 +36,8 @@ def webauthn_authenticate! Admin::Credential.find_by!(external_id: credential.id) end - stored_credential.update!(sign_count: webauthn_credential.sign_count) + stored_credential.update(sign_count: webauthn_credential.sign_count) + stored_credential.touch stored_credential.admin end diff --git a/app/views/admin/admin_users/index.html.erb b/app/views/admin/admin_users/index.html.erb index 202e317c..8bedd81a 100644 --- a/app/views/admin/admin_users/index.html.erb +++ b/app/views/admin/admin_users/index.html.erb @@ -15,6 +15,9 @@ <% row.select %> <% row.link :name, url: :admin_admin_user_path %> <% row.text :email %> + <% row.boolean :credentials, label: "Passkey" do |cell| %> + <%= cell.value.any? ? "Yes" : "No" %> + <% end %> <% end %> <%= table_pagination_with(collection:) %> diff --git a/app/views/admin/admin_users/show.html+self.erb b/app/views/admin/admin_users/show.html+self.erb index 3a8b88b6..d68197b7 100644 --- a/app/views/admin/admin_users/show.html+self.erb +++ b/app/views/admin/admin_users/show.html+self.erb @@ -11,10 +11,9 @@ <%= builder.date :last_sign_in_at, label: { text: "Last sign in" } %> <% end %> -
+ Passkeys are secure secrets that are stored by your device. + You will need the device where your passkey is stored to log in. +
++ Unlike a password, your password doesn't get sent to the server when you log + in and can't be stolen in a data breach. When you log in with a passkey, + your operating system will prompt you for permission to use the passkey + secret to authenticate the login attempt. +
++ We recommend that you store your passkey on your phone or cloud account. + Depending on your browser, you may need to choose "more options" to see + a QR code that you can scan with your phone. +
+