devise-otp 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +4 -4
  3. data/.gitignore +1 -3
  4. data/Appraisals +0 -14
  5. data/CHANGELOG.md +34 -1
  6. data/README.md +15 -1
  7. data/Rakefile +0 -11
  8. data/devise-otp.gemspec +4 -1
  9. data/lib/devise-otp/version.rb +1 -1
  10. data/lib/devise_otp_authenticatable/hooks/refreshable.rb +3 -1
  11. data/test/dummy/app/controllers/admin_posts_controller.rb +0 -72
  12. data/test/dummy/app/controllers/non_otp_posts_controller.rb +13 -0
  13. data/test/dummy/app/controllers/posts_controller.rb +8 -2
  14. data/test/dummy/app/models/admin.rb +1 -13
  15. data/test/dummy/app/models/non_otp_user.rb +4 -0
  16. data/test/dummy/app/models/post.rb +1 -1
  17. data/test/dummy/app/models/user.rb +1 -13
  18. data/test/dummy/app/views/admin_posts/index.html.erb +0 -7
  19. data/test/dummy/app/views/non_otp_posts/index.html.erb +18 -0
  20. data/test/dummy/config/application.rb +0 -7
  21. data/test/dummy/config/database.yml +20 -13
  22. data/test/dummy/config/routes.rb +2 -0
  23. data/test/dummy/db/migrate/20250718092451_create_non_otp_users.rb +9 -0
  24. data/test/dummy/db/migrate/20250718092536_add_devise_to_non_otp_users.rb +52 -0
  25. data/test/dummy/db/schema.rb +118 -0
  26. data/test/dummy/db/seeds.rb +24 -0
  27. data/test/integration/non_otp_user_models_test.rb +21 -0
  28. data/test/integration_tests_helper.rb +11 -0
  29. data/test/test_helper.rb +0 -5
  30. metadata +16 -13
  31. data/gemfiles/rails_7.0.gemfile +0 -25
  32. data/test/dummy/app/views/admin_posts/_form.html.erb +0 -25
  33. data/test/dummy/app/views/admin_posts/edit.html.erb +0 -6
  34. data/test/dummy/app/views/admin_posts/new.html.erb +0 -5
  35. data/test/dummy/app/views/admin_posts/show.html.erb +0 -15
  36. data/test/orm/active_record.rb +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a2884c76d255b2c2bb3b40859db7018c5668d49fcb6897141dacd8d7df0bc62
4
- data.tar.gz: 3c7ee30dd96a9711b4b4194cb2b94cea6abb06113dda2ef58b70c9a96c998ec4
3
+ metadata.gz: f329ff1fa9732961646ad4169f89921f3429a6aa86744486eafa21a891fd1628
4
+ data.tar.gz: 8a4797664986ea6c11e21a50a43d5aeda471000ce5610e91451ec37f3d231121
5
5
  SHA512:
6
- metadata.gz: b099c20c5c6d76fc2e1226511615bb43f2b081620f01bbf1bcc939cec66e286aa3536f8a654495907607e42d23e7f3efd9bb3377ed3a1a8a6ef522ec5e9568e1
7
- data.tar.gz: 731d1dc34877d0fe91bb092b33ce4ee7c302c4cb75183daa3ff67a0df7f6d242bc86b3ab4e0923683b8408c8f1e6bab8e8a1d1c5fa10b9b0e104617a9b87ded7
6
+ metadata.gz: e42b34ea4259e698e75f6408b6d0a0021b39f934d960e039a305ee2f9dc7d443a8dbdc9f99e1df452b75602430e601bf7ece1214b11766ba558fb3806d07355c
7
+ data.tar.gz: 8c1bde0740a5f96dfc440e976c568a8ef0b7f0ac91e7af620d14aea1e8208811e6339384dc9d7b6363c6f4ed9de363c1637422a82b5f5f5c6696fa17c9879764
@@ -12,15 +12,14 @@ jobs:
12
12
  fail-fast: false
13
13
  matrix:
14
14
  ruby:
15
+ - '3.4'
15
16
  - '3.3'
16
17
  - '3.2'
17
- - '3.1'
18
18
  - 'head'
19
19
  rails:
20
20
  - rails_8.0
21
21
  - rails_7.2
22
22
  - rails_7.1
23
- - rails_7.0
24
23
  exclude:
25
24
  - ruby: '3.1'
26
25
  rails: 'rails_8.0'
@@ -38,7 +37,8 @@ jobs:
38
37
  ruby-version: ${{ matrix.ruby }}
39
38
  bundler-cache: true
40
39
 
40
+ - name: Create database
41
+ run: cd test/dummy && RAILS_ENV=test bundle exec rails db:create db:migrate --trace
42
+
41
43
  - name: Run tests
42
- env:
43
- DEVISE_ORM: active_record
44
44
  run: bundle exec rake test
data/.gitignore CHANGED
@@ -34,9 +34,7 @@ lib/bundler/man
34
34
  ## PROJECT::SPECIFIC
