rotp 4.0.0 → 4.0.2

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