Skip to content

Commit 9a10863

Browse files
committed
fix(039): wrap security API responses in {success, data} envelope
The Web UI expected APIResponse format {success: true, data: ...} but security handlers were writing raw data. Changed all handlers to use writeSuccess() which wraps via contracts.NewSuccessResponse(). Updated all 23 httpapi security tests to parse the wrapped format. Rebuilt frontend dist for embedded Web UI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 317333c commit 9a10863

2 files changed

Lines changed: 42 additions & 37 deletions

File tree

internal/httpapi/security_scanner.go

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/go-chi/chi/v5"
99

10+
"github.com/smart-mcp-proxy/mcpproxy-go/internal/contracts"
1011
"github.com/smart-mcp-proxy/mcpproxy-go/internal/security/scanner"
1112
)
1213

@@ -57,7 +58,7 @@ func (s *Server) handleListScanners(w http.ResponseWriter, r *http.Request) {
5758
s.writeError(w, r, http.StatusInternalServerError, err.Error())
5859
return
5960
}
60-
s.writeJSON(w, http.StatusOK, scanners)
61+
s.writeSuccess(w, scanners)
6162
}
6263

6364
func (s *Server) handleInstallScanner(w http.ResponseWriter, r *http.Request) {
@@ -80,7 +81,7 @@ func (s *Server) handleInstallScanner(w http.ResponseWriter, r *http.Request) {
8081
s.writeError(w, r, http.StatusInternalServerError, err.Error())
8182
return
8283
}
83-
s.writeJSON(w, http.StatusOK, map[string]string{"status": "installed", "id": req.ID})
84+
s.writeSuccess(w, map[string]string{"status": "installed", "id": req.ID})
8485
}
8586

8687
func (s *Server) handleRemoveScanner(w http.ResponseWriter, r *http.Request) {
@@ -97,7 +98,7 @@ func (s *Server) handleRemoveScanner(w http.ResponseWriter, r *http.Request) {
9798
s.writeError(w, r, http.StatusInternalServerError, err.Error())
9899
return
99100
}
100-
s.writeJSON(w, http.StatusOK, map[string]string{"status": "removed", "id": id})
101+
s.writeSuccess(w, map[string]string{"status": "removed", "id": id})
101102
}
102103

103104
func (s *Server) handleConfigureScanner(w http.ResponseWriter, r *http.Request) {
@@ -126,7 +127,7 @@ func (s *Server) handleConfigureScanner(w http.ResponseWriter, r *http.Request)
126127
s.writeError(w, r, http.StatusInternalServerError, err.Error())
127128
return
128129
}
129-
s.writeJSON(w, http.StatusOK, map[string]string{"status": "configured", "id": id})
130+
s.writeSuccess(w, map[string]string{"status": "configured", "id": id})
130131
}
131132

132133
func (s *Server) handleGetScannerStatus(w http.ResponseWriter, r *http.Request) {
@@ -144,7 +145,7 @@ func (s *Server) handleGetScannerStatus(w http.ResponseWriter, r *http.Request)
144145
s.writeError(w, r, http.StatusNotFound, err.Error())
145146
return
146147
}
147-
s.writeJSON(w, http.StatusOK, sc)
148+
s.writeSuccess(w, sc)
148149
}
149150

150151
// --- Scan operation handlers ---
@@ -172,7 +173,7 @@ func (s *Server) handleStartScan(w http.ResponseWriter, r *http.Request) {
172173
s.writeError(w, r, http.StatusInternalServerError, err.Error())
173174
return
174175
}
175-
s.writeJSON(w, http.StatusAccepted, job)
176+
s.writeJSON(w, http.StatusAccepted, contracts.NewSuccessResponse(job))
176177
}
177178

178179
func (s *Server) handleGetScanStatus(w http.ResponseWriter, r *http.Request) {
@@ -190,7 +191,7 @@ func (s *Server) handleGetScanStatus(w http.ResponseWriter, r *http.Request) {
190191
s.writeError(w, r, http.StatusNotFound, err.Error())
191192
return
192193
}
193-
s.writeJSON(w, http.StatusOK, job)
194+
s.writeSuccess(w, job)
194195
}
195196

196197
func (s *Server) handleGetScanReport(w http.ResponseWriter, r *http.Request) {
@@ -208,7 +209,7 @@ func (s *Server) handleGetScanReport(w http.ResponseWriter, r *http.Request) {
208209
s.writeError(w, r, http.StatusNotFound, err.Error())
209210
return
210211
}
211-
s.writeJSON(w, http.StatusOK, report)
212+
s.writeSuccess(w, report)
212213
}
213214

