fix: hash refresh tokens at rest in the database#113
Conversation
There was a problem hiding this comment.
Pull request overview
Updates the auth refresh-token flow so refresh tokens are never stored in plaintext, reducing blast radius if the refresh_tokens table is compromised.
Changes:
- Store refresh tokens as
sha256(raw_token)inrefresh_tokens.tokenwhile returning only the raw token to the caller at issuance time. - Hash incoming refresh tokens before DB lookups in
refreshAccessToken()andrevokeRefreshToken(). - Introduce a local
hashToken()helper to centralize the hashing logic.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Store SHA-256(token) in the database instead of the raw token value. The raw token is returned to the caller only; the hash is used for all DB lookups and updates. Existing plaintext tokens become invalid on deploy — users must re-login (acceptable per spec). - Add private hashToken() helper using crypto.sha256 - generateRefreshToken: saves hash, returns raw value - refreshAccessToken: hashes incoming token before lookup - revokeRefreshToken: hashes incoming token before update Closes #96
45d05d7 to
2a4659d
Compare
Covers three contracts: - generateRefreshToken() returns the raw token and persists only its hash - refreshAccessToken() queries by hash, rejects tokens with no hash match - revokeRefreshToken() passes the hash (not the raw token) to the update call
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
setDate(+7) adds calendar days, not a fixed millisecond count, so comparing against 7 * 24h would be flaky across DST transitions. Mirror the implementation by computing expected via setDate(+7).
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The suite now covers password reset, change password, and refresh token hashing — scoping it to "Password Reset" was misleading.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
RefreshToken.id is @PrimaryGeneratedColumn('uuid') — a string, not a
number. Using id: 1 in the mock fixture misrepresents the entity shape
and could mask bugs in code paths that call update(storedToken.id, ...).
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
Why SHA-256 (not bcrypt)
Input has 256 bits of entropy so rainbow tables are infeasible. bcrypt is intentionally slow — this is a hot lookup path and the entropy argument makes the slowness unnecessary.
Test plan
Closes #96