mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge pull request #1981 from ayham291/canary
feat: Git Provider Permissions
This commit is contained in:
@@ -16,9 +16,11 @@ import { api } from "@/utils/api";
|
||||
import { GitBranch, Loader2, UploadCloud } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { SaveBitbucketProvider } from "./save-bitbucket-provider";
|
||||
import { SaveDragNDrop } from "./save-drag-n-drop";
|
||||
import { SaveGitlabProvider } from "./save-gitlab-provider";
|
||||
import { UnauthorizedGitProvider } from "./unauthorized-git-provider";
|
||||
|
||||
type TabState =
|
||||
| "github"
|
||||
@@ -43,12 +45,31 @@ export const ShowProviderForm = ({ applicationId }: Props) => {
|
||||
const { data: giteaProviders, isLoading: isLoadingGitea } =
|
||||
api.gitea.giteaProviders.useQuery();
|
||||
|
||||
const { data: application } = api.application.one.useQuery({ applicationId });
|
||||
const { data: application, refetch } = api.application.one.useQuery({
|
||||
applicationId,
|
||||
});
|
||||
const { mutateAsync: disconnectGitProvider } =
|
||||
api.application.disconnectGitProvider.useMutation();
|
||||
|
||||
const [tab, setSab] = useState<TabState>(application?.sourceType || "github");
|
||||
|
||||
const isLoading =
|
||||
isLoadingGithub || isLoadingGitlab || isLoadingBitbucket || isLoadingGitea;
|
||||
|
||||
const handleDisconnect = async () => {
|
||||
try {
|
||||
await disconnectGitProvider({ applicationId });
|
||||
toast.success("Repository disconnected successfully");
|
||||
await refetch();
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
`Failed to disconnect repository: ${
|
||||
error instanceof Error ? error.message : "Unknown error"
|
||||
}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Card className="group relative w-full bg-transparent">
|
||||
@@ -77,6 +98,38 @@ export const ShowProviderForm = ({ applicationId }: Props) => {
|
||||
);
|
||||
}
|
||||
|
||||
// Check if user doesn't have access to the current git provider
|
||||
if (
|
||||
application &&
|
||||
!application.hasGitProviderAccess &&
|
||||
application.sourceType !== "docker" &&
|
||||
application.sourceType !== "drop"
|
||||
) {
|
||||
return (
|
||||
<Card className="group relative w-full bg-transparent">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-start justify-between">
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="flex flex-col space-y-0.5">Provider</span>
|
||||
<p className="flex items-center text-sm font-normal text-muted-foreground">
|
||||
Repository connection through unauthorized provider
|
||||
</p>
|
||||
</div>
|
||||
<div className="hidden space-y-1 text-sm font-normal md:block">
|
||||
<GitBranch className="size-6 text-muted-foreground" />
|
||||
</div>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<UnauthorizedGitProvider
|
||||
service={application}
|
||||
onDisconnect={handleDisconnect}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="group relative w-full bg-transparent">
|
||||
<CardHeader>
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
import {
|
||||
BitbucketIcon,
|
||||
GitIcon,
|
||||
GiteaIcon,
|
||||
GithubIcon,
|
||||
GitlabIcon,
|
||||
} from "@/components/icons/data-tools-icons";
|
||||
import { DialogAction } from "@/components/shared/dialog-action";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import type { RouterOutputs } from "@/utils/api";
|
||||
import { AlertCircle, GitBranch, Unlink } from "lucide-react";
|
||||
|
||||
interface Props {
|
||||
service:
|
||||
| RouterOutputs["application"]["one"]
|
||||
| RouterOutputs["compose"]["one"];
|
||||
onDisconnect: () => void;
|
||||
}
|
||||
|
||||
export const UnauthorizedGitProvider = ({ service, onDisconnect }: Props) => {
|
||||
const getProviderIcon = (sourceType: string) => {
|
||||
switch (sourceType) {
|
||||
case "github":
|
||||
return <GithubIcon className="size-5 text-muted-foreground" />;
|
||||
case "gitlab":
|
||||
return <GitlabIcon className="size-5 text-muted-foreground" />;
|
||||
case "bitbucket":
|
||||
return <BitbucketIcon className="size-5 text-muted-foreground" />;
|
||||
case "gitea":
|
||||
return <GiteaIcon className="size-5 text-muted-foreground" />;
|
||||
case "git":
|
||||
return <GitIcon className="size-5 text-muted-foreground" />;
|
||||
default:
|
||||
return <GitBranch className="size-5 text-muted-foreground" />;
|
||||
}
|
||||
};
|
||||
|
||||
const getRepositoryInfo = () => {
|
||||
switch (service.sourceType) {
|
||||
case "github":
|
||||
return {
|
||||
repo: service.repository,
|
||||
branch: service.branch,
|
||||
owner: service.owner,
|
||||
};
|
||||
case "gitlab":
|
||||
return {
|
||||
repo: service.gitlabRepository,
|
||||
branch: service.gitlabBranch,
|
||||
owner: service.gitlabOwner,
|
||||
};
|
||||
case "bitbucket":
|
||||
return {
|
||||
repo: service.bitbucketRepository,
|
||||
branch: service.bitbucketBranch,
|
||||
owner: service.bitbucketOwner,
|
||||
};
|
||||
case "gitea":
|
||||
return {
|
||||
repo: service.giteaRepository,
|
||||
branch: service.giteaBranch,
|
||||
owner: service.giteaOwner,
|
||||
};
|
||||
case "git":
|
||||
return {
|
||||
repo: service.customGitUrl,
|
||||
branch: service.customGitBranch,
|
||||
owner: null,
|
||||
};
|
||||
default:
|
||||
return { repo: null, branch: null, owner: null };
|
||||
}
|
||||
};
|
||||
|
||||
const { repo, branch, owner } = getRepositoryInfo();
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Alert>
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
This application is connected to a {service.sourceType} repository
|
||||
through a git provider that you don't have access to. You can see
|
||||
basic repository information below, but cannot modify the
|
||||
configuration.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<Card className="border-dashed border-2 border-muted-foreground/20 bg-transparent">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
{getProviderIcon(service.sourceType)}
|
||||
<span className="capitalize text-sm font-medium">
|
||||
{service.sourceType} Repository
|
||||
</span>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{owner && (
|
||||
<div>
|
||||
<span className="text-sm font-medium text-muted-foreground">
|
||||
Owner:
|
||||
</span>
|
||||
<p className="text-sm">{owner}</p>
|
||||
</div>
|
||||
)}
|
||||
{repo && (
|
||||
<div>
|
||||
<span className="text-sm font-medium text-muted-foreground">
|
||||
Repository:
|
||||
</span>
|
||||
<p className="text-sm">{repo}</p>
|
||||
</div>
|
||||
)}
|
||||
{branch && (
|
||||
<div>
|
||||
<span className="text-sm font-medium text-muted-foreground">
|
||||
Branch:
|
||||
</span>
|
||||
<p className="text-sm">{branch}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="pt-4 border-t">
|
||||
<DialogAction
|
||||
title="Disconnect Repository"
|
||||
description="Are you sure you want to disconnect this repository?"
|
||||
type="default"
|
||||
onClick={async () => {
|
||||
onDisconnect();
|
||||
}}
|
||||
>
|
||||
<Button variant="secondary" className="w-full">
|
||||
<Unlink className="size-4 mr-2" />
|
||||
Disconnect Repository
|
||||
</Button>
|
||||
</DialogAction>
|
||||
<p className="text-xs text-muted-foreground mt-2">
|
||||
Disconnecting will allow you to configure a new repository with
|
||||
your own git providers.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -18,6 +18,8 @@ import { SaveGitProviderCompose } from "./save-git-provider-compose";
|
||||
import { SaveGiteaProviderCompose } from "./save-gitea-provider-compose";
|
||||
import { SaveGithubProviderCompose } from "./save-github-provider-compose";
|
||||
import { SaveGitlabProviderCompose } from "./save-gitlab-provider-compose";
|
||||
import { UnauthorizedGitProvider } from "@/components/dashboard/application/general/generic/unauthorized-git-provider";
|
||||
import { toast } from "sonner";
|
||||
|
||||
type TabState = "github" | "git" | "raw" | "gitlab" | "bitbucket" | "gitea";
|
||||
interface Props {
|
||||
@@ -34,12 +36,29 @@ export const ShowProviderFormCompose = ({ composeId }: Props) => {
|
||||
const { data: giteaProviders, isLoading: isLoadingGitea } =
|
||||
api.gitea.giteaProviders.useQuery();
|
||||
|
||||
const { data: compose } = api.compose.one.useQuery({ composeId });
|
||||
const { mutateAsync: disconnectGitProvider } =
|
||||
api.compose.disconnectGitProvider.useMutation();
|
||||
|
||||
const { data: compose, refetch } = api.compose.one.useQuery({ composeId });
|
||||
const [tab, setSab] = useState<TabState>(compose?.sourceType || "github");
|
||||
|
||||
const isLoading =
|
||||
isLoadingGithub || isLoadingGitlab || isLoadingBitbucket || isLoadingGitea;
|
||||
|
||||
const handleDisconnect = async () => {
|
||||
try {
|
||||
await disconnectGitProvider({ composeId });
|
||||
toast.success("Repository disconnected successfully");
|
||||
await refetch();
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
`Failed to disconnect repository: ${
|
||||
error instanceof Error ? error.message : "Unknown error"
|
||||
}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Card className="group relative w-full bg-transparent">
|
||||
@@ -68,6 +87,37 @@ export const ShowProviderFormCompose = ({ composeId }: Props) => {
|
||||
);
|
||||
}
|
||||
|
||||
// Check if user doesn't have access to the current git provider
|
||||
if (
|
||||
compose &&
|
||||
!compose.hasGitProviderAccess &&
|
||||
compose.sourceType !== "raw"
|
||||
) {
|
||||
return (
|
||||
<Card className="group relative w-full bg-transparent">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-start justify-between">
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="flex flex-col space-y-0.5">Provider</span>
|
||||
<p className="flex items-center text-sm font-normal text-muted-foreground">
|
||||
Repository connection through unauthorized provider
|
||||
</p>
|
||||
</div>
|
||||
<div className="hidden space-y-1 text-sm font-normal md:block">
|
||||
<GitBranch className="size-6 text-muted-foreground" />
|
||||
</div>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<UnauthorizedGitProvider
|
||||
service={compose}
|
||||
onDisconnect={handleDisconnect}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="group relative w-full bg-transparent">
|
||||
<CardHeader>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// @ts-nocheck
|
||||
|
||||
export const extractExpirationDate = (certData: string): Date | null => {
|
||||
try {
|
||||
// Decode PEM base64 to DER binary
|
||||
@@ -12,11 +14,13 @@ export const extractExpirationDate = (certData: string): Date | null => {
|
||||
|
||||
// Helper: read ASN.1 length field
|
||||
function readLength(pos: number): { length: number; offset: number } {
|
||||
// biome-ignore lint/style/noParameterAssign: <explanation>
|
||||
let len = der[pos++];
|
||||
if (len & 0x80) {
|
||||
const bytes = len & 0x7f;
|
||||
len = 0;
|
||||
for (let i = 0; i < bytes; i++) {
|
||||
// biome-ignore lint/style/noParameterAssign: <explanation>
|
||||
len = (len << 8) + der[pos++];
|
||||
}
|
||||
}
|
||||
@@ -68,19 +72,19 @@ export const extractExpirationDate = (certData: string): Date | null => {
|
||||
function parseTime(str: string): Date {
|
||||
if (str.length === 13) {
|
||||
// UTCTime YYMMDDhhmmssZ
|
||||
const year = parseInt(str.slice(0, 2), 10);
|
||||
const year = Number.parseInt(str.slice(0, 2), 10);
|
||||
const fullYear = year < 50 ? 2000 + year : 1900 + year;
|
||||
return new Date(
|
||||
`${fullYear}-${str.slice(2, 4)}-${str.slice(4, 6)}T${str.slice(6, 8)}:${str.slice(8, 10)}:${str.slice(10, 12)}Z`,
|
||||
);
|
||||
} else if (str.length === 15) {
|
||||
}
|
||||
if (str.length === 15) {
|
||||
// GeneralizedTime YYYYMMDDhhmmssZ
|
||||
return new Date(
|
||||
`${str.slice(0, 4)}-${str.slice(4, 6)}-${str.slice(6, 8)}T${str.slice(8, 10)}:${str.slice(10, 12)}:${str.slice(12, 14)}Z`,
|
||||
);
|
||||
} else {
|
||||
throw new Error("Invalid ASN.1 time format");
|
||||
}
|
||||
throw new Error("Invalid ASN.1 time format");
|
||||
}
|
||||
|
||||
return parseTime(notAfterStr);
|
||||
|
||||
@@ -18,6 +18,7 @@ import { useEffect, useState } from "react";
|
||||
export const AddGithubProvider = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { data: activeOrganization } = authClient.useActiveOrganization();
|
||||
const { data: session } = authClient.useSession();
|
||||
const { data } = api.user.get.useQuery();
|
||||
const [manifest, setManifest] = useState("");
|
||||
const [isOrganization, setIsOrganization] = useState(false);
|
||||
@@ -27,7 +28,7 @@ export const AddGithubProvider = () => {
|
||||
const url = document.location.origin;
|
||||
const manifest = JSON.stringify(
|
||||
{
|
||||
redirect_url: `${origin}/api/providers/github/setup?organizationId=${activeOrganization?.id}`,
|
||||
redirect_url: `${origin}/api/providers/github/setup?organizationId=${activeOrganization?.id}&userId=${session?.user?.id}`,
|
||||
name: `Dokploy-${format(new Date(), "yyyy-MM-dd")}`,
|
||||
url: origin,
|
||||
hook_attributes: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { api } from "@/utils/api";
|
||||
import { ImpersonationBar } from "../dashboard/impersonation/impersonation-bar";
|
||||
import Page from "./side";
|
||||
import { ChatwootWidget } from "../shared/ChatwootWidget";
|
||||
import Page from "./side";
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
|
||||
15
apps/dokploy/drizzle/0094_numerous_carmella_unuscione.sql
Normal file
15
apps/dokploy/drizzle/0094_numerous_carmella_unuscione.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
ALTER TABLE "git_provider" ADD COLUMN "userId" text;--> statement-breakpoint
|
||||
|
||||
-- Update existing git providers to be owned by the organization owner
|
||||
-- We need to get the account.user_id for the organization owner
|
||||
UPDATE "git_provider"
|
||||
SET "userId" = (
|
||||
SELECT a.user_id
|
||||
FROM "organization" o
|
||||
JOIN "account" a ON o."owner_id" = a.user_id
|
||||
WHERE o.id = "git_provider"."organizationId"
|
||||
);--> statement-breakpoint
|
||||
|
||||
-- Now make the column NOT NULL since all rows should have values
|
||||
ALTER TABLE "git_provider" ALTER COLUMN "userId" SET NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "git_provider" ADD CONSTRAINT "git_provider_userId_user_temp_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user_temp"("id") ON DELETE cascade ON UPDATE no action;
|
||||
5737
apps/dokploy/drizzle/meta/0094_snapshot.json
Normal file
5737
apps/dokploy/drizzle/meta/0094_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -659,6 +659,13 @@
|
||||
"when": 1750397258622,
|
||||
"tag": "0093_nice_gorilla_man",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 94,
|
||||
"version": "7",
|
||||
"when": 1750559214977,
|
||||
"tag": "0094_numerous_carmella_unuscione",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -10,13 +10,14 @@ type Query = {
|
||||
state: string;
|
||||
installation_id: string;
|
||||
setup_action: string;
|
||||
userId: string;
|
||||
};
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
) {
|
||||
const { code, state, installation_id }: Query = req.query as Query;
|
||||
const { code, state, installation_id, userId }: Query = req.query as Query;
|
||||
|
||||
if (!code) {
|
||||
return res.status(400).json({ error: "Missing code parameter" });
|
||||
@@ -44,6 +45,7 @@ export default async function handler(
|
||||
githubPrivateKey: data.pem,
|
||||
},
|
||||
value as string,
|
||||
userId,
|
||||
);
|
||||
} else if (action === "gh_setup") {
|
||||
await db
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
createApplication,
|
||||
deleteAllMiddlewares,
|
||||
findApplicationById,
|
||||
findGitProviderById,
|
||||
findProjectById,
|
||||
getApplicationStats,
|
||||
mechanizeDockerContainer,
|
||||
@@ -126,7 +127,45 @@ export const applicationRouter = createTRPCRouter({
|
||||
message: "You are not authorized to access this application",
|
||||
});
|
||||
}
|
||||
return application;
|
||||
|
||||
let hasGitProviderAccess = true;
|
||||
let unauthorizedProvider: string | null = null;
|
||||
|
||||
const getGitProviderId = () => {
|
||||
switch (application.sourceType) {
|
||||
case "github":
|
||||
return application.github?.gitProviderId;
|
||||
case "gitlab":
|
||||
return application.gitlab?.gitProviderId;
|
||||
case "bitbucket":
|
||||
return application.bitbucket?.gitProviderId;
|
||||
case "gitea":
|
||||
return application.gitea?.gitProviderId;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const gitProviderId = getGitProviderId();
|
||||
|
||||
if (gitProviderId) {
|
||||
try {
|
||||
const gitProvider = await findGitProviderById(gitProviderId);
|
||||
if (gitProvider.userId !== ctx.session.userId) {
|
||||
hasGitProviderAccess = false;
|
||||
unauthorizedProvider = application.sourceType;
|
||||
}
|
||||
} catch {
|
||||
hasGitProviderAccess = false;
|
||||
unauthorizedProvider = application.sourceType;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...application,
|
||||
hasGitProviderAccess,
|
||||
unauthorizedProvider,
|
||||
};
|
||||
}),
|
||||
|
||||
reload: protectedProcedure
|
||||
@@ -488,6 +527,67 @@ export const applicationRouter = createTRPCRouter({
|
||||
enableSubmodules: input.enableSubmodules,
|
||||
});
|
||||
|
||||
return true;
|
||||
}),
|
||||
disconnectGitProvider: protectedProcedure
|
||||
.input(apiFindOneApplication)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const application = await findApplicationById(input.applicationId);
|
||||
if (
|
||||
application.project.organizationId !== ctx.session.activeOrganizationId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to disconnect this git provider",
|
||||
});
|
||||
}
|
||||
|
||||
// Reset all git provider related fields
|
||||
await updateApplication(input.applicationId, {
|
||||
// GitHub fields
|
||||
repository: null,
|
||||
branch: null,
|
||||
owner: null,
|
||||
buildPath: "/",
|
||||
githubId: null,
|
||||
triggerType: "push",
|
||||
|
||||
// GitLab fields
|
||||
gitlabRepository: null,
|
||||
gitlabOwner: null,
|
||||
gitlabBranch: null,
|
||||
gitlabBuildPath: null,
|
||||
gitlabId: null,
|
||||
gitlabProjectId: null,
|
||||
gitlabPathNamespace: null,
|
||||
|
||||
// Bitbucket fields
|
||||
bitbucketRepository: null,
|
||||
bitbucketOwner: null,
|
||||
bitbucketBranch: null,
|
||||
bitbucketBuildPath: null,
|
||||
bitbucketId: null,
|
||||
|
||||
// Gitea fields
|
||||
giteaRepository: null,
|
||||
giteaOwner: null,
|
||||
giteaBranch: null,
|
||||
giteaBuildPath: null,
|
||||
giteaId: null,
|
||||
|
||||
// Custom Git fields
|
||||
customGitBranch: null,
|
||||
customGitBuildPath: null,
|
||||
customGitUrl: null,
|
||||
customGitSSHKeyId: null,
|
||||
|
||||
// Common fields
|
||||
sourceType: "github", // Reset to default
|
||||
applicationStatus: "idle",
|
||||
watchPaths: null,
|
||||
enableSubmodules: false,
|
||||
});
|
||||
|
||||
return true;
|
||||
}),
|
||||
markRunning: protectedProcedure
|
||||
|
||||
@@ -22,7 +22,11 @@ export const bitbucketRouter = createTRPCRouter({
|
||||
.input(apiCreateBitbucket)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
return await createBitbucket(input, ctx.session.activeOrganizationId);
|
||||
return await createBitbucket(
|
||||
input,
|
||||
ctx.session.activeOrganizationId,
|
||||
ctx.session.userId,
|
||||
);
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
@@ -37,7 +41,8 @@ export const bitbucketRouter = createTRPCRouter({
|
||||
const bitbucketProvider = await findBitbucketById(input.bitbucketId);
|
||||
if (
|
||||
bitbucketProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
bitbucketProvider.gitProvider.userId !== ctx.session.userId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
@@ -56,11 +61,13 @@ export const bitbucketRouter = createTRPCRouter({
|
||||
},
|
||||
});
|
||||
|
||||
result = result.filter(
|
||||
(provider) =>
|
||||
result = result.filter((provider) => {
|
||||
return (
|
||||
provider.gitProvider.organizationId ===
|
||||
ctx.session.activeOrganizationId,
|
||||
);
|
||||
ctx.session.activeOrganizationId &&
|
||||
provider.gitProvider.userId === ctx.session.userId
|
||||
);
|
||||
});
|
||||
return result;
|
||||
}),
|
||||
|
||||
@@ -70,7 +77,8 @@ export const bitbucketRouter = createTRPCRouter({
|
||||
const bitbucketProvider = await findBitbucketById(input.bitbucketId);
|
||||
if (
|
||||
bitbucketProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
bitbucketProvider.gitProvider.userId !== ctx.session.userId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
@@ -87,7 +95,8 @@ export const bitbucketRouter = createTRPCRouter({
|
||||
);
|
||||
if (
|
||||
bitbucketProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
bitbucketProvider.gitProvider.userId !== ctx.session.userId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
@@ -103,7 +112,8 @@ export const bitbucketRouter = createTRPCRouter({
|
||||
const bitbucketProvider = await findBitbucketById(input.bitbucketId);
|
||||
if (
|
||||
bitbucketProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
bitbucketProvider.gitProvider.userId !== ctx.session.userId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
@@ -126,7 +136,8 @@ export const bitbucketRouter = createTRPCRouter({
|
||||
const bitbucketProvider = await findBitbucketById(input.bitbucketId);
|
||||
if (
|
||||
bitbucketProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
bitbucketProvider.gitProvider.userId !== ctx.session.userId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
deleteMount,
|
||||
findComposeById,
|
||||
findDomainsByComposeId,
|
||||
findGitProviderById,
|
||||
findProjectById,
|
||||
findServerById,
|
||||
findUserById,
|
||||
@@ -119,7 +120,45 @@ export const composeRouter = createTRPCRouter({
|
||||
message: "You are not authorized to access this compose",
|
||||
});
|
||||
}
|
||||
return compose;
|
||||
|
||||
let hasGitProviderAccess = true;
|
||||
let unauthorizedProvider: string | null = null;
|
||||
|
||||
const getGitProviderId = () => {
|
||||
switch (compose.sourceType) {
|
||||
case "github":
|
||||
return compose.github?.gitProviderId;
|
||||
case "gitlab":
|
||||
return compose.gitlab?.gitProviderId;
|
||||
case "bitbucket":
|
||||
return compose.bitbucket?.gitProviderId;
|
||||
case "gitea":
|
||||
return compose.gitea?.gitProviderId;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const gitProviderId = getGitProviderId();
|
||||
|
||||
if (gitProviderId) {
|
||||
try {
|
||||
const gitProvider = await findGitProviderById(gitProviderId);
|
||||
if (gitProvider.userId !== ctx.session.userId) {
|
||||
hasGitProviderAccess = false;
|
||||
unauthorizedProvider = compose.sourceType;
|
||||
}
|
||||
} catch {
|
||||
hasGitProviderAccess = false;
|
||||
unauthorizedProvider = compose.sourceType;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...compose,
|
||||
hasGitProviderAccess,
|
||||
unauthorizedProvider,
|
||||
};
|
||||
}),
|
||||
|
||||
update: protectedProcedure
|
||||
@@ -526,6 +565,61 @@ export const composeRouter = createTRPCRouter({
|
||||
const uniqueTags = _.uniq(allTags);
|
||||
return uniqueTags;
|
||||
}),
|
||||
disconnectGitProvider: protectedProcedure
|
||||
.input(apiFindCompose)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const compose = await findComposeById(input.composeId);
|
||||
if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to disconnect this git provider",
|
||||
});
|
||||
}
|
||||
|
||||
// Reset all git provider related fields
|
||||
await updateCompose(input.composeId, {
|
||||
// GitHub fields
|
||||
repository: null,
|
||||
branch: null,
|
||||
owner: null,
|
||||
composePath: undefined,
|
||||
githubId: null,
|
||||
triggerType: "push",
|
||||
|
||||
// GitLab fields
|
||||
gitlabRepository: null,
|
||||
gitlabOwner: null,
|
||||
gitlabBranch: null,
|
||||
gitlabId: null,
|
||||
gitlabProjectId: null,
|
||||
gitlabPathNamespace: null,
|
||||
|
||||
// Bitbucket fields
|
||||
bitbucketRepository: null,
|
||||
bitbucketOwner: null,
|
||||
bitbucketBranch: null,
|
||||
bitbucketId: null,
|
||||
|
||||
// Gitea fields
|
||||
giteaRepository: null,
|
||||
giteaOwner: null,
|
||||
giteaBranch: null,
|
||||
giteaId: null,
|
||||
|
||||
// Custom Git fields
|
||||
customGitBranch: null,
|
||||
customGitUrl: null,
|
||||
customGitSSHKeyId: null,
|
||||
|
||||
// Common fields
|
||||
sourceType: "github", // Reset to default
|
||||
composeStatus: "idle",
|
||||
watchPaths: null,
|
||||
enableSubmodules: false,
|
||||
});
|
||||
|
||||
return true;
|
||||
}),
|
||||
|
||||
move: protectedProcedure
|
||||
.input(
|
||||
|
||||
@@ -3,7 +3,7 @@ import { db } from "@/server/db";
|
||||
import { apiRemoveGitProvider, gitProvider } from "@/server/db/schema";
|
||||
import { findGitProviderById, removeGitProvider } from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
import { and, desc, eq } from "drizzle-orm";
|
||||
|
||||
export const gitProviderRouter = createTRPCRouter({
|
||||
getAll: protectedProcedure.query(async ({ ctx }) => {
|
||||
@@ -15,7 +15,10 @@ export const gitProviderRouter = createTRPCRouter({
|
||||
gitea: true,
|
||||
},
|
||||
orderBy: desc(gitProvider.createdAt),
|
||||
where: eq(gitProvider.organizationId, ctx.session.activeOrganizationId),
|
||||
where: and(
|
||||
eq(gitProvider.userId, ctx.session.userId),
|
||||
eq(gitProvider.organizationId, ctx.session.activeOrganizationId),
|
||||
),
|
||||
});
|
||||
}),
|
||||
remove: protectedProcedure
|
||||
|
||||
@@ -26,7 +26,11 @@ export const giteaRouter = createTRPCRouter({
|
||||
.input(apiCreateGitea)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
return await createGitea(input, ctx.session.activeOrganizationId);
|
||||
return await createGitea(
|
||||
input,
|
||||
ctx.session.activeOrganizationId,
|
||||
ctx.session.userId,
|
||||
);
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
@@ -42,7 +46,8 @@ export const giteaRouter = createTRPCRouter({
|
||||
const giteaProvider = await findGiteaById(input.giteaId);
|
||||
if (
|
||||
giteaProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
giteaProvider.gitProvider.userId !== ctx.session.userId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
@@ -62,7 +67,8 @@ export const giteaRouter = createTRPCRouter({
|
||||
result = result.filter(
|
||||
(provider) =>
|
||||
provider.gitProvider.organizationId ===
|
||||
ctx.session.activeOrganizationId,
|
||||
ctx.session.activeOrganizationId &&
|
||||
provider.gitProvider.userId === ctx.session.userId,
|
||||
);
|
||||
|
||||
const filtered = result
|
||||
@@ -94,7 +100,8 @@ export const giteaRouter = createTRPCRouter({
|
||||
const giteaProvider = await findGiteaById(giteaId);
|
||||
if (
|
||||
giteaProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
giteaProvider.gitProvider.userId !== ctx.session.userId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
@@ -130,7 +137,8 @@ export const giteaRouter = createTRPCRouter({
|
||||
const giteaProvider = await findGiteaById(giteaId);
|
||||
if (
|
||||
giteaProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
giteaProvider.gitProvider.userId !== ctx.session.userId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
@@ -162,7 +170,8 @@ export const giteaRouter = createTRPCRouter({
|
||||
const giteaProvider = await findGiteaById(giteaId);
|
||||
if (
|
||||
giteaProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
giteaProvider.gitProvider.userId !== ctx.session.userId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
@@ -190,7 +199,8 @@ export const giteaRouter = createTRPCRouter({
|
||||
const giteaProvider = await findGiteaById(input.giteaId);
|
||||
if (
|
||||
giteaProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
giteaProvider.gitProvider.userId !== ctx.session.userId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
@@ -231,7 +241,8 @@ export const giteaRouter = createTRPCRouter({
|
||||
const giteaProvider = await findGiteaById(giteaId);
|
||||
if (
|
||||
giteaProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
giteaProvider.gitProvider.userId !== ctx.session.userId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
|
||||
@@ -21,7 +21,8 @@ export const githubRouter = createTRPCRouter({
|
||||
const githubProvider = await findGithubById(input.githubId);
|
||||
if (
|
||||
githubProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
githubProvider.gitProvider.userId === ctx.session.userId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
@@ -36,7 +37,8 @@ export const githubRouter = createTRPCRouter({
|
||||
const githubProvider = await findGithubById(input.githubId);
|
||||
if (
|
||||
githubProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
githubProvider.gitProvider.userId === ctx.session.userId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
@@ -51,7 +53,8 @@ export const githubRouter = createTRPCRouter({
|
||||
const githubProvider = await findGithubById(input.githubId || "");
|
||||
if (
|
||||
githubProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
githubProvider.gitProvider.userId === ctx.session.userId
|
||||
) {
|
||||
//TODO: Remove this line when the cloud version is ready
|
||||
throw new TRPCError({
|
||||
@@ -71,7 +74,8 @@ export const githubRouter = createTRPCRouter({
|
||||
result = result.filter(
|
||||
(provider) =>
|
||||
provider.gitProvider.organizationId ===
|
||||
ctx.session.activeOrganizationId,
|
||||
ctx.session.activeOrganizationId &&
|
||||
provider.gitProvider.userId === ctx.session.userId,
|
||||
);
|
||||
|
||||
const filtered = result
|
||||
@@ -95,7 +99,8 @@ export const githubRouter = createTRPCRouter({
|
||||
const githubProvider = await findGithubById(input.githubId);
|
||||
if (
|
||||
githubProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
githubProvider.gitProvider.userId === ctx.session.userId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
@@ -117,7 +122,8 @@ export const githubRouter = createTRPCRouter({
|
||||
const githubProvider = await findGithubById(input.githubId);
|
||||
if (
|
||||
githubProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
githubProvider.gitProvider.userId === ctx.session.userId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
|
||||
@@ -25,7 +25,11 @@ export const gitlabRouter = createTRPCRouter({
|
||||
.input(apiCreateGitlab)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
return await createGitlab(input, ctx.session.activeOrganizationId);
|
||||
return await createGitlab(
|
||||
input,
|
||||
ctx.session.activeOrganizationId,
|
||||
ctx.session.userId,
|
||||
);
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
@@ -40,7 +44,8 @@ export const gitlabRouter = createTRPCRouter({
|
||||
const gitlabProvider = await findGitlabById(input.gitlabId);
|
||||
if (
|
||||
gitlabProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
gitlabProvider.gitProvider.userId !== ctx.session.userId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
@@ -56,11 +61,13 @@ export const gitlabRouter = createTRPCRouter({
|
||||
},
|
||||
});
|
||||
|
||||
result = result.filter(
|
||||
(provider) =>
|
||||
result = result.filter((provider) => {
|
||||
return (
|
||||
provider.gitProvider.organizationId ===
|
||||
ctx.session.activeOrganizationId,
|
||||
);
|
||||
ctx.session.activeOrganizationId &&
|
||||
provider.gitProvider.userId === ctx.session.userId
|
||||
);
|
||||
});
|
||||
const filtered = result
|
||||
.filter((provider) => haveGitlabRequirements(provider))
|
||||
.map((provider) => {
|
||||
@@ -80,7 +87,8 @@ export const gitlabRouter = createTRPCRouter({
|
||||
const gitlabProvider = await findGitlabById(input.gitlabId);
|
||||
if (
|
||||
gitlabProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
gitlabProvider.gitProvider.userId !== ctx.session.userId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
@@ -96,7 +104,8 @@ export const gitlabRouter = createTRPCRouter({
|
||||
const gitlabProvider = await findGitlabById(input.gitlabId || "");
|
||||
if (
|
||||
gitlabProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
gitlabProvider.gitProvider.userId !== ctx.session.userId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
@@ -112,7 +121,8 @@ export const gitlabRouter = createTRPCRouter({
|
||||
const gitlabProvider = await findGitlabById(input.gitlabId || "");
|
||||
if (
|
||||
gitlabProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
gitlabProvider.gitProvider.userId !== ctx.session.userId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
@@ -135,7 +145,8 @@ export const gitlabRouter = createTRPCRouter({
|
||||
const gitlabProvider = await findGitlabById(input.gitlabId);
|
||||
if (
|
||||
gitlabProvider.gitProvider.organizationId !==
|
||||
ctx.session.activeOrganizationId
|
||||
ctx.session.activeOrganizationId &&
|
||||
gitlabProvider.gitProvider.userId !== ctx.session.userId
|
||||
) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
|
||||
@@ -8,6 +8,7 @@ import { bitbucket } from "./bitbucket";
|
||||
import { gitea } from "./gitea";
|
||||
import { github } from "./github";
|
||||
import { gitlab } from "./gitlab";
|
||||
import { users_temp } from "./user";
|
||||
|
||||
export const gitProviderType = pgEnum("gitProviderType", [
|
||||
"github",
|
||||
@@ -29,6 +30,9 @@ export const gitProvider = pgTable("git_provider", {
|
||||
organizationId: text("organizationId")
|
||||
.notNull()
|
||||
.references(() => organization.id, { onDelete: "cascade" }),
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users_temp.id, { onDelete: "cascade" }),
|
||||
});
|
||||
|
||||
export const gitProviderRelations = relations(gitProvider, ({ one }) => ({
|
||||
@@ -52,6 +56,10 @@ export const gitProviderRelations = relations(gitProvider, ({ one }) => ({
|
||||
fields: [gitProvider.organizationId],
|
||||
references: [organization.id],
|
||||
}),
|
||||
user: one(users_temp, {
|
||||
fields: [gitProvider.userId],
|
||||
references: [users_temp.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
const createSchema = createInsertSchema(gitProvider);
|
||||
|
||||
@@ -13,6 +13,7 @@ export type Bitbucket = typeof bitbucket.$inferSelect;
|
||||
export const createBitbucket = async (
|
||||
input: typeof apiCreateBitbucket._type,
|
||||
organizationId: string,
|
||||
userId: string,
|
||||
) => {
|
||||
return await db.transaction(async (tx) => {
|
||||
const newGitProvider = await tx
|
||||
@@ -21,6 +22,7 @@ export const createBitbucket = async (
|
||||
providerType: "bitbucket",
|
||||
organizationId: organizationId,
|
||||
name: input.name,
|
||||
userId: userId,
|
||||
})
|
||||
.returning()
|
||||
.then((response) => response[0]);
|
||||
|
||||
@@ -12,6 +12,7 @@ export type Gitea = typeof gitea.$inferSelect;
|
||||
export const createGitea = async (
|
||||
input: typeof apiCreateGitea._type,
|
||||
organizationId: string,
|
||||
userId: string,
|
||||
) => {
|
||||
return await db.transaction(async (tx) => {
|
||||
const newGitProvider = await tx
|
||||
@@ -20,6 +21,7 @@ export const createGitea = async (
|
||||
providerType: "gitea",
|
||||
organizationId: organizationId,
|
||||
name: input.name,
|
||||
userId: userId,
|
||||
})
|
||||
.returning()
|
||||
.then((response) => response[0]);
|
||||
|
||||
@@ -13,6 +13,7 @@ export type Github = typeof github.$inferSelect;
|
||||
export const createGithub = async (
|
||||
input: typeof apiCreateGithub._type,
|
||||
organizationId: string,
|
||||
userId: string,
|
||||
) => {
|
||||
return await db.transaction(async (tx) => {
|
||||
const newGitProvider = await tx
|
||||
@@ -21,6 +22,7 @@ export const createGithub = async (
|
||||
providerType: "github",
|
||||
organizationId: organizationId,
|
||||
name: input.name,
|
||||
userId: userId,
|
||||
})
|
||||
.returning()
|
||||
.then((response) => response[0]);
|
||||
|
||||
@@ -12,6 +12,7 @@ export type Gitlab = typeof gitlab.$inferSelect;
|
||||
export const createGitlab = async (
|
||||
input: typeof apiCreateGitlab._type,
|
||||
organizationId: string,
|
||||
userId: string,
|
||||
) => {
|
||||
return await db.transaction(async (tx) => {
|
||||
const newGitProvider = await tx
|
||||
@@ -20,6 +21,7 @@ export const createGitlab = async (
|
||||
providerType: "gitlab",
|
||||
organizationId: organizationId,
|
||||
name: input.name,
|
||||
userId: userId,
|
||||
})
|
||||
.returning()
|
||||
.then((response) => response[0]);
|
||||
|
||||
Reference in New Issue
Block a user