diff options
author | pg9182 <96569817+pg9182@users.noreply.github.com> | 2022-10-15 20:27:36 -0400 |
---|---|---|
committer | pg9182 <96569817+pg9182@users.noreply.github.com> | 2022-10-15 20:27:36 -0400 |
commit | f84e5fba3dc581bb96f9b61820b14462a49ea2b6 (patch) | |
tree | 516dd63e02ab67f42ae541f0ff234629ebc9e21f /pkg/api | |
parent | e965811185c4c5e9c206f2c2ca411d2544ac24a5 (diff) | |
download | Atlas-f84e5fba3dc581bb96f9b61820b14462a49ea2b6.tar.gz Atlas-f84e5fba3dc581bb96f9b61820b14462a49ea2b6.zip |
pkg/api/api0{,/api0gameserver}: Refactor game server auth requests
Diffstat (limited to 'pkg/api')
-rw-r--r-- | pkg/api/api0/api0gameserver/nsserver.go | 84 | ||||
-rw-r--r-- | pkg/api/api0/client.go | 82 | ||||
-rw-r--r-- | pkg/api/api0/server.go | 34 |
3 files changed, 121 insertions, 79 deletions
diff --git a/pkg/api/api0/api0gameserver/nsserver.go b/pkg/api/api0/api0gameserver/nsserver.go new file mode 100644 index 0000000..5897a9c --- /dev/null +++ b/pkg/api/api0/api0gameserver/nsserver.go @@ -0,0 +1,84 @@ +// Package api0gameserver interacts with game servers using the original master +// server api. +package api0gameserver + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "net/http" + "net/netip" + "net/url" + "strconv" +) + +var ( + ErrInvalidResponse = errors.New("invalid response") + ErrAuthFailed = errors.New("authentication failed") +) + +// VerifyText is the expected server response for /verify. +const VerifyText = "I am a northstar server!" + +// Verify checks whether an address is a Northstar auth server. If the HTTP +// request succeeds but the response is incorrect, err is ErrInvalidResponse. +func Verify(ctx context.Context, auth netip.AddrPort) error { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://"+auth.String()+"/verify", nil) + if err != nil { + return err // shouldn't happen + } + req.Header.Set("User-Agent", "Atlas") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + buf, err := io.ReadAll(io.LimitReader(resp.Body, int64(len(VerifyText)*2))) + if err != nil { + return err + } + + if string(bytes.TrimSpace(buf)) != VerifyText { + return ErrInvalidResponse + } + return nil +} + +// AuthenticateIncomingPlayer checks if a player can connect to a game server, +// registers a one-time connection token, and sends the player's pdata. If the +// authentication request returns invalid JSON, err is ErrInvalidResponse. If +// the authentication response .success is false, err is ErrAuthFailed. +func AuthenticateIncomingPlayer(ctx context.Context, auth netip.AddrPort, uid uint64, username, connToken, serverToken string, pdata []byte) error { + u := "http://" + auth.String() + "/authenticate_incoming_player" + + "?id=" + strconv.FormatUint(uid, 10) + + "&authToken=" + url.QueryEscape(connToken) + + "&serverAuthToken=" + url.QueryEscape(serverToken) + + "&username=" + url.QueryEscape(username) + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, bytes.NewReader(pdata)) + if err != nil { + return err // shouldn't happen + } + req.Header.Set("User-Agent", "Atlas") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + var obj struct { + Success bool `json:"success"` + } + if err := json.NewDecoder(resp.Body).Decode(&obj); err != nil { + return ErrInvalidResponse + } + if !obj.Success { + return ErrAuthFailed + } + return nil +} diff --git a/pkg/api/api0/client.go b/pkg/api/api0/client.go index 478a3aa..7964a95 100644 --- a/pkg/api/api0/client.go +++ b/pkg/api/api0/client.go @@ -1,19 +1,17 @@ package api0 import ( - "bytes" "context" "crypto/sha256" - "encoding/json" "errors" "fmt" "net/http" "net/netip" - "net/url" "strconv" "strings" "time" + "github.com/pg9182/atlas/pkg/api/api0/api0gameserver" "github.com/pg9182/atlas/pkg/origin" "github.com/pg9182/atlas/pkg/pdata" "github.com/pg9182/atlas/pkg/stryder" @@ -415,59 +413,39 @@ func (h *Handler) handleClientAuthWithServer(w http.ResponseWriter, r *http.Requ pbuf = b } - if !func() bool { + { ctx, cancel := context.WithTimeout(r.Context(), time.Second*5) defer cancel() - req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("http://%s/authenticate_incoming_player?id=%d&authToken=%s&serverAuthToken=%s&username=%s", srv.AuthAddr(), acct.UID, authToken, srv.ServerAuthToken, url.QueryEscape(acct.Username)), bytes.NewReader(pbuf)) - if err != nil { - 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, - }) - return false - } - req.Header.Set("User-Agent", "Atlas") - - resp, err := http.DefaultClient.Do(req) - if err != nil { - 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, - }) - return false - } - defer resp.Body.Close() - - var obj struct { - Success bool `json:"success"` - } - if err := json.NewDecoder(resp.Body).Decode(&obj); err != nil { - hlog.FromRequest(r).Error(). - Err(err). - Msgf("failed to read gameserver auth response") - respJSON(w, r, http.StatusInternalServerError, map[string]any{ - "success": false, - "error": ErrorCode_BAD_GAMESERVER_RESPONSE, - }) - return false - } - if !obj.Success { - 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 - }) - return false + if err := api0gameserver.AuthenticateIncomingPlayer(ctx, srv.AuthAddr(), acct.UID, acct.Username, authToken, srv.ServerAuthToken, pbuf); err != nil { + if errors.Is(err, context.DeadlineExceeded) { + err = fmt.Errorf("request timed out") + } + 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 + }) + 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, + }) + 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, + }) + } + return } - return true - }() { - return } acct.LastServerID = srv.ID diff --git a/pkg/api/api0/server.go b/pkg/api/api0/server.go index 982b203..5536bcb 100644 --- a/pkg/api/api0/server.go +++ b/pkg/api/api0/server.go @@ -1,18 +1,17 @@ package api0 import ( - "bytes" "context" "encoding/json" "errors" "fmt" - "io" "net/http" "net/netip" "strconv" "time" "github.com/pg9182/atlas/pkg/a2s" + "github.com/pg9182/atlas/pkg/api/api0/api0gameserver" "github.com/rs/zerolog/hlog" ) @@ -356,36 +355,15 @@ func (h *Handler) handleServerUpsert(w http.ResponseWriter, r *http.Request) { } if !nsrv.VerificationDeadline.IsZero() { - if err := func() error { - ctx, cancel := context.WithDeadline(r.Context(), nsrv.VerificationDeadline) - defer cancel() + ctx, cancel := context.WithDeadline(r.Context(), nsrv.VerificationDeadline) + defer cancel() - req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("http://%s/verify", s.AuthAddr()), nil) - if err != nil { - return err - } - req.Header.Set("User-Agent", "Atlas") - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - buf, err := io.ReadAll(io.LimitReader(resp.Body, 100)) - if err != nil { - return err - } - if string(bytes.TrimSpace(buf)) != "I am a northstar server!" { - return fmt.Errorf("unexpected response") - } - return nil - }(); err != nil { + if err := api0gameserver.Verify(ctx, s.AuthAddr()); err != nil { if errors.Is(err, context.DeadlineExceeded) { err = fmt.Errorf("request timed out") } var code ErrorCode - if err.Error() == "unexpected response" { + if errors.Is(err, api0gameserver.ErrInvalidResponse) { code = ErrorCode_BAD_GAMESERVER_RESPONSE } else { code = ErrorCode_NO_GAMESERVER_RESPONSE @@ -397,6 +375,7 @@ func (h *Handler) handleServerUpsert(w http.ResponseWriter, r *http.Request) { }) return } + if err := a2s.Probe(s.Addr, time.Until(nsrv.VerificationDeadline)); err != nil { respJSON(w, r, http.StatusBadGateway, map[string]any{ "success": false, @@ -405,6 +384,7 @@ func (h *Handler) handleServerUpsert(w http.ResponseWriter, r *http.Request) { }) return } + if !h.ServerList.VerifyServer(nsrv.ID) { respJSON(w, r, http.StatusBadGateway, map[string]any{ "success": false, |