From a2097ad06218d49f5aad20254b7bb8c2a9fc0e03 Mon Sep 17 00:00:00 2001 From: Aleksei Sidorenko <88515338+rydve@users.noreply.github.com> Date: Wed, 4 Mar 2026 20:26:53 +0300 Subject: [PATCH] feat: mask password in telegram notification on 2FA failure (#3884) --- web/controller/index.go | 13 +++++++++++-- web/service/user.go | 20 +++++++++----------- web/translation/translate.ar_EG.toml | 1 + web/translation/translate.en_US.toml | 1 + web/translation/translate.es_ES.toml | 1 + web/translation/translate.fa_IR.toml | 1 + web/translation/translate.id_ID.toml | 1 + web/translation/translate.ja_JP.toml | 1 + web/translation/translate.pt_BR.toml | 1 + web/translation/translate.ru_RU.toml | 1 + web/translation/translate.tr_TR.toml | 1 + web/translation/translate.uk_UA.toml | 1 + web/translation/translate.vi_VN.toml | 1 + web/translation/translate.zh_CN.toml | 1 + web/translation/translate.zh_TW.toml | 1 + 15 files changed, 33 insertions(+), 13 deletions(-) diff --git a/web/controller/index.go b/web/controller/index.go index 5f9e1c2c..605f874f 100644 --- a/web/controller/index.go +++ b/web/controller/index.go @@ -4,6 +4,7 @@ import ( "net/http" "text/template" "time" + "fmt" "github.com/mhsanaei/3x-ui/v2/logger" "github.com/mhsanaei/3x-ui/v2/web/service" @@ -71,14 +72,22 @@ func (a *IndexController) login(c *gin.Context) { return } - user := a.userService.CheckUser(form.Username, form.Password, form.TwoFactorCode) + user, checkErr := a.userService.CheckUser(form.Username, form.Password, form.TwoFactorCode) timeStr := time.Now().Format("2006-01-02 15:04:05") safeUser := template.HTMLEscapeString(form.Username) safePass := template.HTMLEscapeString(form.Password) if user == nil { logger.Warningf("wrong username: \"%s\", password: \"%s\", IP: \"%s\"", safeUser, safePass, getRemoteIp(c)) - a.tgbot.UserLoginNotify(safeUser, safePass, getRemoteIp(c), timeStr, 0) + + notifyPass := safePass + + if checkErr != nil && checkErr.Error() == "invalid 2fa code" { + translatedError := a.tgbot.I18nBot("tgbot.messages.2faFailed") + notifyPass = fmt.Sprintf("*** (%s)", translatedError) + } + + a.tgbot.UserLoginNotify(safeUser, notifyPass, getRemoteIp(c), timeStr, 0) pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword")) return } diff --git a/web/service/user.go b/web/service/user.go index 1bde69f6..0a2a3f3e 100644 --- a/web/service/user.go +++ b/web/service/user.go @@ -33,7 +33,7 @@ func (s *UserService) GetFirstUser() (*model.User, error) { return user, nil } -func (s *UserService) CheckUser(username string, password string, twoFactorCode string) *model.User { +func (s *UserService) CheckUser(username string, password string, twoFactorCode string) (*model.User, error) { db := database.GetDB() user := &model.User{} @@ -43,17 +43,16 @@ func (s *UserService) CheckUser(username string, password string, twoFactorCode First(user). Error if err == gorm.ErrRecordNotFound { - return nil + return nil, errors.New("invalid credentials") } else if err != nil { logger.Warning("check user err:", err) - return nil + return nil, err } - // If LDAP enabled and local password check fails, attempt LDAP auth if !crypto.CheckPasswordHash(user.Password, password) { ldapEnabled, _ := s.settingService.GetLdapEnable() if !ldapEnabled { - return nil + return nil, errors.New("invalid credentials") } host, _ := s.settingService.GetLdapHost() @@ -77,15 +76,14 @@ func (s *UserService) CheckUser(username string, password string, twoFactorCode } ok, err := ldaputil.AuthenticateUser(cfg, username, password) if err != nil || !ok { - return nil + return nil, errors.New("invalid credentials") } - // On successful LDAP auth, continue 2FA checks below } twoFactorEnable, err := s.settingService.GetTwoFactorEnable() if err != nil { logger.Warning("check two factor err:", err) - return nil + return nil, err } if twoFactorEnable { @@ -93,15 +91,15 @@ func (s *UserService) CheckUser(username string, password string, twoFactorCode if err != nil { logger.Warning("check two factor token err:", err) - return nil + return nil, err } if gotp.NewDefaultTOTP(twoFactorToken).Now() != twoFactorCode { - return nil + return nil, errors.New("invalid 2fa code") } } - return user + return user, nil } func (s *UserService) UpdateUser(id int, username string, password string) error { diff --git a/web/translation/translate.ar_EG.toml b/web/translation/translate.ar_EG.toml index 582747d8..3fbcee6e 100644 --- a/web/translation/translate.ar_EG.toml +++ b/web/translation/translate.ar_EG.toml @@ -663,6 +663,7 @@ "userSaved" = "✅ حفظت بيانات مستخدم Telegram." "loginSuccess" = "✅ تسجيل الدخول للبانل تم بنجاح.\r\n" "loginFailed" = "❗️فشل محاولة تسجيل الدخول للبانل.\r\n" +"2faFailed" = "فشل 2FA" "report" = "🕰 التقارير المجدولة: {{ .RunTime }}\r\n" "datetime" = "⏰ التاريخ والوقت: {{ .DateTime }}\r\n" "hostname" = "💻 السيرفر: {{ .Hostname }}\r\n" diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index 4ea136ec..7d6ab162 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -663,6 +663,7 @@ "userSaved" = "✅ Telegram User saved." "loginSuccess" = "✅ Logged in to the panel successfully.\r\n" "loginFailed" = "❗️Login attempt to the panel failed.\r\n" +"2faFailed" = "2FA Failed" "report" = "🕰 Scheduled Reports: {{ .RunTime }}\r\n" "datetime" = "⏰ Date&Time: {{ .DateTime }}\r\n" "hostname" = "💻 Host: {{ .Hostname }}\r\n" diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml index d018f94c..14429228 100644 --- a/web/translation/translate.es_ES.toml +++ b/web/translation/translate.es_ES.toml @@ -663,6 +663,7 @@ "userSaved" = "✅ Usuario de Telegram guardado." "loginSuccess" = "✅ Has iniciado sesión en el panel con éxito.\r\n" "loginFailed" = "❗️ Falló el inicio de sesión en el panel.\r\n" +"2faFailed" = "Error de 2FA" "report" = "🕰 Informes programados: {{ .RunTime }}\r\n" "datetime" = "⏰ Fecha y Hora: {{ .DateTime }}\r\n" "hostname" = "💻 Nombre del Host: {{ .Hostname }}\r\n" diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index 144e68a7..cc2220fd 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -663,6 +663,7 @@ "userSaved" = "✅ کاربر تلگرام ذخیره شد." "loginSuccess" = "✅ با موفقیت به پنل وارد شدید.\r\n" "loginFailed" = "❗️ ورود به پنل ناموفق‌بود \r\n" +"2faFailed" = "خطای 2FA" "report" = "🕰 گزارشات‌زمان‌بندی‌شده: {{ .RunTime }}\r\n" "datetime" = "⏰ تاریخ‌وزمان: {{ .DateTime }}\r\n" "hostname" = "💻 نام‌میزبان: {{ .Hostname }}\r\n" diff --git a/web/translation/translate.id_ID.toml b/web/translation/translate.id_ID.toml index e82288ca..65fc04af 100644 --- a/web/translation/translate.id_ID.toml +++ b/web/translation/translate.id_ID.toml @@ -663,6 +663,7 @@ "userSaved" = "✅ Pengguna Telegram tersimpan." "loginSuccess" = "✅ Berhasil masuk ke panel.\r\n" "loginFailed" = "❗️ Gagal masuk ke panel.\r\n" +"2faFailed" = "2FA Gagal" "report" = "🕰 Laporan Terjadwal: {{ .RunTime }}\r\n" "datetime" = "⏰ Tanggal & Waktu: {{ .DateTime }}\r\n" "hostname" = "💻 Host: {{ .Hostname }}\r\n" diff --git a/web/translation/translate.ja_JP.toml b/web/translation/translate.ja_JP.toml index 4227809a..d7ff3451 100644 --- a/web/translation/translate.ja_JP.toml +++ b/web/translation/translate.ja_JP.toml @@ -663,6 +663,7 @@ "userSaved" = "✅ Telegramユーザーが保存されました。" "loginSuccess" = "✅ パネルに正常にログインしました。\r\n" "loginFailed" = "❗️ パネルのログインに失敗しました。\r\n" +"2faFailed" = "2FAエラー" "report" = "🕰 定期報告:{{ .RunTime }}\r\n" "datetime" = "⏰ 日時:{{ .DateTime }}\r\n" "hostname" = "💻 ホスト名:{{ .Hostname }}\r\n" diff --git a/web/translation/translate.pt_BR.toml b/web/translation/translate.pt_BR.toml index 4533d104..dc04c98f 100644 --- a/web/translation/translate.pt_BR.toml +++ b/web/translation/translate.pt_BR.toml @@ -663,6 +663,7 @@ "userSaved" = "✅ Usuário do Telegram salvo." "loginSuccess" = "✅ Conectado ao painel com sucesso.\r\n" "loginFailed" = "❗️Tentativa de login no painel falhou.\r\n" +"2faFailed" = "Falha no 2FA" "report" = "🕰 Relatórios agendados: {{ .RunTime }}\r\n" "datetime" = "⏰ Data&Hora: {{ .DateTime }}\r\n" "hostname" = "💻 Host: {{ .Hostname }}\r\n" diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml index 79c305a2..8a403a1c 100644 --- a/web/translation/translate.ru_RU.toml +++ b/web/translation/translate.ru_RU.toml @@ -663,6 +663,7 @@ "userSaved" = "✅ Пользователь Telegram сохранен." "loginSuccess" = "✅ Успешный вход в панель.\r\n" "loginFailed" = "❗️ Ошибка входа в панель.\r\n" +"2faFailed" = "Ошибка 2FA" "report" = "🕰 Запланированные отчеты: {{ .RunTime }}\r\n" "datetime" = "⏰ Дата и время: {{ .DateTime }}\r\n" "hostname" = "💻 Имя хоста: {{ .Hostname }}\r\n" diff --git a/web/translation/translate.tr_TR.toml b/web/translation/translate.tr_TR.toml index 4b3c3bc5..57b84e07 100644 --- a/web/translation/translate.tr_TR.toml +++ b/web/translation/translate.tr_TR.toml @@ -663,6 +663,7 @@ "userSaved" = "✅ Telegram Kullanıcısı kaydedildi." "loginSuccess" = "✅ Panele başarıyla giriş yapıldı.\r\n" "loginFailed" = "❗️Panele giriş denemesi başarısız oldu.\r\n" +"2faFailed" = "2FA Hatası" "report" = "🕰 Planlanmış Raporlar: {{ .RunTime }}\r\n" "datetime" = "⏰ Tarih&Zaman: {{ .DateTime }}\r\n" "hostname" = "💻 Sunucu: {{ .Hostname }}\r\n" diff --git a/web/translation/translate.uk_UA.toml b/web/translation/translate.uk_UA.toml index e4a53794..b08ddbec 100644 --- a/web/translation/translate.uk_UA.toml +++ b/web/translation/translate.uk_UA.toml @@ -663,6 +663,7 @@ "userSaved" = "✅ Користувача Telegram збережено." "loginSuccess" = "✅ Успішно ввійшли в панель\r\n" "loginFailed" = "❗️ Помилка входу в панель.\r\n" +"2faFailed" = "Помилка 2FA" "report" = "🕰 Заплановані звіти: {{ .RunTime }}\r\n" "datetime" = "⏰ Дата й час: {{ .DateTime }}\r\n" "hostname" = "💻 Хост: {{ .Hostname }}\r\n" diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml index 81b9e4cb..a4d667d0 100644 --- a/web/translation/translate.vi_VN.toml +++ b/web/translation/translate.vi_VN.toml @@ -663,6 +663,7 @@ "userSaved" = "✅ Người dùng Telegram đã được lưu." "loginSuccess" = "✅ Đăng nhập thành công vào bảng điều khiển.\r\n" "loginFailed" = "❗️ Đăng nhập vào bảng điều khiển thất bại.\r\n" +"2faFailed" = "Lỗi 2FA" "report" = "🕰 Báo cáo định kỳ: {{ .RunTime }}\r\n" "datetime" = "⏰ Ngày-Giờ: {{ .DateTime }}\r\n" "hostname" = "💻 Tên máy chủ: {{ .Hostname }}\r\n" diff --git a/web/translation/translate.zh_CN.toml b/web/translation/translate.zh_CN.toml index 3d1b5ded..103698f6 100644 --- a/web/translation/translate.zh_CN.toml +++ b/web/translation/translate.zh_CN.toml @@ -663,6 +663,7 @@ "userSaved" = "✅ 电报用户已保存。" "loginSuccess" = "✅ 成功登录到面板。\r\n" "loginFailed" = "❗️ 面板登录失败。\r\n" +"2faFailed" = "2FA 失败" "report" = "🕰 定时报告:{{ .RunTime }}\r\n" "datetime" = "⏰ 日期时间:{{ .DateTime }}\r\n" "hostname" = "💻 主机名:{{ .Hostname }}\r\n" diff --git a/web/translation/translate.zh_TW.toml b/web/translation/translate.zh_TW.toml index 0eb73b8a..ab083f2c 100644 --- a/web/translation/translate.zh_TW.toml +++ b/web/translation/translate.zh_TW.toml @@ -663,6 +663,7 @@ "userSaved" = "✅ 電報使用者已儲存。" "loginSuccess" = "✅ 成功登入到面板。\r\n" "loginFailed" = "❗️ 面板登入失敗。\r\n" +"2faFailed" = "2FA 失敗" "report" = "🕰 定時報告:{{ .RunTime }}\r\n" "datetime" = "⏰ 日期時間:{{ .DateTime }}\r\n" "hostname" = "💻 主機名:{{ .Hostname }}\r\n"