rotp 4.0.0 → 6.3.0

Sign up to get free protection for your applications and to get access to all the features.
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'