diff --git a/cmd/server/main.go b/cmd/server/main.go index 4209beb..045912f 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -75,7 +75,7 @@ func main() { // --- Инициализация хендлеров --- userHandler := &api.UserHandler{Service: userService} - authHandler := &api.AuthHandler{Service: authService} + authHandler := &api.AuthHandler{Service: authService, ProfileService: profileService, Domain: domain} profileHandler := &api.ProfileHandler{Service: profileService} serverHandler := &api.ServerHandler{Repo: serverRepo} launcherHandler := &api.LauncherHandler{ModpackRepo: modpackRepo} @@ -129,6 +129,31 @@ func main() { r.Get("/profile/{uuid}", profileHandler.GetProfile) }) + // --- Yggdrasil API (for authlib-injector) --- + r.Route("/api/yggdrasil", func(r chi.Router) { + r.Get("/", authHandler.GetMetadata) + + r.Route("/authserver", func(r chi.Router) { + r.Post("/authenticate", authHandler.Authenticate) + r.Post("/invalidate", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) }) + r.Post("/validate", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) }) + r.Post("/signout", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) }) + r.Post("/refresh", func(w http.ResponseWriter, r *http.Request) { + // Fallback for refresh - return 400 or implement real refresh + http.Error(w, "Not implemented", http.StatusForbidden) + }) + }) + + r.Route("/sessionserver/session/minecraft", func(r chi.Router) { + r.Post("/join", authHandler.Join) + r.Get("/hasJoined", func(w http.ResponseWriter, r *http.Request) { + // Legacy hasJoined endpoint if needed, but modern mostly uses join/check + w.WriteHeader(http.StatusNoContent) + }) + r.Get("/profile/{uuid}", profileHandler.GetProfile) + }) + }) + // --- Защищенные роуты --- r.Group(func(r chi.Router) { r.Use(api.AuthMiddleware) diff --git a/internal/api/auth_handler.go b/internal/api/auth_handler.go index ad562be..4b0578d 100644 --- a/internal/api/auth_handler.go +++ b/internal/api/auth_handler.go @@ -12,7 +12,9 @@ import ( ) type AuthHandler struct { - Service *core.AuthService + Service *core.AuthService + ProfileService *core.ProfileService + Domain string } // YggdrasilError - стандартный формат ошибки для authserver @@ -126,3 +128,27 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(response) } + +func (h *AuthHandler) GetMetadata(w http.ResponseWriter, r *http.Request) { + pubKey, err := h.ProfileService.GetPublicKey() + if err != nil { + slog.Error("Failed to get public key", "error", err) + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + + metadata := models.YggdrasilMetadata{ + Meta: models.MetaLinks{ + Homepage: "https://" + h.Domain, + Register: "https://" + h.Domain + "/register", + }, + SkinDomains: models.MetaSkinDomains{ + Deny: []string{}, + }, + Signature: pubKey, + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(metadata) +} diff --git a/internal/core/profile_service.go b/internal/core/profile_service.go index 94544a2..01c9879 100644 --- a/internal/core/profile_service.go +++ b/internal/core/profile_service.go @@ -158,3 +158,19 @@ func (s *ProfileService) UpdateUserSkin(ctx context.Context, userID int, file mu return s.UserRepo.UpdateSkinHash(ctx, userID, hash) } + +// GetPublicKey возвращает публичный ключ в формате PEM +func (s *ProfileService) GetPublicKey() (string, error) { + pubKey := &s.privateKey.PublicKey + pubASN1, err := x509.MarshalPKIXPublicKey(pubKey) + if err != nil { + return "", err + } + + pubBytes := pem.EncodeToMemory(&pem.Block{ + Type: "PUBLIC KEY", + Bytes: pubASN1, + }) + + return string(pubBytes), nil +} diff --git a/internal/models/auth.go b/internal/models/auth.go index 6af3901..7abcf30 100644 --- a/internal/models/auth.go +++ b/internal/models/auth.go @@ -86,3 +86,22 @@ type LoginResponse struct { Token string `json:"token"` User *User `json:"user"` } + +// MetaLinks - ссылки в метаданных +type MetaLinks struct { + Homepage string `json:"homepage,omitempty"` + Register string `json:"register,omitempty"` +} + +// MetaSkinDomains - домены скинов +type MetaSkinDomains struct { + Deny []string `json:"deny,omitempty"` +} + +// YggdrasilMetadata - структура для ответа на запрос метаданных (/) +// См. https://github.com/yushijinhun/authlib-injector/wiki/Yggdrasil-API-Implementation-Spec +type YggdrasilMetadata struct { + Meta MetaLinks `json:"meta,omitempty"` + SkinDomains MetaSkinDomains `json:"skinDomains"` + Signature string `json:"signature"` // Public key in PEM format +}