mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 11:15:03 +03:00
fix(tgbot): keep a login alert's username on one line
The Telegram login alert is a list of "label: value" lines, and the username, whatever the visitor typed on the login form, was inserted with its line breaks intact. Text after a break read as the panel's own IP and time lines, letting a failed login forge the alert. Control characters and Unicode line separators in the username become spaces, in the failed and the successful alert alike.
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/eventbus"
|
||||
)
|
||||
@@ -64,6 +65,17 @@ func (t *Tgbot) isEventEnabled(eventType eventbus.EventType) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// oneLine keeps a value the panel did not write itself on its own line of an
|
||||
// alert: control characters and Unicode line separators become spaces.
|
||||
func oneLine(s string) string {
|
||||
return strings.Map(func(r rune) rune {
|
||||
if unicode.IsControl(r) || r == '
' || r == '
' {
|
||||
return ' '
|
||||
}
|
||||
return r
|
||||
}, s)
|
||||
}
|
||||
|
||||
func (t *Tgbot) formatEventMessage(e eventbus.Event) string {
|
||||
host := getHostname()
|
||||
header := fmt.Sprintf("<b>📡 %s</b>\n", host)
|
||||
@@ -147,7 +159,7 @@ func (t *Tgbot) formatEventMessage(e eventbus.Event) string {
|
||||
if data.Status == "success" {
|
||||
msg := t.I18nBot("tgbot.messages.loginSuccess")
|
||||
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+host)
|
||||
msg += t.I18nBot("tgbot.messages.username", "Username=="+data.Username)
|
||||
msg += t.I18nBot("tgbot.messages.username", "Username=="+oneLine(data.Username))
|
||||
msg += t.I18nBot("tgbot.messages.ip", "IP=="+data.IP)
|
||||
msg += t.I18nBot("tgbot.messages.time", "Time=="+data.Time)
|
||||
return msg
|
||||
@@ -157,7 +169,7 @@ func (t *Tgbot) formatEventMessage(e eventbus.Event) string {
|
||||
if data.Reason != "" {
|
||||
msg += t.I18nBot("tgbot.messages.reason", "Reason=="+data.Reason)
|
||||
}
|
||||
msg += t.I18nBot("tgbot.messages.username", "Username=="+data.Username)
|
||||
msg += t.I18nBot("tgbot.messages.username", "Username=="+oneLine(data.Username))
|
||||
msg += t.I18nBot("tgbot.messages.ip", "IP=="+data.IP)
|
||||
msg += t.I18nBot("tgbot.messages.time", "Time=="+data.Time)
|
||||
return msg
|
||||
|
||||
+31
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
||||
|
||||
"github.com/mymmrac/telego"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -334,3 +335,33 @@ func TestClientUsageDoesNotLoadTheInboundPerClient(t *testing.T) {
|
||||
t.Fatalf("the inbound row was loaded %d times for %d clients", wholeRows, owned)
|
||||
}
|
||||
}
|
||||
|
||||
// The login alert is one field per line and the username is
|
||||
// whatever the visitor typed, so a line break in it must not start a new line.
|
||||
func TestLoginAlertKeepsUsernameOnOneLine(t *testing.T) {
|
||||
draftLocalizer(t,
|
||||
&i18n.Message{ID: "tgbot.messages.loginSuccess", Other: "✅ Logged in to the panel successfully.\r\n"},
|
||||
&i18n.Message{ID: "tgbot.messages.loginFailed", Other: "❗️Login attempt to the panel failed.\r\n"},
|
||||
&i18n.Message{ID: "tgbot.messages.hostname", Other: "💻 Host: {{ .Hostname }}\r\n"},
|
||||
&i18n.Message{ID: "tgbot.messages.reason", Other: "❗️ Reason: {{ .Reason }}\r\n"},
|
||||
&i18n.Message{ID: "tgbot.messages.username", Other: "👤 Username: {{ .Username }}\r\n"},
|
||||
&i18n.Message{ID: "tgbot.messages.ip", Other: "🌐 IP: {{ .IP }}\r\n"},
|
||||
&i18n.Message{ID: "tgbot.messages.time", Other: "⏰ Time: {{ .Time }}\r\n"},
|
||||
)
|
||||
|
||||
for _, lineBreak := range []string{"\r\n", "\n", "\r", "\v", "\f", "\u0085", "\u2028", "\u2029", "\r\n\r\n"} {
|
||||
username := "mallory" + lineBreak + "🌐 IP: 203.0.113.77"
|
||||
for _, status := range []string{"fail", "success"} {
|
||||
got := (&Tgbot{}).formatEventMessage(eventbus.Event{
|
||||
Type: eventbus.EventLoginAttempt,
|
||||
Data: &eventbus.LoginEventData{
|
||||
Username: username, IP: "192.0.2.1", Time: "now", Status: status, Reason: "invalid credentials",
|
||||
},
|
||||
})
|
||||
want := "👤 Username: mallory" + strings.Repeat(" ", len([]rune(lineBreak))) + "🌐 IP: 203.0.113.77\r\n🌐 IP: 192.0.2.1\r\n"
|
||||
if !strings.Contains(got, want) {
|
||||
t.Errorf("login %s, line break %q: message = %q, want it to contain %q", status, lineBreak, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user