This commit is contained in:
veypi
2021-09-30 13:13:34 +08:00
parent 935653ba28
commit 3d194e935d
41 changed files with 2316 additions and 199 deletions
+22
View File
@@ -0,0 +1,22 @@
package api
import (
"OneAuth/api/app"
"OneAuth/api/user"
"OneAuth/api/wx"
"github.com/veypi/OneBD"
"github.com/veypi/OneBD/core"
)
func Router(r OneBD.Router) {
r.SetNotFoundFunc(func(m core.Meta) {
m.Write([]byte("{\"status\": 0}"))
})
r.SetInternalErrorFunc(func(m core.Meta) {
m.Write([]byte("{\"status\": 0}"))
})
user.Router(r.SubRouter("/auth/user"))
wx.Router(r.SubRouter("wx"))
app.Router(r.SubRouter("app"))
//message.Router(r.SubRouter("/message"))
}
+39
View File
@@ -0,0 +1,39 @@
package app
import (
"OneAuth/cfg"
"OneAuth/libs/base"
"OneAuth/libs/oerr"
"OneAuth/models"
"github.com/veypi/OneBD"
"github.com/veypi/OneBD/rfc"
)
func Router(r OneBD.Router) {
r.Set("/:id", appHandlerP, rfc.MethodGet)
}
var appHandlerP = OneBD.NewHandlerPool(func() OneBD.Handler {
h := &appHandler{}
h.Ignore(rfc.MethodGet)
return h
})
type appHandler struct {
base.ApiHandler
query *models.App
}
func (h *appHandler) Get() (interface{}, error) {
id := h.Meta().Params("id")
if id == "" {
return nil, oerr.ApiArgsMissing
}
h.query = &models.App{}
h.query.UUID = id
err := cfg.DB().Where(h.query).Preload("Wx").First(h.query).Error
if err != nil {
return nil, err
}
return h.query, nil
}
+22
View File
@@ -0,0 +1,22 @@
package role
import (
"OneAuth/cfg"
"OneAuth/libs/base"
"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) {
l := make([]*models.Auth, 0, 10)
return &l, cfg.DB().Find(&l).Error
}
+138
View File
@@ -0,0 +1,138 @@
package role
import (
"OneAuth/cfg"
"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")
aid := h.Meta().ParamsInt("aid")
act := h.Meta().Params("action")
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
}
if aid <= 0 {
return role, nil
}
if !h.CheckAuth("admin", "").CanDoAny() {
return nil, oerr.NoAuth
}
at := &models.RoleAuth{}
at.RoleID = role.ID
at.AuthID = uint(aid)
defer models.SyncGlobalRoles()
if act == "bind" {
err = cfg.DB().Where(at).FirstOrCreate(at).Error
} else if act == "unbind" {
err = cfg.DB().Where(at).Delete(at).Error
}
return role, nil
}
roles := make([]*models.Role, 0, 10)
err := cfg.DB().Preload("Auths").Preload("Users").Find(&roles).Error
return roles, err
}
func (h *roleHandler) Post() (interface{}, error) {
if !h.CheckAuth("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.Category == 0 || role.Name == "" {
return nil, oerr.ApiArgsMissing
}
defer models.SyncGlobalRoles()
return role, cfg.DB().Where(role).FirstOrCreate(role).Error
}
func (h *roleHandler) Patch() (interface{}, error) {
if !h.CheckAuth("role", "").CanUpdate() {
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.CheckAuth("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
}
defer models.SyncGlobalRoles()
return nil, cfg.DB().Delete(role).Error
}
+13
View File
@@ -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/:aid", roleP, rfc.MethodGet)
r.Set("/auth/", authP, rfc.MethodGet)
}
+242
View File
@@ -0,0 +1,242 @@
package user
import (
"OneAuth/cfg"
"OneAuth/libs/base"
"OneAuth/libs/oerr"
"OneAuth/models"
//"OneAuth/ws"
"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)
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) {
username := h.Meta().Query("username")
if username != "" {
users := make([]*models.User, 0, 10)
err := cfg.DB().Preload("Scores").Preload("Roles.Auths").Where("username LIKE ? OR nickname LIKE ?", "%"+username+"%", "%"+username+"%").Find(&users).Error
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).Preload("Scores").Preload("Roles.Auths").Preload("Favorites").First(user).Error
} else {
users := make([]models.User, 10)
skip, err := strconv.Atoi(h.Meta().Query("skip"))
if err != nil || skip < 0 {
skip = 0
}
if err := cfg.DB().Preload("Scores").Preload("Roles.Auths").Offset(skip).Find(&users).Error; err != nil {
return nil, err
}
return users, nil
}
}
// Post register user
func (h *handler) Post() (interface{}, error) {
if !h.CheckAuth("user").CanCreate() {
return nil, oerr.NoAuth
}
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.UpdateAuth(string(pass)); err != nil {
log.HandlerErrs(err)
return nil, oerr.ResourceCreatedFailed
}
tx := cfg.DB().Begin()
if err := tx.Create(&h.User).Error; err != nil {
tx.Rollback()
return nil, oerr.ResourceDuplicated
}
tx.Commit()
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)
}
tx := cfg.DB().Begin()
if err := cfg.DB().Where(&target).First(&target).Error; err != nil {
return nil, err
}
if target.ID != h.Payload.ID && !h.CheckAuth("admin").CanDoAny() {
return nil, oerr.NoAuth
}
if len(opts.Password) >= 6 {
if err := target.UpdateAuth(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 := tx.Updates(&target).Error; err != nil {
tx.Rollback()
return nil, err
}
tx.Commit()
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
}
if err := cfg.DB().Preload("Roles").Where(h.User).First(h.User).Error; err != nil {
if err.Error() == gorm.ErrRecordNotFound.Error() {
// admin 登录自动注册
if h.User.Username == "admin" {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
h.User.Icon = fmt.Sprintf("/media/icon/default/%04d.jpg", r.Intn(230))
err = h.User.UpdateAuth(password)
if err != nil {
return nil, err
}
role := &models.Role{}
role.ID = 1
h.User.Roles = []*models.Role{role}
err = cfg.DB().Create(h.User).Error
if err != nil {
return nil, err
}
} else {
return nil, oerr.AccountNotExist
}
} else {
log.HandlerErrs(err)
return nil, err
}
}
isAuth, err := h.User.CheckLogin(password)
if err != nil || !isAuth {
return nil, oerr.PassError.Attach(err)
}
if h.User.Status == "disabled" {
return nil, oerr.DisableLogin
}
token, err := h.User.GetToken(cfg.CFG.Key)
if err != nil {
log.HandlerErrs(err)
return nil, oerr.Unknown.Attach(err)
}
h.Meta().SetHeader("auth_token", token)
log.Info().Msg(h.User.Username + " login")
return nil, nil
}
+86
View File
@@ -0,0 +1,86 @@
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.CheckAuth("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,
"category": query.Category,
"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.CheckAuth("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
View File
@@ -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))
}
+10
View File
@@ -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)
}