two_factor_authentication 1.1.3 → 2.2.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +21 -0
  3. data/.gitignore +2 -0
  4. data/.rubocop.yml +295 -0
  5. data/.travis.yml +14 -7
  6. data/CHANGELOG.md +119 -0
  7. data/Gemfile +12 -3
  8. data/README.md +320 -58
  9. data/app/controllers/devise/two_factor_authentication_controller.rb +65 -25
  10. data/app/views/devise/two_factor_authentication/show.html.erb +11 -2
  11. data/config/locales/en.yml +1 -0
  12. data/config/locales/es.yml +8 -0
  13. data/config/locales/fr.yml +8 -0
  14. data/config/locales/ru.yml +1 -0
  15. data/lib/generators/active_record/templates/migration.rb +9 -11
  16. data/lib/two_factor_authentication/controllers/helpers.rb +3 -3
  17. data/lib/two_factor_authentication/hooks/two_factor_authenticatable.rb +12 -2
  18. data/lib/two_factor_authentication/models/two_factor_authenticatable.rb +158 -29
  19. data/lib/two_factor_authentication/orm/active_record.rb +2 -0
  20. data/lib/two_factor_authentication/routes.rb +3 -1
  21. data/lib/two_factor_authentication/schema.rb +24 -4
  22. data/lib/two_factor_authentication/version.rb +1 -1
  23. data/lib/two_factor_authentication.rb +20 -3
  24. data/spec/controllers/two_factor_authentication_controller_spec.rb +41 -0
  25. data/spec/features/two_factor_authenticatable_spec.rb +179 -30
  26. data/spec/generators/active_record/two_factor_authentication_generator_spec.rb +36 -0
  27. data/spec/lib/two_factor_authentication/models/two_factor_authenticatable_spec.rb +272 -114
  28. data/spec/rails_app/app/controllers/home_controller.rb +1 -1
  29. data/spec/rails_app/app/models/admin.rb +6 -0
  30. data/spec/rails_app/app/models/encrypted_user.rb +15 -0
  31. data/spec/rails_app/app/models/guest_user.rb +8 -1
  32. data/spec/rails_app/app/models/user.rb +3 -4
  33. data/spec/rails_app/config/environments/test.rb +10 -1
  34. data/spec/rails_app/config/initializers/devise.rb +5 -3
  35. data/spec/rails_app/config/routes.rb +1 -0
  36. data/spec/rails_app/db/migrate/20140403184646_devise_create_users.rb +2 -2
  37. data/spec/rails_app/db/migrate/20140407172619_two_factor_authentication_add_to_users.rb +1 -1
  38. data/spec/rails_app/db/migrate/20140407215513_add_nickanme_to_users.rb +1 -1
  39. data/spec/rails_app/db/migrate/20151224171231_add_encrypted_columns_to_user.rb +9 -0
  40. data/spec/rails_app/db/migrate/20151224180310_populate_otp_column.rb +19 -0
  41. data/spec/rails_app/db/migrate/20151228230340_remove_otp_secret_key_from_user.rb +5 -0
  42. data/spec/rails_app/db/migrate/20160209032439_devise_create_admins.rb +42 -0
  43. data/spec/rails_app/db/schema.rb +35 -18
  44. data/spec/spec_helper.rb +4 -0
  45. data/spec/support/authenticated_model_helper.rb +33 -2
  46. data/spec/support/controller_helper.rb +16 -0
  47. data/spec/support/features_spec_helper.rb +24 -1
  48. data/spec/support/totp_helper.rb +11 -0
  49. data/two_factor_authentication.gemspec +4 -2
  50. metadata +133 -30
  51. data/spec/controllers/two_factor_auth_spec.rb +0 -18
@@ -1,168 +1,326 @@
1
1
  require 'spec_helper'
2
2
  include AuthenticatedModelHelper
3
3
 
4
- describe Devise::Models::TwoFactorAuthenticatable, '#otp_code' do
5
- let(:instance) { build_guest_user }
6
- subject { instance.otp_code(time) }
7
- let(:time) { 1392852456 }
8
-
9
- it "should return an error if no secret is set" do
10
- expect {
11
- subject
12
- }.to raise_error
13
- end
4
+ describe Devise::Models::TwoFactorAuthenticatable do
5
+ describe '#create_direct_otp' do
6
+ let(:instance) { build_guest_user }
7
+
8
+ it 'set direct_otp field' do
9
+ expect(instance.direct_otp).to be_nil
10
+ instance.create_direct_otp
11
+ expect(instance.direct_otp).not_to be_nil
12
+ end
14
13
 
