See also: API documentation
BDPA Elections, Inc. has been contracted to once again build yet another secure electronic election system for democracy. The system will allow certain entities to manage IRV- and CPL-based elections at scale without requiring voters to appear at a physical voting location.
This is the solemn duty for which your dev team was assembled.
Summary of requirements (15 total)
The app supports four authenticated user types: voters, moderators, administrators, and reporters. Users can only be one type. Voters are the most common type of user. They vote in elections and can view a complete listing of past election results. Moderators manage elections to determine which voters are eligible to vote in which elections. Administrators can create and manage other users, create and manage elections, and determine which voters/moderators can access which elections. Reporters can view the history of past election results and nothing else.
Note that time-based data in the API is represented as the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC. See the API documentation for details. Further note that you must use the API to complete this problem statement, including interacting with data from other chapters, though you may consider a hybrid approach where you have your own database storing non-API data.
We're looking for feedback! If you have any opinions or ideas, contact us on Slack.
🚧 🚧 To avoid disqualification, please take note of the following:
Expand
-
Any evidence of illegal remote access to AWS WorkSpaces or any other AWS resource will result in a steep penalty or immediate disqualification.
-
Unlike PS2, PS1 is a "chapter-wide problem statement". That is: all students, coaches, and coordinators in the chapter can teach to, talk about, and collaborate on a solution. And then, when the conference comes around, your chapter sends your best five students to finish the job.
-
Your solution’s source code must be located at
%USERPROFILE%\Desktop\source
on your team's AWS WorkSpace. You can also have other files located elsewhere so long as they are reachable (e.g. junctioned/soft-linked) from%USERPROFILE%\Desktop\source
. This is the only location judges are required to access when scoring your source code. -
Judges must not have to type in anything other than
http://127.0.0.1:3000
into the browser to reach your app.
Additionally, observe the following guidelines so your solution doesn't
malfunction in front of everyone after being deployed to
https://XYZ.submissions.hscc.bdpa.org
:
Expand
-
Your solution’s landing page must be available at
http://127.0.0.1:3000
(localhost) on your team's AWS WorkSpace. -
Prefer relative URIs where possible instead of hardcoding your app to use something like
127.0.0.1:3000
. -
Avoid hardcoding the protocol (e.g. use
//localhost:3000/some-file.png
instead ofhttp://localhost:3000/some-file.png
). -
Avoid loading resources in the browser using ports other than 3000 (e.g. running two separate web-visible servers, one on 3000 and one on 80).
-
If using websockets, stick to port 3000 and ensure your web server can handle Upgrade requests for your websocket-specific routes (e.g.
localhost:3000/ws
). -
Consider deploying your solution in production mode instead of development mode, which is slower and results in a degraded UX (e.g. showing errors that would normally be hidden in production, costing you points).
Your app will support 4 types of users: voter, moderator, administrator, and reporter.
Users can only have a single type at a time. You are free to create other types.
Additionally, all users must be authenticated before being allowed to interact with the system. "Guest" users are not allowed except to register a new account.
- Are authenticated users (i.e. users that have already logged in)
- Can access the full Election view and can vote in elections to which they have been assigned
- Can access their own personalized Dashboard view
- Can access the History view
- Are authenticated users (i.e. users that have already logged in)
- Can access their own personalized Dashboard view
- Can assign
voters
to elections they moderate - Can assign
reporters
to elections they moderate - Can access the History view
- Are authenticated users (i.e. users that have already logged in)
- Can access a limited version of the Election view but cannot vote
- Can access their own personalized Dashboard view
- Can assign
voters
to elections - Can assign
reporters
to elections - Can access the History view
- Can access any other relevant view
Note the special super user. The super user is a special administrator
that can create other administrator
users. The super user must always exist,
even when your solution is reset to its initial state. There can only exist one
such user.
- Are authenticated users (i.e. users that have already logged in)
- Can view a paginated listing of all past (closed) elections in the system to which they have been assigned
Your app will support elections.
Each election in the system has at least the following components:
- A globally unique identifier automatically generated by the API
- A title
- A description
- The voting method used
- A unix epoch timestamp indicating when the election was created in the system
- A unix epoch timestamp indicating when the election opens
- A unix epoch timestamp indicating when the election closes
- A boolean representing if the election was deleted or not
- An array of options voters must rank from most favored to least favored
// Example ['jelly', 'butter', 'peanut butter'];
- An array of objects that maps voters to their rankings
// Example // See also: https://hscc35a947d8.docs.apiary.io/#/data-structures/0/ballot [ { voter_id: 'someThey425', ranking: { 'peanut butter': 1, jelly: 2, butter: 3 } }, { voter_id: 'someThem3312', ranking: { butter: 1, jelly: 2, 'peanut butter': 3 } } ];
Warning: all of the above information must be stored using the API. You can cache it locally, but it must also be put into the API. Only accessing your local database without using the API will disqualify your solution.
Feel free to track any other information you deem necessary, but keep in mind extra information cannot be stored using the API.
Elections that are added to the API by your team are considered "owned" by your team. Necessarily, other elections in the system are considered "unowned" by your team. All unowned elections are considered read-only at the API level. Since your users cannot otherwise interact with these elections, they only need to be shown in the History view and anywhere else explicitly noted.
Once an election is closed, its results become immutable, which means: none of
the information about that election can be modified by administrators
and the
election itself cannot be deleted by administrators
.
The super user is exempt from these restrictions; they can delete closed elections or modify some parts of them; specifically: title, description, opening time, and closure timestamp. Updating the closure timestamp to a time in the future will effectively re-open a closed election, making it no different than any other open election.
Therefore, unless re-opened, the outcomes of closed elections can never be modified. This also implies that if a user voted in an election before their account was deleted or unassigned, their ballot must still count and the outcome of the election cannot change.
When using the API to update owned elections based on the actions of moderators and administrators, you must decide what edge cases like changing an election's available options, opening time, and closing time means after an election has already opened, already closed, and/or has already accepted votes.
Election view: view and participate in an election.
This view allows a voter
to access and participate in an
election. A voter
cannot use this view to access an election
they have not been assigned to. This view can also be used by administrators
to view the current state of an election, though they are not able to cast
votes.
For elections that have not yet opened, users should see when the election will open.
Similar to the History view, when viewing election results that are closed:
- The winning option will be emphasized in the frontend UI
- All eliminated options will be clearly marked
- The UI will indicate by how many votes the winning option won versus the total number of votes cast
- If the current user voted in said election, their first choice in the election will be most prominently marked
Warning: whenever you're displaying the results of an election, you must protect the identities of your voters! Do not publicly reveal who voted for which options in the UI.
Additionally, voters
have five minutes, or until the election closes
(whichever is sooner), to change/remove their vote after submitting it. After
five minutes, or once the election closes, voters
can no longer change their
minds.
Dashboard view: view and interact with a personalized user dashboard.
This is the "home page" of your service for users that are authenticated, and is the view to which newly authenticated users are redirected. Each user has their own individualized dashboard.
When accessed by any user, this view will show:
- The name of the user, like "Ray" or "Ray Tiles"
- The IP address of the client recorded the previous five time this user logged
in
- That is: users will be able to see the last five times their account was logged into and from which IP
- The timestamp recorded the previous time this user logged in
You are free to display any other relevant information.
Users can use their dashboard to edit their own personal information, including
their name, email, password, and other metadata without going through an
administrator
.
The very first time a newly-created user logs in, whereupon they are redirected
to this view, said user will be forced to change their password before being
allowed to interact with the system. This only applies to accounts created by
an administrator
and not accounts registered by the user themselves via the
Auth view.
Note that deleted elections should never be displayed to users that are not
administrators
. In such cases, deleted elections should be treated as if they don't exist at all.
Additionally:
When accessed by a voter
, this view will show:
- Open elections the user can currently participate in
- Closed elections they participated in
- Upcoming elections they will eventually be eligible to participate in
When displaying non-closed elections, they will be sorted in ascending order by their opening time (i.e. elections that opened/will open earlier in time are shown first). When displaying past elections, they will be sorted in descending order by their closing time (i.e. elections that have closed later in time are shown first).
When accessed by a moderator
, this view will:
- Enable adding/removing one or more
voters
to/from elections they've been assigned to overseeadministrators
assignmoderators
to oversee elections
- Enable adding/removing one or more
reporters
to/from elections they've been assigned to oversee
When accessed by an administrator
, this view will:
- Enable adding/removing one or more
voters
to/from any election - Enable assigning/removing one or more
moderators
to/from any election - Enable assigning/removing one or more
reporters
to/from any election - Allow creating new
voters
,moderators
, andreporters
- If the administrator is also the
super user, they can create new
administrators
as well
- If the administrator is also the
super user, they can create new
- Allow viewing existing users and updating/deleting non-
administrator
usersadministrators
can update the personal data of any other useradministrators
can view deleted users as well as all others- If the administrator is also the
super user, they can delete other
administrators
but cannot delete themselves
- Allow viewing, creating, updating, and deleting elections in the system
- Elections that are not owned by your team will always be
read-only at the API level. It should be clearly communicated via the UI
that unowned elections cannot be updated/deleted by the
administrator
, only viewed - Elections become immutable once they close, after which they can no longer be updated unless they are re-opened
- The administrator must choose among the available voting methods. The
selected method will be used to determine the winner of the election.
- Once an election has been created, the selected voting method cannot be changed.
- Elections that are not owned by your team will always be
read-only at the API level. It should be clearly communicated via the UI
that unowned elections cannot be updated/deleted by the
When accessed by a reporter
, this view will show:
- The most recent closed elections in the system that the
reporter
has been assigned to (if they are owned by your team).reporters
can see all elections in the system that are not owned by your team regardless of assignment- It must be clear which elections are owned by your team and which are not.
- A link to the History view in case the user wants to see more elections.
History view: view the results of all past (closed) elections.
This view makes available a paginated listing of all past (closed)
election results in the entire system that the current user is
allowed to view. Past elections that are not
owned by your team can always be viewed by anyone. However,
deleted elections can never be viewed and should not show up in the listing
unless the user accessing this view is an administrator
.
Similar to the Election view, when viewing election results:
- The winning option will be emphasized in the frontend UI
- All eliminated options will be clearly marked
- The UI will indicate by how many votes the winning option won versus the total number of votes cast
- If the current user voted in said election, their choice in the election will be most prominently marked
When displaying past elections, they will be sorted in descending order by their closing time (i.e. elections that have closed later in time are shown first) initially. Users can choose to sort results by at least the following: title, creation time, opening time, closing time.
To satisfy this requirement, consider a proper caching strategy to reduce load on the API and improve your app's performance.
Auth view: authenticate existing users and register new users.
Users can use this view to authenticate (login) using their username and their password. This sensitive information is referred to as a user's credentials.
No part of the system other than this view will be accessible to unauthenticated users.
Your app must bring its own authentication system that allows users to login and logout at will. Users that are not logged in cannot interact with the system outside of this view.
Authenticated users can choose to logout, after which your app will treat them like any other unauthenticated client. Logging a user out does not require a call to the API.
There is an open registration feature where users can sign up for accounts
themselves. However, newly registered accounts that were not created by an
administrator
will not be allowed to access the system until they are approved
by an administrator
.
administrators
can also create new users accounts manually. When an account is created in this way, upon initially logging in, the newly created user must choose a new password. Such an account, having been created by anadministrator
explicitly, is automatically "approved" upon creation.
When a new user is created, they must provide at least the following:
- Desired username <required>
- Must be alphanumeric (dashes and underscores are also allowed).
- Email address <required>
- Password <required>
- Password strength must be indicated as well. Weak passwords will be rejected. A weak password is ≤10 characters. A moderate password is 11-17 characters. A strong password is above 17 characters.
- The answer to a simple CAPTCHA challenge of some type <required>
- Example:
what is 2+2=?
- Teams must not call out to any API for this. Teams must create the CAPTCHA manually.
- Example:
- City
- State
- Zip
- Address
Your app must store all user information locally.
- Usernames and email addresses must be unique within your app. That is: no two users can have the same username or email address.
- Users will be prevented from logging in for 1 hour after 3 failed login attempts. Users will always see how many attempts they have left.
- Users will have the option to use remember me functionality to maintain long-running authentication state. That is: if a user logs in and wants to be "remembered," they will not be logged out until they manually log out.
If a user does not remember their password, they can use email to recover their account.
If a user has forgotten their login credentials, they will be able to recover their account by clicking a link in the recovery email sent to their address. Your app will then allow them to set a new password.
The app must not actually send emails out onto the internet. The sending of emails can be simulated however you want, including outputting the would-be email to the console. The app will not use an external API or service for this. For full points, ensure you document how recovery emails are simulated when submitting your solution.
A navigation element containing the BDPA logo, your app title, and a subset of user data is permanently visible to users.
In every view, a navigation element is permanently visible containing the BDPA logo (downloadable here) and the title of your app.
Additionally, the following must always be visible within the navigation element:
- The total number of elections in the system
- The total number of open elections in the system
- The total number of closed elections in the system
When voting in an election, a voter
must rank all options in order of
preference. When an election closes, the winner is determined via either
Instant-Runoff Voting (IRV) or the Copeland method (CPL).
There are a variety of voting algorithms available, each with their pros and cons. When creating elections, administrators choose the algorithm used to determine the winner: either Instant-Runoff Voting (IRV) or the Copeland method (CPL).
When an eligible voter votes in an IRV election, they do not just cast a single vote. They must rank each option from most favored to least favored ranked 1 to N, respectively. After the election closes, the system will calculate the winner by the rules of the IRV algorithm described here.
There is also a YouTube video explaining Instant-Runoff Voting.
When an eligible voter votes in a CPL election, they do not just cast a single vote. Similar to IRV, they must rank each option from most favored to least favored ranked 1 to N, respectively. After the election closes, the system will calculate the winner by the rules of the CPL algorithm described here.
There is also a YouTube video explaining the Copeland method.
Users view election updates in real time; newly cast votes, open/close status, calculated winner, et cetera will appear without a page refresh.
This requirement only applies to elections that are owned by your team.
The Election view must present the most up-to-date election state to users able to view said election. New votes, status updates such as when the election closes and a winner is selected, and any other relevant data must eventually appear within this view without the page refreshing or the user doing anything extra (like pressing a refresh button).
This type of automatic updating/revalidating of data is called asynchronous or "ajaxian" since it occurs outside the usual synchronous event flow. There are many solutions, including interval revalidation, focus revalidation, and visibility-based revalidation (i.e. updating data only for elements that are currently visible). Another solution is to use frontend timers to regularly check a source for new data every now and then.
The app will be performant.
The amount of time the application takes to load and sort data, display information, and act on input is progressively penalized. That is: the teams with the fastest load times will earn the most points and the teams with the slowest load times will earn zero points from this requirement.
Average (median) is used to calculate load times. Measurements will include initial page load times and, depending on the other requirements, various other frontend UI response times.
Tail latencies (e.g. on startup with a cold cache) are ignored. Only averages are considered.
FOUC may also be penalized.
To maximize performance, consider caching (see also) the result of data processing operations and using range queries to retrieve only the data your app hasn't yet processed.
Results and lists of items displayed in the frontend UI will be paginated where appropriate.
Pagination is the strategy of showing a limited number of a large set of results and providing a navigation element where users can switch to different "pages" of that large set. A Google search result (which has multiple pages) is a good example of pagination. Infinite scroll, a specific pagination implementation used by the likes of Facebook/Instagram and Twitter, is another good example.
Security: no XSS, SQLI, insecure database, or other trivial security vulnerabilities.
The app will use modern software engineering practices that protect from common XSS, SQL injection, and other security vulnerabilities. Specifically: form inputs and the like will not be vulnerable to SQL injection attacks. User-generated outputs will not be vulnerable to XSS or similar attacks.
As for database security, any passwords present in the database must be hashed (not encrypted). We recommend using a salted SHA-256 hash construction or something similar. You don't need to do anything fancy. There are many tutorials for how to safely store passwords and other credentials in a database.
Passwords stored in your database in cleartext, hashed incorrectly, or re-encoded (e.g. with base64) will earn your team zero points for this requirement.
The app will fail gracefully when exceptional conditions are encountered.
This includes handling API errors during fetch, login errors, random exceptions, showing spinners when content needs to load, etc.
Every so often, the API will respond with an
HTTP 555
error instead of fulfilling a request. Further, API requests and responses will be manipulated by the judges in an attempt to break the app. If at any time a user is presented with a non-app error page or a completely blank screen for more than a second or so, your solution may lose points on this requirement.
The frontend UI will be responsive to mobile, tablet, and desktop viewports.
The app will be pleasant to the eye when viewed on a smartphone, tablet, and a desktop viewport. The design and functionality will not "break" across these viewports nor will the app become non-functional.
Judges will view and interact with the app through emulated phone and tablet viewports. If the app breaks when viewed, it will lose points on this and other requirements. We recommend using mobile-first software design principles.