Skip to content

Commit 45d8aac

Browse files
authored
feat: Allow for non-standard provisioning URI params, eg. image/icon (#91)
- Added the ability to add custom provisioning params to the provisionsing URI. - Matched up TOTP provisioning URI's with HOTP provisioning URI's - https://github.com/google/google-authenticator/wiki/Key-Uri-Format - Small chore fixes to CHANGELOG formatting
1 parent 3908511 commit 45d8aac

File tree

8 files changed

+134
-49
lines changed

8 files changed

+134
-49
lines changed

CHANGELOG.md

+34-34
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,61 @@
1-
### Changelog
1+
# Changelog
22

3-
### 6.2.2
3+
## 6.2.2
44

55
- Removed `rjust` from `generate_otp` in favor of more time constant version
66

7-
### 6.2.1
7+
## 6.2.1
88

99
- Removed old rdoc folder that was triggering a security warning due to an
1010
old version of JQuery being included in the HTML docs. This has no impact
1111
on the Ruby library.
1212

13-
### 6.2.0
13+
## 6.2.0
1414

1515
- Update to expand compatibility with Ruby 3. This was only a change to the
1616
gemspec, no code changes were necessary.
1717

18-
### 6.1.0
18+
## 6.1.0
1919

2020
- Fixing URI encoding issues again, breaking out into it's own module
2121
due to the complexity - closes #100 (@atcruice)
2222
- Add docker-compose.yml to help with easier testing
2323

24-
### 6.0.0
24+
## 6.0.0
2525

2626
- Dropping support for Ruby <2.3 (Major version bump)
2727
- Fix issue when using --enable-frozen-string-literal Ruby option #95 (jeremyevans)
2828
- URI Encoding fix #94 (ksuh90)
2929
- Update gems (rake, addressable)
3030
- Update Travis tests to include Ruby 2.7
3131

32-
### 5.1.0
32+
## 5.1.0
3333

3434
- Create `random_base32` to perform `random` to avoid breaking changes
3535
- Still needed to bump to 5.x due to Base32 cleanup
3636

37-
### 5.0.0
37+
## 5.0.0
3838

3939
- Clean up base32 implementation to match Google Autheticator
4040
- BREAKING `Base32.random_base32` renamed to random
4141
- The argument is now byte length vs output string length for more precise bit strengths
4242

43-
### 4.1.0
43+
## 4.1.0
4444

4545
- Add a digest option to the CLI #83
4646
- Fix provisioning URI is README #82
4747
- Improvements to docs
4848

49-
### 4.0.2
49+
## 4.0.2
5050

5151
- Fix gemspec requirment for Addressable
5252

53-
### 4.0.1
53+
## 4.0.1
5454

5555
- Rubocop for style fixes
5656
- Replace deprecated URI.encode with Addressable's version
5757

58-
#### 4.0.0
58+
## 4.0.0
5959

6060
- Simplify API
6161
- Remove support for Ruby < 2.0
@@ -65,96 +65,96 @@
6565
- `TOTP#at`
6666
- `TOTP#now` (first argument)
6767

68-
#### 3.3.1
68+
## 3.3.1
6969

7070
- Add OpenSSL as a requirement for Ruby 2.5. Fixes #70 & #64
7171
- Allow Base32 with padding. #71
7272
- Prevent verify with drift being negative #69
7373

74-
#### 3.3.0
74+
## 3.3.0
7575

7676
- Add digest algorithm parameter for non SHA1 digests - #62 from @btalbot
7777

78-
#### 3.2.0
78+
## 3.2.0
7979

8080
- Add 'verify_with_drift_and_prior' to prevent prior token use - #58 from @jlfaber
8181

82-
#### 3.1.0
82+
## 3.1.0
8383

8484
- Add Add digits paramater to provisioning URI. #54 from @sbc100
8585

86-
#### 3.0.1
86+
## 3.0.1
8787

8888
- Use SecureRandom. See mdp/rotp/pull/52
8989

90-
#### 3.0.0
90+
## 3.0.0
9191

9292
- Provisioning URL includes issuer label per RFC 5234 See mdp/rotp/pull/51
9393

94-
#### 2.1.2
94+
## 2.1.2
9595

9696
- Remove string literals to prepare immutable strings in Ruby 3.0
9797

98-
#### 2.1.1
98+
## 2.1.1
9999

100100
- Reorder the params for Windows Phone Authenticator - #43
101101

102-
#### 2.1.0
102+
## 2.1.0
103103

104104
- Add a CLI for generating OTP's mdp/rotp/pull/35
105105

106-
#### 2.0.0
106+
## 2.0.0
107107

108108
- Move to only comparing string OTP's.
109109

110-
#### 1.7.0
110+
## 1.7.0
111111

112112
- Move to only comparing string OTP's. See mdp/rotp/issues/32 - Moved to 2.0.0 - yanked from RubyGems
113113

114-
#### 1.6.1
114+
## 1.6.1
115115

116116
- Remove deprecation warning in Ruby 2.1.0 (@ylansegal)
117117
- Add Ruby 2.0 and 2.1 to Travis
118118

119-
#### 1.6.0
119+
## 1.6.0
120120

121121
- Add verify_with_retries to HOTP
122122
- Fix 'cgi' require and global DEFAULT_INTERVAL
123123

124-
#### 1.5.0
124+
## 1.5.0
125125

126126
- Add support for "issuer" parameter on provisioning url
127127
- Add support for "period/interval" parameter on provisioning url
128128

129-
#### 1.4.6
129+
## 1.4.6
130130

131131
- Revert to previous Base32
132132

133-
#### 1.4.5
133+
## 1.4.5
134134

135135
- Fix and test correct implementation of Base32
136136

137-
#### 1.4.4
137+
## 1.4.4
138138

139139
- Fix issue with base32 decoding of strings in a length that's not a multiple of 8
140140

141-
#### 1.4.3
141+
## 1.4.3
142142

143143
- Bugfix on padding
144144

145-
#### 1.4.2
145+
## 1.4.2
146146

147147
- Better padding options (Pad the output with leading 0's)
148148

149-
#### 1.4.1
149+
## 1.4.1
150150

151151
- Clean up drift logic
152152

153-
#### 1.4.0
153+
## 1.4.0
154154

155155
- Added clock drift support via 'verify_with_drift' for TOTP
156156

157-
####1.3.0
157+
## 1.3.0
158158

159159
- Added support for Ruby 1.9.x
160160
- Removed dependency on Base32

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ totp = ROTP::TOTP.new("base32secret3232", issuer: "My Service")
149149
totp.provisioning_uri("[email protected]") # => 'otpauth://totp/My%20Service:alice%40google.com?secret=base32secret3232&issuer=My%20Service'
150150

151151
hotp = ROTP::HOTP.new("base32secret3232", issuer: "My Service")
152-
hotp.provisioning_uri("[email protected]", 0) # => 'otpauth://hotp/alice%40google.com?secret=base32secret3232&counter=0'
152+
hotp.provisioning_uri("[email protected]", 0) # => 'otpauth://hotp/My%20Service:alice%40google.com?secret=base32secret3232&issuer=My%20Service&counter=0'
153153
```
154154

155155
This can then be rendered as a QR Code which the user can scan using their mobile phone and the appropriate application.

lib/rotp/hotp.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ def verify(otp, counter, retries: 0)
2424
# @param [String] name of the account
2525
# @param [Integer] initial_count starting counter value, defaults to 0
2626
# @return [String] provisioning uri
27-
def provisioning_uri(name, initial_count = 0)
28-
OTP::URI.new(self, account_name: name, counter: initial_count).to_s
27+
def provisioning_uri(name = nil, initial_count = 0)
28+
OTP::URI.new(self, account_name: name || @name, counter: initial_count).to_s
2929
end
3030
end
3131
end

lib/rotp/otp.rb

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module ROTP
22
class OTP
3-
attr_reader :secret, :digits, :digest
3+
attr_reader :secret, :digits, :digest, :name, :issuer, :provisioning_params
44
DEFAULT_DIGITS = 6
55

66
# @param [String] secret in the form of base32
@@ -10,10 +10,22 @@ class OTP
1010
# @option options digest [String] (sha1)
1111
# Digest used in the HMAC.
1212
# Google Authenticate only supports 'sha1' currently
13+
# @option options name [String]
14+
# The name of the account for the OTP.
15+
# Used in the provisioning URL
16+
# @option options issuer [String]
17+
# The issuer of the OTP.
18+
# Used in the provisioning URL
19+
# @option options provisioning_params [Hash] ({})
20+
# Additional non-standard params you may want appended to the
21+
# provisioning URI. Ex. `image: 'https://example.com/icon.png'`
1322
# @returns [OTP] OTP instantiation
1423
def initialize(s, options = {})
1524
@digits = options[:digits] || DEFAULT_DIGITS
1625
@digest = options[:digest] || 'sha1'
26+
@name = options[:name]
27+
@issuer = options[:issuer]
28+
@provisioning_params = options[:provisioning_params] || {}
1729
@secret = s
1830
end
1931

lib/rotp/otp/uri.rb

+3-4
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ module ROTP
22
class OTP
33
# https://github.com/google/google-authenticator/wiki/Key-Uri-Format
44
class URI
5-
def initialize(otp, account_name:, counter: nil)
5+
def initialize(otp, account_name: nil, counter: nil)
66
@otp = otp
7-
@account_name = account_name
7+
@account_name = account_name || ''
88
@counter = counter
99
end
1010

@@ -34,8 +34,6 @@ def digits
3434
end
3535

3636
def issuer
37-
return if @otp.is_a?(HOTP)
38-
3937
@otp.issuer&.strip&.tr(':', '_')
4038
end
4139

@@ -56,6 +54,7 @@ def parameters
5654
period: period,
5755
counter: counter,
5856
}
57+
.merge(@otp.provisioning_params)
5958
.reject { |_, v| v.nil? }
6059
.map { |k, v| "#{k}=#{ERB::Util.url_encode(v)}" }
6160
.join('&')

lib/rotp/totp.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ def verify(otp, drift_ahead: 0, drift_behind: 0, after: nil, at: Time.now)
5353
# to provision the Google Authenticator app
5454
# @param [String] name of the account
5555
# @return [String] provisioning URI
56-
def provisioning_uri(name)
57-
OTP::URI.new(self, account_name: name).to_s
56+
def provisioning_uri(name = nil)
57+
OTP::URI.new(self, account_name: name || @name).to_s
5858
end
5959

6060
private

spec/lib/rotp/hotp_spec.rb

+33-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,15 @@
108108
end
109109

110110
describe '#provisioning_uri' do
111-
it 'accepts the account name' do
111+
let(:hotp) { ROTP::HOTP.new('a' * 32, name: "[email protected]") }
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
118+
119+
it 'allow passing a name to override the OTP name' do
112120
expect(hotp.provisioning_uri('mark@percival'))
113121
.to eq 'otpauth://hotp/mark%40percival?secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&counter=0'
114122
end
@@ -117,5 +125,29 @@
117125
expect(hotp.provisioning_uri('mark@percival', 17))
118126
.to eq 'otpauth://hotp/mark%40percival?secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&counter=17'
119127
end
128+
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'
135+
end
136+
end
137+
138+
context "with an issuer" do
139+
let(:hotp) { ROTP::HOTP.new('a' * 32, name: "[email protected]", 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
145+
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'
149+
end
150+
end
151+
120152
end
121153
end

0 commit comments

Comments
 (0)