mirror of https://github.com/veypi/OneAuth.git
feat: simplify user login
parent
545f49c0f3
commit
7e7e6ed506
@ -0,0 +1,49 @@
|
|||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"oa/cfg"
|
||||||
|
"oa/models"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/veypi/OneBD/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Router.Patch("/:token_id", tokenPatch)
|
||||||
|
|
||||||
|
type patchOpts struct {
|
||||||
|
ID string `json:"id" parse:"path@token_id"`
|
||||||
|
ExpiredAt *time.Time `json:"expired_at" parse:"json"`
|
||||||
|
OverPerm *string `json:"over_perm" parse:"json"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func tokenPatch(x *rest.X) (any, error) {
|
||||||
|
opts := &patchOpts{}
|
||||||
|
err := x.Parse(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data := &models.Token{}
|
||||||
|
|
||||||
|
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
optsMap := make(map[string]interface{})
|
||||||
|
if opts.ExpiredAt != nil {
|
||||||
|
optsMap["expired_at"] = opts.ExpiredAt
|
||||||
|
}
|
||||||
|
if opts.OverPerm != nil {
|
||||||
|
optsMap["over_perm"] = opts.OverPerm
|
||||||
|
}
|
||||||
|
err = cfg.DB().Model(data).Updates(optsMap).Error
|
||||||
|
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Router.Delete("/:token_id", tokenDelete)
|
||||||
|
|
||||||
|
func tokenDelete(x *rest.X) (any, error) {
|
||||||
|
data := &models.Token{}
|
||||||
|
err := cfg.DB().Where("id = ?", x.Params.Get("token_id")).Delete(data).Error
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
//
|
||||||
|
// get.go
|
||||||
|
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||||
|
// 2025-05-09 17:24
|
||||||
|
// Distributed under terms of the MIT license.
|
||||||
|
//
|
||||||
|
|
||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"oa/cfg"
|
||||||
|
"oa/models"
|
||||||
|
|
||||||
|
"github.com/veypi/OneBD/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
type getOpts struct {
|
||||||
|
ID string `json:"id" parse:"path@token_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Router.Get("/:token_id", tokenGet)
|
||||||
|
|
||||||
|
func tokenGet(x *rest.X) (any, error) {
|
||||||
|
opts := &getOpts{}
|
||||||
|
err := x.Parse(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data := &models.Token{}
|
||||||
|
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type listOpts struct {
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
UserID string `json:"user_id" gorm:"index;type:varchar(32)" parse:"query"`
|
||||||
|
AppID string `json:"app_id" gorm:"index;type:varchar(32)" parse:"query"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Router.Get("/", tokenList)
|
||||||
|
|
||||||
|
func tokenList(x *rest.X) (any, error) {
|
||||||
|
opts := &listOpts{}
|
||||||
|
err := x.Parse(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data := make([]*models.Token, 0, 10)
|
||||||
|
|
||||||
|
query := cfg.DB()
|
||||||
|
query = query.Where("user_id = ?", opts.UserID)
|
||||||
|
query = query.Where("app_id = ?", opts.AppID)
|
||||||
|
err = query.Limit(opts.Limit).Find(&data).Error
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
@ -1,258 +0,0 @@
|
|||||||
package token
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"oa/cfg"
|
|
||||||
"oa/errs"
|
|
||||||
"oa/libs/auth"
|
|
||||||
M "oa/models"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
"github.com/veypi/OneBD/rest"
|
|
||||||
"github.com/veypi/utils"
|
|
||||||
"github.com/veypi/utils/logv"
|
|
||||||
)
|
|
||||||
|
|
||||||
func useToken(r rest.Router) {
|
|
||||||
r.Post("/salt", tokenSalt)
|
|
||||||
r.Post("/", tokenPost)
|
|
||||||
r.Get("/:token_id", tokenGet)
|
|
||||||
r.Patch("/:token_id", tokenPatch)
|
|
||||||
r.Delete("/:token_id", tokenDelete)
|
|
||||||
r.Get("/", tokenList)
|
|
||||||
}
|
|
||||||
func tokenSalt(x *rest.X) (any, error) {
|
|
||||||
opts := &M.TokenSalt{}
|
|
||||||
err := x.Parse(opts)
|
|
||||||
logv.Warn().Msg(opts.Username)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data := &M.User{}
|
|
||||||
query := "username = ?"
|
|
||||||
if opts.Typ == nil {
|
|
||||||
} else if *opts.Typ == "email" {
|
|
||||||
query = "email = ?"
|
|
||||||
} else if *opts.Typ == "phone" {
|
|
||||||
query = "phone = ?"
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cfg.DB().Where(query, opts.Username).First(data).Error
|
|
||||||
return map[string]string{"salt": data.Salt, "id": data.ID}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// for user login app
|
|
||||||
func tokenPost(x *rest.X) (any, error) {
|
|
||||||
opts := &M.TokenPost{}
|
|
||||||
err := x.Parse(opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
aid := cfg.Config.ID
|
|
||||||
if opts.AppID != nil && *opts.AppID != "" {
|
|
||||||
aid = *opts.AppID
|
|
||||||
}
|
|
||||||
data := &M.Token{}
|
|
||||||
claim := &auth.Claims{}
|
|
||||||
claim.IssuedAt = jwt.NewNumericDate(time.Now())
|
|
||||||
claim.Issuer = cfg.Config.ID
|
|
||||||
if opts.Refresh != nil {
|
|
||||||
typ := "app"
|
|
||||||
if opts.Typ != nil {
|
|
||||||
typ = *opts.Typ
|
|
||||||
}
|
|
||||||
// for other app redirect
|
|
||||||
refresh, err := auth.ParseJwt(*opts.Refresh)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if refresh.ID == "" {
|
|
||||||
return nil, errs.AuthInvalid
|
|
||||||
}
|
|
||||||
err = cfg.DB().Where("id = ?", refresh.ID).First(data).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
claim.AID = aid
|
|
||||||
claim.UID = refresh.UID
|
|
||||||
claim.Name = refresh.Name
|
|
||||||
claim.Icon = refresh.Icon
|
|
||||||
claim.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Minute * 10))
|
|
||||||
if typ == "app" {
|
|
||||||
if refresh.AID == aid {
|
|
||||||
// refresh token
|
|
||||||
acList := make(auth.Access, 0, 10)
|
|
||||||
logv.AssertError(cfg.DB().Table("accesses a").
|
|
||||||
Select("a.name, a.t_id, a.level").
|
|
||||||
Joins("INNER JOIN user_roles ur ON ur.role_id = a.role_id AND ur.user_id = ? AND a.app_id = ?", refresh.UID, aid).
|
|
||||||
Scan(&acList).Error)
|
|
||||||
claim.Access = acList
|
|
||||||
if aid == cfg.Config.ID {
|
|
||||||
return auth.GenJwt(claim)
|
|
||||||
}
|
|
||||||
app := &M.App{}
|
|
||||||
err = cfg.DB().Where("id = ?", aid).First(app).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return auth.GenJwtWithKey(claim, app.Key)
|
|
||||||
} else if aid != cfg.Config.ID {
|
|
||||||
// 只能生成其他应用的refresh token
|
|
||||||
newToken := &M.Token{}
|
|
||||||
newToken.UserID = refresh.UID
|
|
||||||
newToken.AppID = aid
|
|
||||||
newToken.ExpiredAt = time.Now().Add(time.Hour * 24)
|
|
||||||
if opts.OverPerm != nil {
|
|
||||||
newToken.OverPerm = *opts.OverPerm
|
|
||||||
}
|
|
||||||
if opts.Device != nil {
|
|
||||||
newToken.Device = *opts.Device
|
|
||||||
}
|
|
||||||
newToken.Ip = x.GetRemoteIp()
|
|
||||||
logv.AssertError(cfg.DB().Create(newToken).Error)
|
|
||||||
// gen other app token
|
|
||||||
claim.ID = newToken.ID
|
|
||||||
claim.ExpiresAt = jwt.NewNumericDate(newToken.ExpiredAt)
|
|
||||||
return auth.GenJwt(claim)
|
|
||||||
} else {
|
|
||||||
// 其他应用获取访问oa的token
|
|
||||||
claim.Access = make(auth.Access, 0, 10)
|
|
||||||
if data.OverPerm != "" {
|
|
||||||
err = json.Unmarshal([]byte(data.OverPerm), &claim.Access)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return auth.GenJwt(claim)
|
|
||||||
}
|
|
||||||
} else if typ == "ufs" {
|
|
||||||
claim.AID = refresh.AID
|
|
||||||
claim.UID = refresh.UID
|
|
||||||
claim.Name = refresh.Name
|
|
||||||
claim.Icon = refresh.Icon
|
|
||||||
claim.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Minute * 10))
|
|
||||||
claim.Access = auth.Access{
|
|
||||||
{Name: "fs", TID: "/", Level: auth.Do},
|
|
||||||
}
|
|
||||||
token := logv.AssertFuncErr(auth.GenJwt(claim))
|
|
||||||
|
|
||||||
cookie := &http.Cookie{
|
|
||||||
Name: "fstoken", // Cookie 的名称
|
|
||||||
Value: token, // Cookie 的值
|
|
||||||
Path: "/fs/u/", // Cookie 的路径,通常是根路径
|
|
||||||
MaxAge: 600, // Cookie 的最大年龄,单位是秒
|
|
||||||
HttpOnly: true, // 是否仅限 HTTP(S) 访问
|
|
||||||
Secure: false, // 是否通过安全连接传输 Cookie
|
|
||||||
}
|
|
||||||
http.SetCookie(x, cookie)
|
|
||||||
|
|
||||||
return token, nil
|
|
||||||
} else {
|
|
||||||
return nil, errs.ArgsInvalid
|
|
||||||
}
|
|
||||||
} else if opts.Code != nil && aid == cfg.Config.ID && opts.Salt != nil && opts.UserID != nil {
|
|
||||||
// for oa login
|
|
||||||
user := &M.User{}
|
|
||||||
err = cfg.DB().Where("id = ?", opts.UserID).Find(user).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
logv.Info().Str("user", user.ID).Msg("login")
|
|
||||||
code := *opts.Code
|
|
||||||
salt := logv.AssertFuncErr(hex.DecodeString(*opts.Salt))
|
|
||||||
key := logv.AssertFuncErr(hex.DecodeString(user.Code))
|
|
||||||
de, err := utils.AesDecrypt([]byte(code), key, salt)
|
|
||||||
if err != nil || de != user.ID {
|
|
||||||
return nil, errs.AuthFailed
|
|
||||||
}
|
|
||||||
data.UserID = *opts.UserID
|
|
||||||
data.AppID = aid
|
|
||||||
data.ExpiredAt = time.Now().Add(time.Hour * 72)
|
|
||||||
if opts.OverPerm != nil {
|
|
||||||
data.OverPerm = *opts.OverPerm
|
|
||||||
}
|
|
||||||
if opts.Device != nil {
|
|
||||||
data.Device = *opts.Device
|
|
||||||
}
|
|
||||||
data.Ip = x.GetRemoteIp()
|
|
||||||
logv.AssertError(cfg.DB().Create(data).Error)
|
|
||||||
claim.ID = data.ID
|
|
||||||
claim.AID = aid
|
|
||||||
claim.UID = user.ID
|
|
||||||
claim.Name = user.Username
|
|
||||||
claim.Icon = user.Icon
|
|
||||||
claim.ExpiresAt = jwt.NewNumericDate(data.ExpiredAt)
|
|
||||||
if user.Nickname != "" {
|
|
||||||
claim.Name = user.Nickname
|
|
||||||
}
|
|
||||||
return auth.GenJwt(claim)
|
|
||||||
} else {
|
|
||||||
return nil, errs.ArgsInvalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func tokenGet(x *rest.X) (any, error) {
|
|
||||||
opts := &M.TokenGet{}
|
|
||||||
err := x.Parse(opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data := &M.Token{}
|
|
||||||
|
|
||||||
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
|
||||||
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
func tokenPatch(x *rest.X) (any, error) {
|
|
||||||
opts := &M.TokenPatch{}
|
|
||||||
err := x.Parse(opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data := &M.Token{}
|
|
||||||
|
|
||||||
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
optsMap := make(map[string]interface{})
|
|
||||||
if opts.ExpiredAt != nil {
|
|
||||||
optsMap["expired_at"] = opts.ExpiredAt
|
|
||||||
}
|
|
||||||
if opts.OverPerm != nil {
|
|
||||||
optsMap["over_perm"] = opts.OverPerm
|
|
||||||
}
|
|
||||||
err = cfg.DB().Model(data).Updates(optsMap).Error
|
|
||||||
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
func tokenDelete(x *rest.X) (any, error) {
|
|
||||||
opts := &M.TokenDelete{}
|
|
||||||
err := x.Parse(opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data := &M.Token{}
|
|
||||||
|
|
||||||
err = cfg.DB().Where("id = ?", opts.ID).Delete(data).Error
|
|
||||||
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
func tokenList(x *rest.X) (any, error) {
|
|
||||||
opts := &M.TokenList{}
|
|
||||||
err := x.Parse(opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data := make([]*M.Token, 0, 10)
|
|
||||||
|
|
||||||
query := cfg.DB()
|
|
||||||
query = query.Where("user_id = ?", opts.UserID)
|
|
||||||
query = query.Where("app_id = ?", opts.AppID)
|
|
||||||
err = query.Limit(opts.Limit).Find(&data).Error
|
|
||||||
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
@ -0,0 +1,108 @@
|
|||||||
|
//
|
||||||
|
// create.go
|
||||||
|
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||||
|
// 2025-05-06 15:05
|
||||||
|
// Distributed under terms of the MIT license.
|
||||||
|
//
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"oa/cfg"
|
||||||
|
M "oa/models"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/veypi/OneBD/rest"
|
||||||
|
"github.com/veypi/utils"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Router.Post("/", userPost)
|
||||||
|
|
||||||
|
type postOpts struct {
|
||||||
|
Username string `json:"username" gorm:"varchar(100);unique;default:not null" parse:"json"`
|
||||||
|
Code string `json:"code" gorm:"varchar(128)" parse:"json"`
|
||||||
|
Nickname *string `json:"nickname" parse:"json"`
|
||||||
|
Icon *string `json:"icon" parse:"json"`
|
||||||
|
Email *string `json:"email" gorm:"varchar(20);unique;default:null" parse:"json"`
|
||||||
|
Phone *string `json:"phone" gorm:"varchar(50);unique;default:null" parse:"json"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func userPost(x *rest.X) (any, error) {
|
||||||
|
opts := &postOpts{}
|
||||||
|
err := x.Parse(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data := &M.User{}
|
||||||
|
|
||||||
|
data.ID = strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||||
|
data.Username = opts.Username
|
||||||
|
data.Code = opts.Code
|
||||||
|
data.Salt = utils.RandSeq(16)
|
||||||
|
if len(data.Username) < 2 {
|
||||||
|
return nil, rest.ErrArgInvalid.WithArgs("username length")
|
||||||
|
}
|
||||||
|
code, err := base64.URLEncoding.DecodeString(opts.Code)
|
||||||
|
if err != nil || len(code) < 8 {
|
||||||
|
return nil, rest.ErrArgInvalid.WithArgs("code")
|
||||||
|
}
|
||||||
|
code = utils.PKCS7Padding(code, 32)
|
||||||
|
data.Code, err = utils.AesEncrypt([]byte(data.ID), code, []byte(data.Salt))
|
||||||
|
if err != nil {
|
||||||
|
return nil, rest.ErrArgInvalid.WithArgs("code")
|
||||||
|
}
|
||||||
|
ncode, err := utils.AesDecrypt([]byte(data.Code), code, []byte(data.Salt))
|
||||||
|
if err != nil || ncode != data.ID {
|
||||||
|
return nil, rest.ErrInternalServer.AppendString("code decrypt failed")
|
||||||
|
}
|
||||||
|
if opts.Nickname != nil {
|
||||||
|
data.Nickname = *opts.Nickname
|
||||||
|
}
|
||||||
|
if opts.Icon != nil {
|
||||||
|
data.Icon = *opts.Icon
|
||||||
|
} else {
|
||||||
|
data.Icon = fmt.Sprintf("https://public.veypi.com/img/avatar/%04d.jpg", rand.New(rand.NewSource(time.Now().UnixNano())).Intn(220))
|
||||||
|
}
|
||||||
|
if opts.Email != nil {
|
||||||
|
data.Email = *opts.Email
|
||||||
|
}
|
||||||
|
if opts.Phone != nil {
|
||||||
|
data.Phone = *opts.Phone
|
||||||
|
}
|
||||||
|
data.Status = 1
|
||||||
|
err = cfg.DB().Transaction(func(tx *gorm.DB) error {
|
||||||
|
err := tx.Create(data).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
app := &M.App{}
|
||||||
|
err = tx.Where("id = ?", cfg.Config.ID).First(app).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
status := "ok"
|
||||||
|
switch app.Typ {
|
||||||
|
case "private":
|
||||||
|
return rest.ErrNotPermitted.WithArgs("not enable register")
|
||||||
|
case "apply":
|
||||||
|
status = "applying"
|
||||||
|
case "public":
|
||||||
|
}
|
||||||
|
if app.Typ != "public" {
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Create(&M.AppUser{
|
||||||
|
UserID: data.ID,
|
||||||
|
AppID: cfg.Config.ID,
|
||||||
|
Status: status,
|
||||||
|
}).Error
|
||||||
|
})
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
@ -0,0 +1,101 @@
|
|||||||
|
//
|
||||||
|
// login.go
|
||||||
|
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||||
|
// 2025-05-12 17:35
|
||||||
|
// Distributed under terms of the MIT license.
|
||||||
|
//
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"oa/cfg"
|
||||||
|
"oa/libs/auth"
|
||||||
|
"oa/models"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
"github.com/veypi/OneBD/rest"
|
||||||
|
"github.com/veypi/utils"
|
||||||
|
"github.com/veypi/utils/logv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Router.Post("/login", userLogin)
|
||||||
|
|
||||||
|
type loginOpts struct {
|
||||||
|
UserName string `json:"username" parse:"json"`
|
||||||
|
Code string `json:"code" parse:"json"`
|
||||||
|
Phone *string `json:"phone" parse:"json"`
|
||||||
|
Email *string `json:"email" parse:"json"`
|
||||||
|
Type *string `json:"type" parse:"json"`
|
||||||
|
AppID *string `json:"app_id" parse:"json"`
|
||||||
|
Device *string `json:"device" parse:"json"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func userLogin(x *rest.X) (any, error) {
|
||||||
|
// Implement login logic here
|
||||||
|
// For example, validate user credentials and return a token
|
||||||
|
opts := &loginOpts{}
|
||||||
|
err := x.Parse(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
user := &models.User{}
|
||||||
|
query := cfg.DB()
|
||||||
|
typ := ""
|
||||||
|
if opts.Type != nil {
|
||||||
|
typ = *opts.Type
|
||||||
|
}
|
||||||
|
switch typ {
|
||||||
|
case "phone":
|
||||||
|
query = query.Where("phone = ?", opts.Phone)
|
||||||
|
case "email":
|
||||||
|
query = query.Where("email = ?", opts.Email)
|
||||||
|
default:
|
||||||
|
query = query.Where("username = ?", opts.UserName)
|
||||||
|
}
|
||||||
|
err = query.First(user).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
logv.Info().Str("user", user.ID).Msg("login")
|
||||||
|
code, err := base64.URLEncoding.DecodeString(opts.Code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, rest.ErrArgInvalid.WithArgs("code")
|
||||||
|
}
|
||||||
|
logv.Warn().Msgf("code: %s", code)
|
||||||
|
|
||||||
|
ncode, err := utils.AesDecrypt([]byte(user.Code), utils.PKCS7Padding(code, 32), []byte(user.Salt))
|
||||||
|
logv.Warn().Msgf("id: %s\n%s", ncode, user.ID)
|
||||||
|
if err != nil || string(ncode) != user.ID {
|
||||||
|
return nil, rest.ErrNotAuthorized
|
||||||
|
}
|
||||||
|
aid := cfg.Config.ID
|
||||||
|
if opts.AppID != nil && *opts.AppID != "" {
|
||||||
|
aid = *opts.AppID
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &models.Token{}
|
||||||
|
|
||||||
|
data.UserID = user.ID
|
||||||
|
data.AppID = aid
|
||||||
|
data.ExpiredAt = time.Now().Add(time.Hour * 72)
|
||||||
|
if opts.Device != nil {
|
||||||
|
data.Device = *opts.Device
|
||||||
|
}
|
||||||
|
data.Ip = x.GetRemoteIp()
|
||||||
|
logv.AssertError(cfg.DB().Create(data).Error)
|
||||||
|
claim := &auth.Claims{}
|
||||||
|
claim.IssuedAt = jwt.NewNumericDate(time.Now())
|
||||||
|
claim.Issuer = cfg.Config.ID
|
||||||
|
claim.ID = data.ID
|
||||||
|
claim.AID = aid
|
||||||
|
claim.UID = user.ID
|
||||||
|
claim.Name = user.Username
|
||||||
|
claim.Icon = user.Icon
|
||||||
|
claim.ExpiresAt = jwt.NewNumericDate(data.ExpiredAt)
|
||||||
|
if user.Nickname != "" {
|
||||||
|
claim.Name = user.Nickname
|
||||||
|
}
|
||||||
|
return auth.GenJwt(claim)
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// crud.go
|
||||||
|
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||||
|
// 2025-05-06 15:12
|
||||||
|
// Distributed under terms of the MIT license.
|
||||||
|
//
|
||||||
|
|
||||||
|
package role
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/veypi/OneBD/rest"
|
||||||
|
"oa/cfg"
|
||||||
|
M "oa/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Router.Post("/", userRolePost)
|
||||||
|
|
||||||
|
type postOpts struct {
|
||||||
|
UserID string `json:"user_id" parse:"path"`
|
||||||
|
RoleID string `json:"role_id" parse:"json"`
|
||||||
|
AppID string `json:"app_id" parse:"json"`
|
||||||
|
Status string `json:"status" parse:"json"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func userRolePost(x *rest.X) (any, error) {
|
||||||
|
opts := &postOpts{}
|
||||||
|
err := x.Parse(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data := &M.UserRole{}
|
||||||
|
|
||||||
|
data.UserID = opts.UserID
|
||||||
|
data.RoleID = opts.RoleID
|
||||||
|
data.AppID = opts.AppID
|
||||||
|
data.Status = opts.Status
|
||||||
|
err = cfg.DB().Create(data).Error
|
||||||
|
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// del.go
|
||||||
|
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||||
|
// 2025-05-06 15:24
|
||||||
|
// Distributed under terms of the MIT license.
|
||||||
|
//
|
||||||
|
|
||||||
|
package role
|
||||||
|
|
||||||
|
import (
|
||||||
|
"oa/cfg"
|
||||||
|
"oa/models"
|
||||||
|
|
||||||
|
"github.com/veypi/OneBD/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Router.Delete("/:id", userRoleDelete)
|
||||||
|
|
||||||
|
func userRoleDelete(x *rest.X) (any, error) {
|
||||||
|
id := x.Params.Get("id")
|
||||||
|
if id == "" {
|
||||||
|
return nil, rest.ErrArgInvalid.WithArgs("id")
|
||||||
|
}
|
||||||
|
data := &models.UserRole{}
|
||||||
|
err := cfg.DB().Where("id = ?", id).Delete(data).Error
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
//
|
||||||
|
// get.go
|
||||||
|
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||||
|
// 2025-05-06 15:21
|
||||||
|
// Distributed under terms of the MIT license.
|
||||||
|
//
|
||||||
|
|
||||||
|
package role
|
||||||
|
|
||||||
|
import (
|
||||||
|
"oa/cfg"
|
||||||
|
M "oa/models"
|
||||||
|
|
||||||
|
"github.com/veypi/OneBD/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Router.Get("/:id", `
|
||||||
|
get user role
|
||||||
|
`, userRoleGet)
|
||||||
|
|
||||||
|
func userRoleGet(x *rest.X) (any, error) {
|
||||||
|
data := &M.UserRole{}
|
||||||
|
err := cfg.DB().Where("id = ?", x.Params.Get("id")).First(data).Error
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Router.Get("/", `
|
||||||
|
list user roles
|
||||||
|
`, userRoleList)
|
||||||
|
|
||||||
|
type listOpts struct {
|
||||||
|
UserID *string `json:"user_id" parse:"path"`
|
||||||
|
RoleID *string `json:"role_id" parse:"query"`
|
||||||
|
AppID *string `json:"app_id" parse:"query"`
|
||||||
|
Status *string `json:"status" parse:"query"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func userRoleList(x *rest.X) (any, error) {
|
||||||
|
opts := &listOpts{}
|
||||||
|
err := x.Parse(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data := make([]*struct {
|
||||||
|
M.UserRole
|
||||||
|
Username string `json:"username"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
Icon string `json:"icon"`
|
||||||
|
RoleName string `json:"role_name"`
|
||||||
|
}, 0, 10)
|
||||||
|
// data := make([]*M.UserRole, 0, 10)
|
||||||
|
|
||||||
|
query := cfg.DB().Debug().Table("user_roles").Select("user_roles.*,users.username,users.nickname,users.icon,roles.name as role_name").
|
||||||
|
Joins("JOIN users ON users.id = user_roles.user_id").
|
||||||
|
Joins("JOIN roles ON roles.id = user_roles.role_id")
|
||||||
|
if opts.UserID != nil && *opts.UserID != "-" {
|
||||||
|
query = query.Where("user_id LIKE ?", opts.UserID)
|
||||||
|
}
|
||||||
|
if opts.RoleID != nil {
|
||||||
|
query = query.Where("role_id LIKE ?", opts.RoleID)
|
||||||
|
}
|
||||||
|
if opts.AppID != nil {
|
||||||
|
query = query.Where("app_id LIKE ?", opts.AppID)
|
||||||
|
}
|
||||||
|
if opts.Status != nil {
|
||||||
|
query = query.Where("status LIKE ?", opts.Status)
|
||||||
|
}
|
||||||
|
err = query.Scan(&data).Error
|
||||||
|
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
//
|
||||||
|
// init.go
|
||||||
|
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||||
|
// 2025-05-06 15:12
|
||||||
|
// Distributed under terms of the MIT license.
|
||||||
|
//
|
||||||
|
|
||||||
|
package role
|
||||||
|
|
||||||
|
import "github.com/veypi/OneBD/rest"
|
||||||
|
|
||||||
|
var Router = rest.NewRouter()
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// crud.go
|
||||||
|
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||||
|
// 2025-05-06 15:12
|
||||||
|
// Distributed under terms of the MIT license.
|
||||||
|
//
|
||||||
|
|
||||||
|
package role
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/veypi/OneBD/rest"
|
||||||
|
"oa/cfg"
|
||||||
|
M "oa/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type patchOpts struct {
|
||||||
|
ID string `json:"id" parse:"path"`
|
||||||
|
Status *string `json:"status" parse:"json"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Router.Patch("/:id", userRolePatch)
|
||||||
|
|
||||||
|
func userRolePatch(x *rest.X) (any, error) {
|
||||||
|
opts := &patchOpts{}
|
||||||
|
err := x.Parse(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data := &M.UserRole{}
|
||||||
|
|
||||||
|
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
optsMap := make(map[string]interface{})
|
||||||
|
if opts.Status != nil {
|
||||||
|
optsMap["status"] = opts.Status
|
||||||
|
}
|
||||||
|
err = cfg.DB().Model(data).Updates(optsMap).Error
|
||||||
|
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
@ -1,111 +0,0 @@
|
|||||||
package user
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/veypi/OneBD/rest"
|
|
||||||
"oa/cfg"
|
|
||||||
M "oa/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
r := Router.SubRouter(":user_id/user_role")
|
|
||||||
r.Delete("/:user_role_id", userRoleDelete)
|
|
||||||
r.Get("/:user_role_id", userRoleGet)
|
|
||||||
r.Get("/", userRoleList)
|
|
||||||
r.Patch("/:user_role_id", userRolePatch)
|
|
||||||
r.Post("/", userRolePost)
|
|
||||||
}
|
|
||||||
|
|
||||||
func userRoleList(x *rest.X) (any, error) {
|
|
||||||
opts := &M.UserRoleList{}
|
|
||||||
err := x.Parse(opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data := make([]*struct {
|
|
||||||
M.UserRole
|
|
||||||
Username string `json:"username"`
|
|
||||||
Nickname string `json:"nickname"`
|
|
||||||
Icon string `json:"icon"`
|
|
||||||
RoleName string `json:"role_name"`
|
|
||||||
}, 0, 10)
|
|
||||||
// data := make([]*M.UserRole, 0, 10)
|
|
||||||
|
|
||||||
query := cfg.DB().Debug().Table("user_roles").Select("user_roles.*,users.username,users.nickname,users.icon,roles.name as role_name").
|
|
||||||
Joins("JOIN users ON users.id = user_roles.user_id").
|
|
||||||
Joins("JOIN roles ON roles.id = user_roles.role_id")
|
|
||||||
if opts.UserID != nil && *opts.UserID != "-" {
|
|
||||||
query = query.Where("user_id LIKE ?", opts.UserID)
|
|
||||||
}
|
|
||||||
if opts.RoleID != nil {
|
|
||||||
query = query.Where("role_id LIKE ?", opts.RoleID)
|
|
||||||
}
|
|
||||||
if opts.AppID != nil {
|
|
||||||
query = query.Where("app_id LIKE ?", opts.AppID)
|
|
||||||
}
|
|
||||||
if opts.Status != nil {
|
|
||||||
query = query.Where("status LIKE ?", opts.Status)
|
|
||||||
}
|
|
||||||
err = query.Scan(&data).Error
|
|
||||||
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
func userRolePost(x *rest.X) (any, error) {
|
|
||||||
opts := &M.UserRolePost{}
|
|
||||||
err := x.Parse(opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data := &M.UserRole{}
|
|
||||||
|
|
||||||
data.UserID = opts.UserID
|
|
||||||
data.RoleID = opts.RoleID
|
|
||||||
data.AppID = opts.AppID
|
|
||||||
data.Status = opts.Status
|
|
||||||
err = cfg.DB().Create(data).Error
|
|
||||||
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
func userRoleGet(x *rest.X) (any, error) {
|
|
||||||
opts := &M.UserRoleGet{}
|
|
||||||
err := x.Parse(opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data := &M.UserRole{}
|
|
||||||
|
|
||||||
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
|
||||||
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
func userRolePatch(x *rest.X) (any, error) {
|
|
||||||
opts := &M.UserRolePatch{}
|
|
||||||
err := x.Parse(opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data := &M.UserRole{}
|
|
||||||
|
|
||||||
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
optsMap := make(map[string]interface{})
|
|
||||||
if opts.Status != nil {
|
|
||||||
optsMap["status"] = opts.Status
|
|
||||||
}
|
|
||||||
err = cfg.DB().Model(data).Updates(optsMap).Error
|
|
||||||
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
func userRoleDelete(x *rest.X) (any, error) {
|
|
||||||
opts := &M.UserRoleDelete{}
|
|
||||||
err := x.Parse(opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data := &M.UserRole{}
|
|
||||||
|
|
||||||
err = cfg.DB().Where("id = ?", opts.ID).Delete(data).Error
|
|
||||||
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import ()
|
|
||||||
|
|
||||||
type UserGet struct {
|
|
||||||
ID string `json:"id" gorm:"primaryKey;type:varchar(32)" parse:"path@user_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserPatch struct {
|
|
||||||
ID string `json:"id" gorm:"primaryKey;type:varchar(32)" parse:"path@user_id"`
|
|
||||||
Username *string `json:"username" gorm:"varchar(100);unique;default:not null" parse:"json"`
|
|
||||||
Nickname *string `json:"nickname" parse:"json"`
|
|
||||||
Icon *string `json:"icon" parse:"json"`
|
|
||||||
Email *string `json:"email" gorm:"varchar(20);unique;default:null" parse:"json"`
|
|
||||||
Phone *string `json:"phone" gorm:"varchar(50);unique;default:null" parse:"json"`
|
|
||||||
Status *uint `json:"status" parse:"json"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserDelete struct {
|
|
||||||
ID string `json:"id" gorm:"primaryKey;type:varchar(32)" parse:"path@user_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserPost struct {
|
|
||||||
Username string `json:"username" gorm:"varchar(100);unique;default:not null" parse:"json"`
|
|
||||||
Nickname *string `json:"nickname" parse:"json"`
|
|
||||||
Icon *string `json:"icon" parse:"json"`
|
|
||||||
Email *string `json:"email" gorm:"varchar(20);unique;default:null" parse:"json"`
|
|
||||||
Phone *string `json:"phone" gorm:"varchar(50);unique;default:null" parse:"json"`
|
|
||||||
Salt string `json:"salt" gorm:"varchar(32)" parse:"json"`
|
|
||||||
Code string `json:"code" gorm:"varchar(128)" parse:"json"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserList struct {
|
|
||||||
Username *string `json:"username" gorm:"type:varchar(100);unique;default:not null" parse:"query"`
|
|
||||||
Nickname *string `json:"nickname" gorm:"type:varchar(100)" parse:"query"`
|
|
||||||
Email *string `json:"email" gorm:"unique;type:varchar(50);default:null" parse:"query"`
|
|
||||||
Phone *string `json:"phone" gorm:"type:varchar(30);unique;default:null" parse:"query"`
|
|
||||||
Status *uint `json:"status" parse:"query"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserRoleGet struct {
|
|
||||||
ID string `json:"id" gorm:"primaryKey;type:varchar(32)" parse:"path@user_role_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserRolePatch struct {
|
|
||||||
ID string `json:"id" gorm:"primaryKey;type:varchar(32)" parse:"path@user_role_id"`
|
|
||||||
Status *string `json:"status" parse:"json"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserRoleDelete struct {
|
|
||||||
ID string `json:"id" gorm:"primaryKey;type:varchar(32)" parse:"path@user_role_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserRoleList struct {
|
|
||||||
UserID *string `json:"user_id" parse:"path"`
|
|
||||||
RoleID *string `json:"role_id" parse:"query"`
|
|
||||||
AppID *string `json:"app_id" parse:"query"`
|
|
||||||
Status *string `json:"status" parse:"query"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserRolePost struct {
|
|
||||||
UserID string `json:"user_id" parse:"path"`
|
|
||||||
RoleID string `json:"role_id" parse:"json"`
|
|
||||||
AppID string `json:"app_id" parse:"json"`
|
|
||||||
Status string `json:"status" parse:"json"`
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue