Skip to content

Commit

Permalink
allow secret to be specified
Browse files Browse the repository at this point in the history
  • Loading branch information
alkesh committed Aug 9, 2024
1 parent 21d3dec commit 65c5481
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 13 deletions.
6 changes: 5 additions & 1 deletion lib/one_time_password/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ def rotp

private

attr_reader :issuer
attr_reader :issuer, :secret

def encode_secret(secret)
ROTP::Base32.encode(secret) if secret
end
end
end
5 changes: 3 additions & 2 deletions lib/one_time_password/generator.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
module OneTimePassword
class Generator < Base
def initialize(issuer = nil)
def initialize(issuer = nil, secret: nil)
@issuer = issuer || ISSUER
@secret = encode_secret(secret) || SECRET
end

def code
@code ||= rotp.new(SECRET, issuer: issuer).now
@code ||= rotp.new(secret, issuer: issuer).now
end
end
end
13 changes: 7 additions & 6 deletions lib/one_time_password/validator.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
module OneTimePassword
class Validator < Base
def initialize(code, generated_at = nil)
def initialize(code, generated_at = nil, secret: nil)
@code = code
@generated_at = generated_at
@secret = encode_secret(secret) || SECRET
end

def valid?
code.present? && !wrong_length? && (generated_at && !expired?) && !incorrect?
code.present? && !wrong_length? && !expired? && !incorrect?
end

def warning
return "Enter a passcode" if code.blank?
return "Enter a valid passcode containing #{LENGTH} digits" if wrong_length?
return "Your passcode has expired, request a new one" if generated_at && expired?
return "Your passcode is not valid or has expired" if !generated_at
return "Your passcode has expired, request a new one" if expired?
return "Your passcode is not valid or has expired" if !generated_at && incorrect?
"Enter a valid passcode" if incorrect?
end

Expand All @@ -30,13 +31,13 @@ def wrong_length?
def expired?
return @expired if defined?(@expired)

@expired = generated_at < DRIFT.seconds.ago
@expired = generated_at < DRIFT.seconds.ago if generated_at
end

def incorrect?
return @incorrect if defined? @incorrect

@incorrect = rotp.new(SECRET, issuer: ISSUER).verify(code, drift_behind: DRIFT).nil?
@incorrect = rotp.new(secret, issuer: ISSUER).verify(code, drift_behind: DRIFT).nil?
end
end
end
23 changes: 19 additions & 4 deletions spec/lib/one_time_password/generator_spec.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
require "rails_helper"

RSpec.describe OneTimePassword::Generator do
let(:totp) { instance_double ROTP::TOTP, now: one_time_passcode }
let(:one_time_passcode) { 123456 }

before do
allow(ROTP::TOTP).to receive(:new).and_return(totp)
end

describe "#code" do
subject { described_class.new.code }

let(:totp) { instance_double ROTP::TOTP, now: one_time_passcode }
let(:one_time_passcode) { 123456 }
it "generates a new code" do
expect(subject).to eq one_time_passcode
end
end

context "specifying a secret" do
subject { described_class.new(secret: secret).code }

let(:secret) { "somesecret" }

before do
allow(ROTP::TOTP).to receive(:new).and_return(totp)
it "uses the secret" do
expect(ROTP::TOTP).to receive(:new).with(ROTP::Base32.encode(secret), anything).and_return(totp)
subject
end

it "generates a new code" do
Expand Down
27 changes: 27 additions & 0 deletions spec/lib/one_time_password/validator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,31 @@
end
end
end

context "not specifying generated_at" do
subject { described_class.new(one_time_passcode) }

context "with a valid code" do
it { is_expected.to be_valid }

it "has no warning" do
expect(subject.warning).to be_nil
end
end
end

context "specifying a secret" do
let!(:one_time_passcode) { OneTimePassword::Generator.new(secret: secret).code }
subject { described_class.new(one_time_passcode, secret: secret) }

let(:secret) { "somesecretstring" }

context "with a valid code" do
it { is_expected.to be_valid }

it "has no warning" do
expect(subject.warning).to be_nil
end
end
end
end

0 comments on commit 65c5481

Please sign in to comment.