feat: add psl check in cookie domain

This commit is contained in:
Stavros
2025-09-10 13:43:08 +03:00
parent 74cb8067a8
commit e03eaf4f08
15 changed files with 102 additions and 90 deletions

View File

@@ -8,30 +8,38 @@ import (
"tinyauth/internal/config"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog"
"github.com/weppos/publicsuffix-go/publicsuffix"
)
// Get root domain parses a hostname and returns the upper domain (e.g. sub1.sub2.domain.com -> sub2.domain.com)
func GetRootDomain(u string) (string, error) {
appUrl, err := url.Parse(u)
// Get cookie domain parses a hostname and returns the upper domain (e.g. sub1.sub2.domain.com -> sub2.domain.com)
func GetCookieDomain(u string) (string, error) {
parsed, err := url.Parse(u)
if err != nil {
return "", err
}
host := appUrl.Hostname()
host := parsed.Hostname()
if netIP := net.ParseIP(host); netIP != nil {
return "", errors.New("IP addresses are not allowed")
return "", errors.New("IP addresses not allowed")
}
urlParts := strings.Split(host, ".")
parts := strings.Split(host, ".")
if len(urlParts) < 3 {
return "", errors.New("invalid domain, must be at least second level domain")
if len(parts) < 3 {
return "", errors.New("invalid app url, must be at least second level domain")
}
return strings.Join(urlParts[1:], "."), nil
domain := strings.Join(parts[1:], ".")
_, err = publicsuffix.DomainFromListWithOptions(publicsuffix.DefaultList, domain, nil)
if err != nil {
return "", errors.New("domain in public suffix list, cannot set cookies")
}
return domain, nil
}
func ParseFileToLine(content string) string {
@@ -89,13 +97,13 @@ func IsRedirectSafe(redirectURL string, domain string) bool {
return false
}
upper, err := GetRootDomain(redirectURL)
cookieDomain, err := GetCookieDomain(redirectURL)
if err != nil {
return false
}
if upper != domain {
if cookieDomain != domain {
return false
}

View File

@@ -11,53 +11,58 @@ import (
func TestGetRootDomain(t *testing.T) {
// Normal case
domain := "http://sub.example.com"
expected := "example.com"
result, err := utils.GetRootDomain(domain)
domain := "http://sub.tinyauth.app"
expected := "tinyauth.app"
result, err := utils.GetCookieDomain(domain)
assert.NilError(t, err)
assert.Equal(t, expected, result)
// Domain with multiple subdomains
domain = "http://b.c.example.com"
expected = "c.example.com"
result, err = utils.GetRootDomain(domain)
domain = "http://b.c.tinyauth.app"
expected = "c.tinyauth.app"
result, err = utils.GetCookieDomain(domain)
assert.NilError(t, err)
assert.Equal(t, expected, result)
// Domain with no subdomain
domain = "http://example.com"
expected = "example.com"
_, err = utils.GetRootDomain(domain)
assert.Error(t, err, "invalid domain, must be at least second level domain")
domain = "http://tinyauth.app"
expected = "tinyauth.app"
_, err = utils.GetCookieDomain(domain)
assert.Error(t, err, "invalid app url, must be at least second level domain")
// Invalid domain (only TLD)
domain = "com"
_, err = utils.GetRootDomain(domain)
assert.ErrorContains(t, err, "invalid domain")
_, err = utils.GetCookieDomain(domain)
assert.ErrorContains(t, err, "invalid app url, must be at least second level domain")
// IP address
domain = "http://10.10.10.10"
_, err = utils.GetRootDomain(domain)
assert.ErrorContains(t, err, "IP addresses are not allowed")
_, err = utils.GetCookieDomain(domain)
assert.ErrorContains(t, err, "IP addresses not allowed")
// Invalid URL
domain = "http://[::1]:namedport"
_, err = utils.GetRootDomain(domain)
_, err = utils.GetCookieDomain(domain)
assert.ErrorContains(t, err, "parse \"http://[::1]:namedport\": invalid port \":namedport\" after host")
// URL with scheme and path
domain = "https://sub.example.com/path"
expected = "example.com"
result, err = utils.GetRootDomain(domain)
domain = "https://sub.tinyauth.app/path"
expected = "tinyauth.app"
result, err = utils.GetCookieDomain(domain)
assert.NilError(t, err)
assert.Equal(t, expected, result)
// URL with port
domain = "http://sub.example.com:8080"
expected = "example.com"
result, err = utils.GetRootDomain(domain)
domain = "http://sub.tinyauth.app:8080"
expected = "tinyauth.app"
result, err = utils.GetCookieDomain(domain)
assert.NilError(t, err)
assert.Equal(t, expected, result)
// Domain managed by ICANN
domain = "http://example.co.uk"
_, err = utils.GetCookieDomain(domain)
assert.Error(t, err, "domain in public suffix list, cannot set cookies")
}
func TestParseFileToLine(t *testing.T) {