rotp 4.0.0 → 6.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +19 -0
  3. data/.devcontainer/devcontainer.json +41 -0
  4. data/.github/workflows/release.yaml +36 -0
  5. data/.github/workflows/test.yaml +26 -0
  6. data/.release-please-manifest.json +3 -0
  7. data/CHANGELOG.md +91 -24
  8. data/Dockerfile-2.3 +1 -7
  9. data/Dockerfile-2.7 +11 -0
  10. data/Dockerfile-3.0 +12 -0
  11. data/Guardfile +1 -1
  12. data/README.md +64 -15
  13. data/bin/rotp +1 -1
  14. data/docker-compose.yml +37 -0
  15. data/lib/rotp/arguments.rb +6 -5
  16. data/lib/rotp/base32.rb +56 -30
  17. data/lib/rotp/cli.rb +4 -5
  18. data/lib/rotp/hotp.rb +6 -13
  19. data/lib/rotp/otp/uri.rb +78 -0
  20. data/lib/rotp/otp.rb +26 -25
  21. data/lib/rotp/totp.rb +13 -33
  22. data/lib/rotp/version.rb +1 -1
  23. data/lib/rotp.rb +2 -4
  24. data/release-please-config.json +12 -0
  25. data/rotp.gemspec +13 -15
  26. data/spec/lib/rotp/arguments_spec.rb +5 -6
  27. data/spec/lib/rotp/base32_spec.rb +45 -19
  28. data/spec/lib/rotp/cli_spec.rb +21 -6
  29. data/spec/lib/rotp/hotp_spec.rb +38 -17
  30. data/spec/lib/rotp/otp/uri_spec.rb +99 -0
  31. data/spec/lib/rotp/totp_spec.rb +61 -98
  32. data/spec/spec_helper.rb +1 -2
  33. metadata +25 -43
  34. data/.travis.yml +0 -8
  35. data/Dockerfile-1.9 +0 -15
  36. data/Dockerfile-2.1 +0 -16
  37. data/Rakefile +0 -9
  38. data/doc/ROTP/HOTP.html +0 -308
  39. data/doc/ROTP/OTP.html +0 -593
  40. data/doc/ROTP/TOTP.html +0 -493
  41. data/doc/Rotp.html +0 -179
  42. data/doc/_index.html +0 -144
  43. data/doc/class_list.html +0 -36
  44. data/doc/css/common.css +0 -1
  45. data/doc/css/full_list.css +0 -53
  46. data/doc/css/style.css +0 -310
  47. data/doc/file.README.html +0 -89
  48. data/doc/file_list.html +0 -38
  49. data/doc/frames.html +0 -13
  50. data/doc/index.html +0 -89
  51. data/doc/js/app.js +0 -203
  52. data/doc/js/full_list.js +0 -149
  53. data/doc/js/jquery.js +0 -154
  54. data/doc/method_list.html +0 -155
  55. data/doc/top-level-namespace.html +0 -88
@@ -4,22 +4,30 @@ 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'
18
18
  end
19
19
  end
20
20
 
21
+ context 'generating a TOTP with sha256 digest' do
22
+ let(:argv) { %w[--secret JBSWY3DPEHPK3PXP --digest sha256] }
23
+
24
+ it 'prints the corresponding token' do
25
+ expect(output).to eq '544902'
26
+ end
27
+ end
28
+
21
29
  context 'generating a TOTP with no secret' do
22
- let(:argv) { %W(--time --secret) }
30
+ let(:argv) { %w[--time --secret] }
23
31
 
24
32
  it 'prints the corresponding token' do
25
33
  expect(output).to match 'You must also specify a --secret'
@@ -27,7 +35,7 @@ RSpec.describe ROTP::CLI do
27
35
  end
28
36
 
29
37
  context 'generating a TOTP with bad base32 secret' do
