active_model_otp 1.2.0 → 2.0.1
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/.gitignore +1 -0
- data/.travis.yml +22 -6
- data/Appraisals +25 -0
- data/CHANGELOG.md +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +25 -14
- data/active_model_otp.gemspec +10 -3
- data/gemfiles/rails_4.2.gemfile +7 -0
- data/gemfiles/rails_5.0.gemfile +8 -0
- data/gemfiles/rails_5.1.gemfile +8 -0
- data/gemfiles/rails_5.2.gemfile +8 -0
- data/gemfiles/rails_6.0.0.rc1.gemfile +10 -0
- data/lib/active_model/one_time_password.rb +32 -15
- data/lib/active_model/otp/version.rb +1 -1
- data/test/models/activerecord_user.rb +3 -0
- data/test/models/member.rb +10 -0
- data/test/models/user.rb +5 -0
- data/test/one_time_password_test.rb +53 -21
- data/test/schema.rb +11 -0
- data/test/test_helper.rb +9 -0
- metadata +53 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4f10c7c77b302adbd5a02bb37378c46eafd0d6aaf53f2f5ec7ac069bd888ab07
|
4
|
+
data.tar.gz: 2572cac964589cc665f0a0dbaacaa7ca8fc226cef62ebeb122e591c487b6278d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e78abb140e71aca80842a896ef02abad3df97be46b57e096056b8480c150aa5c8c633725cb8d0aaa49eaf9e26b5846741ac9eb6b99a963f59a7b835e88101605
|
7
|
+
data.tar.gz: 9d7d0fef52db81c831a728808b5768714fcbc64df126a65832a9ed86769379e4ee007f894193183a536ba2d1ea32343f77537fb732bd84de2168e75b02202bef
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,10 +1,26 @@
|
|
1
1
|
rvm:
|
2
|
-
-
|
3
|
-
- 2.
|
4
|
-
- 2.
|
2
|
+
- 2.3
|
3
|
+
- 2.4
|
4
|
+
- 2.5
|
5
|
+
- 2.6
|
6
|
+
- ruby-head
|
7
|
+
gemfile:
|
8
|
+
- gemfiles/rails_4.2.gemfile
|
9
|
+
- gemfiles/rails_5.0.gemfile
|
10
|
+
- gemfiles/rails_5.1.gemfile
|
11
|
+
- gemfiles/rails_5.2.gemfile
|
12
|
+
- gemfiles/rails_6.0.0.rc1.gemfile
|
5
13
|
matrix:
|
6
|
-
|
7
|
-
- rvm:
|
8
|
-
|
14
|
+
exclude:
|
15
|
+
- rvm: 2.3
|
16
|
+
gemfile: gemfiles/rails_6.0.0.rc1.gemfile
|
17
|
+
- rvm: 2.4
|
18
|
+
gemfile: gemfiles/rails_6.0.0.rc1.gemfile
|
19
|
+
fast_finish: true
|
20
|
+
allow_failures:
|
21
|
+
- rvm: ruby-head
|
22
|
+
# include:
|
23
|
+
# - rvm: jruby
|
24
|
+
# env: JRUBY_OPTS="--1.9 --server -Xcext.enabled=true"
|
9
25
|
notifications:
|
10
26
|
email: false
|
data/Appraisals
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
appraise "rails-4.2" do
|
2
|
+
gem "activemodel", "~> 4.2"
|
3
|
+
end
|
4
|
+
|
5
|
+
appraise "rails-5.0" do
|
6
|
+
gem "activemodel", "~> 5.0"
|
7
|
+
gem "activemodel-serializers-xml"
|
8
|
+
end
|
9
|
+
|
10
|
+
appraise "rails-5.1" do
|
11
|
+
gem "activemodel", "~> 5.1"
|
12
|
+
gem "activemodel-serializers-xml"
|
13
|
+
end
|
14
|
+
|
15
|
+
appraise "rails-5.2" do
|
16
|
+
gem "activemodel", "~> 5.2"
|
17
|
+
gem "activemodel-serializers-xml"
|
18
|
+
end
|
19
|
+
|
20
|
+
appraise "rails-6.0.0.rc1" do
|
21
|
+
gem "activerecord", "6.0.0.rc1"
|
22
|
+
gem "activemodel", "6.0.0.rc1"
|
23
|
+
gem "activemodel-serializers-xml"
|
24
|
+
gem "sqlite3", "~> 1.4"
|
25
|
+
end
|
data/CHANGELOG.md
CHANGED
@@ -1,17 +1,17 @@
|
|
1
|
-
#v1.2.0
|
1
|
+
# v1.2.0
|
2
2
|
- Added Counter based OTP (HOTP) (@ResultsMayVary ) https://github.com/heapsource/active_model_otp/pull/19
|
3
3
|
- Adding options to provisioning uri, so we can include issuer (@doon) https://github.com/heapsource/active_model_otp/pull/15
|
4
4
|
|
5
|
-
#v1.1.0
|
5
|
+
# v1.1.0
|
6
6
|
- Add function to re-geterante the OTP secret (@TikiTDO) https://github.com/heapsource/active_model_otp/pull/14
|
7
7
|
- Added option to pass OTP length (@shivanibhanwal) https://github.com/heapsource/active_model_otp/pull/13
|
8
8
|
|
9
|
-
#v1.0.0
|
9
|
+
# v1.0.0
|
10
10
|
- Avoid overriding predefined otp_column value when initializing resource (Ilan Stern) https://github.com/heapsource/active_model_otp/pull/10
|
11
11
|
- Pad OTP codes with less than 6 digits (Johan Brissmyr) https://github.com/heapsource/active_model_otp/pull/7
|
12
12
|
- Get rid of deprecation warnings in Rails 4.1 (Nick DeMonner)
|
13
13
|
|
14
|
-
#v0.1.0
|
14
|
+
# v0.1.0
|
15
15
|
- OTP codes can be in 5 or 6 digits (André Luis Leal Cardoso Junior)
|
16
16
|
- Require 'cgi', rotp needs it for encoding parameters (André Luis Leal Cardoso Junior)
|
17
17
|
- Change column name for otp secret key (robertomiranda)
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
[](https://travis-ci.org/heapsource/active_model_otp)
|
2
2
|
[](http://badge.fury.io/rb/active_model_otp)
|
3
|
-
[](https://codeclimate.com/github/heapsource/active_model_otp)
|
3
|
+
[](https://houndci.com)
|
5
4
|
|
6
5
|
|
7
6
|
# ActiveModel::Otp
|
8
7
|
|
9
|
-
**ActiveModel::Otp** makes adding **Two Factor Authentication** (TFA) to a model simple. Let's see what's required to get AMo::Otp working in our Application, using Rails
|
8
|
+
**ActiveModel::Otp** makes adding **Two Factor Authentication** (TFA) to a model simple. Let's see what's required to get AMo::Otp working in our Application, using Rails 5.0 (AMo::Otp is also compatible with Rails 4.x versions). We're going to use a User model and try to add options provided by **ActiveMOdel::Otp**. Inspired by AM::SecurePassword
|
9
|
+
|
10
|
+
## Dependencies
|
11
|
+
|
12
|
+
* [ROTP](https://github.com/mdp/rotp) 5.0 or higher
|
10
13
|
|
11
14
|
## Installation
|
12
15
|
|
@@ -36,27 +39,27 @@ rails g migration AddOtpSecretKeyToUsers otp_secret_key:string
|
|
36
39
|
We’ll then need to run rake db:migrate to update the users table in the database. The next step is to update the model code. We need to use has_one_time_password to make it use TFA.
|
37
40
|
|
38
41
|
```ruby
|
39
|
-
class User <
|
42
|
+
class User < ApplicationRecord
|
40
43
|
has_one_time_password
|
41
44
|
end
|
42
45
|
```
|
43
46
|
|
44
47
|
Note: If you're adding this to an existing user model you'll need to generate *otp_secret_key* with a migration like:
|
45
48
|
```ruby
|
46
|
-
User.
|
49
|
+
User.find_each { |user| user.update_attribute(:otp_secret_key, User.otp_random_secret) }
|
47
50
|
```
|
48
51
|
|
49
52
|
To use a custom column to store the secret key field you can use the column_name option. It is also possible to generate codes with a specified length.
|
50
53
|
|
51
54
|
```ruby
|
52
|
-
class User <
|
55
|
+
class User < ApplicationRecord
|
53
56
|
has_one_time_password column_name: :my_otp_secret_column, length: 4
|
54
57
|
end
|
55
58
|
```
|
56
59
|
|
57
60
|
## Usage
|
58
61
|
|
59
|
-
The has_one_time_password statement provides to the model some useful methods in order to implement our TFA system. AMo:Otp generates one time passwords according to [TOTP RFC 6238](
|
62
|
+
The has_one_time_password statement provides to the model some useful methods in order to implement our TFA system. AMo:Otp generates one time passwords according to [TOTP RFC 6238](https://tools.ietf.org/html/rfc6238) and the [HOTP RFC 4226](https://www.ietf.org/rfc/rfc4226). This is compatible with Google Authenticator apps available for Android and iPhone, and now in use on GMail.
|
60
63
|
|
61
64
|
The otp_secret_key is saved automatically when an object is created,
|
62
65
|
|
@@ -76,9 +79,6 @@ user.otp_code # => '850738'
|
|
76
79
|
|
77
80
|
# Override current time
|
78
81
|
user.otp_code(time: Time.now + 3600) # => '317438'
|
79
|
-
|
80
|
-
# Don't zero-pad to six digits
|
81
|
-
user.otp_code(padding: false) # => '438'
|
82
82
|
```
|
83
83
|
|
84
84
|
### Authenticating using a code
|
@@ -108,10 +108,15 @@ rails g migration AddCounterForOtpToUsers otp_counter:integer
|
|
108
108
|
create db/migrate/20130707010931_add_counter_for_otp_to_users.rb
|
109
109
|
```
|
110
110
|
|
111
|
+
Set default value for otp_counter to 0.
|
112
|
+
```ruby
|
113
|
+
change_column :users, :otp_counter, :integer, default: 0
|
114
|
+
```
|
115
|
+
|
111
116
|
In addition set the counter flag option to true
|
112
117
|
|
113
118
|
```ruby
|
114
|
-
class User <
|
119
|
+
class User < ApplicationRecord
|
115
120
|
has_one_time_password counter_based: true
|
116
121
|
end
|
117
122
|
```
|
@@ -119,7 +124,7 @@ end
|
|
119
124
|
And for a custom counter column
|
120
125
|
|
121
126
|
```ruby
|
122
|
-
class User <
|
127
|
+
class User < ApplicationRecord
|
123
128
|
has_one_time_password counter_based: true, counter_column_name: :my_otp_secret_counter_column
|
124
129
|
end
|
125
130
|
```
|
@@ -154,6 +159,10 @@ user.provisioning_uri # => 'otpauth://totp/hello@heapsource.com?secret=2z6hxkdwi
|
|
154
159
|
|
155
160
|
# Use a custom field to generate the provisioning_url
|
156
161
|
user.provisioning_uri("hello") # => 'otpauth://totp/hello?secret=2z6hxkdwi3uvrnpn'
|
162
|
+
|
163
|
+
# You can customize the generated url, by passing a hash of Options
|
164
|
+
# `:issuer` lets you set the Issuer name in Google Authenticator, so it doesn't show as a blank entry.
|
165
|
+
user.provisioning_uri(nil, issuer: 'MYAPP') #=> 'otpauth://totp/hello@heapsource.com?secret=2z6hxkdwi3uvrnpn&issuer=MYAPP'
|
157
166
|
```
|
158
167
|
|
159
168
|
This can then be rendered as a QR Code which can be scanned and added to the users list of OTP credentials.
|
@@ -168,6 +177,7 @@ Now run the following and compare the output
|
|
168
177
|
|
169
178
|
```ruby
|
170
179
|
require "active_model_otp"
|
180
|
+
|
171
181
|
class User
|
172
182
|
extend ActiveModel::Callbacks
|
173
183
|
include ActiveModel::Validations
|
@@ -178,6 +188,7 @@ class User
|
|
178
188
|
|
179
189
|
has_one_time_password
|
180
190
|
end
|
191
|
+
|
181
192
|
user = User.new
|
182
193
|
user.email = 'roberto@heapsource.com'
|
183
194
|
user.otp_secret_key = "2z6hxkdwi3uvrnpn"
|
@@ -187,10 +198,10 @@ puts "Current code #{user.otp_code}"
|
|
187
198
|
**Note:** otp_secret_key must be generated using RFC 3548 base32 key strings (for compatilibity with google authenticator)
|
188
199
|
|
189
200
|
### Useful Examples
|
190
|
-
|
201
|
+
- [Drifting Ruby Tutorial](https://www.driftingruby.com/episodes/two-factor-authentication)
|
191
202
|
- [Generate QR code with rqrcode gem](https://github.com/heapsource/active_model_otp/wiki/Generate-QR-code-with-rqrcode-gem)
|
192
203
|
- Generating QR Code with Google Charts API
|
193
|
-
- [
|
204
|
+
- [Sending code via SMS with Twilio](https://github.com/heapsource/active_model_otp/wiki/Send-code-via-Twilio-SMS)
|
194
205
|
- [Using with Mongoid](https://github.com/heapsource/active_model_otp/wiki/Using-with-Mongoid)
|
195
206
|
|
196
207
|
## Contributing
|
data/active_model_otp.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = ActiveModel::Otp::VERSION
|
9
9
|
spec.authors = ["Guillermo Iguaran", "Roberto Miranda", "Heapsource"]
|
10
10
|
spec.email = ["guilleiguaran@gmail.com", "rjmaltamar@gmail.com", "hello@firebase.co"]
|
11
|
-
spec.description = %q{Adds methods to set and authenticate against one time passwords. Inspired in AM::SecurePassword"}
|
11
|
+
spec.description = %q{Adds methods to set and authenticate against one time passwords 2FA(Two factor Authentication). Inspired in AM::SecurePassword"}
|
12
12
|
spec.summary = "Adds methods to set and authenticate against one time passwords."
|
13
13
|
spec.homepage = ""
|
14
14
|
spec.license = "MIT"
|
@@ -19,9 +19,16 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_dependency "activemodel"
|
22
|
-
spec.add_dependency "rotp"
|
22
|
+
spec.add_dependency "rotp", "~> 5.0.0"
|
23
23
|
|
24
|
-
spec.add_development_dependency "
|
24
|
+
spec.add_development_dependency "activerecord"
|
25
25
|
spec.add_development_dependency "rake"
|
26
26
|
spec.add_development_dependency "minitest", "~> 5.4.2"
|
27
|
+
spec.add_development_dependency "appraisal"
|
28
|
+
|
29
|
+
if RUBY_PLATFORM == "java"
|
30
|
+
spec.add_development_dependency "activerecord-jdbcsqlite3-adapter"
|
31
|
+
else
|
32
|
+
spec.add_development_dependency "sqlite3", "~> 1.3.6"
|
33
|
+
end
|
27
34
|
end
|
@@ -3,7 +3,6 @@ module ActiveModel
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
module ClassMethods
|
6
|
-
|
7
6
|
def has_one_time_password(options = {})
|
8
7
|
cattr_accessor :otp_column_name, :otp_counter_column_name
|
9
8
|
class_attribute :otp_digits, :otp_counter_based
|
@@ -12,9 +11,7 @@ module ActiveModel
|
|
12
11
|
self.otp_digits = options[:length] || 6
|
13
12
|
|
14
13
|
self.otp_counter_based = (options[:counter_based] || false)
|
15
|
-
self.otp_counter_column_name = (
|
16
|
-
options[:counter_column_name] || "otp_counter"
|
17
|
-
).to_s
|
14
|
+
self.otp_counter_column_name = (options[:counter_column_name] || "otp_counter").to_s
|
18
15
|
|
19
16
|
include InstanceMethodsOnActivation
|
20
17
|
|
@@ -29,11 +26,17 @@ module ActiveModel
|
|
29
26
|
end
|
30
27
|
end
|
31
28
|
end
|
29
|
+
|
30
|
+
# Defaults to 160 bit long secret
|
31
|
+
# (meaning a 32 character long base32 secret)
|
32
|
+
def otp_random_secret(length = 20)
|
33
|
+
ROTP::Base32.random(length)
|
34
|
+
end
|
32
35
|
end
|
33
36
|
|
34
37
|
module InstanceMethodsOnActivation
|
35
38
|
def otp_regenerate_secret
|
36
|
-
self.otp_column =
|
39
|
+
self.otp_column = self.class.otp_random_secret
|
37
40
|
end
|
38
41
|
|
39
42
|
def otp_regenerate_counter
|
@@ -46,13 +49,13 @@ module ActiveModel
|
|
46
49
|
result = hotp.verify(code, otp_counter)
|
47
50
|
if result && options[:auto_increment]
|
48
51
|
self.otp_counter += 1
|
49
|
-
save if !new_record?
|
52
|
+
save if respond_to?(:new_record) && !new_record?
|
50
53
|
end
|
51
54
|
result
|
52
55
|
else
|
53
56
|
totp = ROTP::TOTP.new(otp_column, digits: otp_digits)
|
54
57
|
if drift = options[:drift]
|
55
|
-
totp.
|
58
|
+
totp.verify(code, drift_behind: drift)
|
56
59
|
else
|
57
60
|
totp.verify(code)
|
58
61
|
end
|
@@ -63,23 +66,22 @@ module ActiveModel
|
|
63
66
|
if otp_counter_based
|
64
67
|
if options[:auto_increment]
|
65
68
|
self.otp_counter += 1
|
66
|
-
save if !new_record?
|
69
|
+
save if respond_to?(:new_record) && !new_record?
|
67
70
|
end
|
68
71
|
ROTP::HOTP.new(otp_column, digits: otp_digits).at(self.otp_counter)
|
69
72
|
else
|
70
73
|
if options.is_a? Hash
|
71
74
|
time = options.fetch(:time, Time.now)
|
72
|
-
padding = options.fetch(:padding, true)
|
73
75
|
else
|
74
76
|
time = options
|
75
|
-
padding = true
|
76
77
|
end
|
77
|
-
ROTP::TOTP.new(otp_column, digits: otp_digits).at(time
|
78
|
+
ROTP::TOTP.new(otp_column, digits: otp_digits).at(time)
|
78
79
|
end
|
79
80
|
end
|
80
81
|
|
81
82
|
def provisioning_uri(account = nil, options = {})
|
82
83
|
account ||= self.email if self.respond_to?(:email)
|
84
|
+
account ||= ""
|
83
85
|
|
84
86
|
if otp_counter_based
|
85
87
|
ROTP::HOTP.new(otp_column, options).provisioning_uri(account)
|
@@ -89,19 +91,34 @@ module ActiveModel
|
|
89
91
|
end
|
90
92
|
|
91
93
|
def otp_column
|
92
|
-
self.
|
94
|
+
self.public_send(self.class.otp_column_name)
|
93
95
|
end
|
94
96
|
|
95
97
|
def otp_column=(attr)
|
96
|
-
self.
|
98
|
+
self.public_send("#{self.class.otp_column_name}=", attr)
|
97
99
|
end
|
98
100
|
|
99
101
|
def otp_counter
|
100
|
-
self.
|
102
|
+
if self.class.otp_counter_column_name != "otp_counter"
|
103
|
+
self.public_send(self.class.otp_counter_column_name)
|
104
|
+
else
|
105
|
+
super
|
106
|
+
end
|
101
107
|
end
|
102
108
|
|
103
109
|
def otp_counter=(attr)
|
104
|
-
self.
|
110
|
+
if self.class.otp_counter_column_name != "otp_counter"
|
111
|
+
self.public_send("#{self.class.otp_counter_column_name}=", attr)
|
112
|
+
else
|
113
|
+
super
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def serializable_hash(options = nil)
|
118
|
+
options ||= {}
|
119
|
+
options[:except] = Array(options[:except])
|
120
|
+
options[:except] << self.class.otp_column_name
|
121
|
+
super(options)
|
105
122
|
end
|
106
123
|
end
|
107
124
|
end
|
data/test/models/user.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
class User
|
2
2
|
extend ActiveModel::Callbacks
|
3
|
+
include ActiveModel::Serializers::JSON
|
4
|
+
include ActiveModel::Serializers::Xml
|
3
5
|
include ActiveModel::Validations
|
4
6
|
include ActiveModel::OneTimePassword
|
5
7
|
|
@@ -7,4 +9,7 @@ class User
|
|
7
9
|
attr_accessor :otp_secret_key, :email
|
8
10
|
|
9
11
|
has_one_time_password
|
12
|
+
def attributes
|
13
|
+
{ "otp_secret_key" => otp_secret_key, "email" => email }
|
14
|
+
end
|
10
15
|
end
|
@@ -9,17 +9,46 @@ class OtpTest < MiniTest::Unit::TestCase
|
|
9
9
|
@visitor = Visitor.new
|
10
10
|
@visitor.email = 'roberto@heapsource.com'
|
11
11
|
@visitor.run_callbacks :create
|
12
|
+
|
13
|
+
@member = Member.new
|
14
|
+
@member.email = nil
|
15
|
+
@member.run_callbacks :create
|
16
|
+
|
17
|
+
@ar_user = ActiverecordUser.new
|
18
|
+
@ar_user.email = 'roberto@heapsource.com'
|
19
|
+
@ar_user.run_callbacks :create
|
12
20
|
end
|
13
21
|
|
14
22
|
def test_authenticate_with_otp
|
15
23
|
code = @user.otp_code
|
16
|
-
|
17
24
|
assert @user.authenticate_otp(code)
|
18
25
|
|
19
26
|
code = @visitor.otp_code
|
20
27
|
assert @visitor.authenticate_otp(code)
|
21
28
|
end
|
22
29
|
|
30
|
+
def test_counter_based_otp
|
31
|
+
code = @member.otp_code
|
32
|
+
assert @member.authenticate_otp(code)
|
33
|
+
assert @member.authenticate_otp(code, auto_increment: true)
|
34
|
+
assert !@member.authenticate_otp(code)
|
35
|
+
@member.otp_counter -= 1
|
36
|
+
assert @member.authenticate_otp(code)
|
37
|
+
assert code == @member.otp_code
|
38
|
+
assert code != @member.otp_code(auto_increment: true)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_counter_based_otp_active_record
|
42
|
+
code = @ar_user.otp_code
|
43
|
+
assert @ar_user.authenticate_otp(code)
|
44
|
+
assert @ar_user.authenticate_otp(code, auto_increment: true)
|
45
|
+
assert !@ar_user.authenticate_otp(code)
|
46
|
+
@ar_user.otp_counter -= 1
|
47
|
+
assert @ar_user.authenticate_otp(code)
|
48
|
+
assert code == @ar_user.otp_code
|
49
|
+
assert code != @ar_user.otp_code(auto_increment: true)
|
50
|
+
end
|
51
|
+
|
23
52
|
def test_authenticate_with_otp_when_drift_is_allowed
|
24
53
|
code = @user.otp_code(Time.now - 30)
|
25
54
|
assert @user.authenticate_otp(code, drift: 60)
|
@@ -34,38 +63,32 @@ class OtpTest < MiniTest::Unit::TestCase
|
|
34
63
|
end
|
35
64
|
|
36
65
|
def test_otp_code_with_specific_length
|
37
|
-
assert_match(/^\d{4}$/, @visitor.otp_code(
|
38
|
-
assert_operator(@visitor.otp_code(
|
66
|
+
assert_match(/^\d{4}$/, @visitor.otp_code(2160).to_s)
|
67
|
+
assert_operator(@visitor.otp_code(2160).to_s.length, :<=, 4)
|
39
68
|
end
|
40
69
|
|
41
70
|
def test_otp_code_without_specific_length
|
42
|
-
|
43
|
-
|
44
|
-
end
|
45
|
-
|
46
|
-
def test_otp_code_padding
|
47
|
-
@user.otp_column = 'kw5jhligwqaiw7jc'
|
48
|
-
assert_match(/^\d{6}$/, @user.otp_code(time: 2160, padding: true).to_s)
|
49
|
-
# Modified this spec as it is not guranteed that without padding we will always
|
50
|
-
# get a 3 digit number
|
51
|
-
assert_operator(@user.otp_code(time: 2160, padding: false).to_s.length, :<= , 6)
|
71
|
+
assert_match(/^\d{6}$/, @user.otp_code(2160).to_s)
|
72
|
+
assert_operator(@user.otp_code(2160).to_s.length, :<=, 6)
|
52
73
|
end
|
53
74
|
|
54
75
|
def test_provisioning_uri_with_provided_account
|
55
|
-
assert_match %r{otpauth://totp/roberto\?secret=\w{
|
56
|
-
assert_match %r{otpauth://totp/roberto\?secret=\w{
|
76
|
+
assert_match %r{^otpauth://totp/roberto\?secret=\w{32}$}, @user.provisioning_uri("roberto")
|
77
|
+
assert_match %r{^otpauth://totp/roberto\?secret=\w{32}$}, @visitor.provisioning_uri("roberto")
|
78
|
+
assert_match %r{^otpauth://hotp/roberto\?secret=\w{32}&counter=0$}, @member.provisioning_uri("roberto")
|
57
79
|
end
|
58
80
|
|
59
81
|
def test_provisioning_uri_with_email_field
|
60
|
-
assert_match %r{otpauth://totp/roberto@heapsource\.com\?secret=\w{
|
61
|
-
assert_match %r{otpauth://totp/roberto@heapsource\.com\?secret=\w{
|
82
|
+
assert_match %r{^otpauth://totp/roberto@heapsource\.com\?secret=\w{32}$}, @user.provisioning_uri
|
83
|
+
assert_match %r{^otpauth://totp/roberto@heapsource\.com\?secret=\w{32}$}, @visitor.provisioning_uri
|
84
|
+
assert_match %r{^otpauth://hotp/\?secret=\w{32}&counter=0$}, @member.provisioning_uri
|
62
85
|
end
|
63
86
|
|
64
87
|
def test_provisioning_uri_with_options
|
65
|
-
assert_match
|
66
|
-
assert_match %r{otpauth://totp/roberto@heapsource\.com\?
|
67
|
-
assert_match %r{otpauth://totp/roberto\?
|
68
|
-
assert_match %r{otpauth://totp/roberto\?
|
88
|
+
assert_match %r{^otpauth://totp/Example\:roberto@heapsource\.com\?secret=\w{32}&issuer=Example$}, @user.provisioning_uri(nil, issuer: "Example")
|
89
|
+
assert_match %r{^otpauth://totp/Example\:roberto@heapsource\.com\?secret=\w{32}&issuer=Example$}, @visitor.provisioning_uri(nil, issuer: "Example")
|
90
|
+
assert_match %r{^otpauth://totp/Example\:roberto\?secret=\w{32}&issuer=Example$}, @user.provisioning_uri("roberto", issuer: "Example")
|
91
|
+
assert_match %r{^otpauth://totp/Example\:roberto\?secret=\w{32}&issuer=Example$}, @visitor.provisioning_uri("roberto", issuer: "Example")
|
69
92
|
end
|
70
93
|
|
71
94
|
def test_regenerate_otp
|
@@ -73,4 +96,13 @@ class OtpTest < MiniTest::Unit::TestCase
|
|
73
96
|
@user.otp_regenerate_secret
|
74
97
|
assert secret != @user.otp_column
|
75
98
|
end
|
99
|
+
|
100
|
+
def test_hide_secret_key_in_serialize
|
101
|
+
refute_match(/otp_secret_key/, @user.to_json)
|
102
|
+
refute_match(/otp_secret_key/, @user.to_xml)
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_otp_random_secret
|
106
|
+
assert_match /^.{32}$/, @user.class.otp_random_secret
|
107
|
+
end
|
76
108
|
end
|
data/test/schema.rb
ADDED
data/test/test_helper.rb
CHANGED
@@ -8,5 +8,14 @@ require "rubygems"
|
|
8
8
|
require "active_model_otp"
|
9
9
|
require "minitest/autorun"
|
10
10
|
require "minitest/unit"
|
11
|
+
require "active_record"
|
12
|
+
|
13
|
+
begin
|
14
|
+
require "activemodel-serializers-xml"
|
15
|
+
rescue LoadError
|
16
|
+
end
|
17
|
+
|
18
|
+
ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
|
19
|
+
load "#{ File.dirname(__FILE__) }/schema.rb"
|
11
20
|
|
12
21
|
Dir["models/*.rb"].each {|file| require file }
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_model_otp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Guillermo Iguaran
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2019-06-07 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activemodel
|
@@ -28,12 +28,40 @@ dependencies:
|
|
28
28
|
version: '0'
|
29
29
|
- !ruby/object:Gem::Dependency
|
30
30
|
name: rotp
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - "~>"
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 5.0.0
|
36
|
+
type: :runtime
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - "~>"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 5.0.0
|
43
|
+
- !ruby/object:Gem::Dependency
|
44
|
+
name: activerecord
|
31
45
|
requirement: !ruby/object:Gem::Requirement
|
32
46
|
requirements:
|
33
47
|
- - ">="
|
34
48
|
- !ruby/object:Gem::Version
|
35
49
|
version: '0'
|
36
|
-
type: :
|
50
|
+
type: :development
|
51
|
+
prerelease: false
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
- !ruby/object:Gem::Dependency
|
58
|
+
name: rake
|
59
|
+
requirement: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
type: :development
|
37
65
|
prerelease: false
|
38
66
|
version_requirements: !ruby/object:Gem::Requirement
|
39
67
|
requirements:
|
@@ -41,21 +69,21 @@ dependencies:
|
|
41
69
|
- !ruby/object:Gem::Version
|
42
70
|
version: '0'
|
43
71
|
- !ruby/object:Gem::Dependency
|
44
|
-
name:
|
72
|
+
name: minitest
|
45
73
|
requirement: !ruby/object:Gem::Requirement
|
46
74
|
requirements:
|
47
75
|
- - "~>"
|
48
76
|
- !ruby/object:Gem::Version
|
49
|
-
version:
|
77
|
+
version: 5.4.2
|
50
78
|
type: :development
|
51
79
|
prerelease: false
|
52
80
|
version_requirements: !ruby/object:Gem::Requirement
|
53
81
|
requirements:
|
54
82
|
- - "~>"
|
55
83
|
- !ruby/object:Gem::Version
|
56
|
-
version:
|
84
|
+
version: 5.4.2
|
57
85
|
- !ruby/object:Gem::Dependency
|
58
|
-
name:
|
86
|
+
name: appraisal
|
59
87
|
requirement: !ruby/object:Gem::Requirement
|
60
88
|
requirements:
|
61
89
|
- - ">="
|
@@ -69,21 +97,21 @@ dependencies:
|
|
69
97
|
- !ruby/object:Gem::Version
|
70
98
|
version: '0'
|
71
99
|
- !ruby/object:Gem::Dependency
|
72
|
-
name:
|
100
|
+
name: sqlite3
|
73
101
|
requirement: !ruby/object:Gem::Requirement
|
74
102
|
requirements:
|
75
103
|
- - "~>"
|
76
104
|
- !ruby/object:Gem::Version
|
77
|
-
version:
|
105
|
+
version: 1.3.6
|
78
106
|
type: :development
|
79
107
|
prerelease: false
|
80
108
|
version_requirements: !ruby/object:Gem::Requirement
|
81
109
|
requirements:
|
82
110
|
- - "~>"
|
83
111
|
- !ruby/object:Gem::Version
|
84
|
-
version:
|
85
|
-
description: Adds methods to set and authenticate against one time passwords
|
86
|
-
in AM::SecurePassword"
|
112
|
+
version: 1.3.6
|
113
|
+
description: Adds methods to set and authenticate against one time passwords 2FA(Two
|
114
|
+
factor Authentication). Inspired in AM::SecurePassword"
|
87
115
|
email:
|
88
116
|
- guilleiguaran@gmail.com
|
89
117
|
- rjmaltamar@gmail.com
|
@@ -94,18 +122,27 @@ extra_rdoc_files: []
|
|
94
122
|
files:
|
95
123
|
- ".gitignore"
|
96
124
|
- ".travis.yml"
|
125
|
+
- Appraisals
|
97
126
|
- CHANGELOG.md
|
98
127
|
- Gemfile
|
99
128
|
- LICENSE.txt
|
100
129
|
- README.md
|
101
130
|
- Rakefile
|
102
131
|
- active_model_otp.gemspec
|
132
|
+
- gemfiles/rails_4.2.gemfile
|
133
|
+
- gemfiles/rails_5.0.gemfile
|
134
|
+
- gemfiles/rails_5.1.gemfile
|
135
|
+
- gemfiles/rails_5.2.gemfile
|
136
|
+
- gemfiles/rails_6.0.0.rc1.gemfile
|
103
137
|
- lib/active_model/one_time_password.rb
|
104
138
|
- lib/active_model/otp/version.rb
|
105
139
|
- lib/active_model_otp.rb
|
140
|
+
- test/models/activerecord_user.rb
|
141
|
+
- test/models/member.rb
|
106
142
|
- test/models/user.rb
|
107
143
|
- test/models/visitor.rb
|
108
144
|
- test/one_time_password_test.rb
|
145
|
+
- test/schema.rb
|
109
146
|
- test/test_helper.rb
|
110
147
|
homepage: ''
|
111
148
|
licenses:
|
@@ -126,13 +163,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
126
163
|
- !ruby/object:Gem::Version
|
127
164
|
version: '0'
|
128
165
|
requirements: []
|
129
|
-
|
130
|
-
rubygems_version: 2.2.2
|
166
|
+
rubygems_version: 3.0.1
|
131
167
|
signing_key:
|
132
168
|
specification_version: 4
|
133
169
|
summary: Adds methods to set and authenticate against one time passwords.
|
134
170
|
test_files:
|
171
|
+
- test/models/activerecord_user.rb
|
172
|
+
- test/models/member.rb
|
135
173
|
- test/models/user.rb
|
136
174
|
- test/models/visitor.rb
|
137
175
|
- test/one_time_password_test.rb
|
176
|
+
- test/schema.rb
|
138
177
|
- test/test_helper.rb
|