From 63556d36c2c48d80c98e7f6c4d954acd78adfd16 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 19 Jul 2026 23:11:54 +0000 Subject: [PATCH] docs: reflect ApiKeyRepository.findAllByPrefix in domain model PR #245 changed ApiKeyRepository.findByPrefix (single ApiKey?) to findAllByPrefix (List) since the 12-char prefix is a non-unique index (~65k possible values) and validate() must bcrypt-match the raw key against every live candidate. docs/domain-model.md still documented the old single-result signature and rationale. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_019eVnGMWmSr6MjzRg6122xR --- docs/domain-model.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/domain-model.md b/docs/domain-model.md index 6131228..aeef910 100644 --- a/docs/domain-model.md +++ b/docs/domain-model.md @@ -318,16 +318,21 @@ to the database and re-populates the cache. ```kotlin interface ApiKeyRepository { fun save(apiKey: ApiKey): ApiKey - fun findByPrefix(prefix: String): ApiKey? + fun findAllByPrefix(prefix: String): List fun findById(id: ApiKeyId, tenantId: TenantId): ApiKey? fun findAllByTenantId(tenantId: TenantId): List } ``` -`findByPrefix` takes no `tenantId` — the auth filter must read the table before the tenant +`findAllByPrefix` takes no `tenantId` — the auth filter must read the table before the tenant context is established. The bcrypt hash is the security boundary, not row-level isolation. The `api_keys` table therefore carries no RLS policy. +The prefix (`rawKey.take(12)` — `sk_live_` + 4 hex chars, ~65k possible values) is **not** +unique by design; its index is non-unique. `findAllByPrefix` returns every candidate sharing +the prefix, and `ApiKeyService.validate()` bcrypt-matches the raw key against each non-revoked +candidate to disambiguate. + `findAllByTenantId` returns all keys for a tenant (active and revoked) for use in key management listings. Used by `ListApiKeysUseCase` / `GET /api/v1/api-keys` (requires `ADMIN` scope). Key hashes are never included in API responses — the list endpoint exposes only