first commit
This commit is contained in:
565
controllers/security_controller.go
Normal file
565
controllers/security_controller.go
Normal file
@@ -0,0 +1,565 @@
|
||||
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...)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user