feat(coderd/database): add DeleteMCPServerUserHeaderValuesByConfigID query

Admin-only query used by the MCP server config update path to clear every user's stored user-set header values when the auth_type changes away from custom_headers or the custom_headers_user_keys list is altered. Without this, stale per-user credentials silently reactivate if the previous key set is later restored.
This commit is contained in:
Steven Masley
2026-05-29 20:15:34 +00:00
parent 14d0434439
commit 0586f17889
7 changed files with 69 additions and 0 deletions
+11
View File
@@ -2121,6 +2121,17 @@ func (q *querier) DeleteMCPServerUserHeaderValues(ctx context.Context, arg datab
return fetchAndExec(q.log, q.auth, policy.ActionUpdatePersonal, fetch, q.db.DeleteMCPServerUserHeaderValues)(ctx, arg)
}
func (q *querier) DeleteMCPServerUserHeaderValuesByConfigID(ctx context.Context, mcpServerConfigID uuid.UUID) error {
// Admin-only operation. Called from the admin MCP server config
// update path when auth_type or custom_headers_user_keys changes,
// so stale per-user header values do not silently reactivate when
// the key set is restored.
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceDeploymentConfig); err != nil {
return err
}
return q.db.DeleteMCPServerUserHeaderValuesByConfigID(ctx, mcpServerConfigID)
}
func (q *querier) DeleteMCPServerUserToken(ctx context.Context, arg database.DeleteMCPServerUserTokenParams) error {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceDeploymentConfig); err != nil {
return err
+5
View File
@@ -1697,6 +1697,11 @@ func (s *MethodTestSuite) TestChats() {
dbm.EXPECT().DeleteMCPServerUserHeaderValues(gomock.Any(), arg).Return(nil).AnyTimes()
check.Args(arg).Asserts(value, policy.ActionUpdatePersonal).Returns()
}))
s.Run("DeleteMCPServerUserHeaderValuesByConfigID", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
id := uuid.New()
dbm.EXPECT().DeleteMCPServerUserHeaderValuesByConfigID(gomock.Any(), id).Return(nil).AnyTimes()
check.Args(id).Asserts(rbac.ResourceDeploymentConfig, policy.ActionUpdate).Returns()
}))
s.Run("InsertMCPServerConfig", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
arg := database.InsertMCPServerConfigParams{
DisplayName: "Test MCP Server",
+8
View File
@@ -585,6 +585,14 @@ func (m queryMetricsStore) DeleteMCPServerUserHeaderValues(ctx context.Context,
return r0
}
func (m queryMetricsStore) DeleteMCPServerUserHeaderValuesByConfigID(ctx context.Context, mcpServerConfigID uuid.UUID) error {
start := time.Now()
r0 := m.s.DeleteMCPServerUserHeaderValuesByConfigID(ctx, mcpServerConfigID)
m.queryLatencies.WithLabelValues("DeleteMCPServerUserHeaderValuesByConfigID").Observe(time.Since(start).Seconds())
m.queryCounts.WithLabelValues(httpmw.ExtractHTTPRoute(ctx), httpmw.ExtractHTTPMethod(ctx), "DeleteMCPServerUserHeaderValuesByConfigID").Inc()
return r0
}
func (m queryMetricsStore) DeleteMCPServerUserToken(ctx context.Context, arg database.DeleteMCPServerUserTokenParams) error {
start := time.Now()
r0 := m.s.DeleteMCPServerUserToken(ctx, arg)
+14
View File
@@ -974,6 +974,20 @@ func (mr *MockStoreMockRecorder) DeleteMCPServerUserHeaderValues(ctx, arg any) *
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMCPServerUserHeaderValues", reflect.TypeOf((*MockStore)(nil).DeleteMCPServerUserHeaderValues), ctx, arg)
}
// DeleteMCPServerUserHeaderValuesByConfigID mocks base method.
func (m *MockStore) DeleteMCPServerUserHeaderValuesByConfigID(ctx context.Context, mcpServerConfigID uuid.UUID) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteMCPServerUserHeaderValuesByConfigID", ctx, mcpServerConfigID)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteMCPServerUserHeaderValuesByConfigID indicates an expected call of DeleteMCPServerUserHeaderValuesByConfigID.
func (mr *MockStoreMockRecorder) DeleteMCPServerUserHeaderValuesByConfigID(ctx, mcpServerConfigID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMCPServerUserHeaderValuesByConfigID", reflect.TypeOf((*MockStore)(nil).DeleteMCPServerUserHeaderValuesByConfigID), ctx, mcpServerConfigID)
}
// DeleteMCPServerUserToken mocks base method.
func (m *MockStore) DeleteMCPServerUserToken(ctx context.Context, arg database.DeleteMCPServerUserTokenParams) error {
m.ctrl.T.Helper()
+5
View File
@@ -139,6 +139,11 @@ type sqlcQuerier interface {
DeleteLicense(ctx context.Context, id int32) (int32, error)
DeleteMCPServerConfigByID(ctx context.Context, id uuid.UUID) error
DeleteMCPServerUserHeaderValues(ctx context.Context, arg DeleteMCPServerUserHeaderValuesParams) error
// Deletes every user's stored header values for the given MCP server
// config. Use when the admin changes auth_type away from custom_headers
// or alters custom_headers_user_keys so stale credentials do not
// silently reactivate when the key set is restored.
DeleteMCPServerUserHeaderValuesByConfigID(ctx context.Context, mcpServerConfigID uuid.UUID) error
DeleteMCPServerUserToken(ctx context.Context, arg DeleteMCPServerUserTokenParams) error
DeleteOAuth2ProviderAppByClientID(ctx context.Context, id uuid.UUID) error
DeleteOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID) error
+16
View File
@@ -15057,6 +15057,22 @@ func (q *sqlQuerier) DeleteMCPServerUserHeaderValues(ctx context.Context, arg De
return err
}
const deleteMCPServerUserHeaderValuesByConfigID = `-- name: DeleteMCPServerUserHeaderValuesByConfigID :exec
DELETE FROM
mcp_server_user_header_values
WHERE
mcp_server_config_id = $1::uuid
`
// Deletes every user's stored header values for the given MCP server
// config. Use when the admin changes auth_type away from custom_headers
// or alters custom_headers_user_keys so stale credentials do not
// silently reactivate when the key set is restored.
func (q *sqlQuerier) DeleteMCPServerUserHeaderValuesByConfigID(ctx context.Context, mcpServerConfigID uuid.UUID) error {
_, err := q.db.ExecContext(ctx, deleteMCPServerUserHeaderValuesByConfigID, mcpServerConfigID)
return err
}
const deleteMCPServerUserToken = `-- name: DeleteMCPServerUserToken :exec
DELETE FROM
mcp_server_user_tokens
@@ -260,6 +260,16 @@ WHERE
mcp_server_config_id = @mcp_server_config_id::uuid
AND user_id = @user_id::uuid;
-- name: DeleteMCPServerUserHeaderValuesByConfigID :exec
-- Deletes every user's stored header values for the given MCP server
-- config. Use when the admin changes auth_type away from custom_headers
-- or alters custom_headers_user_keys so stale credentials do not
-- silently reactivate when the key set is restored.
DELETE FROM
mcp_server_user_header_values
WHERE
mcp_server_config_id = @mcp_server_config_id::uuid;
-- name: CleanupDeletedMCPServerIDsFromChats :exec
UPDATE chats
SET mcp_server_ids = (