refactor: simplified and used db.go for operations

This commit is contained in:
2026-05-04 08:42:37 +03:30
parent d82ab73336
commit 1e439f4ce3

191
auth.go
View File

@@ -4,61 +4,29 @@ import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"log/slog" "log/slog"
"net"
"net/http" "net/http"
"sort"
"sync"
"time"
_ "modernc.org/sqlite"
) )
type tempEntry struct { func CheckAuth(db *sql.DB, r *http.Request) bool {
IP string `json:"ip"`
Reason string `json:"reason,omitempty"`
Expires time.Time `json:"expires"`
}
type tempWhitelist struct {
mu sync.RWMutex
entries map[string]tempEntry
}
func newTempWhitelist() *tempWhitelist {
return &tempWhitelist{entries: make(map[string]tempEntry)}
}
type PermanentWhitelist struct {
mu sync.RWMutex
entries map[string]struct{}
}
func newPermanentWhitelist() *PermanentWhitelist {
return &PermanentWhitelist{entries: make(map[string]struct{})}
}
func (tw *tempWhitelist) CheckAuth(r *http.Request, perm *PermanentWhitelist) bool {
ip := r.Header.Get("X-Real-IP") ip := r.Header.Get("X-Real-IP")
if ip == "" { if ip == "" {
return false return false
} }
perm.mu.RLock() ok, err := IsPermanentWhitelisted(db, ip)
_, ok := perm.entries[ip] if err == nil && ok {
perm.mu.RUnlock()
if ok {
return true return true
} }
tw.mu.RLock() ok, err = IsTempWhitelisted(db, ip)
if e, exists := tw.entries[ip]; exists && e.Expires.After(time.Now()) { if err == nil && ok {
tw.mu.RUnlock()
return true return true
} }
tw.mu.RUnlock()
return false return false
} }
func authHandler(cfg Config, tw *tempWhitelist, perm *PermanentWhitelist) http.HandlerFunc { func authHandler(cfg Config, db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if !tw.CheckAuth(r, perm) { if !CheckAuth(db, r) {
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
return return
} }
@@ -66,7 +34,7 @@ func authHandler(cfg Config, tw *tempWhitelist, perm *PermanentWhitelist) http.H
} }
} }
func (tw *tempWhitelist) whitelistTempHandler(cfg Config, db *sql.DB) http.HandlerFunc { func whitelistTempHandler(cfg Config, db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if !verifyAPIKey(r, cfg.APIToken) { if !verifyAPIKey(r, cfg.APIToken) {
http.Error(w, "unauthorized", http.StatusUnauthorized) http.Error(w, "unauthorized", http.StatusUnauthorized)
@@ -90,40 +58,34 @@ func (tw *tempWhitelist) whitelistTempHandler(cfg Config, db *sql.DB) http.Handl
http.Error(w, "ip and ttl_seconds required", http.StatusBadRequest) http.Error(w, "ip and ttl_seconds required", http.StatusBadRequest)
return return
} }
expires := time.Now().Add(time.Duration(req.TTLSeconds) * time.Second) clientIP := getClientIP(r)
if err := AddTempEntry(db, req.IP, req.TTLSeconds, req.Reason, clientIP); err != nil {
tw.mu.Lock() slog.Error("failed to add temp entry", "error", err)
tw.entries[req.IP] = tempEntry{IP: req.IP, Reason: req.Reason, Expires: expires} http.Error(w, "internal error", http.StatusInternalServerError)
tw.mu.Unlock() return
}
go logWhitelistEvent(db, "add_temp", req.IP, req.TTLSeconds, req.Reason) slog.Info("temp whitelist added", "ip", req.IP, "ttl", req.TTLSeconds)
slog.Info("whitelist added", "ip", req.IP, "ttl", req.TTLSeconds, "reason", req.Reason)
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
} }
} }
func (tw *tempWhitelist) whitelistListHandler(cfg Config) http.HandlerFunc { func whitelistListHandler(cfg Config, db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if !verifyAPIKey(r, cfg.APIToken) { if !verifyAPIKey(r, cfg.APIToken) {
http.Error(w, "unauthorized", http.StatusUnauthorized) http.Error(w, "unauthorized", http.StatusUnauthorized)
return return
} }
tw.mu.RLock() entries, err := ListTempEntries(db)
entries := make([]tempEntry, 0, len(tw.entries)) if err != nil {
for _, e := range tw.entries { http.Error(w, "database error", http.StatusInternalServerError)
entries = append(entries, e) return
} }
tw.mu.RUnlock()
sort.Slice(entries, func(i, j int) bool {
return entries[i].Expires.Before(entries[j].Expires)
})
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(entries) json.NewEncoder(w).Encode(entries)
} }
} }
func (tw *tempWhitelist) whitelistDeleteHandler(cfg Config, db *sql.DB) http.HandlerFunc { func whitelistDeleteHandler(cfg Config, db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if !verifyAPIKey(r, cfg.APIToken) { if !verifyAPIKey(r, cfg.APIToken) {
http.Error(w, "unauthorized", http.StatusUnauthorized) http.Error(w, "unauthorized", http.StatusUnauthorized)
@@ -134,12 +96,13 @@ func (tw *tempWhitelist) whitelistDeleteHandler(cfg Config, db *sql.DB) http.Han
http.Error(w, "ip required", http.StatusBadRequest) http.Error(w, "ip required", http.StatusBadRequest)
return return
} }
tw.mu.Lock() clientIP := getClientIP(r)
delete(tw.entries, ip) if err := DeleteTempEntry(db, ip, clientIP); err != nil {
tw.mu.Unlock() slog.Error("failed to delete temp entry", "error", err)
http.Error(w, "internal error", http.StatusInternalServerError)
go logWhitelistEvent(db, "delete_temp", ip, 0, "", getAPIUser(r)) return
slog.Info("whitelist deleted", "ip", ip) }
slog.Info("temp whitelist deleted", "ip", ip)
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
} }
} }
@@ -150,8 +113,8 @@ func logsHandler(cfg Config, db *sql.DB) http.HandlerFunc {
http.Error(w, "unauthorized", http.StatusUnauthorized) http.Error(w, "unauthorized", http.StatusUnauthorized)
return return
} }
rows, err := db.Query(`SELECT timestamp, action, ip, ttl_seconds, reason, api_user rows, err := db.Query(`SELECT timestamp, action, ip, cidr, ttl_seconds, reason, api_client_ip
FROM whitelist_audit ORDER BY timestamp DESC LIMIT 100`) FROM audit_log ORDER BY timestamp DESC LIMIT 200`)
if err != nil { if err != nil {
http.Error(w, "database error", http.StatusInternalServerError) http.Error(w, "database error", http.StatusInternalServerError)
return return
@@ -160,29 +123,101 @@ func logsHandler(cfg Config, db *sql.DB) http.HandlerFunc {
var logs []map[string]interface{} var logs []map[string]interface{}
for rows.Next() { for rows.Next() {
var ts time.Time var ts time.Time
var action, ip, reason, apiUser string var action, apiClientIP, reason string
var ttl int var ip, cidr sql.NullString
if err := rows.Scan(&ts, &action, &ip, &ttl, &reason); err != nil { var ttlSeconds sql.NullInt64
if err := rows.Scan(&ts, &action, &ip, &cidr, &ttlSeconds, &reason, &apiClientIP); err != nil {
continue continue
} }
logs = append(logs, map[string]interface{}{ entry := map[string]interface{}{
"timestamp": ts, "timestamp": ts,
"action": action, "action": action,
"ip": ip, "api_client_ip": apiClientIP,
"ttl": ttl,
"reason": reason, "reason": reason,
}) }
if ip.Valid {
entry["ip"] = ip.String
}
if cidr.Valid {
entry["cidr"] = cidr.String
}
if ttlSeconds.Valid {
entry["ttl_seconds"] = ttlSeconds.Int64
}
logs = append(logs, entry)
} }
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(logs) json.NewEncoder(w).Encode(logs)
} }
} }
func statusHandler(w http.ResponseWriter, r *http.Request) { // --- Perm whitelist API endpoints (optional but useful) ---
w.Header().Set("Content-Type", "text/plain") func permWhitelistAddHandler(cfg Config, db *sql.DB) http.HandlerFunc {
w.Write([]byte("ok\n")) return func(w http.ResponseWriter, r *http.Request) {
if !verifyAPIKey(r, cfg.APIToken) {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
type request struct {
Entry string `json:"entry"` // IP or CIDR
}
var req request
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
if req.Entry == "" {
http.Error(w, "entry required", http.StatusBadRequest)
return
}
ok, err := AddPermanentEntry(db, req.Entry, getClientIP(r))
if err != nil {
slog.Error("failed to add perm entry", "error", err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
if !ok {
http.Error(w, "entry already exists", http.StatusConflict)
return
}
w.WriteHeader(http.StatusNoContent)
}
}
func permWhitelistDeleteHandler(cfg Config, db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if !verifyAPIKey(r, cfg.APIToken) {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
entry := r.PathValue("entry")
if entry == "" {
http.Error(w, "entry required", http.StatusBadRequest)
return
}
if err := DeletePermanentEntry(db, entry, getClientIP(r)); err != nil {
slog.Error("failed to delete perm entry", "error", err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
}
}
// Helper to get real client IP for audit (X-Real-IP or remote addr)
func getClientIP(r *http.Request) string {
if ip := r.Header.Get("X-Real-IP"); ip != "" {
return ip
}
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
return ip
} }
func verifyAPIKey(r *http.Request, expectedToken string) bool { func verifyAPIKey(r *http.Request, expectedToken string) bool {
return r.Header.Get("Authorization") == "Bearer "+expectedToken return r.Header.Get("Authorization") == "Bearer "+expectedToken
} }
func statusHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte("ok\n"))
}