From 82b64a4bb290f9fd9b5d467d2121dbe9fa6c21b1 Mon Sep 17 00:00:00 2001 From: veypi Date: Thu, 21 Oct 2021 18:09:40 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=8A=A0=E5=AF=86=E6=9C=BA?= =?UTF-8?q?=E5=88=B6=E8=AE=BE=E8=AE=A1=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/api.go | 3 + api/app/app.go | 39 +++++-- api/role/auth.go | 5 + api/role/role.go | 31 ++---- api/role/router.go | 2 +- api/user/user.go | 104 ++++++++++++------- api/user/user_role.go | 5 +- cfg/cfg.go | 38 +++---- libs/app/user.go | 48 +++++++++ libs/auth/auth.go | 66 ++++++++++++ libs/base/api_handler.go | 3 +- libs/{auth => base}/user_handler.go | 18 ++-- libs/key/app.go | 11 ++ libs/{auth/user_key.go => key/user.go} | 14 +-- libs/token/user.go | 127 +++++++++++++++++++++++ main.go | 15 ++- models/app.go | 33 ++++-- models/role.go | 51 +++++++--- models/user.go | 136 ++++--------------------- oaf/package.json | 2 + oaf/public/favicon.ico | Bin 4286 -> 3694 bytes oaf/public/index.html | 33 +++--- oaf/src/App.vue | 8 +- oaf/src/api/index.ts | 29 ++---- oaf/src/components/WxLogin.vue | 10 +- oaf/src/components/one-icon/icon.vue | 28 +++++ oaf/src/components/one-icon/index.ts | 26 +++++ oaf/src/libs/util.ts | 2 +- oaf/src/main.ts | 7 ++ oaf/src/router/index.ts | 21 +++- oaf/src/store/index.ts | 18 +++- oaf/src/views/404.vue | 24 +++++ oaf/src/views/Home.vue | 18 ++++ oaf/src/views/login.vue | 25 +++-- oaf/src/views/register.vue | 16 ++- oaf/yarn.lock | 15 +++ sub/app.go | 54 ++++++++++ sub/init.go | 131 ++++++++++++++++++++++++ sub/role.go | 114 +++++++++++++++++++++ sub/web.go | 17 +--- 40 files changed, 1026 insertions(+), 321 deletions(-) create mode 100644 libs/app/user.go create mode 100644 libs/auth/auth.go rename libs/{auth => base}/user_handler.go (71%) create mode 100644 libs/key/app.go rename libs/{auth/user_key.go => key/user.go} (55%) create mode 100644 libs/token/user.go create mode 100644 oaf/src/components/one-icon/icon.vue create mode 100644 oaf/src/components/one-icon/index.ts create mode 100644 oaf/src/views/404.vue create mode 100644 sub/app.go create mode 100644 sub/init.go create mode 100644 sub/role.go diff --git a/api/api.go b/api/api.go index 9a25ec4..5acc86f 100644 --- a/api/api.go +++ b/api/api.go @@ -2,6 +2,7 @@ package api import ( "OneAuth/api/app" + "OneAuth/api/role" "OneAuth/api/user" "OneAuth/api/wx" "github.com/veypi/OneBD" @@ -18,5 +19,7 @@ func Router(r OneBD.Router) { user.Router(r.SubRouter("/user")) wx.Router(r.SubRouter("wx")) app.Router(r.SubRouter("app")) + role.Router(r) + //message.Router(r.SubRouter("/message")) } diff --git a/api/app/app.go b/api/app/app.go index 4e950be..0faf2c7 100644 --- a/api/app/app.go +++ b/api/app/app.go @@ -2,6 +2,7 @@ package app import ( "OneAuth/cfg" + "OneAuth/libs/auth" "OneAuth/libs/base" "OneAuth/libs/oerr" "OneAuth/models" @@ -26,14 +27,40 @@ type appHandler struct { func (h *appHandler) Get() (interface{}, error) { id := h.Meta().Params("id") - if id == "" { - return nil, oerr.ApiArgsMissing - } h.query = &models.App{} - h.query.UUID = id - err := cfg.DB().Where(h.query).First(h.query).Error + isSelf := h.Meta().Query("is_self") + if isSelf != "" { + // 无权限可以获取本系统基本信息 + h.query.ID = cfg.CFG.APPID + err := cfg.DB().Where(h.query).First(h.query).Error + return h.query, err + } + err := h.ParsePayload(h.Meta()) if err != nil { return nil, err } - return h.query, nil + if !h.GetAuth(auth.APP, id).CanRead() { + return nil, oerr.NoAuth + } + if id != "" { + h.query.UUID = id + err := cfg.DB().Where(h.query).First(h.query).Error + return h.query, err + } + // 注释代码为获取已经绑定的应用 + //user := &models.User{} + //user.ID = h.Payload.ID + //err := cfg.DB().Preload("Roles.Auths").Preload("Auths").Where(user).First(user).Error + //if err != nil { + // return nil, oerr.DBErr.Attach(err) + //} + //ids := make([]string, 0, 10) + //for _, a := range user.GetAuths() { + // if a.RID == auth.Login && a.Level.CanDo() { + // ids = append(ids, a.RUID) + // } + //} + list := make([]*models.App, 0, 10) + err = cfg.DB().Find(&list).Error + return list, err } diff --git a/api/role/auth.go b/api/role/auth.go index 7d3996f..4c2a8a3 100644 --- a/api/role/auth.go +++ b/api/role/auth.go @@ -2,7 +2,9 @@ package role import ( "OneAuth/cfg" + "OneAuth/libs/auth" "OneAuth/libs/base" + "OneAuth/libs/oerr" "OneAuth/models" "github.com/veypi/OneBD" "github.com/veypi/OneBD/core" @@ -17,6 +19,9 @@ type authHandler struct { } func (h *authHandler) Get() (interface{}, error) { + if !h.GetAuth(auth.Auth).CanRead() { + return nil, oerr.NoAuth + } l := make([]*models.Auth, 0, 10) return &l, cfg.DB().Find(&l).Error } diff --git a/api/role/role.go b/api/role/role.go index 1bbefd7..d4bb1b3 100644 --- a/api/role/role.go +++ b/api/role/role.go @@ -2,6 +2,7 @@ package role import ( "OneAuth/cfg" + "OneAuth/libs/auth" "OneAuth/libs/base" "OneAuth/libs/oerr" "OneAuth/models" @@ -20,8 +21,9 @@ type roleHandler struct { func (h *roleHandler) Get() (interface{}, error) { id := h.Meta().ParamsInt("id") - aid := h.Meta().ParamsInt("aid") - act := h.Meta().Params("action") + if !h.GetAuth(auth.Role, h.Meta().Params("id")).CanRead() { + return nil, oerr.NoAuth + } if id > 0 { role := &models.Role{} role.ID = uint(id) @@ -29,21 +31,6 @@ func (h *roleHandler) Get() (interface{}, error) { if err != nil { return nil, err } - if aid <= 0 { - return role, nil - } - if !h.CheckAuth("admin", "").CanDoAny() { - return nil, oerr.NoAuth - } - at := &models.RoleAuth{} - at.RoleID = role.ID - at.AuthID = uint(aid) - defer models.SyncGlobalRoles() - if act == "bind" { - err = cfg.DB().Where(at).FirstOrCreate(at).Error - } else if act == "unbind" { - err = cfg.DB().Where(at).Delete(at).Error - } return role, nil } roles := make([]*models.Role, 0, 10) @@ -52,7 +39,7 @@ func (h *roleHandler) Get() (interface{}, error) { } func (h *roleHandler) Post() (interface{}, error) { - if !h.CheckAuth("role", "").CanCreate() { + if !h.GetAuth(auth.Role).CanCreate() { return nil, oerr.NoAuth } role := &models.Role{} @@ -61,15 +48,14 @@ func (h *roleHandler) Post() (interface{}, error) { return nil, err } role.ID = 0 - if role.Category == 0 || role.Name == "" { + if role.Name == "" { return nil, oerr.ApiArgsMissing } - defer models.SyncGlobalRoles() return role, cfg.DB().Where(role).FirstOrCreate(role).Error } func (h *roleHandler) Patch() (interface{}, error) { - if !h.CheckAuth("role", "").CanUpdate() { + if !h.GetAuth(auth.Role).CanUpdate() { return nil, oerr.NoAuth } query := &struct { @@ -120,7 +106,7 @@ func (h *roleHandler) Patch() (interface{}, error) { } func (h *roleHandler) Delete() (interface{}, error) { - if !h.CheckAuth("role").CanDelete() { + if !h.GetAuth(auth.Role).CanDelete() { return nil, oerr.NoAuth } rid := h.Meta().ParamsInt("id") @@ -133,6 +119,5 @@ func (h *roleHandler) Delete() (interface{}, error) { if err != nil { return nil, err } - defer models.SyncGlobalRoles() return nil, cfg.DB().Delete(role).Error } diff --git a/api/role/router.go b/api/role/router.go index 712bf03..bf17337 100644 --- a/api/role/router.go +++ b/api/role/router.go @@ -8,6 +8,6 @@ import ( func Router(r OneBD.Router) { r.Set("/role/", roleP, rfc.MethodGet, rfc.MethodPost) r.Set("/role/:id", roleP, rfc.MethodGet, rfc.MethodDelete, rfc.MethodPatch) - r.Set("/role/:id/:action/:aid", roleP, rfc.MethodGet) + r.Set("/role/:id/:action/:rid", roleP, rfc.MethodGet) r.Set("/auth/", authP, rfc.MethodGet) } diff --git a/api/user/user.go b/api/user/user.go index 4115131..3faf9f9 100644 --- a/api/user/user.go +++ b/api/user/user.go @@ -2,9 +2,13 @@ package user import ( "OneAuth/cfg" + "OneAuth/libs/app" "OneAuth/libs/base" "OneAuth/libs/oerr" + "OneAuth/libs/token" "OneAuth/models" + "errors" + //"OneAuth/ws" "encoding/base64" "fmt" @@ -68,10 +72,17 @@ func (h *handler) Get() (interface{}, error) { // Post register user func (h *handler) Post() (interface{}, error) { - if !cfg.CFG.EnableRegister { - return nil, oerr.NoAuth.AttachStr("register disabled.") + self := &models.App{} + self.ID = cfg.CFG.APPID + err := cfg.DB().Where(self).First(self).Error + if err != nil { + return nil, oerr.DBErr.Attach(err) + } + if !self.EnableRegister { + return nil, oerr.NoAuth.AttachStr("register disabled") } var userdata = struct { + UUID string `json:"uuid"` Username string `json:"username"` Password string `json:"password"` Nickname string `json:"nickname"` @@ -101,16 +112,40 @@ func (h *handler) Post() (interface{}, error) { h.User.Username = userdata.Username h.User.Email = userdata.Email h.User.Position = userdata.Position - if err := h.User.UpdateAuth(string(pass)); err != nil { + if err := h.User.UpdatePass(string(pass)); err != nil { log.HandlerErrs(err) return nil, oerr.ResourceCreatedFailed } - tx := cfg.DB().Begin() - if err := tx.Create(&h.User).Error; err != nil { - tx.Rollback() - return nil, oerr.ResourceDuplicated + err = cfg.DB().Transaction(func(tx *gorm.DB) error { + if err := tx.Create(&h.User).Error; err != nil { + return oerr.ResourceDuplicated + } + err := app.AddUser(tx, self.ID, h.User.ID, self.InitRoleID) + if err != nil { + return err + } + if userdata.UUID != self.UUID && userdata.UUID != "" { + target := &models.App{} + target.UUID = userdata.UUID + err = tx.Where(target).First(target).Error + if err != nil { + return err + } + if target.EnableRegister { + err := app.AddUser(tx, target.ID, h.User.ID, target.InitRoleID) + if err != nil { + return err + } + } else { + // TODO + } + return err + } + return nil + }) + if err != nil { + return nil, err } - tx.Commit() return h.User, nil } @@ -142,7 +177,7 @@ func (h *handler) Patch() (interface{}, error) { return nil, oerr.NoAuth } if len(opts.Password) >= 6 { - if err := target.UpdateAuth(opts.Password); err != nil { + if err := target.UpdatePass(opts.Password); err != nil { log.HandlerErrs(err) return nil, oerr.ApiArgsError.AttachStr(err.Error()) } @@ -187,9 +222,9 @@ func (h *handler) Head() (interface{}, error) { if len(uid) == 0 || len(password) == 0 { return nil, oerr.ApiArgsError } - appID, err := strconv.Atoi(h.Meta().Query("app_id")) - if err != nil || appID <= 0 { - return nil, oerr.ApiArgsMissing + appUUID := h.Meta().Query("uuid") + if appUUID == "" { + return nil, oerr.ApiArgsMissing.AttachStr("uuid") } h.User = new(models.User) uidType := h.Meta().Query("uid_type") @@ -203,32 +238,15 @@ func (h *handler) Head() (interface{}, error) { default: h.User.Username = uid } - app := &models.App{} - app.ID = uint(appID) - err = cfg.DB().Where(app).Find(app).Error + target := &models.App{} + target.UUID = appUUID + err = cfg.DB().Where(target).Find(target).Error if err != nil { return nil, oerr.DBErr.Attach(err) } - if err := cfg.DB().Preload("Roles").Where(h.User).First(h.User).Error; err != nil { + if err := cfg.DB().Preload("Roles.Auths").Preload("Auths").Where(h.User).First(h.User).Error; err != nil { if err.Error() == gorm.ErrRecordNotFound.Error() { - // admin 登录自动注册 - if h.User.Username == "admin" { - r := rand.New(rand.NewSource(time.Now().UnixNano())) - h.User.Icon = fmt.Sprintf("/media/icon/default/%04d.jpg", r.Intn(230)) - err = h.User.UpdateAuth(password) - if err != nil { - return nil, err - } - role := &models.Role{} - role.ID = 1 - h.User.Roles = []*models.Role{role} - err = cfg.DB().Create(h.User).Error - if err != nil { - return nil, err - } - } else { - return nil, oerr.AccountNotExist - } + return nil, oerr.AccountNotExist } else { log.HandlerErrs(err) return nil, oerr.DBErr.Attach(err) @@ -238,15 +256,27 @@ func (h *handler) Head() (interface{}, error) { if err != nil || !isAuth { return nil, oerr.PassError.Attach(err) } - if h.User.Status == "disabled" { + au := &models.AppUser{} + au.UserID = h.User.ID + au.AppID = target.ID + err = cfg.DB().Where(au).First(au).Error + appID := target.ID + h.Meta().SetHeader("content", target.UserRefreshUrl) + if err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, err + } + appID = cfg.CFG.APPID + h.Meta().SetHeader("content", "/app/"+target.UUID) + } else if au.Disabled { return nil, oerr.DisableLogin } - token, err := h.User.GetToken(app.Key, app.ID) + tokenStr, err := token.GetToken(h.User, appID) if err != nil { log.HandlerErrs(err) return nil, oerr.Unknown.Attach(err) } - h.Meta().SetHeader("auth_token", token) + h.Meta().SetHeader("auth_token", tokenStr) log.Info().Msg(h.User.Username + " login") return nil, nil } diff --git a/api/user/user_role.go b/api/user/user_role.go index be0673c..861958c 100644 --- a/api/user/user_role.go +++ b/api/user/user_role.go @@ -35,9 +35,8 @@ func (h *userRoleHandler) Post() (interface{}, error) { err = cfg.DB().First(query, query.ID).Error } else if query.Name != "" { err = cfg.DB().Where(map[string]interface{}{ - "name": query.Name, - "category": query.Category, - "tag": query.Tag, + "name": query.Name, + "tag": query.Tag, }).First(query).Error if errors.Is(err, gorm.ErrRecordNotFound) { err = cfg.DB().Create(query).Error diff --git a/cfg/cfg.go b/cfg/cfg.go index ba2d52a..05719cb 100644 --- a/cfg/cfg.go +++ b/cfg/cfg.go @@ -8,19 +8,19 @@ import ( "gorm.io/gorm" ) -var Path = cmd.GetCfgPath("OneAuth", "oa") +var Path = cmd.GetCfgPath("oa", "settings") var CFG = &struct { - AdminUser string - Host string - LoggerPath string - LoggerLevel string - Key string - TimeFormat string - Debug bool - EXEDir string - EnableRegister bool - DB struct { + AdminUser string + Host string + LoggerPath string + LoggerLevel string + APPID uint + APPKey string + TimeFormat string + Debug bool + EXEDir string + DB struct { Type string Addr string User string @@ -28,14 +28,14 @@ var CFG = &struct { DB string } }{ - AdminUser: "admin", - Host: "0.0.0.0:4001", - LoggerPath: "", - LoggerLevel: "debug", - TimeFormat: "2006/01/02 15:04:05", - Debug: true, - EXEDir: "./", - EnableRegister: true, + APPID: 1, + AdminUser: "admin", + Host: "0.0.0.0:4001", + LoggerPath: "", + LoggerLevel: "debug", + TimeFormat: "2006/01/02 15:04:05", + Debug: true, + EXEDir: "./", DB: struct { Type string Addr string diff --git a/libs/app/user.go b/libs/app/user.go new file mode 100644 index 0000000..65424f8 --- /dev/null +++ b/libs/app/user.go @@ -0,0 +1,48 @@ +package app + +import ( + "OneAuth/libs/auth" + "OneAuth/libs/oerr" + "OneAuth/models" + "errors" + "gorm.io/gorm" +) + +func AddUser(tx *gorm.DB, appID uint, userID uint, roleID uint) error { + au := &models.AppUser{} + au.AppID = appID + au.UserID = userID + err := tx.Where(au).First(au).Error + if err == nil { + return oerr.ResourceDuplicated + } + if errors.Is(err, gorm.ErrRecordNotFound) { + err = tx.Create(au).Error + if err != nil { + return err + } + err = auth.BindUserRole(tx, userID, roleID) + if err != nil { + return err + } + return tx.Model(&models.App{}).Where("id = ?", appID).Update("user_count", gorm.Expr("user_count + ?", 1)).Error + } + return err +} +func EnableUser(tx *gorm.DB, appID uint, userID uint) error { + au := &models.AppUser{} + au.AppID = appID + au.UserID = userID + err := tx.Where(au).First(au).Error + if err != nil { + return err + } + return tx.Where(au).Update("disabled", false).Error +} + +func DisableUser(tx *gorm.DB, appID uint, userID uint) error { + au := &models.AppUser{} + au.AppID = appID + au.UserID = userID + return tx.Where(au).Update("disabled", true).Error +} diff --git a/libs/auth/auth.go b/libs/auth/auth.go new file mode 100644 index 0000000..22d4b4c --- /dev/null +++ b/libs/auth/auth.go @@ -0,0 +1,66 @@ +package auth + +import ( + "OneAuth/models" + "gorm.io/gorm" +) + +// 定义oa系统权限 + +type Resource = string + +const ( + User Resource = "user" + APP Resource = "app" + Res Resource = "resource" + Role Resource = "role" + Auth Resource = "auth" +) + +func BindUserRole(tx *gorm.DB, userID uint, roleID uint) error { + r := &models.Role{} + r.ID = roleID + err := tx.Where(r).First(r).Error + if err != nil { + return err + } + ur := &models.UserRole{} + ur.RoleID = roleID + if r.IsUnique { + err = tx.Where(ur).Update("user_id", userID).Error + } else { + ur.UserID = userID + err = tx.Where(ur).FirstOrCreate(ur).Error + } + return err +} + +func BindUserAuth(tx *gorm.DB, userID uint, resID uint, level models.AuthLevel, ruid string) error { + return bind(tx, userID, resID, level, ruid, false) +} + +func BindRoleAuth(tx *gorm.DB, roleID uint, resID uint, level models.AuthLevel, ruid string) error { + return bind(tx, roleID, resID, level, ruid, true) +} + +func bind(tx *gorm.DB, id uint, resID uint, level models.AuthLevel, ruid string, isRole bool) error { + r := &models.Resource{} + r.ID = resID + err := tx.Where(r).First(r).Error + if err != nil { + return err + } + au := &models.Auth{ + AppID: r.AppID, + ResourceID: resID, + RID: r.Name, + RUID: ruid, + Level: level, + } + if isRole { + au.RoleID = &id + } else { + au.UserID = &id + } + return tx.Where(au).FirstOrCreate(au).Error +} diff --git a/libs/base/api_handler.go b/libs/base/api_handler.go index 464b787..116a514 100644 --- a/libs/base/api_handler.go +++ b/libs/base/api_handler.go @@ -1,7 +1,6 @@ package base import ( - "OneAuth/libs/auth" "OneAuth/libs/oerr" "OneAuth/libs/tools" "github.com/json-iterator/go" @@ -17,7 +16,7 @@ var json = jsoniter.ConfigFastest type ApiHandler struct { OneBD.BaseHandler - auth.UserHandler + UserHandler } func (h *ApiHandler) Init(m OneBD.Meta) error { diff --git a/libs/auth/user_handler.go b/libs/base/user_handler.go similarity index 71% rename from libs/auth/user_handler.go rename to libs/base/user_handler.go index 6be1042..29d43ba 100644 --- a/libs/auth/user_handler.go +++ b/libs/base/user_handler.go @@ -1,15 +1,15 @@ -package auth +package base import ( - "OneAuth/cfg" "OneAuth/libs/oerr" + "OneAuth/libs/token" "OneAuth/models" "github.com/veypi/OneBD" "github.com/veypi/OneBD/rfc" ) type UserHandler struct { - Payload *models.PayLoad + Payload *token.PayLoad ignoreMethod map[rfc.Method]bool } @@ -17,12 +17,16 @@ func (a *UserHandler) Init(m OneBD.Meta) error { if a.ignoreMethod != nil && a.ignoreMethod[m.Method()] { return nil } - a.Payload = new(models.PayLoad) - token := m.GetHeader("auth_token") - if token == "" { + return a.ParsePayload(m) +} + +func (a *UserHandler) ParsePayload(m OneBD.Meta) error { + a.Payload = new(token.PayLoad) + tokenStr := m.GetHeader("auth_token") + if tokenStr == "" { return oerr.NotLogin } - ok, err := models.ParseToken(token, cfg.CFG.Key, a.Payload) + ok, err := token.ParseToken(tokenStr, a.Payload) if ok { return nil } diff --git a/libs/key/app.go b/libs/key/app.go new file mode 100644 index 0000000..b1953c9 --- /dev/null +++ b/libs/key/app.go @@ -0,0 +1,11 @@ +package key + +import "OneAuth/cfg" + +func App(id uint) string { + if id == cfg.CFG.APPID { + return cfg.CFG.APPKey + } + // TODO + return "" +} diff --git a/libs/auth/user_key.go b/libs/key/user.go similarity index 55% rename from libs/auth/user_key.go rename to libs/key/user.go index 4f6fbf3..4d112da 100644 --- a/libs/auth/user_key.go +++ b/libs/key/user.go @@ -1,24 +1,24 @@ -package auth +package key import ( - "OneAuth/models" + "OneAuth/cfg" "github.com/veypi/utils" "sync" ) var keyCache = sync.Map{} -func GetUserKey(uid uint, app *models.App) string { - if app.ID == 1 { +func User(uid uint, appID uint) string { + if appID == cfg.CFG.APPID { key, _ := keyCache.LoadOrStore(uid, utils.RandSeq(16)) - return key.(string) + return cfg.CFG.APPKey + key.(string) } // TODO: 获取其他应用user_key return "" } -func RefreshUserKey(uid uint, app *models.App) string { - if app.ID == 1 { +func RefreshUser(uid uint, appID uint) string { + if appID == cfg.CFG.APPID { key := utils.RandSeq(16) keyCache.Store(uid, key) return key diff --git a/libs/token/user.go b/libs/token/user.go new file mode 100644 index 0000000..8da80a8 --- /dev/null +++ b/libs/token/user.go @@ -0,0 +1,127 @@ +package token + +import ( + "OneAuth/libs/key" + "OneAuth/models" + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "errors" + "strings" + "time" +) + +var ( + InvalidToken = errors.New("invalid token") + ExpiredToken = errors.New("expired token") +) + +type simpleAuth struct { + RID string `json:"rid"` + // 具体某个资源的id + RUID string `json:"ruid"` + Level models.AuthLevel `json:"level"` +} + +// TODO:: roles 是否会造成token过大 ? +type PayLoad struct { + ID uint `json:"id"` + AppID uint `json:"app_id"` + Iat int64 `json:"iat"` //token time + Exp int64 `json:"exp"` + Auth map[uint]*simpleAuth `json:"auth"` +} + +// GetAuth resource_uuid 缺省或仅第一个有效 权限会被更高权限覆盖 +func (p *PayLoad) GetAuth(ResourceID string, ResourceUUID ...string) models.AuthLevel { + res := models.AuthNone + if p == nil || p.Auth == nil { + return res + } + ruid := "" + if len(ResourceUUID) > 0 { + ruid = ResourceUUID[0] + } + for _, a := range p.Auth { + if a.RID == ResourceID { + if a.RUID != "" { + if a.RUID == ruid { + if a.Level > res { + res = a.Level + } + } else { + continue + } + } else if a.Level > res { + res = a.Level + } + } + } + return res +} + +func GetToken(u *models.User, appID uint) (string, error) { + header := map[string]string{ + "typ": "JWT", + "alg": "HS256", + } + //header := "{\"typ\": \"JWT\", \"alg\": \"HS256\"}" + now := time.Now().Unix() + payload := PayLoad{ + ID: u.ID, + AppID: appID, + Iat: now, + Exp: now + 60*60*24, + Auth: map[uint]*simpleAuth{}, + } + for _, a := range u.GetAuths() { + if appID == a.AppID { + payload.Auth[a.ID] = &simpleAuth{ + RID: a.RID, + RUID: a.RUID, + Level: a.Level, + } + } + } + a, err := json.Marshal(header) + if err != nil { + return "", err + } + b, err := json.Marshal(payload) + if err != nil { + return "", err + } + A := base64.StdEncoding.EncodeToString(a) + B := base64.StdEncoding.EncodeToString(b) + hmacCipher := hmac.New(sha256.New, []byte(key.User(payload.ID, payload.AppID))) + hmacCipher.Write([]byte(A + "." + B)) + C := hmacCipher.Sum(nil) + return A + "." + B + "." + base64.StdEncoding.EncodeToString(C), nil +} + +func ParseToken(token string, payload *PayLoad) (bool, error) { + var A, B, C string + if seqs := strings.Split(token, "."); len(seqs) == 3 { + A, B, C = seqs[0], seqs[1], seqs[2] + } else { + return false, InvalidToken + } + tempPayload, err := base64.StdEncoding.DecodeString(B) + if err != nil { + return false, err + } + if err := json.Unmarshal(tempPayload, payload); err != nil { + return false, err + } + hmacCipher := hmac.New(sha256.New, []byte(key.User(payload.ID, payload.AppID))) + hmacCipher.Write([]byte(A + "." + B)) + tempC := hmacCipher.Sum(nil) + if !hmac.Equal([]byte(C), []byte(base64.StdEncoding.EncodeToString(tempC))) { + return false, nil + } + if time.Now().Unix() > payload.Exp { + return false, ExpiredToken + } + return true, nil +} diff --git a/main.go b/main.go index ec2cd06..4e5c489 100644 --- a/main.go +++ b/main.go @@ -35,10 +35,15 @@ func main() { Value: cfg.CFG.LoggerPath, Destination: &cfg.CFG.LoggerPath, }, + &cli.UintFlag{ + Name: "id", + Value: cfg.CFG.APPID, + Destination: &cfg.CFG.APPID, + }, &cli.StringFlag{ Name: "key", - Value: cfg.CFG.Key, - Destination: &cfg.CFG.Key, + Value: cfg.CFG.APPKey, + Destination: &cfg.CFG.APPKey, }, &cli.StringFlag{ Name: "exe_dir", @@ -52,7 +57,11 @@ func main() { }, } app.Commands = []*cli.Command{ - &sub.Web, + sub.Web, + sub.App, + sub.Role, + sub.Resource, + sub.Init, } srv, err := cmd.NewSrv(app, sub.RunWeb, cfg.CFG, cfg.Path) if err != nil { diff --git a/models/app.go b/models/app.go index c34951d..e7fa0cd 100644 --- a/models/app.go +++ b/models/app.go @@ -4,9 +4,20 @@ var AppKeys = map[string]string{} type App struct { BaseModel - Name string `json:"name"` - Icon string `json:"icon"` - UUID string `json:"uuid"` + Name string `json:"name"` + Icon string `json:"icon"` + UUID string `json:"uuid" gorm:"unique"` + Des string `json:"des"` + Creator uint `json:"creator"` + UserCount uint `json:"user_count"` + Users []*User `json:"users" gorm:"many2many:app_users;"` + // 初始用户角色 + InitRoleID uint `json:"init_role_id"` + InitRole *Role `json:"init_role"` + // 是否在首页隐藏 + Hide bool `json:"hide"` + // PubKey string `json:"pub_key"` + // PrivateKey string `json:"private_key"` // 认证成功跳转链接 Host string `json:"host"` // 加解密用户token (key+key2) @@ -14,11 +25,13 @@ type App struct { // key oa发放给app 双方保存 针对app生成 每个应用有一个 // key2 app发放给oa app保存 oa使用一次销毁 针对当个用户生成 每个用户有一个 // 获取app用户加密秘钥key2 + // TODO UserRefreshUrl string `json:"user_refresh_url"` // app 校验用户token时使用 - Key string `json:"key"` - // 是否允许用户注册 - EnableRegister string `json:"enable_register"` + Key string `json:"-"` + // 是否允许用户自主注册 + EnableRegister bool `json:"enable_register"` + EnableUserKey bool `json:"enable_user_key"` EnableUser bool `json:"enable_user"` EnableWx bool `json:"enable_wx"` EnablePhone bool `json:"enable_phone"` @@ -26,6 +39,14 @@ type App struct { Wx *Wechat `json:"wx" gorm:"foreignkey:AppID;references:ID"` } +type AppUser struct { + BaseModel + AppID uint `json:"app_id"` + UserID uint `json:"user_id"` + Disabled bool `json:"disabled"` + Status string `json:"status"` +} + type Wechat struct { BaseModel AppID uint `json:"app_id"` diff --git a/models/role.go b/models/role.go index 751fe82..0ae083b 100644 --- a/models/role.go +++ b/models/role.go @@ -8,19 +8,22 @@ type UserRole struct { type Role struct { BaseModel - Name string `json:"name"` - // 角色类型 - // 1: 系统定义角色 2: 用户自定义角色 - Category uint `json:"category" gorm:"default:1"` + AppID uint `json:"app_id"` + App *App `json:"app"` + Name string `json:"name"` // 角色标签 Tag string `json:"tag" gorm:"default:''"` - Users []*User `json:"users" gorm:"many2many:user_role;"` + Users []*User `json:"users" gorm:"many2many:user_roles;"` // 具体权限 Auths []*Auth `json:"auths" gorm:"foreignkey:RoleID;references:ID"` IsUnique bool `json:"is_unique" gorm:"default:false"` } // AuthLevel 权限等级 +// 对于操作类权限 +// 0 禁止执行 +// 1 允许执行 +// 对于资源类权限 // 0 相当于没有 // 1 有限读权限 // 2 读权限 @@ -32,6 +35,7 @@ type AuthLevel uint const ( AuthNone AuthLevel = 0 + AuthDo AuthLevel = 1 // AuthPart TODO: 临时权限 AuthPart AuthLevel = 1 AuthRead AuthLevel = 2 @@ -41,6 +45,14 @@ const ( AuthAll AuthLevel = 6 ) +func (a AuthLevel) Upper(b AuthLevel) bool { + return a > b +} + +func (a AuthLevel) CanDo() bool { + return a > AuthNone +} + func (a AuthLevel) CanRead() bool { return a >= AuthRead } @@ -61,22 +73,33 @@ func (a AuthLevel) CanDoAny() bool { return a >= AuthAll } -// 资源权限 - +// Auth 资源权限 type Auth struct { BaseModel - Name string `json:"name"` // 该权限作用的应用 AppID uint `json:"app_id"` + App *App `json:"app"` // 权限绑定只能绑定一个 - RoleID uint `json:"role_id"` - UserID uint `json:"user_id"` + RoleID *uint `json:"role_id" gorm:""` + Role *Role `json:"role"` + UserID *uint `json:"user_id"` + User *User `json:"user"` // 资源id + ResourceID uint `json:"resource_id" gorm:"not null"` + Resource *Resource `json:"resource"` + // resource_name 用于其他系统方便区分权限的名字 RID string `json:"rid" gorm:""` // 具体某个资源的id - RUID string `json:"ruid"` - // 权限标签 - Tag string `json:"tag"` + RUID string `json:"ruid"` Level AuthLevel `json:"level"` - Des string `json:"des"` +} + +type Resource struct { + BaseModel + AppID uint `json:"app_id"` + App *App `json:"app"` + Name string `json:"name"` + // 权限标签 + Tag string `json:"tag"` + Des string `json:"des"` } diff --git a/models/user.go b/models/user.go index e1faf70..64fefd7 100644 --- a/models/user.go +++ b/models/user.go @@ -1,14 +1,7 @@ package models import ( - "crypto/hmac" - "crypto/sha256" - "encoding/base64" - "encoding/json" - "errors" "github.com/veypi/utils" - "strings" - "time" ) // User db user model @@ -25,46 +18,45 @@ type User struct { Status string `json:"status"` Icon string `json:"icon"` - Roles []*Role `json:"roles" gorm:"many2many:user_role;"` + Roles []*Role `json:"roles" gorm:"many2many:user_roles;"` + Apps []*App `json:"apps" gorm:"many2many:app_users;"` Auths []*Auth `json:"auths" gorm:"foreignkey:UserID;references:ID"` } -type simpleAuth struct { - RID string `json:"rid"` - // 具体某个资源的id - RUID string `json:"ruid"` - Level AuthLevel `json:"level"` +func (u *User) String() string { + return u.Username + ":" + u.Nickname } -// TODO:: roles 是否会造成token过大 ? -type PayLoad struct { - ID uint `json:"id"` - Iat int64 `json:"iat"` //token time - Exp int64 `json:"exp"` - Auth map[uint]*simpleAuth `json:"auth"` +func (u *User) GetAuths() []*Auth { + list := make([]*Auth, 0, 10) + for _, r := range u.Roles { + for _, a := range r.Auths { + list = append(list, a) + } + } + for _, a := range u.Auths { + list = append(list, a) + } + return list } -// GetAuth resource_uuid 缺省或仅第一个有效 权限会被更高权限覆盖 -func (p *PayLoad) GetAuth(ResourceID string, ResourceUUID ...string) AuthLevel { - res := AuthNone - if p == nil || p.Auth == nil { - return res - } +func (u *User) GetAuth(appID uint, ResourceID string, ResourceUUID ...string) AuthLevel { + var res = AuthNone ruid := "" if len(ResourceUUID) > 0 { ruid = ResourceUUID[0] } - for _, a := range p.Auth { - if a.RID == ResourceID { + for _, a := range u.GetAuths() { + if a.RID == ResourceID && a.AppID == appID { if a.RUID != "" { if a.RUID == ruid { - if a.Level > res { + if a.Level.Upper(res) { res = a.Level } } else { continue } - } else if a.Level > res { + } else if a.Level.Upper(res) { res = a.Level } } @@ -72,91 +64,7 @@ func (p *PayLoad) GetAuth(ResourceID string, ResourceUUID ...string) AuthLevel { return res } -func (u *User) String() string { - return u.Username + ":" + u.Nickname -} - -func (u *User) GetToken(key string, appID uint) (string, error) { - header := map[string]string{ - "typ": "JWT", - "alg": "HS256", - } - //header := "{\"typ\": \"JWT\", \"alg\": \"HS256\"}" - now := time.Now().Unix() - payload := PayLoad{ - ID: u.ID, - Iat: now, - Exp: now + 60*60*24, - Auth: map[uint]*simpleAuth{}, - } - for _, r := range u.Roles { - for _, a := range r.Auths { - if appID == a.AppID { - payload.Auth[a.ID] = &simpleAuth{ - RID: a.RID, - RUID: a.RUID, - Level: a.Level, - } - } - } - } - for _, a := range u.Auths { - if appID == a.AppID { - payload.Auth[a.ID] = &simpleAuth{ - RID: a.RID, - RUID: a.RUID, - Level: a.Level, - } - } - } - a, err := json.Marshal(header) - if err != nil { - return "", err - } - b, err := json.Marshal(payload) - if err != nil { - return "", err - } - A := base64.StdEncoding.EncodeToString(a) - B := base64.StdEncoding.EncodeToString(b) - hmacCipher := hmac.New(sha256.New, []byte(key)) - hmacCipher.Write([]byte(A + "." + B)) - C := hmacCipher.Sum(nil) - return A + "." + B + "." + base64.StdEncoding.EncodeToString(C), nil -} - -var ( - InvalidToken = errors.New("invalid token") - ExpiredToken = errors.New("expired token") -) - -func ParseToken(token string, key string, payload *PayLoad) (bool, error) { - var A, B, C string - if seqs := strings.Split(token, "."); len(seqs) == 3 { - A, B, C = seqs[0], seqs[1], seqs[2] - } else { - return false, InvalidToken - } - hmacCipher := hmac.New(sha256.New, []byte(key)) - hmacCipher.Write([]byte(A + "." + B)) - tempC := hmacCipher.Sum(nil) - if !hmac.Equal([]byte(C), []byte(base64.StdEncoding.EncodeToString(tempC))) { - return false, nil - } - tempPayload, err := base64.StdEncoding.DecodeString(B) - if err != nil { - return false, err - } - if err := json.Unmarshal(tempPayload, payload); err != nil { - return false, err - } - if time.Now().Unix() > payload.Exp { - return false, ExpiredToken - } - return true, nil -} - -func (u *User) UpdateAuth(ps string) (err error) { +func (u *User) UpdatePass(ps string) (err error) { u.RealCode = utils.RandSeq(32) u.CheckCode, err = utils.AesEncrypt(u.RealCode, []byte(ps)) return err diff --git a/oaf/package.json b/oaf/package.json index 8693115..e89c50c 100644 --- a/oaf/package.json +++ b/oaf/package.json @@ -8,11 +8,13 @@ "lint": "vue-cli-service lint" }, "dependencies": { + "@veypi/one-icon": "^1.0.1", "axios": "^0.21.1", "core-js": "^3.6.5", "js-base64": "^3.6.0", "vue": "^2.6.11", "vue-class-component": "^7.2.3", + "vue-m-message": "^3.1.0", "vue-property-decorator": "^9.1.2", "vue-router": "^3.2.0", "vuetify": "^2.4.0", diff --git a/oaf/public/favicon.ico b/oaf/public/favicon.ico index df36fcfb72584e00488330b560ebcf34a41c64c2..e26bc40e2c5fe25f828982150e79a809d3fb68f8 100644 GIT binary patch literal 3694 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 GdF>ys{&!RW literal 4286 zcmds*O-Phc6o&64GDVCEQHxsW(p4>LW*W<827=Unuo8sGpRux(DN@jWP-e29Wl%wj zY84_aq9}^Am9-cWTD5GGEo#+5Fi2wX_P*bo+xO!)p*7B;iKlbFd(U~_d(U?#hLj56 zPhFkj-|A6~Qk#@g^#D^U0XT1cu=c-vu1+SElX9NR;kzAUV(q0|dl0|%h|dI$%VICy zJnu2^L*Te9JrJMGh%-P79CL0}dq92RGU6gI{v2~|)p}sG5x0U*z<8U;Ij*hB9z?ei z@g6Xq-pDoPl=MANPiR7%172VA%r)kevtV-_5H*QJKFmd;8yA$98zCxBZYXTNZ#QFk2(TX0;Y2dt&WitL#$96|gJY=3xX zpCoi|YNzgO3R`f@IiEeSmKrPSf#h#Qd<$%Ej^RIeeYfsxhPMOG`S`Pz8q``=511zm zAm)MX5AV^5xIWPyEu7u>qYs?pn$I4nL9J!=K=SGlKLXpE<5x+2cDTXq?brj?n6sp= zphe9;_JHf40^9~}9i08r{XM$7HB!`{Ys~TK0kx<}ZQng`UPvH*11|q7&l9?@FQz;8 zx!=3<4seY*%=OlbCbcae?5^V_}*K>Uo6ZWV8mTyE^B=DKy7-sdLYkR5Z?paTgK-zyIkKjIcpyO z{+uIt&YSa_$QnN_@t~L014dyK(fOOo+W*MIxbA6Ndgr=Y!f#Tokqv}n<7-9qfHkc3 z=>a|HWqcX8fzQCT=dqVbogRq!-S>H%yA{1w#2Pn;=e>JiEj7Hl;zdt-2f+j2%DeVD zsW0Ab)ZK@0cIW%W7z}H{&~yGhn~D;aiP4=;m-HCo`BEI+Kd6 z={Xwx{TKxD#iCLfl2vQGDitKtN>z|-AdCN|$jTFDg0m3O`WLD4_s#$S diff --git a/oaf/public/index.html b/oaf/public/index.html index 642a583..fa6b9ab 100644 --- a/oaf/public/index.html +++ b/oaf/public/index.html @@ -1,19 +1,20 @@ - - - - - - <%= htmlWebpackPlugin.options.title %> - - - - - -
- - + + + + + + <%= htmlWebpackPlugin.options.title %> + + + + + +
+ + diff --git a/oaf/src/App.vue b/oaf/src/App.vue index af75969..2757044 100644 --- a/oaf/src/App.vue +++ b/oaf/src/App.vue @@ -6,11 +6,10 @@ dark >
+ glassdoor + 统一认证
- - 统一认证 - @@ -32,8 +31,9 @@ export default Vue.extend({ // }), - mounted() { + beforeCreate() { util.title('统一认证') + this.$store.dispatch('fetchSelf') } }) diff --git a/oaf/src/api/index.ts b/oaf/src/api/index.ts index d52f1d6..0ecde84 100644 --- a/oaf/src/api/index.ts +++ b/oaf/src/api/index.ts @@ -32,7 +32,7 @@ class Interface { const newFail = function (data: any) { if (data && data.code === 40001) { // no login - store.dispatch('handleLogOut') + store.dispatch('handleLogout') return } // eslint-disable-next-line @typescript-eslint/ban-ts-ignore @@ -52,7 +52,7 @@ class Interface { } else { newFail(data) if (data.code === 41001) { - store.dispatch('handleLogOut') + store.dispatch('handleLogout') // bus.$emit('log_out') } } @@ -106,6 +106,9 @@ const role = { const app = { local: '/api/app/', + self() { + return new Interface(ajax.get, this.local, {is_self: true}) + }, get(id: string) { return new Interface(ajax.get, this.local + id) }, @@ -116,15 +119,18 @@ const app = { const user = { local: '/api/user/', - register(username: string, password: string, prop?: any) { + register(username: string, password: string, uuid: string, prop?: any) { const data = Object.assign({ username: username, + uuid: uuid, password: Base64.encode(password) }, prop) return new Interface(ajax.post, this.local, data) }, - login(username: string, password: string) { + login(username: string, password: string, uuid: string) { return new Interface(ajax.head, this.local + username, { + uid_type: 'username', + uuid: uuid, password: Base64.encode(password) }) } @@ -196,21 +202,6 @@ const api = { return new Interface(ajax.get, '/api/user/', { username }) - }, - login(username: string, password: string) { - return new Interface(ajax.head, '/api/user/' + username, { - password: Base64.encode(password) - }) - }, - // @title 职位 - // @domain 部门 - register(username: string, password: string, domain?: string, title?: string) { - return new Interface(ajax.post, '/api/user/', { - username: username, - password: Base64.encode(password), - domain: domain, - title: title - }) } }, message: message diff --git a/oaf/src/components/WxLogin.vue b/oaf/src/components/WxLogin.vue index d2cfc67..4d02a21 100644 --- a/oaf/src/components/WxLogin.vue +++ b/oaf/src/components/WxLogin.vue @@ -9,7 +9,9 @@ import '@/libs/wwLogin.js' components: {} }) export default class WxLogin extends Vue { - goto(id: string, app: string, url: string, state?: string, href?: string) { + goto(id: string, app: string, url: string, state?: number, href?: string) { + // eslint-disable-next-line + // @ts-ignore window.WwLogin({ id: 'wx_reg', appid: id, @@ -21,13 +23,13 @@ export default class WxLogin extends Vue { } @Prop({default: ''}) - aid: '' + aid = '' @Prop({default: ''}) - app: '' + app = '' @Prop({default: ''}) - url: '' + url = '' mounted() { this.goto(this.aid, this.app, this.url, new Date().getTime()) diff --git a/oaf/src/components/one-icon/icon.vue b/oaf/src/components/one-icon/icon.vue new file mode 100644 index 0000000..44af783 --- /dev/null +++ b/oaf/src/components/one-icon/icon.vue @@ -0,0 +1,28 @@ + + + diff --git a/oaf/src/components/one-icon/index.ts b/oaf/src/components/one-icon/index.ts new file mode 100644 index 0000000..f0bd0d0 --- /dev/null +++ b/oaf/src/components/one-icon/index.ts @@ -0,0 +1,26 @@ +import Vue from 'vue' +import OneIcon from './icon.vue' + +function loadJS(url: string) { + const script = document.createElement('script') + script.type = 'text/javascript' + script.src = url + document.getElementsByTagName('head')[0].appendChild(script) +} + +export default { + installed: false, + install(vue: typeof Vue, options?: { href: '' }): void { + if (this.installed) { + return + } + this.installed = true + if (options && options.href) { + console.log(options.href) + loadJS(options.href) + } else { + console.error('not set iconfont href') + } + vue.component('one-icon', OneIcon) + } +} diff --git a/oaf/src/libs/util.ts b/oaf/src/libs/util.ts index 7fbd742..b1b9806 100644 --- a/oaf/src/libs/util.ts +++ b/oaf/src/libs/util.ts @@ -4,7 +4,7 @@ function padLeftZero(str: string): string { const util = { title: function (title: string) { - window.document.title = title ? title + ' - Home' : 'veypi project' + window.document.title = title ? title + ' - oa' : 'veypi project' }, getCookie(name: string) { const reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)') diff --git a/oaf/src/main.ts b/oaf/src/main.ts index 36a87a3..1833341 100644 --- a/oaf/src/main.ts +++ b/oaf/src/main.ts @@ -4,7 +4,14 @@ import router from './router' import store from './store' import vuetify from './plugins/vuetify' import {Api} from '@/api' +import OneIcon from '@veypi/one-icon' +import Message from 'vue-m-message' +import 'vue-m-message/dist/index.css' +Vue.use(Message) // will mount `Vue.prototype.$message` + +// Vue.use(OneIcon, {href: 'https://at.alicdn.com/t/font_2872366_7aws02sx9bl.js'}) +Vue.use(OneIcon, {href: './icon.js'}) Vue.use(Api) Vue.config.productionTip = false diff --git a/oaf/src/router/index.ts b/oaf/src/router/index.ts index 6d6179d..84718f7 100644 --- a/oaf/src/router/index.ts +++ b/oaf/src/router/index.ts @@ -4,13 +4,23 @@ import Home from '../views/Home.vue' import Demo from '@/views/demo.vue' import Login from '@/views/login.vue' import Register from '@/views/register.vue' +import NotFound from '@/views/404.vue' Vue.use(VueRouter) +// 避免push到相同路径报错 +// 获取原型对象上的push函数 +const originalPush = VueRouter.prototype.push +// 修改原型对象中的push方法 +VueRouter.prototype.push = function push(location: any) { + // eslint-disable-next-line + // @ts-ignore + return originalPush.call(this, location).catch(err => err) +} const routes: Array = [ { path: '/', - name: 'Home', + name: 'home', component: Home }, { @@ -19,12 +29,12 @@ const routes: Array = [ component: Demo }, { - path: '/login', + path: '/login/:uuid?', name: 'login', component: Login }, { - path: '/register', + path: '/register/:uuid?', name: 'register', component: Register }, @@ -32,6 +42,11 @@ const routes: Array = [ path: '/wx', name: 'wx', component: () => import('../views/wx.vue') + }, + { + path: '*', + name: '404', + component: NotFound } ] diff --git a/oaf/src/store/index.ts b/oaf/src/store/index.ts index 3d5636e..c2f1632 100644 --- a/oaf/src/store/index.ts +++ b/oaf/src/store/index.ts @@ -1,16 +1,30 @@ import Vue from 'vue' import Vuex from 'vuex' +import api from '@/api' +import router from '@/router' Vue.use(Vuex) export default new Vuex.Store({ state: { + oauuid: '', user: null }, mutations: { + setOA(state: any, data: any) { + state.oauuid = data.uuid + } }, actions: { + fetchSelf({commit}) { + api.app.self().Start(d => { + commit('setOA', d) + }) + }, + handleLogout() { + localStorage.removeItem('auth_token') + router.push({name: 'login'}) + } }, - modules: { - } + modules: {} }) diff --git a/oaf/src/views/404.vue b/oaf/src/views/404.vue new file mode 100644 index 0000000..f930b44 --- /dev/null +++ b/oaf/src/views/404.vue @@ -0,0 +1,24 @@ + + + + diff --git a/oaf/src/views/Home.vue b/oaf/src/views/Home.vue index e15109c..a5f60ee 100644 --- a/oaf/src/views/Home.vue +++ b/oaf/src/views/Home.vue @@ -6,20 +6,38 @@ diff --git a/oaf/src/views/login.vue b/oaf/src/views/login.vue index e952959..0bc2b8d 100644 --- a/oaf/src/views/login.vue +++ b/oaf/src/views/login.vue @@ -54,7 +54,8 @@ 登录 - + 注册 @@ -87,13 +88,26 @@ export default class Login extends Vue { ] } + get app_uuid() { + return this.$route.params.uuid || this.$store.state.oauuid + } + handleSubmit() { - this.$api.auth.login(this.formInline.user, this.formInline.password).Start( + // eslint-disable-next-line + // @ts-ignore + if (!this.$refs.form.validate()) { + return + } + this.$api.user.login(this.formInline.user, this.formInline.password, this.app_uuid).Start( data => { + console.log(data) if (util.checkLogin()) { // this.$message.success('登录成功') // EventBus.$emit('login', true) this.$nextTick(() => { + if (this.$route.query.redirect) { + window.location.href = this.$route.query.redirect as string + } this.$router.push({name: 'home'}) }) } else { @@ -105,12 +119,5 @@ export default class Login extends Vue { } ) } - - mounted() { - } - - created() { - console.log(this.formInline) - } } diff --git a/oaf/src/views/register.vue b/oaf/src/views/register.vue index e008b2e..044c2d0 100644 --- a/oaf/src/views/register.vue +++ b/oaf/src/views/register.vue @@ -90,20 +90,26 @@ export default class Register extends Vue { ] } + get app_uuid() { + return this.$route.params.uuid || this.$store.state.oauuid + } + handleSubmit() { + // eslint-disable-next-line + // @ts-ignore if (!this.$refs.form.validate()) { return } - this.$api.user.register(this.form.username, this.form.passwd).Start( + this.$api.user.register(this.form.username, this.form.passwd, this.app_uuid).Start( (data) => { - // this.$message.success('注册成功!') - this.$router.push({name: 'login'}) + this.$message.success('注册成功!') + this.$router.push({name: 'login', params: this.$route.params, query: this.$route.query}) }, (data) => { if (data && data.code === '31011') { - // this.$message.error('用户名重复') + this.$message.error('用户名重复') } else { - // this.$message.error('注册失败') + this.$message.error('注册失败') } } ) diff --git a/oaf/yarn.lock b/oaf/yarn.lock index 21b5362..6afae0b 100644 --- a/oaf/yarn.lock +++ b/oaf/yarn.lock @@ -1155,6 +1155,16 @@ semver "^7.3.2" tsutils "^3.17.1" +"@veypi/one-icon@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@veypi/one-icon/-/one-icon-1.0.1.tgz#138adbbcf0738ac40cad44552a20e211da8087c6" + integrity sha512-VEjK/SRpGTYKaqXu6FAVUXZtQy4hdZ886OI2eoMqMfMnt1pPExr9Vjz2NQOL1DZgMGT3mwlQHJyR3iJdz/eE1w== + dependencies: + core-js "^3.6.5" + vue "^2.6.11" + vue-class-component "^7.2.3" + vue-property-decorator "^9.1.2" + "@vue/babel-helper-vue-jsx-merge-props@^1.2.1": version "1.2.1" resolved "https://registry.npm.taobao.org/@vue/babel-helper-vue-jsx-merge-props/download/@vue/babel-helper-vue-jsx-merge-props-1.2.1.tgz?cache=0&sync_timestamp=1602853295839&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40vue%2Fbabel-helper-vue-jsx-merge-props%2Fdownload%2F%40vue%2Fbabel-helper-vue-jsx-merge-props-1.2.1.tgz#31624a7a505fb14da1d58023725a4c5f270e6a81" @@ -8876,6 +8886,11 @@ vue-loader@^15.9.2: vue-hot-reload-api "^2.3.0" vue-style-loader "^4.1.0" +vue-m-message@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/vue-m-message/-/vue-m-message-3.1.0.tgz#8061720ac777b624fad95375594ea142c7d55dc7" + integrity sha512-Mu9ykD7vrUFfhGWTbevHoX1JNTAkOl6X6jDzqVoF5eB9dQHG4jSLmKwlHMZLlnT45lwwSze6vuvi8q5dxCWLQw== + vue-property-decorator@^9.1.2: version "9.1.2" resolved "https://registry.npm.taobao.org/vue-property-decorator/download/vue-property-decorator-9.1.2.tgz#266a2eac61ba6527e2e68a6933cfb98fddab5457" diff --git a/sub/app.go b/sub/app.go new file mode 100644 index 0000000..9ed968c --- /dev/null +++ b/sub/app.go @@ -0,0 +1,54 @@ +package sub + +import ( + "OneAuth/cfg" + "OneAuth/models" + "github.com/urfave/cli/v2" + "github.com/veypi/utils" + "github.com/veypi/utils/log" +) + +var App = &cli.Command{ + Name: "app", + Subcommands: []*cli.Command{ + { + Name: "list", + Action: runAppList, + }, + { + Name: "create", + Action: runAppCreate, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "name", + Required: true, + }, + }, + }, + }, +} + +func runAppList(c *cli.Context) error { + list := make([]*models.App, 0, 10) + err := cfg.DB().Find(&list).Error + if err != nil { + return err + } + for _, a := range list { + log.Info().Msgf("%d: %s", a.ID, a.Name) + } + return nil +} + +func runAppCreate(c *cli.Context) error { + app := &models.App{} + app.Name = c.String("name") + app.Key = utils.RandSeq(16) + app.UUID = utils.RandSeq(8) + err := cfg.DB().Create(app).Error + if err != nil { + return err + } + log.Info().Msgf("app: %s\nuuid: %s\nkey: %s", app.Name, app.UUID, app.Key) + return nil +} diff --git a/sub/init.go b/sub/init.go new file mode 100644 index 0000000..1adca9b --- /dev/null +++ b/sub/init.go @@ -0,0 +1,131 @@ +package sub + +import ( + "OneAuth/cfg" + "OneAuth/libs/auth" + "OneAuth/models" + "github.com/urfave/cli/v2" + "github.com/veypi/utils/cmd" + "github.com/veypi/utils/log" + "strconv" +) + +var Init = &cli.Command{ + Name: "init", + Action: runInit, +} + +func runInit(c *cli.Context) error { + return InitSystem() +} + +// 初始化项目 + +func InitSystem() error { + db() + self, err := selfApp() + if err != nil { + return err + } + cfg.CFG.APPID = self.ID + cfg.CFG.APPKey = self.Key + err = cmd.DumpCfg(cfg.Path, cfg.CFG) + // TODO + //if err != nil { + // return err + //} + err = role(self.InitRoleID == 0) + return nil +} + +func db() { + db := cfg.DB() + log.HandlerErrs( + db.SetupJoinTable(&models.User{}, "Roles", &models.UserRole{}), + db.SetupJoinTable(&models.Role{}, "Users", &models.UserRole{}), + db.SetupJoinTable(&models.User{}, "Apps", &models.AppUser{}), + db.SetupJoinTable(&models.App{}, "Users", &models.AppUser{}), + db.AutoMigrate(&models.User{}, &models.Role{}, &models.Auth{}, &models.App{}), + ) + log.HandlerErrs( + db.AutoMigrate(&models.Wechat{}, &models.Resource{}), + ) +} + +func selfApp() (*models.App, error) { + self := &models.App{ + Name: "OA", + Icon: "", + UUID: "jU5Jo5hM", + Des: "", + Creator: 0, + UserCount: 0, + Hide: false, + Host: "", + UserRefreshUrl: "/", + Key: "cB43wF94MLTksyBK", + EnableRegister: true, + EnableUserKey: true, + EnableUser: true, + EnableWx: false, + EnablePhone: false, + EnableEmail: false, + Wx: nil, + } + return self, cfg.DB().Where("uuid = ?", self.UUID).FirstOrCreate(self).Error +} + +func role(reset_init_role bool) error { + authMap := make(map[string]*models.Resource) + n := []string{ + auth.APP, + auth.User, + auth.Res, + auth.Auth, + auth.Role, + } + var err error + adminRole := &models.Role{ + AppID: cfg.CFG.APPID, + Name: "admin", + IsUnique: false, + } + err = cfg.DB().Where(adminRole).FirstOrCreate(adminRole).Error + if err != nil { + return err + } + for _, na := range n { + a := &models.Resource{ + AppID: cfg.CFG.APPID, + Name: na, + Tag: "", + Des: "", + } + err = cfg.DB().Where(a).FirstOrCreate(a).Error + if err != nil { + return err + } + authMap[na] = a + err = auth.BindRoleAuth(cfg.DB(), adminRole.ID, a.ID, models.AuthAll, "") + if err != nil { + return err + } + } + userRole := &models.Role{ + AppID: cfg.CFG.APPID, + Name: "user", + IsUnique: false, + } + err = cfg.DB().Where(userRole).FirstOrCreate(userRole).Error + if err != nil { + return err + } + err = auth.BindRoleAuth(cfg.DB(), userRole.ID, authMap[auth.APP].ID, models.AuthRead, strconv.Itoa(int(cfg.CFG.APPID))) + if err != nil { + return err + } + if reset_init_role { + return cfg.DB().Model(&models.App{}).Where("id = ?", cfg.CFG.APPID).Update("init_role_id", adminRole.ID).Error + } + return nil +} diff --git a/sub/role.go b/sub/role.go new file mode 100644 index 0000000..52a519d --- /dev/null +++ b/sub/role.go @@ -0,0 +1,114 @@ +package sub + +import ( + "OneAuth/cfg" + "OneAuth/models" + "github.com/urfave/cli/v2" + "github.com/veypi/utils/log" +) + +var Role = &cli.Command{ + Name: "role", + Usage: "", + Description: "", + Subcommands: []*cli.Command{ + { + Name: "list", + Action: runRoleList, + }, + { + Name: "create", + Action: runRoleCreate, + Flags: []cli.Flag{ + &cli.UintFlag{ + Name: "id", + Usage: "app id", + Required: true, + }, + &cli.StringFlag{ + Name: "name", + Usage: "role name", + Required: true, + }, + }, + }, + }, + Flags: []cli.Flag{}, +} + +func runRoleList(c *cli.Context) error { + roles := make([]*models.Role, 0, 10) + err := cfg.DB().Find(&roles).Error + if err != nil { + return err + } + for _, r := range roles { + log.Info().Msgf("%d %s@%d", r.ID, r.Name, r.AppID) + } + return nil +} + +func runRoleCreate(c *cli.Context) error { + id := c.Uint("id") + name := c.String("name") + rl := &models.Role{} + rl.AppID = id + rl.Name = name + err := cfg.DB().Where(rl).FirstOrCreate(rl).Error + return err +} + +var Resource = &cli.Command{ + Name: "resource", + Usage: "resource manual", + Subcommands: []*cli.Command{ + { + Name: "list", + Action: runResourceList, + Flags: []cli.Flag{ + &cli.UintFlag{ + Name: "id", + Usage: "app id", + }, + }, + }, + { + Name: "create", + Action: runResourceCreate, + Flags: []cli.Flag{ + &cli.UintFlag{ + Name: "id", + Usage: "app id", + Required: true, + }, + &cli.StringFlag{ + Name: "name", + Usage: "role name", + Required: true, + }, + }, + }, + }, +} + +func runResourceList(c *cli.Context) error { + query := &models.Resource{} + query.AppID = c.Uint("id") + l := make([]*models.Resource, 0, 10) + err := cfg.DB().Where(query).Find(&l).Error + if err != nil { + return nil + } + for _, r := range l { + log.Info().Msgf("%d: %s@%d", r.ID, r.Name, r.AppID) + } + return nil +} + +func runResourceCreate(c *cli.Context) error { + query := &models.Resource{} + query.AppID = c.Uint("id") + query.Name = c.String("name") + err := cfg.DB().Where(query).FirstOrCreate(query).Error + return err +} diff --git a/sub/web.go b/sub/web.go index 0abda23..ce43620 100644 --- a/sub/web.go +++ b/sub/web.go @@ -3,7 +3,6 @@ package sub import ( "OneAuth/api" "OneAuth/cfg" - "OneAuth/models" "embed" "github.com/urfave/cli/v2" "github.com/veypi/OneBD" @@ -20,7 +19,7 @@ var staticFiles embed.FS //go:embed static/index.html var indexFile []byte -var Web = cli.Command{ +var Web = &cli.Command{ Name: "web", Usage: "", Description: "oa 核心http服务", @@ -29,7 +28,6 @@ var Web = cli.Command{ } func RunWeb(c *cli.Context) error { - _ = runSyncDB(c) ll := log.InfoLevel if l, err := log.ParseLevel(cfg.CFG.LoggerLevel); err == nil { ll = l @@ -70,16 +68,3 @@ func RunWeb(c *cli.Context) error { log.Info().Msg("\nRouting Table\n" + app.Router().String()) return app.Run() } - -func runSyncDB(*cli.Context) error { - db := cfg.DB() - log.HandlerErrs( - db.SetupJoinTable(&models.User{}, "Roles", &models.UserRole{}), - db.SetupJoinTable(&models.Role{}, "Users", &models.UserRole{}), - db.AutoMigrate(&models.User{}, &models.Role{}, &models.Auth{}), - ) - log.HandlerErrs( - db.AutoMigrate(&models.App{}, &models.Wechat{}), - ) - return nil -}