feat: harden loose-offline sync for user JWT, schema, and console ops
Enable Binding-scoped agent push/pull, empty-table schema ensure, SyncPage inspect/drop-table, default module import, and agent-bound publish docs from the 宇恒联调意见. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -23,37 +23,47 @@ const (
|
||||
)
|
||||
|
||||
type Account struct {
|
||||
AgentID int64 `json:"agent_id"`
|
||||
TenantID int64 `json:"tenant_id"`
|
||||
Name string `json:"name"`
|
||||
ClientID string `json:"client_id"`
|
||||
HostKey string `json:"host_key,omitempty"`
|
||||
RoleID int64 `json:"role_id,omitempty"`
|
||||
RoleCode string `json:"role_code,omitempty"`
|
||||
RoleName string `json:"role_name,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Perms []string `json:"permissions"`
|
||||
AppSlugs []string `json:"app_slugs"`
|
||||
CreatedBy int64 `json:"created_by"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
LastTokenAt *time.Time `json:"last_token_at,omitempty"`
|
||||
AgentID int64 `json:"agent_id"`
|
||||
TenantID int64 `json:"tenant_id"`
|
||||
Name string `json:"name"`
|
||||
ClientID string `json:"client_id"`
|
||||
HostKey string `json:"host_key,omitempty"`
|
||||
RoleID int64 `json:"role_id,omitempty"`
|
||||
RoleCode string `json:"role_code,omitempty"`
|
||||
RoleName string `json:"role_name,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Perms []string `json:"permissions"`
|
||||
AppSlugs []string `json:"app_slugs"`
|
||||
// Z8b:智能体绑定的同步通道 / 线上库(模块与松离线同一目标)
|
||||
ChannelID string `json:"channel_id,omitempty"`
|
||||
OnlineDBID string `json:"online_db_id,omitempty"`
|
||||
DatabaseName string `json:"database_name,omitempty"`
|
||||
CreatedBy int64 `json:"created_by"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
LastTokenAt *time.Time `json:"last_token_at,omitempty"`
|
||||
}
|
||||
|
||||
type CreateInput struct {
|
||||
Name string
|
||||
Perms []string
|
||||
AppSlugs []string
|
||||
Status string // 空则 active
|
||||
HostKey string
|
||||
RoleID int64
|
||||
Name string
|
||||
Perms []string
|
||||
AppSlugs []string
|
||||
Status string // 空则 active
|
||||
HostKey string
|
||||
RoleID int64
|
||||
ChannelID string
|
||||
OnlineDBID string
|
||||
DatabaseName string
|
||||
}
|
||||
|
||||
type UpdateInput struct {
|
||||
Name *string
|
||||
Status *string
|
||||
Perms *[]string
|
||||
AppSlugs *[]string
|
||||
RoleID *int64
|
||||
Name *string
|
||||
Status *string
|
||||
Perms *[]string
|
||||
AppSlugs *[]string
|
||||
RoleID *int64
|
||||
ChannelID *string
|
||||
OnlineDBID *string
|
||||
DatabaseName *string
|
||||
}
|
||||
|
||||
type Store interface {
|
||||
@@ -165,17 +175,20 @@ func (s *MemoryStore) createLocked(tenantID, createdBy int64, in CreateInput) (*
|
||||
s.seq++
|
||||
a := &memAcc{
|
||||
Account: Account{
|
||||
AgentID: s.seq,
|
||||
TenantID: tenantID,
|
||||
Name: strings.TrimSpace(in.Name),
|
||||
ClientID: cid,
|
||||
HostKey: strings.TrimSpace(in.HostKey),
|
||||
RoleID: in.RoleID,
|
||||
Status: st,
|
||||
Perms: authx.NormalizePerms(uniq(in.Perms)),
|
||||
AppSlugs: uniq(in.AppSlugs),
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
AgentID: s.seq,
|
||||
TenantID: tenantID,
|
||||
Name: strings.TrimSpace(in.Name),
|
||||
ClientID: cid,
|
||||
HostKey: strings.TrimSpace(in.HostKey),
|
||||
RoleID: in.RoleID,
|
||||
Status: st,
|
||||
Perms: authx.NormalizePerms(uniq(in.Perms)),
|
||||
AppSlugs: uniq(in.AppSlugs),
|
||||
ChannelID: strings.TrimSpace(in.ChannelID),
|
||||
OnlineDBID: strings.TrimSpace(in.OnlineDBID),
|
||||
DatabaseName: strings.TrimSpace(in.DatabaseName),
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
},
|
||||
SecretHash: string(hash),
|
||||
}
|
||||
@@ -214,6 +227,15 @@ func (s *MemoryStore) Update(_ context.Context, tenantID, agentID int64, in Upda
|
||||
if in.RoleID != nil {
|
||||
a.RoleID = *in.RoleID
|
||||
}
|
||||
if in.ChannelID != nil {
|
||||
a.ChannelID = strings.TrimSpace(*in.ChannelID)
|
||||
}
|
||||
if in.OnlineDBID != nil {
|
||||
a.OnlineDBID = strings.TrimSpace(*in.OnlineDBID)
|
||||
}
|
||||
if in.DatabaseName != nil {
|
||||
a.DatabaseName = strings.TrimSpace(*in.DatabaseName)
|
||||
}
|
||||
cp := cloneAcc(&a.Account)
|
||||
return &cp, nil
|
||||
}
|
||||
@@ -327,7 +349,9 @@ func NewPostgresStore(db *sql.DB) *PostgresStore { return &PostgresStore{DB: db}
|
||||
|
||||
func (s *PostgresStore) List(ctx context.Context, tenantID int64) ([]Account, error) {
|
||||
rows, err := s.DB.QueryContext(ctx, `
|
||||
SELECT agent_id, tenant_id, name, client_id, COALESCE(host_key,''), COALESCE(role_id,0), status, created_by, created_at, last_token_at
|
||||
SELECT agent_id, tenant_id, name, client_id, COALESCE(host_key,''), COALESCE(role_id,0),
|
||||
COALESCE(channel_id,''), COALESCE(online_db_id,''), COALESCE(database_name,''),
|
||||
status, created_by, created_at, last_token_at
|
||||
FROM platform_meta.agent_accounts WHERE tenant_id=$1
|
||||
ORDER BY CASE status WHEN 'pending' THEN 0 WHEN 'active' THEN 1 ELSE 2 END, agent_id`, tenantID)
|
||||
if err != nil {
|
||||
@@ -338,7 +362,9 @@ ORDER BY CASE status WHEN 'pending' THEN 0 WHEN 'active' THEN 1 ELSE 2 END, agen
|
||||
for rows.Next() {
|
||||
var a Account
|
||||
var last sql.NullTime
|
||||
if err := rows.Scan(&a.AgentID, &a.TenantID, &a.Name, &a.ClientID, &a.HostKey, &a.RoleID, &a.Status, &a.CreatedBy, &a.CreatedAt, &last); err != nil {
|
||||
if err := rows.Scan(&a.AgentID, &a.TenantID, &a.Name, &a.ClientID, &a.HostKey, &a.RoleID,
|
||||
&a.ChannelID, &a.OnlineDBID, &a.DatabaseName,
|
||||
&a.Status, &a.CreatedBy, &a.CreatedAt, &last); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if last.Valid {
|
||||
@@ -359,9 +385,13 @@ func (s *PostgresStore) Get(ctx context.Context, tenantID, agentID int64) (*Acco
|
||||
var a Account
|
||||
var last sql.NullTime
|
||||
err := s.DB.QueryRowContext(ctx, `
|
||||
SELECT agent_id, tenant_id, name, client_id, COALESCE(host_key,''), COALESCE(role_id,0), status, created_by, created_at, last_token_at
|
||||
SELECT agent_id, tenant_id, name, client_id, COALESCE(host_key,''), COALESCE(role_id,0),
|
||||
COALESCE(channel_id,''), COALESCE(online_db_id,''), COALESCE(database_name,''),
|
||||
status, created_by, created_at, last_token_at
|
||||
FROM platform_meta.agent_accounts WHERE agent_id=$1 AND tenant_id=$2`, agentID, tenantID,
|
||||
).Scan(&a.AgentID, &a.TenantID, &a.Name, &a.ClientID, &a.HostKey, &a.RoleID, &a.Status, &a.CreatedBy, &a.CreatedAt, &last)
|
||||
).Scan(&a.AgentID, &a.TenantID, &a.Name, &a.ClientID, &a.HostKey, &a.RoleID,
|
||||
&a.ChannelID, &a.OnlineDBID, &a.DatabaseName,
|
||||
&a.Status, &a.CreatedBy, &a.CreatedAt, &last)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, fmt.Errorf("agent not found")
|
||||
}
|
||||
@@ -390,9 +420,13 @@ func (s *PostgresStore) Register(ctx context.Context, tenantID int64, name, host
|
||||
var a Account
|
||||
var last sql.NullTime
|
||||
err := s.DB.QueryRowContext(ctx, `
|
||||
SELECT agent_id, tenant_id, name, client_id, COALESCE(host_key,''), COALESCE(role_id,0), status, created_by, created_at, last_token_at
|
||||
SELECT agent_id, tenant_id, name, client_id, COALESCE(host_key,''), COALESCE(role_id,0),
|
||||
COALESCE(channel_id,''), COALESCE(online_db_id,''), COALESCE(database_name,''),
|
||||
status, created_by, created_at, last_token_at
|
||||
FROM platform_meta.agent_accounts WHERE tenant_id=$1 AND host_key=$2`, tenantID, hostKey,
|
||||
).Scan(&a.AgentID, &a.TenantID, &a.Name, &a.ClientID, &a.HostKey, &a.RoleID, &a.Status, &a.CreatedBy, &a.CreatedAt, &last)
|
||||
).Scan(&a.AgentID, &a.TenantID, &a.Name, &a.ClientID, &a.HostKey, &a.RoleID,
|
||||
&a.ChannelID, &a.OnlineDBID, &a.DatabaseName,
|
||||
&a.Status, &a.CreatedBy, &a.CreatedAt, &last)
|
||||
if err == nil {
|
||||
if a.Status == StatusPending {
|
||||
secret, err := s.RotateSecret(ctx, tenantID, a.AgentID)
|
||||
@@ -439,11 +473,16 @@ func (s *PostgresStore) insert(ctx context.Context, tenantID, createdBy int64, i
|
||||
}
|
||||
var a Account
|
||||
err = s.DB.QueryRowContext(ctx, `
|
||||
INSERT INTO platform_meta.agent_accounts(tenant_id, name, client_id, client_secret_hash, status, created_by, host_key, role_id)
|
||||
VALUES($1,$2,$3,$4,$5,$6,$7,NULLIF($8,0))
|
||||
RETURNING agent_id, tenant_id, name, client_id, COALESCE(host_key,''), COALESCE(role_id,0), status, created_by, created_at`,
|
||||
INSERT INTO platform_meta.agent_accounts(tenant_id, name, client_id, client_secret_hash, status, created_by, host_key, role_id, channel_id, online_db_id, database_name)
|
||||
VALUES($1,$2,$3,$4,$5,$6,$7,NULLIF($8,0),$9,$10,$11)
|
||||
RETURNING agent_id, tenant_id, name, client_id, COALESCE(host_key,''), COALESCE(role_id,0),
|
||||
COALESCE(channel_id,''), COALESCE(online_db_id,''), COALESCE(database_name,''),
|
||||
status, created_by, created_at`,
|
||||
tenantID, name, cid, string(hash), st, createdBy, strings.TrimSpace(in.HostKey), in.RoleID,
|
||||
).Scan(&a.AgentID, &a.TenantID, &a.Name, &a.ClientID, &a.HostKey, &a.RoleID, &a.Status, &a.CreatedBy, &a.CreatedAt)
|
||||
strings.TrimSpace(in.ChannelID), strings.TrimSpace(in.OnlineDBID), strings.TrimSpace(in.DatabaseName),
|
||||
).Scan(&a.AgentID, &a.TenantID, &a.Name, &a.ClientID, &a.HostKey, &a.RoleID,
|
||||
&a.ChannelID, &a.OnlineDBID, &a.DatabaseName,
|
||||
&a.Status, &a.CreatedBy, &a.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@@ -462,6 +501,7 @@ func (s *PostgresStore) Update(ctx context.Context, tenantID, agentID int64, in
|
||||
}
|
||||
name, status := a.Name, a.Status
|
||||
roleID := a.RoleID
|
||||
channelID, onlineDBID, dbName := a.ChannelID, a.OnlineDBID, a.DatabaseName
|
||||
if in.Name != nil {
|
||||
name = strings.TrimSpace(*in.Name)
|
||||
}
|
||||
@@ -474,9 +514,20 @@ func (s *PostgresStore) Update(ctx context.Context, tenantID, agentID int64, in
|
||||
if in.RoleID != nil {
|
||||
roleID = *in.RoleID
|
||||
}
|
||||
if in.ChannelID != nil {
|
||||
channelID = strings.TrimSpace(*in.ChannelID)
|
||||
}
|
||||
if in.OnlineDBID != nil {
|
||||
onlineDBID = strings.TrimSpace(*in.OnlineDBID)
|
||||
}
|
||||
if in.DatabaseName != nil {
|
||||
dbName = strings.TrimSpace(*in.DatabaseName)
|
||||
}
|
||||
if _, err := s.DB.ExecContext(ctx, `
|
||||
UPDATE platform_meta.agent_accounts SET name=$1, status=$2, role_id=NULLIF($3,0) WHERE agent_id=$4 AND tenant_id=$5`,
|
||||
name, status, roleID, agentID, tenantID); err != nil {
|
||||
UPDATE platform_meta.agent_accounts SET name=$1, status=$2, role_id=NULLIF($3,0),
|
||||
channel_id=$4, online_db_id=$5, database_name=$6
|
||||
WHERE agent_id=$7 AND tenant_id=$8`,
|
||||
name, status, roleID, channelID, onlineDBID, dbName, agentID, tenantID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
perms, slugs := a.Perms, a.AppSlugs
|
||||
@@ -525,9 +576,13 @@ func (s *PostgresStore) Authenticate(ctx context.Context, clientID, clientSecret
|
||||
var hash string
|
||||
var last sql.NullTime
|
||||
err := s.DB.QueryRowContext(ctx, `
|
||||
SELECT agent_id, tenant_id, name, client_id, COALESCE(host_key,''), COALESCE(role_id,0), client_secret_hash, status, created_by, created_at, last_token_at
|
||||
SELECT agent_id, tenant_id, name, client_id, COALESCE(host_key,''), COALESCE(role_id,0),
|
||||
COALESCE(channel_id,''), COALESCE(online_db_id,''), COALESCE(database_name,''),
|
||||
client_secret_hash, status, created_by, created_at, last_token_at
|
||||
FROM platform_meta.agent_accounts WHERE client_id=$1`, clientID,
|
||||
).Scan(&a.AgentID, &a.TenantID, &a.Name, &a.ClientID, &a.HostKey, &a.RoleID, &hash, &a.Status, &a.CreatedBy, &a.CreatedAt, &last)
|
||||
).Scan(&a.AgentID, &a.TenantID, &a.Name, &a.ClientID, &a.HostKey, &a.RoleID,
|
||||
&a.ChannelID, &a.OnlineDBID, &a.DatabaseName,
|
||||
&hash, &a.Status, &a.CreatedBy, &a.CreatedAt, &last)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, fmt.Errorf("invalid client credentials")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user