• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / gotrue / 8337863587

19 Mar 2024 05:08AM UTC coverage: 64.957% (-0.3%) from 65.241%
8337863587

Pull #1474

github

J0
fix: apply suggestions
Pull Request #1474: feat: add custom sms hook

75 of 172 new or added lines in 12 files covered. (43.6%)

25 existing lines in 2 files now uncovered.

7993 of 12305 relevant lines covered (64.96%)

59.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.82
/internal/api/phone.go
1
package api
2

3
import (
4
        "bytes"
5
        "github.com/supabase/auth/internal/hooks"
6
        "net/http"
7
        "regexp"
8
        "strings"
9
        "text/template"
10
        "time"
11

12
        "github.com/pkg/errors"
13
        "github.com/supabase/auth/internal/api/sms_provider"
14
        "github.com/supabase/auth/internal/crypto"
15
        "github.com/supabase/auth/internal/models"
16
        "github.com/supabase/auth/internal/storage"
17
)
18

19
var e164Format = regexp.MustCompile("^[1-9][0-9]{1,14}$")
20

21
const (
22
        phoneConfirmationOtp     = "confirmation"
23
        phoneReauthenticationOtp = "reauthentication"
24
)
25

26
func validatePhone(phone string) (string, error) {
28✔
27
        phone = formatPhoneNumber(phone)
28✔
28
        if isValid := validateE164Format(phone); !isValid {
28✔
UNCOV
29
                return "", badRequestError(ErrorCodeValidationFailed, "Invalid phone number format (E.164 required)")
×
30
        }
×
31
        return phone, nil
28✔
32
}
33

34
// validateE164Format checks if phone number follows the E.164 format
35
func validateE164Format(phone string) bool {
29✔
36
        return e164Format.MatchString(phone)
29✔
37
}
29✔
38

39
// formatPhoneNumber removes "+" and whitespaces in a phone number
40
func formatPhoneNumber(phone string) string {
29✔
41
        return strings.ReplaceAll(strings.TrimPrefix(phone, "+"), " ", "")
29✔
42
}
29✔
43

44
// sendPhoneConfirmation sends an otp to the user's phone number
45
func (a *API) sendPhoneConfirmation(r *http.Request, tx *storage.Connection, user *models.User, phone, otpType string, smsProvider sms_provider.SmsProvider, channel string) (string, error) {
8✔
46
        config := a.config
8✔
47

8✔
48
        var token *string
8✔
49
        var sentAt *time.Time
8✔
50

8✔
51
        includeFields := []string{}
8✔
52
        switch otpType {
8✔
53
        case phoneChangeVerification:
2✔
54
                token = &user.PhoneChangeToken
2✔
55
                sentAt = user.PhoneChangeSentAt
2✔
56
                user.PhoneChange = phone
2✔
57
                includeFields = append(includeFields, "phone_change", "phone_change_token", "phone_change_sent_at")
2✔
58
        case phoneConfirmationOtp:
2✔
59
                token = &user.ConfirmationToken
2✔
60
                sentAt = user.ConfirmationSentAt
2✔
61
                includeFields = append(includeFields, "confirmation_token", "confirmation_sent_at")
2✔
62
        case phoneReauthenticationOtp:
2✔
63
                token = &user.ReauthenticationToken
2✔
64
                sentAt = user.ReauthenticationSentAt
2✔
65
                includeFields = append(includeFields, "reauthentication_token", "reauthentication_sent_at")
2✔
66
        default:
2✔
67
                return "", internalServerError("invalid otp type")
2✔
68
        }
69

70
        // intentionally keeping this before the test OTP, so that the behavior
71
        // of regular and test OTPs is similar
72
        if sentAt != nil && !sentAt.Add(config.Sms.MaxFrequency).Before(time.Now()) {
6✔
UNCOV
73
                return "", MaxFrequencyLimitError
×
74
        }
×
75

76
        now := time.Now()
6✔
77

6✔
78
        var otp, messageID string
6✔
79
        var err error
6✔
80

6✔
81
        if testOTP, ok := config.Sms.GetTestOTP(phone, now); ok {
9✔
82
                otp = testOTP
3✔
83
                messageID = "test-otp"
3✔
84
        }
3✔
85

86
        if otp == "" { // not using test OTPs
9✔
87
                otp, err = crypto.GenerateOtp(config.Sms.OtpLength)
3✔
88
                if err != nil {
3✔
UNCOV
89
                        return "", internalServerError("error generating otp").WithInternalError(err)
×
90
                }
×
91

92
                message, err := generateSMSFromTemplate(config.Sms.SMSTemplate, otp)
3✔
93
                if err != nil {
3✔
UNCOV
94
                        return "", err
×
95
                }
×
96
                if config.Hook.CustomSMSProvider.Enabled {
3✔
NEW
97
                        input := hooks.CustomSMSProviderInput{
×
NEW
98
                                UserID: user.ID,
×
NEW
99
                                Phone:  user.Phone.String(),
×
NEW
100
                                OTP:    otp,
×
NEW
101
                        }
×
NEW
102
                        output := hooks.CustomSMSProviderOutput{}
×
NEW
103
                        err := a.invokeHTTPHook(r, &input, &output, config.Hook.CustomSMSProvider.URI)
×
NEW
104
                        if err != nil {
×
NEW
105
                                return "", err
×
NEW
106
                        }
×
107
                } else {
3✔
108

3✔
109
                        messageID, err = smsProvider.SendMessage(phone, message, channel, otp)
3✔
110
                        if err != nil {
3✔
NEW
111
                                return messageID, err
×
NEW
112
                        }
×
113
                }
114
        }
115

116
        *token = crypto.GenerateTokenHash(phone, otp)
6✔
117

6✔
118
        switch otpType {
6✔
119
        case phoneConfirmationOtp:
2✔
120
                user.ConfirmationSentAt = &now
2✔
121
        case phoneChangeVerification:
2✔
122
                user.PhoneChangeSentAt = &now
2✔
123
        case phoneReauthenticationOtp:
2✔
124
                user.ReauthenticationSentAt = &now
2✔
125
        }
126

127
        return messageID, errors.Wrap(tx.UpdateOnly(user, includeFields...), "Database error updating user for confirmation")
6✔
128
}
129

130
func generateSMSFromTemplate(SMSTemplate *template.Template, otp string) (string, error) {
3✔
131
        var message bytes.Buffer
3✔
132
        if err := SMSTemplate.Execute(&message, struct {
3✔
133
                Code string
3✔
134
        }{Code: otp}); err != nil {
3✔
UNCOV
135
                return "", err
×
136
        }
×
137
        return message.String(), nil
3✔
138
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc