devise-2fa 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|