diff options
| author | srdusr <[email protected]> | 2025-12-15 20:44:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2025-12-15 20:44:00 +0200 |
| commit | 1740327557074df0c8b99635ee949e2540ac94d0 (patch) | |
| tree | eb88af6056350798fa3f53ff4349b1e947c71d6e /README.md | |
| parent | 3dbebfbc9345d2603908f32c0dabebc0ff21feb3 (diff) | |
| download | typerpunk-1740327557074df0c8b99635ee949e2540ac94d0.tar.gz typerpunk-1740327557074df0c8b99635ee949e2540ac94d0.zip | |
Harden for production: dependencies, headers, admin roles, docs
Dependencies
- The server build carried 37 known advisories, including RUSTSEC-2024-0363
in sqlx 0.7, which is the database layer. sqlx moved to 0.8 with
default-features off, which also drops the MySQL and SQLite drivers and
with them rsa and RUSTSEC-2023-0071. reqwest moved to 0.12, which brings
hyper 1.x and was the sole source of every remaining advisory: h2 0.3,
rustls-webpki 0.101, rustls-pemfile 1.0 and idna 0.3.
- The server build now reports no known vulnerabilities against OSV. cargo
audit itself would not compile, so the check queries OSV with the crate
versions cargo tree reports for the server binary.
- Cargo.lock is committed. This workspace produces binaries, so the lockfile
is what makes a deployed build reproducible and the audit above meaningful.
Headers
- The application sent no security headers at all. The static server now
sends a Content-Security-Policy, nosniff, frame options, a referrer policy
and a permissions policy; the API sends a policy of its own, since it
serves JSON and should load and frame nothing.
- The one inline script in index.html moved to a file so script-src needs no
unsafe-inline. WebAssembly needs wasm-unsafe-eval, without which nothing
types at all, so that is present and explained.
- Five style attributes moved to the CSSOM rather than adding unsafe-inline
for styles. A style attribute in markup is refused by the policy; the same
property set through element.style is not.
Production configuration
- With TYPERPUNK_ENV=production the server refuses to start if COOKIE_SECURE
is off, if DATABASE_URL is still the development default, or if
FRONTEND_ORIGIN is http on a non-local host. These were warnings, and a
warning in a log nobody reads is not a safeguard.
Administration
- Moderators were appointed with psql. There is now an admin role,
bootstrapped from TYPERPUNK_ADMIN_USERNAME at startup, and a UI to appoint
and remove moderators. An administrator's own role cannot be changed
through the API, so a mistake cannot lock everyone out of moderation.
Corpus
- scripts/export_approved.js writes approved submissions back into
data/packs/community-*.json. Approved passages are served from the database
and merged at startup, so without this the repository dataset and the live
corpus drift apart, and a fresh checkout or the TUI sees only what shipped.
Documentation
- README rewritten for the repository: what it does, how to run it, the pack
format, the server variables, deployment, and what the security posture
actually is. Plain English, no em dashes, no emoji.
Checked and found already correct: every private endpoint refuses anonymous
callers, session cookies are HttpOnly and SameSite=Lax, CORS names a single
origin, internal errors are logged rather than returned, and every query is
parameterised.
Diffstat (limited to 'README.md')
| -rwxr-xr-x | README.md | 275 |
1 files changed, 210 insertions, 65 deletions
@@ -1,87 +1,232 @@ # TyperPunk -Competitive typing in your terminal (CLI) and in the browser (Web): solo practice, live multiplayer races, code drills and adaptive weak-key training. +Competitive typing for the terminal and the browser. It has solo practice, +live multiplayer races, code and command drills, and a practice mode that +targets the keys you personally get wrong. + +The web client and the terminal client share one Rust core, so both score a +test the same way. + +## Contents + +- [What it does](#what-it-does) +- [Running it](#running-it) +- [Modes](#modes) +- [Text packs](#text-packs) +- [Server](#server) +- [Deployment](#deployment) +- [Development](#development) +- [Security](#security) +- [Licence](#licence) + +## What it does + +- Solo tests by word count, by time, or on a fixed passage. +- Live races against other people, or against bots when nobody else is + around. +- Code and command drills. Each one explains what the line does. +- Practice mode, which generates text weighted toward the characters you + mistype or hesitate on. +- Custom text. Import your own notes and work through them over several + sittings. +- Accounts, personal bests, a leaderboard, and friends. + +## Running it + +You need Rust and Node.js. The web client also needs `wasm-pack`, which +`web/launch.sh` installs if it is missing. + +### Web client + +```bash +./web/launch.sh +``` + +This builds the WebAssembly core, merges the text packs, and serves the app +on http://localhost:4173. There is no bundler and no npm dependency tree. +Edit a file under `web/src/` and reload the page. + +The multiplayer, account and leaderboard features need the server as well. +See [Server](#server). + +### Terminal client + +```bash +cargo run --package typerpunk-tui +``` + +`./install.sh` builds it and puts it on your path. + +## Modes + +| Mode | What it types | +| --- | --- | +| Random | A passage from any pack | +| Words | A fixed number of common words | +| Timed | As many words as you can before the clock runs out | +| Zen | No timer and no word limit | +| Practice | Words weighted toward your own weak keys | +| Custom | Text you import, including markdown notes | +| Lyrics | The song currently playing on Spotify | + +Alongside those are the text packs, listed below. + +Sixteen typing languages are available for the generated-word modes. This +sets the vocabulary you type, not the language of the interface. The +interface is English only. See `TODO-ui-languages.md`. + +## Text packs + +`data/packs/*.json` holds the passages. Each entry looks like this: + +```json +{ + "category": "quotes", + "content": "The best way out is always through.", + "attribution": "Robert Frost" +} +``` + +`attribution` is optional. Leave it out for original prose rather than +inventing a source. Code entries take two more fields: + +```json +{ + "category": "shell", + "language": "shell", + "content": "awk -F: '{print $1, $7}' /etc/passwd", + "attribution": "awk", + "explanation": "-F sets the field separator." +} +``` + +`language` is one of `javascript`, `python`, `rust`, `clike` or `shell`, and +selects the syntax highlighting. `explanation` is shown while you type in +single player, and after the race in multiplayer. + +After editing a pack, rebuild the merged dataset: -## Quick Start +```bash +node scripts/merge_packs.js +``` + +This writes `texts.json` and `web/src/data/texts.json`. + +Passages must be a single line. The typing input is one line, so a passage +containing a newline cannot be finished. + +### Community submissions + +Signed-in users can submit passages from the Contribute screen. Nothing +reaches other players until a moderator approves it. Approved passages are +served from the database and merged over the bundled packs when the client +starts. -- **CLI (Terminal UI)** - ```bash - # Clone and enter - git clone https://github.com/yourusername/typerpunk.git - cd typerpunk +To fold approved submissions back into the repository: - # Install for CLI (builds TUI and optionally merges dataset packs) - ./install.sh +```bash +node scripts/export_approved.js http://localhost:8787 +node scripts/merge_packs.js +``` + +The first administrator is named by `TYPERPUNK_ADMIN_USERNAME` at startup. +Administrators appoint moderators from the Contribute screen. - # Run CLI - cargo run --package typerpunk-tui - ``` +## Server -- **Web** - ```bash - # From repo root: builds WASM and starts the static dev server - ./web/launch.sh - ``` - Opens http://localhost:4173 +`typerpunk-server` provides accounts, stats, the leaderboard, friends, +multiplayer rooms and the Spotify integration. It needs PostgreSQL. -## Dataset (shared by CLI and Web) +```bash +sudo -u postgres createuser --pwprompt typerpunk +sudo -u postgres createdb -O typerpunk typerpunk +sudo -u postgres createdb -O typerpunk typerpunk_test -- **Offline (recommended)** - - Add texts to `data/packs/*.json` with fields: - ```json - { "category": "programming", "content": "80-400 chars…", "attribution": "Author" } - ``` - - Merge packs into the shared `texts.json` at repo root: - ```bash - node scripts/merge_packs.js - ``` +cp crates/server/.env.example crates/server/.env +cargo run --package typerpunk-server +``` -- **Online (optional, web only)** - - Host a `texts.json` and set a URL in the page (e.g., `web/index.html`): - ```html - <script>window.TYPERPUNK_TEXTS_URL = "https://your.cdn/path/to/texts.json";</script> - ``` - - The web app uses the online dataset if reachable; otherwise it falls back to the bundled file. +Migrations run at startup. Configuration is by environment variable; see +`crates/server/.env.example` for the full list. -Notes: -- `web/launch.sh` copies the root `texts.json` into `web/src/data/texts.json` for local dev. -- A small fallback dataset is kept in `web/src/data/texts.json`. +| Variable | Purpose | +| --- | --- | +| `DATABASE_URL` | PostgreSQL connection string | +| `PORT` | Listen port, default 8787 | +| `FRONTEND_ORIGIN` | Origin allowed by CORS | +| `COOKIE_SECURE` | Set to 1 behind TLS | +| `TYPERPUNK_ENV` | Set to `production` to enforce the checks below | +| `TYPERPUNK_ADMIN_USERNAME` | Account to make an administrator at startup | +| `TEXTS_JSON_PATH` | Dataset the race passages come from | +| `SPOTIFY_CLIENT_ID` | Spotify application ID, for Lyrics mode | +| `SPOTIFY_CLIENT_SECRET` | Spotify application secret | -## CLI Keys +## Deployment -- Start: Enter -- Quit: Esc -- Change category: Left/Right -- Delete word: Ctrl+Backspace / Alt+Backspace / Ctrl+H / Ctrl+W +Put a reverse proxy in front. Serve `web/` as static files and route +`/api/*` and `/ws/*` to the server. That makes the API same-origin, so no +CORS configuration is needed in the browser. -## Scripts Scope +Set `TYPERPUNK_ENV=production`. The server then refuses to start if: -- `install.sh`: CLI-focused (Rust toolchain, dataset merge via Node, builds TUI) -- `web/launch.sh`: Web dev workflow (WASM build + zero-dependency static server) +- `COOKIE_SECURE` is not 1, which would send the session cookie in clear. +- `DATABASE_URL` is still the development default. +- `FRONTEND_ORIGIN` is `http://` on a host that is not local. -No npm packages are used anywhere in this repo. Node.js is used only as a -runtime for small built-in-module-only scripts (`scripts/merge_packs.js`, -`web/serve.mjs`); nothing is ever installed from the npm registry. +A warning in a log nobody reads is not a safeguard, so these are refusals +rather than warnings. -## Repo Layout +Terminate TLS at the proxy and send HSTS from there. The application sets the +other security headers itself. +## Development + +```bash +cargo test --workspace --exclude typerpunk-steam # Rust +cd web/tests && python3 run_all.py # browser ``` -typerpunk/ -├── Cargo.toml # Workspace configuration -├── crates/ -│ ├── core/ # Shared core functionality -│ └── tui/ # Terminal UI implementation -├── data/ -│ └── packs/ # Offline dataset packs -├── web/ # Web app (plain HTML/CSS/JS, no build step) -│ ├── src/ -│ ├── index.html -│ └── serve.mjs -├── scripts/ -│ └── merge_packs.js # Merge packs into texts.json -└── README.md + +The browser tests drive the real application with Playwright. They need both +servers running; see `web/tests/README.md`. + +`crates/steam` is a Bevy desktop client and is excluded from the default test +run because it is slow to build. + +Repository layout: + ``` +crates/core typing engine, shared by every client +crates/wasm WebAssembly bindings for the web client +crates/tui terminal client +crates/server HTTP and WebSocket server +crates/steam desktop client (Bevy) +web/ web client, no build step +data/packs/ text packs +scripts/ dataset tools +``` + +### Terminal client parity + +The terminal client has accounts, the leaderboard and friends. Multiplayer +racing is on hold there: it needs a WebSocket client and a live-updating race +view, which is close to the size of the whole web multiplayer build. + +## Security + +- Passwords are hashed with Argon2. Sessions are HttpOnly, SameSite=Lax + cookies, and Secure when `COOKIE_SECURE` is set. +- Every query is parameterised. Every value rendered into the page is + escaped, including imported file content and file names. +- Custom text is read in the browser and never uploaded. +- The Spotify and lyrics endpoints have fixed upstream hosts, so neither can + be pointed elsewhere. Both are rate limited. +- The client sends a Content-Security-Policy that forbids inline script. +- Dependencies are checked against the OSV database. The server build has no + known advisories. + +Report a security problem by opening an issue, or privately if it is +exploitable. -## License +## Licence -MIT +MIT. See [LICENSE](LICENSE). |