feat: add scoped API keys and conditional item updates - #202
krzysiek-kurek wants to merge 6 commits into
Conversation
Reviewer's GuideThis PR introduces hashed, user-owned scoped API keys with lifecycle management and Settings UI integration, while preserving JWT authentication and adding optional revision-based conditional item updates with row locking and standard If-Match validation. It also gates signed image URLs by API-key scope and adds broad backend/frontend regression coverage. Sequence diagram for scoped API key authenticationsequenceDiagram
participant Client
participant API
participant Auth as Auth resolver
participant Keys as ApiKeyService
participant DB as Database
Client->>API: Request with Bearer API key
API->>Auth: get_current_user
Auth->>Auth: _required_api_key_scope
Auth->>Keys: authenticate_with_key(token, required_scope)
Keys->>DB: Find token_hash
DB-->>Keys: ApiKey
Keys->>DB: Validate status, expiry, scope and user
DB-->>Keys: User
Keys->>DB: Update last_used_at
Keys-->>Auth: User and ApiKey
Auth-->>API: Authenticated request
API-->>Client: Scoped response
Sequence diagram for conditional item updatesequenceDiagram
participant Client
participant API
participant Items as ItemService
participant DB as Database
Client->>API: PATCH item with If-Match
API->>Items: get_by_id_for_update(item_id, user_id)
Items->>DB: SELECT item FOR UPDATE
DB-->>Items: Current item
Items-->>API: Locked item
API->>API: item_revision(item.id, item.updated_at)
API->>API: if_match_accepts(header, revision)
alt Revision accepted
API->>Items: update(item, item_data)
Items->>DB: Persist update
DB-->>Items: Updated item
API-->>Client: 200 ItemResponse with revision
else Stale revision
API-->>Client: 412 Precondition Failed
end
Entity relationship diagram for user-owned API keyserDiagram
USERS ||--o{ API_KEYS : owns
USERS {
UUID id PK
}
API_KEYS {
UUID id PK
UUID user_id FK
string token_hash UK
JSONB scopes
datetime expires_at
datetime revoked_at
datetime last_used_at
}
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
@sourcery-ai review |
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="backend/app/services/api_key_service.py" line_range="90-99" />
<code_context>
+ if not token.startswith(API_KEY_PREFIX):
+ return None
+
+ result = await self.db.execute(
+ select(ApiKey).where(ApiKey.token_hash == hash_api_key(token))
+ )
+ api_key = result.scalar_one_or_none()
+ now = datetime.now(UTC)
+ if not api_key or api_key.revoked_at is not None:
+ return None
+ if api_key.expires_at is not None and api_key.expires_at <= now:
+ return None
+ if required_scope not in api_key.scopes:
+ return None
+
</code_context>
<issue_to_address>
**issue (bug_risk):** API-key authentication reads that the key is active, then performs user lookup and updates `last_used_at` in separate statements without rechecking revocation. A concurrent revoke can commit after the initial read but before authentication returns, allowing a request authenticated with the revoked key to complete.
**Triggers:** When a request authenticates concurrently with revocation of the same key.
**Suggested fix:** Make authentication's active-key check and usage update atomic, or lock/recheck the key before returning authenticated access.
</issue_to_address>Sourcery assessment
Needs a human reviewer. 1 finding to address first, and this introduces bearer credentials and a new authorization policy: an incorrect route-to-scope mapping or image capability check could let a key holder read or update data beyond the intended grant. Reverting stops future use, but credentials may already have exposed data or caused updates, and a policy mistake can affect every API-key request without producing an obvious failure.
Blocking findings: backend/app/services/api_key_service.py:99
|
@sourcery-ai review |
There was a problem hiding this comment.
Hey - I've reviewed your changes and they look great!
Sourcery assessment
Needs a human reviewer. A mistake in API-key scope routing or authentication could grant bearer tokens access to user items, updates, or signed image URLs beyond their intended permissions. Reverting would stop future use, but any data or access exposed before the revert cannot be fully recovered; this also establishes a new authentication trust boundary.
Description
Adds scoped API keys for programmatic access to Wardrowbe, a Settings UI for managing them, and optional optimistic concurrency for item updates.
API keys are user-owned and support these scopes:
items:readitems:writeimages:readThe plaintext token is returned only once when the key is created. Only its SHA-256 hash is stored. Keys can expire, be revoked, and revoked or expired keys can be permanently deleted.
A new Settings → API Keys section lets users:
API-key authentication is supported alongside the existing JWT/session authentication. Existing JWT behavior is unchanged.
Image URLs are only exposed to API keys with the
images:readscope.Item responses now also include an opaque
revision. Clients may optionally send that revision usingIf-Matchwhen updating an item. Stale revisions return412 Precondition Failedinstead of silently overwriting newer changes. Existing unconditional item updates remain supported.The conditional update implementation uses row locking when
If-Matchis supplied and supports strong entity tags, comma-separated validators, and the standard*wildcard.Related Issue
N/A
Type of Change
Checklist
Testing
Test Environment
Tests Performed
ruff check .— passedruff format --check .— passedalembic heads— single headAdditional regression coverage includes:
last_used_atupdates under concurrent requests,412,If-Matchvalues,Screenshots (if applicable)
The new API key management UI is available at Settings → API Keys.
Additional Notes
api_keystable and corresponding Alembic migration.This change was developed with AI assistance (ChatGPT, OpenAI). The implementation and tests were reviewed and verified locally before submission.
Summary by Sourcery
Enable secure scoped API access and prevent stale item updates from overwriting newer changes.
New Features:
Enhancements:
Tests:
Chores: