active_model_otp 2.2.0 → 2.3.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: bdc023d65f130fca41bde8f20a7094e587c7940240ae5b36778e54d2efcf76ac
4
- data.tar.gz: 1dab4dd23328538a3a3c8d9a712b9a16d01673b3d997538169126966c653cdf6
3
+ metadata.gz: 2a57a05fda7ae2023dc877a96228e00e5dbca948bc8d17c1058232a42086e285
4
+ data.tar.gz: 9d3a965117edbf33fb77585126be84e2fd44b613bbbc435c74627b198b87ee5f
5
5
  SHA512:
6
- metadata.gz: f896afd30554da73cff730f4277aefe8805b4cbb3da0bf550039b14b8d4611374498b1cf4b0eae2fe80d7a35bdaafdc0ae1558ffbdea3f2f708d2334a81e1af2
7
- data.tar.gz: 0d3b2a4fe8d0e26e9c06bd71e471bcabf4a048f1f1bd531691c0afbc5feda757147ca045ddedec34e587240b0b1337960a9248c0340e6f57ec5d7057cec776ea
6
+ metadata.gz: 847accabdd4eff2942a917f497197b3be90bb5cf9e9caefb0cd753635994e3bb8b730aab0d9387aa13c4e929793816333d094b620037a8f9c002a7cc3f3ebdcb
7
+ data.tar.gz: 6a47a8a378bdb2d9155a165e503f672b5c57576c7f9d4fdef271767422a2d9804a141252707cb0181b02c36a91024d1867b9114cdbc95e475c2e4b292f750b65
@@ -0,0 +1,43 @@
1
+ name: Active Model OTP
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ types: [opened, synchronize, reopened, edited]
8
+
9
+ jobs:
10
+ ci:
11
+ runs-on: ubuntu-latest
12
+
13
+ strategy:
14
+ matrix:
15
+ gemfile: [rails_4.2, rails_5.0, rails_5.1, rails_5.2, rails_6.0, rails_6.1]
16
+ ruby-version: [2.3, 2.4, 2.5, 2.6, 2.7, 3.0]
17
+ exclude:
18
+ - { gemfile: rails_6.0, ruby-version: 2.3 }
19
+ - { gemfile: rails_6.1, ruby-version: 2.3 }
20
+ - { gemfile: rails_6.0, ruby-version: 2.4 }
21
+ - { gemfile: rails_6.1, ruby-version: 2.4 }
22
+ - { gemfile: rails_4.2, ruby-version: 2.7 }
23
+ - { gemfile: rails_4.2, ruby-version: 3.0 }
24
+ - { gemfile: rails_5.0, ruby-version: 3.0 }
25
+ - { gemfile: rails_5.1, ruby-version: 3.0 }
26
+ - { gemfile: rails_5.2, ruby-version: 3.0 }
27
+
28
+ env:
29
+ BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile
30
+
31
+ steps:
32
+ - uses: actions/checkout@v2
33
+
34
+ - name: Install Ruby ${{ matrix.ruby-version }}
35
+ uses: ruby/setup-ruby@v1
36
+ with:
37
+ ruby-version: ${{ matrix.ruby-version }}
38
+
39
+ - name: Install dependencies
40
+ run: bundle install
41
+
42
+ - name: Run tests with Ruby ${{ matrix.ruby-version }} and Gemfile ${{ matrix.gemfile }}
43
+ run: bundle exec rake
data/README.md CHANGED
@@ -213,6 +213,28 @@ user.provisioning_uri(nil, issuer: 'MYAPP') #=> 'otpauth://totp/hello@heapsource
213
213
 
214
214
  This can then be rendered as a QR Code which can be scanned and added to the users list of OTP credentials.
215
215
 
216
+ ### Setting up a customer interval
217
+
218
+ If you define a custom interval for TOTP codes, just as `has_one_time_password interval: 10` (for example), remember to include the interval also in `provisioning_uri` method. If not defined, the default value is 30 seconds (according to ROTP gem: https://github.com/mdp/rotp/blob/master/lib/rotp/totp.rb#L9)
219
+
220
+ ```ruby
221
+ class User < ApplicationRecord
222
+ has_one_time_password interval: 10 # the interval value is in seconds
223
+ end
224
+
225
+ user = User.new
226
+ user.provisioning_uri("hello", interval: 10) # => 'otpauth://totp/hello?secret=2z6hxkdwi3uvrnpn&period=10'
227
+
228
+ # This code snippet generates OTP codes that expires every 10 seconds.
229
+ ```
230
+
231
+ **Note**: Only some authenticator apps are compatible with custom `period` of tokens, for more details check these links:
232
+
233
+ - https://labanskoller.se/blog/2019/07/11/many-common-mobile-authenticator-apps-accept-qr-codes-for-modes-they-dont-support
234
+ - https://www.ibm.com/docs/en/sva/9.0.7?topic=authentication-configuring-totp-one-time-password-mechanism
235
+
236
+ So, be careful and aware when using custom intervals/periods for your TOTP codes beyond the default 30 seconds :)
237
+
216
238
  ### Working example
