105 lines
2.9 KiB
Go
105 lines
2.9 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 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
|
|
}
|