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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +4 -4
- data/.gitignore +1 -3
- data/Appraisals +0 -14
- data/CHANGELOG.md +34 -1
- data/README.md +15 -1
- data/Rakefile +0 -11
- data/devise-otp.gemspec +4 -1
- data/lib/devise-otp/version.rb +1 -1
- data/lib/devise_otp_authenticatable/hooks/refreshable.rb +3 -1
- data/test/dummy/app/controllers/admin_posts_controller.rb +0 -72
- data/test/dummy/app/controllers/non_otp_posts_controller.rb +13 -0
- data/test/dummy/app/controllers/posts_controller.rb +8 -2
- data/test/dummy/app/models/admin.rb +1 -13
- data/test/dummy/app/models/non_otp_user.rb +4 -0
- data/test/dummy/app/models/post.rb +1 -1
- data/test/dummy/app/models/user.rb +1 -13
- data/test/dummy/app/views/admin_posts/index.html.erb +0 -7
- data/test/dummy/app/views/non_otp_posts/index.html.erb +18 -0
- data/test/dummy/config/application.rb +0 -7
- data/test/dummy/config/database.yml +20 -13
- data/test/dummy/config/routes.rb +2 -0
- data/test/dummy/db/migrate/20250718092451_create_non_otp_users.rb +9 -0
- data/test/dummy/db/migrate/20250718092536_add_devise_to_non_otp_users.rb +52 -0
- data/test/dummy/db/schema.rb +118 -0
- data/test/dummy/db/seeds.rb +24 -0
- data/test/integration/non_otp_user_models_test.rb +21 -0
- data/test/integration_tests_helper.rb +11 -0
- data/test/test_helper.rb +0 -5
- metadata +16 -13
- data/gemfiles/rails_7.0.gemfile +0 -25
- data/test/dummy/app/views/admin_posts/_form.html.erb +0 -25
- data/test/dummy/app/views/admin_posts/edit.html.erb +0 -6
- data/test/dummy/app/views/admin_posts/new.html.erb +0 -5
- data/test/dummy/app/views/admin_posts/show.html.erb +0 -15
- data/test/orm/active_record.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f329ff1fa9732961646ad4169f89921f3429a6aa86744486eafa21a891fd1628
|
4
|
+
data.tar.gz: 8a4797664986ea6c11e21a50a43d5aeda471000ce5610e91451ec37f3d231121
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e42b34ea4259e698e75f6408b6d0a0021b39f934d960e039a305ee2f9dc7d443a8dbdc9f99e1df452b75602430e601bf7ece1214b11766ba558fb3806d07355c
|
7
|
+
data.tar.gz: 8c1bde0740a5f96dfc440e976c568a8ef0b7f0ac91e7af620d14aea1e8208811e6339384dc9d7b6363c6f4ed9de363c1637422a82b5f5f5c6696fa17c9879764
|
data/.github/workflows/ci.yml
CHANGED
@@ -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
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
|
-
|
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
|
-
|
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.
|
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"
|
data/lib/devise-otp/version.rb
CHANGED
@@ -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
|
-
|
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
|
@@ -42,7 +42,7 @@ class PostsController < ApplicationController
|
|
42
42
|
# POST /posts
|
43
43
|
# POST /posts.json
|
44
44
|
def create
|
45
|
-
@post = Post.new(
|
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(
|
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 <
|
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
|
|
@@ -1,2 +1,2 @@
|
|
1
|
-
class Post <
|
1
|
+
class Post < ActiveRecord::Base
|
2
2
|
end
|
@@ -1,16 +1,4 @@
|
|
1
|
-
class User <
|
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
|
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
|
6
|
-
|
5
|
+
# gem "sqlite3"
|
6
|
+
#
|
7
|
+
default: &default
|
7
8
|
adapter: sqlite3
|
8
|
-
|
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
|
-
|
17
|
-
database:
|
18
|
-
|
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
|
-
|
23
|
-
database:
|
24
|
-
pool: 5
|
25
|
-
timeout: 5000
|
31
|
+
<<: *default
|
32
|
+
# database: path/to/persistent/storage/production.sqlite3
|
data/test/dummy/config/routes.rb
CHANGED
@@ -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.
|
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:
|
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.
|
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.
|
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:
|
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.
|
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
|
data/gemfiles/rails_7.0.gemfile
DELETED
@@ -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 %>
|
data/test/orm/active_record.rb
DELETED
@@ -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
|