diff --git a/api.go b/api.go index c9bce6e..0acce45 100644 --- a/api.go +++ b/api.go @@ -121,20 +121,20 @@ func (app *App) APIRequestToMaybeUser(c echo.Context) (mo.Option[User], error) { tokenMatch := bearerExp.FindStringSubmatch(authorizationHeader) if tokenMatch == nil || len(tokenMatch) < 2 { - return mo.None[User](), NewUserError(http.StatusUnauthorized, "Malformed Authorization header") + return mo.None[User](), NewUserErrorWithCode(http.StatusUnauthorized, "Malformed Authorization header") } token := tokenMatch[1] var user User if err := app.DB.First(&user, "api_token = ?", token).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return mo.None[User](), NewUserError(http.StatusUnauthorized, "Unknown API token") + return mo.None[User](), NewUserErrorWithCode(http.StatusUnauthorized, "Unknown API token") } return mo.None[User](), err } if user.IsLocked { - return mo.None[User](), NewUserError(http.StatusForbidden, "Account is locked") + return mo.None[User](), NewUserErrorWithCode(http.StatusForbidden, "Account is locked") } return mo.Some(user), nil @@ -147,7 +147,7 @@ func (app *App) withAPIToken(requireLogin bool, f func(c echo.Context, user *Use return err } if maybeUser.IsAbsent() && requireLogin { - return NewUserError(http.StatusUnauthorized, "Route requires authorization. Missing 'Bearer: abcdef' Authorization header") + return NewUserErrorWithCode(http.StatusUnauthorized, "Route requires authorization. Missing 'Bearer: abcdef' Authorization header") } return f(c, maybeUser.ToPointer()) } @@ -345,7 +345,7 @@ func (app *App) APIGetUser() func(c echo.Context) error { uuidParam := c.Param("uuid") if uuidParam != "" { if !caller.IsAdmin && (caller.UUID != uuidParam) { - return NewUserError(http.StatusForbidden, "You are not authorized to access that user.") + return NewUserErrorWithCode(http.StatusForbidden, "You are not authorized to access that user.") } _, err := uuid.Parse(uuidParam) @@ -523,7 +523,7 @@ func (app *App) APIUpdateUser() func(c echo.Context) error { uuidParam := c.Param("uuid") if uuidParam != "" { if !caller.IsAdmin && (caller.UUID != uuidParam) { - return NewUserError(http.StatusForbidden, "You are not authorized to update that user.") + return NewUserErrorWithCode(http.StatusForbidden, "You are not authorized to update that user.") } _, err := uuid.Parse(uuidParam) @@ -586,7 +586,7 @@ func (app *App) APIDeleteUser() func(c echo.Context) error { uuidParam := c.Param("uuid") if uuidParam != "" { if !caller.IsAdmin && (caller.UUID != uuidParam) { - return NewUserError(http.StatusForbidden, "You are not authorized to update that user.") + return NewUserErrorWithCode(http.StatusForbidden, "You are not authorized to update that user.") } _, err := uuid.Parse(uuidParam) @@ -1075,7 +1075,7 @@ func (app *App) APIDeleteInvite() func(c echo.Context) error { return result.Error } if result.RowsAffected == 0 { - return NewUserError(http.StatusNotFound, "Unknown invite code") + return NewUserErrorWithCode(http.StatusNotFound, "Unknown invite code") } return c.NoContent(http.StatusNoContent) diff --git a/common.go b/common.go index d10c081..49a8489 100644 --- a/common.go +++ b/common.go @@ -82,7 +82,7 @@ type UserError struct { Code mo.Option[int] Message string Plural mo.Option[Plural] - Params []interface{} + Params []any } func (e *UserError) Error() string { @@ -93,7 +93,7 @@ func (e *UserError) Error() string { } func (e *UserError) TranslatedError(l *gotext.Locale) string { - translatedParams := make([]interface{}, 0, len(e.Params)) + translatedParams := make([]any, 0, len(e.Params)) for _, param := range e.Params { switch v := param.(type) { case *UserError: @@ -110,7 +110,14 @@ func (e *UserError) TranslatedError(l *gotext.Locale) string { return l.Get(e.Message, translatedParams...) } -func NewUserError(code int, message string, params ...interface{}) error { +func NewUserError(message string, params ...any) error { + return &UserError{ + Message: message, + Params: params, + } +} + +func NewUserErrorWithCode(code int, message string, params ...any) error { return &UserError{ Code: mo.Some(code), Message: message, @@ -118,7 +125,7 @@ func NewUserError(code int, message string, params ...interface{}) error { } } -func NewBadRequestUserError(message string, params ...interface{}) error { +func NewBadRequestUserError(message string, params ...any) error { return &UserError{ Code: mo.Some(http.StatusBadRequest), Message: message, @@ -324,10 +331,10 @@ func (app *App) GetSkinReader(reader io.Reader) (io.Reader, error) { } if app.Config.SkinSizeLimit > 0 && config.Width > app.Config.SkinSizeLimit { - return nil, fmt.Errorf("skin must not be greater than %d pixels wide", app.Config.SkinSizeLimit) + return nil, NewUserError("skin must not be greater than %d pixels wide", app.Config.SkinSizeLimit) } - mustBeMultipleError := fmt.Errorf("skin size must be a multiple of %d pixels wide by %d or %d pixels high", BASE_SKIN_WIDTH, BASE_SKIN_HEIGHT, BASE_SKIN_HEIGHT_LEGACY) + mustBeMultipleError := NewUserError("skin size must be a multiple of %d pixels wide by %d or %d pixels high", BASE_SKIN_WIDTH, BASE_SKIN_HEIGHT, BASE_SKIN_HEIGHT_LEGACY) if config.Width%BASE_SKIN_WIDTH != 0 { return nil, mustBeMultipleError } @@ -351,10 +358,10 @@ func (app *App) GetCapeReader(reader io.Reader) (io.Reader, error) { } if app.Config.SkinSizeLimit > 0 && config.Width > app.Config.SkinSizeLimit { - return nil, fmt.Errorf("cape must not be greater than %d pixels wide", app.Config.SkinSizeLimit) + return nil, NewUserError("cape must not be greater than %d pixels wide", app.Config.SkinSizeLimit) } - mustBeMultipleError := fmt.Errorf("cape size must be a multiple of %d pixels wide by %d pixels high", BASE_CAPE_WIDTH, BASE_CAPE_HEIGHT) + mustBeMultipleError := NewUserError("cape size must be a multiple of %d pixels wide by %d pixels high", BASE_CAPE_WIDTH, BASE_CAPE_HEIGHT) if config.Width%BASE_CAPE_WIDTH != 0 { return nil, mustBeMultipleError } diff --git a/front.go b/front.go index 8f5011a..1fad342 100644 --- a/front.go +++ b/front.go @@ -102,7 +102,7 @@ func (app *App) GetLanguageMiddleware() func(echo.HandlerFunc) echo.HandlerFunc } } -func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error { +func (t *Template) Render(w io.Writer, name string, data any, c echo.Context) error { return t.Templates[name].ExecuteTemplate(w, "base", data) } @@ -155,14 +155,14 @@ func (e *WebError) TranslatedError(l *gotext.Locale) string { return e.Err.TranslatedError(l) } -func NewWebError(returnURL string, message string, args ...interface{}) error { +func NewWebError(returnURL string, message string, args ...any) error { return &WebError{ Err: &UserError{Message: message, Params: args}, ReturnURL: returnURL, } } -func RenderHTML(templateString string, args ...interface{}) (template.HTML, error) { +func RenderHTML(templateString string, args ...any) (template.HTML, error) { // If there are no args, skip parsing and return the "template" as-is if len(args) == 0 { return template.HTML(templateString), nil @@ -185,8 +185,8 @@ func RenderHTML(templateString string, args ...interface{}) (template.HTML, erro type baseContext struct { App *App L *gotext.Locale - T func(string, ...interface{}) string - TN func(string, string, int, ...interface{}) string + T func(string, ...any) string + TN func(string, string, int, ...any) string URL string SuccessMessage string WarningMessage string @@ -540,12 +540,12 @@ func (app *App) getPreferredPlayerName(userInfo *oidc.UserInfo) mo.Option[string func (app *App) getIDTokenCookie(c *echo.Context) (*OIDCProvider, string, oidc.IDTokenClaims, error) { cookie, err := (*c).Cookie(ID_TOKEN_COOKIE_NAME) if err != nil || cookie.Value == "" { - return nil, "", oidc.IDTokenClaims{}, &UserError{Message: "Missing ID token cookie"} + return nil, "", oidc.IDTokenClaims{}, NewUserError("Missing ID token cookie") } idTokenBytes, err := app.DecryptCookieValue(cookie.Value) if err != nil { - return nil, "", oidc.IDTokenClaims{}, &UserError{Message: "Invalid ID token"} + return nil, "", oidc.IDTokenClaims{}, NewUserError("Invalid ID token") } idToken := string(idTokenBytes) diff --git a/main.go b/main.go index 9efc258..8689e7b 100644 --- a/main.go +++ b/main.go @@ -79,7 +79,7 @@ type App struct { LocaleTags []language.Tag } -func LogInfo(args ...interface{}) { +func LogInfo(args ...any) { if !DRASL_TEST() { log.Println(args...) } @@ -146,7 +146,7 @@ func makeRateLimiter(app *App) echo.MiddlewareFunc { // TODO write an IdentifierExtractor per authlib-injector spec "Limits should be placed on users, not client IPs" Store: middleware.NewRateLimiterMemoryStore(requestsPerSecond), DenyHandler: func(c echo.Context, identifier string, err error) error { - return NewUserError(http.StatusTooManyRequests, "Too many requests. Try again later.") + return NewUserErrorWithCode(http.StatusTooManyRequests, "Too many requests. Try again later.") }, }) } @@ -615,7 +615,7 @@ func setup(config *Config) *App { // Post-setup // Make sure all DefaultAdmins are admins - err = app.DB.Table("users").Where("username in (?)", config.DefaultAdmins).Updates(map[string]interface{}{"is_admin": true}).Error + err = app.DB.Table("users").Where("username in (?)", config.DefaultAdmins).Updates(map[string]any{"is_admin": true}).Error Check(err) // Print an initial invite link if necessary diff --git a/model.go b/model.go index f49f7e1..d23c4b2 100644 --- a/model.go +++ b/model.go @@ -72,14 +72,14 @@ func IsValidSkinModel(model string) bool { func UUIDToID(uuid string) (string, error) { if len(uuid) != 36 { - return "", &UserError{Message: "invalid UUID"} + return "", NewUserError("invalid UUID") } return strings.ReplaceAll(uuid, "-", ""), nil } func IDToUUID(id string) (string, error) { if len(id) != 32 { - return "", &UserError{Message: "invalid ID"} + return "", NewUserError("invalid ID") } return id[0:8] + "-" + id[8:12] + "-" + id[12:16] + "-" + id[16:20] + "-" + id[20:], nil } @@ -101,7 +101,7 @@ func ParseUUID(idOrUUID string) (string, error) { } return idOrUUID, nil } - return "", &UserError{Message: "invalid ID or UUID"} + return "", NewUserError("invalid ID or UUID") } type Plural struct { @@ -111,11 +111,11 @@ type Plural struct { func (app *App) ValidatePlayerName(playerName string) error { if app.TransientLoginEligible(playerName) { - return &UserError{Message: "name is reserved for transient login"} + return NewUserError("name is reserved for transient login") } maxLength := Constants.MaxPlayerNameLength if playerName == "" { - return &UserError{Message: "can't be blank"} + return NewUserError("can't be blank") } if len(playerName) > maxLength { return &UserError{ @@ -124,12 +124,12 @@ func (app *App) ValidatePlayerName(playerName string) error { Message: "can't be longer than %d characters", N: maxLength, }), - Params: []interface{}{maxLength}, + Params: []any{maxLength}, } } if !app.ValidPlayerNameRegex.MatchString(playerName) { - return &UserError{Message: "must match the following regular expression: %s", Params: []interface{}{app.Config.ValidPlayerNameRegex}} + return NewUserError("must match the following regular expression: %s", app.Config.ValidPlayerNameRegex) } return nil } @@ -144,7 +144,7 @@ func (app *App) ValidateUsername(username string) error { if emailErr == nil { return nil } - return &UserError{Message: "neither a valid player name (%s) nor an email address", Params: []interface{}{playerNameErr}} + return NewUserError("neither a valid player name (%s) nor an email address", playerNameErr) } func (app *App) ValidatePlayerNameOrUUID(player string) error { @@ -152,7 +152,7 @@ func (app *App) ValidatePlayerNameOrUUID(player string) error { if err != nil { _, uuidErr := uuid.Parse(player) if uuidErr != nil { - return &UserError{Message: "not a valid player name or UUID"} + return NewUserError("not a valid player name or UUID") } return nil } @@ -161,7 +161,7 @@ func (app *App) ValidatePlayerNameOrUUID(player string) error { func (app *App) ValidateMaxPlayerCount(maxPlayerCount int) error { if maxPlayerCount < 0 && maxPlayerCount != app.Constants.MaxPlayerCountUnlimited && maxPlayerCount != app.Constants.MaxPlayerCountUseDefault { - return &UserError{Message: "must be greater than 0, or use -1 to indicate unlimited players, or use -2 to use the system default"} + return NewUserError("must be greater than 0, or use -1 to indicate unlimited players, or use -2 to use the system default") } return nil } @@ -209,7 +209,7 @@ func (app *App) TransientLoginEligible(playerName string) bool { func (app *App) ValidatePassword(password string) error { if password == "" { - return &UserError{Message: "can't be blank"} + return NewUserError("can't be blank") } if len(password) < app.Config.MinPasswordLength { return &UserError{ @@ -218,7 +218,7 @@ func (app *App) ValidatePassword(password string) error { Message: "must be longer than %d characters", N: app.Config.MinPasswordLength, }), - Params: []interface{}{app.Config.MinPasswordLength}, + Params: []any{app.Config.MinPasswordLength}, } } return nil @@ -387,7 +387,7 @@ const ( ) func (app *App) GetClient(accessToken string, stalePolicy StaleTokenPolicy) *Client { - token, err := jwt.ParseWithClaims(accessToken, &TokenClaims{}, func(token *jwt.Token) (interface{}, error) { + token, err := jwt.ParseWithClaims(accessToken, &TokenClaims{}, func(token *jwt.Token) (any, error) { return app.PrivateKey.Public(), nil }) if err != nil { diff --git a/player.go b/player.go index e4d5c85..3d5dadf 100644 --- a/player.go +++ b/player.go @@ -115,7 +115,7 @@ func (app *App) CreatePlayer( Plural: mo.Some(Plural{ Message: "You are only allowed to own %d players", N: maxPlayerCount, }), - Params: []interface{}{maxPlayerCount}, + Params: []any{maxPlayerCount}, } } @@ -403,7 +403,7 @@ func (app *App) ValidateChallenge(playerName string, challengeToken *string) (*P if res.StatusCode != http.StatusOK { log.Printf("Request to registration server at %s resulted in status code %d\n", base.String(), res.StatusCode) - return nil, &UserError{Message: "registration server returned an error"} + return nil, NewUserError("registration server returned an error") } var idRes PlayerNameToIDResponse @@ -429,7 +429,7 @@ func (app *App) ValidateChallenge(playerName string, challengeToken *string) (*P if res.StatusCode != http.StatusOK { log.Printf("Request to registration server at %s resulted in status code %d\n", base.String(), res.StatusCode) - return nil, &UserError{Message: "registration server returned an error"} + return nil, NewUserError("registration server returned an error") } var profileRes SessionProfileResponse @@ -465,7 +465,7 @@ func (app *App) ValidateChallenge(playerName string, challengeToken *string) (*P } if texture.Textures.Skin == nil { - return nil, &UserError{Message: "player does not have a skin"} + return nil, NewUserError("player does not have a skin") } res, err = MakeHTTPClient().Get(texture.Textures.Skin.URL) if err != nil { @@ -479,7 +479,7 @@ func (app *App) ValidateChallenge(playerName string, challengeToken *string) (*P } img, ok := rgba_img.(*image.NRGBA) if !ok { - return nil, &UserError{Message: "invalid image"} + return nil, NewUserError("invalid image") } challenge := make([]byte, 64) @@ -497,19 +497,19 @@ func (app *App) ValidateChallenge(playerName string, challengeToken *string) (*P } if challengeToken == nil { - return nil, &UserError{Message: "missing challenge token"} + return nil, NewUserError("missing challenge token") } correctChallenge := app.GetChallenge(playerName, *challengeToken) if !bytes.Equal(challenge, correctChallenge) { - return nil, &UserError{Message: "skin does not match"} + return nil, NewUserError("skin does not match") } return &details, nil } } - return nil, &UserError{Message: "registration server didn't return textures"} + return nil, NewUserError("registration server didn't return textures") } func MakeChallengeToken() (string, error) { @@ -591,11 +591,11 @@ func (app *App) InvalidateUser(db *gorm.DB, user *User) error { func (app *App) DeletePlayer(caller *User, player *Player) error { if !app.Config.AllowAddingDeletingPlayers && !caller.IsAdmin { - return NewUserError(http.StatusForbidden, "You are not allowed to delete players.") + return NewUserErrorWithCode(http.StatusForbidden, "You are not allowed to delete players.") } if caller.UUID != player.UserUUID && !caller.IsAdmin { - return NewUserError(http.StatusForbidden, "You don't own that player.") + return NewUserErrorWithCode(http.StatusForbidden, "You don't own that player.") } if err := app.DB.Delete(player).Error; err != nil { diff --git a/test_suite_test.go b/test_suite_test.go index ec11f96..493c35e 100644 --- a/test_suite_test.go +++ b/test_suite_test.go @@ -223,7 +223,7 @@ func (ts *TestSuite) Get(t *testing.T, server *echo.Echo, path string, cookies [ return rec } -func (ts *TestSuite) Delete(t *testing.T, server *echo.Echo, path string, payload interface{}, cookies []http.Cookie, accessToken *string) *httptest.ResponseRecorder { +func (ts *TestSuite) Delete(t *testing.T, server *echo.Echo, path string, payload any, cookies []http.Cookie, accessToken *string) *httptest.ResponseRecorder { body, err := json.Marshal(payload) assert.Nil(t, err) req := httptest.NewRequest(http.MethodDelete, path, bytes.NewBuffer(body)) @@ -288,7 +288,7 @@ func (ts *TestSuite) PutMultipart(t *testing.T, server *echo.Echo, path string, return rec } -func (ts *TestSuite) PostJSON(t *testing.T, server *echo.Echo, path string, payload interface{}, cookies []http.Cookie, accessToken *string) *httptest.ResponseRecorder { +func (ts *TestSuite) PostJSON(t *testing.T, server *echo.Echo, path string, payload any, cookies []http.Cookie, accessToken *string) *httptest.ResponseRecorder { body, err := json.Marshal(payload) assert.Nil(t, err) req := httptest.NewRequest(http.MethodPost, path, bytes.NewBuffer(body)) @@ -305,7 +305,7 @@ func (ts *TestSuite) PostJSON(t *testing.T, server *echo.Echo, path string, payl return rec } -func (ts *TestSuite) PatchJSON(t *testing.T, server *echo.Echo, path string, payload interface{}, cookies []http.Cookie, accessToken *string) *httptest.ResponseRecorder { +func (ts *TestSuite) PatchJSON(t *testing.T, server *echo.Echo, path string, payload any, cookies []http.Cookie, accessToken *string) *httptest.ResponseRecorder { body, err := json.Marshal(payload) assert.Nil(t, err) req := httptest.NewRequest(http.MethodPatch, path, bytes.NewBuffer(body)) diff --git a/user.go b/user.go index 0cc52a1..2582a0c 100644 --- a/user.go +++ b/user.go @@ -356,14 +356,14 @@ func (app *App) CreateUser( return user, nil } -var PasswordLoginNotAllowedError error = NewUserError(http.StatusUnauthorized, "Password login is not allowed.") +var PasswordLoginNotAllowedError error = NewUserErrorWithCode(http.StatusUnauthorized, "Password login is not allowed.") func (app *App) AuthenticateUserForMigration(username string, password string) (User, error) { var user User result := app.DB.First(&user, "username = ?", username) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { - return User{}, NewUserError(http.StatusUnauthorized, "User not found.") + return User{}, NewUserErrorWithCode(http.StatusUnauthorized, "User not found.") } return User{}, result.Error } @@ -378,7 +378,7 @@ func (app *App) AuthenticateUserForMigration(username string, password string) ( } if !bytes.Equal(passwordHash, user.PasswordHash) { - return User{}, NewUserError(http.StatusUnauthorized, "Incorrect password.") + return User{}, NewUserErrorWithCode(http.StatusUnauthorized, "Incorrect password.") } return user, nil @@ -389,7 +389,7 @@ func (app *App) AuthenticateUser(username string, password string) (User, error) result := app.DB.First(&user, "username = ?", username) if result.Error != nil { if errors.Is(result.Error, gorm.ErrRecordNotFound) { - return User{}, NewUserError(http.StatusUnauthorized, "User not found.") + return User{}, NewUserErrorWithCode(http.StatusUnauthorized, "User not found.") } return User{}, result.Error } @@ -404,11 +404,11 @@ func (app *App) AuthenticateUser(username string, password string) (User, error) } if !bytes.Equal(passwordHash, user.PasswordHash) { - return User{}, NewUserError(http.StatusUnauthorized, "Incorrect password.") + return User{}, NewUserErrorWithCode(http.StatusUnauthorized, "Incorrect password.") } if user.IsLocked { - return User{}, NewUserError(http.StatusForbidden, "User is locked.") + return User{}, NewUserErrorWithCode(http.StatusForbidden, "User is locked.") } return user, nil @@ -538,7 +538,7 @@ func (app *App) SetIsLocked(db *gorm.DB, user *User, isLocked bool) error { func (app *App) DeleteUser(caller *User, user *User) error { if !caller.IsAdmin && caller.UUID != user.UUID { - return NewUserError(http.StatusForbidden, "You are not an admin.") + return NewUserErrorWithCode(http.StatusForbidden, "You are not an admin.") } oldSkinHashes := make([]*string, 0, len(user.Players)) @@ -661,7 +661,7 @@ func (app *App) DeleteOIDCIdentity( return result.Error } if result.RowsAffected == 0 { - return NewUserError(http.StatusNotFound, "No linked %s account found.", providerName) + return NewUserErrorWithCode(http.StatusNotFound, "No linked %s account found.", providerName) } var count int64