Skip to content

Commit a11cfde

Browse files
authored
fix(Authentication): Local Dev fixed_otp (#5710)
**Story card:** [sc-16869](https://app.shortcut.com/simpledotorg/story/16869/ensure-fixed-otp-works-locally) ## Because `:fixed_otp` is used in generating the OTP but disregarded during authentication ## This addresses - Ensuring the flag is respected during authentication - Ensuring this only works in non-prod environments. ## Test instructions - suite tests - for manual testing, see `prepare_security_environment`
1 parent 646d66f commit a11cfde

File tree

2 files changed

+91
-2
lines changed

2 files changed

+91
-2
lines changed

app/models/phone_number_authentication/authenticate.rb

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ def verify_auth
5252
authentication.unlock
5353
verify_auth
5454
end
55-
elsif authentication.otp != otp
55+
elsif !otp_valid?
5656
track_failed_attempt
5757
failure("login.error_messages.invalid_otp")
58-
elsif !authentication.otp_valid?
58+
elsif !otp_not_expired?
5959
track_failed_attempt
6060
failure("login.error_messages.expired_otp")
6161
elsif !authentication.authenticate(password)
@@ -66,6 +66,20 @@ def verify_auth
6666
end
6767
end
6868

69+
def otp_valid?
70+
return true if using_fixed_otp?
71+
authentication.otp == otp
72+
end
73+
74+
def otp_not_expired?
75+
return true if using_fixed_otp?
76+
authentication.otp_valid?
77+
end
78+
79+
def using_fixed_otp?
80+
!Rails.env.production? && Flipper.enabled?(:fixed_otp) && otp == "000000"
81+
end
82+
6983
def failure(message_key, opts = {})
7084
error_string = I18n.t(message_key, opts)
7185
Result.new(authentication, false, error_string)

spec/models/phone_number_authentication/authenticate_spec.rb

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,81 @@
2929
expect(user.phone_number_authentication.otp_expires_at).to eq(Time.at(0))
3030
expect(user.otp_valid?).to be false
3131
end
32+
33+
context "with fixed_otp feature flag enabled" do
34+
before { Flipper.enable(:fixed_otp) }
35+
after { Flipper.disable(:fixed_otp) }
36+
37+
it "returns success when logging in with 000000" do
38+
user = FactoryBot.create(:user, password: "5489")
39+
result = PhoneNumberAuthentication::Authenticate.call(otp: "000000",
40+
password: "5489",
41+
phone_number: user.phone_number)
42+
expect(result).to be_success
43+
expect(result.error_message).to be_nil
44+
end
45+
46+
it "returns success with 000000 even when user has different OTP in database" do
47+
user = FactoryBot.create(:user, password: "5489")
48+
# Simulate a user already created locally with some random OTP
49+
user.phone_number_authentication.update!(otp: "123456", otp_expires_at: 1.hour.from_now)
50+
51+
result = PhoneNumberAuthentication::Authenticate.call(otp: "000000",
52+
password: "5489",
53+
phone_number: user.phone_number)
54+
expect(result).to be_success
55+
expect(result.error_message).to be_nil
56+
end
57+
58+
it "returns success with 000000 even when stored OTP is expired" do
59+
user = FactoryBot.create(:user, password: "5489")
60+
# Simulate an expired OTP in the database
61+
user.phone_number_authentication.update!(otp: "123456", otp_expires_at: 1.hour.ago)
62+
expect(user.otp_valid?).to be false
63+
64+
result = PhoneNumberAuthentication::Authenticate.call(otp: "000000",
65+
password: "5489",
66+
phone_number: user.phone_number)
67+
expect(result).to be_success
68+
expect(result.error_message).to be_nil
69+
end
70+
71+
it "generates access token when using 000000" do
72+
user = FactoryBot.create(:user, password: "5489")
73+
user.phone_number_authentication.update!(otp: "987654", otp_expires_at: 1.hour.from_now)
74+
75+
expect {
76+
PhoneNumberAuthentication::Authenticate.call(otp: "000000",
77+
password: "5489",
78+
phone_number: user.phone_number)
79+
}.to change { user.phone_number_authentication.reload.access_token }
80+
end
81+
82+
it "invalidates OTP after successful login with 000000" do
83+
user = FactoryBot.create(:user, password: "5489")
84+
user.phone_number_authentication.update!(otp: "456789", otp_expires_at: 1.hour.from_now)
85+
86+
expect(user.otp_valid?).to be true
87+
PhoneNumberAuthentication::Authenticate.call(otp: "000000",
88+
password: "5489",
89+
phone_number: user.phone_number)
90+
expect(user.phone_number_authentication.reload.otp_expires_at).to eq(Time.at(0))
91+
expect(user.otp_valid?).to be false
92+
end
93+
94+
it "does NOT work in non-development environments" do
95+
user = FactoryBot.create(:user, password: "5489")
96+
user.phone_number_authentication.update!(otp: "123456", otp_expires_at: 1.hour.from_now)
97+
98+
allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new("production"))
99+
100+
result = PhoneNumberAuthentication::Authenticate.call(otp: "000000",
101+
password: "5489",
102+
phone_number: user.phone_number)
103+
expect(result).to_not be_success
104+
expect(result.error_message).to eq("Your OTP does not match. Try again?")
105+
end
106+
end
32107
end
33108

34109
context "fails when" do

0 commit comments

Comments
 (0)