Allow setting max player count on edit user page

This commit is contained in:
Evan Goode 2024-11-29 15:18:03 -05:00
parent 1da3ac2601
commit d6d29b2274
6 changed files with 71 additions and 5 deletions

7
api.go
View File

@ -305,6 +305,7 @@ type APICreateUserRequest struct {
SkinURL *string `json:"skinUrl" example:"https://example.com/skin.png"` // Optional. URL to skin file. Do not specify both `skinBase64` and `skinUrl`.
CapeBase64 *string `json:"capeBase64" example:"iVBORw0KGgoAAAANSUhEUgAAAEAAAAAgCAYAAACinX6EAAABcGlDQ1BpY2MAACiRdZG9S8NAGMaf"` // Optional. Base64-encoded cape PNG. Example value truncated for brevity. Do not specify both `capeBase64` and `capeUrl`.
CapeURL *string `json:"capeUrl" example:"https://example.com/cape.png"` // Optional. URL to cape file. Do not specify both `capeBase64` and `capeUrl`.
MaxPlayerCount *int `json:"maxPlayerCount" example:"3"` // Optional. Maximum number of players a user is allowed to create. -1 means unlimited players. -2 means use the default configured value.
}
// Create a user (admin only)
@ -354,6 +355,7 @@ func (app *App) APICreateUser() func(c echo.Context) error {
req.ExistingPlayer,
nil, // challengeToken
req.FallbackPlayer,
req.MaxPlayerCount,
req.SkinModel,
skinReader,
req.SkinURL,
@ -379,6 +381,7 @@ type APIUpdateUserRequest struct {
IsLocked *bool `json:"isLocked" example:"false"` // Optional. Pass `true` to lock (disable), `false` to unlock user.
ResetAPIToken bool `json:"resetApiToken" example:"true"` // Pass `true` to reset the user's API token
PreferredLanguage *string `json:"preferredLanguage" example:"en"` // Optional. One of the two-letter codes in https://www.oracle.com/java/technologies/javase/jdk8-jre8-suported-locales.html. Used by Minecraft.
MaxPlayerCount *int `json:"maxPlayerCount" example:"3"` // Optional. Maximum number of players a user is allowed to create. -1 means unlimited players. -2 means use the default configured value.
}
// APIUpdateUser godoc
@ -426,7 +429,7 @@ func (app *App) APIUpdateUser() func(c echo.Context) error {
req.IsLocked,
req.ResetAPIToken,
req.PreferredLanguage,
nil,
req.MaxPlayerCount,
)
if err != nil {
return err
@ -470,7 +473,7 @@ func (app *App) APIUpdateSelf() func(c echo.Context) error {
req.IsLocked,
req.ResetAPIToken,
req.PreferredLanguage,
nil,
req.MaxPlayerCount,
)
if err != nil {
return err

View File

@ -612,7 +612,7 @@ func FrontUpdateUser(app *App) func(c echo.Context) error {
password := nilIfEmpty(c.FormValue("password"))
resetAPIToken := c.FormValue("resetApiToken") == "on"
preferredLanguage := nilIfEmpty(c.FormValue("preferredLanguage"))
// maxPlayerCount := c.FormValue("maxPlayerCount")
maxPlayerCountString := c.FormValue("maxPlayerCount")
var targetUser *User
if targetUUID == nil || *targetUUID == user.UUID {
@ -629,6 +629,17 @@ func FrontUpdateUser(app *App) func(c echo.Context) error {
}
}
maxPlayerCount := targetUser.MaxPlayerCount
if maxPlayerCountString == "" {
maxPlayerCount = app.Constants.MaxPlayerCountUseDefault
} else {
var err error
maxPlayerCount, err = strconv.Atoi(maxPlayerCountString)
if err != nil {
return NewWebError(returnURL, "Max player count must be an integer.")
}
}
_, err := app.UpdateUser(
app.DB,
user, // caller
@ -638,7 +649,7 @@ func FrontUpdateUser(app *App) func(c echo.Context) error {
nil, // isLocked
resetAPIToken,
preferredLanguage,
nil,
&maxPlayerCount,
)
if err != nil {
var userError *UserError
@ -926,6 +937,7 @@ func FrontRegister(app *App) func(c echo.Context) error {
existingPlayer,
challengeToken,
nil, // fallbackPlayer
nil, // maxPlayerCount
nil, // skinModel
nil, // skinReader
nil, // skinURL

View File

@ -924,6 +924,7 @@ func (ts *TestSuite) testUserUpdate(t *testing.T) {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
assert.Nil(t, writer.WriteField("uuid", takenUser.UUID))
assert.Nil(t, writer.WriteField("maxPlayerCount", "3"))
assert.Nil(t, writer.WriteField("preferredLanguage", "es"))
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/user"))
assert.Nil(t, writer.Close())
@ -941,6 +942,17 @@ func (ts *TestSuite) testUserUpdate(t *testing.T) {
rec := ts.PostMultipart(t, ts.Server, "/web/update-user", body, writer, []http.Cookie{*takenBrowserTokenCookie}, nil)
ts.updateUserShouldFail(t, rec, "You are not an admin.", ts.App.FrontEndURL)
}
{
// Non-admin should not be able to increase their max player count
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
assert.Nil(t, writer.WriteField("uuid", takenUser.UUID))
assert.Nil(t, writer.WriteField("maxPlayerCount", "-1"))
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/user"))
assert.Nil(t, writer.Close())
rec := ts.PostMultipart(t, ts.Server, "/web/update-user", body, writer, []http.Cookie{*takenBrowserTokenCookie}, nil)
ts.updateUserShouldFail(t, rec, "Cannot set a max player count without admin privileges.", ts.App.FrontEndURL+"/web/user")
}
{
// Invalid preferred language should fail
body := &bytes.Buffer{}

View File

@ -677,6 +677,11 @@
"type": "boolean",
"example": false
},
"maxPlayerCount": {
"description": "Optional. Maximum number of players a user is allowed to create. -1 means unlimited players. -2 means use the default configured value.",
"type": "integer",
"example": 3
},
"password": {
"description": "Plaintext password",
"type": "string",
@ -848,6 +853,11 @@
"type": "boolean",
"example": false
},
"maxPlayerCount": {
"description": "Optional. Maximum number of players a user is allowed to create. -1 means unlimited players. -2 means use the default configured value.",
"type": "integer",
"example": 3
},
"password": {
"description": "Optional. New plaintext password",
"type": "string",

18
user.go
View File

@ -31,6 +31,7 @@ func (app *App) CreateUser(
existingPlayer bool,
challengeToken *string,
fallbackPlayer *string,
maxPlayerCount *int,
skinModel *string,
skinReader *io.Reader,
skinURL *string,
@ -159,6 +160,18 @@ func (app *App) CreateUser(
return User{}, NewBadRequestUserError("Cannot make a new locked user without admin privileges.")
}
maxPlayerCountInt := Constants.MaxPlayerCountUseDefault
if maxPlayerCount != nil {
if !callerIsAdmin {
return User{}, NewBadRequestUserError("Cannot set a max player count without admin privileges.")
}
err := app.ValidateMaxPlayerCount(*maxPlayerCount)
if err != nil {
return User{}, NewBadRequestUserError("Invalid max player count: %s", err)
}
maxPlayerCountInt = *maxPlayerCount
}
apiToken, err := MakeAPIToken()
if err != nil {
return User{}, err
@ -172,8 +185,8 @@ func (app *App) CreateUser(
PasswordSalt: passwordSalt,
PasswordHash: passwordHash,
PreferredLanguage: app.Config.DefaultPreferredLanguage,
MaxPlayerCount: maxPlayerCountInt,
APIToken: apiToken,
MaxPlayerCount: Constants.MaxPlayerCountUseDefault,
}
// Player
@ -334,6 +347,9 @@ func (app *App) UpdateUser(
}
if maxPlayerCount != nil {
if !callerIsAdmin {
return User{}, NewBadRequestUserError("Cannot set a max player count without admin privileges.")
}
err := app.ValidateMaxPlayerCount(*maxPlayerCount)
if err != nil {
return User{}, NewBadRequestUserError("Invalid max player count: %s", err)

View File

@ -153,6 +153,19 @@
method="post"
enctype="multipart/form-data"
>
{{ if .User.IsAdmin }}
<p>
<label for="max-player-count">Max number of players. Specify -1 to allow unlimited players or leave blank to reset to the configured default value.</label><br />
<input
name="maxPlayerCount"
type="number"
{{ if .TargetUser.IsAdmin }}disabled{{ end }}
value="{{ if or .TargetUser.IsAdmin (eq .TargetUser.MaxPlayerCount $.App.Constants.MaxPlayerCountUnlimited) }}-1{{ else if eq .TargetUser.MaxPlayerCount $.App.Constants.MaxPlayerCountUseDefault}}{{ else }}{{ .TargetUser.MaxPlayerCount }}{{ end }}"
placeholder="{{ .App.Config.DefaultMaxPlayerCount }}"
min="-1">
</input>
</p>
{{ end }}
<p>
<label for="password">Password</label><br />
<input