-
Notifications
You must be signed in to change notification settings - Fork 401
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add support for Twilio Verify (#1124)
## What kind of change does this PR introduce? Aims to add Twilio Verify Support. Twilio Verify is implemented as a separate provider. Only one of Twilio Verify or Twilio Programmable messaging an be selected. At this time, we only support the use of the `whatsapp` and `sms` channels with Twilio Verify. This will affect the: 1. Signup flow 2. Verification flow (sms and phone_change) 3. Resend The token is still generated, but not used in the Twilio Verify flow. It is used as a placeholder so as to try to ensure that to the OTP returned by the Verify service can only be used with the corresponding flow it was generated for. ## What is the current behaviour? We support programmable messaging. ## What is the new behaviour? Developer can toggle between using Twilio Programmable Messaging on all flows or Twilio Verify on all flows. ## Additional context Manual tests: Probably need to be conducted on both Phone Change and SMS OTP Verification: - [x] Existing Programmable Messaging (SMS/WhatsApp) (Signup/Verify/PhoneChange) - [x] Twilio Verify(SMS/WhatsApp) - [ ] Update Frontend to include Twilio Verify Admin methods shouldn't need to be updated to send to Twilio Verify since admin methods don't require confirmation --------- Co-authored-by: joel@joellee.org <joel@joellee.org> Co-authored-by: Stojan Dimitrovski <sdimitrovski@gmail.com>
- Loading branch information
1 parent
007324c
commit 7e240f8
Showing
7 changed files
with
265 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
package sms_provider | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"strings" | ||
|
||
"github.com/supabase/gotrue/internal/conf" | ||
"github.com/supabase/gotrue/internal/utilities" | ||
) | ||
|
||
const ( | ||
verifyServiceApiBase = "https://verify.twilio.com/v2/Services/" | ||
) | ||
|
||
type TwilioVerifyProvider struct { | ||
Config *conf.TwilioVerifyProviderConfiguration | ||
APIPath string | ||
} | ||
|
||
type VerificationResponse struct { | ||
To string `json:"to"` | ||
Status string `json:"status"` | ||
Channel string `json:"channel"` | ||
Valid bool `json:"valid"` | ||
VerificationSID string `json:"sid"` | ||
ErrorCode string `json:"error_code"` | ||
ErrorMessage string `json:"error_message"` | ||
} | ||
|
||
// See: https://www.twilio.com/docs/verify/api/verification-check | ||
type VerificationCheckResponse struct { | ||
To string `json:"to"` | ||
Status string `json:"status"` | ||
Channel string `json:"channel"` | ||
Valid bool `json:"valid"` | ||
ErrorCode string `json:"error_code"` | ||
ErrorMessage string `json:"error_message"` | ||
} | ||
|
||
// Creates a SmsProvider with the Twilio Config | ||
func NewTwilioVerifyProvider(config conf.TwilioVerifyProviderConfiguration) (SmsProvider, error) { | ||
if err := config.Validate(); err != nil { | ||
return nil, err | ||
} | ||
apiPath := verifyServiceApiBase + config.MessageServiceSid + "/Verifications" | ||
|
||
return &TwilioVerifyProvider{ | ||
Config: &config, | ||
APIPath: apiPath, | ||
}, nil | ||
} | ||
|
||
func (t *TwilioVerifyProvider) SendMessage(phone string, message string, channel string) (string, error) { | ||
switch channel { | ||
case SMSProvider, WhatsappProvider: | ||
return t.SendSms(phone, message, channel) | ||
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 *TwilioVerifyProvider) SendSms(phone, message, channel string) (string, error) { | ||
// Unlike Programmable Messaging, Verify does not require a prefix for channel | ||
receiver := "+" + phone | ||
body := url.Values{ | ||
"To": {receiver}, | ||
"Channel": {channel}, | ||
} | ||
client := &http.Client{Timeout: defaultTimeout} | ||
r, err := http.NewRequest("POST", t.APIPath, strings.NewReader(body.Encode())) | ||
if err != nil { | ||
return "", err | ||
} | ||
r.Header.Add("Content-Type", "application/x-www-form-urlencoded") | ||
r.SetBasicAuth(t.Config.AccountSid, t.Config.AuthToken) | ||
res, err := client.Do(r) | ||
defer utilities.SafeClose(res.Body) | ||
if err != nil { | ||
return "", err | ||
} | ||
if !(res.StatusCode == http.StatusOK || res.StatusCode == http.StatusCreated) { | ||
resp := &twilioErrResponse{} | ||
if err := json.NewDecoder(res.Body).Decode(resp); err != nil { | ||
return "", err | ||
} | ||
return "", resp | ||
} | ||
|
||
resp := &VerificationResponse{} | ||
derr := json.NewDecoder(res.Body).Decode(resp) | ||
if derr != nil { | ||
return "", derr | ||
} | ||
return resp.VerificationSID, nil | ||
} | ||
|
||
func (t *TwilioVerifyProvider) VerifyOTP(phone, code string) error { | ||
verifyPath := verifyServiceApiBase + t.Config.MessageServiceSid + "/VerificationCheck" | ||
receiver := "+" + phone | ||
|
||
body := url.Values{ | ||
"To": {receiver}, // twilio api requires "+" extension to be included | ||
"Code": {code}, | ||
} | ||
client := &http.Client{Timeout: defaultTimeout} | ||
r, err := http.NewRequest("POST", verifyPath, strings.NewReader(body.Encode())) | ||
if err != nil { | ||
return err | ||
} | ||
r.Header.Add("Content-Type", "application/x-www-form-urlencoded") | ||
r.SetBasicAuth(t.Config.AccountSid, t.Config.AuthToken) | ||
res, err := client.Do(r) | ||
defer utilities.SafeClose(res.Body) | ||
if err != nil { | ||
return err | ||
} | ||
if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusCreated { | ||
resp := &twilioErrResponse{} | ||
if err := json.NewDecoder(res.Body).Decode(resp); err != nil { | ||
return err | ||
} | ||
return resp | ||
} | ||
resp := &VerificationCheckResponse{} | ||
derr := json.NewDecoder(res.Body).Decode(resp) | ||
if derr != nil { | ||
return derr | ||
} | ||
|
||
if resp.Status != "approved" || !resp.Valid { | ||
return fmt.Errorf("twilio verification error: %v %v", resp.ErrorMessage, resp.Status) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters