aboutsummaryrefslogtreecommitdiff
path: root/pkg/api/api0/client.go
diff options
context:
space:
mode:
authorpg9182 <96569817+pg9182@users.noreply.github.com>2022-10-14 17:12:58 -0400
committerpg9182 <96569817+pg9182@users.noreply.github.com>2022-10-14 17:12:58 -0400
commit7c46a0dc05c85a62f19a72bafca37a711c17621e (patch)
treebab8ad073d151b85fd325bd0f5b78828d7ee033e /pkg/api/api0/client.go
parent38218809c7ce18d43c73f21be60bad93e8186f4e (diff)
downloadAtlas-7c46a0dc05c85a62f19a72bafca37a711c17621e.tar.gz
Atlas-7c46a0dc05c85a62f19a72bafca37a711c17621e.zip
pkg/api/api0: Implement /client/origin_auth
Diffstat (limited to 'pkg/api/api0/client.go')
-rw-r--r--pkg/api/api0/client.go240
1 files changed, 238 insertions, 2 deletions
diff --git a/pkg/api/api0/client.go b/pkg/api/api0/client.go
index b805261..f0f812a 100644
--- a/pkg/api/api0/client.go
+++ b/pkg/api/api0/client.go
@@ -1,12 +1,17 @@
package api0
import (
+ "context"
"crypto/sha256"
+ "errors"
"net/http"
+ "net/netip"
"strconv"
"time"
+ "github.com/pg9182/atlas/pkg/origin"
"github.com/pg9182/atlas/pkg/pdata"
+ "github.com/pg9182/atlas/pkg/stryder"
"github.com/rs/zerolog/hlog"
)
@@ -59,6 +64,239 @@ func (h *Handler) handleMainMenuPromos(w http.ResponseWriter, r *http.Request) {
respJSON(w, r, http.StatusOK, p)
}
+func (h *Handler) handleClientOriginAuth(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodOptions && r.Method != http.MethodGet { // no HEAD support intentionally
+ 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
+ }
+
+ if !h.checkLauncherVersion(r) {
+ respJSON(w, r, http.StatusBadRequest, map[string]any{
+ "success": false,
+ "error": ErrorCode_UNSUPPORTED_VERSION,
+ })
+ 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"),
+ })
+ 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
+ }
+
+ raddr, err := netip.ParseAddrPort(r.RemoteAddr)
+ if err != nil {
+ 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(),
+ })
+ 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"),
+ })
+ return
+ }
+
+ stryderCtx, cancel := context.WithTimeout(r.Context(), time.Second*5)
+ defer cancel()
+
+ stryderRes, err := stryder.NucleusAuth(stryderCtx, token, uid)
+ if err != nil {
+ switch {
+ case errors.Is(err, stryder.ErrInvalidGame):
+ fallthrough
+ case errors.Is(err, stryder.ErrInvalidToken):
+ fallthrough
+ case errors.Is(err, stryder.ErrMultiplayerNotAllowed):
+ hlog.FromRequest(r).Info().
+ Err(err).
+ Uint64("uid", uid).
+ 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(),
+ })
+ return
+ case errors.Is(err, stryder.ErrStryder):
+ hlog.FromRequest(r).Error().
+ Err(err).
+ Uint64("uid", uid).
+ 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(),
+ })
+ return
+ default:
+ hlog.FromRequest(r).Error().
+ Err(err).
+ Uint64("uid", uid).
+ 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),
+ })
+ return
+ }
+ }
+ }
+
+ var username string
+ if h.OriginAuthMgr != nil {
+ // TODO: maybe just update this from a different thread since we don't
+ // actually need it during the auth process (doing it that way will
+ // speed up auth and also allow us to batch the Origin API calls)
+
+ if tok, ours, err := h.OriginAuthMgr.OriginAuth(false); err == nil {
+ var notfound bool
+ if ui, err := origin.GetUserInfo(r.Context(), tok, uid); err == nil {
+ if len(ui) == 1 {
+ username = ui[0].EAID
+ } else {
+ notfound = true
+ }
+ } else if errors.Is(err, origin.ErrAuthRequired) {
+ if tok, ours, err := h.OriginAuthMgr.OriginAuth(true); err == nil {
+ if ui, err := origin.GetUserInfo(r.Context(), tok, uid); err == nil {
+ if len(ui) == 1 {
+ username = ui[0].EAID
+ } else {
+ notfound = true
+ }
+ }
+ } else if ours {
+ hlog.FromRequest(r).Error().
+ Err(err).
+ Msgf("origin auth token refresh failure")
+ }
+ } else {
+ hlog.FromRequest(r).Error().
+ Err(err).
+ Msgf("failed to get origin user info")
+ }
+ if notfound {
+ hlog.FromRequest(r).Warn().
+ Err(err).
+ Uint64("uid", uid).
+ Msgf("no username found for uid")
+ }
+ } else if ours {
+ hlog.FromRequest(r).Error().
+ Err(err).
+ Msgf("origin auth token refresh failure")
+ }
+ }
+
+ // note: there's small chance of race conditions here if there are multiple
+ // concurrent origin_auth calls, but since we only ever support one session
+ // at a time per uid, it's not a big deal which token gets saved (if it is
+ // ever a problem, we can change AccountStorage to support transactions)
+
+ 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 {
+ acct = &Account{
+ UID: uid,
+ }
+ }
+ if username != "" {
+ acct.Username = username
+ }
+
+ if t, err := cryptoRandHex(32); 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 {
+ acct.AuthToken = t
+ }
+ if h.TokenExpiryTime > 0 {
+ acct.AuthTokenExpiry = time.Now().Add(h.TokenExpiryTime)
+ } else {
+ acct.AuthTokenExpiry = time.Now().Add(time.Hour * 24)
+ }
+ acct.AuthIP = raddr.Addr()
+
+ if err := h.AccountStorage.SaveAccount(acct); err != nil {
+ hlog.FromRequest(r).Error().
+ 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(),
+ })
+ return
+ }
+
+ respJSON(w, r, http.StatusOK, map[string]any{
+ "success": true,
+ "token": acct.AuthToken,
+ })
+}
+
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)
@@ -186,8 +424,6 @@ func (h *Handler) handleClientAuthWithSelf(w http.ResponseWriter, r *http.Reques
}
/*
- /client/origin_auth:
- GET:
/client/auth_with_server:
POST: