rotp-6.2.0/ 0000755 0001751 0001751 00000000000 13753334422 011537 5 ustar pravi pravi rotp-6.2.0/docker-compose.yml 0000644 0001751 0001751 00000001441 13753334422 015174 0 ustar pravi pravi version: "3.8"
services:
ruby_2_3:
build:
context: .
dockerfile: Dockerfile-2.3
volumes:
- "./lib:/usr/src/app/lib"
- "./spec:/usr/src/app/spec"
ruby_2_5:
build:
context: .
dockerfile: Dockerfile-2.5
volumes:
- "./lib:/usr/src/app/lib"
- "./spec:/usr/src/app/spec"
ruby_2_6:
build:
context: .
dockerfile: Dockerfile-2.6
volumes:
- "./lib:/usr/src/app/lib"
- "./spec:/usr/src/app/spec"
ruby_2_7:
build:
context: .
dockerfile: Dockerfile-2.7
volumes:
- "./lib:/usr/src/app/lib"
- "./spec:/usr/src/app/spec"
ruby_3_0_rc:
build:
context: .
dockerfile: Dockerfile-3.0-rc
volumes:
- "./lib:/usr/src/app/lib"
- "./spec:/usr/src/app/spec"
rotp-6.2.0/rotp.gemspec 0000644 0001751 0001751 00000001670 13753334422 014074 0 ustar pravi pravi require './lib/rotp/version'
Gem::Specification.new do |s|
s.name = 'rotp'
s.version = ROTP::VERSION
s.platform = Gem::Platform::RUBY
s.required_ruby_version = '>= 2.3'
s.license = 'MIT'
s.authors = ['Mark Percival']
s.email = ['mark@markpercival.us']
s.homepage = 'https://github.com/mdp/rotp'
s.summary = 'A Ruby library for generating and verifying one time passwords'
s.description = 'Works for both HOTP and TOTP, and includes QR Code provisioning'
s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
s.require_paths = ['lib']
s.add_development_dependency "rake", "~> 13.0"
s.add_development_dependency 'rspec', '~> 3.5'
s.add_development_dependency 'simplecov', '~> 0.12'
s.add_development_dependency 'timecop', '~> 0.8'
end
rotp-6.2.0/spec/ 0000755 0001751 0001751 00000000000 13753334422 012471 5 ustar pravi pravi rotp-6.2.0/spec/spec_helper.rb 0000644 0001751 0001751 00000000513 13753334422 015306 0 ustar pravi pravi require 'simplecov'
SimpleCov.start do
add_filter '/spec/'
end
require 'rotp'
require 'timecop'
RSpec.configure do |config|
config.disable_monkey_patching!
config.raise_errors_for_deprecations!
config.color = true
config.fail_fast = true
config.before do
Timecop.return
end
end
require_relative '../lib/rotp'
rotp-6.2.0/spec/lib/ 0000755 0001751 0001751 00000000000 13753334422 013237 5 ustar pravi pravi rotp-6.2.0/spec/lib/rotp/ 0000755 0001751 0001751 00000000000 13753334422 014223 5 ustar pravi pravi rotp-6.2.0/spec/lib/rotp/otp/ 0000755 0001751 0001751 00000000000 13753334422 015025 5 ustar pravi pravi rotp-6.2.0/spec/lib/rotp/otp/uri_spec.rb 0000644 0001751 0001751 00000010640 13753334422 017164 0 ustar pravi pravi require 'spec_helper'
RSpec.describe ROTP::OTP::URI do
it 'meets basic functionality' do
otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP')
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 issuer' do
otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', issuer: 'Example')
uri = described_class.new(otp, account_name: 'alice@google.com')
expect(uri.to_s).to eq 'otpauth://totp/Example:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example'
end
it 'encodes the account name' do
otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', issuer: 'Provider1')
uri = described_class.new(otp, account_name: 'Alice Smith')
expect(uri.to_s).to eq 'otpauth://totp/Provider1:Alice%20Smith?secret=JBSWY3DPEHPK3PXP&issuer=Provider1'
end
it 'encodes the issuer' do
otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', issuer: 'Big Corporation')
uri = described_class.new(otp, account_name: ' alice@bigco.com')
expect(uri.to_s).to eq 'otpauth://totp/Big%20Corporation:%20alice%40bigco.com?secret=JBSWY3DPEHPK3PXP&issuer=Big%20Corporation'
end
it 'includes non-default SHA256 algorithm' do
otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', digest: 'sha256')
uri = described_class.new(otp, account_name: 'alice@google.com')
expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&algorithm=SHA256'
end
it 'includes non-default SHA512 algorithm' do
otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', digest: 'sha512')
uri = described_class.new(otp, account_name: 'alice@google.com')
expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&algorithm=SHA512'
end
it 'includes non-default 8 digits' do
otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', digits: 8)
uri = described_class.new(otp, account_name: 'alice@google.com')
expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&digits=8'
end
it 'includes non-default period for TOTP' do
otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', interval: 35)
uri = described_class.new(otp, account_name: 'alice@google.com')
expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&period=35'
end
it 'includes non-default counter for HOTP' do
otp = ROTP::HOTP.new('JBSWY3DPEHPK3PXP')
uri = described_class.new(otp, account_name: 'alice@google.com', counter: 17)
expect(uri.to_s).to eq 'otpauth://hotp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&counter=17'
end
it 'can include all parameters' do
otp = ROTP::TOTP.new(
'HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ',
digest: 'sha512',
digits: 8,
interval: 60,
issuer: 'ACME Co',
)
uri = described_class.new(otp, account_name: 'john.doe@email.com')
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'
end
it 'strips leading and trailing whitespace from the issuer' do
otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', issuer: ' Big Corporation ')
uri = described_class.new(otp, account_name: ' alice@bigco.com')
expect(uri.to_s).to eq 'otpauth://totp/Big%20Corporation:%20alice%40bigco.com?secret=JBSWY3DPEHPK3PXP&issuer=Big%20Corporation'
end
it 'strips trailing whitespace from the account name' do
otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP')
uri = described_class.new(otp, account_name: ' alice@google.com ')
expect(uri.to_s).to eq 'otpauth://totp/%20%20alice%40google.com?secret=JBSWY3DPEHPK3PXP'
end
it 'replaces colons in the issuer with underscores' do
otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', issuer: 'Big:Corporation')
uri = described_class.new(otp, account_name: 'alice@bigco.com')
expect(uri.to_s).to eq 'otpauth://totp/Big_Corporation:alice%40bigco.com?secret=JBSWY3DPEHPK3PXP&issuer=Big_Corporation'
end
it 'replaces colons in the account name with underscores' do
otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP')
uri = described_class.new(otp, account_name: 'Alice:Smith')
expect(uri.to_s).to eq 'otpauth://totp/Alice_Smith?secret=JBSWY3DPEHPK3PXP'
end
it 'handles email account names with sub-addressing' do
otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP')
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
end
rotp-6.2.0/spec/lib/rotp/hotp_spec.rb 0000644 0001751 0001751 00000006225 13753334422 016541 0 ustar pravi pravi require 'spec_helper'
RSpec.describe ROTP::HOTP do
let(:counter) { 1234 }
let(:token) { '161024' }
let(:hotp) { ROTP::HOTP.new('a' * 32) }
describe '#at' do
let(:token) { hotp.at counter }
context 'only the counter as argument' do
it 'generates a string OTP' do
expect(token).to eq '161024'
end
end
context 'invalid counter' do
it 'raises an error' do
expect { hotp.at(-123_456) }.to raise_error(ArgumentError)
end
end
context 'RFC compatibility' do
let(:hotp) { ROTP::HOTP.new('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ') }
it 'matches the RFC documentation examples' do
# 12345678901234567890 in Base32
# GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ
expect(hotp.at(0)).to eq '755224'
expect(hotp.at(1)).to eq '287082'
expect(hotp.at(2)).to eq '359152'
expect(hotp.at(3)).to eq '969429'
expect(hotp.at(4)).to eq '338314'
expect(hotp.at(5)).to eq '254676'
expect(hotp.at(6)).to eq '287922'
expect(hotp.at(7)).to eq '162583'
expect(hotp.at(8)).to eq '399871'
expect(hotp.at(9)).to eq '520489'
end
end
end
describe '#verify' do
let(:verification) { hotp.verify token, counter }
context 'numeric token' do
let(:token) { 161_024 }
it 'raises an error' do
expect { verification }.to raise_error(ArgumentError)
end
end
context 'string token' do
it 'is true' do
expect(verification).to be_truthy
end
end
context 'RFC compatibility' do
let(:hotp) { ROTP::HOTP.new('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ') }
let(:token) { '520489' }
it 'verifies and does not allow reuse' do
expect(hotp.verify(token, 9)).to be_truthy
expect(hotp.verify(token, 10)).to be_falsey
end
end
describe 'with retries' do
let(:verification) { hotp.verify token, counter, retries: retries }
context 'counter outside than retries' do
let(:counter) { 1223 }
let(:retries) { 10 }
it 'is false' do
expect(verification).to be_falsey
end
end
context 'counter exactly in retry range' do
let(:counter) { 1224 }
let(:retries) { 10 }
it 'is true' do
expect(verification).to eq 1234
end
end
context 'counter in retry range' do
let(:counter) { 1224 }
let(:retries) { 11 }
it 'is true' do
expect(verification).to eq 1234
end
end
context 'counter ahead of token' do
let(:counter) { 1235 }
let(:retries) { 3 }
it 'is false' do
expect(verification).to be_falsey
end
end
end
end
describe '#provisioning_uri' do
it 'accepts the account name' do
expect(hotp.provisioning_uri('mark@percival'))
.to eq 'otpauth://hotp/mark%40percival?secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&counter=0'
end
it 'also accepts a custom counter value' do
expect(hotp.provisioning_uri('mark@percival', 17))
.to eq 'otpauth://hotp/mark%40percival?secret=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&counter=17'
end
end
end
rotp-6.2.0/spec/lib/rotp/base32_spec.rb 0000644 0001751 0001751 00000005167 13753334422 016652 0 ustar pravi pravi require 'spec_helper'
RSpec.describe ROTP::Base32 do
describe '.random' do
context 'without arguments' do
let(:base32) { ROTP::Base32.random }
it 'is 20 bytes (160 bits) long (resulting in a 32 character base32 code)' do
expect(ROTP::Base32.decode(base32).length).to eq 20
expect(base32.length).to eq 32
end
it 'is base32 charset' do
expect(base32).to match(/\A[A-Z2-7]+\z/)
end
end
context 'with arguments' do
let(:base32) { ROTP::Base32.random 48 }
it 'returns the appropriate byte length code' do
expect(ROTP::Base32.decode(base32).length).to eq 48
end
end
context 'alias to older random_base32' do
let(:base32) { ROTP::Base32.random_base32(36) }
it 'is base32 charset' do
expect(base32.length).to eq 36
expect(ROTP::Base32.decode(base32).length).to eq 22
end
end
end
describe '.decode' do
context 'corrupt input data' do
it 'raises a sane error' do
expect { ROTP::Base32.decode('4BCDEFG234BCDEF1') }.to \
raise_error(ROTP::Base32::Base32Error, "Invalid Base32 Character - '1'")
end
end
context 'valid input data' do
it 'correctly decodes a string' do
expect(ROTP::Base32.decode('2EB7C66WC5TSO').unpack('H*').first).to eq 'd103f17bd6176727'
expect(ROTP::Base32.decode('Y6Y5ZCAC7NABCHSJ').unpack('H*').first).to eq 'c7b1dc8802fb40111e49'
end
it 'correctly decode strings with trailing bits (not a multiple of 8)' do
# Dropbox style 26 characters (26*5==130 bits or 16.25 bytes, but chopped to 128)
# Matches the behavior of Google Authenticator, drops extra 2 empty bits
expect(ROTP::Base32.decode('YVT6Z2XF4BQJNBMTD7M6QBQCEM').unpack('H*').first).to eq 'c567eceae5e0609685931fd9e8060223'
# For completeness, test all the possibilities allowed by Google Authenticator
# Drop the incomplete empty extra 4 bits (28*5==140bits or 17.5 bytes, chopped to 136 bits)
expect(ROTP::Base32.decode('5GGZQB3WN6LD7V3L5HPDYTQUANEQ').unpack('H*').first).to eq 'e98d9807766f963fd76be9de3c4e140349'
end
context 'with padding' do
it 'correctly decodes a string' do
expect(ROTP::Base32.decode('234A===').unpack('H*').first).to eq 'd6f8'
end
end
end
end
describe '.encode' do
context 'encode input data' do
it 'correctly encodes data' do
expect(ROTP::Base32.encode(hex_to_bin('3c204da94294ff82103ee34e96f74b48'))).to eq 'HQQE3KKCST7YEEB64NHJN52LJA'
end
end
end
end
def hex_to_bin(s)
s.scan(/../).map { |x| x.hex }.pack('c*')
end
rotp-6.2.0/spec/lib/rotp/cli_spec.rb 0000644 0001751 0001751 00000003255 13753334422 016336 0 ustar pravi pravi require 'spec_helper'
require 'rotp/cli'
RSpec.describe ROTP::CLI do
let(:cli) { described_class.new('executable', argv) }
let(:output) { cli.output }
let(:now) { Time.utc 2012, 1, 1 }
before do
Timecop.freeze now
end
context 'generating a TOTP' do
let(:argv) { %w[--secret JBSWY3DPEHPK3PXP] }
it 'prints the corresponding token' do
expect(output).to eq '068212'
end
end
context 'generating a TOTP with sha256 digest' do
let(:argv) { %w[--secret JBSWY3DPEHPK3PXP --digest sha256] }
it 'prints the corresponding token' do
expect(output).to eq '544902'
end
end
context 'generating a TOTP with no secret' do
let(:argv) { %w[--time --secret] }
it 'prints the corresponding token' do
expect(output).to match 'You must also specify a --secret'
end
end
context 'generating a TOTP with bad base32 secret' do
let(:argv) { %W[--time --secret #{'1' * 32}] }
it 'prints the corresponding token' do
expect(output).to match 'Secret must be in RFC4648 Base32 format'
end
end
context 'trying to generate an unsupport type' do
let(:argv) { %W[--notreal --secret #{'a' * 32}] }
it 'prints the corresponding token' do
expect(output).to match 'invalid option: --notreal'
end
end
context 'generating a HOTP' do
let(:argv) { %W[--hmac --secret #{'a' * 32} --counter 1234] }
it 'prints the corresponding token' do
expect(output).to eq '161024'
end
end
context 'generating a HOTP' do
let(:argv) { %W[--hmac --secret #{'a' * 32} --counter 1234 --digest sha256] }
it 'prints the corresponding token' do
expect(output).to eq '325941'
end
end
end
rotp-6.2.0/spec/lib/rotp/totp_spec.rb 0000644 0001751 0001751 00000016647 13753334422 016566 0 ustar pravi pravi require 'spec_helper'
TEST_TIME = Time.utc 2016, 9, 23, 9 # 2016-09-23 09:00:00 UTC
TEST_TOKEN = '082630'.freeze
RSpec.describe ROTP::TOTP do
let(:now) { TEST_TIME }
let(:token) { TEST_TOKEN }
let(:totp) { ROTP::TOTP.new 'JBSWY3DPEHPK3PXP' }
describe '#at' do
let(:token) { totp.at now }
it 'is a string number' do
expect(token).to eq TEST_TOKEN
end
context 'RFC compatibility' do
let(:totp) { ROTP::TOTP.new('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ') }
it 'matches the RFC documentation examples' do
expect(totp.at(1_111_111_111)).to eq '050471'
expect(totp.at(1_234_567_890)).to eq '005924'
expect(totp.at(2_000_000_000)).to eq '279037'
end
end
end
describe '#verify' do
let(:verification) { totp.verify token, at: now }
context 'numeric token' do
let(:token) { 82_630 }
it 'raises an error with an integer' do
expect { verification }.to raise_error(ArgumentError)
end
end
context 'unpadded string token' do
let(:token) { '82630' }
it 'fails to verify' do
expect(verification).to be_falsey
end
end
context 'correctly padded string token' do
it 'verifies' do
expect(verification).to be_truthy
end
end
context 'RFC compatibility' do
let(:totp) { ROTP::TOTP.new 'wrn3pqx5uqxqvnqr' }
before do
Timecop.freeze now
end
context 'correct time based OTP' do
let(:token) { '102705' }
let(:now) { Time.at 1_297_553_958 }
it 'verifies' do
expect(totp.verify('102705')).to be_truthy
end
end
context 'wrong time based OTP' do
it 'fails to verify' do
expect(totp.verify('102705')).to be_falsey
end
end
end
context 'invalidating reused tokens' do
let(:verification) do
totp.verify token,
after: after,
at: now
end
let(:after) { nil }
context 'passing in the `after` timestamp' do
let(:after) do
totp.verify TEST_TOKEN, after: nil, at: now
end
it 'returns a timecode' do
expect(after).to be_kind_of(Integer)
expect(after).to be_within(30).of(now.to_i)
end
context 'reusing same token' do
it 'is false' do
expect(verification).to be_falsy
end
end
end
end
end
def get_timecodes(at, b, a)
# Test the private method
totp.send('get_timecodes', at, b, a)
end
describe 'drifting timecodes' do
it 'should get timecodes behind' do
expect(get_timecodes(TEST_TIME + 15, 15, 0)).to eq([49_154_040])
expect(get_timecodes(TEST_TIME, 15, 0)).to eq([49_154_039, 49_154_040])
expect(get_timecodes(TEST_TIME, 40, 0)).to eq([49_154_038, 49_154_039, 49_154_040])
expect(get_timecodes(TEST_TIME, 90, 0)).to eq([49_154_037, 49_154_038, 49_154_039, 49_154_040])
end
it 'should get timecodes ahead' do
expect(get_timecodes(TEST_TIME, 0, 15)).to eq([49_154_040])
expect(get_timecodes(TEST_TIME + 15, 0, 15)).to eq([49_154_040, 49_154_041])
expect(get_timecodes(TEST_TIME, 0, 30)).to eq([49_154_040, 49_154_041])
expect(get_timecodes(TEST_TIME, 0, 70)).to eq([49_154_040, 49_154_041, 49_154_042])
expect(get_timecodes(TEST_TIME, 0, 90)).to eq([49_154_040, 49_154_041, 49_154_042, 49_154_043])
end
it 'should get timecodes behind and ahead' do
expect(get_timecodes(TEST_TIME, 30, 30)).to eq([49_154_039, 49_154_040, 49_154_041])
expect(get_timecodes(TEST_TIME, 60, 60)).to eq([49_154_038, 49_154_039, 49_154_040, 49_154_041, 49_154_042])
end
end
describe '#verify with drift' do
let(:verification) { totp.verify token, drift_ahead: drift_ahead, drift_behind: drift_behind, at: now }
let(:drift_ahead) { 0 }
let(:drift_behind) { 0 }
context 'with an old OTP' do
let(:token) { totp.at TEST_TIME - 30 } # Previous token at 2016-09-23 08:59:30 UTC
let(:drift_behind) { 15 }
# Tested at 2016-09-23 09:00:00 UTC, and with drift back to 2016-09-23 08:59:45 UTC
# This would therefore include 2 intervals
it 'inside of drift range' do
expect(verification).to be_truthy
end
# Tested at 2016-09-23 09:00:20 UTC, and with drift back to 2016-09-23 09:00:05 UTC
# This only includes 1 interval, therefore only the current token is valid
context 'outside of drift range' do
let(:now) { TEST_TIME + 20 }
it 'is nil' do
expect(verification).to be_nil
end
end
end
context 'with a future OTP' do
let(:token) { totp.at TEST_TIME + 30 } # The next valid token - 2016-09-23 09:00:30 UTC
let(:drift_ahead) { 15 }
# Tested at 2016-09-23 09:00:00 UTC, and ahead to 2016-09-23 09:00:15 UTC
# This only includes 1 interval, therefore only the current token is valid
it 'outside of drift range' do
expect(verification).to be_falsey
end
# Tested at 2016-09-23 09:00:20 UTC, and with drift ahead to 2016-09-23 09:00:35 UTC
# This would therefore include 2 intervals
context 'inside of drift range' do
let(:now) { TEST_TIME + 20 }
it 'is true' do
expect(verification).to be_truthy
end
end
end
end
describe '#verify with drift and prevent token reuse' do
let(:verification) { totp.verify token, drift_ahead: drift_ahead, drift_behind: drift_behind, after: after, at: now }
let(:drift_ahead) { 0 }
let(:drift_behind) { 0 }
let(:after) { nil }
context 'with the `after` timestamp set' do
context 'older token' do
let(:token) { totp.at TEST_TIME - 30 }
let(:drift_behind) { 15 }
it 'is true' do
expect(verification).to be_truthy
expect(verification).to eq((TEST_TIME - 30).to_i)
end
context 'after it has been used' do
let(:after) do
totp.verify token, after: nil, at: now, drift_behind: drift_behind
end
it 'is false' do
expect(verification).to be_falsey
end
end
end
context 'newer token' do
let(:token) { totp.at TEST_TIME + 30 }
let(:drift_ahead) { 15 }
let(:now) { TEST_TIME + 15 }
it 'is true' do
expect(verification).to be_truthy
expect(verification).to eq((TEST_TIME + 30).to_i)
end
context 'after it has been used' do
let(:after) do
totp.verify token, after: nil, at: now, drift_ahead: drift_ahead
end
it 'is false' do
expect(verification).to be_falsey
end
end
end
end
end
describe '#provisioning_uri' do
it 'accepts the account name' do
expect(totp.provisioning_uri('mark@percival'))
.to eq 'otpauth://totp/mark%40percival?secret=JBSWY3DPEHPK3PXP'
end
end
describe '#now' do
before do
Timecop.freeze now
end
context 'Google Authenticator' do
let(:totp) { ROTP::TOTP.new 'wrn3pqx5uqxqvnqr' }
let(:now) { Time.at 1_297_553_958 }
it 'matches the known output' do
expect(totp.now).to eq '102705'
end
end
context 'Dropbox 26 char secret output' do
let(:totp) { ROTP::TOTP.new 'tjtpqea6a42l56g5eym73go2oa' }
let(:now) { Time.at 1_378_762_454 }
it 'matches the known output' do
expect(totp.now).to eq '747864'
end
end
end
end
rotp-6.2.0/spec/lib/rotp/arguments_spec.rb 0000644 0001751 0001751 00000004241 13753334422 017570 0 ustar pravi pravi require 'spec_helper'
require 'rotp/arguments'
RSpec.describe ROTP::Arguments do
let(:arguments) { described_class.new filename, argv }
let(:argv) { '' }
let(:filename) { 'rotp' }
let(:options) { arguments.options }
context 'without options' do
describe '#help' do
it 'shows the help text' do
expect(arguments.to_s).to include 'Usage: '
end
end
describe '#options' do
it 'has the default options' do
expect(options.mode).to eq :time
expect(options.secret).to be_nil
expect(options.counter).to eq 0
end
end
end
context 'unknown arguments' do
let(:argv) { %w[--does-not-exist -xyz] }
describe '#options' do
it 'is in help mode' do
expect(options.mode).to eq :help
end
it 'knows about the problem' do
expect(options.warnings).to include 'invalid option: --does-not-exist'
end
end
end
context 'no arguments' do
let(:argv) { [] }
describe '#options' do
it 'is in help mode' do
expect(options.mode).to eq :help
end
end
end
context 'asking for help' do
let(:argv) { %w[--help] }
describe '#options' do
it 'is in help mode' do
expect(options.mode).to eq :help
end
end
end
context 'generating a counter based secret' do
let(:argv) { %w[--hmac --secret s3same] }
describe '#options' do
it 'is in hmac mode' do
expect(options.mode).to eq :hmac
end
it 'knows the secret' do
expect(options.secret).to eq 's3same'
end
end
end
context 'generating a counter based secret' do
let(:argv) { %w[--time --secret s3same] }
describe '#options' do
it 'is in hmac mode' do
expect(options.mode).to eq :time
end
it 'knows the secret' do
expect(options.secret).to eq 's3same'
end
end
end
context 'generating a time based secret' do
let(:argv) { %w[--secret s3same] }
describe '#options' do
it 'is in time mode' do
expect(options.mode).to eq :time
end
it 'knows the secret' do
expect(options.secret).to eq 's3same'
end
end
end
end
rotp-6.2.0/Guardfile 0000644 0001751 0001751 00000000551 13753334422 013365 0 ustar pravi pravi guard :rspec, cmd: 'bundle exec rspec --format progress' do
require 'guard/rspec/dsl'
dsl = Guard::RSpec::Dsl.new(self)
# RSpec files
rspec = dsl.rspec
watch(rspec.spec_helper) { rspec.spec_dir }
watch(rspec.spec_support) { rspec.spec_dir }
watch(rspec.spec_files)
# Ruby files
ruby = dsl.ruby
dsl.watch_spec_files_for(ruby.lib_files)
end
rotp-6.2.0/.travis.yml 0000644 0001751 0001751 00000000176 13753334422 013654 0 ustar pravi pravi language: ruby
before_install: gem install bundler -v '<2'
rvm:
- 2.7
- 2.6
- 2.5
- 2.3
script:
- bundle exec rspec
rotp-6.2.0/doc/ 0000755 0001751 0001751 00000000000 13753334422 012304 5 ustar pravi pravi rotp-6.2.0/doc/index.html 0000644 0001751 0001751 00000012641 13753334422 014305 0 ustar pravi pravi
ROTP.random_base32# returns a 16 character base32 secret. Compatible with Google Authenticator
Google Authenticator Interop
The library works with the Google Authenticator iPhone and Android app, and also includes the ability to generate provisioning URI’s for use with the QR Code scanner built into the app.