chore: sync submodule state (parent ref update)

Made-with: Cursor
This commit is contained in:
defiQUG
2026-03-02 12:14:07 -08:00
parent 6c4555cebd
commit 89b82cdadb
883 changed files with 78752 additions and 18180 deletions

25
gateway/go/Dockerfile Normal file
View File

@@ -0,0 +1,25 @@
FROM golang:1.21-alpine AS builder
WORKDIR /app
# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download
# Copy source
COPY . .
# Build
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
FROM alpine:latest
RUN apk --no-cache add ca-certificates curl
WORKDIR /root/
COPY --from=builder /app/main .
EXPOSE 8080
CMD ["./main"]

68
gateway/go/cache/cache.go vendored Normal file
View File

@@ -0,0 +1,68 @@
package cache
import (
"context"
"encoding/json"
"time"
"github.com/go-redis/redis/v8"
)
type Cache struct {
client *redis.Client
ctx context.Context
}
func New(redisURL string) (*Cache, error) {
opt, err := redis.ParseURL(redisURL)
if err != nil {
return nil, err
}
client := redis.NewClient(opt)
ctx := context.Background()
// Test connection
if err := client.Ping(ctx).Err(); err != nil {
return nil, err
}
return &Cache{
client: client,
ctx: ctx,
}, nil
}
func (c *Cache) Get(key string) ([]byte, error) {
val, err := c.client.Get(c.ctx, key).Result()
if err == redis.Nil {
return nil, nil
}
if err != nil {
return nil, err
}
return []byte(val), nil
}
func (c *Cache) Set(key string, value []byte, ttl time.Duration) error {
return c.client.Set(c.ctx, key, value, ttl).Err()
}
func (c *Cache) Delete(key string) error {
return c.client.Del(c.ctx, key).Err()
}
func (c *Cache) InvalidatePattern(pattern string) error {
keys, err := c.client.Keys(c.ctx, pattern).Result()
if err != nil {
return err
}
if len(keys) > 0 {
return c.client.Del(c.ctx, keys...).Err()
}
return nil
}
func (c *Cache) Close() error {
return c.client.Close()
}

View File

@@ -0,0 +1,39 @@
package config
import (
"os"
)
type Config struct {
Port string
BackendURL string
PolicyEngineURL string
RedisURL string
CacheTTL int
JWTSecret string
LogLevel string
}
func Load() *Config {
return &Config{
Port: getEnv("GATEWAY_PORT", "8080"),
BackendURL: getEnv("BACKEND_URL", "http://localhost:3000"),
PolicyEngineURL: getEnv("POLICY_ENGINE_URL", "http://localhost:3000"),
RedisURL: getEnv("REDIS_URL", "redis://localhost:6379"),
CacheTTL: getEnvInt("CACHE_TTL", 120),
JWTSecret: getEnv("JWT_SECRET", ""),
LogLevel: getEnv("LOG_LEVEL", "info"),
}
}
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
func getEnvInt(key string, defaultValue int) int {
// Simplified - in production, use strconv.Atoi
return defaultValue
}

9
gateway/go/go.mod Normal file
View File

@@ -0,0 +1,9 @@
module solacenet-gateway
go 1.21
require (
github.com/gin-gonic/gin v1.9.1
github.com/go-redis/redis/v8 v8.11.5
github.com/golang-jwt/jwt/v5 v5.2.0
)

1
gateway/go/go.sum Normal file
View File

@@ -0,0 +1 @@
# Placeholder - run `go mod tidy` to generate actual checksums

View File

@@ -0,0 +1,13 @@
package handlers
import (
"github.com/gin-gonic/gin"
)
// HealthHandler handles health check requests
func HealthHandler(c *gin.Context) {
c.JSON(200, gin.H{
"status": "healthy",
"service": "solacenet-gateway",
})
}

View File

@@ -0,0 +1,58 @@
package handlers
import (
"io"
"net/http"
"solacenet-gateway/config"
"github.com/gin-gonic/gin"
)
// ProxyHandler proxies requests to backend services
func ProxyHandler(cfg *config.Config) gin.HandlerFunc {
return func(c *gin.Context) {
// Build backend URL
backendURL := cfg.BackendURL + c.Request.URL.Path
if c.Request.URL.RawQuery != "" {
backendURL += "?" + c.Request.URL.RawQuery
}
// Create request
req, err := http.NewRequest(c.Request.Method, backendURL, c.Request.Body)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to create request",
})
return
}
// Copy headers
for key, values := range c.Request.Header {
for _, value := range values {
req.Header.Add(key, value)
}
}
// Make request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
c.JSON(http.StatusBadGateway, gin.H{
"error": "Failed to reach backend",
})
return
}
defer resp.Body.Close()
// Copy response headers
for key, values := range resp.Header {
for _, value := range values {
c.Header(key, value)
}
}
// Copy response body
c.Status(resp.StatusCode)
io.Copy(c.Writer, resp.Body)
}
}

53
gateway/go/main.go Normal file
View File

