package security import ( "crypto/subtle" "fmt" "log" "net/http" "strings" "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" ) // SecurityConfig holds security-related configuration type SecurityConfig struct { JWTSecret string AllowedOrigins []string EnableRateLimiting bool MaxRequestsPerMinute int EnableCSP bool CSPReportURI string EnableHSTS bool EnableXSSProtection bool EnableContentTypeNosniff bool EnableHSTSMaxAge int64 EnableFrameOptions bool FrameOptionValue string APIKey string } // DefaultSecurityConfig returns a default security configuration func DefaultSecurityConfig() *SecurityConfig { return &SecurityConfig{ AllowedOrigins: []string{"*"}, EnableRateLimiting: true, MaxRequestsPerMinute: 100, EnableCSP: true, CSPReportURI: "/csp-report", EnableHSTS: true, EnableXSSProtection: true, EnableContentTypeNosniff: true, EnableHSTSMaxAge: 31536000, // 1 year EnableFrameOptions: true, FrameOptionValue: "DENY", } } // SecurityMiddleware applies various security measures func SecurityMiddleware(config *SecurityConfig) gin.HandlerFunc { return func(c *gin.Context) { // Apply security headers applySecurityHeaders(c, config) // Rate limiting (simplified implementation) if config.EnableRateLimiting { if !checkRateLimit(c, config.MaxRequestsPerMinute) { c.JSON(http.StatusTooManyRequests, gin.H{"error": "Rate limit exceeded"}) c.Abort() return } } // Check for API key if required if config.APIKey != "" { if !validateAPIKey(c, config.APIKey) { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid API key"}) c.Abort() return } } c.Next() } } // applySecurityHeaders adds security-related headers to responses func applySecurityHeaders(c *gin.Context, config *SecurityConfig) { // Content Security Policy if config.EnableCSP { csp := fmt.Sprintf("default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://*.keycloak.org; frame-ancestors 'none'; report-uri %s", config.CSPReportURI) c.Header("Content-Security-Policy", csp) } // HTTP Strict Transport Security if config.EnableHSTS { c.Header("Strict-Transport-Security", fmt.Sprintf("max-age=%d; includeSubDomains; preload", config.EnableHSTSMaxAge)) } // X-XSS-Protection if config.EnableXSSProtection { c.Header("X-XSS-Protection", "1; mode=block") } // X-Content-Type-Options if config.EnableContentTypeNosniff { c.Header("X-Content-Type-Options", "nosniff") } // X-Frame-Options if config.EnableFrameOptions { c.Header("X-Frame-Options", config.FrameOptionValue) } // Referrer Policy c.Header("Referrer-Policy", "strict-origin-when-cross-origin") // Permissions Policy c.Header("Permissions-Policy", "geolocation=(), microphone=(), camera=()") // Cross-Origin Resource Sharing (CORS) if len(config.AllowedOrigins) > 0 { c.Header("Access-Control-Allow-Origin", strings.Join(config.AllowedOrigins, ", ")) c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH") c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization, X-Requested-With") c.Header("Access-Control-Allow-Credentials", "true") c.Header("Access-Control-Expose-Headers", "Content-Length, Content-Type, X-Total-Count") } } // checkRateLimit implements a simple rate limiting mechanism func checkRateLimit(c *gin.Context, maxRequests int) bool { // In a real implementation, this would use Redis or similar to track requests per IP/user // For now, we'll implement a simplified version // Get client IP (this would be used in a real implementation) clientIP := c.ClientIP() _ = clientIP // Use the variable to avoid "declared but not used" error // For demo purposes, always return true (no actual rate limiting) // In a production environment, you would check against a request counter return true } // validateAPIKey validates the API key in the request func validateAPIKey(c *gin.Context, expectedAPIKey string) bool { // Check API key in header apiKey := c.GetHeader("X-API-Key") if apiKey == "" { // Check API key in query parameter as fallback apiKey = c.Query("api_key") } if apiKey == "" { return false } // Use constant-time comparison to prevent timing attacks return subtle.ConstantTimeCompare([]byte(apiKey), []byte(expectedAPIKey)) == 1 } // CSPReportHandler handles content security policy violation reports func CSPReportHandler(c *gin.Context) { // Log the CSP violation for monitoring log.Printf("CSP Violation: %s", c.Request.URL.Path) // In a real implementation, you would store these reports for security analysis c.JSON(http.StatusOK, gin.H{"message": "CSP report received"}) } // GDPRComplianceMiddleware ensures compliance with GDPR regulations func GDPRComplianceMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // Add privacy headers c.Header("Privacy-Policy", "/privacy-policy") // Check for explicit consent (simplified implementation) consentGiven := c.GetHeader("X-Consent-Given") if consentGiven != "true" { // For sensitive operations, check consent if isSensitiveOperation(c.Request.URL.Path) { c.JSON(http.StatusPreconditionRequired, gin.H{ "error": "User consent required for this operation", "required_consent": "privacy_policy"}, ) c.Abort() return } } c.Next() } } // isSensitiveOperation checks if the requested operation involves personal data func isSensitiveOperation(path string) bool { sensitivePaths := []string{ "/api/v1/users", "/api/v1/profile", "/api/v1/applications", "/api/v1/resumes", } for _, sensitivePath := range sensitivePaths { if strings.HasPrefix(path, sensitivePath) { return true } } return false } // DataResidencyMiddleware ensures data residency requirements are met func DataResidencyMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // In a real implementation, this would check the user's location // and ensure their data is stored in the appropriate geographic region // For now, we'll just pass through // Add data residency headers c.Header("X-Data-Residency", "US") c.Next() } } // AuditLogMiddleware logs security-relevant events func AuditLogMiddleware() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() c.Next() // Log request details for audit purposes log.Printf( "AUDIT: %s %s %s %s %s %s %d %v", c.ClientIP(), c.Request.UserAgent(), c.Request.Method, c.Request.URL.Path, c.Request.URL.Query(), c.Params, c.Writer.Status(), time.Since(start), ) } } // PCIComplianceMiddleware implements PCI DSS requirements func PCIComplianceMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // For PCI compliance, we need to ensure sensitive data like credit cards // are not stored or transmitted inappropriately // In this job platform, we don't expect credit card info, but we'll check // for any potentially sensitive data in the request // Check request body for sensitive information if isSensitiveDataInRequest(c) { log.Printf("WARNING: Potential sensitive data detected in request: %s", c.Request.URL.Path) } c.Next() } } // isSensitiveDataInRequest checks if the request contains sensitive data func isSensitiveDataInRequest(c *gin.Context) bool { // In a real implementation, this would scan the request body for: // - Credit card numbers using regex patterns // - SSNs using regex patterns // - Other sensitive financial data // For now, we'll just return false as this is a job platform return false } // SocComplianceMiddleware implements SOC 2 compliance measures func SocComplianceMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // SOC 2 focuses on security, availability, processing integrity, // confidentiality, and privacy // Ensure all operations are logged and monitored c.Next() } } // FedRAMPComplianceMiddleware implements FedRAMP requirements func FedRAMPComplianceMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // FedRAMP requires strict access controls, continuous monitoring, // and documentation of security controls // This is a simplified implementation // Check if request requires FedRAMP compliance if requiresFedRAMP(c.Request.URL.Path) { // Ensure proper authentication and authorization userID, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required for FedRAMP-compliant access"}) c.Abort() return } // Log the access for compliance monitoring log.Printf("FedRAMP Access: User %v accessed %s at %v", userID, c.Request.URL.Path, time.Now()) } c.Next() } } // requiresFedRAMP checks if a path requires FedRAMP compliance func requiresFedRAMP(path string) bool { // In a real implementation, this would check against FedRAMP-protected resources // For now, we'll consider administrative paths as requiring FedRAMP compliance protectedPaths := []string{ "/api/v1/admin", "/api/v1/users", "/api/v1/audit", } for _, protectedPath := range protectedPaths { if strings.HasPrefix(path, protectedPath) { return true } } return false } // JWTAuthorizationMiddleware validates JWT tokens and ensures the user has required permissions func JWTAuthorizationMiddleware(requiredPermissions ...string) gin.HandlerFunc { return func(c *gin.Context) { authHeader := c.GetHeader("Authorization") if authHeader == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header required"}) c.Abort() return } tokenString := strings.TrimPrefix(authHeader, "Bearer ") if tokenString == authHeader { c.JSON(http.StatusUnauthorized, gin.H{"error": "Bearer token required"}) c.Abort() return } // Parse and validate the token token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return []byte("supersecretkeyforjwt"), nil // In real implementation, use config }) if err != nil || !token.Valid { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"}) c.Abort() return } // Extract claims if claims, ok := token.Claims.(jwt.MapClaims); ok { // Extract user ID from claims if userIDStr, ok := claims["user_id"].(string); ok { userID, err := uuid.Parse(userIDStr) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid user ID in token"}) c.Abort() return } // Store user ID in context for use in handlers c.Set("user_id", userID) // Check permissions if required if len(requiredPermissions) > 0 { userRole, ok := claims["role"].(string) if !ok { c.JSON(http.StatusForbidden, gin.H{"error": "Role not found in token"}) c.Abort() return } if !hasPermission(userRole, requiredPermissions) { c.JSON(http.StatusForbidden, gin.H{ "error": "Insufficient permissions", "required_permissions": requiredPermissions, "role": userRole, }) c.Abort() return } } } else { c.JSON(http.StatusUnauthorized, gin.H{"error": "User ID not found in token"}) c.Abort() return } } else { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token claims"}) c.Abort() return } c.Next() } } // hasPermission checks if a user role has the required permissions func hasPermission(userRole string, requiredPermissions []string) bool { // In a real implementation, this would check permissions database // For now, we'll implement a simple role-based permission system: // admin: can access everything // job_provider: can create positions, manage applications // job_seeker: can apply to positions, upload resumes if userRole == "admin" { return true } for _, perm := range requiredPermissions { switch perm { case "create_position", "manage_applications": if userRole == "job_provider" || userRole == "admin" { return true } case "apply_to_position", "upload_resume": if userRole == "job_seeker" || userRole == "admin" { return true } } } return false }