File: C:/Ruby27-x64/lib/ruby/gems/2.7.0/gems/rotp-6.2.2/spec/lib/rotp/totp_spec.rb
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