package authentik import ( "net/http" "strings" "time" ) const authentikCookiePath = "/auth/" // SerializedCookie stores an HTTP cookie for flow session persistence. type SerializedCookie struct { Name string `json:"name"` Value string `json:"value"` Path string `json:"path,omitempty"` Domain string `json:"domain,omitempty"` MaxAge int `json:"maxAge,omitempty"` Expires time.Time `json:"expires,omitempty"` Secure bool `json:"secure,omitempty"` HTTPOnly bool `json:"httpOnly,omitempty"` SameSite http.SameSite `json:"-"` SameSiteName string `json:"sameSite,omitempty"` } func (fe *FlowExecutor) ExportCookies() []SerializedCookie { if fe.client.Jar == nil { return nil } raw := fe.client.Jar.Cookies(fe.cookieURL()) out := make([]SerializedCookie, 0, len(raw)) for _, c := range raw { out = append(out, serializeCookie(c)) } return out } func (fe *FlowExecutor) ImportCookies(stored []SerializedCookie) { if fe.client.Jar == nil || len(stored) == 0 { return } cookies := make([]*http.Cookie, 0, len(stored)) for _, sc := range stored { cookies = append(cookies, deserializeCookie(sc)) } fe.client.Jar.SetCookies(fe.cookieURL(), cookies) } func serializeCookie(c *http.Cookie) SerializedCookie { sc := SerializedCookie{ Name: c.Name, Value: c.Value, Path: c.Path, Domain: c.Domain, MaxAge: c.MaxAge, Expires: c.Expires, Secure: c.Secure, HTTPOnly: c.HttpOnly, SameSite: c.SameSite, } switch c.SameSite { case http.SameSiteDefaultMode: sc.SameSiteName = "Default" case http.SameSiteLaxMode: sc.SameSiteName = "Lax" case http.SameSiteStrictMode: sc.SameSiteName = "Strict" case http.SameSiteNoneMode: sc.SameSiteName = "None" } return sc } func deserializeCookie(sc SerializedCookie) *http.Cookie { c := &http.Cookie{ Name: sc.Name, Value: sc.Value, Path: sc.Path, Domain: sc.Domain, MaxAge: sc.MaxAge, Expires: sc.Expires, Secure: sc.Secure, HttpOnly: sc.HTTPOnly, } switch strings.ToLower(sc.SameSiteName) { case "lax": c.SameSite = http.SameSiteLaxMode case "strict": c.SameSite = http.SameSiteStrictMode case "none": c.SameSite = http.SameSiteNoneMode default: c.SameSite = http.SameSiteLaxMode } return c } // BrowserAuthentikCookies rewrites stored cookies for the public Authentik path. func BrowserAuthentikCookies(stored []SerializedCookie) []*http.Cookie { out := make([]*http.Cookie, 0, len(stored)) for _, sc := range stored { c := deserializeCookie(sc) c.Path = authentikCookiePath c.Domain = "" out = append(out, c) } return out } // RestoreFlowExecutor rebuilds an executor from persisted cookies. func RestoreFlowExecutor(baseURL, slug string, stored []SerializedCookie) (*FlowExecutor, error) { fe, err := NewFlowExecutor(baseURL, slug) if err != nil { return nil, err } fe.ImportCookies(stored) return fe, nil } // CloneCookieJar copies cookies between executors (tests). func CloneCookieJar(from, to *FlowExecutor) { if from == nil || to == nil { return } to.ImportCookies(from.ExportCookies()) }