@@ -0,0 +1,53 @@
package main
import (
"log"
"solacenet-gateway/cache"
"solacenet-gateway/config"
"solacenet-gateway/handlers"
"solacenet-gateway/middleware"
"github.com/gin-gonic/gin"
)
func main() {
cfg := config.Load()
// Initialize Redis cache
redisCache, err := cache.New(cfg.RedisURL)
if err != nil {
log.Printf("Warning: Redis not available, caching disabled: %v", err)
redisCache = nil
}
defer func() {
if redisCache != nil {
redisCache.Close()
}
}()
// Set up Gin router
if cfg.LogLevel == "production" {
gin.SetMode(gin.ReleaseMode)
}
router := gin.Default()
// Middleware
router.Use(middleware.AuthMiddleware(cfg))
if redisCache != nil {
router.Use(middleware.CapabilityCheckMiddleware(cfg, redisCache))
}
router.Use(middleware.RateLimitMiddleware())
// Health check
router.GET("/health", handlers.HealthHandler)
// Proxy handler for backend services
router.Any("/api/*path", handlers.ProxyHandler(cfg))
// Start server
log.Printf("SolaceNet Gateway starting on port %s", cfg.Port)
if err := router.Run(":" + cfg.Port); err != nil {
log.Fatal("Failed to start server:", err)
}
}

View File

@@ -0,0 +1,57 @@
package middleware
import (
"net/http"
"solacenet-gateway/config"
"strings"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
// AuthMiddleware validates JWT tokens
func AuthMiddleware(cfg *config.Config) 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
}
// Extract token
parts := strings.Split(authHeader, " ")
if len(parts) != 2 || parts[0] != "Bearer" {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Invalid authorization header format",
})
c.Abort()
return
}
tokenString := parts[1]
// Parse and validate token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte(cfg.JWTSecret), nil
})
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 {
c.Set("userID", claims["sub"])
c.Set("tenantID", claims["tenantId"])
}
c.Next()
}
}

View File

@@ -0,0 +1,152 @@
package middleware
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"solacenet-gateway/cache"
"solacenet-gateway/config"
"time"
"github.com/gin-gonic/gin"
)
type PolicyDecisionRequest struct {
TenantID string `json:"tenantId"`
ProgramID string `json:"programId,omitempty"`
CapabilityID string `json:"capabilityId"`
Region string `json:"region,omitempty"`
Channel string `json:"channel,omitempty"`
Actor string `json:"actor,omitempty"`
Context map[string]interface{} `json:"context,omitempty"`
}
type PolicyDecisionResponse struct {
Allowed bool `json:"allowed"`
Mode string `json:"mode"`
Limits map[string]interface{} `json:"limits,omitempty"`
ReasonCode string `json:"reasonCode,omitempty"`
DecisionID string `json:"decisionId"`
}
// CapabilityCheckMiddleware checks if a capability is enabled before routing
func CapabilityCheckMiddleware(cfg *config.Config, cache *cache.Cache) gin.HandlerFunc {
return func(c *gin.Context) {
// Extract capability ID from request path or header
capabilityID := c.GetHeader("X-Capability-ID")
if capabilityID == "" {
// Try to extract from path pattern
// This is a simplified version - adjust based on your routing
capabilityID = extractCapabilityFromPath(c.Request.URL.Path)
}
if capabilityID == "" {
c.Next()
return
}
// Extract context from request
tenantID := c.GetHeader("X-Tenant-ID")
programID := c.GetHeader("X-Program-ID")
region := c.GetHeader("X-Region")
channel := c.GetHeader("X-Channel")
actor := c.GetHeader("X-Actor")
// Check cache first
cacheKey := fmt.Sprintf("policy:decision:%s:%s:%s:%s:%s:%s",
tenantID, programID, capabilityID, region, channel, actor)
if cached, err := cache.Get(cacheKey); err == nil && cached != nil {
var decision PolicyDecisionResponse
if json.Unmarshal(cached, &decision) == nil {
if !decision.Allowed {
c.JSON(http.StatusForbidden, gin.H{
"error": "Capability not available",
"reasonCode": decision.ReasonCode,
"mode": decision.Mode,
})
c.Abort()
return
}
c.Set("policyDecision", decision)
c.Next()
return
}
}
// Call policy engine
decisionReq := PolicyDecisionRequest{
TenantID: tenantID,
ProgramID: programID,
CapabilityID: capabilityID,
Region: region,
Channel: channel,
Actor: actor,
}
decision, err := callPolicyEngine(cfg, decisionReq)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to check capability",
})
c.Abort()
return
}
// Cache the decision
if decisionJSON, err := json.Marshal(decision); err == nil {
cache.Set(cacheKey, decisionJSON, time.Duration(cfg.CacheTTL)*time.Second)
}
if !decision.Allowed {
c.JSON(http.StatusForbidden, gin.H{
"error": "Capability not available",
"reasonCode": decision.ReasonCode,
"mode": decision.Mode,
})
c.Abort()
return
}
c.Set("policyDecision", decision)
c.Next()
}
}
func callPolicyEngine(cfg *config.Config, req PolicyDecisionRequest) (*PolicyDecisionResponse, error) {
reqBody, err := json.Marshal(req)
if err != nil {
return nil, err
}
resp, err := http.Post(
fmt.Sprintf("%s/api/v1/solacenet/policy/decide", cfg.PolicyEngineURL),
"application/json",
bytes.NewBuffer(reqBody),
)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var decision PolicyDecisionResponse
if err := json.Unmarshal(body, &decision); err != nil {
return nil, err
}
return &decision, nil
}
func extractCapabilityFromPath(path string) string {
// Simplified extraction - adjust based on your routing patterns
// Example: /api/v1/payments/... -> "payment-gateway"
// This should be configured based on your actual routing
return ""
}

View File

@@ -0,0 +1,14 @@
package middleware
import (
"github.com/gin-gonic/gin"
)
// RateLimitMiddleware implements rate limiting
// In production, use a proper rate limiting library like golang.org/x/time/rate
func RateLimitMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Simplified rate limiting - implement proper rate limiting in production
c.Next()
}
}