• 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

52.63
/internal/api/resend.go
1
package api
2

3
import (
4
        "errors"
5
        "net/http"
6
        "time"
7

8
        "github.com/supabase/auth/internal/api/sms_provider"
9
        "github.com/supabase/auth/internal/conf"
10
        "github.com/supabase/auth/internal/models"
11
        "github.com/supabase/auth/internal/storage"
12
        "github.com/supabase/auth/internal/utilities"
13
)
14

15
// ResendConfirmationParams holds the parameters for a resend request
16
type ResendConfirmationParams struct {
17
        Type  string `json:"type"`
18
        Email string `json:"email"`
19
        Phone string `json:"phone"`
20
}
21

22
func (p *ResendConfirmationParams) Validate(config *conf.GlobalConfiguration) error {
8✔
23
        switch p.Type {
8✔
24
        case signupVerification, emailChangeVerification, smsVerification, phoneChangeVerification:
7✔
25
                break
7✔
26
        default:
1✔
27
                // type does not match one of the above
1✔
28
                return badRequestError(ErrorCodeValidationFailed, "Missing one of these types: signup, email_change, sms, phone_change")
1✔
29

30
        }
31
        if p.Email == "" && p.Type == signupVerification {
7✔
32
                return badRequestError(ErrorCodeValidationFailed, "Type provided requires an email address")
×
33
        }
×
34
        if p.Phone == "" && p.Type == smsVerification {
8✔
35
                return badRequestError(ErrorCodeValidationFailed, "Type provided requires a phone number")
1✔
36
        }
1✔
37

38
        var err error
6✔
39
        if p.Email != "" && p.Phone != "" {
7✔
40
                return badRequestError(ErrorCodeValidationFailed, "Only an email address or phone number should be provided.")
1✔
41
        } else if p.Email != "" {
9✔
42
                if !config.External.Email.Enabled {
3✔
43
                        return badRequestError(ErrorCodeEmailProviderDisabled, "Email logins are disabled")
×
44
                }
×
45
                p.Email, err = validateEmail(p.Email)
3✔
46
                if err != nil {
3✔
47
                        return err
×
48
                }
×
49
        } else if p.Phone != "" {
4✔
50
                if !config.External.Phone.Enabled {
2✔
51
                        return badRequestError(ErrorCodePhoneProviderDisabled, "Phone logins are disabled")
×
52
                }
×
53
                p.Phone, err = validatePhone(p.Phone)
2✔
54
                if err != nil {
2✔
55
                        return err
×
56
                }
×
57
        } else {
×
58
                // both email and phone are empty
×
59
                return badRequestError(ErrorCodeValidationFailed, "Missing email address or phone number")
×
60
        }
×
61
        return nil
5✔
62
}
63

