Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions docs/domain-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<ApiKey>
fun findById(id: ApiKeyId, tenantId: TenantId): ApiKey?
fun findAllByTenantId(tenantId: TenantId): List<ApiKey>
}
```

`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
Expand Down