15
- context "secret is set" do
16
- before :each do
17
- instance.otp_secret_key = "2z6hxkdwi3uvrnpn"
14
+ it 'set direct_otp_send_at field to current time' do
15
+ Timecop.freeze() do
16
+ instance.create_direct_otp
17
+ expect(instance.direct_otp_sent_at).to eq(Time.now)
18
+ end
18
19
  end
19
20
 
20
- it "should not return an error" do
21
- subject
21
+ it 'honors .direct_otp_length' do
22
+ expect(instance.class).to receive(:direct_otp_length).and_return(10)
23
+ instance.create_direct_otp
24
+ expect(instance.direct_otp.length).to equal(10)
25
+
26
+ expect(instance.class).to receive(:direct_otp_length).and_return(6)
27
+ instance.create_direct_otp
28
+ expect(instance.direct_otp.length).to equal(6)
22
29
  end
23
30
 
24
- it "should be configured length" do
25
- expect(subject.length).to eq(Devise.otp_length)
31
+ it "honors 'direct_otp_length' in options paramater" do
32
+ instance.create_direct_otp(length: 8)
33
+ expect(instance.direct_otp.length).to equal(8)
34
+ instance.create_direct_otp(length: 10)
35
+ expect(instance.direct_otp.length).to equal(10)
26
36
  end
37
+ end
27
38
 
28
- context "with a known time" do
29
- let(:time) { 1392852756 }
39
+ describe '#authenticate_direct_otp' do
40
+ let(:instance) { build_guest_user }
41
+ it 'fails if no direct_otp has been set' do
42
+ expect(instance.authenticate_direct_otp('12345')).to eq(false)
43
+ end
30
44
 
