package http import ( "Moonshark/core/runner" "Moonshark/core/utils" "Moonshark/core/utils/logger" "crypto/subtle" "errors" "github.com/valyala/fasthttp" ) // Error for CSRF validation failure var ErrCSRFValidationFailed = errors.New("CSRF token validation failed") // ValidateCSRFToken checks if the CSRF token is valid for a request func ValidateCSRFToken(ctx *runner.Context) bool { // Only validate for form submissions method, ok := ctx.Get("method").(string) if !ok || (method != "POST" && method != "PUT" && method != "PATCH" && method != "DELETE") { return true } // Get form data formData, ok := ctx.Get("form").(map[string]any) if !ok || formData == nil { logger.Warning("CSRF validation failed: no form data") return false } // Get token from form formToken, ok := formData["csrf"].(string) if !ok || formToken == "" { logger.Warning("CSRF validation failed: no token in form") return false } // Get token from session sessionData := ctx.SessionData if sessionData == nil { logger.Warning("CSRF validation failed: no session data") return false } sessionToken, ok := sessionData["_csrf_token"].(string) if !ok || sessionToken == "" { logger.Warning("CSRF validation failed: no token in session") return false } // Constant-time comparison to prevent timing attacks return subtle.ConstantTimeCompare([]byte(formToken), []byte(sessionToken)) == 1 } // HandleCSRFError handles a CSRF validation error func HandleCSRFError(ctx *fasthttp.RequestCtx, errorConfig utils.ErrorPageConfig) { method := string(ctx.Method()) path := string(ctx.Path()) logger.Warning("CSRF validation failed for %s %s", method, path) ctx.SetContentType("text/html; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusForbidden) errorMsg := "Invalid or missing CSRF token. This could be due to an expired form or a cross-site request forgery attempt." errorHTML := utils.ForbiddenPage(errorConfig, path, errorMsg) ctx.SetBody([]byte(errorHTML)) } // GenerateCSRFToken creates a new CSRF token and stores it in the session func GenerateCSRFToken(ctx *runner.Context, length int) (string, error) { if length < 16 { length = 16 // Minimum token length for security } // Create secure random token token, err := GenerateSecureToken(length) if err != nil { return "", err } // Store token in session ctx.SessionData["_csrf_token"] = token return token, nil } // GetCSRFToken retrieves the current CSRF token or generates a new one func GetCSRFToken(ctx *runner.Context) (string, error) { // Check if token already exists in session if token, ok := ctx.SessionData["_csrf_token"].(string); ok && token != "" { return token, nil } // Generate new token return GenerateCSRFToken(ctx, 32) } // CSRFMiddleware validates CSRF tokens for state-changing requests func CSRFMiddleware(ctx *runner.Context) error { if !ValidateCSRFToken(ctx) { return ErrCSRFValidationFailed } return nil }