devise-2fa 0.1.1 → 0.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.
- checksums.yaml +5 -5
- data/.circleci/config.yml +46 -0
- data/.gitignore +8 -0
- data/Gemfile +3 -22
- data/README.md +13 -14
- data/Rakefile +6 -28
- data/bin/rspec +10 -0
- data/bin/setup +12 -0
- data/{devise-2fa.gemspec → devise_2fa.gemspec} +15 -8
- data/lib/devise-2fa/version.rb +1 -1
- data/lib/devise_two_factorable/models/two_factorable.rb +5 -1
- data/{test → spec}/dummy/Rakefile +2 -3
- data/{test/dummy/app/mailers/.gitkeep → spec/dummy/app/assets/images/.keep} +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +3 -0
- data/{test/dummy/lib/assets/.gitkeep → spec/dummy/app/assets/javascripts/channels/.keep} +0 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/{test → spec}/dummy/app/controllers/application_controller.rb +4 -1
- data/{test/dummy/public/favicon.ico → spec/dummy/app/controllers/concerns/.keep} +0 -0
- data/{test → spec}/dummy/app/helpers/application_helper.rb +0 -0
- data/spec/dummy/app/models/application_record.rb +3 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/models/user.rb +6 -0
- data/spec/dummy/app/views/layouts/application.html.erb +19 -0
- data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +25 -0
- data/spec/dummy/bin/update +25 -0
- data/spec/dummy/bin/yarn +11 -0
- data/spec/dummy/config.ru +5 -0
- data/spec/dummy/config/application.rb +14 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/{test → spec}/dummy/config/database.yml +10 -10
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +61 -0
- data/{test → spec}/dummy/config/environments/test.rb +15 -5
- data/spec/dummy/config/initializers/assets.rb +4 -0
- data/{test → spec}/dummy/config/initializers/backtrace_silencers.rb +0 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/{test → spec}/dummy/config/initializers/devise.rb +134 -56
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/{test → spec}/dummy/config/initializers/inflections.rb +6 -5
- data/{test → spec}/dummy/config/initializers/mime_types.rb +0 -1
- data/{test → spec}/dummy/config/initializers/wrap_parameters.rb +5 -5
- data/spec/dummy/config/locales/devise.en.yml +68 -0
- data/spec/dummy/config/locales/devise.two_factor.en.yml +57 -0
- data/spec/dummy/config/locales/en.yml +2 -0
- data/spec/dummy/config/puma.rb +9 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/config/spring.rb +6 -0
- data/spec/dummy/config/storage.yml +8 -0
- data/spec/dummy/db/migrate/20190311184605_devise_create_users.rb +44 -0
- data/{test/dummy/db/migrate/20130131160351_devise_otp_add_to_users.rb → spec/dummy/db/migrate/20190312222952_devise_two_factor_add_to_users.rb} +4 -5
- data/spec/dummy/db/schema.rb +39 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/package.json +5 -0
- data/spec/dummy/public/404.html +1 -0
- data/spec/dummy/public/422.html +1 -0
- data/spec/dummy/public/500.html +19 -0
- data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/dummy/public/apple-touch-icon.png +0 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/storage/.keep +0 -0
- data/spec/models/user_spec.rb +33 -0
- data/spec/spec_helper.rb +69 -0
- data/spec/system/persistence_spec.rb +59 -0
- data/spec/system/refresh_spec.rb +100 -0
- data/spec/system/token_spec.rb +41 -0
- data/spec/system/users_spec.rb +98 -0
- metadata +213 -123
- data/.travis.yml +0 -28
- data/lib/devise_two_factorable/two_factorable.rb +0 -131
- data/test/dummy/README.rdoc +0 -261
- data/test/dummy/app/assets/javascripts/application.js +0 -13
- data/test/dummy/app/assets/stylesheets/application.css +0 -13
- data/test/dummy/app/controllers/posts_controller.rb +0 -83
- data/test/dummy/app/helpers/posts_helper.rb +0 -2
- data/test/dummy/app/models/post.rb +0 -2
- data/test/dummy/app/models/user.rb +0 -20
- data/test/dummy/app/views/layouts/application.html.erb +0 -14
- data/test/dummy/app/views/posts/_form.html.erb +0 -25
- data/test/dummy/app/views/posts/edit.html.erb +0 -6
- data/test/dummy/app/views/posts/index.html.erb +0 -25
- data/test/dummy/app/views/posts/new.html.erb +0 -5
- data/test/dummy/app/views/posts/show.html.erb +0 -15
- data/test/dummy/config.ru +0 -4
- data/test/dummy/config/application.rb +0 -67
- data/test/dummy/config/boot.rb +0 -10
- data/test/dummy/config/environment.rb +0 -5
- data/test/dummy/config/environments/development.rb +0 -37
- data/test/dummy/config/environments/production.rb +0 -73
- data/test/dummy/config/initializers/secret_token.rb +0 -8
- data/test/dummy/config/initializers/session_store.rb +0 -8
- data/test/dummy/config/locales/en.yml +0 -5
- data/test/dummy/config/routes.rb +0 -6
- data/test/dummy/db/migrate/20130125101430_create_users.rb +0 -9
- data/test/dummy/db/migrate/20130131092406_add_devise_to_users.rb +0 -52
- data/test/dummy/db/migrate/20130131142320_create_posts.rb +0 -10
- data/test/dummy/public/404.html +0 -26
- data/test/dummy/public/422.html +0 -26
- data/test/dummy/public/500.html +0 -25
- data/test/dummy/script/rails +0 -6
- data/test/integration/persistence_test.rb +0 -63
- data/test/integration/refresh_test.rb +0 -103
- data/test/integration/sign_in_test.rb +0 -85
- data/test/integration/token_test.rb +0 -30
- data/test/integration_tests_helper.rb +0 -64
- data/test/model_tests_helper.rb +0 -20
- data/test/models/two_factorable_test.rb +0 -120
- data/test/orm/active_record.rb +0 -4
- data/test/orm/mongoid.rb +0 -13
- data/test/support/mongoid.yml +0 -6
- data/test/support/symmetric_encryption.yml +0 -70
- data/test/test_helper.rb +0 -18
@@ -1,64 +0,0 @@
|
|
1
|
-
class ActionDispatch::IntegrationTest
|
2
|
-
include Warden::Test::Helpers
|
3
|
-
|
4
|
-
def warden
|
5
|
-
request.env['warden']
|
6
|
-
end
|
7
|
-
|
8
|
-
def create_full_user
|
9
|
-
@user ||= begin
|
10
|
-
user = User.create!(
|
11
|
-
email: 'user@email.invalid',
|
12
|
-
password: '12345678',
|
13
|
-
password_confirmation: '12345678'
|
14
|
-
)
|
15
|
-
user
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def enable_otp_and_sign_in_with_otp
|
20
|
-
enable_otp_and_sign_in.tap do |user|
|
21
|
-
fill_in 'user_token', with: ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
22
|
-
click_button 'Submit Token'
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def enable_otp_and_sign_in
|
27
|
-
user = create_full_user
|
28
|
-
sign_user_in(user)
|
29
|
-
visit user_token_path
|
30
|
-
check 'user_otp_enabled'
|
31
|
-
click_button 'Continue...'
|
32
|
-
|
33
|
-
Capybara.reset_sessions!
|
34
|
-
|
35
|
-
sign_user_in(user)
|
36
|
-
user
|
37
|
-
end
|
38
|
-
|
39
|
-
def otp_challenge_for(user)
|
40
|
-
fill_in 'user_token', with: ROTP::TOTP.new(user.otp_auth_secret).at(Time.now)
|
41
|
-
click_button 'Submit Token'
|
42
|
-
end
|
43
|
-
|
44
|
-
def disable_otp
|
45
|
-
visit user_token_path
|
46
|
-
uncheck 'user_otp_enabled'
|
47
|
-
click_button 'Continue...'
|
48
|
-
end
|
49
|
-
|
50
|
-
def sign_out
|
51
|
-
logout :user
|
52
|
-
end
|
53
|
-
|
54
|
-
def sign_user_in(user = nil)
|
55
|
-
user ||= create_full_user
|
56
|
-
resource_name = user.class.name.underscore
|
57
|
-
visit send("new_#{resource_name}_session_path")
|
58
|
-
fill_in "#{resource_name}_email", with: user.email
|
59
|
-
fill_in "#{resource_name}_password", with: user.password
|
60
|
-
|
61
|
-
page.has_content?('Log in') ? click_button('Log in') : click_button('Sign in')
|
62
|
-
user
|
63
|
-
end
|
64
|
-
end
|
data/test/model_tests_helper.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
class ActiveSupport::TestCase
|
2
|
-
#
|
3
|
-
# Helpers for creating new users
|
4
|
-
#
|
5
|
-
def unique_identity
|
6
|
-
@@unique_identity_count ||= 0
|
7
|
-
@@unique_identity_count += 1
|
8
|
-
"user-#{@@unique_identity_count}@mail.invalid"
|
9
|
-
end
|
10
|
-
|
11
|
-
def valid_attributes(attributes = {})
|
12
|
-
{ email: unique_identity,
|
13
|
-
password: '12345678',
|
14
|
-
password_confirmation: '12345678' }.update(attributes)
|
15
|
-
end
|
16
|
-
|
17
|
-
def new_user(attributes = {})
|
18
|
-
User.new(valid_attributes(attributes)).save
|
19
|
-
end
|
20
|
-
end
|
@@ -1,120 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
require 'model_tests_helper'
|
3
|
-
|
4
|
-
class TwoFactorableTest < ActiveSupport::TestCase
|
5
|
-
def setup
|
6
|
-
new_user
|
7
|
-
end
|
8
|
-
|
9
|
-
test 'new users have a non-nil secret set' do
|
10
|
-
assert_not_nil User.first.otp_auth_secret
|
11
|
-
end
|
12
|
-
|
13
|
-
test 'new users have OTP disabled by default' do
|
14
|
-
assert !User.first.otp_enabled
|
15
|
-
end
|
16
|
-
|
17
|
-
test 'users should have an instance of TOTP/ROTP objects' do
|
18
|
-
u = User.first
|
19
|
-
assert u.time_based_otp.is_a? ROTP::TOTP
|
20
|
-
assert u.recovery_otp.is_a? ROTP::HOTP
|
21
|
-
end
|
22
|
-
|
23
|
-
test 'users should have their otp_auth_secret/persistence_seed set on creation' do
|
24
|
-
assert User.first.otp_auth_secret
|
25
|
-
assert User.first.otp_persistence_seed
|
26
|
-
end
|
27
|
-
|
28
|
-
test 'reset_otp_credentials should generate new secrets and disable OTP' do
|
29
|
-
u = User.first
|
30
|
-
u.update_attribute(:otp_enabled, true)
|
31
|
-
assert u.otp_enabled
|
32
|
-
otp_auth_secret = u.otp_auth_secret
|
33
|
-
otp_persistence_seed = u.otp_persistence_seed
|
34
|
-
|
35
|
-
u.reset_otp_credentials!
|
36
|
-
assert !(otp_auth_secret == u.otp_auth_secret)
|
37
|
-
assert !(otp_persistence_seed == u.otp_persistence_seed)
|
38
|
-
assert !u.otp_enabled
|
39
|
-
end
|
40
|
-
|
41
|
-
test 'reset_otp_persistence should generate new persistence_seed but NOT change the otp_auth_secret' do
|
42
|
-
u = User.first
|
43
|
-
u.update_attribute(:otp_enabled, true)
|
44
|
-
assert u.otp_enabled
|
45
|
-
otp_auth_secret = u.otp_auth_secret
|
46
|
-
otp_persistence_seed = u.otp_persistence_seed
|
47
|
-
|
48
|
-
u.reset_otp_persistence!
|
49
|
-
assert (otp_auth_secret == u.otp_auth_secret)
|
50
|
-
assert !(otp_persistence_seed == u.otp_persistence_seed)
|
51
|
-
assert u.otp_enabled
|
52
|
-
end
|
53
|
-
|
54
|
-
test 'generating a challenge, should retrieve the user later' do
|
55
|
-
u = User.first
|
56
|
-
u.update_attribute(:otp_enabled, true)
|
57
|
-
challenge = u.generate_otp_challenge!
|
58
|
-
|
59
|
-
w = User.find_valid_otp_challenge(challenge)
|
60
|
-
assert w.is_a? User
|
61
|
-
assert_equal w, u
|
62
|
-
end
|
63
|
-
|
64
|
-
test 'expiring the challenge, should retrieve nothing' do
|
65
|
-
u = User.first
|
66
|
-
u.update_attribute(:otp_enabled, true)
|
67
|
-
challenge = u.generate_otp_challenge!(1.second)
|
68
|
-
sleep(2)
|
69
|
-
|
70
|
-
w = User.find_valid_otp_challenge(challenge)
|
71
|
-
assert_nil w
|
72
|
-
end
|
73
|
-
|
74
|
-
test 'expired challenges should not be valid' do
|
75
|
-
u = User.first
|
76
|
-
u.update_attribute(:otp_enabled, true)
|
77
|
-
challenge = u.generate_otp_challenge!(1.second)
|
78
|
-
sleep(2)
|
79
|
-
assert_equal false, u.otp_challenge_valid?
|
80
|
-
end
|
81
|
-
|
82
|
-
test 'null otp challenge' do
|
83
|
-
u = User.first
|
84
|
-
u.update_attribute(:otp_enabled, true)
|
85
|
-
assert_equal false, u.validate_otp_token('')
|
86
|
-
assert_equal false, u.validate_otp_token(nil)
|
87
|
-
end
|
88
|
-
|
89
|
-
test 'generated otp token should be valid for the user' do
|
90
|
-
u = User.first
|
91
|
-
u.update_attribute(:otp_enabled, true)
|
92
|
-
|
93
|
-
secret = u.otp_auth_secret
|
94
|
-
token = ROTP::TOTP.new(secret).now
|
95
|
-
|
96
|
-
assert_equal true, u.validate_otp_token(token)
|
97
|
-
end
|
98
|
-
|
99
|
-
test 'generated otp token, out of drift window, should be NOT valid for the user' do
|
100
|
-
u = User.first
|
101
|
-
u.update_attribute(:otp_enabled, true)
|
102
|
-
|
103
|
-
secret = u.otp_auth_secret
|
104
|
-
|
105
|
-
[3.minutes.from_now, 3.minutes.ago].each do |time|
|
106
|
-
token = ROTP::TOTP.new(secret).at(time)
|
107
|
-
assert_equal false, u.valid_otp_token?(token)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
test 'recovery secrets should be valid, and valid only once' do
|
112
|
-
u = User.first
|
113
|
-
u.update_attribute(:otp_enabled, true)
|
114
|
-
recovery = u.next_otp_recovery_tokens
|
115
|
-
|
116
|
-
assert u.valid_otp_recovery_token? recovery.fetch(0)
|
117
|
-
assert_equal false, u.valid_otp_recovery_token?(recovery.fetch(0))
|
118
|
-
assert u.valid_otp_recovery_token? recovery.fetch(2)
|
119
|
-
end
|
120
|
-
end
|
data/test/orm/active_record.rb
DELETED
data/test/orm/mongoid.rb
DELETED
data/test/support/mongoid.yml
DELETED
@@ -1,70 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# Symmetric Encryption for Ruby
|
3
|
-
#
|
4
|
-
---
|
5
|
-
# For the development and test environments the test symmetric encryption keys
|
6
|
-
# can be placed directly in the source code.
|
7
|
-
# And therefore no RSA private key is required
|
8
|
-
development: &development_defaults
|
9
|
-
key: 1234567890ABCDEF1234567890ABCDEF
|
10
|
-
iv: 1234567890ABCDEF
|
11
|
-
cipher_name: aes-128-cbc
|
12
|
-
|
13
|
-
test:
|
14
|
-
<<: *development_defaults
|
15
|
-
|
16
|
-
production:
|
17
|
-
# Since the key to encrypt and decrypt with must NOT be stored along with the
|
18
|
-
# source code, we only hold a RSA key that is used to unlock the file
|
19
|
-
# containing the actual symmetric encryption key
|
20
|
-
#
|
21
|
-
# Sample RSA Key, DO NOT use this RSA key, generate a new one using
|
22
|
-
# openssl genrsa 2048
|
23
|
-
private_rsa_key: |
|
24
|
-
-----BEGIN RSA PRIVATE KEY-----
|
25
|
-
MIIEpAIBAAKCAQEAxIL9H/jYUGpA38v6PowRSRJEo3aNVXULNM/QNRpx2DTf++KH
|
26
|
-
6DcuFTFcNSSSxG9n4y7tKi755be8N0uwCCuOzvXqfWmXYjbLwK3Ib2vm0btpHyvA
|
27
|
-
qxgqeJOOCxKdW/cUFLWn0tACUcEjVCNfWEGaFyvkOUuR7Ub9KfhbW9cZO3BxZMUf
|
28
|
-
IPGlHl/gWyf484sXygd+S7cpDTRRzo9RjG74DwfE0MFGf9a1fTkxnSgeOJ6asTOy
|
29
|
-
fp9tEToUlbglKaYGpOGHYQ9TV5ZsyJ9jRUyb4SP5wK2eK6dHTxTcHvT03kD90Hv4
|
30
|
-
WeKIXv3WOjkwNEyMdpnJJfSDb5oquQvCNi7ZSQIDAQABAoIBAQCbzR7TUoBugU+e
|
31
|
-
ICLvpC2wOYOh9kRoFLwlyv3QnH7WZFWRZzFJszYeJ1xr5etXQtyjCnmOkGAg+WOI
|
32
|
-
k8GlOKOpAuA/PpB/leJFiYL4lBwU/PmDdTT0cdx6bMKZlNCeMW8CXGQKiFDOcMqJ
|
33
|
-
0uGtH5YD+RChPIEeFsJxnC8SyZ9/t2ra7XnMGiCZvRXIUDSEIIsRx/mOymJ7bL+h
|
34
|
-
Lbp46IfXf6ZuIzwzoIk0JReV/r+wdmkAVDkrrMkCmVS4/X1wN/Tiik9/yvbsh/CL
|
35
|
-
ztC55eSIEjATkWxnXfPASZN6oUfQPEveGH3HzNjdncjH/Ho8FaNMIAfFpBhhLPi9
|
36
|
-
nG5sbH+BAoGBAOdoUyVoAA/QUa3/FkQaa7Ajjehe5MR5k6VtaGtcxrLiBjrNR7x+
|
37
|
-
nqlZlGvWDMiCz49dgj+G1Qk1bbYrZLRX/Hjeqy5dZOGLMfgf9eKUmS1rDwAzBMcj
|
38
|
-
M9jnnJEBx8HIlNzaR6wzp3GMd0rrccs660A8URvzkgo9qNbvMLq9vyUtAoGBANll
|
39
|
-
SY1Iv9uaIz8klTXU9YzYtsfUmgXzw7K8StPdbEbo8F1J3JPJB4D7QHF0ObIaSWuf
|
40
|
-
suZqLsvWlYGuJeyX2ntlBN82ORfvUdOrdrbDlmPyj4PfFVl0AK3U3Ai374DNrjKR
|
41
|
-
hF6YFm4TLDaJhUjeV5C43kbE1N2FAMS9LYtPJ44NAoGAFDGHZ/E+aCLerddfwwun
|
42
|
-
MBS6MnftcLPHTZ1RimTrNfsBXipBw1ItWEvn5s0kCm9X24PmdNK4TnhqHYaF4DL5
|
43
|
-
ZjbQK1idEA2Mi8GGPIKJJ2x7P6I0HYiV4qy7fe/w1ZlCXE90B7PuPbtrQY9wO7Ll
|
44
|
-
ipJ45X6I1PnyfOcckn8yafUCgYACtPAlgjJhWZn2v03cTbqA9nHQKyV/zXkyUIXd
|
45
|
-
/XPLrjrP7ouAi5A8WuSChR/yx8ECRgrEM65Be3qBEtoGCB4AS1G0NcigM6qhKBFi
|
46
|
-
VS0aMXr3+V8argcUIwJaWW/x+p2go48yXlJpLHPweeXe8mXEt4iM+QZte6p2yKQ4
|
47
|
-
h9PGQQKBgQCqSydmXBnXGIVTp2sH/2GnpxLYnDBpcJE0tM8bJ42HEQQgRThIChsn
|
48
|
-
PnGA91G9MVikYapgI0VYBHQOTsz8rTIUzsKwXG+TIaK+W84nxH5y6jUkjqwxZmAz
|
49
|
-
r1URaMAun2PfAB4g2N/kEZTExgeOGqXjFhvvjdzl97ux2cTyZhaTXg==
|
50
|
-
-----END RSA PRIVATE KEY-----
|
51
|
-
|
52
|
-
# List Symmetric Key files in the order of current / latest first
|
53
|
-
ciphers:
|
54
|
-
-
|
55
|
-
# Filename containing Symmetric Encryption Key encrypted using the
|
56
|
-
# RSA public key derived from the private key above
|
57
|
-
key_filename: /etc/rails/.rails.key
|
58
|
-
iv_filename: /etc/rails/.rails.iv
|
59
|
-
|
60
|
-
# Encryption cipher_name
|
61
|
-
# Recommended values:
|
62
|
-
# aes-256-cbc
|
63
|
-
# 256 AES CBC Algorithm. Very strong
|
64
|
-
# Ruby 1.8.7 MRI Approximately 100,000 encryptions or decryptions per second
|
65
|
-
# JRuby 1.6.7 with Ruby 1.8.7 Approximately 22,000 encryptions or decryptions per second
|
66
|
-
# aes-128-cbc
|
67
|
-
# 128 AES CBC Algorithm. Less strong.
|
68
|
-
# Ruby 1.8.7 MRI Approximately 100,000 encryptions or decryptions per second
|
69
|
-
# JRuby 1.6.7 with Ruby 1.8.7 Approximately 22,000 encryptions or decryptions per second
|
70
|
-
cipher_name: aes-256-cbc
|
data/test/test_helper.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
ENV['RAILS_ENV'] = 'test'
|
2
|
-
DEVISE_ORM = (ENV['DEVISE_ORM'] || :active_record).to_sym
|
3
|
-
|
4
|
-
$LOAD_PATH.unshift File.dirname(__FILE__)
|
5
|
-
puts "\n==> Devise.orm = #{DEVISE_ORM.inspect}"
|
6
|
-
require 'dummy/config/environment'
|
7
|
-
require "orm/#{DEVISE_ORM}"
|
8
|
-
require 'rails/test_help'
|
9
|
-
require 'capybara/rails'
|
10
|
-
require 'minitest/reporters'
|
11
|
-
|
12
|
-
I18n.load_path << File.expand_path("../support/locale/en.yml", __FILE__) if DEVISE_ORM == :mongoid
|
13
|
-
|
14
|
-
MiniTest::Reporters.use!
|
15
|
-
|
16
|
-
class ActionDispatch::IntegrationTest
|
17
|
-
include Capybara::DSL
|
18
|
-
end
|