-
Notifications
You must be signed in to change notification settings - Fork 379
/
resend.go
154 lines (139 loc) · 4.62 KB
/
resend.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package api
import (
"net/http"
"github.com/supabase/auth/internal/api/sms_provider"
mail "github.com/supabase/auth/internal/mailer"
"github.com/supabase/auth/internal/models"
"github.com/supabase/auth/internal/storage"
)
// ResendConfirmationParams holds the parameters for a resend request
type ResendConfirmationParams struct {
Type string `json:"type"`
Email string `json:"email"`
Phone string `json:"phone"`
}
func (p *ResendConfirmationParams) Validate(a *API) error {
config := a.config
switch p.Type {
case mail.SignupVerification, mail.EmailChangeVerification, smsVerification, phoneChangeVerification:
break
default:
// type does not match one of the above
return badRequestError(ErrorCodeValidationFailed, "Missing one of these types: signup, email_change, sms, phone_change")
}
if p.Email == "" && p.Type == mail.SignupVerification {
return badRequestError(ErrorCodeValidationFailed, "Type provided requires an email address")
}
if p.Phone == "" && p.Type == smsVerification {
return badRequestError(ErrorCodeValidationFailed, "Type provided requires a phone number")
}
var err error
if p.Email != "" && p.Phone != "" {
return badRequestError(ErrorCodeValidationFailed, "Only an email address or phone number should be provided.")
} else if p.Email != "" {
if !config.External.Email.Enabled {
return badRequestError(ErrorCodeEmailProviderDisabled, "Email logins are disabled")
}
p.Email, err = a.validateEmail(p.Email)
if err != nil {
return err
}
} else if p.Phone != "" {
if !config.External.Phone.Enabled {
return badRequestError(ErrorCodePhoneProviderDisabled, "Phone logins are disabled")
}
p.Phone, err = validatePhone(p.Phone)
if err != nil {
return err
}
} else {
// both email and phone are empty
return badRequestError(ErrorCodeValidationFailed, "Missing email address or phone number")
}
return nil
}
// Recover sends a recovery email
func (a *API) Resend(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
db := a.db.WithContext(ctx)
params := &ResendConfirmationParams{}
if err := retrieveRequestParams(r, params); err != nil {
return err
}
if err := params.Validate(a); err != nil {
return err
}
var user *models.User
var err error
aud := a.requestAud(ctx, r)
if params.Email != "" {
user, err = models.FindUserByEmailAndAudience(db, params.Email, aud)
} else if params.Phone != "" {
user, err = models.FindUserByPhoneAndAudience(db, params.Phone, aud)
}
if err != nil {
if models.IsNotFoundError(err) {
return sendJSON(w, http.StatusOK, map[string]string{})
}
return internalServerError("Unable to process request").WithInternalError(err)
}
switch params.Type {
case mail.SignupVerification:
if user.IsConfirmed() {
// if the user's email is confirmed already, we don't need to send a confirmation email again
return sendJSON(w, http.StatusOK, map[string]string{})
}
case smsVerification:
if user.IsPhoneConfirmed() {
// if the user's phone is confirmed already, we don't need to send a confirmation sms again
return sendJSON(w, http.StatusOK, map[string]string{})
}
case mail.EmailChangeVerification:
// do not resend if user doesn't have a new email address
if user.EmailChange == "" {
return sendJSON(w, http.StatusOK, map[string]string{})
}
case phoneChangeVerification:
// do not resend if user doesn't have a new phone number
if user.PhoneChange == "" {
return sendJSON(w, http.StatusOK, map[string]string{})
}
}
messageID := ""
err = db.Transaction(func(tx *storage.Connection) error {
switch params.Type {
case mail.SignupVerification:
if terr := models.NewAuditLogEntry(r, tx, user, models.UserConfirmationRequestedAction, "", nil); terr != nil {
return terr
}
// PKCE not implemented yet
return a.sendConfirmation(r, tx, user, models.ImplicitFlow)
case smsVerification:
if terr := models.NewAuditLogEntry(r, tx, user, models.UserRecoveryRequestedAction, "", nil); terr != nil {
return terr
}
mID, terr := a.sendPhoneConfirmation(r, tx, user, params.Phone, phoneConfirmationOtp, sms_provider.SMSProvider)
if terr != nil {
return terr
}
messageID = mID
case mail.EmailChangeVerification:
return a.sendEmailChange(r, tx, user, user.EmailChange, models.ImplicitFlow)
case phoneChangeVerification:
mID, terr := a.sendPhoneConfirmation(r, tx, user, user.PhoneChange, phoneChangeVerification, sms_provider.SMSProvider)
if terr != nil {
return terr
}
messageID = mID
}
return nil
})
if err != nil {
return err
}
ret := map[string]any{}
if messageID != "" {
ret["message_id"] = messageID
}
return sendJSON(w, http.StatusOK, ret)
}