From 9c59240f48457d98134ab1ada59cd757453d5aa3 Mon Sep 17 00:00:00 2001 From: Toutsu Date: Mon, 25 May 2026 13:59:41 +0300 Subject: [PATCH] fix: connection leak in UpsertDiscordUserAsync + false conflict in LinkIdentityAsync - UpsertDiscordUserAsync: restore await using on opened connection - LinkIdentityAsync: compute effectiveCurrentPrimary before existingLink check to prevent false conflict when current user is a secondary identity Co-Authored-By: Claude Opus 4.7 --- src/GmRelay.Web/Services/SessionService.cs | 27 ++++++++++++---------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/GmRelay.Web/Services/SessionService.cs b/src/GmRelay.Web/Services/SessionService.cs index 0ac82ad..c99c976 100644 --- a/src/GmRelay.Web/Services/SessionService.cs +++ b/src/GmRelay.Web/Services/SessionService.cs @@ -1412,6 +1412,17 @@ public sealed class SessionService( throw new InvalidOperationException("Target identity is already the primary account of another linked set."); } + // Check if current is already a secondary (then their primary becomes the effective primary) + var currentPrimaryId = await conn.QuerySingleOrDefaultAsync( + """ + SELECT primary_player_id + FROM player_links + WHERE secondary_player_id = @CurrentPlayerId + """, + new { CurrentPlayerId = currentPlayerId.Value }, transaction); + + var effectiveCurrentPrimary = currentPrimaryId ?? currentPlayerId.Value; + // Check if target is already linked to someone else as secondary var existingLink = await conn.QuerySingleOrDefaultAsync( """ @@ -1421,23 +1432,14 @@ public sealed class SessionService( """, new { TargetPlayerId = targetPlayerId }, transaction); - if (existingLink is not null && existingLink.Value != currentPlayerId.Value) + if (existingLink is not null && existingLink.Value != effectiveCurrentPrimary) { - await _LogIdentityAuditAsync(conn, currentPlayerId.Value, "link_attempt_conflict", + await _LogIdentityAuditAsync(conn, effectiveCurrentPrimary, "link_attempt_conflict", targetPlatform, targetExternalUserId, currentPlayerId.Value, transaction); await transaction.CommitAsync(); throw new InvalidOperationException("Target identity is already linked to another account."); } - // Check if current is already a secondary (then their primary becomes the effective primary) - var currentPrimaryId = await conn.QuerySingleOrDefaultAsync( - """ - SELECT primary_player_id - FROM player_links - WHERE secondary_player_id = @CurrentPlayerId - """, - new { CurrentPlayerId = currentPlayerId.Value }, transaction); - var effectivePrimary = currentPrimaryId ?? currentPlayerId.Value; // Check if already linked @@ -1527,7 +1529,8 @@ public sealed class SessionService( public async Task UpsertDiscordUserAsync(string discordId, string displayName, string? avatarUrl) { - await _UpsertPlayerAndGetIdAsync(await dataSource.OpenConnectionAsync(), "Discord", discordId, displayName, avatarUrl, null); + await using var conn = await dataSource.OpenConnectionAsync(); + await _UpsertPlayerAndGetIdAsync(conn, "Discord", discordId, displayName, avatarUrl, null); } // --- Private helpers ---