30
- let(:argv) { %W(--time --secret #{'1' * 32}) }
38
+ let(:argv) { %W[--time --secret #{'1' * 32}] }
31
39
 
32
40
  it 'prints the corresponding token' do
33
41
  expect(output).to match 'Secret must be in RFC4648 Base32 format'
@@ -35,7 +43,7 @@ RSpec.describe ROTP::CLI do
35
43
  end
36
44
 
37
45
  context 'trying to generate an unsupport type' do
38
- let(:argv) { %W(--notreal --secret #{'a' * 32}) }
46
+ let(:argv) { %W[--notreal --secret #{'a' * 32}] }
39
47
 
40
48
  it 'prints the corresponding token' do
41
49
  expect(output).to match 'invalid option: --notreal'
@@ -43,11 +51,18 @@ RSpec.describe ROTP::CLI do
43
51
  end
44
52
 
45
53
  context 'generating a HOTP' do
46
- let(:argv) { %W(--hmac --secret #{'a' * 32} --counter 1234) }
54
+ let(:argv) { %W[--hmac --secret #{'a' * 32} --counter 1234] }
47
55
 
48
56
  it 'prints the corresponding token' do
49
57
  expect(output).to eq '161024'
50
58
  end
51
59
  end
52
60
 
61
+ context 'generating a HOTP' do
62
+ let(:argv) { %W[--hmac --secret #{'a' * 32} --counter 1234 --digest sha256] }
63
+
64
+ it 'prints the corresponding token' do
65
+ expect(output).to eq '325941'
66
+ end
67
+ end
53
68
  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 }
@@ -103,30 +108,46 @@ RSpec.describe ROTP::HOTP do
103
108
  end
104
109
 
105
110
  describe '#provisioning_uri' do
106
- let(:uri) { hotp.provisioning_uri('mark@percival') }
107
- let(:params) { CGI::parse URI::parse(uri).query }
111
+ let(:hotp) { ROTP::HOTP.new('a' * 32, name: "m@mdp.im") }
112
+ let(:params) { CGI.parse URI.parse(uri).query }
113
+
114
+ it 'created from the otp instance data' do
115
+ expect(hotp.provisioning_uri())
116
+ .to eq 'otpauth://hotp/m%40mdp.im?secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&counter=0'
117
+ end
108
118
 
109
- it 'has the correct format' do
110
- expect(uri).to match %r{\Aotpauth:\/\/hotp.+}
119
+ it 'allow passing a name to override the OTP name' do
120
+ expect(hotp.provisioning_uri('mark@percival'))
121
+ .to eq 'otpauth://hotp/mark%40percival?secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&counter=0'
111
122
  end
112
123
 
113
- it 'includes the secret as parameter' do
114
- expect(params['secret'].first).to eq 'a' * 32
124
+ it 'also accepts a custom counter value' do
125
+ expect(hotp.provisioning_uri('mark@percival', 17))
126
+ .to eq 'otpauth://hotp/mark%40percival?secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&counter=17'
115
127
  end
116
128
 
117
- context 'with default digits' do
118
- it 'does not include digits parameter with default digits' do
119
- expect(params['digits'].first).to be_nil
129
+ context 'with non-standard provisioning_params' do
130
+ let(:hotp) { ROTP::HOTP.new('a' * 32, digits: 8, provisioning_params: {image: 'https://example.com/icon.png'}) }
131
+ let(:uri) { hotp.provisioning_uri("mark@percival") }
132
+
133
+ it 'includes the issuer as parameter' do
134
+ expect(params['image'].first).to eq 'https://example.com/icon.png'
120
135
  end
121
136
  end
122
137
 
123
- context 'with non-default digits' do
124
- let(:hotp) { ROTP::HOTP.new('a' * 32, digits: 8) }
138
+ context "with an issuer" do
139
+ let(:hotp) { ROTP::HOTP.new('a' * 32, name: "m@mdp.im", issuer: "Example.com") }
140
+
141
+ it 'created from the otp instance data' do
142
+ expect(hotp.provisioning_uri())
143
+ .to eq 'otpauth://hotp/Example.com:m%40mdp.im?secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&issuer=Example.com&counter=0'
144
+ end
125
145
 
126
- it 'includes digits parameter' do
127
- expect(params['digits'].first).to eq '8'
146
+ it 'allow passing a name to override the OTP name' do
147
+ expect(hotp.provisioning_uri('mark@percival'))
148
+ .to eq 'otpauth://hotp/Example.com:mark%40percival?secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&issuer=Example.com&counter=0'
128
149
  end
129
150
  end
130
- end
131
151
 
152
+ end
132
153
  end
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ROTP::OTP::URI do
4
+ it 'meets basic functionality' do
5
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP')
6
+ uri = described_class.new(otp, account_name: 'alice@google.com')
7
+ expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP'
8
+ end
9
+
10
+ it 'includes issuer' do
11
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', issuer: 'Example')
12
+ uri = described_class.new(otp, account_name: 'alice@google.com')
13
+ expect(uri.to_s).to eq 'otpauth://totp/Example:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example'
14
+ end
15
+
16
+ it 'encodes the account name' do
17
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', issuer: 'Provider1')
18
+ uri = described_class.new(otp, account_name: 'Alice Smith')
19
+ expect(uri.to_s).to eq 'otpauth://totp/Provider1:Alice%20Smith?secret=JBSWY3DPEHPK3PXP&issuer=Provider1'
20
+ end
21
+
22
+ it 'encodes the issuer' do
23
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', issuer: 'Big Corporation')
24
+ uri = described_class.new(otp, account_name: ' alice@bigco.com')
25
+ expect(uri.to_s).to eq 'otpauth://totp/Big%20Corporation:%20alice%40bigco.com?secret=JBSWY3DPEHPK3PXP&issuer=Big%20Corporation'
26
+ end
27
+
28
+ it 'includes non-default SHA256 algorithm' do
29
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', digest: 'sha256')
30
+ uri = described_class.new(otp, account_name: 'alice@google.com')
31
+ expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&algorithm=SHA256'
32
+ end
33
+
34
+ it 'includes non-default SHA512 algorithm' do
35
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', digest: 'sha512')
36
+ uri = described_class.new(otp, account_name: 'alice@google.com')
37
+ expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&algorithm=SHA512'
38
+ end
39
+
40
+ it 'includes non-default 8 digits' do
41
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', digits: 8)
42
+ uri = described_class.new(otp, account_name: 'alice@google.com')
43
+ expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&digits=8'
44
+ end
45
+
46
+ it 'includes non-default period for TOTP' do
47
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', interval: 35)
48
+ uri = described_class.new(otp, account_name: 'alice@google.com')
49
+ expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&period=35'
50
+ end
51
+
52
+ it 'includes non-default counter for HOTP' do
53
+ otp = ROTP::HOTP.new('JBSWY3DPEHPK3PXP')
54
+ uri = described_class.new(otp, account_name: 'alice@google.com', counter: 17)
55
+ expect(uri.to_s).to eq 'otpauth://hotp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&counter=17'
56
+ end
57
+
58
+ it 'can include all parameters' do
59
+ otp = ROTP::TOTP.new(
60
+ 'HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ',
61
+ digest: 'sha512',
62
+ digits: 8,
63
+ interval: 60,
64
+ issuer: 'ACME Co',
65
+ )
66
+ uri = described_class.new(otp, account_name: 'john.doe@email.com')
67
+ expect(uri.to_s).to eq'otpauth://totp/ACME%20Co:john.doe%40email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA512&digits=8&period=60'
68
+ end
69
+
70
+ it 'strips leading and trailing whitespace from the issuer' do
71
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', issuer: ' Big Corporation ')
72
+ uri = described_class.new(otp, account_name: ' alice@bigco.com')
73
+ expect(uri.to_s).to eq 'otpauth://totp/Big%20Corporation:%20alice%40bigco.com?secret=JBSWY3DPEHPK3PXP&issuer=Big%20Corporation'
74
+ end
75
+
76
+ it 'strips trailing whitespace from the account name' do
77
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP')
78
+ uri = described_class.new(otp, account_name: ' alice@google.com ')
79
+ expect(uri.to_s).to eq 'otpauth://totp/%20%20alice%40google.com?secret=JBSWY3DPEHPK3PXP'
80
+ end
81
+
82
+ it 'replaces colons in the issuer with underscores' do
83
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', issuer: 'Big:Corporation')
84
+ uri = described_class.new(otp, account_name: 'alice@bigco.com')
85
+ expect(uri.to_s).to eq 'otpauth://totp/Big_Corporation:alice%40bigco.com?secret=JBSWY3DPEHPK3PXP&issuer=Big_Corporation'
86
+ end
87
+
88
+ it 'replaces colons in the account name with underscores' do
89
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP')
90
+ uri = described_class.new(otp, account_name: 'Alice:Smith')
91
+ expect(uri.to_s).to eq 'otpauth://totp/Alice_Smith?secret=JBSWY3DPEHPK3PXP'
92
+ end
93
+
94
+ it 'handles email account names with sub-addressing' do
95
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP')
96
+ uri = described_class.new(otp, account_name: 'alice+1234@google.com')
97
+ expect(uri.to_s).to eq 'otpauth://totp/alice%2B1234%40google.com?secret=JBSWY3DPEHPK3PXP'
98
+ end
99
+ end
@@ -1,12 +1,13 @@
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
+ TEST_SECRET = 'JBSWY3DPEHPK3PXP'
5
6
 
6
7
  RSpec.describe ROTP::TOTP do
7
8
  let(:now) { TEST_TIME }
8
9
  let(:token) { TEST_TOKEN }
9
- let(:totp) { ROTP::TOTP.new 'JBSWY3DPEHPK3PXP' }
10
+ let(:totp) { ROTP::TOTP.new TEST_SECRET }
10
11
 
11
12
  describe '#at' do
12
13
  let(:token) { totp.at now }
@@ -19,11 +20,10 @@ RSpec.describe ROTP::TOTP do
19
20
  let(:totp) { ROTP::TOTP.new('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ') }
20
21
 
21
22
  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'
23
+ expect(totp.at(1_111_111_111)).to eq '050471'
24
+ expect(totp.at(1_234_567_890)).to eq '005924'
25
+ expect(totp.at(2_000_000_000)).to eq '279037'
25
26
  end
26
-
27
27
  end
28
28
  end
29
29
 
@@ -31,7 +31,7 @@ RSpec.describe ROTP::TOTP do
31
31
  let(:verification) { totp.verify token, at: now }
32
32
 
33
33
  context 'numeric token' do
34
- let(:token) { 82630 }
34
+ let(:token) { 82_630 }
35
35
 
36
36
  it 'raises an error with an integer' do
37
37
  expect { verification }.to raise_error(ArgumentError)
@@ -53,7 +53,7 @@ RSpec.describe ROTP::TOTP do
53
53
  end
54
54
 
55
55
  context 'RFC compatibility' do
56
- let(:totp) { ROTP::TOTP.new 'wrn3pqx5uqxqvnqr' }
56
+ let(:totp) { ROTP::TOTP.new 'wrn3pqx5uqxqvnqr' }
57
57
 
58
58
  before do
59
59
  Timecop.freeze now
@@ -61,7 +61,7 @@ RSpec.describe ROTP::TOTP do
61
61
 
62
62
  context 'correct time based OTP' do
63
63
  let(:token) { '102705' }
64
- let(:now) { Time.at 1297553958 }
64
+ let(:now) { Time.at 1_297_553_958 }
65
65
 
66
66
  it 'verifies' do
67
67
  expect(totp.verify('102705')).to be_truthy
@@ -75,17 +75,17 @@ RSpec.describe ROTP::TOTP do
75
75
  end
76
76
  end
77
77
  context 'invalidating reused tokens' do
78
- let(:verification) {
78
+ let(:verification) do
79
79
  totp.verify token,
80
- after: after,
81
- at: now
82
- }
80
+ after: after,
81
+ at: now
82
+ end
83
83
  let(:after) { nil }
84
84
 
85
85
  context 'passing in the `after` timestamp' do
86
- let(:after) {
86
+ let(:after) do
87
87
  totp.verify TEST_TOKEN, after: nil, at: now
88
- }
88
+ end
89
89
 
90
90
  it 'returns a timecode' do
91
91
  expect(after).to be_kind_of(Integer)
@@ -106,23 +106,23 @@ RSpec.describe ROTP::TOTP do
106
106
  totp.send('get_timecodes', at, b, a)
107
107
  end
108
108
 
109
- describe "drifting timecodes" do
109
+ describe 'drifting timecodes' do
110
110
  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])
111
+ expect(get_timecodes(TEST_TIME + 15, 15, 0)).to eq([49_154_040])
112
+ expect(get_timecodes(TEST_TIME, 15, 0)).to eq([49_154_039, 49_154_040])
113
+ expect(get_timecodes(TEST_TIME, 40, 0)).to eq([49_154_038, 49_154_039, 49_154_040])
114
+ expect(get_timecodes(TEST_TIME, 90, 0)).to eq([49_154_037, 49_154_038, 49_154_039, 49_154_040])
115
115
  end
116
116
  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])
117
+ expect(get_timecodes(TEST_TIME, 0, 15)).to eq([49_154_040])
118
+ expect(get_timecodes(TEST_TIME + 15, 0, 15)).to eq([49_154_040, 49_154_041])
119
+ expect(get_timecodes(TEST_TIME, 0, 30)).to eq([49_154_040, 49_154_041])
120
+ expect(get_timecodes(TEST_TIME, 0, 70)).to eq([49_154_040, 49_154_041, 49_154_042])
121
+ expect(get_timecodes(TEST_TIME, 0, 90)).to eq([49_154_040, 49_154_041, 49_154_042, 49_154_043])
122
122
  end
123
123
  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])
124
+ expect(get_timecodes(TEST_TIME, 30, 30)).to eq([49_154_039, 49_154_040, 49_154_041])
125
+ 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
126
  end
127
127
  end
128
128
 
@@ -131,7 +131,6 @@ RSpec.describe ROTP::TOTP do
131
131
  let(:drift_ahead) { 0 }
132
132
  let(:drift_behind) { 0 }
133
133
 
134
-
135
134
  context 'with an old OTP' do
136
135
  let(:token) { totp.at TEST_TIME - 30 } # Previous token at 2016-09-23 08:59:30 UTC
137
136
  let(:drift_behind) { 15 }
@@ -151,7 +150,6 @@ RSpec.describe ROTP::TOTP do
151
150
  expect(verification).to be_nil
152
151
  end
153
152
  end
154
-
155
153
  end
156
154
 
157
155
  context 'with a future OTP' do
@@ -166,14 +164,13 @@ RSpec.describe ROTP::TOTP do
166
164
  # Tested at 2016-09-23 09:00:20 UTC, and with drift ahead to 2016-09-23 09:00:35 UTC
167
165
  # This would therefore include 2 intervals
168
166
  context 'inside of drift range' do
169
- let(:now) { TEST_TIME + 20 }
167
+ let(:now) { TEST_TIME + 20 }
170
168
 
171
169
  it 'is true' do
172
170
  expect(verification).to be_truthy
173
171
  end
174
172
  end
175
173
  end
176
-
177
174
  end
178
175
 
179
176
  describe '#verify with drift and prevent token reuse' do
@@ -183,7 +180,6 @@ RSpec.describe ROTP::TOTP do
183
180
  let(:after) { nil }
184
181
 
185
182
  context 'with the `after` timestamp set' do
186
-
187
183
  context 'older token' do
188
184
  let(:token) { totp.at TEST_TIME - 30 }
189
185
  let(:drift_behind) { 15 }
@@ -194,14 +190,13 @@ RSpec.describe ROTP::TOTP do
194
190
  end
195
191
 
196
192
  context 'after it has been used' do
197
- let(:after) {
193
+ let(:after) do
198
194
  totp.verify token, after: nil, at: now, drift_behind: drift_behind
199
- }
195
+ end
200
196
  it 'is false' do
201
197
  expect(verification).to be_falsey
202
198
  end
203
199
  end
204
-
205
200
  end
206
201
 
207
202
  context 'newer token' do
@@ -215,92 +210,61 @@ RSpec.describe ROTP::TOTP do
215
210
  end
216
211
 
217
212
  context 'after it has been used' do
218
- let(:after) {
213
+ let(:after) do
219
214
  totp.verify token, after: nil, at: now, drift_ahead: drift_ahead
220
- }
215
+ end
221
216
  it 'is false' do
222
217
  expect(verification).to be_falsey
223
218
  end
224
219
  end
225
-
226
220
  end
227
221
  end
228
222
  end
229
223
 
230
- describe '#provisioning_uri' do
231
- let(:uri) { totp.provisioning_uri('mark@percival') }
232
- let(:params) { CGI::parse URI::parse(uri).query }
233
-
234
- context 'without issuer' do
235
- it 'has the correct format' do
236
- expect(uri).to match %r{\Aotpauth:\/\/totp.+}
237
- end
238
224
 
239
- it 'includes the secret as parameter' do
240
- expect(params['secret'].first).to eq 'JBSWY3DPEHPK3PXP'
241
- end
242
- end
225
+ describe '#provisioning_uri' do
226
+ let(:params) { CGI.parse URI.parse(uri).query }
243
227
 
244
- context 'with default digits' do
245
- it 'does does not include digits parameter' do
246
- expect(params['digits'].first).to be_nil
228
+ context "with a provided name on the TOTP instance" do
229
+ let(:totp) { ROTP::TOTP.new(TEST_SECRET, name: "m@mdp.im") }
230
+ it 'creates a provisioning uri from the OTP instance' do
231
+ expect(totp.provisioning_uri())
232
+ .to eq 'otpauth://totp/m%40mdp.im?secret=JBSWY3DPEHPK3PXP'
247
233
  end
248
- end
249
234
 
250
- context 'with non-default digits' do
251
- let(:totp) { ROTP::TOTP.new 'JBSWY3DPEHPK3PXP', digits: 8 }
252
-
253
- it 'does does not include digits parameter' do
254
- expect(params['digits'].first).to eq '8'
235
+ it 'allow passing a name to override the OTP name' do
236
+ expect(totp.provisioning_uri('mark@percival'))
237
+ .to eq 'otpauth://totp/mark%40percival?secret=JBSWY3DPEHPK3PXP'
255
238
  end
256
239
  end
257
240
 
258
- context 'with issuer' do
259
- let(:totp) { ROTP::TOTP.new 'JBSWY3DPEHPK3PXP', issuer: 'FooCo' }
260
-
261
- it 'has the correct format' do
262
- expect(uri).to match %r{\Aotpauth:\/\/totp/FooCo:.+}
263
- end
264
-
265
- it 'includes the secret as parameter' do
266
- expect(params['secret'].first).to eq 'JBSWY3DPEHPK3PXP'
267
- end
241
+ context 'with non-standard provisioning_params' do
242
+ let(:totp) {
243
+ ROTP::TOTP.new(TEST_SECRET,
244
+ provisioning_params: { image: 'https://example.com/icon.png' }
245
+ )
246
+ }
247
+ let(:uri) { totp.provisioning_uri("mark@percival") }
268
248
 
269
249
  it 'includes the issuer as parameter' do
270
- expect(params['issuer'].first).to eq 'FooCo'
250
+ expect(params['image'].first).to eq 'https://example.com/icon.png'
271
251
  end
272
- end
273
-
274
- context 'with custom interval' do
275
- let(:totp) { ROTP::TOTP.new 'JBSWY3DPEHPK3PXP', interval: 60 }
276
252
 
277
- it 'has the correct format' do
278
- expect(uri).to match %r{\Aotpauth:\/\/totp.+}
279
- end
280
-
281
- it 'includes the secret as parameter' do
282
- expect(params['secret'].first).to eq 'JBSWY3DPEHPK3PXP'
283
- end
284
-
285
- it 'includes the interval as period parameter' do
286
- expect(params['period'].first).to eq '60'
287
- end
288
253
  end
289
254
 
290
- context 'with custom digest' do
291
- let(:totp) { ROTP::TOTP.new 'JBSWY3DPEHPK3PXP', digest: 'sha256' }
255
+ context "with an issuer" do
256
+ let(:totp) { ROTP::TOTP.new(TEST_SECRET, name: "m@mdp.im", issuer: "Example.com") }
292
257
 
293
- it 'has the correct format' do
294
- expect(uri).to match %r{\Aotpauth:\/\/totp.+}
258
+ it 'creates a provisioning uri from the OTP instance' do
259
+ expect(totp.provisioning_uri())
260
+ .to eq 'otpauth://totp/Example.com:m%40mdp.im?secret=JBSWY3DPEHPK3PXP&issuer=Example.com'
295
261
  end
296
262
 
297
- it 'includes the secret as parameter' do
298
- expect(params['secret'].first).to eq 'JBSWY3DPEHPK3PXP'
263
+ it 'allow passing a name to override the OTP name' do
264
+ expect(totp.provisioning_uri('mark@percival'))
265
+ .to eq 'otpauth://totp/Example.com:mark%40percival?secret=JBSWY3DPEHPK3PXP&issuer=Example.com'
299
266
  end
300
267
 
301
- it 'includes the digest as algorithm parameter' do
302
- expect(params['algorithm'].first).to eq 'SHA256'
303
- end
304
268
  end
305
269
 
306
270
  end
@@ -312,7 +276,7 @@ RSpec.describe ROTP::TOTP do
312
276
 
313
277
  context 'Google Authenticator' do
314
278
  let(:totp) { ROTP::TOTP.new 'wrn3pqx5uqxqvnqr' }
315
- let(:now) { Time.at 1297553958 }
279
+ let(:now) { Time.at 1_297_553_958 }
316
280
 
317
281
  it 'matches the known output' do
318
282
  expect(totp.now).to eq '102705'
@@ -321,12 +285,11 @@ RSpec.describe ROTP::TOTP do
321
285
 
322
286
  context 'Dropbox 26 char secret output' do
323
287
  let(:totp) { ROTP::TOTP.new 'tjtpqea6a42l56g5eym73go2oa' }
324
- let(:now) { Time.at 1378762454 }
288
+ let(:now) { Time.at 1_378_762_454 }
325
289
 
326
290
  it 'matches the known output' do
327
291
  expect(totp.now).to eq '747864'
328
292
  end
329
293
  end
330
294
  end
331
-
332
295
  end
data/spec/spec_helper.rb CHANGED
@@ -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'