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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: a53fb4e15fa9408222f26928a84d9b1f67919b86
4
- data.tar.gz: f436275d42c9e8e78c074809be7cbc06da3e9c16
2
+ SHA256:
3
+ metadata.gz: 4f10c7c77b302adbd5a02bb37378c46eafd0d6aaf53f2f5ec7ac069bd888ab07
4
+ data.tar.gz: 2572cac964589cc665f0a0dbaacaa7ca8fc226cef62ebeb122e591c487b6278d
5
5
  SHA512:
6
- metadata.gz: 4c409cf8191c2511f7f7c984d0b2623463f65a835e5b86d6cec3dd59b9f91836f8a5d5308b164d328c773dbebe76c51e0c9ae0503048bc81e8326280d57a2379
7
- data.tar.gz: 2b69f18d93545ea5e0e41bc3bc10930887f2f7f1b95dbee947b6e9eed256cc5b78f80014fa7e11e2f3d271f204ccf5dad9b412f9c1193404dcda64478e1d116a
6
+ metadata.gz: e78abb140e71aca80842a896ef02abad3df97be46b57e096056b8480c150aa5c8c633725cb8d0aaa49eaf9e26b5846741ac9eb6b99a963f59a7b835e88101605
7
+ data.tar.gz: 9d7d0fef52db81c831a728808b5768714fcbc64df126a65832a9ed86769379e4ee007f894193183a536ba2d1ea32343f77537fb732bd84de2168e75b02202bef
data/.gitignore CHANGED
@@ -16,3 +16,4 @@ test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
18
  .ruby-version
