mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 20:27:12 +00:00
feat(infisical-pg): completed support for tags in secret snapshot
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
Reference in New Issue
Block a user