Stop reporting a failed login lookup as "Invalid Email"
GetUserByAuthname / GetUserByContactNo / GetUserLogin discarded the Scan error, so a database that could not answer — down, pool exhausted, or booted without its config (2026-07-20) — came back as uid 0 and every user was told their email was wrong. One lookup, GetUserLogin, now returns an error; sql.ErrNoRows is "not found" and anything else reaches the service, which answers 500 "Login is temporarily unavailable" and logs the cause. 409 "Invalid Email" is unchanged for a genuine no-match: the console reads that exact shape as "not registered". NULL password/role columns scan through sql.Null* so they do not become 500s. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"nearle/models"
|
||||
"nearle/repositories"
|
||||
"strings"
|
||||
@@ -9,6 +10,48 @@ import (
|
||||
"github.com/gofiber/fiber"
|
||||
)
|
||||
|
||||
// errLoginUnavailable is returned by lookupLogin when the database could not
|
||||
// answer the sign-in query. It is deliberately not "invalid user": the account
|
||||
// may well exist, and the person needs to be told to try again, not to check
|
||||
// their spelling.
|
||||
var errLoginUnavailable = errors.New("login lookup failed")
|
||||
|
||||
// loginUnavailableResponse is the body for that case. 500 rather than 409,
|
||||
// because the console reads `409` as "this email does not exist" (see
|
||||
// daily_merchant_web/src/services/auth.ts) and would otherwise send a
|
||||
// perfectly good account to the sign-up form while the database was down.
|
||||
func loginUnavailableResponse() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"status": false,
|
||||
"code": 500,
|
||||
"message": "Login is temporarily unavailable. Please try again in a moment.",
|
||||
}
|
||||
}
|
||||
|
||||
// lookupLogin resolves who is signing in, by authname first and contact number
|
||||
// second, and separates "not found" from "could not look".
|
||||
//
|
||||
// Both login paths used to run their own copy of this and both discarded the
|
||||
// repository's error, so a database that was down came back as uid 0 and was
|
||||
// reported as "Invalid Email". That message now means exactly one thing: the
|
||||
// query ran and matched nobody.
|
||||
func (s *userService) lookupLogin(user models.User) (uid int, password, status string, roleid int, err error) {
|
||||
field, value := "authname", user.Authname
|
||||
if user.Authname == "" {
|
||||
field, value = "contactno", user.Contactno
|
||||
}
|
||||
|
||||
uid, password, status, roleid, err = s.repo.GetUserLogin(field, value, user.Configid)
|
||||
if err != nil {
|
||||
// The value is what the caller typed — an email or a phone number —
|
||||
// and is safe to log; the password never reaches this function's
|
||||
// output.
|
||||
log.Printf("login: lookup by %s=%q configid=%d failed: %v", field, value, user.Configid, err)
|
||||
return 0, "", "", 0, errLoginUnavailable
|
||||
}
|
||||
return uid, password, status, roleid, nil
|
||||
}
|
||||
|
||||
type UserService interface {
|
||||
GetAllUsers(roleID, tenantID, pageno, pagesize int, keyword string) ([]models.UserInfo, error)
|
||||
GetUserByID(uid int) (models.UserInfo, error)
|
||||
@@ -80,15 +123,7 @@ func (s *userService) UpdateStaff(user models.User) error {
|
||||
}
|
||||
|
||||
func (s *userService) AppLogin(user models.User) (models.TenantUserInfo, fiber.Map, error) {
|
||||
var uid int
|
||||
var status, dbPassword string
|
||||
|
||||
// Get user by authname or contactno
|
||||
if user.Authname != "" {
|
||||
uid, dbPassword, status = s.repo.GetUserByAuthname(user.Authname, user.Configid)
|
||||
} else if user.Contactno != "" {
|
||||
uid, dbPassword, status = s.repo.GetUserByContactNo(user.Contactno, user.Configid)
|
||||
} else {
|
||||
if user.Authname == "" && user.Contactno == "" {
|
||||
resp := fiber.Map{
|
||||
"code": 400,
|
||||
"status": false,
|
||||
@@ -97,7 +132,12 @@ func (s *userService) AppLogin(user models.User) (models.TenantUserInfo, fiber.M
|
||||
return models.TenantUserInfo{}, resp, errors.New("missing authname or contactno")
|
||||
}
|
||||
|
||||
// Invalid user
|
||||
uid, dbPassword, status, _, err := s.lookupLogin(user)
|
||||
if err != nil {
|
||||
return models.TenantUserInfo{}, fiber.Map(loginUnavailableResponse()), err
|
||||
}
|
||||
|
||||
// Nobody matched. This is the only way to reach "Invalid Email" now.
|
||||
if uid == 0 {
|
||||
resp := fiber.Map{
|
||||
"status": false,
|
||||
@@ -212,14 +252,8 @@ func (s *userService) CreateUser(user models.User) (models.UserInfo, error) {
|
||||
func (s *userService) TenantWebLogin(user models.User) (models.TenantUserInfo, map[string]interface{}) {
|
||||
tenantFormExists := true
|
||||
|
||||
uid, dbPassword, status, roleid := 0, "", "", 0
|
||||
|
||||
// Step 1: Login by authname or contactno
|
||||
if user.Authname != "" {
|
||||
uid, dbPassword, status, roleid = s.repo.GetUserLogin("authname", user.Authname, user.Configid)
|
||||
} else if user.Contactno != "" {
|
||||
uid, dbPassword, status, roleid = s.repo.GetUserLogin("contactno", user.Contactno, user.Configid)
|
||||
} else {
|
||||
if user.Authname == "" && user.Contactno == "" {
|
||||
return models.TenantUserInfo{}, map[string]interface{}{
|
||||
"status": true,
|
||||
"code": 400,
|
||||
@@ -227,7 +261,13 @@ func (s *userService) TenantWebLogin(user models.User) (models.TenantUserInfo, m
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Validate user
|
||||
uid, dbPassword, status, roleid, err := s.lookupLogin(user)
|
||||
if err != nil {
|
||||
return models.TenantUserInfo{}, loginUnavailableResponse()
|
||||
}
|
||||
|
||||
// Step 2: Validate user. Nobody matched — the only way to reach
|
||||
// "Invalid Email" now; a database that could not answer is a 500 above.
|
||||
if uid == 0 {
|
||||
return models.TenantUserInfo{}, map[string]interface{}{
|
||||
"status": false,
|
||||
|
||||
Reference in New Issue
Block a user