fix(server): auth-guard avatar serving, atomic write, user_id assertion in merge
- Move /avatars ServeDir behind require_auth middleware so avatar files can only be fetched by authenticated users (H-11) - Make avatar upload atomic via .tmp write + rename, cleaning up stale extensions only after the rename succeeds (H-12) - Return 401 instead of silently returning an empty username string when the user row is unexpectedly missing a username (L-17) - Add user_id mismatch guard to merge(): returns local payload unchanged with a ConflictReport rather than silently cross-contaminating data (H-2) - Truncate opt-in display_name to 32 chars client-side before sending, matching the server's DISPLAY_NAME_MAX validation (L-5) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -47,6 +47,15 @@ use crate::progress::{level_for_xp, DAILY_CHALLENGE_HISTORY_CAP};
|
||||
pub fn merge(local: &SyncPayload, remote: &SyncPayload) -> (SyncPayload, Vec<ConflictReport>) {
|
||||
let mut conflicts = Vec::new();
|
||||
|
||||
if local.user_id != remote.user_id {
|
||||
conflicts.push(ConflictReport {
|
||||
field: "user_id".to_string(),
|
||||
local_value: local.user_id.to_string(),
|
||||
remote_value: remote.user_id.to_string(),
|
||||
});
|
||||
return (local.clone(), conflicts);
|
||||
}
|
||||
|
||||
let stats = merge_stats(&local.stats, &remote.stats, &mut conflicts);
|
||||
let achievements = merge_achievements(&local.achievements, &remote.achievements);
|
||||
let progress = merge_progress(&local.progress, &remote.progress, &mut conflicts);
|
||||
|
||||
Reference in New Issue
Block a user