feat(docker-swarm): implement server authorization checks and input validation

- Added server authorization checks to ensure users can only access resources for their organization.
- Enhanced input validation for container and app names using regex to prevent invalid entries.
- Updated multiple query procedures in both docker and swarm routers to include these checks and validations.
This commit is contained in:
Mauricio Siu
2025-06-22 23:57:12 -06:00
parent e42f6bc610
commit fb5d2bd5b6
2 changed files with 90 additions and 17 deletions

View File

@@ -1,5 +1,6 @@
import { import {
containerRestart, containerRestart,
findServerById,
getConfig, getConfig,
getContainers, getContainers,
getContainersByAppLabel, getContainersByAppLabel,
@@ -9,6 +10,9 @@ import {
} from "@dokploy/server"; } from "@dokploy/server";
import { z } from "zod"; import { z } from "zod";
import { createTRPCRouter, protectedProcedure } from "../trpc"; import { createTRPCRouter, protectedProcedure } from "../trpc";
import { TRPCError } from "@trpc/server";
export const containerIdRegex = /^[a-zA-Z0-9.\-_]+$/;
export const dockerRouter = createTRPCRouter({ export const dockerRouter = createTRPCRouter({
getContainers: protectedProcedure getContainers: protectedProcedure
@@ -17,14 +21,23 @@ export const dockerRouter = createTRPCRouter({
serverId: z.string().optional(), serverId: z.string().optional(),
}), }),
) )
.query(async ({ input }) => { .query(async ({ input, ctx }) => {
if (input.serverId) {
const server = await findServerById(input.serverId);
if (server.organizationId !== ctx.session?.activeOrganizationId) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
}
return await getContainers(input.serverId); return await getContainers(input.serverId);
}), }),
restartContainer: protectedProcedure restartContainer: protectedProcedure
.input( .input(
z.object({ z.object({
containerId: z.string().min(1), containerId: z
.string()
.min(1)
.regex(containerIdRegex, "Invalid container id."),
}), }),
) )
.mutation(async ({ input }) => { .mutation(async ({ input }) => {
@@ -34,11 +47,20 @@ export const dockerRouter = createTRPCRouter({
getConfig: protectedProcedure getConfig: protectedProcedure
.input( .input(
z.object({ z.object({
containerId: z.string().min(1), containerId: z
.string()
.min(1)
.regex(containerIdRegex, "Invalid container id."),
serverId: z.string().optional(), serverId: z.string().optional(),
}), }),
) )
.query(async ({ input }) => { .query(async ({ input, ctx }) => {
if (input.serverId) {
const server = await findServerById(input.serverId);
if (server.organizationId !== ctx.session?.activeOrganizationId) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
}
return await getConfig(input.containerId, input.serverId); return await getConfig(input.containerId, input.serverId);
}), }),
@@ -48,11 +70,17 @@ export const dockerRouter = createTRPCRouter({
appType: z appType: z
.union([z.literal("stack"), z.literal("docker-compose")]) .union([z.literal("stack"), z.literal("docker-compose")])
.optional(), .optional(),
appName: z.string().min(1), appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."),
serverId: z.string().optional(), serverId: z.string().optional(),
}), }),
) )
.query(async ({ input }) => { .query(async ({ input, ctx }) => {
if (input.serverId) {
const server = await findServerById(input.serverId);
if (server.organizationId !== ctx.session?.activeOrganizationId) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
}
return await getContainersByAppNameMatch( return await getContainersByAppNameMatch(
input.appName, input.appName,
input.appType, input.appType,
@@ -63,12 +91,18 @@ export const dockerRouter = createTRPCRouter({
getContainersByAppLabel: protectedProcedure getContainersByAppLabel: protectedProcedure
.input( .input(
z.object({ z.object({
appName: z.string().min(1), appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."),
serverId: z.string().optional(), serverId: z.string().optional(),
type: z.enum(["standalone", "swarm"]), type: z.enum(["standalone", "swarm"]),
}), }),
) )
.query(async ({ input }) => { .query(async ({ input, ctx }) => {
if (input.serverId) {
const server = await findServerById(input.serverId);
if (server.organizationId !== ctx.session?.activeOrganizationId) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
}
return await getContainersByAppLabel( return await getContainersByAppLabel(
input.appName, input.appName,
input.type, input.type,
@@ -79,22 +113,34 @@ export const dockerRouter = createTRPCRouter({
getStackContainersByAppName: protectedProcedure getStackContainersByAppName: protectedProcedure
.input( .input(
z.object({ z.object({
appName: z.string().min(1), appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."),
serverId: z.string().optional(), serverId: z.string().optional(),
}), }),
) )
.query(async ({ input }) => { .query(async ({ input, ctx }) => {
if (input.serverId) {
const server = await findServerById(input.serverId);
if (server.organizationId !== ctx.session?.activeOrganizationId) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
}
return await getStackContainersByAppName(input.appName, input.serverId); return await getStackContainersByAppName(input.appName, input.serverId);
}), }),
getServiceContainersByAppName: protectedProcedure getServiceContainersByAppName: protectedProcedure
.input( .input(
z.object({ z.object({
appName: z.string().min(1), appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."),
serverId: z.string().optional(), serverId: z.string().optional(),
}), }),
) )
.query(async ({ input }) => { .query(async ({ input, ctx }) => {
if (input.serverId) {
const server = await findServerById(input.serverId);
if (server.organizationId !== ctx.session?.activeOrganizationId) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
}
return await getServiceContainersByAppName(input.appName, input.serverId); return await getServiceContainersByAppName(input.appName, input.serverId);
}), }),
}); });

View File

@@ -6,6 +6,9 @@ import {
} from "@dokploy/server"; } from "@dokploy/server";
import { z } from "zod"; import { z } from "zod";
import { createTRPCRouter, protectedProcedure } from "../trpc"; import { createTRPCRouter, protectedProcedure } from "../trpc";
import { TRPCError } from "@trpc/server";
import { findServerById } from "@dokploy/server";
import { containerIdRegex } from "./docker";
export const swarmRouter = createTRPCRouter({ export const swarmRouter = createTRPCRouter({
getNodes: protectedProcedure getNodes: protectedProcedure
@@ -14,12 +17,24 @@ export const swarmRouter = createTRPCRouter({
serverId: z.string().optional(), serverId: z.string().optional(),
}), }),
) )
.query(async ({ input }) => { .query(async ({ input, ctx }) => {
if (input.serverId) {
const server = await findServerById(input.serverId);
if (server.organizationId !== ctx.session?.activeOrganizationId) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
}
return await getSwarmNodes(input.serverId); return await getSwarmNodes(input.serverId);
}), }),
getNodeInfo: protectedProcedure getNodeInfo: protectedProcedure
.input(z.object({ nodeId: z.string(), serverId: z.string().optional() })) .input(z.object({ nodeId: z.string(), serverId: z.string().optional() }))
.query(async ({ input }) => { .query(async ({ input, ctx }) => {
if (input.serverId) {
const server = await findServerById(input.serverId);
if (server.organizationId !== ctx.session?.activeOrganizationId) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
}
return await getNodeInfo(input.nodeId, input.serverId); return await getNodeInfo(input.nodeId, input.serverId);
}), }),
getNodeApps: protectedProcedure getNodeApps: protectedProcedure
@@ -28,17 +43,29 @@ export const swarmRouter = createTRPCRouter({
serverId: z.string().optional(), serverId: z.string().optional(),
}), }),
) )
.query(async ({ input }) => { .query(async ({ input, ctx }) => {
if (input.serverId) {
const server = await findServerById(input.serverId);
if (server.organizationId !== ctx.session?.activeOrganizationId) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
}
return getNodeApplications(input.serverId); return getNodeApplications(input.serverId);
}), }),
getAppInfos: protectedProcedure getAppInfos: protectedProcedure
.input( .input(
z.object({ z.object({
appName: z.string(), appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."),
serverId: z.string().optional(), serverId: z.string().optional(),
}), }),
) )
.query(async ({ input }) => { .query(async ({ input, ctx }) => {
if (input.serverId) {
const server = await findServerById(input.serverId);
if (server.organizationId !== ctx.session?.activeOrganizationId) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
}
return await getApplicationInfo(input.appName, input.serverId); return await getApplicationInfo(input.appName, input.serverId);
}), }),
}); });