Skip to content

Commit 6d351b4

Browse files
committed
✨ feat: added cors configs #34
1 parent 2bb12b5 commit 6d351b4

10 files changed

+1052
-2
lines changed

.gitignore

-2
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,3 @@ main.go
3636
# Logs
3737
logs/
3838
logs
39-
keys/
40-
keys

common/common_config.go

+114
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,117 @@ const (
55
EntryKeyMysql = "mysql"
66
EntryKeyMongodb = "mongodb"
77
)
8+
9+
// Define constants for HTTP header names
10+
const (
11+
HeaderAccept = "Accept"
12+
HeaderAcceptCharset = "Accept-Charset"
13+
HeaderAcceptEncoding = "Accept-Encoding"
14+
HeaderAcceptLanguage = "Accept-Language"
15+
HeaderAuthorization = "Authorization"
16+
HeaderCacheControl = "Cache-Control"
17+
HeaderContentDisposition = "Content-Disposition"
18+
HeaderContentEncoding = "Content-Encoding"
19+
HeaderContentLength = "Content-Length"
20+
HeaderContentType = "Content-Type"
21+
HeaderCookie = "Cookie"
22+
HeaderHost = "Host"
23+
HeaderOrigin = "Origin"
24+
HeaderReferer = "Referer"
25+
HeaderUserAgent = "User-Agent"
26+
HeaderIfMatch = "If-Match"
27+
HeaderIfNoneMatch = "If-None-Match"
28+
HeaderETag = "ETag"
29+
HeaderLastModified = "Last-Modified"
30+
HeaderLocation = "Location"
31+
HeaderPragma = "Pragma"
32+
HeaderRetryAfter = "Retry-After"
33+
HeaderServer = "Server"
34+
HeaderWWWAuthenticate = "WWW-Authenticate"
35+
HeaderDate = "Date"
36+
HeaderExpires = "Expires"
37+
HeaderAge = "Age"
38+
HeaderConnection = "Connection"
39+
HeaderContentLanguage = "Content-Language"
40+
HeaderForwarded = "Forwarded"
41+
HeaderIfModifiedSince = "If-Modified-Since"
42+
HeaderUpgrade = "Upgrade"
43+
HeaderVia = "Via"
44+
HeaderWarning = "Warning"
45+
HeaderXForwardedFor = "X-Forwarded-For"
46+
HeaderXForwardedHost = "X-Forwarded-Host"
47+
HeaderXForwardedProto = "X-Forwarded-Proto"
48+
HeaderXRequestedWith = "X-Requested-With"
49+
HeaderXFrameOptions = "X-Frame-Options"
50+
HeaderXXSSProtection = "X-XSS-Protection"
51+
HeaderXContentTypeOpts = "X-Content-Type-Options"
52+
HeaderContentSecurity = "Content-Security-Policy"
53+
HeaderStrictTransport = "Strict-Transport-Security"
54+
HeaderPublicKeyPins = "Public-Key-Pins"
55+
HeaderExpectCT = "Expect-CT"
56+
HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin"
57+
HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods"
58+
HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers"
59+
HeaderAccessControlMaxAge = "Access-Control-Max-Age"
60+
HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
61+
HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
62+
HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers"
63+
HeaderAcceptPatch = "Accept-Patch"
64+
HeaderDeltaBase = "Delta-Base"
65+
HeaderIfUnmodifiedSince = "If-Unmodified-Since"
66+
HeaderAcceptRanges = "Accept-Ranges"
67+
HeaderContentRange = "Content-Range"
68+
HeaderAllow = "Allow"
69+
HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
70+
HeaderXCSRFToken = "X-CSRF-Token"
71+
HeaderXRealIP = "X-Real-IP"
72+
HeaderContentSecurityPolicy = "Content-Security-Policy"
73+
HeaderReferrerPolicy = "Referrer-Policy"
74+
HeaderExpectCt = "Expect-CT"
75+
HeaderStrictTransportSecurity = "Strict-Transport-Security"
76+
HeaderUpgradeInsecureRequests = "Upgrade-Insecure-Requests"
77+
)
78+
79+
// Define constants for media types
80+
const (
81+
MediaTypeApplicationJSON = "application/json"
82+
MediaTypeApplicationXML = "application/xml"
83+
MediaTypeApplicationForm = "application/x-www-form-urlencoded"
84+
MediaTypeApplicationOctetStream = "application/octet-stream"
85+
MediaTypeTextPlain = "text/plain"
86+
MediaTypeTextHTML = "text/html"
87+
MediaTypeImageJPEG = "image/jpeg"
88+
MediaTypeImagePNG = "image/png"
89+
MediaTypeImageGIF = "image/gif"
90+
MediaTypeAudioMP3 = "audio/mpeg"
91+
MediaTypeAudioWAV = "audio/wav"
92+
MediaTypeVideoMP4 = "video/mp4"
93+
MediaTypeVideoAVI = "video/x-msvideo"
94+
MediaTypeApplicationPDF = "application/pdf"
95+
MediaTypeApplicationMSWord = "application/msword"
96+
MediaTypeApplicationMSPowerPoint = "application/vnd.ms-powerpoint"
97+
MediaTypeApplicationExcel = "application/vnd.ms-excel"
98+
MediaTypeApplicationZip = "application/zip"
99+
MediaTypeApplicationGzip = "application/gzip"
100+
MediaTypeMultipartFormData = "multipart/form-data"
101+
MediaTypeImageBMP = "image/bmp"
102+
MediaTypeImageTIFF = "image/tiff"
103+
MediaTypeTextCSS = "text/css"
104+
MediaTypeTextJavaScript = "text/javascript"
105+
MediaTypeApplicationJSONLD = "application/ld+json"
106+
MediaTypeApplicationRDFXML = "application/rdf+xml"
107+
MediaTypeApplicationGeoJSON = "application/geo+json"
108+
MediaTypeApplicationMsgpack = "application/msgpack"
109+
MediaTypeApplicationOgg = "application/ogg"
110+
MediaTypeApplicationGraphQL = "application/graphql"
111+
MediaTypeApplicationProtobuf = "application/protobuf"
112+
MediaTypeImageWebP = "image/webp"
113+
MediaTypeFontWOFF = "font/woff"
114+
MediaTypeFontWOFF2 = "font/woff2"
115+
MediaTypeAudioFLAC = "audio/flac"
116+
MediaTypeVideoWebM = "video/webm"
117+
MediaTypeApplicationDart = "application/dart"
118+
MediaTypeApplicationXLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
119+
MediaTypeApplicationPPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation"
120+
MediaTypeApplicationGRPC = "application/grpc"
121+
)

