refactor(auth): delegate access control to be-system

Remove Agent Desk users, roles, login sessions, tokens, and local permission persistence. Expose the backend as an embeddable ai-agent module with host-provided subject lookup and operation authorization callbacks, and complete the frontend/backend repository split.
This commit is contained in:
t
2026-08-21 00:41:07 +08:00
parent 3d47227fbd
commit 2bbf42b741
447 changed files with 1901 additions and 8920 deletions
+12 -67
View File
@@ -1,7 +1,7 @@
package config
import (
"agent-desk/internal/pkg/enums"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
"fmt"
"strings"
@@ -9,17 +9,14 @@ import (
)
type Config struct {
Language string `yaml:"language"`
Server ServerConfig `yaml:"server"`
DB DBConfig `yaml:"db"`
Logger LoggerConfig `yaml:"logger"`
Auth AuthConfig `yaml:"auth"`
Storage StorageConfig `yaml:"storage"`
VectorDB VectorDBConfig `yaml:"vectorDB"`
MCP MCPConfig `yaml:"mcp"`
WxWork WxWorkConfig `yaml:"wxWork"`
OIDC OIDCConfig `yaml:"oidc"`
CustomerSession CustomerSessionConfig `yaml:"customerSession"`
Language string `yaml:"language"`
Server ServerConfig `yaml:"server"`
DB DBConfig `yaml:"db"`
Logger LoggerConfig `yaml:"logger"`
Storage StorageConfig `yaml:"storage"`
VectorDB VectorDBConfig `yaml:"vectorDB"`
MCP MCPConfig `yaml:"mcp"`
WxWork WxWorkConfig `yaml:"wxWork"`
}
func (c Config) LanguageOrDefault() string {
@@ -82,32 +79,6 @@ type LoggerConfig struct {
AddSource bool `yaml:"addSource"`
}
type AuthConfig struct {
TokenTTLHours int `yaml:"tokenTTLHours"`
MaxFailedAttempts int `yaml:"maxFailedAttempts"`
CredentialLockMinute int `yaml:"credentialLockMinute"`
}
type CustomerSessionConfig struct {
Secret string `yaml:"secret"`
TTLMinutes int `yaml:"ttlMinutes"`
RefreshThresholdMinutes int `yaml:"refreshThresholdMinutes"`
}
func (c CustomerSessionConfig) TTL() int {
if c.TTLMinutes <= 0 {
return 120
}
return c.TTLMinutes
}
func (c CustomerSessionConfig) RefreshThreshold() int {
if c.RefreshThresholdMinutes <= 0 {
return 30
}
return c.RefreshThresholdMinutes
}
type StorageConfig struct {
Default enums.AssetProvider `yaml:"default"`
MaxUploadSizeMB int64 `yaml:"maxUploadSizeMB"`
@@ -171,27 +142,10 @@ type MCPServerConfig struct {
Headers map[string]string `yaml:"headers"`
}
type OIDCConfig struct {
Enabled bool `yaml:"enabled"`
Issuer string `yaml:"issuer"`
ClientID string `yaml:"clientId"`
ClientSecret string `yaml:"clientSecret"`
RedirectURL string `yaml:"redirectUrl"`
StateSecret string `yaml:"stateSecret"`
Scopes []string `yaml:"scopes"`
}
// WxWorkConfig 定义企业微信接入配置。
//
// 当前主要用于后台管理台的企业微信登录流程:
// 1. /api/auth/wxwork/login 生成企业微信授权地址
// 2. 企业微信回调到 OAuthRedirect
// 3. 后端通过 code 换取企业成员身份并完成系统登录
//
// 其中 OAuthRedirect、CorpID、CorpSecret、AgentID 为登录流程核心配置。
// WxWorkConfig defines the WeCom application used for customer-service
// callbacks and notifications. Dashboard login is owned by be-system.
type WxWorkConfig struct {
// Enabled 表示是否启用企业微信登录能力。
// false 时不会初始化企业微信 SDK,相关登录接口不可用。
// Enabled controls whether the WeCom SDK is initialized.
Enabled bool `yaml:"enabled"`
// CorpID 为企业微信公司 ID,例如 wwxxxxxxxxxxxxxxxx。
CorpID string `yaml:"corpId"`
@@ -199,20 +153,11 @@ type WxWorkConfig struct {
CorpSecret string `yaml:"corpSecret"`
// AgentID 为企业微信自建应用 AgentID。
AgentID string `yaml:"agentId"`
// OAuthRedirect 为企业微信网页授权回调地址。
// 必须填写完整 URL,且通常指向后端接口 /api/auth/wxwork/callback。
OAuthRedirect string `yaml:"oauthRedirect"`
// StateSecret 为登录 state 的签名密钥,用于防止篡改和重放。
// 建议填写独立随机字符串;留空时业务代码会退回使用 CorpSecret。
StateSecret string `yaml:"stateSecret"`
// RSAPrivateKey 为企业微信回调解密私钥。
// 当前登录流程未使用,保留给消息回调等场景。
RSAPrivateKey string `yaml:"rsaPrivateKey"`
// Token 为企业微信回调 Token。
// 当前登录流程未使用,保留给消息回调等场景。
Token string `yaml:"token"`
// EncodingAESKey 为企业微信消息加解密密钥。
// 当前登录流程未使用,保留给消息回调等场景。
EncodingAESKey string `yaml:"encodingAESKey"`
// Notify 为企业微信应用消息通知配置。
Notify WxWorkNotifyConfig `yaml:"notify"`
+1 -348
View File
@@ -1,27 +1,6 @@
package constants
const (
RoleCodeSuperAdmin = "super_admin" // 超管
RoleCodeAdmin = "admin" // 管理员
RoleCodeCsTeamLeader = "cs_team_leader" // 客服组长
RoleCodeCsUser = "cs_user" // 客服
)
const (
AuthTokenPrefix = "ak_"
)
const (
ClientTypeAdminWeb = "admin_web"
)
const (
BootstrapAdminUsername = "admin"
BootstrapAdminPassword = "ChangeMe123!"
BootstrapAdminNickname = "Super Admin"
)
// Permission 权限结构体
// Permission 仅描述客服业务操作,由宿主系统执行权限验证
type Permission struct {
Name string
Code string
@@ -34,28 +13,6 @@ type Permission struct {
// 权限常量定义
var (
// 用户相关权限
PermissionUserView = Permission{Name: "查看用户", Code: "user.view", Type: "api", GroupName: "user", Method: "ANY", APIPath: "/api/dashboard/user/list", SortNo: 10}
PermissionUserCreate = Permission{Name: "创建用户", Code: "user.create", Type: "api", GroupName: "user", Method: "POST", APIPath: "/api/dashboard/user/create", SortNo: 20}
PermissionUserUpdate = Permission{Name: "更新用户", Code: "user.update", Type: "api", GroupName: "user", Method: "POST", APIPath: "/api/dashboard/user/update", SortNo: 30}
PermissionUserDelete = Permission{Name: "删除用户", Code: "user.delete", Type: "api", GroupName: "user", Method: "POST", APIPath: "/api/dashboard/user/delete", SortNo: 40}
PermissionUserAssignRole = Permission{Name: "分配用户角色", Code: "user.assignRole", Type: "api", GroupName: "user", Method: "POST", APIPath: "/api/dashboard/user/assign_role", SortNo: 50}
// 角色相关权限
PermissionRoleView = Permission{Name: "查看角色", Code: "role.view", Type: "api", GroupName: "role", Method: "ANY", APIPath: "/api/dashboard/role/list", SortNo: 110}
PermissionRoleCreate = Permission{Name: "创建角色", Code: "role.create", Type: "api", GroupName: "role", Method: "POST", APIPath: "/api/dashboard/role/create", SortNo: 120}
PermissionRoleUpdate = Permission{Name: "更新角色", Code: "role.update", Type: "api", GroupName: "role", Method: "POST", APIPath: "/api/dashboard/role/update", SortNo: 130}
PermissionRoleDelete = Permission{Name: "删除角色", Code: "role.delete", Type: "api", GroupName: "role", Method: "POST", APIPath: "/api/dashboard/role/delete", SortNo: 140}
PermissionRoleAssignPermission = Permission{Name: "分配角色权限", Code: "role.assignPermission", Type: "api", GroupName: "role", Method: "POST", APIPath: "/api/dashboard/role/assign_permission", SortNo: 150}
// 权限相关权限
PermissionPermissionView = Permission{Name: "查看权限", Code: "permission.view", Type: "api", GroupName: "permission", Method: "ANY", APIPath: "/api/dashboard/permission/list", SortNo: 210}
PermissionPermissionSync = Permission{Name: "同步权限", Code: "permission.sync", Type: "api", GroupName: "permission", Method: "POST", APIPath: "/api/dashboard/permission/sync", SortNo: 220}
// 会话相关权限
PermissionSessionView = Permission{Name: "查看会话", Code: "session.view", Type: "api", GroupName: "session", Method: "ANY", APIPath: "/api/dashboard/session/list", SortNo: 310}
PermissionSessionRevoke = Permission{Name: "踢除会话", Code: "session.revoke", Type: "api", GroupName: "session", Method: "POST", APIPath: "/api/dashboard/session/revoke", SortNo: 320}
// 客服会话相关权限
PermissionConversationView = Permission{Name: "查看会话", Code: "conversation.view", Type: "api", GroupName: "conversation", Method: "ANY", APIPath: "/api/dashboard/conversation/list", SortNo: 410}
PermissionConversationAssign = Permission{Name: "分配会话", Code: "conversation.assign", Type: "api", GroupName: "conversation", Method: "POST", APIPath: "/api/dashboard/conversation/assign", SortNo: 430}
@@ -63,8 +20,6 @@ var (
PermissionConversationClose = Permission{Name: "关闭会话", Code: "conversation.close", Type: "api", GroupName: "conversation", Method: "POST", APIPath: "/api/dashboard/conversation/close", SortNo: 450}
PermissionConversationSend = Permission{Name: "发送会话消息", Code: "conversation.send", Type: "api", GroupName: "conversation", Method: "POST", APIPath: "/api/dashboard/conversation/send_message", SortNo: 460}
PermissionConversationTag = Permission{Name: "管理会话标签", Code: "conversation.tag", Type: "api", GroupName: "conversation", Method: "POST", APIPath: "/api/dashboard/conversation/add_tag", SortNo: 470}
PermissionConversationHandover = Permission{Name: "处理会话交接", Code: "conversation.handover", Type: "api", GroupName: "conversation", Method: "ANY", APIPath: "/api/dashboard/conversation/handover_list", SortNo: 480}
PermissionConversationRecycle = Permission{Name: "回收会话", Code: "conversation.recycle", Type: "api", GroupName: "conversation", Method: "POST", APIPath: "/api/dashboard/conversation/recycle", SortNo: 490}
PermissionConversationLinkCustomer = Permission{Name: "关联会话客户", Code: "conversation.linkCustomer", Type: "api", GroupName: "conversation", Method: "POST", APIPath: "/api/dashboard/conversation/link_customer", SortNo: 495}
// 工单相关权限
@@ -118,8 +73,6 @@ var (
PermissionAgentCreate = Permission{Name: "创建客服", Code: "agent.create", Type: "api", GroupName: "agent", Method: "POST", APIPath: "/api/dashboard/agent/create", SortNo: 620}
PermissionAgentUpdate = Permission{Name: "更新客服", Code: "agent.update", Type: "api", GroupName: "agent", Method: "POST", APIPath: "/api/dashboard/agent/update", SortNo: 630}
PermissionAgentDelete = Permission{Name: "删除客服", Code: "agent.delete", Type: "api", GroupName: "agent", Method: "POST", APIPath: "/api/dashboard/agent/delete", SortNo: 640}
PermissionAgentUpdateStatus = Permission{Name: "更新客服状态", Code: "agent.updateStatus", Type: "api", GroupName: "agent", Method: "POST", APIPath: "/api/dashboard/agent/update_status", SortNo: 650}
PermissionAgentConfig = Permission{Name: "配置客服服务规则", Code: "agent.config", Type: "api", GroupName: "agent", Method: "POST", APIPath: "/api/dashboard/agent/update_service_config", SortNo: 660}
// 客服组相关权限
PermissionAgentTeamView = Permission{Name: "查看客服组", Code: "agentTeam.view", Type: "api", GroupName: "agentTeam", Method: "ANY", APIPath: "/api/dashboard/agent-team/list", SortNo: 710}
@@ -177,303 +130,3 @@ var (
PermissionMCPView = Permission{Name: "查看MCP调试信息", Code: "mcp.view", Type: "api", GroupName: "mcp", Method: "POST", APIPath: "/api/dashboard/mcp/list_tools", SortNo: 1710}
PermissionMCPCall = Permission{Name: "调用MCP工具", Code: "mcp.call", Type: "api", GroupName: "mcp", Method: "POST", APIPath: "/api/dashboard/mcp/call_tool", SortNo: 1720}
)
// Permissions 内置权限列表
var Permissions = []Permission{
PermissionUserView,
PermissionUserCreate,
PermissionUserUpdate,
PermissionUserDelete,
PermissionUserAssignRole,
PermissionRoleView,
PermissionRoleCreate,
PermissionRoleUpdate,
PermissionRoleDelete,
PermissionRoleAssignPermission,
PermissionPermissionView,
PermissionPermissionSync,
PermissionSessionView,
PermissionSessionRevoke,
PermissionConversationView,
PermissionConversationAssign,
PermissionConversationTransfer,
PermissionConversationClose,
PermissionConversationSend,
PermissionConversationTag,
PermissionConversationHandover,
PermissionConversationRecycle,
PermissionConversationLinkCustomer,
PermissionTicketView,
PermissionTicketCreate,
PermissionTicketUpdate,
PermissionTicketAssign,
PermissionTicketChangeStatus,
PermissionTicketProgress,
PermissionNotificationView,
PermissionNotificationUpdate,
PermissionQuickReplyView,
PermissionQuickReplyCreate,
PermissionQuickReplyUpdate,
PermissionQuickReplyDelete,
PermissionTagView,
PermissionTagCreate,
PermissionTagUpdate,
PermissionTagDelete,
PermissionCompanyView,
PermissionCompanyCreate,
PermissionCompanyUpdate,
PermissionCompanyDelete,
PermissionChannelView,
PermissionChannelCreate,
PermissionChannelUpdate,
PermissionChannelDelete,
PermissionWxWorkOutboxView,
PermissionWxWorkOutboxUpdate,
PermissionCustomerView,
PermissionCustomerCreate,
PermissionCustomerUpdate,
PermissionCustomerDelete,
PermissionAgentView,
PermissionAgentCreate,
PermissionAgentUpdate,
PermissionAgentDelete,
PermissionAgentUpdateStatus,
PermissionAgentConfig,
PermissionAgentTeamView,
PermissionAgentTeamCreate,
PermissionAgentTeamUpdate,
PermissionAgentTeamDelete,
PermissionAgentTeamScheduleView,
PermissionAgentTeamScheduleCreate,
PermissionAgentTeamScheduleUpdate,
PermissionAgentTeamScheduleDelete,
PermissionAgentTeamScheduleBatchGenerate,
PermissionAssetView,
PermissionAssetCreate,
PermissionAssetDelete,
PermissionAIAgentView,
PermissionAIAgentCreate,
PermissionAIAgentUpdate,
PermissionAIAgentDelete,
PermissionAIConfigView,
PermissionAIConfigCreate,
PermissionAIConfigUpdate,
PermissionAIConfigDelete,
PermissionKnowledgeBaseView,
PermissionKnowledgeBaseCreate,
PermissionKnowledgeBaseUpdate,
PermissionKnowledgeBaseDelete,
PermissionKnowledgeDocumentView,
PermissionKnowledgeDocumentCreate,
PermissionKnowledgeDocumentUpdate,
PermissionKnowledgeDocumentDelete,
PermissionKnowledgeFAQView,
PermissionKnowledgeFAQCreate,
PermissionKnowledgeFAQUpdate,
PermissionKnowledgeFAQDelete,
PermissionSkillDefinitionView,
PermissionSkillDefinitionCreate,
PermissionSkillDefinitionUpdate,
PermissionSkillDefinitionDelete,
PermissionMCPView,
PermissionMCPCall,
}
// PermissionMap 权限映射,用于通过 Code 查找 Permission
var PermissionMap = make(map[string]Permission)
// init 初始化 PermissionMap
func init() {
normalizeBuiltinPermissionNames()
for _, permission := range Permissions {
PermissionMap[permission.Code] = permission
}
}
func normalizeBuiltinPermissionNames() {
for i := range Permissions {
Permissions[i].Name = builtinPermissionName(Permissions[i].Code, Permissions[i].Name)
}
}
func builtinPermissionName(code string, fallback string) string {
if name, ok := builtinPermissionNameOverrides[code]; ok {
return name
}
resourceKey, actionKey, ok := splitPermissionCode(code)
if !ok {
return fallback
}
action, ok := builtinPermissionActionLabels[actionKey]
if !ok {
return fallback
}
resource, ok := builtinPermissionResourceLabels[resourceKey]
if !ok {
return fallback
}
return action + " " + resource
}
func splitPermissionCode(code string) (string, string, bool) {
for i := 0; i < len(code); i++ {
if code[i] == '.' {
return code[:i], code[i+1:], i > 0 && i < len(code)-1
}
}
return "", "", false
}
var builtinPermissionActionLabels = map[string]string{
"view": "View",
"create": "Create",
"update": "Update",
"delete": "Delete",
"assignRole": "Assign roles to",
"assignPermission": "Assign permissions to",
"sync": "Sync",
"revoke": "Revoke",
"assign": "Assign",
"transfer": "Transfer",
"close": "Close",
"send": "Send",
"tag": "Manage tags for",
"handover": "Handle handoffs for",
"recycle": "Recycle",
"linkCustomer": "Link customers to",
"changeStatus": "Change status for",
"progress": "Update progress for",
"updateStatus": "Update status for",
"config": "Configure service rules for",
"batchGenerate": "Batch generate",
"call": "Call",
}
var builtinPermissionResourceLabels = map[string]string{
"user": "users",
"role": "roles",
"permission": "permissions",
"session": "sessions",
"conversation": "conversations",
"ticket": "tickets",
"notification": "notifications",
"quickReply": "quick replies",
"tag": "tags",
"company": "companies",
"channel": "channels",
"wxworkOutbox": "WeCom outbox records",
"customer": "customers",
"agent": "agents",
"agentTeam": "agent teams",
"agentTeamSchedule": "agent team schedules",
"asset": "file assets",
"aiAgent": "AI Agents",
"aiConfig": "AI configurations",
"knowledgeBase": "knowledge bases",
"knowledgeDocument": "knowledge documents",
"knowledgeFAQ": "knowledge FAQs",
"skillDefinition": "Skill definitions",
"mcp": "MCP tools",
}
var builtinPermissionNameOverrides = map[string]string{
"user.assignRole": "Assign user roles",
"role.assignPermission": "Assign role permissions",
"session.revoke": "Revoke sessions",
"conversation.send": "Send conversation messages",
"conversation.linkCustomer": "Link conversation customer",
"ticket.changeStatus": "Change ticket status",
"ticket.progress": "Update ticket progress",
"agent.config": "Configure agent service rules",
"agentTeamSchedule.batchGenerate": "Batch generate agent team schedules",
"wxworkOutbox.update": "Handle WeCom outbox records",
"mcp.view": "View MCP debug information",
"mcp.call": "Call MCP tools",
}
type RoleSpec struct {
Name string
Code string
SortNo int
}
var Roles = []RoleSpec{
{Name: "Super Admin", Code: RoleCodeSuperAdmin, SortNo: 1},
{Name: "Admin", Code: RoleCodeAdmin, SortNo: 2},
{Name: "Support Team Lead", Code: RoleCodeCsTeamLeader, SortNo: 3},
{Name: "Support Agent", Code: RoleCodeCsUser, SortNo: 4},
}
var RolePermissions = map[string][]Permission{
RoleCodeSuperAdmin: Permissions,
RoleCodeAdmin: {
PermissionUserView, PermissionUserCreate, PermissionUserUpdate, PermissionUserAssignRole,
PermissionRoleView, PermissionRoleCreate, PermissionRoleUpdate, PermissionRoleAssignPermission,
PermissionPermissionView, PermissionPermissionSync,
PermissionSessionView, PermissionSessionRevoke,
PermissionConversationView, PermissionConversationAssign, PermissionConversationTransfer, PermissionConversationClose, PermissionConversationSend, PermissionConversationTag, PermissionConversationHandover, PermissionConversationRecycle, PermissionConversationLinkCustomer,
PermissionTicketView, PermissionTicketCreate, PermissionTicketUpdate, PermissionTicketAssign, PermissionTicketChangeStatus, PermissionTicketProgress,
PermissionNotificationView, PermissionNotificationUpdate,
PermissionQuickReplyView, PermissionQuickReplyCreate, PermissionQuickReplyUpdate, PermissionQuickReplyDelete,
PermissionTagView, PermissionTagCreate, PermissionTagUpdate, PermissionTagDelete,
PermissionCompanyView, PermissionCompanyCreate, PermissionCompanyUpdate, PermissionCompanyDelete,
PermissionChannelView, PermissionChannelCreate, PermissionChannelUpdate, PermissionChannelDelete, PermissionWxWorkOutboxView, PermissionWxWorkOutboxUpdate,
PermissionCustomerView, PermissionCustomerCreate, PermissionCustomerUpdate, PermissionCustomerDelete,
PermissionAgentView, PermissionAgentCreate, PermissionAgentUpdate, PermissionAgentDelete, PermissionAgentUpdateStatus, PermissionAgentConfig,
PermissionAgentTeamView, PermissionAgentTeamCreate, PermissionAgentTeamUpdate, PermissionAgentTeamDelete,
PermissionAgentTeamScheduleView, PermissionAgentTeamScheduleCreate, PermissionAgentTeamScheduleUpdate, PermissionAgentTeamScheduleDelete, PermissionAgentTeamScheduleBatchGenerate,
PermissionAssetView, PermissionAssetCreate, PermissionAssetDelete,
PermissionAIAgentView, PermissionAIAgentCreate, PermissionAIAgentUpdate, PermissionAIAgentDelete,
PermissionAIConfigView, PermissionAIConfigCreate, PermissionAIConfigUpdate, PermissionAIConfigDelete,
PermissionSkillDefinitionView, PermissionSkillDefinitionCreate, PermissionSkillDefinitionUpdate, PermissionSkillDefinitionDelete,
},
RoleCodeCsTeamLeader: {
PermissionUserView,
PermissionRoleView,
PermissionPermissionView,
PermissionSessionView,
PermissionConversationView, PermissionConversationClose, PermissionConversationSend, PermissionConversationTag, PermissionConversationHandover, PermissionConversationRecycle, PermissionConversationLinkCustomer,
PermissionTicketView, PermissionTicketCreate, PermissionTicketUpdate, PermissionTicketAssign, PermissionTicketChangeStatus, PermissionTicketProgress,
PermissionNotificationView, PermissionNotificationUpdate,
PermissionQuickReplyView, PermissionQuickReplyCreate, PermissionQuickReplyUpdate, PermissionQuickReplyDelete,
PermissionTagView, PermissionTagCreate, PermissionTagUpdate, PermissionTagDelete,
PermissionCompanyView,
PermissionChannelView, PermissionChannelCreate, PermissionChannelUpdate,
PermissionCustomerView, PermissionCustomerCreate, PermissionCustomerUpdate,
PermissionAgentView, PermissionAgentUpdate,
PermissionAgentTeamView,
PermissionAgentTeamScheduleView, PermissionAgentTeamScheduleCreate, PermissionAgentTeamScheduleUpdate, PermissionAgentTeamScheduleDelete, PermissionAgentTeamScheduleBatchGenerate,
PermissionAssetView, PermissionAssetCreate, PermissionAssetDelete,
PermissionAIAgentView, PermissionAIAgentCreate, PermissionAIAgentUpdate,
PermissionAIConfigView,
PermissionSkillDefinitionView, PermissionSkillDefinitionCreate, PermissionSkillDefinitionUpdate,
},
RoleCodeCsUser: {
PermissionUserView,
PermissionRoleView,
PermissionPermissionView,
PermissionConversationView,
PermissionTicketView, PermissionTicketCreate, PermissionTicketAssign, PermissionTicketChangeStatus, PermissionTicketProgress,
PermissionNotificationView, PermissionNotificationUpdate,
PermissionQuickReplyView,
PermissionTagView,
PermissionCompanyView,
PermissionChannelView,
PermissionCustomerView,
PermissionAssetView,
PermissionAgentView,
PermissionAgentTeamView,
PermissionAgentTeamScheduleView,
PermissionAIAgentView,
PermissionAIConfigView,
PermissionSkillDefinitionView,
},
}
func PermissionCodes() []string {
ret := make([]string, 0, len(Permissions))
for _, permission := range Permissions {
ret = append(ret, permission.Code)
}
return ret
}
-47
View File
@@ -1,47 +0,0 @@
package constants
import "testing"
func TestBuiltinAuthSeedNamesDefaultToEnglish(t *testing.T) {
t.Parallel()
if BootstrapAdminNickname != "Super Admin" {
t.Fatalf("BootstrapAdminNickname = %q, want %q", BootstrapAdminNickname, "Super Admin")
}
roles := map[string]string{}
for _, role := range Roles {
roles[role.Code] = role.Name
}
tests := map[string]string{
RoleCodeSuperAdmin: "Super Admin",
RoleCodeAdmin: "Admin",
RoleCodeCsTeamLeader: "Support Team Lead",
RoleCodeCsUser: "Support Agent",
}
for code, want := range tests {
if got := roles[code]; got != want {
t.Fatalf("role %s name = %q, want %q", code, got, want)
}
}
permissions := map[string]string{}
for _, permission := range Permissions {
permissions[permission.Code] = permission.Name
}
permissionTests := map[string]string{
"user.view": "View users",
"ticket.create": "Create tickets",
"conversation.send": "Send conversation messages",
"channel.view": "View channels",
"wxworkOutbox.view": "View WeCom outbox records",
"agent.view": "View agents",
}
for code, want := range permissionTests {
if got := permissions[code]; got != want {
t.Fatalf("permission %s name = %q, want %q", code, got, want)
}
}
}
+13 -11
View File
@@ -1,8 +1,12 @@
package dto
import "agent-desk/internal/pkg/enums"
import (
"code.tczkiot.com/wlw/ai-agent/identity"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
)
type AuthPrincipal struct {
SubjectType identity.SubjectType
UserID int64
Username string
Nickname string
@@ -17,17 +21,15 @@ type WxWorkKFChannelConfig struct {
}
type WebChannelConfig struct {
Title string `json:"title"`
Subtitle string `json:"subtitle"`
ThemeColor string `json:"themeColor"`
Position string `json:"position"`
Width string `json:"width"`
UserTokenSecret string `json:"userTokenSecret,omitempty"`
Title string `json:"title"`
Subtitle string `json:"subtitle"`
ThemeColor string `json:"themeColor"`
Position string `json:"position"`
Width string `json:"width"`
}
type WechatMPChannelConfig struct {
Title string `json:"title"`
Subtitle string `json:"subtitle"`
ThemeColor string `json:"themeColor"`
UserTokenSecret string `json:"userTokenSecret,omitempty"`
Title string `json:"title"`
Subtitle string `json:"subtitle"`
ThemeColor string `json:"themeColor"`
}
-96
View File
@@ -1,96 +0,0 @@
package request
import "agent-desk/internal/pkg/enums"
type RevokeSessionRequest struct {
ID int64 `json:"id"`
}
type RevokeUserSessionsRequest struct {
UserID int64 `json:"userId"`
}
type CreateUserRequest struct {
Username string `json:"username"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
Mobile *string `json:"mobile"`
Email *string `json:"email"`
Remark string `json:"remark"`
RoleIDs []int64 `json:"roleIds"`
}
type UpdateUserRequest struct {
ID int64 `json:"id"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
Mobile *string `json:"mobile"`
Email *string `json:"email"`
Remark string `json:"remark"`
}
type DeleteUserRequest struct {
ID int64 `json:"id"`
}
type UpdateUserStatusRequest struct {
ID int64 `json:"id"`
Status int `json:"status"`
}
type ChangePasswordRequest struct {
Password string `json:"password"`
}
type AssignRoleRequest struct {
UserID int64 `json:"userId"`
RoleIDs []int64 `json:"roleIds"`
}
type CreateRoleRequest struct {
Name string `json:"name"`
Code string `json:"code"`
Remark string `json:"remark"`
}
type UpdateRoleRequest struct {
ID int64 `json:"id"`
Name string `json:"name"`
SortNo int `json:"sortNo"`
Remark string `json:"remark"`
}
type DeleteRoleRequest struct {
ID int64 `json:"id"`
}
type UpdateRoleStatusRequest struct {
ID int64 `json:"id"`
Status enums.Status `json:"status"`
}
type AssignPermissionRequest struct {
RoleID int64 `json:"roleId"`
PermissionIDs []int64 `json:"permissionIds"`
}
type CreateConversationTagRequest struct {
Name string `json:"name"`
Color string `json:"color"`
Status int `json:"status"`
SortNo int `json:"sortNo"`
Remark string `json:"remark"`
}
type UpdateConversationTagRequest struct {
ID int64 `json:"id"`
Name string `json:"name"`
Color string `json:"color"`
Status int `json:"status"`
SortNo int `json:"sortNo"`
Remark string `json:"remark"`
}
type DeleteConversationTagRequest struct {
ID int64 `json:"id"`
}
+1 -1
View File
@@ -1,6 +1,6 @@
package request
import "agent-desk/internal/pkg/enums"
import "code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
type CreateAgentProfileRequest struct {
UserID int64 `json:"userId"`
@@ -1,6 +1,6 @@
package request
import "agent-desk/internal/pkg/enums"
import "code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
type SaveAgentRunQualityFeedbackRequest struct {
AgentRunID int64 `json:"agentRunId"`
+1 -1
View File
@@ -1,6 +1,6 @@
package request
import "agent-desk/internal/pkg/enums"
import "code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
type AIAgentMCPToolRequest struct {
ToolCode string `json:"toolCode"`
@@ -1,6 +1,6 @@
package request
import "agent-desk/internal/ai/workflow/dsl"
import "code.tczkiot.com/wlw/ai-agent/internal/ai/workflow/dsl"
type CreateAIWorkflowRequest struct {
Name string `json:"name"`
-14
View File
@@ -1,14 +0,0 @@
package request
type LoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}
type WxWorkExchangeRequest struct {
Ticket string `json:"ticket"`
}
type OIDCExchangeRequest struct {
Ticket string `json:"ticket"`
}
@@ -28,10 +28,6 @@ type DeleteChannelRequest struct {
ID int64 `json:"id"`
}
type ResetChannelUserTokenSecretRequest struct {
ID int64 `json:"id"`
}
type ChannelMessageOutboxActionRequest struct {
ID int64 `json:"id"`
}
@@ -3,7 +3,7 @@ package request
import (
"io"
"agent-desk/internal/pkg/enums"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
)
type CreateKnowledgeBaseRequest struct {
+1 -1
View File
@@ -1,6 +1,6 @@
package request
import "agent-desk/internal/pkg/enums"
import "code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
type MessageListRequest struct {
ConversationID int64 `json:"conversationId"`
@@ -1,6 +1,6 @@
package request
import "agent-desk/internal/pkg/enums"
import "code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
type QuickReplyListRequest struct {
GroupName string `json:"groupName"`
@@ -1,63 +0,0 @@
package response
import "agent-desk/internal/pkg/enums"
type PermissionResponse struct {
ID int64 `json:"id"`
Name string `json:"name"`
Code string `json:"code"`
Type string `json:"type"`
GroupName string `json:"groupName"`
Method string `json:"method"`
ApiPath string `json:"apiPath"`
Status enums.Status `json:"status"`
SortNo int `json:"sortNo"`
}
type PermissionSyncResponse struct {
Created int `json:"created"`
Updated int `json:"updated"`
RolePermissionsAdded int `json:"rolePermissionsAdded"`
}
type RoleResponse struct {
ID int64 `json:"id"`
Name string `json:"name"`
Code string `json:"code"`
Status enums.Status `json:"status"`
IsSystem bool `json:"isSystem"`
SortNo int `json:"sortNo"`
Permissions []string `json:"permissions,omitempty"`
}
type UserResponse struct {
ID int64 `json:"id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
Mobile string `json:"mobile,omitempty"`
Email string `json:"email,omitempty"`
Status enums.Status `json:"status"`
LastLoginAt string `json:"lastLoginAt,omitempty"`
LastLoginIP string `json:"lastLoginIp,omitempty"`
Roles []RoleResponse `json:"roles,omitempty"`
Permissions []string `json:"permissions,omitempty"`
}
// CreateUserResultResponse 创建用户成功响应;password 仅在本次响应中返回一次。
type CreateUserResultResponse struct {
User *UserResponse `json:"user"`
Password string `json:"password"`
}
type SessionResponse struct {
ID int64 `json:"id"`
UserID int64 `json:"userId"`
Username string `json:"username"`
ClientType string `json:"clientType"`
ClientIP string `json:"clientIp"`
UserAgent string `json:"userAgent"`
ExpiredAt string `json:"expiredAt"`
RevokedAt string `json:"revokedAt"`
LastSeenAt string `json:"lastSeenAt"`
}
+8 -1
View File
@@ -1,6 +1,13 @@
package response
import "agent-desk/internal/pkg/enums"
import "code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
type AgentUserOptionResponse struct {
ID int64 `json:"id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
}
type AgentProfileResponse struct {
ID int64 `json:"id"`
@@ -1,6 +1,6 @@
package response
import "agent-desk/internal/pkg/enums"
import "code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
type AgentRunResponse struct {
ID int64 `json:"id"`
+2 -2
View File
@@ -1,8 +1,8 @@
package response
import (
"agent-desk/internal/models"
"agent-desk/internal/pkg/enums"
"code.tczkiot.com/wlw/ai-agent/internal/models"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
)
type AIAgentTeamResponse struct {
@@ -4,7 +4,7 @@ import (
"encoding/json"
"testing"
"agent-desk/internal/models"
"code.tczkiot.com/wlw/ai-agent/internal/models"
)
func TestBuildAIConfigResponseOmitsAPIKey(t *testing.T) {
@@ -1,10 +1,10 @@
package response
import (
"agent-desk/internal/ai/workflow/dsl"
workflowregistry "agent-desk/internal/ai/workflow/registry"
workflowvalidator "agent-desk/internal/ai/workflow/validator"
"agent-desk/internal/pkg/enums"
"code.tczkiot.com/wlw/ai-agent/internal/ai/workflow/dsl"
workflowregistry "code.tczkiot.com/wlw/ai-agent/internal/ai/workflow/registry"
workflowvalidator "code.tczkiot.com/wlw/ai-agent/internal/ai/workflow/validator"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
)
type AIWorkflowResponse struct {
+1 -1
View File
@@ -1,6 +1,6 @@
package response
import "agent-desk/internal/pkg/enums"
import "code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
type AssetResponse struct {
ID int64 `json:"id"`
+1 -22
View File
@@ -1,26 +1,5 @@
package response
import "agent-desk/internal/pkg/enums"
type AuthUserResponse struct {
ID int64 `json:"id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
Status enums.Status `json:"status"`
Roles []string `json:"roles"`
}
type LoginResponse struct {
AccessToken string `json:"accessToken"`
ExpiresAt string `json:"expiresAt"`
User *AuthUserResponse `json:"user"`
Permissions []string `json:"permissions"`
Roles []string `json:"roles"`
}
type PublicConfigResponse struct {
Language string `json:"language"`
WxWorkEnabled bool `json:"wxworkEnabled"`
OIDCEnabled bool `json:"oidcEnabled"`
Language string `json:"language"`
}
@@ -1,9 +1,9 @@
package response
import (
"agent-desk/internal/models"
"agent-desk/internal/pkg/enums"
"agent-desk/internal/pkg/utils"
"code.tczkiot.com/wlw/ai-agent/internal/models"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/utils"
)
type ChannelResponse struct {
@@ -1,6 +1,6 @@
package response
import "agent-desk/internal/pkg/enums"
import "code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
type CompanyResponse struct {
ID int64 `json:"id"`
@@ -1,6 +1,6 @@
package response
import "agent-desk/internal/pkg/enums"
import "code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
type ConversationTagResponse struct {
ID int64 `json:"id"`
@@ -1,6 +1,6 @@
package response
import "agent-desk/internal/pkg/enums"
import "code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
type CustomerContactResponse struct {
ID int64 `json:"id"`
@@ -1,6 +1,6 @@
package response
import "agent-desk/internal/pkg/enums"
import "code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
type CustomerResponse struct {
ID int64 `json:"id"`
@@ -1,13 +0,0 @@
package response
type CustomerSessionCustomerResponse struct {
ID int64 `json:"id"`
Name string `json:"name"`
}
type CustomerSessionExchangeResponse struct {
CustomerSessionToken string `json:"customerSessionToken"`
ExpiresAt string `json:"expiresAt"`
IdentityKey string `json:"identityKey"`
Customer CustomerSessionCustomerResponse `json:"customer"`
}
@@ -1,7 +1,7 @@
package response
import (
"agent-desk/internal/pkg/enums"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
"time"
)
+2 -2
View File
@@ -1,8 +1,8 @@
package response
import (
"agent-desk/internal/ai/mcps"
"agent-desk/internal/pkg/enums"
"code.tczkiot.com/wlw/ai-agent/internal/ai/mcps"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
)
type MCPConnectionResponse struct {
@@ -1,6 +1,6 @@
package response
import "agent-desk/internal/pkg/enums"
import "code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
type MessageResponse struct {
ID int64 `json:"id"`
@@ -1,6 +1,6 @@
package response
import "agent-desk/internal/pkg/enums"
import "code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
type QuickReplyResponse struct {
ID int64 `json:"id"`
+1 -1
View File
@@ -1,6 +1,6 @@
package response
import "agent-desk/internal/pkg/enums"
import "code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
type TagResponse struct {
ID int64 `json:"id"`
+1 -1
View File
@@ -1,6 +1,6 @@
package response
import "agent-desk/internal/pkg/enums"
import "code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
type TicketProgressResponse struct {
ID int64 `json:"id"`
-29
View File
@@ -57,32 +57,3 @@ func IsValidGender(gender int) bool {
}
return false
}
type ThirdProvider string
const (
ThirdProviderWxWork ThirdProvider = "wxwork"
ThirdProviderDingtalk ThirdProvider = "dingtalk"
ThirdProviderOIDC ThirdProvider = "oidc"
)
var ThirdProviderValues = []ThirdProvider{ThirdProviderWxWork, ThirdProviderDingtalk, ThirdProviderOIDC}
var thirdProviderLabelMap = map[ThirdProvider]string{
ThirdProviderWxWork: "企业微信",
ThirdProviderDingtalk: "钉钉",
ThirdProviderOIDC: "OIDC",
}
func GetThirdProviderLabel(provider ThirdProvider) string {
return thirdProviderLabelMap[provider]
}
func IsValidThirdProvider(provider string) bool {
for _, p := range ThirdProviderValues {
if string(p) == provider {
return true
}
}
return false
}
-1
View File
@@ -254,7 +254,6 @@ const (
IMRealtimeEventConversationClosed = "conversation.closed"
IMRealtimeEventConversationRead = "conversation.read"
IMRealtimeEventNotificationCreated = "notification.created"
IMRealtimeEventCustomerSessionRefresh = "customer_session.refresh"
)
const (
+1 -1
View File
@@ -1,7 +1,7 @@
package errorsx
import (
"agent-desk/internal/pkg/i18nx"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/i18nx"
"github.com/mlogclub/simple/web"
)
+3 -3
View File
@@ -1,9 +1,9 @@
package httpx
import (
"agent-desk/internal/pkg/httpx/params"
"agent-desk/internal/pkg/openidentity"
"agent-desk/internal/pkg/tracex"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/httpx/params"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/openidentity"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/tracex"
"github.com/gin-gonic/gin"
"github.com/mlogclub/simple/common/strs"
+1 -1
View File
@@ -1,7 +1,7 @@
package httpx
import (
"agent-desk/internal/pkg/i18nx"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/i18nx"
"github.com/gin-gonic/gin"
"github.com/mlogclub/simple/web"
+1 -1
View File
@@ -1,7 +1,7 @@
package params
import (
"agent-desk/internal/pkg/errorsx"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/errorsx"
"errors"
"fmt"
"log/slog"
+1 -1
View File
@@ -1,7 +1,7 @@
package httpx
import (
"agent-desk/internal/pkg/i18nx"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/i18nx"
"net/http"
"strconv"
+2 -2
View File
@@ -1,8 +1,8 @@
package httpx
import (
"agent-desk/internal/pkg/errorsx"
"agent-desk/internal/pkg/i18nx"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/errorsx"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/i18nx"
"net/http"
"github.com/gin-gonic/gin"
+2 -2
View File
@@ -1,8 +1,8 @@
package httpx
import (
"agent-desk/internal/pkg/errorsx"
"agent-desk/internal/pkg/i18nx"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/errorsx"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/i18nx"
"encoding/json"
"errors"
"net/http"
-25
View File
@@ -32,18 +32,6 @@ error.e0032: "MCP endpoint is required."
error.e0033: "MCP server is not enabled."
error.e0034: "MCP server configuration not found."
error.e0035: "MCP is not enabled."
error.e0036: "OIDC clientId is not configured."
error.e0037: "OIDC clientSecret is not configured."
error.e0038: "OIDC id_token is missing."
error.e0039: "OIDC issuer is not configured."
error.e0040: "OIDC redirectUrl is not configured."
error.e0041: "OIDC authorization code is required."
error.e0042: "OIDC user information not found."
error.e0043: "OIDC user identifier not found."
error.e0044: "OIDC sign-in secret is not configured."
error.e0045: "OIDC sign-in is not enabled."
error.e0046: "OIDC sign-in state is invalid or has expired."
error.e0047: "The system user linked to this OIDC account does not exist."
error.e0048: "OSS accessKeyId is not configured."
error.e0049: "OSS accessKeySecret is not configured."
error.e0050: "OSS bucket is not configured."
@@ -183,8 +171,6 @@ error.e0183: "Closed conversations cannot be linked to customers."
error.e0184: "Restore this deleted Skill before changing its status."
error.e0185: "This AI Agent is linked to channels and cannot be deleted."
error.e0186: "Platform message not found."
error.e0187: "The current OIDC binding has been disabled."
error.e0188: "The current WeCom binding has been disabled."
error.e0189: "This conversation is not currently handled by AI."
error.e0190: "This conversation has already been assigned."
error.e0191: "This conversation has been assigned to another agent."
@@ -356,22 +342,11 @@ error.mcp.connectServerFailed: "Failed to connect to MCP Server: %v"
error.wxwork.unsupportedOutboundMessageType: "Unsupported WeCom outbound message type: %s"
error.wxwork.currentUnsupportedOutboundMessageType: "The current WeCom outbound message type is not supported yet: %s"
error.param.required: "Parameter %s is required."
error.oidc.loginDisabled: "OIDC login is not enabled."
error.oidc.stateSecretMissing: "OIDC login secret is not configured."
error.oidc.profileMissing: "OIDC user information does not exist."
error.oidc.bindingDisabled: "The current OIDC binding has been disabled."
error.oidc.boundUserMissing: "The system user bound to this OIDC account does not exist."
error.wxwork.loginDisabled: "WeCom login is not enabled."
error.wxwork.loginSecretMissing: "WeCom login secret is not configured."
error.wxwork.profileMissing: "WeCom user information does not exist."
error.wxwork.bindingDisabled: "The current WeCom binding has been disabled."
error.wxwork.boundUserMissing: "The system user bound to this WeCom account does not exist."
error.wxwork.userIDMissing: "Failed to get the WeCom user ID."
error.wxwork.userIDTaken: "The WeCom user ID is already used as a system username."
error.wxwork.mobileTaken: "The WeCom mobile number is already used by a system user."
error.wxwork.emailTaken: "The WeCom email address is already used by a system user."
error.wxwork.configIncomplete: "WeCom is not enabled or its configuration is incomplete."
error.customerSession.secretMissing: "Customer session secret is not configured."
error.conversation.createFailed: "Failed to create conversation."
error.conversation.tagNotFound: "Tag not found."
error.aiConfig.noneEnabled: "No available AI configuration is configured."
-25
View File
@@ -32,18 +32,6 @@ error.e0032: "MCP endpoint不能为空"
error.e0033: "MCP服务未启用"
error.e0034: "MCP服务配置不存在"
error.e0035: "MCP未启用"
error.e0036: "OIDC clientId 未配置"
error.e0037: "OIDC clientSecret 未配置"
error.e0038: "OIDC id_token 不存在"
error.e0039: "OIDC issuer 未配置"
error.e0040: "OIDC redirectUrl 未配置"
error.e0041: "OIDC 授权 code 不能为空"
error.e0042: "OIDC 用户信息不存在"
error.e0043: "OIDC 用户标识不存在"
error.e0044: "OIDC 登录密钥未配置"
error.e0045: "OIDC 登录未启用"
error.e0046: "OIDC 登录状态无效或已过期"
error.e0047: "OIDC 账号绑定的系统用户不存在"
error.e0048: "OSS accessKeyId 未配置"
error.e0049: "OSS accessKeySecret 未配置"
error.e0050: "OSS bucket 未配置"
@@ -183,8 +171,6 @@ error.e0183: "已关闭的会话无法关联客户"
error.e0184: "已删除的 Skill 不能直接修改状态,请先恢复"
error.e0185: "已有接入渠道绑定该 AI Agent,无法删除"
error.e0186: "平台消息不存在"
error.e0187: "当前 OIDC 绑定已停用"
error.e0188: "当前企业微信绑定已停用"
error.e0189: "当前会话不处于 AI 接待状态"
error.e0190: "当前会话已分配客服"
error.e0191: "当前会话已分配给其他客服"
@@ -356,22 +342,11 @@ error.mcp.connectServerFailed: "连接 MCP Server 失败: %v"
error.wxwork.unsupportedOutboundMessageType: "不支持的企业微信下行消息类型: %s"
error.wxwork.currentUnsupportedOutboundMessageType: "当前暂不支持企业微信下行消息类型: %s"
error.param.required: "参数:%s不能为空"
error.oidc.loginDisabled: "OIDC 登录未启用"
error.oidc.stateSecretMissing: "OIDC 登录密钥未配置"
error.oidc.profileMissing: "OIDC 用户信息不存在"
error.oidc.bindingDisabled: "当前 OIDC 绑定已停用"
error.oidc.boundUserMissing: "OIDC 账号绑定的系统用户不存在"
error.wxwork.loginDisabled: "企业微信登录未启用"
error.wxwork.loginSecretMissing: "企业微信登录密钥未配置"
error.wxwork.profileMissing: "企业微信用户信息不存在"
error.wxwork.bindingDisabled: "当前企业微信绑定已停用"
error.wxwork.boundUserMissing: "企业微信账号绑定的系统用户不存在"
error.wxwork.userIDMissing: "企业微信用户ID获取失败"
error.wxwork.userIDTaken: "企业微信用户ID已被系统用户名占用"
error.wxwork.mobileTaken: "企业微信手机号已被系统用户占用"
error.wxwork.emailTaken: "企业微信邮箱已被系统用户占用"
error.wxwork.configIncomplete: "企业微信未启用或配置不完整"
error.customerSession.secretMissing: "客服会话密钥未配置"
error.conversation.createFailed: "创建会话失败"
error.conversation.tagNotFound: "标签不存在"
error.aiConfig.noneEnabled: "未配置可用的 AI 配置"
+4 -131
View File
@@ -1,139 +1,12 @@
package openidentity
import (
"agent-desk/internal/pkg/enums"
"agent-desk/internal/pkg/errorsx"
"errors"
"net/url"
"strings"
import "code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
"agent-desk/internal/pkg/httpx/params"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
"github.com/mlogclub/simple/common/strs"
)
// ExternalUser 外部访客身份(IM 客户),与站内 AuthPrincipal 区分。
// ExternalUser is a be-system user identity adapted for customer-service
// conversations. Authentication is completed by the host before this value is
// created; this package contains no token parsing or verification.
type ExternalUser struct {
ExternalSource enums.ExternalSource `json:"externalSource"`
ExternalID string `json:"externalId"`
ExternalName string `json:"externalName"`
}
type UserTokenClaims struct {
UserID string `json:"userId"`
Name string `json:"name"`
jwt.RegisteredClaims
}
func GetExternalUser(ctx *gin.Context, secret string) (*ExternalUser, error) {
if userToken := getUserToken(ctx); strs.IsNotBlank(userToken) {
claims, err := verifyUserToken(userToken, secret)
if err != nil {
return nil, err
}
return &ExternalUser{
ExternalSource: enums.ExternalSourceUser,
ExternalID: claims.UserID,
ExternalName: claims.Name,
}, nil
}
return getGuestUser(ctx)
}
func verifyUserToken(userToken, secret string) (*UserTokenClaims, error) {
if strs.IsBlank(userToken) {
return nil, errorsx.UnauthorizedI18n("error.e0263")
}
if strs.IsBlank(secret) {
return nil, errorsx.UnauthorizedI18n("error.e0266")
}
claims := &UserTokenClaims{}
token, err := jwt.ParseWithClaims(userToken, claims, func(token *jwt.Token) (any, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("unsupported signing method")
}
return []byte(secret), nil
}, jwt.WithExpirationRequired(), jwt.WithValidMethods([]string{
jwt.SigningMethodHS256.Alg(),
jwt.SigningMethodHS384.Alg(),
jwt.SigningMethodHS512.Alg(),
}))
if err != nil {
if errors.Is(err, jwt.ErrTokenExpired) {
return nil, errorsx.UnauthorizedI18n("error.e0264")
}
return nil, errorsx.UnauthorizedI18n("error.e0265")
}
if token == nil || !token.Valid {
return nil, errorsx.UnauthorizedI18n("error.e0265")
}
if strs.IsBlank(claims.UserID) {
return nil, errorsx.UnauthorizedI18n("error.e0262")
}
if strs.IsBlank(claims.Name) {
return nil, errorsx.UnauthorizedI18n("error.e0261")
}
if claims.ExpiresAt == nil {
return nil, errorsx.UnauthorizedI18n("error.e0264")
}
return claims, nil
}
func getUserToken(ctx *gin.Context) string {
auth := strings.TrimSpace(ctx.GetHeader("Authorization"))
if len(auth) > 7 && strings.EqualFold(auth[:7], "Bearer ") {
if token := strings.TrimSpace(auth[7:]); token != "" {
return token
}
}
userToken, _ := params.Get(ctx, "userToken")
return strings.TrimSpace(userToken)
}
func getGuestUser(ctx *gin.Context) (*ExternalUser, error) {
externalID := getExternalID(ctx)
if strs.IsBlank(externalID) {
return nil, errorsx.UnauthorizedI18n("error.e0262")
}
return &ExternalUser{
ExternalSource: enums.ExternalSourceGuest,
ExternalID: externalID,
ExternalName: getExternalName(ctx),
}, nil
}
func getExternalID(ctx *gin.Context) string {
externalID := ctx.GetHeader("X-External-Id")
if strs.IsBlank(externalID) {
externalID, _ = params.Get(ctx, "externalId")
}
return externalID
}
func getExternalName(ctx *gin.Context) string {
externalName := ctx.GetHeader("X-External-Name")
if strs.IsBlank(externalName) {
externalName, _ = params.Get(ctx, "externalName")
}
if strs.IsNotBlank(externalName) {
externalName, _ = url.QueryUnescape(externalName)
}
return externalName
}
func decodeExternalDisplayName(s string) string {
s = strings.TrimSpace(s)
if s == "" {
return ""
}
dec, err := url.QueryUnescape(s)
if err != nil {
return s
}
return strings.TrimSpace(dec)
}
@@ -1,86 +0,0 @@
package openidentity
import (
"testing"
"time"
"github.com/golang-jwt/jwt/v5"
)
func TestVerifyUserTokenOK(t *testing.T) {
token := signTestUserToken(t, jwt.SigningMethodHS256, map[string]any{
"userId": "u_10001",
"name": "张三",
"exp": time.Now().Add(time.Hour).Unix(),
}, "secret")
claims, err := verifyUserToken(token, "secret")
if err != nil {
t.Fatalf("expected token to verify: %v", err)
}
if claims.UserID != "u_10001" || claims.Name != "张三" {
t.Fatalf("unexpected claims: %#v", claims)
}
}
func TestVerifyUserTokenUsesJWTHeaderAlgorithm(t *testing.T) {
token := signTestUserToken(t, jwt.SigningMethodHS384, map[string]any{
"userId": "u_10001",
"name": "张三",
"exp": time.Now().Add(time.Hour).Unix(),
}, "secret")
claims, err := verifyUserToken(token, "secret")
if err != nil {
t.Fatalf("expected HS384 token to verify from JWT header: %v", err)
}
if claims.UserID != "u_10001" || claims.Name != "张三" {
t.Fatalf("unexpected claims: %#v", claims)
}
}
func TestVerifyUserTokenRejectsInvalidSignature(t *testing.T) {
token := signTestUserToken(t, jwt.SigningMethodHS256, map[string]any{
"userId": "u_10001",
"name": "张三",
"exp": time.Now().Add(time.Hour).Unix(),
}, "secret")
if _, err := verifyUserToken(token, "other-secret"); err == nil {
t.Fatalf("expected invalid signature to fail")
}
}
func TestVerifyUserTokenRejectsExpiredToken(t *testing.T) {
token := signTestUserToken(t, jwt.SigningMethodHS256, map[string]any{
"userId": "u_10001",
"name": "张三",
"exp": time.Now().Add(-time.Minute).Unix(),
}, "secret")
if _, err := verifyUserToken(token, "secret"); err == nil {
t.Fatalf("expected expired token to fail")
}
}
func TestVerifyUserTokenRequiresUserIDAndName(t *testing.T) {
tests := []map[string]any{
{"name": "张三", "exp": time.Now().Add(time.Hour).Unix()},
{"userId": "u_10001", "exp": time.Now().Add(time.Hour).Unix()},
}
for _, payload := range tests {
token := signTestUserToken(t, jwt.SigningMethodHS256, payload, "secret")
if _, err := verifyUserToken(token, "secret"); err == nil {
t.Fatalf("expected payload %#v to fail", payload)
}
}
}
func signTestUserToken(t *testing.T, method jwt.SigningMethod, payload map[string]any, secret string) string {
t.Helper()
token, err := jwt.NewWithClaims(method, jwt.MapClaims(payload)).SignedString([]byte(secret))
if err != nil {
t.Fatal(err)
}
return token
}
+2 -2
View File
@@ -3,8 +3,8 @@ package toolx
import (
"strings"
"agent-desk/internal/pkg/enums"
"agent-desk/internal/pkg/i18nx"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/i18nx"
)
type ToolSpec struct {
+1 -1
View File
@@ -3,7 +3,7 @@ package toolx
import (
"testing"
"agent-desk/internal/pkg/i18nx"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/i18nx"
)
func TestResolveToolMetadata(t *testing.T) {
+1 -1
View File
@@ -3,7 +3,7 @@ package toolx
import (
"strings"
"agent-desk/internal/pkg/dto/request"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/dto/request"
)
const (
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"encoding/json"
"strings"
"agent-desk/internal/pkg/dto/request"
"agent-desk/internal/pkg/errorsx"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/dto/request"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/errorsx"
)
func BuildMCPToolCode(serverCode, toolName string) string {
+1 -1
View File
@@ -1,7 +1,7 @@
package utils
import (
"agent-desk/internal/pkg/enums"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
"strings"
"golang.org/x/net/html"
+1 -1
View File
@@ -1,7 +1,7 @@
package utils
import (
"agent-desk/internal/pkg/enums"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
"reflect"
"testing"
)
+4 -4
View File
@@ -1,10 +1,10 @@
package utils
import (
"agent-desk/internal/models"
"agent-desk/internal/pkg/enums"
"agent-desk/internal/repositories"
"agent-desk/internal/services/storage"
"code.tczkiot.com/wlw/ai-agent/internal/models"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
"code.tczkiot.com/wlw/ai-agent/internal/repositories"
"code.tczkiot.com/wlw/ai-agent/internal/services/storage"
"bytes"
"encoding/json"
"fmt"
+3 -3
View File
@@ -1,9 +1,9 @@
package utils
import (
"agent-desk/internal/models"
"agent-desk/internal/pkg/config"
"agent-desk/internal/pkg/enums"
"code.tczkiot.com/wlw/ai-agent/internal/models"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/config"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
"strings"
"testing"
"time"
+3 -3
View File
@@ -1,9 +1,9 @@
package utils
import (
"agent-desk/internal/models"
"agent-desk/internal/pkg/dto"
"agent-desk/internal/pkg/errorsx"
"code.tczkiot.com/wlw/ai-agent/internal/models"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/dto"
"code.tczkiot.com/wlw/ai-agent/internal/pkg/errorsx"
"crypto/rand"
"strconv"
"strings"