diff --git a/handle/yggdrasil/validate.go b/handle/yggdrasil/validate.go index 8c1257b..69b6d32 100644 --- a/handle/yggdrasil/validate.go +++ b/handle/yggdrasil/validate.go @@ -1,13 +1,35 @@ 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) Validate() httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - + cxt := r.Context() + a, err := utils.DeCodeBody[yggdrasil.ValidateToken](r.Body, y.validate) + if err != nil { + y.logger.DebugContext(cxt, err.Error()) + handleYgError(cxt, w, yggdrasil.Error{ErrorMessage: err.Error()}, 400) + return + } + err = y.yggdrasilService.ValidateToken(cxt, a) + if err != nil { + if errors.Is(err, sutils.ErrTokenInvalid) { + y.logger.DebugContext(cxt, err.Error()) + handleYgError(cxt, w, yggdrasil.Error{ErrorMessage: "Invalid token.", Error: "ForbiddenOperationException"}, 403) + return + } + y.logger.WarnContext(cxt, err.Error()) + handleYgError(cxt, w, yggdrasil.Error{ErrorMessage: err.Error()}, 500) + return + } + w.WriteHeader(204) } } diff --git a/model/yggdrasil/model.go b/model/yggdrasil/model.go index 65aa753..899dbeb 100644 --- a/model/yggdrasil/model.go +++ b/model/yggdrasil/model.go @@ -34,3 +34,8 @@ type TokenUser struct { ID string `json:"id"` Properties []any `json:"properties"` } + +type ValidateToken struct { + AccessToken string `json:"accessToken" validate:"required,jwt"` + ClientToken string `json:"clientToken"` +} diff --git a/service/utils/auth.go b/service/utils/auth.go new file mode 100644 index 0000000..4337eb0 --- /dev/null +++ b/service/utils/auth.go @@ -0,0 +1,58 @@ +package utils + +import ( + "context" + "errors" + "fmt" + "strconv" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/xmdhs/authlib-skin/db/ent" + "github.com/xmdhs/authlib-skin/db/ent/usertoken" + "github.com/xmdhs/authlib-skin/model" + "github.com/xmdhs/authlib-skin/model/yggdrasil" +) + +var ( + ErrTokenInvalid = errors.New("token 无效") +) + +func Auth(ctx context.Context, t yggdrasil.ValidateToken, client *ent.Client, jwtKey string) error { + token, err := jwt.ParseWithClaims(t.AccessToken, &model.TokenClaims{}, func(t *jwt.Token) (interface{}, error) { + return []byte(jwtKey), nil + }) + if err != nil { + return fmt.Errorf("Auth: %w", err) + } + + claims, ok := token.Claims.(*model.TokenClaims) + if !ok || !token.Valid { + return fmt.Errorf("Auth: %w", ErrTokenInvalid) + } + if t.ClientToken != "" && t.ClientToken != claims.CID { + return fmt.Errorf("Auth: %w", ErrTokenInvalid) + } + + it, err := claims.GetIssuedAt() + if err != nil { + return fmt.Errorf("Auth: %w", err) + } + et, err := claims.GetExpirationTime() + if err != nil { + return fmt.Errorf("Auth: %w", err) + } + invalidTime := it.Add(et.Time.Sub(it.Time)) + if time.Now().After(invalidTime) { + return fmt.Errorf("Auth: %w", ErrTokenInvalid) + } + + ut, err := client.UserToken.Query().Where(usertoken.UUIDEQ(claims.Subject)).First(ctx) + if err != nil { + return fmt.Errorf("Auth: %w", err) + } + if strconv.FormatUint(ut.TokenID, 10) != claims.Tid { + return fmt.Errorf("Auth: %w", ErrTokenInvalid) + } + return nil +} diff --git a/service/yggdrasil/validate.go b/service/yggdrasil/validate.go new file mode 100644 index 0000000..49fce96 --- /dev/null +++ b/service/yggdrasil/validate.go @@ -0,0 +1,17 @@ +package yggdrasil + +import ( + "context" + "fmt" + + "github.com/xmdhs/authlib-skin/model/yggdrasil" + "github.com/xmdhs/authlib-skin/service/utils" +) + +func (y *Yggdrasil) ValidateToken(ctx context.Context, t yggdrasil.ValidateToken) error { + err := utils.Auth(ctx, t, y.client, y.config.JwtKey) + if err != nil { + return fmt.Errorf("ValidateToken: %w", err) + } + return nil +}