214215
func (s *Server) handleCancelScan(w http.ResponseWriter, r *http.Request) {
@@ -225,7 +226,7 @@ func (s *Server) handleCancelScan(w http.ResponseWriter, r *http.Request) {
225226
s.writeError(w, r, http.StatusInternalServerError, err.Error())
226227
return
227228
}
228-
s.writeJSON(w, http.StatusOK, map[string]string{"status": "cancelled", "server_name": name})
229+
s.writeSuccess(w, map[string]string{"status": "cancelled", "server_name": name})
229230
}
230231

231232
// --- Approval handlers ---
@@ -250,7 +251,7 @@ func (s *Server) handleSecurityApprove(w http.ResponseWriter, r *http.Request) {
250251
s.writeError(w, r, http.StatusConflict, err.Error())
251252
return
252253
}
253-
s.writeJSON(w, http.StatusOK, map[string]string{"status": "approved", "server_name": name})
254+
s.writeSuccess(w, map[string]string{"status": "approved", "server_name": name})
254255
}
255256

256257
func (s *Server) handleSecurityReject(w http.ResponseWriter, r *http.Request) {
@@ -267,7 +268,7 @@ func (s *Server) handleSecurityReject(w http.ResponseWriter, r *http.Request) {
267268
s.writeError(w, r, http.StatusInternalServerError, err.Error())
268269
return
269270
}
270-
s.writeJSON(w, http.StatusOK, map[string]string{"status": "rejected", "server_name": name})
271+
s.writeSuccess(w, map[string]string{"status": "rejected", "server_name": name})
271272
}
272273

273274
func (s *Server) handleCheckIntegrity(w http.ResponseWriter, r *http.Request) {
@@ -285,7 +286,7 @@ func (s *Server) handleCheckIntegrity(w http.ResponseWriter, r *http.Request) {
285286
s.writeError(w, r, http.StatusNotFound, err.Error())
286287
return
287288
}
288-
s.writeJSON(w, http.StatusOK, result)
289+
s.writeSuccess(w, result)
289290
}
290291

291292
// --- Overview handler ---
@@ -299,5 +300,5 @@ func (s *Server) handleSecurityOverview(w http.ResponseWriter, r *http.Request)
299300
s.writeError(w, r, http.StatusInternalServerError, err.Error())
300301
return
301302
}
302-
s.writeJSON(w, http.StatusOK, overview)
303+
s.writeSuccess(w, overview)
303304
}

internal/httpapi/security_scanner_test.go

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,22 @@ func (m *secTestController) GetCurrentConfig() interface{} {
137137
}
138138

