diff --git a/api/org/add_member.go b/api/org/add_member.go new file mode 100644 index 0000000..12c529a --- /dev/null +++ b/api/org/add_member.go @@ -0,0 +1,68 @@ +// Copyright (C) 2024 veypi +// 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 +} diff --git a/api/org/init.go b/api/org/init.go index b62182d..94ff761 100644 --- a/api/org/init.go +++ b/api/org/init.go @@ -19,6 +19,7 @@ func init() { Router.Delete("/{org_id}", "删除组织", setOrgID, auth.VBaseAuth.Perm("org:delete"), del) Router.Get("/tree", "组织树", auth.VBaseAuth.Perm("org:read"), tree) 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 { diff --git a/api/verification/send.go b/api/verification/send.go index 69cacdd..d87facf 100644 --- a/api/verification/send.go +++ b/api/verification/send.go @@ -56,6 +56,20 @@ func sendCode(x *vigo.X, req *SendRequest) (*SendResponse, error) { 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) if interval == 0 { diff --git a/auth/auth.go b/auth/auth.go index 9aa6ef3..c3629b0 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -84,7 +84,6 @@ func init() { VBaseAuth.AddRole("admin", "管理员", "*:*") VBaseAuth.AddRole("user", "普通用户", "user:read", - "user:update", "org:read", "org:create", "oauth-client:read", @@ -413,11 +412,6 @@ func (a *appAuth) PermWithOwner(permissionID, ownerKey string) func(*vigo.X) err orgID := getOrgID(x) - // 检查是否有基本权限 - if err := a.checkPermission(x.Context(), userID, orgID, permissionID, ""); err != nil { - return err - } - // 获取资源所有者ID ownerID, _ := x.Get(ownerKey).(string) if ownerID == "" { @@ -429,22 +423,6 @@ func (a *appAuth) PermWithOwner(permissionID, ownerKey string) func(*vigo.X) err 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 { return err