aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pkg/api/api0/api.go34
-rw-r--r--pkg/api/api0/client.go128
2 files changed, 160 insertions, 2 deletions
diff --git a/pkg/api/api0/api.go b/pkg/api/api0/api.go
index 7d1e638..e6ed251 100644
--- a/pkg/api/api0/api.go
+++ b/pkg/api/api0/api.go
@@ -14,6 +14,8 @@ package api0
import (
"bytes"
"compress/gzip"
+ "crypto/rand"
+ "encoding/hex"
"encoding/json"
"net/http"
"strconv"
@@ -33,6 +35,11 @@ type Handler struct {
// NotFound handles requests not handled by this Handler.
NotFound http.Handler
+
+ // InsecureDevNoCheckPlayerAuth is an option you shouldn't use since it
+ // makes the server trust that clients are who they say they are. Blame
+ // @BobTheBob9 for this option even existing in the first place.
+ InsecureDevNoCheckPlayerAuth bool
}
// ServeHTTP routes requests to Handler.
@@ -42,6 +49,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/client/mainmenupromos":
h.handleMainMenuPromos(w, r)
+ case "/client/auth_with_self":
+ h.handleClientAuthWithSelf(w, r)
case "/accounts/write_persistence":
h.handleAccountsWritePersistence(w, r)
case "/accounts/get_username":
@@ -105,3 +114,28 @@ func respMaybeCompress(w http.ResponseWriter, r *http.Request, status int, buf [
w.Write(buf)
}
}
+
+// cryptoRandHex gets a string of random hex digits with length n.
+func cryptoRandHex(n int) (string, error) {
+ b := make([]byte, (n+1)/2) // round up
+ if _, err := rand.Read(b); err != nil {
+ return "", err
+ }
+ return hex.EncodeToString(b)[:n], nil
+}
+
+// marshalJSONBytesAsArray marshals b as an array of numbers (rather than the
+// default of base64).
+func marshalJSONBytesAsArray(b []byte) json.RawMessage {
+ var e bytes.Buffer
+ e.Grow(2 + len(b)*3)
+ e.WriteByte('[')
+ for i, c := range b {
+ if i != 0 {
+ e.WriteByte(',')
+ }
+ e.WriteString(strconv.FormatUint(uint64(c), 10))
+ }
+ e.WriteByte(']')
+ return json.RawMessage(e.Bytes())
+}
diff --git a/pkg/api/api0/client.go b/pkg/api/api0/client.go
index 7874872..3f10dcb 100644
--- a/pkg/api/api0/client.go
+++ b/pkg/api/api0/client.go
@@ -1,7 +1,13 @@
package api0
import (
+ "crypto/sha256"
"net/http"
+ "strconv"
+ "time"
+
+ "github.com/pg9182/atlas/pkg/pdata"
+ "github.com/rs/zerolog/hlog"
)
type MainMenuPromos struct {
@@ -53,13 +59,131 @@ func (h *Handler) handleMainMenuPromos(w http.ResponseWriter, r *http.Request) {
respJSON(w, r, http.StatusOK, p)
}
+func (h *Handler) handleClientAuthWithSelf(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodOptions && r.Method != http.MethodPost {
+ http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
+ return
+ }
+
+ w.Header().Set("Cache-Control", "private, no-cache, no-store")
+ w.Header().Set("Expires", "0")
+ w.Header().Set("Pragma", "no-cache")
+
+ if r.Method == http.MethodOptions {
+ w.Header().Set("Allow", "OPTIONS, POST")
+ w.WriteHeader(http.StatusNoContent)
+ return
+ }
+
+ // TODO: version gate
+
+ 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"),
+ })
+ 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,
+ })
+ return
+ }
+
+ playerToken := r.URL.Query().Get("playerToken")
+ if playerToken == "" {
+ respJSON(w, r, http.StatusBadRequest, map[string]any{
+ "success": false,
+ "error": ErrorCode_BAD_REQUEST,
+ "msg": ErrorCode_BAD_REQUEST.Messagef("playerToken param is required"),
+ })
+ return
+ }
+
+ acct, err := h.AccountStorage.GetAccount(uid)
+ if err != nil {
+ hlog.FromRequest(r).Error().
+ 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(),
+ })
+ return
+ }
+ if acct == nil {
+ respJSON(w, r, http.StatusNotFound, map[string]any{
+ "success": false,
+ "error": ErrorCode_PLAYER_NOT_FOUND,
+ })
+ 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,
+ })
+ return
+ }
+ }
+
+ obj := map[string]any{
+ "success": true,
+ "id": acct.UID,
+ }
+
+ // the way we encode this is utterly absurd and inefficient, but we need to do it for backwards compatibility
+ if b, exists, err := h.PdataStorage.GetPdataCached(acct.UID, [sha256.Size]byte{}); err != nil {
+ hlog.FromRequest(r).Error().
+ 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(),
+ })
+ return
+ } else if !exists {
+ obj["persistentData"] = marshalJSONBytesAsArray(pdata.DefaultPdata)
+ } else {
+ obj["persistentData"] = marshalJSONBytesAsArray(b)
+ }
+
+ // this is also stupid (it doesn't use it for self-auth, but it requires it to be in the response)
+ // and of course, it breaks on 32 chars, so we need to give it 31
+ if v, err := cryptoRandHex(31); err != nil {
+ 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(),
+ })
+ return
+ } else {
+ obj["authToken"] = v
+ }
+
+ respJSON(w, r, http.StatusOK, obj)
+}
+
/*
/client/origin_auth:
GET:
/client/auth_with_server:
POST:
- /client/auth_with_self:
- POST:
/client/servers:
GET: