mirror of
https://github.com/TecharoHQ/anubis.git
synced 2025-08-05 18:59:10 -04:00
feat: dynamic cookie domains (#722)
* feat: dynamic cookie domains Replaces #685 I was having weird testing issues when trying to merge #685, so I rewrote it from scratch to be a lot more minimal. * chore: spelling Signed-off-by: Xe Iaso <me@xeiaso.net> --------- Signed-off-by: Xe Iaso <me@xeiaso.net>
This commit is contained in:
parent
7cf6ac5de6
commit
a1b7d2ccda
3
.github/actions/spelling/expect.txt
vendored
3
.github/actions/spelling/expect.txt
vendored
@ -75,6 +75,7 @@ duckduckbot
|
|||||||
eerror
|
eerror
|
||||||
ellenjoe
|
ellenjoe
|
||||||
enbyware
|
enbyware
|
||||||
|
etld
|
||||||
everyones
|
everyones
|
||||||
evilbot
|
evilbot
|
||||||
evilsite
|
evilsite
|
||||||
@ -164,6 +165,7 @@ Linting
|
|||||||
linuxbrew
|
linuxbrew
|
||||||
LLU
|
LLU
|
||||||
loadbalancer
|
loadbalancer
|
||||||
|
locahost
|
||||||
lol
|
lol
|
||||||
LOMINSA
|
LOMINSA
|
||||||
maintainership
|
maintainership
|
||||||
@ -210,6 +212,7 @@ privkey
|
|||||||
promauto
|
promauto
|
||||||
promhttp
|
promhttp
|
||||||
proofofwork
|
proofofwork
|
||||||
|
publicsuffix
|
||||||
pwcmd
|
pwcmd
|
||||||
pwuser
|
pwuser
|
||||||
qualys
|
qualys
|
||||||
|
@ -46,6 +46,7 @@ var (
|
|||||||
bindNetwork = flag.String("bind-network", "tcp", "network family to bind HTTP to, e.g. unix, tcp")
|
bindNetwork = flag.String("bind-network", "tcp", "network family to bind HTTP to, e.g. unix, tcp")
|
||||||
challengeDifficulty = flag.Int("difficulty", anubis.DefaultDifficulty, "difficulty of the challenge")
|
challengeDifficulty = flag.Int("difficulty", anubis.DefaultDifficulty, "difficulty of the challenge")
|
||||||
cookieDomain = flag.String("cookie-domain", "", "if set, the top-level domain that the Anubis cookie will be valid for")
|
cookieDomain = flag.String("cookie-domain", "", "if set, the top-level domain that the Anubis cookie will be valid for")
|
||||||
|
cookieDynamicDomain = flag.Bool("cookie-dynamic-domain", false, "if set, automatically set the cookie Domain value based on the request domain")
|
||||||
cookieExpiration = flag.Duration("cookie-expiration-time", anubis.CookieDefaultExpirationTime, "The amount of time the authorization cookie is valid for")
|
cookieExpiration = flag.Duration("cookie-expiration-time", anubis.CookieDefaultExpirationTime, "The amount of time the authorization cookie is valid for")
|
||||||
cookiePartitioned = flag.Bool("cookie-partitioned", false, "if true, sets the partitioned flag on Anubis cookies, enabling CHIPS support")
|
cookiePartitioned = flag.Bool("cookie-partitioned", false, "if true, sets the partitioned flag on Anubis cookies, enabling CHIPS support")
|
||||||
hs512Secret = flag.String("hs512-secret", "", "secret used to sign JWTs, uses ed25519 if not set")
|
hs512Secret = flag.String("hs512-secret", "", "secret used to sign JWTs, uses ed25519 if not set")
|
||||||
@ -240,6 +241,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *cookieDomain != "" && *cookieDynamicDomain {
|
||||||
|
log.Fatalf("you can't set COOKIE_DOMAIN and COOKIE_DYNAMIC_DOMAIN at the same time")
|
||||||
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Thoth configuration
|
// Thoth configuration
|
||||||
|
@ -46,6 +46,7 @@ And some cleanups/refactors were added:
|
|||||||
- Add `--strip-base-prefix` flag/envvar to strip the base prefix from request paths when forwarding to target servers
|
- Add `--strip-base-prefix` flag/envvar to strip the base prefix from request paths when forwarding to target servers
|
||||||
- Fix an off-by-one in the default threshold config
|
- Fix an off-by-one in the default threshold config
|
||||||
- Add functionality for HS512 JWT algorithm
|
- Add functionality for HS512 JWT algorithm
|
||||||
|
- Add support for dynamic cookie domains with the `--cookie-dynamic-domain`/`COOKIE_DYNAMIC_DOMAIN` flag/envvar
|
||||||
|
|
||||||
Request weight is one of the biggest ticket features in Anubis. This enables Anubis to be much closer to a Web Application Firewall and when combined with custom thresholds allows administrators to have Anubis take advanced reactions. For more information about request weight, see [the request weight section](./admin/policies.mdx#request-weight) of the policy file documentation.
|
Request weight is one of the biggest ticket features in Anubis. This enables Anubis to be much closer to a Web Application Firewall and when combined with custom thresholds allows administrators to have Anubis take advanced reactions. For more information about request weight, see [the request weight section](./admin/policies.mdx#request-weight) of the policy file documentation.
|
||||||
|
|
||||||
|
@ -64,6 +64,7 @@ Anubis uses these environment variables for configuration:
|
|||||||
| `BIND` | `:8923` | The network address that Anubis listens on. For `unix`, set this to a path: `/run/anubis/instance.sock` |
|
| `BIND` | `:8923` | The network address that Anubis listens on. For `unix`, set this to a path: `/run/anubis/instance.sock` |
|
||||||
| `BIND_NETWORK` | `tcp` | The address family that Anubis listens on. Accepts `tcp`, `unix` and anything Go's [`net.Listen`](https://pkg.go.dev/net#Listen) supports. |
|
| `BIND_NETWORK` | `tcp` | The address family that Anubis listens on. Accepts `tcp`, `unix` and anything Go's [`net.Listen`](https://pkg.go.dev/net#Listen) supports. |
|
||||||
| `COOKIE_DOMAIN` | unset | The domain the Anubis challenge pass cookie should be set to. This should be set to the domain you bought from your registrar (EG: `techaro.lol` if your webapp is running on `anubis.techaro.lol`). See this [stackoverflow explanation of cookies](https://stackoverflow.com/a/1063760) for more information.<br/><br/>Note that unlike `REDIRECT_DOMAINS`, you should never include a port number in this variable. |
|
| `COOKIE_DOMAIN` | unset | The domain the Anubis challenge pass cookie should be set to. This should be set to the domain you bought from your registrar (EG: `techaro.lol` if your webapp is running on `anubis.techaro.lol`). See this [stackoverflow explanation of cookies](https://stackoverflow.com/a/1063760) for more information.<br/><br/>Note that unlike `REDIRECT_DOMAINS`, you should never include a port number in this variable. |
|
||||||
|
| `COOKIE_DYNAMIC_DOMAIN` | false | If set to true, automatically set cookie domain fields based on the hostname of the request. EG: if you are making a request to `anubis.techaro.lol`, the Anubis cookie will be valid for any subdomain of `techaro.lol`. |
|
||||||
| `COOKIE_EXPIRATION_TIME` | `168h` | The amount of time the authorization cookie is valid for. |
|
| `COOKIE_EXPIRATION_TIME` | `168h` | The amount of time the authorization cookie is valid for. |
|
||||||
| `COOKIE_PARTITIONED` | `false` | If set to `true`, enables the [partitioned (CHIPS) flag](https://developers.google.com/privacy-sandbox/cookies/chips), meaning that Anubis inside an iframe has a different set of cookies than the domain hosting the iframe. |
|
| `COOKIE_PARTITIONED` | `false` | If set to `true`, enables the [partitioned (CHIPS) flag](https://developers.google.com/privacy-sandbox/cookies/chips), meaning that Anubis inside an iframe has a different set of cookies than the domain hosting the iframe. |
|
||||||
| `DIFFICULTY` | `4` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. |
|
| `DIFFICULTY` | `4` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. |
|
||||||
|
@ -148,21 +148,21 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
|
|||||||
ckie, err := r.Cookie(s.cookieName)
|
ckie, err := r.Cookie(s.cookieName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lg.Debug("cookie not found", "path", r.URL.Path)
|
lg.Debug("cookie not found", "path", r.URL.Path)
|
||||||
s.ClearCookie(w, s.cookieName, cookiePath)
|
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||||
s.RenderIndex(w, r, rule, httpStatusOnly)
|
s.RenderIndex(w, r, rule, httpStatusOnly)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ckie.Valid(); err != nil {
|
if err := ckie.Valid(); err != nil {
|
||||||
lg.Debug("cookie is invalid", "err", err)
|
lg.Debug("cookie is invalid", "err", err)
|
||||||
s.ClearCookie(w, s.cookieName, cookiePath)
|
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||||
s.RenderIndex(w, r, rule, httpStatusOnly)
|
s.RenderIndex(w, r, rule, httpStatusOnly)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Now().After(ckie.Expires) && !ckie.Expires.IsZero() {
|
if time.Now().After(ckie.Expires) && !ckie.Expires.IsZero() {
|
||||||
lg.Debug("cookie expired", "path", r.URL.Path)
|
lg.Debug("cookie expired", "path", r.URL.Path)
|
||||||
s.ClearCookie(w, s.cookieName, cookiePath)
|
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||||
s.RenderIndex(w, r, rule, httpStatusOnly)
|
s.RenderIndex(w, r, rule, httpStatusOnly)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -171,7 +171,7 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
|
|||||||
|
|
||||||
if err != nil || !token.Valid {
|
if err != nil || !token.Valid {
|
||||||
lg.Debug("invalid token", "path", r.URL.Path, "err", err)
|
lg.Debug("invalid token", "path", r.URL.Path, "err", err)
|
||||||
s.ClearCookie(w, s.cookieName, cookiePath)
|
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||||
s.RenderIndex(w, r, rule, httpStatusOnly)
|
s.RenderIndex(w, r, rule, httpStatusOnly)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -179,7 +179,7 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
|
|||||||
claims, ok := token.Claims.(jwt.MapClaims)
|
claims, ok := token.Claims.(jwt.MapClaims)
|
||||||
if !ok {
|
if !ok {
|
||||||
lg.Debug("invalid token claims type", "path", r.URL.Path)
|
lg.Debug("invalid token claims type", "path", r.URL.Path)
|
||||||
s.ClearCookie(w, s.cookieName, cookiePath)
|
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||||
s.RenderIndex(w, r, rule, httpStatusOnly)
|
s.RenderIndex(w, r, rule, httpStatusOnly)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -187,14 +187,14 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
|
|||||||
policyRule, ok := claims["policyRule"].(string)
|
policyRule, ok := claims["policyRule"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
lg.Debug("policyRule claim is not a string")
|
lg.Debug("policyRule claim is not a string")
|
||||||
s.ClearCookie(w, s.cookieName, cookiePath)
|
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||||
s.RenderIndex(w, r, rule, httpStatusOnly)
|
s.RenderIndex(w, r, rule, httpStatusOnly)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if policyRule != rule.Hash() {
|
if policyRule != rule.Hash() {
|
||||||
lg.Debug("user originally passed with a different rule, issuing new challenge", "old", policyRule, "new", rule.Name)
|
lg.Debug("user originally passed with a different rule, issuing new challenge", "old", policyRule, "new", rule.Name)
|
||||||
s.ClearCookie(w, s.cookieName, cookiePath)
|
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||||
s.RenderIndex(w, r, rule, httpStatusOnly)
|
s.RenderIndex(w, r, rule, httpStatusOnly)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -216,7 +216,7 @@ func (s *Server) checkRules(w http.ResponseWriter, r *http.Request, cr policy.Ch
|
|||||||
s.ServeHTTPNext(w, r)
|
s.ServeHTTPNext(w, r)
|
||||||
return true
|
return true
|
||||||
case config.RuleDeny:
|
case config.RuleDeny:
|
||||||
s.ClearCookie(w, s.cookieName, cookiePath)
|
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||||
lg.Info("explicit deny")
|
lg.Info("explicit deny")
|
||||||
if rule == nil {
|
if rule == nil {
|
||||||
lg.Error("rule is nil, cannot calculate checksum")
|
lg.Error("rule is nil, cannot calculate checksum")
|
||||||
@ -235,7 +235,7 @@ func (s *Server) checkRules(w http.ResponseWriter, r *http.Request, cr policy.Ch
|
|||||||
s.RenderBench(w, r)
|
s.RenderBench(w, r)
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
s.ClearCookie(w, s.cookieName, cookiePath)
|
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||||
slog.Error("CONFIG ERROR: unknown rule", "rule", cr.Rule)
|
slog.Error("CONFIG ERROR: unknown rule", "rule", cr.Rule)
|
||||||
s.respondWithError(w, r, "Internal Server Error: administrator has misconfigured Anubis. Please contact the administrator and ask them to look for the logs around \"maybeReverseProxy.Rules\"")
|
s.respondWithError(w, r, "Internal Server Error: administrator has misconfigured Anubis. Please contact the administrator and ask them to look for the logs around \"maybeReverseProxy.Rules\"")
|
||||||
return true
|
return true
|
||||||
@ -302,7 +302,7 @@ func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
|
|||||||
lg = lg.With("check_result", cr)
|
lg = lg.With("check_result", cr)
|
||||||
chal := s.challengeFor(r, rule.Challenge.Difficulty)
|
chal := s.challengeFor(r, rule.Challenge.Difficulty)
|
||||||
|
|
||||||
s.SetCookie(w, anubis.TestCookieName, chal, "/")
|
s.SetCookie(w, anubis.TestCookieName, chal, "/", r.Host)
|
||||||
|
|
||||||
err = encoder.Encode(struct {
|
err = encoder.Encode(struct {
|
||||||
Rules *config.ChallengeRules `json:"rules"`
|
Rules *config.ChallengeRules `json:"rules"`
|
||||||
@ -330,14 +330,14 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _, err := r.Cookie(anubis.TestCookieName); errors.Is(err, http.ErrNoCookie) {
|
if _, err := r.Cookie(anubis.TestCookieName); errors.Is(err, http.ErrNoCookie) {
|
||||||
s.ClearCookie(w, s.cookieName, cookiePath)
|
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||||
s.ClearCookie(w, anubis.TestCookieName, "/")
|
s.ClearCookie(w, anubis.TestCookieName, "/", r.Host)
|
||||||
lg.Warn("user has cookies disabled, this is not an anubis bug")
|
lg.Warn("user has cookies disabled, this is not an anubis bug")
|
||||||
s.respondWithError(w, r, "Your browser is configured to disable cookies. Anubis requires cookies for the legitimate interest of making sure you are a valid client. Please enable cookies for this domain")
|
s.respondWithError(w, r, "Your browser is configured to disable cookies. Anubis requires cookies for the legitimate interest of making sure you are a valid client. Please enable cookies for this domain")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.ClearCookie(w, anubis.TestCookieName, "/")
|
s.ClearCookie(w, anubis.TestCookieName, "/", r.Host)
|
||||||
|
|
||||||
redir := r.FormValue("redir")
|
redir := r.FormValue("redir")
|
||||||
redirURL, err := url.ParseRequestURI(redir)
|
redirURL, err := url.ParseRequestURI(redir)
|
||||||
@ -379,7 +379,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err := impl.Validate(r, lg, rule, challengeStr); err != nil {
|
if err := impl.Validate(r, lg, rule, challengeStr); err != nil {
|
||||||
failedValidations.WithLabelValues(rule.Challenge.Algorithm).Inc()
|
failedValidations.WithLabelValues(rule.Challenge.Algorithm).Inc()
|
||||||
var cerr *challenge.Error
|
var cerr *challenge.Error
|
||||||
s.ClearCookie(w, s.cookieName, cookiePath)
|
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||||
lg.Debug("challenge validate call failed", "err", err)
|
lg.Debug("challenge validate call failed", "err", err)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
@ -402,12 +402,12 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
|||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lg.Error("failed to sign JWT", "err", err)
|
lg.Error("failed to sign JWT", "err", err)
|
||||||
s.ClearCookie(w, s.cookieName, cookiePath)
|
s.ClearCookie(w, s.cookieName, cookiePath, r.Host)
|
||||||
s.respondWithError(w, r, "failed to sign JWT")
|
s.respondWithError(w, r, "failed to sign JWT")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.SetCookie(w, s.cookieName, tokenString, cookiePath)
|
s.SetCookie(w, s.cookieName, tokenString, cookiePath, r.Host)
|
||||||
|
|
||||||
challengesValidated.WithLabelValues(rule.Challenge.Algorithm).Inc()
|
challengesValidated.WithLabelValues(rule.Challenge.Algorithm).Inc()
|
||||||
lg.Debug("challenge passed, redirecting to app")
|
lg.Debug("challenge passed, redirecting to app")
|
||||||
|
@ -28,21 +28,22 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Next http.Handler
|
Next http.Handler
|
||||||
Policy *policy.ParsedConfig
|
Policy *policy.ParsedConfig
|
||||||
Target string
|
Target string
|
||||||
CookieDomain string
|
CookieDynamicDomain bool
|
||||||
CookieName string
|
CookieDomain string
|
||||||
BasePrefix string
|
CookieExpiration time.Duration
|
||||||
WebmasterEmail string
|
CookieName string
|
||||||
RedirectDomains []string
|
CookiePartitioned bool
|
||||||
ED25519PrivateKey ed25519.PrivateKey
|
BasePrefix string
|
||||||
HS512Secret []byte
|
WebmasterEmail string
|
||||||
CookieExpiration time.Duration
|
RedirectDomains []string
|
||||||
StripBasePrefix bool
|
ED25519PrivateKey ed25519.PrivateKey
|
||||||
OpenGraph config.OpenGraph
|
HS512Secret []byte
|
||||||
CookiePartitioned bool
|
StripBasePrefix bool
|
||||||
ServeRobotsTXT bool
|
OpenGraph config.OpenGraph
|
||||||
|
ServeRobotsTXT bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty int) (*policy.ParsedConfig, error) {
|
func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty int) (*policy.ParsedConfig, error) {
|
||||||
|
28
lib/http.go
28
lib/http.go
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -15,21 +16,40 @@ import (
|
|||||||
"github.com/TecharoHQ/anubis/web"
|
"github.com/TecharoHQ/anubis/web"
|
||||||
"github.com/a-h/templ"
|
"github.com/a-h/templ"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
"golang.org/x/net/publicsuffix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) SetCookie(w http.ResponseWriter, name, value, path string) {
|
var domainMatchRegexp = regexp.MustCompile(`^((xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$`)
|
||||||
|
|
||||||
|
func (s *Server) SetCookie(w http.ResponseWriter, name, value, path, host string) {
|
||||||
|
var domain = s.opts.CookieDomain
|
||||||
|
if s.opts.CookieDynamicDomain && domainMatchRegexp.MatchString(host) {
|
||||||
|
if etld, err := publicsuffix.EffectiveTLDPlusOne(host); err == nil {
|
||||||
|
domain = etld
|
||||||
|
name = anubis.WithDomainCookieName + etld
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: name,
|
Name: name,
|
||||||
Value: value,
|
Value: value,
|
||||||
Expires: time.Now().Add(s.opts.CookieExpiration),
|
Expires: time.Now().Add(s.opts.CookieExpiration),
|
||||||
SameSite: http.SameSiteLaxMode,
|
SameSite: http.SameSiteLaxMode,
|
||||||
Domain: s.opts.CookieDomain,
|
Domain: domain,
|
||||||
Partitioned: s.opts.CookiePartitioned,
|
Partitioned: s.opts.CookiePartitioned,
|
||||||
Path: path,
|
Path: path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ClearCookie(w http.ResponseWriter, name, path string) {
|
func (s *Server) ClearCookie(w http.ResponseWriter, name, path, host string) {
|
||||||
|
var domain = s.opts.CookieDomain
|
||||||
|
if s.opts.CookieDynamicDomain && domainMatchRegexp.MatchString(host) {
|
||||||
|
if etld, err := publicsuffix.EffectiveTLDPlusOne(host); err == nil {
|
||||||
|
domain = etld
|
||||||
|
name = anubis.WithDomainCookieName + etld
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: name,
|
Name: name,
|
||||||
Value: "",
|
Value: "",
|
||||||
@ -37,7 +57,7 @@ func (s *Server) ClearCookie(w http.ResponseWriter, name, path string) {
|
|||||||
Expires: time.Now().Add(-1 * time.Minute),
|
Expires: time.Now().Add(-1 * time.Minute),
|
||||||
SameSite: http.SameSiteLaxMode,
|
SameSite: http.SameSiteLaxMode,
|
||||||
Partitioned: s.opts.CookiePartitioned,
|
Partitioned: s.opts.CookiePartitioned,
|
||||||
Domain: s.opts.CookieDomain,
|
Domain: domain,
|
||||||
Path: path,
|
Path: path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,55 @@ import (
|
|||||||
"github.com/TecharoHQ/anubis"
|
"github.com/TecharoHQ/anubis"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestSetCookie(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
options Options
|
||||||
|
host string
|
||||||
|
cookieName string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic",
|
||||||
|
options: Options{},
|
||||||
|
host: "",
|
||||||
|
cookieName: anubis.CookieName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "domain techaro.lol",
|
||||||
|
options: Options{CookieDomain: "techaro.lol"},
|
||||||
|
host: "",
|
||||||
|
cookieName: anubis.WithDomainCookieName + "techaro.lol",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dynamic cookie domain",
|
||||||
|
options: Options{CookieDynamicDomain: true},
|
||||||
|
host: "techaro.lol",
|
||||||
|
cookieName: anubis.WithDomainCookieName + "techaro.lol",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
srv := spawnAnubis(t, tt.options)
|
||||||
|
rw := httptest.NewRecorder()
|
||||||
|
|
||||||
|
srv.SetCookie(rw, srv.cookieName, "test", "/", tt.host)
|
||||||
|
|
||||||
|
resp := rw.Result()
|
||||||
|
cookies := resp.Cookies()
|
||||||
|
|
||||||
|
ckie := cookies[0]
|
||||||
|
|
||||||
|
if ckie.Name != tt.cookieName {
|
||||||
|
t.Errorf("wanted cookie named %q, got cookie named %q", tt.cookieName, ckie.Name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestClearCookie(t *testing.T) {
|
func TestClearCookie(t *testing.T) {
|
||||||
srv := spawnAnubis(t, Options{})
|
srv := spawnAnubis(t, Options{})
|
||||||
rw := httptest.NewRecorder()
|
rw := httptest.NewRecorder()
|
||||||
|
|
||||||
srv.ClearCookie(rw, srv.cookieName, "/")
|
srv.ClearCookie(rw, srv.cookieName, "/", "localhost")
|
||||||
|
|
||||||
resp := rw.Result()
|
resp := rw.Result()
|
||||||
|
|
||||||
@ -36,7 +80,7 @@ func TestClearCookieWithDomain(t *testing.T) {
|
|||||||
srv := spawnAnubis(t, Options{CookieDomain: "techaro.lol"})
|
srv := spawnAnubis(t, Options{CookieDomain: "techaro.lol"})
|
||||||
rw := httptest.NewRecorder()
|
rw := httptest.NewRecorder()
|
||||||
|
|
||||||
srv.ClearCookie(rw, srv.cookieName, "/")
|
srv.ClearCookie(rw, srv.cookieName, "/", "locahost")
|
||||||
|
|
||||||
resp := rw.Result()
|
resp := rw.Result()
|
||||||
|
|
||||||
@ -56,3 +100,28 @@ func TestClearCookieWithDomain(t *testing.T) {
|
|||||||
t.Errorf("wanted cookie max age of -1, got: %d", ckie.MaxAge)
|
t.Errorf("wanted cookie max age of -1, got: %d", ckie.MaxAge)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClearCookieWithDynamicDomain(t *testing.T) {
|
||||||
|
srv := spawnAnubis(t, Options{CookieDynamicDomain: true})
|
||||||
|
rw := httptest.NewRecorder()
|
||||||
|
|
||||||
|
srv.ClearCookie(rw, srv.cookieName, "/", "xeiaso.net")
|
||||||
|
|
||||||
|
resp := rw.Result()
|
||||||
|
|
||||||
|
cookies := resp.Cookies()
|
||||||
|
|
||||||
|
if len(cookies) != 1 {
|
||||||
|
t.Errorf("wanted 1 cookie, got %d cookies", len(cookies))
|
||||||
|
}
|
||||||
|
|
||||||
|
ckie := cookies[0]
|
||||||
|
|
||||||
|
if ckie.Name != anubis.WithDomainCookieName+"xeiaso.net" {
|
||||||
|
t.Errorf("wanted cookie named %q, got cookie named %q", srv.cookieName, ckie.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ckie.MaxAge != -1 {
|
||||||
|
t.Errorf("wanted cookie max age of -1, got: %d", ckie.MaxAge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user