v0.15.0 password recovery, phone layout, CLI pager, embed policy fixes
This commit is contained in:
1 parent
a999177f0c
commit
e1fa5242e3
48 files changed
+2101
-135
No files matched your search
+47
@@ -116,6 +116,48 @@ Errors: 400 `INVALID_REQUEST` without a password, 401 for a wrong password or
|
||||
token, 429 past 5 attempts a minute per IP, 503 `RESOURCE_LIMIT` when password
|
||||
hashing is saturated or `STORAGE_UNAVAILABLE` when storage is down.
|
||||
|
||||
### Request a Password Reset
|
||||
`POST /auth/recover`
|
||||
|
||||
Emails a one-time reset link to the account's address. The response is the
|
||||
same whether or not the account exists or has an email, so nothing is
|
||||
revealed.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{"identifier": "alice"}
|
||||
```
|
||||
|
||||
- `identifier` (string, required): Username or email address
|
||||
|
||||
**Response (202):**
|
||||
```json
|
||||
{"message": "If the account has an email address, a reset link has been sent to it."}
|
||||
```
|
||||
|
||||
Errors: 429 past 3 requests a minute per IP, 503 `NOT_CONFIGURED` when the
|
||||
server has no mail transport (`/health` reports `"mail": "disabled"`), 502
|
||||
when the relay refused the message.
|
||||
|
||||
### Reset a Password
|
||||
`POST /auth/reset`
|
||||
|
||||
Sets a new password with the token from the link (`?reset=<token>` on the web
|
||||
client) and signs the user in; the token works once, for an hour, and every
|
||||
other session of the account ends.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{"token": "…", "password": "NewPass123"}
|
||||
```
|
||||
|
||||
**Response (200):** as login (`token`, `userId`, `username`, `email`,
|
||||
`expiresAt`).
|
||||
|
||||
Errors: 400 `INVALID_REQUEST` for a weak password or an invalid, used or
|
||||
expired token (`"invalid or expired reset link"`), 429 past 10 a minute per
|
||||
IP, 503 when storage or password hashing is unavailable.
|
||||
|
||||
## Game Endpoints
|
||||
|
||||
### Health Check
|
||||
@@ -129,10 +171,14 @@ Returns server and storage status, and the build the server runs.
|
||||
"status": "healthy",
|
||||
"time": 1699123456,
|
||||
"storage": "ok",
|
||||
"mail": "disabled",
|
||||
"version": "v0.14.0-3-g1a2b3c4"
|
||||
}
|
||||
```
|
||||
|
||||
`mail` is `"ok"` when the server can email password-reset links (the web
|
||||
client then offers **Forgot password?**), otherwise `"disabled"`.
|
||||
|
||||
`version` is `git describe` of the source the binary was built from (`make`
|
||||
sets it), or `dev` for a plain `go build`. Compare it after a deployment: a
|
||||
client newer than the server gets 404 `NOT_FOUND` for endpoints the server
|
||||
@@ -556,6 +602,7 @@ Error codes:
|
||||
- Registration: 5 requests/minute/IP
|
||||
- Login: 10 requests/minute/IP
|
||||
- Account deletion: 5 requests/minute/IP
|
||||
- Password recovery: 3 requests/minute/IP; reset: 10 requests/minute/IP
|
||||
|
||||
Exceeding limit returns 429 status. Behind a reverse proxy the client IP is the
|
||||
proxy's `X-Real-IP` header, trusted only from addresses listed in
|
||||
|
||||
@@ -294,6 +294,13 @@ Exit the client.
|
||||
chess > exit
|
||||
```
|
||||
|
||||
### Long output
|
||||
|
||||
`help`, `games`, `pgn`, `state`, `show` and `replay` print one screen at a
|
||||
time when their output is taller than the terminal: `-- More (N lines left;
|
||||
Enter continues, q quits) --`. The terminal's size comes from the TTY, or in
|
||||
the browser from xterm.js; output piped to another program is never paged.
|
||||
|
||||
### Verbose Mode
|
||||
|
||||
Append `-v` flag to any command for detailed output:
|
||||
|
||||
+9
-3
@@ -131,9 +131,15 @@ sudo chess-db user list
|
||||
su -m chess -c '/home/chess/bin/chess-server db user add -username <name> -dsn "postgres:///chess?host=/tmp"'
|
||||
```
|
||||
|
||||
Other `user` subcommands: `delete`, `set-email`, `set-username`, and
|
||||
`set-hash` (an existing PHC string). CLI-created accounts are identical to
|
||||
site registrations, and the CLI ignores the registration cap.
|
||||
Other `user` subcommands: `delete`, `set-email`, `set-username`, `set-hash`
|
||||
(an existing PHC string), and `reset-link -url <web client>`, which prints a
|
||||
one-time password-reset link (valid an hour) to hand to someone who cannot
|
||||
receive the recovery email. CLI-created accounts are identical to site
|
||||
registrations, and the CLI ignores the registration cap.
|
||||
|
||||
Pending password resets live in `password_resets`: the SHA-256 of the token
|
||||
in the emailed link, one per account, deleted when used, expired, or by the
|
||||
hourly cleanup. The token itself is never stored.
|
||||
|
||||
Sign a user out everywhere (takes effect on their next request):
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ file is kept next to it with a timestamp).
|
||||
| `CHESSD_DSN` | `"host=<socket dir> dbname=chess"` | Database over the socket as the OS account |
|
||||
| `CHESSD_JWT_KEY` | `/var/lib/chessd/jwt.key` | Stable signing key |
|
||||
| `CHESSD_TRUSTED_PROXIES` | `TRUSTED_PROXIES` | Comma-separated proxy addresses |
|
||||
| `CHESSD_FLAGS` | empty | Other server flags, e.g. `-max-users 500 -db-cleanup report` |
|
||||
| `CHESSD_FLAGS` | empty | Other server flags, e.g. `-max-users 500 -db-cleanup report`, or password recovery by email: `-smtp-host <relay> -mail-from "Chess <chess@example.com>" -public-url https://example.com/chess/web-client/` (see [deployment.md](deployment.md#password-recovery-by-email); the sandboxed unit can read a password file only under `/var/lib/chessd`) |
|
||||
|
||||
Server flags worth knowing: `-max-users` (registration cap, default 100),
|
||||
`-anonymous-game-ttl` (default `24h`), `-finished-game-ttl` (default `1h`),
|
||||
|
||||
+50
-1
@@ -154,6 +154,10 @@ as `postgres`, list them with `\dt chess.*` and name them as `chess.games`.
|
||||
| `chessd_dsn` | `postgres:///chess?host=/tmp` | Database `chess` over the socket as the OS user |
|
||||
| `chessd_jwt_key` | `${chessd_home}/jwt.key` | JWT signing key; created (0600) on first start if missing |
|
||||
| `chessd_trusted_proxies` | empty | Proxy address(es) whose `X-Real-IP` is trusted |
|
||||
| `chessd_public_url` | empty | The web client's public address, which emailed reset links open (`https://<site>/chess/web-client/`) |
|
||||
| `chessd_smtp_host`, `chessd_smtp_port` | empty, `25` | SMTP relay for password-reset email; unset leaves recovery off |
|
||||
| `chessd_smtp_user`, `chessd_smtp_password_file` | empty, `${chessd_home}/smtp.secret` if present | Relay credentials, when the relay authenticates this host; the file is mode 0600 |
|
||||
| `chessd_mail_from` | empty | Sender address, e.g. `Chess <chess@<site>>` |
|
||||
| `chessd_flags` | empty | Extra flags, e.g. `-max-users 500 -db-cleanup report` |
|
||||
|
||||
The script runs `daemon(8)` as root, which writes the supervisor pidfile
|
||||
@@ -174,6 +178,11 @@ Server flags:
|
||||
| `-anonymous-game-ttl` | | Retention of games without a registered player (default `24h`) |
|
||||
| `-finished-game-ttl` | | Memory retention of finished games (default `1h`) |
|
||||
| `-db-cleanup` | | Hourly integrity sweep: `off` (default), `report` (log findings), or `delete` ([database.md](database.md#integrity-sweep)) |
|
||||
| `-smtp-host`, `-smtp-port` | | SMTP relay for password-reset email (default port 25); recovery is off without a transport |
|
||||
| `-smtp-user`, `-smtp-password-file` | | Relay credentials, optional; the file is mode 0600 and only sent over TLS |
|
||||
| `-mail-from` | | Sender address of server email |
|
||||
| `-mail-dir` | | Development: write email to `.eml` files here instead of a relay |
|
||||
| `-public-url` | | The web client's address that emailed links open; required with a transport |
|
||||
|
||||
Administrative CLI commands run as `chess` so peer authentication selects the
|
||||
right role; root may use `su -m` although the account has no shell:
|
||||
@@ -186,7 +195,47 @@ jail# su -m chess -c '/home/chess/bin/chess-server db user list -dsn "postgres:/
|
||||
(games, with 8-digit ID prefixes), `pgn -gameId <id|prefix> [-ply N]`,
|
||||
`verify [-gameId <id|prefix>]` (replays stored games against the rules and
|
||||
exits non-zero on a mismatch; run it after an upgrade), and `user add|delete|
|
||||
set-password|set-hash|set-email|set-username|list`.
|
||||
set-password|set-hash|set-email|set-username|reset-link|list`.
|
||||
|
||||
A forgotten password is reset from the jail without mail:
|
||||
|
||||
```sh
|
||||
jail# su -m chess -c '/home/chess/bin/chess-server db user set-password -username <name> -dsn "postgres:///chess?host=/tmp"'
|
||||
```
|
||||
|
||||
which prompts for the new password (or takes `-password`), or with a one-time
|
||||
link to hand to the person, the same link the recovery email carries:
|
||||
|
||||
```sh
|
||||
jail# su -m chess -c '/home/chess/bin/chess-server db user reset-link -username <name> -url https://<site>/chess/web-client/ -dsn "postgres:///chess?host=/tmp"'
|
||||
```
|
||||
|
||||
## Password Recovery by Email
|
||||
|
||||
With an SMTP relay the server emails a reset link on request (`POST
|
||||
/api/auth/recover`, the **Forgot password?** link in the web client); the link
|
||||
opens the client, which sets the new password and signs the person in. The
|
||||
link works for an hour and once; using it signs the account out everywhere
|
||||
else. Accounts without an email address cannot use it (the CLI commands above
|
||||
can).
|
||||
|
||||
The relay is a separate jail reached over the internal network. Set in
|
||||
`rc.conf`:
|
||||
|
||||
```sh
|
||||
sysrc chessd_public_url="https://<site>/chess/web-client/"
|
||||
sysrc chessd_smtp_host="<mail jail address>" # chessd_smtp_port, default 25
|
||||
sysrc chessd_mail_from="Chess <chess@<site>>"
|
||||
# only when the relay authenticates this host:
|
||||
sysrc chessd_smtp_user="<user>"
|
||||
(umask 077 && echo '<password>' > /home/chess/smtp.secret) && chown chess:chess /home/chess/smtp.secret
|
||||
service chessd restart
|
||||
```
|
||||
|
||||
The server uses STARTTLS when the relay offers it and sends credentials only
|
||||
over TLS. `GET /chess/health` reports `"mail": "ok"` once recovery is on. For
|
||||
a test without a relay, `-mail-dir <dir>` in `chessd_flags` writes each
|
||||
message to a file instead.
|
||||
|
||||
## Host nginx and Web Clients
|
||||
|
||||
|
||||
+6
-3
@@ -38,6 +38,9 @@ go build ./cmd/chess-client-cli
|
||||
- `-anonymous-game-ttl`: How long games without a registered player survive after their last activity, in memory and in the database (default: `24h`; `0` keeps them)
|
||||
- `-max-users`: Accounts at which public registration closes (default: `100`; `0` = no limit; the CLI is not limited)
|
||||
- `-web-api-url`: Browser-visible API origin for the embedded web client, set on its script element as `data-api`; useful when its public origin differs from the listen address
|
||||
- `-smtp-host`, `-smtp-port`, `-smtp-user`, `-smtp-password-file`, `-mail-from`: SMTP relay for password-reset email; without a transport, recovery is off
|
||||
- `-mail-dir`: Development: write email to `.eml` files in this directory instead of sending
|
||||
- `-public-url`: The web client's address that emailed links open (required with a transport)
|
||||
|
||||
### Modes
|
||||
```bash
|
||||
@@ -223,8 +226,8 @@ go test ./...
|
||||
go test -race ./internal/server/storage ./internal/server/service
|
||||
|
||||
# Web client end-to-end (Playwright): see test/README.md
|
||||
WEB_PORT=9090 test/run-test-server.sh # terminal 1
|
||||
make test-e2e # terminal 2
|
||||
WEB_PORT=9090 MAIL_DIR=/tmp/chess-mail test/run-test-server.sh # terminal 1
|
||||
CHESS_E2E_MAIL_DIR=/tmp/chess-mail make test-e2e # terminal 2
|
||||
```
|
||||
|
||||
## Configuration
|
||||
@@ -308,7 +311,7 @@ make test-e2e # terminal 2
|
||||
- JWT tokens don't support refresh (must re-login after expiry)
|
||||
- User deletion doesn't cascade to games (games keep player and claim IDs)
|
||||
- Games played without a registered player are deleted 24 hours after their last activity
|
||||
- No password recovery mechanism
|
||||
- Password recovery needs an SMTP relay and an email on the account
|
||||
- No email verification for registration
|
||||
- Fixed worker pool size for engine calculations
|
||||
- No push-based game updates (30-second long-polling is used)
|
||||
|
||||
+24
-1
@@ -174,13 +174,36 @@ Test gate:
|
||||
while replaying, `show` shows the replayed position, and the prompt reads
|
||||
`replay <ply>/<plies>`; `live` returns to the game as it was.
|
||||
|
||||
Follow-up polish (done): the phone layout is full-width rows under the board
|
||||
(`svh` units, so the browser's bars never cover the controls; a replay
|
||||
reserves more height than live play), the turn strip says whose move it is in
|
||||
two words with the dot showing the side, the sign-in dialog keeps the focus in
|
||||
the password field after a wrong password, the brand mark is the site's
|
||||
bishop, and the terminal client pages long output (help, games, pgn, state,
|
||||
show, replay) one screen at a time, natively and in the browser.
|
||||
|
||||
Later for the replay clients:
|
||||
|
||||
- [ ] Web: an "Analysis" link on a replay (chess.com-style evaluation needs
|
||||
an engine in the browser or a server endpoint; none exists).
|
||||
- [ ] Web: a light theme, following the host page's choice.
|
||||
- [ ] CLI: `auto` cannot be interrupted before the end of the line.
|
||||
|
||||
## Accounts
|
||||
|
||||
- [x] Password recovery by email: `POST /api/auth/recover` sends a one-time
|
||||
link (an hour, SHA-256 of the token stored in `password_resets`, schema
|
||||
v4) to the account's address through an SMTP relay (`-smtp-host`, with
|
||||
optional credentials over TLS) or, for development, a directory of `.eml`
|
||||
files (`-mail-dir`); `POST /api/auth/reset` sets the password, ends the
|
||||
account's other sessions and signs the person in; the web client offers
|
||||
**Forgot password?** when `/health` reports `"mail": "ok"` and opens the
|
||||
password form from `?reset=<token>`. `db user reset-link` prints the same
|
||||
link for an administrator; `db user set-password` resets without one.
|
||||
- [ ] Deliver from the chess jail through the mail jail (devops): relay
|
||||
address, sender, and `chessd_smtp_*` in `rc.conf` (deployment.md).
|
||||
- [ ] Email verification at registration and on `set-email`; today an
|
||||
address is taken as typed, so a typo sends nobody a link.
|
||||
|
||||
## Phase R5 — Curated Archive (later)
|
||||
|
||||
- [ ] Schema: game origin (`player`, `curated`), archive metadata (event, site,
|
||||
|
||||
Reference in new issue
Block a user