active_model_otp 1.2.0 → 2.0.1

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