fullstackhero/dotnet-starter-kit · error · InvalidOperationException
Only the author can edit a message.
Error message
Only the author can edit a message.
What it means
Message.Edit enforces authorship: only the user whose id equals AuthorUserId (ordinal comparison) may change the body. Any other caller — even a moderator, unlike SoftDelete — throws InvalidOperationException. This is an ownership guard, so authorization must not be delegated to the aggregate.
Solutions
- Check message.AuthorUserId == currentUserId (or a domain-level CanEdit) before calling Edit and return 403 otherwise.
- Catch InvalidOperationException in the handler and translate it to a 403 Forbidden response rather than 500.
- For moderation edits, add an explicit domain operation (e.g. ModerateEdit) instead of bypassing the author check.
- Normalize user id casing/format when creating messages so ordinal comparison matches.
Example fix
// before
message.Edit(request.NewBody, currentUserId);
// after
if (message.AuthorUserId != currentUserId)
{
throw new ForbiddenAccessException("Only the author can edit a message.");
}
message.Edit(request.NewBody, currentUserId); Defensive patterns
Strategy: validation
Validate before calling
public static bool CanEdit(Domain.Message m, string userId) => m.DeletedAtUtc is null && string.Equals(m.AuthorUserId, userId, StringComparison.Ordinal);
Type guard
if (message.AuthorUserId != currentUserId) throw new ForbiddenAccessException("Only the author can edit a message."); Try / catch
try { message.Edit(newBody, userId); } catch (InvalidOperationException ex) when (ex.Message.Contains("Only the author")) { throw new ForbiddenAccessException(ex.Message); } Prevention
- Check authorship in the endpoint/handler before invoking the aggregate
- Return 403 (not 500) for ownership violations
- Don't reuse Edit for moderator flows — add a dedicated operation
- Keep user ids stored in a canonical format
When it happens
Trigger: Calling message.Edit(newBody, editingUserId) where editingUserId != message.AuthorUserId — e.g. a handler passing the current user's id but the client targeted another user's message, or an admin endpoint reusing Edit.
Common situations: Missing authorization check in the endpoint layer letting users edit others' messages (caught here as 500 instead of 403); admin 'edit any message' tooling incorrectly calling Edit; user id casing/format mismatches between token and stored AuthorUserId.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- Cannot edit a deleted message.
- Only the author or a moderator can delete.
- Cannot start a DM with yourself.
- Group DM requires at least 3 distinct members.
- All user ids must be non-empty.
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/66af8030efe4a890.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Chat/Modules.Chat/Domain/Message.cs:87
new MessageCreatedDomainEvent(channelId, m.Id, authorUserId, parentMessageId, id, ts)));
return m;
}
/// <summary>
/// Input shape for <see cref="Create"/>: a resolved mention with the original position range
/// in the body so the UI can render the highlight without re-parsing.
/// </summary>
public readonly record struct ParsedMention(string MentionedUserId, int StartIndex, int Length);
public void Edit(string newBody, string editingUserId)
{
if (DeletedAtUtc.HasValue)
{
throw new InvalidOperationException("Cannot edit a deleted message.");
}
if (!string.Equals(AuthorUserId, editingUserId, StringComparison.Ordinal))
{
throw new InvalidOperationException("Only the author can edit a message.");
}
ArgumentException.ThrowIfNullOrWhiteSpace(newBody);
Body = newBody.Trim();
EditedAtUtc = DateTime.UtcNow;
AddDomainEvent(DomainEvent.Create((id, ts) =>
new MessageEditedDomainEvent(ChannelId, Id, AuthorUserId, id, ts)));
}
public void SoftDelete(string deletingUserId, bool isModerator)
{
if (DeletedAtUtc.HasValue) return;
if (!isModerator && !string.Equals(AuthorUserId, deletingUserId, StringComparison.Ordinal))
{
throw new InvalidOperationException("Only the author or a moderator can delete.");
}
DeletedAtUtc = DateTime.UtcNow;
Body = null;View on GitHub (pinned to 3f2959e683)