add support for 1.19 "Secure Chat Signing"
This commit is contained in:
		
							
								
								
									
										10
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								main.go
									
									
									
									
									
								
							@@ -104,6 +104,7 @@ func main() {
 | 
				
			|||||||
	serverMeta.Meta.ImplementationName = meta.ImplementationName
 | 
						serverMeta.Meta.ImplementationName = meta.ImplementationName
 | 
				
			||||||
	serverMeta.Meta.ImplementationVersion = meta.ImplementationVersion
 | 
						serverMeta.Meta.ImplementationVersion = meta.ImplementationVersion
 | 
				
			||||||
	serverMeta.Meta.FeatureNoMojangNamespace = true
 | 
						serverMeta.Meta.FeatureNoMojangNamespace = true
 | 
				
			||||||
 | 
						serverMeta.Meta.FeatureEnableProfileKey = true
 | 
				
			||||||
	serverMeta.Meta.Links.Homepage = meta.SkinRootUrl + "/profile/user.html"
 | 
						serverMeta.Meta.Links.Homepage = meta.SkinRootUrl + "/profile/user.html"
 | 
				
			||||||
	serverMeta.Meta.Links.Register = meta.SkinRootUrl + "/profile/index.html"
 | 
						serverMeta.Meta.Links.Register = meta.SkinRootUrl + "/profile/index.html"
 | 
				
			||||||
	serverMeta.SkinDomains = meta.SkinDomains
 | 
						serverMeta.SkinDomains = meta.SkinDomains
 | 
				
			||||||
@@ -160,15 +161,18 @@ func checkRsaKeyFile(privateKeyPath string, publicKeyPath string) {
 | 
				
			|||||||
			log.Fatalln("无法序列化 RSA 密钥", err)
 | 
								log.Fatalln("无法序列化 RSA 密钥", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		err = pem.Encode(privatePem, &pem.Block{
 | 
							err = pem.Encode(privatePem, &pem.Block{
 | 
				
			||||||
			Type:  "PRIVATE",
 | 
								Type:  "PRIVATE KEY",
 | 
				
			||||||
			Bytes: privateKeyBytes,
 | 
								Bytes: privateKeyBytes,
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			log.Fatalln("无法写入私钥文件", err)
 | 
								log.Fatalln("无法写入私钥文件", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		publicKeyBytes := x509.MarshalPKCS1PublicKey(&privateKey.PublicKey)
 | 
							publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Fatalln("无法序列化 RSA 公钥", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		err = pem.Encode(publicPem, &pem.Block{
 | 
							err = pem.Encode(publicPem, &pem.Block{
 | 
				
			||||||
			Type:  "PUBLIC",
 | 
								Type:  "PUBLIC KEY",
 | 
				
			||||||
			Bytes: publicKeyBytes,
 | 
								Bytes: publicKeyBytes,
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,6 +33,7 @@ type MetaInfo struct {
 | 
				
			|||||||
	FeatureNonEmailLogin     bool `json:"feature.non_email_login,omitempty"`
 | 
						FeatureNonEmailLogin     bool `json:"feature.non_email_login,omitempty"`
 | 
				
			||||||
	FeatureLegacySkinApi     bool `json:"feature.legacy_skin_api,omitempty"`
 | 
						FeatureLegacySkinApi     bool `json:"feature.legacy_skin_api,omitempty"`
 | 
				
			||||||
	FeatureNoMojangNamespace bool `json:"feature.no_mojang_namespace,omitempty"`
 | 
						FeatureNoMojangNamespace bool `json:"feature.no_mojang_namespace,omitempty"`
 | 
				
			||||||
 | 
						FeatureEnableProfileKey  bool `json:"feature.enable_profile_key,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ServerMeta struct {
 | 
					type ServerMeta struct {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -76,4 +76,5 @@ func InitRouters(router *gin.Engine, db *gorm.DB, meta *ServerMeta, skinRootUrl
 | 
				
			|||||||
		api.DELETE("/user/profile/:uuid/:textureType", textureRouter.DeleteTexture)
 | 
							api.DELETE("/user/profile/:uuid/:textureType", textureRouter.DeleteTexture)
 | 
				
			||||||
		api.GET("/users/profiles/minecraft/:username", userRouter.UsernameToUUID)
 | 
							api.GET("/users/profiles/minecraft/:username", userRouter.UsernameToUUID)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						router.POST("/minecraftservices/player/certificates", userRouter.ProfileKey)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,6 +37,7 @@ type UserRouter interface {
 | 
				
			|||||||
	UsernameToUUID(c *gin.Context)
 | 
						UsernameToUUID(c *gin.Context)
 | 
				
			||||||
	QueryUUIDs(c *gin.Context)
 | 
						QueryUUIDs(c *gin.Context)
 | 
				
			||||||
	QueryProfile(c *gin.Context)
 | 
						QueryProfile(c *gin.Context)
 | 
				
			||||||
 | 
						ProfileKey(c *gin.Context)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type userRouterImpl struct {
 | 
					type userRouterImpl struct {
 | 
				
			||||||
@@ -263,3 +264,18 @@ func (u *userRouterImpl) QueryProfile(c *gin.Context) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	c.JSON(http.StatusOK, response)
 | 
						c.JSON(http.StatusOK, response)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (u *userRouterImpl) ProfileKey(c *gin.Context) {
 | 
				
			||||||
 | 
						bearerToken := c.GetHeader("Authorization")
 | 
				
			||||||
 | 
						if len(bearerToken) < 8 {
 | 
				
			||||||
 | 
							c.AbortWithStatusJSON(http.StatusUnauthorized, util.NewForbiddenOperationError(util.MessageInvalidToken))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						accessToken := bearerToken[7:]
 | 
				
			||||||
 | 
						response, err := u.userService.ProfileKey(accessToken)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							util.HandleError(c, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						c.JSON(http.StatusOK, response)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -47,7 +47,11 @@ func NewSessionService(service TokenService) SessionService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (s *sessionStore) JoinServer(accessToken string, serverId string, selectedProfile string, ip string) error {
 | 
					func (s *sessionStore) JoinServer(accessToken string, serverId string, selectedProfile string, ip string) error {
 | 
				
			||||||
	token, ok := s.tokenService.GetToken(accessToken)
 | 
						token, ok := s.tokenService.GetToken(accessToken)
 | 
				
			||||||
	if ok && util.UnsignedString(token.SelectedProfile.Id) == selectedProfile {
 | 
						if ok {
 | 
				
			||||||
 | 
							if token.GetAvailableLevel() != model.Valid ||
 | 
				
			||||||
 | 
								util.UnsignedString(token.SelectedProfile.Id) != selectedProfile {
 | 
				
			||||||
 | 
								return util.NewForbiddenOperationError(util.MessageInvalidToken)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		session := model.NewAuthenticationSession(serverId, token, ip)
 | 
							session := model.NewAuthenticationSession(serverId, token, ip)
 | 
				
			||||||
		s.sessionCache.Add(serverId, &session)
 | 
							s.sessionCache.Add(serverId, &session)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,11 @@
 | 
				
			|||||||
package service
 | 
					package service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/rand"
 | 
				
			||||||
 | 
						"crypto/rsa"
 | 
				
			||||||
 | 
						"crypto/x509"
 | 
				
			||||||
 | 
						"encoding/pem"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"github.com/google/uuid"
 | 
						"github.com/google/uuid"
 | 
				
			||||||
	lru "github.com/hashicorp/golang-lru"
 | 
						lru "github.com/hashicorp/golang-lru"
 | 
				
			||||||
@@ -28,6 +33,7 @@ import (
 | 
				
			|||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"regexp"
 | 
						"regexp"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
	"yggdrasil-go/model"
 | 
						"yggdrasil-go/model"
 | 
				
			||||||
	"yggdrasil-go/util"
 | 
						"yggdrasil-go/util"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -43,6 +49,7 @@ type UserService interface {
 | 
				
			|||||||
	UsernameToUUID(username string) (*model.ProfileResponse, error)
 | 
						UsernameToUUID(username string) (*model.ProfileResponse, error)
 | 
				
			||||||
	QueryUUIDs(usernames []string) ([]model.ProfileResponse, error)
 | 
						QueryUUIDs(usernames []string) ([]model.ProfileResponse, error)
 | 
				
			||||||
	QueryProfile(profileId uuid.UUID, unsigned bool, textureBaseUrl string) (map[string]interface{}, error)
 | 
						QueryProfile(profileId uuid.UUID, unsigned bool, textureBaseUrl string) (map[string]interface{}, error)
 | 
				
			||||||
 | 
						ProfileKey(accessToken string) (*ProfileKeyResponse, error)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type LoginResponse struct {
 | 
					type LoginResponse struct {
 | 
				
			||||||
@@ -53,23 +60,43 @@ type LoginResponse struct {
 | 
				
			|||||||
	SelectedProfile   *model.ProfileResponse  `json:"selectedProfile"`
 | 
						SelectedProfile   *model.ProfileResponse  `json:"selectedProfile"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type userSrviceImpl struct {
 | 
					type ProfileKeyResponse struct {
 | 
				
			||||||
	tokenService  TokenService
 | 
						ExpiresAt            time.Time       `json:"expiresAt,omitempty"`
 | 
				
			||||||
	db            *gorm.DB
 | 
						KeyPair              *ProfileKeyPair `json:"keyPair,omitempty"`
 | 
				
			||||||
	limitLruCache *lru.Cache
 | 
						PublicKeySignature   string          `json:"publicKeySignature,omitempty"`
 | 
				
			||||||
 | 
						PublicKeySignatureV2 string          `json:"publicKeySignatureV2,omitempty"`
 | 
				
			||||||
 | 
						RefreshedAfter       time.Time       `json:"refreshedAfter,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ProfileKeyPair struct {
 | 
				
			||||||
 | 
						PrivateKey string `json:"privateKey,omitempty"`
 | 
				
			||||||
 | 
						PublicKey  string `json:"publicKey,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type userServiceImpl struct {
 | 
				
			||||||
 | 
						tokenService    TokenService
 | 
				
			||||||
 | 
						db              *gorm.DB
 | 
				
			||||||
 | 
						limitLruCache   *lru.Cache
 | 
				
			||||||
 | 
						profileKeyCache *lru.Cache
 | 
				
			||||||
 | 
						keyPairCh       chan ProfileKeyPair
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewUserService(tokenService TokenService, db *gorm.DB) UserService {
 | 
					func NewUserService(tokenService TokenService, db *gorm.DB) UserService {
 | 
				
			||||||
	cache, _ := lru.New(10000)
 | 
						cache0, _ := lru.New(10000)
 | 
				
			||||||
	userSrvice := userSrviceImpl{
 | 
						cache1, _ := lru.New(10000)
 | 
				
			||||||
		tokenService:  tokenService,
 | 
						ch := make(chan ProfileKeyPair, 100)
 | 
				
			||||||
		db:            db,
 | 
						userService := userServiceImpl{
 | 
				
			||||||
		limitLruCache: cache,
 | 
							tokenService:    tokenService,
 | 
				
			||||||
 | 
							db:              db,
 | 
				
			||||||
 | 
							limitLruCache:   cache0,
 | 
				
			||||||
 | 
							profileKeyCache: cache1,
 | 
				
			||||||
 | 
							keyPairCh:       ch,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return &userSrvice
 | 
						go userService.genKeyPair()
 | 
				
			||||||
 | 
						return &userService
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (u *userSrviceImpl) Register(username string, password string, profileName string) (*model.UserResponse, error) {
 | 
					func (u *userServiceImpl) Register(username string, password string, profileName string) (*model.UserResponse, error) {
 | 
				
			||||||
	var count int64
 | 
						var count int64
 | 
				
			||||||
	if err := u.db.Table("users").Where("email = ?", username).Count(&count).Error; err != nil {
 | 
						if err := u.db.Table("users").Where("email = ?", username).Count(&count).Error; err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
@@ -117,7 +144,7 @@ func isInvalidProfileName(name string) bool {
 | 
				
			|||||||
	//return name == "" || !name.matches("^[0-1a-zA-Z_]{2,16}$");
 | 
						//return name == "" || !name.matches("^[0-1a-zA-Z_]{2,16}$");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (u *userSrviceImpl) Login(username string, password string, clientToken *string, requestUser bool) (*LoginResponse, error) {
 | 
					func (u *userServiceImpl) Login(username string, password string, clientToken *string, requestUser bool) (*LoginResponse, error) {
 | 
				
			||||||
	if !u.allowUser(username) {
 | 
						if !u.allowUser(username) {
 | 
				
			||||||
		return nil, util.YggdrasilError{
 | 
							return nil, util.YggdrasilError{
 | 
				
			||||||
			Status:       http.StatusTooManyRequests,
 | 
								Status:       http.StatusTooManyRequests,
 | 
				
			||||||
@@ -175,7 +202,7 @@ func (u *userSrviceImpl) Login(username string, password string, clientToken *st
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (u *userSrviceImpl) ChangeProfile(accessToken string, clientToken *string, changeTo string) error {
 | 
					func (u *userServiceImpl) ChangeProfile(accessToken string, clientToken *string, changeTo string) error {
 | 
				
			||||||
	if u.tokenService.VerifyToken(accessToken, clientToken) != model.Valid {
 | 
						if u.tokenService.VerifyToken(accessToken, clientToken) != model.Valid {
 | 
				
			||||||
		return util.NewForbiddenOperationError(util.MessageInvalidToken)
 | 
							return util.NewForbiddenOperationError(util.MessageInvalidToken)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -210,7 +237,7 @@ func (u *userSrviceImpl) ChangeProfile(accessToken string, clientToken *string,
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (u *userSrviceImpl) Refresh(accessToken string, clientToken *string, requestUser bool, selectedProfile *model.ProfileResponse) (*LoginResponse, error) {
 | 
					func (u *userServiceImpl) Refresh(accessToken string, clientToken *string, requestUser bool, selectedProfile *model.ProfileResponse) (*LoginResponse, error) {
 | 
				
			||||||
	if len(accessToken) <= 36 {
 | 
						if len(accessToken) <= 36 {
 | 
				
			||||||
		user := model.User{}
 | 
							user := model.User{}
 | 
				
			||||||
		if selectedProfile != nil {
 | 
							if selectedProfile != nil {
 | 
				
			||||||
@@ -259,7 +286,7 @@ func (u *userSrviceImpl) Refresh(accessToken string, clientToken *string, reques
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (u *userSrviceImpl) Validate(accessToken string, clientToken *string) error {
 | 
					func (u *userServiceImpl) Validate(accessToken string, clientToken *string) error {
 | 
				
			||||||
	if len(accessToken) <= 36 {
 | 
						if len(accessToken) <= 36 {
 | 
				
			||||||
		if u.tokenService.VerifyToken(accessToken, clientToken) != model.Valid {
 | 
							if u.tokenService.VerifyToken(accessToken, clientToken) != model.Valid {
 | 
				
			||||||
			return util.NewForbiddenOperationError(util.MessageInvalidToken)
 | 
								return util.NewForbiddenOperationError(util.MessageInvalidToken)
 | 
				
			||||||
@@ -280,7 +307,7 @@ func (u *userSrviceImpl) Validate(accessToken string, clientToken *string) error
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (u *userSrviceImpl) Invalidate(accessToken string) error {
 | 
					func (u *userServiceImpl) Invalidate(accessToken string) error {
 | 
				
			||||||
	if len(accessToken) <= 36 {
 | 
						if len(accessToken) <= 36 {
 | 
				
			||||||
		u.tokenService.RemoveAccessToken(accessToken)
 | 
							u.tokenService.RemoveAccessToken(accessToken)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
@@ -295,7 +322,7 @@ func (u *userSrviceImpl) Invalidate(accessToken string) error {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (u *userSrviceImpl) Signout(username string, password string) error {
 | 
					func (u *userServiceImpl) Signout(username string, password string) error {
 | 
				
			||||||
	if !u.allowUser(username) {
 | 
						if !u.allowUser(username) {
 | 
				
			||||||
		return util.YggdrasilError{
 | 
							return util.YggdrasilError{
 | 
				
			||||||
			Status:       http.StatusTooManyRequests,
 | 
								Status:       http.StatusTooManyRequests,
 | 
				
			||||||
@@ -325,7 +352,7 @@ func (u *userSrviceImpl) Signout(username string, password string) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (u *userSrviceImpl) UsernameToUUID(username string) (*model.ProfileResponse, error) {
 | 
					func (u *userServiceImpl) UsernameToUUID(username string) (*model.ProfileResponse, error) {
 | 
				
			||||||
	user := model.User{}
 | 
						user := model.User{}
 | 
				
			||||||
	if result := u.db.Where("profile_name = ?", username).First(&user); result.Error == nil {
 | 
						if result := u.db.Where("profile_name = ?", username).First(&user); result.Error == nil {
 | 
				
			||||||
		return &model.ProfileResponse{
 | 
							return &model.ProfileResponse{
 | 
				
			||||||
@@ -342,7 +369,7 @@ func (u *userSrviceImpl) UsernameToUUID(username string) (*model.ProfileResponse
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (u *userSrviceImpl) QueryUUIDs(usernames []string) ([]model.ProfileResponse, error) {
 | 
					func (u *userServiceImpl) QueryUUIDs(usernames []string) ([]model.ProfileResponse, error) {
 | 
				
			||||||
	var users []model.User
 | 
						var users []model.User
 | 
				
			||||||
	var names []string
 | 
						var names []string
 | 
				
			||||||
	if len(usernames) > 10 {
 | 
						if len(usernames) > 10 {
 | 
				
			||||||
@@ -362,7 +389,7 @@ func (u *userSrviceImpl) QueryUUIDs(usernames []string) ([]model.ProfileResponse
 | 
				
			|||||||
	return responses, nil
 | 
						return responses, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (u *userSrviceImpl) QueryProfile(profileId uuid.UUID, unsigned bool, textureBaseUrl string) (map[string]interface{}, error) {
 | 
					func (u *userServiceImpl) QueryProfile(profileId uuid.UUID, unsigned bool, textureBaseUrl string) (map[string]interface{}, error) {
 | 
				
			||||||
	user := model.User{}
 | 
						user := model.User{}
 | 
				
			||||||
	if err := u.db.First(&user, profileId).Error; err == nil {
 | 
						if err := u.db.First(&user, profileId).Error; err == nil {
 | 
				
			||||||
		profile, err := user.Profile()
 | 
							profile, err := user.Profile()
 | 
				
			||||||
@@ -386,7 +413,35 @@ func (u *userSrviceImpl) QueryProfile(profileId uuid.UUID, unsigned bool, textur
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (u *userSrviceImpl) allowUser(username string) bool {
 | 
					func (u *userServiceImpl) ProfileKey(accessToken string) (resp *ProfileKeyResponse, err error) {
 | 
				
			||||||
 | 
						token, ok := u.tokenService.GetToken(accessToken)
 | 
				
			||||||
 | 
						if ok && token.GetAvailableLevel() == model.Valid {
 | 
				
			||||||
 | 
							resp = new(ProfileKeyResponse)
 | 
				
			||||||
 | 
							now := time.Now().UTC()
 | 
				
			||||||
 | 
							resp.RefreshedAfter = now
 | 
				
			||||||
 | 
							resp.ExpiresAt = now.Add(time.Hour * 24 * 90)
 | 
				
			||||||
 | 
							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
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							err = util.PostForString("https://api.minecraftservices.com/player/certificates", accessToken, []byte(""), resp)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return resp, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (u *userServiceImpl) allowUser(username string) bool {
 | 
				
			||||||
	if value, ok := u.limitLruCache.Get(username); ok {
 | 
						if value, ok := u.limitLruCache.Get(username); ok {
 | 
				
			||||||
		if limiter, ok := value.(*rate.Limiter); ok {
 | 
							if limiter, ok := value.(*rate.Limiter); ok {
 | 
				
			||||||
			return limiter.Allow()
 | 
								return limiter.Allow()
 | 
				
			||||||
@@ -400,6 +455,51 @@ func (u *userSrviceImpl) allowUser(username string) bool {
 | 
				
			|||||||
	return true
 | 
						return true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (u *userServiceImpl) getProfileKey(profileId uuid.UUID) (*ProfileKeyPair, error) {
 | 
				
			||||||
 | 
						if value, ok := u.profileKeyCache.Get(profileId); ok {
 | 
				
			||||||
 | 
							if keyPair, ok := value.(*ProfileKeyPair); ok {
 | 
				
			||||||
 | 
								return keyPair, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if keyPair, ok := <-u.keyPairCh; ok {
 | 
				
			||||||
 | 
							u.profileKeyCache.Add(profileId, &keyPair)
 | 
				
			||||||
 | 
							return &keyPair, nil
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return nil, errors.New("unable to generate rsa key pair")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (u *userServiceImpl) genKeyPair() {
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							keyPair := ProfileKeyPair{}
 | 
				
			||||||
 | 
							privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
 | 
				
			||||||
 | 
							util.PrivateKey = privateKey
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								close(u.keyPairCh)
 | 
				
			||||||
 | 
								panic(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								close(u.keyPairCh)
 | 
				
			||||||
 | 
								panic(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							keyPair.PrivateKey = string(pem.EncodeToMemory(&pem.Block{
 | 
				
			||||||
 | 
								Type:  "RSA PRIVATE KEY",
 | 
				
			||||||
 | 
								Bytes: privateKeyBytes,
 | 
				
			||||||
 | 
							}))
 | 
				
			||||||
 | 
							publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								close(u.keyPairCh)
 | 
				
			||||||
 | 
								panic(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							keyPair.PublicKey = string(pem.EncodeToMemory(&pem.Block{
 | 
				
			||||||
 | 
								Type:  "RSA PUBLIC KEY",
 | 
				
			||||||
 | 
								Bytes: publicKeyBytes,
 | 
				
			||||||
 | 
							}))
 | 
				
			||||||
 | 
							u.keyPairCh <- keyPair
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func mojangUsernameToUUID(username string) (model.ProfileResponse, error) {
 | 
					func mojangUsernameToUUID(username string) (model.ProfileResponse, error) {
 | 
				
			||||||
	response := model.ProfileResponse{}
 | 
						response := model.ProfileResponse{}
 | 
				
			||||||
	reqUrl := fmt.Sprintf("https://api.mojang.com/users/profiles/minecraft/%s", url.PathEscape(username))
 | 
						reqUrl := fmt.Sprintf("https://api.mojang.com/users/profiles/minecraft/%s", url.PathEscape(username))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,7 @@ package util
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
@@ -138,28 +139,39 @@ func PostObjectForError(url string, data interface{}) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func PostForString(url string, data []byte) (string, error) {
 | 
					func PostForString(url string, accessToken string, data []byte, value interface{}) error {
 | 
				
			||||||
	reader := bytes.NewReader(data)
 | 
						reader := bytes.NewReader(data)
 | 
				
			||||||
	resp, err := http.Post(url, "application/json", reader)
 | 
						request, err := http.NewRequestWithContext(context.Background(), "POST", url, reader)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return "", err
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if accessToken != "" {
 | 
				
			||||||
 | 
							request.Header.Set("Authorization", "Bearer "+accessToken)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						resp, err := http.DefaultClient.Do(request)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer resp.Body.Close()
 | 
						defer resp.Body.Close()
 | 
				
			||||||
	if resp.StatusCode == http.StatusNoContent {
 | 
						if resp.StatusCode == http.StatusNoContent {
 | 
				
			||||||
		return "", nil
 | 
							return nil
 | 
				
			||||||
	} else if resp.StatusCode/100 == 4 {
 | 
						} else if resp.StatusCode/100 == 4 {
 | 
				
			||||||
		decoder := json.NewDecoder(resp.Body)
 | 
					 | 
				
			||||||
		errResp := YggdrasilError{}
 | 
							errResp := YggdrasilError{}
 | 
				
			||||||
 | 
							if resp.ContentLength <= 0 {
 | 
				
			||||||
 | 
								errResp.Status = resp.StatusCode
 | 
				
			||||||
 | 
								return errResp
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							decoder := json.NewDecoder(resp.Body)
 | 
				
			||||||
		err = decoder.Decode(&errResp)
 | 
							err = decoder.Decode(&errResp)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return "", err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return "", errResp
 | 
							return errResp
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		body, err := ioutil.ReadAll(resp.Body)
 | 
							body, err := ioutil.ReadAll(resp.Body)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return "", err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return string(body), nil
 | 
							return json.Unmarshal(body, value)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user