providers/http: Add support for easily getting/setting cookies

This commit is contained in:
Sasha Koshka 2025-03-27 18:28:48 -04:00
parent 88ea929288
commit 11dfb795ed

View File

@ -1,16 +1,21 @@
package http package http
import "log"
import "time"
import "net/url" import "net/url"
import "net/http" import "net/http"
import "html/template" import "html/template"
import "git.tebibyte.media/sashakoshka/step" import "git.tebibyte.media/sashakoshka/step"
import shttp "git.tebibyte.media/sashakoshka/step/http" import shttp "git.tebibyte.media/sashakoshka/step/http"
const defaultLifetime = 24 * 7 * time.Hour
var _ step.FuncProvider = new(Provider) var _ step.FuncProvider = new(Provider)
// Provider provides HTTP and URL functions. // Provider provides HTTP and URL functions.
type Provider struct { type Provider struct {
InsecureCookie bool
CookieDomain string
} }
// Package fulfills the step.Provider interface. // Package fulfills the step.Provider interface.
@ -18,6 +23,22 @@ func (this *Provider) Package () string {
return "http" return "http"
} }
func (this *Provider) Configure (config step.Meta) error {
if insecureCookieStr := config.Get("http.insecure-cookie"); insecureCookieStr != "" {
if insecureCookieStr != "true" && insecureCookieStr != "false" {
return step.ErrTypeMismatch
}
this.InsecureCookie = insecureCookieStr == "true"
if this.InsecureCookie {
log.Println("!!! http.insecure-cookie is active, this is not recommended")
}
}
if cookieDomainStr := config.Get("http.cookie-domain"); cookieDomainStr != "" {
this.CookieDomain = cookieDomainStr
}
return nil
}
// FuncMap fulfills the step.FuncProvider interface. // FuncMap fulfills the step.FuncProvider interface.
func (this *Provider) FuncMap () template.FuncMap { func (this *Provider) FuncMap () template.FuncMap {
return template.FuncMap { return template.FuncMap {
@ -29,6 +50,9 @@ func (this *Provider) FuncMap () template.FuncMap {
"queryEscape": url.QueryEscape, "queryEscape": url.QueryEscape,
"queryUnescape": url.QueryUnescape, "queryUnescape": url.QueryUnescape,
"unsafe": funcUnsafe, "unsafe": funcUnsafe,
"getCookie": this.funcGetCookie,
"setCookie": this.funcSetCookie,
"setCookieExpires": this.funcSetCookieExpires,
} }
} }
@ -64,3 +88,58 @@ func funcParseForm (req *http.Request) url.Values {
func funcUnsafe(text string) template.HTML { func funcUnsafe(text string) template.HTML {
return template.HTML(text) return template.HTML(text)
} }
func (this *Provider) funcGetCookie(
req *http.Request,
name string,
) string {
cookie, err := req.Cookie(name)
if err != nil { return "" }
return cookie.Value
}
func (this *Provider) funcSetCookie(
res shttp.WrappedResponseWriter,
name string,
value string,
) error {
return this.funcSetCookieExpires(res, name, value, 0)
}
func (this *Provider) funcSetCookieExpires(
res shttp.WrappedResponseWriter,
name string,
value string,
lifetime time.Duration,
) error {
if !this.InsecureCookie {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie
// __Host- prefix: Cookies with names starting with __Host- are
// sent only to the host subdomain or domain that set them, and
// not to any other host. They must be set with the secure flag
// must be from a secure page (HTTPS), must not have a domain
// specified, and the path must be /
name = "__Host-" + name
}
var expiration time.Time
if lifetime == 0 {
expiration = time.Now().Add(defaultLifetime)
} else {
expiration = time.Now().Add(lifetime)
}
cookie := &http.Cookie {
Name: name,
Value: value,
Expires: expiration,
SameSite: http.SameSiteStrictMode,
Path: "/",
Domain: this.CookieDomain,
}
if !this.InsecureCookie {
cookie.Secure = true
cookie.HttpOnly = true
}
underlyingRes := shttp.UnderlyingResponseWriter(res)
http.SetCookie(underlyingRes, cookie)
return nil
}