Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions clients/dashboard/src/pages/settings/profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ export function ProfileSettings() {
mutationFn: (url: string | null) => setProfileImage(url),
onSuccess: async () => {
toast.success("Profile image updated");
// Setting the image is a second write to the same row, so ASP.NET Identity rotates the
// concurrency stamp and the tag this form is holding is spent. Adopting the new version (which
// The avatar URL is one of the fields the profile version hashes, so setting the image moves
// the tag and the one this form is holding is spent. Adopting the new version (which
// also refreshes the cache, so the topbar avatar still updates) keeps the next save from
// answering 412 and telling the user someone else edited their profile.
await adoptCurrentVersion();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ internal static RouteHandlerBuilder MapGetMeEndpoint(this IEndpointRouteBuilder
// The profile is a full-representation resource: PUT /profile rewrites every field, so
// a caller editing a stale copy would blank whatever changed meanwhile. Publishing the
// profile version as a strong ETag lets that caller echo it back in If-Match and have
// the server reject the stale write.
// the server reject the stale write. The tag covers only the fields PUT /profile writes
// (not email, status or the host-prefixed avatar URL in this body), so it is a write
// precondition only: never use it to answer a conditional GET with 304.
response.Headers.ETag = new EntityTagHeaderValue($"\"{profile.ProfileVersion}\"", isWeak: false).ToString();

return TypedResults.Ok(profile);
Expand Down
Loading