- 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.
125 lines
3.1 KiB
Go
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())
|
|
} |