Skip to content
This repository was archived by the owner on Jan 30, 2024. It is now read-only.

Commit 5ee6e6b

Browse files
committed
Merge pull request #119 from equivalent/paranoid_verification
reset code after several faild attempts
2 parents 43dd886 + 48c04bb commit 5ee6e6b

File tree

6 files changed

+99
-5
lines changed

6 files changed

+99
-5
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,8 @@ add_index :the_resources, :expired_at
165165
create_table :the_resources do |t|
166166
# other devise fields
167167

168-
t.string :paranoid_verification_code
168+
t.string :paranoid_verification_code
169+
t.integer :paranoid_verification_attempt, default: 0
169170
t.datetime :paranoid_verified_at
170171
end
171172
add_index :the_resources, :paranoid_verification_code

app/views/devise/paranoid_verification_code/show.html.erb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,7 @@
66
<p><%= f.label :paranoid_verification_code, 'Verification code' %><br />
77
<%= f.text_field :paranoid_verification_code, value: '' %></p>
88

9+
<p>After <strong><%= resource.paranoid_attempts_remaining %></strong> faild attemtps, code will be regenerated<p>
10+
911
<p><%= f.submit "Submit" %></p>
1012
<% end %>

lib/devise_security_extension.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ module Devise
7171
@@expire_after = 90.days
7272
mattr_accessor :delete_expired_after
7373
@@delete_expired_after = 90.days
74+
75+
# paranoid_verification will regenerate verifacation code after faild attempt
76+
mattr_accessor :paranoid_code_regenerate_after_attempt
77+
@@paranoid_code_regenerate_after_attempt = 10
7478
end
7579

7680
# an security extension for devise

lib/devise_security_extension/models/paranoid_verification.rb

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
module Devise
44
module Models
5-
65
# PasswordExpirable takes care of change password after
76
module ParanoidVerification
87
extend ActiveSupport::Concern
@@ -12,13 +11,24 @@ def need_paranoid_verification?
1211
end
1312

1413
def verify_code(code)
15-
if code == paranoid_verification_code
16-
update_without_password paranoid_verification_code: nil, paranoid_verified_at: Time.now
14+
attempt = paranoid_verification_attempt
15+
16+
if (attempt += 1) >= Devise.paranoid_code_regenerate_after_attempt
17+
generate_paranoid_code
18+
elsif code == paranoid_verification_code
19+
attempt = 0
20+
update_without_password paranoid_verification_code: nil, paranoid_verified_at: Time.now, paranoid_verification_attempt: attempt
21+
else
22+
update_without_password paranoid_verification_attempt: attempt
1723
end
1824
end
1925

26+
def paranoid_attempts_remaining
27+
Devise.paranoid_code_regenerate_after_attempt - paranoid_verification_attempt
28+
end
29+
2030
def generate_paranoid_code
21-
update_without_password paranoid_verification_code: Devise.verification_code_generator.call()
31+
update_without_password paranoid_verification_code: Devise.verification_code_generator.call(), paranoid_verification_attempt: 0
2232
end
2333
end
2434
end
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class AddVerificationAttemptColumn < ActiveRecord::Migration
2+
def self.up
3+
add_column :users, :paranoid_verification_attempt, :integer, default: 0
4+
end
5+
6+
def self.down
7+
remove_column :users, :paranoid_verification_attempt
8+
end
9+
end

test/test_paranoid_verification.rb

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ class TestPasswordVerifiable < ActiveSupport::TestCase
1414
end
1515

1616
test "generate code" do
17+
user = User.new
18+
user.generate_paranoid_code
19+
assert_equal(0, user.paranoid_verification_attempt)
20+
user.verify_code('wrong')
21+
assert_equal(1, user.paranoid_verification_attempt)
22+
user.generate_paranoid_code
23+
assert_equal(0, user.paranoid_verification_attempt)
24+
end
25+
26+
test "generate code must reset attempt counter" do
1727
user = User.new
1828
user.generate_paranoid_code
1929
# default generator generates 5 char string
@@ -54,4 +64,62 @@ class TestPasswordVerifiable < ActiveSupport::TestCase
5464
user.verify_code('wrong')
5565
assert_equal(nil, user.paranoid_verified_at)
5666
end
67+
68+
test 'when code not match upon verification code too many attempts should generate new code' do
69+
original_regenerate = Devise.paranoid_code_regenerate_after_attempt
70+
Devise.paranoid_code_regenerate_after_attempt = 2
71+
72+
user = User.create(paranoid_verification_code: 'abcde')
73+
user.verify_code('wrong')
74+
assert_equal 'abcde', user.paranoid_verification_code
75+
user.verify_code('wrong-again')
76+
assert_not_equal 'abcde', user.paranoid_verification_code
77+
78+
Devise.paranoid_code_regenerate_after_attempt = original_regenerate
79+
end
80+
81+
test 'upon generating new code due to too many attempts reset attempt counter' do
82+
original_regenerate = Devise.paranoid_code_regenerate_after_attempt
83+
Devise.paranoid_code_regenerate_after_attempt = 3
84+
85+
user = User.create(paranoid_verification_code: 'abcde')
86+
user.verify_code('wrong')
87+
assert_equal 1, user.paranoid_verification_attempt
88+
user.verify_code('wrong-again')
89+
assert_equal 2, user.paranoid_verification_attempt
90+
user.verify_code('WRONG!')
91+
assert_equal 0, user.paranoid_verification_attempt
92+
93+
Devise.paranoid_code_regenerate_after_attempt = original_regenerate
94+
end
95+
96+
97+
test 'by default paranoid code regenerate should have 10 attempts' do
98+
user = User.new(paranoid_verification_code: 'abcde')
99+
assert_equal 10, user.paranoid_attempts_remaining
100+
end
101+
102+
test 'paranoid_attempts_remaining should re-callculate how many attemps remains after each wrong attempt' do
103+
original_regenerate = Devise.paranoid_code_regenerate_after_attempt
104+
Devise.paranoid_code_regenerate_after_attempt = 2
105+
106+
user = User.create(paranoid_verification_code: 'abcde')
107+
assert_equal 2, user.paranoid_attempts_remaining
108+
109+
user.verify_code('WRONG!')
110+
assert_equal 1, user.paranoid_attempts_remaining
111+
112+
Devise.paranoid_code_regenerate_after_attempt = original_regenerate
113+
end
114+
115+
test 'when code not match upon verification code too many times, reset paranoid_attempts_remaining' do
116+
original_regenerate = Devise.paranoid_code_regenerate_after_attempt
117+
Devise.paranoid_code_regenerate_after_attempt = 1
118+
119+
user = User.create(paranoid_verification_code: 'abcde')
120+
user.verify_code('wrong') # at this point code was regenerated
121+
assert_equal Devise.paranoid_code_regenerate_after_attempt, user.paranoid_attempts_remaining
122+
123+
Devise.paranoid_code_regenerate_after_attempt = original_regenerate
124+
end
57125
end

0 commit comments

Comments
 (0)