Skip to content

feat: api key last_used_at and failed auth logging - #266

Merged
Zingzy merged 4 commits into
mainfrom
feat/api-key-hygiene
Jul 22, 2026
Merged

feat: api key last_used_at and failed auth logging#266
Zingzy merged 4 commits into
mainfrom
feat/api-key-hygiene

Conversation

@Zingzy

@Zingzy Zingzy commented Jul 22, 2026

Copy link
Copy Markdown
Member

Stacked on #265.

What

  • last_used_at on API key documents, stamped on successful auth and returned by GET /api/v1/keys as a Unix timestamp (null when never used).
  • Failed API key auth is now logged as api_key_auth_failed with a reason (unknown, revoked, expired).

Performance

The stamp is debounced: it only writes when the stored value is missing or older than one hour, so a key making 100k requests a day costs about 24 single-document writes. The write is best-effort inside a try/except; a failed or slow stamp can never fail or block authentication. The hot path stays read-only otherwise.

Security

Failure logs carry the display prefix only (the same 8 characters the dashboard shows), never hashes or raw material. Volume is bounded upstream by the rate limiter. Revoked and expired entries include key_id and user_id so a revoked key still being retried by a forgotten script is visible and attributable.

Notes

  • last_used_at gives the dashboard a "never used / last used N days ago" signal, which is the piece that makes revoking old keys feel safe.
  • Existing key documents simply read as null until first use after deploy.
  • Unit tests cover the debounce window, naive datetime handling from Mongo, stamp failure tolerance, and all three failure reasons.

Summary by CodeRabbit

  • New Features

    • API key listings now show when each key was last used, or indicate that it has never been used.
    • API key usage timestamps are recorded automatically after successful authentication.
  • Bug Fixes

    • Authentication remains successful even if updating usage metadata fails.
    • Improved handling of UTC timestamps and clearer diagnostics for invalid, revoked, or expired API keys.

Stamp last_used_at on successful API key auth, debounced to one write
per key per hour and best-effort so the hot path never fails or slows.
Expose it on the keys list endpoint. Log failed key auth (unknown,
revoked, expired) with the display prefix only.
Copilot AI review requested due to automatic review settings July 22, 2026 13:22

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

API-key authentication now uses timezone-aware expiry checks, debounced best-effort usage stamping, and structured warnings for unknown, revoked, or expired keys. API-key models, listing responses, repository behavior, and unit tests were updated accordingly.

Changes

API key hygiene

Layer / File(s) Summary
Last-used data and response contracts
schemas/models/api_key.py, schemas/dto/responses/api_key.py, routes/api_v1/keys.py
API-key documents and responses include optional last_used_at timestamps, and the listing endpoint returns the value as a Unix timestamp.
Authentication stamping and diagnostics
dependencies/auth.py, repositories/api_key_repository.py
API-key authentication normalizes UTC expiry handling, logs selected failure reasons, and debounces best-effort updates to last_used_at.
Authentication hygiene validation
tests/unit/test_auth_deps.py
Tests cover timestamp debouncing, naive UTC values, failed stamp writes, and structured warnings for failed API-key authentication.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant get_current_user
  participant ApiKeyRepository
  participant APIKeyDatabase
  get_current_user->>ApiKeyRepository: Authenticate API key
  get_current_user->>ApiKeyRepository: Touch last_used_at when stale
  ApiKeyRepository->>APIKeyDatabase: Update timestamp
  APIKeyDatabase-->>ApiKeyRepository: Return update result
  ApiKeyRepository-->>get_current_user: Continue authentication
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main changes: API key last_used_at tracking and failed authentication logging.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/api-key-hygiene

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Zingzy Zingzy left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mergeable, nothing blocking. Two claims verified, small nits inline.

Verified:

  • Prefix parity: key creation stores token_prefix = raw[:8] (services/api_key_service.py:73), so the unknown-key log line exposes exactly what the dashboard already shows for real keys, nothing more.
  • Merge-order coupling: key_prefix and key_id in api_key_auth_failed only survive the log redactor because of the safe list added in #265 (both names contain "key" and would otherwise land in Axiom as ***REDACTED***). The stack ordering enforces this; do not cherry-pick this without #265.

Notes:

  • Concurrent stale auths can each fire touch_last_used; it is a single-document $set writing the same value, so harmless.
  • DB errors during lookup still return None silently (pre-existing, unchanged here). reason="unknown" only fires when the lookup succeeded and found nothing, which is the right split: a Mongo outage should not masquerade as credential misuse.
  • The debounce write is awaited inline, so roughly one API-key request per key per hour pays a single-document write of latency. Fine at current volume; if it ever shows up in duration percentiles, fire-and-forget is the escape hatch.

Affected test files pass at this head (341). Full matrix will run once this retargets to main.

Comment thread dependencies/auth.py Outdated
Comment thread dependencies/auth.py Outdated
Comment thread tests/unit/test_auth_deps.py Outdated
Zingzy added 2 commits July 22, 2026 20:10
Use shared as_aware_utc for both expiry and last_used normalization,
trim the failure-log comment to its invariant, and drop the unused
key_doc param from the test mock helper.
@Zingzy

Zingzy commented Jul 22, 2026

Copy link
Copy Markdown
Member Author

All three resolved at af9aa97, verified in the diff rather than the descriptions: the comment now states just the invariant, both datetime normalizations go through as_aware_utc (behavior-equivalent: a None expires_at still skips the check, and the hand-rolled tzinfo branch is gone), and the unused test param is dropped from all five call sites. Nothing further.

Base automatically changed from feat/client-source-telemetry to main July 22, 2026 15:01
@Zingzy
Zingzy merged commit 91dc0b2 into main Jul 22, 2026
11 of 12 checks passed
@Zingzy
Zingzy deleted the feat/api-key-hygiene branch July 22, 2026 15:06

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
dependencies/auth.py (1)

95-98: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Unexpected repository errors during API-key auth are silently swallowed.

find_by_hash and find_by_id failures both return None with no logging, unlike the deliberately-logged unknown/revoked/expired reasons. A Mongo outage on the API-key path would look identical to normal invalid-key traffic in logs, undermining the diagnostics goal of this PR. Consider logging (e.g. log.warning("api_key_auth_error", ...)) before returning None in these two branches.

Also applies to: 127-130

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dependencies/auth.py` around lines 95 - 98, Update the exception handlers
around key_repo.find_by_hash and key_repo.find_by_id to log unexpected
repository failures with the existing logger before returning None. Include
sufficient error context, such as the operation and exception, while preserving
the current authentication failure return behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@dependencies/auth.py`:
- Around line 95-98: Update the exception handlers around key_repo.find_by_hash
and key_repo.find_by_id to log unexpected repository failures with the existing
logger before returning None. Include sufficient error context, such as the
operation and exception, while preserving the current authentication failure
return behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 895673aa-b354-4ada-ac4f-4f432ea4afd1

📥 Commits

Reviewing files that changed from the base of the PR and between 18f4711 and 3eae1ca.

📒 Files selected for processing (6)
  • dependencies/auth.py
  • repositories/api_key_repository.py
  • routes/api_v1/keys.py
  • schemas/dto/responses/api_key.py
  • schemas/models/api_key.py
  • tests/unit/test_auth_deps.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: ✔️ Done

Development

Successfully merging this pull request may close these issues.

2 participants