Unify bridge sync contract names

This commit is contained in:
Haitao Pan 2026-04-11 20:25:40 +08:00
parent 412573bdbd
commit 9b0d99c7f0
9 changed files with 205 additions and 204 deletions

View File

@ -26,14 +26,14 @@ type xworkmateAccessContext struct {
}
type xworkmateProfilePayload struct {
OpenclawURL string `json:"openclawUrl"`
OpenclawOrigin string `json:"openclawOrigin"`
VaultURL string `json:"vaultUrl"`
VaultNamespace string `json:"vaultNamespace"`
VaultSecretPath string `json:"vaultSecretPath"`
VaultSecretKey string `json:"vaultSecretKey"`
SecretLocators []xworkmateSecretLocatorPayload `json:"secretLocators"`
ApisixURL string `json:"apisixUrl"`
BridgeServerURL string `json:"BRIDGE_SERVER_URL"`
BridgeServerOrigin string `json:"bridgeServerOrigin"`
VaultURL string `json:"vaultUrl"`
VaultNamespace string `json:"vaultNamespace"`
VaultSecretPath string `json:"vaultSecretPath"`
VaultSecretKey string `json:"vaultSecretKey"`
SecretLocators []xworkmateSecretLocatorPayload `json:"secretLocators"`
ApisixURL string `json:"apisixUrl"`
}
type xworkmateSecretLocatorPayload struct {
@ -46,10 +46,11 @@ type xworkmateSecretLocatorPayload struct {
}
var xworkmateForbiddenTokenFields = map[string]struct{}{
"openclawtoken": {},
"gatewaytoken": {},
"vaulttoken": {},
"apisixtoken": {},
"bridge_auth_token": {},
"bridgeauthtoken": {},
"gatewaytoken": {},
"vaulttoken": {},
"apisixtoken": {},
}
func (h *handler) ensureSharedXWorkmateTenant(ctx context.Context) error {
@ -256,16 +257,16 @@ func buildSessionTenantEntries(memberships []store.TenantMembership) []gin.H {
func buildXWorkmateTokenConfigured(profile *store.XWorkmateProfile) gin.H {
result := gin.H{
"openclaw": false,
"vault": false,
"apisix": false,
"bridge": false,
"vault": false,
"apisix": false,
}
if profile == nil {
return result
}
if hasOpenclawXWorkmateSecretLocator(profile) {
result["openclaw"] = true
if hasBridgeAuthTokenXWorkmateSecretLocator(profile) {
result["bridge"] = true
}
return result
@ -277,8 +278,8 @@ func buildXWorkmateTokenConfiguredWithVaultStatus(profile *store.XWorkmateProfil
return result
}
if configured, ok := vaultStatus[store.XWorkmateSecretLocatorTargetOpenclawGatewayToken]; ok {
result["openclaw"] = configured
if configured, ok := vaultStatus[store.XWorkmateSecretLocatorTargetBridgeAuthToken]; ok {
result["bridge"] = configured
}
if configured, ok := vaultStatus[store.XWorkmateSecretLocatorTargetVaultRootToken]; ok {
result["vault"] = configured
@ -290,7 +291,7 @@ func buildXWorkmateTokenConfiguredWithVaultStatus(profile *store.XWorkmateProfil
return result
}
func hasOpenclawXWorkmateSecretLocator(profile *store.XWorkmateProfile) bool {
func hasBridgeAuthTokenXWorkmateSecretLocator(profile *store.XWorkmateProfile) bool {
if profile == nil {
return false
}
@ -299,7 +300,7 @@ func hasOpenclawXWorkmateSecretLocator(profile *store.XWorkmateProfile) bool {
return true
}
for _, locator := range profile.SecretLocators {
if locator.Target != store.XWorkmateSecretLocatorTargetOpenclawGatewayToken {
if locator.Target != store.XWorkmateSecretLocatorTargetBridgeAuthToken {
continue
}
if strings.TrimSpace(locator.SecretPath) != "" && strings.TrimSpace(locator.SecretKey) != "" {
@ -367,18 +368,18 @@ func (h *handler) buildSessionUser(ctx context.Context, host string, user *store
func buildXWorkmateProfileResponse(access *xworkmateAccessContext, profile *store.XWorkmateProfile, tokenConfigured gin.H) gin.H {
resolvedProfile := gin.H{
"openclawUrl": "",
"openclawOrigin": "",
"vaultUrl": "",
"vaultNamespace": "",
"vaultSecretPath": "",
"vaultSecretKey": "",
"secretLocators": []gin.H{},
"apisixUrl": "",
"BRIDGE_SERVER_URL": "",
"bridgeServerOrigin": "",
"vaultUrl": "",
"vaultNamespace": "",
"vaultSecretPath": "",
"vaultSecretKey": "",
"secretLocators": []gin.H{},
"apisixUrl": "",
}
if profile != nil {
resolvedProfile["openclawUrl"] = profile.OpenclawURL
resolvedProfile["openclawOrigin"] = profile.OpenclawOrigin
resolvedProfile["BRIDGE_SERVER_URL"] = profile.BridgeServerURL
resolvedProfile["bridgeServerOrigin"] = profile.BridgeServerOrigin
resolvedProfile["vaultUrl"] = profile.VaultURL
resolvedProfile["vaultNamespace"] = profile.VaultNamespace
resolvedProfile["vaultSecretPath"] = profile.VaultSecretPath
@ -546,8 +547,8 @@ func statusByTargetFromMetadata(profile *store.XWorkmateProfile, target string)
if profile == nil {
return false
}
if target == store.XWorkmateSecretLocatorTargetOpenclawGatewayToken {
return hasOpenclawXWorkmateSecretLocator(profile)
if target == store.XWorkmateSecretLocatorTargetBridgeAuthToken {
return hasBridgeAuthTokenXWorkmateSecretLocator(profile)
}
for _, locator := range profile.SecretLocators {
if locator.Target == strings.ToLower(strings.TrimSpace(target)) &&
@ -677,17 +678,17 @@ func (h *handler) updateXWorkmateProfile(c *gin.Context) {
profileUserID := resolvedXWorkmateProfileUserID(access, user)
profile := &store.XWorkmateProfile{
TenantID: access.Tenant.ID,
UserID: profileUserID,
Scope: access.ProfileScope,
OpenclawURL: payload.OpenclawURL,
OpenclawOrigin: payload.OpenclawOrigin,
VaultURL: payload.VaultURL,
VaultNamespace: payload.VaultNamespace,
VaultSecretPath: payload.VaultSecretPath,
VaultSecretKey: payload.VaultSecretKey,
SecretLocators: buildStoreXWorkmateSecretLocators(payload.SecretLocators),
ApisixURL: payload.ApisixURL,
TenantID: access.Tenant.ID,
UserID: profileUserID,
Scope: access.ProfileScope,
BridgeServerURL: payload.BridgeServerURL,
BridgeServerOrigin: payload.BridgeServerOrigin,
VaultURL: payload.VaultURL,
VaultNamespace: payload.VaultNamespace,
VaultSecretPath: payload.VaultSecretPath,
VaultSecretKey: payload.VaultSecretKey,
SecretLocators: buildStoreXWorkmateSecretLocators(payload.SecretLocators),
ApisixURL: payload.ApisixURL,
}
if err := h.store.UpsertXWorkmateProfile(c.Request.Context(), profile); err != nil {
respondError(c, http.StatusInternalServerError, "xworkmate_profile_write_failed", "failed to save xworkmate profile")

View File

@ -50,13 +50,13 @@ type bridgeBootstrapIssueResponse struct {
}
type bridgeBootstrapConsumeResponse struct {
TicketID string `json:"ticketId"`
TargetBridge string `json:"targetBridge"`
OpenclawURL string `json:"openclawUrl"`
AuthMode string `json:"authMode"`
ExchangeToken string `json:"exchangeToken"`
ExpiresAt string `json:"expiresAt"`
Scopes []string `json:"scopes"`
TicketID string `json:"ticketId"`
TargetBridge string `json:"targetBridge"`
BridgeServerURL string `json:"BRIDGE_SERVER_URL"`
AuthMode string `json:"authMode"`
BridgeAuthToken string `json:"BRIDGE_AUTH_TOKEN"`
ExpiresAt string `json:"expiresAt"`
Scopes []string `json:"scopes"`
}
func sanitizeBridgeTarget(raw string) string {
@ -288,7 +288,7 @@ func (h *handler) internalConsumeXWorkmateBridgeBootstrapTicket(c *gin.Context)
respondError(c, http.StatusNotFound, "xworkmate_profile_not_found", "xworkmate profile not found")
return
}
locator, ok := findStoredXWorkmateSecretLocator(profile, store.XWorkmateSecretLocatorTargetOpenclawGatewayToken)
locator, ok := findStoredXWorkmateSecretLocator(profile, store.XWorkmateSecretLocatorTargetBridgeAuthToken)
if !ok {
respondError(c, http.StatusConflict, "gateway_token_not_configured", "gateway token is not configured")
return
@ -298,8 +298,8 @@ func (h *handler) internalConsumeXWorkmateBridgeBootstrapTicket(c *gin.Context)
respondError(c, http.StatusConflict, "gateway_token_unavailable", "gateway token is unavailable")
return
}
openclawURL := strings.TrimSpace(profile.OpenclawURL)
if openclawURL == "" {
bridgeServerURL := strings.TrimSpace(profile.BridgeServerURL)
if bridgeServerURL == "" {
respondError(c, http.StatusConflict, "gateway_endpoint_not_configured", "gateway endpoint is not configured")
return
}
@ -308,12 +308,12 @@ func (h *handler) internalConsumeXWorkmateBridgeBootstrapTicket(c *gin.Context)
h.updateBridgeBootstrapTicket(ticket)
c.JSON(http.StatusOK, bridgeBootstrapConsumeResponse{
TicketID: ticket.TicketID,
TargetBridge: ticket.TargetBridge,
OpenclawURL: openclawURL,
AuthMode: "shared-token",
ExchangeToken: gatewayToken,
ExpiresAt: ticket.ExpiresAt.Format(time.RFC3339),
Scopes: append([]string(nil), ticket.Scopes...),
TicketID: ticket.TicketID,
TargetBridge: ticket.TargetBridge,
BridgeServerURL: bridgeServerURL,
AuthMode: "shared-token",
BridgeAuthToken: gatewayToken,
ExpiresAt: ticket.ExpiresAt.Format(time.RFC3339),
Scopes: append([]string(nil), ticket.Scopes...),
})
}

View File

@ -83,11 +83,11 @@ func TestBuildXWorkmateTokenConfiguredUsesSecretLocators(t *testing.T) {
t.Parallel()
tests := []struct {
name string
profile *store.XWorkmateProfile
openclaw bool
vault bool
apisix bool
name string
profile *store.XWorkmateProfile
bridge bool
vault bool
apisix bool
}{
{
name: "missing secret key stays false",
@ -96,26 +96,26 @@ func TestBuildXWorkmateTokenConfiguredUsesSecretLocators(t *testing.T) {
},
},
{
name: "legacy path and key mark openclaw configured",
name: "legacy path and key mark bridge configured",
profile: &store.XWorkmateProfile{
VaultSecretPath: "kv/openclaw",
VaultSecretKey: "token",
},
openclaw: true,
bridge: true,
},
{
name: "explicit openclaw locator marks openclaw configured",
name: "explicit bridge locator marks bridge configured",
profile: &store.XWorkmateProfile{
SecretLocators: []store.XWorkmateSecretLocator{
{
Provider: "vault",
SecretPath: "kv/openclaw",
SecretKey: "token",
Target: store.XWorkmateSecretLocatorTargetOpenclawGatewayToken,
Target: store.XWorkmateSecretLocatorTargetBridgeAuthToken,
},
},
},
openclaw: true,
bridge: true,
},
{
name: "other locator stays false",
@ -142,8 +142,8 @@ func TestBuildXWorkmateTokenConfiguredUsesSecretLocators(t *testing.T) {
t.Parallel()
result := buildXWorkmateTokenConfigured(tt.profile)
if got := result["openclaw"].(bool); got != tt.openclaw {
t.Fatalf("expected openclaw=%v, got %v", tt.openclaw, got)
if got := result["bridge"].(bool); got != tt.bridge {
t.Fatalf("expected bridge=%v, got %v", tt.bridge, got)
}
if got := result["vault"].(bool); got != tt.vault {
t.Fatalf("expected vault=%v, got %v", tt.vault, got)
@ -163,14 +163,14 @@ func TestXWorkmateBridgeBootstrapTicketLifecycle(t *testing.T) {
profileBody, err := json.Marshal(map[string]any{
"profile": map[string]any{
"openclawUrl": "wss://openclaw.example.com",
"BRIDGE_SERVER_URL": "wss://openclaw.example.com",
"secretLocators": []map[string]any{
{
"id": "locator-openclaw",
"provider": "vault",
"secretPath": "kv/openclaw",
"secretKey": "token",
"target": store.XWorkmateSecretLocatorTargetOpenclawGatewayToken,
"target": store.XWorkmateSecretLocatorTargetBridgeAuthToken,
"required": true,
},
},
@ -193,7 +193,7 @@ func TestXWorkmateBridgeBootstrapTicketLifecycle(t *testing.T) {
Provider: "vault",
SecretPath: "kv/openclaw",
SecretKey: "token",
Target: store.XWorkmateSecretLocatorTargetOpenclawGatewayToken,
Target: store.XWorkmateSecretLocatorTargetBridgeAuthToken,
}, "shared-token-value"); err != nil {
t.Fatalf("write secret: %v", err)
}
@ -237,11 +237,11 @@ func TestXWorkmateBridgeBootstrapTicketLifecycle(t *testing.T) {
if err := json.Unmarshal(consumeRec.Body.Bytes(), &consumed); err != nil {
t.Fatalf("decode bootstrap consume response: %v", err)
}
if consumed.ExchangeToken != "shared-token-value" {
t.Fatalf("expected returned exchange token, got %#v", consumed)
if consumed.BridgeAuthToken != "shared-token-value" {
t.Fatalf("expected returned bridge auth token, got %#v", consumed)
}
if consumed.OpenclawURL != "wss://openclaw.example.com" {
t.Fatalf("expected returned openclaw url, got %#v", consumed)
if consumed.BridgeServerURL != "wss://openclaw.example.com" {
t.Fatalf("expected returned bridge server url, got %#v", consumed)
}
replayReq := httptest.NewRequest(http.MethodPost, "/api/internal/xworkmate/bridge/bootstrap/consume", bytes.NewReader([]byte(fmt.Sprintf(`{"ticket":%q,"bridge":%q}`, created.Ticket, created.Bridge))))
@ -296,17 +296,17 @@ func TestUpdateAndGetXWorkmateProfileRoundTripsSecretLocators(t *testing.T) {
router, _, token := newXWorkmateTestHarness(t)
body, err := json.Marshal(map[string]any{
"profile": map[string]any{
"openclawUrl": "wss://gateway.example.com",
"openclawOrigin": "https://gateway.example.com",
"vaultUrl": "https://vault.example.com",
"vaultNamespace": "team-a",
"BRIDGE_SERVER_URL": "wss://gateway.example.com",
"bridgeServerOrigin": "https://gateway.example.com",
"vaultUrl": "https://vault.example.com",
"vaultNamespace": "team-a",
"secretLocators": []map[string]any{
{
"id": "locator-openclaw",
"provider": "vault",
"secretPath": "kv/openclaw",
"secretKey": "token",
"target": store.XWorkmateSecretLocatorTargetOpenclawGatewayToken,
"target": store.XWorkmateSecretLocatorTargetBridgeAuthToken,
"required": true,
},
{
@ -345,11 +345,11 @@ func TestUpdateAndGetXWorkmateProfileRoundTripsSecretLocators(t *testing.T) {
var resp struct {
Profile struct {
OpenclawURL string `json:"openclawUrl"`
OpenclawOrigin string `json:"openclawOrigin"`
VaultURL string `json:"vaultUrl"`
VaultNamespace string `json:"vaultNamespace"`
SecretLocators []struct {
BridgeServerURL string `json:"BRIDGE_SERVER_URL"`
BridgeServerOrigin string `json:"bridgeServerOrigin"`
VaultURL string `json:"vaultUrl"`
VaultNamespace string `json:"vaultNamespace"`
SecretLocators []struct {
ID string `json:"id"`
Provider string `json:"provider"`
SecretPath string `json:"secretPath"`
@ -362,9 +362,9 @@ func TestUpdateAndGetXWorkmateProfileRoundTripsSecretLocators(t *testing.T) {
ApisixURL string `json:"apisixUrl"`
} `json:"profile"`
TokenConfigured struct {
Openclaw bool `json:"openclaw"`
Vault bool `json:"vault"`
Apisix bool `json:"apisix"`
Bridge bool `json:"bridge"`
Vault bool `json:"vault"`
Apisix bool `json:"apisix"`
} `json:"tokenConfigured"`
}
if err := json.Unmarshal(getRec.Body.Bytes(), &resp); err != nil {
@ -380,14 +380,14 @@ func TestUpdateAndGetXWorkmateProfileRoundTripsSecretLocators(t *testing.T) {
if resp.Profile.SecretLocators[0].ID != "locator-openclaw" || !resp.Profile.SecretLocators[0].Required {
t.Fatalf("expected openclaw locator to round-trip, got %#v", resp.Profile.SecretLocators[0])
}
if resp.Profile.SecretLocators[0].Target != store.XWorkmateSecretLocatorTargetOpenclawGatewayToken {
t.Fatalf("expected openclaw target, got %#v", resp.Profile.SecretLocators[0])
if resp.Profile.SecretLocators[0].Target != store.XWorkmateSecretLocatorTargetBridgeAuthToken {
t.Fatalf("expected bridge target, got %#v", resp.Profile.SecretLocators[0])
}
if resp.Profile.SecretLocators[1].Target != store.XWorkmateSecretLocatorTargetAIGatewayAccessToken {
t.Fatalf("expected ai gateway target, got %#v", resp.Profile.SecretLocators[1])
}
if resp.TokenConfigured.Openclaw {
t.Fatalf("expected openclaw tokenConfigured=false until a vault-backed secret exists")
if resp.TokenConfigured.Bridge {
t.Fatalf("expected bridge tokenConfigured=false until a vault-backed secret exists")
}
if resp.TokenConfigured.Vault {
t.Fatalf("expected vault tokenConfigured=false without a vault-backed token locator")
@ -403,13 +403,13 @@ func TestUpdateXWorkmateProfileSynthesizesSecretLocatorsFromLegacyFields(t *test
router, _, token := newXWorkmateTestHarness(t)
body, err := json.Marshal(map[string]any{
"profile": map[string]any{
"openclawUrl": "wss://gateway.example.com",
"openclawOrigin": "https://gateway.example.com",
"vaultUrl": "https://vault.example.com",
"vaultNamespace": "team-a",
"vaultSecretPath": "kv/openclaw",
"vaultSecretKey": "token",
"apisixUrl": "https://apigw.example.com",
"BRIDGE_SERVER_URL": "wss://gateway.example.com",
"bridgeServerOrigin": "https://gateway.example.com",
"vaultUrl": "https://vault.example.com",
"vaultNamespace": "team-a",
"vaultSecretPath": "kv/openclaw",
"vaultSecretKey": "token",
"apisixUrl": "https://apigw.example.com",
},
})
if err != nil {
@ -454,8 +454,8 @@ func TestUpdateXWorkmateProfileSynthesizesSecretLocatorsFromLegacyFields(t *test
if len(resp.Profile.SecretLocators) != 1 {
t.Fatalf("expected synthesized single locator, got %#v", resp.Profile.SecretLocators)
}
if resp.Profile.SecretLocators[0].Provider != "vault" || resp.Profile.SecretLocators[0].Target != store.XWorkmateSecretLocatorTargetOpenclawGatewayToken {
t.Fatalf("expected synthesized openclaw vault locator, got %#v", resp.Profile.SecretLocators[0])
if resp.Profile.SecretLocators[0].Provider != "vault" || resp.Profile.SecretLocators[0].Target != store.XWorkmateSecretLocatorTargetBridgeAuthToken {
t.Fatalf("expected synthesized bridge vault locator, got %#v", resp.Profile.SecretLocators[0])
}
if resp.Profile.VaultSecretPath != "kv/openclaw" || resp.Profile.VaultSecretKey != "token" {
t.Fatalf("expected legacy fields to remain readable, got %#v", resp.Profile)
@ -472,17 +472,17 @@ func TestGetXWorkmateProfileFallsBackWhenVaultStatusReadFails(t *testing.T) {
)
body, err := json.Marshal(map[string]any{
"profile": map[string]any{
"openclawUrl": "wss://gateway.example.com",
"openclawOrigin": "https://gateway.example.com",
"vaultUrl": "https://vault.example.com",
"vaultNamespace": "team-a",
"BRIDGE_SERVER_URL": "wss://gateway.example.com",
"bridgeServerOrigin": "https://gateway.example.com",
"vaultUrl": "https://vault.example.com",
"vaultNamespace": "team-a",
"secretLocators": []map[string]any{
{
"id": "locator-openclaw",
"provider": "vault",
"secretPath": "kv/openclaw",
"secretKey": "token",
"target": store.XWorkmateSecretLocatorTargetOpenclawGatewayToken,
"target": store.XWorkmateSecretLocatorTargetBridgeAuthToken,
"required": true,
},
},
@ -513,23 +513,23 @@ func TestGetXWorkmateProfileFallsBackWhenVaultStatusReadFails(t *testing.T) {
var resp struct {
Profile struct {
OpenclawURL string `json:"openclawUrl"`
BridgeServerURL string `json:"BRIDGE_SERVER_URL"`
} `json:"profile"`
TokenConfigured struct {
Openclaw bool `json:"openclaw"`
Vault bool `json:"vault"`
Apisix bool `json:"apisix"`
Bridge bool `json:"bridge"`
Vault bool `json:"vault"`
Apisix bool `json:"apisix"`
} `json:"tokenConfigured"`
}
if err := json.Unmarshal(getRec.Body.Bytes(), &resp); err != nil {
t.Fatalf("decode profile response: %v", err)
}
if resp.Profile.OpenclawURL != "wss://gateway.example.com" {
if resp.Profile.BridgeServerURL != "wss://gateway.example.com" {
t.Fatalf("expected profile payload to survive vault read failure, got %#v", resp.Profile)
}
if !resp.TokenConfigured.Openclaw {
t.Fatalf("expected locator-derived openclaw tokenConfigured fallback, got %#v", resp.TokenConfigured)
if !resp.TokenConfigured.Bridge {
t.Fatalf("expected locator-derived bridge tokenConfigured fallback, got %#v", resp.TokenConfigured)
}
if resp.TokenConfigured.Vault {
t.Fatalf("expected vault tokenConfigured fallback to stay false, got %#v", resp.TokenConfigured)
@ -580,7 +580,7 @@ func TestUpdateXWorkmateProfileRejectsNestedRawTokenFields(t *testing.T) {
body, err := json.Marshal(map[string]any{
"profile": map[string]any{
"openclawUrl": "wss://gateway.example.com",
"BRIDGE_SERVER_URL": "wss://gateway.example.com",
"security": map[string]any{
"gatewayToken": "secret-value",
},
@ -618,11 +618,11 @@ func TestXWorkmateSecretsWriteReadDeleteAndKeepLocatorMetadata(t *testing.T) {
router, _, token := newXWorkmateTestHarness(t)
profileBody, err := json.Marshal(map[string]any{
"profile": map[string]any{
"openclawUrl": "wss://gateway.example.com",
"openclawOrigin": "https://gateway.example.com",
"vaultUrl": "https://vault.example.com",
"vaultNamespace": "team-a",
"apisixUrl": "https://apigw.example.com",
"BRIDGE_SERVER_URL": "wss://gateway.example.com",
"bridgeServerOrigin": "https://gateway.example.com",
"vaultUrl": "https://vault.example.com",
"vaultNamespace": "team-a",
"apisixUrl": "https://apigw.example.com",
},
})
if err != nil {
@ -640,7 +640,7 @@ func TestXWorkmateSecretsWriteReadDeleteAndKeepLocatorMetadata(t *testing.T) {
}
for _, target := range []string{
store.XWorkmateSecretLocatorTargetOpenclawGatewayToken,
store.XWorkmateSecretLocatorTargetBridgeAuthToken,
store.XWorkmateSecretLocatorTargetVaultRootToken,
store.XWorkmateSecretLocatorTargetAIGatewayAccessToken,
} {
@ -693,15 +693,15 @@ func TestXWorkmateSecretsWriteReadDeleteAndKeepLocatorMetadata(t *testing.T) {
} `json:"secretLocators"`
} `json:"profile"`
TokenConfigured struct {
Openclaw bool `json:"openclaw"`
Vault bool `json:"vault"`
Apisix bool `json:"apisix"`
Bridge bool `json:"bridge"`
Vault bool `json:"vault"`
Apisix bool `json:"apisix"`
} `json:"tokenConfigured"`
}
if err := json.Unmarshal(getProfileRec.Body.Bytes(), &profileResp); err != nil {
t.Fatalf("decode profile response: %v", err)
}
if !profileResp.TokenConfigured.Openclaw || !profileResp.TokenConfigured.Vault || !profileResp.TokenConfigured.Apisix {
if !profileResp.TokenConfigured.Bridge || !profileResp.TokenConfigured.Vault || !profileResp.TokenConfigured.Apisix {
t.Fatalf("expected all synced tokenConfigured fields true, got %#v", profileResp.TokenConfigured)
}
if len(profileResp.Profile.SecretLocators) != 3 {
@ -711,7 +711,7 @@ func TestXWorkmateSecretsWriteReadDeleteAndKeepLocatorMetadata(t *testing.T) {
t.Fatalf("expected openclaw legacy compatibility fields to remain readable, got %#v", profileResp.Profile)
}
deleteReq := httptest.NewRequest(http.MethodDelete, "/api/auth/xworkmate/secrets/"+store.XWorkmateSecretLocatorTargetOpenclawGatewayToken, nil)
deleteReq := httptest.NewRequest(http.MethodDelete, "/api/auth/xworkmate/secrets/"+store.XWorkmateSecretLocatorTargetBridgeAuthToken, nil)
deleteReq.Header.Set("Authorization", "Bearer "+token)
deleteReq.Header.Set("X-Forwarded-Host", store.SharedXWorkmateDomain)
deleteRec := httptest.NewRecorder()
@ -736,16 +736,16 @@ func TestXWorkmateSecretsWriteReadDeleteAndKeepLocatorMetadata(t *testing.T) {
} `json:"secretLocators"`
} `json:"profile"`
TokenConfigured struct {
Openclaw bool `json:"openclaw"`
Vault bool `json:"vault"`
Apisix bool `json:"apisix"`
Bridge bool `json:"bridge"`
Vault bool `json:"vault"`
Apisix bool `json:"apisix"`
} `json:"tokenConfigured"`
}
if err := json.Unmarshal(getProfileAfterDeleteRec.Body.Bytes(), &afterDeleteResp); err != nil {
t.Fatalf("decode post-delete profile response: %v", err)
}
if afterDeleteResp.TokenConfigured.Openclaw {
t.Fatalf("expected deleted openclaw secret to report missing, got %#v", afterDeleteResp.TokenConfigured)
if afterDeleteResp.TokenConfigured.Bridge {
t.Fatalf("expected deleted bridge secret to report missing, got %#v", afterDeleteResp.TokenConfigured)
}
if !afterDeleteResp.TokenConfigured.Vault || !afterDeleteResp.TokenConfigured.Apisix {
t.Fatalf("expected unrelated secret statuses to remain true, got %#v", afterDeleteResp.TokenConfigured)
@ -772,7 +772,7 @@ func TestXWorkmateSharedSecretsRequireAdminMembershipForWrites(t *testing.T) {
t.Fatalf("marshal secret payload: %v", err)
}
req := httptest.NewRequest(http.MethodPut, "/api/auth/xworkmate/secrets/"+store.XWorkmateSecretLocatorTargetOpenclawGatewayToken, bytes.NewReader(body))
req := httptest.NewRequest(http.MethodPut, "/api/auth/xworkmate/secrets/"+store.XWorkmateSecretLocatorTargetBridgeAuthToken, bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("X-Forwarded-Host", store.SharedXWorkmateDomain)
@ -867,7 +867,7 @@ func TestXWorkmatePrivateSecretsAreScopedPerUser(t *testing.T) {
if err != nil {
t.Fatalf("marshal secret payload: %v", err)
}
writeReq := httptest.NewRequest(http.MethodPut, "/api/auth/xworkmate/secrets/"+store.XWorkmateSecretLocatorTargetOpenclawGatewayToken, bytes.NewReader(body))
writeReq := httptest.NewRequest(http.MethodPut, "/api/auth/xworkmate/secrets/"+store.XWorkmateSecretLocatorTargetBridgeAuthToken, bytes.NewReader(body))
writeReq.Header.Set("Content-Type", "application/json")
writeReq.Header.Set("Authorization", "Bearer "+tokenA)
writeReq.Header.Set("X-Forwarded-Host", "tenant-private-1.svc.plus")
@ -897,25 +897,25 @@ func TestXWorkmatePrivateSecretsAreScopedPerUser(t *testing.T) {
var userAResp struct {
TokenConfigured struct {
Openclaw bool `json:"openclaw"`
Bridge bool `json:"bridge"`
} `json:"tokenConfigured"`
}
if err := json.Unmarshal(getARec.Body.Bytes(), &userAResp); err != nil {
t.Fatalf("decode user A profile response: %v", err)
}
if !userAResp.TokenConfigured.Openclaw {
if !userAResp.TokenConfigured.Bridge {
t.Fatalf("expected user A secret to be configured, got %#v", userAResp.TokenConfigured)
}
var userBResp struct {
TokenConfigured struct {
Openclaw bool `json:"openclaw"`
Bridge bool `json:"bridge"`
} `json:"tokenConfigured"`
}
if err := json.Unmarshal(getBRec.Body.Bytes(), &userBResp); err != nil {
t.Fatalf("decode user B profile response: %v", err)
}
if userBResp.TokenConfigured.Openclaw {
if userBResp.TokenConfigured.Bridge {
t.Fatalf("expected user B to remain isolated from user A secret, got %#v", userBResp.TokenConfigured)
}
}

View File

@ -54,9 +54,9 @@ type xworkmateManagedSecretTarget struct {
var xworkmateManagedSecretTargets = []xworkmateManagedSecretTarget{
{
Target: store.XWorkmateSecretLocatorTargetOpenclawGatewayToken,
Target: store.XWorkmateSecretLocatorTargetBridgeAuthToken,
Required: true,
TokenConfiguredID: "openclaw",
TokenConfiguredID: "bridge",
},
{
Target: store.XWorkmateSecretLocatorTargetVaultRootToken,

View File

@ -56,20 +56,20 @@ type TenantMembership struct {
func (TenantMembership) TableName() string { return "tenant_memberships" }
type XWorkmateProfile struct {
ID string `gorm:"column:id;type:text;primaryKey"`
TenantID string `gorm:"column:tenant_id;type:text;not null;uniqueIndex:idx_xworkmate_profiles_scope"`
UserID string `gorm:"column:user_id;type:text;not null;default:'';uniqueIndex:idx_xworkmate_profiles_scope"`
Scope string `gorm:"column:scope;type:text;not null;uniqueIndex:idx_xworkmate_profiles_scope"`
OpenclawURL string `gorm:"column:openclaw_url;type:text;not null;default:''"`
OpenclawOrigin string `gorm:"column:openclaw_origin;type:text;not null;default:''"`
VaultURL string `gorm:"column:vault_url;type:text;not null;default:''"`
VaultNamespace string `gorm:"column:vault_namespace;type:text;not null;default:''"`
VaultSecretPath string `gorm:"column:vault_secret_path;type:text;not null;default:''"`
VaultSecretKey string `gorm:"column:vault_secret_key;type:text;not null;default:''"`
SecretLocators []XWorkmateSecretLocator `gorm:"column:secret_locators;type:text;not null;serializer:json;default:'[]'"`
ApisixURL string `gorm:"column:apisix_url;type:text;not null;default:''"`
CreatedAt time.Time `gorm:"column:created_at;not null;autoCreateTime"`
UpdatedAt time.Time `gorm:"column:updated_at;not null;autoUpdateTime"`
ID string `gorm:"column:id;type:text;primaryKey"`
TenantID string `gorm:"column:tenant_id;type:text;not null;uniqueIndex:idx_xworkmate_profiles_scope"`
UserID string `gorm:"column:user_id;type:text;not null;default:'';uniqueIndex:idx_xworkmate_profiles_scope"`
Scope string `gorm:"column:scope;type:text;not null;uniqueIndex:idx_xworkmate_profiles_scope"`
BridgeServerURL string `gorm:"column:openclaw_url;type:text;not null;default:''"`
BridgeServerOrigin string `gorm:"column:openclaw_origin;type:text;not null;default:''"`
VaultURL string `gorm:"column:vault_url;type:text;not null;default:''"`
VaultNamespace string `gorm:"column:vault_namespace;type:text;not null;default:''"`
VaultSecretPath string `gorm:"column:vault_secret_path;type:text;not null;default:''"`
VaultSecretKey string `gorm:"column:vault_secret_key;type:text;not null;default:''"`
SecretLocators []XWorkmateSecretLocator `gorm:"column:secret_locators;type:text;not null;serializer:json;default:'[]'"`
ApisixURL string `gorm:"column:apisix_url;type:text;not null;default:''"`
CreatedAt time.Time `gorm:"column:created_at;not null;autoCreateTime"`
UpdatedAt time.Time `gorm:"column:updated_at;not null;autoUpdateTime"`
}
type XWorkmateSecretLocator struct {

View File

@ -32,7 +32,7 @@ const (
XWorkmateSecretLocatorProviderVault = "vault"
XWorkmateSecretLocatorTargetOpenclawGatewayToken = "openclaw.gateway_token"
XWorkmateSecretLocatorTargetBridgeAuthToken = "bridge.auth_token"
XWorkmateSecretLocatorTargetVaultRootToken = "vault.root_token"
XWorkmateSecretLocatorTargetAIGatewayAccessToken = "ai_gateway.access_token"
XWorkmateSecretLocatorTargetOllamaCloudAPIKey = "ollama_cloud.api_key"
@ -79,20 +79,20 @@ type TenantMembership struct {
}
type XWorkmateProfile struct {
ID string
TenantID string
UserID string
Scope string
OpenclawURL string
OpenclawOrigin string
VaultURL string
VaultNamespace string
VaultSecretPath string
VaultSecretKey string
SecretLocators []XWorkmateSecretLocator
ApisixURL string
CreatedAt time.Time
UpdatedAt time.Time
ID string
TenantID string
UserID string
Scope string
BridgeServerURL string
BridgeServerOrigin string
VaultURL string
VaultNamespace string
VaultSecretPath string
VaultSecretKey string
SecretLocators []XWorkmateSecretLocator
ApisixURL string
CreatedAt time.Time
UpdatedAt time.Time
}
type XWorkmateSecretLocator struct {
@ -179,7 +179,7 @@ func cloneXWorkmateSecretLocators(locators []XWorkmateSecretLocator) []XWorkmate
func legacyXWorkmateSecretLocatorID(profile *XWorkmateProfile) string {
if profile == nil {
return "legacy|xworkmate|openclaw.gateway_token"
return "legacy|xworkmate|bridge.auth_token"
}
return strings.Join([]string{
@ -187,7 +187,7 @@ func legacyXWorkmateSecretLocatorID(profile *XWorkmateProfile) string {
strings.TrimSpace(profile.TenantID),
strings.TrimSpace(profile.UserID),
NormalizeXWorkmateProfileScope(profile.Scope),
XWorkmateSecretLocatorTargetOpenclawGatewayToken,
XWorkmateSecretLocatorTargetBridgeAuthToken,
}, "|")
}
@ -197,13 +197,13 @@ func synthesizeXWorkmateSecretLocatorFromLegacy(profile *XWorkmateProfile) XWork
Provider: XWorkmateSecretLocatorProviderVault,
SecretPath: profile.VaultSecretPath,
SecretKey: profile.VaultSecretKey,
Target: XWorkmateSecretLocatorTargetOpenclawGatewayToken,
Target: XWorkmateSecretLocatorTargetBridgeAuthToken,
}
}
func compatibilityXWorkmateSecretLocator(locators []XWorkmateSecretLocator) (string, string, bool) {
for _, locator := range locators {
if locator.Target == XWorkmateSecretLocatorTargetOpenclawGatewayToken &&
if locator.Target == XWorkmateSecretLocatorTargetBridgeAuthToken &&
locator.SecretPath != "" && locator.SecretKey != "" {
return locator.SecretPath, locator.SecretKey, true
}
@ -292,8 +292,8 @@ func NormalizeXWorkmateProfile(profile *XWorkmateProfile) {
profile.TenantID = strings.TrimSpace(profile.TenantID)
profile.UserID = strings.TrimSpace(profile.UserID)
profile.Scope = NormalizeXWorkmateProfileScope(profile.Scope)
profile.OpenclawURL = strings.TrimSpace(profile.OpenclawURL)
profile.OpenclawOrigin = strings.TrimSpace(profile.OpenclawOrigin)
profile.BridgeServerURL = strings.TrimSpace(profile.BridgeServerURL)
profile.BridgeServerOrigin = strings.TrimSpace(profile.BridgeServerOrigin)
profile.VaultURL = strings.TrimSpace(profile.VaultURL)
profile.VaultNamespace = strings.TrimSpace(profile.VaultNamespace)
profile.VaultSecretPath = strings.Trim(strings.TrimSpace(profile.VaultSecretPath), "/")

View File

@ -290,8 +290,8 @@ func (s *memoryStore) UpsertXWorkmateProfile(ctx context.Context, profile *XWork
defer s.mu.Unlock()
if existing, ok := s.xworkmateProfiles[key]; ok {
existing.OpenclawURL = profile.OpenclawURL
existing.OpenclawOrigin = profile.OpenclawOrigin
existing.BridgeServerURL = profile.BridgeServerURL
existing.BridgeServerOrigin = profile.BridgeServerOrigin
existing.VaultURL = profile.VaultURL
existing.VaultNamespace = profile.VaultNamespace
existing.VaultSecretPath = profile.VaultSecretPath
@ -305,20 +305,20 @@ func (s *memoryStore) UpsertXWorkmateProfile(ctx context.Context, profile *XWork
}
stored := &XWorkmateProfile{
ID: profile.ID,
TenantID: profile.TenantID,
UserID: profile.UserID,
Scope: profile.Scope,
OpenclawURL: profile.OpenclawURL,
OpenclawOrigin: profile.OpenclawOrigin,
VaultURL: profile.VaultURL,
VaultNamespace: profile.VaultNamespace,
VaultSecretPath: profile.VaultSecretPath,
VaultSecretKey: profile.VaultSecretKey,
SecretLocators: cloneXWorkmateSecretLocators(profile.SecretLocators),
ApisixURL: profile.ApisixURL,
CreatedAt: now,
UpdatedAt: now,
ID: profile.ID,
TenantID: profile.TenantID,
UserID: profile.UserID,
Scope: profile.Scope,
BridgeServerURL: profile.BridgeServerURL,
BridgeServerOrigin: profile.BridgeServerOrigin,
VaultURL: profile.VaultURL,
VaultNamespace: profile.VaultNamespace,
VaultSecretPath: profile.VaultSecretPath,
VaultSecretKey: profile.VaultSecretKey,
SecretLocators: cloneXWorkmateSecretLocators(profile.SecretLocators),
ApisixURL: profile.ApisixURL,
CreatedAt: now,
UpdatedAt: now,
}
s.xworkmateProfiles[key] = stored
profile.CreatedAt = stored.CreatedAt

View File

@ -242,8 +242,8 @@ LIMIT 1`
&profile.TenantID,
&profile.UserID,
&profile.Scope,
&profile.OpenclawURL,
&profile.OpenclawOrigin,
&profile.BridgeServerURL,
&profile.BridgeServerOrigin,
&profile.VaultURL,
&profile.VaultNamespace,
&profile.VaultSecretPath,
@ -304,8 +304,8 @@ RETURNING created_at, updated_at`
profile.TenantID,
profile.UserID,
profile.Scope,
profile.OpenclawURL,
profile.OpenclawOrigin,
profile.BridgeServerURL,
profile.BridgeServerOrigin,
profile.VaultURL,
profile.VaultNamespace,
profile.VaultSecretPath,

View File

@ -87,7 +87,7 @@ func TestMemoryStoreResolveTenantAndProfile(t *testing.T) {
TenantID: privateTenant.ID,
UserID: "user-1",
Scope: XWorkmateProfileScopeUserPrivate,
OpenclawURL: "wss://openclaw.tenant-one.svc.plus",
BridgeServerURL: "wss://openclaw.tenant-one.svc.plus",
VaultSecretPath: "kv/openclaw",
VaultSecretKey: "token",
}); err != nil {
@ -109,8 +109,8 @@ func TestMemoryStoreResolveTenantAndProfile(t *testing.T) {
if err != nil {
t.Fatalf("get private profile: %v", err)
}
if profile.OpenclawURL != "wss://openclaw.tenant-one.svc.plus" {
t.Fatalf("expected persisted openclaw url, got %q", profile.OpenclawURL)
if profile.BridgeServerURL != "wss://openclaw.tenant-one.svc.plus" {
t.Fatalf("expected persisted bridge server url, got %q", profile.BridgeServerURL)
}
if profile.VaultSecretPath != "kv/openclaw" || profile.VaultSecretKey != "token" {
t.Fatalf("expected legacy secret fields to round-trip, got %#v", profile)
@ -121,8 +121,8 @@ func TestMemoryStoreResolveTenantAndProfile(t *testing.T) {
if profile.SecretLocators[0].Provider != XWorkmateSecretLocatorProviderVault {
t.Fatalf("expected vault provider, got %#v", profile.SecretLocators[0])
}
if profile.SecretLocators[0].Target != XWorkmateSecretLocatorTargetOpenclawGatewayToken {
t.Fatalf("expected openclaw target, got %#v", profile.SecretLocators[0])
if profile.SecretLocators[0].Target != XWorkmateSecretLocatorTargetBridgeAuthToken {
t.Fatalf("expected bridge auth token target, got %#v", profile.SecretLocators[0])
}
if profile.SecretLocators[0].SecretPath != "kv/openclaw" || profile.SecretLocators[0].SecretKey != "token" {
t.Fatalf("expected synthesized secret locator path/key, got %#v", profile.SecretLocators[0])
@ -158,7 +158,7 @@ func TestMemoryStorePersistsExplicitSecretLocators(t *testing.T) {
Provider: "vault",
SecretPath: "kv/openclaw",
SecretKey: "gateway-token",
Target: XWorkmateSecretLocatorTargetOpenclawGatewayToken,
Target: XWorkmateSecretLocatorTargetBridgeAuthToken,
Required: true,
},
{