19
+ gemfiles/*.lock
@@ -1,10 +1,26 @@
1
1
  rvm:
2
- - 1.9.3
3
- - 2.0.0
4
- - 2.1
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
- include:
7
- - rvm: jruby
8
- env: JRUBY_OPTS="--1.9 --server -Xcext.enabled=true"
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
@@ -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
@@ -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)
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013 Guillermo Iguaran, Roberto Miranda, Firebase.co
1
+ Copyright (c) 2013-2019 Roberto Miranda, Guillermo Iguaran and contributors.
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,12 +1,15 @@
1
1
  [![Build Status](https://travis-ci.org/heapsource/active_model_otp.png)](https://travis-ci.org/heapsource/active_model_otp)
2
2
  [![Gem Version](https://badge.fury.io/rb/active_model_otp.svg)](http://badge.fury.io/rb/active_model_otp)
3
- [![Dependency Status](https://gemnasium.com/heapsource/active_model_otp.svg)](https://gemnasium.com/heapsource/active_model_otp)
4
- [![Code Climate](https://codeclimate.com/github/heapsource/active_model_otp/badges/gpa.svg)](https://codeclimate.com/github/heapsource/active_model_otp)
3
+ [![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](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 4.0 (AMo::Otp is also compatible with Rails 3.x versions). We're going to use a User model and some authentication to do it. Inspired by AM::SecurePassword
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 < ActiveRecord::Base
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.all.each { |user| user.update_attribute(:otp_secret_key, ROTP::Base32.random_base32) }
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 < ActiveRecord::Base
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](http://tools.ietf.org/html/rfc4226) and the [HOTP RFC 4226](http://tools.ietf.org/html/rfc4226). This is compatible with Google Authenticator apps available for Android and iPhone, and now in use on GMail.
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 < ActiveRecord::Base
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 < ActiveRecord::Base
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
- - [Sendind code via email with Twilio](https://github.com/heapsource/active_model_otp/wiki/Send-code-via-Twilio-SMS)
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
@@ -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 "bundler", "~> 1.3"
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
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activemodel", "~> 4.2"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activemodel", "~> 5.0"
6
+ gem "activemodel-serializers-xml"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activemodel", "~> 5.1"
6
+ gem "activemodel-serializers-xml"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activemodel", "~> 5.2"
6
+ gem "activemodel-serializers-xml"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "6.0.0.rc1"
6
+ gem "activemodel", "6.0.0.rc1"
7
+ gem "activemodel-serializers-xml"
8
+ gem "sqlite3", "~> 1.4"
9
+
10
+ gemspec path: "../"
@@ -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 = ROTP::Base32.random_base32
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.verify_with_drift(code, drift)
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, padding)
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.send(self.class.otp_column_name)
94
+ self.public_send(self.class.otp_column_name)
93
95
  end
94
96
 
95
97
  def otp_column=(attr)
96
- self.send("#{self.class.otp_column_name}=", attr)
98
+ self.public_send("#{self.class.otp_column_name}=", attr)
97
99
  end
98
100
 
99
101
  def otp_counter
100
- self.send(self.class.otp_counter_column_name)
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.send("#{self.class.otp_counter_column_name}=", attr)
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
@@ -1,5 +1,5 @@
1
1
  module ActiveModel
2
2
  module Otp
3
- VERSION = "1.2.0"
3
+ VERSION = "2.0.1"
4
4
  end
5
5
  end
@@ -0,0 +1,3 @@
1
+ class ActiverecordUser < ActiveRecord::Base
2
+ has_one_time_password counter_based: true
3
+ end
@@ -0,0 +1,10 @@
1
+ class Member
2
+ extend ActiveModel::Callbacks
3
+ include ActiveModel::Validations
4
+ include ActiveModel::OneTimePassword
5
+
6
+ define_model_callbacks :create
7
+ attr_accessor :otp_secret_key, :otp_counter, :email
8
+
9
+ has_one_time_password counter_based: true
10
+ end
@@ -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(time: 2160, padding: true).to_s)
38
- assert_operator(@visitor.otp_code(time: 2160, padding: false).to_s.length, :<= , 4)
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
- assert_match(/^\d{6}$/, @user.otp_code(time: 2160, padding: true).to_s)
43
- assert_operator(@user.otp_code(time: 2160, padding: false).to_s.length, :<= , 6)
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{16}}, @user.provisioning_uri("roberto")
56
- assert_match %r{otpauth://totp/roberto\?secret=\w{16}}, @visitor.provisioning_uri("roberto")
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{16}}, @user.provisioning_uri
61
- assert_match %r{otpauth://totp/roberto@heapsource\.com\?secret=\w{16}}, @visitor.provisioning_uri
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 %r{otpauth://totp/roberto@heapsource\.com\?issuer=Example&secret=\w{16}},@user.provisioning_uri(nil,issuer: "Example")
66
- assert_match %r{otpauth://totp/roberto@heapsource\.com\?issuer=Example&secret=\w{16}}, @visitor.provisioning_uri(nil,issuer: "Example")
67
- assert_match %r{otpauth://totp/roberto\?issuer=Example&secret=\w{16}}, @user.provisioning_uri("roberto", issuer: "Example")
68
- assert_match %r{otpauth://totp/roberto\?issuer=Example&secret=\w{16}}, @visitor.provisioning_uri("roberto", issuer: "Example")
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
@@ -0,0 +1,11 @@
1
+ ActiveRecord::Schema.define do
2
+ self.verbose = false
3
+
4
+ create_table :activerecord_users, force: true do |t|
5
+ t.string :key
6
+ t.string :email
7
+ t.integer :otp_counter
8
+ t.string :otp_secret_key
9
+ t.timestamps
10
+ end
11
+ end
@@ -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: 1.2.0
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: 2015-02-26 00:00:00.000000000 Z
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: :runtime
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: bundler
72
+ name: minitest
45
73
  requirement: !ruby/object:Gem::Requirement
46
74
  requirements:
47
75
  - - "~>"
48
76
  - !ruby/object:Gem::Version
49
- version: '1.3'
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: '1.3'
84
+ version: 5.4.2
57
85
  - !ruby/object:Gem::Dependency
58
- name: rake
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: minitest
100
+ name: sqlite3
73
101
  requirement: !ruby/object:Gem::Requirement
74
102
  requirements:
75
103
  - - "~>"
76
104
  - !ruby/object:Gem::Version
77
- version: 5.4.2
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: 5.4.2
85
- description: Adds methods to set and authenticate against one time passwords. Inspired
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
- rubyforge_project:
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