Skip to content

Commit 0c73a42

Browse files
committed
fix: code rabbit can_access_poll_response comment
1 parent 8d7d9de commit 0c73a42

File tree

1 file changed

+65
-0
lines changed

1 file changed

+65
-0
lines changed

docs/developers/polls.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,71 @@ CREATE TABLE live_poll_responses (
111111
- Unique constraint ensures one response per user per poll
112112
- Auto-sets `submitted_at` timestamp via trigger
113113

114+
### Anonymous Response Behavior
115+
116+
**⚠️ CRITICAL: The UNIQUE constraint does not prevent duplicate anonymous responses.**
117+
118+
The constraint `UNIQUE (live_poll_id, public_profile_id)` enforces one response per authenticated user, but due to PostgreSQL's NULL handling, **anonymous users (where `public_profile_id` is NULL) can submit unlimited responses to the same poll**.
119+
120+
#### Current Behavior by User Type:
121+
122+
| User Type | Deduplication | Behavior |
123+
|-----------|--------------|----------|
124+
| **Authenticated** (`require_login = true`) | ✅ Enforced by UNIQUE constraint | One response per user per poll |
125+
| **Anonymous** (`require_login = false`) | ❌ Not enforced | Unlimited responses allowed |
126+
127+
#### Why This Happens:
128+
In PostgreSQL, NULL values in UNIQUE constraints are considered distinct from each other. Multiple rows with `(live_poll_id, NULL)` are all valid and don't violate the constraint.
129+
130+
#### Current Mitigations:
131+
- **None at application level**: No client-side tracking, rate limiting, or session-based restrictions
132+
- **No server-side deduplication**: Anonymous responses go directly to the database without checks
133+
134+
#### Design Decision:
135+
Anonymous responses are intentionally allowed (see RLS policy comments), but **unlimited anonymous responses appear to be an unintended consequence** rather than a deliberate feature.
136+
137+
#### Potential Solutions (if limiting is desired):
138+
139+
1. **Require Login for Sensitive Polls**
140+
- Set `require_login = true` for polls where single-response is critical
141+
- Trade-off: Eliminates anonymous participation
142+
143+
2. **Client-Side Session Tracking** (weak mitigation)
144+
```typescript
145+
// Store in sessionStorage after submission
146+
sessionStorage.setItem(`poll-${pollId}-submitted`, 'true');
147+
// Check before allowing submission
148+
if (sessionStorage.getItem(`poll-${pollId}-submitted`)) {
149+
// Show "already submitted" message
150+
}
151+
```
152+
- ⚠️ Easily bypassed (incognito mode, clearing storage)
153+
154+
3. **Add Anonymous Session Identifier** (requires schema change)
155+
```sql
156+
ALTER TABLE live_poll_responses
157+
ADD COLUMN anonymous_session_id UUID;
158+
159+
CREATE UNIQUE INDEX live_poll_responses_anonymous_unique
160+
ON live_poll_responses (live_poll_id, COALESCE(public_profile_id, anonymous_session_id));
161+
```
162+
- Generate session ID client-side and store in sessionStorage
163+
- More robust than option 2 but still bypassable
164+
165+
4. **Server-Side Rate Limiting** (requires additional infrastructure)
166+
- Track submissions by IP address or request fingerprinting
167+
- Implement time-based or count-based limits
168+
- Most robust but adds complexity
169+
170+
#### Recommendation:
171+
- **For public/informal polls**: Accept unlimited anonymous responses as a trade-off for accessibility
172+
- **For critical polls** (grades, official votes): Use `require_login = true`
173+
- Document this behavior clearly in the UI when creating polls
174+
175+
#### Related Files:
176+
- Response submission: `app/poll/[course_id]/page.tsx` (lines 122-148)
177+
- RLS policies: `supabase/migrations/20251204070101_live-polls.sql` (lines 324-357)
178+
114179
### Database Triggers
115180

116181
| Trigger | Table | Purpose |

0 commit comments

Comments
 (0)