diff --git a/DeepDrftManager/Components/Pages/Tracks/BatchUpload.razor b/DeepDrftManager/Components/Pages/Tracks/BatchUpload.razor index 524d089..21421be 100644 --- a/DeepDrftManager/Components/Pages/Tracks/BatchUpload.razor +++ b/DeepDrftManager/Components/Pages/Tracks/BatchUpload.razor @@ -129,6 +129,11 @@ // Set true once the admin has acknowledged the missing-hero warning, so a second submit proceeds. private bool _heroWarningAcknowledged; + // Captured once at component initialization on the live interactive circuit, while the token + // is known-good, so a mid-session token expiry at submit time cannot discard a long-composed + // release. Only assigned when the id parses successfully. + private long? _createdByUserId; + private string _albumName = string.Empty; private string _artist = string.Empty; private string _genre = string.Empty; @@ -156,6 +161,19 @@ } } + protected override async Task OnInitializedAsync() + { + // Capture the user id once at load, while the token is known-good. The CMS host runs with + // prerender: false (InteractiveServer), so this is the single init pass — auth state is + // fully available. The page is [Authorize]-gated, so the parse should always succeed. + var authState = await AuthStateProvider.GetAuthenticationStateAsync(); + var userIdValue = authState.User.FindFirstValue(ClaimTypes.NameIdentifier); + if (long.TryParse(userIdValue, out var userId)) + { + _createdByUserId = userId; + } + } + // Switching to a single-track medium collapses any multi-track selection to the first row so the // single-track invariant holds before submit. The predicate reads the same MediumRules cardinality // declaration the upload service enforces, so the form and the domain cannot drift. @@ -275,13 +293,12 @@ } } - var authState = await AuthStateProvider.GetAuthenticationStateAsync(); - var userIdValue = authState.User.FindFirstValue(ClaimTypes.NameIdentifier); - if (!long.TryParse(userIdValue, out var createdByUserId)) + if (_createdByUserId is not long createdByUserId) { - // The page is gated by [Authorize] under the Admin role, so a missing or - // unparseable id here is a configuration bug, not normal client state. - Logger.LogError("Authenticated user has no parseable NameIdentifier claim: {Value}", userIdValue); + // _createdByUserId is set at component initialization from the authenticated principal. + // A null here means the id was unavailable even at load — a genuine configuration bug, + // since the page is [Authorize]-gated. + Logger.LogError("User id was not captured at initialization — NameIdentifier claim missing or unparseable."); _errorMessage = "Your session is missing a valid identifier. Please sign in again."; return; }