From e79b95de0c9fe03ca6d45ba504b04203f8fe0117 Mon Sep 17 00:00:00 2001 From: Umputun Date: Thu, 2 Aug 2018 19:26:34 -0500 Subject: [PATCH] Feature/cors (#183) * add cors internally #160 * revendor --- backend/Gopkg.lock | 135 ++++++- backend/app/rest/api/rest.go | 11 + .../vendor/github.com/go-chi/cors/README.md | 32 ++ backend/vendor/github.com/go-chi/cors/cors.go | 369 ++++++++++++++++++ .../vendor/github.com/go-chi/cors/utils.go | 70 ++++ 5 files changed, 605 insertions(+), 12 deletions(-) create mode 100644 backend/vendor/github.com/go-chi/cors/README.md create mode 100644 backend/vendor/github.com/go-chi/cors/cors.go create mode 100644 backend/vendor/github.com/go-chi/cors/utils.go diff --git a/backend/Gopkg.lock b/backend/Gopkg.lock index 6c9c2cf2..c07a44c5 100644 --- a/backend/Gopkg.lock +++ b/backend/Gopkg.lock @@ -2,218 +2,287 @@ [[projects]] + digest = "1:180876db3ec295bb9f0babec5ca926fe9f2036b747b7c5bfcd13b333023e7cfd" name = "cloud.google.com/go" packages = ["compute/metadata"] + pruneopts = "UT" revision = "767c40d6a2e058483c25fa193e963a22da17236d" version = "v0.18.0" [[projects]] + digest = "1:bff7b2530f02b143623e260c11df5cbf34e0faeaca6aa001a8be31f333518ca9" name = "github.com/PuerkitoBio/goquery" packages = ["."] + pruneopts = "UT" revision = "a86ea073017a6beddef78c8659e7224e8ca634b0" version = "v1.4.0" [[projects]] + digest = "1:66b3310cf22cdc96c35ef84ede4f7b9b370971c4025f394c89a2638729653b11" name = "github.com/andybalholm/cascadia" packages = ["."] + pruneopts = "UT" revision = "901648c87902174f774fac311d7f176f8647bdaa" version = "v1.0.0" [[projects]] + digest = "1:c28625428387b63dd7154eb857f51e700465cfbf7c06f619e71f2da33cefe47e" name = "github.com/coreos/bbolt" packages = ["."] + pruneopts = "UT" revision = "583e8937c61f1af6513608ccc75c97b6abdf4ff9" version = "v1.3.0" [[projects]] + digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39" name = "github.com/davecgh/go-spew" packages = ["spew"] + pruneopts = "UT" revision = "346938d642f2ec3594ed81d874461961cd0faa76" version = "v1.1.0" [[projects]] + digest = "1:76dc72490af7174349349838f2fe118996381b31ea83243812a97e5a0fd5ed55" name = "github.com/dgrijalva/jwt-go" packages = ["."] + pruneopts = "UT" revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e" version = "v3.2.0" [[projects]] + digest = "1:82c6357bc57f8417f993d490f6c07a9f0b5682ac68b1a64b93a189dece7c5bf5" name = "github.com/didip/tollbooth" packages = [ ".", "errors", "libstring", - "limiter" + "limiter", ] + pruneopts = "UT" revision = "c95eaa3ddc98f635a91e218b48727fb2e06613ea" version = "v4.0.0" [[projects]] branch = "master" + digest = "1:60fb125752a234a0a43bfc281bfdd9726fd1071a13f66bb35ec4b8e7ed1ef642" name = "github.com/didip/tollbooth_chi" packages = ["."] + pruneopts = "UT" revision = "6ab5f3083f3d925e1944d58cdaebf43bbbff9238" [[projects]] - branch = "master" + digest = "1:4b08116de0de75c041bb341686f0b139930f26cb84dfdf7641d435548114181d" name = "github.com/globalsign/mgo" packages = [ ".", "bson", "internal/json", "internal/sasl", - "internal/scram" + "internal/scram", ] + pruneopts = "UT" revision = "113d3961e7311526535a1ef7042196563d442761" + version = "r2018.06.15" [[projects]] + digest = "1:4eda9f7bf70f5145b3b9ed3f18ac93e9b1a0e38906eb69e526380c34861e2b07" name = "github.com/go-chi/chi" packages = [ ".", - "middleware" + "middleware", ] + pruneopts = "UT" revision = "e83ac2304db3c50cf03d96a2fcd39009d458bc35" version = "v3.3.2" [[projects]] + digest = "1:dfa416a1bb8139f30832543340f972f65c0db9932034cb6a1b42c5ac615a3fb8" + name = "github.com/go-chi/cors" + packages = ["."] + pruneopts = "UT" + revision = "dba6525398619dead495962a916728e7ee2ca322" + version = "v1.0.0" + +[[projects]] + digest = "1:75f324f9a6b76bca2fdd087ba169de30bc28a95a7139a6cefd5a9ac7582f6dab" name = "github.com/go-chi/render" packages = ["."] + pruneopts = "UT" revision = "9f855fadd4b8cde7773f9ef51f6b2705af239519" version = "v1.0.0" [[projects]] + digest = "1:1212e114344a5cdcc834ea69e19d456eef230f9784659080fee67e02ba2cb574" name = "github.com/go-pkgz/mongo" packages = ["."] + pruneopts = "UT" revision = "9a09a574c336c6ae2338a65bbebed2baab2a713c" version = "v1.0.0" [[projects]] + digest = "1:0f24c72d7e9bcb682b907be0461ac552973cd4b3f1b60b04b725f6d74a3e59e7" name = "github.com/go-pkgz/repeater" packages = [ ".", - "strategy" + "strategy", ] + pruneopts = "UT" revision = "f2a67dcf050cab24d57132a7d8b45553ceab817b" version = "v1.0.0" [[projects]] + digest = "1:ffc060c551980d37ee9e428ef528ee2813137249ccebb0bfc412ef83071cac91" name = "github.com/golang/protobuf" packages = ["proto"] + pruneopts = "UT" revision = "925541529c1fa6821df4e44ce2723319eb2be768" version = "v1.0.0" [[projects]] + digest = "1:8f8811f9be822914c3a25c6a071e93beb4c805d7b026cbf298bc577bc1cc945b" name = "github.com/google/uuid" packages = ["."] + pruneopts = "UT" revision = "064e2069ce9c359c118179501254f67d7d37ba24" version = "0.2" [[projects]] + digest = "1:2b418e5e28a68ccab236a22f344140cebab2d90c3a4a3f5593ecbb82cfe0e5ce" name = "github.com/gorilla/feeds" packages = ["."] + pruneopts = "UT" revision = "6edcbcd2d57fd0bbd7f39947a593ed0c06648388" version = "v1.1.0" [[projects]] branch = "master" + digest = "1:07671f8997086ed115824d1974507d2b147d1e0463675ea5dbf3be89b1c2c563" name = "github.com/hashicorp/errwrap" packages = ["."] + pruneopts = "UT" revision = "7554cd9344cec97297fa6649b055a8c98c2a1e55" [[projects]] branch = "master" + digest = "1:e5048c5da80697be2fcdecc944e29d2999e01fd7f48b643168443209779f3463" name = "github.com/hashicorp/go-multierror" packages = ["."] + pruneopts = "UT" revision = "b7773ae218740a7be65057fc60b366a49b538a44" [[projects]] branch = "master" + digest = "1:cf296baa185baae04a9a7004efee8511d08e2f5f51d4cbe5375da89722d681db" name = "github.com/hashicorp/golang-lru" packages = [ ".", - "simplelru" + "simplelru", ] + pruneopts = "UT" revision = "0fb14efe8c47ae851c0034ed7a448854d3d34cf3" [[projects]] branch = "master" + digest = "1:ce3f7860fd68bd2dd4c3735e2aed8c9de7c7d05bd6ad7d97a6bedcf4fe7b84fb" name = "github.com/hashicorp/logutils" packages = ["."] + pruneopts = "UT" revision = "0dc08b1671f34c4250ce212759ebd880f743d883" [[projects]] branch = "master" + digest = "1:3217800110ab50cd0e0784307be46b5344c0c103dbd15a16d0994ae4abdc96ab" name = "github.com/jessevdk/go-flags" packages = ["."] + pruneopts = "UT" revision = "1c38ed7ad0cc3d9e66649ac398c30e45f395c4eb" [[projects]] branch = "master" + digest = "1:e83a8cf54ecc5c4efdbc88aa914578773d4d6897470b698ae315b5734081e8ed" name = "github.com/microcosm-cc/bluemonday" packages = ["."] + pruneopts = "UT" revision = "542fd4642604d0d0c26112396ce5b1a9d01eee0b" [[projects]] branch = "master" + digest = "1:ea014b8bb16b0decc3393baeafc3b19815bcaf92329fe643eef5c0aa89bd3291" name = "github.com/nullrocks/identicon" packages = ["."] + pruneopts = "UT" revision = "7875f45b0022edded6377e40639d8aa620193a62" [[projects]] + digest = "1:808cdddf087fb64baeae67b8dfaee2069034d9704923a3cb8bd96a995421a625" name = "github.com/patrickmn/go-cache" packages = ["."] + pruneopts = "UT" revision = "a3647f8e31d79543b2d0f0ae2fe5c379d72cedc0" version = "v2.1.0" [[projects]] + digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747" name = "github.com/pkg/errors" packages = ["."] + pruneopts = "UT" revision = "645ef00459ed84a119197bfb8d8205042c6df63d" version = "v0.8.0" [[projects]] + digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" name = "github.com/pmezard/go-difflib" packages = ["difflib"] + pruneopts = "UT" revision = "792786c7400a136282c1664665ae0a8db921c6c2" version = "v1.0.0" [[projects]] branch = "master" + digest = "1:def689e73e9252f6f7fe66834a76751a41b767e03daab299e607e7226c58a855" name = "github.com/shurcooL/sanitized_anchor_name" packages = ["."] + pruneopts = "UT" revision = "86672fcb3f950f35f2e675df2240550f2a50762f" [[projects]] + digest = "1:7e8d267900c7fa7f35129a2a37596e38ed0f11ca746d6d9ba727980ee138f9f6" name = "github.com/stretchr/testify" packages = [ "assert", - "require" + "require", ] + pruneopts = "UT" revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71" version = "v1.2.1" [[projects]] branch = "master" + digest = "1:b34062e39d8f3172fdd0c5c22ca1a3badeb2ddde295a997b0b63441e96d916f7" name = "golang.org/x/image" packages = [ "draw", - "math/f64" + "math/f64", ] + pruneopts = "UT" revision = "af66defab954cb421ca110193eed9477c8541e2a" [[projects]] branch = "master" + digest = "1:ac7eaa5f1179480f517d32831225215cc20940152d66be29f3d5204ea15d425f" name = "golang.org/x/net" packages = [ "context", "context/ctxhttp", "html", - "html/atom" + "html/atom", ] + pruneopts = "UT" revision = "f5dfe339be1d06f81b22525fe34671ee7d2c8904" [[projects]] branch = "master" + digest = "1:ccb0526e2eb5d454a25a536634fade769664eaa93ad2e4cd4107967bbc01b4e8" name = "golang.org/x/oauth2" packages = [ ".", @@ -223,23 +292,29 @@ "internal", "jws", "jwt", - "yandex" + "yandex", ] + pruneopts = "UT" revision = "543e37812f10c46c622c9575afd7ad22f22a12ba" [[projects]] branch = "master" + digest = "1:ba7d5e85e8b4f084fae02a1a9d7462980e889d1eb689c747507b30a30b8bfa67" name = "golang.org/x/sys" packages = ["unix"] + pruneopts = "UT" revision = "37707fdb30a5b38865cfb95e5aab41707daec7fd" [[projects]] branch = "master" + digest = "1:51a479a09b7ed06b7be5a854e27fcc328718ae0e5ad159f9ddeef12d0326c2e7" name = "golang.org/x/time" packages = ["rate"] + pruneopts = "UT" revision = "6dc17368e09b0e8634d71cac8168d853e869a0c7" [[projects]] + digest = "1:a48f97fb737d5d61cf13e81cfef040942d217d086766b823757d39d4f6a4c547" name = "google.golang.org/appengine" packages = [ ".", @@ -251,20 +326,56 @@ "internal/modules", "internal/remote_api", "internal/urlfetch", - "urlfetch" + "urlfetch", ] + pruneopts = "UT" revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a" version = "v1.0.0" [[projects]] + digest = "1:39c2113f3a89585666e6f973650cff186b2d06deb4aa202c88addb87b0a201db" name = "gopkg.in/russross/blackfriday.v2" packages = ["."] + pruneopts = "UT" revision = "cadec560ec52d93835bf2f15bd794700d3a2473b" version = "v2.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "da9242a76135f9ee04287bf437ffa42708fc36ec54f39c74127f41a3473f24ac" + input-imports = [ + "github.com/PuerkitoBio/goquery", + "github.com/coreos/bbolt", + "github.com/dgrijalva/jwt-go", + "github.com/didip/tollbooth", + "github.com/didip/tollbooth_chi", + "github.com/globalsign/mgo", + "github.com/globalsign/mgo/bson", + "github.com/go-chi/chi", + "github.com/go-chi/chi/middleware", + "github.com/go-chi/cors", + "github.com/go-chi/render", + "github.com/go-pkgz/mongo", + "github.com/go-pkgz/repeater", + "github.com/google/uuid", + "github.com/gorilla/feeds", + "github.com/hashicorp/go-multierror", + "github.com/hashicorp/golang-lru", + "github.com/hashicorp/logutils", + "github.com/jessevdk/go-flags", + "github.com/microcosm-cc/bluemonday", + "github.com/nullrocks/identicon", + "github.com/patrickmn/go-cache", + "github.com/pkg/errors", + "github.com/stretchr/testify/assert", + "github.com/stretchr/testify/require", + "golang.org/x/image/draw", + "golang.org/x/oauth2", + "golang.org/x/oauth2/facebook", + "golang.org/x/oauth2/github", + "golang.org/x/oauth2/google", + "golang.org/x/oauth2/yandex", + "gopkg.in/russross/blackfriday.v2", + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/backend/app/rest/api/rest.go b/backend/app/rest/api/rest.go index 8b6bd5a1..ddef456d 100644 --- a/backend/app/rest/api/rest.go +++ b/backend/app/rest/api/rest.go @@ -17,6 +17,7 @@ import ( "github.com/didip/tollbooth_chi" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" + "github.com/go-chi/cors" "github.com/go-chi/render" "github.com/pkg/errors" "gopkg.in/russross/blackfriday.v2" @@ -118,6 +119,16 @@ func (s *Rest) routes() chi.Router { avatarProxy: s.AvatarProxy, } + corsMiddleware := cors.New(cors.Options{ + AllowedOrigins: []string{"*"}, + AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-XSRF-Token", "X-JWT"}, + ExposedHeaders: []string{"Link"}, + AllowCredentials: true, + MaxAge: 300, + }) + router.Use(corsMiddleware.Handler) + ipFn := func(ip string) string { return store.HashValue(ip, s.DataService.Secret)[:12] } // logger uses it for anonymization // auth routes for all providers diff --git a/backend/vendor/github.com/go-chi/cors/README.md b/backend/vendor/github.com/go-chi/cors/README.md new file mode 100644 index 00000000..6e865be3 --- /dev/null +++ b/backend/vendor/github.com/go-chi/cors/README.md @@ -0,0 +1,32 @@ +# CORS net/http middleware + +(fork of github.com/rs/cors) + +## Usage + +```go +func main() { + r := chi.NewRouter() + + // Basic CORS + // for more ideas, see: https://developer.github.com/v3/#cross-origin-resource-sharing + cors := cors.New(cors.Options{ + // AllowedOrigins: []string{"https://foo.com"}, // Use this to allow specific origin hosts + AllowedOrigins: []string{"*"}, + // AllowOriginFunc: func(r *http.Request, origin string) bool { return true }, + AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"}, + ExposedHeaders: []string{"Link"}, + AllowCredentials: true, + MaxAge: 300, // Maximum value not ignored by any of major browsers + }) + r.Use(cors.Handler) + + r.Get("/", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("welcome")) + }) + + http.ListenAndServe(":3000", r) +} +``` + diff --git a/backend/vendor/github.com/go-chi/cors/cors.go b/backend/vendor/github.com/go-chi/cors/cors.go new file mode 100644 index 00000000..d85d86cb --- /dev/null +++ b/backend/vendor/github.com/go-chi/cors/cors.go @@ -0,0 +1,369 @@ +// cors package is net/http handler to handle CORS related requests +// as defined by http://www.w3.org/TR/cors/ +// +// You can configure it by passing an option struct to cors.New: +// +// c := cors.New(cors.Options{ +// AllowedOrigins: []string{"foo.com"}, +// AllowedMethods: []string{"GET", "POST", "DELETE"}, +// AllowCredentials: true, +// }) +// +// Then insert the handler in the chain: +// +// handler = c.Handler(handler) +// +// See Options documentation for more options. +// +// The resulting handler is a standard net/http handler. +package cors + +import ( + "log" + "net/http" + "os" + "strconv" + "strings" +) + +// Options is a configuration container to setup the CORS middleware. +type Options struct { + // AllowedOrigins is a list of origins a cross-domain request can be executed from. + // If the special "*" value is present in the list, all origins will be allowed. + // An origin may contain a wildcard (*) to replace 0 or more characters + // (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penality. + // Only one wildcard can be used per origin. + // Default value is ["*"] + AllowedOrigins []string + + // AllowOriginFunc is a custom function to validate the origin. It take the origin + // as argument and returns true if allowed or false otherwise. If this option is + // set, the content of AllowedOrigins is ignored. + AllowOriginFunc func(r *http.Request, origin string) bool + + // AllowedMethods is a list of methods the client is allowed to use with + // cross-domain requests. Default value is simple methods (GET and POST) + AllowedMethods []string + + // AllowedHeaders is list of non simple headers the client is allowed to use with + // cross-domain requests. + // If the special "*" value is present in the list, all headers will be allowed. + // Default value is [] but "Origin" is always appended to the list. + AllowedHeaders []string + + // ExposedHeaders indicates which headers are safe to expose to the API of a CORS + // API specification + ExposedHeaders []string + + // AllowCredentials indicates whether the request can include user credentials like + // cookies, HTTP authentication or client side SSL certificates. + AllowCredentials bool + + // MaxAge indicates how long (in seconds) the results of a preflight request + // can be cached + MaxAge int + + // OptionsPassthrough instructs preflight to let other potential next handlers to + // process the OPTIONS method. Turn this on if your application handles OPTIONS. + OptionsPassthrough bool + + // Debugging flag adds additional output to debug server side CORS issues + Debug bool +} + +// Cors http handler +type Cors struct { + // Debug logger + log *log.Logger + + // Set to true when allowed origins contains a "*" + allowedOriginsAll bool + + // Normalized list of plain allowed origins + allowedOrigins []string + + // List of allowed origins containing wildcards + allowedWOrigins []wildcard + + // Optional origin validator function + allowOriginFunc func(r *http.Request, origin string) bool + + // Set to true when allowed headers contains a "*" + allowedHeadersAll bool + + // Normalized list of allowed headers + allowedHeaders []string + + // Normalized list of allowed methods + allowedMethods []string + + // Normalized list of exposed headers + exposedHeaders []string + allowCredentials bool + maxAge int + optionPassthrough bool +} + +// New creates a new Cors handler with the provided options. +func New(options Options) *Cors { + c := &Cors{ + exposedHeaders: convert(options.ExposedHeaders, http.CanonicalHeaderKey), + allowOriginFunc: options.AllowOriginFunc, + allowCredentials: options.AllowCredentials, + maxAge: options.MaxAge, + optionPassthrough: options.OptionsPassthrough, + } + if options.Debug { + c.log = log.New(os.Stdout, "[cors] ", log.LstdFlags) + } + + // Normalize options + // Note: for origins and methods matching, the spec requires a case-sensitive matching. + // As it may error prone, we chose to ignore the spec here. + + // Allowed Origins + if len(options.AllowedOrigins) == 0 { + // Default is all origins + c.allowedOriginsAll = true + } else { + c.allowedOrigins = []string{} + c.allowedWOrigins = []wildcard{} + for _, origin := range options.AllowedOrigins { + // Normalize + origin = strings.ToLower(origin) + if origin == "*" { + // If "*" is present in the list, turn the whole list into a match all + c.allowedOriginsAll = true + c.allowedOrigins = nil + c.allowedWOrigins = nil + break + } else if i := strings.IndexByte(origin, '*'); i >= 0 { + // Split the origin in two: start and end string without the * + w := wildcard{origin[0:i], origin[i+1 : len(origin)]} + c.allowedWOrigins = append(c.allowedWOrigins, w) + } else { + c.allowedOrigins = append(c.allowedOrigins, origin) + } + } + } + + // Allowed Headers + if len(options.AllowedHeaders) == 0 { + // Use sensible defaults + c.allowedHeaders = []string{"Origin", "Accept", "Content-Type"} + } else { + // Origin is always appended as some browsers will always request for this header at preflight + c.allowedHeaders = convert(append(options.AllowedHeaders, "Origin"), http.CanonicalHeaderKey) + for _, h := range options.AllowedHeaders { + if h == "*" { + c.allowedHeadersAll = true + c.allowedHeaders = nil + break + } + } + } + + // Allowed Methods + if len(options.AllowedMethods) == 0 { + // Default is spec's "simple" methods + c.allowedMethods = []string{"GET", "POST"} + } else { + c.allowedMethods = convert(options.AllowedMethods, strings.ToUpper) + } + + return c +} + +// Default creates a new Cors handler with default options +func Default() *Cors { + return New(Options{}) +} + +// Handler apply the CORS specification on the request, and add relevant CORS headers +// as necessary. +func (c *Cors) Handler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == "OPTIONS" { + c.logf("Handler: Preflight request") + c.handlePreflight(w, r) + // Preflight requests are standalone and should stop the chain as some other + // middleware may not handle OPTIONS requests correctly. One typical example + // is authentication middleware ; OPTIONS requests won't carry authentication + // headers (see #1) + if c.optionPassthrough { + next.ServeHTTP(w, r) + } + } else { + c.logf("Handler: Actual request") + c.handleActualRequest(w, r) + next.ServeHTTP(w, r) + } + }) +} + +// handlePreflight handles pre-flight CORS requests +func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) { + headers := w.Header() + origin := r.Header.Get("Origin") + + if r.Method != "OPTIONS" { + c.logf("Preflight aborted: %s!=OPTIONS", r.Method) + return + } + // Always set Vary headers + // see https://github.com/rs/cors/issues/10, + // https://github.com/rs/cors/commit/dbdca4d95feaa7511a46e6f1efb3b3aa505bc43f#commitcomment-12352001 + headers.Add("Vary", "Origin") + headers.Add("Vary", "Access-Control-Request-Method") + headers.Add("Vary", "Access-Control-Request-Headers") + + if origin == "" { + c.logf("Preflight aborted: empty origin") + return + } + if !c.isOriginAllowed(r, origin) { + c.logf("Preflight aborted: origin '%s' not allowed", origin) + return + } + + reqMethod := r.Header.Get("Access-Control-Request-Method") + if !c.isMethodAllowed(reqMethod) { + c.logf("Preflight aborted: method '%s' not allowed", reqMethod) + return + } + reqHeaders := parseHeaderList(r.Header.Get("Access-Control-Request-Headers")) + if !c.areHeadersAllowed(reqHeaders) { + c.logf("Preflight aborted: headers '%v' not allowed", reqHeaders) + return + } + headers.Set("Access-Control-Allow-Origin", origin) + // Spec says: Since the list of methods can be unbounded, simply returning the method indicated + // by Access-Control-Request-Method (if supported) can be enough + headers.Set("Access-Control-Allow-Methods", strings.ToUpper(reqMethod)) + if len(reqHeaders) > 0 { + + // Spec says: Since the list of headers can be unbounded, simply returning supported headers + // from Access-Control-Request-Headers can be enough + headers.Set("Access-Control-Allow-Headers", strings.Join(reqHeaders, ", ")) + } + if c.allowCredentials { + headers.Set("Access-Control-Allow-Credentials", "true") + } + if c.maxAge > 0 { + headers.Set("Access-Control-Max-Age", strconv.Itoa(c.maxAge)) + } + c.logf("Preflight response headers: %v", headers) +} + +// handleActualRequest handles simple cross-origin requests, actual request or redirects +func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) { + headers := w.Header() + origin := r.Header.Get("Origin") + + if r.Method == "OPTIONS" { + c.logf("Actual request no headers added: method == %s", r.Method) + return + } + // Always set Vary, see https://github.com/rs/cors/issues/10 + headers.Add("Vary", "Origin") + if origin == "" { + c.logf("Actual request no headers added: missing origin") + return + } + if !c.isOriginAllowed(r, origin) { + c.logf("Actual request no headers added: origin '%s' not allowed", origin) + return + } + + // Note that spec does define a way to specifically disallow a simple method like GET or + // POST. Access-Control-Allow-Methods is only used for pre-flight requests and the + // spec doesn't instruct to check the allowed methods for simple cross-origin requests. + // We think it's a nice feature to be able to have control on those methods though. + if !c.isMethodAllowed(r.Method) { + if c.log != nil { + c.logf("Actual request no headers added: method '%s' not allowed", + r.Method) + } + + return + } + headers.Set("Access-Control-Allow-Origin", origin) + if len(c.exposedHeaders) > 0 { + headers.Set("Access-Control-Expose-Headers", strings.Join(c.exposedHeaders, ", ")) + } + if c.allowCredentials { + headers.Set("Access-Control-Allow-Credentials", "true") + } + c.logf("Actual response added headers: %v", headers) +} + +// convenience method. checks if debugging is turned on before printing +func (c *Cors) logf(format string, a ...interface{}) { + if c.log != nil { + c.log.Printf(format, a...) + } +} + +// isOriginAllowed checks if a given origin is allowed to perform cross-domain requests +// on the endpoint +func (c *Cors) isOriginAllowed(r *http.Request, origin string) bool { + if c.allowOriginFunc != nil { + return c.allowOriginFunc(r, origin) + } + if c.allowedOriginsAll { + return true + } + origin = strings.ToLower(origin) + for _, o := range c.allowedOrigins { + if o == origin { + return true + } + } + for _, w := range c.allowedWOrigins { + if w.match(origin) { + return true + } + } + return false +} + +// isMethodAllowed checks if a given method can be used as part of a cross-domain request +// on the endpoing +func (c *Cors) isMethodAllowed(method string) bool { + if len(c.allowedMethods) == 0 { + // If no method allowed, always return false, even for preflight request + return false + } + method = strings.ToUpper(method) + if method == "OPTIONS" { + // Always allow preflight requests + return true + } + for _, m := range c.allowedMethods { + if m == method { + return true + } + } + return false +} + +// areHeadersAllowed checks if a given list of headers are allowed to used within +// a cross-domain request. +func (c *Cors) areHeadersAllowed(requestedHeaders []string) bool { + if c.allowedHeadersAll || len(requestedHeaders) == 0 { + return true + } + for _, header := range requestedHeaders { + header = http.CanonicalHeaderKey(header) + found := false + for _, h := range c.allowedHeaders { + if h == header { + found = true + } + } + if !found { + return false + } + } + return true +} diff --git a/backend/vendor/github.com/go-chi/cors/utils.go b/backend/vendor/github.com/go-chi/cors/utils.go new file mode 100644 index 00000000..cd24831f --- /dev/null +++ b/backend/vendor/github.com/go-chi/cors/utils.go @@ -0,0 +1,70 @@ +package cors + +import "strings" + +const toLower = 'a' - 'A' + +type converter func(string) string + +type wildcard struct { + prefix string + suffix string +} + +func (w wildcard) match(s string) bool { + return len(s) >= len(w.prefix+w.suffix) && strings.HasPrefix(s, w.prefix) && strings.HasSuffix(s, w.suffix) +} + +// convert converts a list of string using the passed converter function +func convert(s []string, c converter) []string { + out := []string{} + for _, i := range s { + out = append(out, c(i)) + } + return out +} + +// parseHeaderList tokenize + normalize a string containing a list of headers +func parseHeaderList(headerList string) []string { + l := len(headerList) + h := make([]byte, 0, l) + upper := true + // Estimate the number headers in order to allocate the right splice size + t := 0 + for i := 0; i < l; i++ { + if headerList[i] == ',' { + t++ + } + } + headers := make([]string, 0, t) + for i := 0; i < l; i++ { + b := headerList[i] + if b >= 'a' && b <= 'z' { + if upper { + h = append(h, b-toLower) + } else { + h = append(h, b) + } + } else if b >= 'A' && b <= 'Z' { + if !upper { + h = append(h, b+toLower) + } else { + h = append(h, b) + } + } else if b == '-' || (b >= '0' && b <= '9') { + h = append(h, b) + } + + if b == ' ' || b == ',' || i == l-1 { + if len(h) > 0 { + // Flush the found header + headers = append(headers, string(h)) + h = h[:0] + upper = true + } + } else { + upper = b == '-' + } + } + return headers +}