Compare commits
	
		
			12 Commits
		
	
	
		
			v0.0.2
			...
			742262b064
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						742262b064
	
				 | 
					
					
						|||
| 
						
						
							
						
						67cd6cb3cf
	
				 | 
					
					
						|||
| 
						
						
							
						
						22fd87a07a
	
				 | 
					
					
						|||
| 
						
						
							
						
						1d825c97f3
	
				 | 
					
					
						|||
| 
						
						
							
						
						044cd3082e
	
				 | 
					
					
						|||
| 
						
						
							
						
						e7c4dc58b7
	
				 | 
					
					
						|||
| 
						
						
							
						
						2722e85a6a
	
				 | 
					
					
						|||
| 
						
						
							
						
						dcec80c184
	
				 | 
					
					
						|||
| 
						
						
							
						
						016dfaf14d
	
				 | 
					
					
						|||
| 
						
						
							
						
						b8487b766b
	
				 | 
					
					
						|||
| 
						
						
							
						
						a537906a17
	
				 | 
					
					
						|||
| 
						
						
							
						
						81d81b8a03
	
				 | 
					
					
						
							
								
								
									
										68
									
								
								.gitea/workflows/build.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								.gitea/workflows/build.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					name: Build and Push Docker Image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  push:
 | 
				
			||||||
 | 
					    branches:
 | 
				
			||||||
 | 
					      - dev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  build-and-push:
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    env:
 | 
				
			||||||
 | 
					      CGO_ENABLED: 1
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - name: Checkout codebase
 | 
				
			||||||
 | 
					        uses: actions/checkout@v3
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          fetch-depth: 0
 | 
				
			||||||
 | 
					      - name: Pre Setup NodeJS
 | 
				
			||||||
 | 
					        uses: actions/setup-node@v4
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          node-version: '18.x'
 | 
				
			||||||
 | 
					      - name: For act to work
 | 
				
			||||||
 | 
					        run: npm -g install yarn
 | 
				
			||||||
 | 
					      - name: Setup NodeJS
 | 
				
			||||||
 | 
					        uses: actions/setup-node@v4
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          node-version: '18.x'
 | 
				
			||||||
 | 
					          cache: 'yarn'
 | 
				
			||||||
 | 
					          cache-dependency-path: frontend
 | 
				
			||||||
 | 
					      - name: Build Frontend
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          make assets
 | 
				
			||||||
 | 
					          rm -rf /host/${{ gitea.workspace }} && mkdir -p /host/${{ gitea.workspace }}
 | 
				
			||||||
 | 
					          cp -a . /host/${{ gitea.workspace }}/
 | 
				
			||||||
 | 
					      - name: Build Yggdrasil Server
 | 
				
			||||||
 | 
					        uses: crazy-max/ghaction-xgo@v2
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          xgo_version: latest
 | 
				
			||||||
 | 
					          go_version: 1.24
 | 
				
			||||||
 | 
					          dest: build
 | 
				
			||||||
 | 
					          prefix: yggdrasil
 | 
				
			||||||
 | 
					          targets: linux/amd64,linux/arm64
 | 
				
			||||||
 | 
					          v: true
 | 
				
			||||||
 | 
					          x: false
 | 
				
			||||||
 | 
					          race: false
 | 
				
			||||||
 | 
					          ldflags: -s -w -buildid=
 | 
				
			||||||
 | 
					          tags: nomsgpack sqlite mysql
 | 
				
			||||||
 | 
					          trimpath: true
 | 
				
			||||||
 | 
					      - name: Store Back Binaries
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          cp -a /host/${{ gitea.workspace }}/build/. build
 | 
				
			||||||
 | 
					      - name: Set up QEMU
 | 
				
			||||||
 | 
					        uses: docker/setup-qemu-action@v2
 | 
				
			||||||
 | 
					      - name: Set up Docker Buildx
 | 
				
			||||||
 | 
					        uses: docker/setup-buildx-action@v3
 | 
				
			||||||
 | 
					      - name: Login to Docker Registry
 | 
				
			||||||
 | 
					        uses: docker/login-action@v3
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          registry: docker.sunxinao.cn
 | 
				
			||||||
 | 
					          username: ${{ secrets.DOCKER_USERNAME }}
 | 
				
			||||||
 | 
					          password: ${{ secrets.DOCKER_PASSWORD }}
 | 
				
			||||||
 | 
					      - name: Build and push
 | 
				
			||||||
 | 
					        uses: docker/build-push-action@v3
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          context: .
 | 
				
			||||||
 | 
					          push: true
 | 
				
			||||||
 | 
					          platforms: linux/amd64,linux/arm64
 | 
				
			||||||
 | 
					          tags: docker.sunxinao.cn/gardel/yggdrasil-go:latest
 | 
				
			||||||
							
								
								
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							@@ -52,7 +52,7 @@ jobs:
 | 
				
			|||||||
          SUFFIX="$(echo "$LINE" | grep -osE '\.\w+' || printf '')"
 | 
					          SUFFIX="$(echo "$LINE" | grep -osE '\.\w+' || printf '')"
 | 
				
			||||||
          cp -v "$LINE" "yggdrasil$SUFFIX"
 | 
					          cp -v "$LINE" "yggdrasil$SUFFIX"
 | 
				
			||||||
          FILE="../$PREFIX.zip"
 | 
					          FILE="../$PREFIX.zip"
 | 
				
			||||||
          zip -9v "$FILE" "yggdrasil$SUFFIX" *.ini assets
 | 
					          zip -9rv "$FILE" "yggdrasil$SUFFIX" *.ini assets
 | 
				
			||||||
          DGST="$FILE.dgst"
 | 
					          DGST="$FILE.dgst"
 | 
				
			||||||
          openssl dgst -md5    "$FILE" | sed 's/([^)]*)//g' >>"$DGST"
 | 
					          openssl dgst -md5    "$FILE" | sed 's/([^)]*)//g' >>"$DGST"
 | 
				
			||||||
          openssl dgst -sha1   "$FILE" | sed 's/([^)]*)//g' >>"$DGST"
 | 
					          openssl dgst -sha1   "$FILE" | sed 's/([^)]*)//g' >>"$DGST"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,10 @@
 | 
				
			|||||||