35
35
  test/dummy/log/**
36
36
  test/dummy/tmp/**
37
- test/dummy/db/*.sqlite3
38
- test/dummy/db/*.sqlite3-shm
39
- test/dummy/db/*.sqlite3-wal
37
+ test/dummy/storage/**
40
38
 
41
39
  # Ignore Gemfile.lock
42
40
  Gemfile.lock
data/Appraisals CHANGED
@@ -1,19 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- appraise 'rails_7.0' do
4
- gem 'rails', '~> 7.0.0'
5
- gem 'sqlite3', '~> 1.5.0'
6
-
7
- # Fix: LoadError: cannot load such file -- base64
8
- install_if '-> { Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.3.0") }' do
9
- gem 'base64'
10
- gem 'bigdecimal'
11
- gem 'mutex_m'
12
- gem 'drb'
13
- gem 'logger'
14
- end
15
- end
16
-
17
3
  appraise 'rails_7.1' do
18
4
  gem 'rails', '~> 7.1.0'
19
5
  gem 'sqlite3', '~> 1.5.0'
data/CHANGELOG.md CHANGED
@@ -2,7 +2,40 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
- - Upgrade gemspec to support Rails v7.2
5
+ ## 1.1.0
6
+
7
+ Bug fixes:
8
+ - Update refreshable hook to ensure that user models without Devise OTP can still sign in
9
+ - Add tests for non-OTP user models to confirm resolution
10
+
11
+ Improvements:
12
+ - Remove references to MongoDB from test suite
13
+ - Standardize test application's database configuration
14
+ - Add Development Instructions to README
15
+
16
+ ## 1.0.1
17
+ - Add support for Ruby 3.4
18
+ - Set minimum Ruby version to 3.2
19
+ - Set miminum Rails version to 7.1
20
+ - Add MIT license type to gemspec
21
+ - Correct Devise spelling error in README
22
+
23
+ ## 1.0.0
24
+ - Add support for Rails 8
25
+ - Generate QR Codes as SVG
26
+ - Fix Issue with Invalid Token Message
27
+ - Simplify OTP Credentials Controller
28
+ - Expand Flash Message Tests
29
+ - Use Appraisal gem to against older Rails versions
30
+
31
+ ## 0.8.0
32
+ - Add support for Rails 7.2 and drop support for Rails 6.1
33
+ - Fix issue with scoped redirects for non-default resources
34
+ - Add migration version numbers
35
+ - Cleanup old docs
36
+
37
+ ## 0.7.1
38
+ - Fix host and port for 3rd-party tests
6
39
 
7
40
  ## 0.7.0
8
41
 
data/README.md CHANGED
@@ -13,7 +13,7 @@ Some of the compatible token devices are:
13
13
  * [Google Authenticator](https://code.google.com/p/google-authenticator/)
14
14
  * [FreeOTP](https://fedorahosted.org/freeotp/)
15
15
 
16
- Device OTP was recently updated to work with Rails 7+ and Turbo.
16
+ Devise OTP was recently updated to work with Rails 7+ and Turbo.
17
17
 
18
18
  ## Sponsor
19
19
 
@@ -101,6 +101,20 @@ Enforcing mandatory OTP requires adding the ensure\_mandatory\_{scope}\_otp! met
101
101
  before_action :authenticate_user!
102
102
  before_action :ensure_mandatory_user_otp!
103
103
 
104
+ ## Development Instructions
105
+ WARNING: Make sure to use the latest Ruby/Rails versions for development. If using older versions of Ruby/Rails, you will need to install all gems for all versions via Appraisal ("bundle exec appraisal install").
106
+
107
+ To run the devise-otp dummy application in the development environment:
108
+ - Navigate to the dummy app directory ("cd test/dummy")
109
+ - Create and seed the database ("rails db:reset")
110
+ - Run the rails console or server (e.g. "rails c")
111
+
112
+ To run the tests for devise-otp against your current Ruby/Rails configuration:
113
+ - Navigate to the dummy app directory ("cd test/dummy")
114
+ - Create and migrate the database for the test environment ("RAILS\_ENV=test rails db:drop db:create db:migrate")
115
+ - Return to the root directory of devise-otp
116
+ - Run "rake test"
117
+
104
118
  ## Authors
105
119
 
106
120
  The project was originally started by Lele Forzani by forking [devise_google_authenticator](https://github.com/AsteriskLabs/devise_google_authenticator) and still contains some devise_google_authenticator code. It's now maintained by [Josef Strzibny](https://github.com/strzibny/) and [Laney Stroup](https://github.com/strouptl).
data/Rakefile CHANGED
@@ -28,14 +28,3 @@ Rake::TestTask.new(:test) do |test|
28
28
  test.pattern = "test/**/*_test.rb"
29
29
  test.verbose = true
30
30
  end
31
-
32
- desc "Run Devise tests for all ORMs."
33
- task :tests do
34
- Dir[File.join(File.dirname(__FILE__), "test", "orm", "*.rb")].each do |file|
35
- orm = File.basename(file).split(".").first
36
- system "rake test DEVISE_ORM=#{orm}"
37
- end
38
- end
39
-
40
- desc "Default: run tests for all ORMs."
41
- task default: :tests
data/devise-otp.gemspec CHANGED
@@ -10,11 +10,14 @@ Gem::Specification.new do |gem|
10
10
  gem.description = "OTP authentication for Devise"
