Skip to content

Commit b934520

Browse files
authored
feat: add security headers and enhance accessibility
1 parent f16f283 commit b934520

4 files changed

Lines changed: 78 additions & 2 deletions

File tree

internal/admin/server.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ func (s *Server) Run(ctx context.Context) error {
253253

254254
srv := &http.Server{
255255
Addr: s.addr,
256-
Handler: mux,
256+
Handler: withSecurityHeaders(mux),
257257
ReadHeaderTimeout: 5 * time.Second,
258258
WriteTimeout: 30 * time.Second,
259259
IdleTimeout: 60 * time.Second,
@@ -282,6 +282,15 @@ func (s *Server) Run(ctx context.Context) error {
282282
}
283283
}
284284

285+
func withSecurityHeaders(next http.Handler) http.Handler {
286+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
287+
w.Header().Set("X-Content-Type-Options", "nosniff")
288+
w.Header().Set("X-Frame-Options", "DENY")
289+
w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
290+
next.ServeHTTP(w, r)
291+
})
292+
}
293+
285294
type healthResponse struct {
286295
Status string `json:"status"`
287296
Errors []string `json:"errors,omitempty"`

internal/admin/server_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,3 +359,27 @@ func TestRefreshMetrics(t *testing.T) {
359359
}
360360
}
361361
}
362+
363+
func TestWithSecurityHeaders(t *testing.T) {
364+
handler := withSecurityHeaders(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
365+
w.WriteHeader(http.StatusOK)
366+
}))
367+
368+
req := httptest.NewRequest(http.MethodGet, "/", nil)
369+
rec := httptest.NewRecorder()
370+
371+
handler.ServeHTTP(rec, req)
372+
373+
res := rec.Result()
374+
defer func() { _ = res.Body.Close() }()
375+
376+
if got := res.Header.Get("X-Content-Type-Options"); got != "nosniff" {
377+
t.Errorf("X-Content-Type-Options = %q, want nosniff", got)
378+
}
379+
if got := res.Header.Get("X-Frame-Options"); got != "DENY" {
380+
t.Errorf("X-Frame-Options = %q, want DENY", got)
381+
}
382+
if got := res.Header.Get("Referrer-Policy"); got != "strict-origin-when-cross-origin" {
383+
t.Errorf("Referrer-Policy = %q, want strict-origin-when-cross-origin", got)
384+
}
385+
}

internal/db/repository/task_repo_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,42 @@ func TestTaskRepo_Complete(t *testing.T) {
421421
}
422422
}
423423

424+
func TestTaskRepo_CompleteWithMessage(t *testing.T) {
425+
db := testDB(t)
426+
repos := repository.NewRepositories(db)
427+
ctx := context.Background()
428+
429+
seedTask(t, repos, model.TaskTypeUpload)
430+
claimed, err := repos.Tasks.ClaimReady(ctx, model.TaskTypeUpload, 5*time.Minute)
431+
if err != nil {
432+
t.Fatalf("ClaimReady: %v", err)
433+
}
434+
if claimed == nil {
435+
t.Fatal("setup: could not claim task")
436+
}
437+
438+
message := "completed successfully with special condition"
439+
if err := repos.Tasks.CompleteWithMessage(ctx, claimed, message); err != nil {
440+
t.Fatalf("CompleteWithMessage: %v", err)
441+
}
442+
443+
task, err := repos.Tasks.GetByID(ctx, claimed.ID)
444+
if err != nil {
445+
t.Fatalf("GetByID: %v", err)
446+
}
447+
if task == nil {
448+
t.Fatal("expected task to be found, got nil")
449+
}
450+
if task.Status != model.TaskStatusCompleted {
451+
t.Errorf("expected status %s, got %s", model.TaskStatusCompleted, task.Status)
452+
}
453+
if task.StatusMessage == nil {
454+
t.Errorf("expected status_message %q, got nil", message)
455+
} else if *task.StatusMessage != message {
456+
t.Errorf("expected status_message %q, got %q", message, *task.StatusMessage)
457+
}
458+
}
459+
424460
func TestTaskRepo_Complete_NotRunning(t *testing.T) {
425461
db := testDB(t)
426462
repos := repository.NewRepositories(db)

ui/src/routes/index.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,14 @@ function StorageHealthCard({ health }: { health: OverviewData['filecoin_storage_
180180
</span>
181181
<span className="font-semibold text-foreground">{formatOptionalPercent(dataSets.readyPercent)}</span>
182182
</div>
183-
<div className="h-2 min-w-0 overflow-hidden rounded-full bg-muted">
183+
<div
184+
role="progressbar"
185+
aria-label="Storage copy readiness"
186+
aria-valuemin={dataSets.readyPercent != null ? 0 : undefined}
187+
aria-valuemax={dataSets.readyPercent != null ? 100 : undefined}
188+
aria-valuenow={dataSets.readyPercent ?? undefined}
189+
className="h-2 min-w-0 overflow-hidden rounded-full bg-muted"
190+
>
184191
<div
185192
className={`h-full rounded-full ${healthStyle.progressClassName}`}
186193
style={{ width: `${dataSets.readyPercent ?? 0}%` }}

0 commit comments

Comments
 (0)