two_factor_authentication 1.1.3 → 1.1.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fb84a503671c99963674feb9a48b9e0b4078c457
4
- data.tar.gz: e1004e05b1adfb4fc9dbae58638f3091a29cd4c9
3
+ metadata.gz: 6d9dcc7b4346ba04374293811dba157adfd55db2
4
+ data.tar.gz: aaa9d10892c25956248fa6c2325c0632e34f4cf3
5
5
  SHA512:
6
- metadata.gz: 7f790fd330b74c0bed8199477a23dce4d52d5035c86495a62d2d5e5b0bc77f5d97c4cea8b6b9aee5a6b3b306d9d45664fb95b5a0c3ca89838b3a832302de1590
7
- data.tar.gz: 48d79bca0d7eb2f51ed606d4c34aea8b7f7d01cf0c2e59e763a0fd6eaad167dbc53c4c7db495bc0931fa6b061c4a095a6d521b868334eeaf77267538861b3cf6
6
+ metadata.gz: c5ac4fa24344211e03465875098f6fb5a09af71335747a9972829f5f304c8b10b12037fd581a47de6ad973cc562dba29c110ca3171d833b8d08d9fb8bd7faec9
7
+ data.tar.gz: 8ce9ce6d83afafdd9014f7cf7c4aef87a0409a54f193a1943c3b071a0e86e3a60b93fe9a58e06beb380ecb42857988c521132fc4b44d16a926c8fbe2c729367c
data/.gitignore CHANGED
@@ -19,3 +19,5 @@ capybara-*.html
19
19
  dump.rdb
20
20
  *.ids
21
21
  .rbenv-version
22
+ .ruby-gemset
23
+ .ruby-version
data/.travis.yml CHANGED
@@ -4,12 +4,14 @@ env:
4
4
  - "RAILS_VERSION=3.2.0"
5
5
  - "RAILS_VERSION=4.0.0"
6
6
  - "RAILS_VERSION=4.1.1"
7
+ - "RAILS_VERSION=4.2.4"
7
8
  - "RAILS_VERSION=master"
8
9
 
9
10
  rvm:
10
11
  - 1.9.3
11
12
  - 2.0
12
13
  - 2.1
14
+ - 2.2
13
15
 
14
16
  matrix:
15
17
  allow_failures:
data/Gemfile CHANGED
@@ -16,6 +16,10 @@ rails = case rails_version
16
16
 
17
17
  gem "rails", rails
18
18
 
19
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.2.0')
20
+ gem "test-unit", "~> 3.0"
21
+ end
22
+
19
23
  group :test do
20
24
  gem "sqlite3"
21
25
  end
data/README.md CHANGED
@@ -9,6 +9,7 @@
9
9
  * configure max login attempts
10
10
  * per user level control if he really need two factor authentication
11
11
  * your own sms logic
12
+ * configurable period where users won't be asked for 2FA again
12
13
 
13
14
  ## Configuration
14
15
 
@@ -24,7 +25,7 @@ Once that's done, run:
24
25
 
25
26
  ### Automatic installation
26
27
 
27
- In order to add two factor authorisation to a model, run the command:
28
+ In order to add two factor authentication to a model, run the command:
28
29
 
29
30
  bundle exec rails g two_factor_authentication MODEL
30
31
 
@@ -38,12 +39,13 @@ Add the following line to your model to fully enable two-factor auth:
38
39
 
39
40
  has_one_time_password
40
41
 
41
- Set config values, if desired, for maximum second factor attempts count, allowed time drift, and OTP length.
42
+ Set config values, if desired:
42
43
 
43
44
  ```ruby
44
- config.max_login_attempts = 3
45
- config.allowed_otp_drift_seconds = 30
46
- config.otp_length = 6
45
+ config.max_login_attempts = 3 # Maximum second factor attempts count
46
+ config.allowed_otp_drift_seconds = 30 # Allowed time drift
47
+ config.otp_length = 6 # OTP code length
48
+ config.remember_otp_session_for_seconds = 30.days # Time before browser has to enter OTP code again
47
49
  ```
48
50
 
49
51
  Override the method to send one-time passwords in your model, this is automatically called when a user logs in:
@@ -67,12 +69,13 @@ Add the following line to your model to fully enable two-factor auth:
67
69
 
68
70
  has_one_time_password
69
71
 
70
- Set config values, if desired, for maximum second factor attempts count, allowed time drift, and OTP length.
72
+ Set config values to devise.rb, if desired:
71
73
 
72
74
  ```ruby
73
- config.max_login_attempts = 3
74
- config.allowed_otp_drift_seconds = 30
75
- config.otp_length = 6
75
+ config.max_login_attempts = 3 # Maximum second factor attempts count
76
+ config.allowed_otp_drift_seconds = 30 # Allowed time drift
77
+ config.otp_length = 6 # OTP code length
78
+ config.remember_otp_session_for_seconds = 30.days # Time before browser has to enter OTP code again
76
79
  ```
77
80
 
78
81
  Override the method to send one-time passwords in your model, this is automatically called when a user logs in:
@@ -9,36 +9,60 @@ class Devise::TwoFactorAuthenticationController < DeviseController
9
9
  render :show and return if params[:code].nil?
10
10
 
11
11
  if resource.authenticate_otp(params[:code])
12
- warden.session(resource_name)[TwoFactorAuthentication::NEED_AUTHENTICATION] = false
13
- sign_in resource_name, resource, :bypass => true
14
- set_flash_message :notice, :success
15
- redirect_to stored_location_for(resource_name) || :root
16
- resource.update_attribute(:second_factor_attempts_count, 0)
12
+ after_two_factor_success_for(resource)
17
13
  else
18
- resource.second_factor_attempts_count += 1
19
- resource.save
20
- flash.now[:error] = find_message(:attempt_failed)
21
- if resource.max_login_attempts?
22
- sign_out(resource)
23
- render :max_login_attempts_reached
24
- else
25
- render :show
26
- end
14
+ after_two_factor_fail_for(resource)
27
15
  end
28
16
  end
29
17
 
30
18
  private
31
19
 
32
- def authenticate_scope!
33
- self.resource = send("current_#{resource_name}")
20
+ def after_two_factor_success_for(resource)
21
+ expires_seconds = resource.class.remember_otp_session_for_seconds
22
+
23
+ if expires_seconds && expires_seconds > 0
24
+ cookies.signed[TwoFactorAuthentication::REMEMBER_TFA_COOKIE_NAME] = {
25
+ value: true,
26
+ expires: expires_seconds.from_now
27
+ }
34
28
  end
35
29
 
36
- def prepare_and_validate
37
- redirect_to :root and return if resource.nil?
38
- @limit = resource.max_login_attempts
39
- if resource.max_login_attempts?
40
- sign_out(resource)
41
- render :max_login_attempts_reached and return
42
- end
30
+ warden.session(resource_name)[TwoFactorAuthentication::NEED_AUTHENTICATION] = false
31
+ sign_in resource_name, resource, :bypass => true
32
+ set_flash_message :notice, :success
33
+ resource.update_attribute(:second_factor_attempts_count, 0)
34
+
35
+ redirect_to after_two_factor_success_path_for(resource)
36
+ end
37
+
38
+ def after_two_factor_success_path_for(resource)
39
+ stored_location_for(resource_name) || :root
40
+ end
41
+
42
+ def after_two_factor_fail_for(resource)
43
+ resource.second_factor_attempts_count += 1
44
+ resource.save
45
+ flash.now[:error] = find_message(:attempt_failed)
46
+
47
+ if resource.max_login_attempts?
48
+ sign_out(resource)
49
+ render :max_login_attempts_reached
50
+
51
+ else
52
+ render :show
43
53
  end
54
+ end
55
+
56
+ def authenticate_scope!
57
+ self.resource = send("current_#{resource_name}")
58
+ end
59
+
60
+ def prepare_and_validate
61
+ redirect_to :root and return if resource.nil?
62
+ @limit = resource.max_login_attempts
63
+ if resource.max_login_attempts?
64
+ sign_out(resource)
65
+ render :max_login_attempts_reached and return
66
+ end
67
+ end
44
68
  end
@@ -5,7 +5,6 @@ require "active_model"
5
5
  require "active_record"
6
6
  require "active_support/core_ext/class/attribute_accessors"
7
7
  require "cgi"
8
- require "rotp"
9
8
 
10
9
  module Devise
11
10
  mattr_accessor :max_login_attempts
