Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.
Expand Down Expand Up @@ -150,6 +149,10 @@ totp.provisioning_uri("[email protected]") # => 'otpauth://totp/My%20Service:alic

hotp = ROTP::HOTP.new("base32secret3232", issuer: "My Service")
hotp.provisioning_uri("[email protected]", 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("[email protected]") # => 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.
Expand Down
8 changes: 5 additions & 3 deletions lib/rotp/otp.rb
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -14,18 +15,19 @@ 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
# 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'
@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

Expand Down
10 changes: 7 additions & 3 deletions lib/rotp/otp/uri.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
1 change: 1 addition & 0 deletions rotp.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -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
33 changes: 33 additions & 0 deletions spec/lib/rotp/otp/uri_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,37 @@
uri = described_class.new(otp, account_name: '[email protected]')
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: '[email protected]')
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: '[email protected]')
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: '[email protected]')
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: '[email protected]')
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
1 change: 1 addition & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

require 'rotp'
require 'timecop'
require 'debug'

RSpec.configure do |config|
config.disable_monkey_patching!
Expand Down