mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-10-28 03:29:30 +08:00
Each IM platform now declares its connection modes, thread support,
console links and a credential config schema in internal/im/platforms.go.
Fields used by one mode carry x-visible-if on "$mode", which config
schemas read from a context (configschema.ValidateInContext, the
SchemaForm context prop) instead of from sibling fields.
GET /api/v1/im-channels/platforms serves the registered platforms, and
IMChannelPanel renders modes, links and credentials from it, dropping
the per-platform v-if blocks. WeChat keeps its QR binding flow; its
bound fields are declared hidden so the token is still treated as a
secret.
Fix IM channel edits wiping credentials: list responses stopped
carrying credentials in 7099a7e18, so the editor sent an empty map that
replaced the stored one. GET /api/v1/im-channels/:id (Admin) now returns
the channel with secrets redacted, and updates merge credentials key by
key, keeping secrets sent empty or as ***.
111 lines
2.8 KiB
Go
111 lines
2.8 KiB
Go
package im
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
|
|
"github.com/Tencent/WeKnora/internal/plugin/configschema"
|
|
)
|
|
|
|
// ParseCredentials parses the JSONB credentials field into a map.
|
|
func ParseCredentials(data []byte) (map[string]any, error) {
|
|
if len(data) == 0 {
|
|
return map[string]any{}, nil
|
|
}
|
|
var creds map[string]any
|
|
if err := json.Unmarshal(data, &creds); err != nil {
|
|
return nil, err
|
|
}
|
|
return creds, nil
|
|
}
|
|
|
|
// GetString safely extracts a string value from a credentials map.
|
|
func GetString(creds map[string]any, key string) string {
|
|
if v, ok := creds[key]; ok {
|
|
if s, ok := v.(string); ok {
|
|
return s
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// GetBool reads a boolean from JSON credentials (bool, string "true"/"1", or non-zero number).
|
|
func GetBool(creds map[string]any, key string) bool {
|
|
v, ok := creds[key]
|
|
if !ok {
|
|
return false
|
|
}
|
|
switch x := v.(type) {
|
|
case bool:
|
|
return x
|
|
case string:
|
|
s := strings.TrimSpace(strings.ToLower(x))
|
|
return s == "true" || s == "1" || s == "yes"
|
|
case float64:
|
|
return x != 0
|
|
case int:
|
|
return x != 0
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// secretKeys returns the credential keys a platform's schema marks secret.
|
|
func secretKeys(platform string) map[string]bool {
|
|
out := map[string]bool{}
|
|
if s := LookupPlatformInfo(platform).ConfigSchema; s != nil {
|
|
for key, prop := range s.Properties {
|
|
if prop.Secret {
|
|
out[key] = true
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// RedactCredentials returns a channel's credentials for the editor: plain
|
|
// fields as stored, set secrets replaced by configschema.RedactedPlaceholder.
|
|
func RedactCredentials(platform string, raw []byte) (map[string]any, error) {
|
|
creds, err := ParseCredentials(raw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
secrets := secretKeys(platform)
|
|
for key, v := range creds {
|
|
if s, ok := v.(string); ok && s != "" && secrets[key] {
|
|
creds[key] = configschema.RedactedPlaceholder
|
|
}
|
|
}
|
|
return creds, nil
|
|
}
|
|
|
|
// MergeCredentials applies credentials sent by the editor to the stored ones.
|
|
// Keys the update leaves out keep their stored value, and so does a secret
|
|
// sent empty or as the redaction placeholder, since the editor never saw it.
|
|
// A plain field sent empty is cleared. An empty update changes nothing, which
|
|
// keeps an edit that never loaded the credentials from wiping them.
|
|
func MergeCredentials(platform string, stored, incoming []byte) ([]byte, error) {
|
|
update, err := ParseCredentials(incoming)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(update) == 0 {
|
|
return stored, nil
|
|
}
|
|
out, err := ParseCredentials(stored)
|
|
if err != nil {
|
|
// Unreadable stored credentials cannot be merged into; take the update.
|
|
out = map[string]any{}
|
|
}
|
|
secrets := secretKeys(platform)
|
|
for key, v := range update {
|
|
if secrets[key] {
|
|
if s, ok := v.(string); v == nil || (ok && (s == "" || s == configschema.RedactedPlaceholder)) {
|
|
continue
|
|
}
|
|
}
|
|
out[key] = v
|
|
}
|
|
return json.Marshal(out)
|
|
}
|