fix(security): rate limit register endpoint; remove RESIDENT_TOKEN cookie#470
fix(security): rate limit register endpoint; remove RESIDENT_TOKEN cookie#470conorluddy wants to merge 7 commits into
Conversation
β¦okie Register endpoint now uses rateLimitTenPerTenMins (10/10min) matching login, magic-login, and reset-password β previously only had the loose 100-req/10-min global limiter. RESIDENT_TOKEN cookie exposed the user's internal ID unnecessarily on every login/refresh response. Removed from all auth flows (login, refreshToken, magicLoginWithToken, googleCallback). logout and refreshToken now resolve userId via DB token lookup instead of trusting a separate cookie. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
β¦alidate comment logout: degrade gracefully when refresh token not in DB (expired/purged) β cookie is already cleared so user is effectively logged out regardless. Fix misleading MISSING_USER_ID β REFRESH_TOKEN_REQUIRED error message. Add test for token-not-found path. refreshToken: add explanatory comment on the intentional delete-before-validate ordering (token theft detection: nuke all sessions on any suspicious reuse). Use guard clause to remove redundant if(token)/if(!token) double-check. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Codecov Reportβ
All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #470 +/- ##
==========================================
+ Coverage 83.71% 83.78% +0.06%
==========================================
Files 109 109
Lines 1781 1776 -5
Branches 295 294 -1
==========================================
- Hits 1491 1488 -3
Misses 190 190
+ Partials 100 98 -2 β View full report in Codecov by Sentry. π New features to boost your workflow:
|
## Summary > Stacks on #467 β merge that first. - **Medium:** `jwt.sign` and `jwt.verify` now explicitly specify `algorithm: 'HS256'` / `algorithms: ['HS256']`. Prevents algorithm confusion attacks (e.g. `alg: none`) if the library ever changes its default or an attacker crafts a malicious token. - **Medium:** `express.json()` now has an explicit `limit: '10kb'` β makes the body size cap intentional and documented rather than relying on the library default. ## Test plan - [ ] `npm run lint && npm test` green - [ ] Manual: attempt a JWT with `alg: none` β verify rejected - [ ] Manual: send a JSON body >10kb β verify 413 response π€ Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Code Review: PR 470 - rate limit register endpoint; remove RESIDENT_TOKEN cookieThis is the strongest of the three security PRs. The changes are correct, well-motivated, and the PR description explains the reasoning clearly. Positives
Issues1. Two dead message constants in messages.ts REFRESH_TOKEN_COUNTERPART_REQUIRED and TOKEN_USER_INVALID are defined in src/constants/messages.ts but are no longer referenced anywhere in the codebase after this PR. They should be removed to prevent dead code accumulating in the constants file. 2. Stale Swagger JSDoc on the refresh route src/routes/auth/refreshToken.ts line 36 still documents a residentToken cookie in its swagger block with the description: HTTP-only cookie containing the user ID for cross checking with the token. This cookie no longer exists. The Swagger UI will advertise an auth mechanism that is not in use. 3. Logout: extra DB round-trip (noted, not blocking) The old logout used userId from the RESIDENT_TOKEN cookie for a single deleteRefreshTokensByUserId call. The new logout calls getToken first, then deleteRefreshTokensByUserId β two DB calls. This is the correct trade-off since we should not trust a cookie for the userId, and logout is low-frequency so the cost is acceptable. Calling it out explicitly so it is a conscious decision rather than an accidental one. 4. Rate limiter is in-memory / single-instance only (pre-existing issue) rateLimitTenPerTenMins uses the default in-memory store. The existing TODO comment in rateLimiter.ts acknowledges this. It is not a new issue introduced by this PR, but worth noting: in a distributed or multi-instance deployment the per-IP counter does not share state across nodes, making the limit bypassable at scale. Minor
Summary: Two action items before merge: (1) remove the two dead message constants REFRESH_TOKEN_COUNTERPART_REQUIRED and TOKEN_USER_INVALID from src/constants/messages.ts, and (2) update the Swagger JSDoc on src/routes/auth/refreshToken.ts to remove the stale residentToken cookie documentation. Everything else is correct and well-implemented. |
Code Review: fix(security): rate limit register endpoint; remove RESIDENT_TOKEN cookieOverall this is a solid, well-scoped security hardening PR. The changes are correct and the rationale is clear. A few items worth discussing: Strengths
Issues1. Version number skips 0.3.9 2. Delete-before-validate DoS surface in
The attacker ends up with a new valid token; the victim loses all other sessions. If an attacker already holds a valid refresh token, they've already achieved session access, so this may be an acceptable trade-off for theft detection. But it's worth documenting this as a known consequence, not just the "replayed expired token" case mentioned in the comment. 3. Orphaned
Minor cleanup, but dead constants in a messages file tend to stick around indefinitely. 4. In-memory rate limiter won't survive horizontal scaling 5. Stale comment in The window is 6. Integration test requires a live DB SummaryThe core security changes (removing π€ Generated with Claude Code |
PR Review: fix(security): rate limit register endpoint; remove RESIDENT_TOKEN cookieOverall this is a solid, well-motivated security hardening PR. The changes are cohesive, the comments explain why not what, and the new integration tests are a welcome addition. A few things worth addressing before merge: Must Fix
Dead code in
They should be removed from Worth Discussing
// Deletes ALL sessions BEFORE checking token.used
await SERVICES.deleteRefreshTokensByUserId({ userId: token.userId })
if (token.used) { throw new ForbiddenError(MESSAGES.TOKEN_USED) }If an attacker obtains any refresh token ID for a user (even one already marked Skipped route test Integration test cleanup strategy Positives
Summary: Two blocking items (lock file sync + dead messages). One design conversation worth having (delete-before-validate scope). Everything else is clean. Happy to approve once those are addressed. |
body-parser sets err.status = 413 on entity.too.large errors, but the error handler had no case for it β falling through to 500. Add explicit check before the catch-all so oversized bodies return 413 as expected. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
# Conflicts: # package.json
PR Review: fix(security): rate limit register endpoint; remove RESIDENT_TOKEN cookie
This is a well-scoped, well-motivated security hardening PR. The changes are correct and the PR description explains the rationale clearly. Three items need addressing before merge. Must Fix1. 2. Dead constants in
Remove them to avoid misleading future readers and to keep the constants file honest. 3. Stale Swagger JSDoc on the refresh route Worth Discussing (not blocking)
Skipped route test in Positives
Summary: Three action items before merge: sync the lock file, remove two dead message constants, update the stale Swagger JSDoc. Core security changes are correct and well-implemented. π€ Generated with Claude Code |
- Remove dead constants REFRESH_TOKEN_COUNTERPART_REQUIRED and TOKEN_USER_INVALID (no consumers remain after RESIDENT_TOKEN cross-check removal) - Remove stale residentToken and xsrfToken cookie entries from Swagger JSDoc - Add TODO comment to skipped logout test explaining wrong method and stale response - Expand refreshToken controller comment to make CUID2 enumeration trade-off explicit Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PR Review: fix(security): rate limit register endpoint; remove RESIDENT_TOKEN cookieOverall this is a solid, well-scoped security hardening PR. The changes are coherent, the intent is clearly documented, and test coverage is updated throughout. A few items worth addressing before merge: Bugs / Issues1. The type declaration is sandwiched between two import blocks. TypeScript hoists type declarations so this compiles, but it violates import ordering conventions and will likely confuse linters. The type should be placed after all imports. 2. Hardcoded string in Every other branch in this handler uses 3.
These should match. If the version was bumped in Security Notes (informational, not blocking)4. The comment explains the theft-detection rationale well. One trade-off worth being explicit about: a stolen (but expired or already-used) token can wipe all of the legitimate owner's sessions without the attacker gaining access. Depending on threat model this is fine, but worth a conscious sign-off that the UX cost (forced re-auth for all devices) is preferred over the alternative. 5. Previously logout used the Test Coverage6. Integration test transaction isolation ( The test wraps in 7. Skipped route test TODO The Positives
Summary: Two mechanical fixes needed (type placement, MESSAGES constant for the 413 handler), plus a lock file sync. Everything else is informational. Nice work on a clean, security-focused PR. |
Code Review -- PR 470: rate limit register; remove RESIDENT_TOKEN cookieOverall this is a solid set of security improvements. The RESIDENT_TOKEN removal and JWT algorithm pinning are both correct and meaningful fixes. A few items worth addressing before merge. Positives
Issues / Concerns1. refreshToken.ts -- session wipe fires before used-token check The comment explains this is intentional theft detection: a replayed token triggers a full session wipe. That is a reasonable design, but a legitimate client that suffers a network timeout during refresh (server processed it, client never got the response and retries) will also get all sessions wiped and be forced to re-authenticate. This is worth a follow-up issue so the product impact is tracked, even if the behaviour stays as-is. 2. errorHandler.ts -- type declaration placed between imports HttpError is declared between two import statements. Move it after all imports -- this is valid TypeScript but non-standard and can confuse tooling/linters. 3. errorHandler.ts -- unused type field on HttpError type?: string is declared on HttpError but never referenced in the handler body. Remove it or use it. 4. src/routes/auth/logout.test.ts -- lingering describe.skip The suite is now skipped and has a fresh TODO comment. Either fix it or delete the file -- skipped tests quietly rot and give false confidence in coverage. 5. tests/integration/security.test.ts -- 413 test missing body assertion The test only asserts status 413. Consider also asserting the response body shape (e.g. { message: 'Payload too large.' }) to verify the error handler returns the right payload, not just the right status code. 6. package.json / package-lock.json version mismatch package.json ends at 0.3.14 but package-lock.json ends at 0.3.8. These should match. Running npm install after bumping package.json will sync the lock file. Relationship with PR #471PR #471 (fix/remove-legacy-babel-v6) appears to share the same auth-file changes as this PR. Both target main from separate branches, so merging one will create conflicts in the other. Coordinate the merge order or rebase #471 on top of this branch once it is merged. Summary
Reviewed with Claude Code |
Summary
POST /users/registerwas only protected by the loose global rate limiter (100 req/10 min). Now usesrateLimitTenPerTenMins(10/10 min), matching login, magic-login, and reset-password.RESIDENT_TOKENcookie was exposing the user's internal DB ID on every auth response with no server-side benefit (the ID is already in the JWT). Removed from all auth flows.logoutandrefreshTokennow resolveuserIdfrom the DB token record.Review feedback addressed:
MISSING_USER_IDβREFRESH_TOKEN_REQUIREDerror message in logoutrefreshTokenon the intentional delete-before-validate ordering (token theft detection)getTokenreturns nullTest plan
npm run lint && npm testgreenPOST /users/register11 times from same IP in 10 min β verify 429 on 11thresidentTokencookieπ€ Generated with Claude Code