From c8ad4988d9cd80e5a8b07ce8578174450c672c21 Mon Sep 17 00:00:00 2001 From: Joaquin Tomas Date: Wed, 3 Sep 2025 17:24:19 -0300 Subject: [PATCH 01/42] chore: invoke rails authentication generator --- .../webauthn_authentication_generator.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb b/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb index 5f1d28b5..d697c5f3 100644 --- a/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb +++ b/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb @@ -11,6 +11,8 @@ class WebauthnAuthenticationGenerator < ::Rails::Generators::Base class_option :api, type: :boolean, desc: "Generate API-only files, with no view templates" + invoke "authentication" + def copy_controllers_and_concerns template "app/controllers/webauthn_credentials_controller.rb" template "app/controllers/registrations_controller.rb" From 2bfd38cd08a1f05f14bfa145ca9c74fba77c5494 Mon Sep 17 00:00:00 2001 From: Joaquin Tomas Date: Wed, 3 Sep 2025 17:25:41 -0300 Subject: [PATCH 02/42] chore: stop generating authentication concern It is the same as the rails one: https://github.com/rails/rails/blob/230cc353c18b94ea825dc37e9566cf2b82289bc6/railties/lib/rails/generators/rails/authentication/templates/app/controllers/concerns/authentication.rb.tt --- .../controllers/concerns/authentication.rb | 52 ------------------- .../webauthn_authentication_generator.rb | 1 - 2 files changed, 53 deletions(-) delete mode 100644 lib/generators/webauthn_authentication/templates/app/controllers/concerns/authentication.rb diff --git a/lib/generators/webauthn_authentication/templates/app/controllers/concerns/authentication.rb b/lib/generators/webauthn_authentication/templates/app/controllers/concerns/authentication.rb deleted file mode 100644 index 3538f485..00000000 --- a/lib/generators/webauthn_authentication/templates/app/controllers/concerns/authentication.rb +++ /dev/null @@ -1,52 +0,0 @@ -module Authentication - extend ActiveSupport::Concern - - included do - before_action :require_authentication - helper_method :authenticated? - end - - class_methods do - def allow_unauthenticated_access(**options) - skip_before_action :require_authentication, **options - end - end - - private - def authenticated? - resume_session - end - - def require_authentication - resume_session || request_authentication - end - - def resume_session - Current.session ||= find_session_by_cookie - end - - def find_session_by_cookie - Session.find_by(id: cookies.signed[:session_id]) if cookies.signed[:session_id] - end - - def request_authentication - session[:return_to_after_authenticating] = request.url - redirect_to new_session_path - end - - def after_authentication_url - session.delete(:return_to_after_authenticating) || root_url - end - - def start_new_session_for(user) - user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session| - Current.session = session - cookies.signed.permanent[:session_id] = { value: session.id, httponly: true, same_site: :lax } - end - end - - def terminate_session - Current.session.destroy - cookies.delete(:session_id) - end -end diff --git a/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb b/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb index d697c5f3..e66d017b 100644 --- a/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb +++ b/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb @@ -17,7 +17,6 @@ def copy_controllers_and_concerns template "app/controllers/webauthn_credentials_controller.rb" template "app/controllers/registrations_controller.rb" template "app/controllers/sessions_controller.rb" - template "app/controllers/concerns/authentication.rb" end def configure_application_controller From b0d4fd1982dbf221081e308dbb7c08f9b91aabc3 Mon Sep 17 00:00:00 2001 From: Joaquin Tomas Date: Wed, 3 Sep 2025 17:27:27 -0300 Subject: [PATCH 03/42] chore: injecting into application controller Rails authentication generator already does it: https://github.com/rails/rails/blob/230cc353c18b94ea825dc37e9566cf2b82289bc6/railties/lib/rails/generators/rails/authentication/authentication_generator.rb#L40-L42 --- .../webauthn_authentication_generator.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb b/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb index e66d017b..5b6713a7 100644 --- a/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb +++ b/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb @@ -19,10 +19,6 @@ def copy_controllers_and_concerns template "app/controllers/sessions_controller.rb" end - def configure_application_controller - inject_into_class "app/controllers/application_controller.rb", "ApplicationController", " include Authentication\n" - end - hook_for :template_engine do |template_engine| invoke template_engine unless options.api? end From 9f596e15f5255c5170811c40333b62f8205d3678 Mon Sep 17 00:00:00 2001 From: Joaquin Tomas Date: Wed, 3 Sep 2025 17:32:05 -0300 Subject: [PATCH 04/42] chore: stop injecting session and current Rails already generates it: https://github.com/rails/rails/blob/230cc353c18b94ea825dc37e9566cf2b82289bc6/railties/lib/rails/generators/rails/authentication/authentication_generator.rb#L60 https://github.com/rails/rails/blob/230cc353c18b94ea825dc37e9566cf2b82289bc6/railties/lib/rails/generators/rails/authentication/authentication_generator.rb#L18 https://github.com/rails/rails/blob/230cc353c18b94ea825dc37e9566cf2b82289bc6/railties/lib/rails/generators/rails/authentication/authentication_generator.rb#L20 --- .../webauthn_authentication/templates/app/models/current.rb | 4 ---- .../webauthn_authentication/templates/app/models/session.rb | 3 --- .../webauthn_authentication_generator.rb | 6 ------ 3 files changed, 13 deletions(-) delete mode 100644 lib/generators/webauthn_authentication/templates/app/models/current.rb delete mode 100644 lib/generators/webauthn_authentication/templates/app/models/session.rb diff --git a/lib/generators/webauthn_authentication/templates/app/models/current.rb b/lib/generators/webauthn_authentication/templates/app/models/current.rb deleted file mode 100644 index 2bef56da..00000000 --- a/lib/generators/webauthn_authentication/templates/app/models/current.rb +++ /dev/null @@ -1,4 +0,0 @@ -class Current < ActiveSupport::CurrentAttributes - attribute :session - delegate :user, to: :session, allow_nil: true -end diff --git a/lib/generators/webauthn_authentication/templates/app/models/session.rb b/lib/generators/webauthn_authentication/templates/app/models/session.rb deleted file mode 100644 index cf376fb2..00000000 --- a/lib/generators/webauthn_authentication/templates/app/models/session.rb +++ /dev/null @@ -1,3 +0,0 @@ -class Session < ApplicationRecord - belongs_to :user -end diff --git a/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb b/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb index 5b6713a7..5e044f56 100644 --- a/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb +++ b/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb @@ -54,12 +54,6 @@ def copy_initializer_file template "config/initializers/webauthn.rb" end - def inject_session_and_current - template "app/models/session.rb" - template "app/models/current.rb" - generate "migration", "CreateSessions", "user:references ip_address:string user_agent:string", "--force" - end - def inject_webauthn_content if File.exist?(File.join(destination_root, "app/models/user.rb")) inject_webauthn_content_to_user_model From ce0bc7ee62054f2871ba5947b6030a5dd1162876 Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Thu, 4 Sep 2025 10:56:05 -0300 Subject: [PATCH 05/42] feat: rename (webauthn) sessions controller to `webauthn_sessions` to avoid conflicts with Rails' sessions controller generated --- .../new.html.erb.tt | 4 ++-- .../webauthn_authentication_generator.rb | 2 +- ...t.rb => webauthn_sessions_controller_test.rb} | 8 ++++---- .../webauthn_authentication_generator.rb | 2 +- ...roller.rb => webauthn_sessions_controller.rb} | 2 +- .../webauthn_authentication_generator.rb | 6 +++--- ...roller.rb => webauthn_sessions_controller.rb} | 2 +- .../{sessions => webauthn_sessions}/new.html.erb | 4 ++-- test/dummy/config/routes.rb | 2 +- ...t.rb => webauthn_sessions_controller_test.rb} | 8 ++++---- .../webauthn_authentication_generator_test.rb | 16 ++++++++-------- 11 files changed, 28 insertions(+), 28 deletions(-) rename lib/generators/erb/webauthn_authentication/templates/app/views/{sessions => webauthn_sessions}/new.html.erb.tt (84%) rename lib/generators/test_unit/webauthn_authentication/templates/test/controllers/{sessions_controller_test.rb => webauthn_sessions_controller_test.rb} (63%) rename lib/generators/webauthn_authentication/templates/app/controllers/{sessions_controller.rb => webauthn_sessions_controller.rb} (97%) rename test/dummy/app/controllers/{sessions_controller.rb => webauthn_sessions_controller.rb} (97%) rename test/dummy/app/views/{sessions => webauthn_sessions}/new.html.erb (84%) rename test/dummy/test/controllers/{sessions_controller_test.rb => webauthn_sessions_controller_test.rb} (63%) diff --git a/lib/generators/erb/webauthn_authentication/templates/app/views/sessions/new.html.erb.tt b/lib/generators/erb/webauthn_authentication/templates/app/views/webauthn_sessions/new.html.erb.tt similarity index 84% rename from lib/generators/erb/webauthn_authentication/templates/app/views/sessions/new.html.erb.tt rename to lib/generators/erb/webauthn_authentication/templates/app/views/webauthn_sessions/new.html.erb.tt index 2f057d1f..c7c2f3fa 100644 --- a/lib/generators/erb/webauthn_authentication/templates/app/views/sessions/new.html.erb.tt +++ b/lib/generators/erb/webauthn_authentication/templates/app/views/webauthn_sessions/new.html.erb.tt @@ -2,12 +2,12 @@ <%%= form_with( scope: :session, - url: session_path, + url: webauthn_session_path, id: "new-session", data: { controller: "webauthn-credentials", action: "webauthn-credentials#get:prevent", - "webauthn-credentials-options-url-value": get_options_session_path, + "webauthn-credentials-options-url-value": get_options_webauthn_session_path, }) do |form| %>
diff --git a/lib/generators/erb/webauthn_authentication/webauthn_authentication_generator.rb b/lib/generators/erb/webauthn_authentication/webauthn_authentication_generator.rb index 7952c190..0fed26b9 100644 --- a/lib/generators/erb/webauthn_authentication/webauthn_authentication_generator.rb +++ b/lib/generators/erb/webauthn_authentication/webauthn_authentication_generator.rb @@ -9,7 +9,7 @@ class WebauthnAuthenticationGenerator < Rails::Generators::Base def create_files template "app/views/webauthn_credentials/new.html.erb.tt" template "app/views/registrations/new.html.erb.tt" - template "app/views/sessions/new.html.erb.tt" + template "app/views/webauthn_sessions/new.html.erb.tt" end end end diff --git a/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/sessions_controller_test.rb b/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/webauthn_sessions_controller_test.rb similarity index 63% rename from lib/generators/test_unit/webauthn_authentication/templates/test/controllers/sessions_controller_test.rb rename to lib/generators/test_unit/webauthn_authentication/templates/test/controllers/webauthn_sessions_controller_test.rb index f75b65f0..393cd7ff 100644 --- a/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/sessions_controller_test.rb +++ b/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/webauthn_sessions_controller_test.rb @@ -1,23 +1,23 @@ require "test_helper" -class SessionsControllerTest < ActionDispatch::IntegrationTest +class WebauthnSessionsControllerTest < ActionDispatch::IntegrationTest test "should initiate registration successfully" do User.create!(username: "alice") - post get_options_session_url, params: { session: { username: "alice" } } + post get_options_webauthn_session_url, params: { session: { username: "alice" } } assert_response :success end test "should return error if creating session with inexisting username" do - post get_options_session_url, params: { session: { username: "alice" } } + post get_options_webauthn_session_url, params: { session: { username: "alice" } } assert_response :unprocessable_entity assert_equal [ "Username doesn't exist" ], JSON.parse(response.body)["errors"] end test "should return error if creating session with blank username" do - post get_options_session_url, params: { session: { username: "" } } + post get_options_webauthn_session_url, params: { session: { username: "" } } assert_response :unprocessable_entity assert_equal [ "Username doesn't exist" ], JSON.parse(response.body)["errors"] diff --git a/lib/generators/test_unit/webauthn_authentication/webauthn_authentication_generator.rb b/lib/generators/test_unit/webauthn_authentication/webauthn_authentication_generator.rb index 001bedba..119963e6 100644 --- a/lib/generators/test_unit/webauthn_authentication/webauthn_authentication_generator.rb +++ b/lib/generators/test_unit/webauthn_authentication/webauthn_authentication_generator.rb @@ -8,7 +8,7 @@ class WebauthnAuthenticationGenerator < Rails::Generators::Base def create_controller_test_files template "test/controllers/registrations_controller_test.rb" - template "test/controllers/sessions_controller_test.rb" + template "test/controllers/webauthn_sessions_controller_test.rb" end def create_system_test_files diff --git a/lib/generators/webauthn_authentication/templates/app/controllers/sessions_controller.rb b/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_sessions_controller.rb similarity index 97% rename from lib/generators/webauthn_authentication/templates/app/controllers/sessions_controller.rb rename to lib/generators/webauthn_authentication/templates/app/controllers/webauthn_sessions_controller.rb index 87b5cdb9..d27630cf 100644 --- a/lib/generators/webauthn_authentication/templates/app/controllers/sessions_controller.rb +++ b/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_sessions_controller.rb @@ -1,4 +1,4 @@ -class SessionsController < ApplicationController +class WebauthnSessionsController < ApplicationController allow_unauthenticated_access only: %i[new get_options create] def new diff --git a/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb b/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb index 5e044f56..4304108f 100644 --- a/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb +++ b/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb @@ -16,7 +16,7 @@ class WebauthnAuthenticationGenerator < ::Rails::Generators::Base def copy_controllers_and_concerns template "app/controllers/webauthn_credentials_controller.rb" template "app/controllers/registrations_controller.rb" - template "app/controllers/sessions_controller.rb" + template "app/controllers/webauthn_sessions_controller.rb" end hook_for :template_engine do |template_engine| @@ -69,7 +69,7 @@ def inject_webauthn_content post :create_options, on: :collection end - resource :session, only: [ :new, :create, :destroy ] do + resource :webauthn_session, only: [ :new, :create, :destroy ] do post :get_options, on: :collection end @@ -112,7 +112,7 @@ def inject_webauthn_content_to_user_model validates :username, presence: true, uniqueness: true has_many :webauthn_credentials, dependent: :destroy - has_many :sessions, dependent: :destroy + has_many :webauthn_sessions, dependent: :destroy after_initialize do self.webauthn_id ||= WebAuthn.generate_user_id diff --git a/test/dummy/app/controllers/sessions_controller.rb b/test/dummy/app/controllers/webauthn_sessions_controller.rb similarity index 97% rename from test/dummy/app/controllers/sessions_controller.rb rename to test/dummy/app/controllers/webauthn_sessions_controller.rb index 87b5cdb9..d27630cf 100644 --- a/test/dummy/app/controllers/sessions_controller.rb +++ b/test/dummy/app/controllers/webauthn_sessions_controller.rb @@ -1,4 +1,4 @@ -class SessionsController < ApplicationController +class WebauthnSessionsController < ApplicationController allow_unauthenticated_access only: %i[new get_options create] def new diff --git a/test/dummy/app/views/sessions/new.html.erb b/test/dummy/app/views/webauthn_sessions/new.html.erb similarity index 84% rename from test/dummy/app/views/sessions/new.html.erb rename to test/dummy/app/views/webauthn_sessions/new.html.erb index 8ca25642..1928e51a 100644 --- a/test/dummy/app/views/sessions/new.html.erb +++ b/test/dummy/app/views/webauthn_sessions/new.html.erb @@ -2,12 +2,12 @@ <%= form_with( scope: :session, - url: session_path, + url: webauthn_session_path, id: "new-session", data: { controller: "webauthn-credentials", action: "webauthn-credentials#get:prevent", - "webauthn-credentials-options-url-value": get_options_session_path, + "webauthn-credentials-options-url-value": get_options_webauthn_session_path, }) do |form| %>
diff --git a/test/dummy/config/routes.rb b/test/dummy/config/routes.rb index 1b2dd859..e812076f 100644 --- a/test/dummy/config/routes.rb +++ b/test/dummy/config/routes.rb @@ -3,7 +3,7 @@ post :create_options, on: :collection end - resource :session, only: [ :new, :create, :destroy ] do + resource :webauthn_session, only: [ :new, :create, :destroy ] do post :get_options, on: :collection end diff --git a/test/dummy/test/controllers/sessions_controller_test.rb b/test/dummy/test/controllers/webauthn_sessions_controller_test.rb similarity index 63% rename from test/dummy/test/controllers/sessions_controller_test.rb rename to test/dummy/test/controllers/webauthn_sessions_controller_test.rb index f75b65f0..393cd7ff 100644 --- a/test/dummy/test/controllers/sessions_controller_test.rb +++ b/test/dummy/test/controllers/webauthn_sessions_controller_test.rb @@ -1,23 +1,23 @@ require "test_helper" -class SessionsControllerTest < ActionDispatch::IntegrationTest +class WebauthnSessionsControllerTest < ActionDispatch::IntegrationTest test "should initiate registration successfully" do User.create!(username: "alice") - post get_options_session_url, params: { session: { username: "alice" } } + post get_options_webauthn_session_url, params: { session: { username: "alice" } } assert_response :success end test "should return error if creating session with inexisting username" do - post get_options_session_url, params: { session: { username: "alice" } } + post get_options_webauthn_session_url, params: { session: { username: "alice" } } assert_response :unprocessable_entity assert_equal [ "Username doesn't exist" ], JSON.parse(response.body)["errors"] end test "should return error if creating session with blank username" do - post get_options_session_url, params: { session: { username: "" } } + post get_options_webauthn_session_url, params: { session: { username: "" } } assert_response :unprocessable_entity assert_equal [ "Username doesn't exist" ], JSON.parse(response.body)["errors"] diff --git a/test/generators/webauthn_authentication_generator_test.rb b/test/generators/webauthn_authentication_generator_test.rb index 919f8d07..be9d6654 100644 --- a/test/generators/webauthn_authentication_generator_test.rb +++ b/test/generators/webauthn_authentication_generator_test.rb @@ -20,7 +20,7 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase run_generator_instance assert_file "app/controllers/registrations_controller.rb" - assert_file "app/controllers/sessions_controller.rb" + assert_file "app/controllers/webauthn_sessions_controller.rb" assert_file "app/controllers/webauthn_credentials_controller.rb" assert_file "app/controllers/concerns/authentication.rb" @@ -28,14 +28,14 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase assert_file "app/views/webauthn_credentials/new.html.erb" assert_file "app/views/registrations/new.html.erb" - assert_file "app/views/sessions/new.html.erb" + assert_file "app/views/webauthn_sessions/new.html.erb" assert_file "app/javascript/controllers/webauthn_credentials_controller.js" assert_file "config/initializers/webauthn.rb", /WebAuthn.configure/ assert_file "test/controllers/registrations_controller_test.rb" - assert_file "test/controllers/sessions_controller_test.rb" + assert_file "test/controllers/webauthn_sessions_controller_test.rb" assert_file "test/system/add_credential_test.rb" assert_file "test/system/registration_test.rb" assert_file "test/system/sign_in_test.rb" @@ -63,7 +63,7 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase run_generator_instance assert_file "app/controllers/registrations_controller.rb" - assert_file "app/controllers/sessions_controller.rb" + assert_file "app/controllers/webauthn_sessions_controller.rb" assert_file "app/controllers/webauthn_credentials_controller.rb" assert_file "app/controllers/concerns/authentication.rb" @@ -71,14 +71,14 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase assert_file "app/views/webauthn_credentials/new.html.erb" assert_file "app/views/registrations/new.html.erb" - assert_file "app/views/sessions/new.html.erb" + assert_file "app/views/webauthn_sessions/new.html.erb" assert_file "app/javascript/controllers/webauthn_credentials_controller.js" assert_file "config/initializers/webauthn.rb", /WebAuthn.configure/ assert_file "test/controllers/registrations_controller_test.rb" - assert_file "test/controllers/sessions_controller_test.rb" + assert_file "test/controllers/webauthn_sessions_controller_test.rb" assert_file "test/system/add_credential_test.rb" assert_file "test/system/registration_test.rb" assert_file "test/system/sign_in_test.rb" @@ -105,7 +105,7 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase run_generator_instance assert_file "app/controllers/registrations_controller.rb" - assert_file "app/controllers/sessions_controller.rb" + assert_file "app/controllers/webauthn_sessions_controller.rb" assert_file "app/controllers/webauthn_credentials_controller.rb" assert_file "app/controllers/concerns/authentication.rb" @@ -113,7 +113,7 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase assert_no_file "app/views/webauthn_credentials/new.html.erb" assert_no_file "app/views/registrations/new.html.erb" - assert_no_file "app/views/sessions/new.html.erb" + assert_no_file "app/views/webauthn_sessions/new.html.erb" assert_file "app/javascript/controllers/webauthn_credentials_controller.js" From ba8324f6e436f0fa1b54338c42c96e8b9f99bb6c Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Thu, 4 Sep 2025 13:50:28 -0300 Subject: [PATCH 06/42] test: `stub` `invoke` to rails auth generator --- .../webauthn_authentication_generator_test.rb | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/test/generators/webauthn_authentication_generator_test.rb b/test/generators/webauthn_authentication_generator_test.rb index be9d6654..8f9df7b3 100644 --- a/test/generators/webauthn_authentication_generator_test.rb +++ b/test/generators/webauthn_authentication_generator_test.rb @@ -1,6 +1,7 @@ require "test_helper" require "rails/generators/test_case" require "generators/webauthn_authentication/webauthn_authentication_generator" +require "minitest/stub_any_instance" class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase tests WebauthnAuthenticationGenerator @@ -17,7 +18,10 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase test "assert all files are properly created when user model does not exist" do generator([ destination_root ], [ "--test-framework=test_unit" ]) - run_generator_instance + + Rails::Generators::AuthenticationGenerator.stub_any_instance(:invoke_all, nil) do + run_generator_instance + end assert_file "app/controllers/registrations_controller.rb" assert_file "app/controllers/webauthn_sessions_controller.rb" @@ -60,7 +64,10 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase test "assert all files are properly created when user model already exists" do add_user_model generator([ destination_root ], [ "--test-framework=test_unit" ]) - run_generator_instance + + Rails::Generators::AuthenticationGenerator.stub_any_instance(:invoke_all, nil) do + run_generator_instance + end assert_file "app/controllers/registrations_controller.rb" assert_file "app/controllers/webauthn_sessions_controller.rb" @@ -102,7 +109,10 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase test "assert all files except for views are created with api flag" do generator([ destination_root ], [ "--api" ]) - run_generator_instance + + Rails::Generators::AuthenticationGenerator.stub_any_instance(:invoke_all, nil) do + run_generator_instance + end assert_file "app/controllers/registrations_controller.rb" assert_file "app/controllers/webauthn_sessions_controller.rb" From f83afa1b8cc51794cc9fc4ad59a274f9bb9632f6 Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Thu, 4 Sep 2025 14:35:52 -0300 Subject: [PATCH 07/42] test: update generator tests --- .../templates/app/models/user.rb | 16 ----- .../webauthn_authentication_generator.rb | 11 +-- .../webauthn_authentication_generator_test.rb | 69 +++---------------- 3 files changed, 13 insertions(+), 83 deletions(-) delete mode 100644 lib/generators/webauthn_authentication/templates/app/models/user.rb diff --git a/lib/generators/webauthn_authentication/templates/app/models/user.rb b/lib/generators/webauthn_authentication/templates/app/models/user.rb deleted file mode 100644 index 6776b1aa..00000000 --- a/lib/generators/webauthn_authentication/templates/app/models/user.rb +++ /dev/null @@ -1,16 +0,0 @@ -class User < ApplicationRecord - CREDENTIAL_MIN_AMOUNT = 1 - - validates :username, presence: true, uniqueness: true - - has_many :webauthn_credentials, dependent: :destroy - has_many :sessions, dependent: :destroy - - after_initialize do - self.webauthn_id ||= WebAuthn.generate_user_id - end - - def can_delete_credentials? - webauthn_credentials.size > CREDENTIAL_MIN_AMOUNT - end -end diff --git a/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb b/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb index 4304108f..bf77a274 100644 --- a/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb +++ b/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb @@ -55,13 +55,8 @@ def copy_initializer_file end def inject_webauthn_content - if File.exist?(File.join(destination_root, "app/models/user.rb")) - inject_webauthn_content_to_user_model - generate "migration", "AddWebauthnToUsers", "username:string:uniq webauthn_id:string" - else - template "app/models/user.rb" - generate "migration", "CreateUsers", "username:string:uniq webauthn_id:string" - end + generate "migration", "AddWebauthnToUsers", "username:string:uniq webauthn_id:string" + inject_webauthn_content_to_user_model inject_into_file "config/routes.rb", after: "Rails.application.routes.draw do\n" do <<-RUBY.strip_heredoc.indent(2) @@ -105,7 +100,7 @@ def has_package_json? end def inject_webauthn_content_to_user_model - inject_into_class "app/models/user.rb", "User" do + inject_into_file "app/models/user.rb", after: "normalizes :email_address, with: ->(e) { e.strip.downcase }\n" do <<-RUBY.strip_heredoc.indent(2) CREDENTIAL_MIN_AMOUNT = 1 diff --git a/test/generators/webauthn_authentication_generator_test.rb b/test/generators/webauthn_authentication_generator_test.rb index 8f9df7b3..8b6b78c7 100644 --- a/test/generators/webauthn_authentication_generator_test.rb +++ b/test/generators/webauthn_authentication_generator_test.rb @@ -2,6 +2,7 @@ require "rails/generators/test_case" require "generators/webauthn_authentication/webauthn_authentication_generator" require "minitest/stub_any_instance" +require "rails/generators/rails/authentication/authentication_generator" class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase tests WebauthnAuthenticationGenerator @@ -14,9 +15,10 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase add_routes add_application_controller add_test_helper + add_rails_auth_user_model end - test "assert all files are properly created when user model does not exist" do + test "generates all expected files and successfully runs the Rails authentication generator" do generator([ destination_root ], [ "--test-framework=test_unit" ]) Rails::Generators::AuthenticationGenerator.stub_any_instance(:invoke_all, nil) do @@ -26,55 +28,6 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase assert_file "app/controllers/registrations_controller.rb" assert_file "app/controllers/webauthn_sessions_controller.rb" assert_file "app/controllers/webauthn_credentials_controller.rb" - assert_file "app/controllers/concerns/authentication.rb" - - assert_file "app/controllers/application_controller.rb", /include Authentication/ - - assert_file "app/views/webauthn_credentials/new.html.erb" - assert_file "app/views/registrations/new.html.erb" - assert_file "app/views/webauthn_sessions/new.html.erb" - - assert_file "app/javascript/controllers/webauthn_credentials_controller.js" - - assert_file "config/initializers/webauthn.rb", /WebAuthn.configure/ - - assert_file "test/controllers/registrations_controller_test.rb" - assert_file "test/controllers/webauthn_sessions_controller_test.rb" - assert_file "test/system/add_credential_test.rb" - assert_file "test/system/registration_test.rb" - assert_file "test/system/sign_in_test.rb" - assert_file "test/test_helpers/virtual_authenticator_test_helper.rb" - - assert_file "app/models/user.rb", /has_many :webauthn_credentials/ - assert_includes @rails_commands, "generate migration CreateUsers username:string:uniq webauthn_id:string" - - assert_file "app/models/webauthn_credential.rb", /belongs_to :user/ - assert_includes @rails_commands, "generate migration CreateWebauthnCredentials user:references! external_id:string:uniq public_key:string nickname:string sign_count:integer{8}" - - assert_file "app/models/session.rb", /belongs_to :user/ - assert_file "app/models/current.rb", /delegate :user, to: :session, allow_nil: true/ - assert_includes @rails_commands, "generate migration CreateSessions user:references ip_address:string user_agent:string --force" - - assert_file "config/routes.rb", /Rails.application.routes.draw do/ - assert_file "config/routes.rb", /resources :webauthn_credentials, only: \[\s*:new, :create, :destroy\s*\] do/ - - assert_file "config/importmap.rb", /pin "@github\/webauthn-json\/browser-ponyfill"/ - end - - test "assert all files are properly created when user model already exists" do - add_user_model - generator([ destination_root ], [ "--test-framework=test_unit" ]) - - Rails::Generators::AuthenticationGenerator.stub_any_instance(:invoke_all, nil) do - run_generator_instance - end - - assert_file "app/controllers/registrations_controller.rb" - assert_file "app/controllers/webauthn_sessions_controller.rb" - assert_file "app/controllers/webauthn_credentials_controller.rb" - assert_file "app/controllers/concerns/authentication.rb" - - assert_file "app/controllers/application_controller.rb", /include Authentication/ assert_file "app/views/webauthn_credentials/new.html.erb" assert_file "app/views/registrations/new.html.erb" @@ -97,10 +50,6 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase assert_file "app/models/webauthn_credential.rb", /belongs_to :user/ assert_includes @rails_commands, "generate migration CreateWebauthnCredentials user:references! external_id:string:uniq public_key:string nickname:string sign_count:integer{8}" - assert_file "app/models/session.rb", /belongs_to :user/ - assert_file "app/models/current.rb", /delegate :user, to: :session, allow_nil: true/ - assert_includes @rails_commands, "generate migration CreateSessions user:references ip_address:string user_agent:string --force" - assert_file "config/routes.rb", /Rails.application.routes.draw do/ assert_file "config/routes.rb", /resources :webauthn_credentials, only: \[\s*:new, :create, :destroy\s*\] do/ @@ -117,9 +66,6 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase assert_file "app/controllers/registrations_controller.rb" assert_file "app/controllers/webauthn_sessions_controller.rb" assert_file "app/controllers/webauthn_credentials_controller.rb" - assert_file "app/controllers/concerns/authentication.rb" - - assert_file "app/controllers/application_controller.rb", /include Authentication/ assert_no_file "app/views/webauthn_credentials/new.html.erb" assert_no_file "app/views/registrations/new.html.erb" @@ -130,7 +76,8 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase assert_file "config/initializers/webauthn.rb", /WebAuthn.configure/ assert_file "app/models/user.rb", /has_many :webauthn_credentials/ - assert_includes @rails_commands, "generate migration CreateUsers username:string:uniq webauthn_id:string" + assert_includes @rails_commands, "generate migration AddWebauthnToUsers username:string:uniq webauthn_id:string" + assert_file "app/models/webauthn_credential.rb", /belongs_to :user/ assert_includes @rails_commands, "generate migration CreateWebauthnCredentials user:references! external_id:string:uniq public_key:string nickname:string sign_count:integer{8}" @@ -155,11 +102,15 @@ def add_routes CONTENT end - def add_user_model + def add_rails_auth_user_model app_folder = FileUtils.mkdir_p(File.join(destination_root, "app")) FileUtils.mkdir_p(File.join(app_folder, "models")) File.write(File.join(destination_root, "app", "models", "user.rb"), <<~CONTENT) class User < ApplicationRecord + has_secure_password + has_many :sessions, dependent: :destroy + + normalizes :email_address, with: ->(e) { e.strip.downcase } end CONTENT end From 4894c50546d026311f4c4ff3b71c2942dfb9108e Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Thu, 4 Sep 2025 15:07:02 -0300 Subject: [PATCH 08/42] test: update dummy app rerunning generator --- Gemfile | 2 ++ .../channels/application_cable/connection.rb | 12 +++++++ .../app/controllers/passwords_controller.rb | 33 +++++++++++++++++++ .../app/controllers/sessions_controller.rb | 21 ++++++++++++ test/dummy/app/mailers/passwords_mailer.rb | 6 ++++ test/dummy/app/models/user.rb | 6 +++- test/dummy/app/views/passwords/edit.html.erb | 9 +++++ test/dummy/app/views/passwords/new.html.erb | 8 +++++ .../app/views/passwords_mailer/reset.html.erb | 4 +++ .../app/views/passwords_mailer/reset.text.erb | 2 ++ test/dummy/app/views/sessions/new.html.erb | 11 +++++++ test/dummy/config/routes.rb | 2 ++ .../db/migrate/20240507150026_create_users.rb | 11 ------- .../db/migrate/20250904175227_create_users.rb | 11 +++++++ ...s.rb => 20250904175229_create_sessions.rb} | 0 .../20250904175233_add_webauthn_to_users.rb | 7 ++++ ...0904175236_create_webauthn_credentials.rb} | 6 ++-- test/dummy/db/schema.rb | 11 ++++--- test/dummy/test/fixtures/users.yml | 9 +++++ .../previews/passwords_mailer_preview.rb | 7 ++++ test/dummy/test/models/user_test.rb | 7 ++++ 21 files changed, 166 insertions(+), 19 deletions(-) create mode 100644 test/dummy/app/controllers/passwords_controller.rb create mode 100644 test/dummy/app/controllers/sessions_controller.rb create mode 100644 test/dummy/app/mailers/passwords_mailer.rb create mode 100644 test/dummy/app/views/passwords/edit.html.erb create mode 100644 test/dummy/app/views/passwords/new.html.erb create mode 100644 test/dummy/app/views/passwords_mailer/reset.html.erb create mode 100644 test/dummy/app/views/passwords_mailer/reset.text.erb create mode 100644 test/dummy/app/views/sessions/new.html.erb delete mode 100644 test/dummy/db/migrate/20240507150026_create_users.rb create mode 100644 test/dummy/db/migrate/20250904175227_create_users.rb rename test/dummy/db/migrate/{20250901180507_create_sessions.rb => 20250904175229_create_sessions.rb} (100%) create mode 100644 test/dummy/db/migrate/20250904175233_add_webauthn_to_users.rb rename test/dummy/db/migrate/{20250804165453_create_webauthn_credentials.rb => 20250904175236_create_webauthn_credentials.rb} (63%) create mode 100644 test/dummy/test/fixtures/users.yml create mode 100644 test/dummy/test/mailers/previews/passwords_mailer_preview.rb create mode 100644 test/dummy/test/models/user_test.rb diff --git a/Gemfile b/Gemfile index fab99593..4d88d44a 100644 --- a/Gemfile +++ b/Gemfile @@ -27,3 +27,5 @@ gem "minitest-stub_any_instance" gem "rubocop-rails-omakase", require: false gem "pry-byebug", "~> 3.11" + +gem "bcrypt", "~> 3.1.7" diff --git a/test/dummy/app/channels/application_cable/connection.rb b/test/dummy/app/channels/application_cable/connection.rb index 0ff5442f..4264c745 100644 --- a/test/dummy/app/channels/application_cable/connection.rb +++ b/test/dummy/app/channels/application_cable/connection.rb @@ -1,4 +1,16 @@ module ApplicationCable class Connection < ActionCable::Connection::Base + identified_by :current_user + + def connect + set_current_user || reject_unauthorized_connection + end + + private + def set_current_user + if session = Session.find_by(id: cookies.signed[:session_id]) + self.current_user = session.user + end + end end end diff --git a/test/dummy/app/controllers/passwords_controller.rb b/test/dummy/app/controllers/passwords_controller.rb new file mode 100644 index 00000000..0c4b4a89 --- /dev/null +++ b/test/dummy/app/controllers/passwords_controller.rb @@ -0,0 +1,33 @@ +class PasswordsController < ApplicationController + allow_unauthenticated_access + before_action :set_user_by_token, only: %i[ edit update ] + + def new + end + + def create + if user = User.find_by(email_address: params[:email_address]) + PasswordsMailer.reset(user).deliver_later + end + + redirect_to new_session_path, notice: "Password reset instructions sent (if user with that email address exists)." + end + + def edit + end + + def update + if @user.update(params.permit(:password, :password_confirmation)) + redirect_to new_session_path, notice: "Password has been reset." + else + redirect_to edit_password_path(params[:token]), alert: "Passwords did not match." + end + end + + private + def set_user_by_token + @user = User.find_by_password_reset_token!(params[:token]) + rescue ActiveSupport::MessageVerifier::InvalidSignature + redirect_to new_password_path, alert: "Password reset link is invalid or has expired." + end +end diff --git a/test/dummy/app/controllers/sessions_controller.rb b/test/dummy/app/controllers/sessions_controller.rb new file mode 100644 index 00000000..9785c923 --- /dev/null +++ b/test/dummy/app/controllers/sessions_controller.rb @@ -0,0 +1,21 @@ +class SessionsController < ApplicationController + allow_unauthenticated_access only: %i[ new create ] + rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_session_url, alert: "Try again later." } + + def new + end + + def create + if user = User.authenticate_by(params.permit(:email_address, :password)) + start_new_session_for user + redirect_to after_authentication_url + else + redirect_to new_session_path, alert: "Try another email address or password." + end + end + + def destroy + terminate_session + redirect_to new_session_path + end +end diff --git a/test/dummy/app/mailers/passwords_mailer.rb b/test/dummy/app/mailers/passwords_mailer.rb new file mode 100644 index 00000000..4f0ac7fd --- /dev/null +++ b/test/dummy/app/mailers/passwords_mailer.rb @@ -0,0 +1,6 @@ +class PasswordsMailer < ApplicationMailer + def reset(user) + @user = user + mail subject: "Reset your password", to: user.email_address + end +end diff --git a/test/dummy/app/models/user.rb b/test/dummy/app/models/user.rb index 6776b1aa..5b22004b 100644 --- a/test/dummy/app/models/user.rb +++ b/test/dummy/app/models/user.rb @@ -1,10 +1,14 @@ class User < ApplicationRecord + has_secure_password + has_many :sessions, dependent: :destroy + + normalizes :email_address, with: ->(e) { e.strip.downcase } CREDENTIAL_MIN_AMOUNT = 1 validates :username, presence: true, uniqueness: true has_many :webauthn_credentials, dependent: :destroy - has_many :sessions, dependent: :destroy + has_many :webauthn_sessions, dependent: :destroy after_initialize do self.webauthn_id ||= WebAuthn.generate_user_id diff --git a/test/dummy/app/views/passwords/edit.html.erb b/test/dummy/app/views/passwords/edit.html.erb new file mode 100644 index 00000000..9f0c87c8 --- /dev/null +++ b/test/dummy/app/views/passwords/edit.html.erb @@ -0,0 +1,9 @@ +

