ultisuite-backend/internal/authentik/flow_cookies.go
R3D347HR4Y 8bbc539d77 feat(auth): implement flow completion and rate limiting for authentication flows
- Added a new handler for completing authentication flows, including session validation and cookie management.
- Implemented flow rate limiting to restrict the number of flow start requests per client IP.
- Enhanced flow session management with Redis support for persistent session storage.
- Updated existing handlers to integrate the new flow completion logic and error handling for various session states.
- Introduced unit tests for the new flow completion and rate limiting functionalities to ensure reliability.
2026-06-20 01:09:42 +02:00

125 lines
3.1 KiB
Go

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