Files
Charon/backend/internal/api/handlers/access_list_handler.go
akanealw eec8c28fb3
Some checks are pending
Go Benchmark / Performance Regression Check (push) Waiting to run
Cerberus Integration / Cerberus Security Stack Integration (push) Waiting to run
Upload Coverage to Codecov / Backend Codecov Upload (push) Waiting to run
Upload Coverage to Codecov / Frontend Codecov Upload (push) Waiting to run
CodeQL - Analyze / CodeQL analysis (go) (push) Waiting to run
CodeQL - Analyze / CodeQL analysis (javascript-typescript) (push) Waiting to run
CrowdSec Integration / CrowdSec Bouncer Integration (push) Waiting to run
Docker Build, Publish & Test / build-and-push (push) Waiting to run
Docker Build, Publish & Test / Security Scan PR Image (push) Blocked by required conditions
Quality Checks / Auth Route Protection Contract (push) Waiting to run
Quality Checks / Codecov Trigger/Comment Parity Guard (push) Waiting to run
Quality Checks / Backend (Go) (push) Waiting to run
Quality Checks / Frontend (React) (push) Waiting to run
Rate Limit integration / Rate Limiting Integration (push) Waiting to run
Security Scan (PR) / Trivy Binary Scan (push) Waiting to run
Supply Chain Verification (PR) / Verify Supply Chain (push) Waiting to run
WAF integration / Coraza WAF Integration (push) Waiting to run
changed perms
2026-04-22 18:19:14 +00:00

209 lines
5.7 KiB
Go
Executable File

package handlers
import (
"fmt"
"net/http"
"strconv"
"github.com/Wikid82/charon/backend/internal/models"
"github.com/Wikid82/charon/backend/internal/services"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// AccessListHandler handles access list API requests.
type AccessListHandler struct {
service *services.AccessListService
}
// NewAccessListHandler creates a new AccessListHandler.
func NewAccessListHandler(db *gorm.DB) *AccessListHandler {
return &AccessListHandler{
service: services.NewAccessListService(db),
}
}
// SetGeoIPService sets the GeoIP service for geo-based ACL lookups.
func (h *AccessListHandler) SetGeoIPService(geoipSvc *services.GeoIPService) {
h.service.SetGeoIPService(geoipSvc)
}
// resolveAccessList resolves an access list by either numeric ID or UUID.
// It first attempts to parse as uint (backward compatibility), then tries UUID.
func (h *AccessListHandler) resolveAccessList(idOrUUID string) (*models.AccessList, error) {
// Try parsing as numeric ID first (backward compatibility)
if id, err := strconv.ParseUint(idOrUUID, 10, 32); err == nil {
return h.service.GetByID(uint(id))
}
// Empty string check
if idOrUUID == "" {
return nil, fmt.Errorf("invalid ID or UUID")
}
// Try as UUID
return h.service.GetByUUID(idOrUUID)
}
// Create handles POST /api/v1/access-lists
func (h *AccessListHandler) Create(c *gin.Context) {
var acl models.AccessList
if err := c.ShouldBindJSON(&acl); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.service.Create(&acl); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
createdACL, err := h.service.GetByUUID(acl.UUID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
return
}
c.JSON(http.StatusCreated, createdACL)
}
// List handles GET /api/v1/access-lists
func (h *AccessListHandler) List(c *gin.Context) {
acls, err := h.service.List()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, acls)
}
// Get handles GET /api/v1/access-lists/:id
func (h *AccessListHandler) Get(c *gin.Context) {
acl, err := h.resolveAccessList(c.Param("id"))
if err != nil {
if err == services.ErrAccessListNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "access list not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
return
}
c.JSON(http.StatusOK, acl)
}
// Update handles PUT /api/v1/access-lists/:id
func (h *AccessListHandler) Update(c *gin.Context) {
// Resolve access list first to get the internal ID
acl, err := h.resolveAccessList(c.Param("id"))
if err != nil {
if err == services.ErrAccessListNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "access list not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
return
}
var updates models.AccessList
err = c.ShouldBindJSON(&updates)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
err = h.service.Update(acl.ID, &updates)
if err != nil {
if err == services.ErrAccessListNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "access list not found"})
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
updatedAcl, err := h.service.GetByID(acl.ID)
if err != nil {
if err == services.ErrAccessListNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "access list not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
return
}
c.JSON(http.StatusOK, updatedAcl)
}
// Delete handles DELETE /api/v1/access-lists/:id
func (h *AccessListHandler) Delete(c *gin.Context) {
// Resolve access list first to get the internal ID
acl, err := h.resolveAccessList(c.Param("id"))
if err != nil {
if err == services.ErrAccessListNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "access list not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
return
}
if err := h.service.Delete(acl.ID); err != nil {
if err == services.ErrAccessListNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "access list not found"})
return
}
if err == services.ErrAccessListInUse {
c.JSON(http.StatusConflict, gin.H{"error": "access list is in use by proxy hosts"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "access list deleted"})
}
// TestIP handles POST /api/v1/access-lists/:id/test
func (h *AccessListHandler) TestIP(c *gin.Context) {
// Resolve access list first to get the internal ID
acl, err := h.resolveAccessList(c.Param("id"))
if err != nil {
if err == services.ErrAccessListNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "access list not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
return
}
var req struct {
IPAddress string `json:"ip_address" binding:"required"`
}
if bindErr := c.ShouldBindJSON(&req); bindErr != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": bindErr.Error()})
return
}
allowed, reason, err := h.service.TestIP(acl.ID, req.IPAddress)
if err != nil {
if err == services.ErrInvalidIPAddress {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid IP address"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"allowed": allowed,
"reason": reason,
})
}
// GetTemplates handles GET /api/v1/access-lists/templates
func (h *AccessListHandler) GetTemplates(c *gin.Context) {
templates := h.service.GetTemplates()
c.JSON(http.StatusOK, templates)
}