Update your password

+ +<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %> + +<%= form_with url: password_path(params[:token]), method: :put do |form| %> + <%= form.password_field :password, required: true, autocomplete: "new-password", placeholder: "Enter new password", maxlength: 72 %>
+ <%= form.password_field :password_confirmation, required: true, autocomplete: "new-password", placeholder: "Repeat new password", maxlength: 72 %>
+ <%= form.submit "Save" %> +<% end %> diff --git a/test/dummy/app/views/passwords/new.html.erb b/test/dummy/app/views/passwords/new.html.erb new file mode 100644 index 00000000..44efb2bf --- /dev/null +++ b/test/dummy/app/views/passwords/new.html.erb @@ -0,0 +1,8 @@ +

Forgot your password?

+ +<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %> + +<%= form_with url: passwords_path do |form| %> + <%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address] %>
+ <%= form.submit "Email reset instructions" %> +<% end %> diff --git a/test/dummy/app/views/passwords_mailer/reset.html.erb b/test/dummy/app/views/passwords_mailer/reset.html.erb new file mode 100644 index 00000000..4a066193 --- /dev/null +++ b/test/dummy/app/views/passwords_mailer/reset.html.erb @@ -0,0 +1,4 @@ +

+ You can reset your password within the next 15 minutes on + <%= link_to "this password reset page", edit_password_url(@user.password_reset_token) %>. +

