feat(elections): token-gated voting route with one-time-use and ballot secrecy
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 35/100
Research direction
Start at the /vote/:token route and the Convex mutation described in the implementation notes, after reviewing the dependent schema issue. Trace token validation, election-window checks, redemption, ballot insertion, and audit recording as one operation. Done means the listed rejection, atomicity, secrecy, rate-limiting, and automated-test requirements are all demonstrated.
Written by the indexing model from the issue text.
Description
Summary
Implement the backend guarantees around ballot submission: tokens can only be used once, ballots are not linked to voter identities, and the election must be `open` for a submission to succeed.
Part of #1933.
Requirements
- `/vote/:token` loads the ballot only if the token hash matches a non-redeemed `voterTokens` row and election status is `open`
- Submission mutation is atomic: in a single transaction, mark token as redeemed and insert ballot rows — without storing the voterId on the ballot
- If submission fails partway, neither the ballot nor the redemption is persisted
- Double-submit returns a friendly "already voted" state, not a stack trace
- Reject submissions when election is `draft`, `closed`, or `published`
- Reject submissions outside the `opensAt`/`closesAt` window even if status has drifted
- Rate-limit token validation attempts per IP to mitigate token-guessing
- Write an audit entry on each redemption (voterId + timestamp, NOT ballot contents)
- Add an automated test proving no database query can join `voters` ↔ `ballots` via any foreign key or index
Context
- Epic: #1933
- Depends on schema issue, ballot UI issue
Non-negotiable
Ballot secrecy is the core security property of this app. A reviewer should be able to read this code and the schema and convince themselves ballots cannot be mapped back to voters.
Implementation notes
- Token validation: lookup by `tokenHash = sha256(salt + plaintextToken)`, then check `usedAt IS NULL` and election window
- Redemption and ballot insert must share a Convex mutation so they commit atomically
- Consider adding a short random delay on submission to obscure timing correlation between redemption and ballot insert (defense in depth against a compromised admin observing write order)
- Dominant language
- HTML
- Stars
- 33
- Forks
- 104
- Avg merge
- 12h 3m
- Merged PRs (30d)
- 10
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from USRSE/usrse.github.io
-
Difficulty 1/5 Under an hour Newbie friendliness 85/100
USRSE/usrse.github.io#2001 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
USRSE/usrse.github.io#2000 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
USRSE/usrse.github.io#1991 · 1 comment ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
USRSE/usrse.github.io#1948 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
USRSE/usrse.github.io#1947 · 1 comment ·
All issues in USRSE/usrse.github.io
Similar issues
-
area/sessions comp/agent P2 tool/skills type/perf
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
NousResearch/hermes-agent#117788 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
code-quality refactoring
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
github/gh-aw-firewall#8816 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
getgrav/grav-plugin-api#45 ·