From 5ffafbd38fd44830685f0e247a0dc211d2130490 Mon Sep 17 00:00:00 2001 From: db123-test Date: Mon, 4 May 2026 13:15:55 +0330 Subject: [PATCH] feat: add filtering and pagination to audit logs endpoint The logsHandler now accepts query parameters to filter logs by action and IP address, with support for pagination via limit and offset parameters. Query parameters are safely bound to prepared statements to prevent SQL injection, and pagination values are validated to ensure they fall within acceptable ranges. --- auth.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/auth.go b/auth.go index b50670d..2c3c03b 100644 --- a/auth.go +++ b/auth.go @@ -3,9 +3,12 @@ package main import ( "database/sql" "encoding/json" + "fmt" "log/slog" "net" "net/http" + "strconv" + "strings" "time" ) @@ -130,14 +133,56 @@ func logsHandler(cfg Config, db *sql.DB) http.HandlerFunc { http.Error(w, "unauthorized", http.StatusUnauthorized) return } - rows, err := db.Query(`SELECT timestamp, action, ip, cidr, ttl_seconds, reason, api_client_ip - FROM audit_log ORDER BY timestamp DESC LIMIT 200`) + + query := `SELECT timestamp, action, ip, cidr, ttl_seconds, reason, api_client_ip FROM audit_log` + var args []any + argIndex := 1 + + filterAction := r.URL.Query().Get("action") + filterIP := r.URL.Query().Get("ip") + limitStr := r.URL.Query().Get("limit") + offsetStr := r.URL.Query().Get("offset") + + var conditions []string + if filterAction != "" { + conditions = append(conditions, fmt.Sprintf(`action = $%d`, argIndex)) + args = append(args, filterAction) + argIndex++ + } + if filterIP != "" { + conditions = append(conditions, fmt.Sprintf(`ip = $%d`, argIndex)) + args = append(args, filterIP) + argIndex++ + } + + if len(conditions) > 0 { + query += " WHERE " + strings.Join(conditions, " AND ") + } + query += ` ORDER BY timestamp DESC` + + limit := 200 + offset := 0 + if limitStr != "" { + if parsed, err := strconv.Atoi(limitStr); err == nil && parsed > 0 && parsed <= 1000 { + limit = parsed + } + } + if offsetStr != "" { + if parsed, err := strconv.Atoi(offsetStr); err == nil && parsed >= 0 { + offset = parsed + } + } + query += fmt.Sprintf(` LIMIT %d OFFSET %d`, limit, offset) + + rows, err := db.Query(query, args...) if err != nil { + slog.Error("failed to query logs", "error", err) http.Error(w, "database error", http.StatusInternalServerError) return } defer rows.Close() - var logs []map[string]interface{} + + logs := make([]map[string]interface{}, 0) for rows.Next() { var ts time.Time var action, apiClientIP, reason string