Moonshark/core/http/Csrf.go
2025-04-10 07:51:15 -05:00

139 lines
3.8 KiB
Go

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 session from context
sessionMap, ok := ctx.Get("session").(map[string]any)
if !ok || sessionMap == nil {
logger.Warning("CSRF validation failed: no session data")
return false
}
// Get session data
sessionData, ok := sessionMap["data"].(map[string]any)
if !ok || sessionData == nil {
logger.Warning("CSRF validation failed: no session data map")
return false
}
// Get token from session
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
}
// Get session from context
sessionMap, ok := ctx.Get("session").(map[string]any)
if !ok || sessionMap == nil {
return "", errors.New("no session found in context")
}
// Get session data
sessionData, ok := sessionMap["data"].(map[string]any)
if !ok {
// Initialize session data if it doesn't exist
sessionData = make(map[string]any)
sessionMap["data"] = sessionData
}
// Store token in session
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) {
// Get session from context
sessionMap, ok := ctx.Get("session").(map[string]any)
if !ok || sessionMap == nil {
return "", errors.New("no session found in context")
}
// Get session data
sessionData, ok := sessionMap["data"].(map[string]any)
if !ok || sessionData == nil {
return GenerateCSRFToken(ctx, 32)
}
// Check if token already exists in session
if token, ok := 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
}