139139
// helper to create a test server with security controller
140+
141+
// secParseData extracts the "data" field from an APIResponse wrapper {success: true, data: ...}
142+
func secParseData(t *testing.T, body *bytes.Buffer, target interface{}) {
143+
t.Helper()
144+
var wrapper struct {
145+
Success bool `json:"success"`
146+
Data json.RawMessage `json:"data"`
147+
}
148+
err := json.NewDecoder(body).Decode(&wrapper)
149+
require.NoError(t, err)
150+
assert.True(t, wrapper.Success)
151+
if target != nil && wrapper.Data != nil {
152+
require.NoError(t, json.Unmarshal(wrapper.Data, target))
153+
}
154+
}
155+
140156
func newTestServerWithSecurity(t *testing.T, secCtrl SecurityController) *Server {
141157
t.Helper()
142158
logger := zap.NewNop().Sugar()
@@ -165,8 +181,7 @@ func TestSecurityHandlerListScanners(t *testing.T) {
165181
assert.Equal(t, http.StatusOK, w.Code)
166182

167183
var scanners []*scanner.ScannerPlugin
168-
err := json.NewDecoder(w.Body).Decode(&scanners)
169-
require.NoError(t, err)
184+
secParseData(t, w.Body, &scanners)
170185
assert.Len(t, scanners, 2)
171186
assert.Equal(t, "mcp-scan", scanners[0].ID)
172187
}
@@ -184,8 +199,7 @@ func TestSecurityHandlerInstallScanner(t *testing.T) {
184199
assert.Equal(t, http.StatusOK, w.Code)
185200

186201
var resp map[string]string
187-
err := json.NewDecoder(w.Body).Decode(&resp)
188-
require.NoError(t, err)
202+
secParseData(t, w.Body, &resp)
189203
assert.Equal(t, "installed", resp["status"])
190204
assert.Equal(t, "mcp-scan", resp["id"])
191205
}
@@ -229,8 +243,7 @@ func TestSecurityHandlerRemoveScanner(t *testing.T) {
229243
assert.Equal(t, http.StatusOK, w.Code)
230244

231245
var resp map[string]string
232-
err := json.NewDecoder(w.Body).Decode(&resp)
233-
require.NoError(t, err)
246+
secParseData(t, w.Body, &resp)
234247
assert.Equal(t, "removed", resp["status"])
235248
}
236249

@@ -247,8 +260,7 @@ func TestSecurityHandlerConfigureScanner(t *testing.T) {
247260
assert.Equal(t, http.StatusOK, w.Code)
248261

249262
var resp map[string]string
250-
err := json.NewDecoder(w.Body).Decode(&resp)
251-
require.NoError(t, err)
263+
secParseData(t, w.Body, &resp)
252264
assert.Equal(t, "configured", resp["status"])
253265
}
254266

@@ -280,8 +292,7 @@ func TestSecurityHandlerGetScannerStatus(t *testing.T) {
280292
assert.Equal(t, http.StatusOK, w.Code)
281293

282294
var sc scanner.ScannerPlugin
283-
err := json.NewDecoder(w.Body).Decode(&sc)
284-
require.NoError(t, err)
295+
secParseData(t, w.Body, &sc)
285296
assert.Equal(t, "mcp-scan", sc.ID)
286297
assert.Equal(t, scanner.ScannerStatusInstalled, sc.Status)
287298
}
@@ -312,8 +323,7 @@ func TestSecurityHandlerStartScan(t *testing.T) {
312323
assert.Equal(t, http.StatusAccepted, w.Code)
313324

314325
var job scanner.ScanJob
315-
err := json.NewDecoder(w.Body).Decode(&job)
316-
require.NoError(t, err)
326+
secParseData(t, w.Body, &job)
317327
assert.Equal(t, "my-server", job.ServerName)
318328
assert.True(t, job.DryRun)
319329
}
@@ -350,8 +360,7 @@ func TestSecurityHandlerGetScanStatus(t *testing.T) {
350360
assert.Equal(t, http.StatusOK, w.Code)
351361

352362
var job scanner.ScanJob
353-
err := json.NewDecoder(w.Body).Decode(&job)
354-
require.NoError(t, err)
363+
secParseData(t, w.Body, &job)
355364
assert.Equal(t, "scan-123", job.ID)
356365
assert.Equal(t, scanner.ScanJobStatusCompleted, job.Status)
357366
}
@@ -387,8 +396,7 @@ func TestSecurityHandlerGetScanReport(t *testing.T) {
387396
assert.Equal(t, http.StatusOK, w.Code)
388397

389398
var report scanner.AggregatedReport
390-
err := json.NewDecoder(w.Body).Decode(&report)
391-
require.NoError(t, err)
399+
secParseData(t, w.Body, &report)
392400
assert.Equal(t, "scan-123", report.JobID)
393401
assert.Len(t, report.Findings, 1)
394402
assert.Equal(t, 1, report.Summary.High)
@@ -431,8 +439,7 @@ func TestSecurityHandlerApproveServer(t *testing.T) {
431439
assert.Equal(t, http.StatusOK, w.Code)
432440

433441
var resp map[string]string
434-
err := json.NewDecoder(w.Body).Decode(&resp)
435-
require.NoError(t, err)
442+
secParseData(t, w.Body, &resp)
436443
assert.Equal(t, "approved", resp["status"])
437444
}
438445

@@ -460,8 +467,7 @@ func TestSecurityHandlerRejectServer(t *testing.T) {
460467
assert.Equal(t, http.StatusOK, w.Code)
461468

462469
var resp map[string]string
463-
err := json.NewDecoder(w.Body).Decode(&resp)
464-
require.NoError(t, err)
470+
secParseData(t, w.Body, &resp)
465471
assert.Equal(t, "rejected", resp["status"])
466472
}
467473

@@ -482,8 +488,7 @@ func TestSecurityHandlerCheckIntegrity(t *testing.T) {
482488
assert.Equal(t, http.StatusOK, w.Code)
483489

484490
var result scanner.IntegrityCheckResult
485-
err := json.NewDecoder(w.Body).Decode(&result)
486-
require.NoError(t, err)
491+
secParseData(t, w.Body, &result)
487492
assert.True(t, result.Passed)
488493
}
489494

@@ -524,8 +529,7 @@ func TestSecurityHandlerOverview(t *testing.T) {
524529
assert.Equal(t, http.StatusOK, w.Code)
525530

526531
var overview scanner.SecurityOverview
527-
err := json.NewDecoder(w.Body).Decode(&overview)
528-
require.NoError(t, err)
532+
secParseData(t, w.Body, &overview)
529533
assert.Equal(t, 5, overview.TotalScans)
530534
assert.Equal(t, 1, overview.ActiveScans)
531535
assert.Equal(t, 2, overview.ScannersInstalled)

0 commit comments

Comments
 (0)