mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
243 lines
6.1 KiB
Go
243 lines
6.1 KiB
Go
//go:build js && wasm
|
|
// +build js,wasm
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// RateLimiter implements rate limiting
|
|
type RateLimiter struct {
|
|
mu sync.RWMutex
|
|
requests map[string]*RequestCounter
|
|
limit int
|
|
window time.Duration
|
|
}
|
|
|
|
// RequestCounter tracks requests
|
|
type RequestCounter struct {
|
|
Count int
|
|
ResetTime time.Time
|
|
}
|
|
|
|
// SecurityConfig holds security configuration
|
|
type SecurityConfig struct {
|
|
EnableRateLimit bool
|
|
RateLimit int
|
|
RateWindow time.Duration
|
|
EnableCSP bool
|
|
CSPPolicy string
|
|
}
|
|
|
|
// Global security configuration
|
|
var securityConfig = &SecurityConfig{
|
|
EnableRateLimit: true,
|
|
RateLimit: 100, // 100 requests
|
|
RateWindow: time.Minute,
|
|
EnableCSP: true,
|
|
CSPPolicy: "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; connect-src 'self' https:; img-src 'self' data: https:; style-src 'self' 'unsafe-inline';",
|
|
}
|
|
|
|
// Global rate limiter
|
|
var rateLimiter = NewRateLimiter(securityConfig.RateLimit, securityConfig.RateWindow)
|
|
|
|
// NewRateLimiter creates a new rate limiter
|
|
func NewRateLimiter(limit int, window time.Duration) *RateLimiter {
|
|
return &RateLimiter{
|
|
requests: make(map[string]*RequestCounter),
|
|
limit: limit,
|
|
window: window,
|
|
}
|
|
}
|
|
|
|
// Allow checks if request is allowed
|
|
func (rl *RateLimiter) Allow(identifier string) bool {
|
|
rl.mu.Lock()
|
|
defer rl.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
counter, exists := rl.requests[identifier]
|
|
|
|
if !exists || now.After(counter.ResetTime) {
|
|
// Create new counter or reset existing one
|
|
rl.requests[identifier] = &RequestCounter{
|
|
Count: 1,
|
|
ResetTime: now.Add(rl.window),
|
|
}
|
|
return true
|
|
}
|
|
|
|
if counter.Count >= rl.limit {
|
|
return false
|
|
}
|
|
|
|
counter.Count++
|
|
return true
|
|
}
|
|
|
|
// SecurityMiddleware wraps handlers with security features
|
|
func SecurityMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
// Apply security headers
|
|
applySecurityHeaders(w)
|
|
|
|
// Rate limiting
|
|
if securityConfig.EnableRateLimit {
|
|
// Use client IP or a default identifier for WASM environment
|
|
identifier := getClientIdentifier(r)
|
|
if !rateLimiter.Allow(identifier) {
|
|
writeError(w, http.StatusTooManyRequests, "Rate limit exceeded")
|
|
return
|
|
}
|
|
}
|
|
|
|
// Call the next handler
|
|
next(w, r)
|
|
}
|
|
}
|
|
|
|
// applySecurityHeaders applies security headers to response
|
|
func applySecurityHeaders(w http.ResponseWriter) {
|
|
// CORS headers (already handled by handleCORS, but adding for completeness)
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
|
|
|
// Security headers
|
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
|
w.Header().Set("X-Frame-Options", "DENY")
|
|
w.Header().Set("X-XSS-Protection", "1; mode=block")
|
|
w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
|
|
w.Header().Set("Permissions-Policy", "geolocation=(), microphone=(), camera=()")
|
|
|
|
// Content Security Policy
|
|
if securityConfig.EnableCSP {
|
|
w.Header().Set("Content-Security-Policy", securityConfig.CSPPolicy)
|
|
}
|
|
|
|
// Strict Transport Security (for HTTPS)
|
|
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload")
|
|
}
|
|
|
|
// getClientIdentifier gets a client identifier for rate limiting
|
|
func getClientIdentifier(r *http.Request) string {
|
|
// In WASM environment, we can't rely on real IP
|
|
// Use a combination of headers for identification
|
|
|
|
// Try X-Forwarded-For
|
|
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
|
return xff
|
|
}
|
|
|
|
// Try X-Real-IP
|
|
if xri := r.Header.Get("X-Real-IP"); xri != "" {
|
|
return xri
|
|
}
|
|
|
|
// Try Origin header (common in browser requests)
|
|
if origin := r.Header.Get("Origin"); origin != "" {
|
|
return origin
|
|
}
|
|
|
|
// Try User-Agent as last resort
|
|
if ua := r.Header.Get("User-Agent"); ua != "" {
|
|
return ua
|
|
}
|
|
|
|
// Default identifier
|
|
return "default-client"
|
|
}
|
|
|
|
// ValidatePaymentData validates payment data for security
|
|
func ValidatePaymentData(data map[string]interface{}) error {
|
|
// Check for required fields
|
|
requiredFields := []string{"amount", "currency"}
|
|
for _, field := range requiredFields {
|
|
if _, exists := data[field]; !exists {
|
|
return fmt.Errorf("missing required field: %s", field)
|
|
}
|
|
}
|
|
|
|
// Validate amount
|
|
if amount, ok := data["amount"].(float64); ok {
|
|
if amount <= 0 || amount > 1000000 {
|
|
return fmt.Errorf("invalid amount")
|
|
}
|
|
}
|
|
|
|
// Validate currency
|
|
if currency, ok := data["currency"].(string); ok {
|
|
validCurrencies := []string{"USD", "EUR", "GBP", "JPY"}
|
|
valid := false
|
|
for _, vc := range validCurrencies {
|
|
if currency == vc {
|
|
valid = true
|
|
break
|
|
}
|
|
}
|
|
if !valid {
|
|
return fmt.Errorf("unsupported currency")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SanitizeInput sanitizes user input
|
|
func SanitizeInput(input string) string {
|
|
// Remove any potentially dangerous characters
|
|
// This is a basic implementation - in production, use a proper sanitization library
|
|
sanitized := input
|
|
|
|
// Remove script tags
|
|
sanitized = strings.ReplaceAll(sanitized, "<script>", "")
|
|
sanitized = strings.ReplaceAll(sanitized, "</script>", "")
|
|
|
|
// Remove other potentially dangerous HTML
|
|
sanitized = strings.ReplaceAll(sanitized, "<iframe>", "")
|
|
sanitized = strings.ReplaceAll(sanitized, "</iframe>", "")
|
|
|
|
// Limit length
|
|
if len(sanitized) > 1000 {
|
|
sanitized = sanitized[:1000]
|
|
}
|
|
|
|
return sanitized
|
|
}
|
|
|
|
// TokenizePaymentMethod creates a token for payment method
|
|
func TokenizePaymentMethod(method map[string]interface{}) string {
|
|
// Create a secure token representing the payment method
|
|
// In production, this would use proper tokenization service
|
|
|
|
token := generateRandomString(32)
|
|
|
|
// Store token mapping (in production, use secure storage)
|
|
// For now, just return the token
|
|
return "pmtoken_" + token
|
|
}
|
|
|
|
// ValidateOrigin validates request origin
|
|
func ValidateOrigin(origin string) bool {
|
|
// List of allowed origins
|
|
allowedOrigins := []string{
|
|
"https://motor.sonr.io",
|
|
"https://localhost:3000",
|
|
"http://localhost:3000",
|
|
"https://sonr.io",
|
|
}
|
|
|
|
for _, allowed := range allowedOrigins {
|
|
if origin == allowed {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|