@@ -16,10 +15,14 @@ module Devise
16
15
 
17
16
  mattr_accessor :otp_length
18
17
  @@otp_length = 6
18
+
19
+ mattr_accessor :remember_otp_session_for_seconds
20
+ @@remember_otp_session_for_seconds = 0
19
21
  end
20
22
 
21
23
  module TwoFactorAuthentication
22
24
  NEED_AUTHENTICATION = 'need_two_factor_authentication'
25
+ REMEMBER_TFA_COOKIE_NAME = "remember_tfa"
23
26
 
24
27
  autoload :Schema, 'two_factor_authentication/schema'
25
28
  module Controllers
@@ -21,7 +21,7 @@ module TwoFactorAuthentication
21
21
 
22
22
  def handle_failed_second_factor(scope)
23
23
  if request.format.present? and request.format.html?
24
- session["#{scope}_return_to"] = "#{request.path}?#{request.query_string}" if request.get?
24
+ session["#{scope}_return_to"] = request.original_fullpath if request.get?
25
25
  redirect_to two_factor_authentication_path_for(scope)
26
26
  else
27
27
  render nothing: true, status: :unauthorized
@@ -1,5 +1,6 @@
1
1
  Warden::Manager.after_authentication do |user, auth, options|
2
- if user.respond_to?(:need_two_factor_authentication?)
2
+ if user.respond_to?(:need_two_factor_authentication?) &&
3
+ !auth.env["action_dispatch.cookies"].signed[TwoFactorAuthentication::REMEMBER_TFA_COOKIE_NAME]
3
4
  if auth.session(options[:scope])[TwoFactorAuthentication::NEED_AUTHENTICATION] = user.need_two_factor_authentication?(auth.request)
4
5
  user.send_two_factor_authentication_code
5
6
  end
@@ -1,4 +1,6 @@
1
1
  require 'two_factor_authentication/hooks/two_factor_authenticatable'
2
+ require 'rotp'
3
+
2
4
  module Devise
3
5
  module Models
4
6
  module TwoFactorAuthenticatable
@@ -20,7 +22,7 @@ module Devise
20
22
  end
21
23
  end
22
24
  end
23
- ::Devise::Models.config(self, :max_login_attempts, :allowed_otp_drift_seconds, :otp_length)
25
+ ::Devise::Models.config(self, :max_login_attempts, :allowed_otp_drift_seconds, :otp_length, :remember_otp_session_for_seconds)
24
26
  end
25
27
 
26
28
  module InstanceMethodsOnActivation
@@ -1,3 +1,3 @@
1
1
  module TwoFactorAuthentication
2
- VERSION = "1.1.3".freeze
2
+ VERSION = "1.1.4".freeze
3
3
  end
@@ -84,5 +84,43 @@ feature "User of two factor authentication" do
84
84
  expect(page).to have_content("Access completely denied")
85
85
  expect(page).to have_content("You are signed out")
86
86
  end
87
+
88
+ describe "rememberable TFA" do
89
+ before do
90
+ @original_remember_otp_session_for_seconds = User.remember_otp_session_for_seconds
91
+ User.remember_otp_session_for_seconds = 30.days
92
+ end
93
+
94
+ after do
95
+ User.remember_otp_session_for_seconds = @original_remember_otp_session_for_seconds
96
+ end
97
+
98
+ scenario "doesn't require TFA code again within 30 days" do
99
+ visit user_two_factor_authentication_path
100
+ fill_in "code", with: user.otp_code
101
+ click_button "Submit"
102
+
103
+ logout
104
+
105
+ login_as user
106
+ visit dashboard_path
107
+ expect(page).to have_content("Your Personal Dashboard")
108
+ expect(page).to have_content("You are signed in as Marissa")
109
+ end
110
+
111
+ scenario "requires TFA code again after 30 days" do
112
+ visit user_two_factor_authentication_path
113
+ fill_in "code", with: user.otp_code
114
+ click_button "Submit"
115
+
116
+ logout
117
+
118
+ Timecop.travel(30.days.from_now)
119
+ login_as user
120
+ visit dashboard_path
121
+ expect(page).to have_content("You are signed in as Marissa")
122
+ expect(page).to have_content("Enter your personal code")
123
+ end
124
+ end
87
125
  end
88
126
  end
