Skip to content

fix(users): bind stellar-address writes to the authenticated caller - #78

Open
rudra496 wants to merge 1 commit into
MergeFi:mainfrom
rudra496:fix/issue-39-stellar-address-ownership
Open

fix(users): bind stellar-address writes to the authenticated caller#78
rudra496 wants to merge 1 commit into
MergeFi:mainfrom
rudra496:fix/issue-39-stellar-address-ownership

Conversation

@rudra496

Copy link
Copy Markdown

Closes #39

What changed

PATCH /users/:id/stellar-address was authenticated but not authorized — the handler never injected @Req(), so nothing compared the caller's identity to the :id in the URL. Since BountiesService.markMergedAndRelease re-reads contributor.stellarAddress fresh at payout time, any logged-in user could point a victim's next merged bounty at their own wallet.

  • UsersController.setStellarAddress now passes req.user.userId (the identity JwtStrategy.validate attaches) through to the service.
  • UsersService.setStellarAddress takes the caller id and rejects cross-user writes with ForbiddenException before any repository write — the acceptance criterion's no-write-before-rejection guarantee. A caller carrying UserRole.MAINTAINER is the one explicit override, using the roles already persisted on the entity; no new role infrastructure.
  • Same-id writes skip the caller lookup entirely (one findOne, not two).

Tests

  • users.service.spec.ts (new): owner sets own ✓; contributor targeting another user → ForbiddenException, save never called, victim's stored address untouched ✓; maintainer override allowed ✓; same-id skips caller lookup ✓.
  • users.e2e-spec.ts: route matrix — unauthenticated rejected, own-id call passes caller through, cross-user forwards the mismatch and returns 403, maintainer path returns 200.
  • Full run: npx jest 122 passing with the same 7 escrow DB-integration failures present on main (verified by baseline run — they need a live database, unrelated to this change). Users suites: 4/4 unit, 6/6 e2e. tsc --noEmit + nest build + eslint clean on changed files.

Sibling-route audit (per the issue's third requirement)

The same "URL :id + mutation + no @Req()" shape exists on every mutating route in the codebase — bounties (fund/claim/refund), escrow (release/split-release/refund), maintenance-pool (deposit/assign-reward), milestones (fund/add-issue/resolve), teams (assign), reputation (recompute), github (sync). Most of those routes are also unauthenticated today (#38) and several trust client-supplied ids in the body (#40), so I've deliberately not touched them here — the fix for those needs the auth-guard rollout plus per-route ownership decisions, and doing it as one blanket change would re-create exactly this bug class. Once guards land, each of those routes should get the same caller-binding treatment this PR applies to the payout-address route.

PATCH /users/:id/stellar-address was authenticated but not authorized:
any valid JWT could overwrite any user's payout address, and the address
is re-read at escrow release time, so the next merged bounty pays the
attacker instead of the contributor.

The controller now passes req.user.userId through to the service, which
rejects cross-user writes with 403 before touching the repository —
unless the caller carries the maintainer role, which covers the explicit
support override. Unit tests prove the no-write guarantee and the
maintainer path; e2e tests cover the route matrix (own id, cross-user,
maintainer, no token).
Copilot AI lite review requested due to automatic review settings August 17, 2026 08:46
@vercel

vercel Bot commented Aug 17, 2026

Copy link
Copy Markdown

@rudra496 is attempting to deploy a commit to the chonilius' projects Team on Vercel.

A member of the Team first needs to authorize it.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.

UsersController.setStellarAddress is authenticated but not authorized: any logged-in user can overwrite another user's payout address

2 participants