mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 02:27:39 +00:00
feat: server logic for secret approval group
This commit is contained in:
@@ -25,10 +25,10 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
.optional()
|
.optional()
|
||||||
.nullable()
|
.nullable()
|
||||||
.transform((val) => (val ? removeTrailingSlash(val) : val)),
|
.transform((val) => (val ? removeTrailingSlash(val) : val)),
|
||||||
approvers: z.string().array().min(1),
|
approverUserIds: z.string().array().min(1),
|
||||||
approvals: z.number().min(1).default(1)
|
approvals: z.number().min(1).default(1)
|
||||||
})
|
})
|
||||||
.refine((data) => data.approvals <= data.approvers.length, {
|
.refine((data) => data.approvals <= data.approverUserIds.length, {
|
||||||
path: ["approvals"],
|
path: ["approvals"],
|
||||||
message: "The number of approvals should be lower than the number of approvers."
|
message: "The number of approvals should be lower than the number of approvers."
|
||||||
}),
|
}),
|
||||||
@@ -66,7 +66,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
body: z
|
body: z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
approvers: z.string().array().min(1),
|
approverUserIds: z.string().array().min(1),
|
||||||
approvals: z.number().min(1).default(1),
|
approvals: z.number().min(1).default(1),
|
||||||
secretPath: z
|
secretPath: z
|
||||||
.string()
|
.string()
|
||||||
@@ -74,7 +74,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
.nullable()
|
.nullable()
|
||||||
.transform((val) => (val ? removeTrailingSlash(val) : val))
|
.transform((val) => (val ? removeTrailingSlash(val) : val))
|
||||||
})
|
})
|
||||||
.refine((data) => data.approvals <= data.approvers.length, {
|
.refine((data) => data.approvals <= data.approverUserIds.length, {
|
||||||
path: ["approvals"],
|
path: ["approvals"],
|
||||||
message: "The number of approvals should be lower than the number of approvers."
|
message: "The number of approvals should be lower than the number of approvers."
|
||||||
}),
|
}),
|
||||||
@@ -139,7 +139,15 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
approvals: sapPubSchema.merge(z.object({ approvers: z.string().array() })).array()
|
approvals: sapPubSchema
|
||||||
|
.extend({
|
||||||
|
userApprovers: z
|
||||||
|
.object({
|
||||||
|
userId: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -170,7 +178,11 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
policy: sapPubSchema.merge(z.object({ approvers: z.string().array() })).optional()
|
policy: sapPubSchema
|
||||||
|
.extend({
|
||||||
|
userApprovers: z.object({ userId: z.string() }).array()
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import {
|
|||||||
SecretApprovalRequestsSecretsSchema,
|
SecretApprovalRequestsSecretsSchema,
|
||||||
SecretsSchema,
|
SecretsSchema,
|
||||||
SecretTagsSchema,
|
SecretTagsSchema,
|
||||||
SecretVersionsSchema
|
SecretVersionsSchema,
|
||||||
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { ApprovalStatus, RequestState } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
import { ApprovalStatus, RequestState } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
||||||
@@ -14,6 +15,15 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
|||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
const approvalRequestUser = z.object({ userId: z.string() }).merge(
|
||||||
|
UsersSchema.pick({
|
||||||
|
email: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
username: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
export const registerSecretApprovalRequestRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretApprovalRequestRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -41,9 +51,10 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
approvers: z.string().array(),
|
approvers: z.string().array(),
|
||||||
secretPath: z.string().optional().nullable()
|
secretPath: z.string().optional().nullable()
|
||||||
}),
|
}),
|
||||||
|
committerUser: approvalRequestUser,
|
||||||
commits: z.object({ op: z.string(), secretId: z.string().nullable().optional() }).array(),
|
commits: z.object({ op: z.string(), secretId: z.string().nullable().optional() }).array(),
|
||||||
environment: z.string(),
|
environment: z.string(),
|
||||||
reviewers: z.object({ member: z.string(), status: z.string() }).array(),
|
reviewers: z.object({ userId: z.string(), status: z.string() }).array(),
|
||||||
approvers: z.string().array()
|
approvers: z.string().array()
|
||||||
}).array()
|
}).array()
|
||||||
})
|
})
|
||||||
@@ -195,7 +206,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
type: isClosing ? EventType.SECRET_APPROVAL_CLOSED : EventType.SECRET_APPROVAL_REOPENED,
|
type: isClosing ? EventType.SECRET_APPROVAL_CLOSED : EventType.SECRET_APPROVAL_REOPENED,
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
metadata: {
|
metadata: {
|
||||||
[isClosing ? ("closedBy" as const) : ("reopenedBy" as const)]: approval.statusChangeBy as string,
|
[isClosing ? ("closedBy" as const) : ("reopenedBy" as const)]: approval.statusChangedByUserId as string,
|
||||||
secretApprovalRequestId: approval.id,
|
secretApprovalRequestId: approval.id,
|
||||||
secretApprovalRequestSlug: approval.slug
|
secretApprovalRequestSlug: approval.slug
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
@@ -216,6 +227,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.optional();
|
.optional();
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:id",
|
url: "/:id",
|
||||||
@@ -235,12 +247,13 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
approvals: z.number(),
|
approvals: z.number(),
|
||||||
approvers: z.string().array(),
|
approvers: approvalRequestUser.array(),
|
||||||
secretPath: z.string().optional().nullable()
|
secretPath: z.string().optional().nullable()
|
||||||
}),
|
}),
|
||||||
environment: z.string(),
|
environment: z.string(),
|
||||||
reviewers: z.object({ member: z.string(), status: z.string() }).array(),
|
statusChangedByUser: approvalRequestUser.optional(),
|
||||||
approvers: z.string().array(),
|
committerUser: approvalRequestUser,
|
||||||
|
reviewers: approvalRequestUser.extend({ status: z.string() }).array(),
|
||||||
secretPath: z.string(),
|
secretPath: z.string(),
|
||||||
commits: SecretApprovalRequestsSecretsSchema.omit({ secretBlindIndex: true })
|
commits: SecretApprovalRequestsSecretsSchema.omit({ secretBlindIndex: true })
|
||||||
.merge(
|
.merge(
|
||||||
|
|||||||
@@ -1,49 +1,59 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName, TSecretApprovalPolicies } from "@app/db/schemas";
|
import { SecretApprovalPoliciesSchema, TableName, TSecretApprovalPolicies } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { buildFindFilter, mergeOneToManyRelation, ormify, selectAllTableCols, TFindFilter } from "@app/lib/knex";
|
import { buildFindFilter, ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
|
||||||
|
|
||||||
export type TSecretApprovalPolicyDALFactory = ReturnType<typeof secretApprovalPolicyDALFactory>;
|
export type TSecretApprovalPolicyDALFactory = ReturnType<typeof secretApprovalPolicyDALFactory>;
|
||||||
|
|
||||||
export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
||||||
const secretApprovalPolicyOrm = ormify(db, TableName.SecretApprovalPolicy);
|
const secretApprovalPolicyOrm = ormify(db, TableName.SecretApprovalPolicy);
|
||||||
|
|
||||||
const sapFindQuery = (tx: Knex, filter: TFindFilter<TSecretApprovalPolicies>) =>
|
const secretApprovalPolicyFindQuery = (tx: Knex, filter: TFindFilter<TSecretApprovalPolicies>) =>
|
||||||
tx(TableName.SecretApprovalPolicy)
|
tx(TableName.SecretApprovalPolicy)
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
.where(buildFindFilter(filter))
|
.where(buildFindFilter(filter))
|
||||||
.join(TableName.Environment, `${TableName.SecretApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
.join(TableName.Environment, `${TableName.SecretApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
||||||
.join(
|
.leftJoin(
|
||||||
TableName.SecretApprovalPolicyApprover,
|
TableName.SecretApprovalPolicyApprover,
|
||||||
`${TableName.SecretApprovalPolicy}.id`,
|
`${TableName.SecretApprovalPolicy}.id`,
|
||||||
`${TableName.SecretApprovalPolicyApprover}.policyId`
|
`${TableName.SecretApprovalPolicyApprover}.policyId`
|
||||||
)
|
)
|
||||||
.select(tx.ref("approverId").withSchema(TableName.SecretApprovalPolicyApprover))
|
.select(tx.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover))
|
||||||
.select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
|
.select(
|
||||||
.select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug"))
|
tx.ref("name").withSchema(TableName.Environment).as("envName"),
|
||||||
.select(tx.ref("id").withSchema(TableName.Environment).as("envId"))
|
tx.ref("slug").withSchema(TableName.Environment).as("envSlug"),
|
||||||
.select(tx.ref("projectId").withSchema(TableName.Environment))
|
tx.ref("id").withSchema(TableName.Environment).as("envId"),
|
||||||
|
tx.ref("projectId").withSchema(TableName.Environment)
|
||||||
|
)
|
||||||
.select(selectAllTableCols(TableName.SecretApprovalPolicy))
|
.select(selectAllTableCols(TableName.SecretApprovalPolicy))
|
||||||
.orderBy("createdAt", "asc");
|
.orderBy("createdAt", "asc");
|
||||||
|
|
||||||
const findById = async (id: string, tx?: Knex) => {
|
const findById = async (id: string, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
const doc = await sapFindQuery(tx || db.replicaNode(), {
|
const doc = await secretApprovalPolicyFindQuery(tx || db.replicaNode(), {
|
||||||
[`${TableName.SecretApprovalPolicy}.id` as "id"]: id
|
[`${TableName.SecretApprovalPolicy}.id` as "id"]: id
|
||||||
});
|
});
|
||||||
const formatedDoc = mergeOneToManyRelation(
|
const formatedDoc = sqlNestRelationships({
|
||||||
doc,
|
data: doc,
|
||||||
"id",
|
key: "id",
|
||||||
({ approverId, envId, envName: name, envSlug: slug, ...el }) => ({
|
parentMapper: (data) => ({
|
||||||
...el,
|
environment: { id: data.envId, name: data.envName, slug: data.envSlug },
|
||||||
envId,
|
projectId: data.projectId,
|
||||||
environment: { id: envId, name, slug }
|
...SecretApprovalPoliciesSchema.parse(data)
|
||||||
}),
|
}),
|
||||||
({ approverId }) => approverId,
|
childrenMapper: [
|
||||||
"approvers"
|
{
|
||||||
);
|
key: "approverUserId",
|
||||||
|
label: "userApprovers" as const,
|
||||||
|
mapper: ({ approverUserId }) => ({
|
||||||
|
userId: approverUserId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
return formatedDoc?.[0];
|
return formatedDoc?.[0];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "FindById" });
|
throw new DatabaseError({ error, name: "FindById" });
|
||||||
@@ -52,18 +62,25 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const find = async (filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>, tx?: Knex) => {
|
const find = async (filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
const docs = await sapFindQuery(tx || db.replicaNode(), filter);
|
const docs = await secretApprovalPolicyFindQuery(tx || db.replicaNode(), filter);
|
||||||
const formatedDoc = mergeOneToManyRelation(
|
const formatedDoc = sqlNestRelationships({
|
||||||
docs,
|
data: docs,
|
||||||
"id",
|
key: "id",
|
||||||
({ approverId, envId, envName: name, envSlug: slug, ...el }) => ({
|
parentMapper: (data) => ({
|
||||||
...el,
|
environment: { id: data.envId, name: data.envName, slug: data.envSlug },
|
||||||
envId,
|
projectId: data.projectId,
|
||||||
environment: { id: envId, name, slug }
|
...SecretApprovalPoliciesSchema.parse(data)
|
||||||
}),
|
}),
|
||||||
({ approverId }) => approverId,
|
childrenMapper: [
|
||||||
"approvers"
|
{
|
||||||
);
|
key: "approverUserId",
|
||||||
|
label: "userApprovers" as const,
|
||||||
|
mapper: ({ approverUserId }) => ({
|
||||||
|
userId: approverUserId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
return formatedDoc;
|
return formatedDoc;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "Find" });
|
throw new DatabaseError({ error, name: "Find" });
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { BadRequestError } from "@app/lib/errors";
|
|||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { containsGlobPatterns } from "@app/lib/picomatch";
|
import { containsGlobPatterns } from "@app/lib/picomatch";
|
||||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||||
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
|
||||||
|
|
||||||
import { TSecretApprovalPolicyApproverDALFactory } from "./secret-approval-policy-approver-dal";
|
import { TSecretApprovalPolicyApproverDALFactory } from "./secret-approval-policy-approver-dal";
|
||||||
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
|
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
|
||||||
@@ -29,7 +28,6 @@ type TSecretApprovalPolicyServiceFactoryDep = {
|
|||||||
secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory;
|
secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory;
|
||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||||
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
|
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find">;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretApprovalPolicyServiceFactory = ReturnType<typeof secretApprovalPolicyServiceFactory>;
|
export type TSecretApprovalPolicyServiceFactory = ReturnType<typeof secretApprovalPolicyServiceFactory>;
|
||||||
@@ -38,8 +36,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
secretApprovalPolicyDAL,
|
secretApprovalPolicyDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
secretApprovalPolicyApproverDAL,
|
secretApprovalPolicyApproverDAL,
|
||||||
projectEnvDAL,
|
projectEnvDAL
|
||||||
projectMembershipDAL
|
|
||||||
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
||||||
const createSecretApprovalPolicy = async ({
|
const createSecretApprovalPolicy = async ({
|
||||||
name,
|
name,
|
||||||
@@ -48,12 +45,12 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
approvals,
|
approvals,
|
||||||
approvers,
|
approverUserIds,
|
||||||
projectId,
|
projectId,
|
||||||
secretPath,
|
secretPath,
|
||||||
environment
|
environment
|
||||||
}: TCreateSapDTO) => {
|
}: TCreateSapDTO) => {
|
||||||
if (approvals > approvers.length)
|
if (approvals > approverUserIds.length)
|
||||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@@ -70,13 +67,6 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
||||||
if (!env) throw new BadRequestError({ message: "Environment not found" });
|
if (!env) throw new BadRequestError({ message: "Environment not found" });
|
||||||
|
|
||||||
const secretApprovers = await projectMembershipDAL.find({
|
|
||||||
projectId,
|
|
||||||
$in: { id: approvers }
|
|
||||||
});
|
|
||||||
if (secretApprovers.length !== approvers.length)
|
|
||||||
throw new BadRequestError({ message: "Approver not found in project" });
|
|
||||||
|
|
||||||
const secretApproval = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
const secretApproval = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
||||||
const doc = await secretApprovalPolicyDAL.create(
|
const doc = await secretApprovalPolicyDAL.create(
|
||||||
{
|
{
|
||||||
@@ -88,8 +78,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
await secretApprovalPolicyApproverDAL.insertMany(
|
await secretApprovalPolicyApproverDAL.insertMany(
|
||||||
secretApprovers.map(({ id }) => ({
|
approverUserIds.map((approverUserId) => ({
|
||||||
approverId: id,
|
approverUserId,
|
||||||
policyId: doc.id
|
policyId: doc.id
|
||||||
})),
|
})),
|
||||||
tx
|
tx
|
||||||
@@ -100,7 +90,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateSecretApprovalPolicy = async ({
|
const updateSecretApprovalPolicy = async ({
|
||||||
approvers,
|
approverUserIds,
|
||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -132,22 +122,11 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
if (approvers) {
|
if (approverUserIds) {
|
||||||
const secretApprovers = await projectMembershipDAL.find(
|
|
||||||
{
|
|
||||||
projectId: secretApprovalPolicy.projectId,
|
|
||||||
$in: { id: approvers }
|
|
||||||
},
|
|
||||||
{ tx }
|
|
||||||
);
|
|
||||||
if (secretApprovers.length !== approvers.length)
|
|
||||||
throw new BadRequestError({ message: "Approver not found in project" });
|
|
||||||
if (doc.approvals > secretApprovers.length)
|
|
||||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
|
||||||
await secretApprovalPolicyApproverDAL.delete({ policyId: doc.id }, tx);
|
await secretApprovalPolicyApproverDAL.delete({ policyId: doc.id }, tx);
|
||||||
await secretApprovalPolicyApproverDAL.insertMany(
|
await secretApprovalPolicyApproverDAL.insertMany(
|
||||||
secretApprovers.map(({ id }) => ({
|
approverUserIds.map((approverUserId) => ({
|
||||||
approverId: id,
|
approverUserId,
|
||||||
policyId: doc.id
|
policyId: doc.id
|
||||||
})),
|
})),
|
||||||
tx
|
tx
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export type TCreateSapDTO = {
|
|||||||
approvals: number;
|
approvals: number;
|
||||||
secretPath?: string | null;
|
secretPath?: string | null;
|
||||||
environment: string;
|
environment: string;
|
||||||
approvers: string[];
|
approverUserIds: string[];
|
||||||
projectId: string;
|
projectId: string;
|
||||||
name: string;
|
name: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
@@ -13,7 +13,7 @@ export type TUpdateSapDTO = {
|
|||||||
secretPolicyId: string;
|
secretPolicyId: string;
|
||||||
approvals?: number;
|
approvals?: number;
|
||||||
secretPath?: string | null;
|
secretPath?: string | null;
|
||||||
approvers: string[];
|
approverUserIds: string[];
|
||||||
name?: string;
|
name?: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import {
|
|||||||
SecretApprovalRequestsSchema,
|
SecretApprovalRequestsSchema,
|
||||||
TableName,
|
TableName,
|
||||||
TSecretApprovalRequests,
|
TSecretApprovalRequests,
|
||||||
TSecretApprovalRequestsSecrets
|
TSecretApprovalRequestsSecrets,
|
||||||
|
TUsers
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols, sqlNestRelationships, stripUndefinedInWhere, TFindFilter } from "@app/lib/knex";
|
import { ormify, selectAllTableCols, sqlNestRelationships, stripUndefinedInWhere, TFindFilter } from "@app/lib/knex";
|
||||||
@@ -16,7 +17,7 @@ export type TSecretApprovalRequestDALFactory = ReturnType<typeof secretApprovalR
|
|||||||
|
|
||||||
type TFindQueryFilter = {
|
type TFindQueryFilter = {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
membershipId: string;
|
userId: string;
|
||||||
status?: RequestState;
|
status?: RequestState;
|
||||||
environment?: string;
|
environment?: string;
|
||||||
committer?: string;
|
committer?: string;
|
||||||
@@ -37,27 +38,63 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.SecretApprovalRequest}.policyId`,
|
`${TableName.SecretApprovalRequest}.policyId`,
|
||||||
`${TableName.SecretApprovalPolicy}.id`
|
`${TableName.SecretApprovalPolicy}.id`
|
||||||
)
|
)
|
||||||
|
.leftJoin<TUsers>(
|
||||||
|
db(TableName.Users).as("statusChangedByUser"),
|
||||||
|
`${TableName.SecretApprovalRequest}.statusChangedByUserId`,
|
||||||
|
`statusChangedByUser.id`
|
||||||
|
)
|
||||||
|
.join<TUsers>(
|
||||||
|
db(TableName.Users).as("committerUser"),
|
||||||
|
`${TableName.SecretApprovalRequest}.committerUserId`,
|
||||||
|
`committerUser.id`
|
||||||
|
)
|
||||||
.join(
|
.join(
|
||||||
TableName.SecretApprovalPolicyApprover,
|
TableName.SecretApprovalPolicyApprover,
|
||||||
`${TableName.SecretApprovalPolicy}.id`,
|
`${TableName.SecretApprovalPolicy}.id`,
|
||||||
`${TableName.SecretApprovalPolicyApprover}.policyId`
|
`${TableName.SecretApprovalPolicyApprover}.policyId`
|
||||||
)
|
)
|
||||||
|
.join<TUsers>(
|
||||||
|
db(TableName.Users).as("secretApprovalPolicyApproverUser"),
|
||||||
|
`${TableName.SecretApprovalPolicyApprover}.approverUserId`,
|
||||||
|
"secretApprovalPolicyApproverUser.id"
|
||||||
|
)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.SecretApprovalRequestReviewer,
|
TableName.SecretApprovalRequestReviewer,
|
||||||
`${TableName.SecretApprovalRequest}.id`,
|
`${TableName.SecretApprovalRequest}.id`,
|
||||||
`${TableName.SecretApprovalRequestReviewer}.requestId`
|
`${TableName.SecretApprovalRequestReviewer}.requestId`
|
||||||
)
|
)
|
||||||
|
.leftJoin<TUsers>(
|
||||||
|
db(TableName.Users).as("secretApprovalReviewerUser"),
|
||||||
|
`${TableName.SecretApprovalRequestReviewer}.reviewerUserId`,
|
||||||
|
`secretApprovalReviewerUser.id`
|
||||||
|
)
|
||||||
.select(selectAllTableCols(TableName.SecretApprovalRequest))
|
.select(selectAllTableCols(TableName.SecretApprovalRequest))
|
||||||
.select(
|
.select(
|
||||||
tx.ref("member").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerMemberId"),
|
tx.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
|
||||||
|
tx.ref("email").withSchema("secretApprovalPolicyApproverUser").as("approverEmail"),
|
||||||
|
tx.ref("username").withSchema("secretApprovalPolicyApproverUser").as("approverUsername"),
|
||||||
|
tx.ref("firstName").withSchema("secretApprovalPolicyApproverUser").as("approverFirstName"),
|
||||||
|
tx.ref("lastName").withSchema("secretApprovalPolicyApproverUser").as("approverLastName"),
|
||||||
|
tx.ref("email").withSchema("statusChangedByUser").as("statusChangedByUserEmail"),
|
||||||
|
tx.ref("username").withSchema("statusChangedByUser").as("statusChangedByUserUsername"),
|
||||||
|
tx.ref("firstName").withSchema("statusChangedByUser").as("statusChangedByUserFirstName"),
|
||||||
|
tx.ref("lastName").withSchema("statusChangedByUser").as("statusChangedByUserLastName"),
|
||||||
|
tx.ref("email").withSchema("committerUser").as("committerUserEmail"),
|
||||||
|
tx.ref("username").withSchema("committerUser").as("committerUserUsername"),
|
||||||
|
tx.ref("firstName").withSchema("committerUser").as("committerUserFirstName"),
|
||||||
|
tx.ref("lastName").withSchema("committerUser").as("committerUserLastName"),
|
||||||
|
tx.ref("reviewerUserId").withSchema(TableName.SecretApprovalRequestReviewer),
|
||||||
tx.ref("status").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerStatus"),
|
tx.ref("status").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerStatus"),
|
||||||
|
tx.ref("email").withSchema("secretApprovalReviewerUser").as("reviewerEmail"),
|
||||||
|
tx.ref("username").withSchema("secretApprovalReviewerUser").as("reviewerUsername"),
|
||||||
|
tx.ref("firstName").withSchema("secretApprovalReviewerUser").as("reviewerFirstName"),
|
||||||
|
tx.ref("lastName").withSchema("secretApprovalReviewerUser").as("reviewerLastName"),
|
||||||
tx.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId"),
|
tx.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId"),
|
||||||
tx.ref("name").withSchema(TableName.SecretApprovalPolicy).as("policyName"),
|
tx.ref("name").withSchema(TableName.SecretApprovalPolicy).as("policyName"),
|
||||||
tx.ref("projectId").withSchema(TableName.Environment),
|
tx.ref("projectId").withSchema(TableName.Environment),
|
||||||
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
|
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
|
||||||
tx.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
tx.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
||||||
tx.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
|
tx.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals")
|
||||||
tx.ref("approverId").withSchema(TableName.SecretApprovalPolicyApprover)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const findById = async (id: string, tx?: Knex) => {
|
const findById = async (id: string, tx?: Knex) => {
|
||||||
@@ -71,6 +108,22 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
...SecretApprovalRequestsSchema.parse(el),
|
...SecretApprovalRequestsSchema.parse(el),
|
||||||
projectId: el.projectId,
|
projectId: el.projectId,
|
||||||
environment: el.environment,
|
environment: el.environment,
|
||||||
|
statusChangedByUser: el.statusChangedByUserId
|
||||||
|
? {
|
||||||
|
userId: el.statusChangedByUserId,
|
||||||
|
email: el.statusChangedByUserEmail,
|
||||||
|
firstName: el.statusChangedByUserFirstName,
|
||||||
|
lastName: el.statusChangedByUserLastName,
|
||||||
|
username: el.statusChangedByUserUsername
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
committerUser: {
|
||||||
|
userId: el.committerUserId,
|
||||||
|
email: el.committerUserEmail,
|
||||||
|
firstName: el.committerUserFirstName,
|
||||||
|
lastName: el.committerUserLastName,
|
||||||
|
username: el.committerUserUsername
|
||||||
|
},
|
||||||
policy: {
|
policy: {
|
||||||
id: el.policyId,
|
id: el.policyId,
|
||||||
name: el.policyName,
|
name: el.policyName,
|
||||||
@@ -80,11 +133,34 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
}),
|
}),
|
||||||
childrenMapper: [
|
childrenMapper: [
|
||||||
{
|
{
|
||||||
key: "reviewerMemberId",
|
key: "reviewerUserId",
|
||||||
label: "reviewers" as const,
|
label: "reviewers" as const,
|
||||||
mapper: ({ reviewerMemberId: member, reviewerStatus: status }) => (member ? { member, status } : undefined)
|
mapper: ({
|
||||||
|
reviewerUserId: userId,
|
||||||
|
reviewerStatus: status,
|
||||||
|
reviewerEmail: email,
|
||||||
|
reviewerLastName: lastName,
|
||||||
|
reviewerUsername: username,
|
||||||
|
reviewerFirstName: firstName
|
||||||
|
}) => (userId ? { userId, status, email, firstName, lastName, username } : undefined)
|
||||||
},
|
},
|
||||||
{ key: "approverId", label: "approvers" as const, mapper: ({ approverId }) => approverId }
|
{
|
||||||
|
key: "approverUserId",
|
||||||
|
label: "approvers" as const,
|
||||||
|
mapper: ({
|
||||||
|
approverUserId,
|
||||||
|
approverEmail: email,
|
||||||
|
approverUsername: username,
|
||||||
|
approverLastName: lastName,
|
||||||
|
approverFirstName: firstName
|
||||||
|
}) => ({
|
||||||
|
userId: approverUserId,
|
||||||
|
email,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
username
|
||||||
|
})
|
||||||
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
if (!formatedDoc?.[0]) return;
|
if (!formatedDoc?.[0]) return;
|
||||||
@@ -97,7 +173,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const findProjectRequestCount = async (projectId: string, membershipId: string, tx?: Knex) => {
|
const findProjectRequestCount = async (projectId: string, userId: string, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
const docs = await (tx || db)
|
const docs = await (tx || db)
|
||||||
.with(
|
.with(
|
||||||
@@ -114,8 +190,8 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
.andWhere(
|
.andWhere(
|
||||||
(bd) =>
|
(bd) =>
|
||||||
void bd
|
void bd
|
||||||
.where(`${TableName.SecretApprovalPolicyApprover}.approverId`, membershipId)
|
.where(`${TableName.SecretApprovalPolicyApprover}.approverUserId`, userId)
|
||||||
.orWhere(`${TableName.SecretApprovalRequest}.committerId`, membershipId)
|
.orWhere(`${TableName.SecretApprovalRequest}.committerUserId`, userId)
|
||||||
)
|
)
|
||||||
.select("status", `${TableName.SecretApprovalRequest}.id`)
|
.select("status", `${TableName.SecretApprovalRequest}.id`)
|
||||||
.groupBy(`${TableName.SecretApprovalRequest}.id`, "status")
|
.groupBy(`${TableName.SecretApprovalRequest}.id`, "status")
|
||||||
@@ -142,7 +218,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const findByProjectId = async (
|
const findByProjectId = async (
|
||||||
{ status, limit = 20, offset = 0, projectId, committer, environment, membershipId }: TFindQueryFilter,
|
{ status, limit = 20, offset = 0, projectId, committer, environment, userId }: TFindQueryFilter,
|
||||||
tx?: Knex
|
tx?: Knex
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
@@ -161,6 +237,11 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.SecretApprovalPolicy}.id`,
|
`${TableName.SecretApprovalPolicy}.id`,
|
||||||
`${TableName.SecretApprovalPolicyApprover}.policyId`
|
`${TableName.SecretApprovalPolicyApprover}.policyId`
|
||||||
)
|
)
|
||||||
|
.join<TUsers>(
|
||||||
|
db(TableName.Users).as("committerUser"),
|
||||||
|
`${TableName.SecretApprovalRequest}.committerUserId`,
|
||||||
|
`committerUser.id`
|
||||||
|
)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.SecretApprovalRequestReviewer,
|
TableName.SecretApprovalRequestReviewer,
|
||||||
`${TableName.SecretApprovalRequest}.id`,
|
`${TableName.SecretApprovalRequest}.id`,
|
||||||
@@ -176,20 +257,21 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
projectId,
|
projectId,
|
||||||
[`${TableName.Environment}.slug` as "slug"]: environment,
|
[`${TableName.Environment}.slug` as "slug"]: environment,
|
||||||
[`${TableName.SecretApprovalRequest}.status`]: status,
|
[`${TableName.SecretApprovalRequest}.status`]: status,
|
||||||
committerId: committer
|
committerUserId: committer
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.andWhere(
|
.andWhere(
|
||||||
(bd) =>
|
(bd) =>
|
||||||
void bd
|
void bd
|
||||||
.where(`${TableName.SecretApprovalPolicyApprover}.approverId`, membershipId)
|
.where(`${TableName.SecretApprovalPolicyApprover}.approverUserId`, userId)
|
||||||
.orWhere(`${TableName.SecretApprovalRequest}.committerId`, membershipId)
|
.orWhere(`${TableName.SecretApprovalRequest}.committerUserId`, userId)
|
||||||
)
|
)
|
||||||
.select(selectAllTableCols(TableName.SecretApprovalRequest))
|
.select(selectAllTableCols(TableName.SecretApprovalRequest))
|
||||||
.select(
|
.select(
|
||||||
db.ref("projectId").withSchema(TableName.Environment),
|
db.ref("projectId").withSchema(TableName.Environment),
|
||||||
db.ref("slug").withSchema(TableName.Environment).as("environment"),
|
db.ref("slug").withSchema(TableName.Environment).as("environment"),
|
||||||
db.ref("id").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerMemberId"),
|
db.ref("id").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerId"),
|
||||||
|
db.ref("reviewerUserId").withSchema(TableName.SecretApprovalRequestReviewer),
|
||||||
db.ref("status").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerStatus"),
|
db.ref("status").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerStatus"),
|
||||||
db.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId"),
|
db.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId"),
|
||||||
db.ref("name").withSchema(TableName.SecretApprovalPolicy).as("policyName"),
|
db.ref("name").withSchema(TableName.SecretApprovalPolicy).as("policyName"),
|
||||||
@@ -201,7 +283,11 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
),
|
),
|
||||||
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
||||||
db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
|
db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
|
||||||
db.ref("approverId").withSchema(TableName.SecretApprovalPolicyApprover)
|
db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
|
||||||
|
db.ref("email").withSchema("committerUser").as("committerUserEmail"),
|
||||||
|
db.ref("username").withSchema("committerUser").as("committerUserUsername"),
|
||||||
|
db.ref("firstName").withSchema("committerUser").as("committerUserFirstName"),
|
||||||
|
db.ref("lastName").withSchema("committerUser").as("committerUserLastName")
|
||||||
)
|
)
|
||||||
.orderBy("createdAt", "desc");
|
.orderBy("createdAt", "desc");
|
||||||
|
|
||||||
@@ -223,18 +309,26 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
name: el.policyName,
|
name: el.policyName,
|
||||||
approvals: el.policyApprovals,
|
approvals: el.policyApprovals,
|
||||||
secretPath: el.policySecretPath
|
secretPath: el.policySecretPath
|
||||||
|
},
|
||||||
|
committerUser: {
|
||||||
|
userId: el.committerUserId,
|
||||||
|
email: el.committerUserEmail,
|
||||||
|
firstName: el.committerUserFirstName,
|
||||||
|
lastName: el.committerUserLastName,
|
||||||
|
username: el.committerUserUsername
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
childrenMapper: [
|
childrenMapper: [
|
||||||
{
|
{
|
||||||
key: "reviewerMemberId",
|
key: "reviewerId",
|
||||||
label: "reviewers" as const,
|
label: "reviewers" as const,
|
||||||
mapper: ({ reviewerMemberId: member, reviewerStatus: s }) => (member ? { member, status: s } : undefined)
|
mapper: ({ reviewerUserId, reviewerStatus: s }) =>
|
||||||
|
reviewerUserId ? { userId: reviewerUserId, status: s } : undefined
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "approverId",
|
key: "approverUserId",
|
||||||
label: "approvers" as const,
|
label: "approvers" as const,
|
||||||
mapper: ({ approverId }) => approverId
|
mapper: ({ approverUserId }) => approverUserId
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "commitId",
|
key: "commitId",
|
||||||
|
|||||||
+24
-30
@@ -87,7 +87,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
||||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||||
|
|
||||||
const { membership } = await permissionService.getProjectPermission(
|
await permissionService.getProjectPermission(
|
||||||
actor as ActorType.USER,
|
actor as ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -95,7 +95,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, membership.id);
|
const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, actorId);
|
||||||
return count;
|
return count;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -113,19 +113,13 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
}: TListApprovalsDTO) => {
|
}: TListApprovalsDTO) => {
|
||||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||||
|
|
||||||
const { membership } = await permissionService.getProjectPermission(
|
await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId);
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId
|
|
||||||
);
|
|
||||||
const approvals = await secretApprovalRequestDAL.findByProjectId({
|
const approvals = await secretApprovalRequestDAL.findByProjectId({
|
||||||
projectId,
|
projectId,
|
||||||
committer,
|
committer,
|
||||||
environment,
|
environment,
|
||||||
status,
|
status,
|
||||||
membershipId: membership.id,
|
userId: actorId,
|
||||||
limit,
|
limit,
|
||||||
offset
|
offset
|
||||||
});
|
});
|
||||||
@@ -145,7 +139,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
||||||
|
|
||||||
const { policy } = secretApprovalRequest;
|
const { policy } = secretApprovalRequest;
|
||||||
const { membership, hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
secretApprovalRequest.projectId,
|
secretApprovalRequest.projectId,
|
||||||
@@ -154,8 +148,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
!hasRole(ProjectMembershipRole.Admin) &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
secretApprovalRequest.committerId !== membership.id &&
|
secretApprovalRequest.committerUserId !== actorId &&
|
||||||
!policy.approvers.find((approverId) => approverId === membership.id)
|
!policy.approvers.find(({ userId }) => userId === actorId)
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError({ message: "User has no access" });
|
throw new UnauthorizedError({ message: "User has no access" });
|
||||||
}
|
}
|
||||||
@@ -180,7 +174,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||||
|
|
||||||
const { policy } = secretApprovalRequest;
|
const { policy } = secretApprovalRequest;
|
||||||
const { membership, hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission(
|
||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
secretApprovalRequest.projectId,
|
secretApprovalRequest.projectId,
|
||||||
@@ -189,8 +183,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
!hasRole(ProjectMembershipRole.Admin) &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
secretApprovalRequest.committerId !== membership.id &&
|
secretApprovalRequest.committerUserId !== actorId &&
|
||||||
!policy.approvers.find((approverId) => approverId === membership.id)
|
!policy.approvers.find(({ userId }) => userId === actorId)
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError({ message: "User has no access" });
|
throw new UnauthorizedError({ message: "User has no access" });
|
||||||
}
|
}
|
||||||
@@ -198,7 +192,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
const review = await secretApprovalRequestReviewerDAL.findOne(
|
const review = await secretApprovalRequestReviewerDAL.findOne(
|
||||||
{
|
{
|
||||||
requestId: secretApprovalRequest.id,
|
requestId: secretApprovalRequest.id,
|
||||||
member: membership.id
|
reviewerUserId: actorId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -207,7 +201,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
{
|
{
|
||||||
status,
|
status,
|
||||||
requestId: secretApprovalRequest.id,
|
requestId: secretApprovalRequest.id,
|
||||||
member: membership.id
|
reviewerUserId: actorId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -230,7 +224,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||||
|
|
||||||
const { policy } = secretApprovalRequest;
|
const { policy } = secretApprovalRequest;
|
||||||
const { membership, hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission(
|
||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
secretApprovalRequest.projectId,
|
secretApprovalRequest.projectId,
|
||||||
@@ -239,8 +233,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
!hasRole(ProjectMembershipRole.Admin) &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
secretApprovalRequest.committerId !== membership.id &&
|
secretApprovalRequest.committerUserId !== actorId &&
|
||||||
!policy.approvers.find((approverId) => approverId === membership.id)
|
!policy.approvers.find(({ userId }) => userId === actorId)
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError({ message: "User has no access" });
|
throw new UnauthorizedError({ message: "User has no access" });
|
||||||
}
|
}
|
||||||
@@ -253,7 +247,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
|
|
||||||
const updatedRequest = await secretApprovalRequestDAL.updateById(secretApprovalRequest.id, {
|
const updatedRequest = await secretApprovalRequestDAL.updateById(secretApprovalRequest.id, {
|
||||||
status,
|
status,
|
||||||
statusChangeBy: membership.id
|
statusChangedByUserId: actorId
|
||||||
});
|
});
|
||||||
return { ...secretApprovalRequest, ...updatedRequest };
|
return { ...secretApprovalRequest, ...updatedRequest };
|
||||||
};
|
};
|
||||||
@@ -270,7 +264,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||||
|
|
||||||
const { policy, folderId, projectId } = secretApprovalRequest;
|
const { policy, folderId, projectId } = secretApprovalRequest;
|
||||||
const { membership, hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission(
|
||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -280,19 +274,19 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
!hasRole(ProjectMembershipRole.Admin) &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
secretApprovalRequest.committerId !== membership.id &&
|
secretApprovalRequest.committerUserId !== actorId &&
|
||||||
!policy.approvers.find((approverId) => approverId === membership.id)
|
!policy.approvers.find(({ userId }) => userId === actorId)
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError({ message: "User has no access" });
|
throw new UnauthorizedError({ message: "User has no access" });
|
||||||
}
|
}
|
||||||
const reviewers = secretApprovalRequest.reviewers.reduce<Record<string, ApprovalStatus>>(
|
const reviewers = secretApprovalRequest.reviewers.reduce<Record<string, ApprovalStatus>>(
|
||||||
(prev, curr) => ({ ...prev, [curr.member.toString()]: curr.status as ApprovalStatus }),
|
(prev, curr) => ({ ...prev, [curr.userId.toString()]: curr.status as ApprovalStatus }),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
const hasMinApproval =
|
const hasMinApproval =
|
||||||
secretApprovalRequest.policy.approvals <=
|
secretApprovalRequest.policy.approvals <=
|
||||||
secretApprovalRequest.policy.approvers.filter(
|
secretApprovalRequest.policy.approvers.filter(
|
||||||
(approverId) => reviewers[approverId.toString()] === ApprovalStatus.APPROVED
|
({ userId: approverId }) => reviewers[approverId.toString()] === ApprovalStatus.APPROVED
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
if (!hasMinApproval) throw new BadRequestError({ message: "Doesn't have minimum approvals needed" });
|
if (!hasMinApproval) throw new BadRequestError({ message: "Doesn't have minimum approvals needed" });
|
||||||
@@ -472,7 +466,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
conflicts: JSON.stringify(conflicts),
|
conflicts: JSON.stringify(conflicts),
|
||||||
hasMerged: true,
|
hasMerged: true,
|
||||||
status: RequestState.Closed,
|
status: RequestState.Closed,
|
||||||
statusChangeBy: membership.id
|
statusChangedByUserId: actorId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -509,7 +503,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
}: TGenerateSecretApprovalRequestDTO) => {
|
}: TGenerateSecretApprovalRequestDTO) => {
|
||||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||||
|
|
||||||
const { permission, membership } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -663,7 +657,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
policyId: policy.id,
|
policyId: policy.id,
|
||||||
status: "open",
|
status: "open",
|
||||||
hasMerged: false,
|
hasMerged: false,
|
||||||
committerId: membership.id
|
committerUserId: actorId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { alphaNumericNanoId } from "@app/lib/nanoid";
|
|||||||
import { QueueName, TQueueServiceFactory } from "@app/queue";
|
import { QueueName, TQueueServiceFactory } from "@app/queue";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||||
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
|
||||||
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
||||||
import { fnSecretBulkInsert, fnSecretBulkUpdate } from "@app/services/secret/secret-fns";
|
import { fnSecretBulkInsert, fnSecretBulkUpdate } from "@app/services/secret/secret-fns";
|
||||||
import { TSecretQueueFactory, uniqueSecretQueueKey } from "@app/services/secret/secret-queue";
|
import { TSecretQueueFactory, uniqueSecretQueueKey } from "@app/services/secret/secret-queue";
|
||||||
@@ -46,7 +45,6 @@ type TSecretReplicationServiceFactoryDep = {
|
|||||||
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
|
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
|
||||||
secretTagDAL: Pick<TSecretTagDALFactory, "findManyTagsById" | "saveTagsToSecret" | "deleteTagsManySecret" | "find">;
|
secretTagDAL: Pick<TSecretTagDALFactory, "findManyTagsById" | "saveTagsToSecret" | "deleteTagsManySecret" | "find">;
|
||||||
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "create" | "transaction">;
|
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "create" | "transaction">;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findOne">;
|
|
||||||
secretApprovalRequestSecretDAL: Pick<
|
secretApprovalRequestSecretDAL: Pick<
|
||||||
TSecretApprovalRequestSecretDALFactory,
|
TSecretApprovalRequestSecretDALFactory,
|
||||||
"insertMany" | "insertApprovalSecretTags"
|
"insertMany" | "insertApprovalSecretTags"
|
||||||
@@ -92,7 +90,6 @@ export const secretReplicationServiceFactory = ({
|
|||||||
secretApprovalRequestSecretDAL,
|
secretApprovalRequestSecretDAL,
|
||||||
secretApprovalRequestDAL,
|
secretApprovalRequestDAL,
|
||||||
secretQueueService,
|
secretQueueService,
|
||||||
projectMembershipDAL,
|
|
||||||
projectBotService
|
projectBotService
|
||||||
}: TSecretReplicationServiceFactoryDep) => {
|
}: TSecretReplicationServiceFactoryDep) => {
|
||||||
const getReplicatedSecrets = (
|
const getReplicatedSecrets = (
|
||||||
@@ -297,12 +294,6 @@ export const secretReplicationServiceFactory = ({
|
|||||||
);
|
);
|
||||||
// this means it should be a approval request rather than direct replication
|
// this means it should be a approval request rather than direct replication
|
||||||
if (policy && actor === ActorType.USER) {
|
if (policy && actor === ActorType.USER) {
|
||||||
const membership = await projectMembershipDAL.findOne({ projectId, userId: actorId });
|
|
||||||
if (!membership) {
|
|
||||||
logger.error("Project membership not found in %s for user %s", projectId, actorId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const localSecretsLatestVersions = destinationLocalSecrets.map(({ id }) => id);
|
const localSecretsLatestVersions = destinationLocalSecrets.map(({ id }) => id);
|
||||||
const latestSecretVersions = await secretVersionDAL.findLatestVersionMany(
|
const latestSecretVersions = await secretVersionDAL.findLatestVersionMany(
|
||||||
destinationReplicationFolderId,
|
destinationReplicationFolderId,
|
||||||
@@ -316,7 +307,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
policyId: policy.id,
|
policyId: policy.id,
|
||||||
status: "open",
|
status: "open",
|
||||||
hasMerged: false,
|
hasMerged: false,
|
||||||
committerId: membership.id,
|
committerUserId: actorId,
|
||||||
isReplicated: true
|
isReplicated: true
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
|
|||||||
@@ -319,7 +319,6 @@ export const registerRoutes = async (
|
|||||||
auditLogStreamDAL
|
auditLogStreamDAL
|
||||||
});
|
});
|
||||||
const secretApprovalPolicyService = secretApprovalPolicyServiceFactory({
|
const secretApprovalPolicyService = secretApprovalPolicyServiceFactory({
|
||||||
projectMembershipDAL,
|
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
secretApprovalPolicyApproverDAL: sapApproverDAL,
|
secretApprovalPolicyApproverDAL: sapApproverDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
@@ -768,7 +767,6 @@ export const registerRoutes = async (
|
|||||||
secretApprovalRequestDAL,
|
secretApprovalRequestDAL,
|
||||||
secretApprovalRequestSecretDAL,
|
secretApprovalRequestSecretDAL,
|
||||||
secretQueueService,
|
secretQueueService,
|
||||||
projectMembershipDAL,
|
|
||||||
projectBotService
|
projectBotService
|
||||||
});
|
});
|
||||||
const secretRotationQueue = secretRotationQueueFactory({
|
const secretRotationQueue = secretRotationQueueFactory({
|
||||||
|
|||||||
@@ -949,7 +949,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
event: {
|
event: {
|
||||||
type: EventType.SECRET_APPROVAL_REQUEST,
|
type: EventType.SECRET_APPROVAL_REQUEST,
|
||||||
metadata: {
|
metadata: {
|
||||||
committedBy: approval.committerId,
|
committedBy: approval.committerUserId,
|
||||||
secretApprovalRequestId: approval.id,
|
secretApprovalRequestId: approval.id,
|
||||||
secretApprovalRequestSlug: approval.slug
|
secretApprovalRequestSlug: approval.slug
|
||||||
}
|
}
|
||||||
@@ -1133,7 +1133,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
event: {
|
event: {
|
||||||
type: EventType.SECRET_APPROVAL_REQUEST,
|
type: EventType.SECRET_APPROVAL_REQUEST,
|
||||||
metadata: {
|
metadata: {
|
||||||
committedBy: approval.committerId,
|
committedBy: approval.committerUserId,
|
||||||
secretApprovalRequestId: approval.id,
|
secretApprovalRequestId: approval.id,
|
||||||
secretApprovalRequestSlug: approval.slug
|
secretApprovalRequestSlug: approval.slug
|
||||||
}
|
}
|
||||||
@@ -1271,7 +1271,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
event: {
|
event: {
|
||||||
type: EventType.SECRET_APPROVAL_REQUEST,
|
type: EventType.SECRET_APPROVAL_REQUEST,
|
||||||
metadata: {
|
metadata: {
|
||||||
committedBy: approval.committerId,
|
committedBy: approval.committerUserId,
|
||||||
secretApprovalRequestId: approval.id,
|
secretApprovalRequestId: approval.id,
|
||||||
secretApprovalRequestSlug: approval.slug
|
secretApprovalRequestSlug: approval.slug
|
||||||
}
|
}
|
||||||
@@ -1397,7 +1397,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
event: {
|
event: {
|
||||||
type: EventType.SECRET_APPROVAL_REQUEST,
|
type: EventType.SECRET_APPROVAL_REQUEST,
|
||||||
metadata: {
|
metadata: {
|
||||||
committedBy: approval.committerId,
|
committedBy: approval.committerUserId,
|
||||||
secretApprovalRequestId: approval.id,
|
secretApprovalRequestId: approval.id,
|
||||||
secretApprovalRequestSlug: approval.slug
|
secretApprovalRequestSlug: approval.slug
|
||||||
}
|
}
|
||||||
@@ -1524,7 +1524,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
event: {
|
event: {
|
||||||
type: EventType.SECRET_APPROVAL_REQUEST,
|
type: EventType.SECRET_APPROVAL_REQUEST,
|
||||||
metadata: {
|
metadata: {
|
||||||
committedBy: approval.committerId,
|
committedBy: approval.committerUserId,
|
||||||
secretApprovalRequestId: approval.id,
|
secretApprovalRequestId: approval.id,
|
||||||
secretApprovalRequestSlug: approval.slug
|
secretApprovalRequestSlug: approval.slug
|
||||||
}
|
}
|
||||||
@@ -1638,7 +1638,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
event: {
|
event: {
|
||||||
type: EventType.SECRET_APPROVAL_REQUEST,
|
type: EventType.SECRET_APPROVAL_REQUEST,
|
||||||
metadata: {
|
metadata: {
|
||||||
committedBy: approval.committerId,
|
committedBy: approval.committerUserId,
|
||||||
secretApprovalRequestId: approval.id,
|
secretApprovalRequestId: approval.id,
|
||||||
secretApprovalRequestSlug: approval.slug
|
secretApprovalRequestSlug: approval.slug
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user