cors

package
v4.28.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 17, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

README

cors

CORS (Cross-Origin Resource Sharing) filter for go-restful. Supports static service-level configuration and optional dynamic namespace-scoped configuration fetched from justice-config-service.

Usage

Use NewCrossOriginResourceSharing to initialize the filter. This is the recommended way to set up CORS as it makes all configuration explicit:

import (
    "github.com/AccelByte/go-restful-plugins/v4/pkg/cors"
    iam "github.com/AccelByte/iam-go-sdk/v2"
)

filter := cors.NewCrossOriginResourceSharing(
    "http://justice-config-service/config", // configServiceURL: set empty string to disable dynamic config
    iamClient,                              // iam.Client from iam-go-sdk/v2; pass nil to disable auth
    []string{"https://example.com", "https://*.mycompany.io"}, // allowedDomains
    []string{"GET", "POST", "PUT", "DELETE"},                  // allowedMethods
    []string{"Content-Type", "Authorization"},                 // allowedHeaders
    []string{},                                                // exposeHeaders
    true,                                                      // cookiesAllowed
    3600,                                                      // maxAge (seconds)
)
filter.Container = container
container.Filter(filter.Filter)

Static config only (no dynamic config, no IAM auth):

filter := cors.NewCrossOriginResourceSharing(
    "",   // empty configServiceURL disables dynamic config
    nil,  // nil iamClient disables auth
    []string{"https://example.com"},
    []string{"GET", "POST"},
    []string{"Content-Type"},
    []string{},
    true,
    0,
)
filter.Container = container
container.Filter(filter.Filter)

Allowed Domain Patterns

AllowedDomains supports four pattern types:

Pattern Example Behavior
Exact https://example.com Matches only that exact origin
Allow-all * Matches any origin
Wildcard https://*.mycompany.io Matches one subdomain level (game.mycompany.io ✅, a.b.mycompany.io ❌)
Regex re:^https://.*\.example\.com$ Full regular expression match

Wildcard validation: the static host after *. must contain at least one dot. https://*.io is rejected as too broad; https://*.accelbyte.io is valid.

Dynamic Namespace-Scoped Configuration

When configServiceURL is non-empty, the filter fetches per-namespace CORS config from justice-config-service on the first request and caches it for 1 minute. If empty, only static config is used.

Namespace Resolution

The namespace is resolved from each request in priority order:

  1. Path parameter — route contains {namespace} (e.g. /namespaces/accelbyte/...accelbyte)
  2. Subdomain — first component of Host header with ≥3 parts (e.g. game-ns.prod.example.iogame-ns)
  3. Headerx-ab-rl-ns request header
  4. Fallback — service static config used if no namespace found
Config Merging

Namespace config is merged with service defaults:

  • List fields (allowed_domains, allowed_headers, allowed_methods, expose_headers): combined and deduplicated
  • Scalar fields (cookies_allowed, max_age): namespace value takes precedence
Service config:   allowed_domains: ["https://service.com"]
Namespace config: allowed_domains: ["https://*.game.example.com"]
Merged result:    allowed_domains: ["https://service.com", "https://*.game.example.com"]
Config Service Response Format
{
  "namespace": "game3",
  "key": "CORS",
  "value": "{\"allowed_domains\":[\"https://*.example.com\"],\"allowed_methods\":[\"GET\",\"POST\"],\"allowed_headers\":[\"Content-Type\"],\"cookies_allowed\":true,\"max_age\":3600}",
  "createdAt": "2026-02-09T07:29:24.357Z",
  "updatedAt": "2026-02-09T07:29:24.357Z",
  "isPublic": false
}
IAM Authentication

The iamClient parameter is typed as iam.Client from github.com/AccelByte/iam-go-sdk/v2 — the same SDK used throughout the rest of the plugin stack. When provided, ClientToken() is called before each config-service request and injected as Authorization: Bearer <token>. Pass nil to make unauthenticated requests.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ExtractNamespace added in v4.28.0

func ExtractNamespace(req *restful.Request, subdomainEnabled bool, baseDomain string) string

ExtractNamespace extracts the namespace from the request using the priority chain: 1. Path parameter (highest priority) 2. Subdomain (from Host header) — only when subdomainEnabled is true 3. x-ab-rl-ns header (lowest priority)

Subdomain extraction is controlled by CORS_SUBDOMAIN_ENABLED. When baseDomain is non-empty (from CORS_SUBDOMAIN_BASE_DOMAIN_SUFFIX), subdomain extraction is further restricted to hosts whose suffix matches ".<baseDomain>", preventing false positives from third-party domains.

Returns the extracted namespace or empty string if not found.

Types

type CORSConfigValue added in v4.28.0

type CORSConfigValue struct {
	AllowedDomains []string `json:"allowed_domains"`
	AllowedHeaders []string `json:"allowed_headers"`
	AllowedMethods []string `json:"allowed_methods"`
	ExposeHeaders  []string `json:"expose_headers"`
	CookiesAllowed bool     `json:"cookies_allowed"`
	MaxAge         int      `json:"max_age"` // 0 = "not set", use default
}