FROM alpine:latest
 | 
					FROM debian:12-slim
 | 
				
			||||||
 | 
					
 | 
				
			||||||
LABEL maintainer="Gardel <sunxinao@hotmail.com>"
 | 
					LABEL maintainer="Gardel <sunxinao@hotmail.com>"
 | 
				
			||||||
LABEL "Description"="Go Yggdrasil Server"
 | 
					LABEL "Description"="Go Yggdrasil Server"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN apt-get update && apt-get install -y ca-certificates
 | 
				
			||||||
 | 
					RUN update-ca-certificates
 | 
				
			||||||
ARG TARGETOS
 | 
					ARG TARGETOS
 | 
				
			||||||
ARG TARGETARCH
 | 
					ARG TARGETARCH
 | 
				
			||||||
RUN mkdir -p /app
 | 
					RUN mkdir -p /app
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							@@ -16,6 +16,12 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
禁止其他违反 [EULA](https://account.mojang.com/documents/minecraft_eula) 的行为。
 | 
					禁止其他违反 [EULA](https://account.mojang.com/documents/minecraft_eula) 的行为。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 准备
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					+ 运行 Linux, Windows 或 MacOS 的主机
 | 
				
			||||||
 | 
					+ SMTP 服务器和账号用于发送密码找回邮件
 | 
				
			||||||
 | 
					+ MySQL 数据库(如果使用 sqlite 则不需要)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 用法
 | 
					## 用法
 | 
				
			||||||
 | 
					
 | 
				
			||||||
下载或编译得到可执行文件并运行,将会自动生成所需的配置文件和数据库文件。
 | 
					下载或编译得到可执行文件并运行,将会自动生成所需的配置文件和数据库文件。
 | 
				
			||||||
@@ -33,3 +39,11 @@
 | 
				
			|||||||
```shell
 | 
					```shell
 | 
				
			||||||
docker run -d --name yggdrasil-go -v $(pwd)/data:/app/data -p 8080:8080 gardel/yggdrasil-go:latest
 | 
					docker run -d --name yggdrasil-go -v $(pwd)/data:/app/data -p 8080:8080 gardel/yggdrasil-go:latest
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 计划
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [x] 支持密码重置
 | 
				
			||||||
 | 
					- [ ] 支持不同的数据库如 PostgreSQL 等
 | 
				
			||||||
 | 
					- [ ] 添加选项以支持完全离线模式(不检查 Mojang 接口)
 | 
				
			||||||
 | 
					- [ ] 添加选项以禁用邮箱验证
 | 
				
			||||||
 | 
					- [ ] 令牌持久化防止升级和重启时令牌生效
 | 
				
			||||||
							
								
								
									
										1
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								main.go
									
									
									
									
									
								
							@@ -151,6 +151,7 @@ func main() {
 | 
				
			|||||||
	serverMeta.Meta.ImplementationVersion = meta.ImplementationVersion
 | 
						serverMeta.Meta.ImplementationVersion = meta.ImplementationVersion
 | 
				
			||||||
	serverMeta.Meta.FeatureNoMojangNamespace = true
 | 
						serverMeta.Meta.FeatureNoMojangNamespace = true
 | 
				
			||||||
	serverMeta.Meta.FeatureEnableProfileKey = true
 | 
						serverMeta.Meta.FeatureEnableProfileKey = true
 | 
				
			||||||
 | 
						serverMeta.Meta.FeatureEnableMojangAntiFeatures = true
 | 
				
			||||||
	serverMeta.Meta.Links.Homepage = meta.SkinRootUrl + "/profile/"
 | 
						serverMeta.Meta.Links.Homepage = meta.SkinRootUrl + "/profile/"
 | 
				
			||||||
	serverMeta.Meta.Links.Register = meta.SkinRootUrl + "/profile/"
 | 
						serverMeta.Meta.Links.Register = meta.SkinRootUrl + "/profile/"
 | 
				
			||||||
	serverMeta.SkinDomains = meta.SkinDomains
 | 
						serverMeta.SkinDomains = meta.SkinDomains
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
/*
 | 
					/*
 | 
				
			||||||
 * Copyright (C) 2022-2023. Gardel <sunxinao@hotmail.com> and contributors
 | 
					 * Copyright (C) 2022-2025. Gardel <sunxinao@hotmail.com> and contributors
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * This program is free software: you can redistribute it and/or modify
 | 
					 * 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
 | 
					 * it under the terms of the GNU Affero General Public License as published by
 | 
				
			||||||
@@ -37,6 +37,7 @@ type MetaInfo struct {
 | 
				
			|||||||
	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"`
 | 
						FeatureEnableProfileKey         bool `json:"feature.enable_profile_key,omitempty"`
 | 
				
			||||||
 | 
						FeatureEnableMojangAntiFeatures bool `json:"feature.enable_mojang_anti_features,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ServerMeta struct {
 | 
					type ServerMeta struct {
 | 
				
			||||||
@@ -63,6 +64,7 @@ type HomeRouter interface {
 | 
				
			|||||||
type homeRouterImpl struct {
 | 
					type homeRouterImpl struct {
 | 
				
			||||||
	serverMeta   ServerMeta
 | 
						serverMeta   ServerMeta
 | 
				
			||||||
	myPubKey     KeyPair
 | 
						myPubKey     KeyPair
 | 
				
			||||||
 | 
						cachedPubKey *PublicKeys
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewHomeRouter(meta *ServerMeta) HomeRouter {
 | 
					func NewHomeRouter(meta *ServerMeta) HomeRouter {
 | 
				
			||||||
@@ -80,6 +82,10 @@ func (h *homeRouterImpl) Home(c *gin.Context) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (h *homeRouterImpl) PublicKeys(c *gin.Context) {
 | 
					func (h *homeRouterImpl) PublicKeys(c *gin.Context) {
 | 
				
			||||||
 | 
						if h.cachedPubKey != nil {
 | 
				
			||||||
 | 
							c.JSON(http.StatusOK, h.cachedPubKey)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	publicKeys := PublicKeys{}
 | 
						publicKeys := PublicKeys{}
 | 
				
			||||||
	err := util.GetObject("https://api.minecraftservices.com/publickeys", &publicKeys)
 | 
						err := util.GetObject("https://api.minecraftservices.com/publickeys", &publicKeys)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -89,4 +95,5 @@ func (h *homeRouterImpl) PublicKeys(c *gin.Context) {
 | 
				
			|||||||
	publicKeys.ProfilePropertyKeys = append(publicKeys.ProfilePropertyKeys, h.myPubKey)
 | 
						publicKeys.ProfilePropertyKeys = append(publicKeys.ProfilePropertyKeys, h.myPubKey)
 | 
				
			||||||
	publicKeys.PlayerCertificateKeys = append(publicKeys.PlayerCertificateKeys, h.myPubKey)
 | 
						publicKeys.PlayerCertificateKeys = append(publicKeys.PlayerCertificateKeys, h.myPubKey)
 | 
				
			||||||
	c.JSON(http.StatusOK, publicKeys)
 | 
						c.JSON(http.StatusOK, publicKeys)
 | 
				
			||||||
 | 
						h.cachedPubKey = &publicKeys
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -79,6 +79,8 @@ func InitRouters(router *gin.Engine, db *gorm.DB, meta *ServerMeta, smtpCfg *ser
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	minecraftservices := router.Group("/minecraftservices")
 | 
						minecraftservices := router.Group("/minecraftservices")
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
 | 
							minecraftservices.GET("/player/attributes", userRouter.PlayerAttributes)
 | 
				
			||||||
 | 
							minecraftservices.GET("/privacy/blocklist", userRouter.PlayerBlockList)
 | 
				
			||||||
		minecraftservices.POST("/player/certificates", userRouter.ProfileKey)
 | 
							minecraftservices.POST("/player/certificates", userRouter.ProfileKey)
 | 
				
			||||||
		minecraftservices.GET("/publickeys", homeRouter.PublicKeys)
 | 
							minecraftservices.GET("/publickeys", homeRouter.PublicKeys)
 | 
				
			||||||
		minecraftservices.GET("/minecraft/profile/lookup/:uuid", userRouter.UUIDToUUID)
 | 
							minecraftservices.GET("/minecraft/profile/lookup/:uuid", userRouter.UUIDToUUID)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,6 +38,8 @@ type UserRouter interface {
 | 
				
			|||||||
	UUIDToUUID(c *gin.Context)
 | 
						UUIDToUUID(c *gin.Context)
 | 
				
			||||||
	QueryUUIDs(c *gin.Context)
 | 
						QueryUUIDs(c *gin.Context)
 | 
				
			||||||
	QueryProfile(c *gin.Context)
 | 
						QueryProfile(c *gin.Context)
 | 
				
			||||||
 | 
						PlayerAttributes(c *gin.Context)
 | 
				
			||||||
 | 
						PlayerBlockList(c *gin.Context)
 | 
				
			||||||
	ProfileKey(c *gin.Context)
 | 
						ProfileKey(c *gin.Context)
 | 
				
			||||||
	SendEmail(c *gin.Context)
 | 
						SendEmail(c *gin.Context)
 | 
				
			||||||
	VerifyEmail(c *gin.Context)
 | 
						VerifyEmail(c *gin.Context)
 | 
				
			||||||
@@ -299,6 +301,27 @@ func (u *userRouterImpl) QueryProfile(c *gin.Context) {
 | 
				
			|||||||
	c.JSON(http.StatusOK, response)
 | 
						c.JSON(http.StatusOK, response)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (u *userRouterImpl) PlayerAttributes(c *gin.Context) {
 | 
				
			||||||
 | 
						c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
							"privileges": gin.H{
 | 
				
			||||||
 | 
								"onlineChat":        true,
 | 
				
			||||||
 | 
								"multiplayerServer": true,
 | 
				
			||||||
 | 
								"multiplayerRealms": false,
 | 
				
			||||||
 | 
								"telemetry":         false,
 | 
				
			||||||
 | 
								"optionalTelemetry": false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"profanityFilterPreferences": gin.H{
 | 
				
			||||||
 | 
								"profanityFilterOn": false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (u *userRouterImpl) PlayerBlockList(c *gin.Context) {
 | 
				
			||||||
 | 
						c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
							"blockedProfiles": []string{},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (u *userRouterImpl) ProfileKey(c *gin.Context) {
 | 
					func (u *userRouterImpl) ProfileKey(c *gin.Context) {
 | 
				
			||||||
	bearerToken := c.GetHeader("Authorization")
 | 
						bearerToken := c.GetHeader("Authorization")
 | 
				
			||||||
	if len(bearerToken) < 8 {
 | 
						if len(bearerToken) < 8 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -118,7 +118,7 @@ func (u *userServiceImpl) Register(username, password, profileName, ip string) (
 | 
				
			|||||||
	} else if _, err := mojangUsernameToUUID(profileName); err == nil {
 | 
						} else if _, err := mojangUsernameToUUID(profileName); err == nil {
 | 
				
			||||||
		return nil, util.NewForbiddenOperationError("profileName duplicate")
 | 
							return nil, util.NewForbiddenOperationError("profileName duplicate")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	matched, err := regexp.MatchString("^(\\w){3,}(\\.\\w+)*@(\\w){2,}((\\.\\w+)+)$", username)
 | 
						matched, err := regexp.MatchString("^\\w+@(\\w){2,}((\\.\\w+)+)$", username)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -394,12 +394,24 @@ func (u *userServiceImpl) QueryProfile(profileId uuid.UUID, unsigned bool, textu
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (u *userServiceImpl) ProfileKey(accessToken string) (resp *ProfileKeyResponse, err error) {
 | 
					func (u *userServiceImpl) ProfileKey(accessToken string) (resp *ProfileKeyResponse, err error) {
 | 
				
			||||||
	token, ok := u.tokenService.GetToken(accessToken)
 | 
						token, ok := u.tokenService.GetToken(accessToken)
 | 
				
			||||||
 | 
						var profileId uuid.UUID
 | 
				
			||||||
	if ok && token.GetAvailableLevel() == model.Valid {
 | 
						if ok && token.GetAvailableLevel() == model.Valid {
 | 
				
			||||||
 | 
							profileId = token.SelectedProfile.Id
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							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)
 | 
						resp = new(ProfileKeyResponse)
 | 
				
			||||||
	now := time.Now().UTC()
 | 
						now := time.Now().UTC()
 | 
				
			||||||
	resp.RefreshedAfter = now
 | 
						resp.RefreshedAfter = now
 | 
				
			||||||
		resp.ExpiresAt = now.Add(time.Hour * 24 * 90)
 | 
						resp.ExpiresAt = now.Add(90 * 24 * time.Hour)
 | 
				
			||||||
		keyPair, err := u.getProfileKey(token.SelectedProfile.Id)
 | 
						keyPair, err := u.getProfileKey(profileId)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -411,12 +423,6 @@ func (u *userServiceImpl) ProfileKey(accessToken string) (resp *ProfileKeyRespon
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	resp.PublicKeySignature = sign
 | 
						resp.PublicKeySignature = sign
 | 
				
			||||||
	resp.PublicKeySignatureV2 = 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
 | 
						return resp, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										75
									
								
								util/token_utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								util/token_utils.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright (C) 2025. Gardel <sunxinao@hotmail.com> 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 <https://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user