mirror of
https://github.com/elyby/chrly.git
synced 2025-05-31 14:11:51 +05:30
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfe8fea3f7 | ||
|
|
27c7b79b32 |
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
- Adjusted Mojang usernames filter to be stickier according to their docs
|
||||
- `/profile/{username}` endpoint now returns the correct signature for the custom property as well.
|
||||
|
||||
### Changed
|
||||
- Bumped Go version to 1.21.
|
||||
|
||||
@@ -243,11 +243,12 @@ Response example:
|
||||
"properties": [
|
||||
{
|
||||
"name": "textures",
|
||||
"signature": "signature value",
|
||||
"signature": "textures signature value",
|
||||
"value": "base64 encoded value"
|
||||
},
|
||||
{
|
||||
"name": "chrly",
|
||||
"signature": "custom property signature value",
|
||||
"value": "how do you tame a horse in Minecraft?"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -106,19 +106,24 @@ func newSkinsystemHandler(
|
||||
capesRepository CapesRepository,
|
||||
mojangTexturesProvider MojangTexturesProvider,
|
||||
texturesSigner TexturesSigner,
|
||||
) *mux.Router {
|
||||
) (*mux.Router, error) {
|
||||
config.SetDefault("textures.extra_param_name", "chrly")
|
||||
config.SetDefault("textures.extra_param_value", "how do you tame a horse in Minecraft?")
|
||||
|
||||
return (&Skinsystem{
|
||||
Emitter: emitter,
|
||||
SkinsRepo: skinsRepository,
|
||||
CapesRepo: capesRepository,
|
||||
MojangTexturesProvider: mojangTexturesProvider,
|
||||
TexturesSigner: texturesSigner,
|
||||
TexturesExtraParamName: config.GetString("textures.extra_param_name"),
|
||||
TexturesExtraParamValue: config.GetString("textures.extra_param_value"),
|
||||
}).Handler()
|
||||
app, err := NewSkinsystem(
|
||||
emitter,
|
||||
skinsRepository,
|
||||
capesRepository,
|
||||
mojangTexturesProvider,
|
||||
texturesSigner,
|
||||
config.GetString("textures.extra_param_name"),
|
||||
config.GetString("textures.extra_param_value"),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return app.Handler(), nil
|
||||
}
|
||||
|
||||
func newApiHandler(skinsRepository SkinsRepository) *mux.Router {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -43,12 +44,39 @@ type TexturesSigner interface {
|
||||
|
||||
type Skinsystem struct {
|
||||
Emitter
|
||||
SkinsRepo SkinsRepository
|
||||
CapesRepo CapesRepository
|
||||
MojangTexturesProvider MojangTexturesProvider
|
||||
TexturesSigner TexturesSigner
|
||||
TexturesExtraParamName string
|
||||
TexturesExtraParamValue string
|
||||
SkinsRepo SkinsRepository
|
||||
CapesRepo CapesRepository
|
||||
MojangTexturesProvider MojangTexturesProvider
|
||||
TexturesSigner TexturesSigner
|
||||
TexturesExtraParamName string
|
||||
TexturesExtraParamValue string
|
||||
texturesExtraParamSignature string
|
||||
}
|
||||
|
||||
func NewSkinsystem(
|
||||
emitter Emitter,
|
||||
skinsRepo SkinsRepository,
|
||||
capesRepo CapesRepository,
|
||||
mojangTexturesProvider MojangTexturesProvider,
|
||||
texturesSigner TexturesSigner,
|
||||
texturesExtraParamName string,
|
||||
texturesExtraParamValue string,
|
||||
) (*Skinsystem, error) {
|
||||
texturesExtraParamSignature, err := texturesSigner.SignTextures(texturesExtraParamValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to generate signature for textures extra param: %w", err)
|
||||
}
|
||||
|
||||
return &Skinsystem{
|
||||
Emitter: emitter,
|
||||
SkinsRepo: skinsRepo,
|
||||
CapesRepo: capesRepo,
|
||||
MojangTexturesProvider: mojangTexturesProvider,
|
||||
TexturesSigner: texturesSigner,
|
||||
TexturesExtraParamName: texturesExtraParamName,
|
||||
TexturesExtraParamValue: texturesExtraParamValue,
|
||||
texturesExtraParamSignature: texturesExtraParamSignature,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type profile struct {
|
||||
@@ -190,8 +218,15 @@ func (ctx *Skinsystem) profileHandler(response http.ResponseWriter, request *htt
|
||||
}
|
||||
|
||||
if profile == nil {
|
||||
response.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
forceResponseWithUuid := request.URL.Query().Get("onUnknownProfileRespondWithUuid")
|
||||
if forceResponseWithUuid == "" {
|
||||
response.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
profile = createEmptyProfile()
|
||||
profile.Id = formatUuid(forceResponseWithUuid)
|
||||
profile.Username = parseUsername(mux.Vars(request)["username"])
|
||||
}
|
||||
|
||||
texturesPropContent := &mojang.TexturesProp{
|
||||
@@ -208,14 +243,20 @@ func (ctx *Skinsystem) profileHandler(response http.ResponseWriter, request *htt
|
||||
Name: "textures",
|
||||
Value: texturesPropEncodedValue,
|
||||
}
|
||||
customProp := &mojang.Property{
|
||||
Name: ctx.TexturesExtraParamName,
|
||||
Value: ctx.TexturesExtraParamValue,
|
||||
}
|
||||
|
||||
if request.URL.Query().Get("unsigned") == "false" {
|
||||
signature, err := ctx.TexturesSigner.SignTextures(texturesProp.Value)
|
||||
customProp.Signature = ctx.texturesExtraParamSignature
|
||||
|
||||
texturesSignature, err := ctx.TexturesSigner.SignTextures(texturesProp.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
texturesProp.Signature = signature
|
||||
texturesProp.Signature = texturesSignature
|
||||
}
|
||||
|
||||
profileResponse := &mojang.SignedTexturesResponse{
|
||||
@@ -223,10 +264,7 @@ func (ctx *Skinsystem) profileHandler(response http.ResponseWriter, request *htt
|
||||
Name: profile.Username,
|
||||
Props: []*mojang.Property{
|
||||
texturesProp,
|
||||
{
|
||||
Name: ctx.TexturesExtraParamName,
|
||||
Value: ctx.TexturesExtraParamValue,
|
||||
},
|
||||
customProp,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -274,12 +312,10 @@ func (ctx *Skinsystem) getProfile(request *http.Request, proxy bool) (*profile,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
profile := &profile{
|
||||
Textures: &mojang.TexturesResponse{}, // Field must be initialized to avoid "null" after json encoding
|
||||
}
|
||||
profile := createEmptyProfile()
|
||||
|
||||
if skin != nil {
|
||||
profile.Id = strings.Replace(skin.Uuid, "-", "", -1)
|
||||
profile.Id = formatUuid(skin.Uuid)
|
||||
profile.Username = skin.Username
|
||||
}
|
||||
|
||||
@@ -354,6 +390,16 @@ func (ctx *Skinsystem) getProfile(request *http.Request, proxy bool) (*profile,
|
||||
return profile, nil
|
||||
}
|
||||
|
||||
func createEmptyProfile() *profile {
|
||||
return &profile{
|
||||
Textures: &mojang.TexturesResponse{}, // Field must be initialized to avoid "null" after json encoding
|
||||
}
|
||||
}
|
||||
|
||||
func formatUuid(uuid string) string {
|
||||
return strings.Replace(uuid, "-", "", -1)
|
||||
}
|
||||
|
||||
func parseUsername(username string) string {
|
||||
return strings.TrimSuffix(username, ".png")
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -141,15 +142,17 @@ func (suite *skinsystemTestSuite) SetupTest() {
|
||||
suite.TexturesSigner = &texturesSignerMock{}
|
||||
suite.Emitter = &emitterMock{}
|
||||
|
||||
suite.App = &Skinsystem{
|
||||
SkinsRepo: suite.SkinsRepository,
|
||||
CapesRepo: suite.CapesRepository,
|
||||
MojangTexturesProvider: suite.MojangTexturesProvider,
|
||||
TexturesSigner: suite.TexturesSigner,
|
||||
Emitter: suite.Emitter,
|
||||
TexturesExtraParamName: "texturesParamName",
|
||||
TexturesExtraParamValue: "texturesParamValue",
|
||||
}
|
||||
suite.TexturesSigner.On("SignTextures", "texturesParamValue").Times(1).Return("texturesParamSignature", nil)
|
||||
|
||||
suite.App, _ = NewSkinsystem(
|
||||
suite.Emitter,
|
||||
suite.SkinsRepository,
|
||||
suite.CapesRepository,
|
||||
suite.MojangTexturesProvider,
|
||||
suite.TexturesSigner,
|
||||
"texturesParamName",
|
||||
"texturesParamValue",
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *skinsystemTestSuite) TearDownTest() {
|
||||
@@ -742,11 +745,12 @@ func (suite *skinsystemTestSuite) TestSignedTextures() {
|
||||
***************************/
|
||||
|
||||
type profileTestCase struct {
|
||||
Name string
|
||||
Signed bool
|
||||
BeforeTest func(suite *skinsystemTestSuite)
|
||||
PanicErr string
|
||||
AfterTest func(suite *skinsystemTestSuite, response *http.Response)
|
||||
Name string
|
||||
Signed bool
|
||||
ForceResponse string
|
||||
BeforeTest func(suite *skinsystemTestSuite)
|
||||
PanicErr string
|
||||
AfterTest func(suite *skinsystemTestSuite, response *http.Response)
|
||||
}
|
||||
|
||||
var profileTestsCases = []*profileTestCase{
|
||||
@@ -799,6 +803,7 @@ var profileTestsCases = []*profileTestCase{
|
||||
},
|
||||
{
|
||||
"name": "texturesParamName",
|
||||
"signature": "texturesParamSignature",
|
||||
"value": "texturesParamValue"
|
||||
}
|
||||
]
|
||||
@@ -828,6 +833,7 @@ var profileTestsCases = []*profileTestCase{
|
||||
},
|
||||
{
|
||||
"name": "texturesParamName",
|
||||
"signature": "texturesParamSignature",
|
||||
"value": "texturesParamValue"
|
||||
}
|
||||
]
|
||||
@@ -857,6 +863,7 @@ var profileTestsCases = []*profileTestCase{
|
||||
},
|
||||
{
|
||||
"name": "texturesParamName",
|
||||
"signature": "texturesParamSignature",
|
||||
"value": "texturesParamValue"
|
||||
}
|
||||
]
|
||||
@@ -890,6 +897,7 @@ var profileTestsCases = []*profileTestCase{
|
||||
},
|
||||
{
|
||||
"name": "texturesParamName",
|
||||
"signature": "texturesParamSignature",
|
||||
"value": "texturesParamValue"
|
||||
}
|
||||
]
|
||||
@@ -923,6 +931,7 @@ var profileTestsCases = []*profileTestCase{
|
||||
},
|
||||
{
|
||||
"name": "texturesParamName",
|
||||
"signature": "texturesParamSignature",
|
||||
"value": "texturesParamValue"
|
||||
}
|
||||
]
|
||||
@@ -952,6 +961,7 @@ var profileTestsCases = []*profileTestCase{
|
||||
},
|
||||
{
|
||||
"name": "texturesParamName",
|
||||
"signature": "texturesParamSignature",
|
||||
"value": "texturesParamValue"
|
||||
}
|
||||
]
|
||||
@@ -981,6 +991,7 @@ var profileTestsCases = []*profileTestCase{
|
||||
},
|
||||
{
|
||||
"name": "texturesParamName",
|
||||
"signature": "texturesParamSignature",
|
||||
"value": "texturesParamValue"
|
||||
}
|
||||
]
|
||||
@@ -1010,6 +1021,7 @@ var profileTestsCases = []*profileTestCase{
|
||||
},
|
||||
{
|
||||
"name": "texturesParamName",
|
||||
"signature": "texturesParamSignature",
|
||||
"value": "texturesParamValue"
|
||||
}
|
||||
]
|
||||
@@ -1028,6 +1040,37 @@ var profileTestsCases = []*profileTestCase{
|
||||
suite.Equal("", string(body))
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Username not exists and Mojang profile unavailable, but there is a forceResponse param",
|
||||
ForceResponse: "a12e41a4-e8e5-4503-987e-0adacf72ab93",
|
||||
Signed: true,
|
||||
BeforeTest: func(suite *skinsystemTestSuite) {
|
||||
suite.SkinsRepository.On("FindSkinByUsername", "mock_username").Return(nil, nil)
|
||||
suite.MojangTexturesProvider.On("GetForUsername", "mock_username").Once().Return(nil, nil)
|
||||
suite.TexturesSigner.On("SignTextures", mock.Anything).Return("chrly signature", nil)
|
||||
},
|
||||
AfterTest: func(suite *skinsystemTestSuite, response *http.Response) {
|
||||
suite.Equal(200, response.StatusCode)
|
||||
suite.Equal("application/json", response.Header.Get("Content-Type"))
|
||||
body, _ := ioutil.ReadAll(response.Body)
|
||||
suite.JSONEq(`{
|
||||
"id": "a12e41a4e8e54503987e0adacf72ab93",
|
||||
"name": "mock_username",
|
||||
"properties": [
|
||||
{
|
||||
"name": "textures",
|
||||
"signature": "chrly signature",
|
||||
"value": "eyJ0aW1lc3RhbXAiOjE2MTQyMTQyMjMwMDAsInByb2ZpbGVJZCI6ImExMmU0MWE0ZThlNTQ1MDM5ODdlMGFkYWNmNzJhYjkzIiwicHJvZmlsZU5hbWUiOiJtb2NrX3VzZXJuYW1lIiwidGV4dHVyZXMiOnt9fQ=="
|
||||
},
|
||||
{
|
||||
"name": "texturesParamName",
|
||||
"signature": "texturesParamSignature",
|
||||
"value": "texturesParamValue"
|
||||
}
|
||||
]
|
||||
}`, string(body))
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Username not exists and Mojang textures proxy returned an error",
|
||||
BeforeTest: func(suite *skinsystemTestSuite) {
|
||||
@@ -1060,12 +1103,18 @@ func (suite *skinsystemTestSuite) TestProfile() {
|
||||
suite.RunSubTest(testCase.Name, func() {
|
||||
testCase.BeforeTest(suite)
|
||||
|
||||
url := "http://chrly/profile/mock_username"
|
||||
u, _ := url.Parse("http://chrly/profile/mock_username")
|
||||
q := make(url.Values)
|
||||
if testCase.Signed {
|
||||
url += "?unsigned=false"
|
||||
q.Set("unsigned", "false")
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", url, nil)
|
||||
if testCase.ForceResponse != "" {
|
||||
q.Set("onUnknownProfileRespondWithUuid", testCase.ForceResponse)
|
||||
}
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
req := httptest.NewRequest("GET", u.String(), nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
if testCase.PanicErr != "" {
|
||||
|
||||
Reference in New Issue
Block a user