64
// Recover sends a recovery email
65
func (a *API) Resend(w http.ResponseWriter, r *http.Request) error {
8✔
66
        ctx := r.Context()
8✔
67
        db := a.db.WithContext(ctx)
8✔
68
        config := a.config
8✔
69
        params := &ResendConfirmationParams{}
8✔
70
        if err := retrieveRequestParams(r, params); err != nil {
8✔
71
                return err
×
72
        }
×
73

74
        if err := params.Validate(config); err != nil {
11✔
75
                return err
3✔
76
        }
3✔
77

78
        var user *models.User
5✔
79
        var err error
5✔
80
        aud := a.requestAud(ctx, r)
5✔
81
        if params.Email != "" {
8✔
82
                user, err = models.FindUserByEmailAndAudience(db, params.Email, aud)
3✔
83
        } else if params.Phone != "" {
7✔
84
                user, err = models.FindUserByPhoneAndAudience(db, params.Phone, aud)
2✔
85
        }
2✔
86

87
        if err != nil {
6✔
88
                if models.IsNotFoundError(err) {
2✔
89
                        return sendJSON(w, http.StatusOK, map[string]string{})
1✔
90
                }
1✔
91
                return internalServerError("Unable to process request").WithInternalError(err)
×
92
        }
93

94
        switch params.Type {
4✔
95
        case signupVerification:
1✔
96
                if user.IsConfirmed() {
1✔
97
                        // if the user's email is confirmed already, we don't need to send a confirmation email again
×
98
                        return sendJSON(w, http.StatusOK, map[string]string{})
×
99
                }
×
100
        case smsVerification:
×
101
                if user.IsPhoneConfirmed() {
×
102
                        // if the user's phone is confirmed already, we don't need to send a confirmation sms again
×
103
                        return sendJSON(w, http.StatusOK, map[string]string{})
×
104
                }
×
105
        case emailChangeVerification:
2✔
106
                // do not resend if user doesn't have a new email address
2✔
107
                if user.EmailChange == "" {
2✔
108
                        return sendJSON(w, http.StatusOK, map[string]string{})
×
109
                }
×
110
        case phoneChangeVerification:
1✔
111
                // do not resend if user doesn't have a new phone number
1✔
112
                if user.PhoneChange == "" {
2✔
113
                        return sendJSON(w, http.StatusOK, map[string]string{})
1✔
114
                }
1✔
115
        }
116

117
        messageID := ""
3✔
118
        mailer := a.Mailer(ctx)
3✔
119
        referrer := utilities.GetReferrer(r, config)
3✔
120
        externalURL := getExternalHost(ctx)
3✔
121
        err = db.Transaction(func(tx *storage.Connection) error {
6✔
122
                switch params.Type {
3✔
123
                case signupVerification:
1✔
124
                        if terr := models.NewAuditLogEntry(r, tx, user, models.UserConfirmationRequestedAction, "", nil); terr != nil {
1✔
125
                                return terr
×
126
                        }
×
127
                        // PKCE not implemented yet
128
                        return sendConfirmation(tx, user, mailer, config.SMTP.MaxFrequency, referrer, externalURL, config.Mailer.OtpLength, models.ImplicitFlow)
1✔
129
                case smsVerification:
×
130
                        if terr := models.NewAuditLogEntry(r, tx, user, models.UserRecoveryRequestedAction, "", nil); terr != nil {
×
131
                                return terr
×
132
                        }
×
133
                        smsProvider, terr := sms_provider.GetSmsProvider(*config)
×
134
                        if terr != nil {
×
135
                                return terr
×
136
                        }
×
NEW
137
                        mID, terr := a.sendPhoneConfirmation(r, tx, user, params.Phone, phoneConfirmationOtp, smsProvider, sms_provider.SMSProvider)
×
138
                        if terr != nil {
×
139
                                return terr
×
140
                        }
×
141
                        messageID = mID
×
142
                case emailChangeVerification:
2✔
143
                        return a.sendEmailChange(tx, config, user, mailer, user.EmailChange, referrer, externalURL, config.Mailer.OtpLength, models.ImplicitFlow)
2✔
144
                case phoneChangeVerification:
×
145
                        smsProvider, terr := sms_provider.GetSmsProvider(*config)
×
146
                        if terr != nil {
×
147
                                return terr
×
148
                        }
×
NEW
149
                        mID, terr := a.sendPhoneConfirmation(r, tx, user, user.PhoneChange, phoneChangeVerification, smsProvider, sms_provider.SMSProvider)
×
150
                        if terr != nil {
×
151
                                return terr
×
152
                        }
×
153
                        messageID = mID
×
154
                }
155
                return nil
×
156
        })
157
        if err != nil {
3✔
158
                if errors.Is(err, MaxFrequencyLimitError) {
×
159
                        reason := ErrorCodeOverEmailSendRateLimit
×
160
                        if params.Type == smsVerification || params.Type == phoneChangeVerification {
×
161
                                reason = ErrorCodeOverSMSSendRateLimit
×
162
                        }
×
163

164
                        until := time.Until(user.ConfirmationSentAt.Add(config.SMTP.MaxFrequency)) / time.Second
×
165
                        return tooManyRequestsError(reason, "For security purposes, you can only request this once every %d seconds.", until)
×
166
                }
167
                return internalServerError("Unable to process request").WithInternalError(err)
×
168
        }
169

170
        ret := map[string]any{}
3✔
171
        if messageID != "" {
3✔
172
                ret["message_id"] = messageID
×
173
        }
×
174

175
        return sendJSON(w, http.StatusOK, ret)
3✔
176
}
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