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.
This commit is contained in:
db123-test
2026-05-04 13:15:55 +03:30
parent 327827da69
commit 5ffafbd38f

51
auth.go
View File

@@ -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