cfgerrors

package
v0.9.2 Latest Latest
Warning

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

Go to latest
Published: Oct 25, 2025 License: MIT Imports: 2 Imported by: 0

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

func All(err error) iter.Seq[error]

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:

For more details, see github.com/jub0bs/cors.Config.Origins.

func (*IncompatibleOriginPatternError) Error

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

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

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

Jump to

Keyboard shortcuts

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