Documentation
¶
Overview ¶
Package cfgerrors provides functionalities for programmatically handling configuration errors produced by package github.com/jub0bs/cors.
Most users of package github.com/jub0bs/cors have no use for this package. However, multi-tenant SaaS companies that allow their tenants to configure CORS (e.g. via some Web portal) may find this package useful: it indeed allows those companies to inform their tenants about CORS-configuration mistakes via custom, human-friendly error messages, perhaps even ones written in a natural language other than English and/or generated on the client side.
Example ¶
The server below lets tenants configure their own CORS middleware; note that it programmatically handles the resulting error (if any) in order to inform tenants of their CORS-configuration mistakes in a human-friendly way.
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"mime"
"net/http"
"github.com/jub0bs/cors"
"github.com/jub0bs/cors/cfgerrors"
)
// The server below lets tenants configure their own CORS middleware;
// note that it programmatically handles the resulting error (if any)
// in order to inform tenants of their CORS-configuration mistakes
// in a human-friendly way.
func main() {
app := TenantApp{id: "jub0bs"}
mux := http.NewServeMux()
mux.HandleFunc("POST /configure-cors", app.handleReconfigureCORS)
api := http.NewServeMux()
api.HandleFunc("GET /hello", handleHello)
mux.Handle("/", app.corsMiddleware.Wrap(api))
if err := http.ListenAndServe(":8080", mux); err != http.ErrServerClosed {
log.Fatal(err)
}
}
type TenantApp struct {
id string
corsMiddleware cors.Middleware
}
func (app *TenantApp) handleReconfigureCORS(w http.ResponseWriter, r *http.Request) {
mediatype, _, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
if err != nil || mediatype != "application/json" {
w.WriteHeader(http.StatusBadRequest)
return
}
var reqData struct {
Origins []string `json:"origins"`
Credentials bool `json:"credentials"`
Methods []string `json:"methods"`
RequestHeaders []string `json:"request_headers"`
MaxAge int `json:"max_age"`
ResponseHeaders []string `json:"response_headers"`
TolerateInsecure bool `json:"tolerate_insecure"`
}
if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
cfg := cors.Config{
Origins: reqData.Origins,
Credentialed: reqData.Credentials,
Methods: reqData.Methods,
RequestHeaders: reqData.RequestHeaders,
MaxAgeInSeconds: reqData.MaxAge,
ResponseHeaders: reqData.ResponseHeaders,
ExtraConfig: cors.ExtraConfig{
DangerouslyTolerateSubdomainsOfPublicSuffixes: reqData.TolerateInsecure,
DangerouslyTolerateInsecureOrigins: reqData.TolerateInsecure,
},
}
if err := app.corsMiddleware.Reconfigure(&cfg); err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
resData := struct {
Errors []string `json:"errors"`
}{
Errors: adaptCORSConfigErrorMessagesForClient(err),
}
if err := json.NewEncoder(w).Encode(resData); err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
}
}
func adaptCORSConfigErrorMessagesForClient(err error) []string {
// Modify the following logic to suit your needs.
var msgs []string
for err := range cfgerrors.All(err) {
switch err := err.(type) {
case *cfgerrors.UnacceptableOriginPatternError:
var msg string
switch err.Reason {
case "missing":
msg = "You must allow at least one Web origin."
case "invalid":
msg = fmt.Sprintf("%q is not a valid Web origin.", err.Value)
case "prohibited":
msg = fmt.Sprintf("For security reasons, you cannot allow Web origin %q.", err.Value)
default:
panic("unknown reason")
}
msgs = append(msgs, msg)
case *cfgerrors.UnacceptableMethodError:
var msg string
switch err.Reason {
case "invalid":
msg = fmt.Sprintf("%q is not a valid HTTP-method name.", err.Value)
case "forbidden":
msg = fmt.Sprintf("No browser-based client can send a %s request.", err.Value)
default:
panic("unknown reason")
}
msgs = append(msgs, msg)
case *cfgerrors.UnacceptableHeaderNameError:
var msg string
switch err.Reason {
case "invalid":
const tmpl = "%q is not a valid %s-header name."
msg = fmt.Sprintf(tmpl, err.Value, err.Type)
case "prohibited":
const tmpl = "You cannot allow %q as a %s-header name."
msg = fmt.Sprintf(tmpl, err.Value, err.Type)
case "forbidden":
switch err.Type {
case "request":
const tmpl = "No browser-based client can include a header named %q in a request."
msg = fmt.Sprintf(tmpl, err.Value)
case "response":
const tmpl = "No browser-based client can read a header named %q from a response."
msg = fmt.Sprintf(tmpl, err.Value)
default:
panic("unknown message type")
}
default:
panic("unknown reason")
}
msgs = append(msgs, msg)
case *cfgerrors.MaxAgeOutOfBoundsError:
const tmpl = "Your max-age value, %d, is either negative or too high (max: %d). Alternatively, you can specify %d to disable caching."
msg := fmt.Sprintf(tmpl, err.Value, err.Max, err.Disable)
msgs = append(msgs, msg)
case *cfgerrors.IncompatibleOriginPatternError:
var msg string
switch err.Reason {
case "credentialed":
if err.Value == "*" {
msg = "For security reasons, you cannot both allow credentialed access and allow all Web origins."
} else {
const tmpl = "For security reasons, you cannot both allow credentialed access and allow insecure origins like %q."
msg = fmt.Sprintf(tmpl, err.Value)
}
case "psl":
const tmpl = "For security reasons, you cannot specify %q as an origin pattern, because it covers all subdomains of a registrable domain."
msg = fmt.Sprintf(tmpl, err.Value)
default:
panic("unknown reason")
}
msgs = append(msgs, msg)
case *cfgerrors.IncompatibleWildcardResponseHeaderNameError:
msg := "You cannot expose all response headers when credentialed access is allowed."
msgs = append(msgs, msg)
default:
panic("unknown configuration issue")
}
}
return msgs
}
func handleHello(w http.ResponseWriter, _ *http.Request) {
io.WriteString(w, "Hello, World!")
}
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func All ¶
All returns an iterator over the CORS-configuration errors contained in err's error tree. The order is unspecified and may change from one release to the next. All only supports error values returned by github.com/jub0bs/cors.NewMiddleware and github.com/jub0bs/cors.Middleware.Reconfigure; it should not be called on any other error value.
Types ¶
type IncompatibleOriginPatternError ¶
type IncompatibleOriginPatternError struct {
Value string // "*" | some other origin pattern
Reason string // credentialed | psl
}
An IncompatibleOriginPatternError indicates an origin pattern that conflicts with other elements of the configuration. Three cases are possible:
- Value == "*" and Reason == "credentialed": the wildcard origin was specified and credentialed access was enabled.
- Value != "*" and Reason == "credentialed": an insecure origin pattern was specified and credentialed access was enabled without also setting github.com/jub0bs/cors.ExtraConfig.DangerouslyTolerateInsecureOriginPatterns.
- Reason == "psl": an origin pattern that encompasses arbitrary subdomains of a public suffix was specified without also setting github.com/jub0bs/cors.ExtraConfig.DangerouslyTolerateSubdomainsOfPublicSuffixes.
For more details, see github.com/jub0bs/cors.Config.Origins.
func (*IncompatibleOriginPatternError) Error ¶
func (err *IncompatibleOriginPatternError) Error() string
type IncompatibleWildcardResponseHeaderNameError ¶
type IncompatibleWildcardResponseHeaderNameError struct{}
An IncompatibleWildcardResponseHeaderNameError indicates an attempt to both expose all response headers and enable credentialed access. For more details, see github.com/jub0bs/cors.Config.ResponseHeaders.
func (*IncompatibleWildcardResponseHeaderNameError) Error ¶
func (*IncompatibleWildcardResponseHeaderNameError) Error() string
type MaxAgeOutOfBoundsError ¶
type MaxAgeOutOfBoundsError struct {
Value int // the unacceptable value that was specified
Default int // max-age value used by browsers if MaxAgeInSeconds is 0
Max int // maximum max-age value permitted by this library
Disable int // sentinel value for disabling preflight caching
}
A MaxAgeOutOfBoundsError indicates a max-age value that's either too low or too high.
For more details, see github.com/jub0bs/cors.Config.MaxAgeInSeconds.
func (*MaxAgeOutOfBoundsError) Error ¶
func (err *MaxAgeOutOfBoundsError) Error() string
type PreflightSuccessStatusOutOfBoundsError ¶
type PreflightSuccessStatusOutOfBoundsError struct {
Value int // the unacceptable value that was specified
Default int // default value used by this library
Min int // minimum value for an ok status
Max int // maximum value for an ok status
}
A PreflightSuccessStatusOutOfBoundsError indicates a preflight-success status that's either too low or too high.
For more details, see github.com/jub0bs/cors.Config.PreflightSuccessStatus.
func (*PreflightSuccessStatusOutOfBoundsError) Error ¶
func (err *PreflightSuccessStatusOutOfBoundsError) Error() string
type UnacceptableHeaderNameError ¶
type UnacceptableHeaderNameError struct {
Value string // the unacceptable value that was specified
Type string // request | response
Reason string // invalid | prohibited | forbidden
}
An UnacceptableHeaderNameError indicates an unacceptable header name. The Type field may take one of two values:
- "request";
- "response".
The Reason field may take one of three values:
- "invalid": the header name is invalid;
- "prohibited": the header name is prohibited by this library;
- "forbidden": the header name is forbidden by the Fetch standard.
For more details, see github.com/jub0bs/cors.Config.RequestHeaders and github.com/jub0bs/cors.Config.ResponseHeaders.
func (*UnacceptableHeaderNameError) Error ¶
func (err *UnacceptableHeaderNameError) Error() string
type UnacceptableMethodError ¶
type UnacceptableMethodError struct {
Value string // the unacceptable value that was specified
Reason string // invalid | forbidden
}
An UnacceptableMethodError indicates an unacceptable method. The Reason field may take one of two values:
- "invalid": the method is invalid;
- "forbidden": the method is forbidden by the Fetch standard.
For more details, see github.com/jub0bs/cors.Config.Methods.
func (*UnacceptableMethodError) Error ¶
func (err *UnacceptableMethodError) Error() string
type UnacceptableOriginPatternError ¶
type UnacceptableOriginPatternError struct {
Value string // the unacceptable value that was specified
Reason string // missing | invalid | prohibited
}
An UnacceptableOriginPatternError indicates an unacceptable origin pattern. The Reason field may take one of three values:
- "missing": no origin pattern was specified;
- "invalid": the origin pattern is invalid;
- "prohibited": the origin pattern is prohibited by this library.
For more details, see github.com/jub0bs/cors.Config.Origins.
func (*UnacceptableOriginPatternError) Error ¶
func (err *UnacceptableOriginPatternError) Error() string