mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #3453 from Infisical/daniel/reminders
feat(reminders): specify recipients
This commit is contained in:
10
backend/src/@types/knex.d.ts
vendored
10
backend/src/@types/knex.d.ts
vendored
@@ -423,6 +423,11 @@ import {
|
|||||||
TWorkflowIntegrationsInsert,
|
TWorkflowIntegrationsInsert,
|
||||||
TWorkflowIntegrationsUpdate
|
TWorkflowIntegrationsUpdate
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
|
import {
|
||||||
|
TSecretReminderRecipients,
|
||||||
|
TSecretReminderRecipientsInsert,
|
||||||
|
TSecretReminderRecipientsUpdate
|
||||||
|
} from "@app/db/schemas/secret-reminder-recipients";
|
||||||
|
|
||||||
declare module "knex" {
|
declare module "knex" {
|
||||||
namespace Knex {
|
namespace Knex {
|
||||||
@@ -994,5 +999,10 @@ declare module "knex/types/tables" {
|
|||||||
TSecretRotationV2SecretMappingsInsert,
|
TSecretRotationV2SecretMappingsInsert,
|
||||||
TSecretRotationV2SecretMappingsUpdate
|
TSecretRotationV2SecretMappingsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.SecretReminderRecipients]: KnexOriginal.CompositeTableType<
|
||||||
|
TSecretReminderRecipients,
|
||||||
|
TSecretReminderRecipientsInsert,
|
||||||
|
TSecretReminderRecipientsUpdate
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasSecretReminderRecipientsTable = await knex.schema.hasTable(TableName.SecretReminderRecipients);
|
||||||
|
|
||||||
|
if (!hasSecretReminderRecipientsTable) {
|
||||||
|
await knex.schema.createTable(TableName.SecretReminderRecipients, (table) => {
|
||||||
|
table.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
table.timestamps(true, true, true);
|
||||||
|
table.uuid("secretId").notNullable();
|
||||||
|
table.uuid("userId").notNullable();
|
||||||
|
table.string("projectId").notNullable();
|
||||||
|
|
||||||
|
// Based on userId rather than project membership ID so we can easily extend group support in the future if need be.
|
||||||
|
// This does however mean we need to manually clean up once a user is removed from a project.
|
||||||
|
table.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||||
|
table.foreign("secretId").references("id").inTable(TableName.SecretV2).onDelete("CASCADE");
|
||||||
|
table.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
|
|
||||||
|
table.index("secretId");
|
||||||
|
table.unique(["secretId", "userId"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasSecretReminderRecipientsTable = await knex.schema.hasTable(TableName.SecretReminderRecipients);
|
||||||
|
|
||||||
|
if (hasSecretReminderRecipientsTable) {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SecretReminderRecipients);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ export const CertificatesSchema = z.object({
|
|||||||
notAfter: z.date(),
|
notAfter: z.date(),
|
||||||
revokedAt: z.date().nullable().optional(),
|
revokedAt: z.date().nullable().optional(),
|
||||||
revocationReason: z.number().nullable().optional(),
|
revocationReason: z.number().nullable().optional(),
|
||||||
altNames: z.string().default("").nullable().optional(),
|
altNames: z.string().nullable().optional(),
|
||||||
caCertId: z.string().uuid(),
|
caCertId: z.string().uuid(),
|
||||||
certificateTemplateId: z.string().uuid().nullable().optional(),
|
certificateTemplateId: z.string().uuid().nullable().optional(),
|
||||||
keyUsages: z.string().array().nullable().optional(),
|
keyUsages: z.string().array().nullable().optional(),
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const KmipOrgServerCertificatesSchema = z.object({
|
|||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
commonName: z.string(),
|
commonName: z.string(),
|
||||||
altNames: z.string(),
|
altNames: z.string().nullable().optional(),
|
||||||
serialNumber: z.string(),
|
serialNumber: z.string(),
|
||||||
keyAlgorithm: z.string(),
|
keyAlgorithm: z.string(),
|
||||||
issuedAt: z.date(),
|
issuedAt: z.date(),
|
||||||
|
|||||||
@@ -146,7 +146,8 @@ export enum TableName {
|
|||||||
KmipOrgServerCertificates = "kmip_org_server_certificates",
|
KmipOrgServerCertificates = "kmip_org_server_certificates",
|
||||||
KmipClientCertificates = "kmip_client_certificates",
|
KmipClientCertificates = "kmip_client_certificates",
|
||||||
SecretRotationV2 = "secret_rotations_v2",
|
SecretRotationV2 = "secret_rotations_v2",
|
||||||
SecretRotationV2SecretMapping = "secret_rotation_v2_secret_mappings"
|
SecretRotationV2SecretMapping = "secret_rotation_v2_secret_mappings",
|
||||||
|
SecretReminderRecipients = "secret_reminder_recipients"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ export const OidcConfigsSchema = z.object({
|
|||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
lastUsed: z.date().nullable().optional(),
|
lastUsed: z.date().nullable().optional(),
|
||||||
|
manageGroupMemberships: z.boolean().default(false),
|
||||||
encryptedOidcClientId: zodBuffer,
|
encryptedOidcClientId: zodBuffer,
|
||||||
encryptedOidcClientSecret: zodBuffer,
|
encryptedOidcClientSecret: zodBuffer,
|
||||||
manageGroupMemberships: z.boolean().default(false),
|
|
||||||
jwtSignatureAlgorithm: z.string().default("RS256")
|
jwtSignatureAlgorithm: z.string().default("RS256")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export const OrganizationsSchema = z.object({
|
|||||||
defaultMembershipRole: z.string().default("member"),
|
defaultMembershipRole: z.string().default("member"),
|
||||||
enforceMfa: z.boolean().default(false),
|
enforceMfa: z.boolean().default(false),
|
||||||
selectedMfaMethod: z.string().nullable().optional(),
|
selectedMfaMethod: z.string().nullable().optional(),
|
||||||
|
secretShareSendToAnyone: z.boolean().default(true).nullable().optional(),
|
||||||
allowSecretSharingOutsideOrganization: z.boolean().default(true).nullable().optional(),
|
allowSecretSharingOutsideOrganization: z.boolean().default(true).nullable().optional(),
|
||||||
shouldUseNewPrivilegeSystem: z.boolean().default(true),
|
shouldUseNewPrivilegeSystem: z.boolean().default(true),
|
||||||
privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(),
|
privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(),
|
||||||
|
|||||||
23
backend/src/db/schemas/secret-reminder-recipients.ts
Normal file
23
backend/src/db/schemas/secret-reminder-recipients.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const SecretReminderRecipientsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
secretId: z.string().uuid(),
|
||||||
|
userId: z.string().uuid(),
|
||||||
|
projectId: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSecretReminderRecipients = z.infer<typeof SecretReminderRecipientsSchema>;
|
||||||
|
export type TSecretReminderRecipientsInsert = Omit<z.input<typeof SecretReminderRecipientsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TSecretReminderRecipientsUpdate = Partial<
|
||||||
|
Omit<z.input<typeof SecretReminderRecipientsSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
||||||
@@ -33,6 +33,7 @@ export type TApprovalCreateSecretV2Bridge = {
|
|||||||
secretComment?: string;
|
secretComment?: string;
|
||||||
reminderNote?: string | null;
|
reminderNote?: string | null;
|
||||||
reminderRepeatDays?: number | null;
|
reminderRepeatDays?: number | null;
|
||||||
|
secretReminderRecipients?: string[] | null;
|
||||||
skipMultilineEncoding?: boolean;
|
skipMultilineEncoding?: boolean;
|
||||||
metadata?: Record<string, string>;
|
metadata?: Record<string, string>;
|
||||||
secretMetadata?: ResourceMetadataDTO;
|
secretMetadata?: ResourceMetadataDTO;
|
||||||
|
|||||||
@@ -807,6 +807,8 @@ export const RAW_SECRETS = {
|
|||||||
tagIds: "The ID of the tags to be attached to the updated secret.",
|
tagIds: "The ID of the tags to be attached to the updated secret.",
|
||||||
secretReminderRepeatDays: "Interval for secret rotation notifications, measured in days.",
|
secretReminderRepeatDays: "Interval for secret rotation notifications, measured in days.",
|
||||||
secretReminderNote: "Note to be attached in notification email.",
|
secretReminderNote: "Note to be attached in notification email.",
|
||||||
|
secretReminderRecipients:
|
||||||
|
"An array of user IDs that will receive the reminder email. If not specified, all project members will receive the reminder email.",
|
||||||
newSecretName: "The new name for the secret."
|
newSecretName: "The new name for the secret."
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
|
|||||||
@@ -215,6 +215,7 @@ import { secretFolderServiceFactory } from "@app/services/secret-folder/secret-f
|
|||||||
import { secretFolderVersionDALFactory } from "@app/services/secret-folder/secret-folder-version-dal";
|
import { secretFolderVersionDALFactory } from "@app/services/secret-folder/secret-folder-version-dal";
|
||||||
import { secretImportDALFactory } from "@app/services/secret-import/secret-import-dal";
|
import { secretImportDALFactory } from "@app/services/secret-import/secret-import-dal";
|
||||||
import { secretImportServiceFactory } from "@app/services/secret-import/secret-import-service";
|
import { secretImportServiceFactory } from "@app/services/secret-import/secret-import-service";
|
||||||
|
import { secretReminderRecipientsDALFactory } from "@app/services/secret-reminder-recipients/secret-reminder-recipients-dal";
|
||||||
import { secretSharingDALFactory } from "@app/services/secret-sharing/secret-sharing-dal";
|
import { secretSharingDALFactory } from "@app/services/secret-sharing/secret-sharing-dal";
|
||||||
import { secretSharingServiceFactory } from "@app/services/secret-sharing/secret-sharing-service";
|
import { secretSharingServiceFactory } from "@app/services/secret-sharing/secret-sharing-service";
|
||||||
import { secretSyncDALFactory } from "@app/services/secret-sync/secret-sync-dal";
|
import { secretSyncDALFactory } from "@app/services/secret-sync/secret-sync-dal";
|
||||||
@@ -419,6 +420,7 @@ export const registerRoutes = async (
|
|||||||
const orgGatewayConfigDAL = orgGatewayConfigDALFactory(db);
|
const orgGatewayConfigDAL = orgGatewayConfigDALFactory(db);
|
||||||
const gatewayDAL = gatewayDALFactory(db);
|
const gatewayDAL = gatewayDALFactory(db);
|
||||||
const projectGatewayDAL = projectGatewayDALFactory(db);
|
const projectGatewayDAL = projectGatewayDALFactory(db);
|
||||||
|
const secretReminderRecipientsDAL = secretReminderRecipientsDALFactory(db);
|
||||||
|
|
||||||
const secretRotationV2DAL = secretRotationV2DALFactory(db, folderDAL);
|
const secretRotationV2DAL = secretRotationV2DALFactory(db, folderDAL);
|
||||||
|
|
||||||
@@ -735,6 +737,7 @@ export const registerRoutes = async (
|
|||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectRoleDAL,
|
projectRoleDAL,
|
||||||
groupProjectDAL,
|
groupProjectDAL,
|
||||||
|
secretReminderRecipientsDAL,
|
||||||
licenseService
|
licenseService
|
||||||
});
|
});
|
||||||
const projectUserAdditionalPrivilegeService = projectUserAdditionalPrivilegeServiceFactory({
|
const projectUserAdditionalPrivilegeService = projectUserAdditionalPrivilegeServiceFactory({
|
||||||
@@ -968,6 +971,7 @@ export const registerRoutes = async (
|
|||||||
secretApprovalRequestDAL,
|
secretApprovalRequestDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectUserMembershipRoleDAL,
|
projectUserMembershipRoleDAL,
|
||||||
|
secretReminderRecipientsDAL,
|
||||||
orgService,
|
orgService,
|
||||||
resourceMetadataDAL,
|
resourceMetadataDAL,
|
||||||
secretSyncQueue
|
secretSyncQueue
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretFoldersSchema, SecretImportsSchema } from "@app/db/schemas";
|
import { SecretFoldersSchema, SecretImportsSchema, UsersSchema } from "@app/db/schemas";
|
||||||
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission";
|
||||||
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
|
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
|
||||||
@@ -594,6 +594,12 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
.optional(),
|
.optional(),
|
||||||
secrets: secretRawSchema
|
secrets: secretRawSchema
|
||||||
.extend({
|
.extend({
|
||||||
|
secretReminderRecipients: z
|
||||||
|
.object({
|
||||||
|
user: UsersSchema.pick({ id: true, email: true, username: true }),
|
||||||
|
id: z.string()
|
||||||
|
})
|
||||||
|
.array(),
|
||||||
secretValueHidden: z.boolean(),
|
secretValueHidden: z.boolean(),
|
||||||
secretPath: z.string().optional(),
|
secretPath: z.string().optional(),
|
||||||
secretMetadata: ResourceMetadataSchema.optional(),
|
secretMetadata: ResourceMetadataSchema.optional(),
|
||||||
|
|||||||
@@ -662,6 +662,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
.optional()
|
.optional()
|
||||||
.nullable()
|
.nullable()
|
||||||
.describe(RAW_SECRETS.UPDATE.secretReminderRepeatDays),
|
.describe(RAW_SECRETS.UPDATE.secretReminderRepeatDays),
|
||||||
|
secretReminderRecipients: z.string().array().optional().describe(RAW_SECRETS.UPDATE.secretReminderRecipients),
|
||||||
newSecretName: SecretNameSchema.optional().describe(RAW_SECRETS.UPDATE.newSecretName),
|
newSecretName: SecretNameSchema.optional().describe(RAW_SECRETS.UPDATE.newSecretName),
|
||||||
secretComment: z.string().optional().describe(RAW_SECRETS.UPDATE.secretComment)
|
secretComment: z.string().optional().describe(RAW_SECRETS.UPDATE.secretComment)
|
||||||
}),
|
}),
|
||||||
@@ -692,6 +693,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
skipMultilineEncoding: req.body.skipMultilineEncoding,
|
skipMultilineEncoding: req.body.skipMultilineEncoding,
|
||||||
tagIds: req.body.tagIds,
|
tagIds: req.body.tagIds,
|
||||||
secretReminderRepeatDays: req.body.secretReminderRepeatDays,
|
secretReminderRepeatDays: req.body.secretReminderRepeatDays,
|
||||||
|
secretReminderRecipients: req.body.secretReminderRecipients,
|
||||||
secretReminderNote: req.body.secretReminderNote,
|
secretReminderNote: req.body.secretReminderNote,
|
||||||
metadata: req.body.metadata,
|
metadata: req.body.metadata,
|
||||||
newSecretName: req.body.newSecretName,
|
newSecretName: req.body.newSecretName,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { TProjectDALFactory } from "../project/project-dal";
|
|||||||
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
|
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
|
||||||
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
|
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
|
||||||
import { TProjectRoleDALFactory } from "../project-role/project-role-dal";
|
import { TProjectRoleDALFactory } from "../project-role/project-role-dal";
|
||||||
|
import { TSecretReminderRecipientsDALFactory } from "../secret-reminder-recipients/secret-reminder-recipients-dal";
|
||||||
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||||
import { TUserDALFactory } from "../user/user-dal";
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
import { TProjectMembershipDALFactory } from "./project-membership-dal";
|
import { TProjectMembershipDALFactory } from "./project-membership-dal";
|
||||||
@@ -53,6 +54,7 @@ type TProjectMembershipServiceFactoryDep = {
|
|||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "findLatestProjectKey" | "delete" | "insertMany">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "findLatestProjectKey" | "delete" | "insertMany">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
||||||
|
secretReminderRecipientsDAL: Pick<TSecretReminderRecipientsDALFactory, "delete">;
|
||||||
groupProjectDAL: TGroupProjectDALFactory;
|
groupProjectDAL: TGroupProjectDALFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -71,6 +73,7 @@ export const projectMembershipServiceFactory = ({
|
|||||||
groupProjectDAL,
|
groupProjectDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
|
secretReminderRecipientsDAL,
|
||||||
licenseService
|
licenseService
|
||||||
}: TProjectMembershipServiceFactoryDep) => {
|
}: TProjectMembershipServiceFactoryDep) => {
|
||||||
const getProjectMemberships = async ({
|
const getProjectMemberships = async ({
|
||||||
@@ -389,6 +392,13 @@ export const projectMembershipServiceFactory = ({
|
|||||||
const membership = await projectMembershipDAL.transaction(async (tx) => {
|
const membership = await projectMembershipDAL.transaction(async (tx) => {
|
||||||
const [deletedMembership] = await projectMembershipDAL.delete({ projectId, id: membershipId }, tx);
|
const [deletedMembership] = await projectMembershipDAL.delete({ projectId, id: membershipId }, tx);
|
||||||
await projectKeyDAL.delete({ receiverId: deletedMembership.userId, projectId }, tx);
|
await projectKeyDAL.delete({ receiverId: deletedMembership.userId, projectId }, tx);
|
||||||
|
await secretReminderRecipientsDAL.delete(
|
||||||
|
{
|
||||||
|
projectId,
|
||||||
|
userId: deletedMembership.userId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
return deletedMembership;
|
return deletedMembership;
|
||||||
});
|
});
|
||||||
return membership;
|
return membership;
|
||||||
@@ -466,6 +476,16 @@ export const projectMembershipServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await secretReminderRecipientsDAL.delete(
|
||||||
|
{
|
||||||
|
projectId,
|
||||||
|
$in: {
|
||||||
|
userId: projectMembers.map(({ user }) => user.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
// delete project keys belonging to users that are not part of any other groups in the project
|
// delete project keys belonging to users that are not part of any other groups in the project
|
||||||
await projectKeyDAL.delete(
|
await projectKeyDAL.delete(
|
||||||
{
|
{
|
||||||
@@ -526,6 +546,15 @@ export const projectMembershipServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await secretReminderRecipientsDAL.delete(
|
||||||
|
{
|
||||||
|
projectId,
|
||||||
|
userId: actorId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
const membership = (
|
const membership = (
|
||||||
await projectMembershipDAL.delete(
|
await projectMembershipDAL.delete(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TSecretReminderRecipientsDALFactory = ReturnType<typeof secretReminderRecipientsDALFactory>;
|
||||||
|
|
||||||
|
export const secretReminderRecipientsDALFactory = (db: TDbClient) => {
|
||||||
|
const secretReminderRecipientsOrm = ormify(db, TableName.SecretReminderRecipients);
|
||||||
|
|
||||||
|
const findUsersBySecretId = async (secretId: string, tx?: Knex) => {
|
||||||
|
const res = await (tx || db.replicaNode())(TableName.SecretReminderRecipients)
|
||||||
|
.where({ secretId })
|
||||||
|
.leftJoin(TableName.Users, `${TableName.SecretReminderRecipients}.userId`, `${TableName.Users}.id`)
|
||||||
|
.leftJoin(TableName.Project, `${TableName.SecretReminderRecipients}.projectId`, `${TableName.Project}.id`)
|
||||||
|
.leftJoin(TableName.OrgMembership, (bd) => {
|
||||||
|
void bd
|
||||||
|
.on(`${TableName.OrgMembership}.userId`, "=", `${TableName.SecretReminderRecipients}.userId`)
|
||||||
|
.andOn(`${TableName.OrgMembership}.orgId`, "=", `${TableName.Project}.orgId`);
|
||||||
|
})
|
||||||
|
|
||||||
|
.where(`${TableName.OrgMembership}.isActive`, true)
|
||||||
|
.select(selectAllTableCols(TableName.SecretReminderRecipients))
|
||||||
|
.select(
|
||||||
|
db.ref("email").withSchema(TableName.Users).as("email"),
|
||||||
|
db.ref("username").withSchema(TableName.Users).as("username"),
|
||||||
|
db.ref("firstName").withSchema(TableName.Users).as("firstName"),
|
||||||
|
db.ref("lastName").withSchema(TableName.Users).as("lastName")
|
||||||
|
);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...secretReminderRecipientsOrm, findUsersBySecretId };
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export type TSecretReminderRecipient = {
|
||||||
|
user: {
|
||||||
|
id: string;
|
||||||
|
username: string;
|
||||||
|
email?: string | null;
|
||||||
|
};
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
@@ -79,7 +79,17 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
|||||||
`${TableName.SecretV2}.id`,
|
`${TableName.SecretV2}.id`,
|
||||||
`${TableName.SecretRotationV2SecretMapping}.secretId`
|
`${TableName.SecretRotationV2SecretMapping}.secretId`
|
||||||
)
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SecretReminderRecipients,
|
||||||
|
`${TableName.SecretV2}.id`,
|
||||||
|
`${TableName.SecretReminderRecipients}.secretId`
|
||||||
|
)
|
||||||
|
.leftJoin(TableName.Users, `${TableName.SecretReminderRecipients}.userId`, `${TableName.Users}.id`)
|
||||||
.select(selectAllTableCols(TableName.SecretV2))
|
.select(selectAllTableCols(TableName.SecretV2))
|
||||||
|
.select(db.ref("id").withSchema(TableName.SecretReminderRecipients).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"))
|
||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.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"))
|
||||||
@@ -103,6 +113,23 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
|||||||
slug,
|
slug,
|
||||||
name: slug
|
name: slug
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "reminderRecipientId",
|
||||||
|
label: "secretReminderRecipients" as const,
|
||||||
|
mapper: ({
|
||||||
|
reminderRecipientId,
|
||||||
|
reminderRecipientUsername,
|
||||||
|
reminderRecipientEmail,
|
||||||
|
reminderRecipientUserId
|
||||||
|
}) => ({
|
||||||
|
user: {
|
||||||
|
id: reminderRecipientUserId,
|
||||||
|
username: reminderRecipientUsername,
|
||||||
|
email: reminderRecipientEmail
|
||||||
|
},
|
||||||
|
id: reminderRecipientId
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -484,6 +511,12 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
|||||||
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
|
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
|
||||||
`${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.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.SecretRotationV2SecretMapping,
|
TableName.SecretRotationV2SecretMapping,
|
||||||
@@ -512,6 +545,10 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
|||||||
}) as rank`
|
}) as rank`
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
.select(db.ref("id").withSchema(TableName.SecretReminderRecipients).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"))
|
||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.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"))
|
||||||
@@ -556,6 +593,23 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
|||||||
isRotatedSecret: Boolean(el.rotationId)
|
isRotatedSecret: Boolean(el.rotationId)
|
||||||
}),
|
}),
|
||||||
childrenMapper: [
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "reminderRecipientId",
|
||||||
|
label: "secretReminderRecipients" as const,
|
||||||
|
mapper: ({
|
||||||
|
reminderRecipientId,
|
||||||
|
reminderRecipientUsername,
|
||||||
|
reminderRecipientEmail,
|
||||||
|
reminderRecipientUserId
|
||||||
|
}) => ({
|
||||||
|
user: {
|
||||||
|
id: reminderRecipientUserId,
|
||||||
|
username: reminderRecipientUsername,
|
||||||
|
email: reminderRecipientEmail
|
||||||
|
},
|
||||||
|
id: reminderRecipientId
|
||||||
|
})
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "tagId",
|
key: "tagId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
|||||||
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
|
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
|
||||||
import { INFISICAL_SECRET_VALUE_HIDDEN_MASK } from "../secret/secret-fns";
|
import { INFISICAL_SECRET_VALUE_HIDDEN_MASK } from "../secret/secret-fns";
|
||||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||||
|
import { TSecretReminderRecipient } from "../secret-reminder-recipients/secret-reminder-recipients-types";
|
||||||
import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal";
|
import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal";
|
||||||
import { TFnSecretBulkDelete, TFnSecretBulkInsert, TFnSecretBulkUpdate } from "./secret-v2-bridge-types";
|
import { TFnSecretBulkDelete, TFnSecretBulkInsert, TFnSecretBulkUpdate } from "./secret-v2-bridge-types";
|
||||||
|
|
||||||
@@ -353,7 +354,7 @@ export const fnSecretBulkDelete = async ({
|
|||||||
deletedSecrets
|
deletedSecrets
|
||||||
.filter(({ reminderRepeatDays }) => Boolean(reminderRepeatDays))
|
.filter(({ reminderRepeatDays }) => Boolean(reminderRepeatDays))
|
||||||
.map(({ id, reminderRepeatDays }) =>
|
.map(({ id, reminderRepeatDays }) =>
|
||||||
secretQueueService.removeSecretReminder({ secretId: id, repeatDays: reminderRepeatDays as number })
|
secretQueueService.removeSecretReminder({ secretId: id, repeatDays: reminderRepeatDays as number }, tx)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -684,6 +685,7 @@ export const reshapeBridgeSecret = (
|
|||||||
secretMetadata?: ResourceMetadataDTO;
|
secretMetadata?: ResourceMetadataDTO;
|
||||||
isRotatedSecret?: boolean;
|
isRotatedSecret?: boolean;
|
||||||
rotationId?: string;
|
rotationId?: string;
|
||||||
|
secretReminderRecipients?: TSecretReminderRecipient[];
|
||||||
},
|
},
|
||||||
secretValueHidden: boolean
|
secretValueHidden: boolean
|
||||||
) => ({
|
) => ({
|
||||||
@@ -715,6 +717,7 @@ export const reshapeBridgeSecret = (
|
|||||||
updatedAt: secret.updatedAt,
|
updatedAt: secret.updatedAt,
|
||||||
isRotatedSecret: secret.isRotatedSecret,
|
isRotatedSecret: secret.isRotatedSecret,
|
||||||
rotationId: secret.rotationId,
|
rotationId: secret.rotationId,
|
||||||
|
secretReminderRecipients: secret.secretReminderRecipients || [],
|
||||||
...(secretValueHidden
|
...(secretValueHidden
|
||||||
? {
|
? {
|
||||||
secretValue: INFISICAL_SECRET_VALUE_HIDDEN_MASK,
|
secretValue: INFISICAL_SECRET_VALUE_HIDDEN_MASK,
|
||||||
|
|||||||
@@ -544,7 +544,12 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
id: updatedSecret[0].id,
|
id: updatedSecret[0].id,
|
||||||
...inputSecret
|
...inputSecret
|
||||||
},
|
},
|
||||||
oldSecret: secret,
|
oldSecret: {
|
||||||
|
id: secret.id,
|
||||||
|
secretReminderNote: secret.reminderNote,
|
||||||
|
secretReminderRepeatDays: secret.reminderRepeatDays,
|
||||||
|
secretReminderRecipients: secret.secretReminderRecipients?.map((el) => el.user.id)
|
||||||
|
},
|
||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ export type TUpdateSecretDTO = TProjectPermission & {
|
|||||||
skipMultilineEncoding?: boolean;
|
skipMultilineEncoding?: boolean;
|
||||||
secretReminderRepeatDays?: number | null;
|
secretReminderRepeatDays?: number | null;
|
||||||
secretReminderNote?: string | null;
|
secretReminderNote?: string | null;
|
||||||
|
secretReminderRecipients?: string[] | null;
|
||||||
metadata?: {
|
metadata?: {
|
||||||
source?: string;
|
source?: string;
|
||||||
};
|
};
|
||||||
@@ -220,7 +221,7 @@ export type TFnSecretBulkDelete = {
|
|||||||
tx?: Knex;
|
tx?: Knex;
|
||||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "deleteMany">;
|
secretDAL: Pick<TSecretV2BridgeDALFactory, "deleteMany">;
|
||||||
secretQueueService: {
|
secretQueueService: {
|
||||||
removeSecretReminder: (data: TRemoveSecretReminderDTO) => Promise<void>;
|
removeSecretReminder: (data: TRemoveSecretReminderDTO, tx?: Knex) => Promise<void>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -407,6 +407,7 @@ export const decryptSecretRaw = (
|
|||||||
id: secret.id,
|
id: secret.id,
|
||||||
user: secret.userId,
|
user: secret.userId,
|
||||||
tags: secret.tags?.map((el) => ({ ...el, name: el.slug })),
|
tags: secret.tags?.map((el) => ({ ...el, name: el.slug })),
|
||||||
|
secretReminderRecipients: [],
|
||||||
skipMultilineEncoding: secret.skipMultilineEncoding,
|
skipMultilineEncoding: secret.skipMultilineEncoding,
|
||||||
secretReminderRepeatDays: secret.secretReminderRepeatDays,
|
secretReminderRepeatDays: secret.secretReminderRepeatDays,
|
||||||
secretReminderNote: secret.secretReminderNote,
|
secretReminderNote: secret.secretReminderNote,
|
||||||
@@ -758,7 +759,7 @@ export const fnSecretBulkDelete = async ({
|
|||||||
deletedSecrets
|
deletedSecrets
|
||||||
.filter(({ secretReminderRepeatDays }) => Boolean(secretReminderRepeatDays))
|
.filter(({ secretReminderRepeatDays }) => Boolean(secretReminderRepeatDays))
|
||||||
.map(({ id, secretReminderRepeatDays }) =>
|
.map(({ id, secretReminderRepeatDays }) =>
|
||||||
secretQueueService.removeSecretReminder({ secretId: id, repeatDays: secretReminderRepeatDays as number })
|
secretQueueService.removeSecretReminder({ secretId: id, repeatDays: secretReminderRepeatDays as number }, tx)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
import opentelemetry from "@opentelemetry/api";
|
import opentelemetry from "@opentelemetry/api";
|
||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
ProjectUpgradeStatus,
|
ProjectUpgradeStatus,
|
||||||
ProjectVersion,
|
ProjectVersion,
|
||||||
|
SecretType,
|
||||||
TSecretSnapshotSecretsV2,
|
TSecretSnapshotSecretsV2,
|
||||||
TSecretVersionsV2
|
TSecretVersionsV2
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
@@ -53,6 +55,7 @@ import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-sche
|
|||||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||||
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
|
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
|
||||||
import { fnSecretsV2FromImports } from "../secret-import/secret-import-fns";
|
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 { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
||||||
import { expandSecretReferencesFactory, getAllSecretReferences } from "../secret-v2-bridge/secret-v2-bridge-fns";
|
import { expandSecretReferencesFactory, getAllSecretReferences } from "../secret-v2-bridge/secret-v2-bridge-fns";
|
||||||
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
|
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
|
||||||
@@ -109,6 +112,10 @@ type TSecretQueueFactoryDep = {
|
|||||||
orgService: Pick<TOrgServiceFactory, "addGhostUser">;
|
orgService: Pick<TOrgServiceFactory, "addGhostUser">;
|
||||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
||||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
||||||
|
secretReminderRecipientsDAL: Pick<
|
||||||
|
TSecretReminderRecipientsDALFactory,
|
||||||
|
"delete" | "findUsersBySecretId" | "insertMany" | "transaction"
|
||||||
|
>;
|
||||||
secretSyncQueue: Pick<TSecretSyncQueueFactory, "queueSecretSyncsSyncSecretsByPath">;
|
secretSyncQueue: Pick<TSecretSyncQueueFactory, "queueSecretSyncsSyncSecretsByPath">;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -170,6 +177,7 @@ export const secretQueueFactory = ({
|
|||||||
projectUserMembershipRoleDAL,
|
projectUserMembershipRoleDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
resourceMetadataDAL,
|
resourceMetadataDAL,
|
||||||
|
secretReminderRecipientsDAL,
|
||||||
secretSyncQueue
|
secretSyncQueue
|
||||||
}: TSecretQueueFactoryDep) => {
|
}: TSecretQueueFactoryDep) => {
|
||||||
const integrationMeter = opentelemetry.metrics.getMeter("Integrations");
|
const integrationMeter = opentelemetry.metrics.getMeter("Integrations");
|
||||||
@@ -178,7 +186,11 @@ export const secretQueueFactory = ({
|
|||||||
unit: "1"
|
unit: "1"
|
||||||
});
|
});
|
||||||
|
|
||||||
const removeSecretReminder = async (dto: TRemoveSecretReminderDTO) => {
|
const removeSecretReminder = async ({ deleteRecipients = true, ...dto }: TRemoveSecretReminderDTO, tx?: Knex) => {
|
||||||
|
if (deleteRecipients) {
|
||||||
|
await secretReminderRecipientsDAL.delete({ secretId: dto.secretId }, tx);
|
||||||
|
}
|
||||||
|
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
await queueService.stopRepeatableJob(
|
await queueService.stopRepeatableJob(
|
||||||
QueueName.SecretReminder,
|
QueueName.SecretReminder,
|
||||||
@@ -224,7 +236,12 @@ export const secretQueueFactory = ({
|
|||||||
.replace(":", "-");
|
.replace(":", "-");
|
||||||
};
|
};
|
||||||
|
|
||||||
const addSecretReminder = async ({ oldSecret, newSecret, projectId }: TCreateSecretReminderDTO) => {
|
const addSecretReminder = async ({
|
||||||
|
oldSecret,
|
||||||
|
newSecret,
|
||||||
|
projectId,
|
||||||
|
deleteRecipients = true
|
||||||
|
}: TCreateSecretReminderDTO) => {
|
||||||
try {
|
try {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
@@ -246,7 +263,8 @@ export const secretQueueFactory = ({
|
|||||||
if (oldSecret.secretReminderRepeatDays) {
|
if (oldSecret.secretReminderRepeatDays) {
|
||||||
await removeSecretReminder({
|
await removeSecretReminder({
|
||||||
repeatDays: oldSecret.secretReminderRepeatDays,
|
repeatDays: oldSecret.secretReminderRepeatDays,
|
||||||
secretId: oldSecret.id
|
secretId: oldSecret.id,
|
||||||
|
deleteRecipients
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,29 +301,57 @@ export const secretQueueFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSecretReminder = async ({ newSecret, oldSecret, projectId }: THandleReminderDTO) => {
|
const handleSecretReminder = async ({ newSecret, oldSecret, projectId }: THandleReminderDTO) => {
|
||||||
const { secretReminderRepeatDays, secretReminderNote } = newSecret;
|
const { secretReminderRepeatDays, secretReminderNote, secretReminderRecipients } = newSecret;
|
||||||
|
|
||||||
if (newSecret.type !== "personal" && secretReminderRepeatDays !== undefined) {
|
const recipientsUpdated =
|
||||||
if (
|
secretReminderRecipients?.some(
|
||||||
(secretReminderRepeatDays && oldSecret.secretReminderRepeatDays !== secretReminderRepeatDays) ||
|
(newId) => !oldSecret.secretReminderRecipients?.find((oldId) => newId === oldId)
|
||||||
(secretReminderNote && oldSecret.secretReminderNote !== secretReminderNote)
|
) || secretReminderRecipients?.length !== oldSecret.secretReminderRecipients?.length;
|
||||||
) {
|
|
||||||
await addSecretReminder({
|
await secretReminderRecipientsDAL.transaction(async (tx) => {
|
||||||
oldSecret,
|
if (newSecret.type !== SecretType.Personal && secretReminderRepeatDays !== undefined) {
|
||||||
newSecret,
|
if (
|
||||||
projectId
|
(secretReminderRepeatDays && oldSecret.secretReminderRepeatDays !== secretReminderRepeatDays) ||
|
||||||
});
|
(secretReminderNote && oldSecret.secretReminderNote !== secretReminderNote)
|
||||||
} else if (
|
) {
|
||||||
secretReminderRepeatDays === null &&
|
await addSecretReminder({
|
||||||
secretReminderNote === null &&
|
oldSecret,
|
||||||
oldSecret.secretReminderRepeatDays
|
newSecret,
|
||||||
) {
|
projectId,
|
||||||
await removeSecretReminder({
|
deleteRecipients: false
|
||||||
secretId: oldSecret.id,
|
});
|
||||||
repeatDays: oldSecret.secretReminderRepeatDays
|
} 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({
|
const createManySecretsRawFn = createManySecretsRawFnFactory({
|
||||||
projectDAL,
|
projectDAL,
|
||||||
@@ -1071,6 +1117,8 @@ export const secretQueueFactory = ({
|
|||||||
const secret = await secretV2BridgeDAL.findById(data.secretId);
|
const secret = await secretV2BridgeDAL.findById(data.secretId);
|
||||||
const [folder] = await folderDAL.findSecretPathByFolderIds(project.id, [secret.folderId]);
|
const [folder] = await folderDAL.findSecretPathByFolderIds(project.id, [secret.folderId]);
|
||||||
|
|
||||||
|
const recipients = await secretReminderRecipientsDAL.findUsersBySecretId(data.secretId);
|
||||||
|
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
logger.info(`secretReminderQueue.process: [secretDocument=${data.secretId}] no organization found`);
|
logger.info(`secretReminderQueue.process: [secretDocument=${data.secretId}] no organization found`);
|
||||||
return;
|
return;
|
||||||
@@ -1088,10 +1136,14 @@ export const secretQueueFactory = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectedRecipients = recipients?.length
|
||||||
|
? recipients.map((r) => r.email as string)
|
||||||
|
: projectMembers.map((m) => m.user.email as string);
|
||||||
|
|
||||||
await smtpService.sendMail({
|
await smtpService.sendMail({
|
||||||
template: SmtpTemplates.SecretReminder,
|
template: SmtpTemplates.SecretReminder,
|
||||||
subjectLine: "Infisical secret reminder",
|
subjectLine: "Infisical secret reminder",
|
||||||
recipients: [...projectMembers.map((m) => m.user.email)].filter((email) => email).map((email) => email as string),
|
recipients: selectedRecipients,
|
||||||
substitutions: {
|
substitutions: {
|
||||||
reminderNote: data.note, // May not be present.
|
reminderNote: data.note, // May not be present.
|
||||||
projectName: project.name,
|
projectName: project.name,
|
||||||
|
|||||||
@@ -546,10 +546,13 @@ export const secretServiceFactory = ({
|
|||||||
|
|
||||||
for await (const secret of secrets) {
|
for await (const secret of secrets) {
|
||||||
if (secret.secretReminderRepeatDays !== null && secret.secretReminderRepeatDays !== undefined) {
|
if (secret.secretReminderRepeatDays !== null && secret.secretReminderRepeatDays !== undefined) {
|
||||||
await secretQueueService.removeSecretReminder({
|
await secretQueueService.removeSecretReminder(
|
||||||
repeatDays: secret.secretReminderRepeatDays,
|
{
|
||||||
secretId: secret.id
|
repeatDays: secret.secretReminderRepeatDays,
|
||||||
});
|
secretId: secret.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,6 +688,7 @@ export const secretServiceFactory = ({
|
|||||||
...secret,
|
...secret,
|
||||||
workspace: projectId,
|
workspace: projectId,
|
||||||
environment,
|
environment,
|
||||||
|
secretReminderRecipients: [],
|
||||||
secretPath: groupedPaths[secret.folderId][0].path
|
secretPath: groupedPaths[secret.folderId][0].path
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
@@ -1073,10 +1077,13 @@ export const secretServiceFactory = ({
|
|||||||
|
|
||||||
for await (const secret of secrets) {
|
for await (const secret of secrets) {
|
||||||
if (secret.secretReminderRepeatDays !== null && secret.secretReminderRepeatDays !== undefined) {
|
if (secret.secretReminderRepeatDays !== null && secret.secretReminderRepeatDays !== undefined) {
|
||||||
await secretQueueService.removeSecretReminder({
|
await secretQueueService.removeSecretReminder(
|
||||||
repeatDays: secret.secretReminderRepeatDays,
|
{
|
||||||
secretId: secret.id
|
repeatDays: secret.secretReminderRepeatDays,
|
||||||
});
|
secretId: secret.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const secretValueHidden = !hasSecretReadValueOrDescribePermission(
|
const secretValueHidden = !hasSecretReadValueOrDescribePermission(
|
||||||
@@ -1786,6 +1793,7 @@ export const secretServiceFactory = ({
|
|||||||
tagIds,
|
tagIds,
|
||||||
secretReminderNote,
|
secretReminderNote,
|
||||||
secretReminderRepeatDays,
|
secretReminderRepeatDays,
|
||||||
|
secretReminderRecipients,
|
||||||
metadata,
|
metadata,
|
||||||
secretComment,
|
secretComment,
|
||||||
newSecretName,
|
newSecretName,
|
||||||
@@ -1828,6 +1836,7 @@ export const secretServiceFactory = ({
|
|||||||
tagIds,
|
tagIds,
|
||||||
reminderNote: secretReminderNote,
|
reminderNote: secretReminderNote,
|
||||||
reminderRepeatDays: secretReminderRepeatDays,
|
reminderRepeatDays: secretReminderRepeatDays,
|
||||||
|
secretReminderRecipients,
|
||||||
secretMetadata
|
secretMetadata
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -1837,8 +1846,9 @@ export const secretServiceFactory = ({
|
|||||||
}
|
}
|
||||||
const secret = await secretV2BridgeService.updateSecret({
|
const secret = await secretV2BridgeService.updateSecret({
|
||||||
secretReminderRepeatDays,
|
secretReminderRepeatDays,
|
||||||
skipMultilineEncoding,
|
|
||||||
secretReminderNote,
|
secretReminderNote,
|
||||||
|
secretReminderRecipients,
|
||||||
|
skipMultilineEncoding,
|
||||||
tagIds,
|
tagIds,
|
||||||
secretComment,
|
secretComment,
|
||||||
secretPath,
|
secretPath,
|
||||||
|
|||||||
@@ -22,9 +22,13 @@ import { SecretUpdateMode } from "../secret-v2-bridge/secret-v2-bridge-types";
|
|||||||
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
|
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
|
||||||
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
|
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
|
||||||
|
|
||||||
type TPartialSecret = Pick<TSecrets, "id" | "secretReminderRepeatDays" | "secretReminderNote">;
|
type TPartialSecret = Pick<TSecrets, "id" | "secretReminderRepeatDays" | "secretReminderNote"> & {
|
||||||
|
secretReminderRecipients?: string[] | null;
|
||||||
|
};
|
||||||
|
|
||||||
type TPartialInputSecret = Pick<TSecrets, "type" | "secretReminderNote" | "secretReminderRepeatDays" | "id">;
|
type TPartialInputSecret = Pick<TSecrets, "type" | "secretReminderNote" | "secretReminderRepeatDays" | "id"> & {
|
||||||
|
secretReminderRecipients?: string[] | null;
|
||||||
|
};
|
||||||
|
|
||||||
export const FailedIntegrationSyncEmailsPayloadSchema = z.object({
|
export const FailedIntegrationSyncEmailsPayloadSchema = z.object({
|
||||||
projectId: z.string(),
|
projectId: z.string(),
|
||||||
@@ -258,6 +262,7 @@ export type TUpdateSecretRawDTO = TProjectPermission & {
|
|||||||
skipMultilineEncoding?: boolean;
|
skipMultilineEncoding?: boolean;
|
||||||
secretReminderRepeatDays?: number | null;
|
secretReminderRepeatDays?: number | null;
|
||||||
secretReminderNote?: string | null;
|
secretReminderNote?: string | null;
|
||||||
|
secretReminderRecipients?: string[] | null;
|
||||||
metadata?: {
|
metadata?: {
|
||||||
source?: string;
|
source?: string;
|
||||||
};
|
};
|
||||||
@@ -374,7 +379,7 @@ export type TFnSecretBulkDelete = {
|
|||||||
tx?: Knex;
|
tx?: Knex;
|
||||||
secretDAL: Pick<TSecretDALFactory, "deleteMany">;
|
secretDAL: Pick<TSecretDALFactory, "deleteMany">;
|
||||||
secretQueueService: {
|
secretQueueService: {
|
||||||
removeSecretReminder: (data: TRemoveSecretReminderDTO) => Promise<void>;
|
removeSecretReminder: (data: TRemoveSecretReminderDTO, tx?: Knex) => Promise<void>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -405,11 +410,14 @@ export type TCreateSecretReminderDTO = {
|
|||||||
oldSecret: TPartialSecret;
|
oldSecret: TPartialSecret;
|
||||||
newSecret: TPartialSecret;
|
newSecret: TPartialSecret;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
|
||||||
|
deleteRecipients?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TRemoveSecretReminderDTO = {
|
export type TRemoveSecretReminderDTO = {
|
||||||
secretId: string;
|
secretId: string;
|
||||||
repeatDays: number;
|
repeatDays: number;
|
||||||
|
deleteRecipients?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TBackFillSecretReferencesDTO = TProjectPermission;
|
export type TBackFillSecretReferencesDTO = TProjectPermission;
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ export const useUpdateSecretV3 = ({
|
|||||||
secretComment,
|
secretComment,
|
||||||
secretReminderRepeatDays,
|
secretReminderRepeatDays,
|
||||||
secretReminderNote,
|
secretReminderNote,
|
||||||
|
secretReminderRecipients,
|
||||||
newSecretName,
|
newSecretName,
|
||||||
skipMultilineEncoding,
|
skipMultilineEncoding,
|
||||||
secretMetadata
|
secretMetadata
|
||||||
@@ -93,6 +94,7 @@ export const useUpdateSecretV3 = ({
|
|||||||
type,
|
type,
|
||||||
secretReminderNote,
|
secretReminderNote,
|
||||||
secretReminderRepeatDays,
|
secretReminderRepeatDays,
|
||||||
|
secretReminderRecipients,
|
||||||
secretPath,
|
secretPath,
|
||||||
skipMultilineEncoding,
|
skipMultilineEncoding,
|
||||||
newSecretName,
|
newSecretName,
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ export const mergePersonalSecrets = (rawSecrets: SecretV3Raw[]) => {
|
|||||||
comment: el.secretComment || "",
|
comment: el.secretComment || "",
|
||||||
reminderRepeatDays: el.secretReminderRepeatDays,
|
reminderRepeatDays: el.secretReminderRepeatDays,
|
||||||
reminderNote: el.secretReminderNote,
|
reminderNote: el.secretReminderNote,
|
||||||
|
secretReminderRecipients: el.secretReminderRecipients,
|
||||||
createdAt: el.createdAt,
|
createdAt: el.createdAt,
|
||||||
updatedAt: el.updatedAt,
|
updatedAt: el.updatedAt,
|
||||||
version: el.version,
|
version: el.version,
|
||||||
|
|||||||
@@ -7,6 +7,14 @@ export enum SecretType {
|
|||||||
Personal = "personal"
|
Personal = "personal"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SecretReminderRecipient = {
|
||||||
|
user: {
|
||||||
|
id: string;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
export type EncryptedSecret = {
|
export type EncryptedSecret = {
|
||||||
id: string;
|
id: string;
|
||||||
version: number;
|
version: number;
|
||||||
@@ -42,6 +50,7 @@ export type SecretV3RawSanitized = {
|
|||||||
comment?: string;
|
comment?: string;
|
||||||
reminderRepeatDays?: number | null;
|
reminderRepeatDays?: number | null;
|
||||||
reminderNote?: string | null;
|
reminderNote?: string | null;
|
||||||
|
reminderRecipients?: string[];
|
||||||
tags?: WsTag[];
|
tags?: WsTag[];
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
@@ -55,6 +64,7 @@ export type SecretV3RawSanitized = {
|
|||||||
secretMetadata?: { key: string; value: string }[];
|
secretMetadata?: { key: string; value: string }[];
|
||||||
isReminderEvent?: boolean;
|
isReminderEvent?: boolean;
|
||||||
isRotatedSecret?: boolean;
|
isRotatedSecret?: boolean;
|
||||||
|
secretReminderRecipients?: SecretReminderRecipient[];
|
||||||
rotationId?: string;
|
rotationId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -80,6 +90,7 @@ export type SecretV3Raw = {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
isRotatedSecret?: boolean;
|
isRotatedSecret?: boolean;
|
||||||
rotationId?: string;
|
rotationId?: string;
|
||||||
|
secretReminderRecipients?: SecretReminderRecipient[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SecretV3RawResponse = {
|
export type SecretV3RawResponse = {
|
||||||
@@ -177,6 +188,7 @@ export type TUpdateSecretsV3DTO = {
|
|||||||
secretReminderNote?: string | null;
|
secretReminderNote?: string | null;
|
||||||
tagIds?: string[];
|
tagIds?: string[];
|
||||||
secretMetadata?: { key: string; value: string }[];
|
secretMetadata?: { key: string; value: string }[];
|
||||||
|
secretReminderRecipients?: string[] | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TDeleteSecretsV3DTO = {
|
export type TDeleteSecretsV3DTO = {
|
||||||
|
|||||||
@@ -6,10 +6,28 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { Button, FormControl, Input, Modal, ModalContent, TextArea } from "@app/components/v2";
|
import {
|
||||||
|
Button,
|
||||||
|
FilterableSelect,
|
||||||
|
FormControl,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
TextArea
|
||||||
|
} from "@app/components/v2";
|
||||||
|
import { useWorkspace } from "@app/context";
|
||||||
|
import { useGetWorkspaceUsers } from "@app/hooks/api";
|
||||||
|
|
||||||
const ReminderFormSchema = z.object({
|
const ReminderFormSchema = z.object({
|
||||||
note: z.string().optional().nullable(),
|
note: z.string().optional().nullable(),
|
||||||
|
recipients: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
label: z.string(),
|
||||||
|
value: z.string().uuid()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
days: z
|
days: z
|
||||||
.number()
|
.number()
|
||||||
.min(1, { message: "Must be at least 1 day" })
|
.min(1, { message: "Must be at least 1 day" })
|
||||||
@@ -22,6 +40,7 @@ interface ReminderFormProps {
|
|||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
repeatDays?: number | null;
|
repeatDays?: number | null;
|
||||||
note?: string | null;
|
note?: string | null;
|
||||||
|
recipients?: string[] | null;
|
||||||
onOpenChange: (isOpen: boolean, data?: TReminderFormSchema) => void;
|
onOpenChange: (isOpen: boolean, data?: TReminderFormSchema) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,12 +48,16 @@ export const CreateReminderForm = ({
|
|||||||
isOpen,
|
isOpen,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
repeatDays,
|
repeatDays,
|
||||||
note
|
note,
|
||||||
|
recipients
|
||||||
}: ReminderFormProps) => {
|
}: ReminderFormProps) => {
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
|
||||||
|
const { data: members = [] } = useGetWorkspaceUsers(currentWorkspace?.id);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
control,
|
control,
|
||||||
reset,
|
|
||||||
setValue,
|
setValue,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { isSubmitting }
|
formState: { isSubmitting }
|
||||||
@@ -51,13 +74,23 @@ export const CreateReminderForm = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
// On initial load, filter the members to only include the recipients
|
||||||
reset({
|
if (members.length) {
|
||||||
days: repeatDays || undefined,
|
const filteredMembers = members.filter((m) => recipients?.find((r) => r === m.user.id));
|
||||||
note: note || ""
|
setValue(
|
||||||
});
|
"recipients",
|
||||||
|
filteredMembers.map((m) => ({
|
||||||
|
label: m.user.username || m.user.email,
|
||||||
|
value: m.user.id
|
||||||
|
}))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [isOpen]);
|
}, [members, isOpen, recipients]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (repeatDays) setValue("days", repeatDays);
|
||||||
|
if (note) setValue("note", note);
|
||||||
|
}, [repeatDays, note]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||||
@@ -74,6 +107,7 @@ export const CreateReminderForm = ({
|
|||||||
render={({ field, fieldState }) => (
|
render={({ field, fieldState }) => (
|
||||||
<>
|
<>
|
||||||
<FormControl
|
<FormControl
|
||||||
|
isRequired
|
||||||
className="mb-0"
|
className="mb-0"
|
||||||
label="Reminder Interval (in days)"
|
label="Reminder Interval (in days)"
|
||||||
isError={Boolean(fieldState.error)}
|
isError={Boolean(fieldState.error)}
|
||||||
@@ -83,6 +117,7 @@ export const CreateReminderForm = ({
|
|||||||
onChange={(el) => setValue("days", parseInt(el.target.value, 10))}
|
onChange={(el) => setValue("days", parseInt(el.target.value, 10))}
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="31"
|
placeholder="31"
|
||||||
|
defaultValue={repeatDays || undefined}
|
||||||
value={field.value || undefined}
|
value={field.value || undefined}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -105,11 +140,43 @@ export const CreateReminderForm = ({
|
|||||||
placeholder="Remember to rotate the AWS secret every month."
|
placeholder="Remember to rotate the AWS secret every month."
|
||||||
className="border border-mineshaft-600 text-sm"
|
className="border border-mineshaft-600 text-sm"
|
||||||
rows={8}
|
rows={8}
|
||||||
|
defaultValue={note || ""}
|
||||||
reSize="none"
|
reSize="none"
|
||||||
cols={30}
|
cols={30}
|
||||||
{...register("note")}
|
{...register("note")}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="recipients"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl
|
||||||
|
tooltipText={
|
||||||
|
<div>
|
||||||
|
Select users to receive reminders.
|
||||||
|
<br />
|
||||||
|
<br /> If none are selected, all project members will receive the reminder.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
label="Recipients"
|
||||||
|
className="mb-0"
|
||||||
|
>
|
||||||
|
<FilterableSelect
|
||||||
|
className="w-full"
|
||||||
|
placeholder="Select reminder recipients..."
|
||||||
|
isMulti
|
||||||
|
name="recipients"
|
||||||
|
options={members.map((member) => ({
|
||||||
|
label: member.user.username || member.user.email,
|
||||||
|
value: member.user.id
|
||||||
|
}))}
|
||||||
|
value={field.value}
|
||||||
|
onChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-7 flex items-center space-x-4">
|
<div className="mt-7 flex items-center space-x-4">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||||
import { subject } from "@casl/ability";
|
import { subject } from "@casl/ability";
|
||||||
import { faCircleQuestion, faEye } from "@fortawesome/free-regular-svg-icons";
|
import { faCircleQuestion, faEye } from "@fortawesome/free-regular-svg-icons";
|
||||||
@@ -220,11 +221,12 @@ export const SecretDetailSidebar = ({
|
|||||||
|
|
||||||
const handleReminderSubmit = async (
|
const handleReminderSubmit = async (
|
||||||
reminderRepeatDays: number | null | undefined,
|
reminderRepeatDays: number | null | undefined,
|
||||||
reminderNote: string | null | undefined
|
reminderNote: string | null | undefined,
|
||||||
|
reminderRecipients: string[] | undefined
|
||||||
) => {
|
) => {
|
||||||
await onSaveSecret(
|
await onSaveSecret(
|
||||||
secret,
|
secret,
|
||||||
{ ...secret, reminderRepeatDays, reminderNote, isReminderEvent: true },
|
{ ...secret, reminderRepeatDays, reminderNote, isReminderEvent: true, reminderRecipients },
|
||||||
() => {}
|
() => {}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -233,6 +235,16 @@ export const SecretDetailSidebar = ({
|
|||||||
|
|
||||||
const secretReminderRepeatDays = watch("reminderRepeatDays");
|
const secretReminderRepeatDays = watch("reminderRepeatDays");
|
||||||
const secretReminderNote = watch("reminderNote");
|
const secretReminderNote = watch("reminderNote");
|
||||||
|
const secretReminderRecipients = watch("reminderRecipients");
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(
|
||||||
|
"reminderRecipients",
|
||||||
|
secret?.secretReminderRecipients?.map((el) => el.user.id),
|
||||||
|
{
|
||||||
|
shouldDirty: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, [secret?.secretReminderRecipients]);
|
||||||
|
|
||||||
const getModifiedByIcon = (userType: string | undefined | null) => {
|
const getModifiedByIcon = (userType: string | undefined | null) => {
|
||||||
switch (userType) {
|
switch (userType) {
|
||||||
@@ -294,14 +306,20 @@ export const SecretDetailSidebar = ({
|
|||||||
<CreateReminderForm
|
<CreateReminderForm
|
||||||
repeatDays={secretReminderRepeatDays}
|
repeatDays={secretReminderRepeatDays}
|
||||||
note={secretReminderNote}
|
note={secretReminderNote}
|
||||||
|
recipients={secretReminderRecipients}
|
||||||
isOpen={createReminderFormOpen}
|
isOpen={createReminderFormOpen}
|
||||||
onOpenChange={(_, data) => {
|
onOpenChange={(_, data) => {
|
||||||
setCreateReminderFormOpen.toggle();
|
setCreateReminderFormOpen.toggle();
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
|
const recipients = data.recipients?.length
|
||||||
|
? data.recipients.map((recipient) => recipient.value)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
setValue("reminderRepeatDays", data.days, { shouldDirty: false });
|
setValue("reminderRepeatDays", data.days, { shouldDirty: false });
|
||||||
setValue("reminderNote", data.note, { shouldDirty: false });
|
setValue("reminderNote", data.note, { shouldDirty: false });
|
||||||
handleReminderSubmit(data.days, data.note);
|
setValue("reminderRecipients", recipients, { shouldDirty: false });
|
||||||
|
handleReminderSubmit(data.days, data.note, recipients);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ export const SecretListView = ({
|
|||||||
isProtectedBranch = false,
|
isProtectedBranch = false,
|
||||||
importedBy
|
importedBy
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
console.log("secretssssss", secrets);
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||||
"deleteSecret",
|
"deleteSecret",
|
||||||
@@ -85,6 +87,7 @@ export const SecretListView = ({
|
|||||||
comment,
|
comment,
|
||||||
reminderRepeatDays,
|
reminderRepeatDays,
|
||||||
reminderNote,
|
reminderNote,
|
||||||
|
reminderRecipients,
|
||||||
tags,
|
tags,
|
||||||
skipMultilineEncoding,
|
skipMultilineEncoding,
|
||||||
newKey,
|
newKey,
|
||||||
@@ -96,6 +99,7 @@ export const SecretListView = ({
|
|||||||
comment: string;
|
comment: string;
|
||||||
reminderRepeatDays: number | null;
|
reminderRepeatDays: number | null;
|
||||||
reminderNote: string | null;
|
reminderNote: string | null;
|
||||||
|
reminderRecipients?: string[] | null;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
skipMultilineEncoding: boolean;
|
skipMultilineEncoding: boolean;
|
||||||
newKey: string;
|
newKey: string;
|
||||||
@@ -131,6 +135,7 @@ export const SecretListView = ({
|
|||||||
secretComment: comment,
|
secretComment: comment,
|
||||||
secretReminderRepeatDays: reminderRepeatDays,
|
secretReminderRepeatDays: reminderRepeatDays,
|
||||||
secretReminderNote: reminderNote,
|
secretReminderNote: reminderNote,
|
||||||
|
secretReminderRecipients: reminderRecipients,
|
||||||
skipMultilineEncoding,
|
skipMultilineEncoding,
|
||||||
secretMetadata
|
secretMetadata
|
||||||
});
|
});
|
||||||
@@ -172,6 +177,7 @@ export const SecretListView = ({
|
|||||||
comment,
|
comment,
|
||||||
reminderRepeatDays,
|
reminderRepeatDays,
|
||||||
reminderNote,
|
reminderNote,
|
||||||
|
reminderRecipients,
|
||||||
secretMetadata,
|
secretMetadata,
|
||||||
isReminderEvent
|
isReminderEvent
|
||||||
} = modSecret;
|
} = modSecret;
|
||||||
@@ -180,6 +186,12 @@ export const SecretListView = ({
|
|||||||
const tagIds = tags?.map(({ id }) => id);
|
const tagIds = tags?.map(({ id }) => id);
|
||||||
const oldTagIds = (orgSecret?.tags || []).map(({ id }) => id);
|
const oldTagIds = (orgSecret?.tags || []).map(({ id }) => id);
|
||||||
const isSameTags = JSON.stringify(tagIds) === JSON.stringify(oldTagIds);
|
const isSameTags = JSON.stringify(tagIds) === JSON.stringify(oldTagIds);
|
||||||
|
|
||||||
|
const isSameRecipients =
|
||||||
|
!reminderRecipients?.some(
|
||||||
|
(newId) => !orgSecret.secretReminderRecipients?.find((oldId) => newId === oldId.user.id)
|
||||||
|
) && reminderRecipients?.length === orgSecret.secretReminderRecipients?.length;
|
||||||
|
|
||||||
const isSharedSecUnchanged =
|
const isSharedSecUnchanged =
|
||||||
(
|
(
|
||||||
[
|
[
|
||||||
@@ -189,9 +201,12 @@ export const SecretListView = ({
|
|||||||
"skipMultilineEncoding",
|
"skipMultilineEncoding",
|
||||||
"reminderRepeatDays",
|
"reminderRepeatDays",
|
||||||
"reminderNote",
|
"reminderNote",
|
||||||
|
"reminderRecipients",
|
||||||
"secretMetadata"
|
"secretMetadata"
|
||||||
] as const
|
] as const
|
||||||
).every((el) => orgSecret[el] === modSecret[el]) && isSameTags;
|
).every((el) => orgSecret[el] === modSecret[el]) &&
|
||||||
|
isSameTags &&
|
||||||
|
isSameRecipients;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// personal secret change
|
// personal secret change
|
||||||
@@ -224,6 +239,7 @@ export const SecretListView = ({
|
|||||||
comment,
|
comment,
|
||||||
reminderRepeatDays,
|
reminderRepeatDays,
|
||||||
reminderNote,
|
reminderNote,
|
||||||
|
reminderRecipients,
|
||||||
secretId: orgSecret.id,
|
secretId: orgSecret.id,
|
||||||
newKey: hasKeyChanged ? key : undefined,
|
newKey: hasKeyChanged ? key : undefined,
|
||||||
skipMultilineEncoding: modSecret.skipMultilineEncoding,
|
skipMultilineEncoding: modSecret.skipMultilineEncoding,
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export const formSchema = z.object({
|
|||||||
.nullable()
|
.nullable()
|
||||||
.optional(),
|
.optional(),
|
||||||
reminderNote: z.string().trim().nullable().optional(),
|
reminderNote: z.string().trim().nullable().optional(),
|
||||||
|
reminderRecipients: z.array(z.string().uuid()).optional(),
|
||||||
secretMetadata: z
|
secretMetadata: z
|
||||||
.object({
|
.object({
|
||||||
key: z.string().trim().min(1),
|
key: z.string().trim().min(1),
|
||||||
|
|||||||
Reference in New Issue
Block a user