fix #63: require deletion token on DELETE /api/pastes/{id}
CI / test (pull_request) Successful in 24s
CI / docker (pull_request) Skipped

- DELETE now demands the create-time deletion token (Authorization
  header: Bearer/Token/bare, or ?token= query param), compared with
  the constant-time store.DeletionTokenEqual. 403 otherwise.
- Creator-browser deletes via the /mine button (matching vwr cookie,
  #37) remain allowed; other browsers and plain API clients get 403.
- Regression tests: no token, wrong token (header+query), correct
  token (header+query), creator-cookie path, token extraction.
- Adapted TestSoftDelete to pass the deletion token.
- docs/API.md delete section updated.
- Based on #58's SoftDelete (bool, error) signature.
This commit is contained in:
2026-09-09 09:21:28 -05:00
parent 78374b2d49
commit 91568c0598
4 changed files with 231 additions and 14 deletions
+18 -6
View File
@@ -42,7 +42,9 @@ func TestCreateAndGetPaste(t *testing.T) {
if rec.Code != 201 {
t.Fatalf("create: got %d want 201: %s", rec.Code, rec.Body.String())
}
var created struct{ ID string `json:"id"` }
var created struct {
ID string `json:"id"`
}
json.Unmarshal(rec.Body.Bytes(), &created)
if len(created.ID) != 6 {
t.Fatalf("unexpected id: %q", created.ID)
@@ -73,7 +75,9 @@ func TestPasswordProtection(t *testing.T) {
req := httptest.NewRequest("POST", "/api/pastes", strings.NewReader(body))
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
var created struct{ ID string `json:"id"` }
var created struct {
ID string `json:"id"`
}
json.Unmarshal(rec.Body.Bytes(), &created)
// without password -> 401
@@ -133,10 +137,14 @@ func TestSoftDelete(t *testing.T) {
req := httptest.NewRequest("POST", "/api/pastes", strings.NewReader(`{"content":"bye"}`))
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
var created struct{ ID string `json:"id"` }
var created struct {
ID string `json:"id"`
DeletionToken string `json:"deletion_token"`
}
json.Unmarshal(rec.Body.Bytes(), &created)
req = httptest.NewRequest("DELETE", "/api/pastes/"+created.ID, nil)
req.Header.Set("Authorization", "Bearer "+created.DeletionToken)
rec = httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != 200 {
@@ -166,7 +174,7 @@ func TestListPublicExcludesUnlisted(t *testing.T) {
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
var resp struct {
Total int `json:"total"`
Total int `json:"total"`
Items []map[string]any `json:"items"`
}
json.Unmarshal(rec.Body.Bytes(), &resp)
@@ -182,7 +190,9 @@ func TestSweepSoftDeletesAfterGrace(t *testing.T) {
req := httptest.NewRequest("POST", "/api/pastes", strings.NewReader(`{"content":"gone soon"}`))
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
var created struct{ ID string `json:"id"` }
var created struct {
ID string `json:"id"`
}
json.Unmarshal(rec.Body.Bytes(), &created)
s.store.SoftDelete(created.ID)
@@ -218,7 +228,9 @@ func TestRawEndpoint(t *testing.T) {
req := httptest.NewRequest("POST", "/api/pastes", strings.NewReader(`{"content":"raw content here"}`))
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
var created struct{ ID string `json:"id"` }
var created struct {
ID string `json:"id"`
}
json.Unmarshal(rec.Body.Bytes(), &created)
req = httptest.NewRequest("GET", "/raw/"+created.ID, nil)