Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions internal/api/recover.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,14 @@ func (a *API) Recover(w http.ResponseWriter, r *http.Request) error {
return a.sendPasswordRecovery(r, tx, user, flowType)
})
if err != nil {
return err
// Prevent account enumeration: if sending password recovery fails because the email address
// is invalid or rejected by the mailer, return HTTP 200 OK to match the non-existent user path.
if httpErr, ok := err.(*apierrors.HTTPError); ok && httpErr.ErrorCode == apierrors.ErrorCodeEmailAddressInvalid {
Comment thread
guptamilind0099 marked this conversation as resolved.
return sendJSON(w, http.StatusOK, map[string]string{})
}
// Return all other errors (rate limits 429, DB transaction errors 500, etc.)
return err
}

return sendJSON(w, http.StatusOK, map[string]string{})
}
}
21 changes: 21 additions & 0 deletions internal/api/recover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,24 @@ func (ts *RecoverTestSuite) TestRecover_NoSideChannelLeak() {
ts.API.handler.ServeHTTP(w, req)
assert.Equal(ts.T(), http.StatusOK, w.Code)
}

func (ts *RecoverTestSuite) TestRecover_EmailAddressInvalidNoLeak() {
email := "test@example.com"

// Request body
var buffer bytes.Buffer
require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{
"email": email,
}))

// Setup request
req := httptest.NewRequest(http.MethodPost, "http://localhost/recover", &buffer)
req.Header.Set("Content-Type", "application/json")

// Setup response recorder
w := httptest.NewRecorder()
ts.API.handler.ServeHTTP(w, req)

// Both existing and missing users should return 200 OK without leaking account existence
assert.Equal(ts.T(), http.StatusOK, w.Code)
}