Merge pull request #4745 from Infisical/fix/duplicate-slug-check-in-create-project

[ENG-4007] chore: adds unique slug check when creating project
This commit is contained in:
Piyush Gupta
2025-10-28 22:35:53 +05:30
committed by GitHub
+55 -41
View File
@@ -10,7 +10,8 @@ import {
ProjectType, ProjectType,
ProjectVersion, ProjectVersion,
TableName, TableName,
TProjectEnvironments TProjectEnvironments,
TProjects
} from "@app/db/schemas"; } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
@@ -40,7 +41,8 @@ import { TSshHostGroupDALFactory } from "@app/ee/services/ssh-host-group/ssh-hos
import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore"; import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { DatabaseErrorCode } from "@app/lib/error-codes";
import { BadRequestError, DatabaseError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn"; import { groupBy } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TProjectPermission } from "@app/lib/types"; import { TProjectPermission } from "@app/lib/types";
@@ -303,19 +305,34 @@ export const projectServiceFactory = ({
}); });
} }
const project = await projectDAL.create( const slug = projectSlug || slugify(`${workspaceName}-${alphaNumericNanoId(4)}`);
{
name: workspaceName, let project: TProjects;
type, try {
description: workspaceDescription, project = await projectDAL.create(
orgId: organization.id, {
slug: projectSlug || slugify(`${workspaceName}-${alphaNumericNanoId(4)}`), name: workspaceName,
kmsSecretManagerKeyId: kmsKeyId, type,
version: ProjectVersion.V3, description: workspaceDescription,
pitVersionLimit: 10 orgId: organization.id,
}, slug,
tx kmsSecretManagerKeyId: kmsKeyId,
); version: ProjectVersion.V3,
pitVersionLimit: 10
},
tx
);
} catch (err) {
if (
err instanceof DatabaseError &&
(err.error as { code: string })?.code === DatabaseErrorCode.UniqueViolation
) {
throw new BadRequestError({
message: `A project with the slug "${slug}" already exists in your organization. Please choose a different name or slug.`
});
}
throw err;
}
if (type === ProjectType.SSH) { if (type === ProjectType.SSH) {
await bootstrapSshProject({ await bootstrapSshProject({
@@ -579,39 +596,36 @@ export const projectServiceFactory = ({
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
if (update.slug) {
const existingProject = await projectDAL.findOne({
slug: update.slug,
orgId: actorOrgId
});
if (existingProject && existingProject.id !== project.id) {
throw new BadRequestError({
message: `Failed to update project slug. The project "${existingProject.name}" with the slug "${existingProject.slug}" already exists in your organization. Please choose a unique slug for your project.`
});
}
}
if (update.secretDetectionIgnoreValues && !hasRole(ProjectMembershipRole.Admin)) { if (update.secretDetectionIgnoreValues && !hasRole(ProjectMembershipRole.Admin)) {
throw new ForbiddenRequestError({ throw new ForbiddenRequestError({
message: "Only admins can update secret detection ignore values" message: "Only admins can update secret detection ignore values"
}); });
} }
const updatedProject = await projectDAL.updateById(project.id, { try {
name: update.name, const updatedProject = await projectDAL.updateById(project.id, {
description: update.description, name: update.name,
autoCapitalization: update.autoCapitalization, description: update.description,
enforceCapitalization: update.autoCapitalization, autoCapitalization: update.autoCapitalization,
hasDeleteProtection: update.hasDeleteProtection, enforceCapitalization: update.autoCapitalization,
slug: update.slug, hasDeleteProtection: update.hasDeleteProtection,
secretSharing: update.secretSharing, slug: update.slug,
defaultProduct: update.defaultProduct, secretSharing: update.secretSharing,
showSnapshotsLegacy: update.showSnapshotsLegacy, defaultProduct: update.defaultProduct,
secretDetectionIgnoreValues: update.secretDetectionIgnoreValues, showSnapshotsLegacy: update.showSnapshotsLegacy,
pitVersionLimit: update.pitVersionLimit secretDetectionIgnoreValues: update.secretDetectionIgnoreValues,
}); pitVersionLimit: update.pitVersionLimit
});
return updatedProject; return updatedProject;
} catch (err) {
if (err instanceof DatabaseError && (err.error as { code: string })?.code === DatabaseErrorCode.UniqueViolation) {
throw new BadRequestError({
message: `Failed to update project. A project with the slug "${update.slug}" already exists in your organization. Please choose a different slug.`
});
}
throw err;
}
}; };
const toggleAutoCapitalization = async ({ const toggleAutoCapitalization = async ({