{"record":{"id":"05350deca2a5c24b","repo":"Dokploy/dokploy","slug":"unauthorized-05350d","errorCode":"UNAUTHORIZED","errorMessage":"Not authorized to delete this network","messagePattern":"Not authorized to delete this network","errorType":"exception","errorClass":"TRPCError","httpStatus":401,"severity":"error","filePath":"apps/dokploy/server/api/routers/network.ts","lineNumber":137,"sourceCode":"\t\t\t\t\tmessage: \"Network not found\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst recreated = await recreateNetwork(input.networkId);\n\t\t\tawait audit(ctx, {\n\t\t\t\taction: \"reload\",\n\t\t\t\tresourceType: \"network\",\n\t\t\t\tresourceId: recreated.networkId,\n\t\t\t\tresourceName: recreated.name,\n\t\t\t});\n\t\t\treturn recreated;\n\t\t}),\n\n\tremove: protectedProcedure\n\t\t.input(apiRemoveNetwork)\n\t\t.mutation(async ({ ctx, input }) => {\n\t\t\tconst network = await findNetworkById(input.networkId);\n\t\t\tif (network.organizationId !== ctx.session.activeOrganizationId) {\n\t\t\t\tthrow new TRPCError({\n\t\t\t\t\tcode: \"UNAUTHORIZED\",\n\t\t\t\t\tmessage: \"Not authorized to delete this network\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst removed = await removeNetwork(input.networkId);\n\t\t\tawait audit(ctx, {\n\t\t\t\taction: \"delete\",\n\t\t\t\tresourceType: \"network\",\n\t\t\t\tresourceId: removed.networkId,\n\t\t\t\tresourceName: removed.name,\n\t\t\t});\n\t\t\treturn removed;\n\t\t}),\n});\n","sourceCodeStart":119,"sourceCodeEnd":152,"githubUrl":"https://github.com/Dokploy/dokploy/blob/546686ea3587f12ec5652217dedd9f7960fb6d15/apps/dokploy/server/api/routers/network.ts#L119-L152","documentation":"Dokploy's network.remove tRPC mutation throws UNAUTHORIZED when the network being deleted belongs to a different organization than the caller's active session organization. Dokploy is multi-tenant (organizations), so every resource is scoped by organizationId and mutations verify ownership before acting.","triggerScenarios":"Calling the network.remove mutation (protectedProcedure with apiRemoveNetwork input) with a networkId whose organizationId differs from ctx.session.activeOrganizationId, e.g. a user switching organizations in the UI but using a stale networkId copied from another org.","commonSituations":"Stale networkId cached in the client after switching active organization in Dokploy; scripts/automation reusing IDs across instances; UI list not refreshed after org change.","solutions":["Switch your active organization (via the org switcher) to the one that owns the network, then retry the delete","Refresh the networks list so the client holds IDs only for the current organization","Verify the networkId is correct with network.one/get before calling remove","If cross-org deletion is legitimately required, have an owner/admin of the owning organization perform it"],"exampleFix":"// before\nawait trpc.network.remove.mutate({ networkId: staleNetworkId });\n// after\nconst nets = await trpc.network.all.query();\nconst net = nets.find(n => n.networkId === id); // only current-org networks returned\nif (net) await trpc.network.remove.mutate({ networkId: net.networkId });","handlingStrategy":"validation","validationCode":"const nets = await trpc.network.all.query();\nconst target = nets.find(n => n.networkId === networkId);\nif (!target) throw new Error('Network not in active organization');","typeGuard":null,"tryCatchPattern":"try { await trpc.network.remove.mutate({ networkId }); }\ncatch (e) { if ((e as TRPCError).code === 'UNAUTHORIZED') alert('Switch organization first'); else throw e; }","preventionTips":["Always resolve resource IDs via a fresh org-scoped list query rather than caching them","Re-fetch lists after switching active organization","Pass IDs, not rely on browser-cached state, in automation scripts"],"tags":["dokploy","trpc","authorization","multi-tenant","network"],"backgroundTag":"cross-tenant-resource-authorization","analyzedSha":"546686ea3587f12ec5652217dedd9f7960fb6d15","analyzedAt":"2026-08-27T05:18:58.095Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}