rotp 5.1.0 → 6.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -1
- data/CHANGELOG.md +19 -0
- data/Dockerfile-2.3 +10 -0
- data/{Dockerfile-2.0 → Dockerfile-2.7} +1 -1
- data/Dockerfile-3.0-rc +12 -0
- data/README.md +17 -7
- data/docker-compose.yml +37 -0
- data/lib/rotp.rb +2 -3
- data/lib/rotp/base32.rb +3 -1
- data/lib/rotp/hotp.rb +1 -6
- data/lib/rotp/otp.rb +0 -10
- data/lib/rotp/otp/uri.rb +79 -0
- data/lib/rotp/totp.rb +1 -13
- data/lib/rotp/version.rb +1 -1
- data/rotp.gemspec +3 -6
- data/spec/lib/rotp/hotp_spec.rb +6 -21
- data/spec/lib/rotp/otp/uri_spec.rb +99 -0
- data/spec/lib/rotp/totp_spec.rb +3 -73
- metadata +13 -24
- data/Rakefile +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd976bfa6985075f5e2b76607256d0afbbdf88a82c38cd094d0eaffbb5bce4f2
|
4
|
+
data.tar.gz: 70df660f1eca3dd9efc7baa1f53061ba9af1bbb49e4bb6ead507509f6e845d38
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7fb326cc887a1a5614c90c492ac43b72188f75caa90fcc50c3338d129abe2efe4f67af88d018c378379806f1bef0c1d0e40fc6c683f4427f40ad411326729022
|
7
|
+
data.tar.gz: 4f913bf0693c1cead926bfe625e226fe8277323f93a552459447a792cd27b9189860ab26d3907baafff0e217430b04fb9e8b1829b0795e4c96e83062fac409fb
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,24 @@
|
|
1
1
|
### Changelog
|
2
2
|
|
3
|
+
### 6.2.0
|
4
|
+
|
5
|
+
- Update to expand compatibility with Ruby 3. This was only a change to the
|
6
|
+
gemspec, no code changes were necessary.
|
7
|
+
|
8
|
+
### 6.1.0
|
9
|
+
|
10
|
+
- Fixing URI encoding issues again, breaking out into it's own module
|
11
|
+
due to the complexity - closes #100 (@atcruice)
|
12
|
+
- Add docker-compose.yml to help with easier testing
|
13
|
+
|
14
|
+
### 6.0.0
|
15
|
+
|
16
|
+
- Dropping support for Ruby <2.3 (Major version bump)
|
17
|
+
- Fix issue when using --enable-frozen-string-literal Ruby option #95 (jeremyevans)
|
18
|
+
- URI Encoding fix #94 (ksuh90)
|
19
|
+
- Update gems (rake, addressable)
|
20
|
+
- Update Travis tests to include Ruby 2.7
|
21
|
+
|
3
22
|
### 5.1.0
|
4
23
|
|
5
24
|
- Create `random_base32` to perform `random` to avoid breaking changes
|
data/Dockerfile-2.3
ADDED
data/Dockerfile-3.0-rc
ADDED
data/README.md
CHANGED
@@ -18,12 +18,16 @@ Many websites use this for [multi-factor authentication](https://www.youtube.com
|
|
18
18
|
|
19
19
|
## Breaking changes
|
20
20
|
|
21
|
+
### Breaking changes in >= 6.0
|
22
|
+
|
23
|
+
- Dropping support for Ruby <2.3
|
24
|
+
|
21
25
|
### Breaking changes in >= 5.0
|
22
26
|
|
23
27
|
- `ROTP::Base32.random_base32` is now `ROTP::Base32.random` and the argument
|
24
28
|
has changed from secret string length to byte length to allow for more
|
25
|
-
precision
|
26
|
-
- Cleaned up the Base32 implementation to
|
29
|
+
precision. There is an alias to allow for `random_base32` for the time being.
|
30
|
+
- Cleaned up the Base32 implementation to match Google Authenticator's version.
|
27
31
|
|
28
32
|
### Breaking changes in >= 4.0
|
29
33
|
|
@@ -66,8 +70,8 @@ hotp.at(1) # => "595254"
|
|
66
70
|
hotp.at(1401) # => "259769"
|
67
71
|
|
68
72
|
# OTP verified with a counter
|
69
|
-
hotp.verify("
|
70
|
-
hotp.verify("
|
73
|
+
hotp.verify("259769", 1401) # => 1401
|
74
|
+
hotp.verify("259769", 1402) # => nil
|
71
75
|
```
|
72
76
|
|
73
77
|
### Preventing reuse of Time based OTP's
|
@@ -78,7 +82,7 @@ the interval window (default 30 seconds)
|
|
78
82
|
The following is an example of this in action:
|
79
83
|
|
80
84
|
```ruby
|
81
|
-
User.find(someUserID)
|
85
|
+
user = User.find(someUserID)
|
82
86
|
totp = ROTP::TOTP.new(user.otp_secret)
|
83
87
|
totp.now # => "492039"
|
84
88
|
|
@@ -129,10 +133,10 @@ Google Authenticator.
|
|
129
133
|
|
130
134
|
```ruby
|
131
135
|
totp = ROTP::TOTP.new("base32secret3232", issuer: "My Service")
|
132
|
-
totp.provisioning_uri("alice@google.com") # => 'otpauth://totp/My%20Service:alice
|
136
|
+
totp.provisioning_uri("alice@google.com") # => 'otpauth://totp/My%20Service:alice%40google.com?secret=base32secret3232&issuer=My%20Service'
|
133
137
|
|
134
138
|
hotp = ROTP::HOTP.new("base32secret3232", issuer: "My Service")
|
135
|
-
hotp.provisioning_uri("alice@google.com", 0) # => 'otpauth://hotp/alice
|
139
|
+
hotp.provisioning_uri("alice@google.com", 0) # => 'otpauth://hotp/alice%40google.com?secret=base32secret3232&counter=0'
|
136
140
|
```
|
137
141
|
|
138
142
|
This can then be rendered as a QR Code which the user can scan using their mobile phone and the appropriate application.
|
@@ -169,6 +173,12 @@ docker build -f Dockerfile-2.6 -t rotp_2.6 .
|
|
169
173
|
docker run --rm -v $(pwd):/usr/src/app rotp_2.6
|
170
174
|
```
|
171
175
|
|
176
|
+
Alternately, you may use docker-compose to run all the tests:
|
177
|
+
|
178
|
+
```
|
179
|
+
docker-compose up
|
180
|
+
```
|
181
|
+
|
172
182
|
## Executable Usage
|
173
183
|
|
174
184
|
The rotp rubygem includes CLI version to help with testing and debugging
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
version: "3.8"
|
2
|
+
services:
|
3
|
+
ruby_2_3:
|
4
|
+
build:
|
5
|
+
context: .
|
6
|
+
dockerfile: Dockerfile-2.3
|
7
|
+
volumes:
|
8
|
+
- "./lib:/usr/src/app/lib"
|
9
|
+
- "./spec:/usr/src/app/spec"
|
10
|
+
ruby_2_5:
|
11
|
+
build:
|
12
|
+
context: .
|
13
|
+
dockerfile: Dockerfile-2.5
|
14
|
+
volumes:
|
15
|
+
- "./lib:/usr/src/app/lib"
|
16
|
+
- "./spec:/usr/src/app/spec"
|
17
|
+
ruby_2_6:
|
18
|
+
build:
|
19
|
+
context: .
|
20
|
+
dockerfile: Dockerfile-2.6
|
21
|
+
volumes:
|
22
|
+
- "./lib:/usr/src/app/lib"
|
23
|
+
- "./spec:/usr/src/app/spec"
|
24
|
+
ruby_2_7:
|
25
|
+
build:
|
26
|
+
context: .
|
27
|
+
dockerfile: Dockerfile-2.7
|
28
|
+
volumes:
|
29
|
+
- "./lib:/usr/src/app/lib"
|
30
|
+
- "./spec:/usr/src/app/spec"
|
31
|
+
ruby_3_0_rc:
|
32
|
+
build:
|
33
|
+
context: .
|
34
|
+
dockerfile: Dockerfile-3.0-rc
|
35
|
+
volumes:
|
36
|
+
- "./lib:/usr/src/app/lib"
|
37
|
+
- "./spec:/usr/src/app/spec"
|
data/lib/rotp.rb
CHANGED
data/lib/rotp/base32.rb
CHANGED
data/lib/rotp/hotp.rb
CHANGED
@@ -25,12 +25,7 @@ module ROTP
|
|
25
25
|
# @param [Integer] initial_count starting counter value, defaults to 0
|
26
26
|
# @return [String] provisioning uri
|
27
27
|
def provisioning_uri(name, initial_count = 0)
|
28
|
-
|
29
|
-
secret: secret,
|
30
|
-
counter: initial_count,
|
31
|
-
digits: digits == DEFAULT_DIGITS ? nil : digits
|
32
|
-
}
|
33
|
-
encode_params("otpauth://hotp/#{Addressable::URI.escape(name)}", params)
|
28
|
+
OTP::URI.new(self, account_name: name, counter: initial_count).to_s
|
34
29
|
end
|
35
30
|
end
|
36
31
|
end
|
data/lib/rotp/otp.rb
CHANGED
@@ -66,16 +66,6 @@ module ROTP
|
|
66
66
|
result.reverse.join.rjust(padding, 0.chr)
|
67
67
|
end
|
68
68
|
|
69
|
-
# A very simple param encoder
|
70
|
-
def encode_params(uri, params)
|
71
|
-
params_str = String.new('?')
|
72
|
-
params.each do |k, v|
|
73
|
-
params_str << "#{k}=#{CGI.escape(v.to_s)}&" if v
|
74
|
-
end
|
75
|
-
params_str.chop!
|
76
|
-
uri + params_str
|
77
|
-
end
|
78
|
-
|
79
69
|
# constant-time compare the strings
|
80
70
|
def time_constant_compare(a, b)
|
81
71
|
return false if a.empty? || b.empty? || a.bytesize != b.bytesize
|
data/lib/rotp/otp/uri.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
module ROTP
|
2
|
+
class OTP
|
3
|
+
# https://github.com/google/google-authenticator/wiki/Key-Uri-Format
|
4
|
+
class URI
|
5
|
+
def initialize(otp, account_name:, counter: nil)
|
6
|
+
@otp = otp
|
7
|
+
@account_name = account_name
|
8
|
+
@counter = counter
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"otpauth://#{type}/#{label}?#{parameters}"
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def algorithm
|
18
|
+
return unless %w[sha256 sha512].include?(@otp.digest)
|
19
|
+
|
20
|
+
@otp.digest.upcase
|
21
|
+
end
|
22
|
+
|
23
|
+
def counter
|
24
|
+
return if @otp.is_a?(TOTP)
|
25
|
+
fail if @counter.nil?
|
26
|
+
|
27
|
+
@counter
|
28
|
+
end
|
29
|
+
|
30
|
+
def digits
|
31
|
+
return if @otp.digits == DEFAULT_DIGITS
|
32
|
+
|
33
|
+
@otp.digits
|
34
|
+
end
|
35
|
+
|
36
|
+
def issuer
|
37
|
+
return if @otp.is_a?(HOTP)
|
38
|
+
|
39
|
+
@otp.issuer&.strip&.tr(':', '_')
|
40
|
+
end
|
41
|
+
|
42
|
+
def label
|
43
|
+
[issuer, @account_name.rstrip]
|
44
|
+
.compact
|
45
|
+
.map { |s| s.tr(':', '_') }
|
46
|
+
.map { |s| ERB::Util.url_encode(s) }
|
47
|
+
.join(':')
|
48
|
+
end
|
49
|
+
|
50
|
+
def parameters
|
51
|
+
{
|
52
|
+
secret: @otp.secret,
|
53
|
+
issuer: issuer,
|
54
|
+
algorithm: algorithm,
|
55
|
+
digits: digits,
|
56
|
+
period: period,
|
57
|
+
counter: counter,
|
58
|
+
}
|
59
|
+
.reject { |_, v| v.nil? }
|
60
|
+
.map { |k, v| "#{k}=#{ERB::Util.url_encode(v)}" }
|
61
|
+
.join('&')
|
62
|
+
end
|
63
|
+
|
64
|
+
def period
|
65
|
+
return if @otp.is_a?(HOTP)
|
66
|
+
return if @otp.interval == DEFAULT_INTERVAL
|
67
|
+
|
68
|
+
@otp.interval
|
69
|
+
end
|
70
|
+
|
71
|
+
def type
|
72
|
+
case @otp
|
73
|
+
when TOTP then 'totp'
|
74
|
+
when HOTP then 'hotp'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/rotp/totp.rb
CHANGED
@@ -54,19 +54,7 @@ module ROTP
|
|
54
54
|
# @param [String] name of the account
|
55
55
|
# @return [String] provisioning URI
|
56
56
|
def provisioning_uri(name)
|
57
|
-
|
58
|
-
# https://github.com/google/google-authenticator/wiki/Key-Uri-Format
|
59
|
-
# For compatibility the issuer appears both before that account name and also in the
|
60
|
-
# query string.
|
61
|
-
issuer_string = issuer.nil? ? '' : "#{Addressable::URI.escape(issuer)}:"
|
62
|
-
params = {
|
63
|
-
secret: secret,
|
64
|
-
period: interval == 30 ? nil : interval,
|
65
|
-
issuer: issuer,
|
66
|
-
digits: digits == DEFAULT_DIGITS ? nil : digits,
|
67
|
-
algorithm: digest.casecmp('SHA1').zero? ? nil : digest.upcase
|
68
|
-
}
|
69
|
-
encode_params("otpauth://totp/#{issuer_string}#{Addressable::URI.escape(name)}", params)
|
57
|
+
OTP::URI.new(self, account_name: name).to_s
|
70
58
|
end
|
71
59
|
|
72
60
|
private
|
data/lib/rotp/version.rb
CHANGED
data/rotp.gemspec
CHANGED
@@ -4,23 +4,20 @@ Gem::Specification.new do |s|
|
|
4
4
|
s.name = 'rotp'
|
5
5
|
s.version = ROTP::VERSION
|
6
6
|
s.platform = Gem::Platform::RUBY
|
7
|
+
s.required_ruby_version = '>= 2.3'
|
7
8
|
s.license = 'MIT'
|
8
9
|
s.authors = ['Mark Percival']
|
9
10
|
s.email = ['mark@markpercival.us']
|
10
|
-
s.homepage = '
|
11
|
+
s.homepage = 'https://github.com/mdp/rotp'
|
11
12
|
s.summary = 'A Ruby library for generating and verifying one time passwords'
|
12
13
|
s.description = 'Works for both HOTP and TOTP, and includes QR Code provisioning'
|
13
14
|
|
14
|
-
s.rubyforge_project = 'rotp'
|
15
|
-
|
16
15
|
s.files = `git ls-files`.split("\n")
|
17
16
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
17
|
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
19
18
|
s.require_paths = ['lib']
|
20
19
|
|
21
|
-
s.
|
22
|
-
|
23
|
-
s.add_development_dependency 'rake', '~> 10.5'
|
20
|
+
s.add_development_dependency "rake", "~> 13.0"
|
24
21
|
s.add_development_dependency 'rspec', '~> 3.5'
|
25
22
|
s.add_development_dependency 'simplecov', '~> 0.12'
|
26
23
|
s.add_development_dependency 'timecop', '~> 0.8'
|
data/spec/lib/rotp/hotp_spec.rb
CHANGED
@@ -108,29 +108,14 @@ RSpec.describe ROTP::HOTP do
|
|
108
108
|
end
|
109
109
|
|
110
110
|
describe '#provisioning_uri' do
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
it 'has the correct format' do
|
115
|
-
expect(uri).to match %r{\Aotpauth:\/\/hotp.+}
|
116
|
-
end
|
117
|
-
|
118
|
-
it 'includes the secret as parameter' do
|
119
|
-
expect(params['secret'].first).to eq 'a' * 32
|
120
|
-
end
|
121
|
-
|
122
|
-
context 'with default digits' do
|
123
|
-
it 'does not include digits parameter with default digits' do
|
124
|
-
expect(params['digits'].first).to be_nil
|
125
|
-
end
|
111
|
+
it 'accepts the account name' do
|
112
|
+
expect(hotp.provisioning_uri('mark@percival'))
|
113
|
+
.to eq 'otpauth://hotp/mark%40percival?secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&counter=0'
|
126
114
|
end
|
127
115
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
it 'includes digits parameter' do
|
132
|
-
expect(params['digits'].first).to eq '8'
|
133
|
-
end
|
116
|
+
it 'also accepts a custom counter value' do
|
117
|
+
expect(hotp.provisioning_uri('mark@percival', 17))
|
118
|
+
.to eq 'otpauth://hotp/mark%40percival?secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&counter=17'
|
134
119
|
end
|
135
120
|
end
|
136
121
|
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
@@ -221,79 +221,9 @@ RSpec.describe ROTP::TOTP do
|
|
221
221
|
end
|
222
222
|
|
223
223
|
describe '#provisioning_uri' do
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
context 'without issuer' do
|
228
|
-
it 'has the correct format' do
|
229
|
-
expect(uri).to match %r{\Aotpauth:\/\/totp.+}
|
230
|
-
end
|
231
|
-
|
232
|
-
it 'includes the secret as parameter' do
|
233
|
-
expect(params['secret'].first).to eq 'JBSWY3DPEHPK3PXP'
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
237
|
-
context 'with default digits' do
|
238
|
-
it 'does does not include digits parameter' do
|
239
|
-
expect(params['digits'].first).to be_nil
|
240
|
-
end
|
241
|
-
end
|
242
|
-
|
243
|
-
context 'with non-default digits' do
|
244
|
-
let(:totp) { ROTP::TOTP.new 'JBSWY3DPEHPK3PXP', digits: 8 }
|
245
|
-
|
246
|
-
it 'does does not include digits parameter' do
|
247
|
-
expect(params['digits'].first).to eq '8'
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
context 'with issuer' do
|
252
|
-
let(:totp) { ROTP::TOTP.new 'JBSWY3DPEHPK3PXP', issuer: 'FooCo' }
|
253
|
-
|
254
|
-
it 'has the correct format' do
|
255
|
-
expect(uri).to match %r{\Aotpauth:\/\/totp/FooCo:.+}
|
256
|
-
end
|
257
|
-
|
258
|
-
it 'includes the secret as parameter' do
|
259
|
-
expect(params['secret'].first).to eq 'JBSWY3DPEHPK3PXP'
|
260
|
-
end
|
261
|
-
|
262
|
-
it 'includes the issuer as parameter' do
|
263
|
-
expect(params['issuer'].first).to eq 'FooCo'
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
context 'with custom interval' do
|
268
|
-
let(:totp) { ROTP::TOTP.new 'JBSWY3DPEHPK3PXP', interval: 60 }
|
269
|
-
|
270
|
-
it 'has the correct format' do
|
271
|
-
expect(uri).to match %r{\Aotpauth:\/\/totp.+}
|
272
|
-
end
|
273
|
-
|
274
|
-
it 'includes the secret as parameter' do
|
275
|
-
expect(params['secret'].first).to eq 'JBSWY3DPEHPK3PXP'
|
276
|
-
end
|
277
|
-
|
278
|
-
it 'includes the interval as period parameter' do
|
279
|
-
expect(params['period'].first).to eq '60'
|
280
|
-
end
|
281
|
-
end
|
282
|
-
|
283
|
-
context 'with custom digest' do
|
284
|
-
let(:totp) { ROTP::TOTP.new 'JBSWY3DPEHPK3PXP', digest: 'sha256' }
|
285
|
-
|
286
|
-
it 'has the correct format' do
|
287
|
-
expect(uri).to match %r{\Aotpauth:\/\/totp.+}
|
288
|
-
end
|
289
|
-
|
290
|
-
it 'includes the secret as parameter' do
|
291
|
-
expect(params['secret'].first).to eq 'JBSWY3DPEHPK3PXP'
|
292
|
-
end
|
293
|
-
|
294
|
-
it 'includes the digest as algorithm parameter' do
|
295
|
-
expect(params['algorithm'].first).to eq 'SHA256'
|
296
|
-
end
|
224
|
+
it 'accepts the account name' do
|
225
|
+
expect(totp.provisioning_uri('mark@percival'))
|
226
|
+
.to eq 'otpauth://totp/mark%40percival?secret=JBSWY3DPEHPK3PXP'
|
297
227
|
end
|
298
228
|
end
|
299
229
|
|
metadata
CHANGED
@@ -1,43 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rotp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 6.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mark Percival
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-09-30 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'
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: rake
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
30
16
|
requirements:
|
31
17
|
- - "~>"
|
32
18
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
19
|
+
version: '13.0'
|
34
20
|
type: :development
|
35
21
|
prerelease: false
|
36
22
|
version_requirements: !ruby/object:Gem::Requirement
|
37
23
|
requirements:
|
38
24
|
- - "~>"
|
39
25
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
26
|
+
version: '13.0'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: rspec
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -92,14 +78,15 @@ files:
|
|
92
78
|
- ".gitignore"
|
93
79
|
- ".travis.yml"
|
94
80
|
- CHANGELOG.md
|
95
|
-
- Dockerfile-2.
|
81
|
+
- Dockerfile-2.3
|
96
82
|
- Dockerfile-2.5
|
97
83
|
- Dockerfile-2.6
|
84
|
+
- Dockerfile-2.7
|
85
|
+
- Dockerfile-3.0-rc
|
98
86
|
- Gemfile
|
99
87
|
- Guardfile
|
100
88
|
- LICENSE
|
101
89
|
- README.md
|
102
|
-
- Rakefile
|
103
90
|
- bin/rotp
|
104
91
|
- doc/ROTP/HOTP.html
|
105
92
|
- doc/ROTP/OTP.html
|
@@ -119,12 +106,14 @@ files:
|
|
119
106
|
- doc/js/jquery.js
|
120
107
|
- doc/method_list.html
|
121
108
|
- doc/top-level-namespace.html
|
109
|
+
- docker-compose.yml
|
122
110
|
- lib/rotp.rb
|
123
111
|
- lib/rotp/arguments.rb
|
124
112
|
- lib/rotp/base32.rb
|
125
113
|
- lib/rotp/cli.rb
|
126
114
|
- lib/rotp/hotp.rb
|
127
115
|
- lib/rotp/otp.rb
|
116
|
+
- lib/rotp/otp/uri.rb
|
128
117
|
- lib/rotp/totp.rb
|
129
118
|
- lib/rotp/version.rb
|
130
119
|
- rotp.gemspec
|
@@ -132,9 +121,10 @@ files:
|
|
132
121
|
- spec/lib/rotp/base32_spec.rb
|
133
122
|
- spec/lib/rotp/cli_spec.rb
|
134
123
|
- spec/lib/rotp/hotp_spec.rb
|
124
|
+
- spec/lib/rotp/otp/uri_spec.rb
|
135
125
|
- spec/lib/rotp/totp_spec.rb
|
136
126
|
- spec/spec_helper.rb
|
137
|
-
homepage:
|
127
|
+
homepage: https://github.com/mdp/rotp
|
138
128
|
licenses:
|
139
129
|
- MIT
|
140
130
|
metadata: {}
|
@@ -146,15 +136,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
146
136
|
requirements:
|
147
137
|
- - ">="
|
148
138
|
- !ruby/object:Gem::Version
|
149
|
-
version: '
|
139
|
+
version: '2.3'
|
150
140
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
141
|
requirements:
|
152
142
|
- - ">="
|
153
143
|
- !ruby/object:Gem::Version
|
154
144
|
version: '0'
|
155
145
|
requirements: []
|
156
|
-
|
157
|
-
rubygems_version: 2.7.6
|
146
|
+
rubygems_version: 3.1.2
|
158
147
|
signing_key:
|
159
148
|
specification_version: 4
|
160
149
|
summary: A Ruby library for generating and verifying one time passwords
|