11
11
  gem.summary = "Time Based OTP/rfc6238 compatible authentication for Devise"
12
12
  gem.homepage = "https://github.com/wmlele/devise-otp"
13
+ gem.license = "MIT"
13
14
 
14
15
  gem.files = `git ls-files`.split($/)
15
16
  gem.require_paths = ["lib"]
16
17
 
17
- gem.add_dependency "rails", ">= 7.0"
18
+ gem.required_ruby_version = ">= 3.2.0"
19
+
20
+ gem.add_dependency "rails", ">= 7.1"
18
21
  gem.add_dependency "devise", ">= 4.8.0", "< 5.0"
19
22
  gem.add_dependency "rotp", ">= 2.0.0"
20
23
  gem.add_dependency "rqrcode", "~> 2.0"
@@ -1,5 +1,5 @@
1
1
  module Devise
2
2
  module OTP
3
- VERSION = "1.0.0"
3
+ VERSION = "1.1.0"
4
4
  end
5
5
  end
@@ -1,5 +1,7 @@
1
1
  # After each sign in, update credentials refreshed at time
2
2
  Warden::Manager.after_set_user except: :fetch do |record, warden, options|
3
- warden.session(options[:scope])["credentials_refreshed_at"] = (Time.now + record.class.otp_credentials_refresh)
3
+ if defined?(record.class.otp_credentials_refresh)
4
+ warden.session(options[:scope])["credentials_refreshed_at"] = (Time.now + record.class.otp_credentials_refresh)
5
+ end
4
6
  end
5
7
 
@@ -1,8 +1,6 @@
1
1
  class AdminPostsController < ApplicationController
2
2
  before_action :authenticate_admin!
3
3
 
4
- # GET /posts
5
- # GET /posts.json
6
4
  def index
7
5
  @posts = Post.all
8
6
 
@@ -12,74 +10,4 @@ class AdminPostsController < ApplicationController
12
10
  end
13
11
  end
14
12
 
15
- # GET /posts/1
16
- # GET /posts/1.json
17
- def show
18
- @post = Post.find(params[:id])
19
-
20
- respond_to do |format|
21
- format.html # show.html.erb
22
- format.json { render json: @post }
23
- end
24
- end
25
-
26
- # GET /posts/new
27
- # GET /posts/new.json
28
- def new
29
- @post = Post.new
30
-
31
- respond_to do |format|
32
- format.html # new.html.erb
33
- format.json { render json: @post }
34
- end
35
- end
36
-
37
- # GET /posts/1/edit
38
- def edit
39
- @post = Post.find(params[:id])
40
- end
41
-
42
- # POST /posts
43
- # POST /posts.json
44
- def create
45
- @post = Post.new(params[:post])
46
-
47
- respond_to do |format|
48
- if @post.save
49
- format.html { redirect_to @post, notice: "Post was successfully created." }
50
- format.json { render json: @post, status: :created, location: @post }
51
- else
52
- format.html { render action: "new" }
53
- format.json { render json: @post.errors, status: :unprocessable_entity }
54
- end
55
- end
56
- end
57
-
58
- # PUT /posts/1
59
- # PUT /posts/1.json
60
- def update
61
- @post = Post.find(params[:id])
62
-
63
- respond_to do |format|
64
- if @post.update_attributes(params[:post])
65
- format.html { redirect_to @post, notice: "Post was successfully updated." }
66
- format.json { head :ok }
67
- else
68
- format.html { render action: "edit" }
69
- format.json { render json: @post.errors, status: :unprocessable_entity }
70
- end
71
- end
72
- end
73
-
74
- # DELETE /posts/1
75
- # DELETE /posts/1.json
76
- def destroy
77
- @post = Post.find(params[:id])
78
- @post.destroy
79
-
80
- respond_to do |format|
81
- format.html { redirect_to posts_url }
82
- format.json { head :ok }
83
- end
84
- end
85
13
  end
@@ -0,0 +1,13 @@
1
+ class NonOtpPostsController < ApplicationController
2
+ before_action :authenticate_non_otp_user!
3
+
4
+ def index
5
+ @posts = Post.all
6
+
7
+ respond_to do |format|
8
+ format.html # index.html.erb
9
+ format.json { render json: @posts }
10
+ end
11
+ end
12
+
13
+ end
@@ -42,7 +42,7 @@ class PostsController < ApplicationController
42
42
  # POST /posts
43
43
  # POST /posts.json
44
44
  def create
45
- @post = Post.new(params[:post])
45
+ @post = Post.new(post_params)
46
46
 
47
47
  respond_to do |format|
48
48
  if @post.save
@@ -61,7 +61,7 @@ class PostsController < ApplicationController
61
61
  @post = Post.find(params[:id])
62
62
 
63
63
  respond_to do |format|
