Skip to content

fix(server): drop unwired Altcha gate on duel start_game (412 on shared links)#60

Merged
Calixteair merged 1 commit into
mainfrom
fix/duel-no-altcha
May 13, 2026
Merged

fix(server): drop unwired Altcha gate on duel start_game (412 on shared links)#60
Calixteair merged 1 commit into
mainfrom
fix/duel-no-altcha

Conversation

@Calixteair
Copy link
Copy Markdown
Owner

What

Every shared duel link returned 412 Precondition Failed when the friend clicked "Play this grid" — `POST /api/games` rejected the body for a missing Altcha solution.

Why

The duel branch of `start_game` was gated on an Altcha PoW that the front never wired:

```rust
let altcha_required = matches!(mode, GameMode::Duel);
if altcha_required {
let solution = body.altcha_solution.as_deref()
.ok_or(ApiError::AltchaRequired)?; // ← every duel
}
```

The existing comment even said "The Altcha widget will be re-introduced on the front when we expose duel creation" — we exposed it in #55/#59 but never reintroduced the widget, so the gate was killing the flow.

The remaining protections are enough for realistic abuse:

  • `tower_governor` rate-limit (1 req/250 ms per device, 60/min per IP)
  • `play_token` HMAC bound to (game_id, device_id, started_at)
  • UNIQUE (grid_id, device_id) — one game per device per grid
  • duel share URLs HMAC-signed via `duel_sig` — grid_id unforgeable

How

Strip the gate from `start_game`, drop the now-unused imports (`crate::altcha`, `crate::services::altcha as altcha_service`). The infrastructure stays: `altcha::decode_solution`, `verify_solution`, `guard_replay` on Redis — re-enabling per-mode is a one-flag change if we ever see spam signals.

Checklist

  • Conventional commit title
  • No mention of AI / Claude / Anthropic / Copilot
  • `cargo fmt --check` passes
  • `cargo clippy --all-targets -- -D warnings` passes
  • `cargo test --workspace` passes
  • No new `unsafe`, no `unwrap()`
  • No new secret
  • No contract change (request shape was always optional)

Test plan

  • Open a shared duel link → click "Play this grid" → 201 Created, cells appear
  • Inspect `POST /api/games` body in DevTools: no `altchaSolution` field, no 412
  • Daily and solo flows still work (untouched)

The duel branch of start_game was gated on an Altcha PoW solution that
the front never produced — every shared duel link returned 412
Precondition Failed when the friend clicked 'Play this grid'.

The remaining protections are sufficient for the abuse vectors we
actually face:
- tower_governor rate-limit (1 req/250ms per device, 60/min per IP)
- play_token HMAC bound to (game_id, device_id, started_at)
- UNIQUE (grid_id, device_id) — one game per device per grid
- duel share URLs HMAC-signed (duel_sig), grid_id unforgeable

Keep altcha::* and the Redis replay-guard infra in place so a future
re-enable is a one-flag change. Just stop forcing it on a flow that
has no widget to satisfy it.
@Calixteair Calixteair merged commit 8f2baf1 into main May 13, 2026
7 checks passed
@Calixteair Calixteair deleted the fix/duel-no-altcha branch May 13, 2026 19:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant