pos login edited with phone number

This commit is contained in:
2026-08-11 11:08:43 +05:30
parent 3531c656d4
commit d35caf34d3
12 changed files with 768 additions and 25 deletions

View File

@@ -47,7 +47,7 @@ func (r *posRepository) PosLogin(req models.PosLoginRequest) (*models.PosSession
return nil, fmt.Errorf("an email or mobile number is required")
}
rows, err := r.posLoginCandidates(field, value, req.Configid)
rows, err := r.posLoginCandidates(field, value, req.Configid, true)
if err != nil {
return nil, err
}
@@ -55,6 +55,21 @@ func (r *posRepository) PosLogin(req models.PosLoginRequest) (*models.PosSession
// One message for "no such account" and for "wrong password", on purpose.
// Distinguishing them turns the login into a directory of who banks here.
if len(rows) == 0 {
// Nothing eligible under that credential. Before answering with the
// deliberately vague rejection, look again without the role filter — a
// back-office account typing its own password at a till deserves to be
// told that is the problem, rather than sent hunting for a password
// that was never wrong.
//
// Only ever reached after that account's own password verifies, so it
// discloses nothing the caller has not just proved. An ambiguous match
// falls through to the vague answer rather than naming anything.
if others, oerr := r.posLoginCandidates(field, value, req.Configid, false); oerr == nil &&
len(others) == 1 &&
strings.TrimSpace(others[0].Password) != "" &&
constantTimeEqual(others[0].Password, req.Password) {
return nil, errPosRoleIneligible
}
return nil, errPosLoginRejected
}
@@ -205,7 +220,7 @@ func posRoleLabel(roleID int) string {
// demanding a number they have never seen would make the login unusable. So it
// is honoured when sent and inferred when not — and inference that finds more
// than one candidate is reported, never guessed.
func (r *posRepository) posLoginCandidates(field, value string, configID int) ([]posLoginRow, error) {
func (r *posRepository) posLoginCandidates(field, value string, configID int, tillOnly bool) ([]posLoginRow, error) {
rows := make([]posLoginRow, 0, 2)
query := fmt.Sprintf(`
@@ -223,6 +238,25 @@ func (r *posRepository) posLoginCandidates(field, value string, configID int) ([
params = append(params, configID)
}
// Only till accounts are candidates.
//
// This matters most for signing in by phone. `contactno` is not unique in
// this schema — 34 numbers are shared by 104 active accounts, one of them
// by eleven — and the caller refuses any lookup returning more than one
// row, because choosing between them could bill into the wrong tenant's
// books. Without this clause a cashier whose number also sits on a tenant
// admin's record simply cannot log in.
//
// Narrowing here means a till phone number only has to be unique among
// till accounts, not across all 608 users. An eligible-but-wrong-role
// account still gets the specific errPosRoleIneligible answer, because the
// caller checks the role again after the password verifies — this clause
// removes ambiguity, it does not replace that check.
if tillOnly {
query += fmt.Sprintf(` AND COALESCE(roleid, 0) IN (%d, %d)`,
models.PosRoleSupervisor, models.PosRoleCashier)
}
// Inactive accounts are excluded from the match rather than matched and
// then refused. A deactivated duplicate would otherwise make a working
// login ambiguous, which turns "this person left" into "nobody can open