feat(infisical-pg): completed support for tags in secret snapshot

This commit is contained in:
Akhil Mohan
2024-01-27 12:39:54 +05:30
parent b8157122e6
commit 42eb01e1e2
9 changed files with 88 additions and 15 deletions
@@ -27,7 +27,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
status: null, status: null,
trial_end: null, trial_end: null,
has_used_trial: true, has_used_trial: true,
secretApproval: true, secretApproval: false,
secretRotation: true secretRotation: true
}); });
@@ -1,12 +1,15 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { TableName, TSecretTagJunctionInsert } from "@app/db/schemas";
import { BadRequestError, InternalServerError } from "@app/lib/errors"; import { BadRequestError, InternalServerError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn"; import { groupBy } from "@app/lib/fn";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { TSecretDALFactory } from "@app/services/secret/secret-dal"; import { TSecretDALFactory } from "@app/services/secret/secret-dal";
import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal"; import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal";
import { TSecretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal";
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
import { TSecretFolderVersionDALFactory } from "@app/services/secret-folder/secret-folder-version-dal"; import { TSecretFolderVersionDALFactory } from "@app/services/secret-folder/secret-folder-version-dal";
import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
import { TLicenseServiceFactory } from "../license/license-service"; import { TLicenseServiceFactory } from "../license/license-service";
import { TPermissionServiceFactory } from "../permission/permission-service"; import { TPermissionServiceFactory } from "../permission/permission-service";
@@ -31,6 +34,8 @@ type TSecretSnapshotServiceFactoryDep = {
"findLatestVersionByFolderId" | "insertMany" "findLatestVersionByFolderId" | "insertMany"
>; >;
secretDAL: Pick<TSecretDALFactory, "delete" | "insertMany">; secretDAL: Pick<TSecretDALFactory, "delete" | "insertMany">;
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecret">;
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
folderDAL: Pick< folderDAL: Pick<
TSecretFolderDALFactory, TSecretFolderDALFactory,
"findById" | "findBySecretPath" | "delete" | "insertMany" "findById" | "findBySecretPath" | "delete" | "insertMany"
@@ -50,7 +55,9 @@ export const secretSnapshotServiceFactory = ({
folderDAL, folderDAL,
secretDAL, secretDAL,
permissionService, permissionService,
licenseService licenseService,
secretTagDAL,
secretVersionTagDAL
}: TSecretSnapshotServiceFactoryDep) => { }: TSecretSnapshotServiceFactoryDep) => {
const projectSecretSnapshotCount = async ({ const projectSecretSnapshotCount = async ({
environment, environment,
@@ -190,7 +197,11 @@ export const secretSnapshotServiceFactory = ({
folderVersion.map(({ name, id, latestFolderVersion }) => ({ folderVersion.map(({ name, id, latestFolderVersion }) => ({
envId: snapshot.envId, envId: snapshot.envId,
id, id,
version: latestFolderVersion + 1, // this means don't bump up the version if not root folder
// because below ones can be same version as nothing changed
version: deletedTopLevelFolders[folderId]
? latestFolderVersion + 1
: latestFolderVersion,
name, name,
parentId: folderId parentId: folderId
})) }))
@@ -208,17 +219,33 @@ export const secretSnapshotServiceFactory = ({
secretId, secretId,
envId, envId,
id, id,
tags,
...el ...el
}) => ({ }) => ({
...el, ...el,
id: secretId, id: secretId,
version: latestSecretVersion + 1, version: deletedTopLevelSecsGroupById[secretId]
? latestSecretVersion + 1
: latestSecretVersion,
folderId folderId
}) })
) )
), ),
tx tx
); );
const secretTagsToBeInsert: TSecretTagJunctionInsert[] = [];
const secretVerTagToBeInsert: Record<string, string[]> = {};
rollbackSnaps.forEach(({ secretVersions }) => {
secretVersions.forEach((secVer) => {
secVer.tags.forEach((tag) => {
secretTagsToBeInsert.push({ secretsId: secVer.secretId, secret_tagsId: tag.id });
if (!secretVerTagToBeInsert?.[secVer.secretId])
secretVerTagToBeInsert[secVer.secretId] = [];
secretVerTagToBeInsert[secVer.secretId].push(tag.id);
});
});
});
await secretTagDAL.saveTagsToSecret(secretTagsToBeInsert, tx);
const folderVersions = await folderVersionDAL.insertMany( const folderVersions = await folderVersionDAL.insertMany(
folders.map(({ version, name, id, envId }) => ({ folders.map(({ version, name, id, envId }) => ({
name, name,
@@ -232,6 +259,17 @@ export const secretSnapshotServiceFactory = ({
secrets.map(({ id, updatedAt, createdAt, ...el }) => ({ ...el, secretId: id })), secrets.map(({ id, updatedAt, createdAt, ...el }) => ({ ...el, secretId: id })),
tx tx
); );
await secretVersionTagDAL.insertMany(
secretVersions.flatMap(({ secretId, id }) =>
secretVerTagToBeInsert?.[secretId]?.length
? secretVerTagToBeInsert[secretId].map((tagId) => ({
[`${TableName.SecretTag}Id` as const]: tagId,
[`${TableName.SecretVersion}Id` as const]: id
}))
: []
),
tx
);
const newSnapshot = await snapshotDAL.create( const newSnapshot = await snapshotDAL.create(
{ {
folderId: snapshot.folderId, folderId: snapshot.folderId,
@@ -223,6 +223,16 @@ export const snapshotDALFactory = (db: TDbClient) => {
`${TableName.SnapshotSecret}.secretVersionId`, `${TableName.SnapshotSecret}.secretVersionId`,
`${TableName.SecretVersion}.id` `${TableName.SecretVersion}.id`
) )
.leftJoin(
TableName.SecretVersionTag,
`${TableName.SecretVersionTag}.${TableName.SecretVersion}Id`,
`${TableName.SecretVersion}.id`
)
.leftJoin(
TableName.SecretTag,
`${TableName.SecretVersionTag}.${TableName.SecretTag}Id`,
`${TableName.SecretTag}.id`
)
.leftJoin<{ latestSecretVersion: number }>( .leftJoin<{ latestSecretVersion: number }>(
(tx || db)(TableName.SecretVersion) (tx || db)(TableName.SecretVersion)
.groupBy("secretId") .groupBy("secretId")
@@ -249,8 +259,14 @@ export const snapshotDALFactory = (db: TDbClient) => {
db.ref("folderVerName").withSchema("parent"), db.ref("folderVerName").withSchema("parent"),
db.ref("folderVerId").withSchema("parent"), db.ref("folderVerId").withSchema("parent"),
db.ref("max").withSchema("secGroupByMaxVersion").as("latestSecretVersion"), db.ref("max").withSchema("secGroupByMaxVersion").as("latestSecretVersion"),
db.ref("max").withSchema("folderGroupByMaxVersion").as("latestFolderVersion") db.ref("max").withSchema("folderGroupByMaxVersion").as("latestFolderVersion"),
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
db.ref("id").withSchema(TableName.SecretVersionTag).as("tagVersionId"),
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
db.ref("name").withSchema(TableName.SecretTag).as("tagName")
); );
const formated = sqlNestRelationships({ const formated = sqlNestRelationships({
data, data,
key: "snapshotId", key: "snapshotId",
@@ -270,7 +286,20 @@ export const snapshotDALFactory = (db: TDbClient) => {
mapper: (el) => ({ mapper: (el) => ({
...SecretVersionsSchema.parse(el), ...SecretVersionsSchema.parse(el),
latestSecretVersion: el.latestSecretVersion latestSecretVersion: el.latestSecretVersion
}) }),
childrenMapper: [
{
key: "tagVersionId",
label: "tags" as const,
mapper: ({
tagId: id,
tagName: name,
tagSlug: slug,
tagColor: color,
tagVersionId: vId
}) => ({ id, name, slug, color, vId })
}
]
}, },
{ {
key: "folderVerId", key: "folderVerId",
+1 -1
View File
@@ -122,7 +122,7 @@ export const sqlNestRelationships = <
lookupTable.add(pk); lookupTable.add(pk);
} }
sqlChildMapper(doc, recordsGroupedByPk, lookupTable, pk, "", childrenMapper); sqlChildMapper(doc, recordsGroupedByPk, lookupTable, pk, pk, childrenMapper);
}); });
return recordsOrder.map((pkId) => recordsGroupedByPk[pkId]); return recordsOrder.map((pkId) => recordsGroupedByPk[pkId]);
}; };
+4 -2
View File
@@ -300,6 +300,8 @@ export const registerRoutes = async (
const projectRoleService = projectRoleServiceFactory({ permissionService, projectRoleDAL }); const projectRoleService = projectRoleServiceFactory({ permissionService, projectRoleDAL });
const snapshotService = secretSnapshotServiceFactory({ const snapshotService = secretSnapshotServiceFactory({
permissionService,
licenseService,
folderDAL, folderDAL,
secretDAL, secretDAL,
snapshotDAL, snapshotDAL,
@@ -307,8 +309,8 @@ export const registerRoutes = async (
snapshotSecretDAL, snapshotSecretDAL,
secretVersionDAL, secretVersionDAL,
folderVersionDAL, folderVersionDAL,
permissionService, secretTagDAL,
licenseService secretVersionTagDAL
}); });
const webhookService = webhookServiceFactory({ const webhookService = webhookServiceFactory({
permissionService, permissionService,
+4 -1
View File
@@ -99,7 +99,8 @@ export const secretDALFactory = (db: TDbClient) => {
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")) .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName")); .select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"));
return sqlNestRelationships({ console.log(JSON.stringify(secs, null, 4));
const data = sqlNestRelationships({
data: secs, data: secs,
key: "id", key: "id",
parentMapper: (el) => SecretsSchema.parse(el), parentMapper: (el) => SecretsSchema.parse(el),
@@ -116,6 +117,8 @@ export const secretDALFactory = (db: TDbClient) => {
} }
] ]
}); });
console.log(JSON.stringify(data, null, 4));
return data;
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "get all secret" }); throw new DatabaseError({ error, name: "get all secret" });
} }
@@ -164,17 +164,19 @@ export const secretServiceFactory = ({
tags !== undefined ? { tags, secretId: newSecrets[i].id } : [] tags !== undefined ? { tags, secretId: newSecrets[i].id } : []
); );
if (secsUpdatedTag.length) { if (secsUpdatedTag.length) {
await secretTagDAL.deleteTagsManySecret( const delTags = await secretTagDAL.deleteTagsManySecret(
projectId, projectId,
secsUpdatedTag.map(({ secretId }) => secretId), secsUpdatedTag.map(({ secretId }) => secretId),
tx tx
); );
console.log(delTags);
const newSecretTags = secsUpdatedTag.flatMap(({ tags: secretTags = [], secretId }) => const newSecretTags = secsUpdatedTag.flatMap(({ tags: secretTags = [], secretId }) =>
secretTags.map((tag) => ({ secretTags.map((tag) => ({
[`${TableName.SecretTag}Id` as const]: tag, [`${TableName.SecretTag}Id` as const]: tag,
[`${TableName.Secret}Id` as const]: secretId [`${TableName.Secret}Id` as const]: secretId
})) }))
); );
console.log(newSecretTags);
if (newSecretTags.length) { if (newSecretTags.length) {
const secTags = await secretTagDAL.saveTagsToSecret(newSecretTags, tx); const secTags = await secretTagDAL.saveTagsToSecret(newSecretTags, tx);
const secVersionsGroupBySecId = groupBy(secretVersions, (i) => i.secretId); const secVersionsGroupBySecId = groupBy(secretVersions, (i) => i.secretId);
@@ -325,7 +325,7 @@ export const SecretItem = memo(
return ( return (
<DropdownMenuItem <DropdownMenuItem
onClick={() => handleTagSelect(tag)} onClick={() => handleTagSelect(tag)}
key={tagId} key={`${secret.id}-${tagId}`}
icon={isTagSelected && <FontAwesomeIcon icon={faCheckCircle} />} icon={isTagSelected && <FontAwesomeIcon icon={faCheckCircle} />}
iconPos="right" iconPos="right"
> >
@@ -339,7 +339,7 @@ export const SecretItem = memo(
</DropdownMenuItem> </DropdownMenuItem>
); );
})} })}
<DropdownMenuItem className="px-1.5"> <DropdownMenuItem className="px-1.5" asChild>
<Button <Button
size="xs" size="xs"
className="w-full" className="w-full"
@@ -97,7 +97,6 @@ export const SnapshotView = ({
{} {}
); );
const diffView: Array<TDiffView<DecryptedSecret>> = []; const diffView: Array<TDiffView<DecryptedSecret>> = [];
console.log({ rollingSecrets, secrets });
rollingSecrets.forEach((rollSecret) => { rollingSecrets.forEach((rollSecret) => {
const { id } = rollSecret; const { id } = rollSecret;
const doesExist = Boolean(secretGroupById?.[id]); const doesExist = Boolean(secretGroupById?.[id]);
@@ -119,7 +118,7 @@ export const SnapshotView = ({
}); });
return diffView; return diffView;
}, [secrets, rollingSecrets]); }, [secrets, rollingSecrets]);
console.log(secretDiffView);
const handleClickRollback = async () => { const handleClickRollback = async () => {
if (!snapshotData?.id) { if (!snapshotData?.id) {
createNotification({ createNotification({