mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-17 08:35:52 +00:00
Merge pull request #3926 from Infisical/feat/remindersImprovement
feat(secret-reminders): rework secret reminders logic
This commit is contained in:
@@ -490,6 +490,7 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
}
|
||||
};
|
||||
|
||||
// This method currently uses too many joins which is not performant, in case we need to add more filters we should consider refactoring this method
|
||||
const findByFolderIds = async (dto: {
|
||||
folderIds: string[];
|
||||
userId?: string;
|
||||
@@ -539,18 +540,15 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
|
||||
`${TableName.SecretTag}.id`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.SecretReminderRecipients,
|
||||
`${TableName.SecretV2}.id`,
|
||||
`${TableName.SecretReminderRecipients}.secretId`
|
||||
)
|
||||
.leftJoin(TableName.Users, `${TableName.SecretReminderRecipients}.userId`, `${TableName.Users}.id`)
|
||||
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
|
||||
.leftJoin(
|
||||
TableName.SecretRotationV2SecretMapping,
|
||||
`${TableName.SecretV2}.id`,
|
||||
`${TableName.SecretRotationV2SecretMapping}.secretId`
|
||||
)
|
||||
.leftJoin(TableName.Reminder, `${TableName.SecretV2}.id`, `${TableName.Reminder}.secretId`)
|
||||
.leftJoin(TableName.ReminderRecipient, `${TableName.Reminder}.id`, `${TableName.ReminderRecipient}.reminderId`)
|
||||
.leftJoin(TableName.Users, `${TableName.ReminderRecipient}.userId`, `${TableName.Users}.id`)
|
||||
.where((qb) => {
|
||||
if (filters?.metadataFilter && filters.metadataFilter.length > 0) {
|
||||
filters.metadataFilter.forEach((meta) => {
|
||||
@@ -573,7 +571,11 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
}) as rank`
|
||||
)
|
||||
)
|
||||
.select(db.ref("id").withSchema(TableName.SecretReminderRecipients).as("reminderRecipientId"))
|
||||
.select(db.ref("id").withSchema(TableName.Reminder).as("reminderId"))
|
||||
.select(db.ref("message").withSchema(TableName.Reminder).as("reminderNote"))
|
||||
.select(db.ref("repeatDays").withSchema(TableName.Reminder).as("reminderRepeatDays"))
|
||||
.select(db.ref("nextReminderDate").withSchema(TableName.Reminder).as("nextReminderDate"))
|
||||
.select(db.ref("id").withSchema(TableName.ReminderRecipient).as("reminderRecipientId"))
|
||||
.select(db.ref("username").withSchema(TableName.Users).as("reminderRecipientUsername"))
|
||||
.select(db.ref("email").withSchema(TableName.Users).as("reminderRecipientEmail"))
|
||||
.select(db.ref("id").withSchema(TableName.Users).as("reminderRecipientUserId"))
|
||||
@@ -835,6 +837,44 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
}
|
||||
};
|
||||
|
||||
const findSecretsWithReminderRecipients = async (ids: string[], limit: number, tx?: Knex) => {
|
||||
try {
|
||||
// Create a subquery to get limited secret IDs
|
||||
const limitedSecretIds = (tx || db)(TableName.SecretV2)
|
||||
.whereIn(`${TableName.SecretV2}.id`, ids)
|
||||
.limit(limit)
|
||||
.select("id");
|
||||
|
||||
// Join with all recipients for the limited secrets
|
||||
const docs = await (tx || db)(TableName.SecretV2)
|
||||
.whereIn(`${TableName.SecretV2}.id`, limitedSecretIds)
|
||||
.leftJoin(TableName.Reminder, `${TableName.SecretV2}.id`, `${TableName.Reminder}.secretId`)
|
||||
.leftJoin(TableName.ReminderRecipient, `${TableName.Reminder}.id`, `${TableName.ReminderRecipient}.reminderId`)
|
||||
.select(selectAllTableCols(TableName.SecretV2))
|
||||
.select(db.ref("userId").withSchema(TableName.ReminderRecipient).as("reminderRecipientUserId"));
|
||||
|
||||
const data = sqlNestRelationships({
|
||||
data: docs,
|
||||
key: "id",
|
||||
parentMapper: (el) => ({
|
||||
_id: el.id,
|
||||
...SecretsV2Schema.parse(el)
|
||||
}),
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "reminderRecipientUserId",
|
||||
label: "recipients" as const,
|
||||
mapper: ({ reminderRecipientUserId }) => reminderRecipientUserId
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindSecretsWithReminderRecipients" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...secretOrm,
|
||||
update,
|
||||
@@ -852,6 +892,7 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
countByFolderIds,
|
||||
findOne,
|
||||
find,
|
||||
invalidateSecretCacheByProjectId
|
||||
invalidateSecretCacheByProjectId,
|
||||
findSecretsWithReminderRecipients
|
||||
};
|
||||
};
|
||||
|
||||
@@ -231,18 +231,7 @@ export const fnSecretBulkUpdate = async ({
|
||||
const sanitizedInputSecrets = inputSecrets.map(
|
||||
({
|
||||
filter,
|
||||
data: {
|
||||
skipMultilineEncoding,
|
||||
type,
|
||||
key,
|
||||
encryptedValue,
|
||||
userId,
|
||||
encryptedComment,
|
||||
metadata,
|
||||
secretMetadata,
|
||||
reminderNote,
|
||||
reminderRepeatDays
|
||||
}
|
||||
data: { skipMultilineEncoding, type, key, encryptedValue, userId, encryptedComment, metadata, secretMetadata }
|
||||
}) => ({
|
||||
filter: { ...filter, folderId },
|
||||
data: {
|
||||
@@ -252,9 +241,7 @@ export const fnSecretBulkUpdate = async ({
|
||||
userId,
|
||||
encryptedComment,
|
||||
metadata: JSON.stringify(metadata || secretMetadata || []),
|
||||
reminderNote,
|
||||
encryptedValue,
|
||||
reminderRepeatDays
|
||||
encryptedValue
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -270,9 +257,7 @@ export const fnSecretBulkUpdate = async ({
|
||||
encryptedComment,
|
||||
version,
|
||||
metadata,
|
||||
reminderNote,
|
||||
encryptedValue,
|
||||
reminderRepeatDays,
|
||||
id: secretId
|
||||
}) => ({
|
||||
skipMultilineEncoding,
|
||||
@@ -282,9 +267,7 @@ export const fnSecretBulkUpdate = async ({
|
||||
encryptedComment,
|
||||
version,
|
||||
metadata: metadata ? JSON.stringify(metadata) : [],
|
||||
reminderNote,
|
||||
encryptedValue,
|
||||
reminderRepeatDays,
|
||||
folderId,
|
||||
secretId,
|
||||
userActorId,
|
||||
@@ -407,6 +390,7 @@ export const fnSecretBulkDelete = async ({
|
||||
secretQueueService,
|
||||
folderCommitService,
|
||||
secretVersionDAL,
|
||||
projectId,
|
||||
commitChanges
|
||||
}: TFnSecretBulkDelete) => {
|
||||
const deletedSecrets = await secretDAL.deleteMany(
|
||||
@@ -419,11 +403,14 @@ export const fnSecretBulkDelete = async ({
|
||||
tx
|
||||
);
|
||||
|
||||
await Promise.allSettled(
|
||||
await Promise.all(
|
||||
deletedSecrets
|
||||
.filter(({ reminderRepeatDays }) => Boolean(reminderRepeatDays))
|
||||
.map(({ id, reminderRepeatDays }) =>
|
||||
secretQueueService.removeSecretReminder({ secretId: id, repeatDays: reminderRepeatDays as number }, tx)
|
||||
secretQueueService.removeSecretReminder(
|
||||
{ secretId: id, repeatDays: reminderRepeatDays as number, projectId },
|
||||
tx
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ import { TCommitResourceChangeDTO, TFolderCommitServiceFactory } from "../folder
|
||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||
import { KmsDataKey } from "../kms/kms-types";
|
||||
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||
import { TReminderServiceFactory } from "../reminder/reminder-types";
|
||||
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
|
||||
import { TSecretQueueFactory } from "../secret/secret-queue";
|
||||
import { TGetASecretByIdDTO } from "../secret/secret-types";
|
||||
@@ -115,6 +116,7 @@ type TSecretV2BridgeServiceFactoryDep = {
|
||||
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
||||
keyStore: Pick<TKeyStoreFactory, "getItem" | "setExpiry" | "setItemWithExpiry" | "deleteItem">;
|
||||
reminderService: Pick<TReminderServiceFactory, "createReminder" | "getReminder">;
|
||||
};
|
||||
|
||||
export type TSecretV2BridgeServiceFactory = ReturnType<typeof secretV2BridgeServiceFactory>;
|
||||
@@ -139,7 +141,8 @@ export const secretV2BridgeServiceFactory = ({
|
||||
secretApprovalRequestSecretDAL,
|
||||
kmsService,
|
||||
resourceMetadataDAL,
|
||||
keyStore
|
||||
keyStore,
|
||||
reminderService
|
||||
}: TSecretV2BridgeServiceFactoryDep) => {
|
||||
const $validateSecretReferences = async (
|
||||
projectId: string,
|
||||
@@ -311,7 +314,6 @@ export const secretV2BridgeServiceFactory = ({
|
||||
{
|
||||
version: 1,
|
||||
type,
|
||||
reminderRepeatDays: inputSecretData.secretReminderRepeatDays,
|
||||
encryptedComment: setKnexStringValue(
|
||||
inputSecretData.secretComment,
|
||||
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob
|
||||
@@ -319,7 +321,6 @@ export const secretV2BridgeServiceFactory = ({
|
||||
encryptedValue: inputSecretData.secretValue
|
||||
? secretManagerEncryptor({ plainText: Buffer.from(inputSecretData.secretValue) }).cipherTextBlob
|
||||
: undefined,
|
||||
reminderNote: inputSecretData.secretReminderNote,
|
||||
skipMultilineEncoding: inputSecretData.skipMultilineEncoding,
|
||||
key: secretName,
|
||||
userId: inputSecret.type === SecretType.Personal ? actorId : null,
|
||||
@@ -345,6 +346,20 @@ export const secretV2BridgeServiceFactory = ({
|
||||
return createdSecret;
|
||||
});
|
||||
|
||||
if (inputSecret.secretReminderRepeatDays) {
|
||||
await reminderService.createReminder({
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
reminder: {
|
||||
secretId: secret.id,
|
||||
message: inputSecret.secretReminderNote,
|
||||
repeatDays: inputSecret.secretReminderRepeatDays
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await secretDAL.invalidateSecretCacheByProjectId(projectId);
|
||||
if (inputSecret.type === SecretType.Shared) {
|
||||
await snapshotService.performSnapshot(folderId);
|
||||
@@ -521,12 +536,10 @@ export const secretV2BridgeServiceFactory = ({
|
||||
{
|
||||
filter: { id: secretId },
|
||||
data: {
|
||||
reminderRepeatDays: inputSecret.secretReminderRepeatDays,
|
||||
encryptedComment: setKnexStringValue(
|
||||
inputSecret.secretComment,
|
||||
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob
|
||||
),
|
||||
reminderNote: inputSecret.secretReminderNote,
|
||||
skipMultilineEncoding: inputSecret.skipMultilineEncoding,
|
||||
key: inputSecret.newSecretName || secretName,
|
||||
tags: inputSecret.tagIds,
|
||||
@@ -547,19 +560,20 @@ export const secretV2BridgeServiceFactory = ({
|
||||
tx
|
||||
})
|
||||
);
|
||||
await secretQueueService.handleSecretReminder({
|
||||
newSecret: {
|
||||
id: updatedSecret[0].id,
|
||||
...inputSecret
|
||||
},
|
||||
oldSecret: {
|
||||
id: secret.id,
|
||||
secretReminderNote: secret.reminderNote,
|
||||
secretReminderRepeatDays: secret.reminderRepeatDays,
|
||||
secretReminderRecipients: secret.secretReminderRecipients?.map((el) => el.user.id)
|
||||
},
|
||||
projectId
|
||||
});
|
||||
if (inputSecret.secretReminderRepeatDays) {
|
||||
await reminderService.createReminder({
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
reminder: {
|
||||
secretId: secret.id,
|
||||
message: inputSecret.secretReminderNote,
|
||||
repeatDays: inputSecret.secretReminderRepeatDays,
|
||||
recipients: inputSecret.secretReminderRecipients
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await secretDAL.invalidateSecretCacheByProjectId(projectId);
|
||||
if (inputSecret.type === SecretType.Shared) {
|
||||
@@ -1930,12 +1944,10 @@ export const secretV2BridgeServiceFactory = ({
|
||||
return {
|
||||
filter: { id: originalSecret.id, type: SecretType.Shared },
|
||||
data: {
|
||||
reminderRepeatDays: el.secretReminderRepeatDays,
|
||||
encryptedComment: setKnexStringValue(
|
||||
el.secretComment,
|
||||
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob
|
||||
),
|
||||
reminderNote: el.secretReminderNote,
|
||||
skipMultilineEncoding: el.skipMultilineEncoding,
|
||||
key: el.newSecretName || el.secretKey,
|
||||
tags: el.tagIds,
|
||||
@@ -2601,9 +2613,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
key: doc.key,
|
||||
encryptedComment: doc.encryptedComment,
|
||||
skipMultilineEncoding: doc.skipMultilineEncoding,
|
||||
reminderNote: doc.reminderNote,
|
||||
secretMetadata: doc.secretMetadata,
|
||||
reminderRepeatDays: doc.reminderRepeatDays,
|
||||
...(doc.encryptedValue
|
||||
? {
|
||||
encryptedValue: doc.encryptedValue,
|
||||
@@ -2997,6 +3007,11 @@ export const secretV2BridgeServiceFactory = ({
|
||||
});
|
||||
};
|
||||
|
||||
const findSecretIdsByFolderIdAndKeys = async ({ folderId, keys }: { folderId: string; keys: string[] }) => {
|
||||
const secrets = await secretDAL.find({ folderId, $in: { [`${TableName.SecretV2}.key` as "key"]: keys } });
|
||||
return secrets.map((el) => ({ id: el.id, key: el.key }));
|
||||
};
|
||||
|
||||
return {
|
||||
createSecret,
|
||||
deleteSecret,
|
||||
@@ -3016,6 +3031,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
getSecretsByFolderMappings,
|
||||
getSecretById,
|
||||
getAccessibleSecrets,
|
||||
getSecretVersionsByIds
|
||||
getSecretVersionsByIds,
|
||||
findSecretIdsByFolderIdAndKeys
|
||||
};
|
||||
};
|
||||
|
||||
@@ -249,6 +249,7 @@ export type TCreateSecretReminderDTO = {
|
||||
export type TRemoveSecretReminderDTO = {
|
||||
secretId: string;
|
||||
repeatDays: number;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export type TBackFillSecretReferencesDTO = TProjectPermission;
|
||||
|
||||
Reference in New Issue
Block a user