mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat: completed easier changes on other files where permission is needed
This commit is contained in:
@@ -14,7 +14,7 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const accessApprovalPolicyFindQuery = async (
|
const accessApprovalPolicyFindQuery = async (
|
||||||
tx: Knex,
|
tx: Knex,
|
||||||
filter: TFindFilter<TAccessApprovalPolicies>,
|
filter: TFindFilter<TAccessApprovalPolicies & { projectId: string }>,
|
||||||
customFilter?: {
|
customFilter?: {
|
||||||
policyId?: string;
|
policyId?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const isApproversValid = async ({
|
|||||||
|
|
||||||
ForbiddenError.from(approverPermission).throwUnlessCan(
|
ForbiddenError.from(approverPermission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: envSlug, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment: envSlug, secretPath, secretName: "", secretTags: [] })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
|
|||||||
import { TGroupDALFactory } from "../group/group-dal";
|
import { TGroupDALFactory } from "../group/group-dal";
|
||||||
import { TAccessApprovalPolicyApproverDALFactory } from "./access-approval-policy-approver-dal";
|
import { TAccessApprovalPolicyApproverDALFactory } from "./access-approval-policy-approver-dal";
|
||||||
import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal";
|
import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal";
|
||||||
import { isApproversValid } from "./access-approval-policy-fns";
|
|
||||||
import {
|
import {
|
||||||
ApproverType,
|
ApproverType,
|
||||||
TCreateAccessApprovalPolicy,
|
TCreateAccessApprovalPolicy,
|
||||||
@@ -132,22 +131,6 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
.map((user) => user.id);
|
.map((user) => user.id);
|
||||||
verifyAllApprovers.push(...verifyGroupApprovers);
|
verifyAllApprovers.push(...verifyGroupApprovers);
|
||||||
|
|
||||||
const approversValid = await isApproversValid({
|
|
||||||
projectId: project.id,
|
|
||||||
orgId: actorOrgId,
|
|
||||||
envSlug: environment,
|
|
||||||
secretPath,
|
|
||||||
actorAuthMethod,
|
|
||||||
permissionService,
|
|
||||||
userIds: verifyAllApprovers
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!approversValid) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "One or more approvers doesn't have access to be specified secret path"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
|
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
|
||||||
const doc = await accessApprovalPolicyDAL.create(
|
const doc = await accessApprovalPolicyDAL.create(
|
||||||
{
|
{
|
||||||
@@ -289,22 +272,6 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
userApproverIds = userApproverIds.concat(approverUsers.map((user) => user.id));
|
userApproverIds = userApproverIds.concat(approverUsers.map((user) => user.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
const approversValid = await isApproversValid({
|
|
||||||
projectId: accessApprovalPolicy.projectId,
|
|
||||||
orgId: actorOrgId,
|
|
||||||
envSlug: accessApprovalPolicy.environment.slug,
|
|
||||||
secretPath: doc.secretPath!,
|
|
||||||
actorAuthMethod,
|
|
||||||
permissionService,
|
|
||||||
userIds: userApproverIds
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!approversValid) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "One or more approvers doesn't have access to be specified secret path"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await accessApprovalPolicyApproverDAL.insertMany(
|
await accessApprovalPolicyApproverDAL.insertMany(
|
||||||
userApproverIds.map((userId) => ({
|
userApproverIds.map((userId) => ({
|
||||||
approverUserId: userId,
|
approverUserId: userId,
|
||||||
@@ -315,41 +282,6 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (groupApprovers) {
|
if (groupApprovers) {
|
||||||
const usersPromises: Promise<
|
|
||||||
{
|
|
||||||
id: string;
|
|
||||||
email: string | null | undefined;
|
|
||||||
username: string;
|
|
||||||
firstName: string | null | undefined;
|
|
||||||
lastName: string | null | undefined;
|
|
||||||
isPartOfGroup: boolean;
|
|
||||||
}[]
|
|
||||||
>[] = [];
|
|
||||||
|
|
||||||
for (const groupId of groupApprovers) {
|
|
||||||
usersPromises.push(groupDAL.findAllGroupPossibleMembers({ orgId: actorOrgId, groupId, offset: 0 }));
|
|
||||||
}
|
|
||||||
const verifyGroupApprovers = (await Promise.all(usersPromises))
|
|
||||||
.flat()
|
|
||||||
.filter((user) => user.isPartOfGroup)
|
|
||||||
.map((user) => user.id);
|
|
||||||
|
|
||||||
const approversValid = await isApproversValid({
|
|
||||||
projectId: accessApprovalPolicy.projectId,
|
|
||||||
orgId: actorOrgId,
|
|
||||||
envSlug: accessApprovalPolicy.environment.slug,
|
|
||||||
secretPath: doc.secretPath!,
|
|
||||||
actorAuthMethod,
|
|
||||||
permissionService,
|
|
||||||
userIds: verifyGroupApprovers
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!approversValid) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "One or more approvers doesn't have access to be specified secret path"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await accessApprovalPolicyApproverDAL.insertMany(
|
await accessApprovalPolicyApproverDAL.insertMany(
|
||||||
groupApprovers.map((groupId) => ({
|
groupApprovers.map((groupId) => ({
|
||||||
approverGroupId: groupId,
|
approverGroupId: groupId,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
|
|||||||
|
|
||||||
import { TAccessApprovalPolicyApproverDALFactory } from "../access-approval-policy/access-approval-policy-approver-dal";
|
import { TAccessApprovalPolicyApproverDALFactory } from "../access-approval-policy/access-approval-policy-approver-dal";
|
||||||
import { TAccessApprovalPolicyDALFactory } from "../access-approval-policy/access-approval-policy-dal";
|
import { TAccessApprovalPolicyDALFactory } from "../access-approval-policy/access-approval-policy-dal";
|
||||||
import { isApproversValid } from "../access-approval-policy/access-approval-policy-fns";
|
|
||||||
import { TGroupDALFactory } from "../group/group-dal";
|
import { TGroupDALFactory } from "../group/group-dal";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
|
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
|
||||||
@@ -78,7 +77,6 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
permissionService,
|
permissionService,
|
||||||
accessApprovalRequestDAL,
|
accessApprovalRequestDAL,
|
||||||
accessApprovalRequestReviewerDAL,
|
accessApprovalRequestReviewerDAL,
|
||||||
projectMembershipDAL,
|
|
||||||
accessApprovalPolicyDAL,
|
accessApprovalPolicyDAL,
|
||||||
accessApprovalPolicyApproverDAL,
|
accessApprovalPolicyApproverDAL,
|
||||||
additionalPrivilegeDAL,
|
additionalPrivilegeDAL,
|
||||||
@@ -323,22 +321,6 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
throw new ForbiddenRequestError({ message: "You are not authorized to approve this request" });
|
throw new ForbiddenRequestError({ message: "You are not authorized to approve this request" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const reviewerProjectMembership = await projectMembershipDAL.findById(membership.id);
|
|
||||||
|
|
||||||
const approversValid = await isApproversValid({
|
|
||||||
projectId: accessApprovalRequest.projectId,
|
|
||||||
orgId: actorOrgId,
|
|
||||||
envSlug: accessApprovalRequest.environment,
|
|
||||||
secretPath: accessApprovalRequest.policy.secretPath!,
|
|
||||||
actorAuthMethod,
|
|
||||||
permissionService,
|
|
||||||
userIds: [reviewerProjectMembership.userId]
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!approversValid) {
|
|
||||||
throw new ForbiddenRequestError({ message: "You don't have access to approve this request" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingReviews = await accessApprovalRequestReviewerDAL.find({ requestId: accessApprovalRequest.id });
|
const existingReviews = await accessApprovalRequestReviewerDAL.find({ requestId: accessApprovalRequest.id });
|
||||||
if (existingReviews.some((review) => review.status === ApprovalStatus.REJECTED)) {
|
if (existingReviews.some((review) => review.status === ApprovalStatus.REJECTED)) {
|
||||||
throw new BadRequestError({ message: "The request has already been rejected by another reviewer" });
|
throw new BadRequestError({ message: "The request has already been rejected by another reviewer" });
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const secretApprovalPolicyFindQuery = (
|
const secretApprovalPolicyFindQuery = (
|
||||||
tx: Knex,
|
tx: Knex,
|
||||||
filter: TFindFilter<TSecretApprovalPolicies>,
|
filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>,
|
||||||
customFilter?: {
|
customFilter?: {
|
||||||
sapId?: string;
|
sapId?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -353,7 +353,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, { secretPath, environment })
|
subject(ProjectPermissionSub.Secrets, { secretPath, environment, secretName: "", secretTags: [] })
|
||||||
);
|
);
|
||||||
return getSecretApprovalPolicy(projectId, environment, secretPath);
|
return getSecretApprovalPolicy(projectId, environment, secretPath);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import {
|
|||||||
fnSecretBulkDelete as fnSecretV2BridgeBulkDelete,
|
fnSecretBulkDelete as fnSecretV2BridgeBulkDelete,
|
||||||
fnSecretBulkInsert as fnSecretV2BridgeBulkInsert,
|
fnSecretBulkInsert as fnSecretV2BridgeBulkInsert,
|
||||||
fnSecretBulkUpdate as fnSecretV2BridgeBulkUpdate,
|
fnSecretBulkUpdate as fnSecretV2BridgeBulkUpdate,
|
||||||
getAllNestedSecretReferences as getAllNestedSecretReferencesV2Bridge
|
getAllSecretReferences as getAllNestedSecretReferencesV2Bridge
|
||||||
} from "@app/services/secret-v2-bridge/secret-v2-bridge-fns";
|
} from "@app/services/secret-v2-bridge/secret-v2-bridge-fns";
|
||||||
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
||||||
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
||||||
@@ -527,7 +527,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretManagerDecryptor({
|
secretManagerDecryptor({
|
||||||
cipherTextBlob: el.encryptedValue
|
cipherTextBlob: el.encryptedValue
|
||||||
}).toString()
|
}).toString()
|
||||||
)
|
).nestedReferences
|
||||||
: [],
|
: [],
|
||||||
type: SecretType.Shared
|
type: SecretType.Shared
|
||||||
})),
|
})),
|
||||||
@@ -551,7 +551,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretManagerDecryptor({
|
secretManagerDecryptor({
|
||||||
cipherTextBlob: el.encryptedValue
|
cipherTextBlob: el.encryptedValue
|
||||||
}).toString()
|
}).toString()
|
||||||
)
|
).nestedReferences
|
||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ import { TSecretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret
|
|||||||
import {
|
import {
|
||||||
fnSecretBulkInsert as fnSecretV2BridgeBulkInsert,
|
fnSecretBulkInsert as fnSecretV2BridgeBulkInsert,
|
||||||
fnSecretBulkUpdate as fnSecretV2BridgeBulkUpdate,
|
fnSecretBulkUpdate as fnSecretV2BridgeBulkUpdate,
|
||||||
getAllNestedSecretReferences,
|
getAllSecretReferences as getAllNestedSecretReferencesV2Bridge,
|
||||||
getAllNestedSecretReferences as getAllNestedSecretReferencesV2Bridge
|
getAllSecretReferences
|
||||||
} from "@app/services/secret-v2-bridge/secret-v2-bridge-fns";
|
} from "@app/services/secret-v2-bridge/secret-v2-bridge-fns";
|
||||||
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
||||||
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
||||||
@@ -416,7 +416,9 @@ export const secretReplicationServiceFactory = ({
|
|||||||
encryptedValue: doc.encryptedValue,
|
encryptedValue: doc.encryptedValue,
|
||||||
encryptedComment: doc.encryptedComment,
|
encryptedComment: doc.encryptedComment,
|
||||||
skipMultilineEncoding: doc.skipMultilineEncoding,
|
skipMultilineEncoding: doc.skipMultilineEncoding,
|
||||||
references: doc.secretValue ? getAllNestedSecretReferencesV2Bridge(doc.secretValue) : []
|
references: doc.secretValue
|
||||||
|
? getAllNestedSecretReferencesV2Bridge(doc.secretValue).nestedReferences
|
||||||
|
: []
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -442,7 +444,9 @@ export const secretReplicationServiceFactory = ({
|
|||||||
encryptedValue: doc.encryptedValue as Buffer,
|
encryptedValue: doc.encryptedValue as Buffer,
|
||||||
encryptedComment: doc.encryptedComment,
|
encryptedComment: doc.encryptedComment,
|
||||||
skipMultilineEncoding: doc.skipMultilineEncoding,
|
skipMultilineEncoding: doc.skipMultilineEncoding,
|
||||||
references: doc.secretValue ? getAllNestedSecretReferencesV2Bridge(doc.secretValue) : []
|
references: doc.secretValue
|
||||||
|
? getAllNestedSecretReferencesV2Bridge(doc.secretValue).nestedReferences
|
||||||
|
: []
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@@ -687,7 +691,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
secretCommentTag: doc.secretCommentTag,
|
secretCommentTag: doc.secretCommentTag,
|
||||||
secretCommentCiphertext: doc.secretCommentCiphertext,
|
secretCommentCiphertext: doc.secretCommentCiphertext,
|
||||||
skipMultilineEncoding: doc.skipMultilineEncoding,
|
skipMultilineEncoding: doc.skipMultilineEncoding,
|
||||||
references: getAllNestedSecretReferences(doc.secretValue)
|
references: getAllSecretReferences(doc.secretValue).nestedReferences
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -723,7 +727,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
secretCommentTag: doc.secretCommentTag,
|
secretCommentTag: doc.secretCommentTag,
|
||||||
secretCommentCiphertext: doc.secretCommentCiphertext,
|
secretCommentCiphertext: doc.secretCommentCiphertext,
|
||||||
skipMultilineEncoding: doc.skipMultilineEncoding,
|
skipMultilineEncoding: doc.skipMultilineEncoding,
|
||||||
references: getAllNestedSecretReferences(doc.secretValue)
|
references: getAllSecretReferences(doc.secretValue).nestedReferences
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -97,8 +97,9 @@ export const secretRotationServiceFactory = ({
|
|||||||
if (!folder) throw new NotFoundError({ message: "Secret path not found" });
|
if (!folder) throw new NotFoundError({ message: "Secret path not found" });
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath, secretName: "", secretTags: [] })
|
||||||
);
|
);
|
||||||
|
|
||||||
const project = await projectDAL.findById(projectId);
|
const project = await projectDAL.findById(projectId);
|
||||||
const shouldUseBridge = project.version === ProjectVersion.V3;
|
const shouldUseBridge = project.version === ProjectVersion.V3;
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path, secretName: "", secretTags: [] })
|
||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
@@ -127,7 +127,7 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path, secretName: "", secretTags: [] })
|
||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
@@ -214,7 +214,9 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: snapshotDetails.environment.slug,
|
environment: snapshotDetails.environment.slug,
|
||||||
secretPath: fullFolderPath
|
secretPath: fullFolderPath,
|
||||||
|
secretName: "",
|
||||||
|
secretTags: []
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
import { AnyAbility, ExtractSubjectType } from "@casl/ability";
|
|
||||||
import { AbilityQuery, rulesToQuery } from "@casl/ability/extra";
|
|
||||||
import { Tables } from "knex/types/tables";
|
|
||||||
|
|
||||||
import { BadRequestError, UnauthorizedError } from "../errors";
|
|
||||||
import { TKnexDynamicOperator } from "../knex/dynamic";
|
|
||||||
|
|
||||||
type TBuildKnexQueryFromCaslDTO<K extends AnyAbility> = {
|
|
||||||
ability: K;
|
|
||||||
subject: ExtractSubjectType<Parameters<K["rulesFor"]>[1]>;
|
|
||||||
action: Parameters<K["rulesFor"]>[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const buildKnexQueryFromCaslOperators = <K extends AnyAbility>({
|
|
||||||
ability,
|
|
||||||
subject,
|
|
||||||
action
|
|
||||||
}: TBuildKnexQueryFromCaslDTO<K>) => {
|
|
||||||
const query = rulesToQuery(ability, action, subject, (rule) => {
|
|
||||||
if (!rule.ast) throw new Error("Ast not defined");
|
|
||||||
return rule.ast;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (query === null) throw new UnauthorizedError({ message: `You don't have permission to do ${action} ${subject}` });
|
|
||||||
return query;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TFieldMapper<T extends keyof Tables> = {
|
|
||||||
[K in T]: `${K}.${Exclude<keyof Tables[K]["base"], symbol>}`;
|
|
||||||
}[T];
|
|
||||||
|
|
||||||
type TFormatCaslFieldsWithTableNames<T extends keyof Tables> = {
|
|
||||||
// handle if any missing operator else throw error let the app break because this is executing again the db
|
|
||||||
missingOperatorCallback?: (operator: string) => void;
|
|
||||||
fieldMapping: (arg: string) => TFieldMapper<T> | null;
|
|
||||||
dynamicQuery: TKnexDynamicOperator;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const formatCaslOperatorFieldsWithTableNames = <T extends keyof Tables>({
|
|
||||||
missingOperatorCallback = (arg) => {
|
|
||||||
throw new BadRequestError({ message: `Unknown permission operator: ${arg}` });
|
|
||||||
},
|
|
||||||
dynamicQuery: dynamicQueryAst,
|
|
||||||
fieldMapping
|
|
||||||
}: TFormatCaslFieldsWithTableNames<T>) => {
|
|
||||||
const stack: [TKnexDynamicOperator, TKnexDynamicOperator | null][] = [[dynamicQueryAst, null]];
|
|
||||||
|
|
||||||
while (stack.length) {
|
|
||||||
const [filterAst, parentAst] = stack.pop()!;
|
|
||||||
|
|
||||||
if (filterAst.operator === "and" || filterAst.operator === "or" || filterAst.operator === "not") {
|
|
||||||
filterAst.value.forEach((el) => {
|
|
||||||
stack.push([el, filterAst]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-continue
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
filterAst.operator === "eq" ||
|
|
||||||
filterAst.operator === "ne" ||
|
|
||||||
filterAst.operator === "in" ||
|
|
||||||
filterAst.operator === "endsWith" ||
|
|
||||||
filterAst.operator === "startsWith"
|
|
||||||
) {
|
|
||||||
const attrPath = fieldMapping(filterAst.field);
|
|
||||||
if (attrPath) {
|
|
||||||
filterAst.field = attrPath;
|
|
||||||
} else if (parentAst && Array.isArray(parentAst.value)) {
|
|
||||||
parentAst.value = parentAst.value.filter((childAst) => childAst !== filterAst) as string[];
|
|
||||||
} else throw new Error("Unknown casl field");
|
|
||||||
// eslint-disable-next-line no-continue
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parentAst && Array.isArray(parentAst.value)) {
|
|
||||||
parentAst.value = parentAst.value.filter((childAst) => childAst !== filterAst) as string[];
|
|
||||||
} else {
|
|
||||||
missingOperatorCallback?.(filterAst.operator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dynamicQueryAst;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const convertCaslOperatorToKnexOperator = <T extends keyof Tables>(
|
|
||||||
caslKnexOperators: AbilityQuery,
|
|
||||||
fieldMapping: (arg: string) => TFieldMapper<T> | null
|
|
||||||
) => {
|
|
||||||
const value = [];
|
|
||||||
if (caslKnexOperators.$and) {
|
|
||||||
value.push({
|
|
||||||
operator: "not" as const,
|
|
||||||
value: caslKnexOperators.$and as TKnexDynamicOperator[]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (caslKnexOperators.$or) {
|
|
||||||
value.push({
|
|
||||||
operator: "or" as const,
|
|
||||||
value: caslKnexOperators.$or as TKnexDynamicOperator[]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatCaslOperatorFieldsWithTableNames({
|
|
||||||
dynamicQuery: {
|
|
||||||
operator: "and",
|
|
||||||
value
|
|
||||||
},
|
|
||||||
fieldMapping
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -2,32 +2,35 @@ import { Knex } from "knex";
|
|||||||
|
|
||||||
import { UnauthorizedError } from "../errors";
|
import { UnauthorizedError } from "../errors";
|
||||||
|
|
||||||
type TKnexDynamicPrimitiveOperator = {
|
type TKnexDynamicPrimitiveOperator<T extends object> = {
|
||||||
operator: "eq" | "ne" | "startsWith" | "endsWith";
|
operator: "eq" | "ne" | "startsWith" | "endsWith";
|
||||||
value: string;
|
value: string;
|
||||||
field: string;
|
field: Extract<keyof T, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TKnexDynamicInOperator = {
|
type TKnexDynamicInOperator<T extends object> = {
|
||||||
operator: "in";
|
operator: "in";
|
||||||
value: string[] | number[];
|
value: string[] | number[];
|
||||||
field: string;
|
field: Extract<keyof T, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TKnexNonGroupOperator = TKnexDynamicInOperator | TKnexDynamicPrimitiveOperator;
|
type TKnexNonGroupOperator<T extends object> = TKnexDynamicInOperator<T> | TKnexDynamicPrimitiveOperator<T>;
|
||||||
|
|
||||||
type TKnexGroupOperator = {
|
type TKnexGroupOperator<T extends object> = {
|
||||||
operator: "and" | "or" | "not";
|
operator: "and" | "or" | "not";
|
||||||
value: (TKnexNonGroupOperator | TKnexGroupOperator)[];
|
value: (TKnexNonGroupOperator<T> | TKnexGroupOperator<T>)[];
|
||||||
};
|
};
|
||||||
|
|
||||||
// akhilmhdh: This is still in pending state and not yet ready. If you want to use it ping me.
|
// akhilmhdh: This is still in pending state and not yet ready. If you want to use it ping me.
|
||||||
// used when you need to write a complex query with the orm
|
// used when you need to write a complex query with the orm
|
||||||
// use it when you need complex or and and condition - most of the time not needed
|
// use it when you need complex or and and condition - most of the time not needed
|
||||||
// majorly used with casl permission to filter data based on permission
|
// majorly used with casl permission to filter data based on permission
|
||||||
export type TKnexDynamicOperator = TKnexGroupOperator | TKnexNonGroupOperator;
|
export type TKnexDynamicOperator<T extends object> = TKnexGroupOperator<T> | TKnexNonGroupOperator<T>;
|
||||||
|
|
||||||
export const buildDynamicKnexQuery = (dynamicQuery: TKnexDynamicOperator, rootQueryBuild: Knex.QueryBuilder) => {
|
export const buildDynamicKnexQuery = <T extends object>(
|
||||||
|
dynamicQuery: TKnexDynamicOperator<T>,
|
||||||
|
rootQueryBuild: Knex.QueryBuilder
|
||||||
|
) => {
|
||||||
const stack = [{ filterAst: dynamicQuery, queryBuilder: rootQueryBuild }];
|
const stack = [{ filterAst: dynamicQuery, queryBuilder: rootQueryBuild }];
|
||||||
|
|
||||||
while (stack.length) {
|
while (stack.length) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Knex } from "knex";
|
|||||||
import { Tables } from "knex/types/tables";
|
import { Tables } from "knex/types/tables";
|
||||||
|
|
||||||
import { DatabaseError } from "../errors";
|
import { DatabaseError } from "../errors";
|
||||||
|
import { buildDynamicKnexQuery, TKnexDynamicOperator } from "./dynamic";
|
||||||
|
|
||||||
export * from "./connection";
|
export * from "./connection";
|
||||||
export * from "./join";
|
export * from "./join";
|
||||||
@@ -20,9 +21,10 @@ export const withTransaction = <K extends object>(db: Knex, dal: K) => ({
|
|||||||
export type TFindFilter<R extends object = object> = Partial<R> & {
|
export type TFindFilter<R extends object = object> = Partial<R> & {
|
||||||
$in?: Partial<{ [k in keyof R]: R[k][] }>;
|
$in?: Partial<{ [k in keyof R]: R[k][] }>;
|
||||||
$search?: Partial<{ [k in keyof R]: R[k] }>;
|
$search?: Partial<{ [k in keyof R]: R[k] }>;
|
||||||
|
$complex?: TKnexDynamicOperator<R>;
|
||||||
};
|
};
|
||||||
export const buildFindFilter =
|
export const buildFindFilter =
|
||||||
<R extends object = object>({ $in, $search, ...filter }: TFindFilter<R>) =>
|
<R extends object = object>({ $in, $search, $complex, ...filter }: TFindFilter<R>) =>
|
||||||
(bd: Knex.QueryBuilder<R, R>) => {
|
(bd: Knex.QueryBuilder<R, R>) => {
|
||||||
void bd.where(filter);
|
void bd.where(filter);
|
||||||
if ($in) {
|
if ($in) {
|
||||||
@@ -39,6 +41,9 @@ export const buildFindFilter =
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if ($complex) {
|
||||||
|
buildDynamicKnexQuery($complex, bd);
|
||||||
|
}
|
||||||
return bd;
|
return bd;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,12 @@ export const integrationServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: sourceEnvironment, secretPath })
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: sourceEnvironment,
|
||||||
|
secretPath,
|
||||||
|
secretName: "",
|
||||||
|
secretTags: []
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(integrationAuth.projectId, sourceEnvironment, secretPath);
|
const folder = await folderDAL.findBySecretPath(integrationAuth.projectId, sourceEnvironment, secretPath);
|
||||||
@@ -162,7 +167,12 @@ export const integrationServiceFactory = ({
|
|||||||
if (environment || secretPath) {
|
if (environment || secretPath) {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: newEnvironment, secretPath: newSecretPath })
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: newEnvironment,
|
||||||
|
secretPath: newSecretPath,
|
||||||
|
secretName: "",
|
||||||
|
secretTags: []
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,14 @@ import { validate as uuidValidate } from "uuid";
|
|||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { SecretsV2Schema, SecretType, TableName, TSecretsV2, TSecretsV2Update } from "@app/db/schemas";
|
import { SecretsV2Schema, SecretType, TableName, TSecretsV2, TSecretsV2Update } from "@app/db/schemas";
|
||||||
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
import {
|
||||||
|
buildFindFilter,
|
||||||
|
ormify,
|
||||||
|
selectAllTableCols,
|
||||||
|
sqlNestRelationships,
|
||||||
|
TFindFilter,
|
||||||
|
TFindOpt
|
||||||
|
} from "@app/lib/knex";
|
||||||
import { OrderByDirection } from "@app/lib/types";
|
import { OrderByDirection } from "@app/lib/types";
|
||||||
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
||||||
|
|
||||||
@@ -13,6 +20,97 @@ export type TSecretV2BridgeDALFactory = ReturnType<typeof secretV2BridgeDALFacto
|
|||||||
export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
||||||
const secretOrm = ormify(db, TableName.SecretV2);
|
const secretOrm = ormify(db, TableName.SecretV2);
|
||||||
|
|
||||||
|
const findOne = async (filter: Partial<TSecretsV2>, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const docs = await (tx || db)(TableName.SecretV2)
|
||||||
|
.where(filter)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SecretV2JnTag,
|
||||||
|
`${TableName.SecretV2}.id`,
|
||||||
|
`${TableName.SecretV2JnTag}.${TableName.SecretV2}Id`
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SecretTag,
|
||||||
|
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
|
||||||
|
`${TableName.SecretTag}.id`
|
||||||
|
)
|
||||||
|
.select(selectAllTableCols(TableName.SecretV2))
|
||||||
|
.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"));
|
||||||
|
|
||||||
|
const data = sqlNestRelationships({
|
||||||
|
data: docs,
|
||||||
|
key: "id",
|
||||||
|
parentMapper: (el) => ({ _id: el.id, ...SecretsV2Schema.parse(el) }),
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "tagId",
|
||||||
|
label: "tags" as const,
|
||||||
|
mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({
|
||||||
|
id,
|
||||||
|
color,
|
||||||
|
slug,
|
||||||
|
name: slug
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
return data?.[0];
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: `${TableName.SecretV2}: FindOne` });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const find = async (filter: TFindFilter<TSecretsV2>, { offset, limit, sort, tx }: TFindOpt<TSecretsV2> = {}) => {
|
||||||
|
try {
|
||||||
|
const query = (tx || db)(TableName.SecretV2)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
.where(buildFindFilter(filter))
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SecretV2JnTag,
|
||||||
|
`${TableName.SecretV2}.id`,
|
||||||
|
`${TableName.SecretV2JnTag}.${TableName.SecretV2}Id`
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SecretTag,
|
||||||
|
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
|
||||||
|
`${TableName.SecretTag}.id`
|
||||||
|
)
|
||||||
|
.select(selectAllTableCols(TableName.SecretV2))
|
||||||
|
.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"));
|
||||||
|
if (limit) void query.limit(limit);
|
||||||
|
if (offset) void query.offset(offset);
|
||||||
|
if (sort) {
|
||||||
|
void query.orderBy(sort.map(([column, order, nulls]) => ({ column: column as string, order, nulls })));
|
||||||
|
}
|
||||||
|
|
||||||
|
const docs = await query;
|
||||||
|
const data = sqlNestRelationships({
|
||||||
|
data: docs,
|
||||||
|
key: "id",
|
||||||
|
parentMapper: (el) => ({ _id: el.id, ...SecretsV2Schema.parse(el) }),
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "tagId",
|
||||||
|
label: "tags" as const,
|
||||||
|
mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({
|
||||||
|
id,
|
||||||
|
color,
|
||||||
|
slug,
|
||||||
|
name: slug
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: `${TableName.SecretV2}: FindOne` });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const update = async (filter: Partial<TSecretsV2>, data: Omit<TSecretsV2Update, "version">, tx?: Knex) => {
|
const update = async (filter: Partial<TSecretsV2>, data: Omit<TSecretsV2Update, "version">, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
const sec = await (tx || db)(TableName.SecretV2)
|
const sec = await (tx || db)(TableName.SecretV2)
|
||||||
@@ -484,6 +582,8 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
upsertSecretReferences,
|
upsertSecretReferences,
|
||||||
findReferencedSecretReferences,
|
findReferencedSecretReferences,
|
||||||
findAllProjectSecretValues,
|
findAllProjectSecretValues,
|
||||||
countByFolderIds
|
countByFolderIds,
|
||||||
|
findOne,
|
||||||
|
find
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,9 +30,10 @@ export const shouldUseSecretV2Bridge = (version: number) => version === 3;
|
|||||||
* // { environment: 'prod', secretPath: '/anotherFolder' }
|
* // { environment: 'prod', secretPath: '/anotherFolder' }
|
||||||
* // ]
|
* // ]
|
||||||
*/
|
*/
|
||||||
export const getAllNestedSecretReferences = (maybeSecretReference: string) => {
|
export const getAllSecretReferences = (maybeSecretReference: string) => {
|
||||||
const references = Array.from(maybeSecretReference.matchAll(INTERPOLATION_SYNTAX_REG), (m) => m[1]);
|
const references = Array.from(maybeSecretReference.matchAll(INTERPOLATION_SYNTAX_REG), (m) => m[1]);
|
||||||
return references
|
|
||||||
|
const nestedReferences = references
|
||||||
.filter((el) => el.includes("."))
|
.filter((el) => el.includes("."))
|
||||||
.map((el) => {
|
.map((el) => {
|
||||||
const [environment, ...secretPathList] = el.split(".");
|
const [environment, ...secretPathList] = el.split(".");
|
||||||
@@ -42,6 +43,8 @@ export const getAllNestedSecretReferences = (maybeSecretReference: string) => {
|
|||||||
secretKey: secretPathList[secretPathList.length - 1]
|
secretKey: secretPathList[secretPathList.length - 1]
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
const localReferences = references.filter((el) => !el.includes("."));
|
||||||
|
return { nestedReferences, localReferences };
|
||||||
};
|
};
|
||||||
|
|
||||||
// these functions are special functions shared by a couple of resources
|
// these functions are special functions shared by a couple of resources
|
||||||
@@ -325,16 +328,13 @@ type TRecursivelyFetchSecretsFromFoldersArg = {
|
|||||||
projectId: string;
|
projectId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
currentPath: string;
|
currentPath: string;
|
||||||
hasAccess: (environment: string, secretPath: string) => boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const recursivelyGetSecretPaths = async ({
|
export const recursivelyGetSecretPaths = async ({
|
||||||
folderDAL,
|
folderDAL,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
projectId,
|
projectId,
|
||||||
environment,
|
environment
|
||||||
currentPath,
|
|
||||||
hasAccess
|
|
||||||
}: TRecursivelyFetchSecretsFromFoldersArg) => {
|
}: TRecursivelyFetchSecretsFromFoldersArg) => {
|
||||||
const env = await projectEnvDAL.findOne({
|
const env = await projectEnvDAL.findOne({
|
||||||
projectId,
|
projectId,
|
||||||
@@ -360,12 +360,7 @@ export const recursivelyGetSecretPaths = async ({
|
|||||||
folderId: p.folderId
|
folderId: p.folderId
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Filter out paths that the user does not have permission to access, and paths that are not in the current path
|
return paths;
|
||||||
const allowedPaths = paths.filter(
|
|
||||||
(folder) => hasAccess(environment, folder.path) && folder.path.startsWith(currentPath === "/" ? "" : currentPath)
|
|
||||||
);
|
|
||||||
|
|
||||||
return allowedPaths;
|
|
||||||
};
|
};
|
||||||
// used to convert multi line ones to quotes ones with \n
|
// used to convert multi line ones to quotes ones with \n
|
||||||
const formatMultiValueEnv = (val?: string) => {
|
const formatMultiValueEnv = (val?: string) => {
|
||||||
@@ -379,7 +374,7 @@ type TInterpolateSecretArg = {
|
|||||||
decryptSecretValue: (encryptedValue?: Buffer | null) => string | undefined;
|
decryptSecretValue: (encryptedValue?: Buffer | null) => string | undefined;
|
||||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "findByFolderId">;
|
secretDAL: Pick<TSecretV2BridgeDALFactory, "findByFolderId">;
|
||||||
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath">;
|
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath">;
|
||||||
canExpandValue: (environment: string, secretPath: string) => boolean;
|
canExpandValue: (environment: string, secretPath: string, secretName: string, secretTagSlugs: string[]) => boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_SECRET_REFERENCE_DEPTH = 10;
|
const MAX_SECRET_REFERENCE_DEPTH = 10;
|
||||||
@@ -390,29 +385,29 @@ export const expandSecretReferencesFactory = ({
|
|||||||
folderDAL,
|
folderDAL,
|
||||||
canExpandValue
|
canExpandValue
|
||||||
}: TInterpolateSecretArg) => {
|
}: TInterpolateSecretArg) => {
|
||||||
const secretCache: Record<string, Record<string, string>> = {};
|
const secretCache: Record<string, Record<string, { value: string; tags: string[] }>> = {};
|
||||||
const getCacheUniqueKey = (environment: string, secretPath: string) => `${environment}-${secretPath}`;
|
const getCacheUniqueKey = (environment: string, secretPath: string) => `${environment}-${secretPath}`;
|
||||||
|
|
||||||
const fetchSecret = async (environment: string, secretPath: string, secretKey: string) => {
|
const fetchSecret = async (environment: string, secretPath: string, secretKey: string) => {
|
||||||
const cacheKey = getCacheUniqueKey(environment, secretPath);
|
const cacheKey = getCacheUniqueKey(environment, secretPath);
|
||||||
|
|
||||||
if (secretCache?.[cacheKey]) {
|
if (secretCache?.[cacheKey]) {
|
||||||
return secretCache[cacheKey][secretKey] || "";
|
return secretCache[cacheKey][secretKey] || { value: "", tags: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||||
if (!folder) return "";
|
if (!folder) return { value: "", tags: [] };
|
||||||
const secrets = await secretDAL.findByFolderId(folder.id);
|
const secrets = await secretDAL.findByFolderId(folder.id);
|
||||||
|
|
||||||
const decryptedSecret = secrets.reduce<Record<string, string>>((prev, secret) => {
|
const decryptedSecret = secrets.reduce<Record<string, { value: string; tags: string[] }>>((prev, secret) => {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
prev[secret.key] = decryptSecret(secret.encryptedValue) || "";
|
prev[secret.key] = { value: decryptSecret(secret.encryptedValue) || "", tags: secret.tags?.map((el) => el.slug) };
|
||||||
return prev;
|
return prev;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
secretCache[cacheKey] = decryptedSecret;
|
secretCache[cacheKey] = decryptedSecret;
|
||||||
|
|
||||||
return secretCache[cacheKey][secretKey] || "";
|
return secretCache[cacheKey][secretKey] || { value: "", tags: [] };
|
||||||
};
|
};
|
||||||
|
|
||||||
const recursivelyExpandSecret = async (dto: { value?: string; secretPath: string; environment: string }) => {
|
const recursivelyExpandSecret = async (dto: { value?: string; secretPath: string; environment: string }) => {
|
||||||
@@ -438,43 +433,43 @@ export const expandSecretReferencesFactory = ({
|
|||||||
if (entities.length === 1) {
|
if (entities.length === 1) {
|
||||||
const [secretKey] = entities;
|
const [secretKey] = entities;
|
||||||
|
|
||||||
if (!canExpandValue(environment, secretPath))
|
// eslint-disable-next-line no-continue,no-await-in-loop
|
||||||
|
const referedValue = await fetchSecret(environment, secretPath, secretKey);
|
||||||
|
if (!canExpandValue(environment, secretPath, secretKey, referedValue.tags))
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
message: `You are attempting to reference secret named ${secretKey} from environment ${environment} in path ${secretPath} which you do not have access to.`
|
message: `You are attempting to reference secret named ${secretKey} from environment ${environment} in path ${secretPath} which you do not have access to.`
|
||||||
});
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line no-continue,no-await-in-loop
|
|
||||||
const referedValue = await fetchSecret(environment, secretPath, secretKey);
|
|
||||||
const cacheKey = getCacheUniqueKey(environment, secretPath);
|
const cacheKey = getCacheUniqueKey(environment, secretPath);
|
||||||
secretCache[cacheKey][secretKey] = referedValue;
|
secretCache[cacheKey][secretKey] = referedValue;
|
||||||
if (INTERPOLATION_SYNTAX_REG.test(referedValue)) {
|
if (INTERPOLATION_SYNTAX_REG.test(referedValue.value)) {
|
||||||
stack.push({
|
stack.push({
|
||||||
value: referedValue,
|
value: referedValue.value,
|
||||||
secretPath,
|
secretPath,
|
||||||
environment,
|
environment,
|
||||||
depth: depth + 1
|
depth: depth + 1
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (referedValue) {
|
if (referedValue) {
|
||||||
expandedValue = expandedValue.replaceAll(interpolationSyntax, referedValue);
|
expandedValue = expandedValue.replaceAll(interpolationSyntax, referedValue.value);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const secretReferenceEnvironment = entities[0];
|
const secretReferenceEnvironment = entities[0];
|
||||||
const secretReferencePath = path.join("/", ...entities.slice(1, entities.length - 1));
|
const secretReferencePath = path.join("/", ...entities.slice(1, entities.length - 1));
|
||||||
const secretReferenceKey = entities[entities.length - 1];
|
const secretReferenceKey = entities[entities.length - 1];
|
||||||
|
|
||||||
if (!canExpandValue(secretReferenceEnvironment, secretReferencePath))
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const referedValue = await fetchSecret(secretReferenceEnvironment, secretReferencePath, secretReferenceKey);
|
||||||
|
if (!canExpandValue(secretReferenceEnvironment, secretReferencePath, secretReferenceKey, referedValue.tags))
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
message: `You are attempting to reference secret named ${secretReferenceKey} from environment ${secretReferenceEnvironment} in path ${secretReferencePath} which you do not have access to.`
|
message: `You are attempting to reference secret named ${secretReferenceKey} from environment ${secretReferenceEnvironment} in path ${secretReferencePath} which you do not have access to.`
|
||||||
});
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
const referedValue = await fetchSecret(secretReferenceEnvironment, secretReferencePath, secretReferenceKey);
|
|
||||||
const cacheKey = getCacheUniqueKey(secretReferenceEnvironment, secretReferencePath);
|
const cacheKey = getCacheUniqueKey(secretReferenceEnvironment, secretReferencePath);
|
||||||
secretCache[cacheKey][secretReferenceKey] = referedValue;
|
secretCache[cacheKey][secretReferenceKey] = referedValue;
|
||||||
if (INTERPOLATION_SYNTAX_REG.test(referedValue)) {
|
if (INTERPOLATION_SYNTAX_REG.test(referedValue.value)) {
|
||||||
stack.push({
|
stack.push({
|
||||||
value: referedValue,
|
value: referedValue.value,
|
||||||
secretPath: secretReferencePath,
|
secretPath: secretReferencePath,
|
||||||
environment: secretReferenceEnvironment,
|
environment: secretReferenceEnvironment,
|
||||||
depth: depth + 1
|
depth: depth + 1
|
||||||
@@ -482,7 +477,7 @@ export const expandSecretReferencesFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (referedValue) {
|
if (referedValue) {
|
||||||
expandedValue = expandedValue.replaceAll(interpolationSyntax, referedValue);
|
expandedValue = expandedValue.replaceAll(interpolationSyntax, referedValue.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, PureAbility, subject } from "@casl/ability";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectMembershipRole, SecretsV2Schema, SecretType } from "@app/db/schemas";
|
import { ProjectMembershipRole, SecretsV2Schema, SecretType } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
@@ -28,7 +29,7 @@ import {
|
|||||||
fnSecretBulkDelete,
|
fnSecretBulkDelete,
|
||||||
fnSecretBulkInsert,
|
fnSecretBulkInsert,
|
||||||
fnSecretBulkUpdate,
|
fnSecretBulkUpdate,
|
||||||
getAllNestedSecretReferences,
|
getAllSecretReferences,
|
||||||
recursivelyGetSecretPaths,
|
recursivelyGetSecretPaths,
|
||||||
reshapeBridgeSecret
|
reshapeBridgeSecret
|
||||||
} from "./secret-v2-bridge-fns";
|
} from "./secret-v2-bridge-fns";
|
||||||
@@ -43,6 +44,7 @@ import {
|
|||||||
TGetSecretsDTO,
|
TGetSecretsDTO,
|
||||||
TGetSecretVersionsDTO,
|
TGetSecretVersionsDTO,
|
||||||
TMoveSecretsDTO,
|
TMoveSecretsDTO,
|
||||||
|
TSecretReference,
|
||||||
TUpdateManySecretDTO,
|
TUpdateManySecretDTO,
|
||||||
TUpdateSecretDTO
|
TUpdateSecretDTO
|
||||||
} from "./secret-v2-bridge-types";
|
} from "./secret-v2-bridge-types";
|
||||||
@@ -56,7 +58,7 @@ type TSecretV2BridgeServiceFactoryDep = {
|
|||||||
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||||
secretTagDAL: TSecretTagDALFactory;
|
secretTagDAL: TSecretTagDALFactory;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne" | "findBySlugs">;
|
||||||
folderDAL: Pick<
|
folderDAL: Pick<
|
||||||
TSecretFolderDALFactory,
|
TSecretFolderDALFactory,
|
||||||
"findBySecretPath" | "updateById" | "findById" | "findByManySecretPath" | "find" | "findBySecretPathMultiEnv"
|
"findBySecretPath" | "updateById" | "findById" | "findByManySecretPath" | "find" | "findBySecretPathMultiEnv"
|
||||||
@@ -93,6 +95,70 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
secretApprovalRequestSecretDAL,
|
secretApprovalRequestSecretDAL,
|
||||||
kmsService
|
kmsService
|
||||||
}: TSecretV2BridgeServiceFactoryDep) => {
|
}: TSecretV2BridgeServiceFactoryDep) => {
|
||||||
|
const $validateSecretReferences = async (
|
||||||
|
projectId: string,
|
||||||
|
permission: PureAbility,
|
||||||
|
references: ReturnType<typeof getAllSecretReferences>["nestedReferences"]
|
||||||
|
) => {
|
||||||
|
if (!references.length) return;
|
||||||
|
|
||||||
|
const uniqueReferenceEnvironmentSlugs = Array.from(new Set(references.map((el) => el.environment)));
|
||||||
|
const referencesEnvironments = await projectEnvDAL.findBySlugs(projectId, uniqueReferenceEnvironmentSlugs);
|
||||||
|
if (referencesEnvironments.length !== uniqueReferenceEnvironmentSlugs.length)
|
||||||
|
throw new BadRequestError({ message: "Referred environment not found" });
|
||||||
|
|
||||||
|
const referencesEnvironmentGroupBySlug = groupBy(referencesEnvironments, (i) => i.slug);
|
||||||
|
const referredFolders = await folderDAL.findByManySecretPath(
|
||||||
|
references.map((el) => ({
|
||||||
|
secretPath: el.secretPath,
|
||||||
|
envId: referencesEnvironmentGroupBySlug[el.environment][0].id
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
const referencesFolderGroupByPath = groupBy(referredFolders.filter(Boolean), (i) => i?.path as string);
|
||||||
|
const referredSecrets = await secretDAL.find({
|
||||||
|
$complex: {
|
||||||
|
operator: "or",
|
||||||
|
value: references.map((el) => {
|
||||||
|
const folderId = referencesFolderGroupByPath[el.secretPath][0]?.id;
|
||||||
|
if (!folderId) throw new BadRequestError({ message: `Reference path ${el.secretPath} doesn't exist` });
|
||||||
|
|
||||||
|
return {
|
||||||
|
operator: "and",
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
operator: "eq",
|
||||||
|
field: "folderId",
|
||||||
|
value: folderId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operator: "eq",
|
||||||
|
field: "key",
|
||||||
|
value: el.secretKey
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (referredSecrets.length !== references.length)
|
||||||
|
throw new BadRequestError({ message: "Reference secret not found" });
|
||||||
|
|
||||||
|
const referredSecretsGroupBySecretKey = groupBy(referredSecrets, (i) => i.key);
|
||||||
|
references.forEach((el) => {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: el.environment,
|
||||||
|
secretPath: el.secretPath,
|
||||||
|
secretName: el.secretKey,
|
||||||
|
tags: referredSecretsGroupBySecretKey[el.secretKey][0]?.tags?.map((i) => i.slug)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return referredSecrets;
|
||||||
|
};
|
||||||
|
|
||||||
const createSecret = async ({
|
const createSecret = async ({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -110,10 +176,6 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Create,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
|
||||||
);
|
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
@@ -134,6 +196,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
if (inputSecret.type === SecretType.Shared && doesSecretExist)
|
if (inputSecret.type === SecretType.Shared && doesSecretExist)
|
||||||
throw new BadRequestError({ message: "Secret already exist" });
|
throw new BadRequestError({ message: "Secret already exist" });
|
||||||
|
|
||||||
// if user creating personal check its shared also exist
|
// if user creating personal check its shared also exist
|
||||||
if (inputSecret.type === SecretType.Personal && !doesSecretExist) {
|
if (inputSecret.type === SecretType.Personal && !doesSecretExist) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
@@ -146,22 +209,28 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
const tags = inputSecret.tagIds ? await secretTagDAL.find({ projectId, $in: { id: inputSecret.tagIds } }) : [];
|
const tags = inputSecret.tagIds ? await secretTagDAL.find({ projectId, $in: { id: inputSecret.tagIds } }) : [];
|
||||||
if ((inputSecret.tagIds || []).length !== tags.length) throw new NotFoundError({ message: "Tag not found" });
|
if ((inputSecret.tagIds || []).length !== tags.length) throw new NotFoundError({ message: "Tag not found" });
|
||||||
|
|
||||||
const { secretName, type, ...el } = inputSecret;
|
const { secretName, type, ...inputSecretData } = inputSecret;
|
||||||
const references = getAllNestedSecretReferences(inputSecret.secretValue);
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secretName,
|
||||||
|
secretTags: tags?.map((el) => el.slug)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const { nestedReferences, localReferences } = getAllSecretReferences(inputSecret.secretValue);
|
||||||
|
const allSecretReferences = nestedReferences.concat(
|
||||||
|
localReferences.map((el) => ({ secretKey: el, secretPath, environment }))
|
||||||
|
);
|
||||||
|
await $validateSecretReferences(projectId, permission, allSecretReferences);
|
||||||
|
|
||||||
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
|
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.SecretManager,
|
type: KmsDataKey.SecretManager,
|
||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
references.forEach((referredSecret) => {
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment: referredSecret.environment,
|
|
||||||
secretPath: referredSecret.secretPath
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const secret = await secretDAL.transaction((tx) =>
|
const secret = await secretDAL.transaction((tx) =>
|
||||||
fnSecretBulkInsert({
|
fnSecretBulkInsert({
|
||||||
folderId,
|
folderId,
|
||||||
@@ -169,20 +238,20 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
{
|
{
|
||||||
version: 1,
|
version: 1,
|
||||||
type,
|
type,
|
||||||
reminderRepeatDays: el.secretReminderRepeatDays,
|
reminderRepeatDays: inputSecretData.secretReminderRepeatDays,
|
||||||
encryptedComment: setKnexStringValue(
|
encryptedComment: setKnexStringValue(
|
||||||
el.secretComment,
|
inputSecretData.secretComment,
|
||||||
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob
|
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob
|
||||||
),
|
),
|
||||||
encryptedValue: el.secretValue
|
encryptedValue: inputSecretData.secretValue
|
||||||
? secretManagerEncryptor({ plainText: Buffer.from(el.secretValue) }).cipherTextBlob
|
? secretManagerEncryptor({ plainText: Buffer.from(inputSecretData.secretValue) }).cipherTextBlob
|
||||||
: undefined,
|
: undefined,
|
||||||
reminderNote: el.secretReminderNote,
|
reminderNote: inputSecretData.secretReminderNote,
|
||||||
skipMultilineEncoding: el.skipMultilineEncoding,
|
skipMultilineEncoding: inputSecretData.skipMultilineEncoding,
|
||||||
key: secretName,
|
key: secretName,
|
||||||
userId: inputSecret.type === SecretType.Personal ? actorId : null,
|
userId: inputSecret.type === SecretType.Personal ? actorId : null,
|
||||||
tagIds: inputSecret.tagIds,
|
tagIds: inputSecret.tagIds,
|
||||||
references
|
references: nestedReferences
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
secretDAL,
|
secretDAL,
|
||||||
@@ -228,10 +297,6 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Edit,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
|
||||||
);
|
|
||||||
|
|
||||||
if (inputSecret.newSecretName === "") {
|
if (inputSecret.newSecretName === "") {
|
||||||
throw new BadRequestError({ message: "New secret name cannot be empty" });
|
throw new BadRequestError({ message: "New secret name cannot be empty" });
|
||||||
@@ -276,6 +341,21 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
secret = sharedSecretToModify;
|
secret = sharedSecretToModify;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secretName: inputSecret.secretName,
|
||||||
|
secretTags: secret.tags.map((el) => el.slug)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// validate tags
|
||||||
|
// fetch all tags and if not same count throw error meaning one was invalid tags
|
||||||
|
const tags = inputSecret.tagIds ? await secretTagDAL.find({ projectId, $in: { id: inputSecret.tagIds } }) : [];
|
||||||
|
if ((inputSecret.tagIds || []).length !== tags.length) throw new NotFoundError({ message: "Tag not found" });
|
||||||
|
|
||||||
if (inputSecret.newSecretName) {
|
if (inputSecret.newSecretName) {
|
||||||
const doesNewNameSecretExist = await secretDAL.findOne({
|
const doesNewNameSecretExist = await secretDAL.findOne({
|
||||||
key: inputSecret.newSecretName,
|
key: inputSecret.newSecretName,
|
||||||
@@ -283,36 +363,34 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
folderId
|
folderId
|
||||||
});
|
});
|
||||||
if (doesNewNameSecretExist) throw new BadRequestError({ message: "Secret with the new name already exist" });
|
if (doesNewNameSecretExist) throw new BadRequestError({ message: "Secret with the new name already exist" });
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secretName: inputSecret.secretName,
|
||||||
|
secretTags: tags?.map((el) => el.slug)
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate tags
|
|
||||||
// fetch all tags and if not same count throw error meaning one was invalid tags
|
|
||||||
const tags = inputSecret.tagIds ? await secretTagDAL.find({ projectId, $in: { id: inputSecret.tagIds } }) : [];
|
|
||||||
if ((inputSecret.tagIds || []).length !== tags.length) throw new NotFoundError({ message: "Tag not found" });
|
|
||||||
|
|
||||||
const { secretName, secretValue } = inputSecret;
|
const { secretName, secretValue } = inputSecret;
|
||||||
|
|
||||||
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
|
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.SecretManager,
|
type: KmsDataKey.SecretManager,
|
||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
const encryptedValue =
|
const encryptedValue = secretValue
|
||||||
typeof secretValue !== "undefined"
|
? secretManagerEncryptor({ plainText: Buffer.from(secretValue) }).cipherTextBlob
|
||||||
? {
|
: undefined;
|
||||||
encryptedValue: secretManagerEncryptor({ plainText: Buffer.from(secretValue) }).cipherTextBlob,
|
const secretReferences = secretValue ? getAllSecretReferences(secretValue) : undefined;
|
||||||
references: getAllNestedSecretReferences(secretValue)
|
|
||||||
}
|
if (secretReferences) {
|
||||||
: {};
|
const { nestedReferences, localReferences } = secretReferences;
|
||||||
if (encryptedValue.references) {
|
const allSecretReferences = nestedReferences.concat(
|
||||||
encryptedValue.references.forEach((referredSecret) => {
|
localReferences.map((el) => ({ secretKey: el, secretPath, environment }))
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
);
|
||||||
ProjectPermissionActions.Read,
|
await $validateSecretReferences(projectId, permission, allSecretReferences);
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment: referredSecret.environment,
|
|
||||||
secretPath: referredSecret.secretPath
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedSecret = await secretDAL.transaction(async (tx) =>
|
const updatedSecret = await secretDAL.transaction(async (tx) =>
|
||||||
@@ -331,7 +409,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
skipMultilineEncoding: inputSecret.skipMultilineEncoding,
|
skipMultilineEncoding: inputSecret.skipMultilineEncoding,
|
||||||
key: inputSecret.newSecretName || secretName,
|
key: inputSecret.newSecretName || secretName,
|
||||||
tags: inputSecret.tagIds,
|
tags: inputSecret.tagIds,
|
||||||
...encryptedValue
|
...(encryptedValue ? { encryptedValue, references: secretReferences?.nestedReferences || [] } : {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -386,10 +464,6 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Delete,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
|
||||||
);
|
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
@@ -414,6 +488,15 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
if (!secretToDelete) throw new NotFoundError({ message: "Secret not found" });
|
if (!secretToDelete) throw new NotFoundError({ message: "Secret not found" });
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secretName: secretToDelete.key,
|
||||||
|
secretTags: secretToDelete.tags?.map((el) => el.slug)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const deletedSecret = await secretDAL.transaction(async (tx) =>
|
const deletedSecret = await secretDAL.transaction(async (tx) =>
|
||||||
fnSecretBulkDelete({
|
fnSecretBulkDelete({
|
||||||
@@ -482,13 +565,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
// verify user has access to all environments
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
||||||
environments.forEach((environment) =>
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environments, path);
|
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environments, path);
|
||||||
@@ -534,10 +611,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
|
||||||
);
|
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
if (!folder) return 0;
|
if (!folder) return 0;
|
||||||
@@ -562,22 +636,15 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
environments: string[];
|
environments: string[];
|
||||||
isInternal?: boolean;
|
isInternal?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
if (!isInternal) {
|
if (!isInternal) {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId
|
|
||||||
);
|
|
||||||
|
|
||||||
// verify user has access to all environments
|
|
||||||
environments.forEach((environment) =>
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let paths: { folderId: string; path: string; environment: string }[] = [];
|
let paths: { folderId: string; path: string; environment: string }[] = [];
|
||||||
@@ -645,6 +712,8 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
||||||
|
|
||||||
let paths: { folderId: string; path: string }[] = [];
|
let paths: { folderId: string; path: string }[] = [];
|
||||||
|
|
||||||
if (recursive) {
|
if (recursive) {
|
||||||
@@ -653,26 +722,13 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
projectId,
|
projectId,
|
||||||
environment,
|
environment,
|
||||||
currentPath: path,
|
currentPath: path
|
||||||
hasAccess: (permissionEnvironment, permissionSecretPath) =>
|
|
||||||
permission.can(
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment: permissionEnvironment,
|
|
||||||
secretPath: permissionSecretPath
|
|
||||||
})
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!deepPaths) return { secrets: [], imports: [] };
|
if (!deepPaths) return { secrets: [], imports: [] };
|
||||||
|
|
||||||
paths = deepPaths.map(({ folderId, path: p }) => ({ folderId, path: p }));
|
paths = deepPaths.map(({ folderId, path: p }) => ({ folderId, path: p }));
|
||||||
} else {
|
} else {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
|
||||||
);
|
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
if (!folder) return { secrets: [], imports: [] };
|
if (!folder) return { secrets: [], imports: [] };
|
||||||
|
|
||||||
@@ -693,27 +749,44 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
const decryptedSecrets = secrets.map((secret) =>
|
const decryptedSecrets = secrets
|
||||||
reshapeBridgeSecret(projectId, environment, groupedPaths[secret.folderId][0].path, {
|
.filter((el) =>
|
||||||
...secret,
|
permission.can(
|
||||||
value: secret.encryptedValue
|
ProjectPermissionActions.Read,
|
||||||
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString()
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
: "",
|
environment,
|
||||||
comment: secret.encryptedComment
|
secretPath: groupedPaths[el.folderId][0].path,
|
||||||
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedComment }).toString()
|
secretName: el.key,
|
||||||
: ""
|
secretTags: el.tags.map((i) => i.slug)
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
)
|
||||||
|
.map((secret) =>
|
||||||
|
reshapeBridgeSecret(projectId, environment, groupedPaths[secret.folderId][0].path, {
|
||||||
|
...secret,
|
||||||
|
value: secret.encryptedValue
|
||||||
|
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString()
|
||||||
|
: "",
|
||||||
|
comment: secret.encryptedComment
|
||||||
|
? secretManagerDecryptor({ cipherTextBlob: secret.encryptedComment }).toString()
|
||||||
|
: ""
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const expandSecretReferences = expandSecretReferencesFactory({
|
const expandSecretReferences = expandSecretReferencesFactory({
|
||||||
projectId,
|
projectId,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
secretDAL,
|
secretDAL,
|
||||||
decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined),
|
decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined),
|
||||||
canExpandValue: (expandEnvironment, expandSecretPath) =>
|
canExpandValue: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) =>
|
||||||
permission.can(
|
permission.can(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: expandEnvironment, secretPath: expandSecretPath })
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: expandEnvironment,
|
||||||
|
secretPath: expandSecretPath,
|
||||||
|
secretName: expandSecretKey,
|
||||||
|
secretTags: expandSecretTags
|
||||||
|
})
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -744,19 +817,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const secretImports = await secretImportDAL.findByFolderIds(paths.map((p) => p.folderId));
|
const secretImports = await secretImportDAL.findByFolderIds(paths.map((p) => p.folderId));
|
||||||
const allowedImports = secretImports.filter(({ importEnv, importPath, isReplication }) =>
|
const allowedImports = secretImports.filter(({ isReplication }) => !isReplication);
|
||||||
!isReplication &&
|
|
||||||
// if its service token allow full access over imported one
|
|
||||||
actor === ActorType.SERVICE
|
|
||||||
? true
|
|
||||||
: permission.can(
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment: importEnv.slug,
|
|
||||||
secretPath: importPath
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const importedSecrets = await fnSecretsV2FromImports({
|
const importedSecrets = await fnSecretsV2FromImports({
|
||||||
allowedImports,
|
allowedImports,
|
||||||
secretDAL,
|
secretDAL,
|
||||||
@@ -793,10 +854,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
|
||||||
);
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
@@ -832,17 +890,43 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
userId: secretType === SecretType.Personal ? actorId : null,
|
userId: secretType === SecretType.Personal ? actorId : null,
|
||||||
key: secretName
|
key: secretName
|
||||||
})
|
})
|
||||||
.then((el) => SecretsV2Schema.parse({ ...el, id: el.secretId })));
|
.then((el) =>
|
||||||
|
SecretsV2Schema.extend({
|
||||||
|
tags: z
|
||||||
|
.object({ slug: z.string(), name: z.string(), id: z.string(), color: z.string() })
|
||||||
|
.array()
|
||||||
|
.default([])
|
||||||
|
.optional()
|
||||||
|
}).parse({
|
||||||
|
...el,
|
||||||
|
id: el.secretId
|
||||||
|
})
|
||||||
|
));
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath: path,
|
||||||
|
secretName,
|
||||||
|
secretTags: (secret?.tags || []).map((el) => el.slug)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const expandSecretReferences = expandSecretReferencesFactory({
|
const expandSecretReferences = expandSecretReferencesFactory({
|
||||||
projectId,
|
projectId,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
secretDAL,
|
secretDAL,
|
||||||
decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined),
|
decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined),
|
||||||
canExpandValue: (expandEnvironment, expandSecretPath) =>
|
canExpandValue: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) =>
|
||||||
permission.can(
|
permission.can(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: expandEnvironment, secretPath: expandSecretPath })
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: expandEnvironment,
|
||||||
|
secretPath: expandSecretPath,
|
||||||
|
secretName: expandSecretKey,
|
||||||
|
secretTags: expandSecretTags
|
||||||
|
})
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -851,20 +935,8 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
// here we consider the import order also thus starting from bottom
|
// here we consider the import order also thus starting from bottom
|
||||||
if (!secret && includeImports) {
|
if (!secret && includeImports) {
|
||||||
const secretImports = await secretImportDAL.find({ folderId, isReplication: false });
|
const secretImports = await secretImportDAL.find({ folderId, isReplication: false });
|
||||||
const allowedImports = secretImports.filter(({ importEnv, importPath }) =>
|
|
||||||
// if its service token allow full access over imported one
|
|
||||||
actor === ActorType.SERVICE
|
|
||||||
? true
|
|
||||||
: permission.can(
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment: importEnv.slug,
|
|
||||||
secretPath: importPath
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const importedSecrets = await fnSecretsV2FromImports({
|
const importedSecrets = await fnSecretsV2FromImports({
|
||||||
allowedImports,
|
allowedImports: secretImports,
|
||||||
secretDAL,
|
secretDAL,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
@@ -927,10 +999,6 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Create,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
|
||||||
);
|
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
@@ -940,13 +1008,32 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
const folderId = folder.id;
|
const folderId = folder.id;
|
||||||
|
|
||||||
const secrets = await secretDAL.findBySecretKeys(
|
const secrets = await secretDAL.find({
|
||||||
folderId,
|
folderId,
|
||||||
inputSecrets.map((el) => ({
|
$complex: {
|
||||||
key: el.secretKey,
|
operator: "and",
|
||||||
type: SecretType.Shared
|
value: [
|
||||||
}))
|
{
|
||||||
);
|
operator: "or",
|
||||||
|
value: inputSecrets.map((el) => ({
|
||||||
|
operator: "and",
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
operator: "eq",
|
||||||
|
field: "key",
|
||||||
|
value: el.secretKey
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operator: "eq",
|
||||||
|
field: "type",
|
||||||
|
value: SecretType.Shared
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
if (secrets.length)
|
if (secrets.length)
|
||||||
throw new BadRequestError({ message: `Secret already exist: ${secrets.map((el) => el.key).join(",")}` });
|
throw new BadRequestError({ message: `Secret already exist: ${secrets.map((el) => el.key).join(",")}` });
|
||||||
|
|
||||||
@@ -954,6 +1041,34 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
const sanitizedTagIds = inputSecrets.flatMap(({ tagIds = [] }) => tagIds);
|
const sanitizedTagIds = inputSecrets.flatMap(({ tagIds = [] }) => tagIds);
|
||||||
const tags = sanitizedTagIds.length ? await secretTagDAL.findManyTagsById(projectId, sanitizedTagIds) : [];
|
const tags = sanitizedTagIds.length ? await secretTagDAL.findManyTagsById(projectId, sanitizedTagIds) : [];
|
||||||
if (tags.length !== sanitizedTagIds.length) throw new NotFoundError({ message: "Tag not found" });
|
if (tags.length !== sanitizedTagIds.length) throw new NotFoundError({ message: "Tag not found" });
|
||||||
|
const tagsGroupByID = groupBy(tags, (i) => i.id);
|
||||||
|
|
||||||
|
inputSecrets.forEach((el) => {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secretName: el.secretKey,
|
||||||
|
secretTags: (el.tagIds || []).map((i) => tagsGroupByID[i][0].slug)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// now get all secret references made and validate the permission
|
||||||
|
const secretReferencesGroupByInputSecretKey: Record<string, ReturnType<typeof getAllSecretReferences>> = {};
|
||||||
|
const secretReferences: TSecretReference[] = [];
|
||||||
|
inputSecrets.forEach((el) => {
|
||||||
|
if (el.secretValue) {
|
||||||
|
const references = getAllSecretReferences(el.secretValue);
|
||||||
|
secretReferencesGroupByInputSecretKey[el.secretKey] = references;
|
||||||
|
secretReferences.push(...references.nestedReferences);
|
||||||
|
references.localReferences.forEach((localRefKey) => {
|
||||||
|
secretReferences.push({ secretKey: localRefKey, secretPath, environment });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await $validateSecretReferences(projectId, permission, secretReferences);
|
||||||
|
|
||||||
const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } =
|
const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } =
|
||||||
await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.SecretManager, projectId });
|
await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.SecretManager, projectId });
|
||||||
@@ -961,16 +1076,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
const newSecrets = await secretDAL.transaction(async (tx) =>
|
const newSecrets = await secretDAL.transaction(async (tx) =>
|
||||||
fnSecretBulkInsert({
|
fnSecretBulkInsert({
|
||||||
inputSecrets: inputSecrets.map((el) => {
|
inputSecrets: inputSecrets.map((el) => {
|
||||||
const references = getAllNestedSecretReferences(el.secretValue);
|
const references = secretReferencesGroupByInputSecretKey[el.secretKey].nestedReferences;
|
||||||
references.forEach((referredSecret) => {
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment: referredSecret.environment,
|
|
||||||
secretPath: referredSecret.secretPath
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
version: 1,
|
version: 1,
|
||||||
@@ -1032,10 +1138,6 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Edit,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
|
||||||
);
|
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
@@ -1045,38 +1147,115 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
const folderId = folder.id;
|
const folderId = folder.id;
|
||||||
|
|
||||||
const secretsToUpdate = await secretDAL.findBySecretKeys(
|
const secretsToUpdate = await secretDAL.find({
|
||||||
folderId,
|
folderId,
|
||||||
inputSecrets.map((el) => ({
|
$complex: {
|
||||||
key: el.secretKey,
|
operator: "and",
|
||||||
type: SecretType.Shared
|
value: [
|
||||||
}))
|
{
|
||||||
);
|
operator: "or",
|
||||||
|
value: inputSecrets.map((el) => ({
|
||||||
|
operator: "and",
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
operator: "eq",
|
||||||
|
field: "key",
|
||||||
|
value: el.secretKey
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operator: "eq",
|
||||||
|
field: "type",
|
||||||
|
value: SecretType.Shared
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
if (secretsToUpdate.length !== inputSecrets.length)
|
if (secretsToUpdate.length !== inputSecrets.length)
|
||||||
throw new NotFoundError({ message: `Secret does not exist: ${secretsToUpdate.map((el) => el.key).join(",")}` });
|
throw new NotFoundError({ message: `Secret does not exist: ${secretsToUpdate.map((el) => el.key).join(",")}` });
|
||||||
const secretsToUpdateInDBGroupedByKey = groupBy(secretsToUpdate, (i) => i.key);
|
const secretsToUpdateInDBGroupedByKey = groupBy(secretsToUpdate, (i) => i.key);
|
||||||
|
|
||||||
// now find any secret that needs to update its name
|
secretsToUpdate.forEach((el) => {
|
||||||
// same process as above
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
const secretsWithNewName = inputSecrets.filter(({ newSecretName }) => Boolean(newSecretName));
|
ProjectPermissionActions.Edit,
|
||||||
if (secretsWithNewName.length) {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
const secrets = await secretDAL.findBySecretKeys(
|
environment,
|
||||||
folderId,
|
secretPath,
|
||||||
secretsWithNewName.map((el) => ({
|
secretName: el.key,
|
||||||
key: el.newSecretName as string,
|
secretTags: el.tags.map((i) => i.slug)
|
||||||
type: SecretType.Shared
|
})
|
||||||
}))
|
|
||||||
);
|
);
|
||||||
if (secrets.length)
|
});
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Secret with new name already exists: ${secretsWithNewName.map((el) => el.newSecretName).join(",")}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// get all tags
|
// get all tags
|
||||||
const sanitizedTagIds = inputSecrets.flatMap(({ tagIds = [] }) => tagIds);
|
const sanitizedTagIds = inputSecrets.flatMap(({ tagIds = [] }) => tagIds);
|
||||||
const tags = sanitizedTagIds.length ? await secretTagDAL.findManyTagsById(projectId, sanitizedTagIds) : [];
|
const tags = sanitizedTagIds.length ? await secretTagDAL.findManyTagsById(projectId, sanitizedTagIds) : [];
|
||||||
if (tags.length !== sanitizedTagIds.length) throw new NotFoundError({ message: "Tag not found" });
|
if (tags.length !== sanitizedTagIds.length) throw new NotFoundError({ message: "Tag not found" });
|
||||||
|
const tagsGroupByID = groupBy(tags, (i) => i.id);
|
||||||
|
|
||||||
|
// now find any secret that needs to update its name
|
||||||
|
// same process as above
|
||||||
|
const secretsWithNewName = inputSecrets.filter(({ newSecretName }) => Boolean(newSecretName));
|
||||||
|
if (secretsWithNewName.length) {
|
||||||
|
const secrets = await secretDAL.find({
|
||||||
|
folderId,
|
||||||
|
$complex: {
|
||||||
|
operator: "and",
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
operator: "or",
|
||||||
|
value: secretsWithNewName.map((el) => ({
|
||||||
|
operator: "and",
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
operator: "eq",
|
||||||
|
field: "key",
|
||||||
|
value: el.secretKey
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operator: "eq",
|
||||||
|
field: "type",
|
||||||
|
value: SecretType.Shared
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (secrets.length)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Secret with new name already exists: ${secretsWithNewName.map((el) => el.newSecretName).join(",")}`
|
||||||
|
});
|
||||||
|
|
||||||
|
secretsWithNewName.forEach((el) => {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secretName: el.newSecretName as string,
|
||||||
|
secretTags: (el.tagIds || []).map((i) => tagsGroupByID[i][0].slug)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// now get all secret references made and validate the permission
|
||||||
|
const secretReferencesGroupByInputSecretKey: Record<string, ReturnType<typeof getAllSecretReferences>> = {};
|
||||||
|
const secretReferences: TSecretReference[] = [];
|
||||||
|
inputSecrets.forEach((el) => {
|
||||||
|
if (el.secretValue) {
|
||||||
|
const references = getAllSecretReferences(el.secretValue);
|
||||||
|
secretReferencesGroupByInputSecretKey[el.secretKey] = references;
|
||||||
|
secretReferences.push(...references.nestedReferences);
|
||||||
|
references.localReferences.forEach((localRefKey) => {
|
||||||
|
secretReferences.push({ secretKey: localRefKey, secretPath, environment });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await $validateSecretReferences(projectId, permission, secretReferences);
|
||||||
|
|
||||||
const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } =
|
const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } =
|
||||||
await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.SecretManager, projectId });
|
await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.SecretManager, projectId });
|
||||||
@@ -1091,22 +1270,10 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
typeof el.secretValue !== "undefined"
|
typeof el.secretValue !== "undefined"
|
||||||
? {
|
? {
|
||||||
encryptedValue: secretManagerEncryptor({ plainText: Buffer.from(el.secretValue) }).cipherTextBlob,
|
encryptedValue: secretManagerEncryptor({ plainText: Buffer.from(el.secretValue) }).cipherTextBlob,
|
||||||
references: getAllNestedSecretReferences(el.secretValue)
|
references: secretReferencesGroupByInputSecretKey[el.secretKey].nestedReferences
|
||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
if (encryptedValue.references) {
|
|
||||||
encryptedValue.references.forEach((referredSecret) => {
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment: referredSecret.environment,
|
|
||||||
secretPath: referredSecret.secretPath
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filter: { id: originalSecret.id, type: SecretType.Shared },
|
filter: { id: originalSecret.id, type: SecretType.Shared },
|
||||||
data: {
|
data: {
|
||||||
@@ -1164,10 +1331,6 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Delete,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
|
||||||
);
|
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
@@ -1177,17 +1340,47 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
const folderId = folder.id;
|
const folderId = folder.id;
|
||||||
|
|
||||||
const secretsToDelete = await secretDAL.findBySecretKeys(
|
const secretsToDelete = await secretDAL.find({
|
||||||
folderId,
|
folderId,
|
||||||
inputSecrets.map((el) => ({
|
$complex: {
|
||||||
key: el.secretKey,
|
operator: "and",
|
||||||
type: SecretType.Shared
|
value: [
|
||||||
}))
|
{
|
||||||
);
|
operator: "or",
|
||||||
|
value: inputSecrets.map((el) => ({
|
||||||
|
operator: "and",
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
operator: "eq",
|
||||||
|
field: "key",
|
||||||
|
value: el.secretKey
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operator: "eq",
|
||||||
|
field: "type",
|
||||||
|
value: SecretType.Shared
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
if (secretsToDelete.length !== inputSecrets.length)
|
if (secretsToDelete.length !== inputSecrets.length)
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: `One or more secrets does not exist: ${secretsToDelete.map((el) => el.key).join(",")}`
|
message: `One or more secrets does not exist: ${secretsToDelete.map((el) => el.key).join(",")}`
|
||||||
});
|
});
|
||||||
|
secretsToDelete.forEach((el) => {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Delete,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secretName: el.key,
|
||||||
|
secretTags: el.tags?.map((i) => i.slug)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const secretsDeleted = await secretDAL.transaction(async (tx) =>
|
const secretsDeleted = await secretDAL.transaction(async (tx) =>
|
||||||
fnSecretBulkDelete({
|
fnSecretBulkDelete({
|
||||||
@@ -1296,7 +1489,8 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
.map(({ id, encryptedValue }) => ({
|
.map(({ id, encryptedValue }) => ({
|
||||||
secretId: id,
|
secretId: id,
|
||||||
references: encryptedValue
|
references: encryptedValue
|
||||||
? getAllNestedSecretReferences(secretManagerDecryptor({ cipherTextBlob: encryptedValue }).toString())
|
? getAllSecretReferences(secretManagerDecryptor({ cipherTextBlob: encryptedValue }).toString())
|
||||||
|
.nestedReferences
|
||||||
: []
|
: []
|
||||||
})),
|
})),
|
||||||
tx
|
tx
|
||||||
@@ -1327,21 +1521,6 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Delete,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: sourceEnvironment, secretPath: sourceSecretPath })
|
|
||||||
);
|
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Create,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: destinationEnvironment, secretPath: destinationSecretPath })
|
|
||||||
);
|
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Edit,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: destinationEnvironment, secretPath: destinationSecretPath })
|
|
||||||
);
|
|
||||||
|
|
||||||
const sourceFolder = await folderDAL.findBySecretPath(projectId, sourceEnvironment, sourceSecretPath);
|
const sourceFolder = await folderDAL.findBySecretPath(projectId, sourceEnvironment, sourceSecretPath);
|
||||||
if (!sourceFolder) {
|
if (!sourceFolder) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
@@ -1367,6 +1546,17 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
id: secretIds
|
id: secretIds
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
sourceSecrets.forEach((secret) => {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Delete,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: sourceEnvironment,
|
||||||
|
secretPath: sourceSecretPath,
|
||||||
|
secretName: secret.key,
|
||||||
|
secretTags: secret.tags.map((el) => el.slug)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
if (sourceSecrets.length !== secretIds.length) {
|
if (sourceSecrets.length !== secretIds.length) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
@@ -1437,6 +1627,32 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
message: "Selected secrets already exist in the destination."
|
message: "Selected secrets already exist in the destination."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// permission check whether can create or edit the ones in the destination folder
|
||||||
|
locallyCreatedSecrets.forEach((secret) => {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: destinationEnvironment,
|
||||||
|
secretPath: destinationEnvironment,
|
||||||
|
secretName: secret.key,
|
||||||
|
secretTags: secret.tags.map((el) => el.slug)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
locallyUpdatedSecrets.forEach((secret) => {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: destinationEnvironment,
|
||||||
|
secretPath: destinationEnvironment,
|
||||||
|
secretName: secret.key,
|
||||||
|
secretTags: secret.tags.map((el) => el.slug)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const destinationFolderPolicy = await secretApprovalPolicyService.getSecretApprovalPolicy(
|
const destinationFolderPolicy = await secretApprovalPolicyService.getSecretApprovalPolicy(
|
||||||
projectId,
|
projectId,
|
||||||
destinationFolder.environment.slug,
|
destinationFolder.environment.slug,
|
||||||
@@ -1503,7 +1719,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
skipMultilineEncoding: doc.skipMultilineEncoding,
|
skipMultilineEncoding: doc.skipMultilineEncoding,
|
||||||
reminderNote: doc.reminderNote,
|
reminderNote: doc.reminderNote,
|
||||||
reminderRepeatDays: doc.reminderRepeatDays,
|
reminderRepeatDays: doc.reminderRepeatDays,
|
||||||
references: doc.value ? getAllNestedSecretReferences(doc.value) : []
|
references: doc.value ? getAllSecretReferences(doc.value).nestedReferences : []
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@@ -1532,7 +1748,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
...(doc.encryptedValue
|
...(doc.encryptedValue
|
||||||
? {
|
? {
|
||||||
encryptedValue: doc.encryptedValue,
|
encryptedValue: doc.encryptedValue,
|
||||||
references: doc.value ? getAllNestedSecretReferences(doc.value) : []
|
references: doc.value ? getAllSecretReferences(doc.value).nestedReferences : []
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
encryptedValue: undefined,
|
encryptedValue: undefined,
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ type TPartialSecret = Pick<TSecretsV2, "id" | "reminderRepeatDays" | "reminderNo
|
|||||||
|
|
||||||
type TPartialInputSecret = Pick<TSecretsV2, "type" | "reminderNote" | "reminderRepeatDays" | "id">;
|
type TPartialInputSecret = Pick<TSecretsV2, "type" | "reminderNote" | "reminderRepeatDays" | "id">;
|
||||||
|
|
||||||
|
export type TSecretReferenceDTO = {
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
secretKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type TGetSecretsDTO = {
|
export type TGetSecretsDTO = {
|
||||||
expandSecretReferences?: boolean;
|
expandSecretReferences?: boolean;
|
||||||
path: string;
|
path: string;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { logger } from "@app/lib/logger";
|
|||||||
import {
|
import {
|
||||||
fnSecretBulkInsert as fnSecretV2BridgeBulkInsert,
|
fnSecretBulkInsert as fnSecretV2BridgeBulkInsert,
|
||||||
fnSecretBulkUpdate as fnSecretV2BridgeBulkUpdate,
|
fnSecretBulkUpdate as fnSecretV2BridgeBulkUpdate,
|
||||||
getAllNestedSecretReferences as getAllNestedSecretReferencesV2Bridge
|
getAllSecretReferences
|
||||||
} from "@app/services/secret-v2-bridge/secret-v2-bridge-fns";
|
} from "@app/services/secret-v2-bridge/secret-v2-bridge-fns";
|
||||||
|
|
||||||
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
||||||
@@ -185,7 +185,9 @@ export const recursivelyGetSecretPaths = ({
|
|||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment,
|
environment,
|
||||||
secretPath: folder.path
|
secretPath: folder.path,
|
||||||
|
secretName: "",
|
||||||
|
secretTags: []
|
||||||
})
|
})
|
||||||
) && folder.path.startsWith(currentPath === "/" ? "" : currentPath)
|
) && folder.path.startsWith(currentPath === "/" ? "" : currentPath)
|
||||||
);
|
);
|
||||||
@@ -791,7 +793,7 @@ export const createManySecretsRawFnFactory = ({
|
|||||||
: null,
|
: null,
|
||||||
skipMultilineEncoding: secret.skipMultilineEncoding,
|
skipMultilineEncoding: secret.skipMultilineEncoding,
|
||||||
tags: secret.tags,
|
tags: secret.tags,
|
||||||
references: getAllNestedSecretReferencesV2Bridge(secret.secretValue)
|
references: getAllSecretReferences(secret.secretValue).nestedReferences
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -971,7 +973,7 @@ export const updateManySecretsRawFnFactory = ({
|
|||||||
: null,
|
: null,
|
||||||
skipMultilineEncoding: secret.skipMultilineEncoding,
|
skipMultilineEncoding: secret.skipMultilineEncoding,
|
||||||
tags: secret.tags,
|
tags: secret.tags,
|
||||||
references: getAllNestedSecretReferencesV2Bridge(secret.secretValue)
|
references: getAllSecretReferences(secret.secretValue).nestedReferences
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ 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 { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
||||||
import { expandSecretReferencesFactory, getAllNestedSecretReferences } 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";
|
||||||
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
|
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
|
||||||
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||||
@@ -1147,7 +1147,7 @@ export const secretQueueFactory = ({
|
|||||||
: "";
|
: "";
|
||||||
const encryptedValue = secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob;
|
const encryptedValue = secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob;
|
||||||
// create references
|
// create references
|
||||||
const references = getAllNestedSecretReferences(value);
|
const references = getAllSecretReferences(value).nestedReferences;
|
||||||
secretReferences.push({ secretId: el.id, references });
|
secretReferences.push({ secretId: el.id, references });
|
||||||
|
|
||||||
const encryptedComment = comment
|
const encryptedComment = comment
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ export const secretServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path, secretName: "", secretTags: [] })
|
||||||
);
|
);
|
||||||
|
|
||||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||||
@@ -296,7 +296,7 @@ export const secretServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path, secretName: "", secretTags: [] })
|
||||||
);
|
);
|
||||||
|
|
||||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||||
@@ -433,7 +433,7 @@ export const secretServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path, secretName: "", secretTags: [] })
|
||||||
);
|
);
|
||||||
|
|
||||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||||
@@ -538,7 +538,7 @@ export const secretServiceFactory = ({
|
|||||||
} else {
|
} else {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path, secretName: "", secretTags: [] })
|
||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
@@ -565,7 +565,9 @@ export const secretServiceFactory = ({
|
|||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: importEnv.slug,
|
environment: importEnv.slug,
|
||||||
secretPath: importPath
|
secretPath: importPath,
|
||||||
|
secretName: "",
|
||||||
|
secretTags: []
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -619,7 +621,7 @@ export const secretServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path, secretName: "", secretTags: [] })
|
||||||
);
|
);
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
@@ -671,7 +673,9 @@ export const secretServiceFactory = ({
|
|||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
environment: importEnv.slug,
|
environment: importEnv.slug,
|
||||||
secretPath: importPath
|
secretPath: importPath,
|
||||||
|
secretName: "",
|
||||||
|
secretTags: []
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -718,7 +722,7 @@ export const secretServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path, secretName: "", secretTags: [] })
|
||||||
);
|
);
|
||||||
|
|
||||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||||
@@ -803,7 +807,7 @@ export const secretServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path, secretName: "", secretTags: [] })
|
||||||
);
|
);
|
||||||
|
|
||||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||||
@@ -909,7 +913,7 @@ export const secretServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path, secretName: "", secretTags: [] })
|
||||||
);
|
);
|
||||||
|
|
||||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||||
@@ -2118,7 +2122,7 @@ export const secretServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath, secretName: "", secretTags: [] })
|
||||||
);
|
);
|
||||||
|
|
||||||
await projectDAL.checkProjectUpgradeStatus(project.id);
|
await projectDAL.checkProjectUpgradeStatus(project.id);
|
||||||
@@ -2220,7 +2224,7 @@ export const secretServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath, secretName: "", secretTags: [] })
|
||||||
);
|
);
|
||||||
|
|
||||||
await projectDAL.checkProjectUpgradeStatus(project.id);
|
await projectDAL.checkProjectUpgradeStatus(project.id);
|
||||||
@@ -2407,17 +2411,32 @@ export const secretServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: sourceEnvironment, secretPath: sourceSecretPath })
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: sourceEnvironment,
|
||||||
|
secretPath: sourceSecretPath,
|
||||||
|
secretName: "",
|
||||||
|
secretTags: []
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: destinationEnvironment, secretPath: destinationSecretPath })
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: destinationEnvironment,
|
||||||
|
secretPath: destinationSecretPath,
|
||||||
|
secretName: "",
|
||||||
|
secretTags: []
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: destinationEnvironment, secretPath: destinationSecretPath })
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: destinationEnvironment,
|
||||||
|
secretPath: destinationSecretPath,
|
||||||
|
secretName: "",
|
||||||
|
secretTags: []
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const { botKey } = await projectBotService.getBotKey(project.id);
|
const { botKey } = await projectBotService.getBotKey(project.id);
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export const serviceTokenServiceFactory = ({
|
|||||||
scopes.forEach(({ environment, secretPath }) => {
|
scopes.forEach(({ environment, secretPath }) => {
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath, secretName: "", secretTags: [] })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user