diff --git a/README.md b/README.md index b24d57c..b307fa0 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Although this library will continue to be maintained, if you're implementing a 2 - [Webauthn Ruby Gem](https://github.com/cedarcode/webauthn-ruby) - [Rails demo app with Webauthn](https://github.com/cedarcode/webauthn-rails-demo-app) ----- +--- # The Ruby One Time Password Library @@ -17,7 +17,6 @@ Although this library will continue to be maintained, if you're implementing a 2 [![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](https://www.rubydoc.info/github/mdp/rotp/master) [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/mdp/rotp/blob/master/LICENSE) - A ruby library for generating and validating one time passwords (HOTP & TOTP) according to [RFC 4226](https://datatracker.ietf.org/doc/html/rfc4226) and [RFC 6238](https://datatracker.ietf.org/doc/html/rfc6238). ROTP is compatible with [Google Authenticator](https://github.com/google/google-authenticator) available for [Android](https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2) and [iPhone](https://itunes.apple.com/en/app/google-authenticator/id388497605) and any other TOTP based implementations. @@ -150,6 +149,10 @@ totp.provisioning_uri("alice@google.com") # => 'otpauth://totp/My%20Service:alic hotp = ROTP::HOTP.new("base32secret3232", issuer: "My Service") hotp.provisioning_uri("alice@google.com", 0) # => 'otpauth://hotp/My%20Service:alice%40google.com?secret=base32secret3232&issuer=My%20Service&counter=0' + +# By default optional values will be skipped from URI. But you can force to specify all values in the URI by passing `skip_default_uri_params: false` +totp = ROTP::TOTP.new("base32secret3232", issuer: "My Service", skip_default_uri_params: false) +totp.provisioning_uri("alice@google.com") # => otpauth://totp/My%20Service:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30 ``` This can then be rendered as a QR Code which the user can scan using their mobile phone and the appropriate application. diff --git a/lib/rotp/otp.rb b/lib/rotp/otp.rb index a1b3141..62e136f 100644 --- a/lib/rotp/otp.rb +++ b/lib/rotp/otp.rb @@ -1,7 +1,8 @@ module ROTP class OTP - attr_reader :secret, :digits, :digest, :name, :issuer, :provisioning_params + attr_reader :secret, :digits, :digest, :name, :issuer, :provisioning_params, :skip_default_uri_params DEFAULT_DIGITS = 6 + DEFAULT_DIGEST = 'sha1' # @param [String] secret in the form of base32 # @option options digits [Integer] (6) @@ -14,7 +15,7 @@ class OTP # The name of the account for the OTP. # Used in the provisioning URL # @option options issuer [String] - # The issuer of the OTP. + # 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 @@ -22,10 +23,11 @@ class OTP # @returns [OTP] OTP instantiation def initialize(s, options = {}) @digits = options[:digits] || DEFAULT_DIGITS - @digest = options[:digest] || 'sha1' + @digest = options[:digest] || DEFAULT_DIGEST @name = options[:name] @issuer = options[:issuer] @provisioning_params = options[:provisioning_params] || {} + @skip_default_uri_params = options[:skip_default_uri_params].nil? ? true : options[:skip_default_uri_params] @secret = s end diff --git a/lib/rotp/otp/uri.rb b/lib/rotp/otp/uri.rb index 8ab9185..cb8eaea 100644 --- a/lib/rotp/otp/uri.rb +++ b/lib/rotp/otp/uri.rb @@ -15,7 +15,7 @@ def to_s private def algorithm - return unless %w[sha256 sha512].include?(@otp.digest) + return if skip_default_uri_params && @otp.digest == DEFAULT_DIGEST @otp.digest.upcase end @@ -28,7 +28,7 @@ def counter end def digits - return if @otp.digits == DEFAULT_DIGITS + return if skip_default_uri_params && @otp.digits == DEFAULT_DIGITS @otp.digits end @@ -62,7 +62,7 @@ def parameters def period return if @otp.is_a?(HOTP) - return if @otp.interval == DEFAULT_INTERVAL + return if skip_default_uri_params && @otp.interval == DEFAULT_INTERVAL @otp.interval end @@ -73,6 +73,10 @@ def type when HOTP then 'hotp' end end + + def skip_default_uri_params + @otp.skip_default_uri_params + end end end end diff --git a/rotp.gemspec b/rotp.gemspec index fdadf44..e572f07 100644 --- a/rotp.gemspec +++ b/rotp.gemspec @@ -21,4 +21,5 @@ Gem::Specification.new do |s| s.add_development_dependency 'rspec', '~> 3.5' s.add_development_dependency 'simplecov', '~> 0.12' s.add_development_dependency 'timecop', '~> 0.8' + s.add_development_dependency 'debug', '~> 1.0' end diff --git a/spec/lib/rotp/otp/uri_spec.rb b/spec/lib/rotp/otp/uri_spec.rb index 370aec4..a21ef6b 100644 --- a/spec/lib/rotp/otp/uri_spec.rb +++ b/spec/lib/rotp/otp/uri_spec.rb @@ -96,4 +96,37 @@ uri = described_class.new(otp, account_name: 'alice+1234@google.com') expect(uri.to_s).to eq 'otpauth://totp/alice%2B1234%40google.com?secret=JBSWY3DPEHPK3PXP' end + + context 'with skip_default_uri_params' do + it 'excludes default SHA1 algorithm' do + otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', digest: 'sha1', skip_default_uri_params: true) + uri = described_class.new(otp, account_name: 'alice@google.com') + expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP' + end + + it 'excludes default 6 digits' do + otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', digits: 6, skip_default_uri_params: true) + uri = described_class.new(otp, account_name: 'alice@google.com') + expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP' + end + + it 'excludes default 30 second period' do + otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', interval: 30, skip_default_uri_params: true) + uri = described_class.new(otp, account_name: 'alice@google.com') + expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP' + end + + it 'includes all parameters when skip_default_uri_params is false' do + otp = ROTP::TOTP.new( + 'JBSWY3DPEHPK3PXP', + digest: 'sha1', + digits: 6, + interval: 30, + issuer: 'ACME Co', + skip_default_uri_params: false + ) + uri = described_class.new(otp, account_name: 'alice@google.com') + expect(uri.to_s).to eq 'otpauth://totp/ACME%20Co:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30' + end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 43f3338..29d3436 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,6 +5,7 @@ require 'rotp' require 'timecop' +require 'debug' RSpec.configure do |config| config.disable_monkey_patching!