mirror of
https://github.com/unmojang/drasl.git
synced 2025-08-03 19:06:04 -04:00
Punycode IDNs while reading config file
It's safest to work with punycoded IDNs everywhere. Neither authlib-injector nor Minecraft's `-Dminecraft.api.*.host` arguments play nice with Unicode IDNs. Related: https://github.com/yushijinhun/authlib-injector/issues/270
This commit is contained in:
parent
258abe0df2
commit
6fe35be090
168
config.go
168
config.go
@ -9,6 +9,8 @@ import (
|
||||
"github.com/BurntSushi/toml"
|
||||
mapset "github.com/deckarep/golang-set/v2"
|
||||
"github.com/dgraph-io/ristretto"
|
||||
"github.com/samber/mo"
|
||||
"golang.org/x/net/idna"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
@ -207,21 +209,70 @@ func DefaultConfig() Config {
|
||||
}
|
||||
}
|
||||
|
||||
func cleanURL(key string, required mo.Option[string], urlString string, trimTrailingSlash bool) (string, error) {
|
||||
if urlString == "" {
|
||||
if example, ok := required.Get(); ok {
|
||||
return "", fmt.Errorf("%s must be set. Example: %s", key, example)
|
||||
}
|
||||
return urlString, nil
|
||||
}
|
||||
|
||||
parsedURL, err := url.Parse(urlString)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Invalid %s: %s", key, err)
|
||||
}
|
||||
|
||||
punycodeHost, err := idna.ToASCII(parsedURL.Host)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Invalid %s: %s", key, err)
|
||||
}
|
||||
parsedURL.Host = punycodeHost
|
||||
|
||||
if trimTrailingSlash {
|
||||
parsedURL.Path = strings.TrimSuffix(parsedURL.Path, "/")
|
||||
}
|
||||
return parsedURL.String(), nil
|
||||
}
|
||||
|
||||
func cleanDomain(key string, required mo.Option[string], domain string) (string, error) {
|
||||
if domain == "" {
|
||||
if example, ok := required.Get(); ok {
|
||||
return "", fmt.Errorf("%s must be set. Example: %s", key, example)
|
||||
}
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
punycoded, err := idna.ToASCII(domain)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Invalid %s: %s", key, err)
|
||||
}
|
||||
return punycoded, nil
|
||||
}
|
||||
|
||||
func CleanConfig(config *Config) error {
|
||||
if config.BaseURL == "" {
|
||||
return errors.New("BaseURL must be set. Example: https://drasl.example.com")
|
||||
var err error
|
||||
config.BaseURL, err = cleanURL("BaseURL", mo.Some("https://drasl.example.com"), config.BaseURL, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := url.Parse(config.BaseURL); err != nil {
|
||||
return fmt.Errorf("Invalid BaseURL: %s", err)
|
||||
}
|
||||
config.BaseURL = strings.TrimRight(config.BaseURL, "/")
|
||||
|
||||
if !IsValidPreferredLanguage(config.DefaultPreferredLanguage) {
|
||||
return fmt.Errorf("Invalid DefaultPreferredLanguage %s", config.DefaultPreferredLanguage)
|
||||
}
|
||||
|
||||
if config.Domain == "" {
|
||||
return errors.New("Domain must be set to a valid fully qualified domain name")
|
||||
}
|
||||
|
||||
config.Domain, err = cleanDomain(
|
||||
"Domain",
|
||||
mo.Some("drasl.example.com"),
|
||||
config.Domain,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if config.InstanceName == "" {
|
||||
return errors.New("InstanceName must be set")
|
||||
}
|
||||
@ -257,66 +308,99 @@ func CleanConfig(config *Config) error {
|
||||
if config.ImportExistingPlayer.Nickname == "" {
|
||||
return errors.New("ImportExistingPlayer.Nickname must be set")
|
||||
}
|
||||
if config.ImportExistingPlayer.SessionURL == "" {
|
||||
return errors.New("ImportExistingPlayer.SessionURL must be set. Example: https://sessionserver.mojang.com")
|
||||
}
|
||||
if _, err := url.Parse(config.ImportExistingPlayer.SessionURL); err != nil {
|
||||
return fmt.Errorf("Invalid ImportExistingPlayer.SessionURL: %s", err)
|
||||
}
|
||||
config.ImportExistingPlayer.SessionURL = strings.TrimRight(config.ImportExistingPlayer.SessionURL, "/")
|
||||
|
||||
if config.ImportExistingPlayer.AccountURL == "" {
|
||||
return errors.New("ImportExistingPlayer.AccountURL must be set. Example: https://api.mojang.com")
|
||||
config.ImportExistingPlayer.SessionURL, err = cleanURL(
|
||||
"ImportExistingPlayer.SessionURL",
|
||||
mo.Some("https://sessionserver.mojang.com"),
|
||||
config.ImportExistingPlayer.SessionURL, true,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := url.Parse(config.ImportExistingPlayer.AccountURL); err != nil {
|
||||
return fmt.Errorf("Invalid ImportExistingPlayer.AccountURL: %s", err)
|
||||
|
||||
config.ImportExistingPlayer.AccountURL, err = cleanURL(
|
||||
"ImportExistingPlayer.AccountURL",
|
||||
mo.Some("https://api.mojang.com"),
|
||||
config.ImportExistingPlayer.AccountURL, true,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.ImportExistingPlayer.SetSkinURL, err = cleanURL(
|
||||
"ImportExistingPlayer.SetSkinURL",
|
||||
mo.None[string](),
|
||||
config.ImportExistingPlayer.SetSkinURL, true,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.ImportExistingPlayer.AccountURL = strings.TrimRight(config.ImportExistingPlayer.AccountURL, "/")
|
||||
}
|
||||
|
||||
fallbackAPIServerNames := mapset.NewSet[string]()
|
||||
for _, fallbackAPIServer := range PtrSlice(config.FallbackAPIServers) {
|
||||
if fallbackAPIServer.Nickname == "" {
|
||||
return errors.New("FallbackAPIServer Nickname must be set")
|
||||
}
|
||||
if fallbackAPIServerNames.Contains(fallbackAPIServer.Nickname) {
|
||||
return fmt.Errorf("Duplicate FallbackAPIServer Nickname: %s", fallbackAPIServer.Nickname)
|
||||
}
|
||||
fallbackAPIServerNames.Add(fallbackAPIServer.Nickname)
|
||||
|
||||
if fallbackAPIServer.AccountURL == "" {
|
||||
return errors.New("FallbackAPIServer AccountURL must be set")
|
||||
fallbackAPIServer.SessionURL, err = cleanURL(
|
||||
fmt.Sprintf("FallbackAPIServer %s SessionURL", fallbackAPIServer.Nickname),
|
||||
mo.Some("https://sessionserver.mojang.com"),
|
||||
fallbackAPIServer.SessionURL, true,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := url.Parse(fallbackAPIServer.AccountURL); err != nil {
|
||||
return fmt.Errorf("Invalid FallbackAPIServer AccountURL %s: %s", fallbackAPIServer.AccountURL, err)
|
||||
}
|
||||
fallbackAPIServer.AccountURL = strings.TrimRight(fallbackAPIServer.AccountURL, "/")
|
||||
|
||||
if fallbackAPIServer.SessionURL == "" {
|
||||
return errors.New("FallbackAPIServer SessionURL must be set")
|
||||
fallbackAPIServer.AccountURL, err = cleanURL(
|
||||
fmt.Sprintf("FallbackAPIServer %s AccountURL", fallbackAPIServer.Nickname),
|
||||
mo.Some("https://api.mojang.com"),
|
||||
fallbackAPIServer.AccountURL, true,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := url.Parse(fallbackAPIServer.SessionURL); err != nil {
|
||||
return fmt.Errorf("Invalid FallbackAPIServer SessionURL %s: %s", fallbackAPIServer.ServicesURL, err)
|
||||
}
|
||||
fallbackAPIServer.SessionURL = strings.TrimRight(fallbackAPIServer.SessionURL, "/")
|
||||
|
||||
if fallbackAPIServer.ServicesURL == "" {
|
||||
return errors.New("FallbackAPIServer ServicesURL must be set")
|
||||
fallbackAPIServer.ServicesURL, err = cleanURL(
|
||||
fmt.Sprintf("FallbackAPIServer %s ServicesURL", fallbackAPIServer.Nickname),
|
||||
mo.Some("https://api.minecraftservices.com"),
|
||||
fallbackAPIServer.ServicesURL, true,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := url.Parse(fallbackAPIServer.ServicesURL); err != nil {
|
||||
return fmt.Errorf("Invalid FallbackAPIServer ServicesURL %s: %s", fallbackAPIServer.ServicesURL, err)
|
||||
}
|
||||
fallbackAPIServer.ServicesURL = strings.TrimRight(fallbackAPIServer.ServicesURL, "/")
|
||||
for _, skinDomain := range fallbackAPIServer.SkinDomains {
|
||||
if skinDomain == "" {
|
||||
return fmt.Errorf("SkinDomain can't be blank for FallbackAPIServer \"%s\"", fallbackAPIServer.Nickname)
|
||||
|
||||
for _, skinDomain := range PtrSlice(fallbackAPIServer.SkinDomains) {
|
||||
*skinDomain, err = cleanDomain(
|
||||
fmt.Sprintf("FallbackAPIServer %s SkinDomain", fallbackAPIServer.Nickname),
|
||||
mo.Some("textures.minecraft.net"),
|
||||
*skinDomain,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
oidcNames := mapset.NewSet[string]()
|
||||
for _, oidcConfig := range PtrSlice(config.RegistrationOIDC) {
|
||||
if oidcConfig.Name == "" {
|
||||
return errors.New("RegistrationOIDC Name must be set")
|
||||
}
|
||||
if oidcNames.Contains(oidcConfig.Name) {
|
||||
return fmt.Errorf("Duplicate RegistrationOIDC Name: %s", oidcConfig.Name)
|
||||
}
|
||||
if _, err := url.Parse(oidcConfig.Issuer); err != nil {
|
||||
return fmt.Errorf("Invalid RegistrationOIDC URL %s: %s", oidcConfig.Issuer, err)
|
||||
}
|
||||
oidcNames.Add(oidcConfig.Name)
|
||||
oidcConfig.Issuer, err = cleanURL(
|
||||
fmt.Sprintf("RegistrationOIDC %s Issuer", oidcConfig.Name),
|
||||
mo.Some("https://idm.example.com/oauth2/openid/drasl"),
|
||||
oidcConfig.Issuer,
|
||||
false,
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -23,9 +23,11 @@ func TestConfig(t *testing.T) {
|
||||
assert.Nil(t, CleanConfig(config))
|
||||
|
||||
config = configTestConfig(sd)
|
||||
config.BaseURL = "https://drasl.example.com/"
|
||||
config.BaseURL = "https://δρασλ.example.com/"
|
||||
config.Domain = "δρασλ.example.com"
|
||||
assert.Nil(t, CleanConfig(config))
|
||||
assert.Equal(t, "https://drasl.example.com", config.BaseURL)
|
||||
assert.Equal(t, "https://xn--mxafwwl.example.com", config.BaseURL)
|
||||
assert.Equal(t, "xn--mxafwwl.example.com", config.Domain)
|
||||
|
||||
config = configTestConfig(sd)
|
||||
config.BaseURL = ""
|
||||
@ -72,10 +74,10 @@ func TestConfig(t *testing.T) {
|
||||
config.RegistrationExistingPlayer.Allow = true
|
||||
config.ImportExistingPlayer.Allow = true
|
||||
config.ImportExistingPlayer.Nickname = "Example"
|
||||
config.ImportExistingPlayer.SessionURL = "https://drasl.example.com/"
|
||||
config.ImportExistingPlayer.SessionURL = "https://δρασλ.example.com/"
|
||||
config.ImportExistingPlayer.AccountURL = "https://drasl.example.com/"
|
||||
assert.Nil(t, CleanConfig(config))
|
||||
assert.Equal(t, "https://drasl.example.com", config.ImportExistingPlayer.SessionURL)
|
||||
assert.Equal(t, "https://xn--mxafwwl.example.com", config.ImportExistingPlayer.SessionURL)
|
||||
assert.Equal(t, "https://drasl.example.com", config.ImportExistingPlayer.AccountURL)
|
||||
|
||||
config = configTestConfig(sd)
|
||||
@ -96,16 +98,22 @@ func TestConfig(t *testing.T) {
|
||||
config = configTestConfig(sd)
|
||||
testFallbackAPIServer := FallbackAPIServer{
|
||||
Nickname: "Nickname",
|
||||
SessionURL: "https://drasl.example.com/",
|
||||
AccountURL: "https://drasl.example.com/",
|
||||
ServicesURL: "https://drasl.example.com/",
|
||||
SessionURL: "https://δρασλ.example.com/",
|
||||
AccountURL: "https://δρασλ.example.com/",
|
||||
ServicesURL: "https://δρασλ.example.com/",
|
||||
SkinDomains: []string{"δρασλ.example.com"},
|
||||
}
|
||||
fb := testFallbackAPIServer
|
||||
config.FallbackAPIServers = []FallbackAPIServer{fb}
|
||||
assert.Nil(t, CleanConfig(config))
|
||||
assert.Equal(t, "https://drasl.example.com", config.FallbackAPIServers[0].SessionURL)
|
||||
assert.Equal(t, "https://drasl.example.com", config.FallbackAPIServers[0].AccountURL)
|
||||
assert.Equal(t, "https://drasl.example.com", config.FallbackAPIServers[0].ServicesURL)
|
||||
|
||||
assert.Equal(t, []FallbackAPIServer{{
|
||||
Nickname: fb.Nickname,
|
||||
SessionURL: "https://xn--mxafwwl.example.com",
|
||||
AccountURL: "https://xn--mxafwwl.example.com",
|
||||
ServicesURL: "https://xn--mxafwwl.example.com",
|
||||
SkinDomains: []string{"xn--mxafwwl.example.com"},
|
||||
}}, config.FallbackAPIServers)
|
||||
|
||||
fb = testFallbackAPIServer
|
||||
fb.Nickname = ""
|
||||
|
Loading…
x
Reference in New Issue
Block a user