mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 17:40:44 +00:00
solved merge conflicts
This commit is contained in:
@@ -14,6 +14,7 @@ export enum AppConnection {
|
||||
Windmill = "windmill",
|
||||
Auth0 = "auth0",
|
||||
HCVault = "hashicorp-vault",
|
||||
LDAP = "ldap",
|
||||
TeamCity = "teamcity"
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
HumanitecConnectionMethod,
|
||||
validateHumanitecConnectionCredentials
|
||||
} from "./humanitec";
|
||||
import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnectionCredentials } from "./ldap";
|
||||
import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql";
|
||||
import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres";
|
||||
import {
|
||||
@@ -83,6 +84,7 @@ export const listAppConnectionOptions = () => {
|
||||
getWindmillConnectionListItem(),
|
||||
getAuth0ConnectionListItem(),
|
||||
getHCVaultConnectionListItem(),
|
||||
getLdapConnectionListItem(),
|
||||
getTeamCityConnectionListItem()
|
||||
].sort((a, b) => a.name.localeCompare(b.name));
|
||||
};
|
||||
@@ -149,6 +151,7 @@ export const validateAppConnectionCredentials = async (
|
||||
[AppConnection.Auth0]: validateAuth0ConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.Windmill]: validateWindmillConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.HCVault]: validateHCVaultConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.LDAP]: validateLdapConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.TeamCity]: validateTeamCityConnectionCredentials as TAppConnectionCredentialsValidator
|
||||
};
|
||||
|
||||
@@ -188,6 +191,8 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
||||
return "Client Credentials";
|
||||
case HCVaultConnectionMethod.AppRole:
|
||||
return "App Role";
|
||||
case LdapConnectionMethod.SimpleBind:
|
||||
return "Simple Bind";
|
||||
default:
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
throw new Error(`Unhandled App Connection Method: ${method}`);
|
||||
@@ -234,5 +239,6 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
|
||||
[AppConnection.Windmill]: platformManagedCredentialsNotSupported,
|
||||
[AppConnection.Auth0]: platformManagedCredentialsNotSupported,
|
||||
[AppConnection.HCVault]: platformManagedCredentialsNotSupported,
|
||||
[AppConnection.LDAP]: platformManagedCredentialsNotSupported, // we could support this in the future
|
||||
[AppConnection.TeamCity]: platformManagedCredentialsNotSupported
|
||||
};
|
||||
|
||||
@@ -16,5 +16,6 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
||||
[AppConnection.Windmill]: "Windmill",
|
||||
[AppConnection.Auth0]: "Auth0",
|
||||
[AppConnection.HCVault]: "Hashicorp Vault",
|
||||
[AppConnection.LDAP]: "LDAP",
|
||||
[AppConnection.TeamCity]: "TeamCity"
|
||||
};
|
||||
|
||||
@@ -45,6 +45,7 @@ import { ValidateHCVaultConnectionCredentialsSchema } from "./hc-vault";
|
||||
import { hcVaultConnectionService } from "./hc-vault/hc-vault-connection-service";
|
||||
import { ValidateHumanitecConnectionCredentialsSchema } from "./humanitec";
|
||||
import { humanitecConnectionService } from "./humanitec/humanitec-connection-service";
|
||||
import { ValidateLdapConnectionCredentialsSchema } from "./ldap";
|
||||
import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql";
|
||||
import { ValidatePostgresConnectionCredentialsSchema } from "./postgres";
|
||||
import { ValidateTeamCityConnectionCredentialsSchema } from "./teamcity";
|
||||
@@ -80,6 +81,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
|
||||
[AppConnection.Windmill]: ValidateWindmillConnectionCredentialsSchema,
|
||||
[AppConnection.Auth0]: ValidateAuth0ConnectionCredentialsSchema,
|
||||
[AppConnection.HCVault]: ValidateHCVaultConnectionCredentialsSchema,
|
||||
[AppConnection.LDAP]: ValidateLdapConnectionCredentialsSchema,
|
||||
[AppConnection.TeamCity]: ValidateTeamCityConnectionCredentialsSchema
|
||||
};
|
||||
|
||||
|
||||
@@ -63,6 +63,12 @@ import {
|
||||
THumanitecConnectionInput,
|
||||
TValidateHumanitecConnectionCredentialsSchema
|
||||
} from "./humanitec";
|
||||
import {
|
||||
TLdapConnection,
|
||||
TLdapConnectionConfig,
|
||||
TLdapConnectionInput,
|
||||
TValidateLdapConnectionCredentialsSchema
|
||||
} from "./ldap";
|
||||
import { TMsSqlConnection, TMsSqlConnectionInput, TValidateMsSqlConnectionCredentialsSchema } from "./mssql";
|
||||
import {
|
||||
TPostgresConnection,
|
||||
@@ -110,6 +116,7 @@ export type TAppConnection = { id: string } & (
|
||||
| TWindmillConnection
|
||||
| TAuth0Connection
|
||||
| THCVaultConnection
|
||||
| TLdapConnection
|
||||
| TTeamCityConnection
|
||||
);
|
||||
|
||||
@@ -133,6 +140,7 @@ export type TAppConnectionInput = { id: string } & (
|
||||
| TWindmillConnectionInput
|
||||
| TAuth0ConnectionInput
|
||||
| THCVaultConnectionInput
|
||||
| TLdapConnectionInput
|
||||
| TTeamCityConnectionInput
|
||||
);
|
||||
|
||||
@@ -162,6 +170,7 @@ export type TAppConnectionConfig =
|
||||
| TWindmillConnectionConfig
|
||||
| TAuth0ConnectionConfig
|
||||
| THCVaultConnectionConfig
|
||||
| TLdapConnectionConfig
|
||||
| TTeamCityConnectionConfig;
|
||||
|
||||
export type TValidateAppConnectionCredentialsSchema =
|
||||
@@ -180,6 +189,7 @@ export type TValidateAppConnectionCredentialsSchema =
|
||||
| TValidateWindmillConnectionCredentialsSchema
|
||||
| TValidateAuth0ConnectionCredentialsSchema
|
||||
| TValidateHCVaultConnectionCredentialsSchema
|
||||
| TValidateLdapConnectionCredentialsSchema
|
||||
| TValidateTeamCityConnectionCredentialsSchema;
|
||||
|
||||
export type TListAwsConnectionKmsKeys = {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./ldap-connection-enums";
|
||||
export * from "./ldap-connection-fns";
|
||||
export * from "./ldap-connection-schemas";
|
||||
export * from "./ldap-connection-types";
|
||||
@@ -0,0 +1,7 @@
|
||||
export enum LdapConnectionMethod {
|
||||
SimpleBind = "simple-bind"
|
||||
}
|
||||
|
||||
export enum LdapProvider {
|
||||
ActiveDirectory = "active-directory"
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import ldap from "ldapjs";
|
||||
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
import { LdapConnectionMethod } from "./ldap-connection-enums";
|
||||
import { TLdapConnectionConfig } from "./ldap-connection-types";
|
||||
|
||||
export const getLdapConnectionListItem = () => {
|
||||
return {
|
||||
name: "LDAP" as const,
|
||||
app: AppConnection.LDAP as const,
|
||||
methods: Object.values(LdapConnectionMethod) as [LdapConnectionMethod.SimpleBind]
|
||||
};
|
||||
};
|
||||
|
||||
const LDAP_TIMEOUT = 15_000;
|
||||
|
||||
export const getLdapConnectionClient = async ({
|
||||
url,
|
||||
dn,
|
||||
password,
|
||||
sslCertificate,
|
||||
sslRejectUnauthorized = true
|
||||
}: TLdapConnectionConfig["credentials"]) => {
|
||||
await blockLocalAndPrivateIpAddresses(url);
|
||||
|
||||
const isSSL = url.startsWith("ldaps");
|
||||
|
||||
return new Promise<ldap.Client>((resolve, reject) => {
|
||||
const client = ldap.createClient({
|
||||
url,
|
||||
timeout: LDAP_TIMEOUT,
|
||||
connectTimeout: LDAP_TIMEOUT,
|
||||
tlsOptions: isSSL
|
||||
? {
|
||||
rejectUnauthorized: sslRejectUnauthorized,
|
||||
ca: sslCertificate ? [sslCertificate] : undefined
|
||||
}
|
||||
: undefined
|
||||
});
|
||||
|
||||
client.on("error", (err: Error) => {
|
||||
logger.error(err, "LDAP Error");
|
||||
client.destroy();
|
||||
reject(new Error(`Provider Error - ${err.message}`));
|
||||
});
|
||||
|
||||
client.on("connectError", (err: Error) => {
|
||||
logger.error(err, "LDAP Connection Error");
|
||||
client.destroy();
|
||||
reject(new Error(`Provider Connect Error - ${err.message}`));
|
||||
});
|
||||
|
||||
client.on("connectRefused", (err: Error) => {
|
||||
logger.error(err, "LDAP Connection Refused");
|
||||
client.destroy();
|
||||
reject(new Error(`Provider Connection Refused - ${err.message}`));
|
||||
});
|
||||
|
||||
client.on("connectTimeout", (err: Error) => {
|
||||
logger.error(err, "LDAP Connection Timeout");
|
||||
client.destroy();
|
||||
reject(new Error(`Provider Connection Timeout - ${err.message}`));
|
||||
});
|
||||
|
||||
client.on("connect", () => {
|
||||
client.bind(dn, password, (err) => {
|
||||
if (err) {
|
||||
logger.error(err, "LDAP Bind Error");
|
||||
reject(new Error(`Bind Error: ${err.message}`));
|
||||
client.destroy();
|
||||
}
|
||||
|
||||
resolve(client);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const validateLdapConnectionCredentials = async ({ credentials }: TLdapConnectionConfig) => {
|
||||
let client: ldap.Client | undefined;
|
||||
|
||||
try {
|
||||
client = await getLdapConnectionClient(credentials);
|
||||
|
||||
// this shouldn't occur as handle connection error events in client but here as fallback
|
||||
if (!client.connected) {
|
||||
throw new BadRequestError({ message: "Unable to connect to LDAP server" });
|
||||
}
|
||||
|
||||
return credentials;
|
||||
} catch (e: unknown) {
|
||||
throw new BadRequestError({
|
||||
message: `Unable to validate connection: ${(e as Error).message || "verify credentials"}`
|
||||
});
|
||||
} finally {
|
||||
client?.destroy();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
import RE2 from "re2";
|
||||
import { z } from "zod";
|
||||
|
||||
import { AppConnections } from "@app/lib/api-docs";
|
||||
import { DistinguishedNameRegex } from "@app/lib/regex";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import {
|
||||
BaseAppConnectionSchema,
|
||||
GenericCreateAppConnectionFieldsSchema,
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
|
||||
import { LdapConnectionMethod, LdapProvider } from "./ldap-connection-enums";
|
||||
|
||||
export const LdapConnectionSimpleBindCredentialsSchema = z.object({
|
||||
provider: z.nativeEnum(LdapProvider).describe(AppConnections.CREDENTIALS.LDAP.provider),
|
||||
url: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "URL required")
|
||||
.regex(new RE2(/^ldaps?:\/\//))
|
||||
.describe(AppConnections.CREDENTIALS.LDAP.url),
|
||||
dn: z
|
||||
.string()
|
||||
.trim()
|
||||
.regex(new RE2(DistinguishedNameRegex), "Invalid DN format, ie; CN=user,OU=users,DC=example,DC=com")
|
||||
.min(1, "Distinguished Name (DN) required")
|
||||
.describe(AppConnections.CREDENTIALS.LDAP.dn),
|
||||
password: z.string().trim().min(1, "Password required").describe(AppConnections.CREDENTIALS.LDAP.password),
|
||||
sslRejectUnauthorized: z.boolean().optional().describe(AppConnections.CREDENTIALS.LDAP.sslRejectUnauthorized),
|
||||
sslCertificate: z
|
||||
.string()
|
||||
.trim()
|
||||
.transform((value) => value || undefined)
|
||||
.optional()
|
||||
.describe(AppConnections.CREDENTIALS.LDAP.sslCertificate)
|
||||
});
|
||||
|
||||
const BaseLdapConnectionSchema = BaseAppConnectionSchema.extend({
|
||||
app: z.literal(AppConnection.LDAP)
|
||||
});
|
||||
|
||||
export const LdapConnectionSchema = z.intersection(
|
||||
BaseLdapConnectionSchema,
|
||||
z.discriminatedUnion("method", [
|
||||
z.object({
|
||||
method: z.literal(LdapConnectionMethod.SimpleBind),
|
||||
credentials: LdapConnectionSimpleBindCredentialsSchema
|
||||
})
|
||||
])
|
||||
);
|
||||
|
||||
export const SanitizedLdapConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseLdapConnectionSchema.extend({
|
||||
method: z.literal(LdapConnectionMethod.SimpleBind),
|
||||
credentials: LdapConnectionSimpleBindCredentialsSchema.pick({
|
||||
provider: true,
|
||||
url: true,
|
||||
dn: true,
|
||||
sslRejectUnauthorized: true,
|
||||
sslCertificate: true
|
||||
})
|
||||
})
|
||||
]);
|
||||
|
||||
export const ValidateLdapConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
z.object({
|
||||
method: z.literal(LdapConnectionMethod.SimpleBind).describe(AppConnections.CREATE(AppConnection.LDAP).method),
|
||||
credentials: LdapConnectionSimpleBindCredentialsSchema.describe(
|
||||
AppConnections.CREATE(AppConnection.LDAP).credentials
|
||||
)
|
||||
})
|
||||
]);
|
||||
|
||||
export const CreateLdapConnectionSchema = ValidateLdapConnectionCredentialsSchema.and(
|
||||
GenericCreateAppConnectionFieldsSchema(AppConnection.LDAP)
|
||||
);
|
||||
|
||||
export const UpdateLdapConnectionSchema = z
|
||||
.object({
|
||||
credentials: LdapConnectionSimpleBindCredentialsSchema.optional().describe(
|
||||
AppConnections.UPDATE(AppConnection.LDAP).credentials
|
||||
)
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.LDAP));
|
||||
|
||||
export const LdapConnectionListItemSchema = z.object({
|
||||
name: z.literal("LDAP"),
|
||||
app: z.literal(AppConnection.LDAP),
|
||||
// the below is preferable but currently breaks with our zod to json schema parser
|
||||
// methods: z.tuple([z.literal(AwsConnectionMethod.ServicePrincipal), z.literal(AwsConnectionMethod.AccessKey)]),
|
||||
methods: z.nativeEnum(LdapConnectionMethod).array()
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { DiscriminativePick } from "@app/lib/types";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
import {
|
||||
CreateLdapConnectionSchema,
|
||||
LdapConnectionSchema,
|
||||
ValidateLdapConnectionCredentialsSchema
|
||||
} from "./ldap-connection-schemas";
|
||||
|
||||
export type TLdapConnection = z.infer<typeof LdapConnectionSchema>;
|
||||
|
||||
export type TLdapConnectionInput = z.infer<typeof CreateLdapConnectionSchema> & {
|
||||
app: AppConnection.LDAP;
|
||||
};
|
||||
|
||||
export type TValidateLdapConnectionCredentialsSchema = typeof ValidateLdapConnectionCredentialsSchema;
|
||||
|
||||
export type TLdapConnectionConfig = DiscriminativePick<TLdapConnection, "method" | "app" | "credentials"> & {
|
||||
orgId: string;
|
||||
};
|
||||
@@ -31,7 +31,8 @@ export const SanitizedMsSqlConnectionSchema = z.discriminatedUnion("method", [
|
||||
port: true,
|
||||
username: true,
|
||||
sslEnabled: true,
|
||||
sslRejectUnauthorized: true
|
||||
sslRejectUnauthorized: true,
|
||||
sslCertificate: true
|
||||
})
|
||||
})
|
||||
]);
|
||||
|
||||
@@ -29,7 +29,8 @@ export const SanitizedPostgresConnectionSchema = z.discriminatedUnion("method",
|
||||
port: true,
|
||||
username: true,
|
||||
sslEnabled: true,
|
||||
sslRejectUnauthorized: true
|
||||
sslRejectUnauthorized: true,
|
||||
sslCertificate: true
|
||||
})
|
||||
})
|
||||
]);
|
||||
|
||||
@@ -476,6 +476,7 @@ export const authLoginServiceFactory = ({
|
||||
|
||||
return {
|
||||
...tokens,
|
||||
user,
|
||||
isMfaEnabled: false
|
||||
};
|
||||
};
|
||||
@@ -784,7 +785,7 @@ export const authLoginServiceFactory = ({
|
||||
organizationId
|
||||
});
|
||||
|
||||
return { token, isMfaEnabled: false, user: userEnc } as const;
|
||||
return { token, isMfaEnabled: false, user: userEnc, decodedProviderToken } as const;
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -177,6 +177,7 @@ export const deleteGithubSecrets = async ({
|
||||
selected_repositories_url?: string | undefined;
|
||||
}
|
||||
|
||||
// @ts-expect-error just octokit ts compatiability issue
|
||||
const OctokitWithRetry = Octokit.plugin(retry);
|
||||
let octokit: Octokit;
|
||||
const appCfg = getConfig();
|
||||
|
||||
@@ -342,9 +342,12 @@ export const kmsServiceFactory = ({
|
||||
}
|
||||
|
||||
return async ({ cipherTextBlob }: Pick<TDecryptWithKmsDTO, "cipherTextBlob">) => {
|
||||
const { data } = await externalKms.decrypt(cipherTextBlob);
|
||||
|
||||
return data;
|
||||
try {
|
||||
const { data } = await externalKms.decrypt(cipherTextBlob);
|
||||
return data;
|
||||
} finally {
|
||||
await externalKms.cleanup();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -557,9 +560,12 @@ export const kmsServiceFactory = ({
|
||||
}
|
||||
|
||||
return async ({ plainText }: Pick<TEncryptWithKmsDTO, "plainText">) => {
|
||||
const { encryptedBlob } = await externalKms.encrypt(plainText);
|
||||
|
||||
return { cipherTextBlob: encryptedBlob };
|
||||
try {
|
||||
const { encryptedBlob } = await externalKms.encrypt(plainText);
|
||||
return { cipherTextBlob: encryptedBlob };
|
||||
} finally {
|
||||
await externalKms.cleanup();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import { TProjectDALFactory } from "../project/project-dal";
|
||||
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
|
||||
import { TProjectKeyDALFactory } from "../project-key/project-key-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 { TUserDALFactory } from "../user/user-dal";
|
||||
import { TProjectMembershipDALFactory } from "./project-membership-dal";
|
||||
@@ -53,6 +54,7 @@ type TProjectMembershipServiceFactoryDep = {
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "findLatestProjectKey" | "delete" | "insertMany">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
||||
secretReminderRecipientsDAL: Pick<TSecretReminderRecipientsDALFactory, "delete">;
|
||||
groupProjectDAL: TGroupProjectDALFactory;
|
||||
};
|
||||
|
||||
@@ -71,6 +73,7 @@ export const projectMembershipServiceFactory = ({
|
||||
groupProjectDAL,
|
||||
projectDAL,
|
||||
projectKeyDAL,
|
||||
secretReminderRecipientsDAL,
|
||||
licenseService
|
||||
}: TProjectMembershipServiceFactoryDep) => {
|
||||
const getProjectMemberships = async ({
|
||||
@@ -389,6 +392,13 @@ export const projectMembershipServiceFactory = ({
|
||||
const membership = await projectMembershipDAL.transaction(async (tx) => {
|
||||
const [deletedMembership] = await projectMembershipDAL.delete({ projectId, id: membershipId }, tx);
|
||||
await projectKeyDAL.delete({ receiverId: deletedMembership.userId, projectId }, tx);
|
||||
await secretReminderRecipientsDAL.delete(
|
||||
{
|
||||
projectId,
|
||||
userId: deletedMembership.userId
|
||||
},
|
||||
tx
|
||||
);
|
||||
return deletedMembership;
|
||||
});
|
||||
return membership;
|
||||
@@ -466,6 +476,16 @@ export const projectMembershipServiceFactory = ({
|
||||
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
|
||||
await projectKeyDAL.delete(
|
||||
{
|
||||
@@ -526,6 +546,15 @@ export const projectMembershipServiceFactory = ({
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
await secretReminderRecipientsDAL.delete(
|
||||
{
|
||||
projectId,
|
||||
userId: actorId
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
const membership = (
|
||||
await projectMembershipDAL.delete(
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||
import { requestContext } from "@fastify/request-context";
|
||||
|
||||
import { ActionProjectType, ProjectMembershipRole, TableName } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
@@ -12,10 +13,12 @@ import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
|
||||
|
||||
import { ActorAuthMethod } from "../auth/auth-type";
|
||||
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
||||
import { TIdentityDALFactory } from "../identity/identity-dal";
|
||||
import { TIdentityProjectMembershipRoleDALFactory } from "../identity-project/identity-project-membership-role-dal";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal";
|
||||
import { TUserDALFactory } from "../user/user-dal";
|
||||
import { TProjectRoleDALFactory } from "./project-role-dal";
|
||||
import { getPredefinedRoles } from "./project-role-fns";
|
||||
import {
|
||||
@@ -29,6 +32,8 @@ import {
|
||||
|
||||
type TProjectRoleServiceFactoryDep = {
|
||||
projectRoleDAL: TProjectRoleDALFactory;
|
||||
identityDAL: Pick<TIdentityDALFactory, "findById">;
|
||||
userDAL: Pick<TUserDALFactory, "findById">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getUserProjectPermission">;
|
||||
identityProjectMembershipRoleDAL: TIdentityProjectMembershipRoleDALFactory;
|
||||
@@ -47,7 +52,9 @@ export const projectRoleServiceFactory = ({
|
||||
permissionService,
|
||||
identityProjectMembershipRoleDAL,
|
||||
projectUserMembershipRoleDAL,
|
||||
projectDAL
|
||||
projectDAL,
|
||||
identityDAL,
|
||||
userDAL
|
||||
}: TProjectRoleServiceFactoryDep) => {
|
||||
const createRole = async ({ data, actor, actorId, actorAuthMethod, actorOrgId, filter }: TCreateRoleDTO) => {
|
||||
let projectId = "";
|
||||
@@ -220,14 +227,42 @@ export const projectRoleServiceFactory = ({
|
||||
actorAuthMethod: ActorAuthMethod,
|
||||
actorOrgId: string | undefined
|
||||
) => {
|
||||
const { permission, membership } = await permissionService.getUserProjectPermission({
|
||||
userId,
|
||||
const { permission, membership } = await permissionService.getProjectPermission({
|
||||
actor: ActorType.USER,
|
||||
actorId: userId,
|
||||
projectId,
|
||||
authMethod: actorAuthMethod,
|
||||
userOrgId: actorOrgId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
return { permissions: packRules(permission.rules), membership };
|
||||
// just to satisfy ts
|
||||
if (!("roles" in membership)) throw new BadRequestError({ message: "Service token not allowed" });
|
||||
|
||||
const assumedPrivilegeDetailsCtx = requestContext.get("assumedPrivilegeDetails");
|
||||
const isAssumingPrivilege = assumedPrivilegeDetailsCtx?.projectId === projectId;
|
||||
const assumedPrivilegeDetails = isAssumingPrivilege
|
||||
? {
|
||||
actorId: assumedPrivilegeDetailsCtx?.actorId,
|
||||
actorType: assumedPrivilegeDetailsCtx?.actorType,
|
||||
actorName: "",
|
||||
actorEmail: ""
|
||||
}
|
||||
: undefined;
|
||||
|
||||
if (assumedPrivilegeDetails?.actorType === ActorType.IDENTITY) {
|
||||
const identityDetails = await identityDAL.findById(assumedPrivilegeDetails.actorId);
|
||||
if (!identityDetails)
|
||||
throw new NotFoundError({ message: `Identity with ID ${assumedPrivilegeDetails.actorId} not found` });
|
||||
assumedPrivilegeDetails.actorName = identityDetails.name;
|
||||
} else if (assumedPrivilegeDetails?.actorType === ActorType.USER) {
|
||||
const userDetails = await userDAL.findById(assumedPrivilegeDetails?.actorId);
|
||||
if (!userDetails)
|
||||
throw new NotFoundError({ message: `User with ID ${assumedPrivilegeDetails.actorId} not found` });
|
||||
assumedPrivilegeDetails.actorName = `${userDetails?.firstName} ${userDetails?.lastName || ""}`;
|
||||
assumedPrivilegeDetails.actorEmail = userDetails?.email || "";
|
||||
}
|
||||
|
||||
return { permissions: packRules(permission.rules), membership, assumedPrivilegeDetails };
|
||||
};
|
||||
|
||||
return { createRole, updateRole, deleteRole, listRoles, getUserPermission, getRoleBySlug };
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -7,6 +7,7 @@ import { ProjectType, SecretsV2Schema, SecretType, TableName, TSecretsV2, TSecre
|
||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { generateCacheKeyFromData } from "@app/lib/crypto/cache";
|
||||
import { applyJitter } from "@app/lib/dates";
|
||||
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
|
||||
import {
|
||||
buildFindFilter,
|
||||
@@ -48,7 +49,7 @@ interface TSecretV2DalArg {
|
||||
keyStore: TKeyStoreFactory;
|
||||
}
|
||||
|
||||
export const SECRET_DAL_TTL = 5 * 60;
|
||||
export const SECRET_DAL_TTL = () => applyJitter(10 * 60, 2 * 60);
|
||||
export const SECRET_DAL_VERSION_TTL = 15 * 60;
|
||||
export const MAX_SECRET_CACHE_BYTES = 25 * 1024 * 1024;
|
||||
export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
@@ -79,7 +80,17 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
`${TableName.SecretV2}.id`,
|
||||
`${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(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("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
||||
@@ -103,6 +114,23 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
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 +512,12 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
|
||||
`${TableName.SecretTag}.id`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.SecretReminderRecipients,
|
||||
`${TableName.SecretV2}.id`,
|
||||
`${TableName.SecretReminderRecipients}.secretId`
|
||||
)
|
||||
.leftJoin(TableName.Users, `${TableName.SecretReminderRecipients}.userId`, `${TableName.Users}.id`)
|
||||
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
|
||||
.leftJoin(
|
||||
TableName.SecretRotationV2SecretMapping,
|
||||
@@ -512,6 +546,10 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
}) 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("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
||||
@@ -556,6 +594,23 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||
isRotatedSecret: Boolean(el.rotationId)
|
||||
}),
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "reminderRecipientId",
|
||||
label: "secretReminderRecipients" as const,
|
||||
mapper: ({
|
||||
reminderRecipientId,
|
||||
reminderRecipientUsername,
|
||||
reminderRecipientEmail,
|
||||
reminderRecipientUserId
|
||||
}) => ({
|
||||
user: {
|
||||
id: reminderRecipientUserId,
|
||||
username: reminderRecipientUsername,
|
||||
email: reminderRecipientEmail
|
||||
},
|
||||
id: reminderRecipientId
|
||||
})
|
||||
},
|
||||
{
|
||||
key: "tagId",
|
||||
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 { INFISICAL_SECRET_VALUE_HIDDEN_MASK } from "../secret/secret-fns";
|
||||
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 { TFnSecretBulkDelete, TFnSecretBulkInsert, TFnSecretBulkUpdate } from "./secret-v2-bridge-types";
|
||||
|
||||
@@ -353,7 +354,7 @@ export const fnSecretBulkDelete = async ({
|
||||
deletedSecrets
|
||||
.filter(({ reminderRepeatDays }) => Boolean(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;
|
||||
isRotatedSecret?: boolean;
|
||||
rotationId?: string;
|
||||
secretReminderRecipients?: TSecretReminderRecipient[];
|
||||
},
|
||||
secretValueHidden: boolean
|
||||
) => ({
|
||||
@@ -715,6 +717,7 @@ export const reshapeBridgeSecret = (
|
||||
updatedAt: secret.updatedAt,
|
||||
isRotatedSecret: secret.isRotatedSecret,
|
||||
rotationId: secret.rotationId,
|
||||
secretReminderRecipients: secret.secretReminderRecipients || [],
|
||||
...(secretValueHidden
|
||||
? {
|
||||
secretValue: INFISICAL_SECRET_VALUE_HIDDEN_MASK,
|
||||
|
||||
@@ -544,7 +544,12 @@ export const secretV2BridgeServiceFactory = ({
|
||||
id: updatedSecret[0].id,
|
||||
...inputSecret
|
||||
},
|
||||
oldSecret: secret,
|
||||
oldSecret: {
|
||||
id: secret.id,
|
||||
secretReminderNote: secret.reminderNote,
|
||||
secretReminderRepeatDays: secret.reminderRepeatDays,
|
||||
secretReminderRecipients: secret.secretReminderRecipients?.map((el) => el.user.id)
|
||||
},
|
||||
projectId
|
||||
});
|
||||
|
||||
@@ -957,7 +962,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
const encryptedCachedSecrets = await keyStore.getItem(cacheKey);
|
||||
if (encryptedCachedSecrets) {
|
||||
try {
|
||||
await keyStore.setExpiry(cacheKey, SECRET_DAL_TTL);
|
||||
await keyStore.setExpiry(cacheKey, SECRET_DAL_TTL());
|
||||
const cachedSecrets = secretManagerDecryptor({ cipherTextBlob: Buffer.from(encryptedCachedSecrets, "base64") });
|
||||
const { secrets, imports = [] } = JSON.parse(cachedSecrets.toString("utf8")) as {
|
||||
secrets: typeof decryptedSecrets;
|
||||
@@ -1127,7 +1132,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
plainText: Buffer.from(JSON.stringify(payload))
|
||||
}).cipherTextBlob;
|
||||
if (encryptedUpdatedCachedSecrets.byteLength < MAX_SECRET_CACHE_BYTES) {
|
||||
await keyStore.setItemWithExpiry(cacheKey, SECRET_DAL_TTL, encryptedUpdatedCachedSecrets.toString("base64"));
|
||||
await keyStore.setItemWithExpiry(cacheKey, SECRET_DAL_TTL(), encryptedUpdatedCachedSecrets.toString("base64"));
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
@@ -1174,7 +1179,7 @@ export const secretV2BridgeServiceFactory = ({
|
||||
plainText: Buffer.from(JSON.stringify(payload))
|
||||
}).cipherTextBlob;
|
||||
if (encryptedUpdatedCachedSecrets.byteLength < MAX_SECRET_CACHE_BYTES) {
|
||||
await keyStore.setItemWithExpiry(cacheKey, SECRET_DAL_TTL, encryptedUpdatedCachedSecrets.toString("base64"));
|
||||
await keyStore.setItemWithExpiry(cacheKey, SECRET_DAL_TTL(), encryptedUpdatedCachedSecrets.toString("base64"));
|
||||
}
|
||||
return payload;
|
||||
};
|
||||
|
||||
@@ -94,6 +94,7 @@ export type TUpdateSecretDTO = TProjectPermission & {
|
||||
skipMultilineEncoding?: boolean;
|
||||
secretReminderRepeatDays?: number | null;
|
||||
secretReminderNote?: string | null;
|
||||
secretReminderRecipients?: string[] | null;
|
||||
metadata?: {
|
||||
source?: string;
|
||||
};
|
||||
@@ -220,7 +221,7 @@ export type TFnSecretBulkDelete = {
|
||||
tx?: Knex;
|
||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "deleteMany">;
|
||||
secretQueueService: {
|
||||
removeSecretReminder: (data: TRemoveSecretReminderDTO) => Promise<void>;
|
||||
removeSecretReminder: (data: TRemoveSecretReminderDTO, tx?: Knex) => Promise<void>;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -407,6 +407,7 @@ export const decryptSecretRaw = (
|
||||
id: secret.id,
|
||||
user: secret.userId,
|
||||
tags: secret.tags?.map((el) => ({ ...el, name: el.slug })),
|
||||
secretReminderRecipients: [],
|
||||
skipMultilineEncoding: secret.skipMultilineEncoding,
|
||||
secretReminderRepeatDays: secret.secretReminderRepeatDays,
|
||||
secretReminderNote: secret.secretReminderNote,
|
||||
@@ -758,7 +759,7 @@ export const fnSecretBulkDelete = async ({
|
||||
deletedSecrets
|
||||
.filter(({ secretReminderRepeatDays }) => Boolean(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 */
|
||||
import opentelemetry from "@opentelemetry/api";
|
||||
import { AxiosError } from "axios";
|
||||
import { Knex } from "knex";
|
||||
|
||||
import {
|
||||
ProjectMembershipRole,
|
||||
ProjectUpgradeStatus,
|
||||
ProjectVersion,
|
||||
SecretType,
|
||||
TSecretSnapshotSecretsV2,
|
||||
TSecretVersionsV2
|
||||
} 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 { 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";
|
||||
@@ -109,6 +112,10 @@ type TSecretQueueFactoryDep = {
|
||||
orgService: Pick<TOrgServiceFactory, "addGhostUser">;
|
||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
||||
secretReminderRecipientsDAL: Pick<
|
||||
TSecretReminderRecipientsDALFactory,
|
||||
"delete" | "findUsersBySecretId" | "insertMany" | "transaction"
|
||||
>;
|
||||
secretSyncQueue: Pick<TSecretSyncQueueFactory, "queueSecretSyncsSyncSecretsByPath">;
|
||||
};
|
||||
|
||||
@@ -170,6 +177,7 @@ export const secretQueueFactory = ({
|
||||
projectUserMembershipRoleDAL,
|
||||
projectKeyDAL,
|
||||
resourceMetadataDAL,
|
||||
secretReminderRecipientsDAL,
|
||||
secretSyncQueue
|
||||
}: TSecretQueueFactoryDep) => {
|
||||
const integrationMeter = opentelemetry.metrics.getMeter("Integrations");
|
||||
@@ -178,7 +186,11 @@ export const secretQueueFactory = ({
|
||||
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();
|
||||
await queueService.stopRepeatableJob(
|
||||
QueueName.SecretReminder,
|
||||
@@ -224,7 +236,12 @@ export const secretQueueFactory = ({
|
||||
.replace(":", "-");
|
||||
};
|
||||
|
||||
const addSecretReminder = async ({ oldSecret, newSecret, projectId }: TCreateSecretReminderDTO) => {
|
||||
const addSecretReminder = async ({
|
||||
oldSecret,
|
||||
newSecret,
|
||||
projectId,
|
||||
deleteRecipients = true
|
||||
}: TCreateSecretReminderDTO) => {
|
||||
try {
|
||||
const appCfg = getConfig();
|
||||
|
||||
@@ -246,7 +263,8 @@ export const secretQueueFactory = ({
|
||||
if (oldSecret.secretReminderRepeatDays) {
|
||||
await removeSecretReminder({
|
||||
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 { secretReminderRepeatDays, secretReminderNote } = newSecret;
|
||||
const { secretReminderRepeatDays, secretReminderNote, secretReminderRecipients } = newSecret;
|
||||
|
||||
if (newSecret.type !== "personal" && secretReminderRepeatDays !== undefined) {
|
||||
if (
|
||||
(secretReminderRepeatDays && oldSecret.secretReminderRepeatDays !== secretReminderRepeatDays) ||
|
||||
(secretReminderNote && oldSecret.secretReminderNote !== secretReminderNote)
|
||||
) {
|
||||
await addSecretReminder({
|
||||
oldSecret,
|
||||
newSecret,
|
||||
projectId
|
||||
});
|
||||
} else if (
|
||||
secretReminderRepeatDays === null &&
|
||||
secretReminderNote === null &&
|
||||
oldSecret.secretReminderRepeatDays
|
||||
) {
|
||||
await removeSecretReminder({
|
||||
secretId: oldSecret.id,
|
||||
repeatDays: oldSecret.secretReminderRepeatDays
|
||||
});
|
||||
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 (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,
|
||||
@@ -1071,6 +1117,8 @@ export const secretQueueFactory = ({
|
||||
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;
|
||||
@@ -1088,10 +1136,14 @@ export const secretQueueFactory = ({
|
||||
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: [...projectMembers.map((m) => m.user.email)].filter((email) => email).map((email) => email as string),
|
||||
recipients: selectedRecipients,
|
||||
substitutions: {
|
||||
reminderNote: data.note, // May not be present.
|
||||
projectName: project.name,
|
||||
|
||||
@@ -546,10 +546,13 @@ export const secretServiceFactory = ({
|
||||
|
||||
for await (const secret of secrets) {
|
||||
if (secret.secretReminderRepeatDays !== null && secret.secretReminderRepeatDays !== undefined) {
|
||||
await secretQueueService.removeSecretReminder({
|
||||
repeatDays: secret.secretReminderRepeatDays,
|
||||
secretId: secret.id
|
||||
});
|
||||
await secretQueueService.removeSecretReminder(
|
||||
{
|
||||
repeatDays: secret.secretReminderRepeatDays,
|
||||
secretId: secret.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -685,6 +688,7 @@ export const secretServiceFactory = ({
|
||||
...secret,
|
||||
workspace: projectId,
|
||||
environment,
|
||||
secretReminderRecipients: [],
|
||||
secretPath: groupedPaths[secret.folderId][0].path
|
||||
}))
|
||||
};
|
||||
@@ -1073,10 +1077,13 @@ export const secretServiceFactory = ({
|
||||
|
||||
for await (const secret of secrets) {
|
||||
if (secret.secretReminderRepeatDays !== null && secret.secretReminderRepeatDays !== undefined) {
|
||||
await secretQueueService.removeSecretReminder({
|
||||
repeatDays: secret.secretReminderRepeatDays,
|
||||
secretId: secret.id
|
||||
});
|
||||
await secretQueueService.removeSecretReminder(
|
||||
{
|
||||
repeatDays: secret.secretReminderRepeatDays,
|
||||
secretId: secret.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
}
|
||||
const secretValueHidden = !hasSecretReadValueOrDescribePermission(
|
||||
@@ -1786,6 +1793,7 @@ export const secretServiceFactory = ({
|
||||
tagIds,
|
||||
secretReminderNote,
|
||||
secretReminderRepeatDays,
|
||||
secretReminderRecipients,
|
||||
metadata,
|
||||
secretComment,
|
||||
newSecretName,
|
||||
@@ -1828,6 +1836,7 @@ export const secretServiceFactory = ({
|
||||
tagIds,
|
||||
reminderNote: secretReminderNote,
|
||||
reminderRepeatDays: secretReminderRepeatDays,
|
||||
secretReminderRecipients,
|
||||
secretMetadata
|
||||
}
|
||||
]
|
||||
@@ -1837,8 +1846,9 @@ export const secretServiceFactory = ({
|
||||
}
|
||||
const secret = await secretV2BridgeService.updateSecret({
|
||||
secretReminderRepeatDays,
|
||||
skipMultilineEncoding,
|
||||
secretReminderNote,
|
||||
secretReminderRecipients,
|
||||
skipMultilineEncoding,
|
||||
tagIds,
|
||||
secretComment,
|
||||
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 { 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({
|
||||
projectId: z.string(),
|
||||
@@ -258,6 +262,7 @@ export type TUpdateSecretRawDTO = TProjectPermission & {
|
||||
skipMultilineEncoding?: boolean;
|
||||
secretReminderRepeatDays?: number | null;
|
||||
secretReminderNote?: string | null;
|
||||
secretReminderRecipients?: string[] | null;
|
||||
metadata?: {
|
||||
source?: string;
|
||||
};
|
||||
@@ -374,7 +379,7 @@ export type TFnSecretBulkDelete = {
|
||||
tx?: Knex;
|
||||
secretDAL: Pick<TSecretDALFactory, "deleteMany">;
|
||||
secretQueueService: {
|
||||
removeSecretReminder: (data: TRemoveSecretReminderDTO) => Promise<void>;
|
||||
removeSecretReminder: (data: TRemoveSecretReminderDTO, tx?: Knex) => Promise<void>;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -405,11 +410,14 @@ export type TCreateSecretReminderDTO = {
|
||||
oldSecret: TPartialSecret;
|
||||
newSecret: TPartialSecret;
|
||||
projectId: string;
|
||||
|
||||
deleteRecipients?: boolean;
|
||||
};
|
||||
|
||||
export type TRemoveSecretReminderDTO = {
|
||||
secretId: string;
|
||||
repeatDays: number;
|
||||
deleteRecipients?: boolean;
|
||||
};
|
||||
|
||||
export type TBackFillSecretReferencesDTO = TProjectPermission;
|
||||
|
||||
Reference in New Issue
Block a user