feat(infisical-pg): fixed missing secret path and commiter message in frontend

This commit is contained in:
Akhil Mohan
2024-01-18 13:56:16 +05:30
parent 3213dafba9
commit 9c2ef15314
7 changed files with 137 additions and 34 deletions

View File

@@ -40,6 +40,10 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
approvers: z.string().array(),
secretPath: z.string().optional().nullable()
}),
commits: z
.object({ op: z.string(), secretId: z.string().nullable().optional() })
.array(),
environment: z.string(),
reviewers: z.object({ member: z.string(), status: z.string() }).array(),
approvers: z.string().array()
})
@@ -105,8 +109,10 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
approvers: z.string().array(),
secretPath: z.string().optional().nullable()
}),
environment: z.string(),
reviewers: z.object({ member: z.string(), status: z.string() }).array(),
approvers: z.string().array(),
secretPath: z.string(),
commits: SaRequestSecretsSchema.omit({ secretBlindIndex: true })
.merge(
z.object({

View File

@@ -53,16 +53,17 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => {
`${TableName.SarReviewer}.requestId`
)
.select(selectAllTableCols(TableName.SecretApprovalRequest))
.select(tx.ref("member").withSchema(TableName.SarReviewer).as("reviewerMemberId"))
.select(tx.ref("status").withSchema(TableName.SarReviewer).as("reviewerStatus"))
.select(tx.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId"))
.select(tx.ref("name").withSchema(TableName.SecretApprovalPolicy).as("policyName"))
.select(tx.ref("projectId").withSchema(TableName.Environment))
.select(
tx.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath")
)
.select(tx.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"))
.select(tx.ref("approverId").withSchema(TableName.SapApprover));
tx.ref("member").withSchema(TableName.SarReviewer).as("reviewerMemberId"),
tx.ref("status").withSchema(TableName.SarReviewer).as("reviewerStatus"),
tx.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId"),
tx.ref("name").withSchema(TableName.SecretApprovalPolicy).as("policyName"),
tx.ref("projectId").withSchema(TableName.Environment),
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
tx.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
tx.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
tx.ref("approverId").withSchema(TableName.SapApprover)
);
const findById = async (id: string, tx?: Knex) => {
try {
@@ -74,6 +75,7 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => {
parentMapper: (el) => ({
...SecretApprovalRequestsSchema.parse(el),
projectId: el.projectId,
environment: el.environment,
policy: {
id: el.policyId,
name: el.policyName,
@@ -193,6 +195,11 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => {
`${TableName.SecretApprovalRequest}.id`,
`${TableName.SarReviewer}.requestId`
)
.leftJoin(
TableName.SarSecret,
`${TableName.SarSecret}.requestId`,
`${TableName.SecretApprovalRequest}.id`
)
.where(
stripUndefinedInWhere({
projectId,
@@ -207,23 +214,23 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => {
.orWhere(`${TableName.SecretApprovalRequest}.committerId`, membershipId)
)
.select(selectAllTableCols(TableName.SecretApprovalRequest))
.select(db.ref("projectId").withSchema(TableName.Environment))
.select(db.ref("id").withSchema(TableName.SarReviewer).as("reviewerMemberId"))
.select(db.ref("status").withSchema(TableName.SarReviewer).as("reviewerStatus"))
.select(db.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId"))
.select(db.ref("name").withSchema(TableName.SecretApprovalPolicy).as("policyName"))
.select(
db.ref("projectId").withSchema(TableName.Environment),
db.ref("slug").withSchema(TableName.Environment).as("environment"),
db.ref("id").withSchema(TableName.SarReviewer).as("reviewerMemberId"),
db.ref("status").withSchema(TableName.SarReviewer).as("reviewerStatus"),
db.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId"),
db.ref("name").withSchema(TableName.SecretApprovalPolicy).as("policyName"),
db.ref("op").withSchema(TableName.SarSecret).as("commitOp"),
db.ref("secretId").withSchema(TableName.SarSecret).as("commitSecretId"),
db.ref("id").withSchema(TableName.SarSecret).as("commitId"),
db.raw(
`DENSE_RANK() OVER (partition by ${TableName.Environment}."projectId" ORDER BY ${TableName.SecretApprovalRequest}."id" DESC) as rank`
)
),
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
db.ref("approverId").withSchema(TableName.SapApprover)
)
.select(
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath")
)
.select(
db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals")
)
.select(db.ref("approverId").withSchema(TableName.SapApprover))
.orderBy("createdAt", "desc");
const docs = await (tx || db)
@@ -232,12 +239,12 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => {
.from<Awaited<typeof query>[number]>("w")
.where("w.rank", ">=", offset)
.andWhere("w.rank", "<", offset + limit);
const formatedDoc = sqlNestRelationships({
data: docs,
key: "id",
parentMapper: (el) => ({
...SecretApprovalRequestsSchema.parse(el),
environment: el.environment,
projectId: el.projectId,
policy: {
id: el.policyId,
@@ -253,7 +260,20 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => {
mapper: ({ reviewerMemberId: member, reviewerStatus: s }) =>
member ? { member, status: s } : undefined
},
{ key: "approverId", label: "approvers" as const, mapper: ({ approverId }) => approverId }
{
key: "approverId",
label: "approvers" as const,
mapper: ({ approverId }) => approverId
},
{
key: "commitId",
label: "commits" as const,
mapper: ({ commitSecretId: secretId, commitId: id, commitOp: op }) => ({
op,
id,
secretId
})
}
]
});
return formatedDoc.map((el) => ({

View File

@@ -41,7 +41,10 @@ type TSecretApprovalRequestServiceFactoryDep = {
secretApprovalRequestDal: TSecretApprovalRequestDalFactory;
sarSecretDal: TSarSecretDalFactory;
sarReviewerDal: TSarReviewerDalFactory;
folderDal: Pick<TSecretFolderDalFactory, "findBySecretPath" | "findById">;
folderDal: Pick<
TSecretFolderDalFactory,
"findBySecretPath" | "findById" | "findSecretPathByFolderIds"
>;
secretBlindIndexDal: Pick<TSecretBlindIndexDalFactory, "findOne">;
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
secretVersionDal: Pick<TSecretVersionDalFactory, "findLatestVersionMany">;
@@ -135,7 +138,10 @@ export const secretApprovalRequestServiceFactory = ({
}
const secrets = await sarSecretDal.findByRequestId(secretApprovalRequest.id);
return { ...secretApprovalRequest, commits: secrets };
const secretPath = await folderDal.findSecretPathByFolderIds(secretApprovalRequest.projectId, [
secretApprovalRequest.folderId
]);
return { ...secretApprovalRequest, secretPath: secretPath?.[0]?.path || "/", commits: secrets };
};
const reviewApproval = async ({ approvalId, actor, status, actorId }: TReviewRequestDTO) => {

View File

@@ -8,6 +8,7 @@ import {
TSecretFoldersUpdate
} from "@app/db/schemas";
import { BadRequestError, DatabaseError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { ormify, selectAllTableCols } from "@app/lib/knex";
export const validateFolderName = (folderName: string) => {
@@ -176,6 +177,54 @@ const sqlFindFolderByPathQuery = (
);
};
const sqlFindSecretPathByFolderId = (db: Knex, projectId: string, folderIds: string[]) =>
db
.withRecursive("parent", (baseQb) => {
// first remember our folders are connected as a link list or known as adjacency list
// Thus each node has connection to parent node
// we first find the folder given in folder id
baseQb
.from(TableName.SecretFolder)
.select(selectAllTableCols(TableName.SecretFolder))
.select({
// this is for root condition
// if the given folder id is root folder id then intial path is set as / instead of /root
// if not root folder the path here will be /<folder name>
path: db.raw(
`CONCAT('/', (CASE WHEN "parentId" is NULL THEN '' ELSE ${TableName.SecretFolder}.name END))`
),
child: db.raw("NULL::uuid")
})
.join(
TableName.Environment,
`${TableName.SecretFolder}.envId`,
`${TableName.Environment}.id`
)
.where({ projectId })
.whereIn(`${TableName.SecretFolder}.id`, folderIds)
.union((qb) =>
// then we keep going up
// until parent id is null
qb
.select(selectAllTableCols(TableName.SecretFolder))
.select({
// then we join join this folder name behind previous as we are going from child to parent
// the root folder check is used to avoid last / and also root name in folders
path: db.raw(
`CONCAT( CASE
WHEN ${TableName.SecretFolder}."parentId" is NULL THEN ''
ELSE CONCAT('/', secret_folders.name)
END, parent.path )`
),
child: db.raw("COALESCE(parent.child, parent.id)")
})
.from(TableName.SecretFolder)
.join("parent", "parent.parentId", `${TableName.SecretFolder}.id`)
);
})
.select("*")
.from<TSecretFolders & { child: string | null; path: string }>("parent");
export type TSecretFolderDalFactory = ReturnType<typeof secretFolderDalFactory>;
// never change this. If u do write a migration for it
export const ROOT_FOLDER_NAME = "root";
@@ -220,6 +269,21 @@ export const secretFolderDalFactory = (db: TDbClient) => {
}
};
// this is used to do an inverse query in folders
// that is instances in which for a given folderid find the secret path
const findSecretPathByFolderIds = async (projectId: string, folderIds: string[], tx?: Knex) => {
try {
const folders = await sqlFindSecretPathByFolderId(tx || db, projectId, folderIds);
const rootFolders = groupBy(
folders.filter(({ parentId }) => parentId === null),
(i) => i.child || i.id // root condition then child and parent will null
);
return folderIds.map((folderId) => rootFolders[folderId]?.[0]);
} catch (error) {
throw new DatabaseError({ error, name: "Find by secret path" });
}
};
const update = async (filter: Partial<TSecretFolders>, data: TSecretFoldersUpdate, tx?: Knex) => {
try {
const folder = await (tx || db)(TableName.SecretFolder)
@@ -259,5 +323,12 @@ export const secretFolderDalFactory = (db: TDbClient) => {
}
};
return { ...secretFolderOrm, update, findBySecretPath, findById, findByManySecretPath };
return {
...secretFolderOrm,
update,
findBySecretPath,
findById,
findByManySecretPath,
findSecretPathByFolderIds
};
};

View File

@@ -55,7 +55,7 @@ export const serviceTokenServiceFactory = ({
scopes.forEach(({ environment, secretPath }) => {
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, { environment, secretPath: secretPath })
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
})

View File

@@ -46,7 +46,7 @@ export type TSecretApprovalRequest<J extends unknown = EncryptedSecret> = {
id: string;
slug: string;
createdAt: string;
committer: string;
committerId: string;
reviewers: {
member: string;
status: ApprovalStatus;

View File

@@ -89,7 +89,7 @@ export const SecretApprovalRequest = () => {
members={membersGroupById}
approvalRequestId={selectedApproval?.id || ""}
onGoBack={handleGoBackSecretRequestDetail}
committer={membersGroupById?.[selectedApproval?.committer || ""]}
committer={membersGroupById?.[selectedApproval?.committerId || ""]}
/>
</motion.div>
) : (
@@ -195,7 +195,7 @@ export const SecretApprovalRequest = () => {
const {
id: reqId,
commits,
committer,
committerId,
createdAt,
policy,
reviewers,
@@ -225,9 +225,9 @@ export const SecretApprovalRequest = () => {
</div>
<span className="text-xs text-gray-500">
Opened {formatDistance(new Date(createdAt), new Date())} ago by{" "}
{membersGroupById?.[committer]?.user?.firstName}{" "}
{membersGroupById?.[committer]?.user?.lastName} (
{membersGroupById?.[committer]?.user?.email}){" "}
{membersGroupById?.[committerId]?.user?.firstName}{" "}
{membersGroupById?.[committerId]?.user?.lastName} (
{membersGroupById?.[committerId]?.user?.email}){" "}
{isApprover && !isReviewed && status === "open" && "- Review required"}
</span>
</div>