fix: require auth + identity binding on POST /profiles - #254
Merged
meshackyaro merged 2 commits intoAug 30, 2026
Merged
Conversation
UserProfileController.create() had no @UseGuards(JwtAuthGuard) at all — unlike update/delete/rate/verify on the same controller — and never checked that the caller actually controlled the walletAddress in the request body. Anyone could POST /profiles with someone else's real Stellar wallet address, name, and skills, squatting on that identity before the real owner ever registered; since create() throws ConflictException on a duplicate walletAddress, the real owner would then be locked out of creating their own profile. Fix: - Add @UseGuards(JwtAuthGuard) + @ApiBearerAuth() to POST /profiles - Compare the parsed dto.walletAddress against req.user.address (from the verified JWT via JwtStrategy.validate()) and throw ForbiddenException on a mismatch - Update the Swagger @apioperation description and add 401/403 @apiresponse entries Tests (backend/src/user-profile/user-profile.e2e.spec.ts, new — runs the real JwtAuthGuard/JwtStrategy pipeline via supertest against a live Nest app, not a mocked guard): - unauthenticated POST /profiles -> 401 - authenticated but walletAddress != req.user.address -> 403 - authenticated with matching walletAddress -> 201 - regression: an attacker can no longer squat on an address before its real owner registers (403 for the attacker, 201 for the real owner) Note: named user-profile.e2e.spec.ts (dot, not the existing auth.e2e-spec.ts dash convention) because jest's testRegex here is .*\.spec\.ts$, which does NOT match *.e2e-spec.ts — auth.e2e-spec.ts is consequently never actually run by `npm test`. Left that pre-existing file as-is since fixing it is outside this issue's scope, but named the new file so it actually executes. This PR only resolves trustflow-protocol#205. trustflow-protocol#203 (webhook event filtering), trustflow-protocol#206 (rateUser identity spoofing / duplicate ratings), and trustflow-protocol#207 (verifyUser admin enforcement) are real, separate issues from the same batch — not addressed here, left open for follow-up. Closes trustflow-protocol#205
|
@codemagician1949 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! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
UserProfileController.create()(POST /profiles) had no@UseGuards(JwtAuthGuard)at all — unlikeupdate/delete/rate/verifyon the same controller — and never verified that the caller actually controlled thewalletAddressin the request body. Anyone couldPOST /profileswith someone else's real Stellar wallet address, name, and skills, squatting on that identity before its real owner ever registered. Sincecreate()throwsConflictExceptionon a duplicatewalletAddress, the real owner would then be locked out of creating their own profile.Fix
POST /profilesnow requires@UseGuards(JwtAuthGuard)+@ApiBearerAuth()dto.walletAddressis compared againstreq.user.address(from the verified JWT, viaJwtStrategy.validate()) — a mismatch throwsForbiddenException@ApiResponseentriesTests
New
backend/src/user-profile/user-profile.e2e.spec.ts— runs the realJwtAuthGuard/JwtStrategypipeline via supertest against a live Nest app (not a mocked guard):POST /profiles→ 401walletAddressdoesn't match the token → 403walletAddress→ 201All 4 pass locally (
npx jest user-profile.e2e.spec.ts), plus the existing 25user-profile.service.spec.tstests still pass.eslintclean.One side note for reviewers: I named the new spec
user-profile.e2e.spec.ts(dot beforespec) rather than matching the existingauth.e2e-spec.tsdash convention, because this repo's jesttestRegex(.*\.spec\.ts$) doesn't actually match*.e2e-spec.ts— soauth.e2e-spec.tscurrently never runs undernpm test. Didn't fix that pre-existing file since it's outside this issue's scope, but wanted the new test to actually execute rather than silently repeat the same gap.Batch context
This PR resolves #205 only. #203 (webhook event filtering), #206 (
rateUseridentity spoofing / duplicate ratings), and #207 (verifyUseradmin enforcement) are real, separate issues from the same batch — genuinely not addressed in this PR, left open for follow-up. Relevant to #203, #206, #207.Closes #205
Closes #203