From 108e750f328f04b16a29b707a1c6526ad65b421e Mon Sep 17 00:00:00 2001 From: Renzo Minelli Date: Sun, 10 Aug 2025 13:54:02 -0300 Subject: [PATCH 01/13] add dummy app --- .gitignore | 6 + .rspec | 2 + Gemfile.lock | 94 ++++++ devise-webauthn.gemspec | 4 + spec/dummy/Rakefile | 8 + .../app/controllers/application_controller.rb | 4 + spec/dummy/app/mailers/application_mailer.rb | 4 + spec/dummy/app/models/application_record.rb | 5 + spec/dummy/app/models/passkey.rb | 8 + spec/dummy/app/models/user.rb | 5 + spec/dummy/config.ru | 8 + spec/dummy/config/application.rb | 14 + spec/dummy/config/boot.rb | 7 + spec/dummy/config/database.yml | 21 ++ spec/dummy/config/environment.rb | 7 + spec/dummy/config/environments/development.rb | 77 +++++ spec/dummy/config/environments/test.rb | 69 ++++ spec/dummy/config/initializers/devise.rb | 313 ++++++++++++++++++ spec/dummy/config/initializers/webauthn.rb | 10 + spec/dummy/config/routes.rb | 7 + spec/dummy/config/storage.yml | 7 + ...0240101000000_create_users_and_passkeys.rb | 24 ++ spec/dummy/db/schema.rb | 34 ++ spec/rails_helper.rb | 8 + spec/spec_helper.rb | 3 + 25 files changed, 749 insertions(+) create mode 100644 spec/dummy/Rakefile create mode 100644 spec/dummy/app/controllers/application_controller.rb create mode 100644 spec/dummy/app/mailers/application_mailer.rb create mode 100644 spec/dummy/app/models/application_record.rb create mode 100644 spec/dummy/app/models/passkey.rb create mode 100644 spec/dummy/app/models/user.rb create mode 100644 spec/dummy/config.ru create mode 100644 spec/dummy/config/application.rb create mode 100644 spec/dummy/config/boot.rb create mode 100644 spec/dummy/config/database.yml create mode 100644 spec/dummy/config/environment.rb create mode 100644 spec/dummy/config/environments/development.rb create mode 100644 spec/dummy/config/environments/test.rb create mode 100644 spec/dummy/config/initializers/devise.rb create mode 100644 spec/dummy/config/initializers/webauthn.rb create mode 100644 spec/dummy/config/routes.rb create mode 100644 spec/dummy/config/storage.yml create mode 100644 spec/dummy/db/migrate/20240101000000_create_users_and_passkeys.rb create mode 100644 spec/dummy/db/schema.rb create mode 100644 spec/rails_helper.rb diff --git a/.gitignore b/.gitignore index b04a8c84..b7a0cf2f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,9 @@ # rspec failure tracking .rspec_status + +/spec/dummy/log/* +/spec/dummy/tmp/* +/spec/dummy/storage/* + +.byebug_history diff --git a/.rspec b/.rspec index 34c5164d..9ee42908 100644 --- a/.rspec +++ b/.rspec @@ -1,3 +1,5 @@ +--require rails_helper +--format progress --format documentation --color --require spec_helper diff --git a/Gemfile.lock b/Gemfile.lock index 35519bff..60b91819 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,6 +8,26 @@ PATH GEM remote: https://rubygems.org/ specs: + actioncable (8.0.2) + actionpack (= 8.0.2) + activesupport (= 8.0.2) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + zeitwerk (~> 2.6) + actionmailbox (8.0.2) + actionpack (= 8.0.2) + activejob (= 8.0.2) + activerecord (= 8.0.2) + activestorage (= 8.0.2) + activesupport (= 8.0.2) + mail (>= 2.8.0) + actionmailer (8.0.2) + actionpack (= 8.0.2) + actionview (= 8.0.2) + activejob (= 8.0.2) + activesupport (= 8.0.2) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) actionpack (8.0.2) actionview (= 8.0.2) activesupport (= 8.0.2) @@ -18,12 +38,34 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) + actiontext (8.0.2) + actionpack (= 8.0.2) + activerecord (= 8.0.2) + activestorage (= 8.0.2) + activesupport (= 8.0.2) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) actionview (8.0.2) activesupport (= 8.0.2) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) + activejob (8.0.2) + activesupport (= 8.0.2) + globalid (>= 0.3.6) + activemodel (8.0.2) + activesupport (= 8.0.2) + activerecord (8.0.2) + activemodel (= 8.0.2) + activesupport (= 8.0.2) + timeout (>= 0.4.0) + activestorage (8.0.2) + actionpack (= 8.0.2) + activejob (= 8.0.2) + activerecord (= 8.0.2) + activesupport (= 8.0.2) + marcel (~> 1.0) activesupport (8.0.2) base64 benchmark (>= 0.3) @@ -45,6 +87,7 @@ GEM bigdecimal (3.2.2) bindata (2.5.1) builder (3.3.0) + byebug (12.0.0) cbor (0.5.10.1) concurrent-ruby (1.3.5) connection_pool (2.5.3) @@ -63,6 +106,8 @@ GEM drb (2.2.3) erb (5.0.2) erubi (1.13.1) + globalid (1.2.1) + activesupport (>= 6.1) i18n (1.14.7) concurrent-ruby (~> 1.0) io-console (0.8.1) @@ -79,7 +124,24 @@ GEM loofah (2.24.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.0.4) + mini_mime (1.1.5) minitest (5.25.5) + net-imap (0.5.9) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.4) nokogiri (1.18.9) racc (~> 1.4) openssl (3.3.0) @@ -106,6 +168,20 @@ GEM rack (>= 1.3) rackup (2.2.1) rack (>= 3) + rails (8.0.2) + actioncable (= 8.0.2) + actionmailbox (= 8.0.2) + actionmailer (= 8.0.2) + actionpack (= 8.0.2) + actiontext (= 8.0.2) + actionview (= 8.0.2) + activejob (= 8.0.2) + activemodel (= 8.0.2) + activerecord (= 8.0.2) + activestorage (= 8.0.2) + activesupport (= 8.0.2) + bundler (>= 1.15.0) + railties (= 8.0.2) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest @@ -144,6 +220,14 @@ GEM rspec-mocks (3.13.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) + rspec-rails (6.1.5) + actionpack (>= 6.1) + activesupport (>= 6.1) + railties (>= 6.1) + rspec-core (~> 3.13) + rspec-expectations (~> 3.13) + rspec-mocks (~> 3.13) + rspec-support (~> 3.13) rspec-support (3.13.4) rubocop (1.79.1) json (~> 2.3) @@ -172,8 +256,10 @@ GEM safety_net_attestation (0.4.0) jwt (~> 2.0) securerandom (0.4.1) + sqlite3 (2.7.3-arm64-darwin) stringio (3.1.7) thor (1.4.0) + timeout (0.4.3) tpm-key_attestation (0.14.1) bindata (~> 2.4) openssl (> 2.0) @@ -195,17 +281,25 @@ GEM openssl (>= 2.2) safety_net_attestation (~> 0.4.0) tpm-key_attestation (~> 0.14.0) + websocket-driver (0.8.0) + base64 + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) zeitwerk (2.7.3) PLATFORMS ruby DEPENDENCIES + byebug devise-webauthn! + rails (~> 8.0) rspec (~> 3.13) + rspec-rails (~> 6.1) rubocop (~> 1.79) rubocop-rails (~> 2.32) rubocop-rspec (~> 3.6) + sqlite3 (~> 2.7) BUNDLED WITH 2.7.1 diff --git a/devise-webauthn.gemspec b/devise-webauthn.gemspec index b6fed429..254f24b2 100644 --- a/devise-webauthn.gemspec +++ b/devise-webauthn.gemspec @@ -24,10 +24,14 @@ Gem::Specification.new do |spec| spec.metadata["rubygems_mfa_required"] = "true" spec.required_ruby_version = ">= 3.1" + spec.add_development_dependency "byebug" + spec.add_development_dependency "rails", "~> 8.0" spec.add_development_dependency "rspec", "~> 3.13" + spec.add_development_dependency "rspec-rails", "~> 6.1" spec.add_development_dependency "rubocop", "~> 1.79" spec.add_development_dependency "rubocop-rails", "~> 2.32" spec.add_development_dependency "rubocop-rspec", "~> 3.6" + spec.add_development_dependency "sqlite3", "~> 2.7" spec.add_dependency "devise", "~> 4.9" spec.add_dependency "webauthn", "~> 3.0" diff --git a/spec/dummy/Rakefile b/spec/dummy/Rakefile new file mode 100644 index 00000000..d2a78aa2 --- /dev/null +++ b/spec/dummy/Rakefile @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/spec/dummy/app/controllers/application_controller.rb b/spec/dummy/app/controllers/application_controller.rb new file mode 100644 index 00000000..7944f9f9 --- /dev/null +++ b/spec/dummy/app/controllers/application_controller.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class ApplicationController < ActionController::Base +end diff --git a/spec/dummy/app/mailers/application_mailer.rb b/spec/dummy/app/mailers/application_mailer.rb new file mode 100644 index 00000000..26148f2d --- /dev/null +++ b/spec/dummy/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class ApplicationMailer < ActionMailer::Base +end diff --git a/spec/dummy/app/models/application_record.rb b/spec/dummy/app/models/application_record.rb new file mode 100644 index 00000000..08dc5379 --- /dev/null +++ b/spec/dummy/app/models/application_record.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/spec/dummy/app/models/passkey.rb b/spec/dummy/app/models/passkey.rb new file mode 100644 index 00000000..4a26cff0 --- /dev/null +++ b/spec/dummy/app/models/passkey.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class Passkey < ApplicationRecord + belongs_to :user + + validates :external_id, :public_key, :name, :sign_count, presence: true + validates :external_id, uniqueness: true +end diff --git a/spec/dummy/app/models/user.rb b/spec/dummy/app/models/user.rb new file mode 100644 index 00000000..7f7edb5a --- /dev/null +++ b/spec/dummy/app/models/user.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class User < ApplicationRecord + devise :database_authenticatable, :passkey_authenticatable +end diff --git a/spec/dummy/config.ru b/spec/dummy/config.ru new file mode 100644 index 00000000..2e030846 --- /dev/null +++ b/spec/dummy/config.ru @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/spec/dummy/config/application.rb b/spec/dummy/config/application.rb new file mode 100644 index 00000000..7c46ddb3 --- /dev/null +++ b/spec/dummy/config/application.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require_relative "boot" +require "rails/all" + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module Dummy + class Application < Rails::Application + config.load_defaults Rails::VERSION::STRING.to_f + end +end diff --git a/spec/dummy/config/boot.rb b/spec/dummy/config/boot.rb new file mode 100644 index 00000000..75b66eb1 --- /dev/null +++ b/spec/dummy/config/boot.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Set up gems listed in the Gemfile. +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("Gemfile", __dir__) + +require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) +$LOAD_PATH.unshift File.expand_path("lib", __dir__) diff --git a/spec/dummy/config/database.yml b/spec/dummy/config/database.yml new file mode 100644 index 00000000..0966d1a6 --- /dev/null +++ b/spec/dummy/config/database.yml @@ -0,0 +1,21 @@ +# SQLite. Versions 3.8.0 and up are supported. +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem "sqlite3" +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: storage/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: storage/test.sqlite3 diff --git a/spec/dummy/config/environment.rb b/spec/dummy/config/environment.rb new file mode 100644 index 00000000..7df99e89 --- /dev/null +++ b/spec/dummy/config/environment.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/spec/dummy/config/environments/development.rb b/spec/dummy/config/environments/development.rb new file mode 100644 index 00000000..9df33be8 --- /dev/null +++ b/spec/dummy/config/environments/development.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded any time + # it changes. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.enable_reloading = true + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing. + config.server_timing = true + + # Enable/disable caching. By default caching is disabled. + # Run rails dev:cache to toggle caching. + if Rails.root.join("tmp/caching-dev.txt").exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + + config.cache_store = :memory_store + config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{2.days.to_i}" } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Disable caching for Action Mailer templates even if Action Controller + # caching is enabled. + config.action_mailer.perform_caching = false + + config.action_mailer.default_url_options = { host: "localhost", port: 3000 } + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise exceptions for disallowed deprecations. + config.active_support.disallowed_deprecation = :raise + + # Tell Active Support which deprecation messages to disallow. + config.active_support.disallowed_deprecation_warnings = [] + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/spec/dummy/config/environments/test.rb b/spec/dummy/config/environments/test.rb new file mode 100644 index 00000000..b8b261ad --- /dev/null +++ b/spec/dummy/config/environments/test.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require "active_support/core_ext/integer/time" + +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # While tests run files are not watched, reloading is not necessary. + config.enable_reloading = false + + # Eager loading loads your entire application. When running a single test locally, + # this is usually not necessary, and can slow down your test suite. However, it's + # recommended that you enable it in continuous integration systems to ensure eager + # loading is working properly before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{1.hour.to_i}" } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + config.cache_store = :null_store + + # Render exception templates for rescuable exceptions and raise for other exceptions. + config.action_dispatch.show_exceptions = :rescuable + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory. + config.active_storage.service = :test + + # Disable caching for Action Mailer templates even if Action Controller + # caching is enabled. + config.action_mailer.perform_caching = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Unlike controllers, the mailer instance doesn't have any context about the + # incoming request so you'll need to provide the :host parameter yourself. + config.action_mailer.default_url_options = { host: "www.example.com" } + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raise exceptions for disallowed deprecations. + config.active_support.disallowed_deprecation = :raise + + # Tell Active Support which deprecation messages to disallow. + config.active_support.disallowed_deprecation_warnings = [] + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Raise error when a before_action's only/except options reference missing actions. + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/spec/dummy/config/initializers/devise.rb b/spec/dummy/config/initializers/devise.rb new file mode 100644 index 00000000..024a9d34 --- /dev/null +++ b/spec/dummy/config/initializers/devise.rb @@ -0,0 +1,313 @@ +# frozen_string_literal: true + +# Assuming you have not yet modified this file, each configuration option below +# is set to its default value. Note that some are commented out while others +# are not: uncommented lines are intended to protect your configuration from +# breaking changes in upgrades (i.e., in the event that future versions of +# Devise change the default values for those options). +# +# Use this hook to configure devise mailer, warden hooks and so forth. +# Many of these configuration options can be set straight in your model. +Devise.setup do |config| + # The secret key used by Devise. Devise uses this key to generate + # random tokens. Changing this key will render invalid all existing + # confirmation, reset password and unlock tokens in the database. + # Devise will use the `secret_key_base` as its `secret_key` + # by default. You can change it below and use your own secret key. + # config.secret_key = 'c07cff704a971b17822a9d6ddfea9ab12d726561ef35914c1ba25423b1c65e9 + # 469604fcab85b30f75cf4ddd3784ab71b322ce16d67f52f1fecb21cf71696c9c1' + + # ==> Controller configuration + # Configure the parent class to the devise controllers. + # config.parent_controller = 'DeviseController' + + # ==> Mailer Configuration + # Configure the e-mail address which will be shown in Devise::Mailer, + # note that it will be overwritten if you use your own mailer class + # with default "from" parameter. + # config.mailer_sender = Rails.application.credentials.sendgrid[:from_email_address] + + # Configure the class responsible to send e-mails. + # config.mailer = 'Devise::Mailer' + + # Configure the parent class responsible to send e-mails. + config.parent_mailer = "ApplicationMailer" + + # ==> ORM configuration + # Load and configure the ORM. Supports :active_record (default) and + # :mongoid (bson_ext recommended) by default. Other ORMs may be + # available as additional gems. + require "devise/orm/active_record" + + # ==> Configuration for any authentication mechanism + # Configure which keys are used when authenticating a user. The default is + # just :email. You can configure it to use [:username, :subdomain], so for + # authenticating a user, both parameters are required. Remember that those + # parameters are used only when authenticating and not when retrieving from + # session. If you need permissions, you should implement that in a before filter. + # You can also supply a hash where the value is a boolean determining whether + # or not authentication should be aborted when the value is not present. + # config.authentication_keys = [:email] + + # Configure parameters from the request object used for authentication. Each entry + # given should be a request method and it will automatically be passed to the + # find_for_authentication method and considered in your model lookup. For instance, + # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. + # The same considerations mentioned for authentication_keys also apply to request_keys. + # config.request_keys = [] + + # Configure which authentication keys should be case-insensitive. + # These keys will be downcased upon creating or modifying a user and when used + # to authenticate or find a user. Default is :email. + config.case_insensitive_keys = [:email] + + # Configure which authentication keys should have whitespace stripped. + # These keys will have whitespace before and after removed upon creating or + # modifying a user and when used to authenticate or find a user. Default is :email. + config.strip_whitespace_keys = [:email] + + # Tell if authentication through request.params is enabled. True by default. + # It can be set to an array that will enable params authentication only for the + # given strategies, for example, `config.params_authenticatable = [:database]` will + # enable it only for database (email + password) authentication. + # config.params_authenticatable = true + + # Tell if authentication through HTTP Auth is enabled. False by default. + # It can be set to an array that will enable http authentication only for the + # given strategies, for example, `config.http_authenticatable = [:database]` will + # enable it only for database authentication. + # For API-only applications to support authentication "out-of-the-box", you will likely want to + # enable this with :database unless you are using a custom strategy. + # The supported strategies are: + # :database = Support basic authentication with authentication key + password + # config.http_authenticatable = false + + # If 401 status code should be returned for AJAX requests. True by default. + # config.http_authenticatable_on_xhr = true + + # The realm used in Http Basic Authentication. 'Application' by default. + # config.http_authentication_realm = 'Application' + + # It will change confirmation, password recovery and other workflows + # to behave the same regardless if the e-mail provided was right or wrong. + # Does not affect registerable. + # config.paranoid = true + + # By default Devise will store the user in session. You can skip storage for + # particular strategies by setting this option. + # Notice that if you are skipping storage for all authentication paths, you + # may want to disable generating routes to Devise's sessions controller by + # passing skip: :sessions to `devise_for` in your config/routes.rb + config.skip_session_storage = [:http_auth] + + # By default, Devise cleans up the CSRF token on authentication to + # avoid CSRF token fixation attacks. This means that, when using AJAX + # requests for sign in and sign up, you need to get a new CSRF token + # from the server. You can disable this option at your own risk. + # config.clean_up_csrf_token_on_authentication = true + + # When false, Devise will not attempt to reload routes on eager load. + # This can reduce the time taken to boot the app but if your application + # requires the Devise mappings to be loaded during boot time the application + # won't boot properly. + # config.reload_routes = true + + # ==> Configuration for :database_authenticatable + # For bcrypt, this is the cost for hashing the password and defaults to 12. If + # using other algorithms, it sets how many times you want the password to be hashed. + # The number of stretches used for generating the hashed password are stored + # with the hashed password. This allows you to change the stretches without + # invalidating existing passwords. + # + # Limiting the stretches to just one in testing will increase the performance of + # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use + # a value less than 10 in other environments. Note that, for bcrypt (the default + # algorithm), the cost increases exponentially with the number of stretches (e.g. + # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation). + config.stretches = Rails.env.test? ? 1 : 12 + + # Set up a pepper to generate the hashed password. + # config.pepper = '58272c52535bb38754158a786f2f1b3f00b66d6bc82ac62601d263f17c2dd22345 + # a4cb452588be7d376a8d9eddfda1e705577cf320e72861bba122d603ee0dac' + + # Send a notification to the original email when the user's email is changed. + # config.send_email_changed_notification = false + + # Send a notification email when the user's password is changed. + # config.send_password_change_notification = false + + # ==> Configuration for :confirmable + # A period that the user is allowed to access the website even without + # confirming their account. For instance, if set to 2.days, the user will be + # able to access the website for two days without confirming their account, + # access will be blocked just in the third day. + # You can also set it to nil, which will allow the user to access the website + # without confirming their account. + # Default is 0.days, meaning the user cannot access the website without + # confirming their account. + # config.allow_unconfirmed_access_for = 2.days + + # A period that the user is allowed to confirm their account before their + # token becomes invalid. For example, if set to 3.days, the user can confirm + # their account within 3 days after the mail was sent, but on the fourth day + # their account can't be confirmed with the token any more. + # Default is nil, meaning there is no restriction on how long a user can take + # before confirming their account. + # config.confirm_within = 3.days + + # If true, requires any email changes to be confirmed (exactly the same way as + # initial account confirmation) to be applied. Requires additional unconfirmed_email + # db field (see migrations). Until confirmed, new email is stored in + # unconfirmed_email column, and copied to email column on successful confirmation. + config.reconfirmable = true + + # Defines which key will be used when confirming an account + # config.confirmation_keys = [:email] + + # ==> Configuration for :rememberable + # The time the user will be remembered without asking for credentials again. + # config.remember_for = 2.weeks + + # Invalidates all the remember me tokens when the user signs out. + config.expire_all_remember_me_on_sign_out = true + + # If true, extends the user's remember period when remembered via cookie. + # config.extend_remember_period = false + + # Options to be passed to the created cookie. For instance, you can set + # secure: true in order to force SSL only cookies. + # config.rememberable_options = {} + + # ==> Configuration for :validatable + # Range for password length. + config.password_length = 6..128 + + # Email regex used to validate email formats. It simply asserts that + # one (and only one) @ exists in the given string. This is mainly + # to give user feedback and not to assert the e-mail validity. + config.email_regexp = /\A[^@\s]+@[^@\s]+\z/ + + # ==> Configuration for :timeoutable + # The time you want to timeout the user session without activity. After this + # time the user will be asked for credentials again. Default is 30 minutes. + # config.timeout_in = 30.minutes + + # ==> Configuration for :lockable + # Defines which strategy will be used to lock an account. + # :failed_attempts = Locks an account after a number of failed attempts to sign in. + # :none = No lock strategy. You should handle locking by yourself. + config.lock_strategy = :failed_attempts + + # Defines which key will be used when locking and unlocking an account + config.unlock_keys = [:email] + + # Defines which strategy will be used to unlock an account. + # :email = Sends an unlock link to the user email + # :time = Re-enables login after a certain amount of time (see :unlock_in below) + # :both = Enables both strategies + # :none = No unlock strategy. You should handle unlocking by yourself. + config.unlock_strategy = :both + + # Number of authentication tries before locking an account if lock_strategy + # is failed attempts. + config.maximum_attempts = 8 + + # Time interval to unlock the account if :time is enabled as unlock_strategy. + config.unlock_in = 1.hour + + # Warn on the last attempt before the account is locked. + config.last_attempt_warning = true + + # ==> Configuration for :recoverable + # + # Defines which key will be used when recovering the password for an account + # config.reset_password_keys = [:email] + + # Time interval you can reset your password with a reset password key. + # Don't put a too small interval or your users won't have the time to + # change their passwords. + config.reset_password_within = 6.hours + + # When set to false, does not sign a user in automatically after their password is + # reset. Defaults to true, so a user is signed in automatically after a reset. + # config.sign_in_after_reset_password = true + + # ==> Configuration for :encryptable + # Allow you to use another hashing or encryption algorithm besides bcrypt (default). + # You can use :sha1, :sha512 or algorithms from others authentication tools as + # :clearance_sha1, :authlogic_sha512 (then you should set stretches above to 20 + # for default behavior) and :restful_authentication_sha1 (then you should set + # stretches to 10, and copy REST_AUTH_SITE_KEY to pepper). + # + # Require the `devise-encryptable` gem when using anything other than bcrypt + # config.encryptor = :sha512 + + # ==> Scopes configuration + # Turn scoped views on. Before rendering "sessions/new", it will first check for + # "users/sessions/new". It's turned off by default because it's slower if you + # are using only default views. + # config.scoped_views = false + + # Configure the default scope given to Warden. By default it's the first + # devise role declared in your routes (usually :user). + # config.default_scope = :user + + # Set this configuration to false if you want /users/sign_out to sign out + # only the current scope. By default, Devise signs out all scopes. + # config.sign_out_all_scopes = true + + # ==> Navigation configuration + # Lists the formats that should be treated as navigational. Formats like + # :html, should redirect to the sign in page when the user does not have + # access, but formats like :xml or :json, should return 401. + # + # If you have any extra navigational formats, like :iphone or :mobile, you + # should add them to the navigational formats lists. + # + # The "*/*" below is required to match Internet Explorer requests. + # config.navigational_formats = ['*/*', :html] + + # The default HTTP method used to sign out a resource. Default is :delete. + config.sign_out_via = :delete + + # ==> OmniAuth + # Add a new OmniAuth provider. Check the wiki for more information on setting + # up on your models and hooks. + # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' + + # ==> Warden configuration + # If you want to use other strategies, that are not supported by Devise, or + # change the failure app, you can configure them inside the config.warden block. + # + # config.warden do |manager| + # manager.intercept_401 = false + # manager.default_strategies(scope: :user).unshift :some_external_strategy + # end + + # ==> Mountable engine configurations + # When using Devise inside an engine, let's call it `MyEngine`, and this engine + # is mountable, there are some extra configurations to be taken into account. + # The following options are available, assuming the engine is mounted as: + # + # mount MyEngine, at: '/my_engine' + # + # The router that invoked `devise_for`, in the example above, would be: + # config.router_name = :my_engine + # + # When using OmniAuth, Devise cannot automatically set OmniAuth path, + # so you need to do it manually. For the users scope, it would be: + # config.omniauth_path_prefix = '/my_engine/users/auth' + + # ==> Turbolinks configuration + # If your app is using Turbolinks, Turbolinks::Controller needs to be included to make redirection work correctly: + # + # ActiveSupport.on_load(:devise_failure_app) do + # include Turbolinks::Controller + # end + + # ==> Configuration for :registerable + + # When set to false, does not sign a user in automatically after their password is + # changed. Defaults to true, so a user is signed in automatically after changing a password. + # config.sign_in_after_change_password = true +end diff --git a/spec/dummy/config/initializers/webauthn.rb b/spec/dummy/config/initializers/webauthn.rb new file mode 100644 index 00000000..889d8634 --- /dev/null +++ b/spec/dummy/config/initializers/webauthn.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +WebAuthn.configure do |config| + # This value needs to match `window.location.origin` evaluated by + # the User Agent during registration and authentication ceremonies. + config.allowed_origins = ["http://localhost:3030"] + + # Relying Party name for display purposes + config.rp_name = "WebAuthn Rails Demo App" +end diff --git a/spec/dummy/config/routes.rb b/spec/dummy/config/routes.rb new file mode 100644 index 00000000..083df430 --- /dev/null +++ b/spec/dummy/config/routes.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +Rails.application.routes.draw do + mount Devise::Webauthn::Engine => "/devise-webauthn" + + devise_for :users +end diff --git a/spec/dummy/config/storage.yml b/spec/dummy/config/storage.yml new file mode 100644 index 00000000..695f17bd --- /dev/null +++ b/spec/dummy/config/storage.yml @@ -0,0 +1,7 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> diff --git a/spec/dummy/db/migrate/20240101000000_create_users_and_passkeys.rb b/spec/dummy/db/migrate/20240101000000_create_users_and_passkeys.rb new file mode 100644 index 00000000..98a9f71b --- /dev/null +++ b/spec/dummy/db/migrate/20240101000000_create_users_and_passkeys.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class CreateUsersAndPasskeys < ActiveRecord::Migration[8.0] + def change + create_table :users do |t| + t.string :email, null: false, default: "" + t.string :encrypted_password, null: false, default: "" + t.string :webauthn_id + t.timestamps null: false + end + add_index :users, :email, unique: true + add_index :users, :webauthn_id, unique: true + + create_table :passkeys do |t| + t.references :user, null: false, foreign_key: true + t.string :external_id, null: false + t.string :public_key, null: false + t.string :name, null: false + t.integer :sign_count, null: false, default: 0 + t.timestamps null: false + end + add_index :passkeys, :external_id, unique: true + end +end diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb new file mode 100644 index 00000000..c8e32aa9 --- /dev/null +++ b/spec/dummy/db/schema.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[8.0].define(version: 20_240_101_000_000) do + create_table :users, force: true do |t| + t.string :email, null: false, default: "" + t.string :encrypted_password, null: false, default: "" + t.string :webauthn_id + t.timestamps null: false + end + add_index :users, :email, unique: true + add_index :users, :webauthn_id, unique: true + + create_table :passkeys, force: true do |t| + t.references :user, null: false + t.string :external_id, null: false + t.string :public_key, null: false + t.string :name, null: false + t.integer :sign_count, null: false, default: 0 + t.timestamps null: false + end + add_index :passkeys, :external_id, unique: true +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb new file mode 100644 index 00000000..346b7945 --- /dev/null +++ b/spec/rails_helper.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +ENV["RAILS_ENV"] ||= "test" + +require_relative "dummy/config/environment" +require "rspec/rails" + +ActiveRecord::Schema.verbose = false diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e4e05a8a..f7731c54 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,6 +2,7 @@ require "bundler/setup" require "devise/webauthn" +require "byebug" RSpec.configure do |config| # Enable flags like --only-failures and --next-failure @@ -13,4 +14,6 @@ config.expect_with :rspec do |c| c.syntax = :expect end + + config.use_transactional_fixtures = true end From e3b6a66e8cbbdb9b7fbbf695cae690bfeeef4f5f Mon Sep 17 00:00:00 2001 From: Renzo Minelli Date: Mon, 11 Aug 2025 22:09:56 -0300 Subject: [PATCH 02/13] remove unnecessary migration --- ...0240101000000_create_users_and_passkeys.rb | 24 ------------------- spec/dummy/db/schema.rb | 6 ++--- 2 files changed, 3 insertions(+), 27 deletions(-) delete mode 100644 spec/dummy/db/migrate/20240101000000_create_users_and_passkeys.rb diff --git a/spec/dummy/db/migrate/20240101000000_create_users_and_passkeys.rb b/spec/dummy/db/migrate/20240101000000_create_users_and_passkeys.rb deleted file mode 100644 index 98a9f71b..00000000 --- a/spec/dummy/db/migrate/20240101000000_create_users_and_passkeys.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -class CreateUsersAndPasskeys < ActiveRecord::Migration[8.0] - def change - create_table :users do |t| - t.string :email, null: false, default: "" - t.string :encrypted_password, null: false, default: "" - t.string :webauthn_id - t.timestamps null: false - end - add_index :users, :email, unique: true - add_index :users, :webauthn_id, unique: true - - create_table :passkeys do |t| - t.references :user, null: false, foreign_key: true - t.string :external_id, null: false - t.string :public_key, null: false - t.string :name, null: false - t.integer :sign_count, null: false, default: 0 - t.timestamps null: false - end - add_index :passkeys, :external_id, unique: true - end -end diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb index c8e32aa9..8ac62cfc 100644 --- a/spec/dummy/db/schema.rb +++ b/spec/dummy/db/schema.rb @@ -18,9 +18,9 @@ t.string :encrypted_password, null: false, default: "" t.string :webauthn_id t.timestamps null: false + t.index :webauthn_id, unique: true + t.index :email, unique: true end - add_index :users, :email, unique: true - add_index :users, :webauthn_id, unique: true create_table :passkeys, force: true do |t| t.references :user, null: false @@ -29,6 +29,6 @@ t.string :name, null: false t.integer :sign_count, null: false, default: 0 t.timestamps null: false + t.index :external_id, unique: true end - add_index :passkeys, :external_id, unique: true end From 94eb8ff897ae0ef4eb9dd012225586715391a4e2 Mon Sep 17 00:00:00 2001 From: Renzo Minelli Date: Mon, 11 Aug 2025 22:19:47 -0300 Subject: [PATCH 03/13] load schema on tests --- spec/rails_helper.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 346b7945..a5f2e439 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -6,3 +6,5 @@ require "rspec/rails" ActiveRecord::Schema.verbose = false + +load Rails.root.join("db/schema.rb") From 67a86f956d754b16bf98e37b13f920835604dd87 Mon Sep 17 00:00:00 2001 From: Renzo Minelli Date: Mon, 11 Aug 2025 22:24:54 -0300 Subject: [PATCH 04/13] add test to verify user and passkey are loaded --- .rubocop.yml | 6 ++++++ spec/devise/models/passkey_spec.rb | 15 +++++++++++++++ spec/devise/models/user_spec.rb | 13 +++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 spec/devise/models/passkey_spec.rb create mode 100644 spec/devise/models/user_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index 85895e1e..6ac5f567 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -17,3 +17,9 @@ Metrics/MethodLength: Gemspec/DevelopmentDependencies: Enabled: false + +RSpec/MultipleExpectations: + Max: 3 + +RSpec/ExampleLength: + Max: 20 diff --git a/spec/devise/models/passkey_spec.rb b/spec/devise/models/passkey_spec.rb new file mode 100644 index 00000000..274c0a1b --- /dev/null +++ b/spec/devise/models/passkey_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +RSpec.describe Passkey do + it "allows building and saving a passkey" do + user = User.new(email: "test@example.com", password: "password") + passkey = described_class.new(name: "test", external_id: "external_id_123", public_key: "public_key_123", + sign_count: 0, user:) + + expect(passkey.save).to be_truthy + + passkey.reload + + expect(passkey).to be_persisted + end +end diff --git a/spec/devise/models/user_spec.rb b/spec/devise/models/user_spec.rb new file mode 100644 index 00000000..818f8995 --- /dev/null +++ b/spec/devise/models/user_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +RSpec.describe User do + it "allows building and saving a user" do + user = described_class.new(email: "test@example.com", password: "password") + + expect(user.save).to be_truthy + + user.reload + + expect(user).to be_persisted + end +end From 1c072f561710680e320b87e712454da668be993e Mon Sep 17 00:00:00 2001 From: Renzo Minelli Date: Tue, 12 Aug 2025 21:52:38 -0300 Subject: [PATCH 05/13] add missing dummy app parts --- Gemfile.lock | 8 +++ Rakefile | 9 +-- bin/rails | 14 ++++ devise-webauthn.gemspec | 2 + spec/dummy/app/assets/config/manifest.js | 5 ++ .../app/assets/stylesheets/application.css | 15 +++++ spec/dummy/app/controllers/home_controller.rb | 7 ++ spec/dummy/app/javascript/application.js | 4 ++ .../app/javascript/controllers/application.js | 9 +++ spec/dummy/app/views/home/index.html.erb | 2 + .../app/views/layouts/application.html.erb | 16 +++++ spec/dummy/bin/importmap | 5 ++ spec/dummy/bin/rails | 6 ++ spec/dummy/bin/rake | 6 ++ spec/dummy/bin/setup | 39 +++++++++++ spec/dummy/config/boot.rb | 4 +- spec/dummy/config/cable.yml | 10 +++ spec/dummy/config/importmap.rb | 9 +++ spec/dummy/config/locales/en.yml | 31 +++++++++ spec/dummy/config/puma.rb | 36 ++++++++++ spec/dummy/config/routes.rb | 2 +- .../migrate/20240507150026_create_tables.rb | 24 +++++++ spec/dummy/db/schema.rb | 49 +++++++++----- spec/dummy/public/404.html | 67 +++++++++++++++++++ spec/dummy/public/422.html | 67 +++++++++++++++++++ spec/dummy/public/500.html | 66 ++++++++++++++++++ .../public/apple-touch-icon-precomposed.png | 0 spec/dummy/public/apple-touch-icon.png | 0 spec/dummy/public/favicon.ico | 0 29 files changed, 489 insertions(+), 23 deletions(-) create mode 100755 bin/rails create mode 100644 spec/dummy/app/assets/config/manifest.js create mode 100644 spec/dummy/app/assets/stylesheets/application.css create mode 100644 spec/dummy/app/controllers/home_controller.rb create mode 100644 spec/dummy/app/javascript/application.js create mode 100644 spec/dummy/app/javascript/controllers/application.js create mode 100644 spec/dummy/app/views/home/index.html.erb create mode 100644 spec/dummy/app/views/layouts/application.html.erb create mode 100755 spec/dummy/bin/importmap create mode 100755 spec/dummy/bin/rails create mode 100755 spec/dummy/bin/rake create mode 100755 spec/dummy/bin/setup create mode 100644 spec/dummy/config/cable.yml create mode 100644 spec/dummy/config/importmap.rb create mode 100644 spec/dummy/config/locales/en.yml create mode 100644 spec/dummy/config/puma.rb create mode 100644 spec/dummy/db/migrate/20240507150026_create_tables.rb create mode 100644 spec/dummy/public/404.html create mode 100644 spec/dummy/public/422.html create mode 100644 spec/dummy/public/500.html create mode 100644 spec/dummy/public/apple-touch-icon-precomposed.png create mode 100644 spec/dummy/public/apple-touch-icon.png create mode 100644 spec/dummy/public/favicon.ico diff --git a/Gemfile.lock b/Gemfile.lock index 60b91819..18e3e50a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -110,6 +110,10 @@ GEM activesupport (>= 6.1) i18n (1.14.7) concurrent-ruby (~> 1.0) + importmap-rails (2.2.2) + actionpack (>= 6.0.0) + activesupport (>= 6.0.0) + railties (>= 6.0.0) io-console (0.8.1) irb (1.15.2) pp (>= 0.6.0) @@ -159,6 +163,8 @@ GEM psych (5.2.6) date stringio + puma (6.6.1) + nio4r (~> 2.0) racc (1.8.1) rack (3.2.0) rack-session (2.1.1) @@ -293,6 +299,8 @@ PLATFORMS DEPENDENCIES byebug devise-webauthn! + importmap-rails + puma rails (~> 8.0) rspec (~> 3.13) rspec-rails (~> 6.1) diff --git a/Rakefile b/Rakefile index b6ae7341..2f60bf9a 100644 --- a/Rakefile +++ b/Rakefile @@ -1,8 +1,9 @@ # frozen_string_literal: true -require "bundler/gem_tasks" -require "rspec/core/rake_task" +require "bundler/setup" -RSpec::Core::RakeTask.new(:spec) +APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__) +load "rails/tasks/engine.rake" +load "rails/tasks/statistics.rake" -task default: :spec +require "bundler/gem_tasks" diff --git a/bin/rails b/bin/rails new file mode 100755 index 00000000..261e1b3e --- /dev/null +++ b/bin/rails @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby +# This command will automatically be run when you run "rails" with Rails gems +# installed from the root of your application. + +ENGINE_ROOT = File.expand_path("..", __dir__) +ENGINE_PATH = File.expand_path("../lib/webauthn/rails/engine", __dir__) +APP_PATH = File.expand_path("../spec/dummy/config/application", __dir__) + +# Set up gems listed in the Gemfile. +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) +require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) + +require "rails/all" +require "rails/engine/commands" diff --git a/devise-webauthn.gemspec b/devise-webauthn.gemspec index 254f24b2..69e7a444 100644 --- a/devise-webauthn.gemspec +++ b/devise-webauthn.gemspec @@ -25,6 +25,8 @@ Gem::Specification.new do |spec| spec.required_ruby_version = ">= 3.1" spec.add_development_dependency "byebug" + spec.add_development_dependency "importmap-rails" + spec.add_development_dependency "puma" spec.add_development_dependency "rails", "~> 8.0" spec.add_development_dependency "rspec", "~> 3.13" spec.add_development_dependency "rspec-rails", "~> 6.1" diff --git a/spec/dummy/app/assets/config/manifest.js b/spec/dummy/app/assets/config/manifest.js new file mode 100644 index 00000000..9e4ceab4 --- /dev/null +++ b/spec/dummy/app/assets/config/manifest.js @@ -0,0 +1,5 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css +//= link webauthn_rails_manifest.js +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js diff --git a/spec/dummy/app/assets/stylesheets/application.css b/spec/dummy/app/assets/stylesheets/application.css new file mode 100644 index 00000000..0ebd7fe8 --- /dev/null +++ b/spec/dummy/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/spec/dummy/app/controllers/home_controller.rb b/spec/dummy/app/controllers/home_controller.rb new file mode 100644 index 00000000..5a507059 --- /dev/null +++ b/spec/dummy/app/controllers/home_controller.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class HomeController < ApplicationController + before_action :authenticate_user! + + def index; end +end diff --git a/spec/dummy/app/javascript/application.js b/spec/dummy/app/javascript/application.js new file mode 100644 index 00000000..795ae9d4 --- /dev/null +++ b/spec/dummy/app/javascript/application.js @@ -0,0 +1,4 @@ +// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails +import "@hotwired/turbo-rails" + +import "controllers" diff --git a/spec/dummy/app/javascript/controllers/application.js b/spec/dummy/app/javascript/controllers/application.js new file mode 100644 index 00000000..1213e85c --- /dev/null +++ b/spec/dummy/app/javascript/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus" + +const application = Application.start() + +// Configure Stimulus development experience +application.debug = false +window.Stimulus = application + +export { application } diff --git a/spec/dummy/app/views/home/index.html.erb b/spec/dummy/app/views/home/index.html.erb new file mode 100644 index 00000000..24b2c761 --- /dev/null +++ b/spec/dummy/app/views/home/index.html.erb @@ -0,0 +1,2 @@ +<%= link_to 'Sign out', webauthn_rails.session_path, data: { turbo_method: :delete } %> + diff --git a/spec/dummy/app/views/layouts/application.html.erb b/spec/dummy/app/views/layouts/application.html.erb new file mode 100644 index 00000000..8217faf4 --- /dev/null +++ b/spec/dummy/app/views/layouts/application.html.erb @@ -0,0 +1,16 @@ + + + + Dummy + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "application" %> + <%= javascript_importmap_tags %> + + + + <%= yield %> + + diff --git a/spec/dummy/bin/importmap b/spec/dummy/bin/importmap new file mode 100755 index 00000000..7dc6a085 --- /dev/null +++ b/spec/dummy/bin/importmap @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative "../config/application" +require "importmap/commands" diff --git a/spec/dummy/bin/rails b/spec/dummy/bin/rails new file mode 100755 index 00000000..22f2d8de --- /dev/null +++ b/spec/dummy/bin/rails @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/spec/dummy/bin/rake b/spec/dummy/bin/rake new file mode 100755 index 00000000..e436ea54 --- /dev/null +++ b/spec/dummy/bin/rake @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/spec/dummy/bin/setup b/spec/dummy/bin/setup new file mode 100755 index 00000000..49a3d4d1 --- /dev/null +++ b/spec/dummy/bin/setup @@ -0,0 +1,39 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "fileutils" + +APP_ROOT = File.expand_path("..", __dir__) +APP_NAME = "dummy" + +def system!(*args) + system(*args, exception: true) +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system! "gem install bundler --conservative" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + puts "\n== Restarting application server ==" + system! "bin/rails restart" + + # puts "\n== Configuring puma-dev ==" + # system "ln -nfs #{APP_ROOT} ~/.puma-dev/#{APP_NAME}" + # system "curl -Is https://#{APP_NAME}.test/up | head -n 1" +end diff --git a/spec/dummy/config/boot.rb b/spec/dummy/config/boot.rb index 75b66eb1..59459d4a 100644 --- a/spec/dummy/config/boot.rb +++ b/spec/dummy/config/boot.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Set up gems listed in the Gemfile. -ENV["BUNDLE_GEMFILE"] ||= File.expand_path("Gemfile", __dir__) +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__) require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) -$LOAD_PATH.unshift File.expand_path("lib", __dir__) +$LOAD_PATH.unshift File.expand_path("../../../lib", __dir__) diff --git a/spec/dummy/config/cable.yml b/spec/dummy/config/cable.yml new file mode 100644 index 00000000..98367f89 --- /dev/null +++ b/spec/dummy/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: dummy_production diff --git a/spec/dummy/config/importmap.rb b/spec/dummy/config/importmap.rb new file mode 100644 index 00000000..631a58a6 --- /dev/null +++ b/spec/dummy/config/importmap.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# Pin npm packages by running ./bin/importmap + +pin "application" +pin "@hotwired/turbo-rails", to: "turbo.min.js" +pin "@hotwired/stimulus", to: "stimulus.min.js" +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js" +pin_all_from "app/javascript/controllers", under: "controllers" diff --git a/spec/dummy/config/locales/en.yml b/spec/dummy/config/locales/en.yml new file mode 100644 index 00000000..6c349ae5 --- /dev/null +++ b/spec/dummy/config/locales/en.yml @@ -0,0 +1,31 @@ +# Files in the config/locales directory are used for internationalization and +# are automatically loaded by Rails. If you want to use locales other than +# English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# To learn more about the API, please read the Rails Internationalization guide +# at https://guides.rubyonrails.org/i18n.html. +# +# Be aware that YAML interprets the following case-insensitive strings as +# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings +# must be quoted to be interpreted as strings. For example: +# +# en: +# "yes": yup +# enabled: "ON" + +en: + hello: "Hello world" diff --git a/spec/dummy/config/puma.rb b/spec/dummy/config/puma.rb new file mode 100644 index 00000000..6e80052c --- /dev/null +++ b/spec/dummy/config/puma.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. + +# Puma starts a configurable number of processes (workers) and each process +# serves each request in a thread from an internal thread pool. +# +# The ideal number of threads per worker depends both on how much time the +# application spends waiting for IO operations and on how much you wish to +# to prioritize throughput over latency. +# +# As a rule of thumb, increasing the number of threads will increase how much +# traffic a given process can handle (throughput), but due to CRuby's +# Global VM Lock (GVL) it has diminishing returns and will degrade the +# response time (latency) of the application. +# +# The default is set to 3 threads as it's deemed a decent compromise between +# throughput and latency for the average Rails application. +# +# Any libraries that use a connection pool or another resource pool should +# be configured to provide at least as many connections as the number of +# threads. This includes Active Record's `pool` parameter in `database.yml`. +threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +port ENV.fetch("PORT", 3000) + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart + +# Specify the PID file. Defaults to tmp/pids/server.pid in development. +# In other environments, only set the PID file if requested. +pidfile ENV["PIDFILE"] if ENV["PIDFILE"] diff --git a/spec/dummy/config/routes.rb b/spec/dummy/config/routes.rb index 083df430..292f963c 100644 --- a/spec/dummy/config/routes.rb +++ b/spec/dummy/config/routes.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true Rails.application.routes.draw do - mount Devise::Webauthn::Engine => "/devise-webauthn" + root "home#index" devise_for :users end diff --git a/spec/dummy/db/migrate/20240507150026_create_tables.rb b/spec/dummy/db/migrate/20240507150026_create_tables.rb new file mode 100644 index 00000000..29723151 --- /dev/null +++ b/spec/dummy/db/migrate/20240507150026_create_tables.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class CreateTables < ActiveRecord::Migration[7.1] + def change + create_table :users, force: true do |t| + t.string :email, null: false, default: "" + t.string :encrypted_password, null: false, default: "" + t.string :webauthn_id + t.timestamps null: false + t.index :webauthn_id, unique: true + t.index :email, unique: true + end + + create_table :passkeys, force: true do |t| + t.references :user, null: false + t.string :external_id, null: false + t.string :public_key, null: false + t.string :name, null: false + t.integer :sign_count, null: false, default: 0 + t.timestamps null: false + t.index :external_id, unique: true + end + end +end diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb index 8ac62cfc..5dd2c5ef 100644 --- a/spec/dummy/db/schema.rb +++ b/spec/dummy/db/schema.rb @@ -12,23 +12,40 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 20_240_101_000_000) do - create_table :users, force: true do |t| - t.string :email, null: false, default: "" - t.string :encrypted_password, null: false, default: "" - t.string :webauthn_id - t.timestamps null: false - t.index :webauthn_id, unique: true - t.index :email, unique: true +ActiveRecord::Schema[8.0].define(version: 20_250_804_165_453) do + create_table "passkeys", force: :cascade do |t| + t.integer "user_id", null: false + t.string "external_id", null: false + t.string "public_key", null: false + t.string "name", null: false + t.integer "sign_count", default: 0, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["external_id"], name: "index_passkeys_on_external_id", unique: true + t.index ["user_id"], name: "index_passkeys_on_user_id" end - create_table :passkeys, force: true do |t| - t.references :user, null: false - t.string :external_id, null: false - t.string :public_key, null: false - t.string :name, null: false - t.integer :sign_count, null: false, default: 0 - t.timestamps null: false - t.index :external_id, unique: true + create_table "users", force: :cascade do |t| + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "webauthn_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["email"], name: "index_users_on_email", unique: true + t.index ["webauthn_id"], name: "index_users_on_webauthn_id", unique: true end + + create_table "webauthn_credentials", force: :cascade do |t| + t.integer "user_id", null: false + t.string "external_id" + t.string "public_key" + t.string "nickname" + t.bigint "sign_count" + 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 + t.index ["user_id"], name: "index_webauthn_credentials_on_user_id" + end + + add_foreign_key "webauthn_credentials", "users" end diff --git a/spec/dummy/public/404.html b/spec/dummy/public/404.html new file mode 100644 index 00000000..2be3af26 --- /dev/null +++ b/spec/dummy/public/404.html @@ -0,0 +1,67 @@ + + + + The page you were looking for doesn't exist (404) + + + + + + +
+
+

The page you were looking for doesn't exist.

+

You may have mistyped the address or the page may have moved.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/spec/dummy/public/422.html b/spec/dummy/public/422.html new file mode 100644 index 00000000..c08eac0d --- /dev/null +++ b/spec/dummy/public/422.html @@ -0,0 +1,67 @@ + + + + The change you wanted was rejected (422) + + + + + + +
+
+

The change you wanted was rejected.

+

Maybe you tried to change something you didn't have access to.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/spec/dummy/public/500.html b/spec/dummy/public/500.html new file mode 100644 index 00000000..78a030af --- /dev/null +++ b/spec/dummy/public/500.html @@ -0,0 +1,66 @@ + + + + We're sorry, but something went wrong (500) + + + + + + +
+
+

We're sorry, but something went wrong.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/spec/dummy/public/apple-touch-icon-precomposed.png b/spec/dummy/public/apple-touch-icon-precomposed.png new file mode 100644 index 00000000..e69de29b diff --git a/spec/dummy/public/apple-touch-icon.png b/spec/dummy/public/apple-touch-icon.png new file mode 100644 index 00000000..e69de29b diff --git a/spec/dummy/public/favicon.ico b/spec/dummy/public/favicon.ico new file mode 100644 index 00000000..e69de29b From ee26c610e3b9de7513b56a0b66b62077f0271d3c Mon Sep 17 00:00:00 2001 From: Renzo Minelli Date: Sat, 16 Aug 2025 15:04:11 -0300 Subject: [PATCH 06/13] fix outdated schema --- .rubocop.yml | 8 ++++++++ spec/dummy/db/schema.rb | 18 +----------------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 6ac5f567..534de163 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -23,3 +23,11 @@ RSpec/MultipleExpectations: RSpec/ExampleLength: Max: 20 + +Style/NumericLiterals: + Exclude: + - 'spec/dummy/db/schema.rb' + +Style/FrozenStringLiteralComment: + Exclude: + - 'spec/dummy/db/schema.rb' diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb index 5dd2c5ef..c17af6f5 100644 --- a/spec/dummy/db/schema.rb +++ b/spec/dummy/db/schema.rb @@ -1,5 +1,3 @@ -# frozen_string_literal: true - # This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. @@ -12,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 20_250_804_165_453) do +ActiveRecord::Schema[8.0].define(version: 2024_05_07_150026) do create_table "passkeys", force: :cascade do |t| t.integer "user_id", null: false t.string "external_id", null: false @@ -34,18 +32,4 @@ t.index ["email"], name: "index_users_on_email", unique: true t.index ["webauthn_id"], name: "index_users_on_webauthn_id", unique: true end - - create_table "webauthn_credentials", force: :cascade do |t| - t.integer "user_id", null: false - t.string "external_id" - t.string "public_key" - t.string "nickname" - t.bigint "sign_count" - 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 - t.index ["user_id"], name: "index_webauthn_credentials_on_user_id" - end - - add_foreign_key "webauthn_credentials", "users" end From 78e366a737238215ad0f4e1430b789d89e978c49 Mon Sep 17 00:00:00 2001 From: Renzo Minelli Date: Sat, 16 Aug 2025 15:10:57 -0300 Subject: [PATCH 07/13] add missing module and fix sign out --- spec/dummy/app/models/user.rb | 2 +- spec/dummy/app/views/home/index.html.erb | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/dummy/app/models/user.rb b/spec/dummy/app/models/user.rb index 7f7edb5a..e02395c8 100644 --- a/spec/dummy/app/models/user.rb +++ b/spec/dummy/app/models/user.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class User < ApplicationRecord - devise :database_authenticatable, :passkey_authenticatable + devise :database_authenticatable, :passkey_authenticatable, :registerable end diff --git a/spec/dummy/app/views/home/index.html.erb b/spec/dummy/app/views/home/index.html.erb index 24b2c761..6082f1f6 100644 --- a/spec/dummy/app/views/home/index.html.erb +++ b/spec/dummy/app/views/home/index.html.erb @@ -1,2 +1 @@ -<%= link_to 'Sign out', webauthn_rails.session_path, data: { turbo_method: :delete } %> - +<%= link_to 'Sign out', destroy_user_session_path, data: { turbo_method: :delete } %> From 17b7455b56c9c4bc5f8f139836668e3b9548b25b Mon Sep 17 00:00:00 2001 From: Renzo Minelli Date: Sat, 16 Aug 2025 15:42:42 -0300 Subject: [PATCH 08/13] fix assets issues --- .gitignore | 1 + Gemfile.lock | 5 +++++ bin/rails | 2 +- devise-webauthn.gemspec | 1 + spec/dummy/app/assets/config/manifest.js | 1 - spec/dummy/app/javascript/application.js | 5 +++-- spec/dummy/config/importmap.rb | 7 ++++--- spec/dummy/config/initializers/webauthn.rb | 2 +- 8 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index b7a0cf2f..1f0c7c8b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,6 @@ /spec/dummy/log/* /spec/dummy/tmp/* /spec/dummy/storage/* +/spec/dummy/vendor .byebug_history diff --git a/Gemfile.lock b/Gemfile.lock index 18e3e50a..4e0dce3d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -160,6 +160,10 @@ GEM prettyprint prettyprint (0.2.0) prism (1.4.0) + propshaft (1.2.1) + actionpack (>= 7.0.0) + activesupport (>= 7.0.0) + rack psych (5.2.6) date stringio @@ -300,6 +304,7 @@ DEPENDENCIES byebug devise-webauthn! importmap-rails + propshaft puma rails (~> 8.0) rspec (~> 3.13) diff --git a/bin/rails b/bin/rails index 261e1b3e..fc08e50a 100755 --- a/bin/rails +++ b/bin/rails @@ -3,7 +3,7 @@ # installed from the root of your application. ENGINE_ROOT = File.expand_path("..", __dir__) -ENGINE_PATH = File.expand_path("../lib/webauthn/rails/engine", __dir__) +ENGINE_PATH = File.expand_path("../lib/devise/webauthn/engine", __dir__) APP_PATH = File.expand_path("../spec/dummy/config/application", __dir__) # Set up gems listed in the Gemfile. diff --git a/devise-webauthn.gemspec b/devise-webauthn.gemspec index 69e7a444..67b1c10a 100644 --- a/devise-webauthn.gemspec +++ b/devise-webauthn.gemspec @@ -26,6 +26,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency "byebug" spec.add_development_dependency "importmap-rails" + spec.add_development_dependency "propshaft" spec.add_development_dependency "puma" spec.add_development_dependency "rails", "~> 8.0" spec.add_development_dependency "rspec", "~> 3.13" diff --git a/spec/dummy/app/assets/config/manifest.js b/spec/dummy/app/assets/config/manifest.js index 9e4ceab4..ddd546a0 100644 --- a/spec/dummy/app/assets/config/manifest.js +++ b/spec/dummy/app/assets/config/manifest.js @@ -1,5 +1,4 @@ //= link_tree ../images //= link_directory ../stylesheets .css -//= link webauthn_rails_manifest.js //= link_tree ../../javascript .js //= link_tree ../../../vendor/javascript .js diff --git a/spec/dummy/app/javascript/application.js b/spec/dummy/app/javascript/application.js index 795ae9d4..b1a75b68 100644 --- a/spec/dummy/app/javascript/application.js +++ b/spec/dummy/app/javascript/application.js @@ -1,4 +1,5 @@ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails -import "@hotwired/turbo-rails" +import { Turbo } from "@hotwired/turbo-rails" +import "controllers/application" -import "controllers" +Turbo.session.drive = false diff --git a/spec/dummy/config/importmap.rb b/spec/dummy/config/importmap.rb index 631a58a6..aabe0a15 100644 --- a/spec/dummy/config/importmap.rb +++ b/spec/dummy/config/importmap.rb @@ -3,7 +3,8 @@ # Pin npm packages by running ./bin/importmap pin "application" -pin "@hotwired/turbo-rails", to: "turbo.min.js" -pin "@hotwired/stimulus", to: "stimulus.min.js" -pin "@hotwired/stimulus-loading", to: "stimulus-loading.js" +pin "@hotwired/turbo-rails", to: "@hotwired--turbo-rails.js" # @8.0.16 +pin "@hotwired/turbo", to: "@hotwired--turbo.js" # @8.0.13 +pin "@rails/actioncable/src", to: "@rails--actioncable--src.js" # @8.0.201 +pin "@hotwired/stimulus", to: "@hotwired--stimulus.js" # @3.2.2 pin_all_from "app/javascript/controllers", under: "controllers" diff --git a/spec/dummy/config/initializers/webauthn.rb b/spec/dummy/config/initializers/webauthn.rb index 889d8634..615e956d 100644 --- a/spec/dummy/config/initializers/webauthn.rb +++ b/spec/dummy/config/initializers/webauthn.rb @@ -6,5 +6,5 @@ config.allowed_origins = ["http://localhost:3030"] # Relying Party name for display purposes - config.rp_name = "WebAuthn Rails Demo App" + config.rp_name = "Devise WebAuthn Demo App" end From 2d363e57b99496a43d4d9c90c0abb592d98866a2 Mon Sep 17 00:00:00 2001 From: Joaquin Tomas Date: Tue, 19 Aug 2025 14:20:29 -0300 Subject: [PATCH 09/13] Stop git ignoring `spec/dummy/vendor` --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1f0c7c8b..b7a0cf2f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,5 @@ /spec/dummy/log/* /spec/dummy/tmp/* /spec/dummy/storage/* -/spec/dummy/vendor .byebug_history From 5fb798b2b635e641c486b2cfd7738398e665bfcd Mon Sep 17 00:00:00 2001 From: Joaquin Tomas Date: Tue, 19 Aug 2025 14:59:53 -0300 Subject: [PATCH 10/13] Add `stimulus-rails` --- Gemfile.lock | 3 +++ devise-webauthn.gemspec | 1 + 2 files changed, 4 insertions(+) diff --git a/Gemfile.lock b/Gemfile.lock index 4e0dce3d..0dd9ced8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -267,6 +267,8 @@ GEM jwt (~> 2.0) securerandom (0.4.1) sqlite3 (2.7.3-arm64-darwin) + stimulus-rails (1.3.4) + railties (>= 6.0.0) stringio (3.1.7) thor (1.4.0) timeout (0.4.3) @@ -313,6 +315,7 @@ DEPENDENCIES rubocop-rails (~> 2.32) rubocop-rspec (~> 3.6) sqlite3 (~> 2.7) + stimulus-rails (~> 1.3) BUNDLED WITH 2.7.1 diff --git a/devise-webauthn.gemspec b/devise-webauthn.gemspec index 67b1c10a..dee166af 100644 --- a/devise-webauthn.gemspec +++ b/devise-webauthn.gemspec @@ -35,6 +35,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency "rubocop-rails", "~> 2.32" spec.add_development_dependency "rubocop-rspec", "~> 3.6" spec.add_development_dependency "sqlite3", "~> 2.7" + spec.add_development_dependency "stimulus-rails", "~> 1.3" spec.add_dependency "devise", "~> 4.9" spec.add_dependency "webauthn", "~> 3.0" From 6979782a2dfb2d2f794be5841fd1bea0de5ad4d9 Mon Sep 17 00:00:00 2001 From: Joaquin Tomas Date: Tue, 19 Aug 2025 15:11:15 -0300 Subject: [PATCH 11/13] Add `turbo-rails` --- Gemfile.lock | 4 ++++ devise-webauthn.gemspec | 1 + 2 files changed, 5 insertions(+) diff --git a/Gemfile.lock b/Gemfile.lock index 0dd9ced8..c214989d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -276,6 +276,9 @@ GEM bindata (~> 2.4) openssl (> 2.0) openssl-signature_algorithm (~> 1.0) + turbo-rails (2.0.16) + actionpack (>= 7.1.0) + railties (>= 7.1.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (3.1.4) @@ -316,6 +319,7 @@ DEPENDENCIES rubocop-rspec (~> 3.6) sqlite3 (~> 2.7) stimulus-rails (~> 1.3) + turbo-rails (~> 2.0) BUNDLED WITH 2.7.1 diff --git a/devise-webauthn.gemspec b/devise-webauthn.gemspec index dee166af..079542eb 100644 --- a/devise-webauthn.gemspec +++ b/devise-webauthn.gemspec @@ -36,6 +36,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency "rubocop-rspec", "~> 3.6" spec.add_development_dependency "sqlite3", "~> 2.7" spec.add_development_dependency "stimulus-rails", "~> 1.3" + spec.add_development_dependency "turbo-rails", "~> 2.0" spec.add_dependency "devise", "~> 4.9" spec.add_dependency "webauthn", "~> 3.0" From 3ba12b866845bcdea7b8e928c7203ba34bd59329 Mon Sep 17 00:00:00 2001 From: Joaquin Tomas Date: Tue, 19 Aug 2025 16:17:46 -0300 Subject: [PATCH 12/13] Fix stimulus and turbo setups --- spec/dummy/app/javascript/controllers/index.js | 4 ++++ spec/dummy/config/importmap.rb | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 spec/dummy/app/javascript/controllers/index.js diff --git a/spec/dummy/app/javascript/controllers/index.js b/spec/dummy/app/javascript/controllers/index.js new file mode 100644 index 00000000..1156bf83 --- /dev/null +++ b/spec/dummy/app/javascript/controllers/index.js @@ -0,0 +1,4 @@ +// Import and register all your controllers from the importmap via controllers/**/*_controller +import { application } from "controllers/application" +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" +eagerLoadControllersFrom("controllers", application) diff --git a/spec/dummy/config/importmap.rb b/spec/dummy/config/importmap.rb index aabe0a15..b834797f 100644 --- a/spec/dummy/config/importmap.rb +++ b/spec/dummy/config/importmap.rb @@ -3,8 +3,8 @@ # Pin npm packages by running ./bin/importmap pin "application" -pin "@hotwired/turbo-rails", to: "@hotwired--turbo-rails.js" # @8.0.16 -pin "@hotwired/turbo", to: "@hotwired--turbo.js" # @8.0.13 pin "@rails/actioncable/src", to: "@rails--actioncable--src.js" # @8.0.201 -pin "@hotwired/stimulus", to: "@hotwired--stimulus.js" # @3.2.2 pin_all_from "app/javascript/controllers", under: "controllers" +pin "@hotwired/turbo-rails", to: "turbo.min.js" +pin "@hotwired/stimulus", to: "stimulus.min.js" +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js" From 7856433f855d86f4f5eba593aa711d5fe5c57e88 Mon Sep 17 00:00:00 2001 From: Joaquin Tomas Date: Tue, 19 Aug 2025 16:56:56 -0300 Subject: [PATCH 13/13] build(deps): Force ruby platform ``` bundle config set force_ruby_platform true bundle install ``` --- Gemfile.lock | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index c214989d..9cce87ec 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -135,6 +135,7 @@ GEM net-smtp marcel (1.0.4) mini_mime (1.1.5) + mini_portile2 (2.8.9) minitest (5.25.5) net-imap (0.5.9) date @@ -147,6 +148,7 @@ GEM net-protocol nio4r (2.7.4) nokogiri (1.18.9) + mini_portile2 (~> 2.8.2) racc (~> 1.4) openssl (3.3.0) openssl-signature_algorithm (1.3.0) @@ -266,7 +268,8 @@ GEM safety_net_attestation (0.4.0) jwt (~> 2.0) securerandom (0.4.1) - sqlite3 (2.7.3-arm64-darwin) + sqlite3 (2.7.3) + mini_portile2 (~> 2.8.0) stimulus-rails (1.3.4) railties (>= 6.0.0) stringio (3.1.7)