mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-10-27 20:25:41 +00:00
Previously IsRedirectSafe rejected redirects to the exact cookie domain when AppURL had multiple subdomain levels, because it stripped the first label twice.
311 lines
9.0 KiB
Go
311 lines
9.0 KiB
Go
package utils_test
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
"tinyauth/internal/config"
|
|
"tinyauth/internal/utils"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gotest.tools/v3/assert"
|
|
)
|
|
|
|
func TestGetRootDomain(t *testing.T) {
|
|
// Normal case
|
|
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.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://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.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.GetCookieDomain(domain)
|
|
assert.ErrorContains(t, err, "IP addresses not allowed")
|
|
|
|
// Invalid URL
|
|
domain = "http://[::1]:namedport"
|
|
_, 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.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.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) {
|
|
// Normal case
|
|
content := "user1\nuser2\nuser3"
|
|
expected := "user1,user2,user3"
|
|
result := utils.ParseFileToLine(content)
|
|
assert.Equal(t, expected, result)
|
|
|
|
// Case with empty lines and spaces
|
|
content = " user1 \n\n user2 \n user3 \n"
|
|
expected = "user1,user2,user3"
|
|
result = utils.ParseFileToLine(content)
|
|
assert.Equal(t, expected, result)
|
|
|
|
// Case with only empty lines
|
|
content = "\n\n\n"
|
|
expected = ""
|
|
result = utils.ParseFileToLine(content)
|
|
assert.Equal(t, expected, result)
|
|
|
|
// Case with single user
|
|
content = "singleuser"
|
|
expected = "singleuser"
|
|
result = utils.ParseFileToLine(content)
|
|
assert.Equal(t, expected, result)
|
|
|
|
// Case with trailing newline
|
|
content = "user1\nuser2\n"
|
|
expected = "user1,user2"
|
|
result = utils.ParseFileToLine(content)
|
|
assert.Equal(t, expected, result)
|
|
}
|
|
|
|
func TestFilter(t *testing.T) {
|
|
// Normal case
|
|
slice := []int{1, 2, 3, 4, 5}
|
|
testFunc := func(n int) bool { return n%2 == 0 }
|
|
expected := []int{2, 4}
|
|
result := utils.Filter(slice, testFunc)
|
|
assert.DeepEqual(t, expected, result)
|
|
|
|
// Case with no matches
|
|
slice = []int{1, 3, 5}
|
|
testFunc = func(n int) bool { return n%2 == 0 }
|
|
expected = []int{}
|
|
result = utils.Filter(slice, testFunc)
|
|
assert.DeepEqual(t, expected, result)
|
|
|
|
// Case with all matches
|
|
slice = []int{2, 4, 6}
|
|
testFunc = func(n int) bool { return n%2 == 0 }
|
|
expected = []int{2, 4, 6}
|
|
result = utils.Filter(slice, testFunc)
|
|
assert.DeepEqual(t, expected, result)
|
|
|
|
// Case with empty slice
|
|
slice = []int{}
|
|
testFunc = func(n int) bool { return n%2 == 0 }
|
|
expected = []int{}
|
|
result = utils.Filter(slice, testFunc)
|
|
assert.DeepEqual(t, expected, result)
|
|
|
|
// Case with different type (string)
|
|
sliceStr := []string{"apple", "banana", "cherry"}
|
|
testFuncStr := func(s string) bool { return len(s) > 5 }
|
|
expectedStr := []string{"banana", "cherry"}
|
|
resultStr := utils.Filter(sliceStr, testFuncStr)
|
|
assert.DeepEqual(t, expectedStr, resultStr)
|
|
}
|
|
|
|
func TestGetContext(t *testing.T) {
|
|
// Setup
|
|
gin.SetMode(gin.TestMode)
|
|
c, _ := gin.CreateTestContext(nil)
|
|
|
|
// Normal case
|
|
c.Set("context", &config.UserContext{Username: "testuser"})
|
|
result, err := utils.GetContext(c)
|
|
assert.NilError(t, err)
|
|
assert.Equal(t, "testuser", result.Username)
|
|
|
|
// Case with no context
|
|
c.Set("context", nil)
|
|
_, err = utils.GetContext(c)
|
|
assert.Error(t, err, "invalid user context in request")
|
|
|
|
// Case with invalid context type
|
|
c.Set("context", "invalid type")
|
|
_, err = utils.GetContext(c)
|
|
assert.Error(t, err, "invalid user context in request")
|
|
}
|
|
|
|
func TestIsRedirectSafe(t *testing.T) {
|
|
// Setup
|
|
domain := "example.com"
|
|
|
|
// Case with no subdomain
|
|
redirectURL := "http://example.com/welcome"
|
|
result := utils.IsRedirectSafe(redirectURL, domain)
|
|
assert.Equal(t, true, result)
|
|
|
|
// Case with different domain
|
|
redirectURL = "http://malicious.com/phishing"
|
|
result = utils.IsRedirectSafe(redirectURL, domain)
|
|
assert.Equal(t, false, result)
|
|
|
|
// Case with subdomain
|
|
redirectURL = "http://sub.example.com/page"
|
|
result = utils.IsRedirectSafe(redirectURL, domain)
|
|
assert.Equal(t, true, result)
|
|
|
|
// Case with empty redirect URL
|
|
redirectURL = ""
|
|
result = utils.IsRedirectSafe(redirectURL, domain)
|
|
assert.Equal(t, false, result)
|
|
|
|
// Case with invalid URL
|
|
redirectURL = "http://[::1]:namedport"
|
|
result = utils.IsRedirectSafe(redirectURL, domain)
|
|
assert.Equal(t, false, result)
|
|
|
|
// Case with URL having port
|
|
redirectURL = "http://sub.example.com:8080/page"
|
|
result = utils.IsRedirectSafe(redirectURL, domain)
|
|
assert.Equal(t, true, result)
|
|
|
|
// Case with URL having different subdomain
|
|
redirectURL = "http://another.example.com/page"
|
|
result = utils.IsRedirectSafe(redirectURL, domain)
|
|
assert.Equal(t, true, result)
|
|
|
|
// Case with URL having different TLD
|
|
redirectURL = "http://example.org/page"
|
|
result = utils.IsRedirectSafe(redirectURL, domain)
|
|
assert.Equal(t, false, result)
|
|
}
|
|
|
|
func TestIsRedirectSafeMultiLevel(t *testing.T) {
|
|
// Setup
|
|
cookieDomain := "tinyauth.example.com"
|
|
|
|
// Case with 3rd level domain
|
|
redirectURL := "http://tinyauth.example.com/welcome"
|
|
result := utils.IsRedirectSafe(redirectURL, cookieDomain)
|
|
assert.Equal(t, true, result)
|
|
|
|
// Case with root domain
|
|
redirectURL = "http://example.com/unsafe"
|
|
result = utils.IsRedirectSafe(redirectURL, cookieDomain)
|
|
assert.Equal(t, false, result)
|
|
|
|
// Case with 4th level domain
|
|
redirectURL = "http://auth.tinyauth.example.com/post-login"
|
|
result = utils.IsRedirectSafe(redirectURL, cookieDomain)
|
|
assert.Equal(t, true, result)
|
|
|
|
// Case with 5th level domain (should be unsafe)
|
|
redirectURL = "http://x.auth.tinyauth.example.com/deep"
|
|
result = utils.IsRedirectSafe(redirectURL, cookieDomain)
|
|
assert.Equal(t, false, result)
|
|
|
|
// Case with different subdomain
|
|
redirectURL = "http://auth.tinyauth.example.net/attack"
|
|
result = utils.IsRedirectSafe(redirectURL, cookieDomain)
|
|
assert.Equal(t, false, result)
|
|
|
|
// Case with malformed URL
|
|
redirectURL = "http://[::1]:namedport"
|
|
result = utils.IsRedirectSafe(redirectURL, cookieDomain)
|
|
assert.Equal(t, false, result)
|
|
}
|
|
|
|
func TestGetOAuthProvidersConfig(t *testing.T) {
|
|
env := []string{"PROVIDERS_CLIENT1_CLIENT_ID=client1-id", "PROVIDERS_CLIENT1_CLIENT_SECRET=client1-secret"}
|
|
args := []string{"/tinyauth/tinyauth", "--providers-client2-client-id=client2-id", "--providers-client2-client-secret=client2-secret"}
|
|
|
|
expected := map[string]config.OAuthServiceConfig{
|
|
"client1": {
|
|
ClientID: "client1-id",
|
|
ClientSecret: "client1-secret",
|
|
Name: "Client1",
|
|
},
|
|
"client2": {
|
|
ClientID: "client2-id",
|
|
ClientSecret: "client2-secret",
|
|
Name: "Client2",
|
|
},
|
|
}
|
|
|
|
result, err := utils.GetOAuthProvidersConfig(env, args, "")
|
|
assert.NilError(t, err)
|
|
assert.DeepEqual(t, expected, result)
|
|
|
|
// Case with no providers
|
|
env = []string{}
|
|
args = []string{"/tinyauth/tinyauth"}
|
|
expected = map[string]config.OAuthServiceConfig{}
|
|
|
|
result, err = utils.GetOAuthProvidersConfig(env, args, "")
|
|
assert.NilError(t, err)
|
|
assert.DeepEqual(t, expected, result)
|
|
|
|
// Case with secret from file
|
|
file, err := os.Create("/tmp/tinyauth_test_file")
|
|
assert.NilError(t, err)
|
|
|
|
_, err = file.WriteString("file content\n")
|
|
assert.NilError(t, err)
|
|
|
|
err = file.Close()
|
|
assert.NilError(t, err)
|
|
defer os.Remove("/tmp/tinyauth_test_file")
|
|
|
|
env = []string{"PROVIDERS_CLIENT1_CLIENT_ID=client1-id", "PROVIDERS_CLIENT1_CLIENT_SECRET_FILE=/tmp/tinyauth_test_file"}
|
|
args = []string{"/tinyauth/tinyauth"}
|
|
expected = map[string]config.OAuthServiceConfig{
|
|
"client1": {
|
|
ClientID: "client1-id",
|
|
ClientSecret: "file content",
|
|
Name: "Client1",
|
|
},
|
|
}
|
|
|
|
result, err = utils.GetOAuthProvidersConfig(env, args, "")
|
|
assert.NilError(t, err)
|
|
assert.DeepEqual(t, expected, result)
|
|
|
|
// Case with google provider and no redirect URL
|
|
env = []string{"PROVIDERS_GOOGLE_CLIENT_ID=google-id", "PROVIDERS_GOOGLE_CLIENT_SECRET=google-secret"}
|
|
args = []string{"/tinyauth/tinyauth"}
|
|
expected = map[string]config.OAuthServiceConfig{
|
|
"google": {
|
|
ClientID: "google-id",
|
|
ClientSecret: "google-secret",
|
|
RedirectURL: "http://app.url/api/oauth/callback/google",
|
|
Name: "Google",
|
|
},
|
|
}
|
|
|
|
result, err = utils.GetOAuthProvidersConfig(env, args, "http://app.url")
|
|
assert.NilError(t, err)
|
|
assert.DeepEqual(t, expected, result)
|
|
}
|