From 8abeae5e633119c610db4ce01b9058ca1a5bc95f Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Thu, 3 Oct 2024 19:34:38 -0600 Subject: [PATCH] refactor(cloud): validate all the routes to prevent get access from private resource --- apps/dokploy/server/api/routers/admin.ts | 35 ++- .../dokploy/server/api/routers/application.ts | 202 +++++++++++++++--- apps/dokploy/server/api/routers/compose.ts | 123 +++++++++-- .../dokploy/server/api/routers/destination.ts | 62 ++++-- apps/dokploy/server/api/routers/mariadb.ts | 74 ++++++- apps/dokploy/server/api/routers/mongo.ts | 85 +++++++- apps/dokploy/server/api/routers/mysql.ts | 82 ++++++- apps/dokploy/server/api/routers/postgres.ts | 85 +++++++- apps/dokploy/server/api/routers/project.ts | 55 +++-- apps/dokploy/server/api/routers/redis.ts | 80 ++++++- apps/dokploy/server/api/routers/user.ts | 23 +- packages/builders/src/db/schema/bitbucket.ts | 1 + packages/builders/src/services/admin.ts | 5 +- packages/builders/src/services/auth.ts | 17 +- packages/builders/src/services/github.ts | 7 +- packages/builders/src/services/gitlab.ts | 7 +- packages/builders/src/services/registry.ts | 16 +- 17 files changed, 796 insertions(+), 163 deletions(-) diff --git a/apps/dokploy/server/api/routers/admin.ts b/apps/dokploy/server/api/routers/admin.ts index 304102d5..3f40e3f8 100644 --- a/apps/dokploy/server/api/routers/admin.ts +++ b/apps/dokploy/server/api/routers/admin.ts @@ -10,16 +10,18 @@ import { import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; import { - findAdmin, createInvitation, getUserByToken, removeUserByAuthId, + findAdminById, + findUserByAuthId, + findUserById, } from "@dokploy/builders"; import { adminProcedure, createTRPCRouter, publicProcedure } from "../trpc"; export const adminRouter = createTRPCRouter({ one: adminProcedure.query(async ({ ctx }) => { - const { sshPrivateKey, ...rest } = await findAdmin(); + const { sshPrivateKey, ...rest } = await findAdminById(ctx.user.adminId); return { haveSSH: !!sshPrivateKey, ...rest, @@ -27,9 +29,9 @@ export const adminRouter = createTRPCRouter({ }), createUserInvitation: adminProcedure .input(apiCreateUserInvitation) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { try { - await createInvitation(input); + await createInvitation(input, ctx.user.adminId); } catch (error) { throw new TRPCError({ code: "BAD_REQUEST", @@ -41,8 +43,16 @@ export const adminRouter = createTRPCRouter({ }), removeUser: adminProcedure .input(apiRemoveUser) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { try { + const user = await findUserByAuthId(input.authId); + + if (user.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not allowed to delete this user", + }); + } return await removeUserByAuthId(input.authId); } catch (error) { throw new TRPCError({ @@ -59,8 +69,16 @@ export const adminRouter = createTRPCRouter({ }), assignPermissions: adminProcedure .input(apiAssignPermissions) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { try { + const user = await findUserById(input.userId); + + if (user.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not allowed to assign permissions", + }); + } await db .update(users) .set({ @@ -68,10 +86,7 @@ export const adminRouter = createTRPCRouter({ }) .where(eq(users.userId, input.userId)); } catch (error) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to assign permissions", - }); + throw error; } }), }); diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts index 23056e1f..85588881 100644 --- a/apps/dokploy/server/api/routers/application.ts +++ b/apps/dokploy/server/api/routers/application.ts @@ -47,6 +47,7 @@ import { removeDeployments, addNewService, checkServiceAccess, + IS_CLOUD, // uploadFileSchema } from "@dokploy/builders"; import { uploadFileSchema } from "@/utils/schema"; @@ -89,13 +90,26 @@ export const applicationRouter = createTRPCRouter({ "access", ); } - return await findApplicationById(input.applicationId); + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this application", + }); + } + return application; }), reload: protectedProcedure .input(apiReloadApplication) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to reload this application", + }); + } if (application.serverId) { await stopServiceRemote(application.serverId, input.appName); } else { @@ -124,6 +138,13 @@ export const applicationRouter = createTRPCRouter({ } const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to delete this application", + }); + } + const result = await db .delete(applications) .where(eq(applications.applicationId, input.applicationId)) @@ -156,8 +177,14 @@ export const applicationRouter = createTRPCRouter({ stop: protectedProcedure .input(apiFindOneApplication) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const service = await findApplicationById(input.applicationId); + if (service.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to stop this application", + }); + } if (service.serverId) { await stopServiceRemote(service.serverId, service.appName); } else { @@ -170,8 +197,15 @@ export const applicationRouter = createTRPCRouter({ start: protectedProcedure .input(apiFindOneApplication) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const service = await findApplicationById(input.applicationId); + if (service.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to start this application", + }); + } + if (service.serverId) { await startServiceRemote(service.serverId, service.appName); } else { @@ -184,8 +218,14 @@ export const applicationRouter = createTRPCRouter({ redeploy: protectedProcedure .input(apiFindOneApplication) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to redeploy this application", + }); + } const jobData: DeploymentJob = { applicationId: input.applicationId, titleLog: "Rebuild deployment", @@ -205,7 +245,14 @@ export const applicationRouter = createTRPCRouter({ }), saveEnvironment: protectedProcedure .input(apiSaveEnvironmentVariables) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this environment", + }); + } await updateApplication(input.applicationId, { env: input.env, buildArgs: input.buildArgs, @@ -214,7 +261,14 @@ export const applicationRouter = createTRPCRouter({ }), saveBuildType: protectedProcedure .input(apiSaveBuildType) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this build type", + }); + } await updateApplication(input.applicationId, { buildType: input.buildType, dockerfile: input.dockerfile, @@ -227,7 +281,14 @@ export const applicationRouter = createTRPCRouter({ }), saveGithubProvider: protectedProcedure .input(apiSaveGithubProvider) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this github provider", + }); + } await updateApplication(input.applicationId, { repository: input.repository, branch: input.branch, @@ -242,7 +303,14 @@ export const applicationRouter = createTRPCRouter({ }), saveGitlabProvider: protectedProcedure .input(apiSaveGitlabProvider) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this gitlab provider", + }); + } await updateApplication(input.applicationId, { gitlabRepository: input.gitlabRepository, gitlabOwner: input.gitlabOwner, @@ -259,7 +327,14 @@ export const applicationRouter = createTRPCRouter({ }), saveBitbucketProvider: protectedProcedure .input(apiSaveBitbucketProvider) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this bitbucket provider", + }); + } await updateApplication(input.applicationId, { bitbucketRepository: input.bitbucketRepository, bitbucketOwner: input.bitbucketOwner, @@ -274,7 +349,14 @@ export const applicationRouter = createTRPCRouter({ }), saveDockerProvider: protectedProcedure .input(apiSaveDockerProvider) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this docker provider", + }); + } await updateApplication(input.applicationId, { dockerImage: input.dockerImage, username: input.username, @@ -287,7 +369,14 @@ export const applicationRouter = createTRPCRouter({ }), saveGitProdiver: protectedProcedure .input(apiSaveGitProvider) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this git provider", + }); + } await updateApplication(input.applicationId, { customGitBranch: input.customGitBranch, customGitBuildPath: input.customGitBuildPath, @@ -301,18 +390,32 @@ export const applicationRouter = createTRPCRouter({ }), markRunning: protectedProcedure .input(apiFindOneApplication) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to mark this application as running", + }); + } await updateApplicationStatus(input.applicationId, "running"); }), update: protectedProcedure .input(apiUpdateApplication) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to update this application", + }); + } const { applicationId, ...rest } = input; - const application = await updateApplication(applicationId, { + const updateApp = await updateApplication(applicationId, { ...rest, }); - if (!application) { + if (!updateApp) { throw new TRPCError({ code: "BAD_REQUEST", message: "Update: Error to update application", @@ -323,7 +426,14 @@ export const applicationRouter = createTRPCRouter({ }), refreshToken: protectedProcedure .input(apiFindOneApplication) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to refresh this application", + }); + } await updateApplication(input.applicationId, { refreshToken: nanoid(), }); @@ -333,6 +443,12 @@ export const applicationRouter = createTRPCRouter({ .input(apiFindOneApplication) .mutation(async ({ input, ctx }) => { const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to deploy this application", + }); + } const jobData: DeploymentJob = { applicationId: input.applicationId, titleLog: "Manual deployment", @@ -353,14 +469,28 @@ export const applicationRouter = createTRPCRouter({ cleanQueues: protectedProcedure .input(apiFindOneApplication) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to clean this application", + }); + } await cleanQueuesByApplication(input.applicationId); }), readTraefikConfig: protectedProcedure .input(apiFindOneApplication) - .query(async ({ input }) => { + .query(async ({ input, ctx }) => { const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to read this application", + }); + } + let traefikConfig = null; if (application.serverId) { traefikConfig = await readRemoteConfig( @@ -384,15 +514,23 @@ export const applicationRouter = createTRPCRouter({ }) .use(uploadProcedure) .input(uploadFileSchema) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const zipFile = input.zip; + const app = await findApplicationById(input.applicationId as string); + + if (app.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to deploy this application", + }); + } + updateApplication(input.applicationId as string, { sourceType: "drop", dropBuildPath: input.dropBuildPath, }); - const app = await findApplicationById(input.applicationId as string); await unzipDrop(zipFile, app); const jobData: DeploymentJob = { @@ -415,9 +553,16 @@ export const applicationRouter = createTRPCRouter({ }), updateTraefikConfig: protectedProcedure .input(z.object({ applicationId: z.string(), traefikConfig: z.string() })) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const application = await findApplicationById(input.applicationId); + if (application.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to update this application", + }); + } + if (application.serverId) { await writeConfigRemote( application.serverId, @@ -431,14 +576,15 @@ export const applicationRouter = createTRPCRouter({ }), readAppMonitoring: protectedProcedure .input(apiFindMonitoringStats) - .query(async ({ input }) => { + .query(async ({ input, ctx }) => { + if (IS_CLOUD) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "Functionality not available in cloud version", + }); + } const stats = await getApplicationStats(input.appName); return stats; }), }); - -// Paketo Buildpacks: paketobuildpacks/builder-jammy-full Ubuntu 22.04 Jammy Jellyfish full image with buildpacks for Apache HTTPD, Go, Java, Java Native Image, .NET, NGINX, Node.js, PHP, Procfile, Python, and Ruby -// Heroku: heroku/builder:22 Heroku-22 (Ubuntu 22.04) base image with buildpacks for Go, Java, Node.js, PHP, Python, Ruby & Scala. -// pack build imageName --path ./ --builder paketobuildpacks/builder-jammy-full -// pack build prueba-pack --path ./ --builder heroku/builder:22 diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts index a0269bbe..2ac3db52 100644 --- a/apps/dokploy/server/api/routers/compose.ts +++ b/apps/dokploy/server/api/routers/compose.ts @@ -83,12 +83,26 @@ export const composeRouter = createTRPCRouter({ await checkServiceAccess(ctx.user.authId, input.composeId, "access"); } - return await findComposeById(input.composeId); + const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this compose", + }); + } + return compose; }), update: protectedProcedure .input(apiUpdateCompose) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to update this compose", + }); + } return updateCompose(input.composeId, input); }), delete: protectedProcedure @@ -99,6 +113,14 @@ export const composeRouter = createTRPCRouter({ } const composeResult = await findComposeById(input.composeId); + if (composeResult.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to delete this compose", + }); + } + 4; + const result = await db .delete(compose) .where(eq(compose.composeId, input.composeId)) @@ -120,20 +142,41 @@ export const composeRouter = createTRPCRouter({ }), cleanQueues: protectedProcedure .input(apiFindCompose) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to clean this compose", + }); + } await cleanQueuesByCompose(input.composeId); }), loadServices: protectedProcedure .input(apiFetchServices) - .query(async ({ input }) => { + .query(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to load this compose", + }); + } return await loadServices(input.composeId, input.type); }), fetchSourceType: protectedProcedure .input(apiFindCompose) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { try { const compose = await findComposeById(input.composeId); + + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to fetch this compose", + }); + } if (compose.serverId) { await cloneComposeRemote(compose); } else { @@ -151,13 +194,26 @@ export const composeRouter = createTRPCRouter({ randomizeCompose: protectedProcedure .input(apiRandomizeCompose) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to randomize this compose", + }); + } return await randomizeComposeFile(input.composeId, input.suffix); }), getConvertedCompose: protectedProcedure .input(apiFindCompose) - .query(async ({ input }) => { + .query(async ({ input, ctx }) => { const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to get this compose", + }); + } const domains = await findDomainsByComposeId(input.composeId); const composeFile = await addDomainToCompose(compose, domains); return dump(composeFile, { @@ -167,8 +223,15 @@ export const composeRouter = createTRPCRouter({ deploy: protectedProcedure .input(apiFindCompose) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const compose = await findComposeById(input.composeId); + + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to deploy this compose", + }); + } const jobData: DeploymentJob = { composeId: input.composeId, titleLog: "Manual deployment", @@ -188,8 +251,14 @@ export const composeRouter = createTRPCRouter({ }), redeploy: protectedProcedure .input(apiFindCompose) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to redeploy this compose", + }); + } const jobData: DeploymentJob = { composeId: input.composeId, titleLog: "Rebuild deployment", @@ -207,21 +276,44 @@ export const composeRouter = createTRPCRouter({ }, ); }), - stop: protectedProcedure.input(apiFindCompose).mutation(async ({ input }) => { - await stopCompose(input.composeId); + stop: protectedProcedure + .input(apiFindCompose) + .mutation(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to stop this compose", + }); + } + await stopCompose(input.composeId); - return true; - }), + return true; + }), getDefaultCommand: protectedProcedure .input(apiFindCompose) - .query(async ({ input }) => { + .query(async ({ input, ctx }) => { const compose = await findComposeById(input.composeId); + + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to get this compose", + }); + } const command = createCommand(compose); return `docker ${command}`; }), refreshToken: protectedProcedure .input(apiFindCompose) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const compose = await findComposeById(input.composeId); + if (compose.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to refresh this compose", + }); + } await updateCompose(input.composeId, { refreshToken: nanoid(), }); @@ -233,6 +325,7 @@ export const composeRouter = createTRPCRouter({ if (ctx.user.rol === "user") { await checkServiceAccess(ctx.user.authId, input.projectId, "create"); } + const composeFile = await readTemplateComposeFile(input.id); const generate = await loadTemplateModule(input.id as TemplatesKeys); diff --git a/apps/dokploy/server/api/routers/destination.ts b/apps/dokploy/server/api/routers/destination.ts index e655570f..f00838cd 100644 --- a/apps/dokploy/server/api/routers/destination.ts +++ b/apps/dokploy/server/api/routers/destination.ts @@ -9,6 +9,7 @@ import { apiFindOneDestination, apiRemoveDestination, apiUpdateDestination, + destinations, } from "@/server/db/schema"; import { TRPCError } from "@trpc/server"; import { @@ -19,14 +20,14 @@ import { removeDestinationById, updateDestinationById, } from "@dokploy/builders"; +import { eq } from "drizzle-orm"; export const destinationRouter = createTRPCRouter({ create: adminProcedure .input(apiCreateDestination) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { try { - await createDestintation(input); - return await findAdmin(); + return await createDestintation(input, ctx.user.adminId); } catch (error) { throw new TRPCError({ code: "BAD_REQUEST", @@ -54,7 +55,6 @@ export const destinationRouter = createTRPCRouter({ const rcloneCommand = `rclone ls ${rcloneFlags.join(" ")} "${rcloneDestination}"`; await execAsync(rcloneCommand); } catch (error) { - console.log(error); throw new TRPCError({ code: "BAD_REQUEST", message: "Error to connect to bucket", @@ -64,36 +64,58 @@ export const destinationRouter = createTRPCRouter({ }), one: protectedProcedure .input(apiFindOneDestination) - .query(async ({ input }) => { + .query(async ({ input, ctx }) => { const destination = await findDestinationById(input.destinationId); + if (destination.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not allowed to access this destination", + }); + } return destination; }), - all: protectedProcedure.query(async () => { - return await db.query.destinations.findMany({}); + all: protectedProcedure.query(async ({ ctx }) => { + return await db.query.destinations.findMany({ + where: eq(destinations.adminId, ctx.user.adminId), + }); }), remove: adminProcedure .input(apiRemoveDestination) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { try { - return await removeDestinationById(input.destinationId); + const destination = await findDestinationById(input.destinationId); + + if (destination.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not allowed to delete this destination", + }); + } + return await removeDestinationById( + input.destinationId, + ctx.user.adminId, + ); } catch (error) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to delete this destination", - }); + throw error; } }), update: adminProcedure .input(apiUpdateDestination) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { try { - return await updateDestinationById(input.destinationId, input); - } catch (error) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to update this destination", - cause: error, + const destination = await findDestinationById(input.destinationId); + if (destination.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not allowed to update this destination", + }); + } + return await updateDestinationById(input.destinationId, { + ...input, + adminId: ctx.user.adminId, }); + } catch (error) { + throw error; } }), }); diff --git a/apps/dokploy/server/api/routers/mariadb.ts b/apps/dokploy/server/api/routers/mariadb.ts index 3db73f11..00878259 100644 --- a/apps/dokploy/server/api/routers/mariadb.ts +++ b/apps/dokploy/server/api/routers/mariadb.ts @@ -66,13 +66,26 @@ export const mariadbRouter = createTRPCRouter({ if (ctx.user.rol === "user") { await checkServiceAccess(ctx.user.authId, input.mariadbId, "access"); } - return await findMariadbById(input.mariadbId); + const mariadb = await findMariadbById(input.mariadbId); + if (mariadb.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this mariadb", + }); + } + return mariadb; }), start: protectedProcedure .input(apiFindOneMariaDB) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const service = await findMariadbById(input.mariadbId); + if (service.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to start this mariadb", + }); + } if (service.serverId) { await startServiceRemote(service.serverId, service.appName); } else { @@ -102,8 +115,14 @@ export const mariadbRouter = createTRPCRouter({ }), saveExternalPort: protectedProcedure .input(apiSaveExternalPortMariaDB) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const mongo = await findMariadbById(input.mariadbId); + if (mongo.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this external port", + }); + } await updateMariadbById(input.mariadbId, { externalPort: input.externalPort, }); @@ -112,13 +131,26 @@ export const mariadbRouter = createTRPCRouter({ }), deploy: protectedProcedure .input(apiDeployMariaDB) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const mariadb = await findMariadbById(input.mariadbId); + if (mariadb.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to deploy this mariadb", + }); + } return deployMariadb(input.mariadbId); }), changeStatus: protectedProcedure .input(apiChangeMariaDBStatus) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const mongo = await findMariadbById(input.mariadbId); + if (mongo.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to change this mariadb status", + }); + } await updateMariadbById(input.mariadbId, { applicationStatus: input.applicationStatus, }); @@ -132,6 +164,12 @@ export const mariadbRouter = createTRPCRouter({ } const mongo = await findMariadbById(input.mariadbId); + if (mongo.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to delete this mariadb", + }); + } const cleanupOperations = [ async () => await removeService(mongo?.appName, mongo.serverId), @@ -148,7 +186,14 @@ export const mariadbRouter = createTRPCRouter({ }), saveEnvironment: protectedProcedure .input(apiSaveEnvironmentVariablesMariaDB) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const mariadb = await findMariadbById(input.mariadbId); + if (mariadb.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this environment", + }); + } const service = await updateMariadbById(input.mariadbId, { env: input.env, }); @@ -164,8 +209,14 @@ export const mariadbRouter = createTRPCRouter({ }), reload: protectedProcedure .input(apiResetMariadb) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const mariadb = await findMariadbById(input.mariadbId); + if (mariadb.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to reload this mariadb", + }); + } if (mariadb.serverId) { await stopServiceRemote(mariadb.serverId, mariadb.appName); } else { @@ -187,8 +238,15 @@ export const mariadbRouter = createTRPCRouter({ }), update: protectedProcedure .input(apiUpdateMariaDB) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const { mariadbId, ...rest } = input; + const mariadb = await findMariadbById(mariadbId); + if (mariadb.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to update this mariadb", + }); + } const service = await updateMariadbById(mariadbId, { ...rest, }); diff --git a/apps/dokploy/server/api/routers/mongo.ts b/apps/dokploy/server/api/routers/mongo.ts index 716db112..2ed159a0 100644 --- a/apps/dokploy/server/api/routers/mongo.ts +++ b/apps/dokploy/server/api/routers/mongo.ts @@ -67,14 +67,28 @@ export const mongoRouter = createTRPCRouter({ await checkServiceAccess(ctx.user.authId, input.mongoId, "access"); } - return await findMongoById(input.mongoId); + const mongo = await findMongoById(input.mongoId); + if (mongo.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this mongo", + }); + } + return mongo; }), start: protectedProcedure .input(apiFindOneMongo) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const service = await findMongoById(input.mongoId); + if (service.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to start this mongo", + }); + } + if (service.serverId) { await startServiceRemote(service.serverId, service.appName); } else { @@ -88,9 +102,16 @@ export const mongoRouter = createTRPCRouter({ }), stop: protectedProcedure .input(apiFindOneMongo) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const mongo = await findMongoById(input.mongoId); + if (mongo.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to stop this mongo", + }); + } + if (mongo.serverId) { await stopServiceRemote(mongo.serverId, mongo.appName); } else { @@ -104,8 +125,14 @@ export const mongoRouter = createTRPCRouter({ }), saveExternalPort: protectedProcedure .input(apiSaveExternalPortMongo) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const mongo = await findMongoById(input.mongoId); + if (mongo.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this external port", + }); + } await updateMongoById(input.mongoId, { externalPort: input.externalPort, }); @@ -114,13 +141,26 @@ export const mongoRouter = createTRPCRouter({ }), deploy: protectedProcedure .input(apiDeployMongo) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const mongo = await findMongoById(input.mongoId); + if (mongo.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to deploy this mongo", + }); + } return deployMongo(input.mongoId); }), changeStatus: protectedProcedure .input(apiChangeMongoStatus) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const mongo = await findMongoById(input.mongoId); + if (mongo.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to change this mongo status", + }); + } await updateMongoById(input.mongoId, { applicationStatus: input.applicationStatus, }); @@ -128,8 +168,14 @@ export const mongoRouter = createTRPCRouter({ }), reload: protectedProcedure .input(apiResetMongo) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const mongo = await findMongoById(input.mongoId); + if (mongo.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to reload this mongo", + }); + } if (mongo.serverId) { await stopServiceRemote(mongo.serverId, mongo.appName); } else { @@ -158,6 +204,13 @@ export const mongoRouter = createTRPCRouter({ const mongo = await findMongoById(input.mongoId); + if (mongo.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to delete this mongo", + }); + } + const cleanupOperations = [ async () => await removeService(mongo?.appName, mongo.serverId), async () => await removeMongoById(input.mongoId), @@ -173,7 +226,14 @@ export const mongoRouter = createTRPCRouter({ }), saveEnvironment: protectedProcedure .input(apiSaveEnvironmentVariablesMongo) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const mongo = await findMongoById(input.mongoId); + if (mongo.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this environment", + }); + } const service = await updateMongoById(input.mongoId, { env: input.env, }); @@ -189,8 +249,15 @@ export const mongoRouter = createTRPCRouter({ }), update: protectedProcedure .input(apiUpdateMongo) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const { mongoId, ...rest } = input; + const mongo = await findMongoById(mongoId); + if (mongo.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to update this mongo", + }); + } const service = await updateMongoById(mongoId, { ...rest, }); diff --git a/apps/dokploy/server/api/routers/mysql.ts b/apps/dokploy/server/api/routers/mysql.ts index bc5ff6fe..46ecac42 100644 --- a/apps/dokploy/server/api/routers/mysql.ts +++ b/apps/dokploy/server/api/routers/mysql.ts @@ -68,13 +68,26 @@ export const mysqlRouter = createTRPCRouter({ if (ctx.user.rol === "user") { await checkServiceAccess(ctx.user.authId, input.mysqlId, "access"); } - return await findMySqlById(input.mysqlId); + const mysql = await findMySqlById(input.mysqlId); + if (mysql.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this mysql", + }); + } + return mysql; }), start: protectedProcedure .input(apiFindOneMySql) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const service = await findMySqlById(input.mysqlId); + if (service.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to start this mysql", + }); + } if (service.serverId) { await startServiceRemote(service.serverId, service.appName); @@ -89,8 +102,14 @@ export const mysqlRouter = createTRPCRouter({ }), stop: protectedProcedure .input(apiFindOneMySql) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const mongo = await findMySqlById(input.mysqlId); + if (mongo.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to stop this mysql", + }); + } if (mongo.serverId) { await stopServiceRemote(mongo.serverId, mongo.appName); } else { @@ -104,8 +123,14 @@ export const mysqlRouter = createTRPCRouter({ }), saveExternalPort: protectedProcedure .input(apiSaveExternalPortMySql) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const mongo = await findMySqlById(input.mysqlId); + if (mongo.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this external port", + }); + } await updateMySqlById(input.mysqlId, { externalPort: input.externalPort, }); @@ -114,13 +139,26 @@ export const mysqlRouter = createTRPCRouter({ }), deploy: protectedProcedure .input(apiDeployMySql) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const mysql = await findMySqlById(input.mysqlId); + if (mysql.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to deploy this mysql", + }); + } return deployMySql(input.mysqlId); }), changeStatus: protectedProcedure .input(apiChangeMySqlStatus) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const mongo = await findMySqlById(input.mysqlId); + if (mongo.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to change this mysql status", + }); + } await updateMySqlById(input.mysqlId, { applicationStatus: input.applicationStatus, }); @@ -128,8 +166,14 @@ export const mysqlRouter = createTRPCRouter({ }), reload: protectedProcedure .input(apiResetMysql) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const mysql = await findMySqlById(input.mysqlId); + if (mysql.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to reload this mysql", + }); + } if (mysql.serverId) { await stopServiceRemote(mysql.serverId, mysql.appName); } else { @@ -155,6 +199,12 @@ export const mysqlRouter = createTRPCRouter({ await checkServiceAccess(ctx.user.authId, input.mysqlId, "delete"); } const mongo = await findMySqlById(input.mysqlId); + if (mongo.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to delete this mysql", + }); + } const cleanupOperations = [ async () => await removeService(mongo?.appName, mongo.serverId), @@ -171,7 +221,14 @@ export const mysqlRouter = createTRPCRouter({ }), saveEnvironment: protectedProcedure .input(apiSaveEnvironmentVariablesMySql) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const mysql = await findMySqlById(input.mysqlId); + if (mysql.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this environment", + }); + } const service = await updateMySqlById(input.mysqlId, { env: input.env, }); @@ -187,8 +244,15 @@ export const mysqlRouter = createTRPCRouter({ }), update: protectedProcedure .input(apiUpdateMySql) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const { mysqlId, ...rest } = input; + const mysql = await findMySqlById(mysqlId); + if (mysql.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to update this mysql", + }); + } const service = await updateMySqlById(mysqlId, { ...rest, }); diff --git a/apps/dokploy/server/api/routers/postgres.ts b/apps/dokploy/server/api/routers/postgres.ts index 5ba9935e..ae4ced08 100644 --- a/apps/dokploy/server/api/routers/postgres.ts +++ b/apps/dokploy/server/api/routers/postgres.ts @@ -67,14 +67,28 @@ export const postgresRouter = createTRPCRouter({ await checkServiceAccess(ctx.user.authId, input.postgresId, "access"); } - return await findPostgresById(input.postgresId); + const postgres = await findPostgresById(input.postgresId); + if (postgres.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this postgres", + }); + } + return postgres; }), start: protectedProcedure .input(apiFindOnePostgres) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const service = await findPostgresById(input.postgresId); + if (service.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to start this postgres", + }); + } + if (service.serverId) { await startServiceRemote(service.serverId, service.appName); } else { @@ -88,8 +102,14 @@ export const postgresRouter = createTRPCRouter({ }), stop: protectedProcedure .input(apiFindOnePostgres) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const postgres = await findPostgresById(input.postgresId); + if (postgres.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to stop this postgres", + }); + } if (postgres.serverId) { await stopServiceRemote(postgres.serverId, postgres.appName); } else { @@ -103,8 +123,15 @@ export const postgresRouter = createTRPCRouter({ }), saveExternalPort: protectedProcedure .input(apiSaveExternalPortPostgres) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const postgres = await findPostgresById(input.postgresId); + + if (postgres.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this external port", + }); + } await updatePostgresById(input.postgresId, { externalPort: input.externalPort, }); @@ -113,13 +140,26 @@ export const postgresRouter = createTRPCRouter({ }), deploy: protectedProcedure .input(apiDeployPostgres) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const postgres = await findPostgresById(input.postgresId); + if (postgres.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to deploy this postgres", + }); + } return deployPostgres(input.postgresId); }), changeStatus: protectedProcedure .input(apiChangePostgresStatus) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const postgres = await findPostgresById(input.postgresId); + if (postgres.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to change this postgres status", + }); + } await updatePostgresById(input.postgresId, { applicationStatus: input.applicationStatus, }); @@ -133,6 +173,13 @@ export const postgresRouter = createTRPCRouter({ } const postgres = await findPostgresById(input.postgresId); + if (postgres.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to delete this postgres", + }); + } + const cleanupOperations = [ removeService(postgres.appName, postgres.serverId), removePostgresById(input.postgresId), @@ -144,7 +191,14 @@ export const postgresRouter = createTRPCRouter({ }), saveEnvironment: protectedProcedure .input(apiSaveEnvironmentVariablesPostgres) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const postgres = await findPostgresById(input.postgresId); + if (postgres.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this environment", + }); + } const service = await updatePostgresById(input.postgresId, { env: input.env, }); @@ -160,8 +214,14 @@ export const postgresRouter = createTRPCRouter({ }), reload: protectedProcedure .input(apiResetPostgres) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const postgres = await findPostgresById(input.postgresId); + if (postgres.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to reload this postgres", + }); + } if (postgres.serverId) { await stopServiceRemote(postgres.serverId, postgres.appName); } else { @@ -183,8 +243,15 @@ export const postgresRouter = createTRPCRouter({ }), update: protectedProcedure .input(apiUpdatePostgres) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const { postgresId, ...rest } = input; + const postgres = await findPostgresById(postgresId); + if (postgres.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to update this postgres", + }); + } const service = await updatePostgresById(postgresId, { ...rest, }); diff --git a/apps/dokploy/server/api/routers/project.ts b/apps/dokploy/server/api/routers/project.ts index fc310ac6..bede31d2 100644 --- a/apps/dokploy/server/api/routers/project.ts +++ b/apps/dokploy/server/api/routers/project.ts @@ -16,7 +16,7 @@ import { } from "@/server/db/schema"; import { TRPCError } from "@trpc/server"; -import { desc, eq, sql } from "drizzle-orm"; +import { and, desc, eq, sql } from "drizzle-orm"; import type { AnyPgColumn } from "drizzle-orm/pg-core"; import { @@ -45,7 +45,6 @@ export const projectRouter = createTRPCRouter({ return project; } catch (error) { - console.log(error); throw new TRPCError({ code: "BAD_REQUEST", message: "Error to create the project", @@ -62,8 +61,11 @@ export const projectRouter = createTRPCRouter({ await checkProjectAccess(ctx.user.authId, "access", input.projectId); - const service = await db.query.projects.findFirst({ - where: eq(projects.projectId, input.projectId), + const project = await db.query.projects.findFirst({ + where: and( + eq(projects.projectId, input.projectId), + eq(projects.adminId, ctx.user.adminId), + ), with: { compose: { where: buildServiceFilter(compose.composeId, accesedServices), @@ -92,15 +94,22 @@ export const projectRouter = createTRPCRouter({ }, }); - if (!service) { + if (!project) { throw new TRPCError({ code: "NOT_FOUND", message: "Project not found", }); } - return service; + return project; } const project = await findProjectById(input.projectId); + + if (project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this project", + }); + } return project; }), all: protectedProcedure.query(async ({ ctx }) => { @@ -170,32 +179,38 @@ export const projectRouter = createTRPCRouter({ if (ctx.user.rol === "user") { await checkProjectAccess(ctx.user.authId, "delete"); } - const project = await deleteProject(input.projectId); + const currentProject = await findProjectById(input.projectId); + if (currentProject.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to delete this project", + }); + } + const deletedProject = await deleteProject(input.projectId); - return project; + return deletedProject; } catch (error) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to delete this project", - cause: error, - }); + throw error; } }), update: protectedProcedure .input(apiUpdateProject) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { try { - const project = updateProjectById(input.projectId, { + const currentProject = await findProjectById(input.projectId); + if (currentProject.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to update this project", + }); + } + const project = await updateProjectById(input.projectId, { ...input, }); return project; } catch (error) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error to update this project", - cause: error, - }); + throw error; } }), }); diff --git a/apps/dokploy/server/api/routers/redis.ts b/apps/dokploy/server/api/routers/redis.ts index b7f5014a..e336cde3 100644 --- a/apps/dokploy/server/api/routers/redis.ts +++ b/apps/dokploy/server/api/routers/redis.ts @@ -26,6 +26,7 @@ import { findRedisById, removeRedisById, updateRedisById, + IS_CLOUD, } from "@dokploy/builders"; export const redisRouter = createTRPCRouter({ @@ -65,13 +66,27 @@ export const redisRouter = createTRPCRouter({ if (ctx.user.rol === "user") { await checkServiceAccess(ctx.user.authId, input.redisId, "access"); } - return await findRedisById(input.redisId); + + const redis = await findRedisById(input.redisId); + if (redis.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to access this redis", + }); + } + return redis; }), start: protectedProcedure .input(apiFindOneRedis) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const redis = await findRedisById(input.redisId); + if (redis.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to start this redis", + }); + } if (redis.serverId) { await startServiceRemote(redis.serverId, redis.appName); @@ -86,8 +101,14 @@ export const redisRouter = createTRPCRouter({ }), reload: protectedProcedure .input(apiResetRedis) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const redis = await findRedisById(input.redisId); + if (redis.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to reload this redis", + }); + } if (redis.serverId) { await stopServiceRemote(redis.serverId, redis.appName); } else { @@ -110,8 +131,14 @@ export const redisRouter = createTRPCRouter({ stop: protectedProcedure .input(apiFindOneRedis) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const redis = await findRedisById(input.redisId); + if (redis.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to stop this redis", + }); + } if (redis.serverId) { await stopServiceRemote(redis.serverId, redis.appName); } else { @@ -125,8 +152,14 @@ export const redisRouter = createTRPCRouter({ }), saveExternalPort: protectedProcedure .input(apiSaveExternalPortRedis) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const mongo = await findRedisById(input.redisId); + if (mongo.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this external port", + }); + } await updateRedisById(input.redisId, { externalPort: input.externalPort, }); @@ -135,13 +168,26 @@ export const redisRouter = createTRPCRouter({ }), deploy: protectedProcedure .input(apiDeployRedis) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { + const redis = await findRedisById(input.redisId); + if (redis.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to deploy this redis", + }); + } return deployRedis(input.redisId); }), changeStatus: protectedProcedure .input(apiChangeRedisStatus) - .mutation(async ({ input }) => { + .mutation(async ({ input, ctx }) => { const mongo = await findRedisById(input.redisId); + if (mongo.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to change this redis status", + }); + } await updateRedisById(input.redisId, { applicationStatus: input.applicationStatus, }); @@ -156,6 +202,13 @@ export const redisRouter = createTRPCRouter({ const redis = await findRedisById(input.redisId); + if (redis.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to delete this redis", + }); + } + const cleanupOperations = [ async () => await removeService(redis?.appName, redis.serverId), async () => await removeRedisById(input.redisId), @@ -171,12 +224,19 @@ export const redisRouter = createTRPCRouter({ }), saveEnvironment: protectedProcedure .input(apiSaveEnvironmentVariablesRedis) - .mutation(async ({ input }) => { - const redis = await updateRedisById(input.redisId, { + .mutation(async ({ input, ctx }) => { + const redis = await findRedisById(input.redisId); + if (redis.project.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not authorized to save this environment", + }); + } + const updatedRedis = await updateRedisById(input.redisId, { env: input.env, }); - if (!redis) { + if (!updatedRedis) { throw new TRPCError({ code: "BAD_REQUEST", message: "Update: Error to add environment variables", diff --git a/apps/dokploy/server/api/routers/user.ts b/apps/dokploy/server/api/routers/user.ts index 8a0f58d4..05bbf31b 100644 --- a/apps/dokploy/server/api/routers/user.ts +++ b/apps/dokploy/server/api/routers/user.ts @@ -1,6 +1,7 @@ import { apiFindOneUser, apiFindOneUserByAuth } from "@/server/db/schema"; import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc"; import { findUserByAuthId, findUserById, findUsers } from "@dokploy/builders"; +import { TRPCError } from "@trpc/server"; export const userRouter = createTRPCRouter({ all: adminProcedure.query(async ({ ctx }) => { @@ -8,12 +9,26 @@ export const userRouter = createTRPCRouter({ }), byAuthId: protectedProcedure .input(apiFindOneUserByAuth) - .query(async ({ input }) => { - return await findUserByAuthId(input.authId); + .query(async ({ input, ctx }) => { + const user = await findUserByAuthId(input.authId); + if (user.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not allowed to access this user", + }); + } + return user; }), byUserId: protectedProcedure .input(apiFindOneUser) - .query(async ({ input }) => { - return await findUserById(input.userId); + .query(async ({ input, ctx }) => { + const user = await findUserById(input.userId); + if (user.adminId !== ctx.user.adminId) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You are not allowed to access this user", + }); + } + return user; }), }); diff --git a/packages/builders/src/db/schema/bitbucket.ts b/packages/builders/src/db/schema/bitbucket.ts index ee4d2202..393cb1e7 100644 --- a/packages/builders/src/db/schema/bitbucket.ts +++ b/packages/builders/src/db/schema/bitbucket.ts @@ -61,4 +61,5 @@ export const apiUpdateBitbucket = createSchema.extend({ name: z.string().min(1), bitbucketUsername: z.string().optional(), bitbucketWorkspaceName: z.string().optional(), + adminId: z.string().optional(), }); diff --git a/packages/builders/src/services/admin.ts b/packages/builders/src/services/admin.ts index b9394bc5..be48dc96 100644 --- a/packages/builders/src/services/admin.ts +++ b/packages/builders/src/services/admin.ts @@ -13,10 +13,9 @@ import { eq } from "drizzle-orm"; export type Admin = typeof admins.$inferSelect; export const createInvitation = async ( input: typeof apiCreateUserInvitation._type, + adminId: string, ) => { await db.transaction(async (tx) => { - const admin = await findAdmin(); - const result = await tx .insert(auth) .values({ @@ -39,7 +38,7 @@ export const createInvitation = async ( await tx .insert(users) .values({ - adminId: admin.adminId, + adminId: adminId, authId: result.id, token, expirationDate: expiresIn24Hours.toISOString(), diff --git a/packages/builders/src/services/auth.ts b/packages/builders/src/services/auth.ts index 6bbf325c..371fb919 100644 --- a/packages/builders/src/services/auth.ts +++ b/packages/builders/src/services/auth.ts @@ -14,6 +14,7 @@ import { eq } from "drizzle-orm"; import encode from "hi-base32"; import { TOTP } from "otpauth"; import QRCode from "qrcode"; +import { IS_CLOUD } from "../constants"; export type Auth = typeof auth.$inferSelect; @@ -37,13 +38,15 @@ export const createAdmin = async (input: typeof apiCreateAdmin._type) => { }); } - await tx - .insert(admins) - .values({ - authId: newAuth.id, - serverIp: await getPublicIpWithFallback(), - }) - .returning(); + if (!IS_CLOUD) { + await tx + .insert(admins) + .values({ + authId: newAuth.id, + serverIp: await getPublicIpWithFallback(), + }) + .returning(); + } return newAuth; }); diff --git a/packages/builders/src/services/github.ts b/packages/builders/src/services/github.ts index 64f8ec93..110dc55e 100644 --- a/packages/builders/src/services/github.ts +++ b/packages/builders/src/services/github.ts @@ -4,13 +4,16 @@ import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; export type Github = typeof github.$inferSelect; -export const createGithub = async (input: typeof apiCreateGithub._type) => { +export const createGithub = async ( + input: typeof apiCreateGithub._type, + adminId: string, +) => { return await db.transaction(async (tx) => { const newGitProvider = await tx .insert(gitProvider) .values({ providerType: "github", - authId: input.authId, + adminId: adminId, name: input.name, }) .returning() diff --git a/packages/builders/src/services/gitlab.ts b/packages/builders/src/services/gitlab.ts index 528fb1b6..53eb1083 100644 --- a/packages/builders/src/services/gitlab.ts +++ b/packages/builders/src/services/gitlab.ts @@ -11,13 +11,16 @@ import { eq } from "drizzle-orm"; export type Gitlab = typeof gitlab.$inferSelect; -export const createGitlab = async (input: typeof apiCreateGitlab._type) => { +export const createGitlab = async ( + input: typeof apiCreateGitlab._type, + adminId: string, +) => { return await db.transaction(async (tx) => { const newGitProvider = await tx .insert(gitProvider) .values({ providerType: "gitlab", - authId: input.authId, + adminId: adminId, name: input.name, }) .returning() diff --git a/packages/builders/src/services/registry.ts b/packages/builders/src/services/registry.ts index f7c20af6..d0b599e1 100644 --- a/packages/builders/src/services/registry.ts +++ b/packages/builders/src/services/registry.ts @@ -9,19 +9,19 @@ import { } from "@/server/utils/traefik/registry"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; -import { findAdmin } from "./admin"; export type Registry = typeof registry.$inferSelect; -export const createRegistry = async (input: typeof apiCreateRegistry._type) => { - const admin = await findAdmin(); - +export const createRegistry = async ( + input: typeof apiCreateRegistry._type, + adminId: string, +) => { return await db.transaction(async (tx) => { const newRegistry = await tx .insert(registry) .values({ ...input, - adminId: admin.adminId, + adminId: adminId, }) .returning() .then((value) => value[0]); @@ -126,7 +126,9 @@ export const findRegistryById = async (registryId: string) => { return registryResponse; }; -export const findAllRegistry = async () => { - const registryResponse = await db.query.registry.findMany(); +export const findAllRegistryByAdminId = async (adminId: string) => { + const registryResponse = await db.query.registry.findMany({ + where: eq(registry.adminId, adminId), + }); return registryResponse; };