diff options
-rw-r--r-- | pkg/api/api0/accounts.go | 109 | ||||
-rw-r--r-- | pkg/api/api0/api.go | 17 | ||||
-rw-r--r-- | pkg/api/api0/client.go | 174 | ||||
-rw-r--r-- | pkg/api/api0/errors.go | 36 | ||||
-rw-r--r-- | pkg/api/api0/playerinfo.go | 34 | ||||
-rw-r--r-- | pkg/api/api0/server.go | 132 |
6 files changed, 137 insertions, 365 deletions
diff --git a/pkg/api/api0/accounts.go b/pkg/api/api0/accounts.go index 76e47b6..e4bc085 100644 --- a/pkg/api/api0/accounts.go +++ b/pkg/api/api0/accounts.go @@ -29,31 +29,19 @@ func (h *Handler) handleAccountsWritePersistence(w http.ResponseWriter, r *http. } if err := r.ParseMultipartForm(2 << 20); err != nil { - respJSON(w, r, http.StatusNotFound, map[string]any{ - "success": false, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("failed to parse multipart form: %v", err), - }) + respFail(w, r, http.StatusNotFound, ErrorCode_BAD_REQUEST.MessageObjf("failed to parse multipart form: %v", err)) return } pf, pfHdr, err := r.FormFile("pdata") if err != nil { - respJSON(w, r, http.StatusNotFound, map[string]any{ - "success": false, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("missing pdata file: %v", err), - }) + respFail(w, r, http.StatusNotFound, ErrorCode_BAD_REQUEST.MessageObjf("missing pdata file: %v", err)) return } defer pf.Close() if pfHdr.Size > (2 << 20) { - respJSON(w, r, http.StatusNotFound, map[string]any{ - "success": false, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("pdata file is too large"), - }) + respFail(w, r, http.StatusNotFound, ErrorCode_BAD_REQUEST.MessageObjf("pdata file is too large")) return } @@ -62,11 +50,7 @@ func (h *Handler) handleAccountsWritePersistence(w http.ResponseWriter, r *http. hlog.FromRequest(r).Error(). Err(err). Msgf("failed to read uploaded data file (size: %d)", pfHdr.Size) - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Message(), - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) return } @@ -75,11 +59,7 @@ func (h *Handler) handleAccountsWritePersistence(w http.ResponseWriter, r *http. hlog.FromRequest(r).Warn(). Err(err). Msgf("invalid pdata rejected") - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("invalid pdata"), - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_BAD_REQUEST.MessageObjf("invalid pdata")) return } @@ -87,30 +67,19 @@ func (h *Handler) handleAccountsWritePersistence(w http.ResponseWriter, r *http. hlog.FromRequest(r).Warn(). Err(err). Msgf("pdata with too much trailing junk rejected") - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("invalid pdata"), - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_BAD_REQUEST.MessageObjf("invalid pdata")) return } uidQ := r.URL.Query().Get("id") if uidQ == "" { - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("id param is required"), - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_BAD_REQUEST.MessageObjf("id param is required")) return } uid, err := strconv.ParseUint(uidQ, 10, 64) if err != nil { - respJSON(w, r, http.StatusNotFound, map[string]any{ - "success": false, - "error": ErrorCode_PLAYER_NOT_FOUND, - }) + respFail(w, r, http.StatusNotFound, ErrorCode_PLAYER_NOT_FOUND.MessageObj()) return } @@ -121,11 +90,7 @@ func (h *Handler) handleAccountsWritePersistence(w http.ResponseWriter, r *http. hlog.FromRequest(r).Error(). Err(err). Msgf("failed to parse remote ip %q", r.RemoteAddr) - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Message(), - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) return } @@ -135,51 +100,31 @@ func (h *Handler) handleAccountsWritePersistence(w http.ResponseWriter, r *http. Err(err). Uint64("uid", uid). Msgf("failed to read account from storage") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Message(), - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) return } if acct == nil { - respJSON(w, r, http.StatusNotFound, map[string]any{ - "success": false, - "error": ErrorCode_PLAYER_NOT_FOUND, - }) + respFail(w, r, http.StatusNotFound, ErrorCode_PLAYER_NOT_FOUND.MessageObj()) return } if acct.IsOnOwnServer() { if acct.AuthIP != raddr.Addr() { - respJSON(w, r, http.StatusForbidden, map[string]any{ - "success": false, - "error": ErrorCode_UNAUTHORIZED_GAMESERVER, - }) + respFail(w, r, http.StatusForbidden, ErrorCode_UNAUTHORIZED_GAMESERVER.MessageObj()) return } } else { srv := h.ServerList.GetServerByID(serverID) if srv == nil { - respJSON(w, r, http.StatusForbidden, map[string]any{ - "success": false, - "error": ErrorCode_UNAUTHORIZED_GAMESERVER, - "msg": ErrorCode_UNAUTHORIZED_GAMESERVER.Messagef("no such game server"), - }) + respFail(w, r, http.StatusForbidden, ErrorCode_UNAUTHORIZED_GAMESERVER.MessageObjf("no such game server")) return } if srv.Addr.Addr() != raddr.Addr() { - respJSON(w, r, http.StatusForbidden, map[string]any{ - "success": false, - "error": ErrorCode_UNAUTHORIZED_GAMESERVER, - }) + respFail(w, r, http.StatusForbidden, ErrorCode_UNAUTHORIZED_GAMESERVER.MessageObj()) return } if acct.LastServerID != srv.ID { - respJSON(w, r, http.StatusForbidden, map[string]any{ - "success": false, - "error": ErrorCode_UNAUTHORIZED_GAMESERVER, - }) + respFail(w, r, http.StatusForbidden, ErrorCode_UNAUTHORIZED_GAMESERVER.MessageObj()) return } } @@ -189,11 +134,7 @@ func (h *Handler) handleAccountsWritePersistence(w http.ResponseWriter, r *http. Err(err). Uint64("uid", uid). Msgf("failed to save pdata") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Message(), - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) return } @@ -223,8 +164,7 @@ func (h *Handler) handleAccountsLookupUID(w http.ResponseWriter, r *http.Request "success": false, "username": "", "matches": []uint64{}, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("username param is required"), + "error": ErrorCode_BAD_REQUEST.MessageObjf("username param is required"), }) return } @@ -243,8 +183,7 @@ func (h *Handler) handleAccountsLookupUID(w http.ResponseWriter, r *http.Request "success": false, "username": username, "matches": []uint64{}, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Message(), + "error": ErrorCode_INTERNAL_SERVER_ERROR.MessageObj(), }) return } @@ -279,8 +218,7 @@ func (h *Handler) handleAccountsGetUsername(w http.ResponseWriter, r *http.Reque "success": false, "uid": "", "matches": []string{}, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("uid param is required"), + "error": ErrorCode_BAD_REQUEST.MessageObjf("uid param is required"), }) return } @@ -291,7 +229,7 @@ func (h *Handler) handleAccountsGetUsername(w http.ResponseWriter, r *http.Reque "success": false, "uid": strconv.FormatUint(uid, 10), "matches": []string{}, - "error": ErrorCode_PLAYER_NOT_FOUND, + "error": ErrorCode_PLAYER_NOT_FOUND.MessageObj(), }) return } @@ -304,8 +242,9 @@ func (h *Handler) handleAccountsGetUsername(w http.ResponseWriter, r *http.Reque Msgf("failed to read account from storage") respJSON(w, r, http.StatusInternalServerError, map[string]any{ "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Message(), + "uid": strconv.FormatUint(uid, 10), + "matches": []string{}, + "error": ErrorCode_INTERNAL_SERVER_ERROR.MessageObj(), }) return } @@ -314,7 +253,7 @@ func (h *Handler) handleAccountsGetUsername(w http.ResponseWriter, r *http.Reque "success": false, "uid": strconv.FormatUint(uid, 10), "matches": []string{}, - "error": ErrorCode_PLAYER_NOT_FOUND, + "error": ErrorCode_PLAYER_NOT_FOUND.MessageObj(), }) return } diff --git a/pkg/api/api0/api.go b/pkg/api/api0/api.go index 818f6b9..398ac17 100644 --- a/pkg/api/api0/api.go +++ b/pkg/api/api0/api.go @@ -151,6 +151,23 @@ func (h *Handler) checkLauncherVersion(r *http.Request) bool { return semver.Compare(rver, mver) >= 0 } +// respFail writes a {success:false,error:ErrorObj} response with the provided +// response status. +func respFail(w http.ResponseWriter, r *http.Request, status int, obj ErrorObj) { + if rid, ok := hlog.IDFromRequest(r); ok { + respJSON(w, r, status, map[string]any{ + "success": false, + "error": obj, + "request_id": rid.String(), + }) + } else { + respJSON(w, r, status, map[string]any{ + "success": false, + "error": obj, + }) + } +} + // respJSON writes the JSON encoding of obj with the provided response status. func respJSON(w http.ResponseWriter, r *http.Request, status int, obj any) { if r.Method == http.MethodHead { diff --git a/pkg/api/api0/client.go b/pkg/api/api0/client.go index 7964a95..d5cd659 100644 --- a/pkg/api/api0/client.go +++ b/pkg/api/api0/client.go @@ -84,29 +84,19 @@ func (h *Handler) handleClientOriginAuth(w http.ResponseWriter, r *http.Request) } if !h.checkLauncherVersion(r) { - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_UNSUPPORTED_VERSION, - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_UNSUPPORTED_VERSION.MessageObj()) return } uidQ := r.URL.Query().Get("id") if uidQ == "" { - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("id param is required"), - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_BAD_REQUEST.MessageObjf("id param is required")) return } uid, err := strconv.ParseUint(uidQ, 10, 64) if err != nil { - respJSON(w, r, http.StatusNotFound, map[string]any{ - "success": false, - "error": ErrorCode_PLAYER_NOT_FOUND, - }) + respFail(w, r, http.StatusNotFound, ErrorCode_PLAYER_NOT_FOUND.MessageObj()) return } @@ -115,22 +105,14 @@ func (h *Handler) handleClientOriginAuth(w http.ResponseWriter, r *http.Request) hlog.FromRequest(r).Error(). Err(err). Msgf("failed to parse remote ip %q", r.RemoteAddr) - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Message(), - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) return } if !h.InsecureDevNoCheckPlayerAuth { token := r.URL.Query().Get("token") if token == "" { - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("token param is required"), - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_BAD_REQUEST.MessageObjf("token param is required")) return } @@ -151,11 +133,7 @@ func (h *Handler) handleClientOriginAuth(w http.ResponseWriter, r *http.Request) Str("stryder_token", string(token)). Str("stryder_resp", string(stryderRes)). Msgf("invalid stryder token") - respJSON(w, r, http.StatusForbidden, map[string]any{ - "success": false, - "error": ErrorCode_UNAUTHORIZED_GAME, - "msg": ErrorCode_UNAUTHORIZED_GAME.Message(), - }) + respFail(w, r, http.StatusForbidden, ErrorCode_UNAUTHORIZED_GAME.MessageObj()) return case errors.Is(err, stryder.ErrStryder): hlog.FromRequest(r).Error(). @@ -164,11 +142,7 @@ func (h *Handler) handleClientOriginAuth(w http.ResponseWriter, r *http.Request) Str("stryder_token", string(token)). Str("stryder_resp", string(stryderRes)). Msgf("unexpected stryder error") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Message(), - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) return default: hlog.FromRequest(r).Error(). @@ -177,11 +151,7 @@ func (h *Handler) handleClientOriginAuth(w http.ResponseWriter, r *http.Request) Str("stryder_token", string(token)). Str("stryder_resp", string(stryderRes)). Msgf("unexpected stryder error") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Messagef("stryder is down: %v", err), - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObjf("stryder is down: %v", err)) return } } @@ -261,11 +231,7 @@ func (h *Handler) handleClientOriginAuth(w http.ResponseWriter, r *http.Request) hlog.FromRequest(r).Error(). Err(err). Msgf("failed to generate random token") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Message(), - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) return } else { acct.AuthToken = t @@ -282,11 +248,7 @@ func (h *Handler) handleClientOriginAuth(w http.ResponseWriter, r *http.Request) Err(err). Uint64("uid", uid). Msgf("failed to save account to storage") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Message(), - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) return } @@ -313,29 +275,19 @@ func (h *Handler) handleClientAuthWithServer(w http.ResponseWriter, r *http.Requ } if !h.checkLauncherVersion(r) { - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_UNSUPPORTED_VERSION, - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_UNSUPPORTED_VERSION.MessageObj()) return } uidQ := r.URL.Query().Get("id") if uidQ == "" { - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("id param is required"), - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_BAD_REQUEST.MessageObjf("id param is required")) return } uid, err := strconv.ParseUint(uidQ, 10, 64) if err != nil { - respJSON(w, r, http.StatusNotFound, map[string]any{ - "success": false, - "error": ErrorCode_PLAYER_NOT_FOUND, - }) + respFail(w, r, http.StatusNotFound, ErrorCode_PLAYER_NOT_FOUND.MessageObj()) return } @@ -345,10 +297,7 @@ func (h *Handler) handleClientAuthWithServer(w http.ResponseWriter, r *http.Requ srv := h.ServerList.GetServerByID(server) if srv == nil || srv.Password != password { - respJSON(w, r, http.StatusUnauthorized, map[string]any{ - "success": false, - "error": ErrorCode_UNAUTHORIZED_PWD, - }) + respFail(w, r, http.StatusUnauthorized, ErrorCode_UNAUTHORIZED_PWD.MessageObj()) return } @@ -358,26 +307,17 @@ func (h *Handler) handleClientAuthWithServer(w http.ResponseWriter, r *http.Requ Err(err). Uint64("uid", uid). Msgf("failed to read account from storage") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) return } if acct == nil { - respJSON(w, r, http.StatusNotFound, map[string]any{ - "success": false, - "error": ErrorCode_PLAYER_NOT_FOUND, - }) + respFail(w, r, http.StatusNotFound, ErrorCode_PLAYER_NOT_FOUND.MessageObj()) return } if !h.InsecureDevNoCheckPlayerAuth { if playerToken != acct.AuthToken || !time.Now().Before(acct.AuthTokenExpiry) { - respJSON(w, r, http.StatusUnauthorized, map[string]any{ - "success": false, - "error": ErrorCode_INVALID_MASTERSERVER_TOKEN, - }) + respFail(w, r, http.StatusUnauthorized, ErrorCode_INVALID_MASTERSERVER_TOKEN.MessageObj()) return } } @@ -387,10 +327,7 @@ func (h *Handler) handleClientAuthWithServer(w http.ResponseWriter, r *http.Requ hlog.FromRequest(r).Error(). Err(err). Msgf("failed to generate random token") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) return } else { authToken = v @@ -402,10 +339,7 @@ func (h *Handler) handleClientAuthWithServer(w http.ResponseWriter, r *http.Requ Err(err). Uint64("uid", acct.UID). Msgf("failed to read pdata from storage") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) return } else if !exists { pbuf = pdata.DefaultPdata @@ -423,26 +357,17 @@ func (h *Handler) handleClientAuthWithServer(w http.ResponseWriter, r *http.Requ } switch { case errors.Is(err, api0gameserver.ErrAuthFailed): - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_JSON_PARSE_ERROR, // this is kind of misleading... but it's what the original master server did - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_JSON_PARSE_ERROR.MessageObj()) // this is kind of misleading... but it's what the original master server did case errors.Is(err, api0gameserver.ErrInvalidResponse): hlog.FromRequest(r).Error(). Err(err). Msgf("failed to make gameserver auth request") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_BAD_GAMESERVER_RESPONSE, - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_BAD_GAMESERVER_RESPONSE.MessageObj()) default: hlog.FromRequest(r).Error(). Err(err). Msgf("failed to make gameserver auth request") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) } return } @@ -455,10 +380,7 @@ func (h *Handler) handleClientAuthWithServer(w http.ResponseWriter, r *http.Requ Err(err). Uint64("uid", uid). Msgf("failed to save account to storage") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) return } @@ -487,29 +409,19 @@ func (h *Handler) handleClientAuthWithSelf(w http.ResponseWriter, r *http.Reques } if !h.checkLauncherVersion(r) { - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_UNSUPPORTED_VERSION, - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_UNSUPPORTED_VERSION.MessageObj()) return } uidQ := r.URL.Query().Get("id") if uidQ == "" { - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("id param is required"), - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_BAD_REQUEST.MessageObjf("id param is required")) return } uid, err := strconv.ParseUint(uidQ, 10, 64) if err != nil { - respJSON(w, r, http.StatusNotFound, map[string]any{ - "success": false, - "error": ErrorCode_PLAYER_NOT_FOUND, - }) + respFail(w, r, http.StatusNotFound, ErrorCode_PLAYER_NOT_FOUND.MessageObj()) return } @@ -521,27 +433,17 @@ func (h *Handler) handleClientAuthWithSelf(w http.ResponseWriter, r *http.Reques Err(err). Uint64("uid", uid). Msgf("failed to read account from storage") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Message(), - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) return } if acct == nil { - respJSON(w, r, http.StatusNotFound, map[string]any{ - "success": false, - "error": ErrorCode_PLAYER_NOT_FOUND, - }) + respFail(w, r, http.StatusNotFound, ErrorCode_PLAYER_NOT_FOUND.MessageObj()) return } if !h.InsecureDevNoCheckPlayerAuth { if playerToken != acct.AuthToken || !time.Now().Before(acct.AuthTokenExpiry) { - respJSON(w, r, http.StatusUnauthorized, map[string]any{ - "success": false, - "error": ErrorCode_INVALID_MASTERSERVER_TOKEN, - }) + respFail(w, r, http.StatusUnauthorized, ErrorCode_INVALID_MASTERSERVER_TOKEN.MessageObj()) return } } @@ -553,11 +455,7 @@ func (h *Handler) handleClientAuthWithSelf(w http.ResponseWriter, r *http.Reques Err(err). Uint64("uid", uid). Msgf("failed to save account to storage") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Message(), - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) return } @@ -572,11 +470,7 @@ func (h *Handler) handleClientAuthWithSelf(w http.ResponseWriter, r *http.Reques Err(err). Uint64("uid", acct.UID). Msgf("failed to read pdata from storage") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Message(), - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) return } else if !exists { obj["persistentData"] = marshalJSONBytesAsArray(pdata.DefaultPdata) @@ -590,11 +484,7 @@ func (h *Handler) handleClientAuthWithSelf(w http.ResponseWriter, r *http.Reques hlog.FromRequest(r).Error(). Err(err). Msgf("failed to generate random token") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Message(), - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) return } else { obj["authToken"] = v diff --git a/pkg/api/api0/errors.go b/pkg/api/api0/errors.go index dc12d42..e407be6 100644 --- a/pkg/api/api0/errors.go +++ b/pkg/api/api0/errors.go @@ -1,6 +1,8 @@ package api0 -import "fmt" +import ( + "fmt" +) // ErrorCode represents a known Northstar error code. type ErrorCode string @@ -26,6 +28,35 @@ const ( ErrorCode_BAD_REQUEST ErrorCode = "BAD_REQUEST" ) +// ErrorObj contains an error code and a message for API responses. +type ErrorObj struct { + Code ErrorCode `json:"enum"` + Message string `json:"msg"` // note: no omitempty +} + +// Obj returns an ErrorObj. +func (n ErrorCode) Obj() ErrorObj { + return ErrorObj{ + Code: n, + } +} + +// MessageObj is like Message, but returns an ErrorObj. +func (n ErrorCode) MessageObj() ErrorObj { + return ErrorObj{ + Code: n, + Message: n.Message(), + } +} + +// MessageObjf is like Messagef, but returns an ErrorObj. +func (n ErrorCode) MessageObjf(format string, a ...interface{}) ErrorObj { + return ErrorObj{ + Code: n, + Message: n.Messagef(format, a...), + } +} + // Message returns the default message for error code n. func (n ErrorCode) Message() string { switch n { @@ -64,5 +95,8 @@ func (n ErrorCode) Message() string { // Messagef returns Message() with additional text appended after ": ". func (n ErrorCode) Messagef(format string, a ...interface{}) string { + if format == "" { + return n.Message() + } return n.Message() + ": " + fmt.Sprintf(format, a...) } diff --git a/pkg/api/api0/playerinfo.go b/pkg/api/api0/playerinfo.go index ca059cd..70555cc 100644 --- a/pkg/api/api0/playerinfo.go +++ b/pkg/api/api0/playerinfo.go @@ -80,20 +80,13 @@ func (h *Handler) handlePlayer(w http.ResponseWriter, r *http.Request) { uidQ := r.URL.Query().Get("id") if uidQ == "" { - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("id param is required"), - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_BAD_REQUEST.MessageObjf("id param is required")) return } uid, err := strconv.ParseUint(uidQ, 10, 64) if err != nil { - respJSON(w, r, http.StatusNotFound, map[string]any{ - "success": false, - "error": ErrorCode_PLAYER_NOT_FOUND, - }) + respFail(w, r, http.StatusNotFound, ErrorCode_PLAYER_NOT_FOUND.MessageObj()) return } @@ -123,18 +116,11 @@ func (h *Handler) handlePlayer(w http.ResponseWriter, r *http.Request) { Err(err). Uint64("uid", uid). Msgf("failed to read pdata from storage") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Message(), - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) return } if !exists { - respJSON(w, r, http.StatusNotFound, map[string]any{ - "success": false, - "error": ErrorCode_PLAYER_NOT_FOUND, - }) + respFail(w, r, http.StatusNotFound, ErrorCode_PLAYER_NOT_FOUND.MessageObj()) return } @@ -148,11 +134,7 @@ func (h *Handler) handlePlayer(w http.ResponseWriter, r *http.Request) { Uint64("uid", uid). Str("pdata_sha256", hex.EncodeToString(hash[:])). Msgf("failed to parse pdata from storage") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Messagef("failed to parse pdata from storage"), - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObjf("failed to parse stored pdata")) return } @@ -163,11 +145,7 @@ func (h *Handler) handlePlayer(w http.ResponseWriter, r *http.Request) { Uint64("uid", uid). Str("pdata_sha256", hex.EncodeToString(hash[:])). Msgf("failed to encode pdata as json") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Messagef("failed to encode pdata as json"), - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObjf("failed to encode pdata as json")) return } jbuf = append(jbuf, '\n') diff --git a/pkg/api/api0/server.go b/pkg/api/api0/server.go index 5536bcb..756c48b 100644 --- a/pkg/api/api0/server.go +++ b/pkg/api/api0/server.go @@ -51,10 +51,7 @@ func (h *Handler) handleServerUpsert(w http.ResponseWriter, r *http.Request) { } if !h.checkLauncherVersion(r) { - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_UNSUPPORTED_VERSION, - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_UNSUPPORTED_VERSION.MessageObj()) return } @@ -63,21 +60,13 @@ func (h *Handler) handleServerUpsert(w http.ResponseWriter, r *http.Request) { hlog.FromRequest(r).Error(). Err(err). Msgf("failed to parse remote ip %q", r.RemoteAddr) - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Message(), - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) return } if !h.AllowGameServerIPv6 { if raddr.Addr().Is6() { - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_NO_GAMESERVER_RESPONSE, - "msg": ErrorCode_NO_GAMESERVER_RESPONSE.Messagef("ipv6 is not currently supported (ip %s)", raddr.Addr()), - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_BAD_REQUEST.MessageObjf("ipv6 is not currently supported (ip %s)", raddr.Addr())) return } } @@ -110,11 +99,7 @@ func (h *Handler) handleServerUpsert(w http.ResponseWriter, r *http.Request) { if canUpdate { if v := r.URL.Query().Get("id"); v == "" { if isUpdate { - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("id param is required"), - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_BAD_REQUEST.MessageObjf("port param is required")) return } } else { @@ -125,19 +110,11 @@ func (h *Handler) handleServerUpsert(w http.ResponseWriter, r *http.Request) { if canCreate { if v := r.URL.Query().Get("port"); v == "" { if isCreate { - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("port param is required"), - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_BAD_REQUEST.MessageObjf("port param is required")) return } } else if n, err := strconv.ParseUint(v, 10, 16); err != nil { - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("port param is invalid: %v", err), - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_BAD_REQUEST.MessageObjf("port param is invalid: %v", err)) return } else { s.Addr = netip.AddrPortFrom(raddr.Addr(), uint16(n)) @@ -145,19 +122,11 @@ func (h *Handler) handleServerUpsert(w http.ResponseWriter, r *http.Request) { if v := r.URL.Query().Get("authPort"); v == "" { if isCreate { - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("authPort param is required"), - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_BAD_REQUEST.MessageObjf("authPort param is required")) + return } - return } else if n, err := strconv.ParseUint(v, 10, 16); err != nil { - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("authPort param is invalid: %v", err), - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_BAD_REQUEST.MessageObjf("authPort param is invalid: %v", err)) return } else { s.AuthPort = uint16(n) @@ -165,11 +134,7 @@ func (h *Handler) handleServerUpsert(w http.ResponseWriter, r *http.Request) { if v := r.URL.Query().Get("password"); len(v) > 128 { if isCreate { - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("password is too long"), - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_BAD_REQUEST.MessageObjf("password is too long")) return } } else { @@ -180,11 +145,7 @@ func (h *Handler) handleServerUpsert(w http.ResponseWriter, r *http.Request) { if canCreate || canUpdate { if v := r.URL.Query().Get("name"); v == "" { if isCreate { - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("name param must not be empty"), - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_BAD_REQUEST.MessageObjf("name param must not be empty")) return } } else { @@ -312,45 +273,25 @@ func (h *Handler) handleServerUpsert(w http.ResponseWriter, r *http.Request) { nsrv, err := h.ServerList.ServerHybridUpdatePut(u, s, l) if err != nil { if errors.Is(err, ErrServerListUpdateWrongIP) { - respJSON(w, r, http.StatusForbidden, map[string]any{ - "success": false, - "error": ErrorCode_UNAUTHORIZED_GAMESERVER, - "msg": ErrorCode_UNAUTHORIZED_GAMESERVER.Messagef("%v", err), - }) + respFail(w, r, http.StatusForbidden, ErrorCode_UNAUTHORIZED_GAMESERVER.MessageObjf("%v", err)) return } if errors.Is(err, ErrServerListUpdateServerDead) { - respJSON(w, r, http.StatusForbidden, map[string]any{ - "success": false, - "error": ErrorCode_UNAUTHORIZED_GAMESERVER, - "msg": ErrorCode_UNAUTHORIZED_GAMESERVER.Messagef("no such server"), - }) + respFail(w, r, http.StatusForbidden, ErrorCode_UNAUTHORIZED_GAMESERVER.MessageObjf("no such server")) return } if errors.Is(err, ErrServerListDuplicateAuthAddr) { - respJSON(w, r, http.StatusForbidden, map[string]any{ - "success": false, - "error": ErrorCode_DUPLICATE_SERVER, - "msg": ErrorCode_DUPLICATE_SERVER.Messagef("%v", err), - }) + respFail(w, r, http.StatusForbidden, ErrorCode_DUPLICATE_SERVER.MessageObjf("%v", err)) return } if errors.Is(err, ErrServerListLimitExceeded) { - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Messagef("%v", err), - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObjf("%v", err)) return } hlog.FromRequest(r).Error(). Err(err). Msgf("failed to update server list") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Message(), - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) return } @@ -368,29 +309,17 @@ func (h *Handler) handleServerUpsert(w http.ResponseWriter, r *http.Request) { } else { code = ErrorCode_NO_GAMESERVER_RESPONSE } - respJSON(w, r, http.StatusBadGateway, map[string]any{ - "success": false, - "error": code, - "msg": code.Messagef("failed to connect to auth port: %v", err), - }) + respFail(w, r, http.StatusBadGateway, code.MessageObjf("failed to connect to auth port: %v", err)) return } if err := a2s.Probe(s.Addr, time.Until(nsrv.VerificationDeadline)); err != nil { - respJSON(w, r, http.StatusBadGateway, map[string]any{ - "success": false, - "error": ErrorCode_BAD_GAMESERVER_RESPONSE, - "msg": ErrorCode_BAD_GAMESERVER_RESPONSE.Messagef("failed to connect to game port: %v", err), - }) + respFail(w, r, http.StatusBadGateway, ErrorCode_BAD_GAMESERVER_RESPONSE.MessageObjf("failed to connect to game port: %v", err)) return } if !h.ServerList.VerifyServer(nsrv.ID) { - respJSON(w, r, http.StatusBadGateway, map[string]any{ - "success": false, - "error": ErrorCode_NO_GAMESERVER_RESPONSE, - "msg": ErrorCode_NO_GAMESERVER_RESPONSE.Messagef("verification timed out"), - }) + respFail(w, r, http.StatusBadGateway, ErrorCode_NO_GAMESERVER_RESPONSE.MessageObjf("verification timed out")) return } } @@ -423,21 +352,13 @@ func (h *Handler) handleServerRemove(w http.ResponseWriter, r *http.Request) { hlog.FromRequest(r).Error(). Err(err). Msgf("failed to parse remote ip %q", r.RemoteAddr) - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_INTERNAL_SERVER_ERROR, - "msg": ErrorCode_INTERNAL_SERVER_ERROR.Message(), - }) + respFail(w, r, http.StatusInternalServerError, ErrorCode_INTERNAL_SERVER_ERROR.MessageObj()) return } var id string if v := r.URL.Query().Get("id"); v == "" { - respJSON(w, r, http.StatusBadRequest, map[string]any{ - "success": false, - "error": ErrorCode_BAD_REQUEST, - "msg": ErrorCode_BAD_REQUEST.Messagef("id param is required"), - }) + respFail(w, r, http.StatusBadRequest, ErrorCode_BAD_REQUEST.MessageObjf("id param is required")) return } else { id = v @@ -445,18 +366,11 @@ func (h *Handler) handleServerRemove(w http.ResponseWriter, r *http.Request) { srv := h.ServerList.GetServerByID(id) if srv == nil { - respJSON(w, r, http.StatusForbidden, map[string]any{ - "success": false, - "error": ErrorCode_UNAUTHORIZED_GAMESERVER, - "msg": ErrorCode_UNAUTHORIZED_GAMESERVER.Messagef("no such game server"), - }) + respFail(w, r, http.StatusForbidden, ErrorCode_UNAUTHORIZED_GAMESERVER.MessageObjf("no such game server")) return } if srv.Addr.Addr() != raddr.Addr() { - respJSON(w, r, http.StatusForbidden, map[string]any{ - "success": false, - "error": ErrorCode_UNAUTHORIZED_GAMESERVER, - }) + respFail(w, r, http.StatusForbidden, ErrorCode_UNAUTHORIZED_GAMESERVER.MessageObj()) return } h.ServerList.DeleteServerByID(id) |