rotp 4.0.0 → 4.0.2

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: 681041e122df89a1947016e02fd7c11e3102ffc3eed78c1dcdcb5056af116afd
4
- data.tar.gz: 0333b10d91241c31b7fb2b3b5b7bd0d99f0e02b471ce0d10dc5961ae98cb89d7
3
+ metadata.gz: 72d869e33ff8ede2ef4233ed0398b730a1fc14e2b764295b090492bed133c4fc
4
+ data.tar.gz: 5ee5f47d3cee494762fcaaabb4dff1dfc247bab7faa0207fc6a3b7ff7b672f05
5
5
  SHA512:
6
- metadata.gz: a7454a2072e7f6b4cabd84338b2c9b1d06363102230d2c93bd244f0a486004b6363e9cd50aece371c951ae68798a6cc109798948c11034f679030bfd7106e464
7
- data.tar.gz: 18e79a2ca2a61d9b6a2dd902744e344fe1be9b7f4ac2c3d4674d6c83929f69b2395d794c9d9081115678fbfc81599f50a07cf8b287ce1c7f8ed05dcee4322e13
6
+ metadata.gz: 42c5bb89a97375204c3198dd9d07080f4728162307c9b99eb6f7c46152bfa8aa21d7e02699fc911c9991ef6ae17c9465f2c8e438bf223b2097cbc9b72766912b
7
+ data.tar.gz: 45dc82dd282328c5e3651a5b660ac327c1d10f07b7ca21025139fbe3edf418b661e1b67648081027e04ea9bce639195ec21d44ada7d6af9ac607984e8fcda47b
@@ -1,5 +1,14 @@
1
1
  ### Changelog
2
2
 
3
+ ### 4.0.2
4
+
5
+ - Fix gemspec requirment for Addressable
6
+
7
+ ### 4.0.1
8
+
9
+ - Rubocop for style fixes
10
+ - Replace deprecated URI.encode with Addressable's version
11
+
3
12
  #### 4.0.0
4
13
 
5
14
  - Simplify API
data/Guardfile CHANGED
@@ -1,5 +1,5 @@
1
1
  guard :rspec, cmd: 'bundle exec rspec --format progress' do
2
- require "guard/rspec/dsl"
2
+ require 'guard/rspec/dsl'
3
3
  dsl = Guard::RSpec::Dsl.new(self)
4
4
 
5
5
  # RSpec files
