diff --git a/.ruby-version b/.ruby-version index 2aa513199..fcdb2e109 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.4.7 +4.0.0 diff --git a/Gemfile.lock b/Gemfile.lock index c398d3a1e..3ac399744 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - katalyst-koi (5.2.0) + katalyst-koi (5.3.0) bcrypt importmap-rails katalyst-content (>= 3.0.0.alpha.1) @@ -21,7 +21,7 @@ PATH GEM remote: https://rubygems.org/ specs: - action_text-trix (2.1.15) + action_text-trix (2.1.16) railties actioncable (8.1.1) actionpack (= 8.1.1) @@ -67,7 +67,7 @@ GEM erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - active_storage_validations (3.0.2) + active_storage_validations (3.0.3) activejob (>= 6.1.4) activemodel (>= 6.1.4) activestorage (>= 6.1.4) @@ -110,7 +110,7 @@ GEM android_key_attestation (0.3.0) ast (2.4.3) base64 (0.3.0) - bcrypt (3.1.20) + bcrypt (3.1.21) better_html (2.2.0) actionview (>= 7.0) activesupport (>= 7.0) @@ -118,9 +118,9 @@ GEM erubi (~> 1.4) parser (>= 2.4) smart_properties - bigdecimal (3.3.1) + bigdecimal (4.0.1) bindata (2.5.1) - brakeman (7.1.1) + brakeman (7.1.2) racc builder (3.3.0) capybara (3.40.0) @@ -136,7 +136,7 @@ GEM chunky_png (1.4.0) compare-xml (0.66) nokogiri (~> 1.8) - concurrent-ruby (1.3.5) + concurrent-ruby (1.3.6) connection_pool (3.0.2) cose (1.3.1) cbor (~> 0.5.9) @@ -148,10 +148,10 @@ GEM cuprite (0.17) capybara (~> 3.0) ferrum (~> 0.17.0) - date (3.5.0) + date (3.5.1) diff-lcs (1.6.2) drb (2.2.3) - erb (6.0.0) + erb (6.0.1) erb_lint (0.9.0) activesupport better_html (>= 2.0.1) @@ -173,9 +173,9 @@ GEM concurrent-ruby (~> 1.1) webrick (~> 1.7) websocket-driver (~> 0.7) - ffi (1.17.2-aarch64-linux-gnu) - ffi (1.17.2-arm64-darwin) - ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.3-aarch64-linux-gnu) + ffi (1.17.3-arm64-darwin) + ffi (1.17.3-x86_64-linux-gnu) flipper (1.3.6) concurrent-ruby (< 2) flipper-active_record (1.3.6) @@ -198,7 +198,7 @@ GEM hashdiff (1.2.1) html-attributes-utils (1.0.2) activesupport (>= 6.1.4.4) - i18n (1.14.7) + i18n (1.14.8) concurrent-ruby (~> 1.0) image_processing (1.14.0) mini_magick (>= 4.9.5, < 6) @@ -207,12 +207,12 @@ GEM actionpack (>= 6.0.0) activesupport (>= 6.0.0) railties (>= 6.0.0) - io-console (0.8.1) - irb (1.15.3) + io-console (0.8.2) + irb (1.16.0) pp (>= 0.6.0) rdoc (>= 4.0.0) reline (>= 0.4.2) - json (2.17.1) + json (2.18.0) jwt (3.1.2) base64 katalyst-basic-auth (1.0.0) @@ -237,7 +237,7 @@ GEM language_server-protocol (3.17.0.5) lint_roller (1.1.0) logger (1.7.0) - loofah (2.24.1) + loofah (2.25.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.9.0) @@ -251,8 +251,9 @@ GEM mini_magick (5.3.1) logger mini_mime (1.1.5) - minitest (5.26.2) - net-imap (0.5.12) + minitest (6.0.1) + prism (~> 1.5) + net-imap (0.6.2) date net-protocol net-pop (0.1.2) @@ -262,16 +263,16 @@ GEM net-smtp (0.5.1) net-protocol nio4r (2.7.5) - nokogiri (1.18.10-aarch64-linux-gnu) + nokogiri (1.19.0-aarch64-linux-gnu) racc (~> 1.4) - nokogiri (1.18.10-arm64-darwin) + nokogiri (1.19.0-arm64-darwin) racc (~> 1.4) - nokogiri (1.18.10-x86_64-linux-gnu) + nokogiri (1.19.0-x86_64-linux-gnu) racc (~> 1.4) - openssl (3.3.2) + openssl (4.0.0) openssl-signature_algorithm (1.3.0) openssl (> 2.0) - pagy (43.2.0) + pagy (43.2.2) json yaml parallel (1.27.0) @@ -281,15 +282,15 @@ GEM pp (0.6.3) prettyprint prettyprint (0.2.0) - prism (1.6.0) + prism (1.7.0) propshaft (1.3.1) actionpack (>= 7.0.0) activesupport (>= 7.0.0) rack - psych (5.2.6) + psych (5.3.1) date stringio - public_suffix (7.0.0) + public_suffix (7.0.2) puma (7.1.0) nio4r (~> 2.0) racc (1.8.1) @@ -344,7 +345,7 @@ GEM zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.3.1) - rdoc (6.17.0) + rdoc (7.0.3) erb psych (>= 4.0.0) tsort @@ -374,7 +375,7 @@ GEM rspec-mocks (~> 3.13) rspec-support (~> 3.13) rspec-support (3.13.6) - rubocop (1.81.7) + rubocop (1.82.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -382,12 +383,12 @@ GEM parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.47.1, < 2.0) + rubocop-ast (>= 1.48.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.48.0) + rubocop-ast (1.49.0) parser (>= 3.3.7.2) - prism (~> 1.4) + prism (~> 1.7) rubocop-capybara (2.22.1) lint_roller (~> 1.1) rubocop (~> 1.72, >= 1.72.1) @@ -408,7 +409,7 @@ GEM lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.47.1, < 2.0) - rubocop-rails (2.34.2) + rubocop-rails (2.34.3) activesupport (>= 4.2.0) lint_roller (~> 1.1) rack (>= 1.1) @@ -425,7 +426,7 @@ GEM rubocop (~> 1.72, >= 1.72.1) rubocop-rspec (~> 3.5) ruby-progressbar (1.13.0) - ruby-vips (2.2.5) + ruby-vips (2.3.0) ffi (~> 1.12) logger safety_net_attestation (0.5.0) @@ -443,14 +444,14 @@ GEM shoulda-matchers (7.0.1) activesupport (>= 7.1) smart_properties (1.17.0) - sqlite3 (2.8.1-aarch64-linux-gnu) - sqlite3 (2.8.1-arm64-darwin) - sqlite3 (2.8.1-x86_64-linux-gnu) + sqlite3 (2.9.0-aarch64-linux-gnu) + sqlite3 (2.9.0-arm64-darwin) + sqlite3 (2.9.0-x86_64-linux-gnu) stimulus-rails (1.3.4) railties (>= 6.0.0) - stringio (3.1.9) + stringio (3.2.0) thor (1.4.0) - timeout (0.5.0) + timeout (0.6.0) tpm-key_attestation (0.14.1) bindata (~> 2.4) openssl (> 2.0) @@ -463,7 +464,7 @@ GEM concurrent-ruby (~> 1.0) unicode-display_width (3.2.0) unicode-emoji (~> 4.1) - unicode-emoji (4.1.0) + unicode-emoji (4.2.0) uri (1.1.1) useragent (0.16.11) view_component (4.1.1) @@ -490,7 +491,7 @@ GEM xpath (3.2.0) nokogiri (~> 1.8) yaml (0.4.0) - zeitwerk (2.7.3) + zeitwerk (2.7.4) PLATFORMS aarch64-linux @@ -528,4 +529,4 @@ DEPENDENCIES webmock BUNDLED WITH - 4.0.1 + 4.0.3 diff --git a/Rakefile b/Rakefile index 63c5659f2..bfbab865f 100644 --- a/Rakefile +++ b/Rakefile @@ -12,7 +12,6 @@ end APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__) load "rails/tasks/engine.rake" -load "rails/tasks/statistics.rake" # prepend test:prepare to run generators, and db:prepare to run migrations RSpec::Core::RakeTask.new(spec: %w[app:spec:prepare]) diff --git a/app/assets/fonts/koi/inconsolata-license.txt b/app/assets/fonts/koi/inconsolata-license.txt new file mode 100644 index 000000000..edd95a173 --- /dev/null +++ b/app/assets/fonts/koi/inconsolata-license.txt @@ -0,0 +1,93 @@ +Copyright 2006 The Inconsolata Project Authors (https://github.com/cyrealtype/Inconsolata) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/app/assets/fonts/koi/inconsolata-v37-latin-regular.woff2 b/app/assets/fonts/koi/inconsolata-v37-latin-regular.woff2 new file mode 100644 index 000000000..a5ef8233e Binary files /dev/null and b/app/assets/fonts/koi/inconsolata-v37-latin-regular.woff2 differ diff --git a/app/assets/fonts/koi/inter-license.txt b/app/assets/fonts/koi/inter-license.txt new file mode 100644 index 000000000..9b2ca37b3 --- /dev/null +++ b/app/assets/fonts/koi/inter-license.txt @@ -0,0 +1,92 @@ +Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION AND CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/app/assets/fonts/koi/inter-variable-v4-1-italic.woff2 b/app/assets/fonts/koi/inter-variable-v4-1-italic.woff2 new file mode 100644 index 000000000..b3530f3f5 Binary files /dev/null and b/app/assets/fonts/koi/inter-variable-v4-1-italic.woff2 differ diff --git a/app/assets/fonts/koi/inter-variable-v4-1.woff2 b/app/assets/fonts/koi/inter-variable-v4-1.woff2 new file mode 100644 index 000000000..5a8d3e72a Binary files /dev/null and b/app/assets/fonts/koi/inter-variable-v4-1.woff2 differ diff --git a/app/assets/stylesheets/koi/global/fonts.css b/app/assets/stylesheets/koi/global/fonts.css index fe2a7722f..ee40397e0 100644 --- a/app/assets/stylesheets/koi/global/fonts.css +++ b/app/assets/stylesheets/koi/global/fonts.css @@ -1,8 +1,5 @@ -@import url("https://rsms.me/inter/inter.css"); -@import url("https://fonts.googleapis.com/css2?family=Inconsolata:wght@200..900&display=swap"); - :root { - --font-base: "Inter", sans-serif; + --font-base: "InterVariable", sans-serif; --font-mono: "Inconsolata", monospace; --leading-micro: 0.85; --leading-flat: 1; @@ -15,8 +12,89 @@ --font-black: 900; } -@supports (font-variation-settings: normal) { - :root { - --font-base: "Inter var", sans-serif; +/** + * Inter + * Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter) + * SIL Open Font License 1.1 https://raw.githubusercontent.com/rsms/inter/refs/heads/master/LICENSE.txt + */ + +@font-face { + font-family: InterVariable; + font-style: normal; + font-weight: 100 900; + font-display: optional; + src: url("../inter-variable-v4-1.woff2") format("woff2"); +} + +@font-face { + font-family: InterVariable; + font-style: italic; + font-weight: 100 900; + font-display: optional; + src: url("../inter-variable-v4-1-italic.woff2") format("woff2"); +} + +/* From https://github.com/rsms/inter */ +@font-feature-values InterVariable { + @character-variant { + cv01: 1; + cv02: 2; + cv03: 3; + cv04: 4; + cv05: 5; + cv06: 6; + cv07: 7; + cv08: 8; + cv09: 9; + cv10: 10; + cv11: 11; + cv12: 12; + cv13: 13; + alt-1: 1; /* Alternate one */ + alt-3: 9; /* Flat-top three */ + open-4: 2; /* Open four */ + open-6: 3; /* Open six */ + open-9: 4; /* Open nine */ + lc-l-with-tail: 5; /* Lower-case L with tail */ + simplified-u: 6; /* Simplified u */ + alt-double-s: 7; /* Alternate German double s */ + uc-i-with-serif: 8; /* Upper-case i with serif */ + uc-g-with-spur: 10; /* Capital G with spur */ + single-story-a: 11; /* Single-story a */ + compact-lc-f: 12; /* Compact f */ + compact-lc-t: 13; /* Compact t */ } + + @styleset { + ss01: 1; + ss02: 2; + ss03: 3; + ss04: 4; + ss05: 5; + ss06: 6; + ss07: 7; + ss08: 8; + open-digits: 1; /* Open digits */ + disambiguation: 2; /* Disambiguation (with zero) */ + disambiguation-except-zero: 4; /* Disambiguation (no zero) */ + round-quotes-and-commas: 3; /* Round quotes & commas */ + square-punctuation: 7; /* Square punctuation */ + square-quotes: 8; /* Square quotes */ + circled-characters: 5; /* Circled characters */ + squared-characters: 6; /* Squared characters */ + } +} + +/** + * Inconsolata + * Copyright 2006 The Inconsolata Project Authors (https://github.com/cyrealtype/Inconsolata) + * SIL Open Font License 1.1 https://raw.githubusercontent.com/googlefonts/Inconsolata/refs/heads/main/OFL.txt + */ + +@font-face { + font-display: optional; + font-family: "Inconsolata"; + font-style: normal; + font-weight: 400; + src: url("../inconsolata-v37-latin-regular.woff2") format("woff2"); } diff --git a/app/controllers/admin/admin_users_controller.rb b/app/controllers/admin/admin_users_controller.rb index bd9a3e997..6622df9a0 100644 --- a/app/controllers/admin/admin_users_controller.rb +++ b/app/controllers/admin/admin_users_controller.rb @@ -94,8 +94,8 @@ class Collection < Admin::Collection attribute :email, :string attribute :last_sign_in_at, :date attribute :sign_in_count, :integer + attribute :password_login, :enum, scope: :has_password_login, multiple: false attribute :passkey, :boolean, scope: :has_passkey - attribute :mfa, :boolean, scope: :has_otp end end end diff --git a/app/controllers/concerns/koi/controller/has_webauthn.rb b/app/controllers/concerns/koi/controller/has_webauthn.rb index 295317599..547a90d4c 100644 --- a/app/controllers/concerns/koi/controller/has_webauthn.rb +++ b/app/controllers/concerns/koi/controller/has_webauthn.rb @@ -41,7 +41,7 @@ def webauthn_authenticate! ) stored_credential.admin - rescue ActiveRecord::RecordNotFound + rescue ActiveRecord::RecordNotFound, WebAuthn::VerificationError false end end diff --git a/app/javascript/koi/controllers/webauthn_authentication_controller.js b/app/javascript/koi/controllers/webauthn_authentication_controller.js index d7959a9be..cd9f2a2d0 100644 --- a/app/javascript/koi/controllers/webauthn_authentication_controller.js +++ b/app/javascript/koi/controllers/webauthn_authentication_controller.js @@ -1,23 +1,24 @@ import { Controller } from "@hotwired/stimulus"; -import { - get, - parseRequestOptionsFromJSON, -} from "@github/webauthn-json/browser-ponyfill"; - export default class WebauthnAuthenticationController extends Controller { static targets = ["response"]; - static values = { options: Object }; + static values = { + options: Object, + }; + + async authenticate() { + const credential = await navigator.credentials.get(this.options); - authenticate() { - get(this.options).then((response) => { - this.responseTarget.value = JSON.stringify(response); + this.responseTarget.value = JSON.stringify(credential.toJSON()); - this.element.requestSubmit(); - }); + this.element.requestSubmit(); } get options() { - return parseRequestOptionsFromJSON(this.optionsValue); + return { + publicKey: PublicKeyCredential.parseRequestOptionsFromJSON( + this.optionsValue.publicKey, + ), + }; } } diff --git a/app/javascript/koi/controllers/webauthn_registration_controller.js b/app/javascript/koi/controllers/webauthn_registration_controller.js index c038ff860..c2a3958c7 100644 --- a/app/javascript/koi/controllers/webauthn_registration_controller.js +++ b/app/javascript/koi/controllers/webauthn_registration_controller.js @@ -1,14 +1,9 @@ import { Controller } from "@hotwired/stimulus"; -import { - create, - parseCreationOptionsFromJSON, -} from "@github/webauthn-json/browser-ponyfill"; - export default class WebauthnRegistrationController extends Controller { static values = { options: Object, - response: String, + response: Object, }; static targets = ["intro", "nickname", "response"]; @@ -18,15 +13,15 @@ export default class WebauthnRegistrationController extends Controller { e.submitter.formMethod !== "dialog" ) { e.preventDefault(); - this.createCredential(); + this.createCredential().then(); } } async createCredential() { - const response = await create(this.options); + const credential = await navigator.credentials.create(this.options); - this.responseValue = JSON.stringify(response); - this.responseTarget.value = JSON.stringify(response); + this.responseValue = credential.toJSON(); + this.responseTarget.value = JSON.stringify(credential.toJSON()); } responseValueChanged(response) { @@ -36,6 +31,10 @@ export default class WebauthnRegistrationController extends Controller { } get options() { - return parseCreationOptionsFromJSON(this.optionsValue); + return { + publicKey: PublicKeyCredential.parseCreationOptionsFromJSON( + this.optionsValue.publicKey, + ), + }; } } diff --git a/app/models/admin/user.rb b/app/models/admin/user.rb index 99eeae674..73fbd8875 100644 --- a/app/models/admin/user.rb +++ b/app/models/admin/user.rb @@ -18,6 +18,9 @@ def self.model_name has_many :credentials, inverse_of: :admin, class_name: "Admin::Credential", dependent: :destroy + attribute :password_login, :string + enum :password_login, { none: "none", password_only: "password_only", mfa: "mfa" }, prefix: true + validates :name, :email, presence: true validates :email, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP } @@ -33,6 +36,17 @@ def self.model_name end end + scope :has_password_login, ->(type) do + case type&.to_sym + when :password_only + where.not(password_digest: "").where(otp_secret: nil) + when :mfa + where.not(password_digest: "").where.not(otp_secret: nil) + else + where(password_digest: "") + end + end + scope :has_otp, ->(otp) do if otp where.not(otp_secret: nil) @@ -54,6 +68,16 @@ def passkey? end alias passkey passkey? + def password_login + if password_digest.blank? + :none + elsif otp_secret.nil? + :password_only + else + :mfa + end + end + def to_s name end diff --git a/app/views/admin/admin_users/index.html.erb b/app/views/admin/admin_users/index.html.erb index 7e6f9ae50..58b49b08f 100644 --- a/app/views/admin/admin_users/index.html.erb +++ b/app/views/admin/admin_users/index.html.erb @@ -19,12 +19,10 @@ <% row.select %> <% row.link :name, url: :admin_admin_user_path %> <% row.text :email %> + <% row.enum :password_login, label: "Password" %> <% row.boolean :credentials, label: "Passkey" do |cell| %> <%= cell.value.any? ? "Yes" : "No" %> <% end %> - <% row.boolean :otp, label: "MFA" do |cell| %> - <%= cell.value.present? ? "Yes" : "No" %> - <% end %> <% row.date :last_sign_in_at, label: "Last active" %> <% end %> diff --git a/app/views/layouts/koi/application.html.erb b/app/views/layouts/koi/application.html.erb index a3abe2462..3cb57e0d2 100644 --- a/app/views/layouts/koi/application.html.erb +++ b/app/views/layouts/koi/application.html.erb @@ -17,14 +17,19 @@ <%= yield :head %> + + <%= preload_link_tag("koi/inter-variable-v4-1.woff2") %> + <%= preload_link_tag("koi/inter-variable-v4-1-italic.woff2") %> + <%= preload_link_tag("koi/inconsolata-v37-latin-regular.woff2") %> + - "> + <%= favicon_link_tag("koi/logo.svg") %> - <%= stylesheet_link_tag Koi.config.admin_stylesheet, "data-turbo-track": "reload" %> + <%= stylesheet_link_tag(Koi.config.admin_stylesheet, "data-turbo-track": "reload") %> - <%= javascript_importmap_tags Koi.config.admin_javascript_entry_point %> + <%= javascript_importmap_tags(Koi.config.admin_javascript_entry_point) %>
<%# include all turbo-track elements so turbo knows it can cache frame visits %> - <%= stylesheet_link_tag Koi.config.admin_stylesheet, "data-turbo-track": "reload" %> - <%= javascript_importmap_tags "koi/admin" %> + <%= stylesheet_link_tag(Koi.config.admin_stylesheet, "data-turbo-track": "reload") %> + <%= javascript_importmap_tags("koi/admin") %> <%= yield :head %> diff --git a/app/views/layouts/koi/login.html.erb b/app/views/layouts/koi/login.html.erb index ae0177e8b..5e5964c99 100644 --- a/app/views/layouts/koi/login.html.erb +++ b/app/views/layouts/koi/login.html.erb @@ -17,15 +17,20 @@ <%= yield :head %> + + <%= preload_link_tag("koi/inter-variable-v4-1.woff2") %> + <%= preload_link_tag("koi/inter-variable-v4-1-italic.woff2") %> + <%= preload_link_tag("koi/inconsolata-v37-latin-regular.woff2") %> + - "> + <%= favicon_link_tag("koi/logo.svg") %> - <%= stylesheet_link_tag path_to_stylesheet("koi/login.css"), "data-turbo-track": "reload" %> - <%= stylesheet_link_tag Koi.config.admin_stylesheet, "data-turbo-track": "reload" %> + <%= stylesheet_link_tag(path_to_stylesheet("koi/login.css"), "data-turbo-track": "reload") %> + <%= stylesheet_link_tag(Koi.config.admin_stylesheet, "data-turbo-track": "reload") %> - <%= javascript_importmap_tags Koi.config.admin_javascript_entry_point %> + <%= javascript_importmap_tags(Koi.config.admin_javascript_entry_point) %>