feat(admin): add security and activity management features
This commit is contained in:
@@ -2,11 +2,14 @@ package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"warpbox/lib/alerts"
|
||||
"warpbox/lib/config"
|
||||
"warpbox/lib/security"
|
||||
)
|
||||
|
||||
const adminSessionCookie = "warpbox_admin_session"
|
||||
@@ -59,17 +62,39 @@ func (app *App) handleAdminLoginPost(ctx *gin.Context) {
|
||||
ctx.Redirect(http.StatusSeeOther, "/")
|
||||
return
|
||||
}
|
||||
ip := clientIP(ctx)
|
||||
guard := app.securityGuard
|
||||
if guard == nil {
|
||||
guard = security.NewGuard()
|
||||
app.securityGuard = guard
|
||||
}
|
||||
if !guard.IsAdminWhitelisted(ip) && guard.IsBanned(ip) {
|
||||
app.logActivity("auth.admin.block", "high", "Blocked admin login from banned IP", ctx, nil)
|
||||
ctx.HTML(http.StatusTooManyRequests, "admin/login.html", gin.H{
|
||||
"ErrorMessage": "Too many failed attempts. Try again later.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
username := strings.TrimSpace(ctx.PostForm("username"))
|
||||
password := ctx.PostForm("password")
|
||||
|
||||
if username != app.config.AdminUsername || password != app.config.AdminPassword {
|
||||
if !guard.IsAdminWhitelisted(ip) {
|
||||
banned, attempts := guard.RegisterFailedLogin(ip, app.config.SecurityLoginWindowSeconds, app.config.SecurityLoginMaxAttempts, app.config.SecurityBanSeconds)
|
||||
app.logActivity("auth.admin.failed", "medium", "Failed admin login", ctx, map[string]string{"attempts": strconv.Itoa(attempts)})
|
||||
if banned {
|
||||
app.createAlert("Admin login brute-force blocked", "high", "security", "401", "auth.admin.bruteforce", "Too many failed admin logins triggered temporary ban.", map[string]string{"ip": ip, "attempts": strconv.Itoa(attempts)})
|
||||
app.logActivity("security.ban", "high", "Auto-banned IP after admin login failures", ctx, map[string]string{"attempts": strconv.Itoa(attempts)})
|
||||
}
|
||||
}
|
||||
ctx.HTML(http.StatusUnauthorized, "admin/login.html", gin.H{
|
||||
"ErrorMessage": "Invalid username or password.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
app.logActivity("auth.admin.success", "low", "Admin login successful", ctx, nil)
|
||||
secure := app.config.AdminCookieSecure
|
||||
maxAge := int(app.config.SessionTTLSeconds)
|
||||
|
||||
@@ -108,9 +133,41 @@ func (app *App) handleAdminAlerts(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
alertsList := []alerts.Alert{}
|
||||
if app.alertStore != nil {
|
||||
var err error
|
||||
alertsList, err = app.alertStore.List(500)
|
||||
if err != nil {
|
||||
ctx.String(http.StatusInternalServerError, "Could not load alerts")
|
||||
return
|
||||
}
|
||||
}
|
||||
openCount := 0
|
||||
highCount := 0
|
||||
ackedCount := 0
|
||||
closedCount := 0
|
||||
for _, alert := range alertsList {
|
||||
switch string(alert.Status) {
|
||||
case "open":
|
||||
openCount++
|
||||
case "acked":
|
||||
ackedCount++
|
||||
case "closed":
|
||||
closedCount++
|
||||
}
|
||||
if alert.Severity == "high" && string(alert.Status) != "closed" {
|
||||
highCount++
|
||||
}
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, "admin/alerts.html", gin.H{
|
||||
"AdminUsername": app.config.AdminUsername,
|
||||
"AdminEmail": app.config.AdminEmail,
|
||||
"ActivePage": "alerts",
|
||||
"Alerts": alertsList,
|
||||
"OpenCount": strconv.Itoa(openCount),
|
||||
"HighCount": strconv.Itoa(highCount),
|
||||
"AckCount": strconv.Itoa(ackedCount),
|
||||
"ClosedCount": strconv.Itoa(closedCount),
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user