feat: harden support, localize knowledge and add operator availability [skip ci]

Source-only publication of reviewed commit 64e96a7f423673dc29ad56485be9fc79bce8af1c.

Passed in isolation: focused frontend/backend tests, race checks, TypeScript typecheck, lint, Go compilation, Flowgram build, and deterministic SDK build/tests.

Not accepted as a production release: Next.js export/native assembly remains blocked by environment memory limits; browser interaction and MySQL staging acceptance remain unverified. No production server, database/schema, runtime configuration, deployment, release tag, or workflow execution was performed.

Expected source tree: 0a4a7528e9.
This commit is contained in:
2026-10-16 15:21:42 +00:00
parent 299ea50fb5
commit 2f7a491666
176 changed files with 11869 additions and 2716 deletions

View File

@@ -1,3 +1,5 @@
// Modified in the independent hardening fork; see docs/hardening.
package services
import (
@@ -98,6 +100,15 @@ func (s *channelService) UpdateChannel(req request.UpdateChannelRequest, operato
if current == nil || current.Status == enums.StatusDeleted {
return errorsx.InvalidParamI18n("error.e0208")
}
if current.ChannelType == enums.ChannelTypeWeb && strings.TrimSpace(req.ChannelType) == enums.ChannelTypeWeb {
// Older dashboard clients do not expose this policy yet. Omitting the field
// during an unrelated edit must not silently turn authenticated-only off.
merged, err := s.preserveWebIdentityPolicy(req.ConfigJSON, current.ConfigJSON)
if err != nil {
return err
}
req.ConfigJSON = merged
}
item, err := s.buildChannelModel(req.ID, req.CreateChannelRequest)
if err != nil {
return err
@@ -118,7 +129,7 @@ func (s *channelService) UpdateChannel(req request.UpdateChannelRequest, operato
if item.AIAgentRolloutPercent != current.AIAgentRolloutPercent {
columns["previous_ai_agent_rollout_percent"] = current.AIAgentRolloutPercent
}
return repositories.ChannelRepository.Updates(sqls.DB(), req.ID, columns)
return s.updateConfigIfUnchanged(current, columns)
}
// RollbackChannelAIAgentRollout restores the last channel-level rollout value
@@ -196,6 +207,13 @@ func (s *channelService) ParseWebChannelConfig(raw string) (*dto.WebChannelConfi
if err := json.Unmarshal([]byte(raw), cfg); err != nil {
return nil, err
}
var fields map[string]json.RawMessage
if err := json.Unmarshal([]byte(raw), &fields); err != nil {
return nil, err
}
if policy, ok := fields["requireSignedIdentity"]; ok && string(policy) != "true" && string(policy) != "false" {
return nil, errorsx.InvalidParamI18n("error.e0060")
}
}
cfg.Title = strings.TrimSpace(cfg.Title)
if cfg.Title == "" {
@@ -459,7 +477,7 @@ func (s *channelService) ResetUserTokenSecret(channelID int64, operator *dto.Aut
}
configJSON = string(raw)
}
if err := repositories.ChannelRepository.Updates(sqls.DB(), channelID, map[string]any{
if err := s.updateConfigIfUnchanged(channel, map[string]any{
"config_json": configJSON,
"update_user_id": operator.UserID,
"update_user_name": operator.Username,
@@ -683,3 +701,54 @@ func (s *channelService) buildChannelModel(id int64, req request.CreateChannelRe
Remark: strings.TrimSpace(req.Remark),
}, nil
}
// RequiresSignedIdentity enforces the channel policy on the server, not only in
// the embedding SDK. Invalid web configuration fails closed.
func (s *channelService) RequiresSignedIdentity(channel *models.Channel) bool {
if channel == nil {
return true
}
if channel.ChannelType != enums.ChannelTypeWeb {
return false
}
cfg, err := s.ParseWebChannelConfig(channel.ConfigJSON)
return err != nil || cfg.RequireSignedIdentity
}
func (s *channelService) preserveWebIdentityPolicy(raw, previousRaw string) (string, error) {
fields := map[string]json.RawMessage{}
if strings.TrimSpace(raw) != "" {
if err := json.Unmarshal([]byte(raw), &fields); err != nil {
return "", errorsx.InvalidParamI18n("error.e0060")
}
}
if _, present := fields["requireSignedIdentity"]; present {
return raw, nil
}
previous, err := s.ParseWebChannelConfig(previousRaw)
if err != nil {
return "", errorsx.InvalidParamI18n("error.e0060")
}
if !previous.RequireSignedIdentity {
return raw, nil
}
if fields == nil {
fields = map[string]json.RawMessage{}
}
fields["requireSignedIdentity"] = json.RawMessage("true")
encoded, err := json.Marshal(fields)
return string(encoded), err
}
// Reject rather than retry a stale complete config document: retrying could turn
// an old explicit field into authority to overwrite the new policy/secret.
func (s *channelService) updateConfigIfUnchanged(expected *models.Channel, columns map[string]any) error {
updated, err := repositories.ChannelRepository.UpdatesConfigIfUnchanged(sqls.DB(), expected, columns)
if err != nil {
return errorsx.BusinessErrorI18n(0, "error.channel.configUpdateFailed")
}
if !updated {
return errorsx.InvalidParamI18n("error.channel.configChanged")
}
return nil
}