566 lines
21 KiB
Go
566 lines
21 KiB
Go
package controllers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
configs "goFiber/config"
|
|
database "goFiber/database/config"
|
|
"goFiber/database/models"
|
|
"goFiber/middlewares"
|
|
"log"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gofiber/fiber/v3"
|
|
"github.com/redis/go-redis/v9"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
const (
|
|
corsWhitelistCacheKey = "admin:cors:whitelist:list"
|
|
corsBlacklistCacheKey = "admin:cors:blacklist:list"
|
|
rateLimitCacheKey = "admin:rate_limit:list"
|
|
securityCacheTTL = 60
|
|
)
|
|
|
|
type CorsWhitelistRequest struct {
|
|
Origin string `json:"origin" validate:"required"`
|
|
Description string `json:"description"`
|
|
IsActive *bool `json:"is_active"`
|
|
}
|
|
|
|
type CorsBlacklistRequest struct {
|
|
Origin string `json:"origin" validate:"required"`
|
|
Reason string `json:"reason"`
|
|
IsActive *bool `json:"is_active"`
|
|
}
|
|
|
|
type RateLimitSettingRequest struct {
|
|
Name string `json:"name" validate:"required"`
|
|
Description string `json:"description"`
|
|
MaxRequests int64 `json:"max_requests" validate:"required,min=1"`
|
|
WindowSeconds int `json:"window_seconds" validate:"required,min=1"`
|
|
IsActive *bool `json:"is_active"`
|
|
}
|
|
|
|
// ListCorsWhitelists godoc
|
|
// @Summary List CORS whitelists (admin only)
|
|
// @Tags Admin Security
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Success 200 {object} map[string]interface{}
|
|
// @Failure 401 {object} map[string]string
|
|
// @Failure 403 {object} map[string]string
|
|
// @Router /api/v1/admin/cors/whitelist [get]
|
|
func ListCorsWhitelists(c fiber.Ctx) error {
|
|
if database.DB == nil {
|
|
return c.Status(http.StatusServiceUnavailable).JSON(fiber.Map{"error": "database is not configured"})
|
|
}
|
|
|
|
var items []models.CorsWhitelist
|
|
if cached, err := database.Get(corsWhitelistCacheKey); err == nil {
|
|
if unmarshalErr := json.Unmarshal([]byte(cached), &items); unmarshalErr == nil {
|
|
securityLogf("[security][cors-whitelist][cache-hit] count=%d", len(items))
|
|
return c.JSON(fiber.Map{"count": len(items), "items": items})
|
|
}
|
|
} else if !errors.Is(err, redis.Nil) {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "cache read error"})
|
|
}
|
|
|
|
if err := database.DB.Order("id DESC").Find(&items).Error; err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "database error"})
|
|
}
|
|
|
|
cacheJSON, _ := json.Marshal(items)
|
|
_ = database.SetEx(corsWhitelistCacheKey, string(cacheJSON), securityCacheTTL)
|
|
securityLogf("[security][cors-whitelist][db-load] count=%d", len(items))
|
|
|
|
return c.JSON(fiber.Map{"count": len(items), "items": items})
|
|
}
|
|
|
|
// CreateCorsWhitelist godoc
|
|
// @Summary Create CORS whitelist (admin only)
|
|
// @Tags Admin Security
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param request body CorsWhitelistRequest true "Whitelist payload"
|
|
// @Success 201 {object} map[string]interface{}
|
|
// @Failure 400 {object} map[string]string
|
|
// @Router /api/v1/admin/cors/whitelist [post]
|
|
func CreateCorsWhitelist(c fiber.Ctx) error {
|
|
var req CorsWhitelistRequest
|
|
if err := c.Bind().JSON(&req); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
|
|
}
|
|
if err := validate.Struct(req); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
|
|
item := models.CorsWhitelist{
|
|
Origin: strings.TrimSpace(req.Origin),
|
|
Description: strings.TrimSpace(req.Description),
|
|
IsActive: boolValue(req.IsActive, true),
|
|
CreatedBy: currentActor(c),
|
|
}
|
|
if err := database.DB.Create(&item).Error; err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "record could not be created"})
|
|
}
|
|
invalidateSecurityCaches()
|
|
securityLogf("[security][cors-whitelist][create] origin=%s by=%s", item.Origin, item.CreatedBy)
|
|
return c.Status(http.StatusCreated).JSON(fiber.Map{"item": item})
|
|
}
|
|
|
|
// UpdateCorsWhitelist godoc
|
|
// @Summary Update CORS whitelist (admin only)
|
|
// @Tags Admin Security
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param id path int true "Whitelist ID"
|
|
// @Param request body CorsWhitelistRequest true "Whitelist payload"
|
|
// @Success 200 {object} map[string]interface{}
|
|
// @Failure 400 {object} map[string]string
|
|
// @Failure 404 {object} map[string]string
|
|
// @Router /api/v1/admin/cors/whitelist/{id} [put]
|
|
func UpdateCorsWhitelist(c fiber.Ctx) error {
|
|
id, err := parseID(c.Params("id"))
|
|
if err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid id"})
|
|
}
|
|
|
|
var req CorsWhitelistRequest
|
|
if err := c.Bind().JSON(&req); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
|
|
}
|
|
if err := validate.Struct(req); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
|
|
var item models.CorsWhitelist
|
|
if err := database.DB.First(&item, id).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return c.Status(http.StatusNotFound).JSON(fiber.Map{"error": "record not found"})
|
|
}
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "database error"})
|
|
}
|
|
|
|
item.Origin = strings.TrimSpace(req.Origin)
|
|
item.Description = strings.TrimSpace(req.Description)
|
|
item.IsActive = boolValue(req.IsActive, item.IsActive)
|
|
item.CreatedBy = currentActor(c)
|
|
if err := database.DB.Save(&item).Error; err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "record could not be updated"})
|
|
}
|
|
invalidateSecurityCaches()
|
|
securityLogf("[security][cors-whitelist][update] id=%d origin=%s by=%s", item.ID, item.Origin, item.CreatedBy)
|
|
return c.JSON(fiber.Map{"item": item})
|
|
}
|
|
|
|
// DeleteCorsWhitelist godoc
|
|
// @Summary Soft delete CORS whitelist (admin only)
|
|
// @Tags Admin Security
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param id path int true "Whitelist ID"
|
|
// @Success 200 {object} map[string]interface{}
|
|
// @Failure 404 {object} map[string]string
|
|
// @Router /api/v1/admin/cors/whitelist/{id} [delete]
|
|
func DeleteCorsWhitelist(c fiber.Ctx) error {
|
|
id, err := parseID(c.Params("id"))
|
|
if err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid id"})
|
|
}
|
|
|
|
if err := database.DB.Delete(&models.CorsWhitelist{}, id).Error; err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "record could not be deleted"})
|
|
}
|
|
invalidateSecurityCaches()
|
|
securityLogf("[security][cors-whitelist][soft-delete] id=%d by=%s", id, currentActor(c))
|
|
return c.JSON(fiber.Map{"message": "soft deleted", "id": id})
|
|
}
|
|
|
|
// HardDeleteCorsWhitelist godoc
|
|
// @Summary Hard delete CORS whitelist (admin only)
|
|
// @Tags Admin Security
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param id path int true "Whitelist ID"
|
|
// @Success 200 {object} map[string]interface{}
|
|
// @Failure 404 {object} map[string]string
|
|
// @Router /api/v1/admin/cors/whitelist/{id}/hard [delete]
|
|
func HardDeleteCorsWhitelist(c fiber.Ctx) error {
|
|
id, err := parseID(c.Params("id"))
|
|
if err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid id"})
|
|
}
|
|
|
|
if err := database.DB.Unscoped().Delete(&models.CorsWhitelist{}, id).Error; err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "record could not be hard-deleted"})
|
|
}
|
|
invalidateSecurityCaches()
|
|
securityLogf("[security][cors-whitelist][hard-delete] id=%d by=%s", id, currentActor(c))
|
|
return c.JSON(fiber.Map{"message": "hard deleted", "id": id})
|
|
}
|
|
|
|
// ListCorsBlacklists godoc
|
|
// @Summary List CORS blacklists (admin only)
|
|
// @Tags Admin Security
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Success 200 {object} map[string]interface{}
|
|
// @Failure 401 {object} map[string]string
|
|
// @Failure 403 {object} map[string]string
|
|
// @Router /api/v1/admin/cors/blacklist [get]
|
|
func ListCorsBlacklists(c fiber.Ctx) error {
|
|
if database.DB == nil {
|
|
return c.Status(http.StatusServiceUnavailable).JSON(fiber.Map{"error": "database is not configured"})
|
|
}
|
|
|
|
var items []models.CorsBlacklist
|
|
if cached, err := database.Get(corsBlacklistCacheKey); err == nil {
|
|
if unmarshalErr := json.Unmarshal([]byte(cached), &items); unmarshalErr == nil {
|
|
securityLogf("[security][cors-blacklist][cache-hit] count=%d", len(items))
|
|
return c.JSON(fiber.Map{"count": len(items), "items": items})
|
|
}
|
|
} else if !errors.Is(err, redis.Nil) {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "cache read error"})
|
|
}
|
|
|
|
if err := database.DB.Order("id DESC").Find(&items).Error; err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "database error"})
|
|
}
|
|
|
|
cacheJSON, _ := json.Marshal(items)
|
|
_ = database.SetEx(corsBlacklistCacheKey, string(cacheJSON), securityCacheTTL)
|
|
securityLogf("[security][cors-blacklist][db-load] count=%d", len(items))
|
|
|
|
return c.JSON(fiber.Map{"count": len(items), "items": items})
|
|
}
|
|
|
|
// CreateCorsBlacklist godoc
|
|
// @Summary Create CORS blacklist (admin only)
|
|
// @Tags Admin Security
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param request body CorsBlacklistRequest true "Blacklist payload"
|
|
// @Success 201 {object} map[string]interface{}
|
|
// @Failure 400 {object} map[string]string
|
|
// @Router /api/v1/admin/cors/blacklist [post]
|
|
func CreateCorsBlacklist(c fiber.Ctx) error {
|
|
var req CorsBlacklistRequest
|
|
if err := c.Bind().JSON(&req); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
|
|
}
|
|
if err := validate.Struct(req); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
|
|
item := models.CorsBlacklist{
|
|
Origin: strings.TrimSpace(req.Origin),
|
|
Reason: strings.TrimSpace(req.Reason),
|
|
IsActive: boolValue(req.IsActive, true),
|
|
CreatedBy: currentActor(c),
|
|
}
|
|
if err := database.DB.Create(&item).Error; err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "record could not be created"})
|
|
}
|
|
invalidateSecurityCaches()
|
|
securityLogf("[security][cors-blacklist][create] origin=%s by=%s", item.Origin, item.CreatedBy)
|
|
return c.Status(http.StatusCreated).JSON(fiber.Map{"item": item})
|
|
}
|
|
|
|
// UpdateCorsBlacklist godoc
|
|
// @Summary Update CORS blacklist (admin only)
|
|
// @Tags Admin Security
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param id path int true "Blacklist ID"
|
|
// @Param request body CorsBlacklistRequest true "Blacklist payload"
|
|
// @Success 200 {object} map[string]interface{}
|
|
// @Failure 400 {object} map[string]string
|
|
// @Failure 404 {object} map[string]string
|
|
// @Router /api/v1/admin/cors/blacklist/{id} [put]
|
|
func UpdateCorsBlacklist(c fiber.Ctx) error {
|
|
id, err := parseID(c.Params("id"))
|
|
if err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid id"})
|
|
}
|
|
|
|
var req CorsBlacklistRequest
|
|
if err := c.Bind().JSON(&req); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
|
|
}
|
|
if err := validate.Struct(req); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
|
|
var item models.CorsBlacklist
|
|
if err := database.DB.First(&item, id).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return c.Status(http.StatusNotFound).JSON(fiber.Map{"error": "record not found"})
|
|
}
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "database error"})
|
|
}
|
|
|
|
item.Origin = strings.TrimSpace(req.Origin)
|
|
item.Reason = strings.TrimSpace(req.Reason)
|
|
item.IsActive = boolValue(req.IsActive, item.IsActive)
|
|
item.CreatedBy = currentActor(c)
|
|
if err := database.DB.Save(&item).Error; err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "record could not be updated"})
|
|
}
|
|
invalidateSecurityCaches()
|
|
securityLogf("[security][cors-blacklist][update] id=%d origin=%s by=%s", item.ID, item.Origin, item.CreatedBy)
|
|
return c.JSON(fiber.Map{"item": item})
|
|
}
|
|
|
|
// DeleteCorsBlacklist godoc
|
|
// @Summary Soft delete CORS blacklist (admin only)
|
|
// @Tags Admin Security
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param id path int true "Blacklist ID"
|
|
// @Success 200 {object} map[string]interface{}
|
|
// @Failure 404 {object} map[string]string
|
|
// @Router /api/v1/admin/cors/blacklist/{id} [delete]
|
|
func DeleteCorsBlacklist(c fiber.Ctx) error {
|
|
id, err := parseID(c.Params("id"))
|
|
if err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid id"})
|
|
}
|
|
|
|
if err := database.DB.Delete(&models.CorsBlacklist{}, id).Error; err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "record could not be deleted"})
|
|
}
|
|
invalidateSecurityCaches()
|
|
securityLogf("[security][cors-blacklist][soft-delete] id=%d by=%s", id, currentActor(c))
|
|
return c.JSON(fiber.Map{"message": "soft deleted", "id": id})
|
|
}
|
|
|
|
// HardDeleteCorsBlacklist godoc
|
|
// @Summary Hard delete CORS blacklist (admin only)
|
|
// @Tags Admin Security
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param id path int true "Blacklist ID"
|
|
// @Success 200 {object} map[string]interface{}
|
|
// @Failure 404 {object} map[string]string
|
|
// @Router /api/v1/admin/cors/blacklist/{id}/hard [delete]
|
|
func HardDeleteCorsBlacklist(c fiber.Ctx) error {
|
|
id, err := parseID(c.Params("id"))
|
|
if err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid id"})
|
|
}
|
|
|
|
if err := database.DB.Unscoped().Delete(&models.CorsBlacklist{}, id).Error; err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "record could not be hard-deleted"})
|
|
}
|
|
invalidateSecurityCaches()
|
|
securityLogf("[security][cors-blacklist][hard-delete] id=%d by=%s", id, currentActor(c))
|
|
return c.JSON(fiber.Map{"message": "hard deleted", "id": id})
|
|
}
|
|
|
|
// ListRateLimitSettings godoc
|
|
// @Summary List rate limit settings (admin only)
|
|
// @Tags Admin Security
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Success 200 {object} map[string]interface{}
|
|
// @Failure 401 {object} map[string]string
|
|
// @Failure 403 {object} map[string]string
|
|
// @Router /api/v1/admin/rate-limit [get]
|
|
func ListRateLimitSettings(c fiber.Ctx) error {
|
|
if database.DB == nil {
|
|
return c.Status(http.StatusServiceUnavailable).JSON(fiber.Map{"error": "database is not configured"})
|
|
}
|
|
|
|
var items []models.RateLimitSetting
|
|
if cached, err := database.Get(rateLimitCacheKey); err == nil {
|
|
if unmarshalErr := json.Unmarshal([]byte(cached), &items); unmarshalErr == nil {
|
|
securityLogf("[security][rate-limit][cache-hit] count=%d", len(items))
|
|
return c.JSON(fiber.Map{"count": len(items), "items": items})
|
|
}
|
|
} else if !errors.Is(err, redis.Nil) {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "cache read error"})
|
|
}
|
|
|
|
if err := database.DB.Order("id DESC").Find(&items).Error; err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "database error"})
|
|
}
|
|
|
|
cacheJSON, _ := json.Marshal(items)
|
|
_ = database.SetEx(rateLimitCacheKey, string(cacheJSON), securityCacheTTL)
|
|
securityLogf("[security][rate-limit][db-load] count=%d", len(items))
|
|
|
|
return c.JSON(fiber.Map{"count": len(items), "items": items})
|
|
}
|
|
|
|
// CreateRateLimitSetting godoc
|
|
// @Summary Create rate limit setting (admin only)
|
|
// @Tags Admin Security
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param request body RateLimitSettingRequest true "Rate limit payload"
|
|
// @Success 201 {object} map[string]interface{}
|
|
// @Failure 400 {object} map[string]string
|
|
// @Router /api/v1/admin/rate-limit [post]
|
|
func CreateRateLimitSetting(c fiber.Ctx) error {
|
|
var req RateLimitSettingRequest
|
|
if err := c.Bind().JSON(&req); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
|
|
}
|
|
if err := validate.Struct(req); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
|
|
item := models.RateLimitSetting{
|
|
Name: strings.TrimSpace(req.Name),
|
|
Description: strings.TrimSpace(req.Description),
|
|
MaxRequests: req.MaxRequests,
|
|
WindowSeconds: req.WindowSeconds,
|
|
IsActive: boolValue(req.IsActive, true),
|
|
UpdatedBy: currentActor(c),
|
|
}
|
|
if err := database.DB.Create(&item).Error; err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "record could not be created"})
|
|
}
|
|
invalidateSecurityCaches()
|
|
securityLogf("[security][rate-limit][create] name=%s max=%d window=%ds by=%s", item.Name, item.MaxRequests, item.WindowSeconds, item.UpdatedBy)
|
|
return c.Status(http.StatusCreated).JSON(fiber.Map{"item": item})
|
|
}
|
|
|
|
// UpdateRateLimitSetting godoc
|
|
// @Summary Update rate limit setting (admin only)
|
|
// @Tags Admin Security
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param id path int true "Rate limit ID"
|
|
// @Param request body RateLimitSettingRequest true "Rate limit payload"
|
|
// @Success 200 {object} map[string]interface{}
|
|
// @Failure 400 {object} map[string]string
|
|
// @Failure 404 {object} map[string]string
|
|
// @Router /api/v1/admin/rate-limit/{id} [put]
|
|
func UpdateRateLimitSetting(c fiber.Ctx) error {
|
|
id, err := parseID(c.Params("id"))
|
|
if err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid id"})
|
|
}
|
|
|
|
var req RateLimitSettingRequest
|
|
if err := c.Bind().JSON(&req); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
|
|
}
|
|
if err := validate.Struct(req); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
|
|
var item models.RateLimitSetting
|
|
if err := database.DB.First(&item, id).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return c.Status(http.StatusNotFound).JSON(fiber.Map{"error": "record not found"})
|
|
}
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "database error"})
|
|
}
|
|
|
|
item.Name = strings.TrimSpace(req.Name)
|
|
item.Description = strings.TrimSpace(req.Description)
|
|
item.MaxRequests = req.MaxRequests
|
|
item.WindowSeconds = req.WindowSeconds
|
|
item.IsActive = boolValue(req.IsActive, item.IsActive)
|
|
item.UpdatedBy = currentActor(c)
|
|
if err := database.DB.Save(&item).Error; err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "record could not be updated"})
|
|
}
|
|
invalidateSecurityCaches()
|
|
securityLogf("[security][rate-limit][update] id=%d name=%s max=%d window=%ds by=%s", item.ID, item.Name, item.MaxRequests, item.WindowSeconds, item.UpdatedBy)
|
|
return c.JSON(fiber.Map{"item": item})
|
|
}
|
|
|
|
// DeleteRateLimitSetting godoc
|
|
// @Summary Soft delete rate limit setting (admin only)
|
|
// @Tags Admin Security
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param id path int true "Rate limit ID"
|
|
// @Success 200 {object} map[string]interface{}
|
|
// @Failure 404 {object} map[string]string
|
|
// @Router /api/v1/admin/rate-limit/{id} [delete]
|
|
func DeleteRateLimitSetting(c fiber.Ctx) error {
|
|
id, err := parseID(c.Params("id"))
|
|
if err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid id"})
|
|
}
|
|
|
|
if err := database.DB.Delete(&models.RateLimitSetting{}, id).Error; err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "record could not be deleted"})
|
|
}
|
|
invalidateSecurityCaches()
|
|
securityLogf("[security][rate-limit][soft-delete] id=%d by=%s", id, currentActor(c))
|
|
return c.JSON(fiber.Map{"message": "soft deleted", "id": id})
|
|
}
|
|
|
|
// HardDeleteRateLimitSetting godoc
|
|
// @Summary Hard delete rate limit setting (admin only)
|
|
// @Tags Admin Security
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param id path int true "Rate limit ID"
|
|
// @Success 200 {object} map[string]interface{}
|
|
// @Failure 404 {object} map[string]string
|
|
// @Router /api/v1/admin/rate-limit/{id}/hard [delete]
|
|
func HardDeleteRateLimitSetting(c fiber.Ctx) error {
|
|
id, err := parseID(c.Params("id"))
|
|
if err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid id"})
|
|
}
|
|
|
|
if err := database.DB.Unscoped().Delete(&models.RateLimitSetting{}, id).Error; err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "record could not be hard-deleted"})
|
|
}
|
|
invalidateSecurityCaches()
|
|
securityLogf("[security][rate-limit][hard-delete] id=%d by=%s", id, currentActor(c))
|
|
return c.JSON(fiber.Map{"message": "hard deleted", "id": id})
|
|
}
|
|
|
|
func parseID(param string) (uint, error) {
|
|
v, err := strconv.ParseUint(strings.TrimSpace(param), 10, 64)
|
|
if err != nil || v == 0 {
|
|
return 0, errors.New("invalid id")
|
|
}
|
|
return uint(v), nil
|
|
}
|
|
|
|
func invalidateSecurityCaches() {
|
|
_ = database.Delete(corsWhitelistCacheKey)
|
|
_ = database.Delete(corsBlacklistCacheKey)
|
|
_ = database.Delete(rateLimitCacheKey)
|
|
_ = database.Delete("cors:active:whitelist")
|
|
_ = database.Delete("cors:active:blacklist")
|
|
}
|
|
|
|
func currentActor(c fiber.Ctx) string {
|
|
if claims, ok := middlewares.GetAuthClaims(c); ok && strings.TrimSpace(claims.Email) != "" {
|
|
return claims.Email
|
|
}
|
|
return "system"
|
|
}
|
|
|
|
func boolValue(v *bool, fallback bool) bool {
|
|
if v == nil {
|
|
return fallback
|
|
}
|
|
return *v
|
|
}
|
|
|
|
func securityLogf(format string, args ...interface{}) {
|
|
if configs.AppConfig != nil && configs.AppConfig.CorsDebug {
|
|
log.Printf(format, args...)
|
|
}
|
|
}
|