64
- if @post.update_attributes(params[:post])
64
+ if @post.update_attributes(post_params)
65
65
  format.html { redirect_to @post, notice: "Post was successfully updated." }
66
66
  format.json { head :ok }
67
67
  else
@@ -82,4 +82,10 @@ class PostsController < ApplicationController
82
82
  format.json { head :ok }
83
83
  end
84
84
  end
85
+
86
+ private
87
+
88
+ def post_params
89
+ params.require(:post).permit(:title, :body)
90
+ end
85
91
  end
@@ -1,16 +1,4 @@
1
- class Admin < PARENT_MODEL_CLASS
2
- if DEVISE_ORM == :mongoid
3
- include Mongoid::Document
4
-
5
- ## Database authenticatable
6
- field :email, type: String, null: false, default: ""
7
- field :encrypted_password, type: String, null: false, default: ""
8
-
9
- ## Recoverable
10
- field :reset_password_token, type: String
11
- field :reset_password_sent_at, type: Time
12
- end
13
-
1
+ class Admin < ActiveRecord::Base
14
2
  devise :otp_authenticatable, :database_authenticatable, :registerable,
15
3
  :trackable, :validatable
16
4
 
@@ -0,0 +1,4 @@
1
+ class NonOtpUser < ActiveRecord::Base
2
+ devise :database_authenticatable, :registerable, :trackable, :validatable
3
+
4
+ end
@@ -1,2 +1,2 @@
1
- class Post < PARENT_MODEL_CLASS
1
+ class Post < ActiveRecord::Base
2
2
  end
@@ -1,16 +1,4 @@
1
- class User < PARENT_MODEL_CLASS
2
- if DEVISE_ORM == :mongoid
3
- include Mongoid::Document
4
-
5
- ## Database authenticatable
6
- field :email, type: String, null: false, default: ""
7
- field :encrypted_password, type: String, null: false, default: ""
8
-
9
- ## Recoverable
10
- field :reset_password_token, type: String
11
- field :reset_password_sent_at, type: Time
12
- end
13
-
1
+ class User < ActiveRecord::Base
14
2
  devise :otp_authenticatable, :database_authenticatable, :registerable,
15
3
  :trackable, :validatable
16
4
 
@@ -13,13 +13,6 @@
13
13
  <tr>
14
14
  <td><%= post.title %></td>
15
15
  <td><%= post.body %></td>
16
- <td><%= link_to 'Show', post %></td>
17
- <td><%= link_to 'Edit', edit_admin_post_path(post) %></td>
18
- <td><%= link_to 'Destroy', [:admin, post], confirm: 'Are you sure?', method: :delete %></td>
19
16
  </tr>
20
17
  <% end %>
21
18
  </table>
22
-
23
- <br />
24
-
25
- <%= link_to 'New Post', new_admin_post_path %>
@@ -0,0 +1,18 @@
1
+ <h1>Listing posts</h1>
2
+
3
+ <table>
4
+ <tr>
5
+ <th>Title</th>
6
+ <th>Body</th>
7
+ <th></th>
8
+ <th></th>
9
+ <th></th>
10
+ </tr>
11
+
12
+ <% @posts.each do |post| %>
13
+ <tr>
14
+ <td><%= post.title %></td>
15
+ <td><%= post.body %></td>
16
+ </tr>
17
+ <% end %>
18
+ </table>
@@ -9,13 +9,6 @@ require "sprockets/railtie"
9
9
  # require "rails/test_unit/railtie"
10
10
 
11
11
  Bundler.require
12
- Bundler.require(:default, DEVISE_ORM) if defined?(Bundler)
13
-
14
- begin
15
- require "#{DEVISE_ORM}/railtie"
16
- rescue LoadError
17
- end
18
- PARENT_MODEL_CLASS = (DEVISE_ORM == :active_record) ? ActiveRecord::Base : Object
19
12
 
20
13
  require "devise"
21
14
  require "devise-otp"
@@ -1,25 +1,32 @@
1
- # SQLite version 3.x
1
+ # SQLite. Versions 3.8.0 and up are supported.
2
2
  # gem install sqlite3
3
3
  #
4
4
  # Ensure the SQLite 3 gem is defined in your Gemfile
5
- # gem 'sqlite3'
6
- development:
5
+ # gem "sqlite3"
6
+ #
7
+ default: &default
7
8
  adapter: sqlite3
8
- database: ":memory:"
9
- pool: 5
9
+ pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
10
10
  timeout: 5000
11
11
 
12
+ development:
13
+ <<: *default
14
+ database: storage/development.sqlite3
15
+
12
16
  # Warning: The database defined as "test" will be erased and
13
17
  # re-generated from your development database when you run "rake".
14
18
  # Do not set this db to the same as development or production.
15
19
  test:
16
- adapter: sqlite3
17
- database: db/test.sqlite3
18
- pool: 5
19
- timeout: 5000
20
+ <<: *default
21
+ database: storage/test.sqlite3
22
+
20
23
 
