mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 07:35:09 +00:00
feature: deep search for secrets dashboard
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
} from "@app/lib/knex";
|
||||
import { OrderByDirection } from "@app/lib/types";
|
||||
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
||||
import { TFindSecretsByFolderIdsFilter } from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
|
||||
|
||||
export type TSecretV2BridgeDALFactory = ReturnType<typeof secretV2BridgeDALFactory>;
|
||||
|
||||
@@ -339,14 +340,7 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
||||
folderIds: string[],
|
||||
userId?: string,
|
||||
tx?: Knex,
|
||||
filters?: {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
orderBy?: SecretsOrderBy;
|
||||
orderDirection?: OrderByDirection;
|
||||
search?: string;
|
||||
tagSlugs?: string[];
|
||||
}
|
||||
filters?: TFindSecretsByFolderIdsFilter
|
||||
) => {
|
||||
try {
|
||||
// check if not uui then userId id is null (corner case because service token's ID is not UUI in effort to keep backwards compatibility from mongo)
|
||||
@@ -356,14 +350,20 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
|
||||
const query = (tx || db.replicaNode())(TableName.SecretV2)
|
||||
.whereIn("folderId", folderIds)
|
||||
.whereIn(`${TableName.SecretV2}.folderId`, folderIds)
|
||||
.where((bd) => {
|
||||
if (filters?.search) {
|
||||
void bd.whereILike("key", `%${filters?.search}%`);
|
||||
if (filters?.includeTagsInSearch) {
|
||||
void bd
|
||||
.whereILike(`${TableName.SecretV2}.key`, `%${filters?.search}%`)
|
||||
.orWhereILike(`${TableName.SecretTag}.slug`, `%${filters?.search}%`);
|
||||
} else {
|
||||
void bd.whereILike(`${TableName.SecretV2}.key`, `%${filters?.search}%`);
|
||||
}
|
||||
}
|
||||
})
|
||||
.where((bd) => {
|
||||
void bd.whereNull("userId").orWhere({ userId: userId || null });
|
||||
void bd.whereNull(`${TableName.SecretV2}.userId`).orWhere({ userId: userId || null });
|
||||
})
|
||||
.leftJoin(
|
||||
TableName.SecretV2JnTag,
|
||||
@@ -385,7 +385,7 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
||||
.where((bd) => {
|
||||
const slugs = filters?.tagSlugs?.filter(Boolean);
|
||||
if (slugs && slugs.length > 0) {
|
||||
void bd.whereIn("slug", slugs);
|
||||
void bd.whereIn(`${TableName.SecretTag}.slug`, slugs);
|
||||
}
|
||||
})
|
||||
.orderBy(
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
TGetASecretDTO,
|
||||
TGetSecretReferencesTreeDTO,
|
||||
TGetSecretsDTO,
|
||||
TGetSecretsRawByFolderMappingsDTO,
|
||||
TGetSecretVersionsDTO,
|
||||
TMoveSecretsDTO,
|
||||
TSecretReference,
|
||||
@@ -652,6 +653,56 @@ export const secretV2BridgeServiceFactory = ({
|
||||
return count;
|
||||
};
|
||||
|
||||
const getSecretsByFolderMappings = async (
|
||||
{ projectId, userId, filters, folderMappings }: TGetSecretsRawByFolderMappingsDTO,
|
||||
projectPermission: Awaited<ReturnType<typeof permissionService.getProjectPermission>>["permission"]
|
||||
) => {
|
||||
const groupedFolderMappings = groupBy(folderMappings, (folderMapping) => folderMapping.folderId);
|
||||
|
||||
const secrets = await secretDAL.findByFolderIds(
|
||||
folderMappings.map((folderMapping) => folderMapping.folderId),
|
||||
userId,
|
||||
undefined,
|
||||
filters
|
||||
);
|
||||
|
||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.SecretManager,
|
||||
projectId
|
||||
});
|
||||
|
||||
const decryptedSecrets = secrets
|
||||
.filter((el) =>
|
||||
projectPermission.can(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment: groupedFolderMappings[el.folderId][0].environment,
|
||||
secretPath: groupedFolderMappings[el.folderId][0].path,
|
||||
secretName: el.key,
|
||||
secretTags: el.tags.map((i) => i.slug)
|
||||
})
|
||||
)
|
||||
)
|
||||
.map((secret) =>
|
||||
reshapeBridgeSecret(
|
||||
projectId,
|
||||
groupedFolderMappings[secret.folderId][0].environment,
|
||||
groupedFolderMappings[secret.folderId][0].path,
|
||||
{
|
||||
...secret,
|
||||
value: secret.encryptedValue
|
||||
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString()
|
||||
: "",
|
||||
comment: secret.encryptedComment
|
||||
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedComment }).toString()
|
||||
: ""
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
return decryptedSecrets;
|
||||
};
|
||||
|
||||
// get secrets for multiple envs
|
||||
const getSecretsMultiEnv = async ({
|
||||
actorId,
|
||||
@@ -678,59 +729,28 @@ export const secretV2BridgeServiceFactory = ({
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
||||
}
|
||||
|
||||
let paths: { folderId: string; path: string; environment: string }[] = [];
|
||||
|
||||
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environments, path);
|
||||
|
||||
if (!folders.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
paths = folders.map((folder) => ({ folderId: folder.id, path, environment: folder.environment.slug }));
|
||||
const folderMappings = folders.map((folder) => ({
|
||||
folderId: folder.id,
|
||||
path,
|
||||
environment: folder.environment.slug
|
||||
}));
|
||||
|
||||
const groupedPaths = groupBy(paths, (p) => p.folderId);
|
||||
|
||||
const secrets = await secretDAL.findByFolderIds(
|
||||
paths.map((p) => p.folderId),
|
||||
actorId,
|
||||
undefined,
|
||||
params
|
||||
const decryptedSecrets = await getSecretsByFolderMappings(
|
||||
{
|
||||
projectId,
|
||||
folderMappings,
|
||||
filters: params,
|
||||
userId: actorId
|
||||
},
|
||||
permission
|
||||
);
|
||||
|
||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.SecretManager,
|
||||
projectId
|
||||
});
|
||||
|
||||
const decryptedSecrets = secrets
|
||||
.filter((el) =>
|
||||
permission.can(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment: groupedPaths[el.folderId][0].environment,
|
||||
secretPath: groupedPaths[el.folderId][0].path,
|
||||
secretName: el.key,
|
||||
secretTags: el.tags.map((i) => i.slug)
|
||||
})
|
||||
)
|
||||
)
|
||||
.map((secret) =>
|
||||
reshapeBridgeSecret(
|
||||
projectId,
|
||||
groupedPaths[secret.folderId][0].environment,
|
||||
groupedPaths[secret.folderId][0].path,
|
||||
{
|
||||
...secret,
|
||||
value: secret.encryptedValue
|
||||
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString()
|
||||
: "",
|
||||
comment: secret.encryptedComment
|
||||
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedComment }).toString()
|
||||
: ""
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
return decryptedSecrets;
|
||||
};
|
||||
|
||||
@@ -2027,6 +2047,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
getSecretsCount,
|
||||
getSecretsCountMultiEnv,
|
||||
getSecretsMultiEnv,
|
||||
getSecretReferenceTree
|
||||
getSecretReferenceTree,
|
||||
getSecretsByFolderMappings
|
||||
};
|
||||
};
|
||||
|
||||
@@ -285,3 +285,20 @@ export type TGetSecretReferencesTreeDTO = {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TFindSecretsByFolderIdsFilter = {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
orderBy?: SecretsOrderBy;
|
||||
orderDirection?: OrderByDirection;
|
||||
search?: string;
|
||||
tagSlugs?: string[];
|
||||
includeTagsInSearch?: boolean;
|
||||
};
|
||||
|
||||
export type TGetSecretsRawByFolderMappingsDTO = {
|
||||
projectId: string;
|
||||
folderMappings: { folderId: string; path: string; environment: string }[];
|
||||
userId: string;
|
||||
filters: TFindSecretsByFolderIdsFilter;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user