two_factor_authentication 1.1.3 → 2.2.0

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