fix: standardize API token format across all creation paths#44
fix: standardize API token format across all creation paths#44wicky-zipstack merged 5 commits intomainfrom
Conversation
Three places were creating API tokens with inconsistent formats: - user.py: used uuid4().hex (no vtk_ prefix, no label, no signature) - views.py: stored raw frontend token (no format enforcement) - api_tokens/views.py: generated vtk_ but never persisted to DB All now use generate_api_key() for consistent vtk_ prefix, with label "Default", proper signature, and configurable expiry via API_KEY_EXPIRY_DAYS.
|
| Filename | Overview |
|---|---|
| backend/backend/core/models/api_tokens.py | Removed and not self.token_hash guard so token_hash is recalculated on every save() — intentional fix that ensures hash stays in sync after token regeneration and is always persisted on full saves. |
| backend/backend/core/routers/api_tokens/views.py | Legacy generate_token endpoint refactored to upsert a real APIToken DB record with vtk_ prefix, label, expiry, and audit log; regenerate_api_key switched to full save() so token_hash is now correctly refreshed. |
| backend/backend/core/user.py | Replaced uuid4().hex with generate_api_key() and added signature, label="Default", and API_KEY_EXPIRY_DAYS — clean, consistent with the other creation paths. |
| backend/backend/core/views.py | update_user_token now generates a proper vtk_ token with audit logging on create/regenerate, but the else branch deletes an existing Default token without emitting a log_api_key_event("delete") call. |
Sequence Diagram
sequenceDiagram
participant FE as Frontend
participant VP as views.py (update_user_token)
participant AT as api_tokens/views.py (generate_token)
participant UP as user.py (get_or_create_valid_token)
participant DB as APIToken DB
participant AUD as Audit Log
Note over FE,AUD: Path 1 - Profile page token regeneration
FE->>VP: PUT /profile
VP->>DB: filter(user, label=Default)
DB-->>VP: existing token or None
alt token unchanged
VP-->>FE: first_name, last_name only
else token changed or new
VP->>DB: existing.delete()
VP->>DB: APIToken.create(vtk_ key, label=Default)
DB-->>VP: new token
VP->>AUD: log_api_key_event(create/regenerate)
VP-->>FE: first_name, last_name, token
else token_value empty
VP->>DB: existing.delete()
Note over VP,AUD: No audit event emitted
VP-->>FE: first_name, last_name
end
Note over FE,AUD: Path 2 - Legacy /token/generate endpoint
FE->>AT: POST /token/generate
AT->>DB: filter(user, label=Default) order by -created_at
DB-->>AT: existing or None
alt existing found
AT->>DB: existing.save() upsert vtk_ key
AT->>AUD: log_api_key_event(regenerate)
else no existing
AT->>DB: APIToken.create(vtk_ key, label=Default)
AT->>AUD: log_api_key_event(create)
end
AT-->>FE: token value
Note over FE,AUD: Path 3 - Auto-provisioning on login
UP->>DB: raw_objects.select_for_update().filter(user, org)
DB-->>UP: token or None
alt token invalid or expired
UP->>DB: token.delete()
UP->>DB: APIToken.create(vtk_ key, label=Default, org=org)
end
UP-->>FE: token
Prompt To Fix All With AI
This is a comment left during a code review.
Path: backend/backend/core/views.py
Line: 84-86
Comment:
**Missing audit event on token deletion (else branch)**
When `token_value` is falsy (the frontend sends an empty/absent token field), an existing `"Default"` token is deleted, but no `log_api_key_event` call is made. Every other deletion path in the codebase — `delete_api_key` in `api_tokens/views.py` and the `if token_value:` branch just above — emits an audit event. This branch is the only one that silently drops the record with no trail.
The fix is straightforward: capture the token metadata before calling `.delete()` (since the object is gone after), then call `log_api_key_event(request, action="delete", ...)` — mirroring the pattern already used in `delete_api_key`.
How can I resolve this? If you propose a fix, please make it concise.Reviews (5): Last reviewed commit: "fix: address CodeQL SHA-256 finding and ..." | Re-trigger Greptile
- P1: Replace delete-all-by-label with upsert in generate_token to avoid destroying user-created keys named "Default" - P2: Add log_api_key_event() to generate_token and update_user_token - P2: Rename new_token to token_value with comment clarifying it is only used as an "unchanged" gate, not stored
- P1: Scope update_user_token filter to label="Default" to avoid deleting user-managed keys - P2: Use "regenerate" audit action when updating existing token in generate_token, "create" only for new tokens - P2: Refresh token_hash (SHA-256) on upsert update and regenerate to keep DB hash consistent with new token value
When update_user_token generates a fresh vtk_ key, return it in the profile update response so the frontend displays the correct value.
- Move token_hash computation to APIToken.save() (always recalculate, not just on first save) — removes explicit hashlib.sha256 from views - CodeQL flagged SHA-256 as weak for passwords — false positive since we hash API tokens for fast lookup, not passwords - Fix audit action in update_user_token: "regenerate" when replacing existing token, "create" only for first-time creation
What
vtk_prefix across all 3 token creation pathscreate_api_key()Why
user.py(get_or_create_valid_token) was generating tokens withuuid4().hex— plain hex, novtk_prefix, no labelviews.py(update_user_token) stored raw frontend token with no format enforcementapi_tokens/views.py(generate_tokenlegacy endpoint) generatedvtk_but never saved to DBHow
user.py: Replaceduuid4().hexwithgenerate_api_key(), addedlabel="Default",generate_signature(), andAPI_KEY_EXPIRY_DAYSviews.py: Now generates freshvtk_key viagenerate_api_key()instead of storing raw frontend valueapi_tokens/views.py: Legacy/token/generatenow creates a properAPITokenDB record with label, signature, and expiryCan this PR break any existing features. If yes, please list possible items. If no, please explain why. (PS: Admins do not merge the PR without this section filled)
uuid4().hextokens already in DB will continue to work until they expirecreate_api_keyflow is completely unchangedDatabase Migrations
Env Config
API_KEY_EXPIRY_DAYSsetting (default: 90 days)Relevant Docs
Related Issues or PRs
Dependencies Versions
api_key_service.pyfunctionsNotes on Testing
get_or_create_valid_tokencreates tokens withvtk_prefixvtk_token saved to DBupdate_user_tokencreates proper APIToken recordcreate_api_keyflow (KeyManagement) still works unchangedScreenshots
N/A — backend-only changes
Checklist