24
+ # SQLite3 write its data on the local filesystem, as such it requires
25
+ # persistent disks. If you are deploying to a managed service, you should
26
+ # make sure it provides disk persistence, as many don't.
27
+ #
28
+ # Similarly, if you deploy your application as a Docker container, you must
29
+ # ensure the database is located in a persisted volume.
21
30
  production:
22
- adapter: sqlite3
23
- database: db/production.sqlite3
24
- pool: 5
25
- timeout: 5000
31
+ <<: *default
32
+ # database: path/to/persistent/storage/production.sqlite3
@@ -1,9 +1,11 @@
1
1
  Dummy::Application.routes.draw do
2
2
  devise_for :admins
3
3
  devise_for :users
4
+ devise_for :non_otp_users
4
5
 
5
6
  resources :posts
6
7
  resources :admin_posts
8
+ resources :non_otp_posts
7
9
 
8
10
  root to: "base#home"
9
11
  end
@@ -0,0 +1,9 @@
1
+ class CreateNonOtpUsers < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :non_otp_users do |t|
4
+ t.string :name
5
+
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,52 @@
1
+ class AddDeviseToNonOtpUsers < ActiveRecord::Migration[5.0]
2
+ def self.up
3
+ change_table(:non_otp_users) do |t|
4
+ ## Database authenticatable
5
+ t.string :email, null: false, default: ""
6
+ t.string :encrypted_password, null: false, default: ""
7
+
8
+ ## Recoverable
9
+ t.string :reset_password_token
10
+ t.datetime :reset_password_sent_at
11
+
12
+ ## Rememberable
13
+ t.datetime :remember_created_at
14
+
15
+ ## Trackable
16
+ t.integer :sign_in_count, default: 0
17
+ t.datetime :current_sign_in_at
18
+ t.datetime :last_sign_in_at
19
+ t.string :current_sign_in_ip
20
+ t.string :last_sign_in_ip
21
+
22
+ ## Confirmable
23
+ # t.string :confirmation_token
24
+ # t.datetime :confirmed_at
25
+ # t.datetime :confirmation_sent_at
26
+ # t.string :unconfirmed_email # Only if using reconfirmable
27
+
28
+ ## Lockable
29
+ t.integer :failed_attempts, default: 0 # Only if lock strategy is :failed_attempts
30
+ t.string :unlock_token # Only if unlock strategy is :email or :both
31
+ t.datetime :locked_at
32
+
33
+ ## Token authenticatable
34
+ t.string :authentication_token
35
+
36
+ # Uncomment below if timestamps were not included in your original model.
37
+ # t.timestamps
38
+ end
39
+
40
+ add_index :non_otp_users, :email, unique: true
41
+ add_index :non_otp_users, :reset_password_token, unique: true
42
+ # add_index :non_otp_users, :confirmation_token, :unique => true
43
+ add_index :non_otp_users, :unlock_token, unique: true
44
+ add_index :non_otp_users, :authentication_token, unique: true
45
+ end
46
+
47
+ def self.down
48
+ # By default, we don't want to make any assumption about how to roll back a migration when your
49
+ # model already existed. Please edit below which fields you would like to remove in this migration.
50
+ raise ActiveRecord::IrreversibleMigration
51
+ end
52
+ end
@@ -0,0 +1,118 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
+ # be faster and is potentially less error prone than running all of your
8
+ # migrations from scratch. Old migrations may fail to apply correctly if those
9
+ # migrations use external dependencies or application code.
10
+ #
11
+ # It's strongly recommended that you check this file into your version control system.
12
+
13
+ ActiveRecord::Schema[8.0].define(version: 2025_07_18_092536) do
14
+ create_table "admins", force: :cascade do |t|
15
+ t.string "name"
16
+ t.datetime "created_at", precision: nil, null: false
17
+ t.datetime "updated_at", precision: nil, null: false
18
+ t.string "email", default: "", null: false
19
+ t.string "encrypted_password", default: "", null: false
20
+ t.string "reset_password_token"
21
+ t.datetime "reset_password_sent_at", precision: nil
22
+ t.datetime "remember_created_at", precision: nil
23
+ t.integer "sign_in_count", default: 0
24
+ t.datetime "current_sign_in_at", precision: nil
25
+ t.datetime "last_sign_in_at", precision: nil
26
+ t.string "current_sign_in_ip"
27
+ t.string "last_sign_in_ip"
28
+ t.integer "failed_attempts", default: 0
29
+ t.string "unlock_token"
30
+ t.datetime "locked_at", precision: nil
31
+ t.string "authentication_token"
32
+ t.string "otp_auth_secret"
33
+ t.string "otp_recovery_secret"
34
+ t.boolean "otp_enabled", default: false, null: false
35
+ t.boolean "otp_mandatory", default: false, null: false
36
+ t.datetime "otp_enabled_on", precision: nil
37
+ t.integer "otp_time_drift", default: 0, null: false
38
+ t.integer "otp_failed_attempts", default: 0, null: false
39
+ t.integer "otp_recovery_counter", default: 0, null: false
40
+ t.string "otp_persistence_seed"
41
+ t.string "otp_session_challenge"
42
+ t.datetime "otp_challenge_expires", precision: nil
43
+ t.index ["authentication_token"], name: "index_admins_on_authentication_token", unique: true
44
+ t.index ["email"], name: "index_admins_on_email", unique: true
45
+ t.index ["otp_challenge_expires"], name: "index_admins_on_otp_challenge_expires"
46
+ t.index ["otp_session_challenge"], name: "index_admins_on_otp_session_challenge", unique: true
47
+ t.index ["reset_password_token"], name: "index_admins_on_reset_password_token", unique: true
48
+ t.index ["unlock_token"], name: "index_admins_on_unlock_token", unique: true
49
+ end
50
+
51
+ create_table "non_otp_users", force: :cascade do |t|
52
+ t.string "name"
53
+ t.datetime "created_at", null: false
54
+ t.datetime "updated_at", null: false
55
+ t.string "email", default: "", null: false
56
+ t.string "encrypted_password", default: "", null: false
57
+ t.string "reset_password_token"
58
+ t.datetime "reset_password_sent_at"
59
+ t.datetime "remember_created_at"
60
+ t.integer "sign_in_count", default: 0
61
+ t.datetime "current_sign_in_at"
62
+ t.datetime "last_sign_in_at"
63
+ t.string "current_sign_in_ip"
64
+ t.string "last_sign_in_ip"
65
+ t.integer "failed_attempts", default: 0
66
+ t.string "unlock_token"
67
+ t.datetime "locked_at"
68
+ t.string "authentication_token"
69
+ t.index ["authentication_token"], name: "index_non_otp_users_on_authentication_token", unique: true
70
+ t.index ["email"], name: "index_non_otp_users_on_email", unique: true
71
+ t.index ["reset_password_token"], name: "index_non_otp_users_on_reset_password_token", unique: true
72
+ t.index ["unlock_token"], name: "index_non_otp_users_on_unlock_token", unique: true
73
+ end
74
+
75
+ create_table "posts", force: :cascade do |t|
76
+ t.string "title"
77
+ t.text "body"
78
+ t.datetime "created_at", precision: nil, null: false
79
+ t.datetime "updated_at", precision: nil, null: false
80
+ end
81
+
82
+ create_table "users", force: :cascade do |t|
83
+ t.string "name"
84
+ t.datetime "created_at", precision: nil, null: false
85
+ t.datetime "updated_at", precision: nil, null: false
86
+ t.string "email", default: "", null: false
87
+ t.string "encrypted_password", default: "", null: false
88
+ t.string "reset_password_token"
89
+ t.datetime "reset_password_sent_at", precision: nil
90
+ t.datetime "remember_created_at", precision: nil
91
+ t.integer "sign_in_count", default: 0
92
+ t.datetime "current_sign_in_at", precision: nil
93
+ t.datetime "last_sign_in_at", precision: nil
94
+ t.string "current_sign_in_ip"
95
+ t.string "last_sign_in_ip"
96
+ t.integer "failed_attempts", default: 0
97
+ t.string "unlock_token"
98
+ t.datetime "locked_at", precision: nil
99
+ t.string "authentication_token"
100
+ t.string "otp_auth_secret"
101
+ t.string "otp_recovery_secret"
102
+ t.boolean "otp_enabled", default: false, null: false
103
+ t.boolean "otp_mandatory", default: false, null: false
104
+ t.datetime "otp_enabled_on", precision: nil
105
+ t.integer "otp_time_drift", default: 0, null: false
106
+ t.integer "otp_failed_attempts", default: 0, null: false
107
+ t.integer "otp_recovery_counter", default: 0, null: false
108
+ t.string "otp_persistence_seed"
109
+ t.string "otp_session_challenge"
110
+ t.datetime "otp_challenge_expires", precision: nil
111
+ t.index ["authentication_token"], name: "index_users_on_authentication_token", unique: true
112
+ t.index ["email"], name: "index_users_on_email", unique: true
113
+ t.index ["otp_challenge_expires"], name: "index_users_on_otp_challenge_expires"
114
+ t.index ["otp_session_challenge"], name: "index_users_on_otp_session_challenge", unique: true
115
+ t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
116
+ t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
117
+ end
118
+ end
@@ -0,0 +1,24 @@
1
+ Admin.create(
2
+ name: "Test Admin",
3
+ email: "admin@devise-otp.local",
4
+ password: "Pass1234"
5
+ )
6
+
7
+ User.create(
8
+ name: "Test User",
9
+ email: "user@devise-otp.local",
10
+ password: "Pass1234"
11
+ )
12
+
13
+ NonOtpUser.create(
14
+ name: "Non OTP User",
15
+ email: "non-otp-user@devise-otp.local",
16
+ password: "Pass1234"
17
+ )
18
+
19
+ 5.times do |n|
20
+ Post.create(
21
+ title: "Post #{n + 1}",
22
+ body: "This is post #{n + 1}."
23
+ )
24
+ end
@@ -0,0 +1,21 @@
1
+ require "test_helper"
2
+ require "integration_tests_helper"
3
+
4
+ class NonOtpUserModelsTest < ActionDispatch::IntegrationTest
5
+
6
+ def teardown
7
+ Capybara.reset_sessions!
8
+ end
9
+
10
+ test "a non-OTP user should be able to sign in without error" do
11
+ create_non_otp_user
12
+
13
+ visit non_otp_posts_path
14
+ fill_in "non_otp_user_email", with: "non-otp-user@email.invalid"
15
+ fill_in "non_otp_user_password", with: "12345678"
16
+ page.has_content?("Log in") ? click_button("Log in") : click_button("Sign in")
17
+
18
+ assert_equal non_otp_posts_path, current_path
19
+ end
20
+
21
+ end
@@ -27,6 +27,17 @@ class ActionDispatch::IntegrationTest
27
27
  end
