feat(elections): token-gated voting route with one-time-use and ballot secrecy

Open
#1,942 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
35/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Quiet

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from USRSE/usrse.github.io

All issues in USRSE/usrse.github.io

Similar issues

More Backend & API Design issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.