Update config examples and test them

This commit is contained in:
Evan Goode 2025-03-28 20:53:45 -04:00
parent 65328999a5
commit d7ffab2612
10 changed files with 104 additions and 53 deletions

2
api.go
View File

@ -72,7 +72,7 @@ func (app *App) HandleAPIError(err error, c *echo.Context) error {
}
if log {
app.LogError(err, c)
LogError(err, c)
}
return (*c).JSON(code, APIError{Message: message})

View File

@ -294,7 +294,7 @@ func (app *App) HandleYggdrasilError(err error, c *echo.Context) error {
return (*c).JSON(httpError.Code, YggdrasilErrorResponse{Path: &path_})
}
}
app.LogError(err, c)
LogError(err, c)
return (*c).JSON(http.StatusInternalServerError, YggdrasilErrorResponse{Path: &path_, ErrorMessage: Ptr("internal server error")})
}

View File

@ -11,7 +11,6 @@ import (
"github.com/dgraph-io/ristretto"
"github.com/samber/mo"
"golang.org/x/net/idna"
"log"
"net/url"
"os"
"path"
@ -131,7 +130,6 @@ type Config struct {
SkinSizeLimit int
OfflineSkins bool
StateDirectory string
TestMode bool
TokenExpireSec int
TokenStaleSec int
TransientUsers transientUsersConfig
@ -199,7 +197,6 @@ func DefaultConfig() Config {
SignPublicKeys: true,
SkinSizeLimit: 128,
StateDirectory: GetDefaultStateDirectory(),
TestMode: false,
TokenExpireSec: 0,
TokenStaleSec: 0,
TransientUsers: transientUsersConfig{
@ -279,9 +276,6 @@ func CleanConfig(config *Config) error {
if config.ListenAddress == "" {
return errors.New("ListenAddress must be set. Example: 0.0.0.0:25585")
}
if _, err := os.Open(config.DataDirectory); err != nil {
return fmt.Errorf("Couldn't open DataDirectory: %s", err)
}
if config.DefaultMaxPlayerCount < 0 && config.DefaultMaxPlayerCount != Constants.MaxPlayerCountUnlimited {
return fmt.Errorf("DefaultMaxPlayerCount must be >= 0, or %d to indicate unlimited players", Constants.MaxPlayerCountUnlimited)
}
@ -421,54 +415,73 @@ Allow = true
RequireInvite = true
`
func HandleDeprecations(config Config, metadata *toml.MetaData) {
func HandleDeprecations(config Config, metadata *toml.MetaData) [][]string {
deprecatedPaths := make([][]string, 0, 0)
warningTemplate := "Warning: config option %s is deprecated and will be removed in a future version. Use %s instead."
if metadata.IsDefined("RegistrationNewPlayer", "AllowChoosingUUID") {
log.Printf(warningTemplate, "RegistrationNewPlayer.AllowChoosingUUID", "CreateNewPlayer.AllowChoosingUUID")
path_ := []string{"RegistrationNewPlayer", "AllowChoosingUUID"}
if metadata.IsDefined(path_...) {
LogInfo(warningTemplate, strings.Join(path_, "."), "CreateNewPlayer.AllowChoosingUUID")
deprecatedPaths = append(deprecatedPaths, path_)
if !metadata.IsDefined("CreateNewPlayer", "AllowChoosingUUID") {
config.CreateNewPlayer.AllowChoosingUUID = config.RegistrationNewPlayer.AllowChoosingUUID
}
}
if metadata.IsDefined("RegistrationExistingPlayer", "Nickname") {
log.Printf(warningTemplate, "RegistrationExistingPlayer.Nickname", "ImportExistingPlayer.Nickname")
path_ = []string{"RegistrationExistingPlayer", "Nickname"}
if metadata.IsDefined(path_...) {
LogInfo(warningTemplate, strings.Join(path_, "."), "ImportExistingPlayer.Nickname")
deprecatedPaths = append(deprecatedPaths, path_)
if !metadata.IsDefined("ImportExistingPlayer", "Nickname") {
config.ImportExistingPlayer.Nickname = config.RegistrationExistingPlayer.Nickname
}
}
if metadata.IsDefined("RegistrationExistingPlayer", "SessionURL") {
log.Printf(warningTemplate, "RegistrationExistingPlayer.SessionURL", "ImportExistingPlayer.SessionURL")
path_ = []string{"RegistrationExistingPlayer", "SessionURL"}
if metadata.IsDefined(path_...) {
LogInfo(warningTemplate, strings.Join(path_, "."), "ImportExistingPlayer.SessionURL")
deprecatedPaths = append(deprecatedPaths, path_)
if !metadata.IsDefined("ImportExistingPlayer", "SessionURL") {
config.ImportExistingPlayer.SessionURL = config.RegistrationExistingPlayer.SessionURL
}
}
if metadata.IsDefined("RegistrationExistingPlayer", "AccountURL") {
log.Printf(warningTemplate, "RegistrationExistingPlayer.AccountURL", "ImportExistingPlayer.AccountURL")
path_ = []string{"RegistrationExistingPlayer", "AccountURL"}
if metadata.IsDefined(path_...) {
LogInfo(warningTemplate, strings.Join(path_, "."), "ImportExistingPlayer.AccountURL")
deprecatedPaths = append(deprecatedPaths, path_)
if !metadata.IsDefined("ImportExistingPlayer", "AccountURL") {
config.ImportExistingPlayer.AccountURL = config.RegistrationExistingPlayer.AccountURL
}
}
if metadata.IsDefined("RegistrationExistingPlayer", "SetSkinURL") {
log.Printf(warningTemplate, "RegistrationExistingPlayer.SetSkinURL", "ImportExistingPlayer.SetSkinURL")
path_ = []string{"RegistrationExistingPlayer", "SetSkinURL"}
if metadata.IsDefined(path_...) {
LogInfo(warningTemplate, strings.Join(path_, "."), "ImportExistingPlayer.SetSkinURL")
deprecatedPaths = append(deprecatedPaths, path_)
if !metadata.IsDefined("ImportExistingPlayer", "SetSkinURL") {
config.ImportExistingPlayer.SetSkinURL = config.RegistrationExistingPlayer.SetSkinURL
}
}
if metadata.IsDefined("RegistrationExistingPlayer", "RequireSkinVerification") {
log.Printf(warningTemplate, "RegistrationExistingPlayer.RequireSkinVerification", "ImportExistingPlayer.RequireSkinVerification")
path_ = []string{"RegistrationExistingPlayer", "RequireSkinVerification"}
if metadata.IsDefined(path_...) {
LogInfo(warningTemplate, strings.Join(path_, "."), "ImportExistingPlayer.RequireSkinVerification")
deprecatedPaths = append(deprecatedPaths, path_)
if !metadata.IsDefined("ImportExistingPlayer", "RequireSkinVerification") {
config.ImportExistingPlayer.RequireSkinVerification = config.RegistrationExistingPlayer.RequireSkinVerification
}
}
return deprecatedPaths
}
func ReadOrCreateConfig(path string) *Config {
func ReadConfig(path string, createIfNotExists bool) (Config, [][]string, error) {
config := DefaultConfig()
_, err := os.Stat(path)
if err != nil {
// File doesn't exist? Try to create it
if !createIfNotExists {
return Config{}, nil, err
}
log.Println("Config file at", path, "doesn't exist, creating it with template values.")
LogInfo("Config file at", path, "doesn't exist, creating it with template values.")
dir := filepath.Dir(path)
err := os.MkdirAll(dir, 0755)
Check(err)
@ -480,21 +493,21 @@ func ReadOrCreateConfig(path string) *Config {
Check(err)
}
log.Println("Loading config from", path)
LogInfo("Loading config from", path)
metadata, err := toml.DecodeFile(path, &config)
Check(err)
for _, key := range metadata.Undecoded() {
log.Println("Warning: unknown config option", strings.Join(key, "."))
LogInfo("Warning: unknown config option", strings.Join(key, "."))
}
HandleDeprecations(config, &metadata)
deprecations := HandleDeprecations(config, &metadata)
err = CleanConfig(&config)
if err != nil {
log.Fatal(fmt.Errorf("Error in config: %s", err))
return Config{}, nil, err
}
return &config
return config, deprecations, nil
}
func ReadOrCreateKey(config *Config) *rsa.PrivateKey {

View File

@ -61,10 +61,6 @@ func TestConfig(t *testing.T) {
config.DefaultMaxPlayerCount = Constants.MaxPlayerCountUnlimited
assert.Nil(t, CleanConfig(config))
config = configTestConfig(sd)
config.DataDirectory = "/tmp/DraslInvalidDataDirectoryNothingHere"
assert.NotNil(t, CleanConfig(config))
// Missing state directory should be ignored
config = configTestConfig(sd)
config.StateDirectory = "/tmp/DraslInvalidStateDirectoryNothingHere"
@ -154,4 +150,21 @@ func TestConfig(t *testing.T) {
var templateConfig Config
_, err := toml.Decode(TEMPLATE_CONFIG_FILE, &templateConfig)
assert.Nil(t, err)
// Test that the example configs are valid
_, deprecations, err := ReadConfig("example/config-example.toml", false)
assert.Empty(t, deprecations)
assert.Nil(t, err)
// The example configs should all be the same
correctBytes, err := os.ReadFile("example/config-example.toml")
assert.Nil(t, err)
configBytes, err := os.ReadFile("example/docker/config/config.toml")
assert.Nil(t, err)
assert.Equal(t, correctBytes, configBytes)
configBytes, err = os.ReadFile("example/docker-caddy/config/config.toml")
assert.Nil(t, err)
assert.Equal(t, correctBytes, configBytes)
}

View File

@ -1,15 +1,17 @@
# Drasl default config file
# Example: drasl.example.com
Domain = ""
Domain = "CHANGEME"
# Example: https://drasl.example.com
BaseURL = ""
BaseURL = "https://CHANGEME"
# List of usernames who automatically become admins of the Drasl instance
DefaultAdmins = [""]
[CreateNewPlayer]
AllowChoosingUUID = true
[RegistrationNewPlayer]
Allow = true
AllowChoosingUUID = true
RequireInvite = true

View File

@ -1,15 +1,17 @@
# Drasl default config file
# Example: drasl.example.com
Domain = ""
Domain = "CHANGEME"
# Example: https://drasl.example.com
BaseURL = ""
BaseURL = "https://CHANGEME"
# List of usernames who automatically become admins of the Drasl instance
DefaultAdmins = [""]
[CreateNewPlayer]
AllowChoosingUUID = true
[RegistrationNewPlayer]
Allow = true
AllowChoosingUUID = true
RequireInvite = true

View File

@ -1,15 +1,17 @@
# Drasl default config file
# Example: drasl.example.com
Domain = ""
Domain = "CHANGEME"
# Example: https://drasl.example.com
BaseURL = ""
BaseURL = "https://CHANGEME"
# List of usernames who automatically become admins of the Drasl instance
DefaultAdmins = [""]
[CreateNewPlayer]
AllowChoosingUUID = true
[RegistrationNewPlayer]
Allow = true
AllowChoosingUUID = true
RequireInvite = true

View File

@ -171,7 +171,7 @@ func (app *App) HandleWebError(err error, c *echo.Context) error {
}
}
app.LogError(err, c)
LogError(err, c)
safeMethods := []string{
"GET",

36
main.go
View File

@ -30,7 +30,13 @@ import (
"time"
)
var DEBUG = os.Getenv("DRASL_DEBUG") != ""
func DRASL_DEBUG() bool {
return os.Getenv("DRASL_DEBUG") != ""
}
func DRASL_TEST() bool {
return os.Getenv("DRASL_TEST") != ""
}
var bodyDump = middleware.BodyDump(func(c echo.Context, reqBody, resBody []byte) {
fmt.Printf("%s\n", reqBody)
@ -65,8 +71,14 @@ type App struct {
OIDCProvidersByIssuer map[string]*OIDCProvider
}
func (app *App) LogError(err error, c *echo.Context) {
if err != nil && !app.Config.TestMode {
func LogInfo(args ...interface{}) {
if !DRASL_TEST() {
log.Println(args...)
}
}
func LogError(err error, c *echo.Context) {
if err != nil && !DRASL_TEST() {
log.Println("Unexpected error in "+(*c).Request().Method+" "+(*c).Path()+":", err)
}
}
@ -83,7 +95,7 @@ func (app *App) HandleError(err error, c echo.Context) {
additionalErr = app.HandleYggdrasilError(err, &c)
}
if additionalErr != nil {
app.LogError(fmt.Errorf("Additional error while handling an error: %w", additionalErr), &c)
LogError(fmt.Errorf("Additional error while handling an error: %w", additionalErr), &c)
}
}
@ -134,7 +146,7 @@ func makeRateLimiter(app *App) echo.MiddlewareFunc {
func (app *App) MakeServer() *echo.Echo {
e := echo.New()
e.HideBanner = true
e.HidePort = app.Config.TestMode
e.HidePort = DRASL_TEST()
e.HTTPErrorHandler = app.HandleError
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
@ -146,7 +158,7 @@ func (app *App) MakeServer() *echo.Echo {
if app.Config.LogRequests {
e.Use(middleware.Logger())
}
if DEBUG {
if DRASL_DEBUG() {
e.Use(bodyDump)
}
if app.Config.RateLimit.Enable {
@ -400,6 +412,9 @@ func setup(config *Config) *App {
log.Fatalf("Couldn't access StateDirectory %s: %s", config.StateDirectory, err)
}
}
if _, err := os.Open(config.DataDirectory); err != nil {
log.Fatalf("Couldn't access DataDirectory: %s", err)
}
// Crypto
key := ReadOrCreateKey(config)
@ -550,7 +565,7 @@ func setup(config *Config) *App {
Check(err)
// Print an initial invite link if necessary
if !app.Config.TestMode {
if !DRASL_TEST() {
newPlayerInvite := app.Config.RegistrationNewPlayer.Allow && config.RegistrationNewPlayer.RequireInvite
existingPlayerInvite := app.Config.RegistrationExistingPlayer.Allow && config.RegistrationExistingPlayer.RequireInvite
if newPlayerInvite || existingPlayerInvite {
@ -595,8 +610,11 @@ func main() {
os.Exit(0)
}
config := ReadOrCreateConfig(*configPath)
app := setup(config)
config, _, err := ReadConfig(*configPath, true)
if err != nil {
log.Fatalf("Error in config: %s", err)
}
app := setup(&config)
Check(app.MakeServer().Start(app.Config.ListenAddress))
}

View File

@ -24,6 +24,8 @@ import (
"time"
)
var _ = os.Setenv("DRASL_TEST", "1")
const TEST_USERNAME = "Username"
const TEST_USERNAME_UPPERCASE = "USERNAME"
const TEST_PLAYER_NAME = "Username"
@ -324,7 +326,6 @@ func testConfig() *Config {
config.RateLimit = noRateLimit
config.FallbackAPIServers = []FallbackAPIServer{}
config.LogRequests = false
config.TestMode = true
return &config
}