Skip to content

Commit

Permalink
support validating OTP without a generated_at time
Browse files Browse the repository at this point in the history
  • Loading branch information
alkesh committed Aug 6, 2024
1 parent 7bae862 commit 3aa13dc
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 29 deletions.
21 changes: 8 additions & 13 deletions lib/one_time_password/validator.rb
Original file line number Diff line number Diff line change
@@ -1,41 +1,36 @@
module OneTimePassword
class Validator < Base
def initialize(code, generated_at)
def initialize(code, generated_at = nil)
@code = code
@generated_at = generated_at
end

def valid?
code.present? && !wrong_length? && !expired? && !incorrect?
code.present? && !wrong_length? && (generated_at && !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 expired?
"Enter a valid passcode" if incorrect?
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
"Enter a valid passcode"
end

private

attr_reader :code, :generated_at

def wrong_length?
return @wrong_length if defined?(@wrong_length)

@wrong_length = code.gsub(/\D/, "").length != LENGTH
@wrong_length ||= code.length != LENGTH
end

def expired?
return @expired if defined?(@expired)

@expired = generated_at < DRIFT.seconds.ago
@expired ||= generated_at < DRIFT.seconds.ago
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
26 changes: 10 additions & 16 deletions spec/features/one_time_password_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
require "rails_helper"

RSpec.feature "Given a one time password" do
let!(:drift) { OneTimePassword::Base::DRIFT }
RSpec.feature "One time password" do
let(:session) { Journeys::AdditionalPaymentsForTeaching::Session.order(:created_at).last }

before do
create(:journey_configuration, :additional_payments)
Expand All @@ -14,29 +14,23 @@
click_on "Continue"
end

scenario "verifies the password" do
# - that is wrong
scenario "that is wrong" do
fill_in "claim-one-time-password-field", with: "000000"

click_on "Confirm"
expect(page).to have_text("Enter a valid passcode")
session = Journeys::AdditionalPaymentsForTeaching::Session.order(:created_at).last
expect(session.answers.email_verified).to_not equal(true)
end

# - that is expired

stub_const("OneTimePassword::Base::DRIFT", -100)

fill_in "claim-one-time-password-field-error", with: get_otp_from_email
scenario "that is expired" do
travel 20.minutes
fill_in "claim-one-time-password-field", with: get_otp_from_email
click_on "Confirm"
expect(page).to have_text("Your passcode has expired, request a new one")
expect(session.reload.answers.email_verified).to_not equal(true)
end

# - that is valid

stub_const("OneTimePassword::Base::DRIFT", drift)

fill_in "claim-one-time-password-field-error", with: get_otp_from_email
scenario "that is valid" do
fill_in "claim-one-time-password-field", with: get_otp_from_email
click_on "Confirm"
expect(page).to_not have_css(".govuk-error-summary")
expect(session.reload.answers.email_verified).to equal(true)
Expand Down
18 changes: 18 additions & 0 deletions spec/lib/one_time_password/generator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require "rails_helper"

RSpec.describe OneTimePassword::Generator do
describe "#code" do
subject { described_class.new.code }

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

it "generates a new code" do
expect(subject).to eq one_time_passcode
end
end
end
72 changes: 72 additions & 0 deletions spec/lib/one_time_password/validator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
require "rails_helper"

RSpec.describe OneTimePassword::Validator do
subject { described_class.new(one_time_passcode, generated_at) }

let!(:one_time_passcode) { OneTimePassword::Generator.new.code }
let!(:generated_at) { Time.now }

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

context "with an empty code" do
let(:one_time_passcode) { "" }

it { is_expected.to_not be_valid }

it "has the correct warning" do
expect(subject.warning).to eq "Enter a passcode"
end
end

context "with a nil code" do
let(:one_time_passcode) { nil }

it { is_expected.to_not be_valid }

it "has the correct warning" do
expect(subject.warning).to eq "Enter a passcode"
end
end

context "with a code that is too short" do
let(:one_time_passcode) { "12345" }

it { is_expected.to_not be_valid }

it "has the correct warning" do
expect(subject.warning).to eq "Enter a valid passcode containing 6 digits"
end
end

context "with a code that is the correct length, but wrong" do
let(:one_time_passcode) { "000000" }

it { is_expected.to_not be_valid }

it "has the correct warning" do
expect(subject.warning).to eq "Enter a valid passcode"
end
end

context "with a code that has expired" do
before { travel 20.minutes }

it { is_expected.to_not be_valid }

it "has the correct warning" do
expect(subject.warning).to eq "Your passcode has expired, request a new one"
end

context "when generated_at is not specified" do
subject { described_class.new(one_time_passcode) }

it { is_expected.to_not be_valid }

it "has the correct warning" do
expect(subject.warning).to eq "Your passcode is not valid or has expired"
end
end
end
end

0 comments on commit 3aa13dc

Please sign in to comment.