Compare commits
7
Commits
7f08874a07
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc3f5e0b0c | ||
|
|
d7aea82ced | ||
|
|
c74c332e6a | ||
|
|
82b64a4bb2 | ||
|
|
cd7029c298 | ||
|
|
3d194e935d | ||
|
|
935653ba28 |
+3
-1
@@ -271,4 +271,6 @@ Sessionx.vim
|
||||
tags
|
||||
# Persistent undo
|
||||
[._]*.un~
|
||||
|
||||
oa.db
|
||||
static
|
||||
OneAuth
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
# OneAuth
|
||||
|
||||
统一验证服务
|
||||
统一验证服务
|
||||
|
||||
|
||||
## 用户验证思路
|
||||
|
||||

|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
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) {
|
||||
base.JSONResponse(m, nil, nil)
|
||||
})
|
||||
r.SetInternalErrorFunc(func(m core.Meta) {
|
||||
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"))
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"OneAuth/cfg"
|
||||
"OneAuth/libs/auth"
|
||||
"OneAuth/libs/base"
|
||||
"OneAuth/libs/oerr"
|
||||
"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, rfc.MethodPost)
|
||||
return h
|
||||
})
|
||||
|
||||
type appHandler struct {
|
||||
base.ApiHandler
|
||||
query *models.App
|
||||
}
|
||||
|
||||
func (h *appHandler) Get() (interface{}, error) {
|
||||
id := h.Meta().Params("id")
|
||||
h.query = &models.App{}
|
||||
isSelf := h.Meta().Query("is_self")
|
||||
if isSelf != "" {
|
||||
// 无权限可以获取本系统基本信息
|
||||
h.query.UUID = cfg.CFG.APPUUID
|
||||
err := cfg.DB().Where(h.query).First(h.query).Error
|
||||
return h.query, err
|
||||
}
|
||||
err := h.ParsePayload(h.Meta())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !h.GetAuth(auth.APP, id).CanRead() {
|
||||
return nil, oerr.NoAuth
|
||||
}
|
||||
if id != "" {
|
||||
h.query.UUID = id
|
||||
err := cfg.DB().Where(h.query).First(h.query).Error
|
||||
return h.query, err
|
||||
}
|
||||
// 注释代码为获取已经绑定的应用
|
||||
//user := &models.User{}
|
||||
//user.ID = h.Payload.ID
|
||||
//err := cfg.DB().Preload("Roles.Auths").Preload("Auths").Where(user).First(user).Error
|
||||
//if err != nil {
|
||||
// return nil, oerr.DBErr.Attach(err)
|
||||
//}
|
||||
//ids := make([]string, 0, 10)
|
||||
//for _, a := range user.GetAuths() {
|
||||
// if a.RID == auth.Login && a.Level.CanDo() {
|
||||
// ids = append(ids, a.RUID)
|
||||
// }
|
||||
//}
|
||||
list := make([]*models.App, 0, 10)
|
||||
err = cfg.DB().Find(&list).Error
|
||||
return list, err
|
||||
}
|
||||
|
||||
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,27 @@
|
||||
package role
|
||||
|
||||
import (
|
||||
"OneAuth/cfg"
|
||||
"OneAuth/libs/auth"
|
||||
"OneAuth/libs/base"
|
||||
"OneAuth/libs/oerr"
|
||||
"OneAuth/models"
|
||||
"github.com/veypi/OneBD"
|
||||
"github.com/veypi/OneBD/core"
|
||||
)
|
||||
|
||||
var authP = OneBD.NewHandlerPool(func() core.Handler {
|
||||
return &authHandler{}
|
||||
})
|
||||
|
||||
type authHandler struct {
|
||||
base.ApiHandler
|
||||
}
|
||||
|
||||
func (h *authHandler) Get() (interface{}, error) {
|
||||
if !h.GetAuth(auth.Auth).CanRead() {
|
||||
return nil, oerr.NoAuth
|
||||
}
|
||||
l := make([]*models.Auth, 0, 10)
|
||||
return &l, cfg.DB().Find(&l).Error
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package role
|
||||
|
||||
import (
|
||||
"OneAuth/cfg"
|
||||
"OneAuth/libs/auth"
|
||||
"OneAuth/libs/base"
|
||||
"OneAuth/libs/oerr"
|
||||
"OneAuth/models"
|
||||
"errors"
|
||||
"github.com/veypi/OneBD"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var roleP = OneBD.NewHandlerPool(func() OneBD.Handler {
|
||||
return &roleHandler{}
|
||||
})
|
||||
|
||||
type roleHandler struct {
|
||||
base.ApiHandler
|
||||
}
|
||||
|
||||
func (h *roleHandler) Get() (interface{}, error) {
|
||||
id := h.Meta().ParamsInt("id")
|
||||
if !h.GetAuth(auth.Role, h.Meta().Params("id")).CanRead() {
|
||||
return nil, oerr.NoAuth
|
||||
}
|
||||
if id > 0 {
|
||||
role := &models.Role{}
|
||||
role.ID = uint(id)
|
||||
err := cfg.DB().Preload("Auths").Preload("Users").First(role).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return role, nil
|
||||
}
|
||||
roles := make([]*models.Role, 0, 10)
|
||||
err := cfg.DB().Preload("Auths").Preload("Users").Find(&roles).Error
|
||||
return roles, err
|
||||
}
|
||||
|
||||
func (h *roleHandler) Post() (interface{}, error) {
|
||||
if !h.GetAuth(auth.Role).CanCreate() {
|
||||
return nil, oerr.NoAuth
|
||||
}
|
||||
role := &models.Role{}
|
||||
err := h.Meta().ReadJson(role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
role.ID = 0
|
||||
if role.Name == "" {
|
||||
return nil, oerr.ApiArgsMissing
|
||||
}
|
||||
return role, cfg.DB().Where(role).FirstOrCreate(role).Error
|
||||
}
|
||||
|
||||
func (h *roleHandler) Patch() (interface{}, error) {
|
||||
if !h.GetAuth(auth.Role).CanUpdate() {
|
||||
return nil, oerr.NoAuth
|
||||
}
|
||||
query := &struct {
|
||||
Name *string `json:"name"`
|
||||
// 角色标签
|
||||
Tag *string `json:"tag" gorm:"default:''"`
|
||||
IsUnique *bool `json:"is_unique" gorm:"default:false"`
|
||||
}{}
|
||||
err := h.Meta().ReadJson(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rid := h.Meta().ParamsInt("id")
|
||||
if rid <= 0 {
|
||||
return nil, oerr.ApiArgsError
|
||||
}
|
||||
role := &models.Role{}
|
||||
role.ID = uint(rid)
|
||||
err = cfg.DB().Preload("Users").Where(role).First(role).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, cfg.DB().Transaction(func(tx *gorm.DB) error {
|
||||
var err error
|
||||
if query.Tag != nil && *query.Tag != role.Tag {
|
||||
err = tx.Model(role).Update("tag", *query.Tag).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if query.Name != nil && *query.Name != role.Name {
|
||||
err = tx.Model(role).Update("name", *query.Name).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if query.IsUnique != nil && *query.IsUnique != role.IsUnique {
|
||||
if *query.IsUnique && len(role.Users) > 1 {
|
||||
return errors.New("该角色绑定用户已超过1个,请解绑后在修改")
|
||||
}
|
||||
err = tx.Table("roles").Where("id = ?", role.ID).Update("is_unique", *query.IsUnique).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (h *roleHandler) Delete() (interface{}, error) {
|
||||
if !h.GetAuth(auth.Role).CanDelete() {
|
||||
return nil, oerr.NoAuth
|
||||
}
|
||||
rid := h.Meta().ParamsInt("id")
|
||||
if rid <= 2 {
|
||||
return nil, oerr.NoAuth
|
||||
}
|
||||
role := &models.Role{}
|
||||
role.ID = uint(rid)
|
||||
err := cfg.DB().Where(role).First(role).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, cfg.DB().Delete(role).Error
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package role
|
||||
|
||||
import (
|
||||
"github.com/veypi/OneBD"
|
||||
"github.com/veypi/OneBD/rfc"
|
||||
)
|
||||
|
||||
func Router(r OneBD.Router) {
|
||||
r.Set("/role/", roleP, rfc.MethodGet, rfc.MethodPost)
|
||||
r.Set("/role/:id", roleP, rfc.MethodGet, rfc.MethodDelete, rfc.MethodPatch)
|
||||
r.Set("/role/:id/:action/:rid", roleP, rfc.MethodGet)
|
||||
r.Set("/auth/", authP, rfc.MethodGet)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"OneAuth/cfg"
|
||||
"OneAuth/libs/app"
|
||||
"OneAuth/libs/auth"
|
||||
"OneAuth/libs/base"
|
||||
"OneAuth/libs/oerr"
|
||||
"OneAuth/libs/token"
|
||||
"OneAuth/models"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/veypi/OneBD"
|
||||
"github.com/veypi/OneBD/rfc"
|
||||
"github.com/veypi/utils/log"
|
||||
"gorm.io/gorm"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Router(r OneBD.Router) {
|
||||
pool := OneBD.NewHandlerPool(func() OneBD.Handler {
|
||||
h := &handler{}
|
||||
h.Ignore(rfc.MethodHead, rfc.MethodPost)
|
||||
return h
|
||||
})
|
||||
r.Set("/", pool, rfc.MethodGet, rfc.MethodPost) // list
|
||||
r.Set("/:user_id", pool, rfc.MethodGet, rfc.MethodPatch, rfc.MethodHead, rfc.MethodDelete)
|
||||
r.Set("/:user_id/role/", userRoleP, rfc.MethodPost)
|
||||
r.Set("/:user_id/role/:role_id", userRoleP, rfc.MethodDelete)
|
||||
//r.WS("/ws", func(m OneBD.Meta) (conn OneBD.WebsocketConn, err error) {
|
||||
//return ws.User.Upgrade(m.ResponseWriter(), m.Request())
|
||||
//})
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
base.ApiHandler
|
||||
User *models.User
|
||||
}
|
||||
|
||||
// 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().Where("username LIKE ? OR nickname LIKE ?", "%"+username+"%", "%"+username+"%").Find(&users).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
userID := h.Meta().ParamsInt("user_id")
|
||||
if userID != 0 {
|
||||
user := &models.User{}
|
||||
user.ID = uint(userID)
|
||||
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().Offset(skip).Find(&users).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Post register user
|
||||
func (h *handler) Post() (interface{}, error) {
|
||||
self := &models.App{}
|
||||
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 && !h.Payload.GetAuth(auth.User, "").CanCreate() {
|
||||
return nil, oerr.NoAuth.AttachStr("register disabled")
|
||||
}
|
||||
var userdata = struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Nickname string `json:"nickname"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
Domain string `json:"domain"`
|
||||
Title string `json:"title"`
|
||||
Position string `json:"position"`
|
||||
}{}
|
||||
if err := h.Meta().ReadJson(&userdata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pass, err := base64.StdEncoding.DecodeString(userdata.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(pass) > 32 || len(pass) < 6 {
|
||||
return nil, oerr.PassError
|
||||
}
|
||||
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
h.User = new(models.User)
|
||||
h.User.Icon = fmt.Sprintf("/media/icon/default/%04d.jpg", r.Intn(230))
|
||||
h.User.Nickname = userdata.Nickname
|
||||
h.User.Phone = userdata.Phone
|
||||
h.User.Username = userdata.Username
|
||||
h.User.Email = userdata.Email
|
||||
h.User.Position = userdata.Position
|
||||
if err := h.User.UpdatePass(string(pass)); err != nil {
|
||||
log.HandlerErrs(err)
|
||||
return nil, oerr.ResourceCreatedFailed
|
||||
}
|
||||
err = cfg.DB().Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Create(&h.User).Error; err != nil {
|
||||
return oerr.ResourceDuplicated
|
||||
}
|
||||
err := app.AddUser(tx, self.ID, h.User.ID, self.InitRoleID, models.AUOK)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.User, nil
|
||||
}
|
||||
|
||||
// Patch update user data
|
||||
func (h *handler) Patch() (interface{}, error) {
|
||||
uid := h.Meta().Params("user_id")
|
||||
opts := struct {
|
||||
Password string `json:"password"`
|
||||
Nickname string `json:"nickname"`
|
||||
Phone string `json:"phone" gorm:"type:varchar(20);unique;default:null" json:",omitempty"`
|
||||
Email string `json:"email" gorm:"type:varchar(50);unique;default:null" json:",omitempty"`
|
||||
Status string `json:"status"`
|
||||
Position string `json:"position"`
|
||||
}{}
|
||||
if err := h.Meta().ReadJson(&opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
target := models.User{}
|
||||
if tempID, err := strconv.Atoi(uid); err != nil || tempID <= 0 {
|
||||
return nil, oerr.ApiArgsError.Attach(err)
|
||||
} else {
|
||||
target.ID = uint(tempID)
|
||||
}
|
||||
if err := cfg.DB().Where(&target).First(&target).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
if err := target.UpdatePass(opts.Password); err != nil {
|
||||
log.HandlerErrs(err)
|
||||
return nil, oerr.ApiArgsError.AttachStr(err.Error())
|
||||
}
|
||||
}
|
||||
if opts.Nickname != "" {
|
||||
target.Nickname = opts.Nickname
|
||||
}
|
||||
if opts.Position != "" {
|
||||
target.Position = opts.Position
|
||||
}
|
||||
if opts.Phone != "" {
|
||||
target.Phone = opts.Phone
|
||||
}
|
||||
if opts.Email != "" {
|
||||
target.Email = opts.Email
|
||||
}
|
||||
if opts.Status != "" {
|
||||
target.Status = opts.Status
|
||||
}
|
||||
if err := cfg.DB().Updates(&target).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Delete delete user
|
||||
func (h *handler) Delete() (interface{}, error) {
|
||||
// TODO::
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Head user login
|
||||
func (h *handler) Head() (interface{}, error) {
|
||||
uid := h.Meta().Params("user_id")
|
||||
pass, err := base64.StdEncoding.DecodeString(h.Meta().Query("password"))
|
||||
if err != nil {
|
||||
return nil, oerr.ApiArgsError.Attach(err)
|
||||
}
|
||||
password := string(pass)
|
||||
if len(uid) == 0 || len(password) == 0 {
|
||||
return nil, oerr.ApiArgsError
|
||||
}
|
||||
h.User = new(models.User)
|
||||
uidType := h.Meta().Query("uid_type")
|
||||
switch uidType {
|
||||
case "username":
|
||||
h.User.Username = uid
|
||||
case "phone":
|
||||
h.User.Phone = uid
|
||||
case "email":
|
||||
h.User.Email = uid
|
||||
default:
|
||||
h.User.Username = uid
|
||||
}
|
||||
target := &models.App{}
|
||||
target.UUID = cfg.CFG.APPUUID
|
||||
err = cfg.DB().Where(target).Find(target).Error
|
||||
if err != nil {
|
||||
return nil, oerr.DBErr.Attach(err)
|
||||
}
|
||||
if err := cfg.DB().Preload("Roles.Auths").Preload("Auths").Where(h.User).First(h.User).Error; err != nil {
|
||||
if err.Error() == gorm.ErrRecordNotFound.Error() {
|
||||
return nil, oerr.AccountNotExist
|
||||
} else {
|
||||
log.HandlerErrs(err)
|
||||
return nil, oerr.DBErr.Attach(err)
|
||||
}
|
||||
}
|
||||
isAuth, err := h.User.CheckLogin(password)
|
||||
if err != nil || !isAuth {
|
||||
return nil, oerr.PassError.Attach(err)
|
||||
}
|
||||
au := &models.AppUser{}
|
||||
au.UserID = h.User.ID
|
||||
au.AppID = target.ID
|
||||
err = cfg.DB().Where(au).First(au).Error
|
||||
appID := target.ID
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if au.Status != models.AUOK {
|
||||
return nil, oerr.DisableLogin
|
||||
}
|
||||
tokenStr, err := token.GetToken(h.User, appID, cfg.CFG.APPKey)
|
||||
if err != nil {
|
||||
log.HandlerErrs(err)
|
||||
return nil, oerr.Unknown.Attach(err)
|
||||
}
|
||||
h.Meta().SetHeader("auth_token", tokenStr)
|
||||
log.Info().Msg(h.User.Username + " login")
|
||||
return nil, nil
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"OneAuth/cfg"
|
||||
"OneAuth/libs/base"
|
||||
"OneAuth/libs/oerr"
|
||||
"OneAuth/models"
|
||||
"errors"
|
||||
"github.com/veypi/OneBD"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var userRoleP = OneBD.NewHandlerPool(func() OneBD.Handler {
|
||||
return &userRoleHandler{}
|
||||
})
|
||||
|
||||
type userRoleHandler struct {
|
||||
base.ApiHandler
|
||||
}
|
||||
|
||||
func (h *userRoleHandler) Post() (interface{}, error) {
|
||||
if !h.GetAuth("role").CanCreate() {
|
||||
return nil, oerr.NoAuth
|
||||
}
|
||||
uid := h.Meta().ParamsInt("user_id")
|
||||
if uid <= 0 {
|
||||
return nil, oerr.ApiArgsMissing
|
||||
}
|
||||
query := &models.Role{}
|
||||
err := h.Meta().ReadJson(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if query.ID != 0 {
|
||||
err = cfg.DB().First(query, query.ID).Error
|
||||
} else if query.Name != "" {
|
||||
err = cfg.DB().Where(map[string]interface{}{
|
||||
"name": query.Name,
|
||||
"tag": query.Tag,
|
||||
}).First(query).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
err = cfg.DB().Create(query).Error
|
||||
}
|
||||
} else {
|
||||
return nil, oerr.ApiArgsMissing
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if query.IsUnique {
|
||||
}
|
||||
link := &models.UserRole{}
|
||||
link.UserID = uint(uid)
|
||||
link.RoleID = query.ID
|
||||
err = cfg.DB().Transaction(func(tx *gorm.DB) (err error) {
|
||||
if query.IsUnique {
|
||||
err = tx.Where("role_id = ?", query.ID).Delete(models.UserRole{}).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Where(link).FirstOrCreate(link).Error
|
||||
})
|
||||
return link, err
|
||||
}
|
||||
|
||||
func (h *userRoleHandler) Delete() (interface{}, error) {
|
||||
if !h.GetAuth("role").CanDelete() {
|
||||
return nil, oerr.NoAuth
|
||||
}
|
||||
uid := h.Meta().ParamsInt("user_id")
|
||||
id := h.Meta().ParamsInt("role_id")
|
||||
if uid <= 0 || id <= 0 {
|
||||
return nil, oerr.ApiArgsMissing
|
||||
}
|
||||
link := &models.UserRole{}
|
||||
link.UserID = uint(uid)
|
||||
link.RoleID = uint(id)
|
||||
err := cfg.DB().Where(link).First(link).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, cfg.DB().Delete(link).Error
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
package wx
|
||||
|
||||
import (
|
||||
"OneAuth/cfg"
|
||||
"OneAuth/libs/tools"
|
||||
"OneAuth/models"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/veypi/OneBD"
|
||||
"github.com/veypi/OneBD/rfc"
|
||||
"github.com/veypi/utils"
|
||||
"github.com/veypi/utils/log"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var tokens = map[uint]string{
|
||||
1: "",
|
||||
}
|
||||
|
||||
func login(m OneBD.Meta) {
|
||||
var loc = ""
|
||||
defer func() {
|
||||
m.Header().Set("Location", loc)
|
||||
log.Warn().Msg(loc)
|
||||
m.WriteHeader(rfc.StatusPermanentRedirect)
|
||||
}()
|
||||
app := &models.App{
|
||||
UUID: m.Params("id"),
|
||||
}
|
||||
err := cfg.DB().Preload("Wx").Where(app).First(app).Error
|
||||
loc = fmt.Sprintf("/#/wx?uuid=%s&msg=", app.UUID)
|
||||
if err != nil {
|
||||
loc += err.Error()
|
||||
return
|
||||
}
|
||||
if app.Wx == nil {
|
||||
loc += "微信登录未绑定"
|
||||
return
|
||||
}
|
||||
if tokens[app.Wx.ID] == "" {
|
||||
tokens[app.Wx.ID], err = requestCorpToken(app.Wx.CorpID, app.Wx.CorpSecret)
|
||||
if err != nil {
|
||||
log.Warn().Msg("get corp token failed: " + err.Error())
|
||||
loc += err.Error()
|
||||
return
|
||||
}
|
||||
}
|
||||
user, err := getUserID(tokens[app.Wx.ID], m.Query("code"))
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "access_token expired") {
|
||||
tokens[app.Wx.ID], err = requestCorpToken(app.Wx.CorpID, app.Wx.CorpSecret)
|
||||
if err != nil {
|
||||
log.Warn().Msg("refresh corp token failed: " + err.Error())
|
||||
loc += err.Error()
|
||||
return
|
||||
}
|
||||
user, err = getUserID(tokens[app.Wx.ID], m.Query("code"))
|
||||
if err != nil {
|
||||
log.Warn().Msg("get user token failed: " + err.Error())
|
||||
loc += err.Error()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
log.Warn().Msg("get user token failed: " + err.Error())
|
||||
loc += err.Error()
|
||||
return
|
||||
}
|
||||
}
|
||||
info, err := getUserInfo(tokens[app.Wx.ID], user)
|
||||
if err != nil {
|
||||
log.Warn().Msg("get user info failed: " + err.Error())
|
||||
loc += err.Error()
|
||||
return
|
||||
}
|
||||
log.Warn().Msgf("\ncode= %s\nstate= %s\nu = %s\n%v",
|
||||
m.Query("code"), m.Query("state"), user, info)
|
||||
pass, err := utils.AesEncrypt(fmt.Sprintf("%s.%d", user, time.Now().Unix()), []byte(app.UUID))
|
||||
if err != nil {
|
||||
loc += err.Error()
|
||||
return
|
||||
}
|
||||
log.Warn().Msgf("pass: %s", pass)
|
||||
v := url.Values{}
|
||||
v.Add("wid", pass)
|
||||
u, err := url.Parse(app.Host)
|
||||
u.RawQuery = v.Encode()
|
||||
if err != nil {
|
||||
loc += err.Error()
|
||||
return
|
||||
}
|
||||
loc = u.String()
|
||||
}
|
||||
|
||||
func requestCorpToken(corpid, corpsecret string) (string, error) {
|
||||
addr := "https://qyapi.weixin.qq.com/cgi-bin/gettoken"
|
||||
query := map[string]string{
|
||||
"corpid": corpid,
|
||||
"corpsecret": corpsecret,
|
||||
}
|
||||
res := &struct {
|
||||
Errmsg string `json:"errmsg"`
|
||||
Errcode *uint `json:"errcode"`
|
||||
AccessToken string `json:"access_token"`
|
||||
}{}
|
||||
err := tools.Query(addr, query, res)
|
||||
if err != nil {
|
||||
return "", errors.New("request token response json parse err :" + err.Error())
|
||||
}
|
||||
if res.Errcode != nil && *res.Errcode == 0 {
|
||||
return res.AccessToken, nil
|
||||
} else {
|
||||
//返回错误信息
|
||||
err = errors.New(fmt.Sprintf("%d:%s", res.Errcode, res.Errmsg))
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
func getUserID(token, code string) (string, error) {
|
||||
addr := "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo"
|
||||
res := &struct {
|
||||
Errmsg string `json:"errmsg"`
|
||||
Errcode *uint `json:"errcode"`
|
||||
UserId string `json:"UserId"`
|
||||
DeviceId string `json:"device_id"`
|
||||
}{}
|
||||
query := map[string]string{
|
||||
"access_token": token,
|
||||
"code": code,
|
||||
}
|
||||
err := tools.Query(addr, query, res)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if res.Errcode != nil && *res.Errcode == 0 {
|
||||
return res.UserId, nil
|
||||
}
|
||||
return "", errors.New(fmt.Sprintf("%d:%s", res.Errcode, res.Errmsg))
|
||||
}
|
||||
|
||||
func getUserInfo(token, id string) (interface{}, error) {
|
||||
addr := "https://qyapi.weixin.qq.com/cgi-bin/user/get"
|
||||
res := map[string]interface{}{}
|
||||
query := map[string]string{
|
||||
"access_token": token,
|
||||
"userid": id,
|
||||
}
|
||||
err := tools.Query(addr, query, &res)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
errcode := int(res["errcode"].(float64))
|
||||
errmsg := res["errmsg"].(string)
|
||||
if errcode == 0 {
|
||||
log.Warn().Msgf("%v", res)
|
||||
return res, nil
|
||||
}
|
||||
return "", errors.New(fmt.Sprintf("%d:%s", errcode, errmsg))
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package wx
|
||||
|
||||
import (
|
||||
"github.com/veypi/OneBD"
|
||||
"github.com/veypi/OneBD/rfc"
|
||||
)
|
||||
|
||||
func Router(r OneBD.Router) {
|
||||
r.Set("/login/:id", login, rfc.MethodGet)
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package cfg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/veypi/utils/cmd"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var Path = cmd.GetCfgPath("oa", "settings")
|
||||
|
||||
var CFG = &struct {
|
||||
AdminUser string
|
||||
Host string
|
||||
LoggerPath string
|
||||
LoggerLevel string
|
||||
APPUUID string
|
||||
APPKey string
|
||||
TimeFormat string
|
||||
Debug bool
|
||||
MediaDir string
|
||||
DB struct {
|
||||
Type string
|
||||
Addr string
|
||||
User string
|
||||
Pass string
|
||||
DB string
|
||||
}
|
||||
}{
|
||||
APPUUID: "jU5Jo5hM",
|
||||
APPKey: "cB43wF94MLTksyBK",
|
||||
AdminUser: "admin",
|
||||
Host: "0.0.0.0:4001",
|
||||
LoggerPath: "",
|
||||
LoggerLevel: "debug",
|
||||
TimeFormat: "2006/01/02 15:04:05",
|
||||
Debug: true,
|
||||
MediaDir: "/Users/light/test/media/",
|
||||
DB: struct {
|
||||
Type string
|
||||
Addr string
|
||||
User string
|
||||
Pass string
|
||||
DB string
|
||||
}{
|
||||
//Type: "sqlite",
|
||||
Addr: "127.0.0.1:3306",
|
||||
//Addr: "oa.db",
|
||||
User: "root",
|
||||
Pass: "123456",
|
||||
DB: "one_auth",
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
db *gorm.DB
|
||||
)
|
||||
|
||||
func DB() *gorm.DB {
|
||||
if db == nil {
|
||||
ConnectDB()
|
||||
}
|
||||
return db
|
||||
}
|
||||
func ConnectDB() *gorm.DB {
|
||||
var err error
|
||||
conn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local", CFG.DB.User, CFG.DB.Pass, CFG.DB.Addr, CFG.DB.DB)
|
||||
if CFG.DB.Type == "sqlite" {
|
||||
conn = CFG.DB.Addr
|
||||
db, err = gorm.Open(sqlite.Open(conn), &gorm.Config{})
|
||||
} else {
|
||||
db, err = gorm.Open(mysql.Open(conn), &gorm.Config{})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return db
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
module OneAuth
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/json-iterator/go v1.1.10
|
||||
github.com/urfave/cli/v2 v2.2.0
|
||||
github.com/veypi/OneBD v0.4.1
|
||||
github.com/veypi/utils v0.3.1
|
||||
gorm.io/driver/mysql v1.0.5
|
||||
gorm.io/driver/sqlite v1.1.4
|
||||
gorm.io/gorm v1.21.3
|
||||
)
|
||||
|
||||
replace github.com/veypi/OneBD v0.4.1 => ../OceanCurrent/OneBD
|
||||
@@ -0,0 +1,77 @@
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E=
|
||||
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/kardianos/service v1.1.0 h1:QV2SiEeWK42P0aEmGcsAgjApw/lRxkwopvT+Gu6t1/0=
|
||||
github.com/kardianos/service v1.1.0/go.mod h1:RrJI2xn5vve/r32U5suTbeaSGoMU6GbNPoj36CVYcHc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ=
|
||||
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.17.2 h1:RMRHFw2+wF7LO0QqtELQwo8hqSmqISyCJeFeAAuWcRo=
|
||||
github.com/rs/zerolog v1.17.2/go.mod h1:9nvC1axdVrAHcu/s9taAVfBuIdTZLVQmKQyvrUjF5+I=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4=
|
||||
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||
github.com/veypi/utils v0.2.2/go.mod h1:rAkC6Fbk5cBa3u+8pyCpsVcnXw74EhEQJGmPND9FvRg=
|
||||
github.com/veypi/utils v0.3.0 h1:vCi0jqMsAMBPblFCmneUw3Wet5y1XHZLA5ZP9c/2owI=
|
||||
github.com/veypi/utils v0.3.0/go.mod h1:rAkC6Fbk5cBa3u+8pyCpsVcnXw74EhEQJGmPND9FvRg=
|
||||
github.com/veypi/utils v0.3.1 h1:QL4Q/+iXNFXNVENiUeEttSwNwkeqrorSpTBpCs7fXBI=
|
||||
github.com/veypi/utils v0.3.1/go.mod h1:rAkC6Fbk5cBa3u+8pyCpsVcnXw74EhEQJGmPND9FvRg=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gorm.io/driver/mysql v1.0.5 h1:WAAmvLK2rG0tCOqrf5XcLi2QUwugd4rcVJ/W3aoon9o=
|
||||
gorm.io/driver/mysql v1.0.5/go.mod h1:N1OIhHAIhx5SunkMGqWbGFVeh4yTNWKmMo1GOAsohLI=
|
||||
gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
|
||||
gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
|
||||
gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||
gorm.io/gorm v1.21.3 h1:qDFi55ZOsjZTwk5eN+uhAmHi8GysJ/qCTichM/yO7ME=
|
||||
gorm.io/gorm v1.21.3/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||
@@ -0,0 +1,63 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"OneAuth/libs/auth"
|
||||
"OneAuth/libs/oerr"
|
||||
"OneAuth/models"
|
||||
"errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func AddUser(tx *gorm.DB, appID uint, userID uint, roleID uint, status models.AUStatus) error {
|
||||
if appID == 0 || userID == 0 {
|
||||
return oerr.FuncArgsError
|
||||
}
|
||||
au := &models.AppUser{}
|
||||
au.AppID = appID
|
||||
au.UserID = userID
|
||||
err := tx.Where(au).First(au).Error
|
||||
if err == nil {
|
||||
return oerr.ResourceDuplicated
|
||||
}
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
au.Status = status
|
||||
err = tx.Create(au).Error
|
||||
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
|
||||
err := tx.Where(au).First(au).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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("status", models.AUDisable).Error
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"OneAuth/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 定义oa系统权限
|
||||
|
||||
type Resource = string
|
||||
|
||||
const (
|
||||
User Resource = "user"
|
||||
APP Resource = "app"
|
||||
Res Resource = "resource"
|
||||
Role Resource = "role"
|
||||
Auth Resource = "auth"
|
||||
)
|
||||
|
||||
func BindUserRole(tx *gorm.DB, userID uint, roleID uint) error {
|
||||
r := &models.Role{}
|
||||
r.ID = roleID
|
||||
err := tx.Where(r).First(r).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ur := &models.UserRole{}
|
||||
ur.RoleID = roleID
|
||||
if r.IsUnique {
|
||||
err = tx.Where(ur).Update("user_id", userID).Error
|
||||
} else {
|
||||
ur.UserID = userID
|
||||
err = tx.Where(ur).FirstOrCreate(ur).Error
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func BindUserAuth(tx *gorm.DB, userID uint, resID uint, level models.AuthLevel, ruid string) error {
|
||||
return bind(tx, userID, resID, level, ruid, false)
|
||||
}
|
||||
|
||||
func BindRoleAuth(tx *gorm.DB, roleID uint, resID uint, level models.AuthLevel, ruid string) error {
|
||||
return bind(tx, roleID, resID, level, ruid, true)
|
||||
}
|
||||
|
||||
func bind(tx *gorm.DB, id uint, resID uint, level models.AuthLevel, ruid string, isRole bool) error {
|
||||
r := &models.Resource{}
|
||||
r.ID = resID
|
||||
err := tx.Where(r).First(r).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
au := &models.Auth{
|
||||
AppID: r.AppID,
|
||||
ResourceID: resID,
|
||||
RID: r.Name,
|
||||
RUID: ruid,
|
||||
Level: level,
|
||||
}
|
||||
if isRole {
|
||||
au.RoleID = &id
|
||||
} else {
|
||||
au.UserID = &id
|
||||
}
|
||||
return tx.Where(au).FirstOrCreate(au).Error
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
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"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (h *ApiHandler) Init(m OneBD.Meta) error {
|
||||
return tools.MultiIniter(m, &h.BaseHandler, &h.UserHandler)
|
||||
}
|
||||
|
||||
func (h *ApiHandler) OnResponse(data interface{}) {
|
||||
JSONResponse(h.Meta(), data, nil)
|
||||
}
|
||||
|
||||
func (h *ApiHandler) OnError(err error) {
|
||||
log.WithNoCaller.Warn().Err(err).Msg(h.Meta().RequestPath())
|
||||
JSONResponse(h.Meta(), nil, err)
|
||||
}
|
||||
|
||||
var ioNumLimit = make(map[string]time.Time)
|
||||
var limitLocker = sync.RWMutex{}
|
||||
|
||||
func (h *ApiHandler) SetAccessDelta(d time.Duration) error {
|
||||
// 尽量对写操作加频率限制
|
||||
now := time.Now()
|
||||
limitLocker.Lock()
|
||||
label := h.Meta().RemoteAddr() + h.Meta().RequestPath()
|
||||
last, ok := ioNumLimit[label]
|
||||
defer func() {
|
||||
ioNumLimit[label] = now
|
||||
limitLocker.Unlock()
|
||||
}()
|
||||
if !ok {
|
||||
return nil
|
||||
} else if now.Sub(last) >= d {
|
||||
return nil
|
||||
}
|
||||
return oerr.AccessTooFast
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"OneAuth/cfg"
|
||||
"OneAuth/libs/oerr"
|
||||
"OneAuth/libs/token"
|
||||
"OneAuth/models"
|
||||
"github.com/veypi/OneBD"
|
||||
"github.com/veypi/OneBD/rfc"
|
||||
)
|
||||
|
||||
type UserHandler struct {
|
||||
Payload *token.PayLoad
|
||||
ignoreMethod map[rfc.Method]bool
|
||||
}
|
||||
|
||||
func (a *UserHandler) Init(m OneBD.Meta) error {
|
||||
if a.ignoreMethod != nil && a.ignoreMethod[m.Method()] {
|
||||
return nil
|
||||
}
|
||||
return a.ParsePayload(m)
|
||||
}
|
||||
|
||||
func (a *UserHandler) ParsePayload(m OneBD.Meta) error {
|
||||
a.Payload = new(token.PayLoad)
|
||||
tokenStr := m.GetHeader("auth_token")
|
||||
if tokenStr == "" {
|
||||
return oerr.NotLogin
|
||||
}
|
||||
ok, err := token.ParseToken(tokenStr, a.Payload, cfg.CFG.APPKey)
|
||||
if ok {
|
||||
return nil
|
||||
}
|
||||
return oerr.NotLogin.Attach(err)
|
||||
}
|
||||
|
||||
func (a *UserHandler) Ignore(methods ...rfc.Method) {
|
||||
if a.ignoreMethod == nil {
|
||||
a.ignoreMethod = make(map[rfc.Method]bool)
|
||||
}
|
||||
for _, m := range methods {
|
||||
a.ignoreMethod[m] = true
|
||||
}
|
||||
}
|
||||
|
||||
func (a *UserHandler) GetAuth(ResourceID string, ResourceUUID ...string) models.AuthLevel {
|
||||
return a.Payload.GetAuth(ResourceID, ResourceUUID...)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package key
|
||||
|
||||
import "OneAuth/cfg"
|
||||
|
||||
func App(id uint) string {
|
||||
if id == cfg.CFG.APPID {
|
||||
return cfg.CFG.APPKey
|
||||
}
|
||||
// TODO
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package key
|
||||
|
||||
import (
|
||||
"OneAuth/cfg"
|
||||
"github.com/veypi/utils"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var keyCache = sync.Map{}
|
||||
|
||||
func User(uid uint, appID uint) string {
|
||||
if appID == cfg.CFG.APPID {
|
||||
key, _ := keyCache.LoadOrStore(uid, utils.RandSeq(16))
|
||||
return cfg.CFG.APPKey + key.(string)
|
||||
}
|
||||
// TODO: 获取其他应用user_key
|
||||
return ""
|
||||
}
|
||||
|
||||
func RefreshUser(uid uint, appID uint) string {
|
||||
if appID == cfg.CFG.APPID {
|
||||
key := utils.RandSeq(16)
|
||||
keyCache.Store(uid, key)
|
||||
return key
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package oerr
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// 错误描述
|
||||
|
||||
type Code uint
|
||||
|
||||
/*
|
||||
|
||||
5位10进制码表示错误, 00000 etc.
|
||||
|
||||
0 代表未知,或不必定义的有通用意义的错误
|
||||
|
||||
## 第1位 错误类型
|
||||
- 1 : 系统级错误 比如 内存申请失败, 系统调用失败,文件打开失败等等
|
||||
- 2 : 数据库错误
|
||||
- 3 : 保留
|
||||
- 4 : 权限错误
|
||||
- 5 : 配置错误
|
||||
- 6 : 参数错误
|
||||
- 7 : 时序(控制)错误
|
||||
|
||||
## 第2位 2级错误类型
|
||||
|
||||
## 第3,4位 具体错误编号
|
||||
|
||||
## 第5位 错误严重程度
|
||||
- 0 : 无任何影响错误,简单重试可以解决
|
||||
- 1 : 无影响错误,重试不可解决
|
||||
- 2 : 有影响用户体验或系统性能错误, 重试可解决
|
||||
- 3 : 有影响用户体验或系统性能错误, 重试不可解决
|
||||
- 4 : 有影响组件功能的错误, 重试可解决
|
||||
- 5 : 有影响组件功能的错误, 重试不可解决
|
||||
- 6 : 有影响服务运行的错误, 重启可解决
|
||||
- 7 : 有影响服务运行的错误,重启不可解决
|
||||
- 8 : 有影响系统运行的错误
|
||||
- 9 : 本不可能发生的错误,例如被人攻击导致数据异常产生的逻辑错误
|
||||
|
||||
*/
|
||||
|
||||
// Unknown error
|
||||
const (
|
||||
Unknown Code = 0
|
||||
)
|
||||
const (
|
||||
// DBErr 2 数据库错误
|
||||
// -1 系统错误
|
||||
// -2 数据读写错误
|
||||
DBErr Code = 20001
|
||||
ResourceCreatedFailed Code = 22012
|
||||
ResourceDuplicated Code = 22021
|
||||
ResourceNotExist Code = 22031
|
||||
)
|
||||
|
||||
const (
|
||||
// LogicErr 3 系统内逻辑错误
|
||||
LogicErr Code = 30000
|
||||
AppNotJoin Code = 30001
|
||||
)
|
||||
|
||||
const (
|
||||
// NotLogin
|
||||
// 4 权限类型错误
|
||||
// 1: 登录权限
|
||||
// 2: 资源操作权限
|
||||
NotLogin Code = 41001
|
||||
LoginExpired Code = 41011
|
||||
PassError Code = 41021
|
||||
DisableLogin Code = 41031
|
||||
AccountNotExist Code = 41041
|
||||
NoAuth Code = 42011
|
||||
)
|
||||
|
||||
// 6 参数类型错误
|
||||
/*
|
||||
-1: 协议参数
|
||||
-2: 接口参数
|
||||
-3: 函数参数
|
||||
-4: 数据依赖错误
|
||||
*/
|
||||
const (
|
||||
MethodNotSupport Code = 61111
|
||||
MethodNotAllowed Code = 61121
|
||||
|
||||
ApiArgsError Code = 62001
|
||||
ApiArgsMissing Code = 62011
|
||||
TableArgsMissing Code = 62021
|
||||
TableArgsErr Code = 62031
|
||||
|
||||
FuncArgsError Code = 63001
|
||||
UrlPatternNotSupport Code = 63117
|
||||
UrlDefinedDuplicate Code = 63127
|
||||
UrlParamDuplicate Code = 63137
|
||||
|
||||
DataError Code = 64009
|
||||
)
|
||||
|
||||
// 7 : 时序(控制)错误
|
||||
/*
|
||||
-1: 访问控制
|
||||
*/
|
||||
|
||||
const (
|
||||
AccessErr Code = 71001
|
||||
AccessTooFast Code = 71010
|
||||
)
|
||||
|
||||
var codeMap = map[Code]string{
|
||||
Unknown: "unknown error",
|
||||
DBErr: "db error",
|
||||
ResourceCreatedFailed: "resource created failed",
|
||||
ResourceDuplicated: "resource duplicated",
|
||||
ResourceNotExist: "Resource not exist",
|
||||
MethodNotSupport: "this http method is not supported",
|
||||
MethodNotAllowed: "this http method is not allowed",
|
||||
ApiArgsError: "base args error",
|
||||
ApiArgsMissing: "missing args",
|
||||
TableArgsMissing: "missing data",
|
||||
TableArgsErr: "invalid table data",
|
||||
FuncArgsError: "func args error",
|
||||
UrlPatternNotSupport: "this router's url pattern is not supported.",
|
||||
UrlDefinedDuplicate: "this router's url has been defined",
|
||||
UrlParamDuplicate: "this param defined in router's url duplicated",
|
||||
DataError: "data error",
|
||||
NotLogin: "not login",
|
||||
LoginExpired: "login expired",
|
||||
DisableLogin: "disabled to login",
|
||||
PassError: "password/account error",
|
||||
AccountNotExist: "account not exist",
|
||||
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 {
|
||||
return strconv.Itoa(int(c)) + ":" + c.String()
|
||||
}
|
||||
|
||||
func (c Code) String() string {
|
||||
s, ok := codeMap[c]
|
||||
if ok && len(s) > 0 {
|
||||
return s
|
||||
}
|
||||
return codeMap[Unknown]
|
||||
}
|
||||
|
||||
// Attach 附加错误详细原因
|
||||
func (c Code) Attach(errs ...error) (e error) {
|
||||
e = c
|
||||
for _, err := range errs {
|
||||
if err != nil {
|
||||
e = &wrapErr{msg: e.Error() + "\n" + err.Error(), err: e}
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (c Code) AttachStr(errs ...string) (e error) {
|
||||
e = c
|
||||
for _, m := range errs {
|
||||
if m != "" {
|
||||
e = &wrapErr{
|
||||
msg: e.Error() + "\n" + m,
|
||||
err: e,
|
||||
}
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func OfType(errMsg string) Code {
|
||||
s := ""
|
||||
if gorm.ErrRecordNotFound.Error() == errMsg {
|
||||
return ResourceNotExist
|
||||
}
|
||||
for _, v := range errMsg {
|
||||
if v == ':' {
|
||||
break
|
||||
}
|
||||
s += string(v)
|
||||
}
|
||||
c, _ := strconv.Atoi(s)
|
||||
return Code(c)
|
||||
}
|
||||
|
||||
type wrapErr struct {
|
||||
msg string
|
||||
err error
|
||||
}
|
||||
|
||||
func (w *wrapErr) Error() string {
|
||||
return w.msg
|
||||
}
|
||||
|
||||
func (w *wrapErr) UnWrap() error {
|
||||
return w.err
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"OneAuth/models"
|
||||
"github.com/veypi/utils/jwt"
|
||||
)
|
||||
|
||||
type simpleAuth struct {
|
||||
RID string `json:"rid"`
|
||||
// 具体某个资源的id
|
||||
RUID string `json:"ruid"`
|
||||
Level models.AuthLevel `json:"level"`
|
||||
}
|
||||
|
||||
// TODO:: roles 是否会造成token过大 ?
|
||||
type PayLoad struct {
|
||||
jwt.Payload
|
||||
ID uint `json:"id"`
|
||||
Auth map[uint]*simpleAuth `json:"auth"`
|
||||
}
|
||||
|
||||
// GetAuth resource_uuid 缺省或仅第一个有效 权限会被更高权限覆盖
|
||||
func (p *PayLoad) GetAuth(ResourceID string, ResourceUUID ...string) models.AuthLevel {
|
||||
res := models.AuthNone
|
||||
if p == nil || p.Auth == nil {
|
||||
return res
|
||||
}
|
||||
ruid := ""
|
||||
if len(ResourceUUID) > 0 {
|
||||
ruid = ResourceUUID[0]
|
||||
}
|
||||
for _, a := range p.Auth {
|
||||
if a.RID == ResourceID {
|
||||
if a.RUID != "" {
|
||||
if a.RUID == ruid {
|
||||
if a.Level > res {
|
||||
res = a.Level
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} else if a.Level > res {
|
||||
res = a.Level
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func GetToken(u *models.User, appID uint, key string) (string, error) {
|
||||
payload := &PayLoad{
|
||||
ID: u.ID,
|
||||
Auth: map[uint]*simpleAuth{},
|
||||
}
|
||||
for _, a := range u.GetAuths() {
|
||||
if appID == a.AppID {
|
||||
payload.Auth[a.ID] = &simpleAuth{
|
||||
RID: a.RID,
|
||||
RUID: a.RUID,
|
||||
Level: a.Level,
|
||||
}
|
||||
}
|
||||
}
|
||||
return jwt.GetToken(payload, []byte(key))
|
||||
}
|
||||
|
||||
func ParseToken(token string, payload *PayLoad, key string) (bool, error) {
|
||||
return jwt.ParseToken(token, payload, []byte(key))
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/veypi/OneBD"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type Initer interface {
|
||||
Init(OneBD.Meta) error
|
||||
}
|
||||
|
||||
func MultiIniter(m OneBD.Meta, is ...Initer) (err error) {
|
||||
for _, i := range is {
|
||||
err = i.Init(m)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Query(addr string, query map[string]string, res interface{}) error {
|
||||
u, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
paras := &url.Values{}
|
||||
//设置请求参数
|
||||
for k, v := range query {
|
||||
paras.Set(k, v)
|
||||
}
|
||||
u.RawQuery = paras.Encode()
|
||||
resp, err := http.Get(u.String())
|
||||
//关闭资源
|
||||
if resp != nil && resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.NewDecoder(resp.Body).Decode(res)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"OneAuth/cfg"
|
||||
"OneAuth/sub"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/veypi/utils/cmd"
|
||||
"github.com/veypi/utils/log"
|
||||
"os"
|
||||
)
|
||||
|
||||
const Version = "v0.1.0"
|
||||
|
||||
func main() {
|
||||
cmd.LoadCfg(cfg.Path, cfg.CFG)
|
||||
app := cli.NewApp()
|
||||
app.Name = "OneAuth"
|
||||
app.Usage = "one auth"
|
||||
app.Version = Version
|
||||
app.Flags = []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Aliases: []string{"d"},
|
||||
Value: cfg.CFG.Debug,
|
||||
Destination: &cfg.CFG.Debug,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "log_level,log",
|
||||
Value: cfg.CFG.LoggerLevel,
|
||||
Destination: &cfg.CFG.LoggerLevel,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "log_path",
|
||||
Value: cfg.CFG.LoggerPath,
|
||||
Destination: &cfg.CFG.LoggerPath,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "host",
|
||||
Value: cfg.CFG.Host,
|
||||
Destination: &cfg.CFG.Host,
|
||||
},
|
||||
}
|
||||
app.Commands = []*cli.Command{
|
||||
sub.Web,
|
||||
sub.App,
|
||||
sub.Role,
|
||||
sub.Resource,
|
||||
sub.Init,
|
||||
}
|
||||
srv, err := cmd.NewSrv(app, sub.RunWeb, cfg.CFG, cfg.Path)
|
||||
if err != nil {
|
||||
log.Warn().Msg(err.Error())
|
||||
return
|
||||
}
|
||||
srv.SetExecMax(1)
|
||||
srv.SetStopFunc(func() {
|
||||
})
|
||||
app.Before = func(c *cli.Context) error {
|
||||
if cfg.CFG.Debug {
|
||||
cfg.CFG.LoggerLevel = "debug"
|
||||
}
|
||||
cfg.ConnectDB()
|
||||
return nil
|
||||
}
|
||||
_ = app.Run(os.Args)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package models
|
||||
|
||||
var AppKeys = map[string]string{}
|
||||
|
||||
type App struct {
|
||||
BaseModel
|
||||
Name string `json:"name"`
|
||||
Icon string `json:"icon"`
|
||||
UUID string `json:"uuid" gorm:"unique"`
|
||||
Des string `json:"des"`
|
||||
Creator uint `json:"creator"`
|
||||
UserCount uint `json:"user_count"`
|
||||
Users []*User `json:"users" gorm:"many2many:app_users;"`
|
||||
// 初始用户角色
|
||||
InitRoleID uint `json:"init_role_id"`
|
||||
InitRole *Role `json:"init_role"`
|
||||
// 是否在首页隐藏
|
||||
Hide bool `json:"hide"`
|
||||
// PubKey string `json:"pub_key"`
|
||||
// PrivateKey string `json:"private_key"`
|
||||
// 认证成功跳转链接
|
||||
Host string `json:"host"`
|
||||
// 加解密用户token (key+key2)
|
||||
// 两个key都是请求获取时刷新
|
||||
// key oa发放给app 双方保存 针对app生成 每个应用有一个
|
||||
// key2 app发放给oa app保存 oa使用一次销毁 针对当个用户生成 每个用户有一个
|
||||
// 获取app用户加密秘钥key2
|
||||
// TODO
|
||||
UserRefreshUrl string `json:"user_refresh_url"`
|
||||
// app 校验用户token时使用
|
||||
Key string `json:"-"`
|
||||
// 是否允许用户自动加入应用
|
||||
EnableRegister bool `json:"enable_register"`
|
||||
//
|
||||
EnableUserKey bool `json:"enable_user_key"`
|
||||
UserKeyUrl string `json:"user_key_url"`
|
||||
// 允许登录方式
|
||||
EnableUser bool `json:"enable_user"`
|
||||
EnableWx bool `json:"enable_wx"`
|
||||
EnablePhone bool `json:"enable_phone"`
|
||||
EnableEmail bool `json:"enable_email"`
|
||||
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"`
|
||||
APP *App `json:"app"`
|
||||
UserID uint `json:"user_id"`
|
||||
User *User `json:"user"`
|
||||
Status AUStatus `json:"status"`
|
||||
}
|
||||
|
||||
type Wechat struct {
|
||||
BaseModel
|
||||
AppID uint `json:"app_id"`
|
||||
// 网页授权登录用
|
||||
WxID string `json:"wx_id"`
|
||||
AgentID string `json:"agent_id"`
|
||||
Url string `json:"url"`
|
||||
|
||||
// 获取access_token用
|
||||
CorpID string `json:"corp_id"`
|
||||
CorpSecret string `json:"corp_secret"`
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"OneAuth/cfg"
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type JSON []byte
|
||||
|
||||
func (j JSON) Value() (driver.Value, error) {
|
||||
if j.IsNull() {
|
||||
return nil, nil
|
||||
}
|
||||
return string(j), nil
|
||||
}
|
||||
func (j *JSON) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
*j = nil
|
||||
return nil
|
||||
}
|
||||
s, ok := value.([]byte)
|
||||
if !ok {
|
||||
return errors.New("invalid scan source")
|
||||
}
|
||||
*j = append((*j)[0:0], s...)
|
||||
return nil
|
||||
}
|
||||
func (j JSON) MarshalJSON() ([]byte, error) {
|
||||
if j == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
return j, nil
|
||||
}
|
||||
func (j *JSON) UnmarshalJSON(data []byte) error {
|
||||
if j == nil {
|
||||
return errors.New("null point exception")
|
||||
}
|
||||
*j = append((*j)[0:0], data...)
|
||||
return nil
|
||||
}
|
||||
func (j JSON) IsNull() bool {
|
||||
return len(j) == 0 || string(j) == "null"
|
||||
}
|
||||
func (j JSON) Equals(j1 JSON) bool {
|
||||
return bytes.Equal(j, j1)
|
||||
}
|
||||
|
||||
// JSONTime custom json time
|
||||
type JSONTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func Now() *JSONTime {
|
||||
return &JSONTime{time.Now()}
|
||||
}
|
||||
|
||||
// MarshalJSON 实现它的json序列化方法
|
||||
func (jt JSONTime) MarshalJSON() ([]byte, error) {
|
||||
var stamp = fmt.Sprintf("\"%s\"", jt.Format(cfg.CFG.TimeFormat))
|
||||
return []byte(stamp), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON 反序列化方法
|
||||
func (jt *JSONTime) UnmarshalJSON(data []byte) (err error) {
|
||||
now, err := time.ParseInLocation(`"`+cfg.CFG.TimeFormat+`"`, string(data), time.Local)
|
||||
*jt = JSONTime{now}
|
||||
return
|
||||
}
|
||||
|
||||
// Value insert timestamp into mysql need this function.
|
||||
func (jt JSONTime) Value() (driver.Value, error) {
|
||||
var zeroTime time.Time
|
||||
if jt.Time.UnixNano() == zeroTime.UnixNano() {
|
||||
return nil, nil
|
||||
}
|
||||
return jt.Time, nil
|
||||
}
|
||||
|
||||
// Scan value of time.Time
|
||||
func (jt *JSONTime) Scan(v interface{}) error {
|
||||
value, ok := v.(time.Time)
|
||||
if ok {
|
||||
*jt = JSONTime{Time: value}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("can not convert %v to timestamp", v)
|
||||
}
|
||||
|
||||
func (jt *JSONTime) SetTime(t time.Time) {
|
||||
jt.Time = t
|
||||
}
|
||||
|
||||
type BaseModel struct {
|
||||
ID uint `json:"id" gorm:"primary_key"`
|
||||
CreatedAt JSONTime `json:"created_at"`
|
||||
UpdatedAt JSONTime `json:"updated_at"`
|
||||
DeletedAt *JSONTime `json:"deleted_at" sql:"index"`
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package models
|
||||
|
||||
type Message struct {
|
||||
BaseModel
|
||||
UserID uint `json:"user_id"`
|
||||
User *User `json:"user"`
|
||||
Title string `json:"title"`
|
||||
Redirect string `json:"redirect"`
|
||||
Content string `json:"content"`
|
||||
From string `json:"from"`
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package models
|
||||
|
||||
type UserRole struct {
|
||||
BaseModel
|
||||
UserID uint `json:"user_id"`
|
||||
RoleID uint `json:"role_id"`
|
||||
}
|
||||
|
||||
type Role struct {
|
||||
BaseModel
|
||||
AppID uint `json:"app_id"`
|
||||
App *App `json:"app"`
|
||||
Name string `json:"name"`
|
||||
// 角色标签
|
||||
Tag string `json:"tag" gorm:"default:''"`
|
||||
Users []*User `json:"users" gorm:"many2many:user_roles;"`
|
||||
// 具体权限
|
||||
Auths []*Auth `json:"auths" gorm:"foreignkey:RoleID;references:ID"`
|
||||
IsUnique bool `json:"is_unique" gorm:"default:false"`
|
||||
}
|
||||
|
||||
// AuthLevel 权限等级
|
||||
// 对于操作类权限
|
||||
// 0 禁止执行
|
||||
// 1 允许执行
|
||||
// 对于资源类权限
|
||||
// 0 相当于没有
|
||||
// 1 有限读权限
|
||||
// 2 读权限
|
||||
// 3 创建权限
|
||||
// 4 修改权限
|
||||
// 5 删除权限
|
||||
// 6 赋予其余人权限
|
||||
type AuthLevel uint
|
||||
|
||||
const (
|
||||
AuthNone AuthLevel = 0
|
||||
AuthDo AuthLevel = 1
|
||||
// AuthPart TODO: 临时权限
|
||||
AuthPart AuthLevel = 1
|
||||
AuthRead AuthLevel = 2
|
||||
AuthCreate AuthLevel = 3
|
||||
AuthUpdate AuthLevel = 4
|
||||
AuthDelete AuthLevel = 5
|
||||
AuthAll AuthLevel = 6
|
||||
)
|
||||
|
||||
func (a AuthLevel) Upper(b AuthLevel) bool {
|
||||
return a > b
|
||||
}
|
||||
|
||||
func (a AuthLevel) CanDo() bool {
|
||||
return a > AuthNone
|
||||
}
|
||||
|
||||
func (a AuthLevel) CanRead() bool {
|
||||
return a >= AuthRead
|
||||
}
|
||||
|
||||
func (a AuthLevel) CanCreate() bool {
|
||||
return a >= AuthCreate
|
||||
}
|
||||
|
||||
func (a AuthLevel) CanUpdate() bool {
|
||||
return a >= AuthUpdate
|
||||
}
|
||||
|
||||
func (a AuthLevel) CanDelete() bool {
|
||||
return a >= AuthDelete
|
||||
}
|
||||
|
||||
func (a AuthLevel) CanDoAny() bool {
|
||||
return a >= AuthAll
|
||||
}
|
||||
|
||||
// Auth 资源权限
|
||||
type Auth struct {
|
||||
BaseModel
|
||||
// 该权限作用的应用
|
||||
AppID uint `json:"app_id"`
|
||||
App *App `json:"app"`
|
||||
// 权限绑定只能绑定一个
|
||||
RoleID *uint `json:"role_id" gorm:""`
|
||||
Role *Role `json:"role"`
|
||||
UserID *uint `json:"user_id"`
|
||||
User *User `json:"user"`
|
||||
// 资源id
|
||||
ResourceID uint `json:"resource_id" gorm:"not null"`
|
||||
Resource *Resource `json:"resource"`
|
||||
// resource_name 用于其他系统方便区分权限的名字
|
||||
RID string `json:"rid" gorm:""`
|
||||
// 具体某个资源的id
|
||||
RUID string `json:"ruid"`
|
||||
Level AuthLevel `json:"level"`
|
||||
}
|
||||
|
||||
type Resource struct {
|
||||
BaseModel
|
||||
AppID uint `json:"app_id"`
|
||||
App *App `json:"app"`
|
||||
Name string `json:"name"`
|
||||
// 权限标签
|
||||
Tag string `json:"tag"`
|
||||
Des string `json:"des"`
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/veypi/utils"
|
||||
)
|
||||
|
||||
// User db user model
|
||||
type User struct {
|
||||
BaseModel
|
||||
Username string `json:"username" gorm:"type:varchar(100);unique;not null"`
|
||||
Nickname string `json:"nickname" gorm:"type:varchar(100)" json:",omitempty"`
|
||||
Phone string `json:"phone" gorm:"type:varchar(20);unique;default:null" json:",omitempty"`
|
||||
Email string `json:"email" gorm:"type:varchar(50);unique;default:null" json:",omitempty"`
|
||||
CheckCode string `gorm:"type:varchar(64);not null" json:"-"`
|
||||
RealCode string `gorm:"type:varchar(32);not null" json:"-"`
|
||||
Position string `json:"position"`
|
||||
// disabled 禁用
|
||||
Status string `json:"status"`
|
||||
|
||||
Icon string `json:"icon"`
|
||||
Roles []*Role `json:"roles" gorm:"many2many:user_roles;"`
|
||||
Apps []*App `json:"apps" gorm:"many2many:app_users;"`
|
||||
Auths []*Auth `json:"auths" gorm:"foreignkey:UserID;references:ID"`
|
||||
}
|
||||
|
||||
func (u *User) String() string {
|
||||
return u.Username + ":" + u.Nickname
|
||||
}
|
||||
|
||||
func (u *User) GetAuths() []*Auth {
|
||||
list := make([]*Auth, 0, 10)
|
||||
for _, r := range u.Roles {
|
||||
for _, a := range r.Auths {
|
||||
list = append(list, a)
|
||||
}
|
||||
}
|
||||
for _, a := range u.Auths {
|
||||
list = append(list, a)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func (u *User) GetAuth(appID uint, ResourceID string, ResourceUUID ...string) AuthLevel {
|
||||
var res = AuthNone
|
||||
ruid := ""
|
||||
if len(ResourceUUID) > 0 {
|
||||
ruid = ResourceUUID[0]
|
||||
}
|
||||
for _, a := range u.GetAuths() {
|
||||
if a.RID == ResourceID && a.AppID == appID {
|
||||
if a.RUID != "" {
|
||||
if a.RUID == ruid {
|
||||
if a.Level.Upper(res) {
|
||||
res = a.Level
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} else if a.Level.Upper(res) {
|
||||
res = a.Level
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (u *User) UpdatePass(ps string) (err error) {
|
||||
u.RealCode = utils.RandSeq(32)
|
||||
u.CheckCode, err = utils.AesEncrypt(u.RealCode, []byte(ps))
|
||||
return err
|
||||
}
|
||||
|
||||
func (u *User) CheckLogin(ps string) (bool, error) {
|
||||
temp, err := utils.AesDecrypt(u.CheckCode, []byte(ps))
|
||||
return temp == u.RealCode, err
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["johnsoncodehk.volar"]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# Vue 3 + Typescript + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 and Typescript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)
|
||||
|
||||
## Type Support For `.vue` Imports in TS
|
||||
|
||||
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's `.vue` type support plugin by running `Volar: Switch TS Plugin on/off` from VSCode command palette.
|
||||
@@ -0,0 +1,13 @@
|
||||
<!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>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+2793
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "oaf",
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
"serve": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@veypi/one-icon": "2.0.5",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^0.24.0",
|
||||
"js-base64": "^3.7.2",
|
||||
"vue": "^3.2.16",
|
||||
"vue-router": "^4.0.12",
|
||||
"vuex": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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",
|
||||
"typescript": "^4.4.3",
|
||||
"vfonts": "^0.1.0",
|
||||
"vite": "^2.6.4",
|
||||
"vue-tsc": "^0.3.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
+137
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<n-config-provider :theme-overrides="Theme.overrides" :locale="zhCN" :date-locale="dateZhCN"
|
||||
:theme="Theme">
|
||||
<n-layout class="font-sans select-none">
|
||||
<n-layout>
|
||||
<n-layout-header class="pr-5" bordered style="height: 64px;line-height: 64px;">
|
||||
<div class="inline-block float-left h-full">
|
||||
<one-icon color="#000" class="inline-block" @click="$router.push('/')" style="font-size: 48px;margin:8px;color:aqua">
|
||||
glassdoor
|
||||
</one-icon>
|
||||
</div>
|
||||
<div class="inline-block float-left h-full" style="margin-left: 10px">
|
||||
<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-footer bordered style="height: 24px;line-height: 24px"
|
||||
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" @click="goto('https://veypi.com')">
|
||||
©2021 veypi
|
||||
</span>
|
||||
</n-layout-footer>
|
||||
</n-layout>
|
||||
</n-config-provider>
|
||||
</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;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 周围滑动留白 */
|
||||
html {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
display: inline-block;
|
||||
font-size: 24px;
|
||||
margin: 20px 10px 20px 10px;
|
||||
}
|
||||
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: #2c3e50;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.main {
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none; /* Chrome Safari */
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,66 @@
|
||||
// @ts-ignore
|
||||
import axios from 'axios'
|
||||
import {store} from '../store'
|
||||
|
||||
|
||||
function baseRequests(url: string, method: any = 'GET', query: any, data: any, success: any, fail?: Function) {
|
||||
return axios({
|
||||
url: url,
|
||||
params: query,
|
||||
data: data,
|
||||
method: method,
|
||||
headers: {
|
||||
auth_token: localStorage.auth_token
|
||||
}
|
||||
}).then((res: any) => {
|
||||
if ('auth_token' in res.headers) {
|
||||
localStorage.auth_token = res.headers.auth_token
|
||||
}
|
||||
if (method === 'HEAD') {
|
||||
success(res.headers)
|
||||
} else {
|
||||
success(res.data)
|
||||
}
|
||||
})
|
||||
.catch((e: any) => {
|
||||
if (e.response && e.response.status === 401) {
|
||||
console.log(e)
|
||||
store.commit('user/logout')
|
||||
return
|
||||
}
|
||||
console.log(e)
|
||||
if (e.response && e.response.status === 500) {
|
||||
return
|
||||
}
|
||||
if (typeof fail === 'function') {
|
||||
fail(e.response)
|
||||
} else if (e.response && e.response.status === 400) {
|
||||
console.log(400)
|
||||
} else {
|
||||
console.log(e.request)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const ajax = {
|
||||
get(url: '', data = {}, success = {}, fail?: Function) {
|
||||
return baseRequests(url, 'GET', data, {}, success, fail)
|
||||
},
|
||||
head(url: '', data = {}, success = {}, fail?: Function) {
|
||||
return baseRequests(url, 'HEAD', data, {}, success, fail)
|
||||
},
|
||||
delete(url: '', data = {}, success = {}, fail?: Function) {
|
||||
return baseRequests(url, 'DELETE', data, {}, success, fail)
|
||||
},
|
||||
post(url: '', data = {}, success = {}, fail?: Function) {
|
||||
return baseRequests(url, 'POST', {}, data, success, fail)
|
||||
},
|
||||
put(url: '', data = {}, success = {}, fail?: Function) {
|
||||
return baseRequests(url, 'PUT', {}, data, success, fail)
|
||||
},
|
||||
patch(url: '', data = {}, success = {}, fail?: Function) {
|
||||
return baseRequests(url, 'PATCH', {}, data, success, fail)
|
||||
}
|
||||
}
|
||||
|
||||
export default ajax
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (C) 2019 light <light@light-laptop>
|
||||
*
|
||||
* Distributed under terms of the MIT license.
|
||||
*/
|
||||
|
||||
import {App} from 'vue'
|
||||
import ajax from './ajax'
|
||||
import {store} from '../store'
|
||||
import {Base64} from 'js-base64'
|
||||
|
||||
|
||||
export type SuccessFunction<T> = (e: any) => void;
|
||||
export type FailedFunction<T> = (e: any) => void;
|
||||
|
||||
const Code = {
|
||||
42011: '无操作权限',
|
||||
22031: '资源不存在 或 您无权操作该资源'
|
||||
}
|
||||
|
||||
class Interface {
|
||||
private readonly method: Function
|
||||
private readonly api: string
|
||||
private readonly data: any
|
||||
|
||||
constructor(method: Function, api: string, data?: any) {
|
||||
this.method = method
|
||||
this.api = api
|
||||
this.data = data
|
||||
}
|
||||
|
||||
Start(success: SuccessFunction<any>, fail?: FailedFunction<any>) {
|
||||
const newFail = function (data: any) {
|
||||
if (data && data.code === 40001) {
|
||||
// no login
|
||||
store.commit('user/logout')
|
||||
return
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
|
||||
// @ts-ignore
|
||||
if (data && data.code && Code[data.code]) {
|
||||
}
|
||||
if (fail) {
|
||||
fail(data.err)
|
||||
}
|
||||
}
|
||||
|
||||
const newSuccess = function (data: any) {
|
||||
if (Number(data.status) === 1) {
|
||||
if (success) {
|
||||
success(data.content)
|
||||
}
|
||||
} else {
|
||||
newFail(data)
|
||||
if (data.code === 41001) {
|
||||
store.commit('user/logout')
|
||||
// bus.$emit('log_out')
|
||||
}
|
||||
}
|
||||
}
|
||||
this.method(this.api, this.data, newSuccess, newFail)
|
||||
}
|
||||
}
|
||||
|
||||
const app = {
|
||||
local: '/api/app/',
|
||||
self() {
|
||||
return new Interface(ajax.get, this.local, {is_self: true})
|
||||
},
|
||||
get(id: string) {
|
||||
return new Interface(ajax.get, this.local + id)
|
||||
},
|
||||
list() {
|
||||
return new Interface(ajax.get, this.local)
|
||||
}
|
||||
}
|
||||
|
||||
const user = {
|
||||
local: '/api/user/',
|
||||
register(username: string, password: string, uuid: string, prop?: any) {
|
||||
const data = Object.assign({
|
||||
username: username,
|
||||
uuid: uuid,
|
||||
password: Base64.encode(password)
|
||||
}, prop)
|
||||
return new Interface(ajax.post, this.local, data)
|
||||
},
|
||||
login(username: string, password: string, uuid: string) {
|
||||
return new Interface(ajax.head, this.local + username, {
|
||||
uid_type: 'username',
|
||||
uuid: uuid,
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const api = {
|
||||
user: user,
|
||||
app: app
|
||||
}
|
||||
|
||||
const Api = {
|
||||
install(vue: App): void {
|
||||
vue.config.globalProperties.$api = api
|
||||
}
|
||||
}
|
||||
export {Api}
|
||||
export default api
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1,52 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps<{ msg: string }>()
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<p>
|
||||
Recommended IDE setup:
|
||||
<a href="https://code.visualstudio.com/" target="_blank">VSCode</a>
|
||||
+
|
||||
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
|
||||
</p>
|
||||
|
||||
<p>See <code>README.md</code> for more information.</p>
|
||||
|
||||
<p>
|
||||
<a href="https://vitejs.dev/guide/features.html" target="_blank">
|
||||
Vite Docs
|
||||
</a>
|
||||
|
|
||||
<a href="https://v3.vuejs.org/" target="_blank">Vue 3 Docs</a>
|
||||
</p>
|
||||
|
||||
<button type="button" @click="count++">count is: {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test hot module replacement.
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
|
||||
label {
|
||||
margin: 0 0.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #eee;
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
color: #304455;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div id="wx_reg"></div>
|
||||
</template>
|
||||
<script setup lang='ts'>
|
||||
import {onMounted} from 'vue'
|
||||
|
||||
function goto(id: string, app: string, url: string, state?: number, href?: string) {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
window.WwLogin({
|
||||
id: 'wx_reg',
|
||||
appid: id,
|
||||
agentid: app,
|
||||
redirect_uri: encodeURIComponent(url),
|
||||
state: state,
|
||||
href: href
|
||||
})
|
||||
}
|
||||
|
||||
let aid = ''
|
||||
|
||||
let app = ''
|
||||
|
||||
let url = ''
|
||||
|
||||
onMounted(() => {
|
||||
goto(aid, app, url, new Date().getTime())
|
||||
})
|
||||
|
||||
</script>
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -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>昵称:    {{ $store.state.user.nickname }}</span>
|
||||
<span>账户:    {{ $store.state.user.username }}</span>
|
||||
<span>邮箱:    {{ $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
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import { DefineComponent } from 'vue'
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/* ./src/index.css */
|
||||
|
||||
/*! @import */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -0,0 +1,67 @@
|
||||
function padLeftZero(str: string): string {
|
||||
return ('00' + str).substr(str.length)
|
||||
}
|
||||
|
||||
const util = {
|
||||
title: function (title: string) {
|
||||
window.document.title = title ? title + ' - oa' : 'veypi project'
|
||||
},
|
||||
getCookie(name: string) {
|
||||
const reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)')
|
||||
const arr = document.cookie.match(reg)
|
||||
if (arr) {
|
||||
return unescape(arr[2])
|
||||
} else return null
|
||||
},
|
||||
delCookie(name: string) {
|
||||
const exp = new Date()
|
||||
exp.setTime(exp.getTime() - 1)
|
||||
const cval = this.getCookie(name)
|
||||
if (cval !== null) {
|
||||
document.cookie = name + '=' + cval + ';expires=' + exp.toLocaleString()
|
||||
}
|
||||
},
|
||||
setCookie(name: string, value: string, time: number) {
|
||||
const exp = new Date()
|
||||
exp.setTime(exp.getTime() + time)
|
||||
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)
|
||||
},
|
||||
|
||||
formatDate(date: Date, fmt: string) {
|
||||
if (/(y+)/.test(fmt)) {
|
||||
fmt = fmt.replace(
|
||||
RegExp.$1,
|
||||
(date.getFullYear() + '').substr(4 - RegExp.$1.length)
|
||||
)
|
||||
}
|
||||
const o = {
|
||||
'M+': date.getMonth() + 1,
|
||||
'd+': date.getDate(),
|
||||
'h+': date.getHours(),
|
||||
'm+': date.getMinutes(),
|
||||
's+': date.getSeconds()
|
||||
}
|
||||
for (const k in o) {
|
||||
if (new RegExp(`(${k})`).test(fmt)) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
|
||||
// @ts-ignore
|
||||
const str = o[k] + ''
|
||||
fmt = fmt.replace(
|
||||
RegExp.$1,
|
||||
RegExp.$1.length === 1 ? str : padLeftZero(str)
|
||||
)
|
||||
}
|
||||
}
|
||||
return fmt
|
||||
}
|
||||
}
|
||||
|
||||
export default util
|
||||
@@ -0,0 +1,2 @@
|
||||
!function(a,b,c){function d(c){var d=b.createElement("iframe"),e="https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid="+c.appid+"&agentid="+c.agentid+"&redirect_uri="+c.redirect_uri+"&state="+c.state+"&login_type=jssdk";e+=c.style?"&style="+c.style:"",e+=c.href?"&href="+c.href:"",d.src=e,d.frameBorder="0",d.allowTransparency="true",d.scrolling="no",d.width="300px",d.height="400px";var f=b.getElementById(c.id);f.innerHTML="",f.appendChild(d),d.onload=function(){d.contentWindow.postMessage&&a.addEventListener&&(a.addEventListener("message",function(b){
|
||||
b.data&&b.origin.indexOf("work.weixin.qq.com")>-1&&(a.location.href=b.data)}),d.contentWindow.postMessage("ask_usePostMessage","*"))}}a.WwLogin=d}(window,document);
|
||||
@@ -0,0 +1,19 @@
|
||||
import {createApp} from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import {store, key} from './store'
|
||||
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)
|
||||
app.use(router)
|
||||
app.use(store, key)
|
||||
app.mount('#app')
|
||||
@@ -0,0 +1,83 @@
|
||||
import {createRouter, createWebHistory} from 'vue-router'
|
||||
import util from '../libs/util'
|
||||
|
||||
declare module 'vue-router' {
|
||||
interface RouteMeta {
|
||||
// 是可选的
|
||||
isAdmin?: boolean
|
||||
// 每个路由都必须声明
|
||||
requiresAuth: boolean
|
||||
}
|
||||
}
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
},
|
||||
component: () => import('../views/home.vue')
|
||||
},
|
||||
{
|
||||
path: '/app/:uuid?',
|
||||
name: 'app',
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
},
|
||||
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',
|
||||
name: 'wx',
|
||||
component: () => import('../views/wx.vue')
|
||||
},
|
||||
{
|
||||
path: '/login/:uuid?',
|
||||
name: 'login',
|
||||
component: () => import('../views/login.vue')
|
||||
},
|
||||
{
|
||||
path: '/register/:uuid?',
|
||||
name: 'register',
|
||||
component: () => import('../views/register.vue')
|
||||
},
|
||||
{
|
||||
path: '/:path(.*)',
|
||||
name: '404',
|
||||
component: () => import('../views/404.vue')
|
||||
}
|
||||
//...
|
||||
],
|
||||
})
|
||||
|
||||
router.beforeEach((to, from) => {
|
||||
// 而不是去检查每条路由记录
|
||||
// to.matched.some(record => record.meta.requiresAuth)
|
||||
if (to.meta.requiresAuth && !util.checkLogin()) {
|
||||
// 此路由需要授权,请检查是否已登录
|
||||
// 如果没有,则重定向到登录页面
|
||||
return {
|
||||
name: 'login',
|
||||
// 保存我们所在的位置,以便以后再来
|
||||
query: {redirect: to.fullPath},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,49 @@
|
||||
import {InjectionKey} from 'vue'
|
||||
import {createStore, useStore as baseUseStore, Store} from 'vuex'
|
||||
import api from "../api";
|
||||
import {User, UserState} from './user'
|
||||
|
||||
export interface State extends Object {
|
||||
oauuid: string
|
||||
user: UserState
|
||||
apps: []
|
||||
}
|
||||
|
||||
export const key: InjectionKey<Store<State>> = Symbol()
|
||||
|
||||
export const store = createStore<State>({
|
||||
modules: {
|
||||
user: User
|
||||
},
|
||||
// @ts-ignore
|
||||
state: {
|
||||
oauuid: '',
|
||||
apps: []
|
||||
},
|
||||
getters: {},
|
||||
mutations: {
|
||||
setOA(state: any, data: any) {
|
||||
state.oauuid = data.uuid
|
||||
},
|
||||
setApps(state: State, data: any) {
|
||||
state.apps = data
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
fetchSelf({commit}) {
|
||||
api.app.self().Start(d => {
|
||||
commit('setOA', d)
|
||||
})
|
||||
},
|
||||
fetchApps({commit}) {
|
||||
api.app.list().Start(e => {
|
||||
commit('setApps', e)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 定义自己的 `useStore` 组合式函数
|
||||
export function useStore() {
|
||||
return baseUseStore(key)
|
||||
}
|
||||
@@ -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,32 @@
|
||||
<template>
|
||||
<div class="flex justify-center items-center">
|
||||
<div class="text-center text-xl">
|
||||
<one-icon style="font-size: 200px">404</one-icon>
|
||||
<span>
|
||||
路径失效啦! {{count}}秒
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {useRouter, useRoute} from 'vue-router'
|
||||
import {onMounted, ref} from "vue";
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
let count = ref(5)
|
||||
onMounted(() => {
|
||||
console.log([route.path, route.params])
|
||||
let timer = setInterval(()=> {
|
||||
count.value--
|
||||
if (count.value === 0) {
|
||||
router.push('/')
|
||||
clearInterval(timer)
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {onMounted, ref} from "vue";
|
||||
import api from "../api";
|
||||
import AppCard from '../components/app.vue'
|
||||
|
||||
let apps = ref([])
|
||||
|
||||
function getApps() {
|
||||
api.app.list().Start(e => {
|
||||
apps.value = e
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getApps()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-center">
|
||||
<div
|
||||
:style="{background:Theme.me.lightBox, 'box-shadow': Theme.me.lightBoxShadow}"
|
||||
class="px-10 pb-9 pt-28 rounded-xl w-96">
|
||||
<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 required label="密码" path="password">
|
||||
<n-input @keydown.enter="login" :ref="el => {if (el) divs[1]=el}" v-model:value="data.password"
|
||||
type="password"></n-input>
|
||||
</n-form-item>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
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()
|
||||
|
||||
const divs = ref([])
|
||||
let form_ref = ref(null)
|
||||
let data = ref({
|
||||
username: '',
|
||||
password: ''
|
||||
})
|
||||
let rules = {
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
validator(r: any, v: any) {
|
||||
return (v && v.length >= 3 && v.length <= 16) || new Error('长度要求3~16')
|
||||
},
|
||||
trigger: ['input', 'blur']
|
||||
}
|
||||
],
|
||||
password: [{
|
||||
required: true,
|
||||
validator(r: any, v: any) {
|
||||
return (v && v.length >= 6 && v.length <= 16) || new Error('长度要求6~16')
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
let uuid = computed(() => {
|
||||
return route.params.uuid || store.state.oauuid
|
||||
})
|
||||
|
||||
function login() {
|
||||
// @ts-ignore
|
||||
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>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</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>
|
||||
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div class='home d-flex justify-center align-center'>
|
||||
<wx-login v-if="enable" :aid="aid" :app="agentID" :url="url"></wx-login>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang='ts'>
|
||||
import WxLogin from '../components/WxLogin.vue'
|
||||
import {computed, onMounted} from "vue";
|
||||
import {useRoute} from 'vue-router'
|
||||
import api from '../api'
|
||||
|
||||
let route = useRoute()
|
||||
|
||||
let aid = ''
|
||||
let agentID = ''
|
||||
let url = ''
|
||||
let uuid = computed(() => {
|
||||
return route.query.uuid
|
||||
})
|
||||
let enable = computed(() => {
|
||||
return uuid && aid && agentID && url
|
||||
})
|
||||
let code = computed(() => {
|
||||
return route.query.code
|
||||
})
|
||||
|
||||
let state = computed(() => {
|
||||
return route.query.state
|
||||
})
|
||||
|
||||
let msg = computed(() => {
|
||||
return route.query.msg
|
||||
})
|
||||
onMounted(() => {
|
||||
if (msg) {
|
||||
console.log(msg)
|
||||
alert(msg)
|
||||
}
|
||||
})
|
||||
|
||||
if (uuid) {
|
||||
api.app.get(uuid.value as string).Start(e => {
|
||||
url = e.wx.url + '/api/wx/login/' + uuid
|
||||
aid = e.wx.corp_id
|
||||
agentID = e.wx.agent_id
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
Vendored
+14
@@ -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>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
|
||||
darkMode: false, // or 'media' or 'class'
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
variants: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": ["esnext", "dom"]
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
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': {
|
||||
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]`
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
+2191
File diff suppressed because it is too large
Load Diff
+54
@@ -0,0 +1,54 @@
|
||||
package sub
|
||||
|
||||
import (
|
||||
"OneAuth/cfg"
|
||||
"OneAuth/models"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/veypi/utils"
|
||||
"github.com/veypi/utils/log"
|
||||
)
|
||||
|
||||
var App = &cli.Command{
|
||||
Name: "app",
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "list",
|
||||
Action: runAppList,
|
||||
},
|
||||
{
|
||||
Name: "create",
|
||||
Action: runAppCreate,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runAppList(c *cli.Context) error {
|
||||
list := make([]*models.App, 0, 10)
|
||||
err := cfg.DB().Find(&list).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, a := range list {
|
||||
log.Info().Msgf("%d: %s", a.ID, a.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runAppCreate(c *cli.Context) error {
|
||||
app := &models.App{}
|
||||
app.Name = c.String("name")
|
||||
app.Key = utils.RandSeq(16)
|
||||
app.UUID = utils.RandSeq(8)
|
||||
err := cfg.DB().Create(app).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info().Msgf("app: %s\nuuid: %s\nkey: %s", app.Name, app.UUID, app.Key)
|
||||
return nil
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
package sub
|
||||
|
||||
import (
|
||||
"OneAuth/cfg"
|
||||
"OneAuth/libs/auth"
|
||||
"OneAuth/models"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/veypi/utils/log"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var Init = &cli.Command{
|
||||
Name: "init",
|
||||
Action: runInit,
|
||||
}
|
||||
|
||||
func runInit(c *cli.Context) error {
|
||||
return InitSystem()
|
||||
}
|
||||
|
||||
// 初始化项目
|
||||
var appid uint
|
||||
|
||||
func InitSystem() error {
|
||||
db()
|
||||
self, err := selfApp()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appid = self.ID
|
||||
err = role(self.InitRoleID == 0)
|
||||
return err
|
||||
}
|
||||
|
||||
func db() {
|
||||
db := cfg.DB()
|
||||
log.HandlerErrs(
|
||||
db.SetupJoinTable(&models.User{}, "Roles", &models.UserRole{}),
|
||||
db.SetupJoinTable(&models.Role{}, "Users", &models.UserRole{}),
|
||||
db.SetupJoinTable(&models.User{}, "Apps", &models.AppUser{}),
|
||||
db.SetupJoinTable(&models.App{}, "Users", &models.AppUser{}),
|
||||
db.AutoMigrate(&models.User{}, &models.Role{}, &models.Auth{}, &models.App{}),
|
||||
)
|
||||
log.HandlerErrs(
|
||||
db.AutoMigrate(&models.Wechat{}, &models.Resource{}),
|
||||
)
|
||||
}
|
||||
|
||||
func selfApp() (*models.App, error) {
|
||||
self := &models.App{
|
||||
Name: "OA",
|
||||
Icon: "",
|
||||
UUID: cfg.CFG.APPUUID,
|
||||
Des: "",
|
||||
Creator: 0,
|
||||
UserCount: 0,
|
||||
Hide: false,
|
||||
Host: "",
|
||||
UserRefreshUrl: "/",
|
||||
Key: cfg.CFG.APPKey,
|
||||
EnableRegister: true,
|
||||
EnableUserKey: true,
|
||||
EnableUser: true,
|
||||
EnableWx: false,
|
||||
EnablePhone: false,
|
||||
EnableEmail: false,
|
||||
Wx: nil,
|
||||
}
|
||||
return self, cfg.DB().Where("uuid = ?", self.UUID).FirstOrCreate(self).Error
|
||||
}
|
||||
|
||||
func role(reset_init_role bool) error {
|
||||
authMap := make(map[string]*models.Resource)
|
||||
n := []string{
|
||||
auth.APP,
|
||||
auth.User,
|
||||
auth.Res,
|
||||
auth.Auth,
|
||||
auth.Role,
|
||||
}
|
||||
var err error
|
||||
adminRole := &models.Role{
|
||||
AppID: appid,
|
||||
Name: "admin",
|
||||
IsUnique: false,
|
||||
}
|
||||
err = cfg.DB().Where(adminRole).FirstOrCreate(adminRole).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, na := range n {
|
||||
a := &models.Resource{
|
||||
AppID: appid,
|
||||
Name: na,
|
||||
Tag: "",
|
||||
Des: "",
|
||||
}
|
||||
err = cfg.DB().Where(a).FirstOrCreate(a).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authMap[na] = a
|
||||
err = auth.BindRoleAuth(cfg.DB(), adminRole.ID, a.ID, models.AuthAll, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
userRole := &models.Role{
|
||||
AppID: appid,
|
||||
Name: "user",
|
||||
IsUnique: false,
|
||||
}
|
||||
err = cfg.DB().Where(userRole).FirstOrCreate(userRole).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = auth.BindRoleAuth(cfg.DB(), userRole.ID, authMap[auth.APP].ID, models.AuthRead, strconv.Itoa(int(appid)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reset_init_role {
|
||||
return cfg.DB().Model(&models.App{}).Where("id = ?", appid).Update("init_role_id", adminRole.ID).Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
package sub
|
||||
|
||||
import (
|
||||
"OneAuth/cfg"
|
||||
"OneAuth/models"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/veypi/utils/log"
|
||||
)
|
||||
|
||||
var Role = &cli.Command{
|
||||
Name: "role",
|
||||
Usage: "",
|
||||
Description: "",
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "list",
|
||||
Action: runRoleList,
|
||||
},
|
||||
{
|
||||
Name: "create",
|
||||
Action: runRoleCreate,
|
||||
Flags: []cli.Flag{
|
||||
&cli.UintFlag{
|
||||
Name: "id",
|
||||
Usage: "app id",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "role name",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Flags: []cli.Flag{},
|
||||
}
|
||||
|
||||
func runRoleList(c *cli.Context) error {
|
||||
roles := make([]*models.Role, 0, 10)
|
||||
err := cfg.DB().Find(&roles).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range roles {
|
||||
log.Info().Msgf("%d %s@%d", r.ID, r.Name, r.AppID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runRoleCreate(c *cli.Context) error {
|
||||
id := c.Uint("id")
|
||||
name := c.String("name")
|
||||
rl := &models.Role{}
|
||||
rl.AppID = id
|
||||
rl.Name = name
|
||||
err := cfg.DB().Where(rl).FirstOrCreate(rl).Error
|
||||
return err
|
||||
}
|
||||
|
||||
var Resource = &cli.Command{
|
||||
Name: "resource",
|
||||
Usage: "resource manual",
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "list",
|
||||
Action: runResourceList,
|
||||
Flags: []cli.Flag{
|
||||
&cli.UintFlag{
|
||||
Name: "id",
|
||||
Usage: "app id",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "create",
|
||||
Action: runResourceCreate,
|
||||
Flags: []cli.Flag{
|
||||
&cli.UintFlag{
|
||||
Name: "id",
|
||||
Usage: "app id",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "role name",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func runResourceList(c *cli.Context) error {
|
||||
query := &models.Resource{}
|
||||
query.AppID = c.Uint("id")
|
||||
l := make([]*models.Resource, 0, 10)
|
||||
err := cfg.DB().Where(query).Find(&l).Error
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for _, r := range l {
|
||||
log.Info().Msgf("%d: %s@%d", r.ID, r.Name, r.AppID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runResourceCreate(c *cli.Context) error {
|
||||
query := &models.Resource{}
|
||||
query.AppID = c.Uint("id")
|
||||
query.Name = c.String("name")
|
||||
err := cfg.DB().Where(query).FirstOrCreate(query).Error
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<!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>
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package sub
|
||||
|
||||
import (
|
||||
"OneAuth/api"
|
||||
"OneAuth/cfg"
|
||||
"embed"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/veypi/OneBD"
|
||||
"github.com/veypi/utils/log"
|
||||
)
|
||||
|
||||
//go:embed static/static
|
||||
var staticFiles embed.FS
|
||||
|
||||
//go:embed static/favicon.ico
|
||||
var icon []byte
|
||||
|
||||
//go:embed static/index.html
|
||||
var indexFile []byte
|
||||
|
||||
var Web = &cli.Command{
|
||||
Name: "web",
|
||||
Usage: "",
|
||||
Description: "oa 核心http服务",
|
||||
Action: RunWeb,
|
||||
Flags: []cli.Flag{},
|
||||
}
|
||||
|
||||
func RunWeb(c *cli.Context) error {
|
||||
ll := log.InfoLevel
|
||||
if l, err := log.ParseLevel(cfg.CFG.LoggerLevel); err == nil {
|
||||
ll = l
|
||||
}
|
||||
app := OneBD.New(&OneBD.Config{
|
||||
Host: cfg.CFG.Host,
|
||||
LoggerPath: cfg.CFG.LoggerPath,
|
||||
LoggerLevel: ll,
|
||||
})
|
||||
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user