mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-10 14:05:18 +00:00
refactor: enhance access control in environment, mount, port, rollback, and schedule routers to ensure users can only interact with resources belonging to their organization
This commit is contained in:
@@ -39,9 +39,18 @@ export const environmentRouter = createTRPCRouter({
|
|||||||
|
|
||||||
one: protectedProcedure
|
one: protectedProcedure
|
||||||
.input(apiFindOneEnvironment)
|
.input(apiFindOneEnvironment)
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
try {
|
try {
|
||||||
const environment = await findEnvironmentById(input.environmentId);
|
const environment = await findEnvironmentById(input.environmentId);
|
||||||
|
if (
|
||||||
|
environment.project.organizationId !==
|
||||||
|
ctx.session.activeOrganizationId
|
||||||
|
) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "FORBIDDEN",
|
||||||
|
message: "You are not allowed to access this environment",
|
||||||
|
});
|
||||||
|
}
|
||||||
return environment;
|
return environment;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -53,9 +62,21 @@ export const environmentRouter = createTRPCRouter({
|
|||||||
|
|
||||||
byProjectId: protectedProcedure
|
byProjectId: protectedProcedure
|
||||||
.input(z.object({ projectId: z.string() }))
|
.input(z.object({ projectId: z.string() }))
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input, ctx }) => {
|
||||||
try {
|
try {
|
||||||
const environments = await findEnvironmentsByProjectId(input.projectId);
|
const environments = await findEnvironmentsByProjectId(input.projectId);
|
||||||
|
if (
|
||||||
|
environments.some(
|
||||||
|
(environment) =>
|
||||||
|
environment.project.organizationId !==
|
||||||
|
ctx.session.activeOrganizationId,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "FORBIDDEN",
|
||||||
|
message: "You are not allowed to access this environment",
|
||||||
|
});
|
||||||
|
}
|
||||||
return environments;
|
return environments;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -67,8 +88,18 @@ export const environmentRouter = createTRPCRouter({
|
|||||||
|
|
||||||
remove: protectedProcedure
|
remove: protectedProcedure
|
||||||
.input(apiRemoveEnvironment)
|
.input(apiRemoveEnvironment)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
try {
|
try {
|
||||||
|
const environment = await findEnvironmentById(input.environmentId);
|
||||||
|
if (
|
||||||
|
environment.project.organizationId !==
|
||||||
|
ctx.session.activeOrganizationId
|
||||||
|
) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "FORBIDDEN",
|
||||||
|
message: "You are not allowed to access this environment",
|
||||||
|
});
|
||||||
|
}
|
||||||
const deletedEnvironment = await deleteEnvironment(input.environmentId);
|
const deletedEnvironment = await deleteEnvironment(input.environmentId);
|
||||||
return deletedEnvironment;
|
return deletedEnvironment;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -81,9 +112,19 @@ export const environmentRouter = createTRPCRouter({
|
|||||||
|
|
||||||
update: protectedProcedure
|
update: protectedProcedure
|
||||||
.input(apiUpdateEnvironment)
|
.input(apiUpdateEnvironment)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
try {
|
try {
|
||||||
const { environmentId, ...updateData } = input;
|
const { environmentId, ...updateData } = input;
|
||||||
|
const currentEnvironment = await findEnvironmentById(environmentId);
|
||||||
|
if (
|
||||||
|
currentEnvironment.project.organizationId !==
|
||||||
|
ctx.session.activeOrganizationId
|
||||||
|
) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "FORBIDDEN",
|
||||||
|
message: "You are not allowed to access this environment",
|
||||||
|
});
|
||||||
|
}
|
||||||
const environment = await updateEnvironmentById(
|
const environment = await updateEnvironmentById(
|
||||||
environmentId,
|
environmentId,
|
||||||
updateData,
|
updateData,
|
||||||
@@ -99,8 +140,18 @@ export const environmentRouter = createTRPCRouter({
|
|||||||
|
|
||||||
duplicate: protectedProcedure
|
duplicate: protectedProcedure
|
||||||
.input(apiDuplicateEnvironment)
|
.input(apiDuplicateEnvironment)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
try {
|
try {
|
||||||
|
const environment = await findEnvironmentById(input.environmentId);
|
||||||
|
if (
|
||||||
|
environment.project.organizationId !==
|
||||||
|
ctx.session.activeOrganizationId
|
||||||
|
) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "FORBIDDEN",
|
||||||
|
message: "You are not allowed to access this environment",
|
||||||
|
});
|
||||||
|
}
|
||||||
const duplicatedEnvironment = await duplicateEnvironment(input);
|
const duplicatedEnvironment = await duplicateEnvironment(input);
|
||||||
return duplicatedEnvironment;
|
return duplicatedEnvironment;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import {
|
|||||||
deleteMount,
|
deleteMount,
|
||||||
findApplicationById,
|
findApplicationById,
|
||||||
findMountById,
|
findMountById,
|
||||||
|
findMountOrganizationId,
|
||||||
getServiceContainer,
|
getServiceContainer,
|
||||||
updateMount,
|
updateMount,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
apiCreateMount,
|
apiCreateMount,
|
||||||
@@ -24,16 +26,39 @@ export const mountRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
remove: protectedProcedure
|
remove: protectedProcedure
|
||||||
.input(apiRemoveMount)
|
.input(apiRemoveMount)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const organizationId = await findMountOrganizationId(input.mountId);
|
||||||
|
if (organizationId !== ctx.session.activeOrganizationId) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to delete this mount",
|
||||||
|
});
|
||||||
|
}
|
||||||
return await deleteMount(input.mountId);
|
return await deleteMount(input.mountId);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
one: protectedProcedure.input(apiFindOneMount).query(async ({ input }) => {
|
one: protectedProcedure
|
||||||
return await findMountById(input.mountId);
|
.input(apiFindOneMount)
|
||||||
}),
|
.query(async ({ input, ctx }) => {
|
||||||
|
const organizationId = await findMountOrganizationId(input.mountId);
|
||||||
|
if (organizationId !== ctx.session.activeOrganizationId) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to access this mount",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return await findMountById(input.mountId);
|
||||||
|
}),
|
||||||
update: protectedProcedure
|
update: protectedProcedure
|
||||||
.input(apiUpdateMount)
|
.input(apiUpdateMount)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const organizationId = await findMountOrganizationId(input.mountId);
|
||||||
|
if (organizationId !== ctx.session.activeOrganizationId) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to update this mount",
|
||||||
|
});
|
||||||
|
}
|
||||||
return await updateMount(input.mountId, input);
|
return await updateMount(input.mountId, input);
|
||||||
}),
|
}),
|
||||||
allNamedByApplicationId: protectedProcedure
|
allNamedByApplicationId: protectedProcedure
|
||||||
|
|||||||
@@ -27,22 +27,44 @@ export const portRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
one: protectedProcedure.input(apiFindOnePort).query(async ({ input }) => {
|
one: protectedProcedure
|
||||||
try {
|
.input(apiFindOnePort)
|
||||||
return await finPortById(input.portId);
|
.query(async ({ input, ctx }) => {
|
||||||
} catch (error) {
|
try {
|
||||||
throw new TRPCError({
|
const port = await finPortById(input.portId);
|
||||||
code: "BAD_REQUEST",
|
if (
|
||||||
message: "Port not found",
|
port.application.environment.project.organizationId !==
|
||||||
cause: error,
|
ctx.session.activeOrganizationId
|
||||||
});
|
) {
|
||||||
}
|
throw new TRPCError({
|
||||||
}),
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to access this port",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return port;
|
||||||
|
} catch (error) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Port not found",
|
||||||
|
cause: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
delete: protectedProcedure
|
delete: protectedProcedure
|
||||||
.input(apiFindOnePort)
|
.input(apiFindOnePort)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const port = await finPortById(input.portId);
|
||||||
|
if (
|
||||||
|
port.application.environment.project.organizationId !==
|
||||||
|
ctx.session.activeOrganizationId
|
||||||
|
) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to delete this port",
|
||||||
|
});
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
return removePortById(input.portId);
|
return await removePortById(input.portId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message =
|
const message =
|
||||||
error instanceof Error ? error.message : "Error input: Deleting port";
|
error instanceof Error ? error.message : "Error input: Deleting port";
|
||||||
@@ -54,9 +76,19 @@ export const portRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
update: protectedProcedure
|
update: protectedProcedure
|
||||||
.input(apiUpdatePort)
|
.input(apiUpdatePort)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const port = await finPortById(input.portId);
|
||||||
|
if (
|
||||||
|
port.application.environment.project.organizationId !==
|
||||||
|
ctx.session.activeOrganizationId
|
||||||
|
) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to update this port",
|
||||||
|
});
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
return updatePortById(input.portId, input);
|
return await updatePortById(input.portId, input);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message =
|
const message =
|
||||||
error instanceof Error ? error.message : "Error updating the port";
|
error instanceof Error ? error.message : "Error updating the port";
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { removeRollbackById, rollback } from "@dokploy/server";
|
import {
|
||||||
|
findRollbackById,
|
||||||
|
removeRollbackById,
|
||||||
|
rollback,
|
||||||
|
} from "@dokploy/server";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
|
||||||
import { apiFindOneRollback } from "@/server/db/schema";
|
import { apiFindOneRollback } from "@/server/db/schema";
|
||||||
@@ -22,8 +26,18 @@ export const rollbackRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
rollback: protectedProcedure
|
rollback: protectedProcedure
|
||||||
.input(apiFindOneRollback)
|
.input(apiFindOneRollback)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
try {
|
try {
|
||||||
|
const currentRollback = await findRollbackById(input.rollbackId);
|
||||||
|
if (
|
||||||
|
currentRollback?.deployment?.application?.environment?.project
|
||||||
|
.organizationId !== ctx.session.activeOrganizationId
|
||||||
|
) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to rollback this deployment",
|
||||||
|
});
|
||||||
|
}
|
||||||
return await rollback(input.rollbackId);
|
return await rollback(input.rollbackId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
@@ -105,13 +105,69 @@ export const findMountById = async (mountId: string) => {
|
|||||||
const mount = await db.query.mounts.findFirst({
|
const mount = await db.query.mounts.findFirst({
|
||||||
where: eq(mounts.mountId, mountId),
|
where: eq(mounts.mountId, mountId),
|
||||||
with: {
|
with: {
|
||||||
application: true,
|
application: {
|
||||||
postgres: true,
|
with: {
|
||||||
mariadb: true,
|
environment: {
|
||||||
mongo: true,
|
with: {
|
||||||
mysql: true,
|
project: true,
|
||||||
redis: true,
|
},
|
||||||
compose: true,
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
postgres: {
|
||||||
|
with: {
|
||||||
|
environment: {
|
||||||
|
with: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mariadb: {
|
||||||
|
with: {
|
||||||
|
environment: {
|
||||||
|
with: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mongo: {
|
||||||
|
with: {
|
||||||
|
environment: {
|
||||||
|
with: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mysql: {
|
||||||
|
with: {
|
||||||
|
environment: {
|
||||||
|
with: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
redis: {
|
||||||
|
with: {
|
||||||
|
environment: {
|
||||||
|
with: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
compose: {
|
||||||
|
with: {
|
||||||
|
environment: {
|
||||||
|
with: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!mount) {
|
if (!mount) {
|
||||||
@@ -123,6 +179,34 @@ export const findMountById = async (mountId: string) => {
|
|||||||
return mount;
|
return mount;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const findMountOrganizationId = async (mountId: string) => {
|
||||||
|
const mount = await findMountById(mountId);
|
||||||
|
|
||||||
|
if (mount.application) {
|
||||||
|
return mount.application.environment.project.organizationId;
|
||||||
|
}
|
||||||
|
if (mount.postgres) {
|
||||||
|
return mount.postgres.environment.project.organizationId;
|
||||||
|
}
|
||||||
|
if (mount.mariadb) {
|
||||||
|
return mount.mariadb.environment.project.organizationId;
|
||||||
|
}
|
||||||
|
if (mount.mongo) {
|
||||||
|
return mount.mongo.environment.project.organizationId;
|
||||||
|
}
|
||||||
|
if (mount.mysql) {
|
||||||
|
return mount.mysql.environment.project.organizationId;
|
||||||
|
}
|
||||||
|
if (mount.redis) {
|
||||||
|
return mount.redis.environment.project.organizationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mount.compose) {
|
||||||
|
return mount.compose.environment.project.organizationId;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
export const updateMount = async (
|
export const updateMount = async (
|
||||||
mountId: string,
|
mountId: string,
|
||||||
mountData: Partial<Mount>,
|
mountData: Partial<Mount>,
|
||||||
|
|||||||
@@ -27,6 +27,17 @@ export const createPort = async (input: typeof apiCreatePort._type) => {
|
|||||||
export const finPortById = async (portId: string) => {
|
export const finPortById = async (portId: string) => {
|
||||||
const result = await db.query.ports.findFirst({
|
const result = await db.query.ports.findFirst({
|
||||||
where: eq(ports.portId, portId),
|
where: eq(ports.portId, portId),
|
||||||
|
with: {
|
||||||
|
application: {
|
||||||
|
with: {
|
||||||
|
environment: {
|
||||||
|
with: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
if (!result) {
|
if (!result) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
|
|||||||
@@ -76,9 +76,24 @@ export const createRollback = async (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const findRollbackById = async (rollbackId: string) => {
|
export const findRollbackById = async (rollbackId: string) => {
|
||||||
const result = await db.query.rollbacks.findFirst({
|
const result = await db.query.rollbacks.findFirst({
|
||||||
where: eq(rollbacks.rollbackId, rollbackId),
|
where: eq(rollbacks.rollbackId, rollbackId),
|
||||||
|
with: {
|
||||||
|
deployment: {
|
||||||
|
with: {
|
||||||
|
application: {
|
||||||
|
with: {
|
||||||
|
environment: {
|
||||||
|
with: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
|||||||
@@ -35,9 +35,29 @@ export const findScheduleById = async (scheduleId: string) => {
|
|||||||
const schedule = await db.query.schedules.findFirst({
|
const schedule = await db.query.schedules.findFirst({
|
||||||
where: eq(schedules.scheduleId, scheduleId),
|
where: eq(schedules.scheduleId, scheduleId),
|
||||||
with: {
|
with: {
|
||||||
application: true,
|
application: {
|
||||||
compose: true,
|
with: {
|
||||||
server: true,
|
environment: {
|
||||||
|
with: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
compose: {
|
||||||
|
with: {
|
||||||
|
environment: {
|
||||||
|
with: {
|
||||||
|
project: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
with: {
|
||||||
|
organization: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -50,6 +70,21 @@ export const findScheduleById = async (scheduleId: string) => {
|
|||||||
return schedule;
|
return schedule;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const findScheduleOrganizationId = async (scheduleId: string) => {
|
||||||
|
const schedule = await findScheduleById(scheduleId);
|
||||||
|
|
||||||
|
if (schedule?.application) {
|
||||||
|
return schedule?.application?.environment?.project?.organizationId;
|
||||||
|
}
|
||||||
|
if (schedule?.compose) {
|
||||||
|
return schedule?.compose?.environment?.project?.organizationId;
|
||||||
|
}
|
||||||
|
if (schedule?.server) {
|
||||||
|
return schedule?.server?.organization?.id;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
export const deleteSchedule = async (scheduleId: string) => {
|
export const deleteSchedule = async (scheduleId: string) => {
|
||||||
const schedule = await findScheduleById(scheduleId);
|
const schedule = await findScheduleById(scheduleId);
|
||||||
const serverId =
|
const serverId =
|
||||||
|
|||||||
Reference in New Issue
Block a user