update router params

v3
veypi 4 months ago
parent 67b92662d1
commit 1880a6ce0f

@ -13,24 +13,18 @@ import (
)
type createOpts struct {
AppID string `json:"app_id"`
UserID *string `json:"user_id"`
RoleID *string `json:"role_id"`
ResourceID *string `json:"resource_id"`
Name string `json:"name"`
TID string `json:"tid"`
Level uint `json:"level"`
AppID string `json:"app_id" parse:"json"`
UserID *string `json:"user_id" parse:"json"`
RoleID *string `json:"role_id" parse:"json"`
ResourceID *string `json:"resource_id" parse:"json"`
Name string `json:"name" parse:"json"`
TID string `json:"tid" parse:"json"`
Level uint `json:"level" parse:"json"`
}
var _ = Router.Post("/", createAccess)
func createAccess(x *vigo.X) (any, error) {
// 解析请求参数
opts := &createOpts{}
if err := x.Parse(opts); err != nil {
return nil, err
}
var _ = Router.Post("/", "创建访问权限", createAccess)
func createAccess(x *vigo.X, opts *createOpts) (any, error) {
// 创建新记录
access := models.Access{
AppID: opts.AppID,

@ -13,18 +13,12 @@ import (
)
type deleteOpts struct {
ID string `parse:"path"`
ID string `parse:"path@id"`
}
var _ = Router.Delete("/:id", deleteAccess)
func deleteAccess(x *vigo.X) (any, error) {
// 解析路径参数
opts := &deleteOpts{}
if err := x.Parse(opts); err != nil {
return nil, err
}
var _ = Router.Delete("/{id}", "删除访问权限", deleteAccess)
func deleteAccess(x *vigo.X, opts *deleteOpts) (any, error) {
// 查找记录
var access models.Access
if err := cfg.DB().Where("id = ?", opts.ID).First(&access).Error; err != nil {

@ -12,18 +12,16 @@ import (
"github.com/vyes-ai/vigo"
)
var _ = Router.Get("/:id", getAccess)
func getAccess(x *vigo.X) (any, error) {
// 获取路径参数
id := x.Params.Get("id")
if id == "" {
return nil, vigo.NewError("ID不能为空").WithCode(400)
type getIDReq struct {
ID string `parse:"path@id"`
}
var _ = Router.Get("/{id}", "获取访问权限详情", getAccess)
func getAccess(x *vigo.X, req *getIDReq) (any, error) {
// 查询数据库
var access models.Access
err := cfg.DB().Where("id = ?", id).First(&access).Error
err := cfg.DB().Where("id = ?", req.ID).First(&access).Error
if err != nil {
return nil, vigo.NewError("未找到资源").WithCode(404)
}

@ -26,15 +26,9 @@ type listResponse struct {
Items []models.Access `json:"items"`
}
var _ = Router.Get("/", listAccess)
func listAccess(x *vigo.X) (any, error) {
// 解析查询参数
opts := &listOpts{}
if err := x.Parse(opts); err != nil {
return nil, err
}
var _ = Router.Get("/", "获取访问权限列表", listAccess)
func listAccess(x *vigo.X, opts *listOpts) (any, error) {
// 构建查询
db := cfg.DB().Model(&models.Access{})
if opts.AppID != "" {

@ -13,21 +13,15 @@ import (
)
type updateOpts struct {
ID string `parse:"path"`
Name *string `json:"name"`
TID *string `json:"tid"`
Level *uint `json:"level"`
ID string `parse:"path@id"`
Name *string `json:"name" parse:"json"`
TID *string `json:"tid" parse:"json"`
Level *uint `json:"level" parse:"json"`
}
var _ = Router.Patch("/:id", updateAccess)
func updateAccess(x *vigo.X) (any, error) {
// 解析请求参数
opts := &updateOpts{}
if err := x.Parse(opts); err != nil {
return nil, err
}
var _ = Router.Patch("/{id}", "更新访问权限", updateAccess)
func updateAccess(x *vigo.X, opts *updateOpts) (any, error) {
// 查找记录
var access models.Access
if err := cfg.DB().Where("id = ?", opts.ID).First(&access).Error; err != nil {

@ -13,37 +13,36 @@ import (
"gorm.io/gorm"
)
var _ = Router.Get("/:app_id/key", auth.Check("app", "app_id", auth.DoDelete), appKey)
func appKey(x *vigo.X) (any, error) {
id := x.Params.Get("app_id")
if id == "" {
return nil, vigo.ErrArgMissing.WithArgs("app_id")
type appIDReq struct {
AppID string `parse:"path@app_id"`
}
var _ = Router.Get("/{app_id}/key", "重置应用密钥", auth.Check("app", "app_id", auth.DoDelete), appKey)
func appKey(x *vigo.X, req *appIDReq) (any, error) {
data := &models.App{}
data.ID = id
data.ID = req.AppID
key := utils.RandSeq(32)
// err := cfg.DB().Where("id = ?", x.Params.GetStr("app_id")).First(data).Error
err := cfg.DB().Model(data).Update("key", key).Error
return key, err
}
var _ = Router.Delete("/:app_id", auth.Check("app", "app_id", auth.DoDelete), appDelete)
var _ = Router.Delete("/{app_id}", "删除应用", auth.Check("app", "app_id", auth.DoDelete), appDelete)
func appDelete(x *vigo.X) (any, error) {
func appDelete(x *vigo.X, req *appIDReq) (any, error) {
data := &models.App{}
err := cfg.DB().Where("id = ?", x.Params.Get("app_id")).Delete(data).Error
err := cfg.DB().Where("id = ?", req.AppID).Delete(data).Error
return data, err
}
var _ = Router.Get("/:app_id", auth.Check("app", "app_id", auth.DoRead), appGet)
var _ = Router.Get("/{app_id}", "获取应用详情", auth.Check("app", "app_id", auth.DoRead), appGet)
func appGet(x *vigo.X) (any, error) {
func appGet(x *vigo.X, req *appIDReq) (any, error) {
data := &models.App{}
err := cfg.DB().Where("id = ?", x.Params.Get("app_id")).First(data).Error
err := cfg.DB().Where("id = ?", req.AppID).First(data).Error
return data, err
}
@ -52,14 +51,9 @@ type listOpts struct {
Name *string `json:"name" parse:"query"`
}
var _ = Router.Get("/", appList)
var _ = Router.Get("/", "获取应用列表", appList)
func appList(x *vigo.X) (any, error) {
opts := &listOpts{}
err := x.Parse(opts)
if err != nil {
return nil, err
}
func appList(x *vigo.X, opts *listOpts) (any, error) {
data := make([]*struct {
models.App
UserStatus string `json:"user_status"`
@ -90,34 +84,32 @@ type postOpts struct {
InitUrl string `json:"init_url" parse:"json"`
}
var _ = Router.Post("/", auth.Check("app", "", auth.DoCreate), appPost)
var _ = Router.Post("/", "创建应用", auth.Check("app", "", auth.DoCreate), appPost)
func appPost(x *vigo.X) (any, error) {
opts := &postOpts{}
err := x.Parse(opts)
if err != nil {
return nil, err
}
func appPost(x *vigo.X, opts *postOpts) (any, error) {
data := &models.App{}
data.Name = opts.Name
data.Icon = opts.Icon
if data.Icon == "" {
data.Icon = fmt.Sprintf("https://public.veypi.com/img/avatar/%04d.jpg", rand.New(rand.NewSource(time.Now().UnixNano())).Intn(220))
}
data.Typ = opts.Typ
data.Des = ""
if opts.Des != nil {
data.Des = *opts.Des
}
err = cfg.DB().Transaction(func(tx *gorm.DB) error {
data.Typ = opts.Typ
data.Status = opts.Status
data.InitUrl = opts.InitUrl
err := cfg.DB().Transaction(func(tx *gorm.DB) error {
data.Key = utils.RandSeq(32)
err := tx.Create(data).Error
if err != nil {
return err
}
uid, _ := x.Get("uid").(string)
au := &models.AppUser{
AppID: data.ID,
UserID: x.Request.Context().Value("uid").(string),
UserID: uid,
Status: models.AUSTATUS_OK,
}
return tx.Create(au).Error
@ -127,27 +119,22 @@ func appPost(x *vigo.X) (any, error) {
}
type patchOpts struct {
ID string `json:"id" gorm:"primaryKey;type:varchar(32)" parse:"path@app_id"`
ID string `json:"id" gorm:"primaryKey;type:varchar(36)" parse:"path@app_id"`
Name *string `json:"name" parse:"json"`
Icon *string `json:"icon" parse:"json"`
Des *string `json:"des" parse:"json"`
Typ *string `json:"typ" gorm:"default:auto" parse:"json"`
InitRoleID *string `json:"init_role_id" gorm:"index;type:varchar(32)" parse:"json"`
InitRoleID *string `json:"init_role_id" gorm:"index;type:varchar(36)" parse:"json"`
Status *string `json:"status" gorm:"default:ok" parse:"json"`
InitUrl *string `json:"init_url" parse:"json"`
}
var _ = Router.Patch("/:app_id", auth.Check("app", "app_id", auth.DoUpdate), appPatch)
var _ = Router.Patch("/{app_id}", "更新应用", auth.Check("app", "app_id", auth.DoUpdate), appPatch)
func appPatch(x *vigo.X) (any, error) {
opts := &patchOpts{}
err := x.Parse(opts)
if err != nil {
return nil, err
}
func appPatch(x *vigo.X, opts *patchOpts) (any, error) {
data := &models.App{}
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
err := cfg.DB().Where("id = ?", opts.ID).First(data).Error
if err != nil {
return nil, err
}

@ -7,19 +7,14 @@ import (
)
type createOpts struct {
AppID string `parse:"path"`
UserID string `json:"user_id"`
Status string `json:"status" default:"ok"`
AppID string `parse:"path@app_id"`
UserID string `json:"user_id" parse:"json"`
Status string `json:"status" parse:"json" default:"ok"`
}
var _ = Router.Post("/", createAppUser)
func createAppUser(x *vigo.X) (any, error) {
opts := &createOpts{}
if err := x.Parse(opts); err != nil {
return nil, err
}
var _ = Router.Post("/", "创建应用用户", createAppUser)
func createAppUser(x *vigo.X, opts *createOpts) (any, error) {
appUser := &models.AppUser{
AppID: opts.AppID,
UserID: opts.UserID,

@ -7,18 +7,13 @@ import (
)
type deleteOpts struct {
AppID string `parse:"path"`
UserID string `parse:"path"`
AppID string `parse:"path@app_id"`
UserID string `parse:"path@user_id"`
}
var _ = Router.Delete("/:user_id", deleteAppUser)
func deleteAppUser(x *vigo.X) (any, error) {
opts := &deleteOpts{}
if err := x.Parse(opts); err != nil {
return nil, err
}
var _ = Router.Delete("/{user_id}", "删除应用用户", deleteAppUser)
func deleteAppUser(x *vigo.X, opts *deleteOpts) (any, error) {
appUser := &models.AppUser{}
if err := cfg.DB().Where("app_id = ? AND user_id = ?", opts.AppID, opts.UserID).First(appUser).Error; err != nil {
return nil, vigo.NewError("app_user not found").WithCode(404)

@ -6,18 +6,16 @@ import (
"github.com/vyes-ai/vigo"
)
var _ = Router.Get("/:user_id", getAppUser)
func getAppUser(x *vigo.X) (any, error) {
appID := x.Params.Get("app_id")
userID := x.Params.Get("user_id")
if appID == "" || userID == "" {
return nil, vigo.NewError("app_id or user_id is empty").WithCode(400)
type appUserIDReq struct {
AppID string `parse:"path@app_id"`
UserID string `parse:"path@user_id"`
}
var _ = Router.Get("/{user_id}", "获取应用用户详情", getAppUser)
func getAppUser(x *vigo.X, req *appUserIDReq) (any, error) {
appUser := &models.AppUser{}
err := cfg.DB().Where("app_id = ? AND user_id = ?", appID, userID).First(appUser).Error
err := cfg.DB().Where("app_id = ? AND user_id = ?", req.AppID, req.UserID).First(appUser).Error
if err != nil {
return nil, vigo.NewError("app_user not found").WithCode(404)
}

@ -7,7 +7,7 @@ import (
)
type listOpts struct {
AppID string `parse:"path"`
AppID string `parse:"path@app_id"`
Page int `parse:"query" default:"1"`
PageSize int `parse:"query" default:"20"`
Status string `parse:"query" default:""`
@ -18,14 +18,9 @@ type listResponse struct {
Items []*models.AppUser `json:"items"`
}
var _ = Router.Get("/", listAppUsers)
func listAppUsers(x *vigo.X) (any, error) {
opts := &listOpts{}
if err := x.Parse(opts); err != nil {
return nil, err
}
var _ = Router.Get("/", "获取应用用户列表", listAppUsers)
func listAppUsers(x *vigo.X, opts *listOpts) (any, error) {
query := cfg.DB().Model(&models.AppUser{}).Where("app_id = ?", opts.AppID)
if opts.Status != "" {

@ -7,19 +7,14 @@ import (
)
type updateOpts struct {
AppID string `parse:"path"`
UserID string `parse:"path"`
Status *string `json:"status"`
AppID string `parse:"path@app_id"`
UserID string `parse:"path@user_id"`
Status *string `json:"status" parse:"json"`
}
var _ = Router.Patch("/:user_id", updateAppUser)
func updateAppUser(x *vigo.X) (any, error) {
opts := &updateOpts{}
if err := x.Parse(opts); err != nil {
return nil, err
}
var _ = Router.Patch("/{user_id}", "更新应用用户", updateAppUser)
func updateAppUser(x *vigo.X, opts *updateOpts) (any, error) {
appUser := &models.AppUser{}
if err := cfg.DB().Where("app_id = ? AND user_id = ?", opts.AppID, opts.UserID).First(appUser).Error; err != nil {
return nil, vigo.NewError("app_user not found").WithCode(404)

@ -8,19 +8,20 @@
package app
import (
"github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/api/app/access"
"github.com/veypi/OneAuth/api/app/app_user"
"github.com/veypi/OneAuth/api/app/resource"
"github.com/veypi/OneAuth/api/app/role"
"github.com/veypi/OneAuth/libs/auth"
"github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
"github.com/vyes-ai/vigo/contrib/crud"
)
var Router = vigo.NewRouter()
var appRouter = Router.SubRouter(":app_id").UseBefore(auth.Check("app", ":app_id", 2))
var appRouter = Router.SubRouter("/{app_id}").Use(auth.Check("app", "{app_id}", 2))
func init() {
crud.All(appRouter.SubRouter("resource"), cfg.DB, models.Resource{})
crud.All(appRouter.SubRouter("user"), cfg.DB, models.AppUser{})
crud.All(appRouter.SubRouter("role"), cfg.DB, models.Role{})
crud.All(appRouter.SubRouter("access"), cfg.DB, models.Access{})
appRouter.Extend("/resource", resource.Router)
appRouter.Extend("/user", app_user.Router)
appRouter.Extend("/role", role.Router)
appRouter.Extend("/access", access.Router)
}

@ -6,21 +6,15 @@ import (
"github.com/veypi/OneAuth/models"
)
var _ = Router.Post("/", createResource)
type createOpts struct {
AppID string `json:"app_id" parse:"path"`
AppID string `json:"app_id" parse:"path@app_id"`
Name string `json:"name" parse:"json"`
Des string `json:"des" parse:"json"`
}
func createResource(x *vigo.X) (any, error) {
// 解析参数
opts := &createOpts{}
if err := x.Parse(opts); err != nil {
return nil, err
}
var _ = Router.Post("/", "创建资源", createResource)
func createResource(x *vigo.X, opts *createOpts) (any, error) {
// 创建资源对象
resource := &models.Resource{
AppID: opts.AppID,

@ -6,19 +6,13 @@ import (
"github.com/veypi/OneAuth/models"
)
var _ = Router.Delete("/:resource_id", deleteResource)
type deleteOpts struct {
ResourceID string `parse:"path"`
ResourceID string `parse:"path@resource_id"`
}
func deleteResource(x *vigo.X) (any, error) {
// 解析参数
opts := &deleteOpts{}
if err := x.Parse(opts); err != nil {
return nil, err
}
var _ = Router.Delete("/{resource_id}", "删除资源", deleteResource)
func deleteResource(x *vigo.X, opts *deleteOpts) (any, error) {
// 查找资源
resource := &models.Resource{}
if err := cfg.DB().Where("id = ?", opts.ResourceID).First(resource).Error; err != nil {

@ -6,18 +6,16 @@ import (
"github.com/veypi/OneAuth/models"
)
var _ = Router.Get("/:resource_id", getResource)
func getResource(x *vigo.X) (any, error) {
// 获取路径参数
resourceID := x.Params.Get("resource_id")
if resourceID == "" {
return nil, vigo.NewError("resource_id is required").WithCode(400)
type getIDReq struct {
ID string `parse:"path@resource_id"`
}
var _ = Router.Get("/{resource_id}", "获取资源详情", getResource)
func getResource(x *vigo.X, req *getIDReq) (any, error) {
// 查询数据库
resource := &models.Resource{}
err := cfg.DB().Where("id = ?", resourceID).First(resource).Error
err := cfg.DB().Where("id = ?", req.ID).First(resource).Error
if err != nil {
return nil, vigo.NewError("resource not found").WithCode(404)
}

@ -18,15 +18,9 @@ type listResponse struct {
Items []*models.Resource `json:"items"`
}
var _ = Router.Get("/", listResources)
func listResources(x *vigo.X) (any, error) {
// 解析参数
opts := &listOpts{}
if err := x.Parse(opts); err != nil {
return nil, err
}
var _ = Router.Get("/", "获取资源列表", listResources)
func listResources(x *vigo.X, opts *listOpts) (any, error) {
// 构建查询
db := cfg.DB().Model(&models.Resource{})
if opts.AppID != "" {

@ -6,21 +6,15 @@ import (
"github.com/veypi/OneAuth/models"
)
var _ = Router.Patch("/:resource_id", updateResource)
type updateOpts struct {
ResourceID string `parse:"path"`
ResourceID string `parse:"path@resource_id"`
Name *string `json:"name" parse:"json"`
Des *string `json:"des" parse:"json"`
}
func updateResource(x *vigo.X) (any, error) {
// 解析参数
opts := &updateOpts{}
if err := x.Parse(opts); err != nil {
return nil, err
}
var _ = Router.Patch("/{resource_id}", "更新资源", updateResource)
func updateResource(x *vigo.X, opts *updateOpts) (any, error) {
// 查找资源
resource := &models.Resource{}
if err := cfg.DB().Where("id = ?", opts.ResourceID).First(resource).Error; err != nil {
@ -43,5 +37,4 @@ func updateResource(x *vigo.X) (any, error) {
}
}
return resource, nil
}

@ -6,21 +6,15 @@ import (
"github.com/vyes-ai/vigo"
)
var _ = Router.Post("/", createRole)
type createOpts struct {
AppID string `parse:"path"` // 应用 ID
Name string `json:"name"` // 角色名称
Des string `json:"des"` // 角色描述
AppID string `parse:"path@app_id"` // 应用 ID
Name string `json:"name" parse:"json"` // 角色名称
Des string `json:"des" parse:"json"` // 角色描述
}
func createRole(x *vigo.X) (any, error) {
// 解析参数
opts := &createOpts{}
if err := x.Parse(opts); err != nil {
return nil, err
}
var _ = Router.Post("/", "创建角色", createRole)
func createRole(x *vigo.X, opts *createOpts) (any, error) {
// 创建角色
role := &models.Role{
AppID: opts.AppID,

@ -6,19 +6,13 @@ import (
"github.com/veypi/OneAuth/models"
)
var _ = Router.Delete("/:role_id", deleteRole)
type deleteOpts struct {
RoleID string `parse:"path"` // 角色 ID
RoleID string `parse:"path@role_id"` // 角色 ID
}
func deleteRole(x *vigo.X) (any, error) {
// 解析参数
opts := &deleteOpts{}
if err := x.Parse(opts); err != nil {
return nil, err
}
var _ = Router.Delete("/{role_id}", "删除角色", deleteRole)
func deleteRole(x *vigo.X, opts *deleteOpts) (any, error) {
// 查找角色
role := &models.Role{}
if err := cfg.DB().Where("id = ?", opts.RoleID).First(role).Error; err != nil {

@ -6,18 +6,16 @@ import (
"github.com/veypi/OneAuth/models"
)
var _ = Router.Get("/:role_id", getRole)
func getRole(x *vigo.X) (any, error) {
// 获取角色 ID
roleID := x.Params.Get("role_id")
if roleID == "" {
return nil, vigo.NewError("role_id is required").WithCode(400)
type getIDReq struct {
ID string `parse:"path@role_id"`
}
var _ = Router.Get("/{role_id}", "获取角色详情", getRole)
func getRole(x *vigo.X, req *getIDReq) (any, error) {
// 查询数据库
role := &models.Role{}
err := cfg.DB().Where("id = ?", roleID).First(role).Error
err := cfg.DB().Where("id = ?", req.ID).First(role).Error
if err != nil {
return nil, vigo.NewError("role not found").WithCode(404)
}

@ -6,9 +6,10 @@ import (
"github.com/vyes-ai/vigo"
)
var _ = Router.Get("/", listRoles)
var _ = Router.Get("/", "获取角色列表", listRoles)
type listOpts struct {
AppID string `parse:"path@app_id"`
Page int `parse:"query" default:"1"`
PageSize int `parse:"query" default:"20"`
Keyword string `parse:"query" default:""`
@ -21,15 +22,9 @@ type listResponse struct {
Items []*models.Role `json:"items"`
}
func listRoles(x *vigo.X) (any, error) {
// 解析参数
opts := &listOpts{}
if err := x.Parse(opts); err != nil {
return nil, err
}
func listRoles(x *vigo.X, opts *listOpts) (any, error) {
// 构建查询
db := cfg.DB().Model(&models.Role{})
db := cfg.DB().Model(&models.Role{}).Where("app_id = ?", opts.AppID)
query := db
// 应用关键词搜索

@ -6,21 +6,15 @@ import (
"github.com/vyes-ai/vigo"
)
var _ = Router.Patch("/:role_id", updateRole)
type updateOpts struct {
RoleID string `parse:"path"` // 角色 ID
Name *string `json:"name"` // 可选,角色名称
Des *string `json:"des"` // 可选,角色描述
RoleID string `parse:"path@role_id"` // 角色 ID
Name *string `json:"name" parse:"json"` // 可选,角色名称
Des *string `json:"des" parse:"json"` // 可选,角色描述
}
func updateRole(x *vigo.X) (any, error) {
// 解析参数
opts := &updateOpts{}
if err := x.Parse(opts); err != nil {
return nil, err
}
var _ = Router.Patch("/{role_id}", "更新角色", updateRole)
func updateRole(x *vigo.X, opts *updateOpts) (any, error) {
// 查找角色
role := &models.Role{}
if err := cfg.DB().Where("id = ?", opts.RoleID).First(role).Error; err != nil {

@ -14,23 +14,33 @@ import (
"github.com/veypi/OneAuth/api/user"
"github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/libs/auth"
"github.com/veypi/OneAuth/oauth"
"github.com/vyes-ai/vigo"
"github.com/vyes-ai/vigo/contrib/common"
)
var Router = vigo.NewRouter().UseBefore(auth.CheckJWT).UseAfter(common.JsonResponse, common.JsonErrorResponse)
var Router = vigo.NewRouter()
var (
_ = Router.Extend("user", user.Router)
_ = Router.Extend("token", token.Router)
_ = Router.Extend("app", app.Router)
_ = Router.Extend("sms", sms.Router)
_ = Router.Get("cfg", vigo.SkipBefore, getCfg)
)
func init() {
// 注册全局中间件
Router.Use(auth.CheckJWT)
Router.After(common.JsonResponse, common.JsonErrorResponse)
// 注册子资源路由
Router.Extend("user", user.Router)
Router.Extend("token", token.Router)
Router.Extend("app", app.Router)
Router.Extend("sms", sms.Router)
Router.Extend("oauth", oauth.Router)
var _ = Router.Any("*", vigo.SkipBefore, func(x *vigo.X) error {
// 注册基础接口
Router.Get("/cfg", "获取配置信息", vigo.SkipBefore, getCfg)
// 404 处理
Router.Any("/**", vigo.SkipBefore, func(x *vigo.X) error {
return vigo.ErrNotFound
})
}
func getCfg(x *vigo.X) any {
return map[string]any{

@ -15,29 +15,24 @@ import (
// SendCodeRequest 发送验证码请求
type SendCodeRequest struct {
Phone string `json:"phone"`
Region string `json:"region"`
Purpose string `json:"purpose"`
TemplateID string `json:"template_id"`
Phone string `json:"phone" parse:"json"`
Region string `json:"region" parse:"json"`
Purpose string `json:"purpose" parse:"json"`
TemplateID string `json:"template_id" parse:"json"`
}
// SendCodeResponse 发送验证码响应
type SendCodeResponse struct {
ID uint `json:"id"`
ID string `json:"id"`
Phone string `json:"phone"`
ExpiresAt time.Time `json:"expires_at"`
Interval int `json:"interval"` // 下次可发送间隔(秒)
}
var _ = Router.Post("/", vigo.SkipBefore, limiter.NewAdvancedRequestLimiter(time.Minute*3, 5, time.Second*30), sendCode)
var _ = Router.Post("/", "发送验证码", vigo.SkipBefore, limiter.NewAdvancedRequestLimiter(time.Minute*3, 5, time.Second*30), sendCode)
// SendCode 发送验证码
func sendCode(x *vigo.X) (any, error) {
req := &SendCodeRequest{}
err := x.Parse(req)
if err != nil {
return nil, err
}
func sendCode(x *vigo.X, req *SendCodeRequest) (any, error) {
// 1. 验证手机号
normalizedPhone := utils.NormalizePhoneNumber(req.Phone)
if !utils.ValidatePhoneNumber(normalizedPhone) {
@ -101,10 +96,9 @@ func sendCode(x *vigo.X) (any, error) {
if err != nil {
return nil, err
}
return &SendCodeResponse{
ID: smsCode.ID,
Phone: normalizedPhone,
Phone: smsCode.Phone,
ExpiresAt: smsCode.ExpiresAt,
Interval: int(cfg.Config.SMS.Global.SendInterval.Seconds()),
}, nil

@ -14,6 +14,7 @@ import (
"github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/libs/utils"
"github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
"gorm.io/gorm"
)
@ -30,9 +31,9 @@ func VerifyCode(Phone, Code, Region, Purpose string) error {
if err != nil {
if err == gorm.ErrRecordNotFound {
return fmt.Errorf("verification code not found or already used")
return vigo.NewError("verification code not found or already used").WithCode(404)
}
return fmt.Errorf("failed to query sms code: %w", err)
return vigo.NewError("failed to query sms code").WithError(err)
}
// 2. 检查验证码是否过期

@ -8,23 +8,18 @@ import (
"github.com/vyes-ai/vigo"
)
// 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 *vigo.X) (any, error) {
opts := &patchOpts{}
err := x.Parse(opts)
if err != nil {
return nil, err
}
var _ = Router.Patch("/{token_id}", "更新 Token", tokenPatch)
func tokenPatch(x *vigo.X, opts *patchOpts) (any, error) {
data := &models.Token{}
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
err := cfg.DB().Where("id = ?", opts.ID).First(data).Error
if err != nil {
return nil, err
}
@ -40,10 +35,14 @@ func tokenPatch(x *vigo.X) (any, error) {
return data, err
}
var _ = Router.Delete("/:token_id", tokenDelete)
type deleteOpts struct {
ID string `parse:"path@token_id"`
}
var _ = Router.Delete("/{token_id}", "删除 Token", tokenDelete)
func tokenDelete(x *vigo.X) (any, error) {
func tokenDelete(x *vigo.X, opts *deleteOpts) (any, error) {
data := &models.Token{}
err := cfg.DB().Where("id = ?", x.Params.Get("token_id")).Delete(data).Error
err := cfg.DB().Where("id = ?", opts.ID).Delete(data).Error
return data, err
}

@ -24,21 +24,16 @@ type postOpts struct {
Refresh *string `json:"refresh" parse:"json"`
Typ *string `json:"typ" parse:"json"`
AppID *string `json:"app_id" gorm:"index;type:varchar(32)" parse:"json"`
AppID *string `json:"app_id" gorm:"index;type:varchar(36)" parse:"json"`
ExpiredAt *time.Time `json:"expired_at" parse:"json"`
OverPerm *string `json:"over_perm" parse:"json"`
Device *string `json:"device" parse:"json"`
}
var _ = Router.Post("/", vigo.SkipBefore, tokenPost)
var _ = Router.Post("/", "创建 Token", vigo.SkipBefore, tokenPost)
// for user login app
func tokenPost(x *vigo.X) (any, error) {
opts := &postOpts{}
err := x.Parse(opts)
if err != nil {
return nil, err
}
func tokenPost(x *vigo.X, opts *postOpts) (any, error) {
aid := cfg.Config.ID
if opts.AppID != nil && *opts.AppID != "" {
aid = *opts.AppID
@ -127,7 +122,7 @@ func tokenPost(x *vigo.X) (any, error) {
HttpOnly: true, // 是否仅限 HTTP(S) 访问
Secure: false, // 是否通过安全连接传输 Cookie
}
http.SetCookie(x, cookie)
http.SetCookie(x.ResponseWriter(), cookie)
return token, nil
} else {
return nil, vigo.ErrArgInvalid

@ -17,38 +17,32 @@ type getOpts struct {
ID string `json:"id" parse:"path@token_id"`
}
// var _ = Router.Get("/:token_id", tokenGet)
var _ = Router.Get("/{token_id}", "获取 Token 详情", tokenGet)
func tokenGet(x *vigo.X) (any, error) {
opts := &getOpts{}
err := x.Parse(opts)
if err != nil {
return nil, err
}
func tokenGet(x *vigo.X, opts *getOpts) (any, error) {
data := &models.Token{}
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
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"`
UserID string `json:"user_id" gorm:"index;type:varchar(36)" parse:"query"`
AppID string `json:"app_id" gorm:"index;type:varchar(36)" parse:"query"`
}
// var _ = Router.Get("/", tokenList)
var _ = Router.Get("/", "获取 Token 列表", tokenList)
func tokenList(x *vigo.X) (any, error) {
opts := &listOpts{}
err := x.Parse(opts)
if err != nil {
return nil, err
}
func tokenList(x *vigo.X, opts *listOpts) (any, error) {
data := make([]*models.Token, 0, 10)
query := cfg.DB()
if opts.UserID != "" {
query = query.Where("user_id = ?", opts.UserID)
}
if opts.AppID != "" {
query = query.Where("app_id = ?", opts.AppID)
err = query.Limit(opts.Limit).Find(&data).Error
}
err := query.Limit(opts.Limit).Find(&data).Error
return data, err
}

@ -23,7 +23,7 @@ import (
"gorm.io/gorm"
)
var _ = Router.Post("/", vigo.SkipBefore, publicLimits, userPost)
var _ = Router.Post("/", "用户注册", vigo.SkipBefore, publicLimits, userPost)
type postOpts struct {
Username string `json:"username" gorm:"varchar(100);unique;default:not null" parse:"json"`
@ -37,19 +37,13 @@ type postOpts struct {
Email *string `json:"email" gorm:"varchar(20);unique;default:null" parse:"json"`
}
func userPost(x *vigo.X) (any, error) {
opts := &postOpts{}
err := x.Parse(opts)
if err != nil {
return nil, err
}
func userPost(x *vigo.X, opts *postOpts) (any, error) {
data := &models.User{}
data.ID = strings.ReplaceAll(uuid.New().String(), "-", "")
if cfg.Config.SMS.Enable {
data.Phone = opts.Region + opts.Phone
data.Region = opts.Region
err = sms.VerifyCode(opts.Phone, opts.VerifyCode, opts.Region, "signup")
err := sms.VerifyCode(opts.Phone, opts.VerifyCode, opts.Region, "signup")
if err != nil {
return nil, vigo.ErrArgInvalid.WithArgs("verify code").WithString(err.Error())
}
@ -65,12 +59,16 @@ func userPost(x *vigo.X) (any, error) {
return nil, vigo.ErrArgInvalid.WithArgs("code")
}
code = utils.PKCS7Padding(code, 32)
// We need ID for encryption, but it's not generated yet.
// We can generate it manually here since vigo.Model doesn't auto-generate it before Create
data.ID = strings.ReplaceAll(uuid.New().String(), "-", "")
data.Code, err = utils.AesEncrypt([]byte(data.ID), code, []byte(data.Salt))
if err != nil {
return nil, vigo.ErrArgInvalid.WithArgs("code")
}
ncode, err := utils.AesDecrypt([]byte(data.Code), code, []byte(data.Salt))
if err != nil || ncode != data.ID {
if err != nil || string(ncode) != data.ID {
return nil, vigo.ErrInternalServer.WithString("code decrypt failed")
}
if opts.Nickname != nil {

@ -8,16 +8,13 @@
package user
import (
"github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/api/user/role"
"github.com/veypi/OneAuth/libs/auth"
"github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
"github.com/vyes-ai/vigo/contrib/crud"
)
var Router = vigo.NewRouter()
var userRouter = Router.SubRouter("/:user_id").UseBefore(userGet)
func init() {
crud.All(Router.SubRouter("/:user_id/user_role"), cfg.DB, models.UserRole{}).UseBefore(auth.Check("user", "", 4))
Router.Extend("/{user_id}/user_role", role.Router).Use(auth.Check("user", "", 4))
}

@ -24,7 +24,7 @@ import (
var publicLimits = limiter.NewAdvancedRequestLimiter(time.Minute*5, 20, time.Second*3, nil).Limit
var _ = Router.Post("/login",
var _ = Router.Post("/login", "用户登录",
vigo.SkipBefore, publicLimits,
userLogin)
@ -42,14 +42,9 @@ type loginOpts struct {
Device *string `json:"device" parse:"json"`
}
func userLogin(x *vigo.X) (any, error) {
func userLogin(x *vigo.X, opts *loginOpts) (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 := ""
@ -64,7 +59,7 @@ func userLogin(x *vigo.X) (any, error) {
default:
query = query.Where("username = ?", opts.UserName)
}
err = query.First(user).Error
err := query.First(user).Error
if err != nil {
return nil, vigo.ErrNotFound
}
@ -99,19 +94,25 @@ func userLogin(x *vigo.X) (any, error) {
if opts.Device != nil {
data.Device = *opts.Device
}
data.Ip = x.GetRemoteIP()
logv.AssertError(cfg.DB().Create(data).Error)
err = cfg.DB().Create(data).Error
if err != nil {
return nil, err
}
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.ID = data.ID
claim.ExpiresAt = jwt.NewNumericDate(data.ExpiredAt)
claim.Name = user.Username
if user.Nickname != "" {
claim.Name = user.Nickname
}
return auth.GenJwt(claim)
claim.Icon = user.Icon
token, err := auth.GenJwt(claim)
return map[string]any{
"token": token,
"expired_at": data.ExpiredAt,
}, err
}

@ -13,7 +13,7 @@ import (
"github.com/vyes-ai/vigo"
)
var _ = Router.Post("/", userRolePost)
var _ = Router.Post("/", "创建用户角色", userRolePost)
type postOpts struct {
UserID string `json:"user_id" parse:"path"`
@ -22,19 +22,14 @@ type postOpts struct {
Status string `json:"status" parse:"json"`
}
func userRolePost(x *vigo.X) (any, error) {
opts := &postOpts{}
err := x.Parse(opts)
if err != nil {
return nil, err
}
func userRolePost(x *vigo.X, opts *postOpts) (any, error) {
data := &models.UserRole{}
data.UserID = opts.UserID
data.RoleID = opts.RoleID
data.AppID = opts.AppID
data.Status = opts.Status
err = cfg.DB().Create(data).Error
err := cfg.DB().Create(data).Error
return data, err
}

@ -13,14 +13,14 @@ import (
"github.com/vyes-ai/vigo"
)
var _ = Router.Delete("/:id", userRoleDelete)
func userRoleDelete(x *vigo.X) (any, error) {
id := x.Params.Get("id")
if id == "" {
return nil, vigo.ErrArgInvalid.WithArgs("id")
type deleteIDReq struct {
ID string `parse:"path@id"`
}
var _ = Router.Delete("/{id}", "删除用户角色", userRoleDelete)
func userRoleDelete(x *vigo.X, req *deleteIDReq) (any, error) {
data := &models.UserRole{}
err := cfg.DB().Where("id = ?", id).Delete(data).Error
err := cfg.DB().Where("id = ?", req.ID).Delete(data).Error
return data, err
}

@ -14,33 +14,28 @@ import (
"github.com/vyes-ai/vigo"
)
var _ = Router.Get("/:id", `
get user role
`, userRoleGet)
type getIDReq struct {
ID string `parse:"path@id"`
}
var _ = Router.Get("/{id}", "获取用户角色详情", userRoleGet)
func userRoleGet(x *vigo.X) (any, error) {
func userRoleGet(x *vigo.X, req *getIDReq) (any, error) {
data := &models.UserRole{}
err := cfg.DB().Where("id = ?", x.Params.Get("id")).First(data).Error
err := cfg.DB().Where("id = ?", req.ID).First(data).Error
return data, err
}
var _ = Router.Get("/", `
list user roles
`, userRoleList)
type listOpts struct {
UserID *string `json:"user_id" parse:"path"`
UserID *string `json:"user_id" parse:"path@user_id"`
RoleID *string `json:"role_id" parse:"query"`
AppID *string `json:"app_id" parse:"query"`
Status *string `json:"status" parse:"query"`
}
func userRoleList(x *vigo.X) (any, error) {
opts := &listOpts{}
err := x.Parse(opts)
if err != nil {
return nil, err
}
var _ = Router.Get("/", "获取用户角色列表", userRoleList)
func userRoleList(x *vigo.X, opts *listOpts) (any, error) {
data := make([]*struct {
models.UserRole
Username string `json:"username"`
@ -65,7 +60,7 @@ func userRoleList(x *vigo.X) (any, error) {
if opts.Status != nil {
query = query.Where("status LIKE ?", opts.Status)
}
err = query.Scan(&data).Error
err := query.Scan(&data).Error
return data, err
}

@ -14,24 +14,19 @@ import (
)
type patchOpts struct {
ID string `json:"id" parse:"path"`
ID string `json:"id" parse:"path@id"`
Status *string `json:"status" parse:"json"`
}
var _ = Router.Patch("/:id", userRolePatch)
var _ = Router.Patch("/{id}", "更新用户角色", userRolePatch)
func userRolePatch(x *vigo.X) (any, error) {
opts := &patchOpts{}
err := x.Parse(opts)
if err != nil {
return nil, err
}
func userRolePatch(x *vigo.X, opts *patchOpts) (any, error) {
data := &models.UserRole{}
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
err := cfg.DB().Where("id = ?", opts.ID).First(data).Error
if err != nil {
return nil, err
}
optsMap := make(map[string]interface{})
optsMap := make(map[string]any)
if opts.Status != nil {
optsMap["status"] = opts.Status
}

@ -8,30 +8,34 @@ import (
"github.com/vyes-ai/vigo/contrib/crud"
)
var _ = Router.Delete("/:user_id", auth.Check("user", "user_id", auth.DoDelete, checkOwner), userDelete)
type userIDReq struct {
UserID string `parse:"path@user_id"`
}
var _ = Router.Delete("/{user_id}", "删除用户", auth.Check("user", "user_id", auth.DoDelete, checkOwner), userDelete)
func userDelete(x *vigo.X) (any, error) {
func userDelete(x *vigo.X, req *userIDReq) (any, error) {
data := &models.User{}
err := cfg.DB().Where("id = ?", x.Params.Get("user_id")).Delete(data).Error
err := cfg.DB().Where("id = ?", req.UserID).Delete(data).Error
return data, err
}
var _ = Router.Get("/:user_id", auth.Check("user", "user_id", auth.DoRead, checkOwner), vigo.DiliverData)
var _ = Router.Get("/{user_id}", "获取用户详情", auth.Check("user", "user_id", auth.DoRead, checkOwner), userGet)
func checkOwner(x *vigo.X, data any) bool {
u, ok1 := data.(*models.User)
return ok1 && u.ID == x.Params.Get("user_id")
return ok1 && u.ID == x.PathParams.Get("user_id")
}
func userGet(x *vigo.X) (any, error) {
func userGet(x *vigo.X, req *userIDReq) (any, error) {
data := &models.User{}
err := cfg.DB().Where("id = ?", x.Params.Get("user_id")).First(data).Error
err := cfg.DB().Where("id = ?", req.UserID).First(data).Error
return data, err
}
var _ = Router.Get("/", "list user", auth.Check("user", "", auth.DoUpdate), crud.List(cfg.DB, &models.User{}))
var _ = Router.Get("/", "用户列表", auth.Check("user", "", auth.DoUpdate), crud.List(cfg.DB, &models.User{}))
var _ = Router.Patch("/:user_id", auth.Check("user", "user_id", auth.DoUpdate, checkOwner), userPatch)
var _ = Router.Patch("/{user_id}", "更新用户", auth.Check("user", "user_id", auth.DoUpdate, checkOwner), userPatch)
type patchOpts struct {
ID string `json:"id" parse:"path@user_id"`
@ -44,13 +48,12 @@ type patchOpts struct {
Status *uint `json:"status" parse:"json"`
}
func userPatch(x *vigo.X, args any) (any, error) {
opts := &patchOpts{}
err := x.Parse(opts)
func userPatch(x *vigo.X, opts *patchOpts) (any, error) {
data := &models.User{}
err := cfg.DB().Where("id = ?", opts.ID).First(data).Error
if err != nil {
return nil, err
return nil, vigo.ErrNotFound
}
data := args.(*models.User)
optsMap := make(map[string]any)
if opts.Username != nil {

@ -69,7 +69,5 @@ func runWeb() error {
return err
}
server.SetRouter(OneAuth.Router)
server.EnableAI()
server.Router().Print()
return server.Run()
}

@ -8,8 +8,9 @@ package OneAuth
import (
"embed"
"github.com/veypi/OneAuth/api"
"github.com/veypi/vyes-ui"
vyesui "github.com/veypi/vyes-ui"
"github.com/vyes-ai/vigo"
"github.com/vyes-ai/vigo/contrib/cors"
"github.com/vyes-ai/vigo/contrib/vyes"
@ -20,9 +21,9 @@ var Router = vigo.NewRouter()
//go:embed ui/*
var uifs embed.FS
var (
_ = Router.Extend("v", vyesui.Router)
_ = Router.Extend("api", api.Router)
_ = Router.SubRouter("/*path").UseBefore(cors.AllowAny)
_ = vyes.WrapUI(Router, uifs)
)
func init() {
Router.Extend("v", vyesui.Router)
Router.Extend("api", api.Router)
Router.SubRouter("/*path").Use(cors.AllowAny)
vyes.WrapUI(Router, uifs)
}

@ -102,8 +102,11 @@ func Check(target string, pid string, l AuthLevel, funcs ...CustomCheckFunc) fun
if strings.HasPrefix(pid, "@") {
tid, _ = x.Get(pid[1:]).(string)
}
if strings.HasPrefix(pid, "{") && strings.HasSuffix(pid, "}") {
tid = x.PathParams.Get(pid[1 : len(pid)-1])
}
if strings.HasPrefix(pid, ":") {
tid = x.Params.Get(pid[1:])
tid = x.PathParams.Get(pid[1:])
}
if !claims.Access.Check(target, tid, l) {
err = AuthNoPerm

@ -1,6 +1,7 @@
package models
import (
"github.com/vyes-ai/vigo"
"github.com/vyes-ai/vigo/logv"
"gorm.io/gorm"
)
@ -11,13 +12,13 @@ const AUSTATUS_APPLYING = "applying"
const AUSTATUS_REJECT = "reject"
type App struct {
BaseModel
vigo.Model
Name string `json:"name" parse:"json"`
Icon string `json:"icon" parse:"json"`
Des string `json:"des" parse:"json"`
Typ string `json:"typ" gorm:"default:public" parse:"json"`
Status string `json:"status" gorm:"default:ok" parse:"json"`
InitRoleID *string `json:"init_role_id" gorm:"index;type:varchar(32);default: null" parse:"json"`
InitRoleID *string `json:"init_role_id" gorm:"index;type:varchar(36);default: null" parse:"json"`
InitRole *Role `json:"init_role" gorm:"foreignKey:InitRoleID;references:ID"`
InitUrl string `json:"init_url" parse:"json"`
UserCount uint `json:"user_count" default:"0"`
@ -25,7 +26,7 @@ type App struct {
}
type AppUser struct {
BaseModel
vigo.Model
AppID string `json:"app_id" parse:"path"`
App *App `json:"app" gorm:"foreignKey:AppID;references:ID"`
@ -68,7 +69,7 @@ func (m *AppUser) AfterUpdate(tx *gorm.DB) error {
}
type Resource struct {
BaseModel
vigo.Model
AppID string `json:"app_id" parse:"path@app_id"`
App *App `json:"-" gorm:"foreignKey:AppID;references:ID"`
Name string `json:"name" parse:"json"`
@ -76,7 +77,7 @@ type Resource struct {
}
type Role struct {
BaseModel
vigo.Model
AppID string `json:"app_id" parse:"path@app_id"`
App *App `json:"-" gorm:"foreignKey:AppID;references:ID"`
Name string `json:"name" parse:"json"`
@ -86,17 +87,17 @@ type Role struct {
}
type Access struct {
BaseModel
AppID string `json:"app_id" gorm:"index;type:varchar(32)" parse:"path@app_id"`
vigo.Model
AppID string `json:"app_id" gorm:"index;type:varchar(36)" parse:"path@app_id"`
App *App `json:"-" gorm:"foreignKey:AppID;references:ID"`
UserID *string `json:"user_id" gorm:"index;type:varchar(32);default: null" parse:"json"`
UserID *string `json:"user_id" gorm:"index;type:varchar(36);default: null" parse:"json"`
User *User `json:"-" gorm:"foreignKey:UserID;references:ID"`
RoleID *string `json:"role_id" gorm:"index;type:varchar(32);default: null" parse:"json"`
RoleID *string `json:"role_id" gorm:"index;type:varchar(36);default: null" parse:"json"`
Role *Role `json:"-" gorm:"foreignKey:RoleID;references:ID"`
ResourceID *string `json:"resource_id" gorm:"index;type:varchar(32);default: null" parse:"json"`
ResourceID *string `json:"resource_id" gorm:"index;type:varchar(36);default: null" parse:"json"`
Resource *Resource `json:"-" gorm:"foreignKey:ResourceID;references:ID"`
Name string `json:"name" parse:"json"`

@ -7,34 +7,35 @@
package models
import (
"github.com/veypi/OneAuth/cfg"
"strings"
"time"
"github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/oauth"
"github.com/google/uuid"
"github.com/vyes-ai/vigo/contrib/dbmodels"
"github.com/vyes-ai/vigo"
"github.com/vyes-ai/vigo/logv"
"gorm.io/gorm"
)
type BaseModel struct {
ID string `json:"id" gorm:"primaryKey;type:varchar(32)" methods:"get,patch,delete" parse:"path"`
CreatedAt time.Time `json:"created_at" methods:"*list" parse:"query"`
UpdatedAt time.Time `json:"updated_at" methods:"*list" parse:"query"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
func (m *BaseModel) BeforeCreate(tx *gorm.DB) error {
if m.ID == "" {
m.ID = strings.ReplaceAll(uuid.New().String(), "-", "")
}
return nil
}
var AllModels = &dbmodels.ModelList{}
var AllModels = &vigo.ModelList{}
func init() {
AllModels.Append(User{}, AppUser{}, Resource{}, Access{}, Role{}, UserRole{}, Token{}, App{}, SMSCode{}, SMSLog{})
AllModels.Append(
oauth.User{},
oauth.UserLoginLog{},
oauth.OAuthClient{},
oauth.OAuthAuthorizationCode{},
oauth.OAuthAccessToken{},
oauth.OAuthRefreshToken{},
oauth.OAuthScope{},
oauth.OAuthClientScope{},
oauth.OAuthProvider{},
oauth.OAuthAccount{},
oauth.UserToken{},
oauth.UserSession{},
oauth.OAuthUserConsent{},
)
}
func Migrate() error {
@ -66,7 +67,7 @@ func InitDB() error {
for rName, l := range roles {
role := &Role{}
logv.AssertError(cfg.DB().Where("app_id = ? AND name = ?", app.ID, rName).Attrs(&Role{
BaseModel: BaseModel{
Model: vigo.Model{
ID: strings.ReplaceAll(uuid.New().String(), "-", ""),
},
AppID: app.ID,
@ -87,5 +88,5 @@ func InitDB() error {
if app.InitRoleID == nil {
logv.AssertError(cfg.DB().Model(app).Update("init_role_id", adminID).Error)
}
return nil
return oauth.InitializeOAuthData(cfg.DB())
}

@ -1,26 +1,24 @@
package models
import (
"gorm.io/gorm"
"time"
"github.com/vyes-ai/vigo"
)
// SMSCode 短信验证码记录
type SMSCode struct {
ID uint `gorm:"primarykey" json:"id"`
Phone string `gorm:"index;not null" json:"phone"` // 手机号
Code string `gorm:"not null" json:"code"` // 验证码
Region string `gorm:"index;not null" json:"region"` // 区域
Purpose string `gorm:"index;not null" json:"purpose"` // 用途(注册、登录、重置密码等)
vigo.Model
Phone string `gorm:"index;type:varchar(20);not null" json:"phone"` // 手机号
Code string `gorm:"type:varchar(10);not null" json:"code"` // 验证码
Region string `gorm:"index;type:varchar(10);not null" json:"region"` // 区域
Purpose string `gorm:"index;type:varchar(20);not null" json:"purpose"` // 用途(注册、登录、重置密码等)
Status CodeStatus `gorm:"default:0" json:"status"` // 状态
ExpiresAt time.Time `gorm:"index;not null" json:"expires_at"` // 过期时间
UsedAt *time.Time `gorm:"index" json:"used_at"` // 使用时间
Attempts int `gorm:"default:0" json:"attempts"` // 验证尝试次数
SendCount int `gorm:"default:1" json:"send_count"` // 发送次数
RemoteIP string `gorm:"index" json:"remote_ip"` // 远程IP地址
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
RemoteIP string `gorm:"index;type:varchar(50)" json:"remote_ip"` // 远程IP地址
}
// CodeStatus 验证码状态
@ -35,18 +33,15 @@ const (
// SMSLog 短信发送日志
type SMSLog struct {
ID uint `gorm:"primarykey" json:"id"`
Phone string `gorm:"index;not null" json:"phone"`
Region string `gorm:"index;not null" json:"region"`
Provider string `gorm:"not null" json:"provider"`
MessageID string `gorm:"index" json:"message_id"` // 服务商返回的消息ID
Content string `json:"content"` // 短信内容
Status string `gorm:"not null" json:"status"` // 发送状态
Error string `json:"error"` // 错误信息
vigo.Model
Phone string `gorm:"index;type:varchar(20);not null" json:"phone"`
Region string `gorm:"index;type:varchar(10);not null" json:"region"`
Provider string `gorm:"type:varchar(20);not null" json:"provider"`
MessageID string `gorm:"index;type:varchar(100)" json:"message_id"` // 服务商返回的消息ID
Content string `gorm:"type:text" json:"content"` // 短信内容
Status string `gorm:"type:varchar(20);not null" json:"status"` // 发送状态
Error string `gorm:"type:text" json:"error"` // 错误信息
Cost float64 `json:"cost"` // 费用
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
}
// IsExpired 检查验证码是否过期

@ -1,15 +1,18 @@
package models
import "time"
import (
"github.com/vyes-ai/vigo"
"time"
)
// refresh token由oa 秘钥签发,有效期长, 存储在token表
// app token, 由app 秘钥签发,有效期短, 不存储
// OverPerm 非oa应用获取oa数据的权限由用户设定
type Token struct {
BaseModel
UserID string `json:"user_id" gorm:"index;type:varchar(32)" parse:"json"`
vigo.Model
UserID string `json:"user_id" gorm:"index;type:varchar(36)" parse:"json"`
User *User `json:"-"`
AppID string `json:"app_id" gorm:"index;type:varchar(32)" parse:"json"`
AppID string `json:"app_id" gorm:"index;type:varchar(36)" parse:"json"`
App *App `json:"-"`
ExpiredAt time.Time `json:"expired_at" parse:"json"`
OverPerm string `json:"over_perm" parse:"json"`

@ -1,6 +1,7 @@
package models
import (
"github.com/vyes-ai/vigo"
"gorm.io/gorm"
)
@ -8,7 +9,7 @@ import (
// salt 32 hex / 16 byte / 128 bit
// code 64 hex / 32 byte / 256 bit
type User struct {
BaseModel
vigo.Model
Username string `json:"username" gorm:"type:varchar(100);unique;default:not null" parse:"json"`
Nickname string `json:"nickname" gorm:"type:varchar(100)" parse:"json"`
Icon string `json:"icon" parse:"json"`
@ -24,7 +25,7 @@ type User struct {
}
type UserRole struct {
BaseModel
vigo.Model
UserID string `json:"user_id" parse:"path@user_id"`
User *User `json:"-" gorm:"foreignKey:UserID;references:ID"`

@ -15,13 +15,13 @@ import (
// AuthorizeRequest 授权请求参数
type AuthorizeRequest struct {
ResponseType string `form:"response_type" binding:"required"`
ClientID string `form:"client_id" binding:"required"`
RedirectURI string `form:"redirect_uri" binding:"required"`
Scope string `form:"scope"`
State string `form:"state"`
CodeChallenge string `form:"code_challenge"`
CodeChallengeMethod string `form:"code_challenge_method"`
ResponseType string `json:"response_type" parse:"query"`
ClientID string `json:"client_id" parse:"query"`
RedirectURI string `json:"redirect_uri" parse:"query"`
Scope string `json:"scope" parse:"query"`
State string `json:"state" parse:"query"`
CodeChallenge string `json:"code_challenge" parse:"query"`
CodeChallengeMethod string `json:"code_challenge_method" parse:"query"`
}
// AuthorizeResponse 授权响应
@ -34,22 +34,17 @@ type AuthorizeResponse struct {
}
// handleAuthorize 处理OAuth授权请求
func handleAuthorize(x *vigo.X) error {
args := &AuthorizeRequest{}
if err := x.Parse(args); err != nil {
return vigo.NewError("参数解析失败").WithError(err).WithCode(400)
}
func handleAuthorize(x *vigo.X, args *AuthorizeRequest) (any, error) {
db := cfg.DB()
// 1. 验证响应类型
if args.ResponseType != ResponseTypeCode {
errorURI := BuildErrorRedirectURI(args.RedirectURI, ErrorUnsupportedResponseType, "不支持的响应类型", args.State)
return x.JSON(&AuthorizeResponse{
return &AuthorizeResponse{
Error: "unsupported_response_type",
ErrorDesc: "不支持的响应类型",
RedirectURI: errorURI,
})
}, nil
}
// 2. 验证客户端
@ -57,28 +52,28 @@ func handleAuthorize(x *vigo.X) error {
if err := db.Where("client_id = ? AND is_active = ?", args.ClientID, true).First(&client).Error; err != nil {
if err == gorm.ErrRecordNotFound {
errorURI := BuildErrorRedirectURI(args.RedirectURI, ErrorInvalidClient, "无效的客户端", args.State)
return x.JSON(&AuthorizeResponse{
return &AuthorizeResponse{
Error: "invalid_client",
ErrorDesc: "无效的客户端",
RedirectURI: errorURI,
})
}, nil
}
return vigo.NewError("数据库查询失败").WithError(err).WithCode(500)
return nil, vigo.NewError("数据库查询失败").WithError(err).WithCode(500)
}
// 3. 验证重定向URI
if !client.IsRedirectURIValid(args.RedirectURI) {
return vigo.NewError("无效的重定向URI").WithCode(400)
return nil, vigo.NewError("无效的重定向URI").WithCode(400)
}
// 4. 验证作用域
if args.Scope != "" && !client.HasScope(args.Scope) {
errorURI := BuildErrorRedirectURI(args.RedirectURI, ErrorInvalidScope, "无效的授权范围", args.State)
return x.JSON(&AuthorizeResponse{
return &AuthorizeResponse{
Error: "invalid_scope",
ErrorDesc: "无效的授权范围",
RedirectURI: errorURI,
})
}, nil
}
// TODO: 在实际应用中,这里应该:
@ -87,48 +82,45 @@ func handleAuthorize(x *vigo.X) error {
// 3. 用户同意后生成授权码
// 为了演示,这里假设用户已登录且同意授权
// 假设当前用户ID (实际应从sessionJWT token中获取)
// 假设当前用户ID (实际应从session or JWT token中获取)
userID := "demo-user-id"
// 5. 生成授权码
code, err := generateRandomString(32)
if err != nil {
errorURI := BuildErrorRedirectURI(args.RedirectURI, ErrorServerError, "授权码生成失败", args.State)
return x.JSON(&AuthorizeResponse{
return &AuthorizeResponse{
Error: "server_error",
ErrorDesc: "授权码生成失败",
RedirectURI: errorURI,
})
}, nil
}
// 6. 创建授权码记录
// 6. 保存授权码
authCode := &OAuthAuthorizationCode{
Code: code,
ClientID: client.ID,
UserID: userID,
RedirectURI: args.RedirectURI,
Scope: args.Scope,
ExpiresAt: time.Now().Add(10 * time.Minute), // 授权码10分钟有效
CodeChallenge: args.CodeChallenge,
CodeChallengeMethod: args.CodeChallengeMethod,
ExpiresAt: time.Now().Add(DefaultAuthorizationCodeExpiry),
Used: false,
}
if err := db.Create(authCode).Error; err != nil {
errorURI := BuildErrorRedirectURI(args.RedirectURI, ErrorServerError, "授权码保存失败", args.State)
return x.JSON(&AuthorizeResponse{
return &AuthorizeResponse{
Error: "server_error",
ErrorDesc: "授权码保存失败",
RedirectURI: errorURI,
})
}, nil
}
// 7. 构建成功重定向URI
redirectURI := BuildRedirectURI(args.RedirectURI, authCode.Code, args.State)
return x.JSON(&AuthorizeResponse{
Code: authCode.Code,
// 7. 返回授权码重定向
return &AuthorizeResponse{
Code: code,
State: args.State,
RedirectURI: redirectURI,
})
RedirectURI: BuildRedirectURI(args.RedirectURI, code, args.State),
}, nil
}

@ -15,13 +15,13 @@ var Router = vigo.NewRouter()
func init() {
// OAuth 授权端点
var _ = Router.Get("/authorize", `OAuth授权端点 - 获取授权码`, AuthorizeRequest{}, handleAuthorize)
var _ = Router.Get("/authorize", "OAuth授权端点 - 获取授权码", vigo.SkipBefore, handleAuthorize)
// OAuth 令牌端点
var _ = Router.Post("/token", `OAuth令牌端点 - 用授权码换取令牌或刷新令牌`, TokenRequest{}, handleToken)
var _ = Router.Post("/token", "OAuth令牌端点 - 用授权码换取令牌或刷新令牌", vigo.SkipBefore, handleToken)
// OAuth 撤销端点
var _ = Router.Post("/revoke", `OAuth撤销端点 - 撤销访问令牌或刷新令牌`, RevokeRequest{}, handleRevoke)
var _ = Router.Post("/revoke", "OAuth撤销端点 - 撤销访问令牌或刷新令牌", vigo.SkipBefore, handleRevoke)
}
// InitializeOAuthData 初始化OAuth相关的基础数据

@ -13,10 +13,10 @@ import (
// RevokeRequest 撤销令牌请求参数
type RevokeRequest struct {
Token string `form:"token" binding:"required"`
TokenTypeHint string `form:"token_type_hint"` // access_token 或 refresh_token
ClientID string `form:"client_id"`
ClientSecret string `form:"client_secret"`
Token string `parse:"form" binding:"required"`
TokenTypeHint string `parse:"form"` // access_token 或 refresh_token
ClientID string `parse:"form"`
ClientSecret string `parse:"form"`
}
// RevokeResponse 撤销令牌响应
@ -26,14 +26,9 @@ type RevokeResponse struct {
}
// handleRevoke 处理OAuth撤销请求
func handleRevoke(x *vigo.X) error {
args := &RevokeRequest{}
if err := x.Parse(args); err != nil {
return vigo.NewError("参数解析失败").WithError(err).WithCode(400)
}
func handleRevoke(x *vigo.X, args *RevokeRequest) (any, error) {
if args.Token == "" {
return vigo.NewError("令牌不能为空").WithCode(400)
return nil, vigo.NewError("令牌不能为空").WithCode(400)
}
db := cfg.DB()
@ -58,8 +53,8 @@ func handleRevoke(x *vigo.X) error {
// 根据OAuth 2.0规范,即使令牌不存在也返回成功
// 这样可以防止攻击者通过响应差异推断令牌是否存在
return x.JSON(&RevokeResponse{
return &RevokeResponse{
Message: "令牌撤销成功",
Success: true,
})
}, nil
}

@ -19,16 +19,16 @@ import (
// TokenRequest 令牌请求参数
type TokenRequest struct {
GrantType string `form:"grant_type" binding:"required"`
Code string `form:"code"`
RedirectURI string `form:"redirect_uri"`
ClientID string `form:"client_id"`
ClientSecret string `form:"client_secret"`
RefreshToken string `form:"refresh_token"`
CodeVerifier string `form:"code_verifier"`
Username string `form:"username"` // for password grant
Password string `form:"password"` // for password grant
Scope string `form:"scope"` // for password grant
GrantType string `json:"grant_type" parse:"form"`
Code string `json:"code" parse:"form"`
RedirectURI string `json:"redirect_uri" parse:"form"`
ClientID string `json:"client_id" parse:"form"`
ClientSecret string `json:"client_secret" parse:"form"`
RefreshToken string `json:"refresh_token" parse:"form"`
CodeVerifier string `json:"code_verifier" parse:"form"`
Username string `json:"username" parse:"form"` // for password grant
Password string `json:"password" parse:"form"` // for password grant
Scope string `json:"scope" parse:"form"` // for password grant
}
// TokenResponse 令牌响应
@ -41,12 +41,7 @@ type TokenResponse struct {
}
// handleToken 处理OAuth令牌请求
func handleToken(x *vigo.X) error {
args := &TokenRequest{}
if err := x.Parse(args); err != nil {
return vigo.NewError("参数解析失败").WithError(err).WithCode(400)
}
func handleToken(x *vigo.X, args *TokenRequest) (any, error) {
db := cfg.DB()
switch args.GrantType {
@ -57,7 +52,7 @@ func handleToken(x *vigo.X) error {
case GrantTypePassword:
return handlePasswordGrant(db, x, args)
default:
return vigo.NewError("不支持的授权类型").WithCode(400)
return nil, vigo.NewError("不支持的授权类型").WithCode(400)
}
}

@ -9,13 +9,13 @@ package oauth
import (
"time"
"github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
"gorm.io/gorm"
)
// User 用户表
type User struct {
models.BaseModel
vigo.Model
Username string `json:"username" gorm:"uniqueIndex;not null;size:50;comment:用户名""`
Email string `json:"email" gorm:"uniqueIndex;size:100;comment:邮箱地址"`
Phone string `json:"phone" gorm:"uniqueIndex;size:20;comment:手机号码"`
@ -42,7 +42,7 @@ type User struct {
// Role 角色表
type Role struct {
models.BaseModel
vigo.Model
Name string `json:"name" gorm:"uniqueIndex;not null;size:50;comment:角色名称" validate:"required"`
DisplayName string `json:"display_name" gorm:"size:100;comment:显示名称"`
Description string `json:"description" gorm:"type:text;comment:角色描述"`
@ -57,7 +57,7 @@ type Role struct {
// Permission 权限表
type Permission struct {
models.BaseModel
vigo.Model
Name string `json:"name" gorm:"uniqueIndex;not null;size:100;comment:权限名称" validate:"required"`
DisplayName string `json:"display_name" gorm:"size:100;comment:显示名称"`
Description string `json:"description" gorm:"type:text;comment:权限描述"`
@ -98,7 +98,7 @@ type RolePermission struct {
// UserLoginLog 用户登录日志表
type UserLoginLog struct {
models.BaseModel
vigo.Model
UserID string `json:"user_id" gorm:"not null;type:varchar(32);index;comment:用户ID"`
IPAddress string `json:"ip_address" gorm:"size:45;comment:IP地址"`
UserAgent string `json:"user_agent" gorm:"type:text;comment:用户代理"`
@ -113,9 +113,6 @@ type UserLoginLog struct {
// GORM Hooks
func (u *User) BeforeCreate(tx *gorm.DB) error {
if err := u.BaseModel.BeforeCreate(tx); err != nil {
return err
}
if u.Locale == "" {
u.Locale = "zh-CN"
}
@ -175,7 +172,7 @@ func (r *Role) HasPermission(resource, action string) bool {
// OAuthClient OAuth客户端表
type OAuthClient struct {
models.BaseModel
vigo.Model
ClientID string `json:"client_id" gorm:"uniqueIndex;not null;size:255;comment:客户端ID"`
ClientSecret string `json:"-" gorm:"not null;size:255;comment:客户端密钥"`
ClientName string `json:"client_name" gorm:"not null;size:255;comment:客户端名称"`
@ -201,7 +198,7 @@ type OAuthClient struct {
// OAuthAuthorizationCode 授权码表
type OAuthAuthorizationCode struct {
models.BaseModel
vigo.Model
Code string `json:"code" gorm:"uniqueIndex;not null;size:255;comment:授权码"`
ClientID string `json:"client_id" gorm:"not null;type:varchar(32);index;comment:客户端ID"`
UserID string `json:"user_id" gorm:"not null;type:varchar(32);index;comment:用户ID"`
@ -219,7 +216,7 @@ type OAuthAuthorizationCode struct {
// OAuthAccessToken 访问令牌表
type OAuthAccessToken struct {
models.BaseModel
vigo.Model
Token string `json:"token" gorm:"uniqueIndex;not null;size:500;comment:访问令牌"`
ClientID string `json:"client_id" gorm:"not null;type:varchar(32);index;comment:客户端ID"`
UserID string `json:"user_id" gorm:"not null;type:varchar(32);index;comment:用户ID"`
@ -235,7 +232,7 @@ type OAuthAccessToken struct {
// OAuthRefreshToken 刷新令牌表
type OAuthRefreshToken struct {
models.BaseModel
vigo.Model
Token string `json:"token" gorm:"uniqueIndex;not null;size:500;comment:刷新令牌"`
AccessTokenID string `json:"access_token_id" gorm:"type:varchar(32);uniqueIndex;comment:访问令牌ID"`
ClientID string `json:"client_id" gorm:"not null;type:varchar(32);index;comment:客户端ID"`
@ -252,7 +249,7 @@ type OAuthRefreshToken struct {
// OAuthScope OAuth授权范围表
type OAuthScope struct {
models.BaseModel
vigo.Model
Name string `json:"name" gorm:"uniqueIndex;not null;size:100;comment:范围名称"`
DisplayName string `json:"display_name" gorm:"size:100;comment:显示名称"`
Description string `json:"description" gorm:"type:text;comment:范围描述"`
@ -272,7 +269,7 @@ type OAuthClientScope struct {
// OAuthProvider 第三方OAuth提供商表用于OAuth客户端模式
type OAuthProvider struct {
models.BaseModel
vigo.Model
Name string `json:"name" gorm:"uniqueIndex;not null;size:100;comment:提供商名称"`
DisplayName string `json:"display_name" gorm:"size:100;comment:显示名称"`
ClientID string `json:"client_id" gorm:"not null;size:255;comment:客户端ID"`
@ -289,7 +286,7 @@ type OAuthProvider struct {
// OAuthAccount 用户OAuth账户表第三方登录
type OAuthAccount struct {
models.BaseModel
vigo.Model
UserID string `json:"user_id" gorm:"not null;type:varchar(32);index;comment:用户ID"`
ProviderID string `json:"provider_id" gorm:"not null;type:varchar(32);index;comment:提供商ID"`
ProviderUserID string `json:"provider_user_id" gorm:"not null;size:255;comment:提供商用户ID"`
@ -308,7 +305,7 @@ type OAuthAccount struct {
// UserToken 用户令牌表API令牌等
type UserToken struct {
models.BaseModel
vigo.Model
UserID string `json:"user_id" gorm:"not null;type:varchar(32);index;comment:用户ID"`
TokenType string `json:"token_type" gorm:"not null;size:50;comment:令牌类型"` // api, session, etc.
Token string `json:"token" gorm:"uniqueIndex;not null;size:500;comment:令牌值"`
@ -325,7 +322,7 @@ type UserToken struct {
// UserSession 用户会话表
type UserSession struct {
models.BaseModel
vigo.Model
UserID string `json:"user_id" gorm:"not null;type:varchar(32);index;comment:用户ID"`
SessionID string `json:"session_id" gorm:"uniqueIndex;not null;size:255;comment:会话ID"`
IPAddress string `json:"ip_address" gorm:"size:45;comment:IP地址"`
@ -340,7 +337,7 @@ type UserSession struct {
// OAuthUserConsent 用户授权同意表
type OAuthUserConsent struct {
models.BaseModel
vigo.Model
UserID string `json:"user_id" gorm:"not null;type:varchar(32);index;comment:用户ID"`
ClientID string `json:"client_id" gorm:"not null;type:varchar(32);index;comment:客户端ID"`
Scope string `json:"scope" gorm:"type:text;comment:授权范围"`

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save