Skip to content

fix(auth,users): harden OAuth callback and user sync (4 issues) - #224

Open
Darkvader-ship-it wants to merge 1 commit into
MergeFi:mainfrom
Darkvader-ship-it:fix/auth-hardening-jwt-handoff-user-sync-takeover
Open

fix(auth,users): harden OAuth callback and user sync (4 issues)#224
Darkvader-ship-it wants to merge 1 commit into
MergeFi:mainfrom
Darkvader-ship-it:fix/auth-hardening-jwt-handoff-user-sync-takeover

Conversation

@Darkvader-ship-it

Copy link
Copy Markdown

Branch: fix/auth-hardening-jwt-handoff-user-sync-takeover

Commit: 62762c1
Status: Created, committed, and pushed to origin.


Fix #1 – JWT in redirect URL

(src/auth/auth.controller.ts:36, src/auth/auth.service.ts:1)

  • AuthService.createHandoffCode() generates a 32-byte hex code with a 5‑minute TTL, single-use via consumeHandoffCode() (prunes expired).
  • githubCallback now:
    • calls createHandoffCode(accessToken),
    • sets an httpOnly access_token cookie (secure in production, sameSite:lax, maxAge 7d, path:/),
    • redirects to ${frontendUrl}/auth/callback?code=${code} (no token= in the URL).
  • Added POST /auth/handoff and alias POST /auth/exchange
    (src/auth/auth.controller.ts:59) — exchanges the code for { accessToken }, throwing UnauthorizedException if missing/expired.

Fix #2 – JWT validate bypass

(src/auth/strategies/jwt.strategy.ts:1)

  • Injected UsersService, added cookieExtractor (checks req.cookies or manual cookie header) and uses fromExtractors([Bearer, cookieExtractor]).
  • validate() is now async and checks payload.sub, then awaits usersService.findOneRaw(payload.sub) — throws UnauthorizedException('User no longer exists') if deleted, preventing token use after account deletion.

Fix #3 – Staleness

(src/users/users.service.ts:53)

  • On an existing GithubAccount path, updates:
    • account.login,
    • synchronises User.username, displayName, avatarUrl, email from fresh input,
    • respects username/email uniqueness before userRepo.save(),
    • then returns findOneRaw().

Fix #4 – Username takeover

(src/users/users.service.ts:105)

  • Removed the findOne({ where: { username: input.login } }) fallback.
  • On a new githubId, always creates a fresh User.
  • If a username collision occurs, generates a unique one via generateUniqueUsername() (username, username-1, …); if an email collision occurs, sets email to null.

Spec updates

(src/users/users.service.spec.ts:93)

  • Replaced the username-link test with a takeover-prevention test.
  • Added a staleness sync test.

All 13 tests pass (default.bash:1).


Extra

  • Fixed duplicate JSDoc in src/escrow/escrow.service.ts:582 that broke tsc parsing.

Verified

  • token= absent from redirect URL
  • code= present
  • npx jest src/users/users.service.spec.ts13 passed
  • git push succeeded

Let me know if you need this as a commit message, PR description, or converted to a different format (JSON, plain text, etc.).

- auth: replace JWT-in-URL with single-use handoff code + httpOnly cookie
  * AuthService.createHandoffCode/consumeHandoffCode stores 5m TTL opaque code
  * githubCallback now sets httpOnly access_token cookie and redirects with ?code=
  * POST /auth/handoff and /auth/exchange exchange code for JWT (single-use)

- auth: jwt strategy validates user existence on every request
  * JwtStrategy now injects UsersService and calls findOneRaw(sub)
  * throws UnauthorizedException when user row no longer exists
  * supports Bearer header and httpOnly cookie extraction

- users: sync User row on re-login to prevent staleness
  * upsertFromGithub now updates username/displayName/avatarUrl/email from
    fresh GitHub profile when GithubAccount already exists
  * respects username/email uniqueness

- users: prevent username-recycling takeover
  * upsertFromGithub never reuses a User by username when githubId is new
  * always creates a fresh User; collision on username generates a unique
    variant via generateUniqueUsername

- escrow: fix duplicate JSDoc that broke tsc parsing
@drips-wave

drips-wave Bot commented Aug 27, 2026

Copy link
Copy Markdown

@Darkvader-ship-it Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@vercel

vercel Bot commented Aug 27, 2026

Copy link
Copy Markdown

@Darkvader-ship-it is attempting to deploy a commit to the chonilius' projects Team on Vercel.

A member of the Team first needs to authorize it.

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