Skip to content

Commit

Permalink
feat: add test OTP support for mobile app reviews
Browse files Browse the repository at this point in the history
  • Loading branch information
hf committed Jul 25, 2023
1 parent 3cd4bd5 commit f89dd79
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 22 deletions.
43 changes: 27 additions & 16 deletions internal/api/phone.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,30 +67,41 @@ func (a *API) sendPhoneConfirmation(ctx context.Context, tx *storage.Connection,
return "", internalServerError("invalid otp type")
}

// intentionally keeping this before the test OTP, so that the behavior
// of regular and test OTPs is similar
if sentAt != nil && !sentAt.Add(config.Sms.MaxFrequency).Before(time.Now()) {
return "", MaxFrequencyLimitError
}
oldToken := *token
otp, err := crypto.GenerateOtp(config.Sms.OtpLength)
if err != nil {
return "", internalServerError("error generating otp").WithInternalError(err)
}
*token = crypto.GenerateTokenHash(phone, otp)

var message string
if config.Sms.Template == "" {
message = fmt.Sprintf(defaultSmsMessage, otp)
} else {
message = strings.Replace(config.Sms.Template, "{{ .Code }}", otp, -1)
now := time.Now()

var otp, messageID string

if testOTP, ok := config.Sms.GetTestOTP(phone, now); ok {
otp = testOTP
messageID = "test-otp"
}

messageID, serr := smsProvider.SendMessage(phone, message, channel)
if serr != nil {
*token = oldToken
return messageID, serr
if otp == "" { // not using test OTPs
otp, err := crypto.GenerateOtp(config.Sms.OtpLength)
if err != nil {
return "", internalServerError("error generating otp").WithInternalError(err)
}

var message string
if config.Sms.Template == "" {
message = fmt.Sprintf(defaultSmsMessage, otp)
} else {
message = strings.Replace(config.Sms.Template, "{{ .Code }}", otp, -1)
}

messageID, err = smsProvider.SendMessage(phone, message, channel)
if err != nil {
return messageID, err
}
}

now := time.Now()
*token = crypto.GenerateTokenHash(phone, otp)

switch otpType {
case phoneConfirmationOtp:
Expand Down
17 changes: 17 additions & 0 deletions internal/api/phone_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,19 @@ func (ts *PhoneTestSuite) TestMissingSmsProviderConfig() {
"message": "Error sending sms:",
},
},
{
desc: "Sms OTP with test phone number",
endpoint: "/otp",
method: http.MethodPost,
header: "",
body: map[string]string{
"phone": "555555555",
},
expected: map[string]interface{}{
"code": http.StatusBadRequest,
"message": "Error sending sms:",
},
},
{
desc: "Phone change",
endpoint: "/user",
Expand Down Expand Up @@ -198,6 +211,10 @@ func (ts *PhoneTestSuite) TestMissingSmsProviderConfig() {
ts.Config.Sms.Messagebird.AccessKey = ""
ts.Config.Sms.Textlocal.ApiKey = ""
ts.Config.Sms.Vonage.ApiKey = ""
ts.Config.Sms.TestOTP = map[string]string{
"555555555": "555555",
}

for _, c := range cases {
for _, provider := range smsProviders {
ts.Config.Sms.Provider = provider
Expand Down
24 changes: 18 additions & 6 deletions internal/conf/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,19 +221,31 @@ type PhoneProviderConfiguration struct {
}

type SmsProviderConfiguration struct {
Autoconfirm bool `json:"autoconfirm"`
MaxFrequency time.Duration `json:"max_frequency" split_words:"true"`
OtpExp uint `json:"otp_exp" split_words:"true"`
OtpLength int `json:"otp_length" split_words:"true"`
Provider string `json:"provider"`
Template string `json:"template"`
Autoconfirm bool `json:"autoconfirm"`
MaxFrequency time.Duration `json:"max_frequency" split_words:"true"`
OtpExp uint `json:"otp_exp" split_words:"true"`
OtpLength int `json:"otp_length" split_words:"true"`
Provider string `json:"provider"`
Template string `json:"template"`
TestOTP map[string]string `json:"test_otp" split_words:"true"`
TestOTPValidUntil time.Time `json:"test_otp_valid_until" split_words:"true"`

Twilio TwilioProviderConfiguration `json:"twilio"`
TwilioVerify TwilioVerifyProviderConfiguration `json:"twilio_verify" split_words:"true"`
Messagebird MessagebirdProviderConfiguration `json:"messagebird"`
Textlocal TextlocalProviderConfiguration `json:"textlocal"`
Vonage VonageProviderConfiguration `json:"vonage"`
}

func (c *SmsProviderConfiguration) GetTestOTP(phone string, now time.Time) (string, bool) {
if c.TestOTP != nil && (c.TestOTPValidUntil.IsZero() || now.Before(c.TestOTPValidUntil)) {
testOTP, ok := c.TestOTP[phone]
return testOTP, ok
}

return "", false
}

type TwilioProviderConfiguration struct {
AccountSid string `json:"account_sid" split_words:"true"`
AuthToken string `json:"auth_token" split_words:"true"`
Expand Down

0 comments on commit f89dd79

Please sign in to comment.