Skip to content

Commit

Permalink
feat: Allow for non-standard provisioning URI params, eg. image/icon (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
mdp authored Aug 30, 2023
1 parent 3908511 commit 45d8aac
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 49 deletions.
68 changes: 34 additions & 34 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,61 +1,61 @@
### Changelog
# Changelog

### 6.2.2
## 6.2.2

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

### 6.2.1
## 6.2.1

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

### 6.2.0
## 6.2.0

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

### 6.1.0
## 6.1.0

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

### 6.0.0
## 6.0.0

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

### 5.1.0
## 5.1.0

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

### 5.0.0
## 5.0.0

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

### 4.1.0
## 4.1.0

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

### 4.0.2
## 4.0.2

- Fix gemspec requirment for Addressable

### 4.0.1
## 4.0.1

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

#### 4.0.0
## 4.0.0

- Simplify API
- Remove support for Ruby < 2.0
Expand All @@ -65,96 +65,96 @@
- `TOTP#at`
- `TOTP#now` (first argument)

#### 3.3.1
## 3.3.1

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

#### 3.3.0
## 3.3.0

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

#### 3.2.0
## 3.2.0

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

#### 3.1.0
## 3.1.0

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

#### 3.0.1
## 3.0.1

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

#### 3.0.0
## 3.0.0

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

#### 2.1.2
## 2.1.2

- Remove string literals to prepare immutable strings in Ruby 3.0

#### 2.1.1
## 2.1.1

- Reorder the params for Windows Phone Authenticator - #43

#### 2.1.0
## 2.1.0

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

#### 2.0.0
## 2.0.0

- Move to only comparing string OTP's.

#### 1.7.0
## 1.7.0

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

#### 1.6.1
## 1.6.1

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

#### 1.6.0
## 1.6.0

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

#### 1.5.0
## 1.5.0

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

#### 1.4.6
## 1.4.6

- Revert to previous Base32

#### 1.4.5
## 1.4.5

- Fix and test correct implementation of Base32

#### 1.4.4
## 1.4.4

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

#### 1.4.3
## 1.4.3

- Bugfix on padding

#### 1.4.2
## 1.4.2

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

#### 1.4.1
## 1.4.1

- Clean up drift logic

#### 1.4.0
## 1.4.0

- Added clock drift support via 'verify_with_drift' for TOTP

####1.3.0
## 1.3.0

- Added support for Ruby 1.9.x
- Removed dependency on Base32
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ totp = ROTP::TOTP.new("base32secret3232", issuer: "My Service")
totp.provisioning_uri("[email protected]") # => 'otpauth://totp/My%20Service:alice%40google.com?secret=base32secret3232&issuer=My%20Service'

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

This can then be rendered as a QR Code which the user can scan using their mobile phone and the appropriate application.
Expand Down
4 changes: 2 additions & 2 deletions lib/rotp/hotp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ def verify(otp, counter, retries: 0)
# @param [String] name of the account
# @param [Integer] initial_count starting counter value, defaults to 0
# @return [String] provisioning uri
def provisioning_uri(name, initial_count = 0)
OTP::URI.new(self, account_name: name, counter: initial_count).to_s
def provisioning_uri(name = nil, initial_count = 0)
OTP::URI.new(self, account_name: name || @name, counter: initial_count).to_s
end
end
end
14 changes: 13 additions & 1 deletion lib/rotp/otp.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module ROTP
class OTP
attr_reader :secret, :digits, :digest
attr_reader :secret, :digits, :digest, :name, :issuer, :provisioning_params
DEFAULT_DIGITS = 6

# @param [String] secret in the form of base32
Expand All @@ -10,10 +10,22 @@ class OTP
# @option options digest [String] (sha1)
# Digest used in the HMAC.
# Google Authenticate only supports 'sha1' currently
# @option options name [String]
# The name of the account for the OTP.
# Used in the provisioning URL
# @option options issuer [String]
# The issuer of the OTP.
# Used in the provisioning URL
# @option options provisioning_params [Hash] ({})
# Additional non-standard params you may want appended to the
# provisioning URI. Ex. `image: 'https://example.com/icon.png'`
# @returns [OTP] OTP instantiation
def initialize(s, options = {})
@digits = options[:digits] || DEFAULT_DIGITS
@digest = options[:digest] || 'sha1'
@name = options[:name]
@issuer = options[:issuer]
@provisioning_params = options[:provisioning_params] || {}
@secret = s
end

Expand Down
7 changes: 3 additions & 4 deletions lib/rotp/otp/uri.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ module ROTP
class OTP
# https://github.com/google/google-authenticator/wiki/Key-Uri-Format
class URI
def initialize(otp, account_name:, counter: nil)
def initialize(otp, account_name: nil, counter: nil)
@otp = otp
@account_name = account_name
@account_name = account_name || ''
@counter = counter
end

Expand Down Expand Up @@ -34,8 +34,6 @@ def digits
end

def issuer
return if @otp.is_a?(HOTP)

@otp.issuer&.strip&.tr(':', '_')
end

Expand All @@ -56,6 +54,7 @@ def parameters
period: period,
counter: counter,
}
.merge(@otp.provisioning_params)
.reject { |_, v| v.nil? }
.map { |k, v| "#{k}=#{ERB::Util.url_encode(v)}" }
.join('&')
Expand Down
4 changes: 2 additions & 2 deletions lib/rotp/totp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ def verify(otp, drift_ahead: 0, drift_behind: 0, after: nil, at: Time.now)
# to provision the Google Authenticator app
# @param [String] name of the account
# @return [String] provisioning URI
def provisioning_uri(name)
OTP::URI.new(self, account_name: name).to_s
def provisioning_uri(name = nil)
OTP::URI.new(self, account_name: name || @name).to_s
end

private
Expand Down
34 changes: 33 additions & 1 deletion spec/lib/rotp/hotp_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,15 @@
end

describe '#provisioning_uri' do
it 'accepts the account name' do
let(:hotp) { ROTP::HOTP.new('a' * 32, name: "[email protected]") }
let(:params) { CGI.parse URI.parse(uri).query }

it 'created from the otp instance data' do
expect(hotp.provisioning_uri())
.to eq 'otpauth://hotp/m%40mdp.im?secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&counter=0'
end

it 'allow passing a name to override the OTP name' do
expect(hotp.provisioning_uri('mark@percival'))
.to eq 'otpauth://hotp/mark%40percival?secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&counter=0'
end
Expand All @@ -117,5 +125,29 @@
expect(hotp.provisioning_uri('mark@percival', 17))
.to eq 'otpauth://hotp/mark%40percival?secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&counter=17'
end

context 'with non-standard provisioning_params' do
let(:hotp) { ROTP::HOTP.new('a' * 32, digits: 8, provisioning_params: {image: 'https://example.com/icon.png'}) }
let(:uri) { hotp.provisioning_uri("mark@percival") }

it 'includes the issuer as parameter' do
expect(params['image'].first).to eq 'https://example.com/icon.png'
end
end

context "with an issuer" do
let(:hotp) { ROTP::HOTP.new('a' * 32, name: "[email protected]", issuer: "Example.com") }

it 'created from the otp instance data' do
expect(hotp.provisioning_uri())
.to eq 'otpauth://hotp/Example.com:m%40mdp.im?secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&issuer=Example.com&counter=0'
end

it 'allow passing a name to override the OTP name' do
expect(hotp.provisioning_uri('mark@percival'))
.to eq 'otpauth://hotp/Example.com:mark%40percival?secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&issuer=Example.com&counter=0'
end
end

end
end
Loading

0 comments on commit 45d8aac

Please sign in to comment.