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.
- checksums.yaml +4 -4
- data/.devcontainer/Dockerfile +19 -0
- data/.devcontainer/devcontainer.json +41 -0
- data/.github/workflows/release.yaml +36 -0
- data/.github/workflows/test.yaml +26 -0
- data/.release-please-manifest.json +3 -0
- data/CHANGELOG.md +91 -24
- data/Dockerfile-2.3 +1 -7
- data/Dockerfile-2.7 +11 -0
- data/Dockerfile-3.0 +12 -0
- data/Guardfile +1 -1
- data/README.md +64 -15
- data/bin/rotp +1 -1
- data/docker-compose.yml +37 -0
- data/lib/rotp/arguments.rb +6 -5
- data/lib/rotp/base32.rb +56 -30
- data/lib/rotp/cli.rb +4 -5
- data/lib/rotp/hotp.rb +6 -13
- data/lib/rotp/otp/uri.rb +78 -0
- data/lib/rotp/otp.rb +26 -25
- data/lib/rotp/totp.rb +13 -33
- data/lib/rotp/version.rb +1 -1
- data/lib/rotp.rb +2 -4
- data/release-please-config.json +12 -0
- data/rotp.gemspec +13 -15
- data/spec/lib/rotp/arguments_spec.rb +5 -6
- data/spec/lib/rotp/base32_spec.rb +45 -19
- data/spec/lib/rotp/cli_spec.rb +21 -6
- data/spec/lib/rotp/hotp_spec.rb +38 -17
- data/spec/lib/rotp/otp/uri_spec.rb +99 -0
- data/spec/lib/rotp/totp_spec.rb +61 -98
- data/spec/spec_helper.rb +1 -2
- metadata +25 -43
- data/.travis.yml +0 -8
- data/Dockerfile-1.9 +0 -15
- data/Dockerfile-2.1 +0 -16
- data/Rakefile +0 -9
- data/doc/ROTP/HOTP.html +0 -308
- data/doc/ROTP/OTP.html +0 -593
- data/doc/ROTP/TOTP.html +0 -493
- data/doc/Rotp.html +0 -179
- data/doc/_index.html +0 -144
- data/doc/class_list.html +0 -36
- data/doc/css/common.css +0 -1
- data/doc/css/full_list.css +0 -53
- data/doc/css/style.css +0 -310
- data/doc/file.README.html +0 -89
- data/doc/file_list.html +0 -38
- data/doc/frames.html +0 -13
- data/doc/index.html +0 -89
- data/doc/js/app.js +0 -203
- data/doc/js/full_list.js +0 -149
- data/doc/js/jquery.js +0 -154
- data/doc/method_list.html +0 -155
- data/doc/top-level-namespace.html +0 -88
data/spec/lib/rotp/cli_spec.rb
CHANGED
@@ -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
|
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) { %
|
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
|
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
|
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
|
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
|
data/spec/lib/rotp/hotp_spec.rb
CHANGED
@@ -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) {
|
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(:
|
107
|
-
let(:params) { CGI
|
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 '
|
110
|
-
expect(
|
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 '
|
114
|
-
expect(
|
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
|
118
|
-
|
119
|
-
|
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
|
124
|
-
let(:hotp) { ROTP::HOTP.new('a' * 32,
|
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 '
|
127
|
-
expect(
|
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
|
data/spec/lib/rotp/totp_spec.rb
CHANGED
@@ -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 =
|
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
|
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
|
23
|
-
expect(totp.at
|
24
|
-
expect(totp.at
|
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) {
|
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)
|
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
|
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
|
-
|
81
|
-
|
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
|
109
|
+
describe 'drifting timecodes' do
|
110
110
|
it 'should get timecodes behind' do
|
111
|
-
expect(get_timecodes(TEST_TIME+15, 15, 0)).to eq([
|
112
|
-
expect(get_timecodes(TEST_TIME, 15, 0)).to eq([
|
113
|
-
expect(get_timecodes(TEST_TIME, 40, 0)).to eq([
|
114
|
-
expect(get_timecodes(TEST_TIME, 90, 0)).to eq([
|
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([
|
118
|
-
expect(get_timecodes(TEST_TIME+15, 0, 15)).to eq([
|
119
|
-
expect(get_timecodes(TEST_TIME, 0, 30)).to eq([
|
120
|
-
expect(get_timecodes(TEST_TIME, 0, 70)).to eq([
|
121
|
-
expect(get_timecodes(TEST_TIME, 0, 90)).to eq([
|
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([
|
125
|
-
expect(get_timecodes(TEST_TIME, 60, 60)).to eq([
|
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)
|
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
|
-
|
240
|
-
|
241
|
-
end
|
242
|
-
end
|
225
|
+
describe '#provisioning_uri' do
|
226
|
+
let(:params) { CGI.parse URI.parse(uri).query }
|
243
227
|
|
244
|
-
context
|
245
|
-
|
246
|
-
|
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
|
-
|
251
|
-
|
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
|
259
|
-
let(:totp)
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
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['
|
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
|
291
|
-
let(:totp)
|
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 '
|
294
|
-
expect(
|
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 '
|
298
|
-
expect(
|
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
|
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
|
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
|