diff --git a/README.md b/README.md index 8eec5fc..f7f04ba 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,11 @@ func main() { // compression middleware app.Use(middleware.Compress()) + // this is with config + app.Use(middleware.CorsWithConfig(middleware.CorsConfig{ + AllowOrigins: []string{"https://www.postgresqltutorial.com"}, + })) + // you can multiple middlewares also app.Use(func(ctx *slide.Ctx) error { fmt.Println("this will run for all URL(s)") diff --git a/example/main.go b/example/main.go index f245cfe..7492f0d 100644 --- a/example/main.go +++ b/example/main.go @@ -25,6 +25,14 @@ func main() { app := slide.InitServer(&config) + // this is with config + app.Use(middleware.CorsWithConfig(middleware.CorsConfig{ + AllowOrigins: []string{"https://www.postgresqltutorial.com"}, + })) + + // without config, which uses default config + // app.Use(middleware.Cors()) + app.HandleNotFound(func(ctx *slide.Ctx) error { return ctx.JSON(http.StatusNotFound, "check url idiot") }) diff --git a/middleware/cors.go b/middleware/cors.go new file mode 100644 index 0000000..a83304b --- /dev/null +++ b/middleware/cors.go @@ -0,0 +1,95 @@ +package middleware + +import ( + "net/http" + "strconv" + "strings" + + "github.com/go-slide/slide" +) + +// CorsConfig configuration for Corsfeat +type CorsConfig struct { + AllowMethods []string + AllowOrigins []string + AllowHeaders []string + AllowCredentials bool + ExposeHeaders []string + MaxAge int +} + +var ( + // DefaultCORSConfig defeault config for cors + DefaultCORSConfig = CorsConfig{ + AllowOrigins: []string{"*"}, + AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete}, + } +) + +// Cors Middleware with default config +// Reference https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS +func Cors() func(ctx *slide.Ctx) error { + return CorsWithConfig(DefaultCORSConfig) +} + +// CorsWithConfig Cors with a config +func CorsWithConfig(config CorsConfig) func(ctx *slide.Ctx) error { + return func(ctx *slide.Ctx) error { + if len(config.AllowOrigins) == 0 { + config.AllowOrigins = DefaultCORSConfig.AllowOrigins + } + if len(config.AllowMethods) == 0 { + config.AllowMethods = DefaultCORSConfig.AllowMethods + } + allowMethods := strings.Join(config.AllowMethods, ",") + allowHeaders := strings.Join(config.AllowHeaders, ",") + exposeHeaders := strings.Join(config.ExposeHeaders, ",") + maxAge := strconv.Itoa(config.MaxAge) + origin := string(ctx.RequestCtx.Request.Header.Peek(slide.HeaderOrigin)) + allowOrigin := "" + for _, o := range config.AllowOrigins { + if o == "*" && config.AllowCredentials { + allowOrigin = origin + break + } + if o == "*" || o == origin { + allowOrigin = o + break + } + } + if string(ctx.RequestCtx.Method()) != http.MethodOptions { + ctx.RequestCtx.Response.Header.Set(slide.HeaderAccessControlAllowOrigin, allowOrigin) + ctx.RequestCtx.Response.Header.Set(slide.HeaderVary, slide.HeaderOrigin) + if config.AllowCredentials { + ctx.RequestCtx.Response.Header.Set(slide.HeaderAccessControlAllowCredentials, "true") + } + if exposeHeaders != "" { + ctx.RequestCtx.Response.Header.Set(slide.HeaderAccessControlExposeHeaders, exposeHeaders) + } + return ctx.Next() + } + // Options request + ctx.RequestCtx.Response.Header.Set(slide.HeaderVary, slide.HeaderOrigin) + ctx.RequestCtx.Response.Header.Set(slide.HeaderVary, slide.HeaderAccessControlRequestMethod) + ctx.RequestCtx.Response.Header.Set(slide.HeaderVary, slide.HeaderAccessControlRequestHeaders) + ctx.RequestCtx.Response.Header.Set(slide.HeaderAccessControlAllowOrigin, allowOrigin) + ctx.RequestCtx.Response.Header.Set(slide.HeaderAccessControlAllowMethods, allowMethods) + + if config.AllowCredentials { + ctx.RequestCtx.Response.Header.Set(slide.HeaderAccessControlAllowCredentials, "true") + } + if allowHeaders != "" { + ctx.RequestCtx.Response.Header.Set(slide.HeaderAccessControlAllowHeaders, allowHeaders) + } else { + h := string(ctx.RequestCtx.Request.Header.Peek(slide.HeaderAccessControlRequestHeaders)) + if h != "" { + ctx.RequestCtx.Response.Header.Set(slide.HeaderAccessControlAllowHeaders, h) + } + } + if config.MaxAge > 0 { + ctx.RequestCtx.Response.Header.Set(slide.HeaderAccessControlMaxAge, maxAge) + } + return ctx.SendStatusCode(http.StatusNoContent) + } + +} diff --git a/utils.go b/utils.go index b439c87..a9e0034 100644 --- a/utils.go +++ b/utils.go @@ -18,6 +18,18 @@ const ( ApplicationJSON = "application/json" Attachment = "attachment" + // cors headers + HeaderOrigin = "Origin" + HeaderVary = "Vary" + HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin" + HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods" + HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials" + HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers" + HeaderAccessControlRequestMethod = "Access-Control-Request-Method" + HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers" + HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" + HeaderAccessControlMaxAge = "Access-Control-Max-Age" + routerRegexReplace = "[a-zA-Z0-9_-]*" // routing error messages