31
- it "should return a known result" do
32
- expect(subject).to eq("0000000524562202".split(//).last(Devise.otp_length).join)
45
+ context 'after generating an OTP' do
46
+ before :each do
47
+ instance.create_direct_otp
33
48
  end
34
- end
35
49
 
36
- context "with a known time yielding a result with less than 6 digits" do
37
- let(:time) { 1393065856 }
50
+ it 'accepts correct OTP' do
51
+ Timecop.freeze(Time.now + instance.class.direct_otp_valid_for - 1.second)
52
+ expect(instance.authenticate_direct_otp(instance.direct_otp)).to eq(true)
53
+ end
38
54
 
39
- it "should return a known result padded with zeroes" do
40
- expect(subject).to eq("0000001608007672".split(//).last(Devise.otp_length).join)
55
+ it 'rejects invalid OTP' do
56
+ Timecop.freeze(Time.now + instance.class.direct_otp_valid_for - 1.second)
57
+ expect(instance.authenticate_direct_otp('12340')).to eq(false)
58
+ end
59
+
60
+ it 'rejects expired OTP' do
61
+ Timecop.freeze(Time.now + instance.class.direct_otp_valid_for + 1.second)
62
+ expect(instance.authenticate_direct_otp(instance.direct_otp)).to eq(false)
63
+ end
64
+
65
+ it 'prevents code re-use' do
66
+ expect(instance.authenticate_direct_otp(instance.direct_otp)).to eq(true)
67
+ expect(instance.authenticate_direct_otp(instance.direct_otp)).to eq(false)
41
68
  end
42
69
  end
43
70
  end
44
- end
45
71
 
46
- describe Devise::Models::TwoFactorAuthenticatable, '#authenticate_otp' do
47
- let(:instance) { build_guest_user }
72
+ describe '#authenticate_totp' do
73
+ shared_examples 'authenticate_totp' do |instance|
74
+ before :each do
75
+ instance.otp_secret_key = '2z6hxkdwi3uvrnpn'
76
+ instance.totp_timestamp = nil
77
+ @totp_helper = TotpHelper.new(instance.otp_secret_key, instance.class.otp_length)
78
+ end
48
79
 
49
- before :each do
50
- instance.otp_secret_key = "2z6hxkdwi3uvrnpn"
51
- end
80
+ def do_invoke(code, user)
81
+ user.authenticate_totp(code)
82
+ end
52
83
 
53
- def do_invoke code, options = {}
54
- instance.authenticate_otp(code, options)
55
- end
84
+ it 'authenticates a recently created code' do
85
+ code = @totp_helper.totp_code
86
+ expect(do_invoke(code, instance)).to eq(true)
87
+ end
56
88
 
57
- it "should be able to authenticate a recently created code" do
58
- code = instance.otp_code
59
- expect(do_invoke(code)).to eq(true)
60
- end
89
+ it 'authenticates a code entered with a space' do
90
+ code = @totp_helper.totp_code.insert(3, ' ')
91
+ expect(do_invoke(code, instance)).to eq(true)
92
+ end
61
93
 
62
- it "should not authenticate an old code" do
63
- code = instance.otp_code(1.minutes.ago.to_i)
64
- expect(do_invoke(code)).to eq(false)
65
- end
66
- end
94
+ it 'does not authenticate an old code' do
95
+ code = @totp_helper.totp_code(1.minutes.ago.to_i)
96
+ expect(do_invoke(code, instance)).to eq(false)
97
+ end
67
98
 
68
- describe Devise::Models::TwoFactorAuthenticatable, '#send_two_factor_authentication_code' do
69
- let(:instance) { build_guest_user }
99
+ it 'prevents code reuse' do
100
+ code = @totp_helper.totp_code
101
+ expect(do_invoke(code, instance)).to eq(true)
102
+ expect(do_invoke(code, instance)).to eq(false)
103
+ end
104
+ end
70
105
 
71
- it "should raise an error by default" do
72
- expect {
73
- instance.send_two_factor_authentication_code
74
- }.to raise_error(NotImplementedError)
106
+ it_behaves_like 'authenticate_totp', GuestUser.new
107
+ it_behaves_like 'authenticate_totp', EncryptedUser.new
75
108
  end
76
109
 
77
- it "should be overrideable" do
78
- def instance.send_two_factor_authentication_code
79
- "Code sent"
110
+ describe '#send_two_factor_authentication_code' do
111
+ let(:instance) { build_guest_user }
112
+
113
+ it 'raises an error by default' do
114
+ expect { instance.send_two_factor_authentication_code(123) }.
115
+ to raise_error(NotImplementedError)
116
+ end
117
+
118
+ it 'is overrideable' do
119
+ def instance.send_two_factor_authentication_code(code)
120
+ 'Code sent'
121
+ end
122
+ expect(instance.send_two_factor_authentication_code(123)).to eq('Code sent')
80
123
  end
81
- expect(instance.send_two_factor_authentication_code).to eq("Code sent")
82
124
  end
83
- end
84
125
 
85
- describe Devise::Models::TwoFactorAuthenticatable, '#provisioning_uri' do
86
- let(:instance) { build_guest_user }
126
+ describe '#provisioning_uri' do
87
127
 
88
- before do
89
- instance.email = "houdini@example.com"
90
- instance.run_callbacks :create
91
- end
128
+ shared_examples 'provisioning_uri' do |instance|
129
+ it 'fails until generate_totp_secret is called' do
130
+ expect { instance.provisioning_uri }.to raise_error(Exception)
131
+ end
92
132
 
93
- it "should return uri with user's email" do
94
- expect(instance.provisioning_uri).to match(%r{otpauth://totp/houdini@example.com\?secret=\w{16}})
95
- end
133
+ describe 'with secret set' do
134
+ before do
135
+ instance.email = 'houdini@example.com'
136
+ instance.otp_secret_key = instance.generate_totp_secret
137
+ end
138
+
139
+ it "returns uri with user's email" do
140
+ expect(instance.provisioning_uri).
141
+ to match(%r{otpauth://totp/houdini@example.com\?secret=\w{32}})
142
+ end
143
+
144
+ it 'returns uri with issuer option' do
145
+ expect(instance.provisioning_uri('houdini')).
146
+ to match(%r{otpauth://totp/houdini\?secret=\w{32}$})
147
+ end
148
+
149
+ it 'returns uri with issuer option' do
150
+ require 'cgi'
151
+ uri = URI.parse(instance.provisioning_uri('houdini', issuer: 'Magic'))
152
+ params = CGI.parse(uri.query)
153
+
154
+ expect(uri.scheme).to eq('otpauth')
155
+ expect(uri.host).to eq('totp')
156
+ expect(uri.path).to eq('/Magic:houdini')
157
+ expect(params['issuer'].shift).to eq('Magic')
158
+ expect(params['secret'].shift).to match(/\w{32}/)
159
+ end
160
+ end
161
+ end
96
162
 
97
- it "should return uri with issuer option" do
98
- expect(instance.provisioning_uri("houdini")).to match(%r{otpauth://totp/houdini\?secret=\w{16}$})
163
+ it_behaves_like 'provisioning_uri', GuestUser.new
164
+ it_behaves_like 'provisioning_uri', EncryptedUser.new
99
165
  end
100
166
 
101
- it "should return uri with issuer option" do
102
- require 'cgi'
167
+ describe '#generate_totp_secret' do
168
+ shared_examples 'generate_totp_secret' do |klass|
169
+ let(:instance) { klass.new }
170
+
171
+ it 'returns a 32 character string' do
172
+ secret = instance.generate_totp_secret
103
173
 
104
- uri = URI.parse(instance.provisioning_uri("houdini", issuer: 'Magic'))
105
- params = CGI::parse(uri.query)
174
+ expect(secret).to match(/\w{32}/)
175
+ end
176
+ end
106
177
 
107
- expect(uri.scheme).to eq("otpauth")
108
- expect(uri.host).to eq("totp")
109
- expect(uri.path).to eq("/houdini")
110
- expect(params['issuer'].shift).to eq('Magic')
111
- expect(params['secret'].shift).to match(%r{\w{16}})
178
+ it_behaves_like 'generate_totp_secret', GuestUser
179
+ it_behaves_like 'generate_totp_secret', EncryptedUser
112
180
  end
113
- end
114
181
 
115
- describe Devise::Models::TwoFactorAuthenticatable, '#populate_otp_column' do
116
- let(:instance) { build_guest_user }
182
+ describe '#confirm_totp_secret' do
183
+ shared_examples 'confirm_totp_secret' do |klass|
184
+ let(:instance) { klass.new }
185
+ let(:secret) { instance.generate_totp_secret }
186
+ let(:totp_helper) { TotpHelper.new(secret, instance.class.otp_length) }
117
187
 
118
- it "populates otp_column on create" do
119
- expect(instance.otp_secret_key).to be_nil
188
+ it 'populates otp_secret_key column when given correct code' do
189
+ instance.confirm_totp_secret(secret, totp_helper.totp_code)
120
190
 
121
- instance.run_callbacks :create # populate_otp_column called via before_create
191
+ expect(instance.otp_secret_key).to match(secret)
192
+ end
122
193
 
123
- expect(instance.otp_secret_key).to match(%r{\w{16}})
124
- end
194
+ it 'does not populate otp_secret_key when when given incorrect code' do
195
+ instance.confirm_totp_secret(secret, '123')
196
+ expect(instance.otp_secret_key).to be_nil
197
+ end
125
198
 
126
- it "repopulates otp_column" do
127
- instance.run_callbacks :create
128
- original_key = instance.otp_secret_key
199
+ it 'returns true when given correct code' do
200
+ expect(instance.confirm_totp_secret(secret, totp_helper.totp_code)).to be true
201
+ end
129
202
 
130
- instance.populate_otp_column
203
+ it 'returns false when given incorrect code' do
204
+ expect(instance.confirm_totp_secret(secret, '123')).to be false
205
+ end
206
+
207
+ end
131
208
 
132
- expect(instance.otp_secret_key).to match(%r{\w{16}})
133
- expect(instance.otp_secret_key).to_not eq(original_key)
209
+ it_behaves_like 'confirm_totp_secret', GuestUser
210
+ it_behaves_like 'confirm_totp_secret', EncryptedUser
134
211
  end
135
- end
136
212
 
137
- describe Devise::Models::TwoFactorAuthenticatable, '#max_login_attempts' do
138
- let(:instance) { build_guest_user }
213
+ describe '#max_login_attempts' do
214
+ let(:instance) { build_guest_user }
139
215
 
140
- before do
141
- @original_max_login_attempts = GuestUser.max_login_attempts
142
- GuestUser.max_login_attempts = 3
143
- end
216
+ before do
217
+ @original_max_login_attempts = GuestUser.max_login_attempts
218
+ GuestUser.max_login_attempts = 3
219
+ end
144
220
 
145
- after { GuestUser.max_login_attempts = @original_max_login_attempts }
221
+ after { GuestUser.max_login_attempts = @original_max_login_attempts }
146
222
 
147
- it "returns class setting" do
148
- expect(instance.max_login_attempts).to eq(3)
149
- end
223
+ it 'returns class setting' do
224
+ expect(instance.max_login_attempts).to eq(3)
225
+ end
150
226
 
151
- it "returns false as boolean" do
152
- instance.second_factor_attempts_count = nil
153
- expect(instance.max_login_attempts?).to be_falsey
154
- instance.second_factor_attempts_count = 0
155
- expect(instance.max_login_attempts?).to be_falsey
156
- instance.second_factor_attempts_count = 1
157
- expect(instance.max_login_attempts?).to be_falsey
158
- instance.second_factor_attempts_count = 2
159
- expect(instance.max_login_attempts?).to be_falsey
227
+ it 'returns false as boolean' do
228
+ instance.second_factor_attempts_count = nil
229
+ expect(instance.max_login_attempts?).to be_falsey
230
+ instance.second_factor_attempts_count = 0
231
+ expect(instance.max_login_attempts?).to be_falsey
232
+ instance.second_factor_attempts_count = 1
233
+ expect(instance.max_login_attempts?).to be_falsey
234
+ instance.second_factor_attempts_count = 2
235
+ expect(instance.max_login_attempts?).to be_falsey
236
+ end
237
+
238
+ it 'returns true as boolean after too many attempts' do
239
+ instance.second_factor_attempts_count = 3
240
+ expect(instance.max_login_attempts?).to be_truthy
241
+ instance.second_factor_attempts_count = 4
242
+ expect(instance.max_login_attempts?).to be_truthy
243
+ end
160
244
  end
161
245
 
162
- it "returns true as boolean after too many attempts" do
163
- instance.second_factor_attempts_count = 3
164
- expect(instance.max_login_attempts?).to be_truthy
165
- instance.second_factor_attempts_count = 4
166
- expect(instance.max_login_attempts?).to be_truthy
246
+ describe '.has_one_time_password' do
247
+ context 'when encrypted: true option is passed' do
248
+ let(:instance) { EncryptedUser.new }
249
+
250
+ it 'encrypts otp_secret_key with iv, salt, and encoding' do
251
+ instance.otp_secret_key = '2z6hxkdwi3uvrnpn'
252
+
253
+ expect(instance.encrypted_otp_secret_key).to match(/.{44}/)
254
+
255
+ expect(instance.encrypted_otp_secret_key_iv).to match(/.{24}/)
256
+
257
+ expect(instance.encrypted_otp_secret_key_salt).to match(/.{25}/)
258
+ end
259
+
260
+ it 'does not encrypt a nil otp_secret_key' do
261
+ instance.otp_secret_key = nil
262
+
263
+ expect(instance.encrypted_otp_secret_key).to be_nil
264
+
265
+ expect(instance.encrypted_otp_secret_key_iv).to be_nil
266
+
267
+ expect(instance.encrypted_otp_secret_key_salt).to be_nil
268
+ end
269
+
270
+ it 'does not encrypt an empty otp_secret_key' do
271
+ instance.otp_secret_key = ''
272
+
273
+ expect(instance.encrypted_otp_secret_key).to eq ''
274
+
275
+ expect(instance.encrypted_otp_secret_key_iv).to be_nil
276
+
277
+ expect(instance.encrypted_otp_secret_key_salt).to be_nil
278
+ end
279
+
280
+ it 'raises an error when Devise.otp_secret_encryption_key is not set' do
281
+ allow(Devise).to receive(:otp_secret_encryption_key).and_return nil
282
+
283
+ # This error is raised by the encryptor gem
284
+ expect { instance.otp_secret_key = '2z6hxkdwi3uvrnpn' }.
285
+ to raise_error ArgumentError
286
+ end
287
+
288
+ it 'passes in the correct options to Encryptor.
289
+ We test here output of
290
+ Devise::Models::TwoFactorAuthenticatable::EncryptionInstanceMethods.encryption_options_for' do
291
+ instance.otp_secret_key = 'testing'
292
+ iv = instance.encrypted_otp_secret_key_iv
293
+ salt = instance.encrypted_otp_secret_key_salt
294
+
295
+ # it's important here to put the same crypto algorithm from that method
296
+ encrypted = Encryptor.encrypt(
297
+ value: 'testing',
298
+ key: Devise.otp_secret_encryption_key,
299
+ iv: iv.unpack('m').first,
300
+ salt: salt.unpack('m').first,
301
+ algorithm: 'aes-256-cbc'
302
+ )
303
+
304
+ expect(instance.encrypted_otp_secret_key).to eq [encrypted].pack('m')
305
+ end
306
+
307
+ it 'varies the iv per instance' do
308
+ instance.otp_secret_key = 'testing'
309
+ user2 = EncryptedUser.new
310
+ user2.otp_secret_key = 'testing'
311
+
312
+ expect(user2.encrypted_otp_secret_key_iv).
313
+ to_not eq instance.encrypted_otp_secret_key_iv
314
+ end
315
+
316
+ it 'varies the salt per instance' do
317
+ instance.otp_secret_key = 'testing'
318
+ user2 = EncryptedUser.new
319
+ user2.otp_secret_key = 'testing'
320
+
321
+ expect(user2.encrypted_otp_secret_key_salt).
322
+ to_not eq instance.encrypted_otp_secret_key_salt
323
+ end
324
+ end
167
325
  end
168
326
  end
@@ -1,5 +1,5 @@
1
1
  class HomeController < ApplicationController
2
- before_filter :authenticate_user!, only: :dashboard
2
+ before_action :authenticate_user!, only: :dashboard
3
3
 
4
4
  def index
5
5
  end
@@ -0,0 +1,6 @@
1
+ class Admin < ActiveRecord::Base
2
+ # Include default devise modules. Others available are:
3
+ # :confirmable, :lockable, :timeoutable and :omniauthable
4
+ devise :database_authenticatable, :registerable,
5
+ :recoverable, :rememberable, :trackable, :validatable
6
+ end
@@ -0,0 +1,15 @@
1
+ class EncryptedUser
2
+ extend ActiveModel::Callbacks
3
+ include ActiveModel::Validations
4
+ include Devise::Models::TwoFactorAuthenticatable
5
+
6
+ define_model_callbacks :create
7
+ attr_accessor :encrypted_otp_secret_key,
8
+ :encrypted_otp_secret_key_iv,
9
+ :encrypted_otp_secret_key_salt,
10
+ :email,
11
+ :second_factor_attempts_count,
12
+ :totp_timestamp
13
+
14
+ has_one_time_password(encrypted: true)
15
+ end
@@ -4,7 +4,14 @@ class GuestUser
4
4
  include Devise::Models::TwoFactorAuthenticatable
5
5
 
6
6
  define_model_callbacks :create
7
- attr_accessor :otp_secret_key, :email, :second_factor_attempts_count
7
+ attr_accessor :direct_otp, :direct_otp_sent_at, :otp_secret_key, :email,
8
+ :second_factor_attempts_count, :totp_timestamp
9
+
10
+ def update_attributes(attrs)
11
+ attrs.each do |key, value|
12
+ send(key.to_s + '=', value)
13
+ end
14
+ end
8
15
 
9
16
  has_one_time_password
10
17
  end
@@ -1,12 +1,11 @@
1
1
  class User < ActiveRecord::Base
2
2
  devise :two_factor_authenticatable, :database_authenticatable, :registerable,
3
- :recoverable, :rememberable, :trackable, :validatable,
4
- :two_factor_authenticatable
3
+ :recoverable, :rememberable, :trackable, :validatable
5
4
 
6
5
  has_one_time_password
7
6
 
8
- def send_two_factor_authentication_code
9
- SMSProvider.send_message(to: phone_number, body: otp_code)
7
+ def send_two_factor_authentication_code(code)
8
+ SMSProvider.send_message(to: phone_number, body: code)
10
9
  end
11
10
 
12
11
  def phone_number
@@ -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
@@ -29,4 +35,7 @@ Dummy::Application.configure do
29
35
 
30
36
  # Print deprecation notices to the stderr
31
37
  config.active_support.deprecation = :stderr
38
+
39
+ # For testing session variables in Capybara specs
40
+ config.middleware.use RackSessionAccess::Middleware
32
41
  end
@@ -206,11 +206,11 @@ Devise.setup do |config|
206
206
 
207
207
  # Configure the default scope given to Warden. By default it's the first
208
208
  # devise role declared in your routes (usually :user).
209
- # config.default_scope = :user
209
+ config.default_scope = :user
210
210
 
211
211
  # Set this configuration to false if you want /users/sign_out to sign out
212
212
  # only the current scope. By default, Devise signs out all scopes.
213
- # config.sign_out_all_scopes = true
213
+ config.sign_out_all_scopes = false
214
214
 
215
215
  # ==> Navigation configuration
216
216
  # Lists the formats that should be treated as navigational. Formats like
@@ -224,7 +224,7 @@ Devise.setup do |config|
224
224
  # config.navigational_formats = ['*/*', :html]
225
225
 
226
226
  # The default HTTP method used to sign out a resource. Default is :delete.
227
- config.sign_out_via = :delete
227
+ config.sign_out_via = Rails.env.test? ? :get : :delete
228
228
 
229
229
  # ==> OmniAuth
230
230
  # Add a new OmniAuth provider. Check the wiki for more information on setting
@@ -253,4 +253,6 @@ Devise.setup do |config|
253
253
  # When using omniauth, Devise cannot automatically set Omniauth path,
254
254
  # so you need to do it manually. For the users scope, it would be:
255
255
  # config.omniauth_path_prefix = '/my_engine/users/auth'
256
+
257
+ config.otp_secret_encryption_key = '0a8283fba984da1de24e4df1e93046cb53c5787944ef037b2dbf3e61d20fe11f25e25a855cec605fdf65b162329890d7230afdf64f681b4c32020281054e73ec'
256
258
  end
@@ -1,4 +1,5 @@
1
1
  Dummy::Application.routes.draw do
2
+ devise_for :admins
2
3
  root to: "home#index"
3
4
 
4
5
  match "/dashboard", to: "home#dashboard", as: :dashboard, via: [:get]
@@ -1,4 +1,4 @@
1
- class DeviseCreateUsers < ActiveRecord::Migration
1
+ class DeviseCreateUsers < ActiveRecord::Migration[4.2]
2
2
  def change
3
3
  create_table(:users) do |t|
4
4
  ## Database authenticatable
@@ -31,7 +31,7 @@ class DeviseCreateUsers < ActiveRecord::Migration
31
31
  # t.datetime :locked_at
32
32
 
33
33
 
34
- t.timestamps
34
+ t.timestamps null: false
35
35
  end
36
36
 
37
37
  add_index :users, :email, unique: true
@@ -1,4 +1,4 @@
1
- class TwoFactorAuthenticationAddToUsers < ActiveRecord::Migration
1
+ class TwoFactorAuthenticationAddToUsers < ActiveRecord::Migration[4.2]
2
2
  def up
3
3
  change_table :users do |t|
4
4
  t.string :otp_secret_key
@@ -1,4 +1,4 @@
1
- class AddNickanmeToUsers < ActiveRecord::Migration
1
+ class AddNickanmeToUsers < ActiveRecord::Migration[4.2]
2
2
  def change
3
3
  change_table :users do |t|
4
4
  t.column :nickname, :string, limit: 64
@@ -0,0 +1,9 @@
1
+ class AddEncryptedColumnsToUser < ActiveRecord::Migration[4.2]
2
+ def change
3
+ add_column :users, :encrypted_otp_secret_key, :string
4
+ add_column :users, :encrypted_otp_secret_key_iv, :string
5
+ add_column :users, :encrypted_otp_secret_key_salt, :string
6
+
7
+ add_index :users, :encrypted_otp_secret_key, unique: true
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ class PopulateOtpColumn < ActiveRecord::Migration[4.2]
2
+ def up
3
+ User.reset_column_information
4
+
5
+ User.find_each do |user|
6
+ user.otp_secret_key = user.read_attribute('otp_secret_key')
7
+ user.save!
8
+ end
9
+ end
10
+
11
+ def down
12
+ User.reset_column_information
13
+
14
+ User.find_each do |user|
15
+ user.otp_secret_key = ROTP::Base32.random_base32
16
+ user.save!
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ class RemoveOtpSecretKeyFromUser < ActiveRecord::Migration[4.2]
2
+ def change
3
+ remove_column :users, :otp_secret_key, :string
4
+ end
5
+ end