Replace scopes JSON with a viewer/contributor/admin role column, move
unsafe-method authorization from path-prefix middleware to declarative
APIKey* guards on routes, and close remaining KB-scope gaps in handlers.
When editing a data source whose tree loads one level at a time (Feishu
wiki), a saved deep-node selection was invisible: only top-level spaces
were listed, so the selected node could not be seen and its ancestor
space rendered as unchecked.
- Add Connector.ResolveResourceAncestors: given selected resource IDs,
return the ancestor IDs that must be expanded to reveal them. Feishu
walks up via GetWikiNode (O(depth) per selection, no full-tree walk);
Notion/Yuque return empty.
- Fix wikiNode.ParentNodeID JSON tag: the Feishu API field is
parent_node_token, not parent_node_id, so GetWikiNode can return the
parent and the ancestor walk works in production.
- Expose POST /datasource/{id}/resource-ancestors (service + handler +
route) and a matching frontend API client.
- Edit-mode picker reveals hidden selections by expanding and lazily
loading each ancestor level, so selections are visible and checked.
- Gate child fetching on treeFullyLoaded instead of a partial-children
heuristic, avoiding a redundant request for full-tree connectors.
Listing resources for a Feishu data source eagerly walked the entire wiki
tree (one API call per node across every space), which exceeded the request
timeout for large knowledge bases. ListResources now takes a parentID and
returns a single level at a time: empty lists the wiki spaces, a space/node
ID lists only its direct children. The frontend fetches children on demand
when a node is expanded. Notion (full tree in one call) and Yuque (flat)
keep their existing behavior by ignoring non-empty parentID.
Refs: https://github.com/Tencent/WeKnora/issues/1672
Replace the legacy "redacted placeholder + Clear* boolean" pattern with
dedicated per-resource credential subresources across MCP services,
Models, Web search providers, and Data sources.
Why
---
The previous design had three problems:
1. Main PUT body carried secret fields. The frontend echoed back a
redacted "***" placeholder, and a fragile MergeUpdate / IsRedactedOrEmpty
defense in the service layer tried to detect "user did not change this"
vs "user wants to clear this". A regression in that defense (or a new
frontend forgetting it) silently overwrites the stored secret with the
placeholder.
2. The "remove this credential" UX was a red checkbox under a pre-filled
password input. Three intents (preserve / replace / clear) collapsed
onto one field, and credential changes were bundled with unrelated
config edits in the same submit. Users wiped working keys by mistake.
3. Secret presence was inferred from "did the response come back with a
'***' placeholder", which couples the contract to a magic string.
Design
------
Each of the four resources now exposes:
PUT /{resource}/{id}/credentials # write one or more fields
DELETE /{resource}/{id}/credentials/{field} # clear a single field
"Is this configured?" metadata travels on the main resource response as
a typed map (dto.MCPServiceResponse.Credentials etc.) — no separate GET
endpoint. The frontend reads the configured boolean from the main GET
and never sees secret values at all.
Main PUT endpoints now ignore any secret fields in the request body and
log a deprecation warning if they appear, so legacy clients fail loudly
rather than overwriting silently.
Frontend
--------
- New reusable <CredentialResource> component renders a three-state card
(unconfigured / configured / editing) and drives the dedicated
endpoints. Used by MCP, Model, Web search; DataSource has a bespoke
card with the same behaviour because its credentials are a single
atomic per-connector map.
- Cancel from edit mode now restores state synchronously from the meta
prop. The previous async refresh() was a no-op while state was still
'editing', leaving the input frozen open.
- Remove is single-click + toast. The danger-themed button is the
deterrent; a modal confirm adds friction without adding safety (the
plaintext is irrecoverable client-side either way — recovery means
re-typing).
DTOs (internal/handler/dto/) are deliberately separate from the GORM
models so "no secret in response" is a compile-time invariant: a future
contributor cannot leak a secret without explicitly adding the field to
the DTO, which is review-able in a single diff.
Storage is unchanged — credentials still live in the existing jsonb /
parameters columns. No schema migration.
Cleanup
-------
- types.MCPService / types.Model / types.WebSearchProviderEntity /
types.DataSourceConfig: drop ClearAPIKey / ClearToken / ClearAppSecret
/ ClearCredentials boolean fields, MergeUpdate(), RedactSensitiveData().
- utils/types/secret.go: drop PreserveIfRedacted / IsRedactedOrEmpty.
RedactedSecretPlaceholder constant is retained because VectorStore
still uses the old pattern and is out of scope here.
- Frontend hasExistingApiKey / clearApiKey / convertToLegacyFormat
redaction handling removed; i18n keys renamed secret -> credential.
Closes#988
Unify the "write-only secrets" pattern across MCP services, Models,
WebSearch providers, and DataSources. Fix two related defects:
1. GET responses leaked plaintext credentials — GetMCPServiceByID
while List masked, all Model / WebSearchProvider / DataSource GET
responses unconditionally.
2. When the frontend echoed a masked placeholder back on save, the
update path overwrote the stored secret with the mask — silently
wiping the real credential. MCP surfaced this most visibly:
ListMCPServices masked Token to "first4****last4" and
McpServiceDialog round-tripped that masked string back into the DB.
- internal/types/secret.go introduces the shared sentinel
RedactedSecretPlaceholder = "***" and two helpers:
- IsRedactedOrEmpty(s): true for "" or the sentinel
- PreserveIfRedacted(incoming, existing): merge predicate
- Each resource adds a RedactSensitiveData (mutates-in-place) or
Redacted (returns-a-copy) method and a MergeUpdate contract:
empty or placeholder preserves, any other value replaces, and a
write-only Clear* bool flag (gorm:"-", json omitempty) wipes with
precedence over preserve / replace. Clear* never persists.
- Every GET / List / Create / Update response path now redacts.
VectorStore's pre-existing "***" literal is refactored onto the
shared constant for consistency.
All four dialogs — McpServiceDialog, ModelEditorDialog,
WebSearchSettings, DataSourceEditorDialog — never pre-fill secret
inputs. The stored-state signal is collapsed into the input
placeholder: "•••••••• (Enter new value to replace)" when the server
reports a stored value, falling back to the connector-specific
placeholder otherwise. The input itself carries the "something is
there" cue, matching how password dots render.
An opt-in "Remove this credential" checkbox appears in edit mode when
a value is stored; checking it disables the input and triggers a
danger-themed confirmation dialog before submit. DataSource rolls the
clear up to a connector-level "Remove all credentials" flag since its
credentials are per-connector atomic bundles (e.g. Feishu app_id +
app_secret).
i18n updates across en-US, zh-CN, ko-KR, ru-RU:
secret.storedPlaceholder / clearHint / confirmClearTitle /
confirmClearBody.
- MCP update handler's manual map-to-struct projection was silently
dropping clear_api_key / clear_token / custom_headers on partial
updates. Pull them off the map explicitly.
- MCP configChanged detection now snapshots URL / StdioConfig /
TransportType before the merge so connection recycling still
triggers on real transport changes (not just whenever AuthConfig
is present).
- MCP CustomHeaders: nil preserves existing, non-nil replaces. Was
always overwriting, silently wiping headers on any auth-bearing
update.
- GetMCPServiceByID now redacts (was plaintext while List masked).
- WebSearchProvider update preserves Name / Description when empty
instead of clobbering them.
- WebSearchProvider TestProviderRaw rejects the "***" placeholder up
front with an actionable message rather than forwarding it to the
upstream provider API as a literal key.
- Model builtin hideSensitiveInfo now preserves non-secret metadata
(SupportsVision, Provider, ExtraConfig, InterfaceType,
ParameterSize, EmbeddingParameters) that were previously zeroed
out for builtin model cards.
- Replace local equalStringSlices / equalStringMaps helpers with
stdlib slices.Equal / maps.Equal (Go 1.21+).
- internal/types/{secret,mcp,model,web_search_provider,datasource}_test.go:
table-driven coverage of merge, redact, clear, and write-only flag
invariants for every resource.
- internal/application/service/mcp_service_test.go: full service layer
with a fake repo; covers ListMCPServices / GetMCPServiceByID
redaction, UpdateMCPService partial-update honoring of Clear*,
CustomHeaders preserve-on-nil / replace-on-non-nil, and
clear-flag precedence over submitted values.
Additive only. No changes to request or response JSON shape beyond
the write-only Clear* flags (omitted by default via omitempty). No DB
schema changes. No SDK struct field removals. Existing clients that
don't know about the new flags continue to function unchanged.
- DataSource.Config jsonb at-rest encryption. Pre-existing residual
risk; tracked as a follow-up.
- Renaming VectorStore.MaskSensitiveFields to match the new
RedactSensitiveData / Redacted naming convention. Left alone to
minimize diff; a follow-up rename PR can unify.
- Added checks to ensure that the tenant ID matches between data sources and knowledge bases during creation and updates.
- Implemented ownership verification for data sources and knowledge bases in the DataSourceHandler, enhancing security and access control.
- Refactored data source retrieval methods to centralize ownership validation, improving code maintainability and clarity.
- Updated error handling to provide more informative responses when access is denied or when knowledge bases are not found.
- Added a new API for managing data sources, including CRUD operations and connection validation.
- Introduced UI components for data source settings, sync logs, and editor dialog, enhancing user experience in managing external data sources.
- Updated localization files to support new data source features in multiple languages.
- Implemented cron expression humanization for better scheduling visibility.
- Enhanced knowledge base editor to include data source management options.