diff --git a/apps/dokploy/server/api/routers/schedule.ts b/apps/dokploy/server/api/routers/schedule.ts index 144f7c74a..7da745b8e 100644 --- a/apps/dokploy/server/api/routers/schedule.ts +++ b/apps/dokploy/server/api/routers/schedule.ts @@ -7,19 +7,25 @@ import { updateScheduleSchema, } from "@dokploy/server/db/schema/schedule"; import { runCommand } from "@dokploy/server/index"; -import { checkServicePermissionAndAccess } from "@dokploy/server/services/permission"; +import { + checkPermission, + checkServicePermissionAndAccess, + findMemberByUserId, +} from "@dokploy/server/services/permission"; import { createSchedule, deleteSchedule, findScheduleById, updateSchedule, } from "@dokploy/server/services/schedule"; +import { findServerById } from "@dokploy/server/services/server"; import { TRPCError } from "@trpc/server"; import { asc, desc, eq } from "drizzle-orm"; import { z } from "zod"; import { audit } from "@/server/api/utils/audit"; import { removeJob, schedule } from "@/server/utils/backup"; import { createTRPCRouter, protectedProcedure } from "../trpc"; + export const scheduleRouter = createTRPCRouter({ create: protectedProcedure .input(createScheduleSchema) @@ -29,6 +35,45 @@ export const scheduleRouter = createTRPCRouter({ await checkServicePermissionAndAccess(ctx, serviceId, { schedule: ["create"], }); + } else { + if (input.scheduleType === "dokploy-server" && IS_CLOUD) { + throw new TRPCError({ + code: "FORBIDDEN", + message: + "Host-level schedules are not available in the cloud version.", + }); + } + + await checkPermission(ctx, { schedule: ["create"] }); + + if ( + input.scheduleType === "server" || + input.scheduleType === "dokploy-server" + ) { + const member = await findMemberByUserId( + ctx.user.id, + ctx.session.activeOrganizationId, + ); + if (member.role !== "owner" && member.role !== "admin") { + throw new TRPCError({ + code: "FORBIDDEN", + message: + "Only owners and admins can manage server-level schedules.", + }); + } + } + + if (input.scheduleType === "server" && input.serverId) { + const targetServer = await findServerById(input.serverId); + if ( + targetServer.organizationId !== ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You don't have access to this server.", + }); + } + } } const newSchedule = await createSchedule(input); @@ -57,12 +102,77 @@ export const scheduleRouter = createTRPCRouter({ .input(updateScheduleSchema) .mutation(async ({ input, ctx }) => { const existingSchedule = await findScheduleById(input.scheduleId); + + if ( + IS_CLOUD && + input.scheduleType && + input.scheduleType !== existingSchedule.scheduleType + ) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "Changing scheduleType is not allowed in the cloud version.", + }); + } + const serviceId = existingSchedule.applicationId || existingSchedule.composeId; if (serviceId) { await checkServicePermissionAndAccess(ctx, serviceId, { schedule: ["update"], }); + } else { + if (existingSchedule.scheduleType === "dokploy-server" && IS_CLOUD) { + throw new TRPCError({ + code: "FORBIDDEN", + message: + "Host-level schedules are not available in the cloud version.", + }); + } + + await checkPermission(ctx, { schedule: ["update"] }); + + if ( + existingSchedule.scheduleType === "server" || + existingSchedule.scheduleType === "dokploy-server" + ) { + const member = await findMemberByUserId( + ctx.user.id, + ctx.session.activeOrganizationId, + ); + if (member.role !== "owner" && member.role !== "admin") { + throw new TRPCError({ + code: "FORBIDDEN", + message: + "Only owners and admins can manage server-level schedules.", + }); + } + } + + if ( + existingSchedule.scheduleType === "server" && + existingSchedule.serverId + ) { + const targetServer = await findServerById(existingSchedule.serverId); + if ( + targetServer.organizationId !== ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You don't have access to this server.", + }); + } + } + + if ( + existingSchedule.scheduleType === "dokploy-server" && + existingSchedule.userId && + existingSchedule.userId !== ctx.user.id + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You can only manage your own host-level schedules.", + }); + } } const updatedSchedule = await updateSchedule(input); @@ -107,6 +217,56 @@ export const scheduleRouter = createTRPCRouter({ await checkServicePermissionAndAccess(ctx, serviceId, { schedule: ["delete"], }); + } else { + if (scheduleItem.scheduleType === "dokploy-server" && IS_CLOUD) { + throw new TRPCError({ + code: "FORBIDDEN", + message: + "Host-level schedules are not available in the cloud version.", + }); + } + + await checkPermission(ctx, { schedule: ["delete"] }); + + if ( + scheduleItem.scheduleType === "server" || + scheduleItem.scheduleType === "dokploy-server" + ) { + const member = await findMemberByUserId( + ctx.user.id, + ctx.session.activeOrganizationId, + ); + if (member.role !== "owner" && member.role !== "admin") { + throw new TRPCError({ + code: "FORBIDDEN", + message: + "Only owners and admins can manage server-level schedules.", + }); + } + } + + if (scheduleItem.scheduleType === "server" && scheduleItem.serverId) { + const targetServer = await findServerById(scheduleItem.serverId); + if ( + targetServer.organizationId !== ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You don't have access to this server.", + }); + } + } + + if ( + scheduleItem.scheduleType === "dokploy-server" && + scheduleItem.userId && + scheduleItem.userId !== ctx.user.id + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You can only manage your own host-level schedules.", + }); + } } await deleteSchedule(input.scheduleId); @@ -148,6 +308,30 @@ export const scheduleRouter = createTRPCRouter({ await checkServicePermissionAndAccess(ctx, input.id, { schedule: ["read"], }); + } else { + await checkPermission(ctx, { schedule: ["read"] }); + + if (input.scheduleType === "server") { + const targetServer = await findServerById(input.id); + if ( + targetServer.organizationId !== ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You don't have access to this server.", + }); + } + } + + if ( + input.scheduleType === "dokploy-server" && + input.id !== ctx.user.id + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You can only list your own host-level schedules.", + }); + } } const where = { application: eq(schedules.applicationId, input.id), @@ -178,6 +362,31 @@ export const scheduleRouter = createTRPCRouter({ await checkServicePermissionAndAccess(ctx, serviceId, { schedule: ["read"], }); + } else { + await checkPermission(ctx, { schedule: ["read"] }); + + if (schedule.scheduleType === "server" && schedule.serverId) { + const targetServer = await findServerById(schedule.serverId); + if ( + targetServer.organizationId !== ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You don't have access to this schedule.", + }); + } + } + + if ( + schedule.scheduleType === "dokploy-server" && + schedule.userId && + schedule.userId !== ctx.user.id + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You don't have access to this schedule.", + }); + } } return schedule; }), @@ -191,6 +400,56 @@ export const scheduleRouter = createTRPCRouter({ await checkServicePermissionAndAccess(ctx, serviceId, { schedule: ["create"], }); + } else { + if (scheduleItem.scheduleType === "dokploy-server" && IS_CLOUD) { + throw new TRPCError({ + code: "FORBIDDEN", + message: + "Host-level schedules are not available in the cloud version.", + }); + } + + await checkPermission(ctx, { schedule: ["create"] }); + + if ( + scheduleItem.scheduleType === "server" || + scheduleItem.scheduleType === "dokploy-server" + ) { + const member = await findMemberByUserId( + ctx.user.id, + ctx.session.activeOrganizationId, + ); + if (member.role !== "owner" && member.role !== "admin") { + throw new TRPCError({ + code: "FORBIDDEN", + message: + "Only owners and admins can manage server-level schedules.", + }); + } + } + + if (scheduleItem.scheduleType === "server" && scheduleItem.serverId) { + const targetServer = await findServerById(scheduleItem.serverId); + if ( + targetServer.organizationId !== ctx.session.activeOrganizationId + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You don't have access to this server.", + }); + } + } + + if ( + scheduleItem.scheduleType === "dokploy-server" && + scheduleItem.userId && + scheduleItem.userId !== ctx.user.id + ) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "You can only manage your own host-level schedules.", + }); + } } try { await runCommand(input.scheduleId);