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

supabase / gotrue / 8370607605

21 Mar 2024 06:24AM UTC coverage: 65.015% (-0.2%) from 65.241%
8370607605

Pull #1474

github

J0
fix: split error codes
Pull Request #1474: feat: add custom sms hook

76 of 169 new or added lines in 12 files covered. (44.97%)

68 existing lines in 1 file now uncovered.

8006 of 12314 relevant lines covered (65.02%)

59.58 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
        "context"
6
        "github.com/supabase/auth/internal/hooks"
7
        "net/http"
8
        "regexp"
9
        "strings"
10
        "text/template"
11
        "time"
12

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

131
func generateSMSFromTemplate(SMSTemplate *template.Template, otp string) (string, error) {
3✔
132
        var message bytes.Buffer
3✔
133
        if err := SMSTemplate.Execute(&message, struct {
3✔
134
                Code string
3✔
135
        }{Code: otp}); err != nil {
3✔
136
                return "", err
×
137
        }
×
138
        return message.String(), nil
3✔
139
}
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