feat: add tenant login IP whitelist management
This commit is contained in:
@@ -57,3 +57,14 @@ type ThirdPartyReq struct {
|
|||||||
type ThirdPartyRes struct {
|
type ThirdPartyRes struct {
|
||||||
Values map[string]string `json:"values" dc:"配置值"`
|
Values map[string]string `json:"values" dc:"配置值"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TenantLoginIPWhitelistReq struct {
|
||||||
|
g.Meta `path:"/internal/im-config/tenants/by-id/{id}/login-ip-whitelist" method:"get" tags:"IM内部配置" summary:"获取租户渠道登录IP白名单"`
|
||||||
|
Id int64 `json:"id" in:"path" dc:"租户ID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TenantLoginIPWhitelistRes struct {
|
||||||
|
TenantId int64 `json:"tenant_id" dc:"租户ID"`
|
||||||
|
AppList []string `json:"app_list" dc:"APP登录IP白名单"`
|
||||||
|
WebList []string `json:"web_list" dc:"Web登录IP白名单"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -55,3 +55,16 @@ type MemberRemoveReq struct {
|
|||||||
sysin.ImUserPolicyGroupMemberRemoveInp
|
sysin.ImUserPolicyGroupMemberRemoveInp
|
||||||
}
|
}
|
||||||
type MemberRemoveRes struct{}
|
type MemberRemoveRes struct{}
|
||||||
|
|
||||||
|
type LoginIPWhitelistReq struct {
|
||||||
|
g.Meta `path:"/imUserPolicyGroup/loginIPWhitelist" method:"get" tags:"IM用户策略分组" summary:"获取租户登录IP白名单"`
|
||||||
|
sysin.ImTenantLoginIPWhitelistInp
|
||||||
|
}
|
||||||
|
type LoginIPWhitelistRes struct {
|
||||||
|
*sysin.ImTenantLoginIPWhitelistModel
|
||||||
|
}
|
||||||
|
type LoginIPWhitelistSaveReq struct {
|
||||||
|
g.Meta `path:"/imUserPolicyGroup/loginIPWhitelistSave" method:"post" tags:"IM用户策略分组" summary:"保存租户登录IP白名单"`
|
||||||
|
sysin.ImTenantLoginIPWhitelistSaveInp
|
||||||
|
}
|
||||||
|
type LoginIPWhitelistSaveRes struct{}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ const (
|
|||||||
ImUserRegisterTypeAdmin = "admin"
|
ImUserRegisterTypeAdmin = "admin"
|
||||||
ImUserRegisterTypeMobile = "mobile"
|
ImUserRegisterTypeMobile = "mobile"
|
||||||
ImUserRegisterTypeInviteCode = "invite_code"
|
ImUserRegisterTypeInviteCode = "invite_code"
|
||||||
|
ImLoginChannelApp = "app"
|
||||||
|
ImLoginChannelWeb = "web"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ImUserRegisterTypeOptions = []*model.Option{
|
var ImUserRegisterTypeOptions = []*model.Option{
|
||||||
|
|||||||
@@ -64,3 +64,16 @@ func (c *cImUserPolicyGroup) MemberRemove(ctx context.Context, req *imuserpolicy
|
|||||||
err = service.SysImUserPolicyGroup().MemberRemove(ctx, &req.ImUserPolicyGroupMemberRemoveInp)
|
err = service.SysImUserPolicyGroup().MemberRemove(ctx, &req.ImUserPolicyGroupMemberRemoveInp)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *cImUserPolicyGroup) LoginIPWhitelist(ctx context.Context, req *imuserpolicygroup.LoginIPWhitelistReq) (res *imuserpolicygroup.LoginIPWhitelistRes, err error) {
|
||||||
|
data, err := service.SysImUserPolicyGroup().LoginIPWhitelist(ctx, &req.ImTenantLoginIPWhitelistInp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &imuserpolicygroup.LoginIPWhitelistRes{ImTenantLoginIPWhitelistModel: data}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cImUserPolicyGroup) LoginIPWhitelistSave(ctx context.Context, req *imuserpolicygroup.LoginIPWhitelistSaveReq) (res *imuserpolicygroup.LoginIPWhitelistSaveRes, err error) {
|
||||||
|
err = service.SysImUserPolicyGroup().LoginIPWhitelistSave(ctx, &req.ImTenantLoginIPWhitelistSaveInp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"hotgo/internal/dao"
|
"hotgo/internal/dao"
|
||||||
"hotgo/internal/library/imtenant"
|
"hotgo/internal/library/imtenant"
|
||||||
"hotgo/internal/model/entity"
|
"hotgo/internal/model/entity"
|
||||||
|
"hotgo/internal/service"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/errors/gerror"
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
@@ -90,6 +91,17 @@ func (c *cConfig) ThirdParty(ctx context.Context, req *api.ThirdPartyReq) (res *
|
|||||||
return &api.ThirdPartyRes{Values: values}, nil
|
return &api.ThirdPartyRes{Values: values}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *cConfig) TenantLoginIPWhitelist(ctx context.Context, req *api.TenantLoginIPWhitelistReq) (res *api.TenantLoginIPWhitelistRes, err error) {
|
||||||
|
if _, err = getTenant(ctx, dao.ImTenant.Columns().Id, req.Id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, err := service.SysImUserPolicyGroup().LoginIPWhitelistByTenant(ctx, req.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &api.TenantLoginIPWhitelistRes{TenantId: data.TenantId, AppList: data.AppList, WebList: data.WebList}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func getTenant(ctx context.Context, field string, value any) (*entity.ImTenant, error) {
|
func getTenant(ctx context.Context, field string, value any) (*entity.ImTenant, error) {
|
||||||
var tenant *entity.ImTenant
|
var tenant *entity.ImTenant
|
||||||
if err := imtenant.RegistryDB().Model(dao.ImTenant.Table()).Safe().Ctx(ctx).Where(field, value).Scan(&tenant); err != nil {
|
if err := imtenant.RegistryDB().Model(dao.ImTenant.Table()).Safe().Ctx(ctx).Where(field, value).Scan(&tenant); err != nil {
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import (
|
|||||||
|
|
||||||
type sSysImUserPolicyGroup struct{}
|
type sSysImUserPolicyGroup struct{}
|
||||||
|
|
||||||
|
const imTenantLoginIPWhitelistTable = "im_tenant_login_ip_whitelists"
|
||||||
|
|
||||||
func NewSysImUserPolicyGroup() *sSysImUserPolicyGroup { return &sSysImUserPolicyGroup{} }
|
func NewSysImUserPolicyGroup() *sSysImUserPolicyGroup { return &sSysImUserPolicyGroup{} }
|
||||||
func init() { service.RegisterSysImUserPolicyGroup(NewSysImUserPolicyGroup()) }
|
func init() { service.RegisterSysImUserPolicyGroup(NewSysImUserPolicyGroup()) }
|
||||||
|
|
||||||
@@ -288,3 +290,85 @@ func (s *sSysImUserPolicyGroup) MemberRemove(ctx context.Context, in *sysin.ImUs
|
|||||||
_ = writeImAdminAudit(ctx, tenantID, "im_user_policy_group_member_remove", "im_user_policy_group", groupID, "success", g.Map{"user_id": row[mc.UserId].Int64()})
|
_ = writeImAdminAudit(ctx, tenantID, "im_user_policy_group_member_remove", "im_user_policy_group", groupID, "success", g.Map{"user_id": row[mc.UserId].Int64()})
|
||||||
return publishImPolicyEvent(ctx, tenantID, tenantCode, "user_policy_group.updated", "user_policy_groups", uint(groupID))
|
return publishImPolicyEvent(ctx, tenantID, tenantCode, "user_policy_group.updated", "user_policy_groups", uint(groupID))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *sSysImUserPolicyGroup) LoginIPWhitelist(ctx context.Context, in *sysin.ImTenantLoginIPWhitelistInp) (res *sysin.ImTenantLoginIPWhitelistModel, err error) {
|
||||||
|
if err = in.Filter(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tenantID, _, err := resolveImPolicyTenant(ctx, in.TenantId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s.LoginIPWhitelistByTenant(ctx, tenantID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sSysImUserPolicyGroup) LoginIPWhitelistByTenant(ctx context.Context, tenantID int64) (res *sysin.ImTenantLoginIPWhitelistModel, err error) {
|
||||||
|
if tenantID <= 0 {
|
||||||
|
return nil, gerror.New("租户ID不合法")
|
||||||
|
}
|
||||||
|
rows, err := imtenant.RegistryDB().Model(imTenantLoginIPWhitelistTable).Safe().Ctx(ctx).
|
||||||
|
Fields("channel", "ip_cidr").
|
||||||
|
Where("tenant_id", tenantID).
|
||||||
|
Where("status", consts.StatusEnabled).
|
||||||
|
WhereNull("deleted_at").
|
||||||
|
OrderAsc("channel").OrderAsc("id").All()
|
||||||
|
if err != nil {
|
||||||
|
return nil, gerror.Wrap(err, "获取租户登录IP白名单失败")
|
||||||
|
}
|
||||||
|
res = &sysin.ImTenantLoginIPWhitelistModel{
|
||||||
|
TenantId: tenantID,
|
||||||
|
AppList: make([]string, 0),
|
||||||
|
WebList: make([]string, 0),
|
||||||
|
}
|
||||||
|
for _, row := range rows {
|
||||||
|
switch row["channel"].String() {
|
||||||
|
case consts.ImLoginChannelApp:
|
||||||
|
res.AppList = append(res.AppList, row["ip_cidr"].String())
|
||||||
|
case consts.ImLoginChannelWeb:
|
||||||
|
res.WebList = append(res.WebList, row["ip_cidr"].String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sSysImUserPolicyGroup) LoginIPWhitelistSave(ctx context.Context, in *sysin.ImTenantLoginIPWhitelistSaveInp) (err error) {
|
||||||
|
if err = in.Filter(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tenantID, _, err := resolveImPolicyTenant(ctx, in.TenantId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
now := gtime.Now()
|
||||||
|
operatorID := contexts.GetUserId(ctx)
|
||||||
|
db := imtenant.RegistryDB()
|
||||||
|
err = db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||||
|
if _, txErr := tx.Model(imTenantLoginIPWhitelistTable).Safe().Ctx(ctx).
|
||||||
|
Where("tenant_id", tenantID).WhereNull("deleted_at").
|
||||||
|
Data(g.Map{"status": consts.StatusDisable, "deleted_at": now, "deleted_by": operatorID, "updated_at": now, "updated_by": operatorID}).Update(); txErr != nil {
|
||||||
|
return txErr
|
||||||
|
}
|
||||||
|
for channel, values := range map[string][]string{
|
||||||
|
consts.ImLoginChannelApp: in.AppList,
|
||||||
|
consts.ImLoginChannelWeb: in.WebList,
|
||||||
|
} {
|
||||||
|
for _, value := range values {
|
||||||
|
if _, txErr := tx.Exec(`INSERT INTO im_tenant_login_ip_whitelists
|
||||||
|
(tenant_id,channel,ip_cidr,status,created_by,updated_by)
|
||||||
|
VALUES(?,?,?,?,?,?)
|
||||||
|
ON DUPLICATE KEY UPDATE status=VALUES(status),deleted_at=NULL,deleted_by=NULL,updated_at=CURRENT_TIMESTAMP(3),updated_by=VALUES(updated_by)`,
|
||||||
|
tenantID, channel, value, consts.StatusEnabled, operatorID, operatorID); txErr != nil {
|
||||||
|
return txErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return gerror.Wrap(err, "保存租户登录IP白名单失败")
|
||||||
|
}
|
||||||
|
_ = writeImAdminAudit(ctx, tenantID, "im_tenant_login_ip_whitelist_save", "im_tenant", tenantID, "success", g.Map{
|
||||||
|
"app_count": len(in.AppList), "web_count": len(in.WebList),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -143,3 +143,21 @@ func TestImUserPolicyGroupInputFilter(t *testing.T) {
|
|||||||
t.Fatal("无效用户ID应被拒绝")
|
t.Fatal("无效用户ID应被拒绝")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImTenantLoginIPWhitelistSaveFilter(t *testing.T) {
|
||||||
|
in := &ImTenantLoginIPWhitelistSaveInp{
|
||||||
|
TenantId: 1,
|
||||||
|
AppList: []string{" 203.0.113.10 ", "2001:db8::/32", "203.0.113.10"},
|
||||||
|
WebList: []string{"198.51.100.7/24"},
|
||||||
|
}
|
||||||
|
if err := in.Filter(context.Background()); err != nil {
|
||||||
|
t.Fatalf("合法租户登录白名单不应报错: %v", err)
|
||||||
|
}
|
||||||
|
if len(in.AppList) != 2 || in.AppList[0] != "203.0.113.10" || in.WebList[0] != "198.51.100.0/24" {
|
||||||
|
t.Fatalf("IP白名单未正确规范化: app=%v web=%v", in.AppList, in.WebList)
|
||||||
|
}
|
||||||
|
in.AppList = []string{"not-an-ip"}
|
||||||
|
if err := in.Filter(context.Background()); err == nil {
|
||||||
|
t.Fatal("非法IP地址应被拒绝")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package sysin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/netip"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -155,3 +156,66 @@ type ImUserPolicyGroupMemberRemoveInp struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (in *ImUserPolicyGroupMemberRemoveInp) Filter(ctx context.Context) error { return nil }
|
func (in *ImUserPolicyGroupMemberRemoveInp) Filter(ctx context.Context) error { return nil }
|
||||||
|
|
||||||
|
type ImTenantLoginIPWhitelistInp struct {
|
||||||
|
TenantId int64 `json:"tenantId" dc:"租户ID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *ImTenantLoginIPWhitelistInp) Filter(ctx context.Context) error {
|
||||||
|
if in.TenantId < 0 {
|
||||||
|
return gerror.New("租户ID不合法")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImTenantLoginIPWhitelistSaveInp struct {
|
||||||
|
TenantId int64 `json:"tenantId" dc:"租户ID"`
|
||||||
|
AppList []string `json:"appList" dc:"APP登录IP白名单"`
|
||||||
|
WebList []string `json:"webList" dc:"Web登录IP白名单"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *ImTenantLoginIPWhitelistSaveInp) Filter(ctx context.Context) (err error) {
|
||||||
|
if in.TenantId < 0 {
|
||||||
|
return gerror.New("租户ID不合法")
|
||||||
|
}
|
||||||
|
if in.AppList, err = normalizeTenantLoginIPList(in.AppList); err != nil {
|
||||||
|
return gerror.Wrap(err, "APP登录白名单不合法")
|
||||||
|
}
|
||||||
|
if in.WebList, err = normalizeTenantLoginIPList(in.WebList); err != nil {
|
||||||
|
return gerror.Wrap(err, "Web登录白名单不合法")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImTenantLoginIPWhitelistModel struct {
|
||||||
|
TenantId int64 `json:"tenantId" dc:"租户ID"`
|
||||||
|
AppList []string `json:"appList" dc:"APP登录IP白名单"`
|
||||||
|
WebList []string `json:"webList" dc:"Web登录IP白名单"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeTenantLoginIPList(values []string) ([]string, error) {
|
||||||
|
if len(values) > 500 {
|
||||||
|
return nil, gerror.New("单个渠道最多配置500条IP或CIDR")
|
||||||
|
}
|
||||||
|
result := make([]string, 0, len(values))
|
||||||
|
seen := make(map[string]struct{}, len(values))
|
||||||
|
for _, value := range values {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
if value == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if prefix, err := netip.ParsePrefix(value); err == nil {
|
||||||
|
value = prefix.Masked().String()
|
||||||
|
} else if address, addressErr := netip.ParseAddr(value); addressErr == nil {
|
||||||
|
value = address.String()
|
||||||
|
} else {
|
||||||
|
return nil, gerror.Newf("%s 不是有效的IPv4、IPv6或CIDR", value)
|
||||||
|
}
|
||||||
|
if _, ok := seen[value]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[value] = struct{}{}
|
||||||
|
result = append(result, value)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -377,6 +377,9 @@ type (
|
|||||||
MemberList(ctx context.Context, in *sysin.ImUserPolicyGroupMemberListInp) (list []*sysin.ImUserPolicyGroupMemberModel, totalCount int, err error)
|
MemberList(ctx context.Context, in *sysin.ImUserPolicyGroupMemberListInp) (list []*sysin.ImUserPolicyGroupMemberModel, totalCount int, err error)
|
||||||
MemberAdd(ctx context.Context, in *sysin.ImUserPolicyGroupMemberAddInp) (err error)
|
MemberAdd(ctx context.Context, in *sysin.ImUserPolicyGroupMemberAddInp) (err error)
|
||||||
MemberRemove(ctx context.Context, in *sysin.ImUserPolicyGroupMemberRemoveInp) (err error)
|
MemberRemove(ctx context.Context, in *sysin.ImUserPolicyGroupMemberRemoveInp) (err error)
|
||||||
|
LoginIPWhitelist(ctx context.Context, in *sysin.ImTenantLoginIPWhitelistInp) (res *sysin.ImTenantLoginIPWhitelistModel, err error)
|
||||||
|
LoginIPWhitelistByTenant(ctx context.Context, tenantId int64) (res *sysin.ImTenantLoginIPWhitelistModel, err error)
|
||||||
|
LoginIPWhitelistSave(ctx context.Context, in *sysin.ImTenantLoginIPWhitelistSaveInp) (err error)
|
||||||
}
|
}
|
||||||
ISysImSensitiveWord interface {
|
ISysImSensitiveWord interface {
|
||||||
List(ctx context.Context, in *sysin.ImSensitiveWordListInp) (list []*sysin.ImSensitiveWordListModel, totalCount int, err error)
|
List(ctx context.Context, in *sysin.ImSensitiveWordListInp) (list []*sysin.ImSensitiveWordListModel, totalCount int, err error)
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
-- Tenant-scoped APP/Web login IP whitelist and admin permissions.
|
||||||
|
-- Empty whitelist for a channel means unrestricted access.
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
USE `hotgo`;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `im_tenant_login_ip_whitelists` (
|
||||||
|
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||||
|
`tenant_id` bigint NOT NULL COMMENT 'IM tenant ID',
|
||||||
|
`channel` varchar(16) NOT NULL COMMENT 'app or web',
|
||||||
|
`ip_cidr` varchar(64) NOT NULL COMMENT 'IPv4, IPv6 or CIDR',
|
||||||
|
`status` tinyint NOT NULL DEFAULT 1 COMMENT '1 enabled, 2 disabled',
|
||||||
|
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
`created_by` bigint NOT NULL DEFAULT 0,
|
||||||
|
`updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||||
|
`updated_by` bigint NOT NULL DEFAULT 0,
|
||||||
|
`deleted_at` datetime(3) DEFAULT NULL,
|
||||||
|
`deleted_by` bigint DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_im_tenant_login_ip` (`tenant_id`, `channel`, `ip_cidr`),
|
||||||
|
KEY `idx_im_tenant_login_ip_active` (`tenant_id`, `channel`, `status`, `deleted_at`),
|
||||||
|
CONSTRAINT `chk_im_tenant_login_ip_channel` CHECK (`channel` IN ('app', 'web'))
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Tenant APP/Web login IP whitelist';
|
||||||
|
|
||||||
|
SET @now = NOW();
|
||||||
|
SELECT @groupMenuId := `id` FROM `hg_admin_menu` WHERE `name` = 'imUserPolicyGroupIndex' LIMIT 1;
|
||||||
|
|
||||||
|
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
|
||||||
|
SELECT NULL, @groupMenuId, '查看租户登录IP白名单', 'imTenantLoginIPWhitelist', '', '', '3', '', '/imUserPolicyGroup/loginIPWhitelist', '', '', '1', '', '0', '0', '', '0', '1', '0', '4', CONCAT(`tree`, 'tr_', `id`, ' '), '9', '', '1', @now, @now
|
||||||
|
FROM `hg_admin_menu` WHERE `id` = @groupMenuId
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imTenantLoginIPWhitelist');
|
||||||
|
|
||||||
|
INSERT INTO `hg_admin_menu` (`id`, `pid`, `title`, `name`, `path`, `icon`, `type`, `redirect`, `permissions`, `permission_name`, `component`, `always_show`, `active_menu`, `is_root`, `is_frame`, `frame_src`, `keep_alive`, `hidden`, `affix`, `level`, `tree`, `sort`, `remark`, `status`, `created_at`, `updated_at`)
|
||||||
|
SELECT NULL, @groupMenuId, '保存租户登录IP白名单', 'imTenantLoginIPWhitelistSave', '', '', '3', '', '/imUserPolicyGroup/loginIPWhitelistSave', '', '', '1', '', '0', '0', '', '0', '1', '0', '4', CONCAT(`tree`, 'tr_', `id`, ' '), '10', '', '1', @now, @now
|
||||||
|
FROM `hg_admin_menu` WHERE `id` = @groupMenuId
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM `hg_admin_menu` WHERE `name` = 'imTenantLoginIPWhitelistSave');
|
||||||
|
|
||||||
|
INSERT IGNORE INTO `hg_admin_role_menu` (`role_id`, `menu_id`)
|
||||||
|
SELECT parent_role.`role_id`, child.`id`
|
||||||
|
FROM `hg_admin_role_menu` parent_role
|
||||||
|
JOIN `hg_admin_menu` parent ON parent.`id` = parent_role.`menu_id` AND parent.`name` = 'imUserPolicyGroupIndex'
|
||||||
|
JOIN `hg_admin_menu` child ON child.`pid` = parent.`id`
|
||||||
|
WHERE child.`name` IN ('imTenantLoginIPWhitelist', 'imTenantLoginIPWhitelistSave');
|
||||||
@@ -16,3 +16,7 @@ export const MemberAdd = (params) =>
|
|||||||
http.request({ url: '/imUserPolicyGroup/memberAdd', method: 'POST', params });
|
http.request({ url: '/imUserPolicyGroup/memberAdd', method: 'POST', params });
|
||||||
export const MemberRemove = (params) =>
|
export const MemberRemove = (params) =>
|
||||||
http.request({ url: '/imUserPolicyGroup/memberRemove', method: 'POST', params });
|
http.request({ url: '/imUserPolicyGroup/memberRemove', method: 'POST', params });
|
||||||
|
export const LoginIPWhitelist = (params) =>
|
||||||
|
http.request({ url: '/imUserPolicyGroup/loginIPWhitelist', method: 'GET', params });
|
||||||
|
export const LoginIPWhitelistSave = (params) =>
|
||||||
|
http.request({ url: '/imUserPolicyGroup/loginIPWhitelistSave', method: 'POST', params });
|
||||||
|
|||||||
@@ -9,6 +9,17 @@
|
|||||||
@reset="reloadTable"
|
@reset="reloadTable"
|
||||||
@keyup.enter="reloadTable"
|
@keyup.enter="reloadTable"
|
||||||
/>
|
/>
|
||||||
|
<n-alert type="info" :show-icon="true" class="whitelist-summary">
|
||||||
|
租户渠道 IP 白名单 APP:{{
|
||||||
|
whitelistSummary.appList.length
|
||||||
|
? `已配置 ${whitelistSummary.appList.length} 条`
|
||||||
|
: '未配置'
|
||||||
|
}} Web:{{
|
||||||
|
whitelistSummary.webList.length
|
||||||
|
? `已配置 ${whitelistSummary.webList.length} 条`
|
||||||
|
: '未配置'
|
||||||
|
}}
|
||||||
|
</n-alert>
|
||||||
<BasicTable
|
<BasicTable
|
||||||
ref="actionRef"
|
ref="actionRef"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
@@ -18,17 +29,67 @@
|
|||||||
:scroll-x="scrollX"
|
:scroll-x="scrollX"
|
||||||
:resizeHeightOffset="-10000"
|
:resizeHeightOffset="-10000"
|
||||||
>
|
>
|
||||||
<template #tableTitle
|
<template #tableTitle>
|
||||||
><n-button
|
<n-space>
|
||||||
type="primary"
|
<n-button
|
||||||
:disabled="!hasPermission(['/imUserPolicyGroup/edit'])"
|
type="primary"
|
||||||
@click="openEdit()"
|
:disabled="!hasPermission(['/imUserPolicyGroup/edit'])"
|
||||||
>新增分组</n-button
|
@click="openEdit()"
|
||||||
></template
|
>新增分组</n-button
|
||||||
>
|
>
|
||||||
|
<n-button
|
||||||
|
:disabled="!hasPermission(['/imUserPolicyGroup/loginIPWhitelist'])"
|
||||||
|
@click="openLoginIPWhitelist"
|
||||||
|
>
|
||||||
|
<template #icon
|
||||||
|
><n-icon><ShieldCheckmarkOutline /></n-icon
|
||||||
|
></template>
|
||||||
|
IP 白名单
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
</BasicTable>
|
</BasicTable>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
|
<n-modal
|
||||||
|
v-model:show="loginIPVisible"
|
||||||
|
preset="dialog"
|
||||||
|
title="租户渠道 IP 白名单"
|
||||||
|
:show-icon="false"
|
||||||
|
:style="{ width: whitelistDialogWidth }"
|
||||||
|
>
|
||||||
|
<n-form label-placement="top">
|
||||||
|
<n-form-item label="APP 登录白名单">
|
||||||
|
<n-input
|
||||||
|
v-model:value="loginIPForm.appText"
|
||||||
|
type="textarea"
|
||||||
|
:rows="7"
|
||||||
|
placeholder="每行一个 IPv4、IPv6 或 CIDR,例如 203.0.113.10"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="Web 登录白名单">
|
||||||
|
<n-input
|
||||||
|
v-model:value="loginIPForm.webText"
|
||||||
|
type="textarea"
|
||||||
|
:rows="7"
|
||||||
|
placeholder="每行一个 IPv4、IPv6 或 CIDR,例如 198.51.100.0/24"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
<template #action>
|
||||||
|
<n-space>
|
||||||
|
<n-button @click="loginIPVisible = false">取消</n-button>
|
||||||
|
<n-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!hasPermission(['/imUserPolicyGroup/loginIPWhitelistSave'])"
|
||||||
|
:loading="submitting"
|
||||||
|
@click="saveLoginIPWhitelist"
|
||||||
|
>确定</n-button
|
||||||
|
>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
|
||||||
<n-modal
|
<n-modal
|
||||||
v-model:show="editVisible"
|
v-model:show="editVisible"
|
||||||
preset="dialog"
|
preset="dialog"
|
||||||
@@ -160,6 +221,7 @@
|
|||||||
import { useDictStore } from '@/store/modules/dict';
|
import { useDictStore } from '@/store/modules/dict';
|
||||||
import { usePermission } from '@/hooks/web/usePermission';
|
import { usePermission } from '@/hooks/web/usePermission';
|
||||||
import { useUserStore } from '@/store/modules/user';
|
import { useUserStore } from '@/store/modules/user';
|
||||||
|
import { ShieldCheckmarkOutline } from '@vicons/ionicons5';
|
||||||
import { adaModalWidth, adaTableScrollX } from '@/utils/hotgo';
|
import { adaModalWidth, adaTableScrollX } from '@/utils/hotgo';
|
||||||
import {
|
import {
|
||||||
Delete,
|
Delete,
|
||||||
@@ -168,6 +230,8 @@
|
|||||||
MemberAdd,
|
MemberAdd,
|
||||||
MemberList,
|
MemberList,
|
||||||
MemberRemove,
|
MemberRemove,
|
||||||
|
LoginIPWhitelist,
|
||||||
|
LoginIPWhitelistSave,
|
||||||
Status,
|
Status,
|
||||||
} from '@/api/imUserPolicyGroup';
|
} from '@/api/imUserPolicyGroup';
|
||||||
import { columns, defaultPolicy, loadOptions, schemas } from './model';
|
import { columns, defaultPolicy, loadOptions, schemas } from './model';
|
||||||
@@ -184,10 +248,18 @@
|
|||||||
memberTableRef = ref();
|
memberTableRef = ref();
|
||||||
const editVisible = ref(false),
|
const editVisible = ref(false),
|
||||||
memberVisible = ref(false),
|
memberVisible = ref(false),
|
||||||
|
loginIPVisible = ref(false),
|
||||||
submitting = ref(false),
|
submitting = ref(false),
|
||||||
memberUserId = ref<number | null>(null),
|
memberUserId = ref<number | null>(null),
|
||||||
activeGroupId = ref(0);
|
activeGroupId = ref(0);
|
||||||
const dialogWidth = computed(() => adaModalWidth(820));
|
const dialogWidth = computed(() => adaModalWidth(820));
|
||||||
|
const whitelistDialogWidth = computed(() => adaModalWidth(760));
|
||||||
|
const whitelistSummary = reactive({
|
||||||
|
tenantId: 0,
|
||||||
|
appList: [] as string[],
|
||||||
|
webList: [] as string[],
|
||||||
|
});
|
||||||
|
const loginIPForm = reactive({ appText: '', webText: '' });
|
||||||
const statusOptions = dict.getOption('sys_normal_disable');
|
const statusOptions = dict.getOption('sys_normal_disable');
|
||||||
const emptyForm = () => ({
|
const emptyForm = () => ({
|
||||||
id: 0,
|
id: 0,
|
||||||
@@ -214,7 +286,11 @@
|
|||||||
schemas,
|
schemas,
|
||||||
});
|
});
|
||||||
const loadDataTable = (res) => List({ ...searchFormRef.value?.formModel, ...res });
|
const loadDataTable = (res) => List({ ...searchFormRef.value?.formModel, ...res });
|
||||||
const reloadTable = () => actionRef.value?.reload();
|
const currentTenantId = () => Number(searchFormRef.value?.formModel?.tenantId || 0);
|
||||||
|
const reloadTable = () => {
|
||||||
|
actionRef.value?.reload();
|
||||||
|
refreshLoginIPSummary();
|
||||||
|
};
|
||||||
const actionColumn = reactive({
|
const actionColumn = reactive({
|
||||||
width: 300,
|
width: 300,
|
||||||
title: '操作',
|
title: '操作',
|
||||||
@@ -252,6 +328,52 @@
|
|||||||
.map((v) => v.trim())
|
.map((v) => v.trim())
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
}
|
}
|
||||||
|
async function fetchLoginIPWhitelist() {
|
||||||
|
const tenantId = currentTenantId();
|
||||||
|
if (userStore.isCompanyDept && tenantId <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await LoginIPWhitelist({ tenantId });
|
||||||
|
}
|
||||||
|
async function refreshLoginIPSummary() {
|
||||||
|
const result = await fetchLoginIPWhitelist();
|
||||||
|
if (!result) {
|
||||||
|
Object.assign(whitelistSummary, { tenantId: 0, appList: [], webList: [] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object.assign(whitelistSummary, {
|
||||||
|
tenantId: Number(result.tenantId || 0),
|
||||||
|
appList: result.appList || [],
|
||||||
|
webList: result.webList || [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async function openLoginIPWhitelist() {
|
||||||
|
const result = await fetchLoginIPWhitelist();
|
||||||
|
if (!result) {
|
||||||
|
message.warning('请先选择具体租户');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object.assign(loginIPForm, {
|
||||||
|
appText: (result.appList || []).join('\n'),
|
||||||
|
webText: (result.webList || []).join('\n'),
|
||||||
|
});
|
||||||
|
loginIPVisible.value = true;
|
||||||
|
}
|
||||||
|
async function saveLoginIPWhitelist() {
|
||||||
|
submitting.value = true;
|
||||||
|
try {
|
||||||
|
await LoginIPWhitelistSave({
|
||||||
|
tenantId: currentTenantId(),
|
||||||
|
appList: lines(loginIPForm.appText),
|
||||||
|
webList: lines(loginIPForm.webText),
|
||||||
|
});
|
||||||
|
message.success('租户渠道 IP 白名单保存成功');
|
||||||
|
loginIPVisible.value = false;
|
||||||
|
await refreshLoginIPSummary();
|
||||||
|
} finally {
|
||||||
|
submitting.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
function openEdit(row?: any) {
|
function openEdit(row?: any) {
|
||||||
editForm.value = row
|
editForm.value = row
|
||||||
? {
|
? {
|
||||||
@@ -361,4 +483,7 @@
|
|||||||
.member-toolbar {
|
.member-toolbar {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
.whitelist-summary {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user