From b011910d2016df2d69656c175ce3dfc44a22cf16 Mon Sep 17 00:00:00 2001 From: xmdhs Date: Sun, 8 Oct 2023 00:05:36 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B9=E6=88=90=20go-chi/chi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 4 +- go.sum | 8 ++- handle/admin.go | 13 ++-- handle/config.go | 5 +- handle/user.go | 19 +++--- handle/yggdrasil/error.go | 1 - handle/yggdrasil/session.go | 11 ++-- handle/yggdrasil/texture.go | 20 +++--- handle/yggdrasil/user.go | 37 +++++------ handle/yggdrasil/yggdrasil.go | 16 +++-- model/model.go | 1 + server/route/middleware.go | 71 +++++++++++++++++++-- server/route/route.go | 114 ++++++++++++++++++---------------- server/server.go | 39 +----------- server/slog.go | 32 ++-------- server/wire_gen.go | 9 +-- service/admin.go | 4 ++ service/user.go | 9 +-- service/yggdrasil/user.go | 4 ++ utils/ip.go | 23 +------ 20 files changed, 219 insertions(+), 221 deletions(-) diff --git a/go.mod b/go.mod index 57484a5..656e153 100644 --- a/go.mod +++ b/go.mod @@ -6,12 +6,13 @@ require ( entgo.io/ent v0.12.3 github.com/VictoriaMetrics/fastcache v1.12.1 github.com/alecthomas/binary v0.0.0-20221018225505-74871811ee56 + github.com/go-chi/chi/v5 v5.0.10 + github.com/go-chi/cors v1.2.1 github.com/go-playground/validator/v10 v10.15.3 github.com/go-sql-driver/mysql v1.7.1 github.com/golang-jwt/jwt/v5 v5.0.0 github.com/google/uuid v1.3.1 github.com/google/wire v0.5.0 - github.com/julienschmidt/httprouter v1.3.0 github.com/samber/lo v1.38.1 golang.org/x/crypto v0.7.0 ) @@ -22,6 +23,7 @@ require ( github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/go-chi/chi v1.5.5 github.com/go-openapi/inflect v0.19.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect diff --git a/go.sum b/go.sum index 07920e9..114ebc1 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE= +github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw= +github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= +github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= +github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -52,8 +58,6 @@ github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= github.com/hashicorp/hcl/v2 v2.13.0 h1:0Apadu1w6M11dyGFxWnmhhcMjkbAiKCv7G1r/2QgCNc= github.com/hashicorp/hcl/v2 v2.13.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0= -github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= diff --git a/handle/admin.go b/handle/admin.go index f3c779b..ab657e1 100644 --- a/handle/admin.go +++ b/handle/admin.go @@ -6,13 +6,12 @@ import ( "net/http" "strconv" - "github.com/julienschmidt/httprouter" "github.com/xmdhs/authlib-skin/model" utilsService "github.com/xmdhs/authlib-skin/service/utils" ) -func (h *Handel) NeedAdmin(handle httprouter.Handle) httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func (h *Handel) NeedAdmin(handle http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() token := h.getTokenbyAuthorization(ctx, w, r) if token == "" { @@ -27,12 +26,12 @@ func (h *Handel) NeedAdmin(handle httprouter.Handle) httprouter.Handle { h.handleError(ctx, w, err.Error(), model.ErrService, 500, slog.LevelWarn) return } - handle(w, r, p) - } + handle.ServeHTTP(w, r) + }) } -func (h *Handel) ListUser() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func (h *Handel) ListUser() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() page := r.FormValue("page") pagei := 1 diff --git a/handle/config.go b/handle/config.go index 62d8c9e..b3f2c49 100644 --- a/handle/config.go +++ b/handle/config.go @@ -4,12 +4,11 @@ import ( "encoding/json" "net/http" - "github.com/julienschmidt/httprouter" "github.com/xmdhs/authlib-skin/model" ) -func (h *Handel) GetConfig() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func (h *Handel) GetConfig() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() c := h.webService.GetConfig(ctx) m := model.API[model.Config]{ diff --git a/handle/user.go b/handle/user.go index 971ad7e..a51f61a 100644 --- a/handle/user.go +++ b/handle/user.go @@ -5,18 +5,17 @@ import ( "log/slog" "net/http" - "github.com/julienschmidt/httprouter" "github.com/xmdhs/authlib-skin/model" "github.com/xmdhs/authlib-skin/service" utilsService "github.com/xmdhs/authlib-skin/service/utils" "github.com/xmdhs/authlib-skin/utils" ) -func (h *Handel) Reg() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func (h *Handel) Reg() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - ip, err := utils.GetIP(r, h.config.RaelIP) + ip, err := utils.GetIP(r) if err != nil { h.handleError(ctx, w, err.Error(), model.ErrInput, 400, slog.LevelDebug) return @@ -55,8 +54,8 @@ func (h *Handel) Reg() httprouter.Handle { } } -func (h *Handel) UserInfo() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func (h *Handel) UserInfo() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() token := h.getTokenbyAuthorization(ctx, w, r) if token == "" { @@ -79,8 +78,8 @@ func (h *Handel) UserInfo() httprouter.Handle { } } -func (h *Handel) ChangePasswd() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func (h *Handel) ChangePasswd() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() token := h.getTokenbyAuthorization(ctx, w, r) if token == "" { @@ -108,8 +107,8 @@ func (h *Handel) ChangePasswd() httprouter.Handle { } } -func (h *Handel) ChangeName() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func (h *Handel) ChangeName() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() token := h.getTokenbyAuthorization(ctx, w, r) if token == "" { diff --git a/handle/yggdrasil/error.go b/handle/yggdrasil/error.go index 7c5c136..920aab8 100644 --- a/handle/yggdrasil/error.go +++ b/handle/yggdrasil/error.go @@ -20,5 +20,4 @@ func handleYgError(ctx context.Context, w http.ResponseWriter, e yggdrasil.Error func (y *Yggdrasil) handleYgError(ctx context.Context, w http.ResponseWriter, err error) { y.logger.WarnContext(ctx, err.Error()) handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: err.Error()}, 500) - return } diff --git a/handle/yggdrasil/session.go b/handle/yggdrasil/session.go index 94152c8..08135e1 100644 --- a/handle/yggdrasil/session.go +++ b/handle/yggdrasil/session.go @@ -5,21 +5,20 @@ import ( "errors" "net/http" - "github.com/julienschmidt/httprouter" "github.com/samber/lo" "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) { +func (y *Yggdrasil) SessionJoin() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { 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) + ip, err := utils.GetIP(r) if err != nil { y.handleYgError(ctx, w, err) return @@ -38,8 +37,8 @@ func (y *Yggdrasil) SessionJoin() httprouter.Handle { } } -func (y *Yggdrasil) HasJoined() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func (y *Yggdrasil) HasJoined() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() name := r.FormValue("username") serverId := r.FormValue("serverId") diff --git a/handle/yggdrasil/texture.go b/handle/yggdrasil/texture.go index 6475065..75febe8 100644 --- a/handle/yggdrasil/texture.go +++ b/handle/yggdrasil/texture.go @@ -11,7 +11,7 @@ import ( "net/http" "strings" - "github.com/julienschmidt/httprouter" + "github.com/go-chi/chi/v5" "github.com/xmdhs/authlib-skin/model/yggdrasil" "github.com/xmdhs/authlib-skin/service/utils" ) @@ -44,10 +44,10 @@ func (y *Yggdrasil) validTextureType(ctx context.Context, w http.ResponseWriter, return true } -func (y *Yggdrasil) PutTexture() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func (y *Yggdrasil) PutTexture() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - uuid, textureType, ok := getUUIDbyParams(ctx, p, y.logger, w) + uuid, textureType, ok := getUUIDbyParams(ctx, y.logger, w) if !ok { return } @@ -115,9 +115,9 @@ func (y *Yggdrasil) PutTexture() httprouter.Handle { } } -func getUUIDbyParams(ctx context.Context, p httprouter.Params, l *slog.Logger, w http.ResponseWriter) (string, string, bool) { - uuid := p.ByName("uuid") - textureType := p.ByName("textureType") +func getUUIDbyParams(ctx context.Context, l *slog.Logger, w http.ResponseWriter) (string, string, bool) { + uuid := chi.URLParamFromCtx(ctx, "uuid") + textureType := chi.URLParamFromCtx(ctx, "textureType") if uuid == "" { l.DebugContext(ctx, "路径中缺少参数 uuid") handleYgError(ctx, w, yggdrasil.Error{ErrorMessage: "路径中缺少参数 uuid / textureType"}, 400) @@ -132,10 +132,10 @@ func getUUIDbyParams(ctx context.Context, p httprouter.Params, l *slog.Logger, w return uuid, textureType, true } -func (y *Yggdrasil) DelTexture() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func (y *Yggdrasil) DelTexture() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - uuid, textureType, ok := getUUIDbyParams(ctx, p, y.logger, w) + uuid, textureType, ok := getUUIDbyParams(ctx, y.logger, w) if !ok { return } diff --git a/handle/yggdrasil/user.go b/handle/yggdrasil/user.go index ad531de..1b405e9 100644 --- a/handle/yggdrasil/user.go +++ b/handle/yggdrasil/user.go @@ -5,15 +5,15 @@ import ( "errors" "net/http" - "github.com/julienschmidt/httprouter" + "github.com/go-chi/chi/v5" "github.com/samber/lo" "github.com/xmdhs/authlib-skin/model/yggdrasil" sutils "github.com/xmdhs/authlib-skin/service/utils" yggdrasilS "github.com/xmdhs/authlib-skin/service/yggdrasil" ) -func (y *Yggdrasil) Authenticate() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func (y *Yggdrasil) Authenticate() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { cxt := r.Context() a, has := getAnyModel[yggdrasil.Authenticate](cxt, w, r.Body, y.validate, y.logger) if !has { @@ -34,8 +34,8 @@ func (y *Yggdrasil) Authenticate() httprouter.Handle { } } -func (y *Yggdrasil) Validate() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func (y *Yggdrasil) Validate() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { cxt := r.Context() a, has := getAnyModel[yggdrasil.ValidateToken](cxt, w, r.Body, y.validate, y.logger) if !has { @@ -55,8 +55,8 @@ func (y *Yggdrasil) Validate() httprouter.Handle { } } -func (y *Yggdrasil) Signout() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func (y *Yggdrasil) Signout() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { cxt := r.Context() a, has := getAnyModel[yggdrasil.Pass](cxt, w, r.Body, y.validate, y.logger) if !has { @@ -76,8 +76,8 @@ func (y *Yggdrasil) Signout() httprouter.Handle { } } -func (y *Yggdrasil) Invalidate() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func (y *Yggdrasil) Invalidate() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) cxt := r.Context() a, has := getAnyModel[yggdrasil.ValidateToken](cxt, w, r.Body, y.validate, y.logger) @@ -95,8 +95,8 @@ func (y *Yggdrasil) Invalidate() httprouter.Handle { } } -func (y *Yggdrasil) Refresh() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func (y *Yggdrasil) Refresh() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { cxt := r.Context() a, has := getAnyModel[yggdrasil.RefreshToken](cxt, w, r.Body, y.validate, y.logger) if !has { @@ -117,10 +117,11 @@ func (y *Yggdrasil) Refresh() httprouter.Handle { } } -func (y *Yggdrasil) GetProfile() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func (y *Yggdrasil) GetProfile() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - uuid := p.ByName("uuid") + uuid := chi.URLParamFromCtx(ctx, "uuid") + unsigned := r.FormValue("unsigned") unsignedBool := true @@ -151,8 +152,8 @@ func (y *Yggdrasil) GetProfile() httprouter.Handle { } } -func (y *Yggdrasil) BatchProfile() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func (y *Yggdrasil) BatchProfile() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() a, has := getAnyModel[[]string](ctx, w, r.Body, nil, y.logger) if !has { @@ -172,8 +173,8 @@ func (y *Yggdrasil) BatchProfile() httprouter.Handle { } } -func (y *Yggdrasil) PlayerCertificates() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func (y *Yggdrasil) PlayerCertificates() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() token := y.getTokenbyAuthorization(ctx, w, r) if token == "" { diff --git a/handle/yggdrasil/yggdrasil.go b/handle/yggdrasil/yggdrasil.go index 468fe7b..b428a7c 100644 --- a/handle/yggdrasil/yggdrasil.go +++ b/handle/yggdrasil/yggdrasil.go @@ -10,11 +10,9 @@ import ( "net/url" "github.com/go-playground/validator/v10" - "github.com/julienschmidt/httprouter" "github.com/samber/lo" "github.com/xmdhs/authlib-skin/config" "github.com/xmdhs/authlib-skin/model/yggdrasil" - yggdrasilM "github.com/xmdhs/authlib-skin/model/yggdrasil" yggdrasilS "github.com/xmdhs/authlib-skin/service/yggdrasil" "github.com/xmdhs/authlib-skin/utils" ) @@ -49,8 +47,8 @@ func getAnyModel[K any](ctx context.Context, w http.ResponseWriter, r io.Reader, return a, true } -func (y *Yggdrasil) YggdrasilRoot() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func (y *Yggdrasil) YggdrasilRoot() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { var host string if y.config.TextureBaseUrl != "" { u := lo.Must(url.Parse(y.config.TextureBaseUrl)) @@ -64,11 +62,11 @@ func (y *Yggdrasil) YggdrasilRoot() httprouter.Handle { homepage, _ := url.JoinPath(y.config.WebBaseUrl, "/login") register, _ := url.JoinPath(y.config.WebBaseUrl, "/register") - w.Write(lo.Must1(json.Marshal(yggdrasilM.Yggdrasil{ - Meta: yggdrasilM.YggdrasilMeta{ + w.Write(lo.Must1(json.Marshal(yggdrasil.Yggdrasil{ + Meta: yggdrasil.YggdrasilMeta{ ImplementationName: "authlib-skin", ImplementationVersion: "0.0.1", - Links: yggdrasilM.YggdrasilMetaLinks{ + Links: yggdrasil.YggdrasilMetaLinks{ Homepage: homepage, Register: register, }, @@ -82,8 +80,8 @@ func (y *Yggdrasil) YggdrasilRoot() httprouter.Handle { } } -func (y *Yggdrasil) TextureAssets() httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func (y *Yggdrasil) TextureAssets() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "image/png") http.StripPrefix("/texture/", http.FileServer(http.Dir(y.config.TexturePath))).ServeHTTP(w, r) } diff --git a/model/model.go b/model/model.go index 461b27a..5758365 100644 --- a/model/model.go +++ b/model/model.go @@ -50,6 +50,7 @@ type UserList struct { UserInfo Email string `json:"email"` RegIp string `json:"reg_ip"` + Name string `json:"name"` } type ChangeName struct { diff --git a/server/route/middleware.go b/server/route/middleware.go index e067a3c..46d0ef1 100644 --- a/server/route/middleware.go +++ b/server/route/middleware.go @@ -1,14 +1,75 @@ package route import ( + "context" + "fmt" + "log/slog" "net/http" + "time" - "github.com/julienschmidt/httprouter" + "github.com/go-chi/chi/v5/middleware" ) -func warpHJSON(handle httprouter.Handle) httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { +func warpHJSON(handle http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") - handle(w, r, p) - } + handle.ServeHTTP(w, r) + }) +} + +func NewStructuredLogger(handler slog.Handler) func(next http.Handler) http.Handler { + return middleware.RequestLogger(&StructuredLogger{Logger: handler}) +} + +type StructuredLogger struct { + Logger slog.Handler +} + +func (l *StructuredLogger) NewLogEntry(r *http.Request) middleware.LogEntry { + var logFields []slog.Attr + logFields = append(logFields, slog.String("ts", time.Now().UTC().Format(time.RFC1123))) + ctx := r.Context() + + if reqID := middleware.GetReqID(ctx); reqID != "" { + logFields = append(logFields, slog.String("req_id", reqID)) + } + + scheme := "http" + if r.TLS != nil { + scheme = "https" + } + + handler := l.Logger.WithAttrs(append(logFields, + slog.String("http_scheme", scheme), + slog.String("http_proto", r.Proto), + slog.String("http_method", r.Method), + slog.String("remote_addr", r.RemoteAddr), + slog.String("user_agent", r.UserAgent()), + slog.String("uri", fmt.Sprintf("%s://%s%s", scheme, r.Host, r.RequestURI)))) + + entry := StructuredLoggerEntry{Logger: slog.New(handler), ctx: ctx} + + entry.Logger.LogAttrs(ctx, slog.LevelDebug, "request started") + + return &entry +} + +type StructuredLoggerEntry struct { + Logger *slog.Logger + ctx context.Context +} + +func (l *StructuredLoggerEntry) Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{}) { + l.Logger.LogAttrs(l.ctx, slog.LevelDebug, "request complete", + slog.Int("resp_status", status), + slog.Int("resp_byte_length", bytes), + slog.Float64("resp_elapsed_ms", float64(elapsed.Nanoseconds())/1000000.0), + ) +} + +func (l *StructuredLoggerEntry) Panic(v interface{}, stack []byte) { + l.Logger.LogAttrs(l.ctx, slog.LevelDebug, "", + slog.String("stack", string(stack)), + slog.String("panic", fmt.Sprintf("%+v", v)), + ) } diff --git a/server/route/route.go b/server/route/route.go index ad500e6..6ed5304 100644 --- a/server/route/route.go +++ b/server/route/route.go @@ -1,63 +1,73 @@ package route import ( - "fmt" + "log/slog" "net/http" - "github.com/julienschmidt/httprouter" + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + "github.com/go-chi/cors" + "github.com/xmdhs/authlib-skin/config" "github.com/xmdhs/authlib-skin/handle" "github.com/xmdhs/authlib-skin/handle/yggdrasil" ) -func NewRoute(yggService *yggdrasil.Yggdrasil, handel *handle.Handel) (*httprouter.Router, error) { - r := httprouter.New() - r.HandleOPTIONS = true - r.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(204) +func NewRoute(handelY *yggdrasil.Yggdrasil, handel *handle.Handel, c config.Config, sl slog.Handler) http.Handler { + r := chi.NewRouter() + r.Use(middleware.RequestID) + r.Use(NewStructuredLogger(sl)) + r.Use(middleware.Recoverer) + r.Use(cors.AllowAll().Handler) + if c.RaelIP { + r.Use(middleware.RealIP) + } + + r.Mount("/api/v1", newSkinApi(handel)) + r.Mount("/api/yggdrasil", newYggdrasil(handelY)) + + r.Get("/texture/*", handelY.TextureAssets()) + + return r +} + +func newYggdrasil(handelY *yggdrasil.Yggdrasil) http.Handler { + r := chi.NewRouter() + r.Use(warpHJSON) + + r.Post("/authserver/authenticate", handelY.Authenticate()) + r.Post("/authserver/validate", handelY.Validate()) + r.Post("/authserver/signout", handelY.Signout()) + r.Post("/authserver/invalidate", handelY.Invalidate()) + r.Post("/authserver/refresh", handelY.Refresh()) + + r.Put("/api/user/profile/{uuid}/{textureType}", handelY.PutTexture()) + r.Delete("/api/user/profile/{uuid}/{textureType}", handelY.DelTexture()) + + r.Get("/sessionserver/session/minecraft/profile/{uuid}", handelY.GetProfile()) + r.Post("/api/profiles/minecraft", handelY.BatchProfile()) + + r.Post("/sessionserver/session/minecraft/join", handelY.SessionJoin()) + r.Get("/sessionserver/session/minecraft/hasJoined", handelY.HasJoined()) + + r.Post("/minecraftservices/player/certificates", handelY.PlayerCertificates()) + + r.Get("/", handelY.YggdrasilRoot()) + return r +} + +func newSkinApi(handel *handle.Handel) http.Handler { + r := chi.NewRouter() + + r.Put("/user/reg", handel.Reg()) + r.Get("/config", handel.GetConfig()) + r.Get("/user", handel.UserInfo()) + r.Post("/user/password", handel.ChangePasswd()) + r.Post("/user/name", handel.ChangeName()) + + r.Group(func(r chi.Router) { + r.Use(handel.NeedAdmin) + r.Get("/admin/users", handel.ListUser()) + }) - err := newYggdrasil(r, *yggService) - if err != nil { - return nil, fmt.Errorf("NewRoute: %w", err) - } - err = newSkinApi(r, handel) - if err != nil { - return nil, fmt.Errorf("NewRoute: %w", err) - } - return r, nil -} - -func newYggdrasil(r *httprouter.Router, handelY yggdrasil.Yggdrasil) error { - r.POST("/api/yggdrasil/authserver/authenticate", warpHJSON(handelY.Authenticate())) - r.POST("/api/yggdrasil/authserver/validate", warpHJSON(handelY.Validate())) - r.POST("/api/yggdrasil/authserver/signout", warpHJSON(handelY.Signout())) - r.POST("/api/yggdrasil/authserver/invalidate", handelY.Invalidate()) - r.POST("/api/yggdrasil/authserver/refresh", warpHJSON(handelY.Refresh())) - - r.PUT("/api/yggdrasil/api/user/profile/:uuid/:textureType", handelY.PutTexture()) - r.DELETE("/api/yggdrasil/api/user/profile/:uuid/:textureType", warpHJSON(handelY.DelTexture())) - - 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.HasJoined())) - - r.POST("/api/yggdrasil/minecraftservices/player/certificates", warpHJSON(handelY.PlayerCertificates())) - - r.GET("/api/yggdrasil", warpHJSON(handelY.YggdrasilRoot())) - r.GET("/api/yggdrasil/", warpHJSON(handelY.YggdrasilRoot())) - - r.GET("/texture/*filepath", handelY.TextureAssets()) - return nil -} - -func newSkinApi(r *httprouter.Router, handel *handle.Handel) error { - r.PUT("/api/v1/user/reg", handel.Reg()) - r.GET("/api/v1/config", handel.GetConfig()) - r.GET("/api/v1/user", handel.UserInfo()) - r.POST("/api/v1/user/password", handel.ChangePasswd()) - r.POST("/api/v1/user/name", handel.ChangeName()) - - r.GET("/api/v1/admin/users", handel.NeedAdmin(handel.ListUser())) - return nil + return r } diff --git a/server/server.go b/server/server.go index caf0ea6..9118611 100644 --- a/server/server.go +++ b/server/server.go @@ -1,54 +1,19 @@ package server import ( - "log/slog" "net/http" - "sync/atomic" "time" - "github.com/julienschmidt/httprouter" "github.com/xmdhs/authlib-skin/config" - "github.com/xmdhs/authlib-skin/utils" ) -func NewServer(c config.Config, sl *slog.Logger, route *httprouter.Router) (*http.Server, func()) { - trackid := atomic.Uint64{} +func NewServer(c config.Config, route http.Handler) (*http.Server, func()) { s := &http.Server{ ReadTimeout: 10 * time.Second, ReadHeaderTimeout: 5 * time.Second, WriteTimeout: 20 * time.Second, Addr: c.Port, - Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - if sl.Enabled(ctx, slog.LevelInfo) { - ip, _ := utils.GetIP(r, c.RaelIP) - trackid.Add(1) - ctx = setCtx(ctx, &reqInfo{ - URL: r.URL.String(), - IP: ip, - TrackId: trackid.Load(), - }) - r = r.WithContext(ctx) - } - if c.Debug && sl.Enabled(ctx, slog.LevelDebug) { - sl.DebugContext(ctx, r.Method) - } - cors(route).ServeHTTP(w, r) - }), + Handler: route, } return s, func() { s.Close() } } - -func cors(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - header := w.Header() - if r.Header.Get("Access-Control-Request-Method") != "" { - header.Set("Access-Control-Allow-Methods", r.Header.Get("Access-Control-Request-Method")) - } - header.Set("Access-Control-Allow-Origin", "*") - header.Set("Access-Control-Allow-Headers", "*") - header.Set("Access-Control-Allow-Private-Network", "true") - header.Set("Access-Control-Max-Age", "3600") - h.ServeHTTP(w, r) - }) -} diff --git a/server/slog.go b/server/slog.go index 3240ace..e75f68a 100644 --- a/server/slog.go +++ b/server/slog.go @@ -3,40 +3,18 @@ package server import ( "context" "log/slog" + + "github.com/go-chi/chi/v5/middleware" ) -type reqInfo struct { - URL string - IP string - TrackId uint64 -} - -type reqInfoKeyType string - -var reqinfoKey reqInfoKeyType = "reqinfoKey" - -func setCtx(ctx context.Context, r *reqInfo) context.Context { - return context.WithValue(ctx, reqinfoKey, r) -} - -func getFromCtx(ctx context.Context) *reqInfo { - v := ctx.Value(reqinfoKey) - if v == nil { - return nil - } - return v.(*reqInfo) -} - type warpSlogHandle struct { slog.Handler } func (w *warpSlogHandle) Handle(ctx context.Context, r slog.Record) error { - if w.Enabled(ctx, slog.LevelInfo) { - ri := getFromCtx(ctx) - if ri != nil { - r.AddAttrs(slog.String("ip", ri.IP), slog.String("url", ri.URL), slog.Uint64("trackID", ri.TrackId)) - } + id := middleware.GetReqID(ctx) + if id != "" { + r.AddAttrs(slog.String("trackID", id)) } return w.Handler.Handle(ctx, r) } diff --git a/server/wire_gen.go b/server/wire_gen.go index ec3affd..0a1a339 100644 --- a/server/wire_gen.go +++ b/server/wire_gen.go @@ -54,13 +54,8 @@ func InitializeRoute(ctx context.Context, c config.Config) (*http.Server, func() httpClient := ProvideHttpClient() webService := service.NewWebService(c, client, httpClient, cache, privateKey) handel := handle.NewHandel(webService, validate, c, logger) - router, err := route.NewRoute(yggdrasil3, handel) - if err != nil { - cleanup2() - cleanup() - return nil, nil, err - } - server, cleanup3 := NewServer(c, logger, router) + httpHandler := route.NewRoute(yggdrasil3, handel, c, handler) + server, cleanup3 := NewServer(c, httpHandler) return server, func() { cleanup3() cleanup2() diff --git a/service/admin.go b/service/admin.go index 0bfa672..9f84e46 100644 --- a/service/admin.go +++ b/service/admin.go @@ -36,6 +36,9 @@ func (w *WebService) ListUser(ctx context.Context, page int) ([]model.UserList, ul := make([]model.UserList, 0, len(u)) for _, v := range u { + if v.Edges.Profile == nil { + continue + } ul = append(ul, model.UserList{ UserInfo: model.UserInfo{ UID: v.ID, @@ -44,6 +47,7 @@ func (w *WebService) ListUser(ctx context.Context, page int) ([]model.UserList, }, Email: v.Email, RegIp: v.RegIP, + Name: v.Edges.Profile.Name, }) } diff --git a/service/user.go b/service/user.go index b6011dc..7612743 100644 --- a/service/user.go +++ b/service/user.go @@ -124,10 +124,11 @@ func (w *WebService) ChangePasswd(ctx context.Context, p model.ChangePasswd, tok return fmt.Errorf("ChangePasswd: %w", ErrPassWord) } pass, salt := utils.Argon2ID(p.New) - - err = w.client.UserToken.UpdateOne(u.Edges.Token).AddTokenID(1).Exec(ctx) - if err != nil { - return fmt.Errorf("ChangePasswd: %w", err) + if u.Edges.Token != nil { + err := w.client.UserToken.UpdateOne(u.Edges.Token).AddTokenID(1).Exec(ctx) + if err != nil { + return fmt.Errorf("ChangePasswd: %w", err) + } } err = w.cache.Del([]byte("auth" + strconv.Itoa(t.UID))) if err != nil { diff --git a/service/yggdrasil/user.go b/service/yggdrasil/user.go index 6c6f087..30f1eb4 100644 --- a/service/yggdrasil/user.go +++ b/service/yggdrasil/user.go @@ -95,6 +95,10 @@ func (y *Yggdrasil) Authenticate(cxt context.Context, auth yggdrasil.Authenticat if err != nil { return yggdrasil.Token{}, fmt.Errorf("Authenticate: %w", err) } + if u.Edges.Profile == nil { + return yggdrasil.Token{}, fmt.Errorf("Authenticate: %w", ErrUserDisable) + } + jwts, err := newJwtToken(y.prikey, strconv.FormatUint(utoken.TokenID, 10), clientToken, u.Edges.Profile.UUID, u.ID) if err != nil { return yggdrasil.Token{}, fmt.Errorf("Authenticate: %w", err) diff --git a/utils/ip.go b/utils/ip.go index a4e3ad1..768fbb3 100644 --- a/utils/ip.go +++ b/utils/ip.go @@ -4,30 +4,9 @@ import ( "fmt" "net" "net/http" - "strings" ) -func GetIP(r *http.Request, fromHeader bool) (string, error) { - if fromHeader { - //Get IP from the X-REAL-IP header - ip := r.Header.Get("X-REAL-IP") - netIP := net.ParseIP(ip) - if netIP != nil { - return ip, nil - } - - //Get IP from X-FORWARDED-FOR header - ips := r.Header.Get("X-FORWARDED-FOR") - splitIps := strings.Split(ips, ",") - for _, ip := range splitIps { - netIP := net.ParseIP(ip) - if netIP != nil { - return ip, nil - } - } - } - - //Get IP from RemoteAddr +func GetIP(r *http.Request) (string, error) { ip, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { return "", err