diff --git a/README.md b/README.md index 5a8fb54..e93bf3c 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ 统一验证服务 + ## 用户验证思路 ![](https://public.veypi.com/img/screenshot/20211012194238.png) diff --git a/api/api.go b/api/api.go index 5acc86f..58b73de 100644 --- a/api/api.go +++ b/api/api.go @@ -3,22 +3,25 @@ package api import ( "OneAuth/api/app" "OneAuth/api/role" + "OneAuth/api/token" "OneAuth/api/user" "OneAuth/api/wx" + "OneAuth/libs/base" "github.com/veypi/OneBD" "github.com/veypi/OneBD/core" ) func Router(r OneBD.Router) { r.SetNotFoundFunc(func(m core.Meta) { - m.Write([]byte("{\"status\": 0}")) + base.JSONResponse(m, nil, nil) }) r.SetInternalErrorFunc(func(m core.Meta) { - m.Write([]byte("{\"status\": 0}")) + base.JSONResponse(m, nil, nil) }) user.Router(r.SubRouter("/user")) wx.Router(r.SubRouter("wx")) app.Router(r.SubRouter("app")) + token.Router(r.SubRouter("token")) role.Router(r) //message.Router(r.SubRouter("/message")) diff --git a/api/app/app.go b/api/app/app.go index 0faf2c7..5a2ee3e 100644 --- a/api/app/app.go +++ b/api/app/app.go @@ -8,15 +8,17 @@ import ( "OneAuth/models" "github.com/veypi/OneBD" "github.com/veypi/OneBD/rfc" + "github.com/veypi/utils" ) func Router(r OneBD.Router) { + r.Set("/", appHandlerP, rfc.MethodPost, rfc.MethodGet) r.Set("/:id", appHandlerP, rfc.MethodGet) } var appHandlerP = OneBD.NewHandlerPool(func() OneBD.Handler { h := &appHandler{} - h.Ignore(rfc.MethodGet) + h.Ignore(rfc.MethodGet, rfc.MethodPost) return h }) @@ -31,7 +33,7 @@ func (h *appHandler) Get() (interface{}, error) { isSelf := h.Meta().Query("is_self") if isSelf != "" { // 无权限可以获取本系统基本信息 - h.query.ID = cfg.CFG.APPID + h.query.UUID = cfg.CFG.APPUUID err := cfg.DB().Where(h.query).First(h.query).Error return h.query, err } @@ -64,3 +66,35 @@ func (h *appHandler) Get() (interface{}, error) { err = cfg.DB().Find(&list).Error return list, err } + +func (h *appHandler) Post() (interface{}, error) { + data := &struct { + Name string `json:"name"` + UUID string `json:"uuid"` + }{} + err := h.Meta().ReadJson(data) + if err != nil { + return nil, err + } + if data.Name == "" { + return nil, oerr.ApiArgsMissing.AttachStr("name") + } + _ = h.ParsePayload(h.Meta()) + a := &models.App{ + UUID: data.UUID, + Name: data.Name, + Key: utils.RandSeq(32), + Creator: h.Payload.ID, + } + a.Key = utils.RandSeq(32) + if data.UUID != "" { + err = cfg.DB().Where("uuid = ?", data.UUID).FirstOrCreate(a).Error + } else { + data.UUID = utils.RandSeq(16) + err = cfg.DB().Create(a).Error + } + if err != nil { + return nil, err + } + return a, nil +} diff --git a/api/token/token.go b/api/token/token.go new file mode 100644 index 0000000..a02846f --- /dev/null +++ b/api/token/token.go @@ -0,0 +1,69 @@ +package token + +import ( + "OneAuth/cfg" + "OneAuth/libs/app" + "OneAuth/libs/base" + "OneAuth/libs/oerr" + "OneAuth/libs/token" + "OneAuth/models" + "errors" + "github.com/veypi/OneBD" + "github.com/veypi/OneBD/rfc" + "gorm.io/gorm" +) + +func Router(r OneBD.Router) { + p := OneBD.NewHandlerPool(func() OneBD.Handler { + return &tokenHandler{} + }) + r.Set("/:uuid", p, rfc.MethodGet) +} + +type tokenHandler struct { + base.ApiHandler +} + +func (h *tokenHandler) Get() (interface{}, error) { + uuid := h.Meta().Params("uuid") + if uuid == "" { + return nil, oerr.ApiArgsMissing.AttachStr("uuid") + } + a := &models.App{} + a.UUID = uuid + err := cfg.DB().Where("uuid = ?", uuid).First(a).Error + if err != nil { + return nil, err + } + au := &models.AppUser{ + UserID: h.Payload.ID, + AppID: a.ID, + } + err = cfg.DB().Where(au).First(au).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + if a.EnableRegister { + err = cfg.DB().Transaction(func(tx *gorm.DB) error { + return app.AddUser(cfg.DB(), au.AppID, au.UserID, a.InitRoleID, models.AUOK) + }) + if err != nil { + return nil, err + } + au.Status = models.AUOK + } else { + return nil, oerr.AppNotJoin.AttachStr(a.Name) + } + } + return nil, oerr.DBErr.Attach(err) + } + if au.Status != models.AUOK { + return nil, oerr.NoAuth.AttachStr(string(au.Status)) + } + u := &models.User{} + err = cfg.DB().Preload("Auths").Preload("Roles.Auths").Where("id = ?", h.Payload.ID).First(u).Error + if err != nil { + return nil, err + } + t, err := token.GetToken(u, a.ID, a.Key) + return t, err +} diff --git a/api/user/user.go b/api/user/user.go index 3faf9f9..1fe9969 100644 --- a/api/user/user.go +++ b/api/user/user.go @@ -3,13 +3,11 @@ package user import ( "OneAuth/cfg" "OneAuth/libs/app" + "OneAuth/libs/auth" "OneAuth/libs/base" "OneAuth/libs/oerr" "OneAuth/libs/token" "OneAuth/models" - "errors" - - //"OneAuth/ws" "encoding/base64" "fmt" "github.com/veypi/OneBD" @@ -43,10 +41,13 @@ type handler struct { // Get get user data func (h *handler) Get() (interface{}, error) { + if !h.Payload.GetAuth(auth.User, "").CanRead() { + return nil, oerr.NoAuth.AttachStr("to read user list") + } username := h.Meta().Query("username") if username != "" { users := make([]*models.User, 0, 10) - err := cfg.DB().Preload("Scores").Preload("Roles.Auths").Where("username LIKE ? OR nickname LIKE ?", "%"+username+"%", "%"+username+"%").Find(&users).Error + err := cfg.DB().Where("username LIKE ? OR nickname LIKE ?", "%"+username+"%", "%"+username+"%").Find(&users).Error if err != nil { return nil, err } @@ -56,14 +57,14 @@ func (h *handler) Get() (interface{}, error) { if userID != 0 { user := &models.User{} user.ID = uint(userID) - return user, cfg.DB().Where(user).Preload("Scores").Preload("Roles.Auths").Preload("Favorites").First(user).Error + return user, cfg.DB().Where(user).First(user).Error } else { users := make([]models.User, 10) skip, err := strconv.Atoi(h.Meta().Query("skip")) if err != nil || skip < 0 { skip = 0 } - if err := cfg.DB().Preload("Scores").Preload("Roles.Auths").Offset(skip).Find(&users).Error; err != nil { + if err := cfg.DB().Offset(skip).Find(&users).Error; err != nil { return nil, err } return users, nil @@ -73,16 +74,15 @@ func (h *handler) Get() (interface{}, error) { // Post register user func (h *handler) Post() (interface{}, error) { self := &models.App{} - self.ID = cfg.CFG.APPID + self.UUID = cfg.CFG.APPUUID err := cfg.DB().Where(self).First(self).Error if err != nil { return nil, oerr.DBErr.Attach(err) } - if !self.EnableRegister { + if !self.EnableRegister && !h.Payload.GetAuth(auth.User, "").CanCreate() { 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"` @@ -120,27 +120,10 @@ func (h *handler) Post() (interface{}, error) { if err := tx.Create(&h.User).Error; err != nil { return oerr.ResourceDuplicated } - err := app.AddUser(tx, self.ID, h.User.ID, self.InitRoleID) + err := app.AddUser(tx, self.ID, h.User.ID, self.InitRoleID, models.AUOK) 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 { @@ -169,11 +152,10 @@ func (h *handler) Patch() (interface{}, error) { } else { target.ID = uint(tempID) } - tx := cfg.DB().Begin() if err := cfg.DB().Where(&target).First(&target).Error; err != nil { return nil, err } - if target.ID != h.Payload.ID { + if target.ID != h.Payload.ID && h.Payload.GetAuth(auth.User, strconv.Itoa(int(target.ID))).CanUpdate() { return nil, oerr.NoAuth } if len(opts.Password) >= 6 { @@ -197,11 +179,9 @@ func (h *handler) Patch() (interface{}, error) { if opts.Status != "" { target.Status = opts.Status } - if err := tx.Updates(&target).Error; err != nil { - tx.Rollback() + if err := cfg.DB().Updates(&target).Error; err != nil { return nil, err } - tx.Commit() return nil, nil } @@ -222,10 +202,6 @@ func (h *handler) Head() (interface{}, error) { if len(uid) == 0 || len(password) == 0 { return nil, oerr.ApiArgsError } - appUUID := h.Meta().Query("uuid") - if appUUID == "" { - return nil, oerr.ApiArgsMissing.AttachStr("uuid") - } h.User = new(models.User) uidType := h.Meta().Query("uid_type") switch uidType { @@ -239,7 +215,7 @@ func (h *handler) Head() (interface{}, error) { h.User.Username = uid } target := &models.App{} - target.UUID = appUUID + target.UUID = cfg.CFG.APPUUID err = cfg.DB().Where(target).Find(target).Error if err != nil { return nil, oerr.DBErr.Attach(err) @@ -261,17 +237,12 @@ func (h *handler) Head() (interface{}, error) { 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, err + } else if au.Status != models.AUOK { return nil, oerr.DisableLogin } - tokenStr, err := token.GetToken(h.User, appID) + tokenStr, err := token.GetToken(h.User, appID, cfg.CFG.APPKey) if err != nil { log.HandlerErrs(err) return nil, oerr.Unknown.Attach(err) diff --git a/cfg/cfg.go b/cfg/cfg.go index 05719cb..60fe07c 100644 --- a/cfg/cfg.go +++ b/cfg/cfg.go @@ -15,11 +15,11 @@ var CFG = &struct { Host string LoggerPath string LoggerLevel string - APPID uint + APPUUID string APPKey string TimeFormat string Debug bool - EXEDir string + MediaDir string DB struct { Type string Addr string @@ -28,14 +28,15 @@ var CFG = &struct { DB string } }{ - APPID: 1, + APPUUID: "jU5Jo5hM", + APPKey: "cB43wF94MLTksyBK", AdminUser: "admin", Host: "0.0.0.0:4001", LoggerPath: "", LoggerLevel: "debug", TimeFormat: "2006/01/02 15:04:05", Debug: true, - EXEDir: "./", + MediaDir: "/Users/light/test/media/", DB: struct { Type string Addr string diff --git a/libs/app/user.go b/libs/app/user.go index 65424f8..e86222f 100644 --- a/libs/app/user.go +++ b/libs/app/user.go @@ -8,7 +8,10 @@ import ( "gorm.io/gorm" ) -func AddUser(tx *gorm.DB, appID uint, userID uint, roleID uint) error { +func AddUser(tx *gorm.DB, appID uint, userID uint, roleID uint, status models.AUStatus) error { + if appID == 0 || userID == 0 { + return oerr.FuncArgsError + } au := &models.AppUser{} au.AppID = appID au.UserID = userID @@ -17,19 +20,25 @@ func AddUser(tx *gorm.DB, appID uint, userID uint, roleID uint) error { return oerr.ResourceDuplicated } if errors.Is(err, gorm.ErrRecordNotFound) { + au.Status = status err = tx.Create(au).Error if err != nil { return err } - err = auth.BindUserRole(tx, userID, roleID) - if err != nil { - return err + if roleID > 0 { + 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 { + if appID == 0 || userID == 0 { + return oerr.FuncArgsError + } au := &models.AppUser{} au.AppID = appID au.UserID = userID @@ -37,12 +46,18 @@ func EnableUser(tx *gorm.DB, appID uint, userID uint) error { if err != nil { return err } - return tx.Where(au).Update("disabled", false).Error + if au.Status != models.AUOK { + return tx.Where(au).Update("status", models.AUOK).Error + } + return nil } func DisableUser(tx *gorm.DB, appID uint, userID uint) error { + if appID == 0 || userID == 0 { + return oerr.FuncArgsError + } au := &models.AppUser{} au.AppID = appID au.UserID = userID - return tx.Where(au).Update("disabled", true).Error + return tx.Where(au).Update("status", models.AUDisable).Error } diff --git a/libs/base/api_handler.go b/libs/base/api_handler.go index 116a514..76f6e37 100644 --- a/libs/base/api_handler.go +++ b/libs/base/api_handler.go @@ -3,10 +3,12 @@ package base import ( "OneAuth/libs/oerr" "OneAuth/libs/tools" + "errors" "github.com/json-iterator/go" "github.com/veypi/OneBD" "github.com/veypi/OneBD/rfc" "github.com/veypi/utils/log" + "gorm.io/gorm" "strconv" "sync" "time" @@ -14,6 +16,41 @@ import ( var json = jsoniter.ConfigFastest +func JSONResponse(m OneBD.Meta, data interface{}, err error) { + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + err = oerr.ResourceNotExist + } + } + if m.Method() == rfc.MethodHead { + if err != nil { + m.SetHeader("status", "0") + m.SetHeader("code", strconv.Itoa(int(oerr.OfType(err.Error())))) + m.SetHeader("err", err.Error()) + } else { + m.SetHeader("status", "1") + } + return + } + res := map[string]interface{}{ + "status": 1, + } + if err != nil { + res["status"] = 0 + res["code"] = oerr.OfType(err.Error()) + res["err"] = err.Error() + } else { + res["status"] = 1 + res["content"] = data + } + p, err := json.Marshal(res) + if err != nil { + log.Warn().Err(err).Msg("encode json data error") + return + } + _, _ = m.Write(p) +} + type ApiHandler struct { OneBD.BaseHandler UserHandler @@ -24,29 +61,12 @@ func (h *ApiHandler) Init(m OneBD.Meta) error { } func (h *ApiHandler) OnResponse(data interface{}) { - if h.Meta().Method() == rfc.MethodHead { - h.Meta().SetHeader("status", "1") - return - } - p, err := json.Marshal(map[string]interface{}{"status": 1, "content": data}) - if err != nil { - log.Warn().Err(err).Msg("encode json data error") - return - } - h.Meta().Write(p) + JSONResponse(h.Meta(), data, nil) } func (h *ApiHandler) OnError(err error) { log.WithNoCaller.Warn().Err(err).Msg(h.Meta().RequestPath()) - msg := err.Error() - if h.Meta().Method() == rfc.MethodHead { - h.Meta().SetHeader("status", "0") - h.Meta().SetHeader("code", strconv.Itoa(int(oerr.OfType(msg)))) - h.Meta().SetHeader("err", msg) - } else { - p, _ := json.Marshal(map[string]interface{}{"status": 0, "code": oerr.OfType(msg), "err": msg}) - h.Meta().Write(p) - } + JSONResponse(h.Meta(), nil, err) } var ioNumLimit = make(map[string]time.Time) diff --git a/libs/base/user_handler.go b/libs/base/user_handler.go index 29d43ba..af33bb3 100644 --- a/libs/base/user_handler.go +++ b/libs/base/user_handler.go @@ -1,6 +1,7 @@ package base import ( + "OneAuth/cfg" "OneAuth/libs/oerr" "OneAuth/libs/token" "OneAuth/models" @@ -26,7 +27,7 @@ func (a *UserHandler) ParsePayload(m OneBD.Meta) error { if tokenStr == "" { return oerr.NotLogin } - ok, err := token.ParseToken(tokenStr, a.Payload) + ok, err := token.ParseToken(tokenStr, a.Payload, cfg.CFG.APPKey) if ok { return nil } diff --git a/libs/oerr/oerr.go b/libs/oerr/oerr.go index d5f20c7..398ef04 100644 --- a/libs/oerr/oerr.go +++ b/libs/oerr/oerr.go @@ -1,7 +1,6 @@ package oerr import ( - "errors" "gorm.io/gorm" "strconv" ) @@ -42,11 +41,13 @@ type Code uint - 9 : 本不可能发生的错误,例如被人攻击导致数据异常产生的逻辑错误 */ + +// Unknown error const ( Unknown Code = 0 ) const ( - // 2 数据库错误 + // DBErr 2 数据库错误 // -1 系统错误 // -2 数据读写错误 DBErr Code = 20001 @@ -56,10 +57,13 @@ const ( ) const ( -// 3 + // LogicErr 3 系统内逻辑错误 + LogicErr Code = 30000 + AppNotJoin Code = 30001 ) const ( + // NotLogin // 4 权限类型错误 // 1: 登录权限 // 2: 资源操作权限 @@ -130,6 +134,8 @@ var codeMap = map[Code]string{ NoAuth: "no auth to access", AccessErr: "access error", AccessTooFast: "access too fast", + LogicErr: "logic error", + AppNotJoin: "not join in app", } func (c Code) Error() string { @@ -144,7 +150,7 @@ func (c Code) String() string { return codeMap[Unknown] } -// 附加错误详细原因 +// Attach 附加错误详细原因 func (c Code) Attach(errs ...error) (e error) { e = c for _, err := range errs { @@ -195,16 +201,3 @@ func (w *wrapErr) Error() string { func (w *wrapErr) UnWrap() error { return w.err } - -func CheckMultiErr(errs ...error) error { - msg := "" - for _, e := range errs { - if e != nil { - msg += e.Error() + "\n" - } - } - if msg != "" { - return errors.New(msg) - } - return nil -} diff --git a/libs/token/user.go b/libs/token/user.go index 790982c..12154f9 100644 --- a/libs/token/user.go +++ b/libs/token/user.go @@ -1,7 +1,6 @@ package token import ( - "OneAuth/libs/key" "OneAuth/models" "github.com/veypi/utils/jwt" ) @@ -16,9 +15,8 @@ type simpleAuth struct { // TODO:: roles 是否会造成token过大 ? type PayLoad struct { jwt.Payload - ID uint `json:"id"` - AppID uint `json:"app_id"` - Auth map[uint]*simpleAuth `json:"auth"` + ID uint `json:"id"` + Auth map[uint]*simpleAuth `json:"auth"` } // GetAuth resource_uuid 缺省或仅第一个有效 权限会被更高权限覆盖 @@ -49,11 +47,10 @@ func (p *PayLoad) GetAuth(ResourceID string, ResourceUUID ...string) models.Auth return res } -func GetToken(u *models.User, appID uint) (string, error) { +func GetToken(u *models.User, appID uint, key string) (string, error) { payload := &PayLoad{ - ID: u.ID, - AppID: appID, - Auth: map[uint]*simpleAuth{}, + ID: u.ID, + Auth: map[uint]*simpleAuth{}, } for _, a := range u.GetAuths() { if appID == a.AppID { @@ -64,9 +61,9 @@ func GetToken(u *models.User, appID uint) (string, error) { } } } - return jwt.GetToken(payload, []byte(key.User(payload.ID, payload.AppID))) + return jwt.GetToken(payload, []byte(key)) } -func ParseToken(token string, payload *PayLoad) (bool, error) { - return jwt.ParseToken(token, payload, []byte(key.User(payload.ID, payload.AppID))) +func ParseToken(token string, payload *PayLoad, key string) (bool, error) { + return jwt.ParseToken(token, payload, []byte(key)) } diff --git a/main.go b/main.go index 4e5c489..bed4067 100644 --- a/main.go +++ b/main.go @@ -7,7 +7,6 @@ import ( "github.com/veypi/utils/cmd" "github.com/veypi/utils/log" "os" - "path/filepath" ) const Version = "v0.1.0" @@ -35,21 +34,6 @@ 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.APPKey, - Destination: &cfg.CFG.APPKey, - }, - &cli.StringFlag{ - Name: "exe_dir", - Value: cfg.CFG.EXEDir, - Destination: &cfg.CFG.EXEDir, - }, &cli.StringFlag{ Name: "host", Value: cfg.CFG.Host, @@ -72,11 +56,6 @@ func main() { srv.SetStopFunc(func() { }) app.Before = func(c *cli.Context) error { - var err error - cfg.CFG.EXEDir, err = filepath.Abs(cfg.CFG.EXEDir) - if err != nil { - return err - } if cfg.CFG.Debug { cfg.CFG.LoggerLevel = "debug" } diff --git a/models/app.go b/models/app.go index 287c858..27ce949 100644 --- a/models/app.go +++ b/models/app.go @@ -42,12 +42,22 @@ type App struct { Wx *Wechat `json:"wx" gorm:"foreignkey:AppID;references:ID"` } +type AUStatus string + +const ( + AUOK AUStatus = "ok" + AUDisable AUStatus = "disabled" + AUApply AUStatus = "apply" + AUDeny AUStatus = "deny" +) + type AppUser struct { BaseModel - AppID uint `json:"app_id"` - UserID uint `json:"user_id"` - Disabled bool `json:"disabled"` - Status string `json:"status"` + AppID uint `json:"app_id"` + APP *App `json:"app"` + UserID uint `json:"user_id"` + User *User `json:"user"` + Status AUStatus `json:"status"` } type Wechat struct { diff --git a/oaf/package.json b/oaf/package.json index a72df91..87848fe 100644 --- a/oaf/package.json +++ b/oaf/package.json @@ -7,7 +7,8 @@ "serve": "vite preview" }, "dependencies": { - "@veypi/one-icon": "2", + "@veypi/one-icon": "2.0.5", + "animate.css": "^4.1.1", "axios": "^0.24.0", "js-base64": "^3.7.2", "vue": "^3.2.16", @@ -18,6 +19,7 @@ "@tailwindcss/postcss7-compat": "^2.1.0", "@vitejs/plugin-vue": "^1.9.3", "autoprefixer": "^9.8.8", + "less": "^4.1.2", "naive-ui": "^2.19.11", "postcss": "^7.0.39", "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17", diff --git a/oaf/public/icon.js b/oaf/public/icon.js deleted file mode 100644 index 24a3819..0000000 --- a/oaf/public/icon.js +++ /dev/null @@ -1 +0,0 @@ -!function(c){var t,l,a,e,o,h='',i=(i=document.getElementsByTagName("script"))[i.length-1].getAttribute("data-injectcss"),d=function(c,t){t.parentNode.insertBefore(c,t)};if(i&&!c.__iconfont__svg__cssinject__){c.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(c){console&&console.log(c)}}function n(){o||(o=!0,a())}function s(){try{e.documentElement.doScroll("left")}catch(c){return void setTimeout(s,50)}n()}t=function(){var c,t;(t=document.createElement("div")).innerHTML=h,h=null,(c=t.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",t=c,(c=document.body).firstChild?d(t,c.firstChild):c.appendChild(t))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(t,0):(l=function(){document.removeEventListener("DOMContentLoaded",l,!1),t()},document.addEventListener("DOMContentLoaded",l,!1)):document.attachEvent&&(a=t,e=c.document,o=!1,s(),e.onreadystatechange=function(){"complete"==e.readyState&&(e.onreadystatechange=null,n())})}(window); \ No newline at end of file diff --git a/oaf/src/App.vue b/oaf/src/App.vue index 8f2ad97..1454e9b 100644 --- a/oaf/src/App.vue +++ b/oaf/src/App.vue @@ -1,59 +1,99 @@ - - + + + +")}catch(c){console&&console.log(c)}}function s(){o||(o=!0,a())}function n(){try{h.documentElement.doScroll("left")}catch(c){return void setTimeout(n,50)}s()}l=function(){var c,l;(l=document.createElement("div")).innerHTML=e,e=null,(c=l.getElementsByTagName("svg")[0])&&(c.setAttribute("aria-hidden","true"),c.style.position="absolute",c.style.width=0,c.style.height=0,c.style.overflow="hidden",l=c,(c=document.body).firstChild?d(l,c.firstChild):c.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),l()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(a=l,h=c.document,o=!1,n(),h.onreadystatechange=function(){"complete"==h.readyState&&(h.onreadystatechange=null,s())})}(window); \ No newline at end of file diff --git a/oaf/src/components/app.vue b/oaf/src/components/app.vue new file mode 100644 index 0000000..6f9da40 --- /dev/null +++ b/oaf/src/components/app.vue @@ -0,0 +1,29 @@ + + + diff --git a/oaf/src/components/avatar/avatar.vue b/oaf/src/components/avatar/avatar.vue new file mode 100644 index 0000000..7eb47ff --- /dev/null +++ b/oaf/src/components/avatar/avatar.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/oaf/src/components/avatar/frame.vue b/oaf/src/components/avatar/frame.vue new file mode 100644 index 0000000..f54c60d --- /dev/null +++ b/oaf/src/components/avatar/frame.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/oaf/src/components/avatar/index.ts b/oaf/src/components/avatar/index.ts new file mode 100644 index 0000000..249b4ba --- /dev/null +++ b/oaf/src/components/avatar/index.ts @@ -0,0 +1,3 @@ +import avatar from './avatar.vue' + +export default avatar diff --git a/oaf/src/components/frame.vue b/oaf/src/components/frame.vue new file mode 100644 index 0000000..cbc8f2b --- /dev/null +++ b/oaf/src/components/frame.vue @@ -0,0 +1,10 @@ + + + + + diff --git a/oaf/src/components/fullscreen/fullscreen.vue b/oaf/src/components/fullscreen/fullscreen.vue new file mode 100644 index 0000000..d5b7f41 --- /dev/null +++ b/oaf/src/components/fullscreen/fullscreen.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/oaf/src/components/fullscreen/index.ts b/oaf/src/components/fullscreen/index.ts new file mode 100644 index 0000000..5c4a3c6 --- /dev/null +++ b/oaf/src/components/fullscreen/index.ts @@ -0,0 +1,2 @@ +import fullscreen from './fullscreen.vue' +export default fullscreen diff --git a/oaf/src/libs/util.ts b/oaf/src/libs/util.ts index b1b9806..669d43a 100644 --- a/oaf/src/libs/util.ts +++ b/oaf/src/libs/util.ts @@ -27,6 +27,9 @@ const util = { document.cookie = name + '=' + escape(value) + ';expires=' + exp.toLocaleString() }, + getToken() { + return localStorage.auth_token + }, checkLogin() { // return parseInt(this.getCookie('stat')) === 1 return Boolean(localStorage.auth_token) diff --git a/oaf/src/main.ts b/oaf/src/main.ts index 1266804..641b63e 100644 --- a/oaf/src/main.ts +++ b/oaf/src/main.ts @@ -6,12 +6,14 @@ import OneIcon from '@veypi/one-icon' import naive from 'naive-ui' import './index.css' import {Api} from './api' +import './assets/icon.js' +import 'animate.css' const app = createApp(App) app.use(Api) app.use(naive) -app.use(OneIcon, {href: './icon.js'}) +app.use(OneIcon) app.use(router) app.use(store, key) app.mount('#app') diff --git a/oaf/src/router/index.ts b/oaf/src/router/index.ts index a6caa9d..a9a8911 100644 --- a/oaf/src/router/index.ts +++ b/oaf/src/router/index.ts @@ -22,12 +22,25 @@ const router = createRouter({ component: () => import('../views/home.vue') }, { - path: '/app', + path: '/app/:uuid?', name: 'app', meta: { requiresAuth: true, }, - component: () => import('../views/demo.vue') + component: () => import('../views/app.vue') + }, + { + path: '/user/setting', + name: 'user_setting', + meta: { + requiresAuth: true + }, + component: () => import('../views/user_setting.vue') + }, + { + path: '/about', + name: 'about', + component: () => import('../views/about.vue') }, { path: '/wx', diff --git a/oaf/src/store/index.ts b/oaf/src/store/index.ts index 29d4d6a..7c278bb 100644 --- a/oaf/src/store/index.ts +++ b/oaf/src/store/index.ts @@ -1,58 +1,45 @@ import {InjectionKey} from 'vue' import {createStore, useStore as baseUseStore, Store} from 'vuex' import api from "../api"; -import router from "../router"; -import {darkTheme} from 'naive-ui' +import {User, UserState} from './user' -export interface State { +export interface State extends Object { oauuid: string - user: object - theme: string - + user: UserState + apps: [] } export const key: InjectionKey> = Symbol() export const store = createStore({ + modules: { + user: User + }, +// @ts-ignore state: { - theme: 'light', oauuid: '', - user: {} - }, - getters: { - IsDark(state: any) { - return state.theme === 'dark' - }, - GetTheme(state: any, getters) { - return getters.IsDark ? darkTheme : null - } + apps: [] }, + getters: {}, mutations: { setOA(state: any, data: any) { state.oauuid = data.uuid }, - setTheme(state: any, t: string) { - state.theme = t + setApps(state: State, data: any) { + state.apps = data } }, actions: { - changeTheme(context) { - if (context.getters.IsDark) { - context.commit('setTheme', 'light') - } else { - context.commit('setTheme', 'dark') - } - }, fetchSelf({commit}) { api.app.self().Start(d => { commit('setOA', d) }) }, - handleLogout() { - localStorage.removeItem('auth_token') - router.push({name: 'login'}) + fetchApps({commit}) { + api.app.list().Start(e => { + commit('setApps', e) + }) } - } }) diff --git a/oaf/src/store/user.ts b/oaf/src/store/user.ts new file mode 100644 index 0000000..dd70e53 --- /dev/null +++ b/oaf/src/store/user.ts @@ -0,0 +1,73 @@ +import {Module} from "vuex"; +import api from "../api"; +import util from '../libs/util' +import {Base64} from 'js-base64' +import {State} from './index' +import router from "../router"; + +export interface UserState { + id: number + username: string + nickname: string + phone: string + icon: string + email: string + ready: boolean + auth: [auth?] + + [key: string]: any +} + +interface auth { + rid: string + ruid: string + level: number +} + +export const User: Module = { + namespaced: true, + state: { + id: 0, + username: '', + nickname: '', + phone: '', + icon: '', + email: '', + auth: [], + ready: false + }, + mutations: { + setBase(state: UserState, data: any) { + state.id = data.id + state.icon = data.icon + state.username = data.username + state.nickname = data.nickname + state.phone = data.phone + state.email = data.email + state.ready = true + }, + setAuth(state: UserState, data: any) { + state.auth = data + }, + logout(state: UserState) { + state.ready = false + localStorage.removeItem('auth_token') + router.push({name: 'login'}) + } + }, + actions: { + fetchUserData(context) { + let token = util.getToken()?.split('.'); + if (!token || token.length !== 3) { + return false + } + let data = JSON.parse(Base64.decode(token[1])) + if (data.id > 0) { + context.commit('setAuth', data.auth) + api.user.get(data.id).Start(e => { + context.commit('setBase', e) + }) + } + } + } +} diff --git a/oaf/src/theme/index.ts b/oaf/src/theme/index.ts new file mode 100644 index 0000000..fbccfae --- /dev/null +++ b/oaf/src/theme/index.ts @@ -0,0 +1,72 @@ +import {darkTheme} from 'naive-ui/lib/themes' +import {BuiltInGlobalTheme} from 'naive-ui/lib/themes/interface' +import {lightTheme} from 'naive-ui/lib/themes/light' +import {ref} from 'vue' +import {useOsTheme, GlobalThemeOverrides} from 'naive-ui' + +interface builtIn extends BuiltInGlobalTheme { + overrides: GlobalThemeOverrides + me: { + lightBox: string, + lightBoxShadow: string + } +} + +let light = lightTheme as builtIn +let dark = darkTheme as builtIn +let intputNone = { + color: 'url(0) no-repeat', + colorFocus: 'url(0) no-repeat', + colorFocusWarning: 'url(0) no-repeat', + colorFocusError: 'url(0) no-repeat' +} +light.overrides = { + Input: Object.assign({}, intputNone) +} +dark.overrides = { + Input: Object.assign({ + border: '1px solid #aaa' + }, intputNone) +} +light.common.cardColor = '#f4f4f4' +light.common.bodyColor = '#eee' +dark.common.bodyColor = '#2e2e2e' +light.me = { + lightBox: '#f4f4f4', + lightBoxShadow: '18px 18px 36px #c6c6c6, -18px -18px 36px #fff' +} + +dark.me = { + lightBox: '#2e2e2e', + lightBoxShadow: '21px 21px 42px #272727, -21px -21px 42px #353535' +} +export const OsThemeRef = useOsTheme() + +let theme = 'light' + +export let Theme = ref(light) + +export let IsDark = ref(false) + +function change(t: string) { + if (t === 'dark') { + theme = 'dark' + Theme.value = dark + } else { + theme = 'light' + Theme.value = light + } + IsDark.value = theme === 'dark' +} + +export function ChangeTheme() { + if (IsDark.value) { + change('light') + } else { + change('dark') + } +} + +if (OsThemeRef.value === 'dark') { + change('dark') +} diff --git a/oaf/src/views/about.vue b/oaf/src/views/about.vue new file mode 100644 index 0000000..a4aa17e --- /dev/null +++ b/oaf/src/views/about.vue @@ -0,0 +1,12 @@ + + + + + diff --git a/oaf/src/views/app.vue b/oaf/src/views/app.vue new file mode 100644 index 0000000..a0020f2 --- /dev/null +++ b/oaf/src/views/app.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/oaf/src/views/home.vue b/oaf/src/views/home.vue index 873b0d8..523225b 100644 --- a/oaf/src/views/home.vue +++ b/oaf/src/views/home.vue @@ -1,11 +1,18 @@ diff --git a/oaf/src/views/login.vue b/oaf/src/views/login.vue index ac4b116..6d4607d 100644 --- a/oaf/src/views/login.vue +++ b/oaf/src/views/login.vue @@ -1,61 +1,98 @@ - diff --git a/oaf/src/views/user_setting.vue b/oaf/src/views/user_setting.vue new file mode 100644 index 0000000..21264a4 --- /dev/null +++ b/oaf/src/views/user_setting.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/oaf/src/vuex.d.ts b/oaf/src/vuex.d.ts new file mode 100644 index 0000000..0ef57c5 --- /dev/null +++ b/oaf/src/vuex.d.ts @@ -0,0 +1,14 @@ +import {ComponentCustomProperties} from 'vue' +import {Store} from 'vuex' +import {State as root} from './store' + +declare module '@vue/runtime-core' { + // 声明自己的 store state + interface State extends root { + } + + // 为 `this.$store` 提供类型声明 + interface ComponentCustomProperties { + $store: Store + } +} diff --git a/oaf/vite.config.ts b/oaf/vite.config.ts index 9384742..09b039a 100644 --- a/oaf/vite.config.ts +++ b/oaf/vite.config.ts @@ -1,19 +1,36 @@ -import { defineConfig } from 'vite' +import {defineConfig} from 'vite' import vue from '@vitejs/plugin-vue' // https://vitejs.dev/config/ export default defineConfig({ - plugins: [vue()], - server: { - // host: '0.0.0.0', - host: '127.0.0.1', - port: 8080, - proxy: { - '/api': 'http://127.0.0.1:4001/' + plugins: [vue()], + server: { + // host: '0.0.0.0', + host: '127.0.0.1', + port: 8080, + proxy: { + '/api': { + target: 'http://127.0.0.1:4001/', + changeOrigin: true, + ws: true + }, + '/media': { + target: 'http://127.0.0.1:4001/', + changeOrigin: true, + ws: true + } + } + }, + build: { + outDir: '../sub/static/', + assetsDir: './', + rollupOptions: { + output: { + // 重点在这里哦 + entryFileNames: `static/[name].[hash].js`, + chunkFileNames: `static/[name].[hash].js`, + assetFileNames: `static/[name].[hash].[ext]` + } + } } - }, - build: { - outDir: '../build/static/', - assetsDir: 'assets' - } }) diff --git a/oaf/yarn.lock b/oaf/yarn.lock index 539df17..a1688be 100644 --- a/oaf/yarn.lock +++ b/oaf/yarn.lock @@ -216,10 +216,10 @@ dependencies: "@types/yargs-parser" "*" -"@veypi/one-icon@2": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@veypi/one-icon/-/one-icon-2.0.0.tgz#15262e87644903d90a2124fbc9c95fbb6bb6bcb8" - integrity sha512-zcUx3YzTIRiZe5XkJRZyKDgVzQThEWyZSCpQTreeYtKEObCl4B0Oa6CFYo4IToGD6uQmReM0R6oNPg5mhPjc2A== +"@veypi/one-icon@2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@veypi/one-icon/-/one-icon-2.0.5.tgz#1083be30fc3bbb89aaf19501b00110d3ae9b1c77" + integrity sha512-THnQh1zbH+glwDBLjP7rtcF3UtTQnzsxnUQEtUjAjv9Eo6rVSa1u4RsFA8CMDpoVcQiTMhqgRalbfjovdr11wg== dependencies: vue "^3.2.20" @@ -396,6 +396,11 @@ acorn@^7.0.0, acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +animate.css@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/animate.css/-/animate.css-4.1.1.tgz#614ec5a81131d7e4dc362a58143f7406abd68075" + integrity sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ== + ansi-regex@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" diff --git a/sub/init.go b/sub/init.go index 1adca9b..8efcb2e 100644 --- a/sub/init.go +++ b/sub/init.go @@ -5,7 +5,6 @@ import ( "OneAuth/libs/auth" "OneAuth/models" "github.com/urfave/cli/v2" - "github.com/veypi/utils/cmd" "github.com/veypi/utils/log" "strconv" ) @@ -20,6 +19,7 @@ func runInit(c *cli.Context) error { } // 初始化项目 +var appid uint func InitSystem() error { db() @@ -27,15 +27,9 @@ func InitSystem() error { 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 - //} + appid = self.ID err = role(self.InitRoleID == 0) - return nil + return err } func db() { @@ -56,14 +50,14 @@ func selfApp() (*models.App, error) { self := &models.App{ Name: "OA", Icon: "", - UUID: "jU5Jo5hM", + UUID: cfg.CFG.APPUUID, Des: "", Creator: 0, UserCount: 0, Hide: false, Host: "", UserRefreshUrl: "/", - Key: "cB43wF94MLTksyBK", + Key: cfg.CFG.APPKey, EnableRegister: true, EnableUserKey: true, EnableUser: true, @@ -86,7 +80,7 @@ func role(reset_init_role bool) error { } var err error adminRole := &models.Role{ - AppID: cfg.CFG.APPID, + AppID: appid, Name: "admin", IsUnique: false, } @@ -96,7 +90,7 @@ func role(reset_init_role bool) error { } for _, na := range n { a := &models.Resource{ - AppID: cfg.CFG.APPID, + AppID: appid, Name: na, Tag: "", Des: "", @@ -112,7 +106,7 @@ func role(reset_init_role bool) error { } } userRole := &models.Role{ - AppID: cfg.CFG.APPID, + AppID: appid, Name: "user", IsUnique: false, } @@ -120,12 +114,12 @@ func role(reset_init_role bool) 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))) + err = auth.BindRoleAuth(cfg.DB(), userRole.ID, authMap[auth.APP].ID, models.AuthRead, strconv.Itoa(int(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 cfg.DB().Model(&models.App{}).Where("id = ?", appid).Update("init_role_id", adminRole.ID).Error } return nil } diff --git a/sub/static/index.html b/sub/static/index.html index 9831347..4b79a1f 100644 --- a/sub/static/index.html +++ b/sub/static/index.html @@ -1 +1,17 @@ -oaf
\ No newline at end of file + + + + + + + Vite App + + + + + + +
+ + + diff --git a/sub/web.go b/sub/web.go index ce43620..a0cde02 100644 --- a/sub/web.go +++ b/sub/web.go @@ -6,16 +6,15 @@ import ( "embed" "github.com/urfave/cli/v2" "github.com/veypi/OneBD" - "github.com/veypi/OneBD/core" - "github.com/veypi/OneBD/rfc" "github.com/veypi/utils/log" - "net/http" - "os" ) -//go:embed static +//go:embed static/static var staticFiles embed.FS +//go:embed static/favicon.ico +var icon []byte + //go:embed static/index.html var indexFile []byte @@ -37,34 +36,15 @@ func RunWeb(c *cli.Context) error { LoggerPath: cfg.CFG.LoggerPath, LoggerLevel: ll, }) - app.Router().EmbedFile("/", indexFile) - app.Router().EmbedDir("/", staticFiles, "static/") - - // TODO media 文件需要检验权限 - //app.Router().SubRouter("/media/").Static("/", cfg.CFG.EXEDir+"/media") - - app.Router().SetNotFoundFunc(func(m core.Meta) { - f, err := os.Open(cfg.CFG.EXEDir + "/static/index.html") - if err != nil { - m.WriteHeader(rfc.StatusNotFound) - return - } - defer f.Close() - info, err := f.Stat() - if err != nil { - m.WriteHeader(rfc.StatusNotFound) - return - } - if info.IsDir() { - // TODO:: dir list - m.WriteHeader(rfc.StatusNotFound) - return - } - http.ServeContent(m, m.Request(), info.Name(), info.ModTime(), f) - }) api.Router(app.Router().SubRouter("api")) + // TODO media 文件需要检验权限 + app.Router().SubRouter("/media/").Static("/", cfg.CFG.MediaDir) + app.Router().EmbedDir("/static", staticFiles, "static/static/") + app.Router().EmbedFile("/favicon.ico", icon) + app.Router().EmbedFile("/*", indexFile) + log.Info().Msg("\nRouting Table\n" + app.Router().String()) return app.Run() }