diff --git a/config/config.go b/config/config.go index 7cc818f..b0e4c18 100644 --- a/config/config.go +++ b/config/config.go @@ -10,7 +10,12 @@ type Config struct { Sql struct { MysqlDsn string } - Node int64 - Epoch int64 - Debug bool + Node int64 + Epoch int64 + Debug bool + JwtKey string + Cache struct { + Type string + Ram int + } } diff --git a/db/ent/schema/user.go b/db/ent/schema/user.go index 6652fc0..8e4075a 100644 --- a/db/ent/schema/user.go +++ b/db/ent/schema/user.go @@ -19,6 +19,7 @@ func (User) Fields() []ent.Field { field.String("email").Unique(), field.String("password"), field.String("salt"), + // 二进制状态位,保留 field.Int("state"), field.Int64("reg_time"), } diff --git a/db/ent/schema/usertoken.go b/db/ent/schema/usertoken.go index d51f2aa..a9d844c 100644 --- a/db/ent/schema/usertoken.go +++ b/db/ent/schema/usertoken.go @@ -13,6 +13,7 @@ type UserToken struct { // Fields of the UserToken. func (UserToken) Fields() []ent.Field { return []ent.Field{ + // 用于验证 jwt token 是否被注销,若相同则有效 field.Uint64("token_id"), } } diff --git a/go.mod b/go.mod index ef36a59..d70af21 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ go 1.21.0 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/bwmarrin/snowflake v0.3.0 github.com/go-playground/validator/v10 v10.15.3 github.com/go-sql-driver/mysql v1.7.1 @@ -15,15 +17,14 @@ require ( require ( ariga.io/atlas v0.10.2-0.20230427182402-87a07dfb83bf // indirect - github.com/VictoriaMetrics/fastcache v1.12.1 // indirect github.com/agext/levenshtein v1.2.1 // indirect - github.com/alecthomas/binary v0.0.0-20221018225505-74871811ee56 // indirect 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-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 + github.com/golang-jwt/jwt/v5 v5.0.0 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/go-cmp v0.5.6 // indirect github.com/hashicorp/hcl/v2 v2.13.0 // indirect diff --git a/go.sum b/go.sum index 9eafec7..877838d 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,7 @@ github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tj github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/alecthomas/binary v0.0.0-20221018225505-74871811ee56 h1:CXWdlGkIdY4W1KGym1dFxwzRrLhneeonNSOwrhuhwQM= github.com/alecthomas/binary v0.0.0-20221018225505-74871811ee56/go.mod h1:v4e05/vzE8ubOim1No9Xx5eIQ/WRq6AtcnQIy/Z/JPs= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= @@ -36,6 +37,8 @@ github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrt github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= @@ -51,8 +54,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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 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= @@ -65,22 +66,14 @@ github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3v github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -114,8 +107,6 @@ golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.8.1-0.20230428195545-5283a0178901 h1:0wxTF6pSjIIhNt7mo9GvjDfzyCOiWhmICgtO/Ah948s= -golang.org/x/tools v0.8.1-0.20230428195545-5283a0178901/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/handle/handle.go b/handle/handle.go new file mode 100644 index 0000000..08aa07d --- /dev/null +++ b/handle/handle.go @@ -0,0 +1,25 @@ +package handle + +import ( + "log/slog" + + "github.com/go-playground/validator/v10" + "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/service" +) + +type Handel struct { + webService *service.WebService + validate *validator.Validate + config config.Config + logger *slog.Logger +} + +func NewHandel(webService *service.WebService, validate *validator.Validate, config config.Config, logger *slog.Logger) *Handel { + return &Handel{ + webService: webService, + validate: validate, + config: config, + logger: logger, + } +} diff --git a/handle/user.go b/handle/user.go index 7f86351..27f48d4 100644 --- a/handle/user.go +++ b/handle/user.go @@ -2,37 +2,32 @@ package handle import ( "errors" - "log/slog" "net/http" - "github.com/bwmarrin/snowflake" - "github.com/go-playground/validator/v10" "github.com/julienschmidt/httprouter" - "github.com/xmdhs/authlib-skin/config" - "github.com/xmdhs/authlib-skin/db/ent" "github.com/xmdhs/authlib-skin/model" "github.com/xmdhs/authlib-skin/service" "github.com/xmdhs/authlib-skin/utils" ) -func Reg(l *slog.Logger, client *ent.Client, v *validator.Validate, snow *snowflake.Node, c config.Config) httprouter.Handle { +func (h *Handel) Reg() httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { ctx := r.Context() - u, err := utils.DeCodeBody[model.User](r.Body, v) + u, err := utils.DeCodeBody[model.User](r.Body, h.validate) if err != nil { - l.InfoContext(ctx, err.Error()) + h.logger.InfoContext(ctx, err.Error()) handleError(ctx, w, err.Error(), model.ErrInput, 400) return } - err = service.Reg(ctx, u, snow, c, client) + err = h.webService.Reg(ctx, u) if err != nil { if errors.Is(err, service.ErrExistUser) { - l.DebugContext(ctx, err.Error()) + h.logger.DebugContext(ctx, err.Error()) handleError(ctx, w, err.Error(), model.ErrExistUser, 400) return } - l.WarnContext(ctx, err.Error()) + h.logger.WarnContext(ctx, err.Error()) handleError(ctx, w, err.Error(), model.ErrService, 500) return } diff --git a/handle/yggdrasil/authenticate.go b/handle/yggdrasil/authenticate.go index 8d4782b..98ce5e6 100644 --- a/handle/yggdrasil/authenticate.go +++ b/handle/yggdrasil/authenticate.go @@ -1,21 +1,19 @@ package yggdrasil import ( - "log/slog" "net/http" - "github.com/go-playground/validator/v10" "github.com/julienschmidt/httprouter" "github.com/xmdhs/authlib-skin/model/yggdrasil" "github.com/xmdhs/authlib-skin/utils" ) -func Authenticate(l *slog.Logger, v *validator.Validate) httprouter.Handle { +func (y *Yggdrasil) Authenticate() httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { cxt := r.Context() - a, err := utils.DeCodeBody[yggdrasil.Authenticate](r.Body, v) + a, err := utils.DeCodeBody[yggdrasil.Authenticate](r.Body, y.validate) if err != nil { - l.InfoContext(cxt, err.Error()) + y.logger.InfoContext(cxt, err.Error()) handleYgError(cxt, w, yggdrasil.Error{ErrorMessage: err.Error()}, 400) return } diff --git a/handle/yggdrasil/yggdrasil.go b/handle/yggdrasil/yggdrasil.go new file mode 100644 index 0000000..8449c36 --- /dev/null +++ b/handle/yggdrasil/yggdrasil.go @@ -0,0 +1,22 @@ +package yggdrasil + +import ( + "log/slog" + + "github.com/go-playground/validator/v10" + yggdrasilS "github.com/xmdhs/authlib-skin/service/yggdrasil" +) + +type Yggdrasil struct { + logger *slog.Logger + validate *validator.Validate + yggdrasilService *yggdrasilS.Yggdrasil +} + +func NewYggdrasil(logger *slog.Logger, validate *validator.Validate, yggdrasilService *yggdrasilS.Yggdrasil) *Yggdrasil { + return &Yggdrasil{ + logger: logger, + validate: validate, + yggdrasilService: yggdrasilService, + } +} diff --git a/model/model.go b/model/model.go index 452d961..e718339 100644 --- a/model/model.go +++ b/model/model.go @@ -1,5 +1,7 @@ package model +import "github.com/golang-jwt/jwt/v5" + type API[T any] struct { Code APIStatus `json:"code"` Data T `json:"data"` @@ -11,3 +13,8 @@ type User struct { Password string `validate:"required,min=6,max=50"` Name string `validate:"required,min=3,max=16"` } + +type TokenClaims struct { + Tid string `json:"tid"` + jwt.RegisteredClaims +} diff --git a/model/yggdrasil/model.go b/model/yggdrasil/model.go index 03962d0..43dabb8 100644 --- a/model/yggdrasil/model.go +++ b/model/yggdrasil/model.go @@ -16,3 +16,21 @@ type Error struct { Error string `json:"error,omitempty"` ErrorMessage string `json:"errorMessage,omitempty"` } + +type Token struct { + AccessToken string `json:"accessToken"` + AvailableProfiles []TokenProfile `json:"availableProfiles"` + ClientToken string `json:"clientToken"` + SelectedProfile TokenProfile `json:"selectedProfile"` + User TokenUser `json:"user"` +} + +type TokenProfile struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type TokenUser struct { + ID string `json:"id"` + Properties []any `json:"properties"` +} diff --git a/server/provide.go b/server/provide.go index 963e17b..583a50d 100644 --- a/server/provide.go +++ b/server/provide.go @@ -13,6 +13,7 @@ import ( _ "github.com/go-sql-driver/mysql" "github.com/google/wire" "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/db/cache" "github.com/xmdhs/authlib-skin/db/ent" ) @@ -81,4 +82,8 @@ func ProvideSnowflake(c config.Config) (*snowflake.Node, error) { return n, nil } -var Set = wire.NewSet(ProvideSlog, ProvideDB, ProvideEnt, ProvideValidate, ProvideSnowflake) +func ProvideCache(c config.Config) cache.Cache { + return cache.NewFastCache(c.Cache.Ram) +} + +var Set = wire.NewSet(ProvideSlog, ProvideDB, ProvideEnt, ProvideValidate, ProvideSnowflake, ProvideCache) diff --git a/server/route/route.go b/server/route/route.go index e71eb99..95a12ee 100644 --- a/server/route/route.go +++ b/server/route/route.go @@ -2,23 +2,19 @@ package route import ( "fmt" - "log/slog" - "github.com/bwmarrin/snowflake" - "github.com/go-playground/validator/v10" "github.com/julienschmidt/httprouter" - "github.com/xmdhs/authlib-skin/config" - "github.com/xmdhs/authlib-skin/db/ent" "github.com/xmdhs/authlib-skin/handle" + "github.com/xmdhs/authlib-skin/handle/yggdrasil" ) -func NewRoute(l *slog.Logger, client *ent.Client, v *validator.Validate, snow *snowflake.Node, c config.Config) (*httprouter.Router, error) { +func NewRoute(yggService *yggdrasil.Yggdrasil, handel *handle.Handel) (*httprouter.Router, error) { r := httprouter.New() err := newYggdrasil(r) if err != nil { return nil, fmt.Errorf("NewRoute: %w", err) } - err = newSkinApi(r, l, client, v, snow, c) + err = newSkinApi(r, handel) if err != nil { return nil, fmt.Errorf("NewRoute: %w", err) } @@ -30,7 +26,7 @@ func newYggdrasil(r *httprouter.Router) error { return nil } -func newSkinApi(r *httprouter.Router, l *slog.Logger, client *ent.Client, v *validator.Validate, snow *snowflake.Node, c config.Config) error { - r.PUT("/api/v1/user/reg", handle.Reg(l, client, v, snow, c)) +func newSkinApi(r *httprouter.Router, handel *handle.Handel) error { + r.PUT("/api/v1/user/reg", handel.Reg()) return nil } diff --git a/server/wire.go b/server/wire.go index 44ca990..e0993b8 100644 --- a/server/wire.go +++ b/server/wire.go @@ -8,9 +8,16 @@ import ( "github.com/google/wire" "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/handle" + "github.com/xmdhs/authlib-skin/handle/yggdrasil" "github.com/xmdhs/authlib-skin/server/route" + "github.com/xmdhs/authlib-skin/service" + yggdrasilS "github.com/xmdhs/authlib-skin/service/yggdrasil" ) func InitializeRoute(ctx context.Context, c config.Config) (*http.Server, func(), error) { - panic(wire.Build(Set, route.NewRoute, NewSlog, NewServer)) + panic(wire.Build(Set, route.NewRoute, NewSlog, + NewServer, handle.NewHandel, yggdrasil.NewYggdrasil, + service.NewWebService, yggdrasilS.NewYggdrasil, + )) } diff --git a/server/wire_gen.go b/server/wire_gen.go index 118467b..1bdcc1c 100644 --- a/server/wire_gen.go +++ b/server/wire_gen.go @@ -9,7 +9,11 @@ package server import ( "context" "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/handle" + yggdrasil2 "github.com/xmdhs/authlib-skin/handle/yggdrasil" "github.com/xmdhs/authlib-skin/server/route" + "github.com/xmdhs/authlib-skin/service" + "github.com/xmdhs/authlib-skin/service/yggdrasil" "net/http" ) @@ -22,6 +26,7 @@ import ( func InitializeRoute(ctx context.Context, c config.Config) (*http.Server, func(), error) { handler := ProvideSlog(c) logger := NewSlog(handler) + validate := ProvideValidate() db, cleanup, err := ProvideDB(c) if err != nil { return nil, nil, err @@ -31,14 +36,12 @@ func InitializeRoute(ctx context.Context, c config.Config) (*http.Server, func() cleanup() return nil, nil, err } - validate := ProvideValidate() - node, err := ProvideSnowflake(c) - if err != nil { - cleanup2() - cleanup() - return nil, nil, err - } - router, err := route.NewRoute(logger, client, validate, node, c) + cache := ProvideCache(c) + yggdrasilYggdrasil := yggdrasil.NewYggdrasil(client, cache, c) + yggdrasil3 := yggdrasil2.NewYggdrasil(logger, validate, yggdrasilYggdrasil) + webService := service.NewWebService(c, client) + handel := handle.NewHandel(webService, validate, c, logger) + router, err := route.NewRoute(yggdrasil3, handel) if err != nil { cleanup2() cleanup() diff --git a/service/user.go b/service/user.go index 8b5780a..06a9055 100644 --- a/service/user.go +++ b/service/user.go @@ -10,10 +10,7 @@ import ( "strings" "time" - "github.com/bwmarrin/snowflake" "github.com/google/uuid" - "github.com/xmdhs/authlib-skin/config" - "github.com/xmdhs/authlib-skin/db/ent" "github.com/xmdhs/authlib-skin/db/ent/user" "github.com/xmdhs/authlib-skin/model" "github.com/xmdhs/authlib-skin/utils" @@ -24,10 +21,8 @@ var ( ErrExitsName = errors.New("用户名已存在") ) -func Reg(ctx context.Context, u model.User, snow *snowflake.Node, - c config.Config, e *ent.Client, -) error { - tx, err := e.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted}) +func (w *WebService) Reg(ctx context.Context, u model.User) error { + tx, err := w.client.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted}) if err != nil { return fmt.Errorf("Reg: %w", err) } @@ -41,7 +36,7 @@ func Reg(ctx context.Context, u model.User, snow *snowflake.Node, } p, s := utils.Argon2ID(u.Password) - du, err := e.User.Create(). + du, err := w.client.User.Create(). SetEmail(u.Email). SetPassword(p). SetSalt(s). @@ -52,13 +47,13 @@ func Reg(ctx context.Context, u model.User, snow *snowflake.Node, } var userUuid string - if c.OfflineUUID { + if w.config.OfflineUUID { userUuid = uuidGen(u.Name) } else { userUuid = strings.ReplaceAll(uuid.New().String(), "-", "") } - _, err = e.UserProfile.Create(). + _, err = w.client.UserProfile.Create(). SetUser(du). SetName(u.Name). SetUUID(userUuid). diff --git a/service/web.go b/service/web.go new file mode 100644 index 0000000..0981599 --- /dev/null +++ b/service/web.go @@ -0,0 +1,18 @@ +package service + +import ( + "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/db/ent" +) + +type WebService struct { + config config.Config + client *ent.Client +} + +func NewWebService(c config.Config, e *ent.Client) *WebService { + return &WebService{ + config: c, + client: e, + } +} diff --git a/service/yggdrasil/authenticate.go b/service/yggdrasil/authenticate.go index 2cb5f8a..a8c8373 100644 --- a/service/yggdrasil/authenticate.go +++ b/service/yggdrasil/authenticate.go @@ -5,37 +5,69 @@ import ( "encoding/binary" "errors" "fmt" + "strings" "time" - "github.com/xmdhs/authlib-skin/db/cache" + "github.com/golang-jwt/jwt/v5" + "github.com/google/uuid" "github.com/xmdhs/authlib-skin/db/ent" + "github.com/xmdhs/authlib-skin/db/ent/user" + "github.com/xmdhs/authlib-skin/model" "github.com/xmdhs/authlib-skin/model/yggdrasil" + "github.com/xmdhs/authlib-skin/utils" ) var ( - ErrRate = errors.New("频率限制") + ErrRate = errors.New("频率限制") + ErrPassWord = errors.New("错误的密码或邮箱") ) -func Authenticate(cxt context.Context, client *ent.Client, auth yggdrasil.Authenticate, cache cache.Cache) error { +func (y *Yggdrasil) Authenticate(cxt context.Context, auth yggdrasil.Authenticate) (yggdrasil.Token, error) { key := []byte("Authenticate" + auth.Username) - v, err := cache.Get(key) + v, err := y.cache.Get(key) if err != nil { - return fmt.Errorf("Authenticate: %w", err) + return yggdrasil.Token{}, fmt.Errorf("Authenticate: %w", err) } if v != nil { u := binary.BigEndian.Uint64(v) t := time.Unix(int64(u), 0) if time.Now().Before(t) { - return fmt.Errorf("Authenticate: %w", ErrRate) + return yggdrasil.Token{}, fmt.Errorf("Authenticate: %w", ErrRate) } } b := make([]byte, 8) binary.BigEndian.PutUint64(b, uint64(time.Now().Add(10*time.Second).Unix())) - err = cache.Put(key, b, time.Now().Add(20*time.Second)) + err = y.cache.Put(key, b, time.Now().Add(20*time.Second)) if err != nil { - return fmt.Errorf("Authenticate: %w", err) + return yggdrasil.Token{}, fmt.Errorf("Authenticate: %w", err) } - return nil + u, err := y.client.User.Query().Where(user.EmailEQ(auth.Username)).WithProfile().WithToken().First(cxt) + if err != nil { + var nf *ent.NotFoundError + if errors.As(err, &nf) { + return yggdrasil.Token{}, fmt.Errorf("Authenticate: %w", ErrPassWord) + } + return yggdrasil.Token{}, fmt.Errorf("Authenticate: %w", err) + } + if !utils.Argon2Compare(auth.Password, u.Password, u.Salt) { + return yggdrasil.Token{}, fmt.Errorf("Authenticate: %w", ErrPassWord) + } + clientToken := auth.ClientToken + if clientToken == "" { + clientToken = strings.ReplaceAll(uuid.New().String(), "-", "") + } + + claims := model.TokenClaims{ + Tid: u.Edges.Profile.UUID, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * 24 * time.Hour)), + Issuer: "authlib-skin", + Subject: u.Edges.Profile.UUID, + }, + } + _ = claims + + return yggdrasil.Token{}, nil } diff --git a/service/yggdrasil/yggdrasil.go b/service/yggdrasil/yggdrasil.go new file mode 100644 index 0000000..bac2d4a --- /dev/null +++ b/service/yggdrasil/yggdrasil.go @@ -0,0 +1,21 @@ +package yggdrasil + +import ( + "github.com/xmdhs/authlib-skin/config" + "github.com/xmdhs/authlib-skin/db/cache" + "github.com/xmdhs/authlib-skin/db/ent" +) + +type Yggdrasil struct { + client *ent.Client + cache cache.Cache + c config.Config +} + +func NewYggdrasil(client *ent.Client, cache cache.Cache, c config.Config) *Yggdrasil { + return &Yggdrasil{ + client: client, + cache: cache, + c: c, + } +} diff --git a/utils/argon2id.go b/utils/argon2id.go index 6e24ed0..9ee3292 100644 --- a/utils/argon2id.go +++ b/utils/argon2id.go @@ -18,8 +18,12 @@ func Argon2ID(pass string) (password string, salt string) { return base64.StdEncoding.EncodeToString(b), base64.StdEncoding.EncodeToString(s) } -func Argon2Compare(pass, hashPass string, salt []byte) bool { - b := argon2.IDKey([]byte(pass), salt, 1, 64*1024, 1, 32) +func Argon2Compare(pass, hashPass string, salt string) bool { + s, err := base64.StdEncoding.DecodeString(hashPass) + if err != nil { + return false + } + b := argon2.IDKey([]byte(pass), s, 1, 64*1024, 1, 32) hb, err := base64.StdEncoding.DecodeString(hashPass) if err != nil { return false