fix(auth): Fix permission logic and add org member API

- Add api/org/add_member.go for adding organization members
    - Register POST /api/orgs/{id}/members endpoint
    - Fix PermWithOwner to check owner before permission
    - Remove user:update from user role (should use owner check)
    - Add service enabled check in verification send
master
veypi 1 week ago
parent e96277ee85
commit 11a689f28d

@ -0,0 +1,68 @@
// Copyright (C) 2024 veypi <i@veypi.com>
// 2025-03-04 16:08:06
// Distributed under terms of the MIT license.
package org
import (
"time"
"github.com/veypi/vbase/auth"
"github.com/veypi/vbase/cfg"
"github.com/veypi/vbase/models"
"github.com/veypi/vigo"
)
type AddMemberRequest struct {
OrgID string `src:"path@org_id" desc:"组织ID"`
UserID string `json:"user_id" src:"json" desc:"用户ID"`
Role string `json:"role,omitempty" src:"json" desc:"角色代码 (默认: member)"`
}
func addMember(x *vigo.X, req *AddMemberRequest) (*models.OrgMember, error) {
// 检查组织是否存在
var org models.Org
if err := cfg.DB().First(&org, "id = ?", req.OrgID).Error; err != nil {
return nil, vigo.ErrNotFound
}
// 检查用户是否存在
var user models.User
if err := cfg.DB().First(&user, "id = ?", req.UserID).Error; err != nil {
return nil, vigo.ErrNotFound.WithString("user not found")
}
// 检查用户是否已经是成员
var count int64
cfg.DB().Model(&models.OrgMember{}).Where("org_id = ? AND user_id = ?", req.OrgID, req.UserID).Count(&count)
if count > 0 {
return nil, vigo.ErrInvalidArg.WithString("user is already a member of this organization")
}
// 确定角色
roleCode := req.Role
if roleCode == "" {
roleCode = "member"
}
// 授予角色
if err := auth.VBaseAuth.GrantRole(x.Context(), req.UserID, req.OrgID, roleCode); err != nil {
return nil, vigo.ErrInternalServer.WithError(err)
}
// 创建成员记录
member := &models.OrgMember{
OrgID: req.OrgID,
UserID: req.UserID,
Status: models.MemberStatusActive,
JoinedAt: time.Now().Format("2006-01-02 15:04:05"),
}
if err := cfg.DB().Create(member).Error; err != nil {
// 回滚角色授予
auth.VBaseAuth.RevokeRole(x.Context(), req.UserID, req.OrgID, roleCode)
return nil, vigo.ErrInternalServer.WithError(err)
}
return member, nil
}

@ -19,6 +19,7 @@ func init() {
Router.Delete("/{org_id}", "删除组织", setOrgID, auth.VBaseAuth.Perm("org:delete"), del) Router.Delete("/{org_id}", "删除组织", setOrgID, auth.VBaseAuth.Perm("org:delete"), del)
Router.Get("/tree", "组织树", auth.VBaseAuth.Perm("org:read"), tree) Router.Get("/tree", "组织树", auth.VBaseAuth.Perm("org:read"), tree)
Router.Get("/{org_id}/members", "组织成员列表", setOrgID, auth.VBaseAuth.Perm("org:read"), listMembers) Router.Get("/{org_id}/members", "组织成员列表", setOrgID, auth.VBaseAuth.Perm("org:read"), listMembers)
Router.Post("/{org_id}/members", "添加组织成员", setOrgID, auth.VBaseAuth.Perm("org:update"), addMember)
} }
func setOrgID(x *vigo.X) error { func setOrgID(x *vigo.X) error {

@ -56,6 +56,20 @@ func sendCode(x *vigo.X, req *SendRequest) (*SendResponse, error) {
db := cfg.DB() db := cfg.DB()
// 检查服务是否启用(在生成验证码前检查,避免产生无用记录)
switch req.Type {
case "email":
enabled, _ := models.GetSettingBool(models.SettingEmailEnabled)
if !enabled {
return nil, vigo.ErrForbidden.WithString("email service not enabled")
}
case "sms":
enabled, _ := models.GetSettingBool(models.SettingSMSEnabled)
if !enabled {
return nil, vigo.ErrForbidden.WithString("sms service not enabled")
}
}
// 检查发送频率限制 // 检查发送频率限制
interval, _ := models.GetSettingInt(models.SettingCodeSendInterval) interval, _ := models.GetSettingInt(models.SettingCodeSendInterval)
if interval == 0 { if interval == 0 {

@ -84,7 +84,6 @@ func init() {
VBaseAuth.AddRole("admin", "管理员", "*:*") VBaseAuth.AddRole("admin", "管理员", "*:*")
VBaseAuth.AddRole("user", "普通用户", VBaseAuth.AddRole("user", "普通用户",
"user:read", "user:read",
"user:update",
"org:read", "org:read",
"org:create", "org:create",
"oauth-client:read", "oauth-client:read",
@ -413,11 +412,6 @@ func (a *appAuth) PermWithOwner(permissionID, ownerKey string) func(*vigo.X) err
orgID := getOrgID(x) orgID := getOrgID(x)
// 检查是否有基本权限
if err := a.checkPermission(x.Context(), userID, orgID, permissionID, ""); err != nil {
return err
}
// 获取资源所有者ID // 获取资源所有者ID
ownerID, _ := x.Get(ownerKey).(string) ownerID, _ := x.Get(ownerKey).(string)
if ownerID == "" { if ownerID == "" {
@ -429,22 +423,6 @@ func (a *appAuth) PermWithOwner(permissionID, ownerKey string) func(*vigo.X) err
return nil return nil
} }
// 如果不是所有者,且拥有全局管理权限(如admin),也可以放行
// 这里简化为再次检查是否有更高级别的权限,或者该权限本身隐含了管理权
// 实际上CheckPermission 已经检查了用户是否拥有该 permissionID
// 如果设计上 PermWithOwner 意味着 "所有者 OR 拥有该权限的管理员"
// 那么前面的 CheckPermission 已经保证了 "拥有该权限"
// 但通常 Owner 权限是针对特定资源的,而 CheckPermission 检查的是通用权限
// 这里逻辑稍微有点混淆,通常 PermWithOwner 意思是:
// 1. 用户必须登录
// 2. 如果用户是资源所有者,允许
// 3. 如果用户不是所有者,必须拥有特定权限 (permissionID)
// 修正逻辑:
if ownerID == userID {
return nil
}
// 不是所有者,检查是否有权限 // 不是所有者,检查是否有权限
if err := a.checkPermission(x.Context(), userID, orgID, permissionID, ""); err != nil { if err := a.checkPermission(x.Context(), userID, orgID, permissionID, ""); err != nil {
return err return err

Loading…
Cancel
Save