Implement CSRF protection and security hardening across the application (#4179)

* Implement CSRF protection and security hardening across the application

- Added CSRF token handling in axios requests and HTML templates.
- Introduced CSRF middleware to validate tokens for unsafe HTTP methods.
- Implemented login limiter to prevent brute-force attacks.
- Enhanced security headers in middleware for improved response security.
- Updated login notification to include safe metadata without passwords.
- Added tests for CSRF middleware and login limiter functionality.

* fix
This commit is contained in:
Farhad H. P. Shirvan
2026-05-07 23:36:11 +02:00
committed by GitHub
parent a1b2382877
commit 10ebc6cbdc
28 changed files with 525 additions and 41 deletions

View File

@@ -104,6 +104,16 @@ const (
EmptyTelegramUserID = int64(0) // Default value for empty Telegram user ID
)
// LoginAttempt contains safe metadata for panel login notifications.
// It intentionally does not include attempted passwords.
type LoginAttempt struct {
Username string
IP string
Time string
Status LoginStatus
Reason string
}
// Tgbot provides business logic for Telegram bot integration.
// It handles bot commands, user interactions, and status reporting via Telegram.
type Tgbot struct {
@@ -2769,12 +2779,12 @@ func (t *Tgbot) prepareServerUsageInfo() string {
}
// UserLoginNotify sends a notification about user login attempts to admins.
func (t *Tgbot) UserLoginNotify(username string, password string, ip string, time string, status LoginStatus) {
func (t *Tgbot) UserLoginNotify(attempt LoginAttempt) {
if !t.IsRunning() {
return
}
if username == "" || ip == "" || time == "" {
if attempt.Username == "" || attempt.IP == "" || attempt.Time == "" {
logger.Warning("UserLoginNotify failed, invalid info!")
return
}
@@ -2785,18 +2795,20 @@ func (t *Tgbot) UserLoginNotify(username string, password string, ip string, tim
}
msg := ""
switch status {
switch attempt.Status {
case LoginSuccess:
msg += t.I18nBot("tgbot.messages.loginSuccess")
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
case LoginFail:
msg += t.I18nBot("tgbot.messages.loginFailed")
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
msg += t.I18nBot("tgbot.messages.password", "Password=="+password)
if attempt.Reason != "" {
msg += t.I18nBot("tgbot.messages.reason", "Reason=="+attempt.Reason)
}
}
msg += t.I18nBot("tgbot.messages.username", "Username=="+username)
msg += t.I18nBot("tgbot.messages.ip", "IP=="+ip)
msg += t.I18nBot("tgbot.messages.time", "Time=="+time)
msg += t.I18nBot("tgbot.messages.username", "Username=="+attempt.Username)
msg += t.I18nBot("tgbot.messages.ip", "IP=="+attempt.IP)
msg += t.I18nBot("tgbot.messages.time", "Time=="+attempt.Time)
t.SendMsgToTgbotAdmins(msg)
}