fullstackhero/dotnet-starter-kit · error · ForbiddenException
Not allowed to attach files to this owner.
Error message
Not allowed to attach files to this owner.
What it means
ForbiddenException thrown when the resolved IFileAccessPolicy's CanAttachAsync(cmd.OwnerId, userId, ...) returns false — the policy itself decided this user may not attach files to that owner object. This is a deliberate authorization denial, distinct from a missing policy.
Solutions
- Use an account with permission to modify the target owner entity, or attach the file to an owner the current user controls.
- Verify the ownerId is correct and belongs to the caller's tenant.
- Adjust the owner entity's access policy implementation only if the business rules are wrong — not to bypass authorization.
- Check the UI hides or disables attachment actions the current user cannot perform.
Example fix
// before
await requestUploadUrl({ ownerType: "ticket", ownerId: otherTeamsTicketId, ... }); // CanAttachAsync false
// after
await requestUploadUrl({ ownerType: "ticket", ownerId: myTicketId, ... }); // caller can modify this ticket Defensive patterns
Strategy: try-catch
Validate before calling
// check the user can modify the owner entity first
const perms = await getOwnerPermissions(ownerType, ownerId);
if (!perms.canEdit) throw new Error("You cannot attach files to this item"); Try / catch
try { await requestUploadUrl(cmd); } catch (e) { if (e.status === 403 && e.message === "Not allowed to attach files to this owner.") { showToast("You don't have permission to attach files here"); } else throw e; } Prevention
- Hide/disable attachment UI when the user lacks edit rights on the owner entity.
- Pass the ownerId from the loaded entity object, never from ad-hoc user input.
- Keep per-owner-type access rules documented and covered by tests.
When it happens
Trigger: Attaching a file to an entity the caller cannot modify: not the ticket's assignee/creator, not a member of the project that owns the object, an owner id from another tenant, or passing a nonexistent/mistyped ownerId so ownership checks fail.
Common situations: Frontend letting users attach files to items they can only view; copying an ownerId from another record; cross-tenant access attempts; role restrictions where only certain roles may modify attachments; tests using another user's credentials.
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
- Cross-tenant audit access requires…
- Cross-tenant audit summary requires…
- invalid tenant
- No file access policy registered for owner type
- no policy
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/e98972f5f870eed8.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Files/Modules.Files/Features/v1/RequestUploadUrl/RequestUploadUrlCommandHandler.cs:67
$"Extension '{extension}' not allowed for category '{cmd.Category}'.",
(IEnumerable<string>?)null,
HttpStatusCode.BadRequest);
}
if (cmd.SizeBytes > category.MaxBytes)
{
throw new CustomException(
$"File exceeds max size of {category.MaxBytes} bytes for category '{cmd.Category}'.",
(IEnumerable<string>?)null,
HttpStatusCode.BadRequest);
}
// Authorization: policy must exist and allow the attach.
var policy = policies.Resolve(cmd.OwnerType)
?? throw new ForbiddenException($"No file access policy registered for owner type '{cmd.OwnerType}'.");
if (!await policy.CanAttachAsync(cmd.OwnerId, userId.ToString(), cancellationToken).ConfigureAwait(false))
{
throw new ForbiddenException("Not allowed to attach files to this owner.");
}
// Quota pre-check (no debit yet — debit happens on finalize with actual bytes).
var quotaCheck = await quotas.CheckAsync(tenantId, QuotaResource.StorageBytes, cmd.SizeBytes, cancellationToken).ConfigureAwait(false);
if (!quotaCheck.Allowed)
{
throw new CustomException(
$"Storage quota exceeded ({quotaCheck.CurrentUsage}/{quotaCheck.Limit} bytes).",
(IEnumerable<string>?)null,
(HttpStatusCode)507);
}
// Generate id + storage key + presigned URL.
var id = Guid.CreateVersion7();
var storageKey = StorageKeyBuilder.Build(tenantId, cmd.OwnerType, id, cmd.FileName, DateTimeOffset.UtcNow);
var ttl = TimeSpan.FromMinutes(options.Value.UploadUrlTtlMinutes);
var presigned = await storage.GenerateUploadUrlAsync(storageKey, cmd.ContentType, category.MaxBytes, ttl, cancellationToken).ConfigureAwait(false);
View on GitHub (pinned to 3f2959e683)