diff options
-rw-r--r-- | pkg/stryder/stryder.go | 20 | ||||
-rw-r--r-- | pkg/stryder/stryder_test.go | 35 |
2 files changed, 47 insertions, 8 deletions
diff --git a/pkg/stryder/stryder.go b/pkg/stryder/stryder.go index 35bffbb..b73d608 100644 --- a/pkg/stryder/stryder.go +++ b/pkg/stryder/stryder.go @@ -103,6 +103,26 @@ func nucleusAuth(r *http.Response) ([]byte, error) { return buf, nil } +// NucleusAuthUsername extracts the username field from the nucleus auth +// response (since October 2, 2023). This field is usually empty for +// unsuccessful responses, but is always present (if not, an error will be +// returned). If resp is empty or invalid, an error will be returned. +func NucleusAuthUsername(resp []byte) (string, error) { + var obj struct { + Username *string `json:"userName,omitempty"` + } + if len(resp) == 0 { + return "", fmt.Errorf("empty or missing nucleus auth response") + } + if err := json.Unmarshal(resp, &obj); err != nil { + return "", fmt.Errorf("%w: invalid nucleus auth response json: %v", ErrStryder, err) + } + if obj.Username == nil { + return "", fmt.Errorf("missing userName field in nucleus auth response %q", string(resp)) + } + return *obj.Username, nil +} + func castOr[T any](v any, d T) T { if x, ok := v.(T); ok { return x diff --git a/pkg/stryder/stryder_test.go b/pkg/stryder/stryder_test.go index 23ce717..fe5594f 100644 --- a/pkg/stryder/stryder_test.go +++ b/pkg/stryder/stryder_test.go @@ -10,16 +10,18 @@ import ( ) func TestNucleusAuth(t *testing.T) { - testNucleusAuth(t, "Success", `{"token":"...","hasOnlineAccess":"1","expiry":"14399","storeUri":"https://www.origin.com/store/titanfall/titanfall-2/standard-edition"}`, nil) - testNucleusAuth(t, "NoMultiplayer", `{"token":"...","hasOnlineAccess":"0","expiry":"14399","storeUri":"https://www.origin.com/store/titanfall/titanfall-2/standard-edition"}`, ErrMultiplayerNotAllowed) - testNucleusAuth(t, "InvalidToken", `{"success": false, "status": "400", "error": "{"error":"invalid_grant","error_description":"code is invalid","code":100100}"}`, ErrInvalidToken) - testNucleusAuth(t, "StryderBadRequest", `{"success": false, "status": "400", "error": "{"error":"invalid_request","error_description":"code is not issued to this environment","code":100119}"}`, ErrStryder) - testNucleusAuth(t, "StryderBadEndpoint", ``, ErrStryder) - testNucleusAuth(t, "StryderGoAway", "Go away.\n", ErrStryder) - testNucleusAuth(t, "InvalidGame", `{"token":"...","hasOnlineAccess":"1","expiry":"1234","storeUri":"https://www.origin.com/store/titanfall/titanfall-3/future-edition"}`, ErrInvalidGame) // never seen this, but test it + testNucleusAuth(t, "Success", `{"token":"...","hasOnlineAccess":"1","expiry":"14399","storeUri":"https://www.origin.com/store/titanfall/titanfall-2/standard-edition"}`, nil, nil) + testNucleusAuth(t, "SuccessNew", `{"token":"...","hasOnlineAccess":"1","expiry":"14399","storeUri":"https://www.origin.com/store/titanfall/titanfall-2/standard-edition","userName":"test"}`, strPtr("test"), nil) + testNucleusAuth(t, "NoMultiplayer", `{"token":"NO_ONLINE_ACCESS","hasOnlineAccess":"0","expiry":"14399","storeUri":"https://www.origin.com/store/titanfall/titanfall-2/standard-edition"}`, nil, ErrMultiplayerNotAllowed) + testNucleusAuth(t, "NoMultiplayerNew", `{"token":"NO_ONLINE_ACCESS","hasOnlineAccess":"0","expiry":"14399","storeUri":"https://www.origin.com/store/titanfall/titanfall-2/standard-edition","userName":""}`, strPtr(""), ErrMultiplayerNotAllowed) + testNucleusAuth(t, "InvalidToken", `{"success": false, "status": "400", "error": "{"error":"invalid_grant","error_description":"code is invalid","code":100100}"}`, nil, ErrInvalidToken) + testNucleusAuth(t, "StryderBadRequest", `{"success": false, "status": "400", "error": "{"error":"invalid_request","error_description":"code is not issued to this environment","code":100119}"}`, nil, ErrStryder) + testNucleusAuth(t, "StryderBadEndpoint", ``, nil, ErrStryder) + testNucleusAuth(t, "StryderGoAway", "Go away.\n", nil, ErrStryder) + testNucleusAuth(t, "InvalidGame", `{"token":"...","hasOnlineAccess":"1","expiry":"1234","storeUri":"https://www.origin.com/store/titanfall/titanfall-3/future-edition"}`, nil, ErrInvalidGame) // never seen this, but test it } -func testNucleusAuth(t *testing.T, name, resp string, res error) { +func testNucleusAuth(t *testing.T, name, resp string, username *string, res error) { t.Run(name, func(t *testing.T) { buf, err := nucleusAuth(&http.Response{ Status: "200 OK", @@ -38,5 +40,22 @@ func testNucleusAuth(t *testing.T, name, resp string, res error) { t.Errorf("expected error %q, got %q", res, err) } } + su, err := NucleusAuthUsername(buf) + if username == nil { + if err == nil { + t.Errorf("expected username error for response %q, got none", string(buf)) + } + } else { + if err != nil { + t.Errorf("unexpected username error for response %q: %v", string(buf), err) + } + if su != *username { + t.Errorf("expected username %q for response %q, got %q", *username, string(buf), su) + } + } }) } + +func strPtr(x string) *string { + return &x +} |