mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 11:15:03 +03:00
fix(node): apply the client gate to emails with non-ASCII capitals
The node client gate loaded the records it judges with LOWER(email) IN the lowered reported emails, but SQLite's LOWER folds ASCII only. For a stored email such as Иван no record was found, so the node's claim was treated as a new client and passed untouched, rewriting a client that lives only on the master's inbounds. The lookup matches the lowered or the stored spellings, as the shared email checks already do.
This commit is contained in:
@@ -84,14 +84,15 @@ func loadNodeClientClaims(tx *gorm.DB, nodeID int, snap *runtime.TrafficSnapshot
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
lowered := make([]string, 0, len(subIDByEmail))
|
||||
reported := make([]string, 0, len(subIDByEmail))
|
||||
for email := range subIDByEmail {
|
||||
lowered = append(lowered, strings.ToLower(email))
|
||||
reported = append(reported, email)
|
||||
}
|
||||
// SQLite's LOWER folds ASCII only, so a non-ASCII capital is matched by its spellings too.
|
||||
var records []model.ClientRecord
|
||||
for _, batch := range chunkStrings(uniqueNonEmptyStrings(lowered), sqlInChunk) {
|
||||
for _, batch := range chunkStrings(emailSpellings(reported), sqlInChunk) {
|
||||
var rows []model.ClientRecord
|
||||
if err := tx.Where("LOWER(email) IN ?", batch).Find(&rows).Error; err != nil {
|
||||
if err := tx.Where("LOWER(email) IN ? OR email IN ?", batch, batch).Find(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
records = append(records, rows...)
|
||||
|
||||
@@ -439,3 +439,23 @@ func TestNodeOversizeInboundIsNotTakenIn(t *testing.T) {
|
||||
t.Fatal("an inbound with an oversized remark was adopted")
|
||||
}
|
||||
}
|
||||
|
||||
// SQLite's LOWER folds ASCII only, so a lookup by lowered email missed a stored non-ASCII
|
||||
// capital and the node's claim on that master-only client went through untouched.
|
||||
func TestNodeCannotClaimNonASCIICapitalEmail(t *testing.T) {
|
||||
db := initTrafficTestDB(t)
|
||||
svc := &InboundService{}
|
||||
seedNodeRow(t, db, &model.Node{Id: 1, Name: "n1", Address: "127.0.0.1", Port: 2096, ApiToken: "tok", Enable: true})
|
||||
seedMasterInbound(t, db, "master-in", 40001, `{"email":"Иван@x","id":"11111111-1111-4111-8111-111111111111","subId":"ivan-sub","enable":true}`)
|
||||
createNodeInbound(t, db, 1, "n1-in", 41001)
|
||||
|
||||
syncNodeWithSettings(t, svc, 1, "n1-in",
|
||||
`{"clients":[{"email":"Иван@x","subId":"picked-by-node","enable":true},{"email":"иван@x","subId":"lower","enable":true}]}`)
|
||||
|
||||
if row := readClientRecord(t, db, "Иван@x"); row.SubID != "ivan-sub" {
|
||||
t.Fatalf("node rewrote a master-only non-ASCII client: subId=%q", row.SubID)
|
||||
}
|
||||
if n := countClientsWhere(t, db, "email = ?", "иван@x"); n != 0 {
|
||||
t.Fatal("a case variant of a non-ASCII email created a look-alike record")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user