feat: add yggdrasil metadata endpoint and support authlib-injector
This commit is contained in:
@@ -75,7 +75,7 @@ func main() {
|
|||||||
|
|
||||||
// --- Инициализация хендлеров ---
|
// --- Инициализация хендлеров ---
|
||||||
userHandler := &api.UserHandler{Service: userService}
|
userHandler := &api.UserHandler{Service: userService}
|
||||||
authHandler := &api.AuthHandler{Service: authService}
|
authHandler := &api.AuthHandler{Service: authService, ProfileService: profileService, Domain: domain}
|
||||||
profileHandler := &api.ProfileHandler{Service: profileService}
|
profileHandler := &api.ProfileHandler{Service: profileService}
|
||||||
serverHandler := &api.ServerHandler{Repo: serverRepo}
|
serverHandler := &api.ServerHandler{Repo: serverRepo}
|
||||||
launcherHandler := &api.LauncherHandler{ModpackRepo: modpackRepo}
|
launcherHandler := &api.LauncherHandler{ModpackRepo: modpackRepo}
|
||||||
@@ -129,6 +129,31 @@ func main() {
|
|||||||
r.Get("/profile/{uuid}", profileHandler.GetProfile)
|
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.Group(func(r chi.Router) {
|
||||||
r.Use(api.AuthMiddleware)
|
r.Use(api.AuthMiddleware)
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import (
|
|||||||
|
|
||||||
type AuthHandler struct {
|
type AuthHandler struct {
|
||||||
Service *core.AuthService
|
Service *core.AuthService
|
||||||
|
ProfileService *core.ProfileService
|
||||||
|
Domain string
|
||||||
}
|
}
|
||||||
|
|
||||||
// YggdrasilError - стандартный формат ошибки для authserver
|
// YggdrasilError - стандартный формат ошибки для authserver
|
||||||
@@ -126,3 +128,27 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
json.NewEncoder(w).Encode(response)
|
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -158,3 +158,19 @@ func (s *ProfileService) UpdateUserSkin(ctx context.Context, userID int, file mu
|
|||||||
|
|
||||||
return s.UserRepo.UpdateSkinHash(ctx, userID, hash)
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -86,3 +86,22 @@ type LoginResponse struct {
|
|||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
User *User `json:"user"`
|
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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user