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 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") }