28
28
  end
29
29
 
30
+ def create_non_otp_user
31
+ @non_otp_user ||= begin
32
+ non_otp_user = NonOtpUser.create!(
33
+ email: "non-otp-user@email.invalid",
34
+ password: "12345678",
35
+ password_confirmation: "12345678"
36
+ )
37
+ non_otp_user
38
+ end
39
+ end
40
+
30
41
  def enable_otp_and_sign_in_with_otp
31
42
  enable_otp_and_sign_in.tap do |user|
32
43
  fill_in "token", with: ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
data/test/test_helper.rb CHANGED
@@ -1,17 +1,12 @@
1
1
  ENV["RAILS_ENV"] = "test"
2
- DEVISE_ORM = (ENV["DEVISE_ORM"] || :active_record).to_sym
3
2
 
4
- puts "\n==> Devise.orm = #{DEVISE_ORM.inspect}"
5
3
  require "dummy/config/environment"
6
- require "orm/#{DEVISE_ORM}"
7
4
  require "rails/test_help"
8
5
  require "capybara/rails"
9
6
  require "minitest/reporters"
10
7
 
11
8
  Minitest::Reporters.use!
12
9
 
13
- # I18n.load_path << File.expand_path("../support/locale/en.yml", __FILE__) if DEVISE_ORM == :mongoid
14
-
15
10
  # ActiveSupport::Deprecation.silenced = true
