fullstackhero/dotnet-starter-kit · error · ForbiddenException
no policy
Error message
no policy
What it means
The delete path resolves the access policy for the file's OwnerType via FileAccessPolicyRegistry and throws ForbiddenException("no policy") when resolution returns null. Like the visibility path, this means no IFileAccessPolicy is registered for that OwnerType — a wiring/registration gap, not a user authorization failure.
Solutions
- Register the missing IFileAccessPolicy implementation for that OwnerType in the DI container.
- Inspect the row's OwnerType and reconcile it with the set of policy keys known to FileAccessPolicyRegistry.
- Add a registration coverage test/startup assertion covering all OwnerType values.
- Repair or remove rows carrying invalid OwnerType values via a data fix.
Example fix
// before // OwnerType "Archive" unregistered -> 403 "no policy" on delete // after services.AddSingleton<IFileAccessPolicy, ArchiveFileAccessPolicy>();
Defensive patterns
Strategy: validation
Validate before calling
var ownerTypes = new[] { "User", "Organization" /* all valid values */ };
if (!ownerTypes.Contains(asset.OwnerType)) throw new InvalidOperationException($"No delete policy registered for OwnerType '{asset.OwnerType}'."); Type guard
bool HasPolicy(FileAccessPolicyRegistry registry, string ownerType) => registry.Resolve(ownerType) is not null;
Try / catch
catch (ForbiddenException e) when (e.Message == "no policy") {
logger.LogError(e, "Missing delete policy for OwnerType on file {FileId}", fileId);
return Results.Problem("Server misconfiguration: delete policy missing.", statusCode: 500);
} Prevention
- Ship the delete policy together with any new OwnerType.
- Startup-check registry coverage of all owner types.
- Validate OwnerType values in import/seed pipelines.
- Unit-test registry resolution per owner type.
When it happens
Trigger: FileAssets row with an OwnerType that has no registered IFileAccessPolicy; DI registration missing or module not loaded; OwnerType value in data does not match any policy key (typo, renamed constant, hand-edited seed row).
Common situations: New owner type shipped without its delete policy; integration environment missing a module registration; data imported from another system with unrecognized OwnerType strings; policy class removed during refactor while data still references it.
Related errors
- no policy
- not allowed to change this file's visibility
- not allowed to delete this file
- invalid tenant
- Unknown category ' '.
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/5bd32f525d161c54.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Files/Modules.Files/Features/v1/DeleteFile/DeleteFileCommandHandler.cs:29
public sealed class DeleteFileCommandHandler(
FilesDbContext db,
FileAccessPolicyRegistry policies,
ICurrentUser currentUser)
: ICommandHandler<DeleteFileCommand, Unit>
{
public async ValueTask<Unit> Handle(DeleteFileCommand cmd, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(cmd);
var f = await db.FileAssets
.FirstOrDefaultAsync(x => x.Id == cmd.FileAssetId, cancellationToken)
.ConfigureAwait(false)
?? throw new NotFoundException("file not found");
var userId = currentUser.GetUserId().ToString();
var policy = policies.Resolve(f.OwnerType)
?? throw new ForbiddenException("no policy");
var ctx = new FileAccessContext(f.Id, f.OwnerType, f.OwnerId, f.CreatedByUserId, (int)f.Visibility);
if (!await policy.CanDeleteAsync(ctx, userId, cancellationToken).ConfigureAwait(false))
{
throw new ForbiddenException("not allowed to delete this file");
}
// Soft-delete: AuditableEntitySaveChangesInterceptor sets IsDeleted/DeletedOnUtc/DeletedBy on
// Remove() for ISoftDeletable; byte purge runs later via PurgeDeletedFilesJob post-retention.
db.FileAssets.Remove(f);
await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
return Unit.Value;
}
}
View on GitHub (pinned to 3f2959e683)