From 00ee75c5509facc668295a57ba9130064c267b31 Mon Sep 17 00:00:00 2001 From: Joel Lee Date: Tue, 10 Oct 2023 09:59:34 +0800 Subject: [PATCH] feat: reinstate upgrade whatsapp support on Twilio Programmable Messaging to support Content API (#1266) Resinstates WhatsApp support for the content API by reinstating: https://github.com/supabase/gotrue/pull/1249 After further discussion, it looks like we do have users who are using custom SMS templates (e.g. "Your code is {{.code}} ") We were notified that this is required as alphanumeric senders require the company name in the message. This means we will need to support the Content API as custom messages aren't allowed under Programmable Messaging API + WhatsApp Template There will be 2 more PRs after this: 1. PR to allow for backward compatibility with Basic Authentication Template 2. PR to add ContentSid env var 3. Dashboard PR to expose variable --- internal/api/phone.go | 2 +- internal/api/phone_test.go | 2 +- internal/api/sms_provider/messagebird.go | 2 +- internal/api/sms_provider/sms_provider.go | 2 +- .../api/sms_provider/sms_provider_test.go | 5 +++- internal/api/sms_provider/textlocal.go | 2 +- internal/api/sms_provider/twilio.go | 28 +++++++++++++------ internal/api/sms_provider/twilio_verify.go | 2 +- internal/api/sms_provider/vonage.go | 2 +- internal/conf/configuration.go | 1 + 10 files changed, 31 insertions(+), 17 deletions(-) diff --git a/internal/api/phone.go b/internal/api/phone.go index 4599e8f2a..3586ca781 100644 --- a/internal/api/phone.go +++ b/internal/api/phone.go @@ -93,7 +93,7 @@ func (a *API) sendPhoneConfirmation(ctx context.Context, tx *storage.Connection, return "", err } - messageID, err = smsProvider.SendMessage(phone, message, channel) + messageID, err = smsProvider.SendMessage(phone, message, channel, otp) if err != nil { return messageID, err } diff --git a/internal/api/phone_test.go b/internal/api/phone_test.go index 7047de896..9243b32aa 100644 --- a/internal/api/phone_test.go +++ b/internal/api/phone_test.go @@ -32,7 +32,7 @@ type TestSmsProvider struct { SentMessages int } -func (t *TestSmsProvider) SendMessage(phone string, message string, channel string) (string, error) { +func (t *TestSmsProvider) SendMessage(phone, message, channel, otp string) (string, error) { t.SentMessages += 1 return "", nil } diff --git a/internal/api/sms_provider/messagebird.go b/internal/api/sms_provider/messagebird.go index 0125b9d15..d65ce4728 100644 --- a/internal/api/sms_provider/messagebird.go +++ b/internal/api/sms_provider/messagebird.go @@ -56,7 +56,7 @@ func NewMessagebirdProvider(config conf.MessagebirdProviderConfiguration) (SmsPr }, nil } -func (t *MessagebirdProvider) SendMessage(phone string, message string, channel string) (string, error) { +func (t *MessagebirdProvider) SendMessage(phone, message, channel, otp string) (string, error) { switch channel { case SMSProvider: return t.SendSms(phone, message) diff --git a/internal/api/sms_provider/sms_provider.go b/internal/api/sms_provider/sms_provider.go index 4337ae377..4f4a0e138 100644 --- a/internal/api/sms_provider/sms_provider.go +++ b/internal/api/sms_provider/sms_provider.go @@ -26,7 +26,7 @@ func init() { } type SmsProvider interface { - SendMessage(phone, message, channel string) (string, error) + SendMessage(phone, message, channel, otp string) (string, error) } func GetSmsProvider(config conf.GlobalConfiguration) (SmsProvider, error) { diff --git a/internal/api/sms_provider/sms_provider_test.go b/internal/api/sms_provider/sms_provider_test.go index 5d17dbd0f..831f66d63 100644 --- a/internal/api/sms_provider/sms_provider_test.go +++ b/internal/api/sms_provider/sms_provider_test.go @@ -84,6 +84,7 @@ func (ts *SmsProviderTestSuite) TestTwilioSendSms() { Desc string TwilioResponse *gock.Response ExpectedError error + OTP string }{ { Desc: "Successfully sent sms", @@ -97,6 +98,7 @@ func (ts *SmsProviderTestSuite) TestTwilioSendSms() { Body: message, MessageSID: "abcdef", }), + OTP: "123456", ExpectedError: nil, }, { @@ -123,6 +125,7 @@ func (ts *SmsProviderTestSuite) TestTwilioSendSms() { MoreInfo: "error", Status: 500, }), + OTP: "123456", ExpectedError: &twilioErrResponse{ Code: 500, Message: "Internal server error", @@ -134,7 +137,7 @@ func (ts *SmsProviderTestSuite) TestTwilioSendSms() { for _, c := range cases { ts.Run(c.Desc, func() { - _, err = twilioProvider.SendSms(phone, message, SMSProvider) + _, err = twilioProvider.SendSms(phone, message, SMSProvider, c.OTP) require.Equal(ts.T(), c.ExpectedError, err) }) } diff --git a/internal/api/sms_provider/textlocal.go b/internal/api/sms_provider/textlocal.go index 78cff912a..f3d5e8de1 100644 --- a/internal/api/sms_provider/textlocal.go +++ b/internal/api/sms_provider/textlocal.go @@ -49,7 +49,7 @@ func NewTextlocalProvider(config conf.TextlocalProviderConfiguration) (SmsProvid }, nil } -func (t *TextlocalProvider) SendMessage(phone string, message string, channel string) (string, error) { +func (t *TextlocalProvider) SendMessage(phone, message, channel, otp string) (string, error) { switch channel { case SMSProvider: return t.SendSms(phone, message) diff --git a/internal/api/sms_provider/twilio.go b/internal/api/sms_provider/twilio.go index ee20b65f2..c90197464 100644 --- a/internal/api/sms_provider/twilio.go +++ b/internal/api/sms_provider/twilio.go @@ -63,31 +63,41 @@ func NewTwilioProvider(config conf.TwilioProviderConfiguration) (SmsProvider, er }, nil } -func (t *TwilioProvider) SendMessage(phone string, message string, channel string) (string, error) { +func (t *TwilioProvider) SendMessage(phone, message, channel, otp string) (string, error) { switch channel { case SMSProvider, WhatsappProvider: - return t.SendSms(phone, message, channel) + return t.SendSms(phone, message, channel, otp) default: return "", fmt.Errorf("channel type %q is not supported for Twilio", channel) } } // Send an SMS containing the OTP with Twilio's API -func (t *TwilioProvider) SendSms(phone, message, channel string) (string, error) { +func (t *TwilioProvider) SendSms(phone, message, channel, otp string) (string, error) { sender := t.Config.MessageServiceSid receiver := "+" + phone - if channel == WhatsappProvider { - receiver = channel + ":" + receiver - if isPhoneNumber.MatchString(formatPhoneNumber(sender)) { - sender = channel + ":" + sender - } - } body := url.Values{ "To": {receiver}, // twilio api requires "+" extension to be included "Channel": {channel}, "From": {sender}, "Body": {message}, } + if channel == WhatsappProvider { + receiver = channel + ":" + receiver + if isPhoneNumber.MatchString(formatPhoneNumber(sender)) { + sender = channel + ":" + sender + } + // Used to substitute OTP. See https://www.twilio.com/docs/content/whatsappauthentication for more details + contentVariables := fmt.Sprintf(`{"1": "%s"}`, otp) + // Programmable Messaging (WhatsApp) takes in different set of inputs + body = url.Values{ + "To": {receiver}, // twilio api requires "+" extension to be included + "Channel": {channel}, + "From": {sender}, + "ContentSid": {t.Config.ContentSid}, + "ContentVariables": {contentVariables}, + } + } client := &http.Client{Timeout: defaultTimeout} r, err := http.NewRequest("POST", t.APIPath, strings.NewReader(body.Encode())) if err != nil { diff --git a/internal/api/sms_provider/twilio_verify.go b/internal/api/sms_provider/twilio_verify.go index 0f9599eee..ba3c5ac94 100644 --- a/internal/api/sms_provider/twilio_verify.go +++ b/internal/api/sms_provider/twilio_verify.go @@ -53,7 +53,7 @@ func NewTwilioVerifyProvider(config conf.TwilioVerifyProviderConfiguration) (Sms }, nil } -func (t *TwilioVerifyProvider) SendMessage(phone string, message string, channel string) (string, error) { +func (t *TwilioVerifyProvider) SendMessage(phone, message, channel, otp string) (string, error) { switch channel { case SMSProvider, WhatsappProvider: return t.SendSms(phone, message, channel) diff --git a/internal/api/sms_provider/vonage.go b/internal/api/sms_provider/vonage.go index a41bc0a13..fc82a3ac2 100644 --- a/internal/api/sms_provider/vonage.go +++ b/internal/api/sms_provider/vonage.go @@ -45,7 +45,7 @@ func NewVonageProvider(config conf.VonageProviderConfiguration) (SmsProvider, er }, nil } -func (t *VonageProvider) SendMessage(phone string, message string, channel string) (string, error) { +func (t *VonageProvider) SendMessage(phone, message, channel, otp string) (string, error) { switch channel { case SMSProvider: return t.SendSms(phone, message) diff --git a/internal/conf/configuration.go b/internal/conf/configuration.go index 32fe877f9..b6563fd02 100644 --- a/internal/conf/configuration.go +++ b/internal/conf/configuration.go @@ -271,6 +271,7 @@ type TwilioProviderConfiguration struct { AccountSid string `json:"account_sid" split_words:"true"` AuthToken string `json:"auth_token" split_words:"true"` MessageServiceSid string `json:"message_service_sid" split_words:"true"` + ContentSid string `json:"content_sid" split_words:"true"` } type TwilioVerifyProviderConfiguration struct {