configx/configx.go

+23
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/sivaosorg/govm/asterisk"
1313
"github.com/sivaosorg/govm/bot/slack"
1414
"github.com/sivaosorg/govm/bot/telegram"
15+
"github.com/sivaosorg/govm/corsx"
1516
"github.com/sivaosorg/govm/logger"
1617
"github.com/sivaosorg/govm/mongodb"
1718
"github.com/sivaosorg/govm/mysql"
@@ -173,6 +174,7 @@ func GetKeysDefaultConfig() *KeysConfig {
173174
k.SetRedis(*redisx.GetRedisConfigSample().SetEnabled(false))
174175
k.SetTelegram(*telegram.GetTelegramConfigSample().SetEnabled(false))
175176
k.SetSlack(*slack.GetSlackConfigSample().SetEnabled(false))
177+
k.SetCors(*corsx.GetCorsConfigSample().SetEnabled(false))
176178
return k
177179
}
178180

@@ -193,6 +195,7 @@ func (KeysConfig) WriteDefaultConfig() {
193195
"redis": fmt.Sprintf("################################\n%s\n%s\n################################", "Redis Config", timex.With(time.Now()).Format(timex.DateTimeFormYearMonthDayHourMinuteSecond)),
194196
"telegram": fmt.Sprintf("################################\n%s\n%s\n################################", "Telegram Config", timex.With(time.Now()).Format(timex.DateTimeFormYearMonthDayHourMinuteSecond)),
195197
"slack": fmt.Sprintf("################################\n%s\n%s\n################################", "Slack Config", timex.With(time.Now()).Format(timex.DateTimeFormYearMonthDayHourMinuteSecond)),
198+
"cors": fmt.Sprintf("################################\n%s\n%s\n################################", "Cors Config", timex.With(time.Now()).Format(timex.DateTimeFormYearMonthDayHourMinuteSecond)),
196199
})
197200
err = CreateConfigWithComments[KeysConfig](filepath.Join(".", FilenameDefaultConf), *m)
198201
if err != nil {
@@ -275,6 +278,16 @@ func (ClusterMultiTenancyKeysConfig) ReadDefaultConfig() {
275278
logger.Infof("%+v", keys)
276279
}
277280

281+
func (ClusterMultiTenancyKeysConfig) ReadCurrentConfig() (ClusterMultiTenancyKeysConfig, error) {
282+
keys, err := ReadConfig[ClusterMultiTenancyKeysConfig](filepath.Join(".", FilenameDefaultClusterMultiTenantConf))
283+
return *keys, err
284+
}
285+
286+
func (ClusterMultiTenancyKeysConfig) ReadCurrentConfigWith(filename string) (ClusterMultiTenancyKeysConfig, error) {
287+
keys, err := ReadConfig[ClusterMultiTenancyKeysConfig](filepath.Join(".", filename))
288+
return *keys, err
289+
}
290+
278291
func ReadConfig[T any](path string) (*T, error) {
279292
file, err := os.Open(path)
280293
if err != nil {
@@ -385,3 +398,13 @@ func (k *KeysConfig) SetSlackCursor(value *slack.SlackConfig) *KeysConfig {
385398
k.Slack = *value
386399
return k
387400
}
401+
402+
func (k *KeysConfig) SetCors(value corsx.CorsConfig) *KeysConfig {
403+
k.Cors = value
404+
return k
405+
}
406+
407+
func (k *KeysConfig) SetCorsCursor(value *corsx.CorsConfig) *KeysConfig {
408+
k.Cors = *value
409+
return k
410+
}

configx/configx_model.go

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"github.com/sivaosorg/govm/asterisk"
55
"github.com/sivaosorg/govm/bot/slack"
66
"github.com/sivaosorg/govm/bot/telegram"
7+
"github.com/sivaosorg/govm/corsx"
78
"github.com/sivaosorg/govm/mongodb"
89
"github.com/sivaosorg/govm/mysql"
910
"github.com/sivaosorg/govm/postgres"
@@ -29,6 +30,7 @@ type KeysConfig struct {
2930
Redis redisx.RedisConfig `json:"redis,omitempty" yaml:"redis"`
3031
Telegram telegram.TelegramConfig `json:"telegram,omitempty" yaml:"telegram"`
3132
Slack slack.SlackConfig `json:"slack,omitempty" yaml:"slack"`
33+
Cors corsx.CorsConfig `json:"cors,omitempty" yaml:"cors"`
3234
}
3335

3436
type MultiTenancyKeysConfig struct {

corsx/cors.go

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package corsx
2+
3+
import (
4+
"log"
5+
"net/http"
6+
"strconv"
7+
"strings"
8+
9+
"github.com/sivaosorg/govm/common"
10+
"github.com/sivaosorg/govm/restify"
11+
"github.com/sivaosorg/govm/utils"
12+
)
13+
14+
func NewCorsConfig() *CorsConfig {
15+
c := &CorsConfig{}
16+
c.SetMaxAge(0)
17+
return c
18+
}
19+
20+
func (c *CorsConfig) SetEnabled(value bool) *CorsConfig {
21+
c.IsEnabled = value
22+
return c
23+
}
24+
25+
func (c *CorsConfig) SetAllowCredentials(value bool) *CorsConfig {
26+
c.AllowCredentials = value
27+
return c
28+
}
29+
30+
func (c *CorsConfig) SetMaxAge(value int) *CorsConfig {
31+
if value < 0 {
32+
log.Panicf("Invalid max-age: %v", value)
33+
}
34+
c.MaxAge = value
35+
return c
36+
}
37+
38+
func (c *CorsConfig) SetAllowedOrigins(values []string) *CorsConfig {
39+
c.AllowedOrigins = values
40+
return c
41+
}
42+
43+
func (c *CorsConfig) AppendAllowedOrigins(values ...string) *CorsConfig {
44+
c.AllowedOrigins = append(c.AllowedOrigins, values...)
45+
return c
46+
}
47+
48+
func (c *CorsConfig) SetAllowedHeaders(values []string) *CorsConfig {
49+
c.AllowedHeaders = values
50+
return c
51+
}
52+
53+
func (c *CorsConfig) AppendAllowedHeaders(values ...string) *CorsConfig {
54+
c.AllowedHeaders = append(c.AllowedHeaders, values...)
55+
return c
56+
}
57+
58+
func (c *CorsConfig) SetAllowedMethods(values []string) *CorsConfig {
59+
c.AllowedMethods = values
60+
return c
61+
}
62+
63+
func (c *CorsConfig) AppendAllowedMethods(values ...string) *CorsConfig {
64+
c.AllowedMethods = append(c.AllowedMethods, values...)
65+
return c
66+
}
67+
68+
func (c *CorsConfig) SetExposedHeaders(values []string) *CorsConfig {
69+
c.ExposedHeaders = values
70+
return c
71+
}
72+
73+
func (c *CorsConfig) AppendExposedHeaders(values ...string) *CorsConfig {
74+
c.ExposedHeaders = append(c.ExposedHeaders, values...)
75+
return c
76+
}
77+
78+
func (c *CorsConfig) Json() string {
79+
return utils.ToJson(c)
80+
}
81+
82+
func GetCorsConfigSample() *CorsConfig {
83+
c := NewCorsConfig().
84+
SetEnabled(true).
85+
SetMaxAge(0).
86+
SetAllowCredentials(true).
87+
AppendAllowedOrigins("*").
88+
AppendAllowedMethods(restify.MethodGet, restify.MethodPost, restify.MethodPut, restify.MethodDelete, restify.MethodOptions).
89+
SetExposedHeaders(make([]string, 0)).
90+
AppendAllowedHeaders(common.HeaderOrigin, common.HeaderAccept, common.HeaderContentType, common.HeaderAuthorization)
91+
return c
92+
}
93+
94+
// ApplyCorsHeaders applies CORS headers to an HTTP response based on the configuration.
95+
func (c *CorsConfig) ApplyCorsHeaders(w http.ResponseWriter, r *http.Request) {
96+
if !c.IsEnabled {
97+
return
98+
}
99+
origin := r.Header.Get(common.HeaderOrigin)
100+
if c.isOriginAllowed(origin) {
101+
w.Header().Set(common.HeaderAccessControlAllowOrigin, origin)
102+
w.Header().Set(common.HeaderAccessControlAllowMethods, strings.Join(c.AllowedMethods, ","))
103+
w.Header().Set(common.HeaderAccessControlAllowHeaders, strings.Join(c.AllowedHeaders, ","))
104+
w.Header().Set(common.HeaderAccessControlExposeHeaders, strings.Join(c.ExposedHeaders, ","))
105+
if c.AllowCredentials {
106+
w.Header().Set(common.HeaderAccessControlAllowCredentials, strconv.FormatBool(c.AllowCredentials))
107+
}
108+
if c.MaxAge > 0 {
109+
w.Header().Set(common.HeaderAccessControlMaxAge, strconv.Itoa(c.MaxAge))
110+
}
111+
}
112+
}
113+
114+
// isOriginAllowed checks if the provided origin is allowed based on the configuration.
115+
func (c *CorsConfig) isOriginAllowed(origin string) bool {
116+
for _, allowedOrigin := range c.AllowedOrigins {
117+
if allowedOrigin == "*" || allowedOrigin == origin {
118+
return true
119+
}
120+
}
121+
return false
122+
}

corsx/cors_config.go

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package corsx

corsx/cors_model.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package corsx
2+
3+
// CorsConfig represents the CORS (Cross-Origin Resource Sharing) configuration.
4+
type CorsConfig struct {
5+
IsEnabled bool `json:"enabled" yaml:"enabled"`
6+
AllowedOrigins []string `json:"allowed_origins" yaml:"allowed-origins"`
7+
AllowedMethods []string `json:"allowed_methods" yaml:"allowed-methods"`
8+
AllowedHeaders []string `json:"allowed_headers" yaml:"allowed-headers"`
9+
ExposedHeaders []string `json:"exposed_headers" yaml:"exposed-headers"`
10+
AllowCredentials bool `json:"allow_credentials" yaml:"allow-credentials"`
11+
MaxAge int `json:"max_age" binding:"gte=0" yaml:"max-age"`
12+
}

0 commit comments

Comments
 (0)