diff --git a/service/user_service.go b/service/user_service.go index 79d833e..40bad18 100644 --- a/service/user_service.go +++ b/service/user_service.go @@ -394,29 +394,35 @@ func (u *userServiceImpl) QueryProfile(profileId uuid.UUID, unsigned bool, textu func (u *userServiceImpl) ProfileKey(accessToken string) (resp *ProfileKeyResponse, err error) { token, ok := u.tokenService.GetToken(accessToken) + var profileId uuid.UUID if ok && token.GetAvailableLevel() == model.Valid { - resp = new(ProfileKeyResponse) - now := time.Now().UTC() - resp.RefreshedAfter = now - resp.ExpiresAt = now.Add(10 * time.Minute) - keyPair, err := u.getProfileKey(token.SelectedProfile.Id) - if err != nil { - return nil, err - } - resp.KeyPair = keyPair - signStr := fmt.Sprintf("%d%s", resp.ExpiresAt.UnixMilli(), keyPair.PublicKey) - sign, err := util.Sign(signStr) - if err != nil { - return nil, err - } - resp.PublicKeySignature = sign - resp.PublicKeySignatureV2 = sign + profileId = token.SelectedProfile.Id } else { - err = util.PostForString("https://api.minecraftservices.com/player/certificates", accessToken, []byte(""), resp) + id, _, err := util.ParseOfficialToken(accessToken) + if err != nil { + return nil, err + } + profileId, err = util.ToUUID(id) if err != nil { return nil, err } } + resp = new(ProfileKeyResponse) + now := time.Now().UTC() + resp.RefreshedAfter = now + resp.ExpiresAt = now.Add(10 * time.Minute) + keyPair, err := u.getProfileKey(profileId) + if err != nil { + return nil, err + } + resp.KeyPair = keyPair + signStr := fmt.Sprintf("%d%s", resp.ExpiresAt.UnixMilli(), keyPair.PublicKey) + sign, err := util.Sign(signStr) + if err != nil { + return nil, err + } + resp.PublicKeySignature = sign + resp.PublicKeySignatureV2 = sign return resp, nil } diff --git a/util/token_utils.go b/util/token_utils.go new file mode 100644 index 0000000..77e3d96 --- /dev/null +++ b/util/token_utils.go @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2025. Gardel and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package util + +import ( + "encoding/base64" + "encoding/json" + "errors" + "strings" +) + +type officialTokenPayload struct { + Xuid string `json:"xuid"` + Agg string `json:"agg"` + Sub string `json:"sub"` + Auth string `json:"auth"` + Ns string `json:"ns"` + Roles []interface{} `json:"roles"` + Iss string `json:"iss"` + Flags []string `json:"flags"` + Profiles struct { + Mc string `json:"mc"` + } `json:"profiles"` + Platform string `json:"platform"` + Pfd []struct { + Type string `json:"type"` + Id string `json:"id"` + Name string `json:"name"` + } `json:"pfd"` + Nbf int `json:"nbf"` + Exp int `json:"exp"` + Iat int `json:"iat"` +} + +func ParseOfficialToken(token string) (id, name string, err error) { + firstDot := strings.IndexRune(token, '.') + if firstDot == -1 { + return id, name, errors.New("invalid token") + } + secondDot := 1 + firstDot + strings.IndexRune(token[firstDot+1:], '.') + if secondDot == -1 { + return id, name, errors.New("invalid token") + } + jsonBase64 := token[firstDot+1 : secondDot] + jsonDecoded, err := base64.RawURLEncoding.DecodeString(jsonBase64) + if err != nil { + return id, name, err + } + payload := officialTokenPayload{} + err = json.Unmarshal(jsonDecoded, &payload) + if err != nil { + return id, name, err + } + if payload.Pfd == nil || len(payload.Pfd) == 0 { + return id, name, errors.New("invalid token") + } + id = payload.Pfd[0].Id + name = payload.Pfd[0].Name + return id, name, nil +}