login bug
This commit is contained in:
46
services/newAccount.go
Normal file
46
services/newAccount.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"nearle/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The console's default configuration id.
|
||||||
|
//
|
||||||
|
// `weblogin` filters on it — `WHERE authname = ? AND configid = ?` — so an
|
||||||
|
// account carrying 0 is invisible to the sign-in query however correct
|
||||||
|
// everything else about it is.
|
||||||
|
const ConsoleConfigID = 1
|
||||||
|
|
||||||
|
// PrepareNewAccount fills in the two fields without which an account cannot
|
||||||
|
// sign in.
|
||||||
|
//
|
||||||
|
// Both were left to whatever the caller sent, and the caller is a form that has
|
||||||
|
// no reason to know about either. The result was an account that is created
|
||||||
|
// successfully, appears in every list, has the right name, email, role and
|
||||||
|
// branch — and is refused at the login screen with "we do not recognise that
|
||||||
|
// email", because:
|
||||||
|
//
|
||||||
|
// - **authname** is what `GetUserLogin` matches on. An account with an email
|
||||||
|
// and no authname is unreachable: nothing on the sign-in path ever looks at
|
||||||
|
// the email column.
|
||||||
|
// - **configid** is ANDed into the same query. Zero matches no console
|
||||||
|
// account, and zero is what an omitted field arrives as.
|
||||||
|
//
|
||||||
|
// Observed 2026-09-01: a merchant added their own administrator through the
|
||||||
|
// console, got a green confirmation, and could not sign in as them.
|
||||||
|
//
|
||||||
|
// Neither value is ever overwritten. A caller that supplies its own authname —
|
||||||
|
// somebody whose sign-in name is not their email — keeps it.
|
||||||
|
func PrepareNewAccount(user models.User) models.User {
|
||||||
|
if strings.TrimSpace(user.Authname) == "" {
|
||||||
|
// The email IS the sign-in name everywhere in this console; the form
|
||||||
|
// even labels it "This is how they sign in".
|
||||||
|
user.Authname = strings.TrimSpace(user.Email)
|
||||||
|
}
|
||||||
|
if user.Configid == 0 {
|
||||||
|
user.Configid = ConsoleConfigID
|
||||||
|
}
|
||||||
|
return user
|
||||||
|
}
|
||||||
64
services/newAccount_test.go
Normal file
64
services/newAccount_test.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"nearle/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
An account that is created perfectly and cannot sign in.
|
||||||
|
|
||||||
|
`weblogin` matches `WHERE authname = ? AND configid = ?`. A form that sends an
|
||||||
|
email and a name — which is every form in this console — produces a row with an
|
||||||
|
empty authname and a configid of 0, so the sign-in query matches nothing and
|
||||||
|
answers "we do not recognise that email".
|
||||||
|
|
||||||
|
Observed on 1 Sep 2026: a merchant added their own administrator through the
|
||||||
|
console, saw it succeed, saw it listed, and was refused at the login screen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
func TestANewAccountCanActuallySignIn(t *testing.T) {
|
||||||
|
ready := PrepareNewAccount(models.User{Email: "thiruomart@gmail.com"})
|
||||||
|
|
||||||
|
if ready.Authname != "thiruomart@gmail.com" {
|
||||||
|
t.Errorf("authname was not derived from the email: %q", ready.Authname)
|
||||||
|
}
|
||||||
|
if ready.Configid != ConsoleConfigID {
|
||||||
|
t.Errorf("configid = %d, want %d — 0 matches no console account", ready.Configid, ConsoleConfigID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A caller with its own sign-in name keeps it. Deriving from the email is a
|
||||||
|
// fallback for forms that do not ask, not a rule that overwrites an answer.
|
||||||
|
func TestAnExplicitSignInNameIsKept(t *testing.T) {
|
||||||
|
ready := PrepareNewAccount(models.User{Authname: "suriya.nsn", Email: "suriya@example.com"})
|
||||||
|
if ready.Authname != "suriya.nsn" {
|
||||||
|
t.Errorf("an explicit authname was overwritten with %q", ready.Authname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnExplicitConfigIsKept(t *testing.T) {
|
||||||
|
ready := PrepareNewAccount(models.User{Email: "a@b.com", Configid: 4})
|
||||||
|
if ready.Configid != 4 {
|
||||||
|
t.Errorf("configid = %d, want the caller's 4", ready.Configid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whitespace around a pasted email would become an authname nothing can match —
|
||||||
|
// the same invisible failure, one space wide.
|
||||||
|
func TestASurroundingSpaceDoesNotBecomePartOfTheSignInName(t *testing.T) {
|
||||||
|
ready := PrepareNewAccount(models.User{Email: " thiru@omart.com "})
|
||||||
|
if ready.Authname != "thiru@omart.com" {
|
||||||
|
t.Errorf("authname = %q, want it trimmed", ready.Authname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An account with no email at all cannot be given a sign-in name, and must not
|
||||||
|
// be given a blank one that looks like it has been handled.
|
||||||
|
func TestNoEmailLeavesTheSignInNameEmpty(t *testing.T) {
|
||||||
|
ready := PrepareNewAccount(models.User{})
|
||||||
|
if ready.Authname != "" {
|
||||||
|
t.Errorf("authname = %q, want empty", ready.Authname)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -105,7 +105,10 @@ func (s *tenantService) GetStaffs(tid int) ([]models.StaffInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *tenantService) CreateStaff(user models.User) error {
|
func (s *tenantService) CreateStaff(user models.User) error {
|
||||||
return s.repo.CreateStaff(user)
|
// Same two fields, same reason: without an authname and a console configid
|
||||||
|
// the account is created, listed, and refused at the login screen. This path
|
||||||
|
// and users/create both make back-office accounts, so both need it.
|
||||||
|
return s.repo.CreateStaff(PrepareNewAccount(user))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *tenantService) UpdateStaff(user models.User) error {
|
func (s *tenantService) UpdateStaff(user models.User) error {
|
||||||
|
|||||||
@@ -139,8 +139,6 @@ func (s *userService) AppLogin(user models.User) (models.TenantUserInfo, fiber.M
|
|||||||
_ = s.repo.UpdateFCMToken(uid, user.Userfcmtoken)
|
_ = s.repo.UpdateFCMToken(uid, user.Userfcmtoken)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ✅ Fetch tenant user info
|
// ✅ Fetch tenant user info
|
||||||
info := s.repo.GetTenantUserById(uid)
|
info := s.repo.GetTenantUserById(uid)
|
||||||
|
|
||||||
@@ -184,7 +182,6 @@ func (s *userService) CreateUser(user models.User) (models.UserInfo, error) {
|
|||||||
return info, nil
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (s *userService) TenantWebLogin(user models.User) (models.TenantUserInfo, map[string]interface{}) {
|
func (s *userService) TenantWebLogin(user models.User) (models.TenantUserInfo, map[string]interface{}) {
|
||||||
tenantFormExists := true
|
tenantFormExists := true
|
||||||
|
|
||||||
@@ -291,5 +288,3 @@ func (s *userService) TenantWebLogin(user models.User) (models.TenantUserInfo, m
|
|||||||
func (s *userService) DeleteUser(userid int) error {
|
func (s *userService) DeleteUser(userid int) error {
|
||||||
return s.repo.DeleteUser(userid)
|
return s.repo.DeleteUser(userid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user