[#268] Writer dashboard genre prompt#283
Conversation
- PATCH /api/storyline/[id]/metadata endpoint validates writer ownership and updates genre/language - GenrePrompt inline component on writer dashboard for NULL-genre stories - StoryCard shows "Uncategorized" fallback when genre is null Fixes #268 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
project7-interns
left a comment
There was a problem hiding this comment.
T2b Review — REQUEST CHANGES
BLOCKING: route.ts — No real ownership verification
The PATCH endpoint accepts address from the request body and compares it against the writer_address in the DB. But there is no proof the caller actually owns that address — no wallet signature, no session auth. Anyone who knows a writer's address and storyline ID can spoof the request and change their genre/language.
This is the equivalent of an auth endpoint that takes { userId: 123 } and trusts it.
Fix options (pick one):
- Require a signed message (
personal_signor EIP-712) and recover the signer address server-side - If there's existing session/auth middleware in this app that ties the request to a verified wallet, use that instead of the body
addressfield
If this codebase already has other endpoints that use the same address-from-body pattern without signature verification, this is a pre-existing issue — note it but don't block on it here. But if other endpoints verify ownership differently, this one should too.
Non-blocking
StoryCard.tsx: "Uncategorized" badge is now always rendered even for categorized stories (the conditional was removed). This means every story card has a genre badge — fine UX-wise, just confirming that's intentional.GenrePrompt: Clean implementation, proper query invalidation.
project7-interns
left a comment
There was a problem hiding this comment.
Verdict: REQUEST CHANGES
Summary
The UI changes are scoped correctly, but the new metadata endpoint does not authenticate the caller. As written, anyone can update a storyline by spoofing the writer address in the request body.
Findings
- [high]
PATCH /api/storyline/[storylineId]/metadataauthorizes based only on a user-suppliedaddressfield, which is trivially forgeable.- File:
src/app/api/storyline/[storylineId]/metadata/route.ts:33 - Suggestion: Do not trust
addressfrom the JSON body as proof of wallet ownership. The current check only verifiesstoryline.writer_address === body.address, so any client can send the writer's public address and overwrite genre/language for that storyline. This needs a real authenticated identity source the server can verify, or the write must stay client-mediated through an authenticated backend path.
- File:
Decision
Requesting changes because this is an authorization bypass on a write endpoint.
Server verifies a signed message binding storylineId + genre + language before allowing updates. Same pattern as /api/ratings. Client signs via useSignMessage before sending the PATCH request. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
project7-interns
left a comment
There was a problem hiding this comment.
T2b Re-review — APPROVED
Auth fix is correct:
- Server constructs expected message (
Update storyline {id} metadata genre:{g} language:{l}) and compares against signed message — prevents replay and cross-action attacks publicClient.verifyMessagesupports both EOA and EIP-1271 contract wallets- Follows existing
/api/ratingssignature pattern - Client uses
useSignMessagehook, signs before fetch
Rest of the PR (GenrePrompt, StoryCard fallback) is clean. Ship it.
project7-interns
left a comment
There was a problem hiding this comment.
Verdict: APPROVE
Summary
The metadata update endpoint now requires a signed message that is verified server-side and matched to the storyline writer before updating Supabase. The dashboard prompt and uncategorized fallback are aligned with issue #268.
Findings
- No blocking findings from this review pass.
Decision
Approving from my side. The authorization bypass is addressed; GitHub checks were still pending at the time of review.
Summary
PATCH /api/storyline/[storylineId]/metadataendpoint: validates caller is the storyline writer, accepts{ genre?, language?, address }, updates SupabaseGenrePromptinline component on writer dashboard: appears for storylines withgenre=NULL, uses same genre/language lists from [feat] Add genre and language metadata to storylines with discovery filters #265, disappears after saveStoryCardshows "Uncategorized" (muted, italic) when genre is null instead of hiding the badgeTest plan
npm run typecheckpassesnpm run lintpasses (no new warnings)Fixes #268
🤖 Generated with Claude Code