two_factor_authentication 1.1.3 → 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
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