Files
dokploy/apps/dokploy/server/api/routers/mount.ts
Mauricio Siu 8127dc4536 feat: add comprehensive permission tests and enhance permission checks in components
- Introduced new test files for permission checks, including `check-permission.test.ts`, `enterprise-only-resources.test.ts`, `resolve-permissions.test.ts`, and `service-access.test.ts`.
- Implemented permission checks in various components to ensure actions are gated by user permissions, including `ShowTraefikConfig`, `UpdateTraefikConfig`, `ShowVolumes`, `ShowDomains`, and others.
- Enhanced the logic for displaying UI elements based on user permissions, ensuring that only authorized users can access or modify resources.
2026-03-15 16:42:48 -06:00

194 lines
5.1 KiB
TypeScript

import {
createMount,
deleteMount,
findApplicationById,
findComposeById,
findMariadbById,
findMongoById,
findMountById,
findMountsByApplicationId,
findMySqlById,
findPostgresById,
findRedisById,
getServiceContainer,
updateMount,
} from "@dokploy/server";
import {
checkServiceAccess,
checkServicePermissionAndAccess,
} from "@dokploy/server/services/permission";
import type { ServiceType } from "@dokploy/server/db/schema/mount";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import {
apiCreateMount,
apiFindMountByApplicationId,
apiFindOneMount,
apiRemoveMount,
apiUpdateMount,
} from "@/server/db/schema";
import { createTRPCRouter, protectedProcedure } from "../trpc";
import { audit } from "@/server/api/utils/audit";
async function getServiceOrganizationId(
serviceId: string,
serviceType: ServiceType,
): Promise<string | null> {
switch (serviceType) {
case "application": {
const app = await findApplicationById(serviceId);
return app?.environment?.project?.organizationId ?? null;
}
case "postgres": {
const postgres = await findPostgresById(serviceId);
return postgres?.environment?.project?.organizationId ?? null;
}
case "mariadb": {
const mariadb = await findMariadbById(serviceId);
return mariadb?.environment?.project?.organizationId ?? null;
}
case "mongo": {
const mongo = await findMongoById(serviceId);
return mongo?.environment?.project?.organizationId ?? null;
}
case "mysql": {
const mysql = await findMySqlById(serviceId);
return mysql?.environment?.project?.organizationId ?? null;
}
case "redis": {
const redis = await findRedisById(serviceId);
return redis?.environment?.project?.organizationId ?? null;
}
case "compose": {
const compose = await findComposeById(serviceId);
return compose?.environment?.project?.organizationId ?? null;
}
default:
return null;
}
}
export const mountRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateMount)
.mutation(async ({ input, ctx }) => {
await checkServicePermissionAndAccess(ctx, input.serviceId, {
volume: ["create"],
});
const mount = await createMount(input);
await audit(ctx, {
action: "create",
resourceType: "mount",
resourceId: mount.mountId,
resourceName: input.mountPath,
});
return mount;
}),
remove: protectedProcedure
.input(apiRemoveMount)
.mutation(async ({ input, ctx }) => {
const mount = await findMountById(input.mountId);
const serviceId =
mount.applicationId ||
mount.postgresId ||
mount.mariadbId ||
mount.mongoId ||
mount.mysqlId ||
mount.redisId ||
mount.composeId;
if (serviceId) {
await checkServicePermissionAndAccess(ctx, serviceId, {
volume: ["delete"],
});
}
await audit(ctx, {
action: "delete",
resourceType: "mount",
resourceId: input.mountId,
});
return await deleteMount(input.mountId);
}),
one: protectedProcedure
.input(apiFindOneMount)
.query(async ({ input, ctx }) => {
const mount = await findMountById(input.mountId);
const serviceId =
mount.applicationId ||
mount.postgresId ||
mount.mariadbId ||
mount.mongoId ||
mount.mysqlId ||
mount.redisId ||
mount.composeId;
if (serviceId) {
await checkServicePermissionAndAccess(ctx, serviceId, {
volume: ["read"],
});
}
return mount;
}),
update: protectedProcedure
.input(apiUpdateMount)
.mutation(async ({ input, ctx }) => {
const mount = await findMountById(input.mountId);
const serviceId =
mount.applicationId ||
mount.postgresId ||
mount.mariadbId ||
mount.mongoId ||
mount.mysqlId ||
mount.redisId ||
mount.composeId;
if (serviceId) {
await checkServicePermissionAndAccess(ctx, serviceId, {
volume: ["create"],
});
}
await audit(ctx, {
action: "update",
resourceType: "mount",
resourceId: input.mountId,
resourceName: input.mountPath,
});
return await updateMount(input.mountId, input);
}),
allNamedByApplicationId: protectedProcedure
.input(z.object({ applicationId: z.string().min(1) }))
.query(async ({ input, ctx }) => {
await checkServicePermissionAndAccess(ctx, input.applicationId, {
volume: ["read"],
});
const app = await findApplicationById(input.applicationId);
const container = await getServiceContainer(app.appName, app.serverId);
const mounts = container?.Mounts.filter(
(mount) => mount.Type === "volume" && mount.Source !== "",
);
return mounts;
}),
listByServiceId: protectedProcedure
.input(apiFindMountByApplicationId)
.query(async ({ input, ctx }) => {
console.log("input", input);
await checkServiceAccess(ctx, input.serviceId, "read");
const organizationId = await getServiceOrganizationId(
input.serviceId,
input.serviceType,
);
if (
organizationId === null ||
organizationId !== ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
message:
"You are not authorized to access this service or it does not exist",
});
}
return await findMountsByApplicationId(
input.serviceId,
input.serviceType,
);
}),
});