Releases: fsek/rustsystem
v2.0.4-beta
Release Notes — v2.0.4-beta
Pre-release date: 2026-04-10
Built on top of v2.0.3-beta.
Status: pre-release — requires testing before promotion to full release.
Overview
v2.0.4-beta is a small patch release addressing two security gaps and one reliability bug found during testing of v2.0.3-beta.
- Rate limiting added to all public endpoints (server and trustauth)
- Hosts can now be removed by other hosts; admin page gains session guard
invite-watchSSE fixed to suppress spurious events
Changes
Rate Limiting on Public Endpoints
Prior to this release, public-facing endpoints on both the server and trustauth had no protection against high request volumes. A client could flood the registration or login endpoints without restriction.
Both services now apply IP-based rate limiting via tower-governor (10 requests/second sustained, burst of 30) when running in HTTPS (production) mode. In HTTP mode (local development and integration tests) limiting is intentionally disabled, since tests do not provide ConnectInfo and should be able to send requests freely. A background task runs every 60 seconds on each service to evict stale entries and prevent unbounded memory growth.
| Service | Limit | Mode |
|---|---|---|
rustsystem-server |
10 req/s, burst 30 | HTTPS only |
rustsystem-trustauth |
10 req/s, burst 30 | HTTPS only |
Hosts Can Remove Other Hosts; Admin Page Session Guard
Two related gaps existed in host management:
-
A host could not be removed mid-session. The voter list remove button was hidden for all users with host status, including those who were not the currently logged-in user. The button is now visible for every voter except the currently logged-in user. Hosts can be kicked by other hosts, but cannot kick themselves.
-
The admin page had no session invalidation handling. If a host was removed by another host, their admin page would continue to render and operate normally as it never detected that their session was gone. The admin page now runs the same
sessionValidguard already used on the meeting page: arefreshSessioncallback polls/api/common/vote-progressevery 10 seconds and on a401marks the session invalid. The initial data load also detects401immediately. When the session is marked invalid, a "Not an administrator" panel replaces the normal admin UI.
| Behaviour | Before | After |
|---|---|---|
| Remove button shown for other hosts | No | Yes |
| Remove button shown for self | N/A | No |
| Admin page detects own removal | No | Yes (10 s polling + on load) |
getSessionIds on non-OK response |
Silent | Throws (consistent with other helpers) |
invite-watch SSE: Suppress Spurious Events
The invite-watch SSE stream used WatchStream::new, which emits the current value immediately upon subscription in addition to subsequent changes. This caused the host-side invite watcher to fire as soon as a client connected, before any real state change had occurred.
Switched to WatchStream::from_changes, which only emits on actual state transitions. The stream now stays silent until the invited voter genuinely completes login.
v2.0.3-beta
Release Notes — v2.0.3-beta
Pre-release date: 2026-03-20
Built on top of v2.0.2-beta.
Status: pre-release — requires testing before promotion to stable.
Overview
v2.0.3-beta is a small patch release. It fixes a bug in the invite-watch SSE that caused incorrect behaviour when multiple hosts were inviting voters simultaneously, adds a CI pipeline for the stage branch, and improves documentation for the APIHandler trait and the versioning section of the contributing guide.
- Fix: invite-watch SSE fires on all hosts and shows the wrong voter name
- Add CI pipeline for
stage - Documentation: updated
APIHandlerusage guide - Documentation: versioning wording in CONTRIBUTE.md
Changes
Fix: Invite-Watch SSE Fires on All Hosts and Shows Wrong Name
When multiple hosts were inviting voters at the same time, two problems occurred:
| Problem | Cause |
|---|---|
| The QR code panel was dismissed on all hosts' screens when any voter logged in | invite_auth broadcast a plain bool; all subscribers reacted identically |
| The "has logged in" alert showed the wrong voter's name | The frontend read the name from its own local qrInfo state, not from the SSE event |
Both problems are fixed together. The invite_auth channel now carries Option<String> — the name of the voter who just logged in — instead of a boolean. The SSE event data is the voter's name directly, so every host sees the correct name regardless of who they were inviting. The QR code panel no longer auto-dismisses on login at all; hosts dismiss it manually. This also removes a spurious initial "Ready" event that the old start-invite endpoint sent unconditionally.
Add CI Pipeline for stage
A CI pipeline has been added for the stage branch. It runs automatically on pushes and pull requests targeting stage.
Documentation: Updated APIHandler Usage Guide
The documentation explaining how to use the APIHandler trait has been updated to better reflect current usage patterns.
Documentation: Versioning Wording in CONTRIBUTE.md
Minor wording improvements to the versioning section of the contributing guide for clarity.
v2.0.2-beta
Release Notes — v2.0.2-beta
Pre-release date: 2026-03-20
Built on top of v2.0.1-beta.
Status: pre-release — requires testing before promotion to v2.0.2.
Overview
v2.0.2-beta is a small patch release addressing two bugs discovered during testing of v2.0.1-beta — one a correctness issue in concurrent vote initialisation, the other a mobile layout regression. It also adds a contributor guide, an end-to-end load test, and minor housekeeping.
- Fixed concurrency race condition in
start-votethat could corrupt round keypairs - Fixed mobile navbar tabs pushing the theme toggle button off-screen
- Fixed feature pills on the landing page overlapping the scroll cue
- Added
CONTRIBUTE.mdcontribution guide - Added end-to-end load test in the frontend
- Removed
TODO.mdin favour of GitHub issues - Minor cleanup in
start_vote.rs
Changes
Concurrency race condition in start-vote
Concurrent POST /api/host/start-vote requests could race to initialise the BLS12-381 keypair for a voting round. Because the keypair generation was not mutually exclusive, two callers could produce different RoundState values — leaving trustauth and the server with inconsistent keys and causing all vote proofs submitted in that round to fail verification.
A forced exclusive lock is now held for the duration of start-vote, serialising any concurrent calls so that exactly one keypair is generated per round.
Mobile layout issues on navbar and landing page
Two layout problems were present on portrait mobile viewports:
| Element | Problem | Fix |
|---|---|---|
| Navbar tabs | Pushed the theme toggle button off-screen to the right | Hidden below the sm breakpoint (640 px) |
| Landing page scroll cue | Obscured by the feature pills above it | Hidden below the sm breakpoint (640 px) |
Both elements are decorative at small screen sizes and are hidden rather than reflowed, keeping the mobile layout clean without restructuring the page.
CONTRIBUTE.md
A contribution guide has been added to the repository root. It covers the branching model, versioning rules, and release note format expected for all contributors.
End-to-end load test
A load test has been added to the frontend test suite. It exercises the full voting flow under concurrent load, and is intended to catch concurrency regressions (such as the start-vote race above) before they reach production.
Removed TODO.md
TODO.md has been removed. Outstanding tasks are now tracked as GitHub issues, which provides better visibility, assignability, and linkability from PRs and commits.
Minor cleanup in start_vote.rs
An unnecessary intermediate variable was removed and formatting was improved in rustsystem-server/src/api/host/start_vote.rs. No behaviour change.
v2.0.1-beta
Release Notes — v2.0.1-beta
Pre-release date: 2026-03-04
Built on top of v2.0.0-beta.
Status: pre-release — see v2.0.0-beta for overall pre-release status.
Overview
v2.0.1-beta is a small patch release containing two changes. No breaking changes have been made and no endpoints have been modified.
- README lock ordering correction
- Global footer added to the frontend
Changes
README: Lock Ordering Correction
The documented lock ordering in the README was wrong. The previous version listed voters before vote_auth, but start-vote — the only endpoint that holds both locks simultaneously — always acquires vote_auth first. Documenting the wrong order could mislead future contributors into introducing a deadlock.
| Lock | Correct position in ordering |
|---|---|
vote_auth |
Before voters |
voters |
After vote_auth |
invite_auth |
Last (no simultaneous-hold relationships) |
Two additional clarifications were made: login holds its locks sequentially rather than simultaneously, and invite_auth has been moved to the end of the ordering table to reflect that it is never held at the same time as any other lock.
Global Footer
A persistent footer is now rendered at the bottom of every page via the root layout. Previously the landing page had its own local footer that was not visible elsewhere in the application.
The footer contains:
- Navigation links to the Guide and Cryptography pages.
- An attribution line for F-sektionen · Lund University.
- The current release tag, injected at build time using
git describe --tags --abbrev=0and displayed as the version string.
v2.0.0-beta
Release Notes — v2.0.0-beta
Pre-release date: 2026-03-03
Covers all changes merged since 2026-02-16.
Status: pre-release — requires stress testing before production use.
Overview
v2.0.0-beta is a pre-release. It is currently being stress tested and will not be put into full production use until that process is complete. The beta label reflects this: the feature set is complete and the system is stable enough for controlled testing, but it has not yet been validated under real meeting load.
The headline change is a fundamental architectural split of the backend into two independent services — rustsystem-server and rustsystem-trustauth — which enforces the zero-knowledge anonymity guarantee at the infrastructure level rather than by policy. Beyond that, the entire frontend has been redesigned, encrypted tally persistence has been added, and the codebase now has comprehensive tests, structured logging, and proper error handling throughout.
Pre-release status
This release includes a dedicated stress test that exercises concurrent meeting and voting operations (see Testing). The results of that test will inform whether any changes are needed before v2.0 is tagged as stable.
The system should not be used to run official FSEK votes until the stress test has passed and v2.0 is promoted to a full release.
Breaking Changes
- The monolithic backend has been replaced by two separate services (
rustsystem-serverandrustsystem-trustauth). Deployment now requires both services to be running. See the updated Dockerfile and deployment instructions. rustsystem-clienthas been removed. All cryptographic operations on the client's side are now perfomed in native typescript. WASM has been scrapped.- The
api-corecrate has been renamed torustsystem-core. - Several environment variables have been renamed or added. See
.env.
Architecture: Server / Trustauth Split
The most significant change in this release. Previously, all server logic was combined in a single binary. The backend is now split into two services that communicate over mutual TLS (mTLS):
| Service | Role |
|---|---|
rustsystem-server |
Meeting management, vote rounds, tallies, and the frontend SPA |
rustsystem-trustauth |
Blind-signing authority — issues BBS blind signatures and stores voter credentials |
This split provides a cryptographic separation of concerns: trustauth knows who is eligible but never sees the vote; the server records what was voted but never learns who submitted it. Neither service can reconstruct the other half of the picture, even in principle.
- Trustauth's
start-voteendpoint is accessible only from the server via mTLS, preventing any public caller from triggering round creation. - PEM certificates are embedded directly into the executables via
include_bytes!so they are never stored on the build machine. - Inter-service URLs are configured at compile time via environment variables (
API_ENDPOINT_SERVER_TO_TRUSTAUTH,API_ENDPOINT_TRUSTAUTH_TO_SERVER).
Frontend: Stateless Client
Voting credentials (blind signatures and related state) are now stored in trustauth, not in the browser's local storage. The frontend is fully stateless: session information is fetched from the server on demand rather than persisted in the browser. This eliminates an entire class of client-side state management bugs and makes it impossible for a voter to lose credentials by clearing their browser.
Session IDs have been removed from local storage entirely.
Frontend: Complete Redesign (Spring 2026)
The entire UI has been redesigned from scratch:
- New design system — reusable components live in
frontend/src/components/, each with asizeprop (s,sm,m,ml,l,xl) and acolorprop (primary,secondary,accent). - Multiple themes — theme selection is persisted in local storage.
- Navbar and global theme switcher.
- Preview page (
/preview) — shows every component in all size and color combinations, making it easy to review the design system. - VoteSection / VoteOption — vote options use checkboxes (was radio buttons). Multiple options are grouped in a
VoteSectioncomponent. - Is-authorized indicator — voters can see on the meeting page that they are correctly logged in to both server and trustauth.
- Search field on the voter list panel.
- Rejoin buttons on the landing page so returning users can quickly navigate back to their meeting.
New Features
BBS Crypto in the Browser
The BBS blind-signature protocol (@noble/curves) is now implemented entirely in the TypeScript frontend. The signature-dev developer page lets you simulate the full meeting workflow — from host keypair generation through voter registration and vote submission — entirely in the browser, which is useful for verifying the cryptographic flow without a running backend.
Encrypted Tally Persistence
Vote tallies are now saved to disk on the server as encrypted files:
- Encryption uses X25519 ECDH + HKDF-SHA256 + ChaCha20-Poly1305 (ECIES).
- The public key is derived from the meeting password at creation time; the private key never reaches the server.
- Files are written to
meetings/{meeting-id}/tally-{datetime}.enc. Timestamps replace the earlier epoch-based naming for readability and uniqueness. - Tally saving has been moved to
/api/host/tally(was/api/host/get-tally).
Download All Tallies at Meeting Close
A Download tallies section on the close-meeting panel lets the host download and decrypt every tally from the meeting in one step, producing a single tallies.json file. Decryption happens entirely in the browser; the private key is never transmitted.
Meeting Deletion
A new endpoint (DELETE /api/meeting) allows the host to delete a meeting and discard all in-memory state.
Vote State Endpoints
New state-check endpoints let the client query the current vote state directly from the server. This removes the need for a client-side state machine and eliminates a whole category of state synchronisation bugs.
Performance & Reliability
Per-Field AsyncRwLock
Mutex has been replaced throughout rustsystem-server and rustsystem-trustauth with per-field AsyncRwLock. The two-level locking strategy (outer map lock + independent per-field locks inside each Meeting / RoundState) allows concurrent requests on different fields of the same meeting to proceed without blocking each other. Lock ordering rules are documented in the README to prevent deadlocks.
Error Handling
All fallible panics (unwrap, expect on non-trivial paths) have been removed. The entire codebase now uses the APIError / APIErrorCode result types throughout, with no anyhow, Box<dyn Error>, or io::Result leaking into handler code.
Testing
| Test area | What was added |
|---|---|
| Frontend signatures | Unit tests for the BBS blind-signature workflow in TypeScript |
| Multi-profile E2E | End-to-end tests exercising multiple browser profiles simultaneously |
| Server unit tests | Additional unit tests covering the refactored server endpoints |
| Trustauth unit tests | Basic coverage of trustauth handlers |
| Server ↔ trustauth E2E | Integration tests exercising the full server-trustauth flow over mTLS |
| Frontend integration | Integration tests updated and fixed for the new stateless architecture |
| Stress test | Load test for concurrent meeting and voting operations |
Playwright-based tests have been removed as they are incompatible with the new two-service architecture.
Logging
Structured logging has been added throughout the backend:
- A custom
MeetingLogLayer(tracingLayer) writes log events that contain amuuidfield to per-meeting log files atmeetings/{muuid}/log. - The main server log is a daily-rolling file at
logs/server.log. error!is reserved for genuine server faults (I/O, crypto failures);info!is used for all normal flow events.
Documentation
- A proper README has been added, covering the quick-start guide, architecture (with Mermaid diagrams), cryptography, RwLock structure, and deployment.
- In-app Guide and Cryptography pages explain the system to end-users and technically curious voters.
- Dev pages (
/preview,/signature-dev) are locked behind a flag in production builds.
Infrastructure
- The Dockerfile has been updated for the new two-service project structure.
- Deployment scripts (
deploy.sh) have been updated and tested for the new architecture.