active_model_otp 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3dc6bbf3b7c11ec96a00e223a7c894cf0f4aee17edd3173e800ba383013c0a5f
4
- data.tar.gz: 739a0af12431fae65602b63fe9d69c3344f7add487cf3eaeaa199c75cfb0fdd5
3
+ metadata.gz: bdc023d65f130fca41bde8f20a7094e587c7940240ae5b36778e54d2efcf76ac
4
+ data.tar.gz: 1dab4dd23328538a3a3c8d9a712b9a16d01673b3d997538169126966c653cdf6
5
5
  SHA512:
6
- metadata.gz: 27e8578a087bd151ad1930fe91ab596a0459c754e788623ffe95fa0779c3f9d7e431fde02ee16448937997d6430d931299a2510bfdd6068212d6cc62fe33d66b
7
- data.tar.gz: b6a32d54be8b38502e197ad7478cfa7fc2932fc8c0408bc158230975b8f9e753c95096ba75909a07a5573c8c68c1f65315002261b3813f8b1806050b28bf6ee8
6
+ metadata.gz: f896afd30554da73cff730f4277aefe8805b4cbb3da0bf550039b14b8d4611374498b1cf4b0eae2fe80d7a35bdaafdc0ae1558ffbdea3f2f708d2334a81e1af2
7
+ data.tar.gz: 0d3b2a4fe8d0e26e9c06bd71e471bcabf4a048f1f1bd531691c0afbc5feda757147ca045ddedec34e587240b0b1337960a9248c0340e6f57ec5d7057cec776ea
@@ -58,37 +58,17 @@ module ActiveModel
58
58
  return true if backup_codes_enabled? && authenticate_backup_code(code)
59
59
 
60
60
  if otp_counter_based
61
- hotp = ROTP::HOTP.new(otp_column, digits: otp_digits)
62
- result = hotp.verify(code, otp_counter)
63
- if result && options[:auto_increment]
64
- self.otp_counter += 1
65
- save if respond_to?(:changed?) && !new_record?
66
- end
67
- result
61
+ otp_counter == authenticate_hotp(code, options)
68
62
  else
69
- totp = ROTP::TOTP.new(otp_column, digits: otp_digits)
70
- if drift = options[:drift]
71
- totp.verify(code, drift_behind: drift)
72
- else
73
- totp.verify(code)
74
- end
63
+ authenticate_totp(code, options).present?
75
64
  end
76
65
  end
77
66
 
78
67
  def otp_code(options = {})
79
68
  if otp_counter_based
80
- if options[:auto_increment]
81
- self.otp_counter += 1
82
- save if respond_to?(:changed?) && !new_record?
83
- end
84
- ROTP::HOTP.new(otp_column, digits: otp_digits).at(self.otp_counter)
69
+ hotp_code(options)
85
70
  else
86
- if options.is_a? Hash
87
- time = options.fetch(:time, Time.now)
88
- else
89
- time = options
90
- end
91
- ROTP::TOTP.new(otp_column, digits: otp_digits).at(time)
71
+ totp_code(options)
92
72
  end
93
73
  end
94
74
 
@@ -97,9 +77,13 @@ module ActiveModel
97
77
  account ||= ""
98
78
 
99
79
  if otp_counter_based
100
- ROTP::HOTP.new(otp_column, options).provisioning_uri(account)
80
+ ROTP::HOTP
81
+ .new(otp_column, options)
82
+ .provisioning_uri(account, self.otp_counter)
101
83
  else
102
- ROTP::TOTP.new(otp_column, options).provisioning_uri(account)
84
+ ROTP::TOTP
85
+ .new(otp_column, options)
86
+ .provisioning_uri(account)
103
87
  end
104
88
  end
105
89
 
@@ -149,10 +133,46 @@ module ActiveModel
149
133
 
150
134
  private
151
135
 
136
+ def authenticate_hotp(code, options = {})
137
+ hotp = ROTP::HOTP.new(otp_column, digits: otp_digits)
138
+ result = hotp.verify(code, otp_counter)
139
+ if result && options[:auto_increment]
140
+ self.otp_counter += 1
141
+ save if respond_to?(:changed?) && !new_record?
142
+ end
143
+ result
144
+ end
145
+
146
+ def authenticate_totp(code, options = {})
147
+ totp = ROTP::TOTP.new(otp_column, digits: otp_digits)
148
+ if (drift = options[:drift])
149
+ totp.verify(code, drift_behind: drift)
150
+ else
151
+ totp.verify(code)
152
+ end
153
+ end
154
+
155
+ def hotp_code(options = {})
156
+ if options[:auto_increment]
157
+ self.otp_counter += 1
158
+ save if respond_to?(:changed?) && !new_record?
159
+ end
160
+ ROTP::HOTP.new(otp_column, digits: otp_digits).at(otp_counter)
161
+ end
162
+
163
+ def totp_code(options = {})
164
+ time = if options.is_a?(Hash)
165
+ options.fetch(:time, Time.now)
166
+ else
167
+ options
168
+ end
169
+ ROTP::TOTP.new(otp_column, digits: otp_digits).at(time)
170
+ end
171
+
152
172
  def authenticate_backup_code(code)
153
173
  backup_codes_column_name = self.class.otp_backup_codes_column_name
154
174
  backup_codes = public_send(backup_codes_column_name)
155
- return false unless backup_codes.include?(code)
175
+ return false unless backup_codes.present? && backup_codes.include?(code)
156
176
 
157
177
  if self.class.otp_one_time_backup_codes
158
178
  backup_codes.delete(code)
@@ -1,5 +1,5 @@
1
1
  module ActiveModel
2
2
  module Otp
3
- VERSION = "2.1.1".freeze
3
+ VERSION = "2.2.0".freeze
4
4
  end
5
5
  end
data/test/models/user.rb CHANGED
@@ -8,6 +8,7 @@ class User
8
8
  attr_accessor :otp_secret_key, :otp_backup_codes, :email
9
9
 
10
10
  has_one_time_password one_time_backup_codes: true
11
+
11
12
  def attributes
12
13
  { "otp_secret_key" => otp_secret_key, "email" => email }
13
14
  end
@@ -1,4 +1,6 @@
1
- require "test_helper"
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
2
4
 
3
5
  class OtpTest < MiniTest::Test
4
6
  def setup
@@ -58,30 +60,30 @@ class OtpTest < MiniTest::Test
58
60
 
59
61
  @opt_in.otp_regenerate_secret
60
62
  code = @opt_in.otp_code
61
- assert @opt_in.authenticate_otp(code)
63
+ assert_equal true, @opt_in.authenticate_otp(code)
62
64
  end
63
65
 
64
66
  def test_authenticate_with_otp_when_drift_is_allowed
65
67
  code = @user.otp_code(Time.now - 30)
66
- assert @user.authenticate_otp(code, drift: 60)
68
+ assert_equal true, @user.authenticate_otp(code, drift: 60)
67
69
 
68
70
  code = @visitor.otp_code(Time.now - 30)
69
- assert @visitor.authenticate_otp(code, drift: 60)
71
+ assert_equal true, @visitor.authenticate_otp(code, drift: 60)
70
72
  end
71
73
 
72
74
  def test_authenticate_with_backup_code
73
75
  backup_code = @user.public_send(@user.otp_backup_codes_column_name).first
74
- assert @user.authenticate_otp(backup_code)
76
+ assert_equal true, @user.authenticate_otp(backup_code)
75
77
 
76
78
  backup_code = @user.public_send(@user.otp_backup_codes_column_name).last
77
79
  @user.otp_regenerate_backup_codes
78
- assert !@user.authenticate_otp(backup_code)
80
+ assert_equal true, !@user.authenticate_otp(backup_code)
79
81
  end
80
82
 
81
83
  def test_authenticate_with_one_time_backup_code
82
84
  backup_code = @user.public_send(@user.otp_backup_codes_column_name).first
83
- assert @user.authenticate_otp(backup_code)
84
- assert !@user.authenticate_otp(backup_code)
85
+ assert_equal true, @user.authenticate_otp(backup_code)
86
+ assert_equal true, !@user.authenticate_otp(backup_code)
85
87
  end
86
88
 
87
89
  def test_otp_code
@@ -100,22 +102,51 @@ class OtpTest < MiniTest::Test
100
102
  end
101
103
 
102
104
  def test_provisioning_uri_with_provided_account
103
- assert_match %r{^otpauth://totp/roberto\?secret=\w{32}$}, @user.provisioning_uri("roberto")
104
- assert_match %r{^otpauth://totp/roberto\?secret=\w{32}$}, @visitor.provisioning_uri("roberto")
105
- assert_match %r{^otpauth://hotp/roberto\?secret=\w{32}&counter=0$}, @member.provisioning_uri("roberto")
105
+ totp = %r{^otpauth://totp/roberto\?secret=\w{32}$}
106
+ hotp = %r{^otpauth://hotp/roberto\?secret=\w{32}&counter=1$}
107
+
108
+ assert_match totp, @user.provisioning_uri('roberto')
109
+ assert_match totp, @visitor.provisioning_uri('roberto')
110
+ assert_match hotp, @member.provisioning_uri('roberto')
106
111
  end
107
112
 
108
113
  def test_provisioning_uri_with_email_field
109
- assert_match %r{^otpauth://totp/roberto%40heapsource\.com\?secret=\w{32}$}, @user.provisioning_uri
110
- assert_match %r{^otpauth://totp/roberto%40heapsource\.com\?secret=\w{32}$}, @visitor.provisioning_uri
111
- assert_match %r{^otpauth://hotp/\?secret=\w{32}&counter=0$}, @member.provisioning_uri
114
+ totp = %r{^otpauth://totp/roberto%40heapsource\.com\?secret=\w{32}$}
115
+ hotp = %r{^otpauth://hotp/\?secret=\w{32}&counter=1$}
116
+
117
+ assert_match totp, @user.provisioning_uri
118
+ assert_match totp, @visitor.provisioning_uri
119
+ assert_match hotp, @member.provisioning_uri
112
120
  end
113
121
 
114
122
  def test_provisioning_uri_with_options
115
- assert_match %r{^otpauth://totp/Example\:roberto%40heapsource\.com\?secret=\w{32}&issuer=Example$}, @user.provisioning_uri(nil, issuer: "Example")
116
- assert_match %r{^otpauth://totp/Example\:roberto%40heapsource\.com\?secret=\w{32}&issuer=Example$}, @visitor.provisioning_uri(nil, issuer: "Example")
117
- assert_match %r{^otpauth://totp/Example\:roberto\?secret=\w{32}&issuer=Example$}, @user.provisioning_uri("roberto", issuer: "Example")
118
- assert_match %r{^otpauth://totp/Example\:roberto\?secret=\w{32}&issuer=Example$}, @visitor.provisioning_uri("roberto", issuer: "Example")
123
+ account = %r{
124
+ ^otpauth://totp/Example\:roberto\?secret=\w{32}&issuer=Example$
125
+ }x
126
+
127
+ email = %r{
128
+ ^otpauth://totp/Example\:roberto%40heapsource\.com\?secret=\w{32}
129
+ &issuer=Example$
130
+ }x
131
+
132
+ assert_match(
133
+ account, @user.provisioning_uri('roberto', issuer: 'Example')
134
+ )
135
+
136
+ assert_match(
137
+ account, @visitor.provisioning_uri('roberto', issuer: 'Example')
138
+ )
139
+
140
+ assert_match email, @user.provisioning_uri(nil, issuer: 'Example')
141
+ assert_match email, @visitor.provisioning_uri(nil, issuer: 'Example')
142
+ end
143
+
144
+ def test_provisioning_uri_with_incremented_counter
145
+ 2.times { @member.otp_code(auto_increment: true) }
146
+
147
+ hotp = %r{^otpauth://hotp/\?secret=\w{32}&counter=3$}
148
+
149
+ assert_match hotp, @member.provisioning_uri
119
150
  end
120
151
 
121
152
  def test_regenerate_otp
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: 2.1.1
4
+ version: 2.2.0
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: 2021-03-07 00:00:00.000000000 Z
13
+ date: 2021-05-30 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activemodel