diff --git a/README.md b/README.md index 1c024142a..73d67f67e 100644 --- a/README.md +++ b/README.md @@ -458,7 +458,7 @@ Controls the number of digits of the sms otp sent. `SMS_PROVIDER` - `string` -Available options are: `twilio`, `messagebird`, and `vonage` +Available options are: `twilio`, `messagebird`, `textlocal`, and `vonage` Then you can use your [twilio credentials](https://www.twilio.com/docs/usage/requests-to-twilio#credentials): diff --git a/api/sms_provider/sms_provider.go b/api/sms_provider/sms_provider.go index 5ad55df9d..11b3b3f2b 100644 --- a/api/sms_provider/sms_provider.go +++ b/api/sms_provider/sms_provider.go @@ -16,6 +16,8 @@ func GetSmsProvider(config conf.Configuration) (SmsProvider, error) { return NewTwilioProvider(config.Sms.Twilio) case "messagebird": return NewMessagebirdProvider(config.Sms.Messagebird) + case "textlocal": + return NewTextlocalProvider(config.Sms.Textlocal) case "vonage": return NewVonageProvider(config.Sms.Vonage) default: diff --git a/api/sms_provider/textlocal.go b/api/sms_provider/textlocal.go new file mode 100644 index 000000000..c45cab703 --- /dev/null +++ b/api/sms_provider/textlocal.go @@ -0,0 +1,83 @@ +package sms_provider + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "strings" + + "github.com/netlify/gotrue/conf" +) + +const ( + defaultTextLocalApiBase = "https://api.textlocal.in" +) + +type TextlocalProvider struct { + Config *conf.TextlocalProviderConfiguration + APIPath string +} + +type TextlocalError struct { + Code int `json:"code"` + Message string `json:"message"` +} + +type TextlocalResponse struct { + Status string `json:"status"` + Errors []TextlocalError `json:"errors"` +} + +// Creates a SmsProvider with the Textlocal Config +func NewTextlocalProvider(config conf.TextlocalProviderConfiguration) (SmsProvider, error) { + if err := config.Validate(); err != nil { + return nil, err + } + + apiPath := defaultTextLocalApiBase + "/send" + return &TextlocalProvider{ + Config: &config, + APIPath: apiPath, + }, nil +} + +// Send an SMS containing the OTP with Textlocal's API +func (t TextlocalProvider) SendSms(phone string, message string) error { + body := url.Values{ + "sender": {t.Config.Sender}, + "apikey": {t.Config.ApiKey}, + "message": {message}, + "numbers": {phone}, + } + + client := &http.Client{} + 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") + res, err := client.Do(r) + if err != nil { + return err + } + defer res.Body.Close() + + resp := &TextlocalResponse{} + derr := json.NewDecoder(res.Body).Decode(resp) + if derr != nil { + return derr + } + + if len(resp.Errors) == 0 { + return errors.New("Textlocal error: Internal Error") + } + + if resp.Status != "success" { + return fmt.Errorf("Textlocal error: %v (code: %v)", resp.Errors[0].Message, resp.Errors[0].Code) + } + + return nil +} diff --git a/conf/configuration.go b/conf/configuration.go index f617ebd06..f30ed282a 100644 --- a/conf/configuration.go +++ b/conf/configuration.go @@ -132,6 +132,7 @@ type SmsProviderConfiguration struct { Template string `json:"template"` Twilio TwilioProviderConfiguration `json:"twilio"` Messagebird MessagebirdProviderConfiguration `json:"messagebird"` + Textlocal TextlocalProviderConfiguration `json:"textlocal"` Vonage VonageProviderConfiguration `json:"vonage"` } @@ -146,6 +147,11 @@ type MessagebirdProviderConfiguration struct { Originator string `json:"originator" split_words:"true"` } +type TextlocalProviderConfiguration struct { + ApiKey string `json:"api_key" split_words:"true"` + Sender string `json:"sender" split_words:"true"` +} + type VonageProviderConfiguration struct { ApiKey string `json:"api_key" split_words:"true"` ApiSecret string `json:"api_secret" split_words:"true"` @@ -375,6 +381,16 @@ func (t *MessagebirdProviderConfiguration) Validate() error { return nil } +func (t *TextlocalProviderConfiguration) Validate() error { + if t.ApiKey == "" { + return errors.New("Missing Textlocal API key") + } + if t.Sender == "" { + return errors.New("Missing Textlocal sender") + } + return nil +} + func (t *VonageProviderConfiguration) Validate() error { if t.ApiKey == "" { return errors.New("Missing Vonage API key") @@ -385,6 +401,5 @@ func (t *VonageProviderConfiguration) Validate() error { if t.From == "" { return errors.New("Missing Vonage 'from' parameter") } - return nil } diff --git a/example.env b/example.env index e33fd8eb9..60f009c9e 100644 --- a/example.env +++ b/example.env @@ -149,6 +149,8 @@ GOTRUE_SMS_TWILIO_MESSAGE_SERVICE_SID="" GOTRUE_SMS_TEMPLATE="This is from supabase. Your code is {{ .Code }} ." GOTRUE_SMS_MESSAGEBIRD_ACCESS_KEY="" GOTRUE_SMS_MESSAGEBIRD_ORIGINATOR="" +GOTRUE_SMS_TEXTLOCAL_API_KEY="" +GOTRUE_SMS_TEXTLOCAL_SENDER="" GOTRUE_SMS_VONAGE_API_KEY="" GOTRUE_SMS_VONAGE_API_SECRET="" GOTRUE_SMS_VONAGE_FROM=""