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

  1. Register the missing IFileAccessPolicy implementation for that OwnerType in the DI container.
  2. Inspect the row's OwnerType and reconcile it with the set of policy keys known to FileAccessPolicyRegistry.
  3. Add a registration coverage test/startup assertion covering all OwnerType values.
  4. 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

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


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)