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`. 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`. 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`. 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) // Create a user (admin only)
@ -354,6 +355,7 @@ func (app *App) APICreateUser() func(c echo.Context) error {
req.ExistingPlayer, req.ExistingPlayer,
nil, // challengeToken nil, // challengeToken
req.FallbackPlayer, req.FallbackPlayer,
req.MaxPlayerCount,
req.SkinModel, req.SkinModel,
skinReader, skinReader,
req.SkinURL, 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. 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 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. 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 // APIUpdateUser godoc
@ -426,7 +429,7 @@ func (app *App) APIUpdateUser() func(c echo.Context) error {
req.IsLocked, req.IsLocked,
req.ResetAPIToken, req.ResetAPIToken,
req.PreferredLanguage, req.PreferredLanguage,
nil, req.MaxPlayerCount,
) )
if err != nil { if err != nil {
return err return err
@ -470,7 +473,7 @@ func (app *App) APIUpdateSelf() func(c echo.Context) error {
req.IsLocked, req.IsLocked,
req.ResetAPIToken, req.ResetAPIToken,
req.PreferredLanguage, req.PreferredLanguage,
nil, req.MaxPlayerCount,
) )
if err != nil { if err != nil {
return err return err

View File

@ -612,7 +612,7 @@ func FrontUpdateUser(app *App) func(c echo.Context) error {
password := nilIfEmpty(c.FormValue("password")) password := nilIfEmpty(c.FormValue("password"))
resetAPIToken := c.FormValue("resetApiToken") == "on" resetAPIToken := c.FormValue("resetApiToken") == "on"
preferredLanguage := nilIfEmpty(c.FormValue("preferredLanguage")) preferredLanguage := nilIfEmpty(c.FormValue("preferredLanguage"))
// maxPlayerCount := c.FormValue("maxPlayerCount") maxPlayerCountString := c.FormValue("maxPlayerCount")
var targetUser *User var targetUser *User
if targetUUID == nil || *targetUUID == user.UUID { 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( _, err := app.UpdateUser(
app.DB, app.DB,
user, // caller user, // caller
@ -638,7 +649,7 @@ func FrontUpdateUser(app *App) func(c echo.Context) error {
nil, // isLocked nil, // isLocked
resetAPIToken, resetAPIToken,
preferredLanguage, preferredLanguage,
nil, &maxPlayerCount,
) )
if err != nil { if err != nil {
var userError *UserError var userError *UserError
@ -926,6 +937,7 @@ func FrontRegister(app *App) func(c echo.Context) error {
existingPlayer, existingPlayer,
challengeToken, challengeToken,
nil, // fallbackPlayer nil, // fallbackPlayer
nil, // maxPlayerCount
nil, // skinModel nil, // skinModel
nil, // skinReader nil, // skinReader
nil, // skinURL nil, // skinURL

View File

@ -924,6 +924,7 @@ func (ts *TestSuite) testUserUpdate(t *testing.T) {
body := &bytes.Buffer{} body := &bytes.Buffer{}
writer := multipart.NewWriter(body) writer := multipart.NewWriter(body)
assert.Nil(t, writer.WriteField("uuid", takenUser.UUID)) 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("preferredLanguage", "es"))
assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/user")) assert.Nil(t, writer.WriteField("returnUrl", ts.App.FrontEndURL+"/web/user"))
assert.Nil(t, writer.Close()) 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) 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) 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 // Invalid preferred language should fail
body := &bytes.Buffer{} body := &bytes.Buffer{}

View File

@ -677,6 +677,11 @@
"type": "boolean", "type": "boolean",
"example": false "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": { "password": {
"description": "Plaintext password", "description": "Plaintext password",
"type": "string", "type": "string",
@ -848,6 +853,11 @@
"type": "boolean", "type": "boolean",
"example": false "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": { "password": {
"description": "Optional. New plaintext password", "description": "Optional. New plaintext password",
"type": "string", "type": "string",

18
user.go
View File

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

View File

@ -153,6 +153,19 @@
method="post" method="post"
enctype="multipart/form-data" 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> <p>
<label for="password">Password</label><br /> <label for="password">Password</label><br />
<input <input