mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-22 07:35:22 +02:00
feat(preview): add collaborator permission requirement for preview deployments
- Introduced a new boolean field `previewRequireCollaboratorPermissions` in the application schema to enforce permission checks for preview deployments. - Updated the UI to include a toggle for this setting in the preview deployment settings. - Enhanced GitHub deployment handler to validate PR authors against the required permissions, blocking unauthorized deployments and providing security notifications. - Added SQL migration to update the database schema accordingly.
This commit is contained in:
@@ -10,6 +10,9 @@ import {
|
||||
findPreviewDeploymentsByPullRequestId,
|
||||
removePreviewDeployment,
|
||||
shouldDeploy,
|
||||
findGithubById,
|
||||
checkUserRepositoryPermissions,
|
||||
createSecurityBlockedComment,
|
||||
} from "@dokploy/server";
|
||||
import { Webhooks } from "@octokit/webhooks";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
@@ -346,6 +349,18 @@ export default async function handler(
|
||||
const deploymentHash = githubBody?.pull_request?.head?.sha;
|
||||
const branch = githubBody?.pull_request?.base?.ref;
|
||||
const owner = githubBody?.repository?.owner?.login;
|
||||
const prAuthor = githubBody?.pull_request?.user?.login;
|
||||
|
||||
// Validate PR author information is present
|
||||
if (!prAuthor) {
|
||||
console.warn(
|
||||
"⚠️ SECURITY: PR author information missing in webhook payload",
|
||||
);
|
||||
res.status(400).json({
|
||||
message: "PR author information missing",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const apps = await db.query.applications.findMany({
|
||||
where: and(
|
||||
@@ -361,15 +376,74 @@ export default async function handler(
|
||||
},
|
||||
});
|
||||
|
||||
// SECURITY: Check collaborator permissions per application setting
|
||||
const secureApps: typeof apps = [];
|
||||
const blockedApps: string[] = [];
|
||||
let userPermission: string | null = null;
|
||||
|
||||
for (const app of apps) {
|
||||
// If the app requires collaborator permissions, verify them
|
||||
if (app.previewRequireCollaboratorPermissions !== false) {
|
||||
try {
|
||||
const githubProvider = await findGithubById(githubResult.githubId);
|
||||
const { hasWriteAccess, permission } =
|
||||
await checkUserRepositoryPermissions(
|
||||
githubProvider,
|
||||
owner,
|
||||
repository,
|
||||
prAuthor,
|
||||
);
|
||||
|
||||
userPermission = permission; // Store permission for comment
|
||||
|
||||
if (!hasWriteAccess) {
|
||||
console.warn(
|
||||
`🚨 SECURITY: Blocked preview deployment for ${app.name} from unauthorized user ${prAuthor} on ${owner}/${repository}. Permission: ${permission || "none"}`,
|
||||
);
|
||||
blockedApps.push(app.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`✅ SECURITY: Preview deployment authorized for ${app.name} from user ${prAuthor} on ${owner}/${repository}. Permission: ${permission}`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error validating PR author permissions for ${app.name}:`,
|
||||
error,
|
||||
);
|
||||
blockedApps.push(app.name);
|
||||
continue; // Skip this app on error
|
||||
}
|
||||
} else {
|
||||
console.warn(
|
||||
`⚠️ SECURITY: Preview deployment for ${app.name} allows deployment from any PR author (security check disabled)`,
|
||||
);
|
||||
}
|
||||
secureApps.push(app);
|
||||
}
|
||||
|
||||
const prBranch = githubBody?.pull_request?.head?.ref;
|
||||
|
||||
const prNumber = githubBody?.pull_request?.number;
|
||||
const prTitle = githubBody?.pull_request?.title;
|
||||
const prURL = githubBody?.pull_request?.html_url;
|
||||
|
||||
for (const app of apps) {
|
||||
// Create security notification comment if any apps were blocked
|
||||
if (blockedApps.length > 0) {
|
||||
await createSecurityBlockedComment({
|
||||
owner,
|
||||
repository,
|
||||
prNumber: Number.parseInt(prNumber),
|
||||
prAuthor,
|
||||
permission: userPermission,
|
||||
githubId: githubResult.githubId,
|
||||
});
|
||||
}
|
||||
|
||||
for (const app of secureApps) {
|
||||
const previewLimit = app?.previewLimit || 0;
|
||||
if (app?.previewDeployments?.length > previewLimit) {
|
||||
if (app?.previewDeployments?.length >= previewLimit) {
|
||||
continue;
|
||||
}
|
||||
const previewDeploymentResult =
|
||||
|
||||
Reference in New Issue
Block a user