mirror of https://github.com/coder/coder.git
73 lines
2.1 KiB
Go
73 lines
2.1 KiB
Go
package httpmw
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"golang.org/x/xerrors"
|
|
)
|
|
|
|
const (
|
|
hstsHeader = "Strict-Transport-Security"
|
|
)
|
|
|
|
type HSTSConfig struct {
|
|
// HeaderValue is an empty string if hsts header is disabled.
|
|
HeaderValue string
|
|
}
|
|
|
|
func HSTSConfigOptions(maxAge int, options []string) (HSTSConfig, error) {
|
|
if maxAge <= 0 {
|
|
// No header, so no need to build the header string.
|
|
return HSTSConfig{HeaderValue: ""}, nil
|
|
}
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
|
|
var str strings.Builder
|
|
_, err := str.WriteString(fmt.Sprintf("max-age=%d", maxAge))
|
|
if err != nil {
|
|
return HSTSConfig{}, xerrors.Errorf("hsts: write max-age: %w", err)
|
|
}
|
|
|
|
for _, option := range options {
|
|
switch {
|
|
// Only allow valid options and fix any casing mistakes
|
|
case strings.EqualFold(option, "includeSubDomains"):
|
|
option = "includeSubDomains"
|
|
case strings.EqualFold(option, "preload"):
|
|
option = "preload"
|
|
default:
|
|
return HSTSConfig{}, xerrors.Errorf("hsts: invalid option: %q. Must be 'preload' and/or 'includeSubDomains'", option)
|
|
}
|
|
_, err = str.WriteString("; " + option)
|
|
if err != nil {
|
|
return HSTSConfig{}, xerrors.Errorf("hsts: write option: %w", err)
|
|
}
|
|
}
|
|
return HSTSConfig{
|
|
HeaderValue: str.String(),
|
|
}, nil
|
|
}
|
|
|
|
// HSTS will add the strict-transport-security header if enabled. This header
|
|
// forces a browser to always use https for the domain after it loads https once.
|
|
// Meaning: On first load of product.coder.com, they are redirected to https. On
|
|
// all subsequent loads, the client's local browser forces https. This prevents
|
|
// man in the middle.
|
|
//
|
|
// This header only makes sense if the app is using tls.
|
|
//
|
|
// Full header example:
|
|
// Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
|
|
func HSTS(next http.Handler, cfg HSTSConfig) http.Handler {
|
|
if cfg.HeaderValue == "" {
|
|
// No header, so no need to wrap the handler.
|
|
return next
|
|
}
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set(hstsHeader, cfg.HeaderValue)
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|