diff --git a/test/dummy/app/views/passwords_mailer/reset.text.erb b/test/dummy/app/views/passwords_mailer/reset.text.erb new file mode 100644 index 00000000..2cf03fce --- /dev/null +++ b/test/dummy/app/views/passwords_mailer/reset.text.erb @@ -0,0 +1,2 @@ +You can reset your password within the next 15 minutes on this password reset page: +<%= edit_password_url(@user.password_reset_token) %> diff --git a/test/dummy/app/views/sessions/new.html.erb b/test/dummy/app/views/sessions/new.html.erb new file mode 100644 index 00000000..ff641c4f --- /dev/null +++ b/test/dummy/app/views/sessions/new.html.erb @@ -0,0 +1,11 @@ +<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %> +<%= tag.div(flash[:notice], style: "color:green") if flash[:notice] %> + +<%= form_with url: session_path do |form| %> + <%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address] %>
+ <%= form.password_field :password, required: true, autocomplete: "current-password", placeholder: "Enter your password", maxlength: 72 %>
+ <%= form.submit "Sign in" %> +<% end %> +
+ +<%= link_to "Forgot password?", new_password_path %> diff --git a/test/dummy/config/routes.rb b/test/dummy/config/routes.rb index e812076f..d9a7e325 100644 --- a/test/dummy/config/routes.rb +++ b/test/dummy/config/routes.rb @@ -1,4 +1,6 @@ Rails.application.routes.draw do + resource :session + resources :passwords, param: :token resource :registration, only: [ :new, :create ] do post :create_options, on: :collection end diff --git a/test/dummy/db/migrate/20240507150026_create_users.rb b/test/dummy/db/migrate/20240507150026_create_users.rb deleted file mode 100644 index 32fa6d39..00000000 --- a/test/dummy/db/migrate/20240507150026_create_users.rb +++ /dev/null @@ -1,11 +0,0 @@ -class CreateUsers < ActiveRecord::Migration[7.1] - def change - create_table :users do |t| - t.string :username - t.string :webauthn_id - - t.timestamps - end - add_index :users, :username, unique: true - end -end diff --git a/test/dummy/db/migrate/20250904175227_create_users.rb b/test/dummy/db/migrate/20250904175227_create_users.rb new file mode 100644 index 00000000..2075edf7 --- /dev/null +++ b/test/dummy/db/migrate/20250904175227_create_users.rb @@ -0,0 +1,11 @@ +class CreateUsers < ActiveRecord::Migration[8.0] + def change + create_table :users do |t| + t.string :email_address, null: false + t.string :password_digest, null: false + + t.timestamps + end + add_index :users, :email_address, unique: true + end +end diff --git a/test/dummy/db/migrate/20250901180507_create_sessions.rb b/test/dummy/db/migrate/20250904175229_create_sessions.rb similarity index 100% rename from test/dummy/db/migrate/20250901180507_create_sessions.rb rename to test/dummy/db/migrate/20250904175229_create_sessions.rb diff --git a/test/dummy/db/migrate/20250904175233_add_webauthn_to_users.rb b/test/dummy/db/migrate/20250904175233_add_webauthn_to_users.rb new file mode 100644 index 00000000..bd93d781 --- /dev/null +++ b/test/dummy/db/migrate/20250904175233_add_webauthn_to_users.rb @@ -0,0 +1,7 @@ +class AddWebauthnToUsers < ActiveRecord::Migration[8.0] + def change + add_column :users, :username, :string + add_index :users, :username, unique: true + add_column :users, :webauthn_id, :string + end +end diff --git a/test/dummy/db/migrate/20250804165453_create_webauthn_credentials.rb b/test/dummy/db/migrate/20250904175236_create_webauthn_credentials.rb similarity index 63% rename from test/dummy/db/migrate/20250804165453_create_webauthn_credentials.rb rename to test/dummy/db/migrate/20250904175236_create_webauthn_credentials.rb index f17a12a6..d93b09c2 100644 --- a/test/dummy/db/migrate/20250804165453_create_webauthn_credentials.rb +++ b/test/dummy/db/migrate/20250904175236_create_webauthn_credentials.rb @@ -1,11 +1,11 @@ -class CreateWebauthnCredentials < ActiveRecord::Migration[7.2] +class CreateWebauthnCredentials < ActiveRecord::Migration[8.0] def change - create_table :webauthn_credentials do |t| + create_table :webauthn_credentials do |t| t.references :user, null: false, foreign_key: true t.string :external_id t.string :public_key t.string :nickname - t.bigint :sign_count + t.integer :sign_count, limit: 8 t.timestamps end diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb index 4e554c50..a707d499 100644 --- a/test/dummy/db/schema.rb +++ b/test/dummy/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_09_01_180507) do +ActiveRecord::Schema[8.0].define(version: 2025_09_04_175236) do create_table "sessions", force: :cascade do |t| t.integer "user_id", null: false t.string "ip_address" @@ -21,10 +21,13 @@ end create_table "users", force: :cascade do |t| - t.string "username" - t.string "webauthn_id" + t.string "email_address", null: false + t.string "password_digest", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "username" + t.string "webauthn_id" + t.index ["email_address"], name: "index_users_on_email_address", unique: true t.index ["username"], name: "index_users_on_username", unique: true end @@ -33,7 +36,7 @@ t.string "external_id" t.string "public_key" t.string "nickname" - t.bigint "sign_count" + t.integer "sign_count", limit: 8 t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["external_id"], name: "index_webauthn_credentials_on_external_id", unique: true diff --git a/test/dummy/test/fixtures/users.yml b/test/dummy/test/fixtures/users.yml new file mode 100644 index 00000000..09515632 --- /dev/null +++ b/test/dummy/test/fixtures/users.yml @@ -0,0 +1,9 @@ +<% password_digest = BCrypt::Password.create("password") %> + +one: + email_address: one@example.com + password_digest: <%= password_digest %> + +two: + email_address: two@example.com + password_digest: <%= password_digest %> diff --git a/test/dummy/test/mailers/previews/passwords_mailer_preview.rb b/test/dummy/test/mailers/previews/passwords_mailer_preview.rb new file mode 100644 index 00000000..01d07ecf --- /dev/null +++ b/test/dummy/test/mailers/previews/passwords_mailer_preview.rb @@ -0,0 +1,7 @@ +# Preview all emails at http://localhost:3000/rails/mailers/passwords_mailer +class PasswordsMailerPreview < ActionMailer::Preview + # Preview this email at http://localhost:3000/rails/mailers/passwords_mailer/reset + def reset + PasswordsMailer.reset(User.take) + end +end diff --git a/test/dummy/test/models/user_test.rb b/test/dummy/test/models/user_test.rb new file mode 100644 index 00000000..5c07f490 --- /dev/null +++ b/test/dummy/test/models/user_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class UserTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From 80a0ae8510b26f5ba744d5525c7783528a6d23a2 Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Thu, 4 Sep 2025 16:30:30 -0300 Subject: [PATCH 09/42] feat: registration flow with username and password only --- .../app/views/registrations/new.html.erb.tt | 16 +++--- .../controllers/registrations_controller.rb | 54 +++---------------- .../controllers/registrations_controller.rb | 54 +++---------------- .../app/views/registrations/new.html.erb | 16 +++--- test/dummy/app/views/sessions/new.html.erb | 1 + 5 files changed, 29 insertions(+), 112 deletions(-) diff --git a/lib/generators/erb/webauthn_authentication/templates/app/views/registrations/new.html.erb.tt b/lib/generators/erb/webauthn_authentication/templates/app/views/registrations/new.html.erb.tt index 0b31b505..0af508eb 100644 --- a/lib/generators/erb/webauthn_authentication/templates/app/views/registrations/new.html.erb.tt +++ b/lib/generators/erb/webauthn_authentication/templates/app/views/registrations/new.html.erb.tt @@ -3,12 +3,7 @@ <%%= form_with( scope: :registration, url: registration_path, - id: "new-registration", - data: { - controller: "webauthn-credentials", - action: "webauthn-credentials#create:prevent", - "webauthn-credentials-options-url-value": create_options_registration_path, - }) do |form| %> + ) do |form| %>

<%%= form.label :username %>

@@ -16,11 +11,14 @@
-

<%%= form.label :nickname, 'Security Key nickname' %>

-

<%%= form.text_field :nickname, required: true %>

+

<%%= form.label :password, 'Password' %>

+

<%%= form.password_field :password, required: true %>

- <%%= form.hidden_field :public_key_credential, data: { "webauthn-credentials-target": "credentialHiddenInput" } %> +
+

<%%= form.label :password_confirmation, 'Confirm Password' %>

+

<%%= form.password_field :password_confirmation, required: true %>

+
<%%= form.submit "Sign up" %> diff --git a/lib/generators/webauthn_authentication/templates/app/controllers/registrations_controller.rb b/lib/generators/webauthn_authentication/templates/app/controllers/registrations_controller.rb index 6d100ebc..2a310d0b 100644 --- a/lib/generators/webauthn_authentication/templates/app/controllers/registrations_controller.rb +++ b/lib/generators/webauthn_authentication/templates/app/controllers/registrations_controller.rb @@ -4,61 +4,21 @@ class RegistrationsController < ApplicationController def new end - def create_options - user = User.new(username: registration_params[:username]) - - create_options = WebAuthn::Credential.options_for_create( - user: { - name: registration_params[:username], - id: user.webauthn_id - }, - authenticator_selection: { user_verification: "required" } - ) - - if user.valid? - session[:current_registration] = { challenge: create_options.challenge, user_attributes: user.attributes } - - render json: create_options - else - render json: { errors: user.errors.full_messages }, status: :unprocessable_entity - end - end - def create - webauthn_credential = WebAuthn::Credential.from_create(JSON.parse(registration_params[:public_key_credential])) - - user = User.new(session[:current_registration][:user_attributes] || session[:current_registration]["user_attributes"]) - - begin - webauthn_credential.verify( - session[:current_registration][:challenge] || session[:current_registration]["challenge"], - user_verification: true, - ) + user = User.new(username: registration_params[:username], password: registration_params[:password], password_confirmation: registration_params[:password_confirmation]) - user.webauthn_credentials.build( - external_id: webauthn_credential.id, - nickname: registration_params[:nickname], - public_key: webauthn_credential.public_key, - sign_count: webauthn_credential.sign_count - ) + if user.save + start_new_session_for user - if user.save - start_new_session_for(user) - - redirect_to after_authentication_url, notice: "Security Key registered successfully" - else - redirect_to new_registration_path, alert: "Error registering credential" - end - rescue WebAuthn::Error => e - render json: "Verification failed: #{e.message}", status: :unprocessable_entity - ensure - session.delete(:current_registration) + redirect_to after_authentication_url, notice: "User registered successfully" + else + redirect_to new_registration_path, alert: "Error registering user" end end private def registration_params - params.require(:registration).permit(:username, :nickname, :public_key_credential) + params.require(:registration).permit(:username, :password, :password_confirmation) end end diff --git a/test/dummy/app/controllers/registrations_controller.rb b/test/dummy/app/controllers/registrations_controller.rb index 6d100ebc..2a310d0b 100644 --- a/test/dummy/app/controllers/registrations_controller.rb +++ b/test/dummy/app/controllers/registrations_controller.rb @@ -4,61 +4,21 @@ class RegistrationsController < ApplicationController def new end - def create_options - user = User.new(username: registration_params[:username]) - - create_options = WebAuthn::Credential.options_for_create( - user: { - name: registration_params[:username], - id: user.webauthn_id - }, - authenticator_selection: { user_verification: "required" } - ) - - if user.valid? - session[:current_registration] = { challenge: create_options.challenge, user_attributes: user.attributes } - - render json: create_options - else - render json: { errors: user.errors.full_messages }, status: :unprocessable_entity - end - end - def create - webauthn_credential = WebAuthn::Credential.from_create(JSON.parse(registration_params[:public_key_credential])) - - user = User.new(session[:current_registration][:user_attributes] || session[:current_registration]["user_attributes"]) - - begin - webauthn_credential.verify( - session[:current_registration][:challenge] || session[:current_registration]["challenge"], - user_verification: true, - ) + user = User.new(username: registration_params[:username], password: registration_params[:password], password_confirmation: registration_params[:password_confirmation]) - user.webauthn_credentials.build( - external_id: webauthn_credential.id, - nickname: registration_params[:nickname], - public_key: webauthn_credential.public_key, - sign_count: webauthn_credential.sign_count - ) + if user.save + start_new_session_for user - if user.save - start_new_session_for(user) - - redirect_to after_authentication_url, notice: "Security Key registered successfully" - else - redirect_to new_registration_path, alert: "Error registering credential" - end - rescue WebAuthn::Error => e - render json: "Verification failed: #{e.message}", status: :unprocessable_entity - ensure - session.delete(:current_registration) + redirect_to after_authentication_url, notice: "User registered successfully" + else + redirect_to new_registration_path, alert: "Error registering user" end end private def registration_params - params.require(:registration).permit(:username, :nickname, :public_key_credential) + params.require(:registration).permit(:username, :password, :password_confirmation) end end diff --git a/test/dummy/app/views/registrations/new.html.erb b/test/dummy/app/views/registrations/new.html.erb index fed0e29a..e10a8069 100644 --- a/test/dummy/app/views/registrations/new.html.erb +++ b/test/dummy/app/views/registrations/new.html.erb @@ -3,12 +3,7 @@ <%= form_with( scope: :registration, url: registration_path, - id: "new-registration", - data: { - controller: "webauthn-credentials", - action: "webauthn-credentials#create:prevent", - "webauthn-credentials-options-url-value": create_options_registration_path, - }) do |form| %> + ) do |form| %>

<%= form.label :username %>

@@ -16,11 +11,14 @@
-

<%= form.label :nickname, 'Security Key nickname' %>

-

<%= form.text_field :nickname, required: true %>

+

<%= form.label :password, 'Password' %>

+

<%= form.password_field :password, required: true %>

- <%= form.hidden_field :public_key_credential, data: { "webauthn-credentials-target": "credentialHiddenInput" } %> +
+

<%= form.label :password_confirmation, 'Confirm Password' %>

+

<%= form.password_field :password_confirmation, required: true %>

