diff --git a/handle/yggdrasil/session.go b/handle/yggdrasil/session.go new file mode 100644 index 0000000..6c476e3 --- /dev/null +++ b/handle/yggdrasil/session.go @@ -0,0 +1,54 @@ +package yggdrasil + +import ( + "errors" + "net/http" + + "github.com/julienschmidt/httprouter" + "github.com/xmdhs/authlib-skin/model/yggdrasil" + sutils "github.com/xmdhs/authlib-skin/service/utils" + "github.com/xmdhs/authlib-skin/utils" +) + +func (y *Yggdrasil) SessionJoin() httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + ctx := r.Context() + a, has := getAnyModel[yggdrasil.Session](ctx, w, r.Body, y.validate, y.logger) + if !has { + return + } + ip, err := utils.GetIP(r, y.config.RaelIP) + if err != nil { + y.logger.WarnContext(ctx, err.Error()) + handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: err.Error()}, 500) + return + } + err = y.yggdrasilService.SessionJoin(ctx, a, ip) + if err != nil { + if errors.Is(err, sutils.ErrTokenInvalid) { + y.logger.DebugContext(ctx, err.Error()) + handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: "Invalid token.", Error: "ForbiddenOperationException"}, 403) + return + } + y.logger.WarnContext(ctx, err.Error()) + handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: err.Error()}, 500) + return + } + w.WriteHeader(204) + } +} + +func (y *Yggdrasil) HasJoined() httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + ctx := r.Context() + name := r.FormValue("username") + serverId := r.FormValue("serverId") + ip := r.FormValue("ip") + if name == "" || serverId == "" { + y.logger.DebugContext(ctx, "name 或 serverID 为空") + w.WriteHeader(204) + return + } + y.yggdrasilService.HasJoined(ctx, name, serverId, ip, r.Host) + } +} diff --git a/handle/yggdrasil/yggdrasil.go b/handle/yggdrasil/yggdrasil.go index 233ddd3..9e50d3a 100644 --- a/handle/yggdrasil/yggdrasil.go +++ b/handle/yggdrasil/yggdrasil.go @@ -7,6 +7,7 @@ import ( "net/http" "github.com/go-playground/validator/v10" + "github.com/xmdhs/authlib-skin/config" "github.com/xmdhs/authlib-skin/model/yggdrasil" yggdrasilS "github.com/xmdhs/authlib-skin/service/yggdrasil" "github.com/xmdhs/authlib-skin/utils" @@ -16,13 +17,15 @@ type Yggdrasil struct { logger *slog.Logger validate *validator.Validate yggdrasilService *yggdrasilS.Yggdrasil + config config.Config } -func NewYggdrasil(logger *slog.Logger, validate *validator.Validate, yggdrasilService *yggdrasilS.Yggdrasil) *Yggdrasil { +func NewYggdrasil(logger *slog.Logger, validate *validator.Validate, yggdrasilService *yggdrasilS.Yggdrasil, config config.Config) *Yggdrasil { return &Yggdrasil{ logger: logger, validate: validate, yggdrasilService: yggdrasilService, + config: config, } } diff --git a/model/yggdrasil/model.go b/model/yggdrasil/model.go index a1226a2..d94f21b 100644 --- a/model/yggdrasil/model.go +++ b/model/yggdrasil/model.go @@ -57,3 +57,9 @@ type UserProperties struct { Value string `json:"value"` Signature string `json:"signature,omitempty"` } + +type Session struct { + AccessToken string `json:"accessToken" validate:"required,jwt"` + SelectedProfile string `json:"selectedProfile" validate:"required,uuid"` + ServerID string `json:"serverId"` +} diff --git a/server/route/route.go b/server/route/route.go index 347d4b1..02e86b1 100644 --- a/server/route/route.go +++ b/server/route/route.go @@ -35,6 +35,9 @@ func newYggdrasil(r *httprouter.Router, handelY yggdrasil.Yggdrasil) error { r.GET("/api/yggdrasil/sessionserver/session/minecraft/profile/:uuid", warpHJSON(handelY.GetProfile())) r.POST("/api/yggdrasil/api/profiles/minecraft", warpHJSON(handelY.BatchProfile())) + r.POST("/api/yggdrasil/sessionserver/session/minecraft/join", warpHJSON(handelY.SessionJoin())) + r.GET("/api/yggdrasil/sessionserver/session/minecraft/hasJoined", warpHJSON(handelY.SessionJoin())) + r.GET("/api/yggdrasil", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { w.Write([]byte(`{ "meta": { diff --git a/server/wire_gen.go b/server/wire_gen.go index 03d63b8..bc5c2d5 100644 --- a/server/wire_gen.go +++ b/server/wire_gen.go @@ -44,7 +44,7 @@ func InitializeRoute(ctx context.Context, c config.Config) (*http.Server, func() return nil, nil, err } yggdrasilYggdrasil := yggdrasil.NewYggdrasil(client, cache, c, privateKey) - yggdrasil3 := yggdrasil2.NewYggdrasil(logger, validate, yggdrasilYggdrasil) + yggdrasil3 := yggdrasil2.NewYggdrasil(logger, validate, yggdrasilYggdrasil, c) webService := service.NewWebService(c, client) handel := handle.NewHandel(webService, validate, c, logger) router, err := route.NewRoute(yggdrasil3, handel) diff --git a/service/yggdrasil/session.go b/service/yggdrasil/session.go new file mode 100644 index 0000000..98ccfa5 --- /dev/null +++ b/service/yggdrasil/session.go @@ -0,0 +1,64 @@ +package yggdrasil + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/samber/lo" + "github.com/xmdhs/authlib-skin/db/ent/userprofile" + "github.com/xmdhs/authlib-skin/model" + "github.com/xmdhs/authlib-skin/model/yggdrasil" + sutils "github.com/xmdhs/authlib-skin/service/utils" +) + +type sessionWithIP struct { + user model.TokenClaims + IP string +} + +func (y *Yggdrasil) SessionJoin(ctx context.Context, s yggdrasil.Session, ip string) error { + t, err := sutils.Auth(ctx, yggdrasil.ValidateToken{ + AccessToken: s.AccessToken, + }, y.client, &y.prikey.PublicKey, true) + if err != nil { + return fmt.Errorf("SessionJoin: %w", err) + } + if s.SelectedProfile != t.Subject { + return fmt.Errorf("SessionJoin: %w", sutils.ErrTokenInvalid) + } + err = y.cache.Put([]byte("session"+s.ServerID), lo.Must1(json.Marshal(sessionWithIP{ + user: *t, + IP: ip, + })), time.Now().Add(30*time.Second)) + if err != nil { + return fmt.Errorf("SessionJoin: %w", err) + } + return nil +} + +func (y *Yggdrasil) HasJoined(ctx context.Context, username, serverId string, ip string, host string) (yggdrasil.UserInfo, error) { + b := lo.Must1(y.cache.Get([]byte("session" + serverId))) + sIP := sessionWithIP{} + lo.Must0(json.Unmarshal(b, &sIP)) + + if ip != "" && ip != sIP.IP { + return yggdrasil.UserInfo{}, fmt.Errorf("ip 不相同") + } + + up, err := y.client.UserProfile.Query().Where(userprofile.Name(username)).Only(ctx) + if err != nil { + return yggdrasil.UserInfo{}, fmt.Errorf("HasJoined: %w", err) + } + + if up.UUID != sIP.user.Subject { + return yggdrasil.UserInfo{}, fmt.Errorf("uuid 不相同") + } + + u, err := y.GetProfile(ctx, up.UUID, false, host) + if err != nil { + return yggdrasil.UserInfo{}, fmt.Errorf("HasJoined: %w", err) + } + return u, nil +}