data/README.md CHANGED
@@ -21,7 +21,7 @@ Many websites use this for [multi-factor authentication](https://www.youtube.com
21
21
  - `verify` now takes options for `drift` and `after`
22
22
  - `verify` returns a timestamp if true, nil if false
23
23
  - Dropping support for Ruby < 2.0
24
- - Docs for 3.x can be found [here](https://github.com/mdp/rotp/tree/v3.3.0)
24
+ - Docs for 3.x can be found [here](https://github.com/mdp/rotp/tree/v3.x)
25
25
 
26
26
  ## Installation
27
27
 
data/Rakefile CHANGED
@@ -1,9 +1,9 @@
1
1
  require 'bundler'
2
2
  Bundler::GemHelper.install_tasks
3
- require "rspec/core/rake_task"
3
+ require 'rspec/core/rake_task'
4
4
 
5
5
  RSpec::Core::RakeTask.new(:rspec) do |spec|
6
6
  spec.pattern = 'spec/**/*_spec.rb'
7
7
  end
8
8
 
9
- task :default => :rspec
9
+ task default: :rspec
data/bin/rotp CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- $: << File.expand_path('../../lib', __FILE__)
3
+ $LOAD_PATH << File.expand_path('../lib', __dir__)
4
4
  require 'rotp'
5
5
  require 'rotp/cli'
6
6
 
@@ -1,5 +1,5 @@
1
1
  require 'cgi'
2
- require 'uri'
2
+ require 'addressable'
3
3
  require 'securerandom'
4
4
  require 'openssl'
5
5
  require 'rotp/base32'
@@ -7,6 +7,5 @@ require 'rotp/otp'
7
7
  require 'rotp/hotp'
8
8
  require 'rotp/totp'
9
9
 
10
-
11
10
  module ROTP
12
11
  end
@@ -3,7 +3,6 @@ require 'ostruct'
3
3
 
4
4
  module ROTP
5
5
  class Arguments
6
-
7
6
  def initialize(filename, arguments)
8
7
  @filename = filename
9
8
  @arguments = Array(arguments)
@@ -32,11 +31,11 @@ module ROTP
32
31
 
33
32
  def parse
34
33
  return options!.mode = :help if arguments.empty?
35
- parser.parse arguments
36
34
 
35
+ parser.parse arguments
37
36
  rescue OptionParser::InvalidOption => exception
38
37
  options!.mode = :help
39
- options!.warnings = red(exception.message + '. Try --help for help.')
38
+ options!.warnings = red(exception.message + '. Try --help for help.')
40
39
  end
41
40
 
42
41
  def parser
@@ -83,7 +82,5 @@ module ROTP
83
82
  def red(string)
84
83
  "\033[31m#{string}\033[0m"
85
84
  end
86
-
87
85
  end
88
86
  end
89
-
@@ -1,21 +1,21 @@
1
1
  module ROTP
2
2
  class Base32
3
3
  class Base32Error < RuntimeError; end
4
- CHARS = "abcdefghijklmnopqrstuvwxyz234567".each_char.to_a
4
+ CHARS = 'abcdefghijklmnopqrstuvwxyz234567'.each_char.to_a
5
5
 
6
6
  class << self
7
7
  def decode(str)
8
- str = str.tr('=','')
8
+ str = str.tr('=', '')
9
9
  output = []
10
10
  str.scan(/.{1,8}/).each do |block|
11
- char_array = decode_block(block).map{|c| c.chr}
11
+ char_array = decode_block(block).map(&:chr)
12
12
  output << char_array
13
13
  end
14
14
  output.join
15
15
  end
16
16
 
17
- def random_base32(length=32)
18
- b32 = String.new
17
+ def random_base32(length = 32)
18
+ b32 = ''
19
19
  SecureRandom.random_bytes(length).each_byte do |b|
20
20
  b32 << CHARS[b % 32]
21
21
  end
@@ -26,25 +26,27 @@ module ROTP
26
26
 
27
27
  def decode_block(block)
28
28
  length = block.scan(/[^=]/).length
29
- quints = block.each_char.map {|c| decode_quint(c)}
29
+ quints = block.each_char.map { |c| decode_quint(c) }
30
30
  bytes = []
31
31
  bytes[0] = (quints[0] << 3) + (quints[1] ? quints[1] >> 2 : 0)
32
32
  return bytes if length < 3
33
+
33
34
  bytes[1] = ((quints[1] & 3) << 6) + (quints[2] << 1) + (quints[3] ? quints[3] >> 4 : 0)
34
35
  return bytes if length < 4
36
+
35
37
  bytes[2] = ((quints[3] & 15) << 4) + (quints[4] ? quints[4] >> 1 : 0)
36
38
  return bytes if length < 6
39
+
37
40
  bytes[3] = ((quints[4] & 1) << 7) + (quints[5] << 2) + (quints[6] ? quints[6] >> 3 : 0)
38
41
  return bytes if length < 7
42
+
39
43
  bytes[4] = ((quints[6] & 7) << 5) + (quints[7] || 0)
40
44
  bytes
41
45
  end
42
46
 
43
47
  def decode_quint(q)
44
- CHARS.index(q.downcase) or raise(Base32Error, "Invalid Base32 Character - '#{q}'")
48
+ CHARS.index(q.downcase) || raise(Base32Error, "Invalid Base32 Character - '#{q}'")
45
49
  end
46
-
47
50
  end
48
-
49
51
  end
50
52
  end
@@ -16,10 +16,10 @@ module ROTP
16
16
  # :nocov:
17
17
 
18
18
  def errors
19
- if [:time, :hmac].include?(options.mode)
19
+ if %i[time hmac].include?(options.mode)
20
20
  if options.secret.to_s == ''
21
21
  red 'You must also specify a --secret. Try --help for help.'
22
- elsif options.secret.to_s.chars.any? { |c| ROTP::Base32::CHARS.index(c.downcase) == nil }
22
+ elsif options.secret.to_s.chars.any? { |c| ROTP::Base32::CHARS.index(c.downcase).nil? }
23
23
  red 'Secret must be in RFC4648 Base32 format - http://en.wikipedia.org/wiki/Base32#RFC_4648_Base32_alphabet'
24
24
  end
25
25
  end
@@ -48,6 +48,5 @@ module ROTP
48
48
  def red(string)
49
49
  "\033[31m#{string}\033[0m"
50
50
  end
51
-
52
51
  end
53
52
  end
@@ -12,10 +12,10 @@ module ROTP
12
12
  # @param counter [Integer] the counter of the OTP
13
13
  # @param retries [Integer] number of counters to incrementally retry
14
14
  def verify(otp, counter, retries: 0)
15
- counters = (counter..counter+retries).to_a
16
- counters.find { |c|
17
- super(otp, self.at(c))
18
- }
15
+ counters = (counter..counter + retries).to_a
16
+ counters.find do |c|
17
+ super(otp, at(c))
18
+ end
19
19
  end
20
20
 
21
21
  # Returns the provisioning URI for the OTP
@@ -24,15 +24,13 @@ module ROTP
24
24
  # @param [String] name of the account
25
25
  # @param [Integer] initial_count starting counter value, defaults to 0
26
26
  # @return [String] provisioning uri
27
- def provisioning_uri(name, initial_count=0)
27
+ def provisioning_uri(name, initial_count = 0)
28
28
  params = {
29
29
  secret: secret,
30
30
  counter: initial_count,
31
31
  digits: digits == DEFAULT_DIGITS ? nil : digits
32
32
  }
33
- encode_params("otpauth://hotp/#{URI.encode(name)}", params)
33
+ encode_params("otpauth://hotp/#{Addressable::URI.escape(name)}", params)
34
34
  end
35
-
36
35
  end
37
-
38
36
  end
@@ -13,7 +13,7 @@ module ROTP
13
13
  # @returns [OTP] OTP instantiation
14
14
  def initialize(s, options = {})
15
15
  @digits = options[:digits] || DEFAULT_DIGITS
16
- @digest = options[:digest] || "sha1"
16
+ @digest = options[:digest] || 'sha1'
17
17
  @secret = s
18
18
  end
19
19
 
@@ -30,17 +30,18 @@ module ROTP
30
30
 
31
31
  offset = hmac[-1].ord & 0xf
32
32
  code = (hmac[offset].ord & 0x7f) << 24 |
33
- (hmac[offset + 1].ord & 0xff) << 16 |
34
- (hmac[offset + 2].ord & 0xff) << 8 |
35
- (hmac[offset + 3].ord & 0xff)
36
- (code % 10 ** digits).to_s.rjust(digits, '0')
33
+ (hmac[offset + 1].ord & 0xff) << 16 |
34
+ (hmac[offset + 2].ord & 0xff) << 8 |
35
+ (hmac[offset + 3].ord & 0xff)
36
+ (code % 10**digits).to_s.rjust(digits, '0')
37
37
  end
38
38
 
39
39
  private
40
40
 
41
41
  def verify(input, generated)
42
- raise ArgumentError, "`otp` should be a String" unless
42
+ raise ArgumentError, '`otp` should be a String' unless
43
43
  input.is_a?(String)
44
+
44
45
  time_constant_compare(input, generated)
45
46
  end
46
47
 
@@ -54,24 +55,22 @@ module ROTP
54
55
  #
55
56
  def int_to_bytestring(int, padding = 8)
56
57
  unless int >= 0
57
- raise ArgumentError, "#int_to_bytestring requires a positive number"
58
+ raise ArgumentError, '#int_to_bytestring requires a positive number'
58
59
  end
59
60
 
60
61
  result = []
61
62
  until int == 0
62
63
  result << (int & 0xFF).chr
63
- int >>= 8
64
+ int >>= 8
64
65
  end
65
66
  result.reverse.join.rjust(padding, 0.chr)
66
67
  end
67
68
 
68
69
  # A very simple param encoder
69
70
  def encode_params(uri, params)
70
- params_str = String.new("?")
71
- params.each do |k,v|
72
- if v
73
- params_str << "#{k}=#{CGI::escape(v.to_s)}&"
74
- end
71
+ params_str = String.new('?')
72
+ params.each do |k, v|
73
+ params_str << "#{k}=#{CGI.escape(v.to_s)}&" if v
75
74
  end
76
75
  params_str.chop!
77
76
  uri + params_str
@@ -80,11 +79,11 @@ module ROTP
80
79
  # constant-time compare the strings
81
80
  def time_constant_compare(a, b)
82
81
  return false if a.empty? || b.empty? || a.bytesize != b.bytesize
82
+
83
83
  l = a.unpack "C#{a.bytesize}"
84
84
  res = 0
85
85
  b.each_byte { |byte| res |= byte ^ l.shift }
86
86
  res == 0
87
87
  end
88
-
89
88
  end
90
89
  end
@@ -1,7 +1,6 @@
1
1
  module ROTP
2
2
  DEFAULT_INTERVAL = 30
3
3
  class TOTP < OTP
4
-
5
4
  attr_reader :interval, :issuer
6
5
 
7
6
  # @option options [Integer] interval (30) the time interval in seconds for OTP
@@ -21,7 +20,7 @@ module ROTP
21
20
 
22
21
  # Generate the current time OTP
23
22
  # @return [Integer] the OTP as an integer
24
- def now()
23
+ def now
25
24
  generate_otp(timecode(Time.now))
26
25
  end
27
26
 
@@ -40,20 +39,15 @@ module ROTP
40
39
  def verify(otp, drift_ahead: 0, drift_behind: 0, after: nil, at: Time.now)
41
40
  timecodes = get_timecodes(at, drift_behind, drift_ahead)
42
41
 
43
- if after
44
- timecodes = timecodes.select { |t| t > timecode(after) }
45
- end
42
+ timecodes = timecodes.select { |t| t > timecode(after) } if after
46
43
 
47
44
  result = nil
48
- timecodes.each { |t|
49
- if (super(otp, self.generate_otp(t)))
50
- result = t * interval
51
- end
52
- }
53
- return result
45
+ timecodes.each do |t|
46
+ result = t * interval if super(otp, generate_otp(t))
47
+ end
48
+ result
54
49
  end
55
50
 
56
-
57
51
  # Returns the provisioning URI for the OTP
58
52
  # This can then be encoded in a QR Code and used
59
53
  # to provision the Google Authenticator app
@@ -64,15 +58,15 @@ module ROTP
64
58
  # https://github.com/google/google-authenticator/wiki/Key-Uri-Format
65
59
  # For compatibility the issuer appears both before that account name and also in the
66
60
  # query string.
67
- issuer_string = issuer.nil? ? "" : "#{URI.encode(issuer)}:"
61
+ issuer_string = issuer.nil? ? '' : "#{Addressable::URI.escape(issuer)}:"
68
62
  params = {
69
63
  secret: secret,
70
64
  period: interval == 30 ? nil : interval,
71
65
  issuer: issuer,
72
66
  digits: digits == DEFAULT_DIGITS ? nil : digits,
73
- algorithm: digest.upcase == 'SHA1' ? nil : digest.upcase,
67
+ algorithm: digest.casecmp('SHA1').zero? ? nil : digest.upcase
74
68
  }
75
- encode_params("otpauth://totp/#{issuer_string}#{URI.encode(name)}", params)
69
+ encode_params("otpauth://totp/#{issuer_string}#{Addressable::URI.escape(name)}", params)
76
70
  end
77
71
 
78
72
  private
@@ -82,20 +76,18 @@ module ROTP
82
76
  now = timeint(at)
83
77
  timecode_start = timecode(now - drift_behind)
84
78
  timecode_end = timecode(now + drift_ahead)
85
- return (timecode_start..timecode_end).step(1).to_a
79
+ (timecode_start..timecode_end).step(1).to_a
86
80
  end
87
81
 
88
82
  # Ensure UTC int
89
83
  def timeint(time)
90
- unless time.class == Time
91
- return time.to_i
92
- end
93
- return time.utc.to_i
84
+ return time.to_i unless time.class == Time
85
+
86
+ time.utc.to_i
94
87
  end
95
88
 
96
89
  def timecode(time)
97
- return timeint(time) / interval
90
+ timeint(time) / interval
98
91
  end
99
-
100
92
  end
101
93
  end
@@ -1,3 +1,3 @@
1
1
  module ROTP
2
- VERSION = "4.0.0"
2
+ VERSION = '4.0.2'.freeze
3
3
  end
@@ -1,26 +1,27 @@
1
- # -*- encoding: utf-8 -*-
2
- require "./lib/rotp/version"
1
+ require './lib/rotp/version'
3
2
 
4
3
  Gem::Specification.new do |s|
5
- s.name = "rotp"
4
+ s.name = 'rotp'
6
5
  s.version = ROTP::VERSION
7
6
  s.platform = Gem::Platform::RUBY
8
- s.license = "MIT"
9
- s.authors = ["Mark Percival"]
10
- s.email = ["mark@markpercival.us"]
11
- s.homepage = "http://github.com/mdp/rotp"
12
- s.summary = %q{A Ruby library for generating and verifying one time passwords}
13
- s.description = %q{Works for both HOTP and TOTP, and includes QR Code provisioning}
7
+ s.license = 'MIT'
8
+ s.authors = ['Mark Percival']
9
+ s.email = ['mark@markpercival.us']
10
+ s.homepage = 'http://github.com/mdp/rotp'
11
+ s.summary = 'A Ruby library for generating and verifying one time passwords'
12
+ s.description = 'Works for both HOTP and TOTP, and includes QR Code provisioning'
14
13
 
15
- s.rubyforge_project = "rotp"
14
+ s.rubyforge_project = 'rotp'
16
15
 
17
16
  s.files = `git ls-files`.split("\n")
18
17
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
- s.require_paths = ["lib"]
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
19
+ s.require_paths = ['lib']
20
+
21
+ s.add_runtime_dependency 'addressable', '~> 2.5'
21
22
 
22
23
  s.add_development_dependency 'rake', '~> 10.5'
23
24
  s.add_development_dependency 'rspec', '~> 3.5'
24
- s.add_development_dependency 'timecop', '~> 0.8'
25
25
  s.add_development_dependency 'simplecov', '~> 0.12'
26
+ s.add_development_dependency 'timecop', '~> 0.8'
26
27
  end
@@ -24,7 +24,7 @@ RSpec.describe ROTP::Arguments do
24
24
  end
25
25
 
26
26
  context 'unknown arguments' do
27
- let(:argv) { %w(--does-not-exist -xyz) }
27
+ let(:argv) { %w[--does-not-exist -xyz] }
28
28
 
29
29
  describe '#options' do
30
30
  it 'is in help mode' do
@@ -48,7 +48,7 @@ RSpec.describe ROTP::Arguments do
48
48
  end
49
49
 
50
50
  context 'asking for help' do
51
- let(:argv) { %w(--help) }
51
+ let(:argv) { %w[--help] }
52
52
 
53
53
  describe '#options' do
54
54
  it 'is in help mode' do
@@ -58,7 +58,7 @@ RSpec.describe ROTP::Arguments do
58
58
  end
59
59
 
60
60
  context 'generating a counter based secret' do
61
- let(:argv) { %w(--hmac --secret s3same) }
61
+ let(:argv) { %w[--hmac --secret s3same] }
62
62
 
63
63
  describe '#options' do
64
64
  it 'is in hmac mode' do
@@ -72,7 +72,7 @@ RSpec.describe ROTP::Arguments do
72
72
  end
73
73
 
74
74
  context 'generating a counter based secret' do
75
- let(:argv) { %w(--time --secret s3same) }
75
+ let(:argv) { %w[--time --secret s3same] }
76
76
 
77
77
  describe '#options' do
78
78
  it 'is in hmac mode' do
@@ -86,7 +86,7 @@ RSpec.describe ROTP::Arguments do
86
86
  end
87
87
 
88
88
  context 'generating a time based secret' do
89
- let(:argv) { %w(--secret s3same) }
89
+ let(:argv) { %w[--secret s3same] }
90
90
 
91
91
  describe '#options' do
92
92
  it 'is in time mode' do
@@ -98,5 +98,4 @@ RSpec.describe ROTP::Arguments do
98
98
  end
99
99
  end
100
100
  end
101
-
102
101
  end
@@ -1,7 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe ROTP::Base32 do
4
-
5
4
  describe '.random_base32' do
6
5
  context 'without arguments' do
7
6
  let(:base32) { ROTP::Base32.random_base32 }
@@ -10,8 +9,8 @@ RSpec.describe ROTP::Base32 do
10
9
  expect(base32.length).to eq 32
11
10
  end
12
11
 
13
- it 'is hexadecimal' do
14
- expect(base32).to match %r{\A[a-z2-7]+\z}
12
+ it 'is base32 charset' do
13
+ expect(base32).to match(/\A[a-z2-7]+\z/)
15
14
  end
16
15
  end
17
16
 
@@ -4,14 +4,14 @@ require 'rotp/cli'
4
4
  RSpec.describe ROTP::CLI do
5
5
  let(:cli) { described_class.new('executable', argv) }
6
6
  let(:output) { cli.output }
7
- let(:now) { Time.utc 2012,1,1 }
7
+ let(:now) { Time.utc 2012, 1, 1 }
8
8
 
9
9
  before do
10
10
  Timecop.freeze now
11
11
  end
12
12
 
13
13
  context 'generating a TOTP' do
14
- let(:argv) { %w(--secret JBSWY3DPEHPK3PXP) }
14
+ let(:argv) { %w[--secret JBSWY3DPEHPK3PXP] }
15
15
 
16
16
  it 'prints the corresponding token' do
17
17
  expect(output).to eq '068212'
@@ -19,7 +19,7 @@ RSpec.describe ROTP::CLI do
19
19
  end
20
20
 
21
21
  context 'generating a TOTP with no secret' do
22
- let(:argv) { %W(--time --secret) }
22
+ let(:argv) { %w[--time --secret] }
23
23
 
24
24
  it 'prints the corresponding token' do
25
25
  expect(output).to match 'You must also specify a --secret'
@@ -27,7 +27,7 @@ RSpec.describe ROTP::CLI do
27
27
  end
28
28
 
29
29
  context 'generating a TOTP with bad base32 secret' do
30
- let(:argv) { %W(--time --secret #{'1' * 32}) }
30
+ let(:argv) { %W[--time --secret #{'1' * 32}] }
31
31
 
32
32
  it 'prints the corresponding token' do
33
33
  expect(output).to match 'Secret must be in RFC4648 Base32 format'
@@ -35,7 +35,7 @@ RSpec.describe ROTP::CLI do
35
35
  end
36
36
 
37
37
  context 'trying to generate an unsupport type' do
38
- let(:argv) { %W(--notreal --secret #{'a' * 32}) }
38
+ let(:argv) { %W[--notreal --secret #{'a' * 32}] }
39
39
 
40
40
  it 'prints the corresponding token' do
41
41
  expect(output).to match 'invalid option: --notreal'
@@ -43,11 +43,10 @@ RSpec.describe ROTP::CLI do
43
43
  end
44
44
 
45
45
  context 'generating a HOTP' do
46
- let(:argv) { %W(--hmac --secret #{'a' * 32} --counter 1234) }
46
+ let(:argv) { %W[--hmac --secret #{'a' * 32} --counter 1234] }
47
47
 
48
48
  it 'prints the corresponding token' do
49
49
  expect(output).to eq '161024'
50
50
  end
51
51
  end
52
-
53
52
  end
@@ -14,6 +14,12 @@ RSpec.describe ROTP::HOTP do
14
14
  end
15
15
  end
16
16
 
17
+ context 'invalid counter' do
18
+ it 'raises an error' do
19
+ expect { hotp.at(-123_456) }.to raise_error(ArgumentError)
20
+ end
21
+ end
22
+
17
23
  context 'RFC compatibility' do
18
24
  let(:hotp) { ROTP::HOTP.new('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ') }
19
25
 
@@ -31,7 +37,6 @@ RSpec.describe ROTP::HOTP do
31
37
  expect(hotp.at(8)).to eq '399871'
32
38
  expect(hotp.at(9)).to eq '520489'
33
39
  end
34
-
35
40
  end
36
41
  end
37
42
 
@@ -39,7 +44,7 @@ RSpec.describe ROTP::HOTP do
39
44
  let(:verification) { hotp.verify token, counter }
40
45
 
41
46
  context 'numeric token' do
42
- let(:token) { 161024 }
47
+ let(:token) { 161_024 }
43
48
 
44
49
  it 'raises an error' do
45
50
  expect { verification }.to raise_error(ArgumentError)
@@ -62,7 +67,7 @@ RSpec.describe ROTP::HOTP do
62
67
  end
63
68
  end
64
69
  describe 'with retries' do
65
- let(:verification) { hotp.verify token, counter, retries:retries }
70
+ let(:verification) { hotp.verify token, counter, retries: retries }
66
71
 
67
72
  context 'counter outside than retries' do
68
73
  let(:counter) { 1223 }
@@ -104,7 +109,7 @@ RSpec.describe ROTP::HOTP do
104
109
 
105
110
  describe '#provisioning_uri' do
106
111
  let(:uri) { hotp.provisioning_uri('mark@percival') }
107
- let(:params) { CGI::parse URI::parse(uri).query }
112
+ let(:params) { CGI.parse URI.parse(uri).query }
108
113
 
109
114
  it 'has the correct format' do
110
115
  expect(uri).to match %r{\Aotpauth:\/\/hotp.+}
@@ -128,5 +133,4 @@ RSpec.describe ROTP::HOTP do
128
133
  end
129
134
  end
130
135
  end
131
-
132
136
  end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
- TEST_TIME = Time.utc 2016,9,23,9 # 2016-09-23 09:00:00 UTC
4
- TEST_TOKEN = "082630"
3
+ TEST_TIME = Time.utc 2016, 9, 23, 9 # 2016-09-23 09:00:00 UTC
4
+ TEST_TOKEN = '082630'.freeze
5
5
 
6
6
  RSpec.describe ROTP::TOTP do
7
7
  let(:now) { TEST_TIME }
@@ -19,11 +19,10 @@ RSpec.describe ROTP::TOTP do
19
19
  let(:totp) { ROTP::TOTP.new('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ') }
20
20
 
21
21
  it 'matches the RFC documentation examples' do
22
- expect(totp.at 1111111111).to eq '050471'
23
- expect(totp.at 1234567890).to eq '005924'
24
- expect(totp.at 2000000000).to eq '279037'
22
+ expect(totp.at(1_111_111_111)).to eq '050471'
23
+ expect(totp.at(1_234_567_890)).to eq '005924'
24
+ expect(totp.at(2_000_000_000)).to eq '279037'
25
25
  end
26
-
27
26
  end
28
27
  end
29
28
 
@@ -31,7 +30,7 @@ RSpec.describe ROTP::TOTP do
31
30
  let(:verification) { totp.verify token, at: now }
32
31
 
33
32
  context 'numeric token' do
34
- let(:token) { 82630 }
33
+ let(:token) { 82_630 }
35
34
 
36
35
  it 'raises an error with an integer' do
37
36
  expect { verification }.to raise_error(ArgumentError)
@@ -53,7 +52,7 @@ RSpec.describe ROTP::TOTP do
53
52
  end
54
53
 
55
54
  context 'RFC compatibility' do
56
- let(:totp) { ROTP::TOTP.new 'wrn3pqx5uqxqvnqr' }
55
+ let(:totp) { ROTP::TOTP.new 'wrn3pqx5uqxqvnqr' }
57
56
 
58
57
  before do
59
58
  Timecop.freeze now
@@ -61,7 +60,7 @@ RSpec.describe ROTP::TOTP do
61
60
 
62
61
  context 'correct time based OTP' do
63
62
  let(:token) { '102705' }
64
- let(:now) { Time.at 1297553958 }
63
+ let(:now) { Time.at 1_297_553_958 }
65
64
 
66
65
  it 'verifies' do
67
66
  expect(totp.verify('102705')).to be_truthy
@@ -75,17 +74,17 @@ RSpec.describe ROTP::TOTP do
75
74
  end
76
75
  end
77
76
  context 'invalidating reused tokens' do
78
- let(:verification) {
77
+ let(:verification) do
79
78
  totp.verify token,
80
- after: after,
81
- at: now
82
- }
79
+ after: after,
80
+ at: now
81
+ end
83
82
  let(:after) { nil }
84
83
 
85
84
  context 'passing in the `after` timestamp' do
86
- let(:after) {
85
+ let(:after) do
87
86
  totp.verify TEST_TOKEN, after: nil, at: now
88
- }
87
+ end
89
88
 
90
89
  it 'returns a timecode' do
91
90
  expect(after).to be_kind_of(Integer)
@@ -106,23 +105,23 @@ RSpec.describe ROTP::TOTP do
106
105
  totp.send('get_timecodes', at, b, a)
107
106
  end
108
107
 
109
- describe "drifting timecodes" do
108
+ describe 'drifting timecodes' do
110
109
  it 'should get timecodes behind' do
111
- expect(get_timecodes(TEST_TIME+15, 15, 0)).to eq([49154040])
112
- expect(get_timecodes(TEST_TIME, 15, 0)).to eq([49154039, 49154040])
113
- expect(get_timecodes(TEST_TIME, 40, 0)).to eq([49154038, 49154039, 49154040])
114
- expect(get_timecodes(TEST_TIME, 90, 0)).to eq([49154037, 49154038, 49154039, 49154040])
110
+ expect(get_timecodes(TEST_TIME + 15, 15, 0)).to eq([49_154_040])
111
+ expect(get_timecodes(TEST_TIME, 15, 0)).to eq([49_154_039, 49_154_040])
112
+ expect(get_timecodes(TEST_TIME, 40, 0)).to eq([49_154_038, 49_154_039, 49_154_040])
113
+ expect(get_timecodes(TEST_TIME, 90, 0)).to eq([49_154_037, 49_154_038, 49_154_039, 49_154_040])
115
114
  end
116
115
  it 'should get timecodes ahead' do
117
- expect(get_timecodes(TEST_TIME, 0, 15)).to eq([49154040])
118
- expect(get_timecodes(TEST_TIME+15, 0, 15)).to eq([49154040, 49154041])
119
- expect(get_timecodes(TEST_TIME, 0, 30)).to eq([49154040, 49154041])
120
- expect(get_timecodes(TEST_TIME, 0, 70)).to eq([49154040, 49154041, 49154042])
121
- expect(get_timecodes(TEST_TIME, 0, 90)).to eq([49154040, 49154041, 49154042, 49154043])
116
+ expect(get_timecodes(TEST_TIME, 0, 15)).to eq([49_154_040])
117
+ expect(get_timecodes(TEST_TIME + 15, 0, 15)).to eq([49_154_040, 49_154_041])
118
+ expect(get_timecodes(TEST_TIME, 0, 30)).to eq([49_154_040, 49_154_041])
119
+ expect(get_timecodes(TEST_TIME, 0, 70)).to eq([49_154_040, 49_154_041, 49_154_042])
120
+ expect(get_timecodes(TEST_TIME, 0, 90)).to eq([49_154_040, 49_154_041, 49_154_042, 49_154_043])
122
121
  end
123
122
  it 'should get timecodes behind and ahead' do
124
- expect(get_timecodes(TEST_TIME, 30, 30)).to eq([49154039, 49154040, 49154041])
125
- expect(get_timecodes(TEST_TIME, 60, 60)).to eq([49154038, 49154039, 49154040, 49154041, 49154042])
123
+ expect(get_timecodes(TEST_TIME, 30, 30)).to eq([49_154_039, 49_154_040, 49_154_041])
124
+ expect(get_timecodes(TEST_TIME, 60, 60)).to eq([49_154_038, 49_154_039, 49_154_040, 49_154_041, 49_154_042])
126
125
  end
127
126
  end
128
127
 
@@ -131,7 +130,6 @@ RSpec.describe ROTP::TOTP do
131
130
  let(:drift_ahead) { 0 }
132
131
  let(:drift_behind) { 0 }
133
132
 
134
-
135
133
  context 'with an old OTP' do
136
134
  let(:token) { totp.at TEST_TIME - 30 } # Previous token at 2016-09-23 08:59:30 UTC
137
135
  let(:drift_behind) { 15 }
@@ -151,7 +149,6 @@ RSpec.describe ROTP::TOTP do
151
149
  expect(verification).to be_nil
152
150
  end
153
151
  end
154
-
155
152
  end
156
153
 
157
154
  context 'with a future OTP' do
@@ -166,14 +163,13 @@ RSpec.describe ROTP::TOTP do
166
163
  # Tested at 2016-09-23 09:00:20 UTC, and with drift ahead to 2016-09-23 09:00:35 UTC
167
164
  # This would therefore include 2 intervals
168
165
  context 'inside of drift range' do
169
- let(:now) { TEST_TIME + 20 }
166
+ let(:now) { TEST_TIME + 20 }
170
167
 
171
168
  it 'is true' do
172
169
  expect(verification).to be_truthy
173
170
  end
174
171
  end
175
172
  end
176
-
177
173
  end
178
174
 
179
175
  describe '#verify with drift and prevent token reuse' do
@@ -183,7 +179,6 @@ RSpec.describe ROTP::TOTP do
183
179
  let(:after) { nil }
184
180
 
185
181
  context 'with the `after` timestamp set' do
186
-
187
182
  context 'older token' do
188
183
  let(:token) { totp.at TEST_TIME - 30 }
189
184
  let(:drift_behind) { 15 }
@@ -194,14 +189,13 @@ RSpec.describe ROTP::TOTP do
194
189
  end
195
190
 
196
191
  context 'after it has been used' do
197
- let(:after) {
192
+ let(:after) do
198
193
  totp.verify token, after: nil, at: now, drift_behind: drift_behind
199
- }
194
+ end
200
195
  it 'is false' do
201
196
  expect(verification).to be_falsey
202
197
  end
203
198
  end
204
-
205
199
  end
206
200
 
207
201
  context 'newer token' do
@@ -215,21 +209,20 @@ RSpec.describe ROTP::TOTP do
215
209
  end
216
210
 
217
211
  context 'after it has been used' do
218
- let(:after) {
212
+ let(:after) do
219
213
  totp.verify token, after: nil, at: now, drift_ahead: drift_ahead
220
- }
214
+ end
221
215
  it 'is false' do
222
216
  expect(verification).to be_falsey
223
217
  end
224
218
  end
225
-
226
219
  end
227
220
  end
228
221
  end
229
222
 
230
223
  describe '#provisioning_uri' do
231
224
  let(:uri) { totp.provisioning_uri('mark@percival') }
232
- let(:params) { CGI::parse URI::parse(uri).query }
225
+ let(:params) { CGI.parse URI.parse(uri).query }
233
226
 
234
227
  context 'without issuer' do
235
228
  it 'has the correct format' do
@@ -302,7 +295,6 @@ RSpec.describe ROTP::TOTP do
302
295
  expect(params['algorithm'].first).to eq 'SHA256'
303
296
  end
304
297
  end
305
-
306
298
  end
307
299
 
308
300
  describe '#now' do
@@ -312,7 +304,7 @@ RSpec.describe ROTP::TOTP do
312
304
 
313
305
  context 'Google Authenticator' do
314
306
  let(:totp) { ROTP::TOTP.new 'wrn3pqx5uqxqvnqr' }
315
- let(:now) { Time.at 1297553958 }
307
+ let(:now) { Time.at 1_297_553_958 }
316
308
 
317
309
  it 'matches the known output' do
318
310
  expect(totp.now).to eq '102705'
@@ -321,12 +313,11 @@ RSpec.describe ROTP::TOTP do
321
313
 
322
314
  context 'Dropbox 26 char secret output' do
323
315
  let(:totp) { ROTP::TOTP.new 'tjtpqea6a42l56g5eym73go2oa' }
324
- let(:now) { Time.at 1378762454 }
316
+ let(:now) { Time.at 1_378_762_454 }
325
317
 
326
318
  it 'matches the known output' do
327
319
  expect(totp.now).to eq '747864'
328
320
  end
329
321
  end
330
322
  end
331
-
332
323
  end
@@ -1,6 +1,6 @@
1
1
  require 'simplecov'
2
2
  SimpleCov.start do
3
- add_filter "/spec/"
3
+ add_filter '/spec/'
4
4
  end
5
5
 
6
6
  require 'rotp'
@@ -17,5 +17,4 @@ RSpec.configure do |config|
17
17
  end
18
18
  end
19
19
 
20
-
21
20
  require_relative '../lib/rotp'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rotp
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 4.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Percival
@@ -10,6 +10,20 @@ bindir: bin
10
10
  cert_chain: []
11
11
  date: 2018-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: addressable
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.5'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rake
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -39,33 +53,33 @@ dependencies:
39
53
  - !ruby/object:Gem::Version
40
54
  version: '3.5'
41
55
  - !ruby/object:Gem::Dependency
42
- name: timecop
56
+ name: simplecov
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - "~>"
46
60
  - !ruby/object:Gem::Version
47
- version: '0.8'
61
+ version: '0.12'
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
- version: '0.8'
68
+ version: '0.12'
55
69
  - !ruby/object:Gem::Dependency
56
- name: simplecov
70
+ name: timecop
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
73
  - - "~>"
60
74
  - !ruby/object:Gem::Version
61
- version: '0.12'
75
+ version: '0.8'
62
76
  type: :development
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
80
  - - "~>"
67
81
  - !ruby/object:Gem::Version
68
- version: '0.12'
82
+ version: '0.8'
69
83
  description: Works for both HOTP and TOTP, and includes QR Code provisioning
70
84
  email:
71
85
  - mark@markpercival.us