16
11
 
17
12
  class ActionDispatch::IntegrationTest
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise-otp
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lele Forzani
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2024-11-21 00:00:00.000000000 Z
13
+ date: 2025-07-20 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rails
@@ -18,14 +18,14 @@ dependencies:
18
18
  requirements:
19
19
  - - ">="
20
20
  - !ruby/object:Gem::Version
21
- version: '7.0'
21
+ version: '7.1'
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
- version: '7.0'
28
+ version: '7.1'
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: devise
31
31
  requirement: !ruby/object:Gem::Requirement
@@ -104,7 +104,6 @@ files:
104
104
  - app/views/devise/otp_tokens/show.html.erb
105
105
  - config/locales/en.yml
106
106
  - devise-otp.gemspec
107
- - gemfiles/rails_7.0.gemfile
108
107
  - gemfiles/rails_7.1.gemfile
109
108
  - gemfiles/rails_7.2.gemfile
110
109
  - gemfiles/rails_8.0.gemfile
@@ -131,20 +130,19 @@ files:
131
130
  - test/dummy/app/controllers/admin_posts_controller.rb
132
131
  - test/dummy/app/controllers/application_controller.rb
133
132
  - test/dummy/app/controllers/base_controller.rb
133
+ - test/dummy/app/controllers/non_otp_posts_controller.rb
134
134
  - test/dummy/app/controllers/posts_controller.rb
135
135
  - test/dummy/app/helpers/application_helper.rb
136
136
  - test/dummy/app/helpers/posts_helper.rb
137
137
  - test/dummy/app/mailers/.gitkeep
138
138
  - test/dummy/app/models/admin.rb
139
+ - test/dummy/app/models/non_otp_user.rb
139
140
  - test/dummy/app/models/post.rb
140
141
  - test/dummy/app/models/user.rb
141
- - test/dummy/app/views/admin_posts/_form.html.erb
142
- - test/dummy/app/views/admin_posts/edit.html.erb
143
142
  - test/dummy/app/views/admin_posts/index.html.erb
144
- - test/dummy/app/views/admin_posts/new.html.erb
145
- - test/dummy/app/views/admin_posts/show.html.erb
146
143
  - test/dummy/app/views/base/home.html.erb
147
144
  - test/dummy/app/views/layouts/application.html.erb
145
+ - test/dummy/app/views/non_otp_posts/index.html.erb
148
146
  - test/dummy/app/views/posts/_form.html.erb
149
147
  - test/dummy/app/views/posts/edit.html.erb
150
148
  - test/dummy/app/views/posts/index.html.erb
@@ -174,6 +172,10 @@ files:
174
172
  - test/dummy/db/migrate/20240604000001_create_admins.rb
175
173
  - test/dummy/db/migrate/20240604000002_add_devise_to_admins.rb
176
174
  - test/dummy/db/migrate/20240604000003_devise_otp_add_to_admins.rb
