bug(server): sync push always rejected — user_id nil placeholder fails mismatch check #73

Closed
opened 2026-05-28 21:36:21 +00:00 by funman300 · 1 comment
Owner

Bug

POST /api/sync/push returns 400 Bad Request: user_id mismatch for every authenticated push from the desktop client.

Root cause

build_payload() in solitaire_engine/src/sync_plugin.rs always sets user_id: Uuid::nil() with the comment "the server replaces it with the authenticated user's real ID when it processes the push request".

However the server handler (solitaire_server/src/sync.rs line 148) does not replace it — it rejects it:

// server/src/sync.rs:148
if client_payload.user_id.to_string() != user.user_id {
    return Err(AppError::BadRequest("user_id mismatch".into()));
}

Because Uuid::nil() ("00000000-0000-0000-0000-000000000000") never equals the authenticated user's real UUID, all pushes fail and no data is ever synced to the server.

Impact

  • Server sync is silently broken for all users.
  • push_on_exit always returns an error that is only logged, not visible to the player.
  • No player data is ever persisted server-side.

Fix

Before the mismatch check, replace a nil user_id placeholder with the authenticated user's real UUID (fulfilling the original design intent).

## Bug `POST /api/sync/push` returns `400 Bad Request: user_id mismatch` for every authenticated push from the desktop client. ## Root cause `build_payload()` in `solitaire_engine/src/sync_plugin.rs` always sets `user_id: Uuid::nil()` with the comment *"the server replaces it with the authenticated user's real ID when it processes the push request"*. However the server handler (`solitaire_server/src/sync.rs` line 148) does **not** replace it — it rejects it: ```rust // server/src/sync.rs:148 if client_payload.user_id.to_string() != user.user_id { return Err(AppError::BadRequest("user_id mismatch".into())); } ``` Because `Uuid::nil()` ("00000000-0000-0000-0000-000000000000") never equals the authenticated user's real UUID, **all pushes fail** and no data is ever synced to the server. ## Impact - Server sync is silently broken for all users. - `push_on_exit` always returns an error that is only logged, not visible to the player. - No player data is ever persisted server-side. ## Fix Before the mismatch check, replace a nil `user_id` placeholder with the authenticated user's real UUID (fulfilling the original design intent).
Author
Owner

Fix (commit 7eb1181)

The server's push handler now accepts Uuid::nil() as a valid placeholder and replaces it with the authenticated user's real UUID before the mismatch check:

// Before — nil UUID was rejected as a mismatch:
if client_payload.user_id.to_string() != user.user_id {
    return Err(AppError::BadRequest("user_id mismatch".into()));
}

// After — nil is treated as a placeholder, real mismatches still rejected:
let user_uuid: Uuid = user.user_id.parse()
    .map_err(|_| AppError::Internal("invalid user_id UUID in JWT".into()))?;
if client_payload.user_id == Uuid::nil() {
    client_payload.user_id = user_uuid;
} else if client_payload.user_id != user_uuid {
    return Err(AppError::BadRequest("user_id mismatch".into()));
}

The desktop client's build_payload() always sends Uuid::nil() as a placeholder (as documented in its docstring). The server now fulfils the original design intent by stamping the authenticated user's real UUID onto the payload before merging and persisting. Payloads that explicitly claim a different user's non-nil UUID are still rejected.

## Fix (commit `7eb1181`) The server's `push` handler now accepts `Uuid::nil()` as a valid placeholder and replaces it with the authenticated user's real UUID before the mismatch check: ```rust // Before — nil UUID was rejected as a mismatch: if client_payload.user_id.to_string() != user.user_id { return Err(AppError::BadRequest("user_id mismatch".into())); } // After — nil is treated as a placeholder, real mismatches still rejected: let user_uuid: Uuid = user.user_id.parse() .map_err(|_| AppError::Internal("invalid user_id UUID in JWT".into()))?; if client_payload.user_id == Uuid::nil() { client_payload.user_id = user_uuid; } else if client_payload.user_id != user_uuid { return Err(AppError::BadRequest("user_id mismatch".into())); } ``` The desktop client's `build_payload()` always sends `Uuid::nil()` as a placeholder (as documented in its docstring). The server now fulfils the original design intent by stamping the authenticated user's real UUID onto the payload before merging and persisting. Payloads that explicitly claim a different user's non-nil UUID are still rejected.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: funman300/Ferrous-Solitaire#73