Skip to content

fix: per-algorithm JWT secret minimum; domain exceptions mapped once; datetime polish - #14

Merged
JsNcAr merged 3 commits into
mainfrom
fix/jwt-key-minimum-and-polish
Oct 8, 2026
Merged

JsNcAr merged 3 commits into
mainfrom
fix/jwt-key-minimum-and-polish

Conversation

@JsNcAr

@JsNcAr JsNcAr commented Oct 8, 2026

Copy link
Copy Markdown
Owner

What this PR does

Third and last PR of the "use the new stack" plan. Three small commits, one concern each.

1. fix(auth): the secret minimum follows the algorithm

config.py enforced a flat 32 bytes, right for HS256 but short for HS384 (48) and HS512 (64), and PyJWT 2.15 warns on every sign and verify when the key is shorter than the hash it drives (RFC 7518 section 3.2). config.minimum_secret_bytes(algorithm) returns the per-algorithm minimum, the startup check uses it, and the message names the algorithm and the generator command. decode_access_token also passes PyJWT's enforce_minimum_key_length, so the library's own rule is an error rather than a warning, a second line behind the startup check. Checked by hand: HS512 with a 40-byte secret now refuses to start. This is the first piece of startup validation that is a tested function; the Known Issues entry says so.

2. refactor: domain exceptions mapped once

IdentifierTakenError (409) and PasswordVerificationError (400) are handled by app-level exception handlers in main.py. The four routes that caught them by hand now re-raise any AppError and keep only the branch that logs an unexpected failure. Same codes, same bodies; the existing tests pin them. (FastAPI 0.137's release notes list per-router exception handlers under planned; APIRouter has no such parameter, so app-level is the right tool.)

3. chore: datetime polish and the Python 3.14 notes

utc_now() uses datetime.UTC; UserResponseSchema declares created_at and updated_at as AwareDatetime, the contract SQLModel's UTCDateTime fulfils. The setup guide gains a note on python -m asyncio ps|pstree <pid>, new in 3.14, for a hung worker. The changelog records which 3.14 features were evaluated and left unused, and why: template strings, deferred annotations, multiple interpreters and free-threading, compression.zstd, functools.Placeholder. Adopted across the plan: uuid.uuid7() (#12), PEP 758 via Black, the asyncio introspection commands.

Verified

  • black --check, flake8, mypy pass; 40 database-free tests pass (four new for the secret minimum); 83 collect.
  • By hand: HS512 with a 40-byte secret is refused at startup; both domain handlers are registered on the app; a token round-trips with the enforced key-length option.
  • The database-backed tests, which pin every 409 and 400 the handlers now produce, run in this PR's test job.

🤖 Generated with Claude Code

JsNcAr and others added 3 commits October 7, 2026 23:53
A flat 32-byte minimum let an HS384 or HS512 deployment start with a key
shorter than the hash it drives, which PyJWT 2.15 warns about on every
operation. config.minimum_secret_bytes() returns the RFC 7518 section 3.2
minimum per algorithm (32, 48, 64); the startup check uses it and names the
algorithm and the generator command in its message. decode_access_token
passes enforce_minimum_key_length so PyJWT's own rule is an error rather than
a warning, a second line behind the startup check. The function is the first
piece of startup validation with a test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
IdentifierTakenError (409) and PasswordVerificationError (400) are handled by
app-level exception handlers in main.py; the four routes that caught them by
hand now re-raise any AppError and keep only the branch that logs an
unexpected failure. Same codes, same bodies; the self-service and
registration tests pin them. The services stay HTTP-free.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
utc_now() uses datetime.UTC, the 3.11+ alias of timezone.utc, which reads as
intended. UserResponseSchema declares created_at and updated_at as
AwareDatetime, the contract SQLModel's UTCDateTime fulfils. The setup guide
gains a note on `python -m asyncio ps|pstree`, new in 3.14, for a hung worker.
The changelog records which 3.14 features were evaluated and left unused and
why, so the question need not be reopened.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@JsNcAr
JsNcAr merged commit 4155ffe into main Oct 8, 2026
2 checks passed
@JsNcAr
JsNcAr deleted the fix/jwt-key-minimum-and-polish branch October 8, 2026 05:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant