diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03122cd..c73e69e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,8 +20,6 @@ jobs: rails: - rails_8.1 - rails_8.0 - - rails_7.2 - - rails_7.1 exclude: - ruby: '3.1' rails: 'rails_8.0' diff --git a/Appraisals b/Appraisals index a1ea097..3686f0e 100644 --- a/Appraisals +++ b/Appraisals @@ -1,22 +1,5 @@ # frozen_string_literal: true -appraise 'rails_7.1' do - gem 'rails', '~> 7.1.0' - gem 'sqlite3', '~> 1.5.0' - - # Fix: - # warning: logger was loaded from the standard library, but will no longer be part of the default gems since Ruby 3.5.0. - # Add logger to your Gemfile or gemspec. - install_if '-> { Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0") }' do - gem 'logger' - end -end - -appraise 'rails_7.2' do - gem 'rails', '~> 7.2.0' - gem 'sqlite3', '~> 1.5.0' -end - appraise 'rails_8.0' do gem 'rails', '~> 8.0.0' end diff --git a/CHANGELOG.md b/CHANGELOG.md index c1fc09c..abcd8f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Unreleased +Maintenance +- Disable tests for Rails 7.1 (EOL on 10/2025) and 7.2 (EOL on 8/9/2026) + +README +- Switch README to feature native CSS import for Propshaft; +- Add Getting Started section with sample link to OTP Token page; + ## v2.0.0 Bug fixes: diff --git a/README.md b/README.md index cb7c7b2..6a85e57 100644 --- a/README.md +++ b/README.md @@ -50,23 +50,31 @@ Run the following generator to add the necessary configuration options to Devise rails g devise_otp:install -After you've created your Devise user models (which is usually done with a `rails g devise MODEL`), set up your Devise OTP additions: +## Getting Started + +Create your Devise user model if you have not done so + + rails g devise MODEL + +Add Devise OTP to the model rails g devise_otp MODEL -Don't forget to migrate: +Add a link to the OTP Token settings to your navigation menu (or equivalent) for the user model - rake db:migrate + <%= link_to "OTP Token", user_otp_token_path %> -### Default CSS +NOTE: replace "user" with the lowercase name of the model (same as the main devise gem) -To use the default CSS for devise-otp, just require the devise-otp.css file as usual in your application.css file (or equivalent): +## Default CSS - *= require devise-otp +To use the default CSS for devise-otp, just import the devise-otp.css file into your application.css file (or equivalent): -It might be even easier to just copy the styles to your project. + @import url("devise-otp.css"); -### Custom views +NOTE: You can also copy/paste the styles from [devise-otp.css](https://github.com/wmlele/devise-otp/blob/master/app/assets/stylesheets/devise-otp.css) into your stylesheet. + +## Custom views If you want to customise your views, you can use the following generator to eject the default view files: @@ -74,11 +82,11 @@ If you want to customise your views, you can use the following generator to ejec By default, the files live within the Devise namespace (`app/views/devise`, but if you want to move them or want to match the Devise configuration, set `config.otp_controller_path` in your initializers. -### I18n +## I18n The install generator also installs an english copy of a Devise OTP i18n file. This can be modified (or used to create other language versions) and is located at: _config/locales/devise.otp.en.yml_ -### QR codes +## QR codes Devise OTP generates QR Codes directly as SVG's via the [rqrcode](https://github.com/whomwah/rqrcode), so there are no JavaScript (or Sprockets) dependencies. @@ -110,10 +118,21 @@ To run the devise-otp dummy application in the development environment: - Run the rails console or server (e.g. "rails c") To run the tests for devise-otp against your current Ruby/Rails configuration: -- Navigate to the dummy app directory ("cd test/dummy") -- Create and migrate the database for the test environment ("RAILS\_ENV=test rails db:drop db:create db:migrate") +- Navigate to the dummy app directory + + cd test/dummy + +- Create and migrate the database for the test environment + + RAILS\_ENV=test rails db:drop + RAILS\_ENV=test rails db:create + RAILS\_ENV=test rails db:migrate + - Return to the root directory of devise-otp -- Run "rake test" + +- Execute the tests + + rake test To check Ruby syntax and formatting: - Rubocop: "./bin/rubocop" diff --git a/gemfiles/rails_7.1.gemfile b/gemfiles/rails_7.1.gemfile deleted file mode 100644 index 2af8dab..0000000 --- a/gemfiles/rails_7.1.gemfile +++ /dev/null @@ -1,25 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git" -gem "capybara" -gem "minitest-reporters", ">= 0.5.0" -gem "puma" -gem "rake" -gem "rdoc" -gem "shoulda" -gem "sprockets-rails" -gem "sqlite3", "~> 1.5.0" -gem "timecop", "~> 0.9.10" -gem "standardrb", require: false -gem "rubocop-rails-omakase", require: false -gem "erb_lint", require: false -gem "debug" -gem "rails", "~> 7.1.0" - -install_if -> { Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0") } do - gem "logger" -end - -gemspec path: "../" diff --git a/gemfiles/rails_7.2.gemfile b/gemfiles/rails_7.2.gemfile deleted file mode 100644 index 5e5b8be..0000000 --- a/gemfiles/rails_7.2.gemfile +++ /dev/null @@ -1,21 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git" -gem "capybara" -gem "minitest-reporters", ">= 0.5.0" -gem "puma" -gem "rake" -gem "rdoc" -gem "shoulda" -gem "sprockets-rails" -gem "sqlite3", "~> 1.5.0" -gem "timecop", "~> 0.9.10" -gem "standardrb", require: false -gem "rubocop-rails-omakase", require: false -gem "erb_lint", require: false -gem "debug" -gem "rails", "~> 7.2.0" - -gemspec path: "../" diff --git a/test/dummy/app/assets/stylesheets/application.css b/test/dummy/app/assets/stylesheets/application.css index e852dae..89d2a6f 100644 --- a/test/dummy/app/assets/stylesheets/application.css +++ b/test/dummy/app/assets/stylesheets/application.css @@ -8,7 +8,21 @@ * You're free to add application-wide styles to this file and they'll appear at the top of the * compiled file, but it's generally better to create a new file per style scope. * - *= require devise-otp *= require_self *= require_tree . */ + +@import url("devise-otp.css"); + +nav { + ul { + float: right; + margin: 0; + display: flex; + } + li { + list-style: none; + padding: 0.25rem 0.5rem;; + margin: 0; + } +} diff --git a/test/dummy/app/views/shared/_navbar.html.erb b/test/dummy/app/views/shared/_navbar.html.erb index c7906ec..809ff45 100644 --- a/test/dummy/app/views/shared/_navbar.html.erb +++ b/test/dummy/app/views/shared/_navbar.html.erb @@ -1,15 +1,23 @@ diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb index 30b9020..f9a4832 100644 --- a/test/dummy/db/schema.rb +++ b/test/dummy/db/schema.rb @@ -12,34 +12,34 @@ ActiveRecord::Schema[8.0].define(version: 2025_08_18_030305) do create_table "admins", force: :cascade do |t| - t.string "name" + t.string "authentication_token" t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false + t.datetime "current_sign_in_at", precision: nil + t.string "current_sign_in_ip" t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" - t.datetime "reset_password_sent_at", precision: nil - t.datetime "remember_created_at", precision: nil - t.integer "sign_in_count", default: 0 - t.datetime "current_sign_in_at", precision: nil + t.integer "failed_attempts", default: 0 t.datetime "last_sign_in_at", precision: nil - t.string "current_sign_in_ip" t.string "last_sign_in_ip" - t.integer "failed_attempts", default: 0 - t.string "unlock_token" t.datetime "locked_at", precision: nil - t.string "authentication_token" + t.string "name" t.string "otp_auth_secret" - t.string "otp_recovery_secret" + t.datetime "otp_challenge_expires", precision: nil t.boolean "otp_enabled", default: false, null: false - t.boolean "otp_mandatory", default: false, null: false t.datetime "otp_enabled_on", precision: nil - t.integer "otp_time_drift", default: 0, null: false t.integer "otp_failed_attempts", default: 0, null: false - t.integer "otp_recovery_counter", default: 0, null: false + t.boolean "otp_mandatory", default: false, null: false t.string "otp_persistence_seed" + t.integer "otp_recovery_counter", default: 0, null: false + t.string "otp_recovery_secret" t.string "otp_session_challenge" - t.datetime "otp_challenge_expires", precision: nil + t.integer "otp_time_drift", default: 0, null: false + t.datetime "remember_created_at", precision: nil + t.datetime "reset_password_sent_at", precision: nil + t.string "reset_password_token" + t.integer "sign_in_count", default: 0 + t.string "unlock_token" + t.datetime "updated_at", precision: nil, null: false t.index ["authentication_token"], name: "index_admins_on_authentication_token", unique: true t.index ["email"], name: "index_admins_on_email", unique: true t.index ["otp_challenge_expires"], name: "index_admins_on_otp_challenge_expires" @@ -49,34 +49,34 @@ end create_table "lockable_users", force: :cascade do |t| - t.string "name" + t.string "authentication_token" t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false + t.datetime "current_sign_in_at", precision: nil + t.string "current_sign_in_ip" t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" - t.datetime "reset_password_sent_at", precision: nil - t.datetime "remember_created_at", precision: nil - t.integer "sign_in_count", default: 0 - t.datetime "current_sign_in_at", precision: nil + t.integer "failed_attempts", default: 0 t.datetime "last_sign_in_at", precision: nil - t.string "current_sign_in_ip" t.string "last_sign_in_ip" - t.integer "failed_attempts", default: 0 - t.string "unlock_token" t.datetime "locked_at", precision: nil - t.string "authentication_token" + t.string "name" t.string "otp_auth_secret" - t.string "otp_recovery_secret" + t.datetime "otp_challenge_expires", precision: nil t.boolean "otp_enabled", default: false, null: false - t.boolean "otp_mandatory", default: false, null: false t.datetime "otp_enabled_on", precision: nil - t.integer "otp_time_drift", default: 0, null: false t.integer "otp_failed_attempts", default: 0, null: false - t.integer "otp_recovery_counter", default: 0, null: false + t.boolean "otp_mandatory", default: false, null: false t.string "otp_persistence_seed" + t.integer "otp_recovery_counter", default: 0, null: false + t.string "otp_recovery_secret" t.string "otp_session_challenge" - t.datetime "otp_challenge_expires", precision: nil + t.integer "otp_time_drift", default: 0, null: false + t.datetime "remember_created_at", precision: nil + t.datetime "reset_password_sent_at", precision: nil + t.string "reset_password_token" + t.integer "sign_in_count", default: 0 + t.string "unlock_token" + t.datetime "updated_at", precision: nil, null: false t.index ["authentication_token"], name: "index_lockable_users_on_authentication_token", unique: true t.index ["email"], name: "index_lockable_users_on_email", unique: true t.index ["otp_challenge_expires"], name: "index_lockable_users_on_otp_challenge_expires" @@ -86,23 +86,23 @@ end create_table "non_otp_users", force: :cascade do |t| - t.string "name" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.string "authentication_token" + t.datetime "created_at", precision: nil, null: false + t.datetime "current_sign_in_at", precision: nil + t.string "current_sign_in_ip" t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false + t.integer "failed_attempts", default: 0 + t.datetime "last_sign_in_at", precision: nil + t.string "last_sign_in_ip" + t.datetime "locked_at", precision: nil + t.string "name" + t.datetime "remember_created_at", precision: nil + t.datetime "reset_password_sent_at", precision: nil t.string "reset_password_token" - t.datetime "reset_password_sent_at" - t.datetime "remember_created_at" t.integer "sign_in_count", default: 0 - t.datetime "current_sign_in_at" - t.datetime "last_sign_in_at" - t.string "current_sign_in_ip" - t.string "last_sign_in_ip" - t.integer "failed_attempts", default: 0 t.string "unlock_token" - t.datetime "locked_at" - t.string "authentication_token" + t.datetime "updated_at", precision: nil, null: false t.index ["authentication_token"], name: "index_non_otp_users_on_authentication_token", unique: true t.index ["email"], name: "index_non_otp_users_on_email", unique: true t.index ["reset_password_token"], name: "index_non_otp_users_on_reset_password_token", unique: true @@ -110,35 +110,35 @@ end create_table "posts", force: :cascade do |t| - t.string "title" t.text "body" t.datetime "created_at", precision: nil, null: false + t.string "title" t.datetime "updated_at", precision: nil, null: false end create_table "rememberable_users", force: :cascade do |t| - t.string "name" + t.datetime "current_sign_in_at" + t.string "current_sign_in_ip" t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" - t.datetime "reset_password_sent_at" - t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 - t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" - t.string "current_sign_in_ip" t.string "last_sign_in_ip" + t.string "name" t.string "otp_auth_secret" - t.string "otp_recovery_secret" + t.datetime "otp_challenge_expires" t.boolean "otp_enabled", default: false, null: false - t.boolean "otp_mandatory", default: false, null: false t.datetime "otp_enabled_on" - t.integer "otp_time_drift", default: 0, null: false t.integer "otp_failed_attempts", default: 0, null: false - t.integer "otp_recovery_counter", default: 0, null: false + t.boolean "otp_mandatory", default: false, null: false t.string "otp_persistence_seed" + t.integer "otp_recovery_counter", default: 0, null: false + t.string "otp_recovery_secret" t.string "otp_session_challenge" - t.datetime "otp_challenge_expires" + t.integer "otp_time_drift", default: 0, null: false + t.datetime "remember_created_at" + t.datetime "reset_password_sent_at" + t.string "reset_password_token" + t.integer "sign_in_count", default: 0 t.index ["email"], name: "index_rememberable_users_on_email", unique: true t.index ["otp_challenge_expires"], name: "index_rememberable_users_on_otp_challenge_expires" t.index ["otp_session_challenge"], name: "index_rememberable_users_on_otp_session_challenge", unique: true @@ -146,34 +146,34 @@ end create_table "users", force: :cascade do |t| - t.string "name" + t.string "authentication_token" t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false + t.datetime "current_sign_in_at", precision: nil + t.string "current_sign_in_ip" t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" - t.datetime "reset_password_sent_at", precision: nil - t.datetime "remember_created_at", precision: nil - t.integer "sign_in_count", default: 0 - t.datetime "current_sign_in_at", precision: nil + t.integer "failed_attempts", default: 0 t.datetime "last_sign_in_at", precision: nil - t.string "current_sign_in_ip" t.string "last_sign_in_ip" - t.integer "failed_attempts", default: 0 - t.string "unlock_token" t.datetime "locked_at", precision: nil - t.string "authentication_token" + t.string "name" t.string "otp_auth_secret" - t.string "otp_recovery_secret" + t.datetime "otp_challenge_expires", precision: nil t.boolean "otp_enabled", default: false, null: false - t.boolean "otp_mandatory", default: false, null: false t.datetime "otp_enabled_on", precision: nil - t.integer "otp_time_drift", default: 0, null: false t.integer "otp_failed_attempts", default: 0, null: false - t.integer "otp_recovery_counter", default: 0, null: false + t.boolean "otp_mandatory", default: false, null: false t.string "otp_persistence_seed" + t.integer "otp_recovery_counter", default: 0, null: false + t.string "otp_recovery_secret" t.string "otp_session_challenge" - t.datetime "otp_challenge_expires", precision: nil + t.integer "otp_time_drift", default: 0, null: false + t.datetime "remember_created_at", precision: nil + t.datetime "reset_password_sent_at", precision: nil + t.string "reset_password_token" + t.integer "sign_in_count", default: 0 + t.string "unlock_token" + t.datetime "updated_at", precision: nil, null: false t.index ["authentication_token"], name: "index_users_on_authentication_token", unique: true t.index ["email"], name: "index_users_on_email", unique: true t.index ["otp_challenge_expires"], name: "index_users_on_otp_challenge_expires"