first commit
This commit is contained in:
49
api/middlewares/admin_middleware.go
Normal file
49
api/middlewares/admin_middleware.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"gauth-central/internal/database"
|
||||
"gauth-central/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AdminMiddleware - Sadece admin rolündeki kullanıcıların erişimini sağlar
|
||||
func AdminMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// Get user_id from context (set by AuthMiddleware)
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch user with roles
|
||||
var user models.User
|
||||
err := database.DB.Preload("Roles").Where("id = ?", userID).First(&user).Error
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not found"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user has admin role
|
||||
hasAdminRole := false
|
||||
for _, role := range user.Roles {
|
||||
if role.Name == "admin" {
|
||||
hasAdminRole = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !hasAdminRole {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Admin access required"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
30
api/middlewares/auth_middleware.go
Normal file
30
api/middlewares/auth_middleware.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"gauth-central/internal/services"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func AuthMiddleware(jwtService *services.JWTService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Authorization header is required"})
|
||||
return
|
||||
}
|
||||
|
||||
tokenString := strings.Replace(authHeader, "Bearer ", "", 1)
|
||||
claims, err := jwtService.ValidateToken(tokenString)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.Set("user_id", claims.UserID)
|
||||
c.Set("email", claims.Email)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
53
api/middlewares/dynamic_cors_middleware.go
Normal file
53
api/middlewares/dynamic_cors_middleware.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"gauth-central/internal/services"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// DynamicCorsMiddleware - Database'den okunan CORS ayarlarıyla çalışan middleware
|
||||
func DynamicCorsMiddleware(settingsService *services.SettingsService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
origin := c.Request.Header.Get("Origin")
|
||||
|
||||
// If no origin header, skip CORS
|
||||
if origin == "" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// Check if origin is allowed
|
||||
allowed, err := settingsService.IsOriginAllowed(origin)
|
||||
if err != nil {
|
||||
// On error, log and deny
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "Failed to verify CORS policy",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
||||
"error": "Origin not allowed by CORS policy",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Set CORS headers
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
|
||||
c.Writer.Header().Set("Access-Control-Max-Age", "86400") // 24 hours
|
||||
|
||||
// Handle preflight requests
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
200
api/middlewares/rate_limit_middleware.go
Normal file
200
api/middlewares/rate_limit_middleware.go
Normal file
@@ -0,0 +1,200 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gauth-central/internal/services"
|
||||
"gauth-central/pkg/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RateLimitMiddleware creates a rate limiting middleware
|
||||
func RateLimitMiddleware(maxRequests int64, duration time.Duration) gin.HandlerFunc {
|
||||
cacheService := services.NewCacheService()
|
||||
settingsService := services.NewSettingsService()
|
||||
|
||||
return func(c *gin.Context) {
|
||||
// Get client IP
|
||||
clientIP := c.ClientIP()
|
||||
path := c.Request.URL.Path
|
||||
method := c.Request.Method
|
||||
|
||||
// Skip checks for localhost (hardcoded safety)
|
||||
if clientIP == "::1" || clientIP == "127.0.0.1" || clientIP == "localhost" {
|
||||
fmt.Printf("%s[LOCALHOST BYPASS]%s IP: %s accessed %s %s\n", utils.ColorCyan, utils.ColorReset, clientIP, method, path)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Check Blacklist from DB
|
||||
blacklist, err := settingsService.GetActiveBlacklistOrigins()
|
||||
if err == nil {
|
||||
for _, blocked := range blacklist {
|
||||
if blocked == clientIP || strings.Contains(blocked, clientIP) {
|
||||
fmt.Printf("%s[BLACKLIST BLOCKED]%s IP: %s tried to access %s %s\n", utils.ColorRed, utils.ColorReset, clientIP, method, path)
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": "Access denied. Your IP is blacklisted.",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Check Whitelist from DB (Skip Rate Limit)
|
||||
whitelist, err := settingsService.GetActiveWhitelistOrigins()
|
||||
if err == nil {
|
||||
for _, allowed := range whitelist {
|
||||
if allowed == clientIP || strings.Contains(allowed, clientIP) {
|
||||
fmt.Printf("%s[WHITELIST ALLOWED]%s IP: %s accessed %s %s (Rate Limit Skipped)\n", utils.ColorGreen, utils.ColorReset, clientIP, method, path)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
key := clientIP
|
||||
|
||||
// Increment counter
|
||||
count, err := cacheService.IncrementRateLimit(key, duration)
|
||||
if err != nil {
|
||||
// If Redis is down, allow the request but log error
|
||||
fmt.Printf("%s[REDIS ERROR]%s Could not increment rate limit for %s: %v\n", utils.ColorRed, utils.ColorReset, clientIP, err)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
remaining := maxRequests - count
|
||||
if remaining < 0 {
|
||||
remaining = 0
|
||||
}
|
||||
|
||||
// Check if limit exceeded
|
||||
if count > maxRequests {
|
||||
fmt.Printf("%s[RATE LIMIT EXCEEDED]%s IP: %s - %s %s - Limit: %d\n", utils.ColorYellow, utils.ColorReset, clientIP, method, path, maxRequests)
|
||||
c.JSON(http.StatusTooManyRequests, gin.H{
|
||||
"error": "Too many requests. Please try again later.",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// Log normal access with remaining limit
|
||||
fmt.Printf("[Rate Limit] IP: %s - %s %s - Used: %d/%d - Remaining: %d\n", clientIP, method, path, count, maxRequests, remaining)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// DynamicRateLimitMiddleware - Database'den ayarları okuyan rate limit middleware
|
||||
func DynamicRateLimitMiddleware(settingName string, settingsService *services.SettingsService) gin.HandlerFunc {
|
||||
cacheService := services.NewCacheService()
|
||||
|
||||
return func(c *gin.Context) {
|
||||
// Get client IP
|
||||
clientIP := c.ClientIP()
|
||||
path := c.Request.URL.Path
|
||||
method := c.Request.Method
|
||||
|
||||
// Skip checks for localhost
|
||||
if clientIP == "::1" || clientIP == "127.0.0.1" || clientIP == "localhost" {
|
||||
fmt.Printf("%s[LOCALHOST BYPASS]%s IP: %s accessed %s %s\n", utils.ColorCyan, utils.ColorReset, clientIP, method, path)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Check Blacklist from DB
|
||||
blacklist, err := settingsService.GetActiveBlacklistOrigins()
|
||||
if err == nil {
|
||||
for _, blocked := range blacklist {
|
||||
if blocked == clientIP || strings.Contains(blocked, clientIP) {
|
||||
fmt.Printf("%s[BLACKLIST BLOCKED]%s IP: %s tried to access %s %s\n", utils.ColorRed, utils.ColorReset, clientIP, method, path)
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": "Access denied. Your IP is blacklisted.",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Check Whitelist from DB (Skip Rate Limit)
|
||||
whitelist, err := settingsService.GetActiveWhitelistOrigins()
|
||||
if err == nil {
|
||||
for _, allowed := range whitelist {
|
||||
if allowed == clientIP || strings.Contains(allowed, clientIP) {
|
||||
fmt.Printf("%s[WHITELIST ALLOWED]%s IP: %s accessed %s %s (Rate Limit Skipped)\n", utils.ColorGreen, utils.ColorReset, clientIP, method, path)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get rate limit settings from database/cache
|
||||
setting, err := settingsService.GetRateLimitSettingByName(settingName)
|
||||
if err != nil || setting == nil {
|
||||
// If error or not found, use default and allow
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// Check if setting is active
|
||||
if !setting.IsActive {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
key := settingName + ":" + clientIP
|
||||
|
||||
// Increment counter
|
||||
duration := time.Duration(setting.WindowSeconds) * time.Second
|
||||
count, err := cacheService.IncrementRateLimit(key, duration)
|
||||
if err != nil {
|
||||
// If Redis is down, allow the request
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
remaining := setting.MaxRequests - count
|
||||
if remaining < 0 {
|
||||
remaining = 0
|
||||
}
|
||||
|
||||
// Check if limit exceeded
|
||||
if count > setting.MaxRequests {
|
||||
fmt.Printf("%s[RATE LIMIT EXCEEDED]%s IP: %s - %s %s - Limit: %d\n", utils.ColorYellow, utils.ColorReset, clientIP, method, path, setting.MaxRequests)
|
||||
c.JSON(http.StatusTooManyRequests, gin.H{
|
||||
"error": "Too many requests. Please try again later.",
|
||||
"limit": setting.MaxRequests,
|
||||
"window": setting.WindowSeconds,
|
||||
"retry_after": setting.WindowSeconds,
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// Log normal access with remaining limit
|
||||
fmt.Printf("[Rate Limit] IP: %s - %s %s - Used: %d/%d - Remaining: %d\n", clientIP, method, path, count, setting.MaxRequests, remaining)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// LoginRateLimitMiddleware limits login attempts per IP
|
||||
func LoginRateLimitMiddleware() gin.HandlerFunc {
|
||||
return RateLimitMiddleware(5, 1*time.Minute) // 5 login attempts per minute
|
||||
}
|
||||
|
||||
// RegisterRateLimitMiddleware limits registration attempts per IP
|
||||
func RegisterRateLimitMiddleware() gin.HandlerFunc {
|
||||
return RateLimitMiddleware(3, 5*time.Minute) // 3 registration attempts per 5 minutes
|
||||
}
|
||||
|
||||
// APIRateLimitMiddleware general API rate limiting
|
||||
func APIRateLimitMiddleware() gin.HandlerFunc {
|
||||
return RateLimitMiddleware(100, 1*time.Minute) // 100 requests per minute
|
||||
}
|
||||
Reference in New Issue
Block a user