217
239
 
218
240
  Scan the following barcode with your phone, using Google Authenticator
@@ -2,25 +2,42 @@ module ActiveModel
2
2
  module OneTimePassword
3
3
  extend ActiveSupport::Concern
4
4
 
5
+ OTP_DEFAULT_COLUMN_NAME = 'otp_secret_key'.freeze
6
+ OTP_DEFAULT_COUNTER_COLUMN_NAME = 'otp_counter'.freeze
7
+ OTP_DEFAULT_BACKUP_CODES_COLUMN_NAME = 'otp_backup_codes'.freeze
8
+ OTP_DEFAULT_DIGITS = 6
9
+ OTP_DEFAULT_BACKUP_CODES_COUNT = 12
10
+ OTP_COUNTER_ENABLED_BY_DEFAULT = false
11
+ OTP_BACKUP_CODES_ENABLED_BY_DEFAULT = false
12
+
5
13
  module ClassMethods
6
14
  def has_one_time_password(options = {})
7
15
  cattr_accessor :otp_column_name, :otp_counter_column_name,
8
16
  :otp_backup_codes_column_name
9
17
  class_attribute :otp_digits, :otp_counter_based,
10
- :otp_backup_codes_count, :otp_one_time_backup_codes
11
-
12
- self.otp_column_name = (options[:column_name] || "otp_secret_key").to_s
13
- self.otp_digits = options[:length] || 6
14
-
15
- self.otp_counter_based = (options[:counter_based] || false)
16
- self.otp_counter_column_name = (options[:counter_column_name] || "otp_counter").to_s
18
+ :otp_backup_codes_count, :otp_one_time_backup_codes,
19
+ :otp_interval
17
20
 
21
+ self.otp_column_name = (
22
+ options[:column_name] || OTP_DEFAULT_COLUMN_NAME
23
+ ).to_s
24
+ self.otp_digits = options[:length] || OTP_DEFAULT_DIGITS
25
+ self.otp_counter_based = (
26
+ options[:counter_based] || OTP_COUNTER_ENABLED_BY_DEFAULT
27
+ )
28
+ self.otp_counter_column_name = (
29
+ options[:counter_column_name] || OTP_DEFAULT_COUNTER_COLUMN_NAME
30
+ ).to_s
31
+ self.otp_interval = options[:interval]
18
32
  self.otp_backup_codes_column_name = (
19
- options[:backup_codes_column_name] || 'otp_backup_codes'
33
+ options[:backup_codes_column_name] ||
34
+ OTP_DEFAULT_BACKUP_CODES_COLUMN_NAME
20
35
  ).to_s
21
- self.otp_backup_codes_count = options[:backup_codes_count] || 12
36
+ self.otp_backup_codes_count = (
37
+ options[:backup_codes_count] || OTP_DEFAULT_BACKUP_CODES_COUNT
38
+ )
22
39
  self.otp_one_time_backup_codes = (
23
- options[:one_time_backup_codes] || false
40
+ options[:one_time_backup_codes] || OTP_BACKUP_CODES_ENABLED_BY_DEFAULT
24
41
  )
25
42
 
26
43
  include InstanceMethodsOnActivation
@@ -144,7 +161,11 @@ module ActiveModel
144
161
  end
145
162
 
146
163
  def authenticate_totp(code, options = {})
147
- totp = ROTP::TOTP.new(otp_column, digits: otp_digits)
164
+ totp = ROTP::TOTP.new(
165
+ otp_column,
166
+ digits: otp_digits,
167
+ interval: otp_interval
168
+ )
148
169
  if (drift = options[:drift])
149
170
  totp.verify(code, drift_behind: drift)
150
171
  else
@@ -166,7 +187,11 @@ module ActiveModel
166
187
  else
167
188
  options
168
189
  end
169
- ROTP::TOTP.new(otp_column, digits: otp_digits).at(time)
190
+ ROTP::TOTP.new(
191
+ otp_column,
192
+ digits: otp_digits,
193
+ interval: otp_interval
194
+ ).at(time)
170
195
  end
171
196
 
172
197
  def authenticate_backup_code(code)
@@ -1,5 +1,5 @@
1
1
  module ActiveModel
2
2
  module Otp
3
- VERSION = "2.2.0".freeze
3
+ VERSION = '2.3.0'.freeze
4
4
  end
5
5
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DefaultIntervalUser < ActiveRecord::Base
4
+ has_one_time_password interval: 500
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class IntervalUser < ActiveRecord::Base
4
+ has_one_time_password interval: 2
5
+ end
@@ -162,4 +162,24 @@ class OtpTest < MiniTest::Test
162
162
  def test_otp_random_secret
