master
veypi 3 years ago
parent d7aea82ced
commit bc3f5e0b0c

@ -2,6 +2,7 @@
统一验证服务 统一验证服务
## 用户验证思路 ## 用户验证思路
![](https://public.veypi.com/img/screenshot/20211012194238.png) ![](https://public.veypi.com/img/screenshot/20211012194238.png)

@ -3,22 +3,25 @@ package api
import ( import (
"OneAuth/api/app" "OneAuth/api/app"
"OneAuth/api/role" "OneAuth/api/role"
"OneAuth/api/token"
"OneAuth/api/user" "OneAuth/api/user"
"OneAuth/api/wx" "OneAuth/api/wx"
"OneAuth/libs/base"
"github.com/veypi/OneBD" "github.com/veypi/OneBD"
"github.com/veypi/OneBD/core" "github.com/veypi/OneBD/core"
) )
func Router(r OneBD.Router) { func Router(r OneBD.Router) {
r.SetNotFoundFunc(func(m core.Meta) { r.SetNotFoundFunc(func(m core.Meta) {
m.Write([]byte("{\"status\": 0}")) base.JSONResponse(m, nil, nil)
}) })
r.SetInternalErrorFunc(func(m core.Meta) { r.SetInternalErrorFunc(func(m core.Meta) {
m.Write([]byte("{\"status\": 0}")) base.JSONResponse(m, nil, nil)
}) })
user.Router(r.SubRouter("/user")) user.Router(r.SubRouter("/user"))
wx.Router(r.SubRouter("wx")) wx.Router(r.SubRouter("wx"))
app.Router(r.SubRouter("app")) app.Router(r.SubRouter("app"))
token.Router(r.SubRouter("token"))
role.Router(r) role.Router(r)
//message.Router(r.SubRouter("/message")) //message.Router(r.SubRouter("/message"))

@ -8,15 +8,17 @@ import (
"OneAuth/models" "OneAuth/models"
"github.com/veypi/OneBD" "github.com/veypi/OneBD"
"github.com/veypi/OneBD/rfc" "github.com/veypi/OneBD/rfc"
"github.com/veypi/utils"
) )
func Router(r OneBD.Router) { func Router(r OneBD.Router) {
r.Set("/", appHandlerP, rfc.MethodPost, rfc.MethodGet)
r.Set("/:id", appHandlerP, rfc.MethodGet) r.Set("/:id", appHandlerP, rfc.MethodGet)
} }
var appHandlerP = OneBD.NewHandlerPool(func() OneBD.Handler { var appHandlerP = OneBD.NewHandlerPool(func() OneBD.Handler {
h := &appHandler{} h := &appHandler{}
h.Ignore(rfc.MethodGet) h.Ignore(rfc.MethodGet, rfc.MethodPost)
return h return h
}) })
@ -31,7 +33,7 @@ func (h *appHandler) Get() (interface{}, error) {
isSelf := h.Meta().Query("is_self") isSelf := h.Meta().Query("is_self")
if isSelf != "" { if isSelf != "" {
// 无权限可以获取本系统基本信息 // 无权限可以获取本系统基本信息
h.query.ID = cfg.CFG.APPID h.query.UUID = cfg.CFG.APPUUID
err := cfg.DB().Where(h.query).First(h.query).Error err := cfg.DB().Where(h.query).First(h.query).Error
return h.query, err return h.query, err
} }
@ -64,3 +66,35 @@ func (h *appHandler) Get() (interface{}, error) {
err = cfg.DB().Find(&list).Error err = cfg.DB().Find(&list).Error
return list, err 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
}

@ -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
}

@ -3,13 +3,11 @@ package user
import ( import (
"OneAuth/cfg" "OneAuth/cfg"
"OneAuth/libs/app" "OneAuth/libs/app"
"OneAuth/libs/auth"
"OneAuth/libs/base" "OneAuth/libs/base"
"OneAuth/libs/oerr" "OneAuth/libs/oerr"
"OneAuth/libs/token" "OneAuth/libs/token"
"OneAuth/models" "OneAuth/models"
"errors"
//"OneAuth/ws"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"github.com/veypi/OneBD" "github.com/veypi/OneBD"
@ -43,10 +41,13 @@ type handler struct {
// Get get user data // Get get user data
func (h *handler) Get() (interface{}, error) { 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") username := h.Meta().Query("username")
if username != "" { if username != "" {
users := make([]*models.User, 0, 10) 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 { if err != nil {
return nil, err return nil, err
} }
@ -56,14 +57,14 @@ func (h *handler) Get() (interface{}, error) {
if userID != 0 { if userID != 0 {
user := &models.User{} user := &models.User{}
user.ID = uint(userID) 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 { } else {
users := make([]models.User, 10) users := make([]models.User, 10)
skip, err := strconv.Atoi(h.Meta().Query("skip")) skip, err := strconv.Atoi(h.Meta().Query("skip"))
if err != nil || skip < 0 { if err != nil || skip < 0 {
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 nil, err
} }
return users, nil return users, nil
@ -73,16 +74,15 @@ func (h *handler) Get() (interface{}, error) {
// Post register user // Post register user
func (h *handler) Post() (interface{}, error) { func (h *handler) Post() (interface{}, error) {
self := &models.App{} self := &models.App{}
self.ID = cfg.CFG.APPID self.UUID = cfg.CFG.APPUUID
err := cfg.DB().Where(self).First(self).Error err := cfg.DB().Where(self).First(self).Error
if err != nil { if err != nil {
return nil, oerr.DBErr.Attach(err) 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") return nil, oerr.NoAuth.AttachStr("register disabled")
} }
var userdata = struct { var userdata = struct {
UUID string `json:"uuid"`
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
Nickname string `json:"nickname"` Nickname string `json:"nickname"`
@ -120,27 +120,10 @@ func (h *handler) Post() (interface{}, error) {
if err := tx.Create(&h.User).Error; err != nil { if err := tx.Create(&h.User).Error; err != nil {
return oerr.ResourceDuplicated 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 { if err != nil {
return err 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 return nil
}) })
if err != nil { if err != nil {
@ -169,11 +152,10 @@ func (h *handler) Patch() (interface{}, error) {
} else { } else {
target.ID = uint(tempID) target.ID = uint(tempID)
} }
tx := cfg.DB().Begin()
if err := cfg.DB().Where(&target).First(&target).Error; err != nil { if err := cfg.DB().Where(&target).First(&target).Error; err != nil {
return nil, err 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 return nil, oerr.NoAuth
} }
if len(opts.Password) >= 6 { if len(opts.Password) >= 6 {
@ -197,11 +179,9 @@ func (h *handler) Patch() (interface{}, error) {
if opts.Status != "" { if opts.Status != "" {
target.Status = opts.Status target.Status = opts.Status
} }
if err := tx.Updates(&target).Error; err != nil { if err := cfg.DB().Updates(&target).Error; err != nil {
tx.Rollback()
return nil, err return nil, err
} }
tx.Commit()
return nil, nil return nil, nil
} }
@ -222,10 +202,6 @@ func (h *handler) Head() (interface{}, error) {
if len(uid) == 0 || len(password) == 0 { if len(uid) == 0 || len(password) == 0 {
return nil, oerr.ApiArgsError return nil, oerr.ApiArgsError
} }
appUUID := h.Meta().Query("uuid")
if appUUID == "" {
return nil, oerr.ApiArgsMissing.AttachStr("uuid")
}
h.User = new(models.User) h.User = new(models.User)
uidType := h.Meta().Query("uid_type") uidType := h.Meta().Query("uid_type")
switch uidType { switch uidType {
@ -239,7 +215,7 @@ func (h *handler) Head() (interface{}, error) {
h.User.Username = uid h.User.Username = uid
} }
target := &models.App{} target := &models.App{}
target.UUID = appUUID target.UUID = cfg.CFG.APPUUID
err = cfg.DB().Where(target).Find(target).Error err = cfg.DB().Where(target).Find(target).Error
if err != nil { if err != nil {
return nil, oerr.DBErr.Attach(err) return nil, oerr.DBErr.Attach(err)
@ -261,17 +237,12 @@ func (h *handler) Head() (interface{}, error) {
au.AppID = target.ID au.AppID = target.ID
err = cfg.DB().Where(au).First(au).Error err = cfg.DB().Where(au).First(au).Error
appID := target.ID appID := target.ID
h.Meta().SetHeader("content", target.UserRefreshUrl)
if err != nil { if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) { return nil, err
return nil, err } else if au.Status != models.AUOK {
}
appID = cfg.CFG.APPID
h.Meta().SetHeader("content", "/app/"+target.UUID)
} else if au.Disabled {
return nil, oerr.DisableLogin return nil, oerr.DisableLogin
} }
tokenStr, err := token.GetToken(h.User, appID) tokenStr, err := token.GetToken(h.User, appID, cfg.CFG.APPKey)
if err != nil { if err != nil {
log.HandlerErrs(err) log.HandlerErrs(err)
return nil, oerr.Unknown.Attach(err) return nil, oerr.Unknown.Attach(err)

@ -15,11 +15,11 @@ var CFG = &struct {
Host string Host string
LoggerPath string LoggerPath string
LoggerLevel string LoggerLevel string
APPID uint APPUUID string
APPKey string APPKey string
TimeFormat string TimeFormat string
Debug bool Debug bool
EXEDir string MediaDir string
DB struct { DB struct {
Type string Type string
Addr string Addr string
@ -28,14 +28,15 @@ var CFG = &struct {
DB string DB string
} }
}{ }{
APPID: 1, APPUUID: "jU5Jo5hM",
APPKey: "cB43wF94MLTksyBK",
AdminUser: "admin", AdminUser: "admin",
Host: "0.0.0.0:4001", Host: "0.0.0.0:4001",
LoggerPath: "", LoggerPath: "",
LoggerLevel: "debug", LoggerLevel: "debug",
TimeFormat: "2006/01/02 15:04:05", TimeFormat: "2006/01/02 15:04:05",
Debug: true, Debug: true,
EXEDir: "./", MediaDir: "/Users/light/test/media/",
DB: struct { DB: struct {
Type string Type string
Addr string Addr string

@ -8,7 +8,10 @@ import (
"gorm.io/gorm" "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 := &models.AppUser{}
au.AppID = appID au.AppID = appID
au.UserID = userID au.UserID = userID
@ -17,19 +20,25 @@ func AddUser(tx *gorm.DB, appID uint, userID uint, roleID uint) error {
return oerr.ResourceDuplicated return oerr.ResourceDuplicated
} }
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
au.Status = status
err = tx.Create(au).Error err = tx.Create(au).Error
if err != nil { if err != nil {
return err return err
} }
err = auth.BindUserRole(tx, userID, roleID) if roleID > 0 {
if err != nil { err = auth.BindUserRole(tx, userID, roleID)
return err if err != nil {
return err
}
} }
return tx.Model(&models.App{}).Where("id = ?", appID).Update("user_count", gorm.Expr("user_count + ?", 1)).Error return tx.Model(&models.App{}).Where("id = ?", appID).Update("user_count", gorm.Expr("user_count + ?", 1)).Error
} }
return err return err
} }
func EnableUser(tx *gorm.DB, appID uint, userID uint) error { func EnableUser(tx *gorm.DB, appID uint, userID uint) error {
if appID == 0 || userID == 0 {
return oerr.FuncArgsError
}
au := &models.AppUser{} au := &models.AppUser{}
au.AppID = appID au.AppID = appID
au.UserID = userID au.UserID = userID
@ -37,12 +46,18 @@ func EnableUser(tx *gorm.DB, appID uint, userID uint) error {
if err != nil { if err != nil {
return err 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 { func DisableUser(tx *gorm.DB, appID uint, userID uint) error {
if appID == 0 || userID == 0 {
return oerr.FuncArgsError
}
au := &models.AppUser{} au := &models.AppUser{}
au.AppID = appID au.AppID = appID
au.UserID = userID au.UserID = userID
return tx.Where(au).Update("disabled", true).Error return tx.Where(au).Update("status", models.AUDisable).Error
} }

@ -3,10 +3,12 @@ package base
import ( import (
"OneAuth/libs/oerr" "OneAuth/libs/oerr"
"OneAuth/libs/tools" "OneAuth/libs/tools"
"errors"
"github.com/json-iterator/go" "github.com/json-iterator/go"
"github.com/veypi/OneBD" "github.com/veypi/OneBD"
"github.com/veypi/OneBD/rfc" "github.com/veypi/OneBD/rfc"
"github.com/veypi/utils/log" "github.com/veypi/utils/log"
"gorm.io/gorm"
"strconv" "strconv"
"sync" "sync"
"time" "time"
@ -14,6 +16,41 @@ import (
var json = jsoniter.ConfigFastest 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 { type ApiHandler struct {
OneBD.BaseHandler OneBD.BaseHandler
UserHandler UserHandler
@ -24,29 +61,12 @@ func (h *ApiHandler) Init(m OneBD.Meta) error {
} }
func (h *ApiHandler) OnResponse(data interface{}) { func (h *ApiHandler) OnResponse(data interface{}) {
if h.Meta().Method() == rfc.MethodHead { JSONResponse(h.Meta(), data, nil)
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)
} }
func (h *ApiHandler) OnError(err error) { func (h *ApiHandler) OnError(err error) {
log.WithNoCaller.Warn().Err(err).Msg(h.Meta().RequestPath()) log.WithNoCaller.Warn().Err(err).Msg(h.Meta().RequestPath())
msg := err.Error() JSONResponse(h.Meta(), nil, err)
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)
}
} }
var ioNumLimit = make(map[string]time.Time) var ioNumLimit = make(map[string]time.Time)

@ -1,6 +1,7 @@
package base package base
import ( import (
"OneAuth/cfg"
"OneAuth/libs/oerr" "OneAuth/libs/oerr"
"OneAuth/libs/token" "OneAuth/libs/token"
"OneAuth/models" "OneAuth/models"
@ -26,7 +27,7 @@ func (a *UserHandler) ParsePayload(m OneBD.Meta) error {
if tokenStr == "" { if tokenStr == "" {
return oerr.NotLogin return oerr.NotLogin
} }
ok, err := token.ParseToken(tokenStr, a.Payload) ok, err := token.ParseToken(tokenStr, a.Payload, cfg.CFG.APPKey)
if ok { if ok {
return nil return nil
} }

@ -1,7 +1,6 @@
package oerr package oerr
import ( import (
"errors"
"gorm.io/gorm" "gorm.io/gorm"
"strconv" "strconv"
) )
@ -42,11 +41,13 @@ type Code uint
- 9 : - 9 :
*/ */
// Unknown error
const ( const (
Unknown Code = 0 Unknown Code = 0
) )
const ( const (
// 2 数据库错误 // DBErr 2 数据库错误
// -1 系统错误 // -1 系统错误
// -2 数据读写错误 // -2 数据读写错误
DBErr Code = 20001 DBErr Code = 20001
@ -56,10 +57,13 @@ const (
) )
const ( const (
// 3 // LogicErr 3 系统内逻辑错误
LogicErr Code = 30000
AppNotJoin Code = 30001
) )
const ( const (
// NotLogin
// 4 权限类型错误 // 4 权限类型错误
// 1: 登录权限 // 1: 登录权限
// 2: 资源操作权限 // 2: 资源操作权限
@ -130,6 +134,8 @@ var codeMap = map[Code]string{
NoAuth: "no auth to access", NoAuth: "no auth to access",
AccessErr: "access error", AccessErr: "access error",
AccessTooFast: "access too fast", AccessTooFast: "access too fast",
LogicErr: "logic error",
AppNotJoin: "not join in app",
} }
func (c Code) Error() string { func (c Code) Error() string {
@ -144,7 +150,7 @@ func (c Code) String() string {
return codeMap[Unknown] return codeMap[Unknown]
} }
// 附加错误详细原因 // Attach 附加错误详细原因
func (c Code) Attach(errs ...error) (e error) { func (c Code) Attach(errs ...error) (e error) {
e = c e = c
for _, err := range errs { for _, err := range errs {
@ -195,16 +201,3 @@ func (w *wrapErr) Error() string {
func (w *wrapErr) UnWrap() error { func (w *wrapErr) UnWrap() error {
return w.err 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
}

@ -1,7 +1,6 @@
package token package token
import ( import (
"OneAuth/libs/key"
"OneAuth/models" "OneAuth/models"
"github.com/veypi/utils/jwt" "github.com/veypi/utils/jwt"
) )
@ -16,9 +15,8 @@ type simpleAuth struct {
// TODO:: roles 是否会造成token过大 ? // TODO:: roles 是否会造成token过大 ?
type PayLoad struct { type PayLoad struct {
jwt.Payload jwt.Payload
ID uint `json:"id"` ID uint `json:"id"`
AppID uint `json:"app_id"` Auth map[uint]*simpleAuth `json:"auth"`
Auth map[uint]*simpleAuth `json:"auth"`
} }
// GetAuth resource_uuid 缺省或仅第一个有效 权限会被更高权限覆盖 // GetAuth resource_uuid 缺省或仅第一个有效 权限会被更高权限覆盖
@ -49,11 +47,10 @@ func (p *PayLoad) GetAuth(ResourceID string, ResourceUUID ...string) models.Auth
return res return res
} }
func GetToken(u *models.User, appID uint) (string, error) { func GetToken(u *models.User, appID uint, key string) (string, error) {
payload := &PayLoad{ payload := &PayLoad{
ID: u.ID, ID: u.ID,
AppID: appID, Auth: map[uint]*simpleAuth{},
Auth: map[uint]*simpleAuth{},
} }
for _, a := range u.GetAuths() { for _, a := range u.GetAuths() {
if appID == a.AppID { 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) { func ParseToken(token string, payload *PayLoad, key string) (bool, error) {
return jwt.ParseToken(token, payload, []byte(key.User(payload.ID, payload.AppID))) return jwt.ParseToken(token, payload, []byte(key))
} }

@ -7,7 +7,6 @@ import (
"github.com/veypi/utils/cmd" "github.com/veypi/utils/cmd"
"github.com/veypi/utils/log" "github.com/veypi/utils/log"
"os" "os"
"path/filepath"
) )
const Version = "v0.1.0" const Version = "v0.1.0"
@ -35,21 +34,6 @@ func main() {
Value: cfg.CFG.LoggerPath, Value: cfg.CFG.LoggerPath,
Destination: &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{ &cli.StringFlag{
Name: "host", Name: "host",
Value: cfg.CFG.Host, Value: cfg.CFG.Host,
@ -72,11 +56,6 @@ func main() {
srv.SetStopFunc(func() { srv.SetStopFunc(func() {
}) })
app.Before = func(c *cli.Context) error { 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 { if cfg.CFG.Debug {
cfg.CFG.LoggerLevel = "debug" cfg.CFG.LoggerLevel = "debug"
} }

@ -42,12 +42,22 @@ type App struct {
Wx *Wechat `json:"wx" gorm:"foreignkey:AppID;references:ID"` 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 { type AppUser struct {
BaseModel BaseModel
AppID uint `json:"app_id"` AppID uint `json:"app_id"`
UserID uint `json:"user_id"` APP *App `json:"app"`
Disabled bool `json:"disabled"` UserID uint `json:"user_id"`
Status string `json:"status"` User *User `json:"user"`
Status AUStatus `json:"status"`
} }
type Wechat struct { type Wechat struct {

@ -7,7 +7,8 @@
"serve": "vite preview" "serve": "vite preview"
}, },
"dependencies": { "dependencies": {
"@veypi/one-icon": "2", "@veypi/one-icon": "2.0.5",
"animate.css": "^4.1.1",
"axios": "^0.24.0", "axios": "^0.24.0",
"js-base64": "^3.7.2", "js-base64": "^3.7.2",
"vue": "^3.2.16", "vue": "^3.2.16",
@ -18,6 +19,7 @@
"@tailwindcss/postcss7-compat": "^2.1.0", "@tailwindcss/postcss7-compat": "^2.1.0",
"@vitejs/plugin-vue": "^1.9.3", "@vitejs/plugin-vue": "^1.9.3",
"autoprefixer": "^9.8.8", "autoprefixer": "^9.8.8",
"less": "^4.1.2",
"naive-ui": "^2.19.11", "naive-ui": "^2.19.11",
"postcss": "^7.0.39", "postcss": "^7.0.39",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17", "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17",

File diff suppressed because one or more lines are too long

@ -1,59 +1,99 @@
<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
import { onBeforeMount, ref} from 'vue'
import util from './libs/util'
import {store} from "./store";
import {useOsTheme} from 'naive-ui'
const osThemeRef = useOsTheme()
onBeforeMount(() => {
util.title("统一认证")
store.dispatch('fetchSelf')
store.commit('setTheme', osThemeRef.value)
})
</script>
<template> <template>
<n-config-provider :theme="store.getters.GetTheme" class="h-full w-full"> <n-config-provider :theme-overrides="Theme.overrides" :locale="zhCN" :date-locale="dateZhCN"
<n-layout class="h-full w-full font-sans select-none"> :theme="Theme">
<n-layout has-sider style="height: calc(100% - 24px)"> <n-layout class="font-sans select-none">
<n-layout-sider <n-layout>
class="h-full" <n-layout-header class="pr-5" bordered style="height: 64px;line-height: 64px;">
collapse-mode="transform" <div class="inline-block float-left h-full">
:collapsed-width="0" <one-icon color="#000" class="inline-block" @click="$router.push('/')" style="font-size: 48px;margin:8px;color:aqua">
:width="120" glassdoor
show-trigger="bar"
content-style="padding: 24px;"
bordered
default-collapsed
>
-
</n-layout-sider>
<n-layout>
<n-layout-header bordered style="height: 64px;line-height: 64px;">
{{ osThemeRef }}
<one-icon @click="store.dispatch('changeTheme')" class="float-right" style="font-size: 36px; margin: 14px">
{{ store.getters.IsDark ? 'Daytimemode' : 'nightmode-fill' }}
</one-icon> </one-icon>
</n-layout-header> </div>
<n-layout style="height: calc(100% - 64px)"> <div class="inline-block float-left h-full" style="margin-left: 10px">
<router-view class="h-full w-full"></router-view> <n-h6 prefix="bar" align-text><n-text type="primary">统一认证系统</n-text></n-h6>
</div>
<div v-if="store.state.user.ready" class="inline-block h-full float-right flex justify-center items-center">
<avatar></avatar>
</div>
<div class="inline-block float-right h-full px-3">
<fullscreen v-model="isFullScreen" class="header-icon">fullscreen</fullscreen>
<div class="header-icon">
<one-icon @click="ChangeTheme">
{{ IsDark ? 'Daytimemode' : 'nightmode-fill' }}
</one-icon>
</div>
</div>
</n-layout-header>
<n-layout has-sider style="height: calc(100vh - 88px)">
<n-layout-sider
collapse-mode="transform"
:collapsed-width="0"
:width="120"
show-trigger="bar"
content-style="padding: 24px;"
bordered
default-collapsed
:native-scrollbar="false"
>
-
</n-layout-sider>
<n-layout class="main" :native-scrollbar="false">
<n-message-provider>
<router-view v-slot="{ Component }">
<transition mode="out-in" enter-active-class="animate__fadeInLeft" leave-active-class="animate__fadeOutRight">
<component class="animate__animated animate__400ms" :is="Component" style="margin: 10px; min-height: calc(100vh - 108px)"
></component>
</transition>
</router-view>
</n-message-provider>
</n-layout> </n-layout>
</n-layout> </n-layout>
</n-layout> </n-layout>
<n-layout-footer style="height: 24px;line-height: 24px" class="flex justify-around px-3 text-gray-500 text-xs"> <n-layout-footer bordered style="height: 24px;line-height: 24px"
<span class="hover:text-black cursor-pointer">关于OA</span> class="flex justify-around px-3 text-gray-500 text-xs">
<span class="hover:text-black cursor-pointer" @click="$router.push({name: 'about'})">关于OA</span>
<span class="hover:text-black cursor-pointer">使用须知</span> <span class="hover:text-black cursor-pointer">使用须知</span>
<span class="hover:text-black cursor-pointer"> <span class="hover:text-black cursor-pointer" @click="goto('https://veypi.com')">
©2021 veypi ©2021 veypi
</span> </span>
</n-layout-footer> </n-layout-footer>
</n-layout> </n-layout>
</n-config-provider> </n-config-provider>
</template> </template>
<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
import {onBeforeMount, ref} from 'vue'
import util from './libs/util'
import {useStore} from "./store";
import {Theme, IsDark, ChangeTheme} from "./theme";
import {zhCN, dateZhCN} from 'naive-ui'
import avatar from "./components/avatar";
import fullscreen from './components/fullscreen'
import Fullscreen from "./components/fullscreen/fullscreen.vue";
let isFullScreen = ref(false)
let store = useStore()
onBeforeMount(() => {
util.title("统一认证")
store.dispatch('fetchSelf')
store.dispatch('user/fetchUserData')
})
let goto = (url: any) => {
window.open(url, "_blank")
}
let collapsed = ref(true)
</script>
<style lang="less">
.animate__400ms {
--animate-duration: 400ms;
}
<style>
html, html,
body { body {
width: 100%; width: 100%;
@ -73,6 +113,12 @@ body {
height: 100%; height: 100%;
} }
.header-icon {
display: inline-block;
font-size: 24px;
margin: 20px 10px 20px 10px;
}
#app { #app {
font-family: Avenir, Helvetica, Arial, sans-serif; font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
@ -82,6 +128,9 @@ body {
height: 100%; height: 100%;
} }
.main {
}
::-webkit-scrollbar { ::-webkit-scrollbar {
display: none; /* Chrome Safari */ display: none; /* Chrome Safari */
} }

@ -13,19 +13,19 @@ function baseRequests(url: string, method: any = 'GET', query: any, data: any, s
auth_token: localStorage.auth_token auth_token: localStorage.auth_token
} }
}).then((res: any) => { }).then((res: any) => {
if ('auth_token' in res.headers) { if ('auth_token' in res.headers) {
localStorage.auth_token = res.headers.auth_token localStorage.auth_token = res.headers.auth_token
} }
if (method === 'HEAD') { if (method === 'HEAD') {
success(res.headers) success(res.headers)
} else { } else {
success(res.data) success(res.data)
} }
}) })
.catch((e: any) => { .catch((e: any) => {
if (e.response && e.response.status === 401) { if (e.response && e.response.status === 401) {
console.log(e) console.log(e)
store.dispatch('handleLogout') store.commit('user/logout')
return return
} }
console.log(e) console.log(e)

@ -10,7 +10,6 @@ import {store} from '../store'
import {Base64} from 'js-base64' import {Base64} from 'js-base64'
export type SuccessFunction<T> = (e: any) => void; export type SuccessFunction<T> = (e: any) => void;
export type FailedFunction<T> = (e: any) => void; export type FailedFunction<T> = (e: any) => void;
@ -34,15 +33,15 @@ class Interface {
const newFail = function (data: any) { const newFail = function (data: any) {
if (data && data.code === 40001) { if (data && data.code === 40001) {
// no login // no login
store.dispatch('handleLogout') store.commit('user/logout')
return return
} }
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore // @ts-ignore
if (data && data.code > 0 && Code[data.code]) { if (data && data.code && Code[data.code]) {
} }
if (fail) { if (fail) {
fail(data) fail(data.err)
} }
} }
@ -54,7 +53,7 @@ class Interface {
} else { } else {
newFail(data) newFail(data)
if (data.code === 41001) { if (data.code === 41001) {
store.dispatch('handleLogout') store.commit('user/logout')
// bus.$emit('log_out') // bus.$emit('log_out')
} }
} }
@ -92,6 +91,15 @@ const user = {
uuid: uuid, uuid: uuid,
password: Base64.encode(password) password: Base64.encode(password)
}) })
},
get(id: number) {
return new Interface(ajax.get, this.local + id)
},
list() {
return new Interface(ajax.get, this.local)
},
update(id: number, props: any) {
return new Interface(ajax.patch, this.local + id, props)
} }
} }

File diff suppressed because one or more lines are too long

@ -0,0 +1,29 @@
<template>
<div class="core rounded-2xl p-3">
<div class="grid gap-4 grid-cols-5">
<div class="col-span-2">
<n-avatar @click="$router.push({name: 'app', params: {uuid: core.uuid}})" round :size="80" :src="core.icon">
{{ core.icon ? '' : core.name }}
</n-avatar>
</div>
<div class="col-span-3 grid grid-cols-1 items-center text-left">
<div class="h-10 flex items-center text-2xl italic font-bold">{{ core.name }}</div>
<div class="select-all">{{ core.uuid }}</div>
</div>
</div>
<textarea disabled style="background: none;border: none" class="focus:outline-none w-full">{{core.des}}</textarea>
</div>
</template>
<script setup lang='ts'>
import {defineProps} from "vue";
let props = defineProps<{
core: any
}>()
</script>
<style scoped>
.core {
width: 256px;
background: #2c3e50;
}
</style>

@ -0,0 +1,61 @@
<template>
<base_frame style="line-height:40px" v-model="shown" :isDark="IsDark">
<div class="flex">
<n-avatar :src="$store.state.user.icon" round></n-avatar>
</div>
<template v-slot:main>
<div style="height: 100%">
<div style="height: calc(100% - 50px)">
<div class="w-full px-3">
<div class="h-16 flex justify-between items-center">
<span style="color: #777">我的账户</span>
<span @click="$router.push({name: 'user_setting'});shown=false" class="cursor-pointer"
style="color:#f36828">账户中心</span>
</div>
<div class="grid grid-cols-4 gap-4 h-20">
<div class="flex items-center justify-center">
<n-avatar size="50" :src="$store.state.user.icon" round></n-avatar>
</div>
<div class="col-span-2 text-xs grid grid-cols-1 items-center" style="">
<span>昵称: &ensp;&ensp; {{ $store.state.user.nickname }}</span>
<span>账户: &ensp;&ensp; {{ $store.state.user.username }}</span>
<span>邮箱: &ensp;&ensp; {{ $store.state.user.email }}</span>
</div>
<div class="">123</div>
</div>
<hr class="mt-10" style="border:none;border-top:1px solid #777;">
</div>
</div>
<hr style="border:none;border-top:2px solid #777;">
<div style="height: 48px">
<div @click="$store.commit('user/logout')"
class="w-full h-full flex justify-center items-center cursor-pointer transition duration-500 ease-in-out transform hover:scale-125">
<one-icon :color="IsDark?'#eee': '#333'" class="inline-block" style="font-size: 24px;">
logout
</one-icon>
<div>
退出登录
</div>
</div>
</div>
</div>
</template>
</base_frame>
</template>
<script lang="ts" setup>
import base_frame from './frame.vue'
import {IsDark} from '../../theme'
import {ref} from "vue";
let shown = ref(false)
function asd(e) {
console.log([e, shown.value])
}
</script>
<style scoped>
</style>

@ -0,0 +1,82 @@
<template>
<div>
<div @click="setValue(true)">
<slot>
</slot>
</div>
<div @click.self="setValue(false)" class="core" style="height: 100vh;width: 100vw;" v-if="props.modelValue">
<div style="height: 100%; width: 300px" class="core-right">
<transition appear enter-active-class="animate__slideInRight">
<div class="right-title animate__animated animate__faster">
<slot name="title"></slot>
<div class="flex items-center float-right h-full px-1">
<one-icon @click="setValue(false)" color="#fff" style="font-size: 24px">close</one-icon>
</div>
</div>
</transition>
<div class="right-main">
<transition appear enter-active-class="animate__slideInDown">
<div class="right-main-core animate__animated animate__faster"
:style="{'background': props.isDark ? '#222': '#eee'}">
<slot name="main"></slot>
</div>
</transition>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { defineProps, defineEmits, watch} from "vue";
let emits = defineEmits<{
(e: 'update:modelValue', v: boolean): void
}>()
let props = defineProps<{
isDark: boolean,
modelValue: boolean
}>()
function setValue(b: boolean) {
emits('update:modelValue', b)
}
</script>
<style scoped>
.core {
position: fixed;
left: 0;
top: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 100;
}
.core-right {
position: absolute;
right: 0;
top: 0;
}
.right-main {
width: 100%;
height: calc(100% - 50px);
overflow: hidden;
}
.right-main-core {
height: 100%;
width: 100%;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
--animate-duration: 400ms;
}
.right-title {
width: 100%;
height: 50px;
line-height: 50px;
background: linear-gradient(90deg, #f74d22, #fa9243);
}
</style>

@ -0,0 +1,3 @@
import avatar from './avatar.vue'
export default avatar

@ -0,0 +1,10 @@
<template>
<div></div>
</template>
<script lang="ts" setup>
</script>
<style scoped>
</style>

@ -0,0 +1,71 @@
<template>
<div @click="handleFullscreen">
<one-icon>{{ props.modelValue ? 'fullscreen-exit' : 'fullscreen' }}</one-icon>
</div>
</template>
<script lang="ts" setup>
import {defineEmits, onMounted, defineProps} from "vue";
let emit = defineEmits<{
(e: 'update:modelValue', v: boolean): void
}>()
let props = defineProps<{
modelValue: boolean
}>()
function handleFullscreen() {
let main = document.body
if (props.modelValue) {
if (document.exitFullscreen) {
document.exitFullscreen()
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen()
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen()
} else if (document.msExitFullscreen) {
document.msExitFullscreen()
}
} else {
if (main.requestFullscreen) {
main.requestFullscreen()
} else if (main.mozRequestFullScreen) {
main.mozRequestFullScreen()
} else if (main.webkitRequestFullScreen) {
main.webkitRequestFullScreen()
} else if (main.msRequestFullscreen) {
main.msRequestFullscreen()
}
}
}
onMounted(() => {
let isFullscreen =
document.fullscreenElement ||
document.mozFullScreenElement ||
document.webkitFullscreenElement ||
document.fullScreen ||
document.mozFullScreen ||
document.webkitIsFullScreen
isFullscreen = !!isFullscreen
document.addEventListener('fullscreenchange', () => {
emit('update:modelValue', !props.modelValue)
})
document.addEventListener('mozfullscreenchange', () => {
emit('update:modelValue', !props.modelValue)
})
document.addEventListener('webkitfullscreenchange', () => {
emit('update:modelValue', !props.modelValue)
})
document.addEventListener('msfullscreenchange', () => {
emit('update:modelValue', !props.modelValue)
})
emit('update:modelValue', isFullscreen)
})
</script>
<style>
</style>

@ -0,0 +1,2 @@
import fullscreen from './fullscreen.vue'
export default fullscreen

@ -27,6 +27,9 @@ const util = {
document.cookie = document.cookie =
name + '=' + escape(value) + ';expires=' + exp.toLocaleString() name + '=' + escape(value) + ';expires=' + exp.toLocaleString()
}, },
getToken() {
return localStorage.auth_token
},
checkLogin() { checkLogin() {
// return parseInt(this.getCookie('stat')) === 1 // return parseInt(this.getCookie('stat')) === 1
return Boolean(localStorage.auth_token) return Boolean(localStorage.auth_token)

@ -6,12 +6,14 @@ import OneIcon from '@veypi/one-icon'
import naive from 'naive-ui' import naive from 'naive-ui'
import './index.css' import './index.css'
import {Api} from './api' import {Api} from './api'
import './assets/icon.js'
import 'animate.css'
const app = createApp(App) const app = createApp(App)
app.use(Api) app.use(Api)
app.use(naive) app.use(naive)
app.use(OneIcon, {href: './icon.js'}) app.use(OneIcon)
app.use(router) app.use(router)
app.use(store, key) app.use(store, key)
app.mount('#app') app.mount('#app')

@ -22,12 +22,25 @@ const router = createRouter({
component: () => import('../views/home.vue') component: () => import('../views/home.vue')
}, },
{ {
path: '/app', path: '/app/:uuid?',
name: 'app', name: 'app',
meta: { meta: {
requiresAuth: true, 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', path: '/wx',

@ -1,58 +1,45 @@
import {InjectionKey} from 'vue' import {InjectionKey} from 'vue'
import {createStore, useStore as baseUseStore, Store} from 'vuex' import {createStore, useStore as baseUseStore, Store} from 'vuex'
import api from "../api"; import api from "../api";
import router from "../router"; import {User, UserState} from './user'
import {darkTheme} from 'naive-ui'
export interface State { export interface State extends Object {
oauuid: string oauuid: string
user: object user: UserState
theme: string apps: []
} }
export const key: InjectionKey<Store<State>> = Symbol() export const key: InjectionKey<Store<State>> = Symbol()
export const store = createStore<State>({ export const store = createStore<State>({
modules: {
user: User
},
// @ts-ignore
state: { state: {
theme: 'light',
oauuid: '', oauuid: '',
user: {} apps: []
},
getters: {
IsDark(state: any) {
return state.theme === 'dark'
},
GetTheme(state: any, getters) {
return getters.IsDark ? darkTheme : null
}
}, },
getters: {},
mutations: { mutations: {
setOA(state: any, data: any) { setOA(state: any, data: any) {
state.oauuid = data.uuid state.oauuid = data.uuid
}, },
setTheme(state: any, t: string) { setApps(state: State, data: any) {
state.theme = t state.apps = data
} }
}, },
actions: { actions: {
changeTheme(context) {
if (context.getters.IsDark) {
context.commit('setTheme', 'light')
} else {
context.commit('setTheme', 'dark')
}
},
fetchSelf({commit}) { fetchSelf({commit}) {
api.app.self().Start(d => { api.app.self().Start(d => {
commit('setOA', d) commit('setOA', d)
}) })
}, },
handleLogout() { fetchApps({commit}) {
localStorage.removeItem('auth_token') api.app.list().Start(e => {
router.push({name: 'login'}) commit('setApps', e)
})
} }
} }
}) })

@ -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<UserState, State> = {
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)
})
}
}
}
}

@ -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')
}

@ -0,0 +1,12 @@
<template>
<div>
about
</div>
</template>
<script lang="ts" setup>
</script>
<style scoped>
</style>

@ -0,0 +1,28 @@
<template>
<div>
{{ uuid }}
</div>
</template>
<script lang="ts" setup>
import {useRoute, useRouter} from "vue-router";
import {computed, onMounted} from "vue";
import api from "../api";
let route = useRoute()
let router = useRouter()
let uuid = computed(() => route.params.uuid)
onMounted(() => {
if (uuid.value === '') {
router.push({name: '404', params: {path: route.path}})
return
}
api.app.get(uuid.value as string).Start(e => {
console.log(e)
})
})
</script>
<style scoped>
</style>

@ -1,11 +1,18 @@
<template> <template>
<div> <div class="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 text-center">
<div class="flex items-center justify-center" v-for="(item, k) in apps" :key="k">
<AppCard :core="item"></AppCard>
</div>
<div class="flex items-center justify-center" v-for="(item) in '1234567890'" :key="item">
<AppCard :core="{}"></AppCard>
</div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {ref} from "vue"; import {onMounted, ref} from "vue";
import api from "../api"; import api from "../api";
import AppCard from '../components/app.vue'
let apps = ref([]) let apps = ref([])
@ -15,7 +22,9 @@ function getApps() {
}) })
} }
getApps() onMounted(() => {
getApps()
})
</script> </script>

@ -1,61 +1,98 @@
<template> <template>
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
<div class="p-3" style=""> <div
<n-form ref="formRef" label-placement="left"> :style="{background:Theme.me.lightBox, 'box-shadow': Theme.me.lightBoxShadow}"
<n-form-item required label="username" :validation-status="rules.username[0]" :feedback="rules.username[1]"> class="px-10 pb-9 pt-28 rounded-xl w-96">
<n-input v-model:value="data.username"></n-input> <n-form label-width="70px" label-align="left" :model="data" ref="form_ref" label-placement="left" :rules="rules">
<n-form-item required label="用户名" path="username">
<n-input @keydown.enter="divs[1].focus()" :ref="el => {if (el)divs[0]=el}"
v-model:value="data.username"></n-input>
</n-form-item> </n-form-item>
<n-form-item required label="username" :validation-status="rules.username[0]" :feedback="rules.username[1]"> <n-form-item required label="密码" path="password">
<n-input v-model:value="data.username"></n-input> <n-input @keydown.enter="login" :ref="el => {if (el) divs[1]=el}" v-model:value="data.password"
type="password"></n-input>
</n-form-item> </n-form-item>
<n-button @click="login"></n-button> <div class="flex justify-around mt-4">
<n-button @click="login"></n-button>
<n-button @click="router.push({name:'register'})"></n-button>
</div>
</n-form> </n-form>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {computed, ref} from "vue"; import {computed, onMounted, ref} from "vue";
import {Theme} from "../theme";
import {useMessage} from 'naive-ui'
import api from "../api"
import {useRoute, useRouter} from "vue-router";
import {store} from "../store";
let msg = useMessage()
const route = useRoute()
const router = useRouter()
let formRef = ref(null) const divs = ref([])
let form_ref = ref(null)
let data = ref({ let data = ref({
username: null, username: '',
password: null password: ''
}) })
let ruleInline = { let rules = {
username: [ username: [
(v: string) => !!v || 'required', {
(v: string) => (v && v.length >= 3 && v.length <= 16) || '长度要求3~16' required: true,
validator(r: any, v: any) {
return (v && v.length >= 3 && v.length <= 16) || new Error('长度要求3~16')
},
trigger: ['input', 'blur']
}
], ],
password: [ password: [{
(v: string) => !!v || 'required', required: true,
(v: string) => (v && v.length >= 6 && v.length <= 16) || '长度要求6~16' validator(r: any, v: any) {
] return (v && v.length >= 6 && v.length <= 16) || new Error('长度要求6~16')
}
function check(rs: [], v: any) {
for (let r of rs) {
let res = r(v)
if (res !== true) {
return ['error', res]
} }
} }]
return ['', '']
} }
let rules = ref({ let uuid = computed(() => {
username: computed(() => { return route.params.uuid || store.state.oauuid
return check(ruleInline.username, data.value.username)
})
}) })
function login() { function login() {
formRef.value.validate(e => { // @ts-ignore
console.log(e) form_ref.value.validate((e:any) => {
if (!e) {
api.user.login(data.value.username, data.value.password, uuid.value as string).Start((url: string) => {
msg.success('登录成功')
store.dispatch('user/fetchUserData')
let target = url
if (route.query.redirect) {
target = route.query.redirect as string
}
if (target && target.startsWith('http')) {
window.location.href = target
} else if (target) {
router.push(target)
} else {
router.push({name: 'home'})
}
}, e => {
console.log(e)
msg.warning('登录失败:' + e)
})
}
}) })
} }
onMounted(() => {
if (divs.value[0]) {
// @ts-ignore
divs.value[0].focus()
}
})
</script> </script>
<style scoped> <style scoped>
</style> </style>

@ -0,0 +1,96 @@
<template>
<div class="pt-10">
<div class="flex justify-center">
<div class="relative rounded-xl text-lg text-black" :style="{background: IsDark?'#555': '#d5d5d5'}">
<div @click="ifInfo=true" class="inline-block px-5 rounded-xl" :style="{background: ifInfo ? '#fc0005': ''}">
个人信息
</div>
<div @click="ifInfo=false" class="inline-block px-5 rounded-xl" :style="{background: ifInfo ? '': '#fc0005'}">
账户管理
</div>
</div>
</div>
<div class="inline-block flex justify-center mt-10">
<transition mode="out-in" enter-active-class="animate__fadeInLeft" leave-active-class="animate__fadeOutRight">
<div v-if="ifInfo" class="animate__animated animate__faster">
<n-form label-placement="left" label-width="80px" label-align="left">
<n-form-item label="昵称">
<n-input v-model:value="user.nickname" @blur="update('nickname')"></n-input>
</n-form-item>
<n-form-item label="头像">
<n-upload
action=""
:headers="{'': ''}"
:data="{}"
>
<n-avatar size="large" round :src="user.icon">
</n-avatar>
</n-upload>
</n-form-item>
</n-form>
</div>
<div v-else class="animate__animated animate__faster">
<n-form label-align="left" label-width="80px" label-placement="left">
<n-form-item label="username">
<n-input disabled v-model:value="user.username"></n-input>
</n-form-item>
<n-form-item label="phone">
<n-input v-model:value="user.phone" @blur="update('phone')"></n-input>
</n-form-item>
<n-form-item label="email">
<n-auto-complete :options="emailOptions" v-model:value="user.email"
@blur="update('email')"></n-auto-complete>
</n-form-item>
</n-form>
</div>
</transition>
</div>
</div>
</template>
<script lang="ts" setup>
import {ref, computed} from "vue";
import {IsDark} from "../theme";
import {useStore} from "../store";
import api from "../api";
import {useMessage} from "naive-ui";
let msg = useMessage()
let store = useStore()
let ifInfo = ref(true)
let user = ref({
username: store.state.user.username,
nickname: store.state.user.nickname,
icon: store.state.user.icon,
email: store.state.user.email,
phone: store.state.user.phone,
})
let emailOptions = computed(() => {
return ['@gmail.com', '@163.com', '@qq.com'].map((suffix) => {
const prefix = user.value.email.split('@')[0]
return {
label: prefix + suffix,
value: prefix + suffix
}
})
})
function update(key: string) {
// @ts-ignore
let v = user.value[key]
if (v === store.state.user[key]) {
return
}
api.user.update(store.state.user.id, {[key]: v}).Start(e => {
msg.success('更新成功')
store.state.user[key] = v
}, e => {
msg.error('更新失败: ' + e.err)
})
}
</script>
<style scoped>
</style>

14
oaf/src/vuex.d.ts vendored

@ -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<State>
}
}

@ -1,19 +1,36 @@
import { defineConfig } from 'vite' import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [vue()], plugins: [vue()],
server: { server: {
// host: '0.0.0.0', // host: '0.0.0.0',
host: '127.0.0.1', host: '127.0.0.1',
port: 8080, port: 8080,
proxy: { proxy: {
'/api': 'http://127.0.0.1:4001/' '/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'
}
}) })

@ -216,10 +216,10 @@
dependencies: dependencies:
"@types/yargs-parser" "*" "@types/yargs-parser" "*"
"@veypi/one-icon@2": "@veypi/one-icon@2.0.5":
version "2.0.0" version "2.0.5"
resolved "https://registry.yarnpkg.com/@veypi/one-icon/-/one-icon-2.0.0.tgz#15262e87644903d90a2124fbc9c95fbb6bb6bcb8" resolved "https://registry.yarnpkg.com/@veypi/one-icon/-/one-icon-2.0.5.tgz#1083be30fc3bbb89aaf19501b00110d3ae9b1c77"
integrity sha512-zcUx3YzTIRiZe5XkJRZyKDgVzQThEWyZSCpQTreeYtKEObCl4B0Oa6CFYo4IToGD6uQmReM0R6oNPg5mhPjc2A== integrity sha512-THnQh1zbH+glwDBLjP7rtcF3UtTQnzsxnUQEtUjAjv9Eo6rVSa1u4RsFA8CMDpoVcQiTMhqgRalbfjovdr11wg==
dependencies: dependencies:
vue "^3.2.20" 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" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== 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: ansi-regex@^5.0.0:
version "5.0.1" version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"

@ -5,7 +5,6 @@ import (
"OneAuth/libs/auth" "OneAuth/libs/auth"
"OneAuth/models" "OneAuth/models"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/veypi/utils/cmd"
"github.com/veypi/utils/log" "github.com/veypi/utils/log"
"strconv" "strconv"
) )
@ -20,6 +19,7 @@ func runInit(c *cli.Context) error {
} }
// 初始化项目 // 初始化项目
var appid uint
func InitSystem() error { func InitSystem() error {
db() db()
@ -27,15 +27,9 @@ func InitSystem() error {
if err != nil { if err != nil {
return err return err
} }
cfg.CFG.APPID = self.ID 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) err = role(self.InitRoleID == 0)
return nil return err
} }
func db() { func db() {
@ -56,14 +50,14 @@ func selfApp() (*models.App, error) {
self := &models.App{ self := &models.App{
Name: "OA", Name: "OA",
Icon: "", Icon: "",
UUID: "jU5Jo5hM", UUID: cfg.CFG.APPUUID,
Des: "", Des: "",
Creator: 0, Creator: 0,
UserCount: 0, UserCount: 0,
Hide: false, Hide: false,
Host: "", Host: "",
UserRefreshUrl: "/", UserRefreshUrl: "/",
Key: "cB43wF94MLTksyBK", Key: cfg.CFG.APPKey,
EnableRegister: true, EnableRegister: true,
EnableUserKey: true, EnableUserKey: true,
EnableUser: true, EnableUser: true,
@ -86,7 +80,7 @@ func role(reset_init_role bool) error {
} }
var err error var err error
adminRole := &models.Role{ adminRole := &models.Role{
AppID: cfg.CFG.APPID, AppID: appid,
Name: "admin", Name: "admin",
IsUnique: false, IsUnique: false,
} }
@ -96,7 +90,7 @@ func role(reset_init_role bool) error {
} }
for _, na := range n { for _, na := range n {
a := &models.Resource{ a := &models.Resource{
AppID: cfg.CFG.APPID, AppID: appid,
Name: na, Name: na,
Tag: "", Tag: "",
Des: "", Des: "",
@ -112,7 +106,7 @@ func role(reset_init_role bool) error {
} }
} }
userRole := &models.Role{ userRole := &models.Role{
AppID: cfg.CFG.APPID, AppID: appid,
Name: "user", Name: "user",
IsUnique: false, IsUnique: false,
} }
@ -120,12 +114,12 @@ func role(reset_init_role bool) error {
if err != nil { if err != nil {
return err 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 { if err != nil {
return err return err
} }
if reset_init_role { 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 return nil
} }

@ -1 +1,17 @@
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>oaf</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"><link href="/css/chunk-6cfd0404.487f8d50.css" rel="prefetch"><link href="/js/about.a3055c06.js" rel="prefetch"><link href="/js/chunk-6cfd0404.65d5baaf.js" rel="prefetch"><link href="/app.b131f8d0f62acd99ab8e.js" rel="preload" as="script"><link href="/css/app.dafd5329.css" rel="preload" as="style"><link href="/css/chunk-vendors.dfe6062e.css" rel="preload" as="style"><link href="/js/chunk-vendors.83bac771.js" rel="preload" as="script"><link href="/css/chunk-vendors.dfe6062e.css" rel="stylesheet"><link href="/css/app.dafd5329.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but oaf doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/js/chunk-vendors.83bac771.js"></script><script src="/app.b131f8d0f62acd99ab8e.js"></script></body></html> <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
<script type="module" crossorigin src="/static/index.dddecd43.js"></script>
<link rel="modulepreload" href="/static/vendor.ba3bd51d.js">
<link rel="stylesheet" href="/static/vendor.3a295b6b.css">
<link rel="stylesheet" href="/static/index.c49db26f.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

@ -6,16 +6,15 @@ import (
"embed" "embed"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/veypi/OneBD" "github.com/veypi/OneBD"
"github.com/veypi/OneBD/core"
"github.com/veypi/OneBD/rfc"
"github.com/veypi/utils/log" "github.com/veypi/utils/log"
"net/http"
"os"
) )
//go:embed static //go:embed static/static
var staticFiles embed.FS var staticFiles embed.FS
//go:embed static/favicon.ico
var icon []byte
//go:embed static/index.html //go:embed static/index.html
var indexFile []byte var indexFile []byte
@ -37,34 +36,15 @@ func RunWeb(c *cli.Context) error {
LoggerPath: cfg.CFG.LoggerPath, LoggerPath: cfg.CFG.LoggerPath,
LoggerLevel: ll, 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")) 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()) log.Info().Msg("\nRouting Table\n" + app.Router().String())
return app.Run() return app.Run()
} }

Loading…
Cancel
Save