zitadel/internal/api/http/cookie.go
Elio Bischof 681541f41b
feat: add quotas (#4779)
adds possibilities to cap authenticated requests and execution seconds of actions on a defined intervall
2023-02-15 02:52:11 +01:00

149 lines
3.3 KiB
Go

package http
import (
"net/http"
"strings"
"github.com/gorilla/securecookie"
"github.com/zitadel/zitadel/internal/errors"
)
const (
prefixSecure = "__Secure-"
prefixHost = "__Host-"
)
type CookieHandler struct {
securecookie *securecookie.SecureCookie
secureOnly bool
httpOnly bool
sameSite http.SameSite
path string
maxAge int
}
func NewCookieHandler(opts ...CookieHandlerOpt) *CookieHandler {
c := &CookieHandler{
secureOnly: true,
httpOnly: true,
sameSite: http.SameSiteLaxMode,
path: "/",
}
for _, opt := range opts {
opt(c)
}
return c
}
type CookieHandlerOpt func(*CookieHandler)
func WithEncryption(hashKey, encryptKey []byte) CookieHandlerOpt {
return func(c *CookieHandler) {
c.securecookie = securecookie.New(hashKey, encryptKey)
}
}
func WithUnsecure() CookieHandlerOpt {
return func(c *CookieHandler) {
c.secureOnly = false
}
}
func WithNonHttpOnly() CookieHandlerOpt {
return func(c *CookieHandler) {
c.httpOnly = false
}
}
func WithSameSite(sameSite http.SameSite) CookieHandlerOpt {
return func(c *CookieHandler) {
c.sameSite = sameSite
}
}
func WithPath(path string) CookieHandlerOpt {
return func(c *CookieHandler) {
c.path = path
}
}
func WithMaxAge(maxAge int) CookieHandlerOpt {
return func(c *CookieHandler) {
c.maxAge = maxAge
if c.securecookie != nil {
c.securecookie.MaxAge(maxAge)
}
}
}
func SetCookiePrefix(name, domain, path string, secureOnly bool) string {
if !secureOnly {
return name
}
if domain != "" || path != "/" {
return prefixSecure + name
}
return prefixHost + name
}
func (c *CookieHandler) GetCookieValue(r *http.Request, name string) (string, error) {
cookie, err := r.Cookie(name)
if err != nil {
return "", err
}
return cookie.Value, nil
}
func (c *CookieHandler) GetEncryptedCookieValue(r *http.Request, name string, value interface{}) error {
cookie, err := r.Cookie(SetCookiePrefix(name, r.Host, c.path, c.secureOnly))
if err != nil {
return err
}
if c.securecookie == nil {
return errors.ThrowInternal(nil, "HTTP-X6XpnL", "securecookie not configured")
}
return c.securecookie.Decode(name, cookie.Value, value)
}
func (c *CookieHandler) SetCookie(w http.ResponseWriter, name, domain, value string) {
c.httpSet(w, name, domain, value, c.maxAge)
}
func (c *CookieHandler) SetEncryptedCookie(w http.ResponseWriter, name, domain string, value interface{}) error {
if c.securecookie == nil {
return errors.ThrowInternal(nil, "HTTP-s2HUtx", "securecookie not configured")
}
encoded, err := c.securecookie.Encode(name, value)
if err != nil {
return err
}
c.httpSet(w, name, domain, encoded, c.maxAge)
return nil
}
func (c *CookieHandler) DeleteCookie(w http.ResponseWriter, r *http.Request, name string) {
c.httpSet(w, name, r.Host, "", -1)
}
func (c *CookieHandler) httpSet(w http.ResponseWriter, name, domain, value string, maxage int) {
http.SetCookie(w, &http.Cookie{
Name: SetCookiePrefix(name, domain, c.path, c.secureOnly),
Value: value,
Domain: strings.Split(domain, ":")[0],
Path: c.path,
MaxAge: maxage,
HttpOnly: c.httpOnly,
Secure: c.secureOnly,
SameSite: c.sameSite,
})
varyValues := w.Header().Values("vary")
for _, vary := range varyValues {
if vary == "Cookie" {
return
}
}
w.Header().Add("vary", "Cookie")
}