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
209 lines
5.7 KiB
Go
Executable File
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)
|
|
}
|