feat(user): add email check endpoint for registered user detection

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-06-11 22:08:22 +01:00
co-authored by Sisyphus
parent b750b3c203
commit 6de0371e1d
2 changed files with 222 additions and 0 deletions
+59
View File
@@ -1,7 +1,9 @@
package user
import (
"database/sql"
"encoding/json"
"errors"
"log"
"net/http"
"net/mail"
@@ -126,3 +128,60 @@ func CreateGuestUserHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(CreateGuestUserResponse{ID: userID, Role: "guest"})
}
// GET /api/check-email?email=...&firstName=...&lastName=...&phone=...
// Returns { suggestion: "login" | "check" | null } based on whether the email
// belongs to a registered user and how closely the provided details match.
// Relies on nginx restricting access to frontend-only traffic.
func CheckEmailHandler(w http.ResponseWriter, r *http.Request) {
email := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("email")))
if email == "" {
http.Error(w, "email query parameter required", http.StatusBadRequest)
return
}
if _, err := mail.ParseAddress(email); err != nil {
http.Error(w, "invalid email format", http.StatusBadRequest)
return
}
firstName := strings.TrimSpace(r.URL.Query().Get("firstName"))
lastName := strings.TrimSpace(r.URL.Query().Get("lastName"))
phone := strings.TrimSpace(r.URL.Query().Get("phone"))
var dbFirstName, dbLastName, dbPhone *string
err := db.DB.QueryRow(r.Context(), `
SELECT n_first_name, n_last_name, phone
FROM users
WHERE email = $1 AND account_role != 'guest'
`, email).Scan(&dbFirstName, &dbLastName, &dbPhone)
var suggestion *string
if err == nil {
matchesNames := firstName != "" && lastName != "" &&
dbFirstName != nil && dbLastName != nil &&
strings.EqualFold(firstName, *dbFirstName) &&
strings.EqualFold(lastName, *dbLastName)
matchesPhone := phone != "" &&
dbPhone != nil &&
phone == *dbPhone
if matchesNames && matchesPhone {
s := "login"
suggestion = &s
} else {
s := "check"
suggestion = &s
}
} else if !errors.Is(err, sql.ErrNoRows) {
log.Printf("Failed to check email: %v", err)
http.Error(w, "database error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"suggestion": suggestion,
})
}