- Updated main.go to initialize email service and include it in the dependency injection container. - Refactored handlers to utilize context in service method calls, improving consistency and error handling. - Introduced new service options for upload, security, and captcha services, enhancing modularity and testability. - Removed unused repository implementations to streamline the codebase. This commit continues the effort to improve the architecture by ensuring all services are properly injected and utilized across the application.
594 lines
20 KiB
Go
594 lines
20 KiB
Go
package handler
|
||
|
||
import (
|
||
"bytes"
|
||
"carrotskin/internal/container"
|
||
"carrotskin/internal/model"
|
||
"carrotskin/pkg/utils"
|
||
"io"
|
||
"net/http"
|
||
"regexp"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
// 常量定义
|
||
const (
|
||
ErrInternalServer = "服务器内部错误"
|
||
// 错误类型
|
||
ErrInvalidEmailFormat = "邮箱格式不正确"
|
||
ErrInvalidPassword = "密码必须至少包含8个字符,只能包含字母、数字和特殊字符"
|
||
ErrWrongPassword = "密码错误"
|
||
ErrUserNotMatch = "用户不匹配"
|
||
|
||
// 错误消息
|
||
ErrInvalidRequest = "请求格式无效"
|
||
ErrJoinServerFailed = "加入服务器失败"
|
||
ErrServerIDRequired = "服务器ID不能为空"
|
||
ErrUsernameRequired = "用户名不能为空"
|
||
ErrSessionVerifyFailed = "会话验证失败"
|
||
ErrProfileNotFound = "未找到用户配置文件"
|
||
ErrInvalidParams = "无效的请求参数"
|
||
ErrEmptyUserID = "用户ID为空"
|
||
ErrUnauthorized = "无权操作此配置文件"
|
||
ErrGetProfileService = "获取配置文件服务失败"
|
||
|
||
// 成功信息
|
||
SuccessProfileCreated = "创建成功"
|
||
MsgRegisterSuccess = "注册成功"
|
||
|
||
// 错误消息
|
||
ErrGetProfile = "获取配置文件失败"
|
||
ErrGetTextureService = "获取材质服务失败"
|
||
ErrInvalidContentType = "无效的请求内容类型"
|
||
ErrParseMultipartForm = "解析多部分表单失败"
|
||
ErrGetFileFromForm = "从表单获取文件失败"
|
||
ErrInvalidFileType = "无效的文件类型,仅支持PNG图片"
|
||
ErrSaveTexture = "保存材质失败"
|
||
ErrSetTexture = "设置材质失败"
|
||
ErrGetTexture = "获取材质失败"
|
||
|
||
// 内存限制
|
||
MaxMultipartMemory = 32 << 20 // 32 MB
|
||
|
||
// 材质类型
|
||
TextureTypeSkin = "SKIN"
|
||
TextureTypeCape = "CAPE"
|
||
|
||
// 内容类型
|
||
ContentTypePNG = "image/png"
|
||
ContentTypeMultipart = "multipart/form-data"
|
||
|
||
// 表单参数
|
||
FormKeyModel = "model"
|
||
FormKeyFile = "file"
|
||
|
||
// 元数据键
|
||
MetaKeyModel = "model"
|
||
)
|
||
|
||
// 正则表达式
|
||
var (
|
||
// 邮箱正则表达式
|
||
emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
|
||
|
||
// 密码强度正则表达式(最少8位,只允许字母、数字和特定特殊字符)
|
||
passwordRegex = regexp.MustCompile(`^[a-zA-Z0-9!@#$%^&*]{8,}$`)
|
||
)
|
||
|
||
// 请求结构体
|
||
type (
|
||
// AuthenticateRequest 认证请求
|
||
AuthenticateRequest struct {
|
||
Agent map[string]interface{} `json:"agent"`
|
||
ClientToken string `json:"clientToken"`
|
||
Identifier string `json:"username" binding:"required"`
|
||
Password string `json:"password" binding:"required"`
|
||
RequestUser bool `json:"requestUser"`
|
||
}
|
||
|
||
// ValidTokenRequest 验证令牌请求
|
||
ValidTokenRequest struct {
|
||
AccessToken string `json:"accessToken" binding:"required"`
|
||
ClientToken string `json:"clientToken"`
|
||
}
|
||
|
||
// RefreshRequest 刷新令牌请求
|
||
RefreshRequest struct {
|
||
AccessToken string `json:"accessToken" binding:"required"`
|
||
ClientToken string `json:"clientToken"`
|
||
RequestUser bool `json:"requestUser"`
|
||
SelectedProfile map[string]interface{} `json:"selectedProfile"`
|
||
}
|
||
|
||
// SignOutRequest 登出请求
|
||
SignOutRequest struct {
|
||
Email string `json:"username" binding:"required"`
|
||
Password string `json:"password" binding:"required"`
|
||
}
|
||
|
||
// JoinServerRequest 加入服务器请求
|
||
JoinServerRequest struct {
|
||
ServerID string `json:"serverId" binding:"required"`
|
||
AccessToken string `json:"accessToken" binding:"required"`
|
||
SelectedProfile string `json:"selectedProfile" binding:"required"`
|
||
}
|
||
)
|
||
|
||
// 响应结构体
|
||
type (
|
||
// AuthenticateResponse 认证响应
|
||
AuthenticateResponse struct {
|
||
AccessToken string `json:"accessToken"`
|
||
ClientToken string `json:"clientToken"`
|
||
SelectedProfile map[string]interface{} `json:"selectedProfile,omitempty"`
|
||
AvailableProfiles []map[string]interface{} `json:"availableProfiles"`
|
||
User map[string]interface{} `json:"user,omitempty"`
|
||
}
|
||
|
||
// RefreshResponse 刷新令牌响应
|
||
RefreshResponse struct {
|
||
AccessToken string `json:"accessToken"`
|
||
ClientToken string `json:"clientToken"`
|
||
SelectedProfile map[string]interface{} `json:"selectedProfile,omitempty"`
|
||
User map[string]interface{} `json:"user,omitempty"`
|
||
}
|
||
)
|
||
|
||
// APIResponse API响应
|
||
type APIResponse struct {
|
||
Status int `json:"status"`
|
||
Data interface{} `json:"data"`
|
||
Error interface{} `json:"error"`
|
||
}
|
||
|
||
// standardResponse 生成标准响应
|
||
func standardResponse(c *gin.Context, status int, data interface{}, err interface{}) {
|
||
c.JSON(status, APIResponse{
|
||
Status: status,
|
||
Data: data,
|
||
Error: err,
|
||
})
|
||
}
|
||
|
||
// YggdrasilHandler Yggdrasil API处理器
|
||
type YggdrasilHandler struct {
|
||
container *container.Container
|
||
logger *zap.Logger
|
||
}
|
||
|
||
// NewYggdrasilHandler 创建YggdrasilHandler实例
|
||
func NewYggdrasilHandler(c *container.Container) *YggdrasilHandler {
|
||
return &YggdrasilHandler{
|
||
container: c,
|
||
logger: c.Logger,
|
||
}
|
||
}
|
||
|
||
// Authenticate 用户认证
|
||
func (h *YggdrasilHandler) Authenticate(c *gin.Context) {
|
||
rawData, err := io.ReadAll(c.Request.Body)
|
||
if err != nil {
|
||
h.logger.Error("读取请求体失败", zap.Error(err))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "读取请求体失败"})
|
||
return
|
||
}
|
||
c.Request.Body = io.NopCloser(bytes.NewBuffer(rawData))
|
||
|
||
var request AuthenticateRequest
|
||
if err = c.ShouldBindJSON(&request); err != nil {
|
||
h.logger.Error("解析认证请求失败", zap.Error(err))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
var userId int64
|
||
var profile *model.Profile
|
||
var UUID string
|
||
|
||
if emailRegex.MatchString(request.Identifier) {
|
||
userId, err = h.container.YggdrasilService.GetUserIDByEmail(c.Request.Context(), request.Identifier)
|
||
} else {
|
||
profile, err = h.container.ProfileRepo.FindByName(request.Identifier)
|
||
if err != nil {
|
||
h.logger.Error("用户名不存在", zap.String("identifier", request.Identifier), zap.Error(err))
|
||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
userId = profile.UserID
|
||
UUID = profile.UUID
|
||
}
|
||
|
||
if err != nil {
|
||
h.logger.Warn("认证失败: 用户不存在", zap.String("identifier", request.Identifier), zap.Error(err))
|
||
c.JSON(http.StatusForbidden, gin.H{"error": "用户不存在"})
|
||
return
|
||
}
|
||
|
||
if err := h.container.YggdrasilService.VerifyPassword(c.Request.Context(), request.Password, userId); err != nil {
|
||
h.logger.Warn("认证失败: 密码错误", zap.Error(err))
|
||
c.JSON(http.StatusForbidden, gin.H{"error": ErrWrongPassword})
|
||
return
|
||
}
|
||
|
||
selectedProfile, availableProfiles, accessToken, clientToken, err := h.container.TokenService.Create(c.Request.Context(), userId, UUID, request.ClientToken)
|
||
if err != nil {
|
||
h.logger.Error("生成令牌失败", zap.Error(err), zap.Int64("userId", userId))
|
||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
user, err := h.container.UserService.GetByID(c.Request.Context(), userId)
|
||
if err != nil {
|
||
h.logger.Error("获取用户信息失败", zap.Error(err), zap.Int64("userId", userId))
|
||
}
|
||
|
||
availableProfilesData := make([]map[string]interface{}, 0, len(availableProfiles))
|
||
for _, p := range availableProfiles {
|
||
availableProfilesData = append(availableProfilesData, h.container.YggdrasilService.SerializeProfile(c.Request.Context(), *p))
|
||
}
|
||
|
||
response := AuthenticateResponse{
|
||
AccessToken: accessToken,
|
||
ClientToken: clientToken,
|
||
AvailableProfiles: availableProfilesData,
|
||
}
|
||
|
||
if selectedProfile != nil {
|
||
response.SelectedProfile = h.container.YggdrasilService.SerializeProfile(c.Request.Context(), *selectedProfile)
|
||
}
|
||
|
||
if request.RequestUser && user != nil {
|
||
response.User = h.container.YggdrasilService.SerializeUser(c.Request.Context(), user, UUID)
|
||
}
|
||
|
||
h.logger.Info("用户认证成功", zap.Int64("userId", userId))
|
||
c.JSON(http.StatusOK, response)
|
||
}
|
||
|
||
// ValidToken 验证令牌
|
||
func (h *YggdrasilHandler) ValidToken(c *gin.Context) {
|
||
var request ValidTokenRequest
|
||
if err := c.ShouldBindJSON(&request); err != nil {
|
||
h.logger.Error("解析验证令牌请求失败", zap.Error(err))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
if h.container.TokenService.Validate(c.Request.Context(), request.AccessToken, request.ClientToken) {
|
||
h.logger.Info("令牌验证成功", zap.String("accessToken", request.AccessToken))
|
||
c.JSON(http.StatusNoContent, gin.H{"valid": true})
|
||
} else {
|
||
h.logger.Warn("令牌验证失败", zap.String("accessToken", request.AccessToken))
|
||
c.JSON(http.StatusForbidden, gin.H{"valid": false})
|
||
}
|
||
}
|
||
|
||
// RefreshToken 刷新令牌
|
||
func (h *YggdrasilHandler) RefreshToken(c *gin.Context) {
|
||
var request RefreshRequest
|
||
if err := c.ShouldBindJSON(&request); err != nil {
|
||
h.logger.Error("解析刷新令牌请求失败", zap.Error(err))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
UUID, err := h.container.TokenService.GetUUIDByAccessToken(c.Request.Context(), request.AccessToken)
|
||
if err != nil {
|
||
h.logger.Warn("刷新令牌失败: 无效的访问令牌", zap.String("token", request.AccessToken), zap.Error(err))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
userID, _ := h.container.TokenService.GetUserIDByAccessToken(c.Request.Context(), request.AccessToken)
|
||
UUID = utils.FormatUUID(UUID)
|
||
|
||
profile, err := h.container.ProfileService.GetByUUID(c.Request.Context(), UUID)
|
||
if err != nil {
|
||
h.logger.Error("刷新令牌失败: 无法获取用户信息", zap.Error(err))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
var profileData map[string]interface{}
|
||
var userData map[string]interface{}
|
||
var profileID string
|
||
|
||
if request.SelectedProfile != nil {
|
||
profileIDValue, ok := request.SelectedProfile["id"]
|
||
if !ok {
|
||
h.logger.Error("刷新令牌失败: 缺少配置文件ID", zap.Int64("userId", userID))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "缺少配置文件ID"})
|
||
return
|
||
}
|
||
|
||
profileID, ok = profileIDValue.(string)
|
||
if !ok {
|
||
h.logger.Error("刷新令牌失败: 配置文件ID类型错误", zap.Int64("userId", userID))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "配置文件ID必须是字符串"})
|
||
return
|
||
}
|
||
|
||
profileID = utils.FormatUUID(profileID)
|
||
|
||
if profile.UserID != userID {
|
||
h.logger.Warn("刷新令牌失败: 用户不匹配",
|
||
zap.Int64("userId", userID),
|
||
zap.Int64("profileUserId", profile.UserID),
|
||
)
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": ErrUserNotMatch})
|
||
return
|
||
}
|
||
|
||
profileData = h.container.YggdrasilService.SerializeProfile(c.Request.Context(), *profile)
|
||
}
|
||
|
||
user, _ := h.container.UserService.GetByID(c.Request.Context(), userID)
|
||
if request.RequestUser && user != nil {
|
||
userData = h.container.YggdrasilService.SerializeUser(c.Request.Context(), user, UUID)
|
||
}
|
||
|
||
newAccessToken, newClientToken, err := h.container.TokenService.Refresh(c.Request.Context(),
|
||
request.AccessToken,
|
||
request.ClientToken,
|
||
profileID,
|
||
)
|
||
if err != nil {
|
||
h.logger.Error("刷新令牌失败", zap.Error(err), zap.Int64("userId", userID))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
h.logger.Info("刷新令牌成功", zap.Int64("userId", userID))
|
||
c.JSON(http.StatusOK, RefreshResponse{
|
||
AccessToken: newAccessToken,
|
||
ClientToken: newClientToken,
|
||
SelectedProfile: profileData,
|
||
User: userData,
|
||
})
|
||
}
|
||
|
||
// InvalidToken 使令牌失效
|
||
func (h *YggdrasilHandler) InvalidToken(c *gin.Context) {
|
||
var request ValidTokenRequest
|
||
if err := c.ShouldBindJSON(&request); err != nil {
|
||
h.logger.Error("解析使令牌失效请求失败", zap.Error(err))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
h.container.TokenService.Invalidate(c.Request.Context(), request.AccessToken)
|
||
h.logger.Info("令牌已失效", zap.String("token", request.AccessToken))
|
||
c.JSON(http.StatusNoContent, gin.H{})
|
||
}
|
||
|
||
// SignOut 用户登出
|
||
func (h *YggdrasilHandler) SignOut(c *gin.Context) {
|
||
var request SignOutRequest
|
||
if err := c.ShouldBindJSON(&request); err != nil {
|
||
h.logger.Error("解析登出请求失败", zap.Error(err))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
if !emailRegex.MatchString(request.Email) {
|
||
h.logger.Warn("登出失败: 邮箱格式不正确", zap.String("email", request.Email))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": ErrInvalidEmailFormat})
|
||
return
|
||
}
|
||
|
||
user, err := h.container.UserService.GetByEmail(c.Request.Context(), request.Email)
|
||
if err != nil || user == nil {
|
||
h.logger.Warn("登出失败: 用户不存在", zap.String("email", request.Email), zap.Error(err))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": "用户不存在"})
|
||
return
|
||
}
|
||
|
||
if err := h.container.YggdrasilService.VerifyPassword(c.Request.Context(), request.Password, user.ID); err != nil {
|
||
h.logger.Warn("登出失败: 密码错误", zap.Int64("userId", user.ID))
|
||
c.JSON(http.StatusBadRequest, gin.H{"error": ErrWrongPassword})
|
||
return
|
||
}
|
||
|
||
h.container.TokenService.InvalidateUserTokens(c.Request.Context(), user.ID)
|
||
h.logger.Info("用户登出成功", zap.Int64("userId", user.ID))
|
||
c.JSON(http.StatusNoContent, gin.H{"valid": true})
|
||
}
|
||
|
||
// GetProfileByUUID 根据UUID获取档案
|
||
func (h *YggdrasilHandler) GetProfileByUUID(c *gin.Context) {
|
||
uuid := utils.FormatUUID(c.Param("uuid"))
|
||
h.logger.Info("获取配置文件请求", zap.String("uuid", uuid))
|
||
|
||
profile, err := h.container.ProfileService.GetByUUID(c.Request.Context(), uuid)
|
||
if err != nil {
|
||
h.logger.Error("获取配置文件失败", zap.Error(err), zap.String("uuid", uuid))
|
||
standardResponse(c, http.StatusInternalServerError, nil, err.Error())
|
||
return
|
||
}
|
||
|
||
h.logger.Info("成功获取配置文件", zap.String("uuid", uuid), zap.String("name", profile.Name))
|
||
c.JSON(http.StatusOK, h.container.YggdrasilService.SerializeProfile(c.Request.Context(), *profile))
|
||
}
|
||
|
||
// JoinServer 加入服务器
|
||
func (h *YggdrasilHandler) JoinServer(c *gin.Context) {
|
||
var request JoinServerRequest
|
||
clientIP := c.ClientIP()
|
||
|
||
if err := c.ShouldBindJSON(&request); err != nil {
|
||
h.logger.Error("解析加入服务器请求失败", zap.Error(err), zap.String("ip", clientIP))
|
||
standardResponse(c, http.StatusBadRequest, nil, ErrInvalidRequest)
|
||
return
|
||
}
|
||
|
||
h.logger.Info("收到加入服务器请求",
|
||
zap.String("serverId", request.ServerID),
|
||
zap.String("userUUID", request.SelectedProfile),
|
||
zap.String("ip", clientIP),
|
||
)
|
||
|
||
if err := h.container.YggdrasilService.JoinServer(c.Request.Context(), request.ServerID, request.AccessToken, request.SelectedProfile, clientIP); err != nil {
|
||
h.logger.Error("加入服务器失败",
|
||
zap.Error(err),
|
||
zap.String("serverId", request.ServerID),
|
||
zap.String("userUUID", request.SelectedProfile),
|
||
zap.String("ip", clientIP),
|
||
)
|
||
standardResponse(c, http.StatusInternalServerError, nil, ErrJoinServerFailed)
|
||
return
|
||
}
|
||
|
||
h.logger.Info("加入服务器成功",
|
||
zap.String("serverId", request.ServerID),
|
||
zap.String("userUUID", request.SelectedProfile),
|
||
zap.String("ip", clientIP),
|
||
)
|
||
c.Status(http.StatusNoContent)
|
||
}
|
||
|
||
// HasJoinedServer 验证玩家是否已加入服务器
|
||
func (h *YggdrasilHandler) HasJoinedServer(c *gin.Context) {
|
||
clientIP, _ := c.GetQuery("ip")
|
||
|
||
serverID, exists := c.GetQuery("serverId")
|
||
if !exists || serverID == "" {
|
||
h.logger.Warn("缺少服务器ID参数", zap.String("ip", clientIP))
|
||
standardResponse(c, http.StatusNoContent, nil, ErrServerIDRequired)
|
||
return
|
||
}
|
||
|
||
username, exists := c.GetQuery("username")
|
||
if !exists || username == "" {
|
||
h.logger.Warn("缺少用户名参数", zap.String("serverId", serverID), zap.String("ip", clientIP))
|
||
standardResponse(c, http.StatusNoContent, nil, ErrUsernameRequired)
|
||
return
|
||
}
|
||
|
||
h.logger.Info("收到会话验证请求",
|
||
zap.String("serverId", serverID),
|
||
zap.String("username", username),
|
||
zap.String("ip", clientIP),
|
||
)
|
||
|
||
if err := h.container.YggdrasilService.HasJoinedServer(c.Request.Context(), serverID, username, clientIP); err != nil {
|
||
h.logger.Warn("会话验证失败",
|
||
zap.Error(err),
|
||
zap.String("serverId", serverID),
|
||
zap.String("username", username),
|
||
zap.String("ip", clientIP),
|
||
)
|
||
standardResponse(c, http.StatusNoContent, nil, ErrSessionVerifyFailed)
|
||
return
|
||
}
|
||
|
||
profile, err := h.container.ProfileService.GetByUUID(c.Request.Context(), username)
|
||
if err != nil {
|
||
h.logger.Error("获取用户配置文件失败", zap.Error(err), zap.String("username", username))
|
||
standardResponse(c, http.StatusNoContent, nil, ErrProfileNotFound)
|
||
return
|
||
}
|
||
|
||
h.logger.Info("会话验证成功",
|
||
zap.String("serverId", serverID),
|
||
zap.String("username", username),
|
||
zap.String("uuid", profile.UUID),
|
||
)
|
||
c.JSON(200, h.container.YggdrasilService.SerializeProfile(c.Request.Context(), *profile))
|
||
}
|
||
|
||
// GetProfilesByName 批量获取配置文件
|
||
func (h *YggdrasilHandler) GetProfilesByName(c *gin.Context) {
|
||
var names []string
|
||
|
||
if err := c.ShouldBindJSON(&names); err != nil {
|
||
h.logger.Error("解析名称数组请求失败", zap.Error(err))
|
||
standardResponse(c, http.StatusBadRequest, nil, ErrInvalidParams)
|
||
return
|
||
}
|
||
|
||
h.logger.Info("接收到批量获取配置文件请求", zap.Int("count", len(names)))
|
||
|
||
profiles, err := h.container.ProfileService.GetByNames(c.Request.Context(), names)
|
||
if err != nil {
|
||
h.logger.Error("获取配置文件失败", zap.Error(err))
|
||
}
|
||
|
||
h.logger.Info("成功获取配置文件", zap.Int("requested", len(names)), zap.Int("returned", len(profiles)))
|
||
c.JSON(http.StatusOK, profiles)
|
||
}
|
||
|
||
// GetMetaData 获取Yggdrasil元数据
|
||
func (h *YggdrasilHandler) GetMetaData(c *gin.Context) {
|
||
meta := gin.H{
|
||
"implementationName": "CellAuth",
|
||
"implementationVersion": "0.0.1",
|
||
"serverName": "LittleLan's Yggdrasil Server Implementation.",
|
||
"links": gin.H{
|
||
"homepage": "https://skin.littlelan.cn",
|
||
"register": "https://skin.littlelan.cn/auth",
|
||
},
|
||
"feature.non_email_login": true,
|
||
"feature.enable_profile_key": true,
|
||
}
|
||
|
||
skinDomains := []string{".hitwh.games", ".littlelan.cn"}
|
||
signature, err := h.container.YggdrasilService.GetPublicKey(c.Request.Context())
|
||
if err != nil {
|
||
h.logger.Error("获取公钥失败", zap.Error(err))
|
||
standardResponse(c, http.StatusInternalServerError, nil, ErrInternalServer)
|
||
return
|
||
}
|
||
|
||
h.logger.Info("提供元数据")
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"meta": meta,
|
||
"skinDomains": skinDomains,
|
||
"signaturePublickey": signature,
|
||
})
|
||
}
|
||
|
||
// GetPlayerCertificates 获取玩家证书
|
||
func (h *YggdrasilHandler) GetPlayerCertificates(c *gin.Context) {
|
||
authHeader := c.GetHeader("Authorization")
|
||
if authHeader == "" {
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header not provided"})
|
||
c.Abort()
|
||
return
|
||
}
|
||
|
||
bearerPrefix := "Bearer "
|
||
if len(authHeader) < len(bearerPrefix) || authHeader[:len(bearerPrefix)] != bearerPrefix {
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid Authorization format"})
|
||
c.Abort()
|
||
return
|
||
}
|
||
|
||
tokenID := authHeader[len(bearerPrefix):]
|
||
if tokenID == "" {
|
||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid Authorization format"})
|
||
c.Abort()
|
||
return
|
||
}
|
||
|
||
uuid, err := h.container.TokenService.GetUUIDByAccessToken(c.Request.Context(), tokenID)
|
||
if uuid == "" {
|
||
h.logger.Error("获取玩家UUID失败", zap.Error(err))
|
||
standardResponse(c, http.StatusInternalServerError, nil, ErrInternalServer)
|
||
return
|
||
}
|
||
|
||
uuid = utils.FormatUUID(uuid)
|
||
|
||
certificate, err := h.container.YggdrasilService.GeneratePlayerCertificate(c.Request.Context(), uuid)
|
||
if err != nil {
|
||
h.logger.Error("生成玩家证书失败", zap.Error(err))
|
||
standardResponse(c, http.StatusInternalServerError, nil, ErrInternalServer)
|
||
return
|
||
}
|
||
|
||
h.logger.Info("成功生成玩家证书")
|
||
c.JSON(http.StatusOK, certificate)
|
||
}
|