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" const adminSessionMarker = "1" func (app *App) adminLoginEnabled() bool { return app.config.AdminLoginEnabled(app.config.AdminPassword != "") } func (app *App) adminAuthMiddleware(ctx *gin.Context) { if !app.adminLoginEnabled() { ctx.Redirect(http.StatusSeeOther, "/") ctx.Abort() return } token, err := ctx.Cookie(adminSessionCookie) if err != nil || token != app.adminSessionToken() { ctx.Redirect(http.StatusSeeOther, "/admin/login") ctx.Abort() return } ctx.Next() } func (app *App) adminSessionToken() string { // A simple deterministic token derived from the admin credentials. // This will improve when proper user/session storage is added. return app.config.AdminUsername + ":" + app.config.AdminPassword } func (app *App) handleAdminLogin(ctx *gin.Context) { if !app.adminLoginEnabled() { ctx.Redirect(http.StatusSeeOther, "/") return } // Already logged in. if token, err := ctx.Cookie(adminSessionCookie); err == nil && token == app.adminSessionToken() { ctx.Redirect(http.StatusSeeOther, "/admin/dashboard") return } ctx.HTML(http.StatusOK, "admin/login.html", gin.H{}) } func (app *App) handleAdminLoginPost(ctx *gin.Context) { if !app.adminLoginEnabled() { 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) ctx.SetCookie(adminSessionCookie, app.adminSessionToken(), maxAge, "/admin", "", secure, true) ctx.Redirect(http.StatusSeeOther, "/admin/dashboard") } func (app *App) handleAdminLogout(ctx *gin.Context) { secure := app.config.AdminCookieSecure ctx.SetCookie(adminSessionCookie, "", -1, "/admin", "", secure, true) ctx.Redirect(http.StatusSeeOther, "/admin/login") } func (app *App) handleAdminDashboard(ctx *gin.Context) { if !app.adminLoginEnabled() { ctx.Redirect(http.StatusSeeOther, "/") return } dashboardEnabled := config.AdminEnabledTrue if cfgVal := app.config.AdminEnabled; cfgVal == config.AdminEnabledAuto || cfgVal == config.AdminEnabledTrue { dashboardEnabled = cfgVal } ctx.HTML(http.StatusOK, "admin/dashboard.html", gin.H{ "AdminUsername": app.config.AdminUsername, "AdminEmail": app.config.AdminEmail, "ActivePage": "dashboard", "DashboardEnabled": string(dashboardEnabled), }) } func (app *App) handleAdminAlerts(ctx *gin.Context) { if !app.adminLoginEnabled() { ctx.Redirect(http.StatusSeeOther, "/") 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), }) }