diff --git a/global/config.go b/config/config.go similarity index 98% rename from global/config.go rename to config/config.go index b00a9616..6dae08e1 100644 --- a/global/config.go +++ b/config/config.go @@ -1,4 +1,4 @@ -package global +package config import ( "io" @@ -11,6 +11,9 @@ import ( "github.com/yaoapp/kun/exception" ) +// Conf 配置参数 +var Conf Config + // Config 系统配置 type Config struct { XiangConfig @@ -132,3 +135,7 @@ func (cfg *Config) SetDefaults() { cfg.RootScreen = cfg.Root + "/screens" } } + +func init() { + Conf = NewConfig() +} diff --git a/global/config_test.go b/config/config_test.go similarity index 99% rename from global/config_test.go rename to config/config_test.go index cc608233..6057aa6d 100644 --- a/global/config_test.go +++ b/config/config_test.go @@ -1,4 +1,4 @@ -package global +package config import ( "os" diff --git a/global/init_test.go b/global/init_test.go index 6b7fcf55..8525468f 100644 --- a/global/init_test.go +++ b/global/init_test.go @@ -6,10 +6,11 @@ import ( "testing" "github.com/yaoapp/gou" + "github.com/yaoapp/xiang/config" "github.com/yaoapp/xiang/table" ) -var cfg Config +var cfg config.Config func TestMain(m *testing.M) { diff --git a/global/load.go b/global/load.go index e92cde74..67212d2f 100644 --- a/global/load.go +++ b/global/load.go @@ -10,6 +10,7 @@ import ( "github.com/yaoapp/gou" "github.com/yaoapp/kun/exception" + "github.com/yaoapp/xiang/config" "github.com/yaoapp/xiang/table" ) @@ -33,7 +34,7 @@ type AppRoot struct { } // Load 根据配置加载 API, FLow, Model, Plugin -func Load(cfg Config) { +func Load(cfg config.Config) { LoadEngine(cfg.Path) LoadApp(AppRoot{ APIs: cfg.RootAPI, @@ -47,7 +48,7 @@ func Load(cfg Config) { } // Reload 根据配置重新加载 API, FLow, Model, Plugin -func Reload(cfg Config) { +func Reload(cfg config.Config) { gou.APIs = map[string]*gou.API{} gou.Models = map[string]*gou.Model{} gou.Flows = map[string]*gou.Flow{} diff --git a/global/user_test.go b/global/user_test.go new file mode 100644 index 00000000..310fdefa --- /dev/null +++ b/global/user_test.go @@ -0,0 +1,28 @@ +package global + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/xiang/user" +) + +func TestUserAuth(t *testing.T) { + res := user.Auth("email", "xiang@iqka.com", "A123456p+") + assert.True(t, res.Has("user")) + assert.True(t, res.Has("token")) + assert.True(t, res.Has("expires_at")) + assert.Panics(t, func() { + user.Auth("email", "xiang@iqka.com", "A123456p+22") + }) + + res = user.Auth("mobile", "13900001111", "U123456p+") + assert.True(t, res.Has("user")) + assert.True(t, res.Has("token")) + assert.True(t, res.Has("expires_at")) + + assert.Panics(t, func() { + user.Auth("email", "1390000111", "A123456p+22") + }) + +} diff --git a/global/vars.go b/global/vars.go index 67800468..0c407b41 100644 --- a/global/vars.go +++ b/global/vars.go @@ -8,6 +8,7 @@ import ( assetfs "github.com/elazarl/go-bindata-assetfs" "github.com/yaoapp/gou" + "github.com/yaoapp/xiang/config" "github.com/yaoapp/xun/capsule" ) @@ -21,7 +22,7 @@ const DOMAIN = "*.iqka.com" var AllowHosts = []string{} // Conf 配置文件 -var Conf Config +var Conf config.Config // FileServer 静态服务 var FileServer http.Handler = http.FileServer(assetFS()) @@ -43,7 +44,7 @@ func init() { AllowHosts = append(AllowHosts, domain) } - Conf = NewConfig() + Conf = config.Conf // 数据库连接 if len(Conf.Database.Primary) > 0 { diff --git a/go.mod b/go.mod index 3754093b..b6d1586f 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/gin-gonic/gin v1.7.4 // indirect github.com/go-sql-driver/mysql v1.6.0 // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/hashicorp/go-plugin v1.4.3 // indirect github.com/hashicorp/yamux v0.0.0-20210826001029-26ff87cf9493 // indirect github.com/jmoiron/sqlx v1.3.4 // indirect @@ -23,8 +24,8 @@ require ( github.com/robertkrimen/otto v0.0.0-20210927222213-f9375a256948 // indirect github.com/spf13/cobra v1.2.1 // indirect github.com/stretchr/testify v1.7.0 - github.com/yaoapp/gou v0.0.0-20210929100518-8de252e7bab8 // indirect - github.com/yaoapp/kun v0.6.3 + github.com/yaoapp/gou v0.0.0-20210929112923-09baf3629b95 // indirect + github.com/yaoapp/kun v0.6.4 github.com/yaoapp/xun v0.5.2 // indirect golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect diff --git a/go.sum b/go.sum index ff9d7ec7..047cf8f0 100644 --- a/go.sum +++ b/go.sum @@ -117,6 +117,8 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -367,12 +369,16 @@ github.com/yaoapp/gou v0.0.0-20210929042348-e322f26e3a51 h1:LlWIuWmbEETkY91qP/yg github.com/yaoapp/gou v0.0.0-20210929042348-e322f26e3a51/go.mod h1:mJj2wa3oXVlk/WOHazW8eEZTyva98zDVjIur9lj8aiA= github.com/yaoapp/gou v0.0.0-20210929100518-8de252e7bab8 h1:MPVIqqqF5PV3SEy9a4XNghWoErLp5MQDMDTqA2ikbvw= github.com/yaoapp/gou v0.0.0-20210929100518-8de252e7bab8/go.mod h1:mJj2wa3oXVlk/WOHazW8eEZTyva98zDVjIur9lj8aiA= +github.com/yaoapp/gou v0.0.0-20210929112923-09baf3629b95 h1:KOMi0RR1ynNgLyaN7YIXVcS0Aj5YUhkad1IQ7Px8SL4= +github.com/yaoapp/gou v0.0.0-20210929112923-09baf3629b95/go.mod h1:EKDxzk74c04dULx5+lJfTpMkR+z4QswAl0NC7DXDiWU= github.com/yaoapp/kun v0.6.1 h1:gbjP5wmAuJLe1RGZ2evH4rPCVWykf0iqbksaHikF/GI= github.com/yaoapp/kun v0.6.1/go.mod h1:igsTcWDnzpp0HtRN7sBP+XOEN2tzoC5hk2MyoPFs3xA= github.com/yaoapp/kun v0.6.2 h1:QbtdZpVIklRDGEvL5YYRf/eR05YFZVFk/8rX2vy2K0Q= github.com/yaoapp/kun v0.6.2/go.mod h1:igsTcWDnzpp0HtRN7sBP+XOEN2tzoC5hk2MyoPFs3xA= github.com/yaoapp/kun v0.6.3 h1:0wD0lo0Cl78O+CoKi+DRVp6AeQFSrFInF+ghu+mww8M= github.com/yaoapp/kun v0.6.3/go.mod h1:igsTcWDnzpp0HtRN7sBP+XOEN2tzoC5hk2MyoPFs3xA= +github.com/yaoapp/kun v0.6.4 h1:5iEoZwaE0GZCLzB5uGmEr8X547CGvy0L61Ochmp47nY= +github.com/yaoapp/kun v0.6.4/go.mod h1:igsTcWDnzpp0HtRN7sBP+XOEN2tzoC5hk2MyoPFs3xA= github.com/yaoapp/xun v0.5.2 h1:DedZ26FpcXfkLnPKXzJsrbVmpWDRx9adUo8AvOcNT+g= github.com/yaoapp/xun v0.5.2/go.mod h1:y107NMHO635nhqJxMt582i1iqLjnNxAoeYr5/500UBw= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/init_test.go b/init_test.go index 687e0123..e10f6e5b 100644 --- a/init_test.go +++ b/init_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/yaoapp/gou" - "github.com/yaoapp/xiang/global" + "github.com/yaoapp/xiang/config" ) -var cfg global.Config +var cfg config.Config func TestMain(m *testing.M) { diff --git a/user/process.go b/user/process.go index 692030de..7a6a8880 100644 --- a/user/process.go +++ b/user/process.go @@ -3,16 +3,32 @@ package user import ( "github.com/yaoapp/gou" "github.com/yaoapp/kun/any" + "github.com/yaoapp/kun/exception" "github.com/yaoapp/kun/maps" + "github.com/yaoapp/kun/utils" ) func init() { - // 注册处理器 gou.RegisterProcessHandler("xiang.user.Captcha", ProcessCaptcha) + gou.RegisterProcessHandler("xiang.user.Login", ProcessLogin) } // ProcessLogin xiang.user.Login 用户登录 func ProcessLogin(process *gou.Process) interface{} { + process.ValidateArgNums(1) + payload := process.ArgsMap(0) + utils.Dump(payload) + email := any.Of(payload.Get("email")).CString() + mobile := any.Of(payload.Get("mobile")).CString() + password := any.Of(payload.Get("password")).CString() + if email != "" { + return Auth("email", email, password) + } else if mobile != "" { + utils.Dump(mobile, password) + return Auth("mobile", mobile, password) + } + + exception.New("参数错误", 400).Ctx(payload).Throw() return nil } diff --git a/user/user.go b/user/user.go index a00006b6..6a013d3c 100644 --- a/user/user.go +++ b/user/user.go @@ -1 +1,91 @@ package user + +import ( + "fmt" + "time" + + "github.com/golang-jwt/jwt" + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/exception" + "github.com/yaoapp/kun/maps" + "github.com/yaoapp/xiang/config" + "golang.org/x/crypto/bcrypt" +) + +// JwtClaims JWT claims +type JwtClaims struct { + ID int + Type string + Name string + jwt.StandardClaims +} + +var loginTypes = map[string]string{ + "email": "email", + "mobile": "mobile", +} + +// Auth 用户身份鉴权 +func Auth(field string, value string, password string) maps.Map { + column, has := loginTypes[field] + if !has { + exception.New("登录方式(%s)尚未支持", 400, field).Throw() + } + + user := gou.Select("xiang.user") + rows, err := user.Get(gou.QueryParam{ + Select: []interface{}{"id", "password", "name", "type", "email", "mobile", "extra"}, + Limit: 1, + Wheres: []gou.QueryWhere{ + {Column: column, Value: value}, + {Column: "status", Value: "enabled"}, + }, + }) + + if err != nil { + exception.New("数据库查询错误", 500, field).Throw() + } + + if len(rows) == 0 { + exception.New("用户不存在(%s)", 404, value).Throw() + } + + row := rows[0] + passwordHash := row.Get("password").(string) + + err = bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(password)) + if err != nil { + exception.New("登录密码错误", 403, value).Throw() + } + + expiresAt := time.Now().Unix() + 3600 + token := MakeToken(row, expiresAt) + row.Del("password") + return maps.Map{ + "expires_at": expiresAt, + "token": token, + "user": row, + } +} + +// MakeToken 生成 JWT Token +func MakeToken(row maps.Map, ExpiresAt int64) string { + claims := &JwtClaims{ + ID: int(row.Get("id").(int64)), + Type: row.Get("type").(string), + Name: row.Get("name").(string), + StandardClaims: jwt.StandardClaims{ + Subject: fmt.Sprintf("%d", row.Get("id")), + ExpiresAt: ExpiresAt, + Issuer: fmt.Sprintf("%d", row.Get("id")), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + tokenString, err := token.SignedString([]byte(config.Conf.JWT.Secret)) + if err != nil { + exception.New("生成登录口令失败 %s", 500, err).Throw() + } + + return tokenString +} diff --git a/user/user_test.go b/user/user_test.go deleted file mode 100644 index 7c5f604c..00000000 --- a/user/user_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package user - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestCaptcha(t *testing.T) { - id, content := MakeCaptcha(CaptchaOption{ - Type: "audio", - Width: 240, - Height: 80, - Length: 4, - Lang: "zh", - }) - assert.IsType(t, "string", id) - assert.IsType(t, "string", content) - captchaStore.Get(id, false) - assert.True(t, ValidateCaptcha(id, captchaStore.Get(id, false))) - - id, content = MakeCaptcha(CaptchaOption{ - Type: "math", - Width: 240, - Height: 80, - Length: 4, - Lang: "zh", - }) - assert.IsType(t, "string", id) - assert.IsType(t, "string", content) - captchaStore.Get(id, false) - assert.True(t, ValidateCaptcha(id, captchaStore.Get(id, false))) - - id, content = MakeCaptcha(CaptchaOption{ - Type: "digit", - Width: 240, - Height: 80, - Length: 4, - Lang: "zh", - }) - assert.IsType(t, "string", id) - assert.IsType(t, "string", content) - captchaStore.Get(id, false) - assert.True(t, ValidateCaptcha(id, captchaStore.Get(id, false))) -} diff --git a/xiang/apis/user.http.json b/xiang/apis/user.http.json index cb666614..db701bb2 100644 --- a/xiang/apis/user.http.json +++ b/xiang/apis/user.http.json @@ -21,7 +21,7 @@ "method": "POST", "guard": "-", "process": "xiang.user.Login", - "in": ["$payload.email", "$payload.password", "$payload.captcha"], + "in": [":payload"], "out": { "status": 200, "type": "application/json"