fix(security): API keys only deliver data; one auth check everywhere
Until API keys get scopes they have exactly one right: POST /api/v1/inbox (thought or task via target) and the webhook ingest; every other endpoint answers 403 for a key. Middleware and webhook ingest share one token check (revocation list, token_version, active account), closing FLAW-08. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
62463bc6fd
commit
300fd6a7be
4 files changed
+99
-49
No files matched your search
@@ -1406,3 +1406,46 @@ func TestLegalTextsArePublic(t *testing.T) {
|
||||
t.Fatalf("privacy without text should be 404, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// API keys have exactly one right until scopes exist: delivering data. Revoked sessions are refused
|
||||
// everywhere, the webhook ingest included.
|
||||
func TestAPIKeysOnlyDeliverData(t *testing.T) {
|
||||
srv, store, token := setupTestServer(t)
|
||||
router := srv.Router()
|
||||
claims, _ := auth.ValidateToken(testJWTSecret, token)
|
||||
key, err := store.CreateAPIKey(claims.UserID, "Home Assistant")
|
||||
if err != nil {
|
||||
t.Fatalf("CreateAPIKey: %v", err)
|
||||
}
|
||||
do := func(method, path, auth, body string) int {
|
||||
req := httptest.NewRequest(method, path, strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if strings.HasPrefix(auth, "mindsift_sec_") {
|
||||
req.Header.Set("X-API-Key", auth)
|
||||
} else {
|
||||
req.Header.Set("Authorization", "Bearer "+auth)
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
return rec.Code
|
||||
}
|
||||
if code := do(http.MethodPost, "/api/v1/inbox", key.RawKey, `{"content":"Waschmaschine fertig"}`); code >= 300 {
|
||||
t.Fatalf("API key must be able to post into the inbox, got %d", code)
|
||||
}
|
||||
if code := do(http.MethodPost, "/api/v1/inbox", key.RawKey, `{"content":"Filter wechseln","target":"task"}`); code >= 300 {
|
||||
t.Fatalf("API key must be able to create a task via the inbox, got %d", code)
|
||||
}
|
||||
if code := do(http.MethodGet, "/api/v1/documents", key.RawKey, ""); code != http.StatusForbidden {
|
||||
t.Fatalf("API key must not read notes, got %d", code)
|
||||
}
|
||||
if code := do(http.MethodPost, "/api/v1/webhooks/ingest", key.RawKey, `{"content":"Rauchmelder"}`); code >= 300 {
|
||||
t.Fatalf("API key must work on the webhook ingest, got %d", code)
|
||||
}
|
||||
// Logged-out session token: refused by the ingest as well (was FLAW-08)
|
||||
if code := do(http.MethodPost, "/api/v1/auth/logout", token, ""); code >= 300 {
|
||||
t.Fatalf("logout: %d", code)
|
||||
}
|
||||
if code := do(http.MethodPost, "/api/v1/webhooks/ingest", token, `{"content":"x"}`); code != http.StatusUnauthorized {
|
||||
t.Fatalf("revoked token must be refused by the ingest, got %d", code)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user