Skip to content

Commit e5f98cb

Browse files
kangmingtayJ0
andauthored
fix: sms verify should update is_anonymous field (#1580)
## What kind of change does this PR introduce? * verifying the phone number of a user should update the `is_anonymous` field to false * add test to prevent any future regression --------- Co-authored-by: Joel Lee <[email protected]>
1 parent ed2b490 commit e5f98cb

File tree

2 files changed

+113
-59
lines changed

2 files changed

+113
-59
lines changed

internal/api/anonymous_test.go

Lines changed: 106 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/stretchr/testify/require"
1313
"github.com/stretchr/testify/suite"
1414
"github.com/supabase/auth/internal/conf"
15+
mail "github.com/supabase/auth/internal/mailer"
1516
"github.com/supabase/auth/internal/models"
1617
)
1718

@@ -77,66 +78,112 @@ func (ts *AnonymousTestSuite) TestAnonymousLogins() {
7778

7879
func (ts *AnonymousTestSuite) TestConvertAnonymousUserToPermanent() {
7980
ts.Config.External.AnonymousUsers.Enabled = true
80-
// Request body
81-
var buffer bytes.Buffer
82-
require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{}))
83-
84-
req := httptest.NewRequest(http.MethodPost, "/signup", &buffer)
85-
req.Header.Set("Content-Type", "application/json")
86-
87-
w := httptest.NewRecorder()
88-
89-
ts.API.handler.ServeHTTP(w, req)
90-
require.Equal(ts.T(), http.StatusOK, w.Code)
91-
92-
signupResponse := &AccessTokenResponse{}
93-
require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&signupResponse))
94-
95-
// Add email to anonymous user
96-
require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{
97-
"email": "[email protected]",
98-
}))
99-
100-
req = httptest.NewRequest(http.MethodPut, "/user", &buffer)
101-
req.Header.Set("Content-Type", "application/json")
102-
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", signupResponse.Token))
103-
104-
w = httptest.NewRecorder()
105-
ts.API.handler.ServeHTTP(w, req)
106-
require.Equal(ts.T(), http.StatusOK, w.Code)
107-
108-
// Check if anonymous user is still anonymous
109-
user, err := models.FindUserByID(ts.API.db, signupResponse.User.ID)
110-
require.NoError(ts.T(), err)
111-
require.NotEmpty(ts.T(), user)
112-
require.True(ts.T(), user.IsAnonymous)
113-
114-
// Verify email change
115-
require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{
116-
"token_hash": user.EmailChangeTokenNew,
117-
"type": "email_change",
118-
}))
119-
120-
req = httptest.NewRequest(http.MethodPost, "/verify", &buffer)
121-
req.Header.Set("Content-Type", "application/json")
122-
123-
w = httptest.NewRecorder()
124-
ts.API.handler.ServeHTTP(w, req)
125-
require.Equal(ts.T(), http.StatusOK, w.Code)
126-
127-
data := &AccessTokenResponse{}
128-
require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))
129-
130-
// User is a permanent user and not anonymous anymore
131-
assert.Equal(ts.T(), signupResponse.User.ID, data.User.ID)
132-
assert.Equal(ts.T(), ts.Config.JWT.Aud, data.User.Aud)
133-
assert.Equal(ts.T(), "[email protected]", data.User.GetEmail())
134-
assert.Equal(ts.T(), models.JSONMap(models.JSONMap{"provider": "email", "providers": []interface{}{"email"}}), data.User.AppMetaData)
135-
assert.False(ts.T(), data.User.IsAnonymous)
136-
assert.NotEmpty(ts.T(), data.User.EmailConfirmedAt)
81+
ts.Config.Sms.TestOTP = map[string]string{"1234567890": "000000"}
82+
// test OTPs still require setting up an sms provider
83+
ts.Config.Sms.Provider = "twilio"
84+
ts.Config.Sms.Twilio.AccountSid = "fake-sid"
85+
ts.Config.Sms.Twilio.AuthToken = "fake-token"
86+
ts.Config.Sms.Twilio.MessageServiceSid = "fake-message-service-sid"
87+
88+
cases := []struct {
89+
desc string
90+
body map[string]interface{}
91+
verificationType string
92+
}{
93+
{
94+
desc: "convert anonymous user to permanent user with email",
95+
body: map[string]interface{}{
96+
"email": "[email protected]",
97+
},
98+
verificationType: "email_change",
99+
},
100+
{
101+
desc: "convert anonymous user to permanent user with phone",
102+
body: map[string]interface{}{
103+
"phone": "1234567890",
104+
},
105+
verificationType: "phone_change",
106+
},
107+
}
137108

138-
// User should have an email identity
139-
assert.Len(ts.T(), data.User.Identities, 1)
109+
for _, c := range cases {
110+
ts.Run(c.desc, func() {
111+
// Request body
112+
var buffer bytes.Buffer
113+
require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{}))
114+
115+
req := httptest.NewRequest(http.MethodPost, "/signup", &buffer)
116+
req.Header.Set("Content-Type", "application/json")
117+
118+
w := httptest.NewRecorder()
119+
120+
ts.API.handler.ServeHTTP(w, req)
121+
require.Equal(ts.T(), http.StatusOK, w.Code)
122+
123+
signupResponse := &AccessTokenResponse{}
124+
require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&signupResponse))
125+
126+
// Add email to anonymous user
127+
require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(c.body))
128+
129+
req = httptest.NewRequest(http.MethodPut, "/user", &buffer)
130+
req.Header.Set("Content-Type", "application/json")
131+
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", signupResponse.Token))
132+
133+
w = httptest.NewRecorder()
134+
ts.API.handler.ServeHTTP(w, req)
135+
require.Equal(ts.T(), http.StatusOK, w.Code)
136+
137+
// Check if anonymous user is still anonymous
138+
user, err := models.FindUserByID(ts.API.db, signupResponse.User.ID)
139+
require.NoError(ts.T(), err)
140+
require.NotEmpty(ts.T(), user)
141+
require.True(ts.T(), user.IsAnonymous)
142+
143+
switch c.verificationType {
144+
case mail.EmailChangeVerification:
145+
require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{
146+
"token_hash": user.EmailChangeTokenNew,
147+
"type": c.verificationType,
148+
}))
149+
case phoneChangeVerification:
150+
require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{
151+
"phone": "1234567890",
152+
"token": "000000",
153+
"type": c.verificationType,
154+
}))
155+
}
156+
157+
req = httptest.NewRequest(http.MethodPost, "/verify", &buffer)
158+
req.Header.Set("Content-Type", "application/json")
159+
160+
w = httptest.NewRecorder()
161+
ts.API.handler.ServeHTTP(w, req)
162+
require.Equal(ts.T(), http.StatusOK, w.Code)
163+
164+
data := &AccessTokenResponse{}
165+
require.NoError(ts.T(), json.NewDecoder(w.Body).Decode(&data))
166+
167+
// User is a permanent user and not anonymous anymore
168+
assert.Equal(ts.T(), signupResponse.User.ID, data.User.ID)
169+
assert.Equal(ts.T(), ts.Config.JWT.Aud, data.User.Aud)
170+
assert.False(ts.T(), data.User.IsAnonymous)
171+
172+
// User should have an identity
173+
assert.Len(ts.T(), data.User.Identities, 1)
174+
175+
switch c.verificationType {
176+
case mail.EmailChangeVerification:
177+
assert.Equal(ts.T(), "[email protected]", data.User.GetEmail())
178+
assert.Equal(ts.T(), models.JSONMap(models.JSONMap{"provider": "email", "providers": []interface{}{"email"}}), data.User.AppMetaData)
179+
assert.NotEmpty(ts.T(), data.User.EmailConfirmedAt)
180+
case phoneChangeVerification:
181+
assert.Equal(ts.T(), "1234567890", data.User.GetPhone())
182+
assert.Equal(ts.T(), models.JSONMap(models.JSONMap{"provider": "phone", "providers": []interface{}{"phone"}}), data.User.AppMetaData)
183+
assert.NotEmpty(ts.T(), data.User.PhoneConfirmedAt)
184+
}
185+
})
186+
}
140187
}
141188

142189
func (ts *AnonymousTestSuite) TestRateLimitAnonymousSignups() {

internal/api/verify.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,13 @@ func (a *API) smsVerify(r *http.Request, conn *storage.Connection, user *models.
406406
}
407407
}
408408

409+
if user.IsAnonymous {
410+
user.IsAnonymous = false
411+
if terr := tx.UpdateOnly(user, "is_anonymous"); terr != nil {
412+
return terr
413+
}
414+
}
415+
409416
if terr := tx.Load(user, "Identities"); terr != nil {
410417
return internalServerError("Error refetching identities").WithInternalError(terr)
411418
}

0 commit comments

Comments
 (0)