175
+ - test/dummy/db/migrate/20250718092451_create_non_otp_users.rb
176
+ - test/dummy/db/migrate/20250718092536_add_devise_to_non_otp_users.rb
177
+ - test/dummy/db/schema.rb
178
+ - test/dummy/db/seeds.rb
177
179
  - test/dummy/lib/assets/.gitkeep
178
180
  - test/dummy/public/404.html
179
181
  - test/dummy/public/422.html
@@ -182,6 +184,7 @@ files:
182
184
  - test/dummy/script/rails
183
185
  - test/integration/disable_token_test.rb
184
186
  - test/integration/enable_otp_form_test.rb
187
+ - test/integration/non_otp_user_models_test.rb
185
188
  - test/integration/persistence_test.rb
186
189
  - test/integration/refresh_test.rb
187
190
  - test/integration/reset_token_test.rb
@@ -190,10 +193,10 @@ files:
190
193
  - test/integration_tests_helper.rb
191
194
  - test/model_tests_helper.rb
192
195
  - test/models/otp_authenticatable_test.rb
193
- - test/orm/active_record.rb
194
196
  - test/test_helper.rb
195
197
  homepage: https://github.com/wmlele/devise-otp
196
- licenses: []
198
+ licenses:
199
+ - MIT
197
200
  metadata: {}
198
201
  post_install_message:
199
202
  rdoc_options: []
@@ -203,14 +206,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
203
206
  requirements:
204
207
  - - ">="
205
208
  - !ruby/object:Gem::Version
206
- version: '0'
209
+ version: 3.2.0
207
210
  required_rubygems_version: !ruby/object:Gem::Requirement
208
211
  requirements:
209
212
  - - ">="
210
213
  - !ruby/object:Gem::Version
211
214
  version: '0'
212
215
  requirements: []
213
- rubygems_version: 3.5.16
216
+ rubygems_version: 3.5.22
214
217
  signing_key:
215
218
  specification_version: 4
216
219
  summary: Time Based OTP/rfc6238 compatible authentication for Devise
@@ -1,25 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "appraisal", git: "https://github.com/thoughtbot/appraisal.git"
6
- gem "capybara"
7
- gem "minitest-reporters", ">= 0.5.0"
8
- gem "puma"
9
- gem "rake"
10
- gem "rdoc"
11
- gem "shoulda"
12
- gem "sprockets-rails"
13
- gem "sqlite3", "~> 1.5.0"
14
- gem "standardrb"
15
- gem "rails", "~> 7.0.0"
16
-
17
- install_if -> { Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.3.0") } do
18
- gem "base64"
19
- gem "bigdecimal"
20
- gem "mutex_m"
21
- gem "drb"
22
- gem "logger"
23
- end
24
-
25
- gemspec path: "../"
@@ -1,25 +0,0 @@
1
- <%= form_for([:admin, @post]) do |f| %>
2
- <% if @post.errors.any? %>
3
- <div id="error_explanation">
4
- <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>
5
-
6
- <ul>
7
- <% @post.errors.full_messages.each do |msg| %>
8
- <li><%= msg %></li>
9
- <% end %>
10
- </ul>
11
- </div>
12
- <% end %>
13
-
14
- <div class="field">
15
- <%= f.label :title %><br />
16
- <%= f.text_field :title %>
17
- </div>
18
- <div class="field">
19
- <%= f.label :body %><br />
20
- <%= f.text_area :body %>
21
- </div>
22
- <div class="actions">
23
- <%= f.submit %>
24
- </div>
25
- <% end %>
@@ -1,6 +0,0 @@
1
- <h1>Editing post</h1>
2
-
3
- <%= render 'form' %>
4
-
5
- <%= link_to 'Show', @post %> |
6
- <%= link_to 'Back', admin_posts_path %>
@@ -1,5 +0,0 @@
1
- <h1>New post</h1>
2
-
3
- <%= render 'form' %>
4
-
5
- <%= link_to 'Back', admin_posts_path %>
@@ -1,15 +0,0 @@
1
- <p id="notice"><%= notice %></p>
2
-
3
- <p>
4
- <b>Title:</b>
5
- <%= @post.title %>
6
- </p>
7
-
8
- <p>
9
- <b>Body:</b>
10
- <%= @post.body %>
11
- </p>
12
-
13
-
14
- <%= link_to 'Edit', edit_admin_post_path(@post) %> |
15
- <%= link_to 'Back', admin_posts_path %>
@@ -1,11 +0,0 @@
1
- ActiveRecord::Migration.verbose = false
2
- ActiveRecord::Base.logger = Logger.new(nil)
3
-
4
- migrations_path = File.expand_path("../../dummy/db/migrate/", __FILE__)
5
-
6
- if Rails.version.to_f >= 7.2
7
- ActiveRecord::MigrationContext.new(migrations_path).migrate
8
- else
9
- # To support order versions of Rails (pre v7.2)
10
- ActiveRecord::MigrationContext.new(migrations_path, ActiveRecord::SchemaMigration).migrate
11
- end