diff options
-rw-r--r-- | pkg/api/api0/api.go | 7 | ||||
-rw-r--r-- | pkg/api/api0/client.go | 141 | ||||
-rw-r--r-- | pkg/atlas/config.go | 12 | ||||
-rw-r--r-- | pkg/atlas/server.go | 22 |
4 files changed, 126 insertions, 56 deletions
diff --git a/pkg/api/api0/api.go b/pkg/api/api0/api.go index 1c2683f..3ed5999 100644 --- a/pkg/api/api0/api.go +++ b/pkg/api/api0/api.go @@ -43,8 +43,11 @@ type Handler struct { // PdataStorage stores player data. It must be non-nil. PdataStorage PdataStorage - // OriginAuthMgr manages Origin nucleus tokens (used for checking - // usernames). If not provided, usernames will not be updated. + // UsernameSource configures the source to use for usernames. + UsernameSource UsernameSource + + // OriginAuthMgr, if provided, manages Origin nucleus tokens for checking + // usernames. OriginAuthMgr *origin.AuthMgr // CleanBadWords is used to filter bad words from server names and diff --git a/pkg/api/api0/client.go b/pkg/api/api0/client.go index b64659a..9b1408f 100644 --- a/pkg/api/api0/client.go +++ b/pkg/api/api0/client.go @@ -18,6 +18,17 @@ import ( "github.com/rs/zerolog/hlog" ) +// UsernameSource determines where to get player in-game usernames from. +type UsernameSource string + +const ( + // Don't get usernames. + UsernameSourceNone UsernameSource = "" + + // Get the username from the Origin API. + UsernameSourceOrigin UsernameSource = "origin" +) + type MainMenuPromos struct { NewInfo MainMenuPromosNew `json:"newInfo"` LargeButton MainMenuPromosButtonLarge `json:"largeButton"` @@ -186,56 +197,7 @@ func (h *Handler) handleClientOriginAuth(w http.ResponseWriter, r *http.Request) } } - var username string - if h.OriginAuthMgr != nil { - originStart := time.Now() - 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 - h.m().client_originauth_origin_username_lookup_calls_total.success.Inc() - } else { - notfound = true - h.m().client_originauth_origin_username_lookup_calls_total.notfound.Inc() - } - } 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 - h.m().client_originauth_origin_username_lookup_calls_total.success.Inc() - } else { - notfound = true - h.m().client_originauth_origin_username_lookup_calls_total.notfound.Inc() - } - } - } else if ours { - hlog.FromRequest(r).Error(). - Err(err). - Msgf("origin auth token refresh failure") - h.m().client_originauth_origin_username_lookup_calls_total.fail_authtok_refresh.Inc() - } - } else if !errors.Is(err, context.Canceled) { - hlog.FromRequest(r).Error(). - Err(err). - Msgf("failed to get origin user info") - h.m().client_originauth_origin_username_lookup_calls_total.fail_other_error.Inc() - } - 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") - h.m().client_originauth_origin_username_lookup_calls_total.fail_authtok_refresh.Inc() - } - h.m().client_originauth_origin_username_lookup_duration_seconds.UpdateDuration(originStart) - } + username := h.lookupUsername(r, uid) // 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 @@ -258,7 +220,7 @@ func (h *Handler) handleClientOriginAuth(w http.ResponseWriter, r *http.Request) } if acct != nil && username != "" && acct.Username != username { - hlog.FromRequest(r).Info().Uint64("uid", acct.UID).Str("username", username).Str("prev_username", acct.Username).Msg("got updated username from origin") + hlog.FromRequest(r).Info().Uint64("uid", acct.UID).Str("username", username).Str("prev_username", acct.Username).Msg("got updated username") } if acct == nil { acct = &Account{ @@ -306,6 +268,83 @@ func (h *Handler) handleClientOriginAuth(w http.ResponseWriter, r *http.Request) }) } +// lookupUsername gets the username for uid according to the configured +// UsernameSource, returning an empty string if not found or on error. +func (h *Handler) lookupUsername(r *http.Request, uid uint64) (username string) { + switch h.UsernameSource { + case UsernameSourceNone: + break + case UsernameSourceOrigin: + username, _ = h.lookupUsernameOrigin(r, uid) + default: + hlog.FromRequest(r).Error(). + Msgf("unknown username source %q", h.UsernameSource) + } + return +} + +// lookupUsernameOrigin gets the username for uid from the Origin API, returning +// an empty string if a username does not exist for the uid, and false on error. +func (h *Handler) lookupUsernameOrigin(r *http.Request, uid uint64) (username string, ok bool) { + if h.OriginAuthMgr == nil { + hlog.FromRequest(r).Error(). + Str("username_source", "origin"). + Msgf("no origin auth available for username lookup") + return + } + originStart := time.Now() + if tok, ours, err := h.OriginAuthMgr.OriginAuth(false); err == nil { + if ui, err := origin.GetUserInfo(r.Context(), tok, uid); err == nil { + if len(ui) == 1 { + username = ui[0].EAID + h.m().client_originauth_origin_username_lookup_calls_total.success.Inc() + } else { + h.m().client_originauth_origin_username_lookup_calls_total.notfound.Inc() + } + ok = 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 + h.m().client_originauth_origin_username_lookup_calls_total.success.Inc() + } else { + h.m().client_originauth_origin_username_lookup_calls_total.notfound.Inc() + } + ok = true + } + } else if ours { + hlog.FromRequest(r).Error(). + Err(err). + Str("username_source", "origin"). + Msgf("origin auth token refresh failure") + h.m().client_originauth_origin_username_lookup_calls_total.fail_authtok_refresh.Inc() + } + } else if !errors.Is(err, context.Canceled) { + hlog.FromRequest(r).Error(). + Err(err). + Str("username_source", "origin"). + Msgf("failed to get origin user info") + h.m().client_originauth_origin_username_lookup_calls_total.fail_other_error.Inc() + } + if username == "" && ok { + hlog.FromRequest(r).Warn(). + Err(err). + Uint64("uid", uid). + Str("username_source", "origin"). + Msgf("no origin username found for uid") + } + } else if ours { + hlog.FromRequest(r).Error(). + Err(err). + Str("username_source", "origin"). + Msgf("origin auth token refresh failure") + h.m().client_originauth_origin_username_lookup_calls_total.fail_authtok_refresh.Inc() + } + h.m().client_originauth_origin_username_lookup_duration_seconds.UpdateDuration(originStart) + return +} + func (h *Handler) handleClientAuthWithServer(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodOptions && r.Method != http.MethodPost { h.m().client_authwithserver_requests_total.http_method_not_allowed.Inc() diff --git a/pkg/atlas/config.go b/pkg/atlas/config.go index d519094..2829fce 100644 --- a/pkg/atlas/config.go +++ b/pkg/atlas/config.go @@ -160,9 +160,15 @@ type Config struct { // API0_MinimumLauncherVersion. API0_MainMenuPromos_UpdateNeeded string `env:"ATLAS_API0_MAINMENUPROMOS_UPDATENEEDED=none"` - // The email address to use for Origin login. If not provided, usernames are - // not resolved during authentication. If it begins with @, it is treated - // as the name of a systemd credential to load. + // Sets the source used for resolving usernames. If not specified, "origin" + // is used if OriginEmail is provided, otherwise, "none" is used. + // - none (don't get usernames) + // - origin (get the username from the Origin API) + UsernameSource string `env:"ATLAS_USERNAMESOURCE"` + + // The email address to use for Origin login. If not provided, the Origin + // API will not be used. If it begins with @, it is treated as the name of a + // systemd credential to load. OriginEmail string `env:"ATLAS_ORIGIN_EMAIL" sdcreds:"load,trimspace"` // The password for Origin login. If it begins with @, it is treated as the diff --git a/pkg/atlas/server.go b/pkg/atlas/server.go index 145bb45..40d2abf 100644 --- a/pkg/atlas/server.go +++ b/pkg/atlas/server.go @@ -299,6 +299,11 @@ func NewServer(c *Config) (*Server, error) { } else { return nil, fmt.Errorf("initialize origin auth: %w", err) } + if x, err := configureUsernameSource(c); err == nil { + s.API0.UsernameSource = x + } else { + return nil, fmt.Errorf("initialize username lookup: %w", err) + } if astore, err := configureAccountStorage(c); err == nil { s.API0.AccountStorage = astore } else { @@ -610,6 +615,23 @@ func configureOrigin(c *Config, l zerolog.Logger) (*origin.AuthMgr, error) { return mgr, nil } +func configureUsernameSource(c *Config) (api0.UsernameSource, error) { + switch typ := c.UsernameSource; typ { + case "none": + return api0.UsernameSourceNone, nil + case "origin": + return api0.UsernameSourceOrigin, nil + case "": + // backwards compat + if c.OriginEmail != "" { + return api0.UsernameSourceOrigin, nil + } + return api0.UsernameSourceNone, nil + default: + return "", fmt.Errorf("unknown source %q", typ) + } +} + func configureAccountStorage(c *Config) (api0.AccountStorage, error) { switch typ, arg, _ := strings.Cut(c.API0_Storage_Accounts, ":"); typ { case "memory": |