Skip to content

v1.4.1 - API token verification cost

Choose a tag to compare

@ibuilder ibuilder released this 08 Aug 08:42
· 13 commits to main since this release

A performance fix on the API's authentication path, and test coverage for a
security control that had none.

API token verification: 151 ms → 7.6 ms

Argon2 is deliberately slow. That is correct for a password typed once and wrong
for a credential presented on every call — verifying the bearer token was most
of a ~150 ms API request.

A short-lived per-process cache now skips the hash comparison for a token that
has already verified. Measured back to back in one process on /api/v1/me:

Median
Argon2 every request (previous) 150.8 ms
With the verified-token cache 7.6 ms

What is cached is the verification, never the authorization decision. Every
request still loads the row and re-checks revocation and expiry, so revoking a
token takes effect on the very next call
. There are tests for revocation,
expiry and deletion specifically, because that is the property a later refactor
would quietly break.

The raw token is never a cache key — it is hashed with BLAKE2b keyed on
SECRET_KEY, so a memory dump yields nothing usable. The cache is bounded and
entries expire after five minutes.

last_used_at now writes at a five-minute resolution rather than on every
request. It answers "roughly when was this token last seen", which does not
justify a database write per API call.

Rate limiting is now actually tested

The entire suite ran with RATELIMIT_ENABLED = False — rate limits and fixtures
that sign in dozens of times do not mix — which left the only thing standing
between a password guesser and unlimited attempts with no coverage at all. An
upgrade could have turned it into a no-op and nothing would have failed.

tests/test_rate_limiting.py builds its own application with limiting on and
asserts the eleventh login POST inside a minute is refused, that a correct
password does not bypass an exhausted limit, and that GET is untouched.

Dependencies

actions/checkout v4→v7, actions/setup-python v5→v7,
actions/configure-pages v5→v6, actions/deploy-pages v4→v5,
docker/setup-buildx-action v3→v4, and Flask-Limiter widened to allow 4.x —
safe to take now that the limiter has real coverage. Verified against both 3.12
and 4.1.1.

Upgrading

No migration. No configuration change.

Full changelog: https://github.com/ibuilder/scopemaker/blob/main/CHANGELOG.md