+
<%= form.submit "Sign up" %> diff --git a/test/dummy/app/views/sessions/new.html.erb b/test/dummy/app/views/sessions/new.html.erb index ff641c4f..713746d1 100644 --- a/test/dummy/app/views/sessions/new.html.erb +++ b/test/dummy/app/views/sessions/new.html.erb @@ -9,3 +9,4 @@
<%= link_to "Forgot password?", new_password_path %> +<%= link_to "Sign up", new_registration_path %> From f7587304d64dda26891ee4e54109c6b0a5ea254b Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Thu, 4 Sep 2025 16:45:13 -0300 Subject: [PATCH 10/42] feat: remove username from User model, as we now use `email` --- .../webauthn_authentication_generator.rb | 2 +- test/dummy/db/migrate/20250904175233_add_webauthn_to_users.rb | 2 -- test/dummy/db/schema.rb | 2 -- test/generators/webauthn_authentication_generator_test.rb | 4 ++-- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb b/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb index bf77a274..41567074 100644 --- a/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb +++ b/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb @@ -55,7 +55,7 @@ def copy_initializer_file end def inject_webauthn_content - generate "migration", "AddWebauthnToUsers", "username:string:uniq webauthn_id:string" + generate "migration", "AddWebauthnToUsers", "webauthn_id:string" inject_webauthn_content_to_user_model inject_into_file "config/routes.rb", after: "Rails.application.routes.draw do\n" do diff --git a/test/dummy/db/migrate/20250904175233_add_webauthn_to_users.rb b/test/dummy/db/migrate/20250904175233_add_webauthn_to_users.rb index bd93d781..ec0df6eb 100644 --- a/test/dummy/db/migrate/20250904175233_add_webauthn_to_users.rb +++ b/test/dummy/db/migrate/20250904175233_add_webauthn_to_users.rb @@ -1,7 +1,5 @@ class AddWebauthnToUsers < ActiveRecord::Migration[8.0] def change - add_column :users, :username, :string - add_index :users, :username, unique: true add_column :users, :webauthn_id, :string end end diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb index a707d499..eaa970a3 100644 --- a/test/dummy/db/schema.rb +++ b/test/dummy/db/schema.rb @@ -25,10 +25,8 @@ t.string "password_digest", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.string "username" t.string "webauthn_id" t.index ["email_address"], name: "index_users_on_email_address", unique: true - t.index ["username"], name: "index_users_on_username", unique: true end create_table "webauthn_credentials", force: :cascade do |t| diff --git a/test/generators/webauthn_authentication_generator_test.rb b/test/generators/webauthn_authentication_generator_test.rb index 8b6b78c7..6b2a041b 100644 --- a/test/generators/webauthn_authentication_generator_test.rb +++ b/test/generators/webauthn_authentication_generator_test.rb @@ -45,7 +45,7 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase assert_file "test/test_helpers/virtual_authenticator_test_helper.rb" assert_file "app/models/user.rb", /has_many :webauthn_credentials/ - assert_includes @rails_commands, "generate migration AddWebauthnToUsers username:string:uniq webauthn_id:string" + assert_includes @rails_commands, "generate migration AddWebauthnToUsers webauthn_id:string" assert_file "app/models/webauthn_credential.rb", /belongs_to :user/ assert_includes @rails_commands, "generate migration CreateWebauthnCredentials user:references! external_id:string:uniq public_key:string nickname:string sign_count:integer{8}" @@ -76,7 +76,7 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase assert_file "config/initializers/webauthn.rb", /WebAuthn.configure/ assert_file "app/models/user.rb", /has_many :webauthn_credentials/ - assert_includes @rails_commands, "generate migration AddWebauthnToUsers username:string:uniq webauthn_id:string" + assert_includes @rails_commands, "generate migration AddWebauthnToUsers webauthn_id:string" assert_file "app/models/webauthn_credential.rb", /belongs_to :user/ assert_includes @rails_commands, "generate migration CreateWebauthnCredentials user:references! external_id:string:uniq public_key:string nickname:string sign_count:integer{8}" From 4cc37222c157b9cf40fbbc748b1d8e22eeb72abf Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Thu, 4 Sep 2025 16:50:02 -0300 Subject: [PATCH 11/42] feat: remove username from User model --- .../webauthn_authentication_generator.rb | 2 -- test/dummy/app/models/user.rb | 2 -- 2 files changed, 4 deletions(-) diff --git a/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb b/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb index 41567074..e0ea3a3f 100644 --- a/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb +++ b/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb @@ -104,8 +104,6 @@ def inject_webauthn_content_to_user_model <<-RUBY.strip_heredoc.indent(2) CREDENTIAL_MIN_AMOUNT = 1 - validates :username, presence: true, uniqueness: true - has_many :webauthn_credentials, dependent: :destroy has_many :webauthn_sessions, dependent: :destroy diff --git a/test/dummy/app/models/user.rb b/test/dummy/app/models/user.rb index 5b22004b..50ca63ea 100644 --- a/test/dummy/app/models/user.rb +++ b/test/dummy/app/models/user.rb @@ -5,8 +5,6 @@ class User < ApplicationRecord normalizes :email_address, with: ->(e) { e.strip.downcase } CREDENTIAL_MIN_AMOUNT = 1 - validates :username, presence: true, uniqueness: true - has_many :webauthn_credentials, dependent: :destroy has_many :webauthn_sessions, dependent: :destroy From 79207b74a538e514f26d90be9eea8b9d6c26d129 Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Fri, 5 Sep 2025 09:34:40 -0300 Subject: [PATCH 12/42] feat: changing `username` for `email` --- .../templates/app/views/registrations/new.html.erb.tt | 4 ++-- .../templates/app/controllers/registrations_controller.rb | 8 ++++++-- .../app/controllers/webauthn_credentials_controller.rb | 2 +- test/dummy/app/controllers/registrations_controller.rb | 8 ++++++-- .../app/controllers/webauthn_credentials_controller.rb | 2 +- test/dummy/app/views/registrations/new.html.erb | 4 ++-- 6 files changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/generators/erb/webauthn_authentication/templates/app/views/registrations/new.html.erb.tt b/lib/generators/erb/webauthn_authentication/templates/app/views/registrations/new.html.erb.tt index 0af508eb..5fd31731 100644 --- a/lib/generators/erb/webauthn_authentication/templates/app/views/registrations/new.html.erb.tt +++ b/lib/generators/erb/webauthn_authentication/templates/app/views/registrations/new.html.erb.tt @@ -6,8 +6,8 @@ ) do |form| %>
-

<%%= form.label :username %>

-

<%%= form.text_field :username, autofocus: true, autocapitalize: "none", required: true %>

+

<%%= form.label :email_address %>

+

<%%= form.email_field :email_address, required: true %>

diff --git a/lib/generators/webauthn_authentication/templates/app/controllers/registrations_controller.rb b/lib/generators/webauthn_authentication/templates/app/controllers/registrations_controller.rb index 2a310d0b..faa1a097 100644 --- a/lib/generators/webauthn_authentication/templates/app/controllers/registrations_controller.rb +++ b/lib/generators/webauthn_authentication/templates/app/controllers/registrations_controller.rb @@ -5,7 +5,11 @@ def new end def create - user = User.new(username: registration_params[:username], password: registration_params[:password], password_confirmation: registration_params[:password_confirmation]) + if registration_params[:password] != registration_params[:password_confirmation] + redirect_to new_registration_path, alert: "Password and confirmation do not match" + return + end + user = User.new(email_address: registration_params[:email_address], password: registration_params[:password]) if user.save start_new_session_for user @@ -19,6 +23,6 @@ def create private def registration_params - params.require(:registration).permit(:username, :password, :password_confirmation) + params.require(:registration).permit(:email_address, :password, :password_confirmation) end end diff --git a/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_credentials_controller.rb b/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_credentials_controller.rb index bb9acac7..b3e0b2f6 100644 --- a/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_credentials_controller.rb +++ b/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_credentials_controller.rb @@ -3,7 +3,7 @@ def create_options create_options = WebAuthn::Credential.options_for_create( user: { id: Current.user.webauthn_id, - name: Current.user.username + name: Current.user.email_address }, exclude: Current.user.webauthn_credentials.pluck(:external_id), authenticator_selection: { user_verification: "required" } diff --git a/test/dummy/app/controllers/registrations_controller.rb b/test/dummy/app/controllers/registrations_controller.rb index 2a310d0b..faa1a097 100644 --- a/test/dummy/app/controllers/registrations_controller.rb +++ b/test/dummy/app/controllers/registrations_controller.rb @@ -5,7 +5,11 @@ def new end def create - user = User.new(username: registration_params[:username], password: registration_params[:password], password_confirmation: registration_params[:password_confirmation]) + if registration_params[:password] != registration_params[:password_confirmation] + redirect_to new_registration_path, alert: "Password and confirmation do not match" + return + end + user = User.new(email_address: registration_params[:email_address], password: registration_params[:password]) if user.save start_new_session_for user @@ -19,6 +23,6 @@ def create private def registration_params - params.require(:registration).permit(:username, :password, :password_confirmation) + params.require(:registration).permit(:email_address, :password, :password_confirmation) end end diff --git a/test/dummy/app/controllers/webauthn_credentials_controller.rb b/test/dummy/app/controllers/webauthn_credentials_controller.rb index bb9acac7..b3e0b2f6 100644 --- a/test/dummy/app/controllers/webauthn_credentials_controller.rb +++ b/test/dummy/app/controllers/webauthn_credentials_controller.rb @@ -3,7 +3,7 @@ def create_options create_options = WebAuthn::Credential.options_for_create( user: { id: Current.user.webauthn_id, - name: Current.user.username + name: Current.user.email_address }, exclude: Current.user.webauthn_credentials.pluck(:external_id), authenticator_selection: { user_verification: "required" } diff --git a/test/dummy/app/views/registrations/new.html.erb b/test/dummy/app/views/registrations/new.html.erb index e10a8069..d92eb32c 100644 --- a/test/dummy/app/views/registrations/new.html.erb +++ b/test/dummy/app/views/registrations/new.html.erb @@ -6,8 +6,8 @@ ) do |form| %>
-

<%= form.label :username %>

-

<%= form.text_field :username, autofocus: true, autocapitalize: "none", required: true %>

+

<%= form.label :email_address, 'Email' %>

+

<%= form.email_field :email_address, required: true %>

From 4ac57636ece5e913191e7338d611dd28cfce4365 Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Fri, 5 Sep 2025 11:27:50 -0300 Subject: [PATCH 13/42] feat: make Credentials discoverable, continue replacing parameter `username` for `email` --- .../webauthn_credentials_controller.rb | 5 ++- .../webauthn_sessions_controller.rb | 31 +++++++------------ .../webauthn_credentials_controller.rb | 5 ++- .../webauthn_sessions_controller.rb | 29 ++++++----------- .../webauthn_credentials_controller.js | 2 +- test/dummy/app/views/sessions/new.html.erb | 26 ++++++++++++++-- .../app/views/webauthn_sessions/new.html.erb | 25 --------------- 7 files changed, 54 insertions(+), 69 deletions(-) delete mode 100644 test/dummy/app/views/webauthn_sessions/new.html.erb diff --git a/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_credentials_controller.rb b/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_credentials_controller.rb index b3e0b2f6..7c6459ef 100644 --- a/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_credentials_controller.rb +++ b/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_credentials_controller.rb @@ -6,7 +6,10 @@ def create_options name: Current.user.email_address }, exclude: Current.user.webauthn_credentials.pluck(:external_id), - authenticator_selection: { user_verification: "required" } + authenticator_selection: { + resident_key: 'required', + user_verification: 'required' + } ) session[:current_registration] = { challenge: create_options.challenge } diff --git a/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_sessions_controller.rb b/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_sessions_controller.rb index d27630cf..8d321d8e 100644 --- a/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_sessions_controller.rb +++ b/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_sessions_controller.rb @@ -5,32 +5,23 @@ def new end def get_options - user = User.find_by(username: session_params[:username]) + get_options = WebAuthn::Credential.options_for_get(user_verification: "required") + session[:current_authentication] = { challenge: get_options.challenge } - if user - get_options = WebAuthn::Credential.options_for_get( - allow: user.webauthn_credentials.pluck(:external_id), - user_verification: "required" - ) - - session[:current_authentication] = { challenge: get_options.challenge, username: session_params[:username] } - - render json: get_options - else - render json: { errors: [ "Username doesn't exist" ] }, status: :unprocessable_entity - end + render json: get_options end def create webauthn_credential = WebAuthn::Credential.from_get(JSON.parse(session_params[:public_key_credential])) - user = User.find_by(username: session[:current_authentication][:username] || session[:current_authentication]["username"]) - raise "user #{session[:current_authentication][:username]} never initiated sign up" unless user - - stored_credential = user.webauthn_credentials.find_by(external_id: webauthn_credential.id) + stored_credential = WebauthnCredential.find_by(external_id: webauthn_credential.id) + unless stored_credential + render json: { errors: [ "Credential not recognized" ] }, status: :unprocessable_content + return + end begin - webauthn_credential.verify( + webauthn_credential.verify( session[:current_authentication][:challenge] || session[:current_authentication]["challenge"], public_key: stored_credential.public_key, sign_count: stored_credential.sign_count, @@ -38,7 +29,7 @@ def create ) stored_credential.update!(sign_count: webauthn_credential.sign_count) - start_new_session_for(user) + start_new_session_for stored_credential.user redirect_to after_authentication_url, notice: "Credential authenticated successfully" rescue WebAuthn::Error => e @@ -57,6 +48,6 @@ def destroy private def session_params - params.require(:session).permit(:username, :public_key_credential) + params.require(:session).permit(:public_key_credential) end end diff --git a/test/dummy/app/controllers/webauthn_credentials_controller.rb b/test/dummy/app/controllers/webauthn_credentials_controller.rb index b3e0b2f6..7c6459ef 100644 --- a/test/dummy/app/controllers/webauthn_credentials_controller.rb +++ b/test/dummy/app/controllers/webauthn_credentials_controller.rb @@ -6,7 +6,10 @@ def create_options name: Current.user.email_address }, exclude: Current.user.webauthn_credentials.pluck(:external_id), - authenticator_selection: { user_verification: "required" } + authenticator_selection: { + resident_key: 'required', + user_verification: 'required' + } ) session[:current_registration] = { challenge: create_options.challenge } diff --git a/test/dummy/app/controllers/webauthn_sessions_controller.rb b/test/dummy/app/controllers/webauthn_sessions_controller.rb index d27630cf..f5a8600a 100644 --- a/test/dummy/app/controllers/webauthn_sessions_controller.rb +++ b/test/dummy/app/controllers/webauthn_sessions_controller.rb @@ -5,29 +5,20 @@ def new end def get_options - user = User.find_by(username: session_params[:username]) + get_options = WebAuthn::Credential.options_for_get(user_verification: "required") + session[:current_authentication] = { challenge: get_options.challenge } - if user - get_options = WebAuthn::Credential.options_for_get( - allow: user.webauthn_credentials.pluck(:external_id), - user_verification: "required" - ) - - session[:current_authentication] = { challenge: get_options.challenge, username: session_params[:username] } - - render json: get_options - else - render json: { errors: [ "Username doesn't exist" ] }, status: :unprocessable_entity - end + render json: get_options end def create webauthn_credential = WebAuthn::Credential.from_get(JSON.parse(session_params[:public_key_credential])) - user = User.find_by(username: session[:current_authentication][:username] || session[:current_authentication]["username"]) - raise "user #{session[:current_authentication][:username]} never initiated sign up" unless user - - stored_credential = user.webauthn_credentials.find_by(external_id: webauthn_credential.id) + stored_credential = WebauthnCredential.find_by(external_id: webauthn_credential.id) + unless stored_credential + render json: { errors: [ "Credential not recognized" ] }, status: :unprocessable_content + return + end begin webauthn_credential.verify( @@ -38,7 +29,7 @@ def create ) stored_credential.update!(sign_count: webauthn_credential.sign_count) - start_new_session_for(user) + start_new_session_for stored_credential.user redirect_to after_authentication_url, notice: "Credential authenticated successfully" rescue WebAuthn::Error => e @@ -57,6 +48,6 @@ def destroy private def session_params - params.require(:session).permit(:username, :public_key_credential) + params.require(:session).permit(:public_key_credential) end end diff --git a/test/dummy/app/javascript/controllers/webauthn_credentials_controller.js b/test/dummy/app/javascript/controllers/webauthn_credentials_controller.js index 133e1d7c..d03dda5f 100644 --- a/test/dummy/app/javascript/controllers/webauthn_credentials_controller.js +++ b/test/dummy/app/javascript/controllers/webauthn_credentials_controller.js @@ -16,7 +16,7 @@ export default class extends Controller { }); const optionsJson = await optionsResponse.json(); - if (optionsResponse.ok && optionsJson.user) { + if (optionsResponse.ok) { const credentialOptions = PublicKeyCredential.parseCreationOptionsFromJSON(optionsJson); const credential = await createWebAuthnJSON({ publicKey: credentialOptions }); diff --git a/test/dummy/app/views/sessions/new.html.erb b/test/dummy/app/views/sessions/new.html.erb index 713746d1..68d49847 100644 --- a/test/dummy/app/views/sessions/new.html.erb +++ b/test/dummy/app/views/sessions/new.html.erb @@ -8,5 +8,27 @@ <% end %>
-<%= link_to "Forgot password?", new_password_path %> -<%= link_to "Sign up", new_registration_path %> +
+ <%= link_to "Forgot password?", new_password_path %> +
+
+ <%= link_to "Sign up", new_registration_path %> +
+ +<%= form_with( + scope: :session, + url: webauthn_session_path, + method: :post, + data: { + controller: "webauthn-credentials", + action: "webauthn-credentials#get:prevent", + "webauthn-credentials-options-url-value": get_options_webauthn_session_path, + }) do |f| %> + + <%= f.hidden_field :public_key_credential, data: { "webauthn-credentials-target": "credentialHiddenInput" } %> + +
+ <%= f.submit "Ingresar con Passkey"%> +
+ +<% end %> diff --git a/test/dummy/app/views/webauthn_sessions/new.html.erb b/test/dummy/app/views/webauthn_sessions/new.html.erb deleted file mode 100644 index 1928e51a..00000000 --- a/test/dummy/app/views/webauthn_sessions/new.html.erb +++ /dev/null @@ -1,25 +0,0 @@ -

Sign in

- -<%= form_with( - scope: :session, - url: webauthn_session_path, - id: "new-session", - data: { - controller: "webauthn-credentials", - action: "webauthn-credentials#get:prevent", - "webauthn-credentials-options-url-value": get_options_webauthn_session_path, - }) do |form| %> - -
-

<%= form.label :username %>

-

<%= form.text_field :username, autofocus: true, autocapitalize: "none", required: true %>

-
- - <%= form.hidden_field :public_key_credential, data: { "webauthn-credentials-target": "credentialHiddenInput" } %> - -
- <%= form.submit "Sign in" %> -
-<% end %> - -<%= link_to "Sign up", new_registration_path %> From 1be5bf749618cafbecfa08c39659513818af5d65 Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Fri, 5 Sep 2025 11:45:53 -0300 Subject: [PATCH 14/42] feat: append into new session rails template new passkey button form --- .../views/webauthn_sessions/new.html.erb.tt | 25 ----------------- .../webauthn_authentication_generator.rb | 28 ++++++++++++++++++- test/dummy/app/views/sessions/new.html.erb | 6 ++-- .../webauthn_authentication_generator_test.rb | 2 -- 4 files changed, 29 insertions(+), 32 deletions(-) delete mode 100644 lib/generators/erb/webauthn_authentication/templates/app/views/webauthn_sessions/new.html.erb.tt diff --git a/lib/generators/erb/webauthn_authentication/templates/app/views/webauthn_sessions/new.html.erb.tt b/lib/generators/erb/webauthn_authentication/templates/app/views/webauthn_sessions/new.html.erb.tt deleted file mode 100644 index c7c2f3fa..00000000 --- a/lib/generators/erb/webauthn_authentication/templates/app/views/webauthn_sessions/new.html.erb.tt +++ /dev/null @@ -1,25 +0,0 @@ -

Sign in

- -<%%= form_with( - scope: :session, - url: webauthn_session_path, - id: "new-session", - data: { - controller: "webauthn-credentials", - action: "webauthn-credentials#get:prevent", - "webauthn-credentials-options-url-value": get_options_webauthn_session_path, - }) do |form| %> - -
-

<%%= form.label :username %>

-

<%%= form.text_field :username, autofocus: true, autocapitalize: "none", required: true %>

-
- - <%%= form.hidden_field :public_key_credential, data: { "webauthn-credentials-target": "credentialHiddenInput" } %> - -
- <%%= form.submit "Sign in" %> -
-<%% end %> - -<%%= link_to "Sign up", new_registration_path %> diff --git a/lib/generators/erb/webauthn_authentication/webauthn_authentication_generator.rb b/lib/generators/erb/webauthn_authentication/webauthn_authentication_generator.rb index 0fed26b9..ff298d9c 100644 --- a/lib/generators/erb/webauthn_authentication/webauthn_authentication_generator.rb +++ b/lib/generators/erb/webauthn_authentication/webauthn_authentication_generator.rb @@ -9,7 +9,33 @@ class WebauthnAuthenticationGenerator < Rails::Generators::Base def create_files template "app/views/webauthn_credentials/new.html.erb.tt" template "app/views/registrations/new.html.erb.tt" - template "app/views/webauthn_sessions/new.html.erb.tt" + end + + def inject_into_rails_session_view + append_to_file "app/views/sessions/new.html.erb" do + <<-ERB.strip_heredoc.indent(2) +
+ <%= link_to "Sign up", new_registration_path %> +
+ + <%= form_with( + scope: :session, + url: webauthn_session_path, + method: :post, + data: { + controller: "webauthn-credentials", + action: "webauthn-credentials#get:prevent", + "webauthn-credentials-options-url-value": get_options_webauthn_session_path, + }) do |f| %> + + <%= f.hidden_field :public_key_credential, data: { "webauthn-credentials-target": "credentialHiddenInput" } %> + +
+ <%= f.submit "Ingresar con Passkey"%> +
+ <% end %> + ERB + end end end end diff --git a/test/dummy/app/views/sessions/new.html.erb b/test/dummy/app/views/sessions/new.html.erb index 68d49847..00802d68 100644 --- a/test/dummy/app/views/sessions/new.html.erb +++ b/test/dummy/app/views/sessions/new.html.erb @@ -8,9 +8,8 @@ <% end %>
-
- <%= link_to "Forgot password?", new_password_path %> -
+<%= link_to "Forgot password?", new_password_path %> +
<%= link_to "Sign up", new_registration_path %>
@@ -30,5 +29,4 @@
<%= f.submit "Ingresar con Passkey"%>
- <% end %> diff --git a/test/generators/webauthn_authentication_generator_test.rb b/test/generators/webauthn_authentication_generator_test.rb index 6b2a041b..bb35c79c 100644 --- a/test/generators/webauthn_authentication_generator_test.rb +++ b/test/generators/webauthn_authentication_generator_test.rb @@ -31,7 +31,6 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase assert_file "app/views/webauthn_credentials/new.html.erb" assert_file "app/views/registrations/new.html.erb" - assert_file "app/views/webauthn_sessions/new.html.erb" assert_file "app/javascript/controllers/webauthn_credentials_controller.js" @@ -69,7 +68,6 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase assert_no_file "app/views/webauthn_credentials/new.html.erb" assert_no_file "app/views/registrations/new.html.erb" - assert_no_file "app/views/webauthn_sessions/new.html.erb" assert_file "app/javascript/controllers/webauthn_credentials_controller.js" From a726cd3c9bd572e330b67e13611b8564f8a5cdfa Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Fri, 5 Sep 2025 12:27:32 -0300 Subject: [PATCH 15/42] fix: add email unique model validation, change errors handling in registration controller --- .../templates/app/controllers/registrations_controller.rb | 3 ++- .../webauthn_authentication_generator.rb | 2 ++ test/dummy/app/controllers/registrations_controller.rb | 3 ++- test/dummy/app/models/user.rb | 2 ++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/generators/webauthn_authentication/templates/app/controllers/registrations_controller.rb b/lib/generators/webauthn_authentication/templates/app/controllers/registrations_controller.rb index faa1a097..d17f836d 100644 --- a/lib/generators/webauthn_authentication/templates/app/controllers/registrations_controller.rb +++ b/lib/generators/webauthn_authentication/templates/app/controllers/registrations_controller.rb @@ -16,7 +16,8 @@ def create redirect_to after_authentication_url, notice: "User registered successfully" else - redirect_to new_registration_path, alert: "Error registering user" + flash[:alert] = @user.errors.full_messages.join("\n") + render :new end end diff --git a/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb b/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb index e0ea3a3f..df05ea6d 100644 --- a/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb +++ b/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb @@ -102,6 +102,8 @@ def has_package_json? def inject_webauthn_content_to_user_model inject_into_file "app/models/user.rb", after: "normalizes :email_address, with: ->(e) { e.strip.downcase }\n" do <<-RUBY.strip_heredoc.indent(2) + validates :email_address, presence: true, uniqueness: true + CREDENTIAL_MIN_AMOUNT = 1 has_many :webauthn_credentials, dependent: :destroy diff --git a/test/dummy/app/controllers/registrations_controller.rb b/test/dummy/app/controllers/registrations_controller.rb index faa1a097..60f3c0d8 100644 --- a/test/dummy/app/controllers/registrations_controller.rb +++ b/test/dummy/app/controllers/registrations_controller.rb @@ -16,7 +16,8 @@ def create redirect_to after_authentication_url, notice: "User registered successfully" else - redirect_to new_registration_path, alert: "Error registering user" + flash[:alert] = user.errors.full_messages.join("\n") + render :new end end diff --git a/test/dummy/app/models/user.rb b/test/dummy/app/models/user.rb index 50ca63ea..1eddf9da 100644 --- a/test/dummy/app/models/user.rb +++ b/test/dummy/app/models/user.rb @@ -3,6 +3,8 @@ class User < ApplicationRecord has_many :sessions, dependent: :destroy normalizes :email_address, with: ->(e) { e.strip.downcase } + validates :email_address, presence: true, uniqueness: true + CREDENTIAL_MIN_AMOUNT = 1 has_many :webauthn_credentials, dependent: :destroy From 4c692774b7b4731e10b5ea16953ca6818cc92dbe Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Fri, 5 Sep 2025 12:36:23 -0300 Subject: [PATCH 16/42] feat: add notices and alerts --- .../templates/app/views/registrations/new.html.erb.tt | 2 ++ .../templates/app/views/webauthn_credentials/new.html.erb.tt | 3 +++ .../app/controllers/webauthn_credentials_controller.rb | 3 ++- test/dummy/app/controllers/webauthn_credentials_controller.rb | 3 ++- test/dummy/app/views/registrations/new.html.erb | 2 ++ test/dummy/app/views/webauthn_credentials/new.html.erb | 3 +++ 6 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/generators/erb/webauthn_authentication/templates/app/views/registrations/new.html.erb.tt b/lib/generators/erb/webauthn_authentication/templates/app/views/registrations/new.html.erb.tt index 5fd31731..b4cd17d6 100644 --- a/lib/generators/erb/webauthn_authentication/templates/app/views/registrations/new.html.erb.tt +++ b/lib/generators/erb/webauthn_authentication/templates/app/views/registrations/new.html.erb.tt @@ -1,4 +1,6 @@

Sign up

+<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %> +<%= tag.div(flash[:notice], style: "color:green") if flash[:notice] %> <%%= form_with( scope: :registration, diff --git a/lib/generators/erb/webauthn_authentication/templates/app/views/webauthn_credentials/new.html.erb.tt b/lib/generators/erb/webauthn_authentication/templates/app/views/webauthn_credentials/new.html.erb.tt index 3f2aa7e9..bbd7dff1 100644 --- a/lib/generators/erb/webauthn_authentication/templates/app/views/webauthn_credentials/new.html.erb.tt +++ b/lib/generators/erb/webauthn_authentication/templates/app/views/webauthn_credentials/new.html.erb.tt @@ -1,3 +1,6 @@ +<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %> +<%= tag.div(flash[:notice], style: "color:green") if flash[:notice] %> + <%%= form_with( scope: :credential, url: webauthn_credentials_path, diff --git a/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_credentials_controller.rb b/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_credentials_controller.rb index 7c6459ef..f39a137c 100644 --- a/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_credentials_controller.rb +++ b/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_credentials_controller.rb @@ -37,7 +37,8 @@ def create ) redirect_to root_path, notice: "Security Key registered successfully" else - redirect_to new_webauthn_credential_path, alert: "Error registering credential" + flash[:alert] = "Error registering credential" + render :new end rescue WebAuthn::Error => e render json: "Verification failed: #{e.message}", status: :unprocessable_entity diff --git a/test/dummy/app/controllers/webauthn_credentials_controller.rb b/test/dummy/app/controllers/webauthn_credentials_controller.rb index 7c6459ef..f39a137c 100644 --- a/test/dummy/app/controllers/webauthn_credentials_controller.rb +++ b/test/dummy/app/controllers/webauthn_credentials_controller.rb @@ -37,7 +37,8 @@ def create ) redirect_to root_path, notice: "Security Key registered successfully" else - redirect_to new_webauthn_credential_path, alert: "Error registering credential" + flash[:alert] = "Error registering credential" + render :new end rescue WebAuthn::Error => e render json: "Verification failed: #{e.message}", status: :unprocessable_entity diff --git a/test/dummy/app/views/registrations/new.html.erb b/test/dummy/app/views/registrations/new.html.erb index d92eb32c..9ebefc3f 100644 --- a/test/dummy/app/views/registrations/new.html.erb +++ b/test/dummy/app/views/registrations/new.html.erb @@ -1,4 +1,6 @@

Sign up

+<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %> +<%= tag.div(flash[:notice], style: "color:green") if flash[:notice] %> <%= form_with( scope: :registration, diff --git a/test/dummy/app/views/webauthn_credentials/new.html.erb b/test/dummy/app/views/webauthn_credentials/new.html.erb index c823aba6..dc5aad63 100644 --- a/test/dummy/app/views/webauthn_credentials/new.html.erb +++ b/test/dummy/app/views/webauthn_credentials/new.html.erb @@ -1,3 +1,6 @@ +<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %> +<%= tag.div(flash[:notice], style: "color:green") if flash[:notice] %> + <%= form_with( scope: :credential, url: webauthn_credentials_path, From 968e6ecf298f2d8166982c852f8163de5a25da27 Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Fri, 5 Sep 2025 13:18:06 -0300 Subject: [PATCH 17/42] feat: add alerts and notices in home page dummy app --- test/dummy/app/views/home/index.html.erb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/dummy/app/views/home/index.html.erb b/test/dummy/app/views/home/index.html.erb index 00504ba9..2a38e0c7 100644 --- a/test/dummy/app/views/home/index.html.erb +++ b/test/dummy/app/views/home/index.html.erb @@ -1,6 +1,9 @@ <%= link_to 'Sign out', session_path, data: { turbo_method: :delete } %>

Your Security Keys

+<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %> +<%= tag.div(flash[:notice], style: "color:green") if flash[:notice] %> +
    <% Current.user.webauthn_credentials.each do |credential| %>
  • From 40f46bd5ff294475a5df3613a25d9c57208577eb Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Fri, 5 Sep 2025 13:27:20 -0300 Subject: [PATCH 18/42] feat: change notices messages --- .../app/controllers/webauthn_credentials_controller.rb | 2 +- .../templates/app/controllers/webauthn_sessions_controller.rb | 2 +- test/dummy/app/controllers/webauthn_credentials_controller.rb | 2 +- test/dummy/app/controllers/webauthn_sessions_controller.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_credentials_controller.rb b/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_credentials_controller.rb index f39a137c..5a0754de 100644 --- a/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_credentials_controller.rb +++ b/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_credentials_controller.rb @@ -52,7 +52,7 @@ def destroy Current.user.webauthn_credentials.destroy(params[:id]) end - redirect_to root_path + redirect_to root_path, notice: "Security Key deleted successfully" end private diff --git a/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_sessions_controller.rb b/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_sessions_controller.rb index 8d321d8e..50e71cd9 100644 --- a/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_sessions_controller.rb +++ b/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_sessions_controller.rb @@ -31,7 +31,7 @@ def create stored_credential.update!(sign_count: webauthn_credential.sign_count) start_new_session_for stored_credential.user - redirect_to after_authentication_url, notice: "Credential authenticated successfully" + redirect_to after_authentication_url rescue WebAuthn::Error => e render json: "Verification failed: #{e.message}", status: :unprocessable_entity ensure diff --git a/test/dummy/app/controllers/webauthn_credentials_controller.rb b/test/dummy/app/controllers/webauthn_credentials_controller.rb index f39a137c..5a0754de 100644 --- a/test/dummy/app/controllers/webauthn_credentials_controller.rb +++ b/test/dummy/app/controllers/webauthn_credentials_controller.rb @@ -52,7 +52,7 @@ def destroy Current.user.webauthn_credentials.destroy(params[:id]) end - redirect_to root_path + redirect_to root_path, notice: "Security Key deleted successfully" end private diff --git a/test/dummy/app/controllers/webauthn_sessions_controller.rb b/test/dummy/app/controllers/webauthn_sessions_controller.rb index f5a8600a..d187a2f1 100644 --- a/test/dummy/app/controllers/webauthn_sessions_controller.rb +++ b/test/dummy/app/controllers/webauthn_sessions_controller.rb @@ -31,7 +31,7 @@ def create stored_credential.update!(sign_count: webauthn_credential.sign_count) start_new_session_for stored_credential.user - redirect_to after_authentication_url, notice: "Credential authenticated successfully" + redirect_to after_authentication_url rescue WebAuthn::Error => e render json: "Verification failed: #{e.message}", status: :unprocessable_entity ensure From 66b16516199b61cb48b21f3b5bfa885ad65668e1 Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Fri, 5 Sep 2025 14:21:20 -0300 Subject: [PATCH 19/42] refactor: remove `registration` controller from the gem, add seeds with user --- .../app/views/registrations/new.html.erb.tt | 30 ------ .../webauthn_authentication_generator.rb | 5 - .../registrations_controller_test.rb | 92 ------------------- .../test/system/registration_test.rb | 28 ------ .../webauthn_authentication_generator.rb | 2 - .../controllers/registrations_controller.rb | 29 ------ .../webauthn_authentication_generator.rb | 5 - .../controllers/registrations_controller.rb | 29 ------ .../app/views/registrations/new.html.erb | 30 ------ test/dummy/app/views/sessions/new.html.erb | 4 - test/dummy/config/routes.rb | 3 - test/dummy/db/seeds.rb | 6 ++ .../registrations_controller_test.rb | 92 ------------------- test/dummy/test/system/registration_test.rb | 28 ------ .../webauthn_authentication_generator_test.rb | 6 -- 15 files changed, 6 insertions(+), 383 deletions(-) delete mode 100644 lib/generators/erb/webauthn_authentication/templates/app/views/registrations/new.html.erb.tt delete mode 100644 lib/generators/test_unit/webauthn_authentication/templates/test/controllers/registrations_controller_test.rb delete mode 100644 lib/generators/test_unit/webauthn_authentication/templates/test/system/registration_test.rb delete mode 100644 lib/generators/webauthn_authentication/templates/app/controllers/registrations_controller.rb delete mode 100644 test/dummy/app/controllers/registrations_controller.rb delete mode 100644 test/dummy/app/views/registrations/new.html.erb create mode 100644 test/dummy/db/seeds.rb delete mode 100644 test/dummy/test/controllers/registrations_controller_test.rb delete mode 100644 test/dummy/test/system/registration_test.rb diff --git a/lib/generators/erb/webauthn_authentication/templates/app/views/registrations/new.html.erb.tt b/lib/generators/erb/webauthn_authentication/templates/app/views/registrations/new.html.erb.tt deleted file mode 100644 index b4cd17d6..00000000 --- a/lib/generators/erb/webauthn_authentication/templates/app/views/registrations/new.html.erb.tt +++ /dev/null @@ -1,30 +0,0 @@ -

    Sign up

    -<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %> -<%= tag.div(flash[:notice], style: "color:green") if flash[:notice] %> - -<%%= form_with( - scope: :registration, - url: registration_path, - ) do |form| %> - -
    -

    <%%= form.label :email_address %>

    -

    <%%= form.email_field :email_address, required: true %>

    -
    - -
    -

    <%%= form.label :password, 'Password' %>

    -

    <%%= form.password_field :password, required: true %>

    -
    - -
    -

    <%%= form.label :password_confirmation, 'Confirm Password' %>

    -

    <%%= form.password_field :password_confirmation, required: true %>

    -
    - -
    - <%%= form.submit "Sign up" %> -
    -<%% end %> - -<%%= link_to "Sign in", new_session_path %> diff --git a/lib/generators/erb/webauthn_authentication/webauthn_authentication_generator.rb b/lib/generators/erb/webauthn_authentication/webauthn_authentication_generator.rb index ff298d9c..cf33df27 100644 --- a/lib/generators/erb/webauthn_authentication/webauthn_authentication_generator.rb +++ b/lib/generators/erb/webauthn_authentication/webauthn_authentication_generator.rb @@ -8,16 +8,11 @@ class WebauthnAuthenticationGenerator < Rails::Generators::Base def create_files template "app/views/webauthn_credentials/new.html.erb.tt" - template "app/views/registrations/new.html.erb.tt" end def inject_into_rails_session_view append_to_file "app/views/sessions/new.html.erb" do <<-ERB.strip_heredoc.indent(2) -
    - <%= link_to "Sign up", new_registration_path %> -
    - <%= form_with( scope: :session, url: webauthn_session_path, diff --git a/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/registrations_controller_test.rb b/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/registrations_controller_test.rb deleted file mode 100644 index b73e942e..00000000 --- a/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/registrations_controller_test.rb +++ /dev/null @@ -1,92 +0,0 @@ -require "test_helper" -require "webauthn/fake_client" - -class RegistrationsControllerTest < ActionDispatch::IntegrationTest - test "should initiate registration successfully" do - post create_options_registration_url, params: { registration: { username: "alice" } } - - assert_response :success - end - - test "should return error if registrating taken username" do - User.create!(username: "alice") - - post create_options_registration_url, params: { registration: { username: "alice" } } - - assert_response :unprocessable_entity - assert_equal [ "Username has already been taken" ], JSON.parse(response.body)["errors"] - end - - test "should return error if registrating blank username" do - post create_options_registration_url params: { registration: { username: "" } } - - assert_response :unprocessable_entity - assert_equal [ "Username can't be blank" ], JSON.parse(response.body)["errors"] - end - - test "should return error if registering existing credential" do - raw_challenge = SecureRandom.random_bytes(32) - challenge = WebAuthn.configuration.encoder.encode(raw_challenge) - - WebAuthn::PublicKeyCredential::CreationOptions.stub_any_instance(:raw_challenge, raw_challenge) do - post create_options_registration_url, params: { registration: { username: "alice" } } - - assert_response :success - end - - public_key_credential = - WebAuthn::FakeClient - .new("http://localhost:3030") - .create(challenge:, user_verified: true) - - webauthn_credential = WebAuthn::Credential.from_create(public_key_credential) - - User.create!( - username: "bob", - webauthn_credentials: [ - WebauthnCredential.new( - external_id: webauthn_credential.id, - nickname: "Bob's USB Key", - public_key: webauthn_credential.public_key, - sign_count: webauthn_credential.sign_count - ) - ] - ) - - assert_no_difference -> { User.count } do - post( - registration_url, - params: { registration: { nickname: "USB Key", public_key_credential: public_key_credential.to_json } } - ) - end - - assert_redirected_to new_registration_path - end - - test "should register successfully" do - raw_challenge = SecureRandom.random_bytes(32) - challenge = WebAuthn.configuration.encoder.encode(raw_challenge) - - WebAuthn::PublicKeyCredential::CreationOptions.stub_any_instance(:raw_challenge, raw_challenge) do - post create_options_registration_url, params: { registration: { username: "alice" } } - - assert_response :success - end - - public_key_credential = - WebAuthn::FakeClient - .new("http://localhost:3030") - .create(challenge:, user_verified: true) - - assert_difference "User.count", +1 do - assert_difference "WebauthnCredential.count", +1 do - post( - registration_url, - params: { registration: { nickname: "USB Key", public_key_credential: public_key_credential.to_json } }, - ) - end - end - - assert_redirected_to "/" - end -end diff --git a/lib/generators/test_unit/webauthn_authentication/templates/test/system/registration_test.rb b/lib/generators/test_unit/webauthn_authentication/templates/test/system/registration_test.rb deleted file mode 100644 index cd06b533..00000000 --- a/lib/generators/test_unit/webauthn_authentication/templates/test/system/registration_test.rb +++ /dev/null @@ -1,28 +0,0 @@ -require "application_system_test_case" -require_relative "../test_helpers/virtual_authenticator_test_helper" - -class RegistrationTest < ApplicationSystemTestCase - include VirtualAuthenticatorTestHelper - - def setup - @authenticator = add_virtual_authenticator - end - - def teardown - @authenticator.remove! - end - - test "register user" do - visit new_registration_path - - fill_in "registration_username", with: "User1" - fill_in "Security Key nickname", with: "USB key" - - click_on "Sign up" - # wait for async response - assert_selector "h3", text: "Your Security Keys" - - assert_current_path "/" - assert_selector "span", text: "USB key" - end -end diff --git a/lib/generators/test_unit/webauthn_authentication/webauthn_authentication_generator.rb b/lib/generators/test_unit/webauthn_authentication/webauthn_authentication_generator.rb index 119963e6..f8875f51 100644 --- a/lib/generators/test_unit/webauthn_authentication/webauthn_authentication_generator.rb +++ b/lib/generators/test_unit/webauthn_authentication/webauthn_authentication_generator.rb @@ -7,13 +7,11 @@ class WebauthnAuthenticationGenerator < Rails::Generators::Base source_root File.expand_path("../templates", __FILE__) def create_controller_test_files - template "test/controllers/registrations_controller_test.rb" template "test/controllers/webauthn_sessions_controller_test.rb" end def create_system_test_files template "test/system/add_credential_test.rb" - template "test/system/registration_test.rb" template "test/system/sign_in_test.rb" end diff --git a/lib/generators/webauthn_authentication/templates/app/controllers/registrations_controller.rb b/lib/generators/webauthn_authentication/templates/app/controllers/registrations_controller.rb deleted file mode 100644 index d17f836d..00000000 --- a/lib/generators/webauthn_authentication/templates/app/controllers/registrations_controller.rb +++ /dev/null @@ -1,29 +0,0 @@ -class RegistrationsController < ApplicationController - allow_unauthenticated_access - - def new - end - - def create - if registration_params[:password] != registration_params[:password_confirmation] - redirect_to new_registration_path, alert: "Password and confirmation do not match" - return - end - user = User.new(email_address: registration_params[:email_address], password: registration_params[:password]) - - if user.save - start_new_session_for user - - redirect_to after_authentication_url, notice: "User registered successfully" - else - flash[:alert] = @user.errors.full_messages.join("\n") - render :new - end - end - - private - - def registration_params - params.require(:registration).permit(:email_address, :password, :password_confirmation) - end -end diff --git a/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb b/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb index df05ea6d..cde17ceb 100644 --- a/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb +++ b/lib/generators/webauthn_authentication/webauthn_authentication_generator.rb @@ -15,7 +15,6 @@ class WebauthnAuthenticationGenerator < ::Rails::Generators::Base def copy_controllers_and_concerns template "app/controllers/webauthn_credentials_controller.rb" - template "app/controllers/registrations_controller.rb" template "app/controllers/webauthn_sessions_controller.rb" end @@ -60,10 +59,6 @@ def inject_webauthn_content inject_into_file "config/routes.rb", after: "Rails.application.routes.draw do\n" do <<-RUBY.strip_heredoc.indent(2) - resource :registration, only: [ :new, :create ] do - post :create_options, on: :collection - end - resource :webauthn_session, only: [ :new, :create, :destroy ] do post :get_options, on: :collection end diff --git a/test/dummy/app/controllers/registrations_controller.rb b/test/dummy/app/controllers/registrations_controller.rb deleted file mode 100644 index 60f3c0d8..00000000 --- a/test/dummy/app/controllers/registrations_controller.rb +++ /dev/null @@ -1,29 +0,0 @@ -class RegistrationsController < ApplicationController - allow_unauthenticated_access - - def new - end - - def create - if registration_params[:password] != registration_params[:password_confirmation] - redirect_to new_registration_path, alert: "Password and confirmation do not match" - return - end - user = User.new(email_address: registration_params[:email_address], password: registration_params[:password]) - - if user.save - start_new_session_for user - - redirect_to after_authentication_url, notice: "User registered successfully" - else - flash[:alert] = user.errors.full_messages.join("\n") - render :new - end - end - - private - - def registration_params - params.require(:registration).permit(:email_address, :password, :password_confirmation) - end -end diff --git a/test/dummy/app/views/registrations/new.html.erb b/test/dummy/app/views/registrations/new.html.erb deleted file mode 100644 index 9ebefc3f..00000000 --- a/test/dummy/app/views/registrations/new.html.erb +++ /dev/null @@ -1,30 +0,0 @@ -

    Sign up

    -<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %> -<%= tag.div(flash[:notice], style: "color:green") if flash[:notice] %> - -<%= form_with( - scope: :registration, - url: registration_path, - ) do |form| %> - -
    -

    <%= form.label :email_address, 'Email' %>

    -

    <%= form.email_field :email_address, required: true %>

    -
    - -
    -

    <%= form.label :password, 'Password' %>

    -

    <%= form.password_field :password, required: true %>

    -
    - -
    -

    <%= form.label :password_confirmation, 'Confirm Password' %>

    -

    <%= form.password_field :password_confirmation, required: true %>

    -
    - -
    - <%= form.submit "Sign up" %> -
    -<% end %> - -<%= link_to "Sign in", new_session_path %> diff --git a/test/dummy/app/views/sessions/new.html.erb b/test/dummy/app/views/sessions/new.html.erb index 00802d68..f638846d 100644 --- a/test/dummy/app/views/sessions/new.html.erb +++ b/test/dummy/app/views/sessions/new.html.erb @@ -10,10 +10,6 @@ <%= link_to "Forgot password?", new_password_path %> -
    - <%= link_to "Sign up", new_registration_path %> -
    - <%= form_with( scope: :session, url: webauthn_session_path, diff --git a/test/dummy/config/routes.rb b/test/dummy/config/routes.rb index d9a7e325..2d375718 100644 --- a/test/dummy/config/routes.rb +++ b/test/dummy/config/routes.rb @@ -1,9 +1,6 @@ Rails.application.routes.draw do resource :session resources :passwords, param: :token - resource :registration, only: [ :new, :create ] do - post :create_options, on: :collection - end resource :webauthn_session, only: [ :new, :create, :destroy ] do post :get_options, on: :collection diff --git a/test/dummy/db/seeds.rb b/test/dummy/db/seeds.rb new file mode 100644 index 00000000..626acba6 --- /dev/null +++ b/test/dummy/db/seeds.rb @@ -0,0 +1,6 @@ +require 'bcrypt' + +User.create!( + email_address: "test@example.com", + password: "password123" +) diff --git a/test/dummy/test/controllers/registrations_controller_test.rb b/test/dummy/test/controllers/registrations_controller_test.rb deleted file mode 100644 index b73e942e..00000000 --- a/test/dummy/test/controllers/registrations_controller_test.rb +++ /dev/null @@ -1,92 +0,0 @@ -require "test_helper" -require "webauthn/fake_client" - -class RegistrationsControllerTest < ActionDispatch::IntegrationTest - test "should initiate registration successfully" do - post create_options_registration_url, params: { registration: { username: "alice" } } - - assert_response :success - end - - test "should return error if registrating taken username" do - User.create!(username: "alice") - - post create_options_registration_url, params: { registration: { username: "alice" } } - - assert_response :unprocessable_entity - assert_equal [ "Username has already been taken" ], JSON.parse(response.body)["errors"] - end - - test "should return error if registrating blank username" do - post create_options_registration_url params: { registration: { username: "" } } - - assert_response :unprocessable_entity - assert_equal [ "Username can't be blank" ], JSON.parse(response.body)["errors"] - end - - test "should return error if registering existing credential" do - raw_challenge = SecureRandom.random_bytes(32) - challenge = WebAuthn.configuration.encoder.encode(raw_challenge) - - WebAuthn::PublicKeyCredential::CreationOptions.stub_any_instance(:raw_challenge, raw_challenge) do - post create_options_registration_url, params: { registration: { username: "alice" } } - - assert_response :success - end - - public_key_credential = - WebAuthn::FakeClient - .new("http://localhost:3030") - .create(challenge:, user_verified: true) - - webauthn_credential = WebAuthn::Credential.from_create(public_key_credential) - - User.create!( - username: "bob", - webauthn_credentials: [ - WebauthnCredential.new( - external_id: webauthn_credential.id, - nickname: "Bob's USB Key", - public_key: webauthn_credential.public_key, - sign_count: webauthn_credential.sign_count - ) - ] - ) - - assert_no_difference -> { User.count } do - post( - registration_url, - params: { registration: { nickname: "USB Key", public_key_credential: public_key_credential.to_json } } - ) - end - - assert_redirected_to new_registration_path - end - - test "should register successfully" do - raw_challenge = SecureRandom.random_bytes(32) - challenge = WebAuthn.configuration.encoder.encode(raw_challenge) - - WebAuthn::PublicKeyCredential::CreationOptions.stub_any_instance(:raw_challenge, raw_challenge) do - post create_options_registration_url, params: { registration: { username: "alice" } } - - assert_response :success - end - - public_key_credential = - WebAuthn::FakeClient - .new("http://localhost:3030") - .create(challenge:, user_verified: true) - - assert_difference "User.count", +1 do - assert_difference "WebauthnCredential.count", +1 do - post( - registration_url, - params: { registration: { nickname: "USB Key", public_key_credential: public_key_credential.to_json } }, - ) - end - end - - assert_redirected_to "/" - end -end diff --git a/test/dummy/test/system/registration_test.rb b/test/dummy/test/system/registration_test.rb deleted file mode 100644 index cd06b533..00000000 --- a/test/dummy/test/system/registration_test.rb +++ /dev/null @@ -1,28 +0,0 @@ -require "application_system_test_case" -require_relative "../test_helpers/virtual_authenticator_test_helper" - -class RegistrationTest < ApplicationSystemTestCase - include VirtualAuthenticatorTestHelper - - def setup - @authenticator = add_virtual_authenticator - end - - def teardown - @authenticator.remove! - end - - test "register user" do - visit new_registration_path - - fill_in "registration_username", with: "User1" - fill_in "Security Key nickname", with: "USB key" - - click_on "Sign up" - # wait for async response - assert_selector "h3", text: "Your Security Keys" - - assert_current_path "/" - assert_selector "span", text: "USB key" - end -end diff --git a/test/generators/webauthn_authentication_generator_test.rb b/test/generators/webauthn_authentication_generator_test.rb index bb35c79c..8426fc5c 100644 --- a/test/generators/webauthn_authentication_generator_test.rb +++ b/test/generators/webauthn_authentication_generator_test.rb @@ -25,21 +25,17 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase run_generator_instance end - assert_file "app/controllers/registrations_controller.rb" assert_file "app/controllers/webauthn_sessions_controller.rb" assert_file "app/controllers/webauthn_credentials_controller.rb" assert_file "app/views/webauthn_credentials/new.html.erb" - assert_file "app/views/registrations/new.html.erb" assert_file "app/javascript/controllers/webauthn_credentials_controller.js" assert_file "config/initializers/webauthn.rb", /WebAuthn.configure/ - assert_file "test/controllers/registrations_controller_test.rb" assert_file "test/controllers/webauthn_sessions_controller_test.rb" assert_file "test/system/add_credential_test.rb" - assert_file "test/system/registration_test.rb" assert_file "test/system/sign_in_test.rb" assert_file "test/test_helpers/virtual_authenticator_test_helper.rb" @@ -62,12 +58,10 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase run_generator_instance end - assert_file "app/controllers/registrations_controller.rb" assert_file "app/controllers/webauthn_sessions_controller.rb" assert_file "app/controllers/webauthn_credentials_controller.rb" assert_no_file "app/views/webauthn_credentials/new.html.erb" - assert_no_file "app/views/registrations/new.html.erb" assert_file "app/javascript/controllers/webauthn_credentials_controller.js" From cce6f8b2843360ae9252bb703913916ef06dc81f Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Fri, 5 Sep 2025 14:36:32 -0300 Subject: [PATCH 20/42] fix: change Add Passkey button name --- test/dummy/app/views/sessions/new.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/dummy/app/views/sessions/new.html.erb b/test/dummy/app/views/sessions/new.html.erb index f638846d..74007fca 100644 --- a/test/dummy/app/views/sessions/new.html.erb +++ b/test/dummy/app/views/sessions/new.html.erb @@ -23,6 +23,6 @@ <%= f.hidden_field :public_key_credential, data: { "webauthn-credentials-target": "credentialHiddenInput" } %>
    - <%= f.submit "Ingresar con Passkey"%> + <%= f.submit "Sign in with Passkey"%>
    <% end %> From 3a483a3728924501dfc2685ea14c6b3de1679a75 Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Fri, 5 Sep 2025 15:25:29 -0300 Subject: [PATCH 21/42] fix: view templates --- .../templates/app/views/webauthn_credentials/new.html.erb.tt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/generators/erb/webauthn_authentication/templates/app/views/webauthn_credentials/new.html.erb.tt b/lib/generators/erb/webauthn_authentication/templates/app/views/webauthn_credentials/new.html.erb.tt index bbd7dff1..101e4f7a 100644 --- a/lib/generators/erb/webauthn_authentication/templates/app/views/webauthn_credentials/new.html.erb.tt +++ b/lib/generators/erb/webauthn_authentication/templates/app/views/webauthn_credentials/new.html.erb.tt @@ -1,5 +1,5 @@ -<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %> -<%= tag.div(flash[:notice], style: "color:green") if flash[:notice] %> +<%%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %> +<%%= tag.div(flash[:notice], style: "color:green") if flash[:notice] %> <%%= form_with( scope: :credential, From ed7a3d2f3b455d628ef4e2e197e552b46e05f872 Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Mon, 8 Sep 2025 12:14:02 -0300 Subject: [PATCH 22/42] tests: update tests and helpers with feature discoverable credentials --- .../webauthn_sessions_controller_test.rb | 19 +------- .../test/system/add_credential_test.rb | 47 ------------------- .../templates/test/system/sign_in_test.rb | 34 -------------- .../virtual_authenticator_test_helper.rb | 1 + .../webauthn_sessions_controller_test.rb | 19 +------- test/dummy/test/system/add_credential_test.rb | 47 ------------------- test/dummy/test/system/sign_in_test.rb | 34 -------------- .../virtual_authenticator_test_helper.rb | 1 + 8 files changed, 6 insertions(+), 196 deletions(-) delete mode 100644 lib/generators/test_unit/webauthn_authentication/templates/test/system/add_credential_test.rb delete mode 100644 lib/generators/test_unit/webauthn_authentication/templates/test/system/sign_in_test.rb delete mode 100644 test/dummy/test/system/add_credential_test.rb delete mode 100644 test/dummy/test/system/sign_in_test.rb diff --git a/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/webauthn_sessions_controller_test.rb b/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/webauthn_sessions_controller_test.rb index 393cd7ff..9056d8ec 100644 --- a/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/webauthn_sessions_controller_test.rb +++ b/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/webauthn_sessions_controller_test.rb @@ -1,25 +1,10 @@ require "test_helper" class WebauthnSessionsControllerTest < ActionDispatch::IntegrationTest - test "should initiate registration successfully" do - User.create!(username: "alice") + test "should initiate sign_in using passkeys successfully" do - post get_options_webauthn_session_url, params: { session: { username: "alice" } } + post get_options_webauthn_session_url assert_response :success end - - test "should return error if creating session with inexisting username" do - post get_options_webauthn_session_url, params: { session: { username: "alice" } } - - assert_response :unprocessable_entity - assert_equal [ "Username doesn't exist" ], JSON.parse(response.body)["errors"] - end - - test "should return error if creating session with blank username" do - post get_options_webauthn_session_url, params: { session: { username: "" } } - - assert_response :unprocessable_entity - assert_equal [ "Username doesn't exist" ], JSON.parse(response.body)["errors"] - end end diff --git a/lib/generators/test_unit/webauthn_authentication/templates/test/system/add_credential_test.rb b/lib/generators/test_unit/webauthn_authentication/templates/test/system/add_credential_test.rb deleted file mode 100644 index 54a741a1..00000000 --- a/lib/generators/test_unit/webauthn_authentication/templates/test/system/add_credential_test.rb +++ /dev/null @@ -1,47 +0,0 @@ -require "application_system_test_case" -require_relative "../test_helpers/virtual_authenticator_test_helper" - -class AddCredentialTest < ApplicationSystemTestCase - include VirtualAuthenticatorTestHelper - - def setup - sign_up(username: "User1") - - @authenticator = add_virtual_authenticator - end - - def teardown - @authenticator.remove! - end - - test "add credentials" do - visit root_path - - click_on "Add credential" - - fill_in("Security Key nickname", with: "Touch ID") - - click_on "Add Security Key" - assert_selector "span", text: "Touch ID" - - assert_current_path "/" - assert_selector "span", text: "USB key" - end - - private - - def sign_up(username:, credential_nickname: "USB key") - authenticator = add_virtual_authenticator - - visit new_registration_path - - fill_in "registration_username", with: username - fill_in "Security Key nickname", with: credential_nickname - - click_on "Sign up" - # wait for async response - assert_selector "h3", text: "Your Security Keys" - - authenticator.remove! - end -end diff --git a/lib/generators/test_unit/webauthn_authentication/templates/test/system/sign_in_test.rb b/lib/generators/test_unit/webauthn_authentication/templates/test/system/sign_in_test.rb deleted file mode 100644 index 2445cb16..00000000 --- a/lib/generators/test_unit/webauthn_authentication/templates/test/system/sign_in_test.rb +++ /dev/null @@ -1,34 +0,0 @@ -require "application_system_test_case" -require_relative "../test_helpers/virtual_authenticator_test_helper" - -class SignInTest < ApplicationSystemTestCase - include VirtualAuthenticatorTestHelper - - def setup - @authenticator = add_virtual_authenticator - end - - def teardown - @authenticator.remove! - end - - test "register and then sign in" do - visit new_registration_path - - fill_in "registration_username", with: "User1" - fill_in "Security Key nickname", with: "USB key" - - click_on "Sign up" - # wait for async response - assert_selector "h3", text: "Your Security Keys" - - click_on "Sign out" - assert_selector("input[type=submit][value='Sign in']") - - fill_in "Username", with: "User1" - - click_button "Sign in" - assert_selector "h3", text: "Your Security Keys" - assert_current_path "/" - end -end diff --git a/lib/generators/test_unit/webauthn_authentication/templates/test/test_helpers/virtual_authenticator_test_helper.rb b/lib/generators/test_unit/webauthn_authentication/templates/test/test_helpers/virtual_authenticator_test_helper.rb index 429944d9..e031b30a 100644 --- a/lib/generators/test_unit/webauthn_authentication/templates/test/test_helpers/virtual_authenticator_test_helper.rb +++ b/lib/generators/test_unit/webauthn_authentication/templates/test/test_helpers/virtual_authenticator_test_helper.rb @@ -3,6 +3,7 @@ def add_virtual_authenticator options = ::Selenium::WebDriver::VirtualAuthenticatorOptions.new options.user_verification = true options.user_verified = true + options.resident_key = true page.driver.browser.add_virtual_authenticator(options) end end diff --git a/test/dummy/test/controllers/webauthn_sessions_controller_test.rb b/test/dummy/test/controllers/webauthn_sessions_controller_test.rb index 393cd7ff..9056d8ec 100644 --- a/test/dummy/test/controllers/webauthn_sessions_controller_test.rb +++ b/test/dummy/test/controllers/webauthn_sessions_controller_test.rb @@ -1,25 +1,10 @@ require "test_helper" class WebauthnSessionsControllerTest < ActionDispatch::IntegrationTest - test "should initiate registration successfully" do - User.create!(username: "alice") + test "should initiate sign_in using passkeys successfully" do - post get_options_webauthn_session_url, params: { session: { username: "alice" } } + post get_options_webauthn_session_url assert_response :success end - - test "should return error if creating session with inexisting username" do - post get_options_webauthn_session_url, params: { session: { username: "alice" } } - - assert_response :unprocessable_entity - assert_equal [ "Username doesn't exist" ], JSON.parse(response.body)["errors"] - end - - test "should return error if creating session with blank username" do - post get_options_webauthn_session_url, params: { session: { username: "" } } - - assert_response :unprocessable_entity - assert_equal [ "Username doesn't exist" ], JSON.parse(response.body)["errors"] - end end diff --git a/test/dummy/test/system/add_credential_test.rb b/test/dummy/test/system/add_credential_test.rb deleted file mode 100644 index 54a741a1..00000000 --- a/test/dummy/test/system/add_credential_test.rb +++ /dev/null @@ -1,47 +0,0 @@ -require "application_system_test_case" -require_relative "../test_helpers/virtual_authenticator_test_helper" - -class AddCredentialTest < ApplicationSystemTestCase - include VirtualAuthenticatorTestHelper - - def setup - sign_up(username: "User1") - - @authenticator = add_virtual_authenticator - end - - def teardown - @authenticator.remove! - end - - test "add credentials" do - visit root_path - - click_on "Add credential" - - fill_in("Security Key nickname", with: "Touch ID") - - click_on "Add Security Key" - assert_selector "span", text: "Touch ID" - - assert_current_path "/" - assert_selector "span", text: "USB key" - end - - private - - def sign_up(username:, credential_nickname: "USB key") - authenticator = add_virtual_authenticator - - visit new_registration_path - - fill_in "registration_username", with: username - fill_in "Security Key nickname", with: credential_nickname - - click_on "Sign up" - # wait for async response - assert_selector "h3", text: "Your Security Keys" - - authenticator.remove! - end -end diff --git a/test/dummy/test/system/sign_in_test.rb b/test/dummy/test/system/sign_in_test.rb deleted file mode 100644 index 2445cb16..00000000 --- a/test/dummy/test/system/sign_in_test.rb +++ /dev/null @@ -1,34 +0,0 @@ -require "application_system_test_case" -require_relative "../test_helpers/virtual_authenticator_test_helper" - -class SignInTest < ApplicationSystemTestCase - include VirtualAuthenticatorTestHelper - - def setup - @authenticator = add_virtual_authenticator - end - - def teardown - @authenticator.remove! - end - - test "register and then sign in" do - visit new_registration_path - - fill_in "registration_username", with: "User1" - fill_in "Security Key nickname", with: "USB key" - - click_on "Sign up" - # wait for async response - assert_selector "h3", text: "Your Security Keys" - - click_on "Sign out" - assert_selector("input[type=submit][value='Sign in']") - - fill_in "Username", with: "User1" - - click_button "Sign in" - assert_selector "h3", text: "Your Security Keys" - assert_current_path "/" - end -end diff --git a/test/dummy/test/test_helpers/virtual_authenticator_test_helper.rb b/test/dummy/test/test_helpers/virtual_authenticator_test_helper.rb index 429944d9..e031b30a 100644 --- a/test/dummy/test/test_helpers/virtual_authenticator_test_helper.rb +++ b/test/dummy/test/test_helpers/virtual_authenticator_test_helper.rb @@ -3,6 +3,7 @@ def add_virtual_authenticator options = ::Selenium::WebDriver::VirtualAuthenticatorOptions.new options.user_verification = true options.user_verified = true + options.resident_key = true page.driver.browser.add_virtual_authenticator(options) end end From a380b9f3b714fbdb8d53e69f1e5e731a73f85eef Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Mon, 8 Sep 2025 12:15:09 -0300 Subject: [PATCH 23/42] test: new system test --- .../manage_webauthn_credentials_test.rb | 51 +++++++++++++++++++ .../manage_webauthn_credentials_test.rb | 51 +++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 lib/generators/test_unit/webauthn_authentication/templates/test/system/manage_webauthn_credentials_test.rb create mode 100644 test/dummy/test/system/manage_webauthn_credentials_test.rb diff --git a/lib/generators/test_unit/webauthn_authentication/templates/test/system/manage_webauthn_credentials_test.rb b/lib/generators/test_unit/webauthn_authentication/templates/test/system/manage_webauthn_credentials_test.rb new file mode 100644 index 00000000..a890d706 --- /dev/null +++ b/lib/generators/test_unit/webauthn_authentication/templates/test/system/manage_webauthn_credentials_test.rb @@ -0,0 +1,51 @@ +require "application_system_test_case" +require_relative "../test_helpers/virtual_authenticator_test_helper" +require "test_helper" + +class ManageWebauthnCredentialsTest < ApplicationSystemTestCase + include VirtualAuthenticatorTestHelper + + def setup + @user = users(:one) + sign_in_as(@user) + @authenticator = add_virtual_authenticator + end + + def teardown + @authenticator.remove! + end + + test "add credentials and sign in" do + visit root_path + + click_on "Add credential" + + fill_in("Security Key nickname", with: "Touch ID") + click_on "Add Security Key" + + assert_text "Security Key registered successfully" + assert_selector "span", text: "Touch ID" + assert_current_path "/" + + click_on "Sign out" + assert_selector("input[type=submit][value='Sign in']") + + click_on "Sign in with Passkey" + + assert_selector "h3", text: "Your Security Keys" + assert_current_path "/" + end + + private + + def sign_in_as(user) + visit new_session_path + + fill_in "email_address", with: user.email_address + fill_in "password", with: "password" + + click_on "Sign in" + + assert_selector "h3", text: "Your Security Keys" + end +end diff --git a/test/dummy/test/system/manage_webauthn_credentials_test.rb b/test/dummy/test/system/manage_webauthn_credentials_test.rb new file mode 100644 index 00000000..a890d706 --- /dev/null +++ b/test/dummy/test/system/manage_webauthn_credentials_test.rb @@ -0,0 +1,51 @@ +require "application_system_test_case" +require_relative "../test_helpers/virtual_authenticator_test_helper" +require "test_helper" + +class ManageWebauthnCredentialsTest < ApplicationSystemTestCase + include VirtualAuthenticatorTestHelper + + def setup + @user = users(:one) + sign_in_as(@user) + @authenticator = add_virtual_authenticator + end + + def teardown + @authenticator.remove! + end + + test "add credentials and sign in" do + visit root_path + + click_on "Add credential" + + fill_in("Security Key nickname", with: "Touch ID") + click_on "Add Security Key" + + assert_text "Security Key registered successfully" + assert_selector "span", text: "Touch ID" + assert_current_path "/" + + click_on "Sign out" + assert_selector("input[type=submit][value='Sign in']") + + click_on "Sign in with Passkey" + + assert_selector "h3", text: "Your Security Keys" + assert_current_path "/" + end + + private + + def sign_in_as(user) + visit new_session_path + + fill_in "email_address", with: user.email_address + fill_in "password", with: "password" + + click_on "Sign in" + + assert_selector "h3", text: "Your Security Keys" + end +end From 7a709bba58e28d82b0fb122aa24b3711f57a9688 Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Mon, 8 Sep 2025 12:16:02 -0300 Subject: [PATCH 24/42] test: create webauthn credentials controller test --- .../webauthn_credentials_controller_test.rb | 29 +++++++++++++++++++ .../webauthn_credentials_controller_test.rb | 29 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 lib/generators/test_unit/webauthn_authentication/templates/test/controllers/webauthn_credentials_controller_test.rb create mode 100644 test/dummy/test/controllers/webauthn_credentials_controller_test.rb diff --git a/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/webauthn_credentials_controller_test.rb b/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/webauthn_credentials_controller_test.rb new file mode 100644 index 00000000..733a686d --- /dev/null +++ b/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/webauthn_credentials_controller_test.rb @@ -0,0 +1,29 @@ +require "test_helper" + +class WebauthnCredentialsControllerTest < ActionDispatch::IntegrationTest + test "initiates Passkey creation when user is authenticated" do + user = User.create!(email_address: "alice@example.com", password: "password") + sign_in_as user + post create_options_webauthn_credentials_url + + assert_response :success + end + + test "requires authentication to initiate Passkey creation" do + post create_options_webauthn_credentials_url + + assert_response :redirect + assert_redirected_to new_session_url + end + + private + + def sign_in_as(user) + Current.session = user.sessions.create! + + ActionDispatch::TestRequest.create.cookie_jar.tap do |cookie_jar| + cookie_jar.signed[:session_id] = Current.session.id + cookies[:session_id] = cookie_jar[:session_id] + end + end +end diff --git a/test/dummy/test/controllers/webauthn_credentials_controller_test.rb b/test/dummy/test/controllers/webauthn_credentials_controller_test.rb new file mode 100644 index 00000000..733a686d --- /dev/null +++ b/test/dummy/test/controllers/webauthn_credentials_controller_test.rb @@ -0,0 +1,29 @@ +require "test_helper" + +class WebauthnCredentialsControllerTest < ActionDispatch::IntegrationTest + test "initiates Passkey creation when user is authenticated" do + user = User.create!(email_address: "alice@example.com", password: "password") + sign_in_as user + post create_options_webauthn_credentials_url + + assert_response :success + end + + test "requires authentication to initiate Passkey creation" do + post create_options_webauthn_credentials_url + + assert_response :redirect + assert_redirected_to new_session_url + end + + private + + def sign_in_as(user) + Current.session = user.sessions.create! + + ActionDispatch::TestRequest.create.cookie_jar.tap do |cookie_jar| + cookie_jar.signed[:session_id] = Current.session.id + cookies[:session_id] = cookie_jar[:session_id] + end + end +end From 8f03a41d20cde67dd622ff0661128f746c08a566 Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Mon, 8 Sep 2025 12:16:29 -0300 Subject: [PATCH 25/42] feat: update generator with changed templates --- .../webauthn_authentication_generator.rb | 4 ++-- .../webauthn_authentication_generator_test.rb | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/generators/test_unit/webauthn_authentication/webauthn_authentication_generator.rb b/lib/generators/test_unit/webauthn_authentication/webauthn_authentication_generator.rb index f8875f51..a5bfb425 100644 --- a/lib/generators/test_unit/webauthn_authentication/webauthn_authentication_generator.rb +++ b/lib/generators/test_unit/webauthn_authentication/webauthn_authentication_generator.rb @@ -7,12 +7,12 @@ class WebauthnAuthenticationGenerator < Rails::Generators::Base source_root File.expand_path("../templates", __FILE__) def create_controller_test_files + template "test/controllers/webauthn_credentials_controller_test.rb" template "test/controllers/webauthn_sessions_controller_test.rb" end def create_system_test_files - template "test/system/add_credential_test.rb" - template "test/system/sign_in_test.rb" + template "test/system/manage_webauthn_credentials_test.rb" end def create_test_helper_files diff --git a/test/generators/webauthn_authentication_generator_test.rb b/test/generators/webauthn_authentication_generator_test.rb index 8426fc5c..b0228dd8 100644 --- a/test/generators/webauthn_authentication_generator_test.rb +++ b/test/generators/webauthn_authentication_generator_test.rb @@ -16,6 +16,7 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase add_application_controller add_test_helper add_rails_auth_user_model + add_session_view end test "generates all expected files and successfully runs the Rails authentication generator" do @@ -35,8 +36,8 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase assert_file "config/initializers/webauthn.rb", /WebAuthn.configure/ assert_file "test/controllers/webauthn_sessions_controller_test.rb" - assert_file "test/system/add_credential_test.rb" - assert_file "test/system/sign_in_test.rb" + assert_file "test/controllers/webauthn_credentials_controller_test.rb" + assert_file "test/system/manage_webauthn_credentials_test.rb" assert_file "test/test_helpers/virtual_authenticator_test_helper.rb" assert_file "app/models/user.rb", /has_many :webauthn_credentials/ @@ -52,7 +53,7 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase end test "assert all files except for views are created with api flag" do - generator([ destination_root ], [ "--api" ]) + generator([ destination_root ], [ "--api", "--test-framework=test_unit" ]) Rails::Generators::AuthenticationGenerator.stub_any_instance(:invoke_all, nil) do run_generator_instance @@ -67,6 +68,11 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase assert_file "config/initializers/webauthn.rb", /WebAuthn.configure/ + assert_file "test/controllers/webauthn_sessions_controller_test.rb" + assert_file "test/controllers/webauthn_credentials_controller_test.rb" + assert_file "test/system/manage_webauthn_credentials_test.rb" + assert_file "test/test_helpers/virtual_authenticator_test_helper.rb" + assert_file "app/models/user.rb", /has_many :webauthn_credentials/ assert_includes @rails_commands, "generate migration AddWebauthnToUsers webauthn_id:string" @@ -127,6 +133,12 @@ class TestCase RUBY end + def add_session_view + FileUtils.mkdir_p("#{destination_root}/app/views/sessions") + File.write("#{destination_root}/app/views/sessions/new.html.erb", <<~ERB) + ERB + end + def run_generator_instance @rails_commands = [] @rails_command_stub ||= ->(command, *_) { @rails_commands << command } From 960ebeb6ee4a2532635099468929db08a2fad7d4 Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Mon, 8 Sep 2025 12:19:09 -0300 Subject: [PATCH 26/42] ci: make rubocop happy --- .../test/controllers/webauthn_sessions_controller_test.rb | 1 - .../app/controllers/webauthn_credentials_controller.rb | 4 ++-- test/dummy/app/controllers/webauthn_credentials_controller.rb | 4 ++-- test/dummy/db/seeds.rb | 2 +- .../test/controllers/webauthn_sessions_controller_test.rb | 1 - 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/webauthn_sessions_controller_test.rb b/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/webauthn_sessions_controller_test.rb index 9056d8ec..f7448aff 100644 --- a/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/webauthn_sessions_controller_test.rb +++ b/lib/generators/test_unit/webauthn_authentication/templates/test/controllers/webauthn_sessions_controller_test.rb @@ -2,7 +2,6 @@ class WebauthnSessionsControllerTest < ActionDispatch::IntegrationTest test "should initiate sign_in using passkeys successfully" do - post get_options_webauthn_session_url assert_response :success diff --git a/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_credentials_controller.rb b/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_credentials_controller.rb index 5a0754de..5e75964f 100644 --- a/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_credentials_controller.rb +++ b/lib/generators/webauthn_authentication/templates/app/controllers/webauthn_credentials_controller.rb @@ -7,8 +7,8 @@ def create_options }, exclude: Current.user.webauthn_credentials.pluck(:external_id), authenticator_selection: { - resident_key: 'required', - user_verification: 'required' + resident_key: "required", + user_verification: "required" } ) diff --git a/test/dummy/app/controllers/webauthn_credentials_controller.rb b/test/dummy/app/controllers/webauthn_credentials_controller.rb index 5a0754de..5e75964f 100644 --- a/test/dummy/app/controllers/webauthn_credentials_controller.rb +++ b/test/dummy/app/controllers/webauthn_credentials_controller.rb @@ -7,8 +7,8 @@ def create_options }, exclude: Current.user.webauthn_credentials.pluck(:external_id), authenticator_selection: { - resident_key: 'required', - user_verification: 'required' + resident_key: "required", + user_verification: "required" } ) diff --git a/test/dummy/db/seeds.rb b/test/dummy/db/seeds.rb index 626acba6..b25d8d6f 100644 --- a/test/dummy/db/seeds.rb +++ b/test/dummy/db/seeds.rb @@ -1,4 +1,4 @@ -require 'bcrypt' +require "bcrypt" User.create!( email_address: "test@example.com", diff --git a/test/dummy/test/controllers/webauthn_sessions_controller_test.rb b/test/dummy/test/controllers/webauthn_sessions_controller_test.rb index 9056d8ec..f7448aff 100644 --- a/test/dummy/test/controllers/webauthn_sessions_controller_test.rb +++ b/test/dummy/test/controllers/webauthn_sessions_controller_test.rb @@ -2,7 +2,6 @@ class WebauthnSessionsControllerTest < ActionDispatch::IntegrationTest test "should initiate sign_in using passkeys successfully" do - post get_options_webauthn_session_url assert_response :success From e24bdc4760879b96764d66ba799e2166f32ee21a Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Mon, 8 Sep 2025 14:27:22 -0300 Subject: [PATCH 27/42] refactor: remove redundant require --- .../templates/test/system/manage_webauthn_credentials_test.rb | 1 - test/dummy/test/system/manage_webauthn_credentials_test.rb | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/generators/test_unit/webauthn_authentication/templates/test/system/manage_webauthn_credentials_test.rb b/lib/generators/test_unit/webauthn_authentication/templates/test/system/manage_webauthn_credentials_test.rb index a890d706..45c40e54 100644 --- a/lib/generators/test_unit/webauthn_authentication/templates/test/system/manage_webauthn_credentials_test.rb +++ b/lib/generators/test_unit/webauthn_authentication/templates/test/system/manage_webauthn_credentials_test.rb @@ -1,6 +1,5 @@ require "application_system_test_case" require_relative "../test_helpers/virtual_authenticator_test_helper" -require "test_helper" class ManageWebauthnCredentialsTest < ApplicationSystemTestCase include VirtualAuthenticatorTestHelper diff --git a/test/dummy/test/system/manage_webauthn_credentials_test.rb b/test/dummy/test/system/manage_webauthn_credentials_test.rb index a890d706..45c40e54 100644 --- a/test/dummy/test/system/manage_webauthn_credentials_test.rb +++ b/test/dummy/test/system/manage_webauthn_credentials_test.rb @@ -1,6 +1,5 @@ require "application_system_test_case" require_relative "../test_helpers/virtual_authenticator_test_helper" -require "test_helper" class ManageWebauthnCredentialsTest < ApplicationSystemTestCase include VirtualAuthenticatorTestHelper From 604cde5ce1ad40702d3ef65df11412d382139d6b Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Mon, 8 Sep 2025 16:20:16 -0300 Subject: [PATCH 28/42] tests: fix user variable --- .../templates/test/system/manage_webauthn_credentials_test.rb | 4 ++-- test/dummy/test/system/manage_webauthn_credentials_test.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/generators/test_unit/webauthn_authentication/templates/test/system/manage_webauthn_credentials_test.rb b/lib/generators/test_unit/webauthn_authentication/templates/test/system/manage_webauthn_credentials_test.rb index 45c40e54..2855766e 100644 --- a/lib/generators/test_unit/webauthn_authentication/templates/test/system/manage_webauthn_credentials_test.rb +++ b/lib/generators/test_unit/webauthn_authentication/templates/test/system/manage_webauthn_credentials_test.rb @@ -5,8 +5,8 @@ class ManageWebauthnCredentialsTest < ApplicationSystemTestCase include VirtualAuthenticatorTestHelper def setup - @user = users(:one) - sign_in_as(@user) + user = users(:one) + sign_in_as(user) @authenticator = add_virtual_authenticator end diff --git a/test/dummy/test/system/manage_webauthn_credentials_test.rb b/test/dummy/test/system/manage_webauthn_credentials_test.rb index 45c40e54..2855766e 100644 --- a/test/dummy/test/system/manage_webauthn_credentials_test.rb +++ b/test/dummy/test/system/manage_webauthn_credentials_test.rb @@ -5,8 +5,8 @@ class ManageWebauthnCredentialsTest < ApplicationSystemTestCase include VirtualAuthenticatorTestHelper def setup - @user = users(:one) - sign_in_as(@user) + user = users(:one) + sign_in_as(user) @authenticator = add_virtual_authenticator end From 6f1549363189c5bc96d4c1ff01c59d19713127d1 Mon Sep 17 00:00:00 2001 From: rafaella-martino Date: Mon, 8 Sep 2025 16:24:44 -0300 Subject: [PATCH 29/42] tests: do not add `application controller` in generator test as it is no longer needed --- .../webauthn_authentication_generator_test.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/generators/webauthn_authentication_generator_test.rb b/test/generators/webauthn_authentication_generator_test.rb index b0228dd8..efdc216b 100644 --- a/test/generators/webauthn_authentication_generator_test.rb +++ b/test/generators/webauthn_authentication_generator_test.rb @@ -13,7 +13,6 @@ class WebauthnAuthenticationGeneratorTest < Rails::Generators::TestCase add_config_folder add_importmap add_routes - add_application_controller add_test_helper add_rails_auth_user_model add_session_view @@ -113,15 +112,6 @@ class User < ApplicationRecord CONTENT end - def add_application_controller - app_folder = FileUtils.mkdir_p(File.join(destination_root, "app")) - FileUtils.mkdir_p(File.join(app_folder, "controllers")) - File.write(File.join(destination_root, "app", "controllers", "application_controller.rb"), <<~CONTENT) - class ApplicationController < ActionController::Base - end - CONTENT - end - def add_test_helper FileUtils.mkdir_p("#{destination_root}/test") File.write("#{destination_root}/test/test_helper.rb", <<~RUBY) From 75b16d676a4d8846ff9d73d2785c88e20d8b799a Mon Sep 17 00:00:00 2001 From: Joaquin Tomas Date: Tue, 9 Sep 2025 14:46:30 -0300 Subject: [PATCH 30/42] wip --- test/dummy/app/controllers/webauthn_sessions_controller.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/dummy/app/controllers/webauthn_sessions_controller.rb b/test/dummy/app/controllers/webauthn_sessions_controller.rb index d187a2f1..290d66ed 100644 --- a/test/dummy/app/controllers/webauthn_sessions_controller.rb +++ b/test/dummy/app/controllers/webauthn_sessions_controller.rb @@ -14,6 +14,7 @@ def get_options def create webauthn_credential = WebAuthn::Credential.from_get(JSON.parse(session_params[:public_key_credential])) + puts "DEBUG #{webauthn_credential}" stored_credential = WebauthnCredential.find_by(external_id: webauthn_credential.id) unless stored_credential render json: { errors: [ "Credential not recognized" ] }, status: :unprocessable_content @@ -21,6 +22,7 @@ def create end begin + puts "DEBUG #{stored_credential}" webauthn_credential.verify( session[:current_authentication][:challenge] || session[:current_authentication]["challenge"], public_key: stored_credential.public_key, @@ -33,6 +35,7 @@ def create redirect_to after_authentication_url rescue WebAuthn::Error => e + puts "DEBUG #{e.message}" render json: "Verification failed: #{e.message}", status: :unprocessable_entity ensure session.delete(:current_authentication) From c5a99f72c923c72d48bdcf82b7c5c9782b51034d Mon Sep 17 00:00:00 2001 From: Joaquin Tomas Date: Tue, 9 Sep 2025 14:49:27 -0300 Subject: [PATCH 31/42] wip --- test/dummy/test/system/manage_webauthn_credentials_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/dummy/test/system/manage_webauthn_credentials_test.rb b/test/dummy/test/system/manage_webauthn_credentials_test.rb index 2855766e..be113b81 100644 --- a/test/dummy/test/system/manage_webauthn_credentials_test.rb +++ b/test/dummy/test/system/manage_webauthn_credentials_test.rb @@ -16,6 +16,7 @@ def teardown test "add credentials and sign in" do visit root_path + puts "DEBUG: Current path is #{current_path}" click_on "Add credential" From 8a413c5cad24dc95b8a3b50711a38fec4c49b554 Mon Sep 17 00:00:00 2001 From: Joaquin Tomas Date: Tue, 9 Sep 2025 14:54:19 -0300 Subject: [PATCH 32/42] wip --- test/dummy/app/controllers/sessions_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/dummy/app/controllers/sessions_controller.rb b/test/dummy/app/controllers/sessions_controller.rb index 9785c923..2050f608 100644 --- a/test/dummy/app/controllers/sessions_controller.rb +++ b/test/dummy/app/controllers/sessions_controller.rb @@ -6,6 +6,7 @@ def new end def create + puts "DEBUG session" if user = User.authenticate_by(params.permit(:email_address, :password)) start_new_session_for user redirect_to after_authentication_url From 4eb622ea3b17bf67f991966a9d07ef644c667630 Mon Sep 17 00:00:00 2001 From: Joaquin Tomas Date: Tue, 9 Sep 2025 15:05:20 -0300 Subject: [PATCH 33/42] wip --- test/dummy/test/system/manage_webauthn_credentials_test.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/dummy/test/system/manage_webauthn_credentials_test.rb b/test/dummy/test/system/manage_webauthn_credentials_test.rb index be113b81..b9dd4d50 100644 --- a/test/dummy/test/system/manage_webauthn_credentials_test.rb +++ b/test/dummy/test/system/manage_webauthn_credentials_test.rb @@ -15,10 +15,7 @@ def teardown end test "add credentials and sign in" do - visit root_path - puts "DEBUG: Current path is #{current_path}" - - click_on "Add credential" + visit new_webauthn_credential_path fill_in("Security Key nickname", with: "Touch ID") click_on "Add Security Key" From b09c9ef8c6975823dee1c705ca9993201263b889 Mon Sep 17 00:00:00 2001 From: Joaquin Tomas Date: Tue, 9 Sep 2025 15:12:11 -0300 Subject: [PATCH 34/42] wip --- test/dummy/app/controllers/webauthn_credentials_controller.rb | 2 ++ test/dummy/test/system/manage_webauthn_credentials_test.rb | 1 + 2 files changed, 3 insertions(+) diff --git a/test/dummy/app/controllers/webauthn_credentials_controller.rb b/test/dummy/app/controllers/webauthn_credentials_controller.rb index 83208db4..9fff8790 100644 --- a/test/dummy/app/controllers/webauthn_credentials_controller.rb +++ b/test/dummy/app/controllers/webauthn_credentials_controller.rb @@ -19,6 +19,7 @@ def create_options def create webauthn_credential = WebAuthn::Credential.from_create(JSON.parse(create_credential_params[:public_key_credential])) + puts "DEBUG #{webauthn_credential}" begin webauthn_credential.verify( @@ -41,6 +42,7 @@ def create render :new end rescue WebAuthn::Error => e + puts "DEBUG ERROR #{e.message}" render json: "Verification failed: #{e.message}", status: :unprocessable_content ensure session.delete(:current_registration) diff --git a/test/dummy/test/system/manage_webauthn_credentials_test.rb b/test/dummy/test/system/manage_webauthn_credentials_test.rb index b9dd4d50..9b4b7efa 100644 --- a/test/dummy/test/system/manage_webauthn_credentials_test.rb +++ b/test/dummy/test/system/manage_webauthn_credentials_test.rb @@ -6,6 +6,7 @@ class ManageWebauthnCredentialsTest < ApplicationSystemTestCase def setup user = users(:one) + puts "DEBUG: Signing in as user with email #{user.email_address}" sign_in_as(user) @authenticator = add_virtual_authenticator end From 7f660bf4f32dcd68a8ab8230afae4c656668ceda Mon Sep 17 00:00:00 2001 From: Joaquin Tomas Date: Tue, 9 Sep 2025 15:47:58 -0300 Subject: [PATCH 35/42] wip --- test/dummy/test/system/manage_webauthn_credentials_test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/dummy/test/system/manage_webauthn_credentials_test.rb b/test/dummy/test/system/manage_webauthn_credentials_test.rb index 9b4b7efa..52c42f1f 100644 --- a/test/dummy/test/system/manage_webauthn_credentials_test.rb +++ b/test/dummy/test/system/manage_webauthn_credentials_test.rb @@ -23,7 +23,6 @@ def teardown assert_text "Security Key registered successfully" assert_selector "span", text: "Touch ID" - assert_current_path "/" click_on "Sign out" assert_selector("input[type=submit][value='Sign in']") From 39f91b182121c905bd29afc4426ff306433d125c Mon Sep 17 00:00:00 2001 From: Joaquin Tomas Date: Tue, 9 Sep 2025 15:52:33 -0300 Subject: [PATCH 36/42] wip --- test/dummy/test/system/manage_webauthn_credentials_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/dummy/test/system/manage_webauthn_credentials_test.rb b/test/dummy/test/system/manage_webauthn_credentials_test.rb index 52c42f1f..fbe19c63 100644 --- a/test/dummy/test/system/manage_webauthn_credentials_test.rb +++ b/test/dummy/test/system/manage_webauthn_credentials_test.rb @@ -25,6 +25,7 @@ def teardown assert_selector "span", text: "Touch ID" click_on "Sign out" + sleep 5 assert_selector("input[type=submit][value='Sign in']") click_on "Sign in with Passkey" From 20e54b24eecf084ea4b8a41656f2e0bd02d2e8e3 Mon Sep 17 00:00:00 2001 From: Santiago Rodriguez Date: Tue, 9 Sep 2025 17:16:58 -0300 Subject: [PATCH 37/42] add capybara lockstep --- Gemfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Gemfile b/Gemfile index b56c68a8..09b77955 100644 --- a/Gemfile +++ b/Gemfile @@ -31,3 +31,5 @@ gem "pry-byebug", "~> 3.11" gem "bcrypt", "~> 3.1.7" gem "webauthn" + +gem "capybara-lockstep" From a335fa6e3965b05b298ea843108591f631ea394c Mon Sep 17 00:00:00 2001 From: Santiago Rodriguez Date: Tue, 9 Sep 2025 17:19:17 -0300 Subject: [PATCH 38/42] test: include `capybara_lockstep` snippet in application layout --- test/dummy/app/views/layouts/application.html.erb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/dummy/app/views/layouts/application.html.erb b/test/dummy/app/views/layouts/application.html.erb index ad13c70c..532df64b 100644 --- a/test/dummy/app/views/layouts/application.html.erb +++ b/test/dummy/app/views/layouts/application.html.erb @@ -8,6 +8,8 @@ <%= stylesheet_link_tag :app %> <%= javascript_importmap_tags %> + + <%= capybara_lockstep if defined?(Capybara::Lockstep) %> From de39e18d7dd4bfcc49b4c67cbb25c3b88c247dda Mon Sep 17 00:00:00 2001 From: Santiago Rodriguez Date: Tue, 9 Sep 2025 17:34:20 -0300 Subject: [PATCH 39/42] test: include `capybara_lockstep` middleware --- test/dummy/config/environments/test.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/dummy/config/environments/test.rb b/test/dummy/config/environments/test.rb index c2095b11..5abaab04 100644 --- a/test/dummy/config/environments/test.rb +++ b/test/dummy/config/environments/test.rb @@ -50,4 +50,6 @@ # Raise error when a before_action's only/except options reference missing actions. config.action_controller.raise_on_missing_callback_actions = true + + config.middleware.insert_before 0, Capybara::Lockstep::Middleware end From 7d3c7355f8c4bcf3bf73e749ca1a1c6538bf5219 Mon Sep 17 00:00:00 2001 From: Santiago Rodriguez Date: Tue, 9 Sep 2025 17:34:47 -0300 Subject: [PATCH 40/42] test: configure browser to not automatically dismiss user prompts --- test/dummy/test/application_system_test_case.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/dummy/test/application_system_test_case.rb b/test/dummy/test/application_system_test_case.rb index 5c3b8101..7c4eb38d 100644 --- a/test/dummy/test/application_system_test_case.rb +++ b/test/dummy/test/application_system_test_case.rb @@ -1,7 +1,9 @@ require "test_helper" class ApplicationSystemTestCase < ActionDispatch::SystemTestCase - driven_by :selenium, using: (ENV["TEST_BROWSER"] || :headless_chrome).to_sym, screen_size: [ 1400, 1400 ] + driven_by :selenium, using: (ENV["TEST_BROWSER"] || :headless_chrome).to_sym, screen_size: [ 1400, 1400 ] do |options| + options.unhandled_prompt_behavior = "ignore" + end setup do Capybara.app_host = "http://localhost:3030" From 64c05f17505348d3e457f209c5c22fe23d46987b Mon Sep 17 00:00:00 2001 From: Santiago Rodriguez Date: Tue, 9 Sep 2025 17:55:22 -0300 Subject: [PATCH 41/42] test: turn on `capybara_lockstep` debug mode --- test/dummy/test/system/manage_webauthn_credentials_test.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/dummy/test/system/manage_webauthn_credentials_test.rb b/test/dummy/test/system/manage_webauthn_credentials_test.rb index fbe19c63..23022a16 100644 --- a/test/dummy/test/system/manage_webauthn_credentials_test.rb +++ b/test/dummy/test/system/manage_webauthn_credentials_test.rb @@ -9,6 +9,8 @@ def setup puts "DEBUG: Signing in as user with email #{user.email_address}" sign_in_as(user) @authenticator = add_virtual_authenticator + + Capybara::Lockstep.debug = true end def teardown From 61f2f970af5c9d003e298319d28502218050cfea Mon Sep 17 00:00:00 2001 From: Santiago Rodriguez Date: Tue, 9 Sep 2025 17:54:41 -0300 Subject: [PATCH 42/42] test: use more secure password to avoid Chrome leaked password detection --- .../templates/test/system/manage_webauthn_credentials_test.rb | 2 +- test/dummy/test/fixtures/users.yml | 2 +- test/dummy/test/system/manage_webauthn_credentials_test.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/generators/test_unit/webauthn_authentication/templates/test/system/manage_webauthn_credentials_test.rb b/lib/generators/test_unit/webauthn_authentication/templates/test/system/manage_webauthn_credentials_test.rb index 2855766e..de25d80e 100644 --- a/lib/generators/test_unit/webauthn_authentication/templates/test/system/manage_webauthn_credentials_test.rb +++ b/lib/generators/test_unit/webauthn_authentication/templates/test/system/manage_webauthn_credentials_test.rb @@ -41,7 +41,7 @@ def sign_in_as(user) visit new_session_path fill_in "email_address", with: user.email_address - fill_in "password", with: "password" + fill_in "password", with: "S3cr3tP@ssw0rd!" click_on "Sign in" diff --git a/test/dummy/test/fixtures/users.yml b/test/dummy/test/fixtures/users.yml index 09515632..7b449d7f 100644 --- a/test/dummy/test/fixtures/users.yml +++ b/test/dummy/test/fixtures/users.yml @@ -1,4 +1,4 @@ -<% password_digest = BCrypt::Password.create("password") %> +<% password_digest = BCrypt::Password.create("S3cr3tP@ssw0rd!") %> one: email_address: one@example.com diff --git a/test/dummy/test/system/manage_webauthn_credentials_test.rb b/test/dummy/test/system/manage_webauthn_credentials_test.rb index 23022a16..68a4a4c1 100644 --- a/test/dummy/test/system/manage_webauthn_credentials_test.rb +++ b/test/dummy/test/system/manage_webauthn_credentials_test.rb @@ -42,7 +42,7 @@ def sign_in_as(user) visit new_session_path fill_in "email_address", with: user.email_address - fill_in "password", with: "password" + fill_in "password", with: "S3cr3tP@ssw0rd!" click_on "Sign in"