fix: allow members with git providers permission to create and delete their own (#4713)

The canAccessToGitProviders legacy override only granted read access, so
members with the Git Providers toggle enabled could not add providers — the
create/delete endpoints require gitProviders.create / gitProviders.delete.
This mirrors how the SSH Keys toggle already grants read/create/delete.

The git-provider remove endpoint now restricts non owner/admin roles to
deleting only their own providers (matching the ownership model used for
visibility and sharing), while owner/admin can still delete any provider in
the organization.

Closes #4695
This commit is contained in:
Mauricio Siu
2026-06-30 15:57:50 -06:00
committed by GitHub
parent c2a95870f5
commit aa72091316
4 changed files with 59 additions and 0 deletions

View File

@@ -183,4 +183,29 @@ describe("legacy boolean overrides for member", () => {
memberToReturn = mockMemberData("member");
await expect(checkPermission(ctx, { docker: ["read"] })).rejects.toThrow();
});
it("member passes gitProviders.create with canAccessToGitProviders=true", async () => {
memberToReturn = mockMemberData("member", {
canAccessToGitProviders: true,
});
await expect(
checkPermission(ctx, { gitProviders: ["create"] }),
).resolves.toBeUndefined();
});
it("member passes gitProviders.delete with canAccessToGitProviders=true", async () => {
memberToReturn = mockMemberData("member", {
canAccessToGitProviders: true,
});
await expect(
checkPermission(ctx, { gitProviders: ["delete"] }),
).resolves.toBeUndefined();
});
it("member fails gitProviders.create with canAccessToGitProviders=false", async () => {
memberToReturn = mockMemberData("member");
await expect(
checkPermission(ctx, { gitProviders: ["create"] }),
).rejects.toThrow();
});
});

View File

@@ -143,6 +143,24 @@ describe("free-tier resources for member", () => {
const perms = await resolvePermissions(ctx);
expect(perms.docker.read).toBe(true);
});
it("member gets gitProviders create/delete=false without legacy override", async () => {
memberToReturn = mockMemberData("member");
const perms = await resolvePermissions(ctx);
expect(perms.gitProviders.read).toBe(false);
expect(perms.gitProviders.create).toBe(false);
expect(perms.gitProviders.delete).toBe(false);
});
it("member gets gitProviders read/create/delete=true with canAccessToGitProviders", async () => {
memberToReturn = mockMemberData("member", {
canAccessToGitProviders: true,
});
const perms = await resolvePermissions(ctx);
expect(perms.gitProviders.read).toBe(true);
expect(perms.gitProviders.create).toBe(true);
expect(perms.gitProviders.delete).toBe(true);
});
});
describe("free-tier resources for owner", () => {

View File

@@ -5,6 +5,7 @@ import {
updateGitProvider,
} from "@dokploy/server";
import { db } from "@dokploy/server/db";
import { findMemberByUserId } from "@dokploy/server/services/permission";
import { hasValidLicense } from "@dokploy/server/services/proprietary/license-key";
import { TRPCError } from "@trpc/server";
import { desc, eq, inArray } from "drizzle-orm";
@@ -144,6 +145,19 @@ export const gitProviderRouter = createTRPCRouter({
message: "You are not allowed to delete this Git provider",
});
}
const memberRecord = await findMemberByUserId(
ctx.user.id,
ctx.session.activeOrganizationId,
);
const isPrivileged =
memberRecord.role === "owner" || memberRecord.role === "admin";
if (!isPrivileged && gitProvider.userId !== ctx.session.userId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You can only delete your own Git providers",
});
}
await audit(ctx, {
action: "delete",
resourceType: "gitProvider",

View File

@@ -170,6 +170,8 @@ const getLegacyOverrides = (
},
gitProviders: {
read: !!memberRecord.canAccessToGitProviders,
create: !!memberRecord.canAccessToGitProviders,
delete: !!memberRecord.canAccessToGitProviders,
},
};
};