{"record":{"id":"84981283f90be1d7","repo":"fullstackhero/dotnet-starter-kit","slug":"no-file-access-policy-registered-for-owner-type-cmd","errorCode":null,"errorMessage":"No file access policy registered for owner type '{cmd.OwnerType}'.","messagePattern":"No file access policy registered for owner type '(.+?)'\\.","errorType":"exception","errorClass":"ForbiddenException","httpStatus":403,"severity":"error","filePath":"src/Modules/Files/Modules.Files/Features/v1/RequestUploadUrl/RequestUploadUrlCommandHandler.cs","lineNumber":64,"sourceCode":"            !category.AllowedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))\n        {\n            throw new CustomException(\n                $\"Extension '{extension}' not allowed for category '{cmd.Category}'.\",\n                (IEnumerable<string>?)null,\n                HttpStatusCode.BadRequest);\n        }\n\n        if (cmd.SizeBytes > category.MaxBytes)\n        {\n            throw new CustomException(\n                $\"File exceeds max size of {category.MaxBytes} bytes for category '{cmd.Category}'.\",\n                (IEnumerable<string>?)null,\n                HttpStatusCode.BadRequest);\n        }\n\n        // Authorization: policy must exist and allow the attach.\n        var policy = policies.Resolve(cmd.OwnerType)\n            ?? throw new ForbiddenException($\"No file access policy registered for owner type '{cmd.OwnerType}'.\");\n        if (!await policy.CanAttachAsync(cmd.OwnerId, userId.ToString(), cancellationToken).ConfigureAwait(false))\n        {\n            throw new ForbiddenException(\"Not allowed to attach files to this owner.\");\n        }\n\n        // Quota pre-check (no debit yet — debit happens on finalize with actual bytes).\n        var quotaCheck = await quotas.CheckAsync(tenantId, QuotaResource.StorageBytes, cmd.SizeBytes, cancellationToken).ConfigureAwait(false);\n        if (!quotaCheck.Allowed)\n        {\n            throw new CustomException(\n                $\"Storage quota exceeded ({quotaCheck.CurrentUsage}/{quotaCheck.Limit} bytes).\",\n                (IEnumerable<string>?)null,\n                (HttpStatusCode)507);\n        }\n\n        // Generate id + storage key + presigned URL.\n        var id = Guid.CreateVersion7();\n        var storageKey = StorageKeyBuilder.Build(tenantId, cmd.OwnerType, id, cmd.FileName, DateTimeOffset.UtcNow);","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Files/Modules.Files/Features/v1/RequestUploadUrl/RequestUploadUrlCommandHandler.cs#L46-L82","documentation":"ForbiddenException thrown when policies.Resolve(cmd.OwnerType) returns null — i.e. no IFileAccessPolicy is registered for the owner type supplied in the command. The Files module delegates attach authorization to per-owner-type policies (ticket, catalog item, etc.); an unregistered owner type is treated as forbidden, not as an open door.","triggerScenarios":"RequestUploadUrlCommand with cmd.OwnerType spelled incorrectly ('Tickets' vs 'ticket'), a new owner type introduced by a caller without implementing/registering an IFileAccessPolicy for it in DI, or a policy registered under a different key string than the client sends.","commonSituations":"New business entity added with file attachments but the developer forgot to implement and register its access policy; frontends hardcoding an ownerType string that drifted from backend constants; renaming an entity on one side only.","solutions":["Send an ownerType that has a registered policy — check the string against the IFileAccessPolicy registration keys in the Files module.","Implement an IFileAccessPolicy for the new owner type and register it in DI so Resolve() finds it.","Centralize ownerType values as shared constants (Contracts) so client and server cannot drift.","Add a validator rule listing the supported owner types to fail fast with a helpful message."],"exampleFix":"// before\nawait requestUploadUrl({ ownerType: \"Tickets\", ownerId: id, ... }); // no policy registered for \"Tickets\"\n// after\nawait requestUploadUrl({ ownerType: \"ticket\", ownerId: id, ... }); // matches registered FileAccessPolicy key","handlingStrategy":"validation","validationCode":"const SUPPORTED_OWNER_TYPES = [\"ticket\", \"catalogitem\"]; // mirrors registered IFileAccessPolicy keys\nif (!SUPPORTED_OWNER_TYPES.includes(ownerType)) throw new Error(`No policy for owner type '${ownerType}'`);","typeGuard":"function isSupportedOwnerType(t) { return SUPPORTED_OWNER_TYPES.includes(t); }","tryCatchPattern":"try { await requestUploadUrl(cmd); } catch (e) { if (e.status === 403 && e.message?.includes(\"No file access policy\")) { console.error(\"ownerType not registered:\", cmd.ownerType); } else throw e; }","preventionTips":["Reference owner types from shared constants in module Contracts instead of raw strings.","When adding a new owner entity, implement and register its IFileAccessPolicy as part of the feature checklist.","Add a unit test asserting every owner type the client uses resolves to a policy."],"tags":["authorization","files","dependency-registration"],"backgroundTag":"insufficient-permissions","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}