[#79] Rating API with signature verification + token gate#84
[#79] Rating API with signature verification + token gate#84realproject7 merged 2 commits intomainfrom
Conversation
- POST /api/ratings: recovers address via recoverMessageAddress(), checks balanceOf() on storyline token, upserts via service role - GET /api/ratings?storylineId=N: returns ratings array + average - Rejects if rater holds 0 tokens (403) Fixes #79 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
project7-interns
left a comment
There was a problem hiding this comment.
Verdict: REQUEST CHANGES
Summary
The API shape matches most of issue #79, but the POST endpoint currently treats any valid wallet signature as authorization for any submitted rating payload. That leaves the token gate tied only to wallet ownership, not to the specific rating request being written.
Findings
- [high]
POST /api/ratingsrecovers an address from the suppliedmessageandsignature, then trusts the separately submittedstorylineId,rating, andcommentfields without verifying that those values were part of the signed message. An attacker can replay any previously captured signature from a token holder over an unrelated message and submit arbitrary ratings/comments for that wallet as long as the holder still owns tokens. This is a real auth bypass, not just a missing nonce. The server needs to verify a canonical message that includes the rating payload being authorized.- File:
src/app/api/ratings/route.ts:67 - Suggestion: define a canonical signed message format that embeds at least
storylineId,rating, and optionallycomment(plus a nonce/timestamp), then either validate that exact message string server-side before callingrecoverMessageAddress, or switch toverifyMessage()against the expected signer and expected message.
- File:
Decision
Requesting changes because the current signature flow does not bind the signature to the rating being persisted, so a valid signature can authorize unintended writes.
- Signed message must match "Rate storyline {id} with rating {n}" to
prevent replay attacks across storylines/ratings
- Added Number.isInteger() check for rating value
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
project7-interns
left a comment
There was a problem hiding this comment.
Verdict: APPROVE
Summary
The follow-up binds the signed message to the submitted rating action and adds the missing integer validation. The API now matches the security intent of issue #79.
Findings
- No blocking findings.
Decision
Approving because the POST flow now reconstructs the expected message server-side, enforces exact equality before signature recovery, and preserves the token-gate/write-path requirements.
Summary
recoverMessageAddress()(viem), looks up storyline token, checksbalanceOf()on-chain (rejects with 403 if 0 balance), upserts rating via service role client(storyline_id, rater_address)— one rating per user per storylineTest plan
npm run lintpassesnpm run typecheckpassesFixes #79
🤖 Generated with Claude Code