CORSConfigValue represents CORS configuration for a service or namespace. List fields (AllowedDomains, AllowedHeaders, AllowedMethods, ExposeHeaders) are additive during merge. Scalar fields (CookiesAllowed, MaxAge) use the namespace value as override if set (MaxAge=0 means "not set").

type CORSSubdomainConfig added in v4.28.0

type CORSSubdomainConfig struct {
	SubdomainEnabled    bool   `json:"subdomain_enabled"`
	SubdomainBaseDomain string `json:"subdomain_base_domain"`
}

CORSSubdomainConfig holds subdomain-based namespace extraction settings for the publisher namespace. Fetched from the config service using the CORS_SUBDOMAIN key.

type ConfigCache added in v4.28.0

type ConfigCache struct {
	// contains filtered or unexported fields
}

ConfigCache is a loading cache for CORS configurations backed by gcache. When a namespace is not present, it automatically calls the configured loader function to fetch the config, then caches the result for the configured TTL duration.

func NewConfigCache added in v4.28.0

func NewConfigCache(ttl time.Duration, loader func(string) (*CORSConfigValue, error)) *ConfigCache

NewConfigCache creates a new LRU loading cache backed by gcache. The loader is called automatically on cache miss; its result is cached for the TTL duration. Use defaultCacheSize (200) entries maximum with LRU eviction policy.

func (*ConfigCache) Get added in v4.28.0

func (cc *ConfigCache) Get(namespace string) (*CORSConfigValue, error)

Get retrieves the CORS config for the given namespace. On a cache miss the loader is invoked automatically. Returns (nil, nil) when the namespace has no config (e.g. 404 from the config service).

type ConfigClient added in v4.28.0

type ConfigClient interface {
	GetCORSConfig(namespace string) (*CORSConfigValue, error)
	GetSubdomainConfig(publisherNamespace string) (*CORSSubdomainConfig, error)
}

ConfigClient is the interface for fetching CORS configuration from a remote service.

type ConfigServiceResponse added in v4.28.0

type ConfigServiceResponse struct {
	Namespace string `json:"namespace"`
	Key       string `json:"key"`
	// Value is a JSON string that must be decoded to get CORSConfigValue
	Value     string `json:"value"`
	CreatedAt string `json:"createdAt"`
	UpdatedAt string `json:"updatedAt"`
	IsPublic  bool   `json:"isPublic"`
}

ConfigServiceResponse represents the API response from the justice-config-service. The CORS configuration is nested: the outer response has a "value" field which is a JSON string containing the actual CORSConfigValue. Example:

{
  "namespace": "game3",
  "key": "CORS",
  "value": "{\"expose_headers\":[],\"allowed_headers\":[],\"allowed_domains\":[...],\"allowed_methods\":[],\"cookies_allowed\":true}",
  "createdAt": "2026-02-09T07:29:24.357Z",
  "updatedAt": "2026-02-09T07:29:24.357Z",
  "isPublic": false
}

type CrossOriginResourceSharing

type CrossOriginResourceSharing struct {
	ExposeHeaders  []string // list of exposed Headers
	AllowedHeaders []string // list of allowed Headers
	AllowedDomains []string // list of allowed values for Http Origin. An allowed value can be a regular expression to support subdomain matching. If empty all are allowed.
	AllowedMethods []string // list of allowed Methods
	MaxAge         int      // number of seconds that indicates how long the results of a preflight request can be cached.
	CookiesAllowed bool
	Container      *restful.Container

	// Dynamic CORS config support (optional - if ConfigServiceURL is set, dynamic config is enabled)
	ConfigServiceURL   string        // Base URL of justice-config-service (e.g. "http://justice-config-service/config"). If empty, static config is used.
	ConfigClient       ConfigClient  // Client for fetching namespace-scoped CORS config (set automatically from ConfigServiceURL on first request)
	ConfigFetchTimeout time.Duration // Per-request timeout for config service calls (default 200ms)
	IAMClient          iam.Client    // IAM client for obtaining bearer tokens to authenticate config service requests (optional)
	PublisherNamespace string        // Publisher namespace used to fetch subdomain extraction settings (CORS_SUBDOMAIN config key)
	// contains filtered or unexported fields
}

CrossOriginResourceSharing is used to create a Container Filter that implements CORS. Cross-origin resource sharing (CORS) is a mechanism that allows JavaScript on a web page to make XMLHttpRequests to another domain, not the domain the JavaScript originated from.

http://en.wikipedia.org/wiki/Cross-origin_resource_sharing http://enable-cors.org/server.html https://web.dev/cross-origin-resource-sharing

func NewCrossOriginResourceSharing added in v4.28.0

func NewCrossOriginResourceSharing(
	configServiceURL string,
	iamClient iam.Client,
	publisherNamespace string,
	allowedDomains []string,
	allowedMethods []string,
	allowedHeaders []string,
	exposeHeaders []string,
	cookiesAllowed bool,
	maxAge int,
) (*CrossOriginResourceSharing, error)