163
163
  assert_match(/^.{32}$/, @user.class.otp_random_secret)
164
164
  end
165
+
166
+ def test_otp_interval
167
+ @interval_user = IntervalUser.new
168
+ @interval_user.email = 'roberto@heapsource.com'
169
+ @interval_user.run_callbacks :create
170
+ otp_code = @interval_user.otp_code
171
+ 2.times { assert_match(otp_code, @interval_user.otp_code) }
172
+ sleep 5
173
+ refute_match(otp_code, @interval_user.otp_code)
174
+ end
175
+
176
+ def test_otp_default_interval
177
+ @default_interval_user = DefaultIntervalUser.new
178
+ @default_interval_user.email = 'roberto@heapsource.com'
179
+ @default_interval_user.run_callbacks :create
180
+ otp_code = @default_interval_user.otp_code
181
+ 2.times { assert_match(otp_code, @default_interval_user.otp_code) }
182
+ sleep 5
183
+ assert_match(otp_code, @default_interval_user.otp_code)
184
+ end
165
185
  end
data/test/schema.rb CHANGED
@@ -8,4 +8,20 @@ ActiveRecord::Schema.define do
8
8
  t.string :otp_secret_key
9
9
  t.timestamps
10
10
  end
11
+
12
+ create_table :interval_users, force: true do |t|
13
+ t.string :key
14
+ t.string :email
15
+ t.integer :otp_counter
16
+ t.string :otp_secret_key
17
+ t.timestamps
18
+ end
19
+
20
+ create_table :default_interval_users, force: true do |t|
21
+ t.string :key
22
+ t.string :email
23
+ t.integer :otp_counter
24
+ t.string :otp_secret_key
25
+ t.timestamps
26
+ end
11
27
  end
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.2.0
4
+ version: 2.3.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-05-30 00:00:00.000000000 Z
13
+ date: 2021-06-22 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activemodel
@@ -120,8 +120,8 @@ executables: []
120
120
  extensions: []
121
121
  extra_rdoc_files: []
122
122
  files:
123
+ - ".github/workflows/active_model_otp.yml"
123
124
  - ".gitignore"
124
- - ".travis.yml"
125
125
  - Appraisals
126
126
  - CHANGELOG.md
127
127
  - Gemfile
@@ -139,6 +139,8 @@ files:
139
139
  - lib/active_model/otp/version.rb
140
140
  - lib/active_model_otp.rb
141
141
  - test/models/activerecord_user.rb
142
+ - test/models/default_interval_user.rb
143
+ - test/models/interval_user.rb
142
144
  - test/models/member.rb
143
145
  - test/models/opt_in_two_factor.rb
144
146
  - test/models/user.rb
@@ -171,6 +173,8 @@ specification_version: 4
171
173
  summary: Adds methods to set and authenticate against one time passwords.
172
174
  test_files:
173
175
  - test/models/activerecord_user.rb
176
+ - test/models/default_interval_user.rb
177
+ - test/models/interval_user.rb
174
178
  - test/models/member.rb
175
179
  - test/models/opt_in_two_factor.rb
176
180
  - test/models/user.rb
data/.travis.yml DELETED
@@ -1,43 +0,0 @@
1
- rvm:
2
- - 2.3
3
- - 2.4
4
- - 2.5
5
- - 2.6
6
- - 2.7
7
- - 3.0
8
- - ruby-head
9
- gemfile:
10
- - gemfiles/rails_4.2.gemfile
11
- - gemfiles/rails_5.0.gemfile
12
- - gemfiles/rails_5.1.gemfile
13
- - gemfiles/rails_5.2.gemfile
14
- - gemfiles/rails_6.0.gemfile
15
- - gemfiles/rails_6.1.gemfile
16
- matrix:
17
- exclude:
18
- - rvm: 2.3
19
- gemfile: gemfiles/rails_6.0.gemfile
20
- - rvm: 2.3
21
- gemfile: gemfiles/rails_6.1.gemfile
22
- - rvm: 2.4
23
- gemfile: gemfiles/rails_6.0.gemfile
24
- - rvm: 2.4
25
- gemfile: gemfiles/rails_6.1.gemfile
26
- - rvm: 2.7
27
- gemfile: gemfiles/rails_4.2.gemfile
28
- - rvm: 3.0
29
- gemfile: gemfiles/rails_4.2.gemfile
30
- - rvm: 3.0
31
- gemfile: gemfiles/rails_5.0.gemfile
32
- - rvm: 3.0
33
- gemfile: gemfiles/rails_5.1.gemfile
34
- - rvm: 3.0
35
- gemfile: gemfiles/rails_5.2.gemfile
36
- fast_finish: true
37
- allow_failures:
38
- - rvm: ruby-head
39
- # include:
40
- # - rvm: jruby
41
- # env: JRUBY_OPTS="--1.9 --server -Xcext.enabled=true"
42
- notifications:
43
- email: false