@@ -9,7 +9,7 @@ describe Devise::Models::TwoFactorAuthenticatable, '#otp_code' do
9
9
  it "should return an error if no secret is set" do
10
10
  expect {
11
11
  subject
12
- }.to raise_error
12
+ }.to raise_error Exception
13
13
  end
14
14
 
15
15
  context "secret is set" do
@@ -9,7 +9,13 @@ Dummy::Application.configure do
9
9
  config.eager_load = false
10
10
 
11
11
  # Configure static asset server for tests with Cache-Control for performance
12
- config.serve_static_assets = true
12
+ if Rails::VERSION::MAJOR == 4 && Rails::VERSION::MINOR >= 2 ||
13
+ Rails::VERSION::MAJOR >= 5
14
+ config.serve_static_files = true
15
+ else
16
+ config.serve_static_assets = true
17
+ end
18
+
13
19
  config.static_cache_control = "public, max-age=3600"
14
20
 
15
21
  # Show full error reports and disable caching
data/spec/spec_helper.rb CHANGED
@@ -2,6 +2,7 @@ ENV["RAILS_ENV"] ||= "test"
2
2
  require File.expand_path("../rails_app/config/environment.rb", __FILE__)
3
3
 
4
4
  require 'rspec/rails'
5
+ require 'timecop'
5
6
 
6
7
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
8
  RSpec.configure do |config|
@@ -17,6 +18,8 @@ RSpec.configure do |config|
17
18
  # the seed, which is printed after each run.
18
19
  # --seed 1234
19
20
  config.order = 'random'
21
+
22
+ config.after(:each) { Timecop.return }
20
23
  end
21
24
 
22
25
  Dir["#{Dir.pwd}/spec/support/**/*.rb"].each {|f| require f}
@@ -34,4 +34,5 @@ Gem::Specification.new do |s|
34
34
  s.add_development_dependency 'rspec-rails', '>= 3.0.1'
35
35
  s.add_development_dependency 'capybara', '2.4.1'
36
36
  s.add_development_dependency 'pry'
37
+ s.add_development_dependency 'timecop'
37
38
  end
metadata CHANGED
@@ -1,111 +1,111 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: two_factor_authentication
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.3
4
+ version: 1.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitrii Golub
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-14 00:00:00.000000000 Z
11
+ date: 2016-01-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 3.1.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 3.1.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: devise
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: randexp
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rotp
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - '>='
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: bundler
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - '>='
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - '>='
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rake
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - '>='
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
89
  version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - '>='
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: rspec-rails
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - '>='
101
+ - - ">="
102
102
  - !ruby/object:Gem::Version
103
103
  version: 3.0.1
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - '>='
108
+ - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: 3.0.1
111
111
  - !ruby/object:Gem::Dependency
@@ -126,14 +126,28 @@ dependencies:
126
126
  name: pry
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
- - - '>='
129
+ - - ">="
130
130
  - !ruby/object:Gem::Version
131
131
  version: '0'
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
- - - '>='
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: timecop
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
137
151
  - !ruby/object:Gem::Version
138
152
  version: '0'
139
153
  description: |2
@@ -148,8 +162,8 @@ executables: []
148
162
  extensions: []
149
163
  extra_rdoc_files: []
150
164
  files:
151
- - .gitignore
152
- - .travis.yml
165
+ - ".gitignore"
166
+ - ".travis.yml"
153
167
  - Gemfile
154
168
  - LICENSE
155
169
  - README.md
@@ -234,17 +248,17 @@ require_paths:
234
248
  - lib
235
249
  required_ruby_version: !ruby/object:Gem::Requirement
236
250
  requirements:
237
- - - '>='
251
+ - - ">="
238
252
  - !ruby/object:Gem::Version
239
253
  version: '0'
240
254
  required_rubygems_version: !ruby/object:Gem::Requirement
241
255
  requirements:
242
- - - '>='
256
+ - - ">="
243
257
  - !ruby/object:Gem::Version
244
258
  version: '0'
245
259
  requirements: []
246
260
  rubyforge_project: two_factor_authentication
247
- rubygems_version: 2.2.2
261
+ rubygems_version: 2.5.0
248
262
  signing_key:
249
263
  specification_version: 4
250
264
  summary: Two factor authentication plugin for devise