NewCrossOriginResourceSharing creates a new CORS filter with service-level default configuration. Parameters:

  • configServiceURL: Base URL of justice-config-service (e.g. "http://justice-config-service/config"). Set to empty string to disable dynamic config and use static config only.
  • iamClient: IAM client (iam.Client from iam-go-sdk/v2) for bearer-token authentication to config-service. Required when configServiceURL is non-empty; pass nil only when configServiceURL is empty.
  • publisherNamespace: Publisher namespace used to fetch subdomain extraction settings from the config service (CORS_SUBDOMAIN config key). Subdomain extraction is disabled when empty or when configServiceURL is empty.
  • allowedDomains: Service-level allowed origin domains (exact, wildcard, or re: regex patterns)
  • allowedMethods: Service-level allowed HTTP methods
  • allowedHeaders: Service-level allowed request headers
  • exposeHeaders: Service-level exposed response headers
  • cookiesAllowed: Whether credentials/cookies are allowed
  • maxAge: Max age for preflight cache in seconds

func (*CrossOriginResourceSharing) Filter

Filter is a filter function that implements the CORS flow

type DefaultConfigClient added in v4.28.0

type DefaultConfigClient struct {
	// contains filtered or unexported fields
}

DefaultConfigClient is the HTTP transport implementation of ConfigClient. It uses a gcache loading cache: on a cache miss the cache automatically calls fetchFromService, so GetCORSConfig never needs to manage cache reads/writes manually.

func NewConfigClientWithIAM added in v4.28.0

func NewConfigClientWithIAM(baseURL string, ttl time.Duration, iamClient iam.Client, cfg TransportConfig) *DefaultConfigClient

NewConfigClientWithIAM creates a config client with IAM bearer-token authentication. The iamClient is called before each config service request to obtain a fresh token. HTTP transport is configured via cfg; zero fields fall back to defaults. HTTP requests are traced via OpenTelemetry using otelhttp.

func (*DefaultConfigClient) GetCORSConfig added in v4.28.0

func (c *DefaultConfigClient) GetCORSConfig(namespace string) (*CORSConfigValue, error)

GetCORSConfig returns the CORS configuration for the given namespace. The underlying gcache loading cache calls fetchFromService automatically on a miss. Returns (nil, nil) when the namespace has no config (graceful 404 fallback).

func (*DefaultConfigClient) GetSubdomainConfig added in v4.28.0

func (c *DefaultConfigClient) GetSubdomainConfig(publisherNamespace string) (*CORSSubdomainConfig, error)

GetSubdomainConfig fetches subdomain extraction settings for the publisher namespace from the CORS_SUBDOMAIN config key. Returns (nil, nil) when no config exists (404).

type MergedCORSConfig added in v4.28.0

type MergedCORSConfig struct {
	AllowedDomains []string
	AllowedHeaders []string
	AllowedMethods []string
	ExposeHeaders  []string
	CookiesAllowed bool
	MaxAge         int
}

MergedCORSConfig is the result of merging service-level and namespace-level configs. It combines the deduplicated lists from both sources with namespace scalars as overrides.

func MergeConfigs added in v4.28.0

func MergeConfigs(service, ns *CORSConfigValue) *MergedCORSConfig

MergeConfigs merges service-level and namespace-level CORS configurations. List fields are deduplicated and concatenated; scalar fields use the namespace value as override.

If ns is nil, a copy of the service config is returned. If both are nil, a zero-valued MergedCORSConfig is returned.

type PatternMatcher added in v4.28.0

type PatternMatcher struct {
	Pattern string
	Type    PatternType
	// contains filtered or unexported fields
}

PatternMatcher matches origin values against configured CORS patterns. Supports exact matching, wildcard matching (*.domain.io), and regex matching (re:pattern).

func Compile added in v4.28.0

func Compile(pattern string) (*PatternMatcher, error)

Compile creates a PatternMatcher from a pattern string. Patterns can be:

  • "*" (matches any origin)
  • "https://example.com" (exact match)
  • "https://*.example.io" (wildcard subdomain match)
  • "re:https://.*\.example\.com" (regex match)

No structural validation is performed on wildcard patterns — validation of allowed values is the responsibility of the caller.

func (*PatternMatcher) MatchOrigin added in v4.28.0

func (pm *PatternMatcher) MatchOrigin(origin string) bool

MatchOrigin checks if the given origin matches this pattern.

type PatternType added in v4.28.0

type PatternType int

PatternType represents the type of CORS pattern.

const (
	PatternTypeExact PatternType = iota
	PatternTypeWildcard
	PatternTypeRegex
)

type TransportConfig added in v4.28.0

type TransportConfig struct {
	HTTPTimeout             time.Duration
	HTTPMaxIdleConns        int
	HTTPMaxIdleConnsPerHost int
	HTTPIdleConnTimeout     time.Duration
}

TransportConfig holds configurable HTTP transport settings for the config client. Zero values fall back to the defaults defined by defaultTransportConfig.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL