aboutsummaryrefslogtreecommitdiff
path: root/pkg/api/api0
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/api/api0')
-rw-r--r--pkg/api/api0/api0gameserver/nsserver.go84
-rw-r--r--pkg/api/api0/client.go82
-rw-r--r--pkg/api/api0/server.go34
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,