active_model_otp 2.1.1 → 2.2.0

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
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