mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-19 09:12:39 +00:00
feat(secret-reminders): rework secret reminders logic
This commit is contained in:
@@ -5,8 +5,6 @@ import { TDbClient } from "@app/db";
|
||||
import { SecretsSchema, SecretType, TableName, TSecrets, TSecretsUpdate } from "@app/db/schemas";
|
||||
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
|
||||
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { QueueName, TQueueServiceFactory } from "@app/queue";
|
||||
|
||||
export type TSecretDALFactory = ReturnType<typeof secretDALFactory>;
|
||||
|
||||
@@ -383,94 +381,6 @@ export const secretDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const pruneSecretReminders = async (queueService: TQueueServiceFactory) => {
|
||||
const REMINDER_PRUNE_BATCH_SIZE = 5_000;
|
||||
const MAX_RETRY_ON_FAILURE = 3;
|
||||
let numberOfRetryOnFailure = 0;
|
||||
let deletedReminderCount = 0;
|
||||
|
||||
logger.info(`${QueueName.DailyResourceCleanUp}: secret reminders started`);
|
||||
|
||||
try {
|
||||
const repeatableJobs = await queueService.getRepeatableJobs(QueueName.SecretReminder);
|
||||
const reminderJobs = repeatableJobs
|
||||
.map((job) => ({ secretId: job.id?.replace("reminder-", "") as string, jobKey: job.key }))
|
||||
.filter(Boolean);
|
||||
|
||||
if (reminderJobs.length === 0) {
|
||||
logger.info(`${QueueName.DailyResourceCleanUp}: no reminder jobs found`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let offset = 0; offset < reminderJobs.length; offset += REMINDER_PRUNE_BATCH_SIZE) {
|
||||
try {
|
||||
const batchIds = reminderJobs.slice(offset, offset + REMINDER_PRUNE_BATCH_SIZE).map((r) => r.secretId);
|
||||
|
||||
const payload = {
|
||||
$in: {
|
||||
id: batchIds
|
||||
}
|
||||
};
|
||||
|
||||
const opts = {
|
||||
limit: REMINDER_PRUNE_BATCH_SIZE
|
||||
};
|
||||
|
||||
// Find existing secrets with pagination
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const [secrets, secretsV2] = await Promise.all([
|
||||
ormify(db, TableName.Secret).find(payload, opts),
|
||||
ormify(db, TableName.SecretV2).find(payload, opts)
|
||||
]);
|
||||
|
||||
const foundSecretIds = new Set([
|
||||
...secrets.map((secret) => secret.id),
|
||||
...secretsV2.map((secret) => secret.id)
|
||||
]);
|
||||
|
||||
// Find IDs that don't exist in either table
|
||||
const secretIdsNotFound = batchIds.filter((secretId) => !foundSecretIds.has(secretId));
|
||||
|
||||
// Delete reminders for non-existent secrets
|
||||
for (const secretId of secretIdsNotFound) {
|
||||
const jobKey = reminderJobs.find((r) => r.secretId === secretId)?.jobKey;
|
||||
|
||||
if (jobKey) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await queueService.stopRepeatableJobByKey(QueueName.SecretReminder, jobKey);
|
||||
deletedReminderCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
numberOfRetryOnFailure = 0;
|
||||
} catch (error) {
|
||||
numberOfRetryOnFailure += 1;
|
||||
logger.error(error, `Failed to process batch at offset ${offset}`);
|
||||
|
||||
if (numberOfRetryOnFailure >= MAX_RETRY_ON_FAILURE) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Retry the current batch
|
||||
offset -= REMINDER_PRUNE_BATCH_SIZE;
|
||||
|
||||
// eslint-disable-next-line no-promise-executor-return, @typescript-eslint/no-loop-func, no-await-in-loop
|
||||
await new Promise((resolve) => setTimeout(resolve, 500 * numberOfRetryOnFailure));
|
||||
}
|
||||
|
||||
// Small delay between batches
|
||||
// eslint-disable-next-line no-promise-executor-return, @typescript-eslint/no-loop-func, no-await-in-loop
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error, "Failed to complete secret reminder pruning");
|
||||
} finally {
|
||||
logger.info(
|
||||
`${QueueName.DailyResourceCleanUp}: secret reminders completed. Deleted ${deletedReminderCount} reminders`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...secretOrm,
|
||||
update,
|
||||
@@ -485,7 +395,6 @@ export const secretDALFactory = (db: TDbClient) => {
|
||||
upsertSecretReferences,
|
||||
findReferencedSecretReferences,
|
||||
findAllProjectSecretValues,
|
||||
pruneSecretReminders,
|
||||
findManySecretsWithTags
|
||||
};
|
||||
};
|
||||
|
||||
@@ -20,11 +20,9 @@ import {
|
||||
decryptSymmetric128BitHexKeyUTF8,
|
||||
encryptSymmetric128BitHexKeyUTF8
|
||||
} from "@app/lib/crypto";
|
||||
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { groupBy, unique } from "@app/lib/fn";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||
import {
|
||||
fnSecretBulkInsert as fnSecretV2BridgeBulkInsert,
|
||||
fnSecretBulkUpdate as fnSecretV2BridgeBulkUpdate,
|
||||
@@ -36,6 +34,7 @@ import { KmsDataKey } from "../kms/kms-types";
|
||||
import { getBotKeyFnFactory } from "../project-bot/project-bot-fns";
|
||||
import { TProjectBotServiceFactory } from "../project-bot/project-bot-service";
|
||||
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||
import { TReminderServiceFactory } from "../reminder/reminder-service";
|
||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
||||
import { TSecretDALFactory } from "./secret-dal";
|
||||
@@ -1184,14 +1183,14 @@ export const decryptSecretWithBot = (
|
||||
type TFnDeleteProjectSecretReminders = {
|
||||
secretDAL: Pick<TSecretDALFactory, "find">;
|
||||
secretV2BridgeDAL: Pick<TSecretV2BridgeDALFactory, "find">;
|
||||
queueService: Pick<TQueueServiceFactory, "stopRepeatableJob">;
|
||||
reminderService: Pick<TReminderServiceFactory, "deleteReminderBySecretId">;
|
||||
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
|
||||
folderDAL: Pick<TSecretFolderDALFactory, "findByProjectId">;
|
||||
};
|
||||
|
||||
export const fnDeleteProjectSecretReminders = async (
|
||||
projectId: string,
|
||||
{ secretDAL, secretV2BridgeDAL, queueService, projectBotService, folderDAL }: TFnDeleteProjectSecretReminders
|
||||
{ secretDAL, secretV2BridgeDAL, reminderService, projectBotService, folderDAL }: TFnDeleteProjectSecretReminders
|
||||
) => {
|
||||
const projectFolders = await folderDAL.findByProjectId(projectId);
|
||||
const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId, false);
|
||||
@@ -1206,23 +1205,13 @@ export const fnDeleteProjectSecretReminders = async (
|
||||
$notNull: ["secretReminderRepeatDays"]
|
||||
});
|
||||
|
||||
const appCfg = getConfig();
|
||||
for await (const secret of projectSecrets) {
|
||||
const repeatDays = shouldUseSecretV2Bridge
|
||||
? (secret as { reminderRepeatDays: number }).reminderRepeatDays
|
||||
: (secret as { secretReminderRepeatDays: number }).secretReminderRepeatDays;
|
||||
|
||||
// We're using the queue service directly to get around conflicting imports.
|
||||
if (repeatDays) {
|
||||
await queueService.stopRepeatableJob(
|
||||
QueueName.SecretReminder,
|
||||
QueueJobs.SecretReminder,
|
||||
{
|
||||
// on prod it this will be in days, in development this will be second
|
||||
every: appCfg.NODE_ENV === "development" ? secondsToMillis(repeatDays) : daysToMillisecond(repeatDays)
|
||||
},
|
||||
`reminder-${secret.id}`
|
||||
);
|
||||
await reminderService.deleteReminderBySecretId(secret.id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,7 +20,6 @@ import { KeyStorePrefixes, KeyStoreTtls, TKeyStoreFactory } from "@app/keystore/
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { getTimeDifferenceInSeconds, groupBy, isSamePath, unique } from "@app/lib/fn";
|
||||
import { logger } from "@app/lib/logger";
|
||||
@@ -41,7 +40,6 @@ import { TIntegrationAuthServiceFactory } from "../integration-auth/integration-
|
||||
import { syncIntegrationSecrets } from "../integration-auth/integration-sync-secret";
|
||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||
import { KmsDataKey } from "../kms/kms-types";
|
||||
import { TOrgDALFactory } from "../org/org-dal";
|
||||
import { TOrgServiceFactory } from "../org/org-service";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
import { createProjectKey } from "../project/project-fns";
|
||||
@@ -50,12 +48,12 @@ import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
|
||||
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
|
||||
import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal";
|
||||
import { TReminderServiceFactory } from "../reminder/reminder-service";
|
||||
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
|
||||
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
|
||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
|
||||
import { fnSecretsV2FromImports } from "../secret-import/secret-import-fns";
|
||||
import { TSecretReminderRecipientsDALFactory } from "../secret-reminder-recipients/secret-reminder-recipients-dal";
|
||||
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
||||
import { expandSecretReferencesFactory, getAllSecretReferences } from "../secret-v2-bridge/secret-v2-bridge-fns";
|
||||
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
|
||||
@@ -93,7 +91,6 @@ type TSecretQueueFactoryDep = {
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "create">;
|
||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findAllProjectMembers" | "create">;
|
||||
smtpService: TSmtpService;
|
||||
orgDAL: Pick<TOrgDALFactory, "findOrgByProjectId">;
|
||||
secretVersionDAL: TSecretVersionDALFactory;
|
||||
secretBlindIndexDAL: TSecretBlindIndexDALFactory;
|
||||
secretTagDAL: TSecretTagDALFactory;
|
||||
@@ -113,11 +110,11 @@ type TSecretQueueFactoryDep = {
|
||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
||||
folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">;
|
||||
secretReminderRecipientsDAL: Pick<
|
||||
TSecretReminderRecipientsDALFactory,
|
||||
"delete" | "findUsersBySecretId" | "insertMany" | "transaction"
|
||||
>;
|
||||
secretSyncQueue: Pick<TSecretSyncQueueFactory, "queueSecretSyncsSyncSecretsByPath">;
|
||||
reminderService: Pick<
|
||||
TReminderServiceFactory,
|
||||
"createReminderInternal" | "deleteReminderBySecretId" | "removeReminderRecipients"
|
||||
>;
|
||||
};
|
||||
|
||||
export type TGetSecrets = {
|
||||
@@ -155,7 +152,6 @@ export const secretQueueFactory = ({
|
||||
userDAL,
|
||||
webhookDAL,
|
||||
projectEnvDAL,
|
||||
orgDAL,
|
||||
smtpService,
|
||||
projectDAL,
|
||||
projectBotDAL,
|
||||
@@ -178,9 +174,9 @@ export const secretQueueFactory = ({
|
||||
projectUserMembershipRoleDAL,
|
||||
projectKeyDAL,
|
||||
resourceMetadataDAL,
|
||||
secretReminderRecipientsDAL,
|
||||
secretSyncQueue,
|
||||
folderCommitService
|
||||
folderCommitService,
|
||||
reminderService
|
||||
}: TSecretQueueFactoryDep) => {
|
||||
const integrationMeter = opentelemetry.metrics.getMeter("Integrations");
|
||||
const errorHistogram = integrationMeter.createHistogram("integration_secret_sync_errors", {
|
||||
@@ -190,19 +186,8 @@ export const secretQueueFactory = ({
|
||||
|
||||
const removeSecretReminder = async ({ deleteRecipients = true, ...dto }: TRemoveSecretReminderDTO, tx?: Knex) => {
|
||||
if (deleteRecipients) {
|
||||
await secretReminderRecipientsDAL.delete({ secretId: dto.secretId }, tx);
|
||||
await reminderService.deleteReminderBySecretId(dto.secretId, tx);
|
||||
}
|
||||
|
||||
const appCfg = getConfig();
|
||||
await queueService.stopRepeatableJob(
|
||||
QueueName.SecretReminder,
|
||||
QueueJobs.SecretReminder,
|
||||
{
|
||||
// on prod it this will be in days, in development this will be second
|
||||
every: appCfg.NODE_ENV === "development" ? secondsToMillis(dto.repeatDays) : daysToMillisecond(dto.repeatDays)
|
||||
},
|
||||
`reminder-${dto.secretId}`
|
||||
);
|
||||
};
|
||||
|
||||
const $generateActor = async (actorId?: string, isManual?: boolean): Promise<Actor> => {
|
||||
@@ -238,15 +223,8 @@ export const secretQueueFactory = ({
|
||||
.replace(":", "-");
|
||||
};
|
||||
|
||||
const addSecretReminder = async ({
|
||||
oldSecret,
|
||||
newSecret,
|
||||
projectId,
|
||||
deleteRecipients = true
|
||||
}: TCreateSecretReminderDTO) => {
|
||||
const addSecretReminder = async ({ oldSecret, newSecret, secretReminderRecipients }: TCreateSecretReminderDTO) => {
|
||||
try {
|
||||
const appCfg = getConfig();
|
||||
|
||||
if (oldSecret.id !== newSecret.id) {
|
||||
throw new BadRequestError({
|
||||
name: "SecretReminderIdMismatch",
|
||||
@@ -261,38 +239,12 @@ export const secretQueueFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
// If the secret already has a reminder, we should remove the existing one first.
|
||||
if (oldSecret.secretReminderRepeatDays) {
|
||||
await removeSecretReminder({
|
||||
repeatDays: oldSecret.secretReminderRepeatDays,
|
||||
secretId: oldSecret.id,
|
||||
deleteRecipients
|
||||
});
|
||||
}
|
||||
|
||||
await queueService.queue(
|
||||
QueueName.SecretReminder,
|
||||
QueueJobs.SecretReminder,
|
||||
{
|
||||
note: newSecret.secretReminderNote,
|
||||
projectId,
|
||||
repeatDays: newSecret.secretReminderRepeatDays,
|
||||
secretId: newSecret.id
|
||||
},
|
||||
{
|
||||
jobId: `reminder-${newSecret.id}`,
|
||||
repeat: {
|
||||
// on prod it this will be in days, in development this will be second
|
||||
every:
|
||||
appCfg.NODE_ENV === "development"
|
||||
? secondsToMillis(newSecret.secretReminderRepeatDays)
|
||||
: daysToMillisecond(newSecret.secretReminderRepeatDays),
|
||||
immediately: true
|
||||
},
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true
|
||||
}
|
||||
);
|
||||
await reminderService.createReminderInternal({
|
||||
secretId: newSecret.id,
|
||||
message: newSecret.secretReminderNote,
|
||||
repeatDays: newSecret.secretReminderRepeatDays,
|
||||
recipients: secretReminderRecipients
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err, "Failed to create secret reminder.");
|
||||
throw new BadRequestError({
|
||||
@@ -302,58 +254,31 @@ export const secretQueueFactory = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleSecretReminder = async ({ newSecret, oldSecret, projectId }: THandleReminderDTO) => {
|
||||
const handleSecretReminder = async ({ newSecret, oldSecret }: THandleReminderDTO) => {
|
||||
const { secretReminderRepeatDays, secretReminderNote, secretReminderRecipients } = newSecret;
|
||||
|
||||
const recipientsUpdated =
|
||||
secretReminderRecipients?.some(
|
||||
(newId) => !oldSecret.secretReminderRecipients?.find((oldId) => newId === oldId)
|
||||
) || secretReminderRecipients?.length !== oldSecret.secretReminderRecipients?.length;
|
||||
|
||||
await secretReminderRecipientsDAL.transaction(async (tx) => {
|
||||
if (newSecret.type !== SecretType.Personal && secretReminderRepeatDays !== undefined) {
|
||||
if (
|
||||
(secretReminderRepeatDays && oldSecret.secretReminderRepeatDays !== secretReminderRepeatDays) ||
|
||||
(secretReminderNote && oldSecret.secretReminderNote !== secretReminderNote)
|
||||
) {
|
||||
await addSecretReminder({
|
||||
oldSecret,
|
||||
newSecret,
|
||||
projectId,
|
||||
deleteRecipients: false
|
||||
});
|
||||
} else if (
|
||||
secretReminderRepeatDays === null &&
|
||||
secretReminderNote === null &&
|
||||
oldSecret.secretReminderRepeatDays
|
||||
) {
|
||||
await removeSecretReminder({
|
||||
secretId: oldSecret.id,
|
||||
repeatDays: oldSecret.secretReminderRepeatDays
|
||||
});
|
||||
}
|
||||
if (newSecret.type !== SecretType.Personal && secretReminderRepeatDays !== undefined) {
|
||||
if (
|
||||
(secretReminderRepeatDays && oldSecret.secretReminderRepeatDays !== secretReminderRepeatDays) ||
|
||||
(secretReminderNote && oldSecret.secretReminderNote !== secretReminderNote)
|
||||
) {
|
||||
await addSecretReminder({
|
||||
oldSecret,
|
||||
newSecret,
|
||||
secretReminderRecipients: secretReminderRecipients ?? [],
|
||||
deleteRecipients: false
|
||||
});
|
||||
} else if (
|
||||
secretReminderRepeatDays === null &&
|
||||
secretReminderNote === null &&
|
||||
oldSecret.secretReminderRepeatDays
|
||||
) {
|
||||
await removeSecretReminder({
|
||||
secretId: oldSecret.id,
|
||||
repeatDays: oldSecret.secretReminderRepeatDays
|
||||
});
|
||||
}
|
||||
|
||||
if (recipientsUpdated) {
|
||||
// if no recipients, delete all existing recipients
|
||||
if (!secretReminderRecipients?.length) {
|
||||
const existingRecipients = await secretReminderRecipientsDAL.findUsersBySecretId(newSecret.id, tx);
|
||||
if (existingRecipients) {
|
||||
await secretReminderRecipientsDAL.delete({ secretId: newSecret.id }, tx);
|
||||
}
|
||||
} else {
|
||||
await secretReminderRecipientsDAL.delete({ secretId: newSecret.id }, tx);
|
||||
await secretReminderRecipientsDAL.insertMany(
|
||||
secretReminderRecipients.map((r) => ({
|
||||
secretId: newSecret.id,
|
||||
userId: r,
|
||||
projectId
|
||||
})),
|
||||
tx
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
const createManySecretsRawFn = createManySecretsRawFnFactory({
|
||||
projectDAL,
|
||||
@@ -1111,62 +1036,9 @@ export const secretQueueFactory = ({
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: remove this queue (needed for queue initialization and perform the migration)
|
||||
queueService.start(QueueName.SecretReminder, async ({ data }) => {
|
||||
logger.info(`secretReminderQueue.process: [secretDocument=${data.secretId}]`);
|
||||
|
||||
const { projectId } = data;
|
||||
|
||||
const organization = await orgDAL.findOrgByProjectId(projectId);
|
||||
const project = await projectDAL.findById(projectId);
|
||||
const secret = await secretV2BridgeDAL.findById(data.secretId);
|
||||
const [folder] = await folderDAL.findSecretPathByFolderIds(project.id, [secret.folderId]);
|
||||
|
||||
const recipients = await secretReminderRecipientsDAL.findUsersBySecretId(data.secretId);
|
||||
|
||||
if (!organization) {
|
||||
logger.info(`secretReminderQueue.process: [secretDocument=${data.secretId}] no organization found`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!project) {
|
||||
logger.info(`secretReminderQueue.process: [secretDocument=${data.secretId}] no project found`);
|
||||
return;
|
||||
}
|
||||
|
||||
const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId);
|
||||
|
||||
if (!projectMembers || !projectMembers.length) {
|
||||
logger.info(`secretReminderQueue.process: [secretDocument=${data.secretId}] no project members found`);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedRecipients = recipients?.length
|
||||
? recipients.map((r) => r.email as string)
|
||||
: projectMembers.map((m) => m.user.email as string);
|
||||
|
||||
await smtpService.sendMail({
|
||||
template: SmtpTemplates.SecretReminder,
|
||||
subjectLine: "Infisical secret reminder",
|
||||
recipients: selectedRecipients,
|
||||
substitutions: {
|
||||
reminderNote: data.note, // May not be present.
|
||||
projectName: project.name,
|
||||
organizationName: organization.name
|
||||
}
|
||||
});
|
||||
|
||||
await queueService.queue(QueueName.SecretWebhook, QueueJobs.SecWebhook, {
|
||||
type: WebhookEvents.SecretReminderExpired,
|
||||
payload: {
|
||||
projectName: project.name,
|
||||
projectId: project.id,
|
||||
secretPath: folder?.path,
|
||||
environment: folder?.environmentSlug || "",
|
||||
reminderNote: data.note,
|
||||
secretName: secret?.key,
|
||||
secretId: data.secretId
|
||||
}
|
||||
});
|
||||
logger.info(`(deprecated) secretReminderQueue.process: [secretDocument=${data.secretId}]`);
|
||||
});
|
||||
|
||||
const startSecretV2Migration = async (projectId: string) => {
|
||||
|
||||
@@ -48,6 +48,7 @@ import { ChangeType } from "../folder-commit/folder-commit-service";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
import { TProjectBotServiceFactory } from "../project-bot/project-bot-service";
|
||||
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||
import { TReminderServiceFactory } from "../reminder/reminder-service";
|
||||
import { TSecretBlindIndexDALFactory } from "../secret-blind-index/secret-blind-index-dal";
|
||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
|
||||
@@ -131,6 +132,7 @@ type TSecretServiceFactoryDep = {
|
||||
"insertMany" | "insertApprovalSecretTags"
|
||||
>;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
reminderService: Pick<TReminderServiceFactory, "createReminder">;
|
||||
};
|
||||
|
||||
export type TSecretServiceFactory = ReturnType<typeof secretServiceFactory>;
|
||||
@@ -153,7 +155,8 @@ export const secretServiceFactory = ({
|
||||
secretApprovalRequestSecretDAL,
|
||||
secretV2BridgeService,
|
||||
secretApprovalRequestService,
|
||||
licenseService
|
||||
licenseService,
|
||||
reminderService
|
||||
}: TSecretServiceFactoryDep) => {
|
||||
const getSecretReference = async (projectId: string) => {
|
||||
// if bot key missing means e2e still exist
|
||||
@@ -390,8 +393,7 @@ export const secretServiceFactory = ({
|
||||
id: secrets[0].id,
|
||||
...inputSecret
|
||||
},
|
||||
oldSecret: secrets[0],
|
||||
projectId
|
||||
oldSecret: secrets[0]
|
||||
});
|
||||
|
||||
const tags = inputSecret.tags ? await secretTagDAL.findManyTagsById(projectId, inputSecret.tags) : [];
|
||||
@@ -1660,8 +1662,6 @@ export const secretServiceFactory = ({
|
||||
secretComment,
|
||||
secretValue,
|
||||
tagIds,
|
||||
reminderNote: secretReminderNote,
|
||||
reminderRepeatDays: secretReminderRepeatDays,
|
||||
secretMetadata
|
||||
}
|
||||
]
|
||||
@@ -1824,9 +1824,6 @@ export const secretServiceFactory = ({
|
||||
secretComment,
|
||||
secretValue,
|
||||
tagIds,
|
||||
reminderNote: secretReminderNote,
|
||||
reminderRepeatDays: secretReminderRepeatDays,
|
||||
secretReminderRecipients,
|
||||
secretMetadata
|
||||
}
|
||||
]
|
||||
@@ -1835,9 +1832,6 @@ export const secretServiceFactory = ({
|
||||
return { type: SecretProtectionType.Approval as const, approval };
|
||||
}
|
||||
const secret = await secretV2BridgeService.updateSecret({
|
||||
secretReminderRepeatDays,
|
||||
secretReminderNote,
|
||||
secretReminderRecipients,
|
||||
skipMultilineEncoding,
|
||||
tagIds,
|
||||
secretComment,
|
||||
@@ -1855,6 +1849,22 @@ export const secretServiceFactory = ({
|
||||
secretValue,
|
||||
secretMetadata
|
||||
});
|
||||
|
||||
if (secretReminderRepeatDays) {
|
||||
await reminderService.createReminder({
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
projectId,
|
||||
reminder: {
|
||||
secretId: secret.id,
|
||||
message: secretReminderNote,
|
||||
repeatDays: secretReminderRepeatDays,
|
||||
recipients: secretReminderRecipients
|
||||
}
|
||||
});
|
||||
}
|
||||
return { type: SecretProtectionType.Direct as const, secret };
|
||||
}
|
||||
|
||||
@@ -2246,6 +2256,30 @@ export const secretServiceFactory = ({
|
||||
secrets: inputSecrets,
|
||||
mode
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
inputSecrets
|
||||
.filter((el) => el.secretReminderRepeatDays)
|
||||
.map(async (secret) => {
|
||||
await reminderService.createReminder({
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
projectId,
|
||||
reminder: {
|
||||
secretId: secrets.find(
|
||||
(el) =>
|
||||
(el.secretKey === secret.secretKey || el.secretKey === secret.newSecretName) &&
|
||||
el.secretPath === (secret.secretPath || secretPath)
|
||||
)?.id,
|
||||
message: secret.secretReminderNote,
|
||||
repeatDays: secret.secretReminderRepeatDays
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
return { type: SecretProtectionType.Direct as const, secrets };
|
||||
}
|
||||
|
||||
|
||||
@@ -310,6 +310,7 @@ export type TUpdateManySecretRawDTO = Omit<TProjectPermission, "projectId"> & {
|
||||
secretMetadata?: ResourceMetadataDTO;
|
||||
secretReminderRepeatDays?: number | null;
|
||||
secretReminderNote?: string | null;
|
||||
secretPath?: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
@@ -403,13 +404,12 @@ export type TFnSecretBlindIndexCheckV2 = {
|
||||
export type THandleReminderDTO = {
|
||||
newSecret: TPartialInputSecret;
|
||||
oldSecret: TPartialSecret;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export type TCreateSecretReminderDTO = {
|
||||
oldSecret: TPartialSecret;
|
||||
newSecret: TPartialSecret;
|
||||
projectId: string;
|
||||
secretReminderRecipients: string[];
|
||||
|
||||
deleteRecipients?: boolean;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user