mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 21:27:10 +00:00
Allow multiple environments on secret and access policies
This commit is contained in:
Vendored
+21
@@ -489,6 +489,11 @@ import {
|
|||||||
TWorkflowIntegrationsInsert,
|
TWorkflowIntegrationsInsert,
|
||||||
TWorkflowIntegrationsUpdate
|
TWorkflowIntegrationsUpdate
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
|
import {
|
||||||
|
TAccessApprovalPoliciesEnvironments,
|
||||||
|
TAccessApprovalPoliciesEnvironmentsInsert,
|
||||||
|
TAccessApprovalPoliciesEnvironmentsUpdate
|
||||||
|
} from "@app/db/schemas/access-approval-policies-environments";
|
||||||
import {
|
import {
|
||||||
TIdentityLdapAuths,
|
TIdentityLdapAuths,
|
||||||
TIdentityLdapAuthsInsert,
|
TIdentityLdapAuthsInsert,
|
||||||
@@ -504,6 +509,11 @@ import {
|
|||||||
TProjectMicrosoftTeamsConfigsInsert,
|
TProjectMicrosoftTeamsConfigsInsert,
|
||||||
TProjectMicrosoftTeamsConfigsUpdate
|
TProjectMicrosoftTeamsConfigsUpdate
|
||||||
} from "@app/db/schemas/project-microsoft-teams-configs";
|
} from "@app/db/schemas/project-microsoft-teams-configs";
|
||||||
|
import {
|
||||||
|
TSecretApprovalPoliciesEnvironments,
|
||||||
|
TSecretApprovalPoliciesEnvironmentsInsert,
|
||||||
|
TSecretApprovalPoliciesEnvironmentsUpdate
|
||||||
|
} from "@app/db/schemas/secret-approval-policies-environments";
|
||||||
import {
|
import {
|
||||||
TSecretReminderRecipients,
|
TSecretReminderRecipients,
|
||||||
TSecretReminderRecipientsInsert,
|
TSecretReminderRecipientsInsert,
|
||||||
@@ -881,6 +891,12 @@ declare module "knex/types/tables" {
|
|||||||
TAccessApprovalPoliciesBypassersUpdate
|
TAccessApprovalPoliciesBypassersUpdate
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
[TableName.AccessApprovalPolicyEnvironment]: KnexOriginal.CompositeTableType<
|
||||||
|
TAccessApprovalPoliciesEnvironments,
|
||||||
|
TAccessApprovalPoliciesEnvironmentsInsert,
|
||||||
|
TAccessApprovalPoliciesEnvironmentsUpdate
|
||||||
|
>;
|
||||||
|
|
||||||
[TableName.AccessApprovalRequest]: KnexOriginal.CompositeTableType<
|
[TableName.AccessApprovalRequest]: KnexOriginal.CompositeTableType<
|
||||||
TAccessApprovalRequests,
|
TAccessApprovalRequests,
|
||||||
TAccessApprovalRequestsInsert,
|
TAccessApprovalRequestsInsert,
|
||||||
@@ -929,6 +945,11 @@ declare module "knex/types/tables" {
|
|||||||
TSecretApprovalRequestSecretTagsInsert,
|
TSecretApprovalRequestSecretTagsInsert,
|
||||||
TSecretApprovalRequestSecretTagsUpdate
|
TSecretApprovalRequestSecretTagsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.SecretApprovalPolicyEnvironment]: KnexOriginal.CompositeTableType<
|
||||||
|
TSecretApprovalPoliciesEnvironments,
|
||||||
|
TSecretApprovalPoliciesEnvironmentsInsert,
|
||||||
|
TSecretApprovalPoliciesEnvironmentsUpdate
|
||||||
|
>;
|
||||||
[TableName.SecretRotation]: KnexOriginal.CompositeTableType<
|
[TableName.SecretRotation]: KnexOriginal.CompositeTableType<
|
||||||
TSecretRotations,
|
TSecretRotations,
|
||||||
TSecretRotationsInsert,
|
TSecretRotationsInsert,
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { selectAllTableCols } from "@app/lib/knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasTable(TableName.AccessApprovalPolicyEnvironment))) {
|
||||||
|
await knex.schema.createTable(TableName.AccessApprovalPolicyEnvironment, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.uuid("policyId").notNullable();
|
||||||
|
t.foreign("policyId").references("id").inTable(TableName.AccessApprovalPolicy).onDelete("CASCADE");
|
||||||
|
t.uuid("envId").notNullable();
|
||||||
|
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!(await knex.schema.hasTable(TableName.SecretApprovalPolicyEnvironment))) {
|
||||||
|
await knex.schema.createTable(TableName.SecretApprovalPolicyEnvironment, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.uuid("policyId").notNullable();
|
||||||
|
t.foreign("policyId").references("id").inTable(TableName.SecretApprovalPolicy).onDelete("CASCADE");
|
||||||
|
t.uuid("envId").notNullable();
|
||||||
|
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (t) => {
|
||||||
|
t.dropForeign(["envId"]);
|
||||||
|
|
||||||
|
// Add the new foreign key constraint with ON DELETE SET NULL
|
||||||
|
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("SET NULL");
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.SecretApprovalPolicy, (t) => {
|
||||||
|
t.dropForeign(["envId"]);
|
||||||
|
|
||||||
|
// Add the new foreign key constraint with ON DELETE SET NULL
|
||||||
|
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("SET NULL");
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.AccessApprovalPolicyEnvironment);
|
||||||
|
await createOnUpdateTrigger(knex, TableName.SecretApprovalPolicyEnvironment);
|
||||||
|
|
||||||
|
const existingAccessApprovalPolicies = await knex(TableName.AccessApprovalPolicy)
|
||||||
|
.select(selectAllTableCols(TableName.AccessApprovalPolicy))
|
||||||
|
.whereNotNull(`${TableName.AccessApprovalPolicy}.envId`);
|
||||||
|
|
||||||
|
const accessApprovalPolicies = existingAccessApprovalPolicies.map(async (policy) => {
|
||||||
|
await knex(TableName.AccessApprovalPolicyEnvironment).insert({
|
||||||
|
policyId: policy.id,
|
||||||
|
envId: policy.envId
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(accessApprovalPolicies);
|
||||||
|
|
||||||
|
const existingSecretApprovalPolicies = await knex(TableName.SecretApprovalPolicy)
|
||||||
|
.select(selectAllTableCols(TableName.SecretApprovalPolicy))
|
||||||
|
.whereNotNull(`${TableName.SecretApprovalPolicy}.envId`);
|
||||||
|
|
||||||
|
const secretApprovalPolicies = existingSecretApprovalPolicies.map(async (policy) => {
|
||||||
|
await knex(TableName.SecretApprovalPolicyEnvironment).insert({
|
||||||
|
policyId: policy.id,
|
||||||
|
envId: policy.envId
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(secretApprovalPolicies);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.AccessApprovalPolicyEnvironment)) {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.AccessApprovalPolicyEnvironment);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.AccessApprovalPolicyEnvironment);
|
||||||
|
}
|
||||||
|
if (await knex.schema.hasTable(TableName.SecretApprovalPolicyEnvironment)) {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SecretApprovalPolicyEnvironment);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.SecretApprovalPolicyEnvironment);
|
||||||
|
}
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (t) => {
|
||||||
|
t.dropForeign(["envId"]);
|
||||||
|
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.SecretApprovalPolicy, (t) => {
|
||||||
|
t.dropForeign(["envId"]);
|
||||||
|
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const AccessApprovalPoliciesEnvironmentsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
policyId: z.string().uuid(),
|
||||||
|
envId: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TAccessApprovalPoliciesEnvironments = z.infer<typeof AccessApprovalPoliciesEnvironmentsSchema>;
|
||||||
|
export type TAccessApprovalPoliciesEnvironmentsInsert = Omit<
|
||||||
|
z.input<typeof AccessApprovalPoliciesEnvironmentsSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TAccessApprovalPoliciesEnvironmentsUpdate = Partial<
|
||||||
|
Omit<z.input<typeof AccessApprovalPoliciesEnvironmentsSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
||||||
@@ -100,6 +100,7 @@ export enum TableName {
|
|||||||
AccessApprovalPolicyBypasser = "access_approval_policies_bypassers",
|
AccessApprovalPolicyBypasser = "access_approval_policies_bypassers",
|
||||||
AccessApprovalRequest = "access_approval_requests",
|
AccessApprovalRequest = "access_approval_requests",
|
||||||
AccessApprovalRequestReviewer = "access_approval_requests_reviewers",
|
AccessApprovalRequestReviewer = "access_approval_requests_reviewers",
|
||||||
|
AccessApprovalPolicyEnvironment = "access_approval_policies_environments",
|
||||||
SecretApprovalPolicy = "secret_approval_policies",
|
SecretApprovalPolicy = "secret_approval_policies",
|
||||||
SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
|
SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
|
||||||
SecretApprovalPolicyBypasser = "secret_approval_policies_bypassers",
|
SecretApprovalPolicyBypasser = "secret_approval_policies_bypassers",
|
||||||
@@ -107,6 +108,7 @@ export enum TableName {
|
|||||||
SecretApprovalRequestReviewer = "secret_approval_requests_reviewers",
|
SecretApprovalRequestReviewer = "secret_approval_requests_reviewers",
|
||||||
SecretApprovalRequestSecret = "secret_approval_requests_secrets",
|
SecretApprovalRequestSecret = "secret_approval_requests_secrets",
|
||||||
SecretApprovalRequestSecretTag = "secret_approval_request_secret_tags",
|
SecretApprovalRequestSecretTag = "secret_approval_request_secret_tags",
|
||||||
|
SecretApprovalPolicyEnvironment = "secret_approval_policies_environments",
|
||||||
SecretRotation = "secret_rotations",
|
SecretRotation = "secret_rotations",
|
||||||
SecretRotationOutput = "secret_rotation_outputs",
|
SecretRotationOutput = "secret_rotation_outputs",
|
||||||
SamlConfig = "saml_configs",
|
SamlConfig = "saml_configs",
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const SecretApprovalPoliciesEnvironmentsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
policyId: z.string().uuid(),
|
||||||
|
envId: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSecretApprovalPoliciesEnvironments = z.infer<typeof SecretApprovalPoliciesEnvironmentsSchema>;
|
||||||
|
export type TSecretApprovalPoliciesEnvironmentsInsert = Omit<
|
||||||
|
z.input<typeof SecretApprovalPoliciesEnvironmentsSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TSecretApprovalPoliciesEnvironmentsUpdate = Partial<
|
||||||
|
Omit<z.input<typeof SecretApprovalPoliciesEnvironmentsSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
||||||
@@ -17,52 +17,66 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z
|
||||||
projectSlug: z.string().trim(),
|
.object({
|
||||||
name: z.string().optional(),
|
projectSlug: z.string().trim(),
|
||||||
secretPath: z.string().trim().min(1, { message: "Secret path cannot be empty" }).transform(removeTrailingSlash),
|
name: z.string().optional(),
|
||||||
environment: z.string(),
|
secretPath: z
|
||||||
approvers: z
|
.string()
|
||||||
.discriminatedUnion("type", [
|
.trim()
|
||||||
z.object({
|
.min(1, { message: "Secret path cannot be empty" })
|
||||||
type: z.literal(ApproverType.Group),
|
.transform(removeTrailingSlash),
|
||||||
id: z.string(),
|
environment: z.string().optional(),
|
||||||
sequence: z.number().int().default(1)
|
environments: z.string().array().optional(),
|
||||||
}),
|
approvers: z
|
||||||
z.object({
|
.discriminatedUnion("type", [
|
||||||
type: z.literal(ApproverType.User),
|
z.object({
|
||||||
id: z.string().optional(),
|
type: z.literal(ApproverType.Group),
|
||||||
username: z.string().optional(),
|
id: z.string(),
|
||||||
sequence: z.number().int().default(1)
|
sequence: z.number().int().default(1)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
type: z.literal(ApproverType.User),
|
||||||
|
id: z.string().optional(),
|
||||||
|
username: z.string().optional(),
|
||||||
|
sequence: z.number().int().default(1)
|
||||||
|
})
|
||||||
|
])
|
||||||
|
.array()
|
||||||
|
.max(100, "Cannot have more than 100 approvers")
|
||||||
|
.min(1, { message: "At least one approver should be provided" })
|
||||||
|
.refine(
|
||||||
|
// @ts-expect-error this is ok
|
||||||
|
(el) => el.every((i) => Boolean(i?.id) || Boolean(i?.username)),
|
||||||
|
"Must provide either username or id"
|
||||||
|
),
|
||||||
|
bypassers: z
|
||||||
|
.discriminatedUnion("type", [
|
||||||
|
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
|
||||||
|
z.object({
|
||||||
|
type: z.literal(BypasserType.User),
|
||||||
|
id: z.string().optional(),
|
||||||
|
username: z.string().optional()
|
||||||
|
})
|
||||||
|
])
|
||||||
|
.array()
|
||||||
|
.max(100, "Cannot have more than 100 bypassers")
|
||||||
|
.optional(),
|
||||||
|
approvalsRequired: z
|
||||||
|
.object({
|
||||||
|
numberOfApprovals: z.number().int(),
|
||||||
|
stepNumber: z.number().int()
|
||||||
})
|
})
|
||||||
])
|
.array()
|
||||||
.array()
|
.optional(),
|
||||||
.max(100, "Cannot have more than 100 approvers")
|
approvals: z.number().min(1).default(1),
|
||||||
.min(1, { message: "At least one approver should be provided" })
|
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
||||||
.refine(
|
allowedSelfApprovals: z.boolean().default(true)
|
||||||
// @ts-expect-error this is ok
|
})
|
||||||
(el) => el.every((i) => Boolean(i?.id) || Boolean(i?.username)),
|
.refine(
|
||||||
"Must provide either username or id"
|
(val) => Boolean(val.environment) || Boolean(val.environments),
|
||||||
),
|
"Must provide either environment or environments"
|
||||||
bypassers: z
|
),
|
||||||
.discriminatedUnion("type", [
|
|
||||||
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
|
|
||||||
z.object({ type: z.literal(BypasserType.User), id: z.string().optional(), username: z.string().optional() })
|
|
||||||
])
|
|
||||||
.array()
|
|
||||||
.max(100, "Cannot have more than 100 bypassers")
|
|
||||||
.optional(),
|
|
||||||
approvalsRequired: z
|
|
||||||
.object({
|
|
||||||
numberOfApprovals: z.number().int(),
|
|
||||||
stepNumber: z.number().int()
|
|
||||||
})
|
|
||||||
.array()
|
|
||||||
.optional(),
|
|
||||||
approvals: z.number().min(1).default(1),
|
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
|
||||||
allowedSelfApprovals: z.boolean().default(true)
|
|
||||||
}),
|
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
approval: sapPubSchema
|
approval: sapPubSchema
|
||||||
@@ -78,7 +92,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.body,
|
...req.body,
|
||||||
projectSlug: req.body.projectSlug,
|
projectSlug: req.body.projectSlug,
|
||||||
name: req.body.name ?? `${req.body.environment}-${nanoid(3)}`,
|
name: req.body.name ?? `${req.body.environment || req.body.environments?.join("-")}-${nanoid(3)}`,
|
||||||
enforcementLevel: req.body.enforcementLevel
|
enforcementLevel: req.body.enforcementLevel
|
||||||
});
|
});
|
||||||
return { approval };
|
return { approval };
|
||||||
@@ -211,6 +225,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
approvals: z.number().min(1).optional(),
|
approvals: z.number().min(1).optional(),
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
||||||
allowedSelfApprovals: z.boolean().default(true),
|
allowedSelfApprovals: z.boolean().default(true),
|
||||||
|
environments: z.array(z.string()).optional(),
|
||||||
approvalsRequired: z
|
approvalsRequired: z
|
||||||
.object({
|
.object({
|
||||||
numberOfApprovals: z.number().int(),
|
numberOfApprovals: z.number().int(),
|
||||||
|
|||||||
@@ -17,34 +17,45 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z
|
||||||
workspaceId: z.string(),
|
.object({
|
||||||
name: z.string().optional(),
|
workspaceId: z.string(),
|
||||||
environment: z.string(),
|
name: z.string().optional(),
|
||||||
secretPath: z
|
environment: z.string().optional(),
|
||||||
.string()
|
environments: z.string().array().optional(),
|
||||||
.min(1, { message: "Secret path cannot be empty" })
|
secretPath: z
|
||||||
.transform((val) => removeTrailingSlash(val)),
|
.string()
|
||||||
approvers: z
|
.min(1, { message: "Secret path cannot be empty" })
|
||||||
.discriminatedUnion("type", [
|
.transform((val) => removeTrailingSlash(val)),
|
||||||
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
approvers: z
|
||||||
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), username: z.string().optional() })
|
.discriminatedUnion("type", [
|
||||||
])
|
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
||||||
.array()
|
z.object({
|
||||||
.min(1, { message: "At least one approver should be provided" })
|
type: z.literal(ApproverType.User),
|
||||||
.max(100, "Cannot have more than 100 approvers"),
|
id: z.string().optional(),
|
||||||
bypassers: z
|
username: z.string().optional()
|
||||||
.discriminatedUnion("type", [
|
})
|
||||||
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
|
])
|
||||||
z.object({ type: z.literal(BypasserType.User), id: z.string().optional(), username: z.string().optional() })
|
.array()
|
||||||
])
|
.min(1, { message: "At least one approver should be provided" })
|
||||||
.array()
|
.max(100, "Cannot have more than 100 approvers"),
|
||||||
.max(100, "Cannot have more than 100 bypassers")
|
bypassers: z
|
||||||
.optional(),
|
.discriminatedUnion("type", [
|
||||||
approvals: z.number().min(1).default(1),
|
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
z.object({
|
||||||
allowedSelfApprovals: z.boolean().default(true)
|
type: z.literal(BypasserType.User),
|
||||||
}),
|
id: z.string().optional(),
|
||||||
|
username: z.string().optional()
|
||||||
|
})
|
||||||
|
])
|
||||||
|
.array()
|
||||||
|
.max(100, "Cannot have more than 100 bypassers")
|
||||||
|
.optional(),
|
||||||
|
approvals: z.number().min(1).default(1),
|
||||||
|
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
||||||
|
allowedSelfApprovals: z.boolean().default(true)
|
||||||
|
})
|
||||||
|
.refine((data) => data.environment || data.environments, "At least one environment should be provided"),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
approval: sapPubSchema
|
approval: sapPubSchema
|
||||||
@@ -60,7 +71,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
projectId: req.body.workspaceId,
|
projectId: req.body.workspaceId,
|
||||||
...req.body,
|
...req.body,
|
||||||
name: req.body.name ?? `${req.body.environment}-${nanoid(3)}`,
|
name: req.body.name ?? `${req.body.environment || req.body.environments?.join(",")}-${nanoid(3)}`,
|
||||||
enforcementLevel: req.body.enforcementLevel
|
enforcementLevel: req.body.enforcementLevel
|
||||||
});
|
});
|
||||||
return { approval };
|
return { approval };
|
||||||
@@ -103,7 +114,8 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
.optional()
|
.optional()
|
||||||
.transform((val) => (val ? removeTrailingSlash(val) : undefined)),
|
.transform((val) => (val ? removeTrailingSlash(val) : undefined)),
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).optional(),
|
enforcementLevel: z.nativeEnum(EnforcementLevel).optional(),
|
||||||
allowedSelfApprovals: z.boolean().default(true)
|
allowedSelfApprovals: z.boolean().default(true),
|
||||||
|
environments: z.array(z.string()).optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export interface TAccessApprovalPolicyDALFactory
|
|||||||
>,
|
>,
|
||||||
customFilter?: {
|
customFilter?: {
|
||||||
policyId?: string;
|
policyId?: string;
|
||||||
|
envId?: string;
|
||||||
},
|
},
|
||||||
tx?: Knex
|
tx?: Knex
|
||||||
) => Promise<
|
) => Promise<
|
||||||
@@ -55,11 +56,6 @@ export interface TAccessApprovalPolicyDALFactory
|
|||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
environment: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
};
|
|
||||||
projectId: string;
|
projectId: string;
|
||||||
bypassers: (
|
bypassers: (
|
||||||
| {
|
| {
|
||||||
@@ -72,6 +68,11 @@ export interface TAccessApprovalPolicyDALFactory
|
|||||||
type: BypasserType.Group;
|
type: BypasserType.Group;
|
||||||
}
|
}
|
||||||
)[];
|
)[];
|
||||||
|
environments: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
}[];
|
||||||
}[]
|
}[]
|
||||||
>;
|
>;
|
||||||
findById: (
|
findById: (
|
||||||
@@ -95,11 +96,11 @@ export interface TAccessApprovalPolicyDALFactory
|
|||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
environment: {
|
environments: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
};
|
}[];
|
||||||
projectId: string;
|
projectId: string;
|
||||||
}
|
}
|
||||||
| undefined
|
| undefined
|
||||||
@@ -143,6 +144,26 @@ export interface TAccessApprovalPolicyDALFactory
|
|||||||
}
|
}
|
||||||
| undefined
|
| undefined
|
||||||
>;
|
>;
|
||||||
|
findPoliciesByEnvIdAndSecretPath: (
|
||||||
|
{ envIds, secretPath }: { envIds: string[]; secretPath: string },
|
||||||
|
tx?: Knex
|
||||||
|
) => Promise<{
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
approvals: number;
|
||||||
|
enforcementLevel: string;
|
||||||
|
allowedSelfApprovals: boolean;
|
||||||
|
secretPath: string;
|
||||||
|
deletedAt?: Date | null | undefined;
|
||||||
|
environments: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
}[];
|
||||||
|
projectId: string;
|
||||||
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TAccessApprovalPolicyServiceFactory {
|
export interface TAccessApprovalPolicyServiceFactory {
|
||||||
@@ -367,6 +388,7 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
|
|||||||
filter: TFindFilter<TAccessApprovalPolicies & { projectId: string }>,
|
filter: TFindFilter<TAccessApprovalPolicies & { projectId: string }>,
|
||||||
customFilter?: {
|
customFilter?: {
|
||||||
policyId?: string;
|
policyId?: string;
|
||||||
|
envId?: string;
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
const result = await tx(TableName.AccessApprovalPolicy)
|
const result = await tx(TableName.AccessApprovalPolicy)
|
||||||
@@ -377,7 +399,17 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
|
|||||||
void qb.where(`${TableName.AccessApprovalPolicy}.id`, "=", customFilter.policyId);
|
void qb.where(`${TableName.AccessApprovalPolicy}.id`, "=", customFilter.policyId);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.join(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
.where((qb) => {
|
||||||
|
if (customFilter?.envId) {
|
||||||
|
void qb.where(`${TableName.AccessApprovalPolicyEnvironment}.envId`, "=", customFilter.envId);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join(
|
||||||
|
TableName.AccessApprovalPolicyEnvironment,
|
||||||
|
`${TableName.AccessApprovalPolicy}.id`,
|
||||||
|
`${TableName.AccessApprovalPolicyEnvironment}.policyId`
|
||||||
|
)
|
||||||
|
.join(TableName.Environment, `${TableName.AccessApprovalPolicyEnvironment}.envId`, `${TableName.Environment}.id`)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.AccessApprovalPolicyApprover,
|
TableName.AccessApprovalPolicyApprover,
|
||||||
`${TableName.AccessApprovalPolicy}.id`,
|
`${TableName.AccessApprovalPolicy}.id`,
|
||||||
@@ -404,7 +436,7 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
|
|||||||
.select(tx.ref("bypasserGroupId").withSchema(TableName.AccessApprovalPolicyBypasser))
|
.select(tx.ref("bypasserGroupId").withSchema(TableName.AccessApprovalPolicyBypasser))
|
||||||
.select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
|
.select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
|
||||||
.select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug"))
|
.select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug"))
|
||||||
.select(tx.ref("id").withSchema(TableName.Environment).as("envId"))
|
.select(tx.ref("id").withSchema(TableName.Environment).as("environmentId"))
|
||||||
.select(tx.ref("projectId").withSchema(TableName.Environment))
|
.select(tx.ref("projectId").withSchema(TableName.Environment))
|
||||||
.select(selectAllTableCols(TableName.AccessApprovalPolicy));
|
.select(selectAllTableCols(TableName.AccessApprovalPolicy));
|
||||||
|
|
||||||
@@ -448,6 +480,15 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
|
|||||||
sequence: approverSequence,
|
sequence: approverSequence,
|
||||||
approvalsRequired
|
approvalsRequired
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "environmentId",
|
||||||
|
label: "environments" as const,
|
||||||
|
mapper: ({ environmentId: id, envName, envSlug }) => ({
|
||||||
|
id,
|
||||||
|
name: envName,
|
||||||
|
slug: envSlug
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -470,11 +511,6 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
|
|||||||
data: docs,
|
data: docs,
|
||||||
key: "id",
|
key: "id",
|
||||||
parentMapper: (data) => ({
|
parentMapper: (data) => ({
|
||||||
environment: {
|
|
||||||
id: data.envId,
|
|
||||||
name: data.envName,
|
|
||||||
slug: data.envSlug
|
|
||||||
},
|
|
||||||
projectId: data.projectId,
|
projectId: data.projectId,
|
||||||
...AccessApprovalPoliciesSchema.parse(data)
|
...AccessApprovalPoliciesSchema.parse(data)
|
||||||
// secretPath: data.secretPath || undefined,
|
// secretPath: data.secretPath || undefined,
|
||||||
@@ -517,6 +553,15 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
|
|||||||
id,
|
id,
|
||||||
type: BypasserType.Group as const
|
type: BypasserType.Group as const
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "environmentId",
|
||||||
|
label: "environments" as const,
|
||||||
|
mapper: ({ environmentId: id, envName, envSlug }) => ({
|
||||||
|
id,
|
||||||
|
name: envName,
|
||||||
|
slug: envSlug
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -545,14 +590,20 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
buildFindFilter(
|
buildFindFilter(
|
||||||
{
|
{
|
||||||
envId,
|
|
||||||
secretPath
|
secretPath
|
||||||
},
|
},
|
||||||
TableName.AccessApprovalPolicy
|
TableName.AccessApprovalPolicy
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
.join(
|
||||||
|
TableName.AccessApprovalPolicyEnvironment,
|
||||||
|
`${TableName.AccessApprovalPolicyEnvironment}.policyId`,
|
||||||
|
`${TableName.AccessApprovalPolicy}.id`
|
||||||
|
)
|
||||||
|
.where(`${TableName.AccessApprovalPolicyEnvironment}.envId`, "=", envId)
|
||||||
.orderBy("deletedAt", "desc")
|
.orderBy("deletedAt", "desc")
|
||||||
.orderByRaw(`"deletedAt" IS NULL`)
|
.orderByRaw(`"deletedAt" IS NULL`)
|
||||||
|
.select(selectAllTableCols(TableName.AccessApprovalPolicy))
|
||||||
.first();
|
.first();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -561,5 +612,81 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...accessApprovalPolicyOrm, find, findById, softDeleteById, findLastValidPolicy };
|
const findPoliciesByEnvIdAndSecretPath: TAccessApprovalPolicyDALFactory["findPoliciesByEnvIdAndSecretPath"] = async (
|
||||||
|
{ envIds, secretPath },
|
||||||
|
tx
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const docs = await (tx || db.replicaNode())(TableName.AccessApprovalPolicy)
|
||||||
|
.join(
|
||||||
|
TableName.AccessApprovalPolicyEnvironment,
|
||||||
|
`${TableName.AccessApprovalPolicyEnvironment}.policyId`,
|
||||||
|
`${TableName.AccessApprovalPolicy}.id`
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
TableName.Environment,
|
||||||
|
`${TableName.AccessApprovalPolicyEnvironment}.envId`,
|
||||||
|
`${TableName.Environment}.id`
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
buildFindFilter(
|
||||||
|
{
|
||||||
|
$in: {
|
||||||
|
envId: envIds
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TableName.AccessApprovalPolicyEnvironment
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
buildFindFilter(
|
||||||
|
{
|
||||||
|
secretPath
|
||||||
|
},
|
||||||
|
TableName.AccessApprovalPolicy
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.whereNull(`${TableName.AccessApprovalPolicy}.deletedAt`)
|
||||||
|
.orderBy("deletedAt", "desc")
|
||||||
|
.orderByRaw(`"deletedAt" IS NULL`)
|
||||||
|
.select(selectAllTableCols(TableName.AccessApprovalPolicy))
|
||||||
|
.select(db.ref("name").withSchema(TableName.Environment).as("envName"))
|
||||||
|
.select(db.ref("slug").withSchema(TableName.Environment).as("envSlug"))
|
||||||
|
.select(db.ref("id").withSchema(TableName.Environment).as("environmentId"))
|
||||||
|
.select(db.ref("projectId").withSchema(TableName.Environment));
|
||||||
|
const formattedDocs = sqlNestRelationships({
|
||||||
|
data: docs,
|
||||||
|
key: "id",
|
||||||
|
parentMapper: (data) => ({
|
||||||
|
projectId: data.projectId,
|
||||||
|
...AccessApprovalPoliciesSchema.parse(data)
|
||||||
|
}),
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "environmentId",
|
||||||
|
label: "environments" as const,
|
||||||
|
mapper: ({ environmentId: id, envName, envSlug }) => ({
|
||||||
|
id,
|
||||||
|
name: envName,
|
||||||
|
slug: envSlug
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
return formattedDocs?.[0];
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "FindPoliciesByEnvIdAndSecretPath" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...accessApprovalPolicyOrm,
|
||||||
|
find,
|
||||||
|
findById,
|
||||||
|
softDeleteById,
|
||||||
|
findLastValidPolicy,
|
||||||
|
findPoliciesByEnvIdAndSecretPath
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TAccessApprovalPolicyEnvironmentDALFactory = ReturnType<typeof accessApprovalPolicyEnvironmentDALFactory>;
|
||||||
|
|
||||||
|
export const accessApprovalPolicyEnvironmentDALFactory = (db: TDbClient) => {
|
||||||
|
const accessApprovalPolicyEnvironmentOrm = ormify(db, TableName.AccessApprovalPolicyEnvironment);
|
||||||
|
|
||||||
|
const findAvailablePoliciesIds = async (envId: string, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const docs = await (tx || db.replicaNode())(TableName.AccessApprovalPolicyEnvironment)
|
||||||
|
.join(
|
||||||
|
TableName.AccessApprovalPolicy,
|
||||||
|
`${TableName.AccessApprovalPolicyEnvironment}.policyId`,
|
||||||
|
`${TableName.AccessApprovalPolicy}.id`
|
||||||
|
)
|
||||||
|
.where({ [`${TableName.AccessApprovalPolicyEnvironment}.envId` as "envId"]: envId })
|
||||||
|
.whereNull(`${TableName.AccessApprovalPolicy}.deletedAt`)
|
||||||
|
.select(selectAllTableCols(TableName.AccessApprovalPolicyEnvironment));
|
||||||
|
return docs;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "findAvailablePoliciesIds" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...accessApprovalPolicyEnvironmentOrm, findAvailablePoliciesIds };
|
||||||
|
};
|
||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
TAccessApprovalPolicyBypasserDALFactory
|
TAccessApprovalPolicyBypasserDALFactory
|
||||||
} from "./access-approval-policy-approver-dal";
|
} from "./access-approval-policy-approver-dal";
|
||||||
import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal";
|
import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal";
|
||||||
|
import { TAccessApprovalPolicyEnvironmentDALFactory } from "./access-approval-policy-environment-dal";
|
||||||
import {
|
import {
|
||||||
ApproverType,
|
ApproverType,
|
||||||
BypasserType,
|
BypasserType,
|
||||||
@@ -44,12 +45,14 @@ type TAccessApprovalPolicyServiceFactoryDep = {
|
|||||||
additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
||||||
accessApprovalRequestReviewerDAL: Pick<TAccessApprovalRequestReviewerDALFactory, "update" | "delete">;
|
accessApprovalRequestReviewerDAL: Pick<TAccessApprovalRequestReviewerDALFactory, "update" | "delete">;
|
||||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find">;
|
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find">;
|
||||||
|
accessApprovalPolicyEnvironmentDAL: TAccessApprovalPolicyEnvironmentDALFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const accessApprovalPolicyServiceFactory = ({
|
export const accessApprovalPolicyServiceFactory = ({
|
||||||
accessApprovalPolicyDAL,
|
accessApprovalPolicyDAL,
|
||||||
accessApprovalPolicyApproverDAL,
|
accessApprovalPolicyApproverDAL,
|
||||||
accessApprovalPolicyBypasserDAL,
|
accessApprovalPolicyBypasserDAL,
|
||||||
|
accessApprovalPolicyEnvironmentDAL,
|
||||||
groupDAL,
|
groupDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
@@ -62,21 +65,22 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
}: TAccessApprovalPolicyServiceFactoryDep): TAccessApprovalPolicyServiceFactory => {
|
}: TAccessApprovalPolicyServiceFactoryDep): TAccessApprovalPolicyServiceFactory => {
|
||||||
const $policyExists = async ({
|
const $policyExists = async ({
|
||||||
envId,
|
envId,
|
||||||
|
envIds,
|
||||||
secretPath,
|
secretPath,
|
||||||
policyId
|
policyId
|
||||||
}: {
|
}: {
|
||||||
envId: string;
|
envId?: string;
|
||||||
|
envIds?: string[];
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
policyId?: string;
|
policyId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const policy = await accessApprovalPolicyDAL
|
if (!envId && !envIds) {
|
||||||
.findOne({
|
throw new BadRequestError({ message: "Must provide either envId or envIds" });
|
||||||
envId,
|
}
|
||||||
secretPath,
|
const policy = await accessApprovalPolicyDAL.findPoliciesByEnvIdAndSecretPath({
|
||||||
deletedAt: null
|
secretPath,
|
||||||
})
|
envIds: envId ? [envId] : envIds || []
|
||||||
.catch(() => null);
|
});
|
||||||
|
|
||||||
return policyId ? policy && policy.id !== policyId : Boolean(policy);
|
return policyId ? policy && policy.id !== policyId : Boolean(policy);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -92,6 +96,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
bypassers,
|
bypassers,
|
||||||
projectSlug,
|
projectSlug,
|
||||||
environment,
|
environment,
|
||||||
|
environments,
|
||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals,
|
allowedSelfApprovals,
|
||||||
approvalsRequired
|
approvalsRequired
|
||||||
@@ -123,13 +128,23 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
);
|
);
|
||||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
|
const mergedEnvs = (environment ? [environment] : environments) || [];
|
||||||
if (!env) throw new NotFoundError({ message: `Environment with slug '${environment}' not found` });
|
if (mergedEnvs.length === 0) {
|
||||||
|
throw new BadRequestError({ message: "Must provide either environment or environments" });
|
||||||
|
}
|
||||||
|
const envs = await projectEnvDAL.find({ $in: { slug: mergedEnvs }, projectId: project.id });
|
||||||
|
if (!envs.length || envs.length !== mergedEnvs.length) {
|
||||||
|
const notFoundEnvs = mergedEnvs.filter((env) => !envs.find((el) => el.slug === env));
|
||||||
|
throw new NotFoundError({ message: `One or more environments not found: ${notFoundEnvs.join(", ")}` });
|
||||||
|
}
|
||||||
|
|
||||||
if (await $policyExists({ envId: env.id, secretPath })) {
|
for (const env of envs) {
|
||||||
throw new BadRequestError({
|
// eslint-disable-next-line no-await-in-loop
|
||||||
message: `A policy for secret path '${secretPath}' already exists in environment '${environment}'`
|
if (await $policyExists({ envId: env.id, secretPath })) {
|
||||||
});
|
throw new BadRequestError({
|
||||||
|
message: `A policy for secret path '${secretPath}' already exists in environment '${env.slug}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let approverUserIds = userApprovers;
|
let approverUserIds = userApprovers;
|
||||||
@@ -197,7 +212,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
|
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
|
||||||
const doc = await accessApprovalPolicyDAL.create(
|
const doc = await accessApprovalPolicyDAL.create(
|
||||||
{
|
{
|
||||||
envId: env.id,
|
envId: envs[0].id,
|
||||||
approvals,
|
approvals,
|
||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
@@ -206,6 +221,10 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
await accessApprovalPolicyEnvironmentDAL.insertMany(
|
||||||
|
envs.map((el) => ({ policyId: doc.id, envId: el.id })),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
if (approverUserIds.length) {
|
if (approverUserIds.length) {
|
||||||
await accessApprovalPolicyApproverDAL.insertMany(
|
await accessApprovalPolicyApproverDAL.insertMany(
|
||||||
@@ -258,7 +277,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
return doc;
|
return doc;
|
||||||
});
|
});
|
||||||
|
|
||||||
return { ...accessApproval, environment: env, projectId: project.id };
|
return { ...accessApproval, environments: envs, projectId: project.id, environment: envs[0] };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAccessApprovalPolicyByProjectSlug: TAccessApprovalPolicyServiceFactory["getAccessApprovalPolicyByProjectSlug"] =
|
const getAccessApprovalPolicyByProjectSlug: TAccessApprovalPolicyServiceFactory["getAccessApprovalPolicyByProjectSlug"] =
|
||||||
@@ -276,7 +295,10 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null });
|
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null });
|
||||||
return accessApprovalPolicies;
|
return accessApprovalPolicies.map((policy) => ({
|
||||||
|
...policy,
|
||||||
|
environment: policy.environments[0]
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateAccessApprovalPolicy: TAccessApprovalPolicyServiceFactory["updateAccessApprovalPolicy"] = async ({
|
const updateAccessApprovalPolicy: TAccessApprovalPolicyServiceFactory["updateAccessApprovalPolicy"] = async ({
|
||||||
@@ -292,7 +314,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
approvals,
|
approvals,
|
||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals,
|
allowedSelfApprovals,
|
||||||
approvalsRequired
|
approvalsRequired,
|
||||||
|
environments
|
||||||
}: TUpdateAccessApprovalPolicy) => {
|
}: TUpdateAccessApprovalPolicy) => {
|
||||||
const groupApprovers = approvers.filter((approver) => approver.type === ApproverType.Group);
|
const groupApprovers = approvers.filter((approver) => approver.type === ApproverType.Group);
|
||||||
|
|
||||||
@@ -320,15 +343,23 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let envs = accessApprovalPolicy.environments;
|
||||||
|
if (
|
||||||
|
environments &&
|
||||||
|
(environments.length !== envs.length || environments.some((env) => !envs.find((el) => el.slug === env)))
|
||||||
|
) {
|
||||||
|
envs = await projectEnvDAL.find({ $in: { slug: environments }, projectId: accessApprovalPolicy.projectId });
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
await $policyExists({
|
await $policyExists({
|
||||||
envId: accessApprovalPolicy.envId,
|
envIds: envs.map((env) => env.id),
|
||||||
secretPath: secretPath || accessApprovalPolicy.secretPath,
|
secretPath: secretPath || accessApprovalPolicy.secretPath,
|
||||||
policyId: accessApprovalPolicy.id
|
policyId: accessApprovalPolicy.id
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: `A policy for secret path '${secretPath}' already exists in environment '${accessApprovalPolicy.environment.slug}'`
|
message: `A policy for secret path '${secretPath}' already exists`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,6 +515,14 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (environments) {
|
||||||
|
await accessApprovalPolicyEnvironmentDAL.delete({ policyId: doc.id }, tx);
|
||||||
|
await accessApprovalPolicyEnvironmentDAL.insertMany(
|
||||||
|
envs.map((env) => ({ policyId: doc.id, envId: env.id })),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await accessApprovalPolicyBypasserDAL.delete({ policyId: doc.id }, tx);
|
await accessApprovalPolicyBypasserDAL.delete({ policyId: doc.id }, tx);
|
||||||
|
|
||||||
if (bypasserUserIds.length) {
|
if (bypasserUserIds.length) {
|
||||||
@@ -513,7 +552,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...updatedPolicy,
|
...updatedPolicy,
|
||||||
environment: accessApprovalPolicy.environment,
|
environments: accessApprovalPolicy.environments,
|
||||||
|
environment: accessApprovalPolicy.environments[0],
|
||||||
projectId: accessApprovalPolicy.projectId
|
projectId: accessApprovalPolicy.projectId
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -563,7 +603,10 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return policy;
|
return {
|
||||||
|
...policy,
|
||||||
|
environment: policy.environments[0]
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAccessPolicyCountByEnvSlug: TAccessApprovalPolicyServiceFactory["getAccessPolicyCountByEnvSlug"] = async ({
|
const getAccessPolicyCountByEnvSlug: TAccessApprovalPolicyServiceFactory["getAccessPolicyCountByEnvSlug"] = async ({
|
||||||
@@ -592,11 +635,13 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug });
|
const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug });
|
||||||
if (!environment) throw new NotFoundError({ message: `Environment with slug '${envSlug}' not found` });
|
if (!environment) throw new NotFoundError({ message: `Environment with slug '${envSlug}' not found` });
|
||||||
|
|
||||||
const policies = await accessApprovalPolicyDAL.find({
|
const policies = await accessApprovalPolicyDAL.find(
|
||||||
envId: environment.id,
|
{
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
deletedAt: null
|
deletedAt: null
|
||||||
});
|
},
|
||||||
|
{ envId: environment.id }
|
||||||
|
);
|
||||||
if (!policies) throw new NotFoundError({ message: `No policies found in environment with slug '${envSlug}'` });
|
if (!policies) throw new NotFoundError({ message: `No policies found in environment with slug '${envSlug}'` });
|
||||||
|
|
||||||
return { count: policies.length };
|
return { count: policies.length };
|
||||||
@@ -627,7 +672,10 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
return policy;
|
return {
|
||||||
|
...policy,
|
||||||
|
environment: policy.environments[0]
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ export enum BypasserType {
|
|||||||
export type TCreateAccessApprovalPolicy = {
|
export type TCreateAccessApprovalPolicy = {
|
||||||
approvals: number;
|
approvals: number;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
environment: string;
|
environment?: string;
|
||||||
|
environments?: string[];
|
||||||
approvers: (
|
approvers: (
|
||||||
| { type: ApproverType.Group; id: string; sequence?: number }
|
| { type: ApproverType.Group; id: string; sequence?: number }
|
||||||
| { type: ApproverType.User; id?: string; username?: string; sequence?: number }
|
| { type: ApproverType.User; id?: string; username?: string; sequence?: number }
|
||||||
@@ -58,6 +59,7 @@ export type TUpdateAccessApprovalPolicy = {
|
|||||||
enforcementLevel?: EnforcementLevel;
|
enforcementLevel?: EnforcementLevel;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
|
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
|
||||||
|
environments?: string[];
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TDeleteAccessApprovalPolicy = {
|
export type TDeleteAccessApprovalPolicy = {
|
||||||
@@ -113,6 +115,15 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
slug: string;
|
slug: string;
|
||||||
position: number;
|
position: number;
|
||||||
};
|
};
|
||||||
|
environments: {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
projectId: string;
|
||||||
|
slug: string;
|
||||||
|
position: number;
|
||||||
|
}[];
|
||||||
projectId: string;
|
projectId: string;
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
@@ -153,6 +164,11 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
|
environments: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
}[];
|
||||||
projectId: string;
|
projectId: string;
|
||||||
}>;
|
}>;
|
||||||
updateAccessApprovalPolicy: ({
|
updateAccessApprovalPolicy: ({
|
||||||
@@ -168,13 +184,19 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
approvals,
|
approvals,
|
||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals,
|
allowedSelfApprovals,
|
||||||
approvalsRequired
|
approvalsRequired,
|
||||||
|
environments
|
||||||
}: TUpdateAccessApprovalPolicy) => Promise<{
|
}: TUpdateAccessApprovalPolicy) => Promise<{
|
||||||
environment: {
|
environment: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
|
environments: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
}[];
|
||||||
projectId: string;
|
projectId: string;
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
@@ -225,6 +247,11 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
|
environments: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
}[];
|
||||||
projectId: string;
|
projectId: string;
|
||||||
bypassers: (
|
bypassers: (
|
||||||
| {
|
| {
|
||||||
@@ -276,6 +303,11 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
|
environments: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
}[];
|
||||||
projectId: string;
|
projectId: string;
|
||||||
bypassers: (
|
bypassers: (
|
||||||
| {
|
| {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export interface TAccessApprovalRequestDALFactory extends Omit<TOrmify<TableName
|
|||||||
deletedAt: Date | null | undefined;
|
deletedAt: Date | null | undefined;
|
||||||
};
|
};
|
||||||
projectId: string;
|
projectId: string;
|
||||||
environment: string;
|
environments: string[];
|
||||||
requestedByUser: {
|
requestedByUser: {
|
||||||
userId: string;
|
userId: string;
|
||||||
email: string | null | undefined;
|
email: string | null | undefined;
|
||||||
@@ -515,7 +515,17 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
|||||||
`accessApprovalReviewerUser.id`
|
`accessApprovalReviewerUser.id`
|
||||||
)
|
)
|
||||||
|
|
||||||
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
.leftJoin(
|
||||||
|
TableName.AccessApprovalPolicyEnvironment,
|
||||||
|
`${TableName.AccessApprovalPolicy}.id`,
|
||||||
|
`${TableName.AccessApprovalPolicyEnvironment}.policyId`
|
||||||
|
)
|
||||||
|
|
||||||
|
.leftJoin(
|
||||||
|
TableName.Environment,
|
||||||
|
`${TableName.AccessApprovalPolicyEnvironment}.envId`,
|
||||||
|
`${TableName.Environment}.id`
|
||||||
|
)
|
||||||
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
||||||
.select(
|
.select(
|
||||||
tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover),
|
tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover),
|
||||||
@@ -683,6 +693,11 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
|
|||||||
lastName,
|
lastName,
|
||||||
username
|
username
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "environment",
|
||||||
|
label: "environments" as const,
|
||||||
|
mapper: ({ environment }) => environment
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -86,6 +86,25 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
projectMicrosoftTeamsConfigDAL,
|
projectMicrosoftTeamsConfigDAL,
|
||||||
projectSlackConfigDAL
|
projectSlackConfigDAL
|
||||||
}: TSecretApprovalRequestServiceFactoryDep): TAccessApprovalRequestServiceFactory => {
|
}: TSecretApprovalRequestServiceFactoryDep): TAccessApprovalRequestServiceFactory => {
|
||||||
|
const $getEnvironmentFromPermissions = (permissions: unknown): string | null => {
|
||||||
|
if (!Array.isArray(permissions) || permissions.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstPermission = permissions[0] as unknown[];
|
||||||
|
if (!Array.isArray(firstPermission) || firstPermission.length < 3) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadata = firstPermission[2] as Record<string, unknown>;
|
||||||
|
if (typeof metadata === "object" && metadata !== null && "environment" in metadata) {
|
||||||
|
const env = metadata.environment;
|
||||||
|
return typeof env === "string" ? env : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
const createAccessApprovalRequest: TAccessApprovalRequestServiceFactory["createAccessApprovalRequest"] = async ({
|
const createAccessApprovalRequest: TAccessApprovalRequestServiceFactory["createAccessApprovalRequest"] = async ({
|
||||||
isTemporary,
|
isTemporary,
|
||||||
temporaryRange,
|
temporaryRange,
|
||||||
@@ -323,13 +342,27 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
throw new NotFoundError({ message: `Secret approval request with ID '${requestId}' not found` });
|
throw new NotFoundError({ message: `Secret approval request with ID '${requestId}' not found` });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { policy, environment } = accessApprovalRequest;
|
const { policy, environments, permissions } = accessApprovalRequest;
|
||||||
if (policy.deletedAt) {
|
if (policy.deletedAt) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "The policy associated with this access request has been deleted."
|
message: "The policy associated with this access request has been deleted."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const permissionEnvironment = $getEnvironmentFromPermissions(permissions);
|
||||||
|
if (
|
||||||
|
!permissionEnvironment ||
|
||||||
|
(!environments.includes(permissionEnvironment) && status === ApprovalStatus.APPROVED)
|
||||||
|
) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `The original policy ${policy.name} is not attached to environment '${permissionEnvironment}'.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const environment = await projectEnvDAL.findOne({
|
||||||
|
projectId: accessApprovalRequest.projectId,
|
||||||
|
slug: permissionEnvironment
|
||||||
|
});
|
||||||
|
|
||||||
const { membership, hasRole } = await permissionService.getProjectPermission({
|
const { membership, hasRole } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -550,7 +583,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
requesterEmail: actingUser.email,
|
requesterEmail: actingUser.email,
|
||||||
bypassReason: bypassReason || "No reason provided",
|
bypassReason: bypassReason || "No reason provided",
|
||||||
secretPath: policy.secretPath || "/",
|
secretPath: policy.secretPath || "/",
|
||||||
environment,
|
environment: environment?.name || permissionEnvironment,
|
||||||
approvalUrl: `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval`,
|
approvalUrl: `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval`,
|
||||||
requestType: "access"
|
requestType: "access"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>,
|
filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>,
|
||||||
customFilter?: {
|
customFilter?: {
|
||||||
sapId?: string;
|
sapId?: string;
|
||||||
|
envId?: string;
|
||||||
}
|
}
|
||||||
) =>
|
) =>
|
||||||
tx(TableName.SecretApprovalPolicy)
|
tx(TableName.SecretApprovalPolicy)
|
||||||
@@ -33,7 +34,17 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
void qb.where(`${TableName.SecretApprovalPolicy}.id`, "=", customFilter.sapId);
|
void qb.where(`${TableName.SecretApprovalPolicy}.id`, "=", customFilter.sapId);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.join(TableName.Environment, `${TableName.SecretApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
.join(
|
||||||
|
TableName.SecretApprovalPolicyEnvironment,
|
||||||
|
`${TableName.SecretApprovalPolicyEnvironment}.policyId`,
|
||||||
|
`${TableName.SecretApprovalPolicy}.id`
|
||||||
|
)
|
||||||
|
.join(TableName.Environment, `${TableName.SecretApprovalPolicyEnvironment}.envId`, `${TableName.Environment}.id`)
|
||||||
|
.where((qb) => {
|
||||||
|
if (customFilter?.envId) {
|
||||||
|
void qb.where(`${TableName.SecretApprovalPolicyEnvironment}.envId`, "=", customFilter.envId);
|
||||||
|
}
|
||||||
|
})
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.SecretApprovalPolicyApprover,
|
TableName.SecretApprovalPolicyApprover,
|
||||||
`${TableName.SecretApprovalPolicy}.id`,
|
`${TableName.SecretApprovalPolicy}.id`,
|
||||||
@@ -97,7 +108,7 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
.select(
|
.select(
|
||||||
tx.ref("name").withSchema(TableName.Environment).as("envName"),
|
tx.ref("name").withSchema(TableName.Environment).as("envName"),
|
||||||
tx.ref("slug").withSchema(TableName.Environment).as("envSlug"),
|
tx.ref("slug").withSchema(TableName.Environment).as("envSlug"),
|
||||||
tx.ref("id").withSchema(TableName.Environment).as("envId"),
|
tx.ref("id").withSchema(TableName.Environment).as("environmentId"),
|
||||||
tx.ref("projectId").withSchema(TableName.Environment)
|
tx.ref("projectId").withSchema(TableName.Environment)
|
||||||
)
|
)
|
||||||
.select(selectAllTableCols(TableName.SecretApprovalPolicy))
|
.select(selectAllTableCols(TableName.SecretApprovalPolicy))
|
||||||
@@ -146,6 +157,15 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
firstName,
|
firstName,
|
||||||
lastName
|
lastName
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "environmentId",
|
||||||
|
label: "environments" as const,
|
||||||
|
mapper: ({ environmentId, envName, envSlug }) => ({
|
||||||
|
id: environmentId,
|
||||||
|
name: envName,
|
||||||
|
slug: envSlug
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -160,6 +180,7 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>,
|
filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>,
|
||||||
customFilter?: {
|
customFilter?: {
|
||||||
sapId?: string;
|
sapId?: string;
|
||||||
|
envId?: string;
|
||||||
},
|
},
|
||||||
tx?: Knex
|
tx?: Knex
|
||||||
) => {
|
) => {
|
||||||
@@ -221,6 +242,15 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
mapper: ({ approverGroupUserId: userId }) => ({
|
mapper: ({ approverGroupUserId: userId }) => ({
|
||||||
userId
|
userId
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "environmentId",
|
||||||
|
label: "environments" as const,
|
||||||
|
mapper: ({ environmentId, envName, envSlug }) => ({
|
||||||
|
id: environmentId,
|
||||||
|
name: envName,
|
||||||
|
slug: envSlug
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -235,5 +265,74 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
return softDeletedPolicy;
|
return softDeletedPolicy;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...secretApprovalPolicyOrm, findById, find, softDeleteById };
|
const findPoliciesByEnvIdAndSecretPath = async (
|
||||||
|
{ envIds, secretPath }: { envIds: string[]; secretPath: string },
|
||||||
|
tx?: Knex
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const docs = await (tx || db.replicaNode())(TableName.SecretApprovalPolicy)
|
||||||
|
.join(
|
||||||
|
TableName.SecretApprovalPolicyEnvironment,
|
||||||
|
`${TableName.SecretApprovalPolicyEnvironment}.policyId`,
|
||||||
|
`${TableName.SecretApprovalPolicy}.id`
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
TableName.Environment,
|
||||||
|
`${TableName.SecretApprovalPolicyEnvironment}.envId`,
|
||||||
|
`${TableName.Environment}.id`
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
buildFindFilter(
|
||||||
|
{
|
||||||
|
$in: {
|
||||||
|
envId: envIds
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TableName.SecretApprovalPolicyEnvironment
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
buildFindFilter(
|
||||||
|
{
|
||||||
|
secretPath
|
||||||
|
},
|
||||||
|
TableName.SecretApprovalPolicy
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.whereNull(`${TableName.SecretApprovalPolicy}.deletedAt`)
|
||||||
|
.orderBy("deletedAt", "desc")
|
||||||
|
.orderByRaw(`"deletedAt" IS NULL`)
|
||||||
|
.select(selectAllTableCols(TableName.SecretApprovalPolicy))
|
||||||
|
.select(db.ref("name").withSchema(TableName.Environment).as("envName"))
|
||||||
|
.select(db.ref("slug").withSchema(TableName.Environment).as("envSlug"))
|
||||||
|
.select(db.ref("id").withSchema(TableName.Environment).as("environmentId"))
|
||||||
|
.select(db.ref("projectId").withSchema(TableName.Environment));
|
||||||
|
const formattedDocs = sqlNestRelationships({
|
||||||
|
data: docs,
|
||||||
|
key: "id",
|
||||||
|
parentMapper: (data) => ({
|
||||||
|
projectId: data.projectId,
|
||||||
|
...SecretApprovalPoliciesSchema.parse(data)
|
||||||
|
}),
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "environmentId",
|
||||||
|
label: "environments" as const,
|
||||||
|
mapper: ({ environmentId: id, envName, envSlug }) => ({
|
||||||
|
id,
|
||||||
|
name: envName,
|
||||||
|
slug: envSlug
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
return formattedDocs?.[0];
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "FindPoliciesByEnvIdAndSecretPath" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...secretApprovalPolicyOrm, findById, find, softDeleteById, findPoliciesByEnvIdAndSecretPath };
|
||||||
};
|
};
|
||||||
|
|||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TSecretApprovalPolicyEnvironmentDALFactory = ReturnType<typeof secretApprovalPolicyEnvironmentDALFactory>;
|
||||||
|
|
||||||
|
export const secretApprovalPolicyEnvironmentDALFactory = (db: TDbClient) => {
|
||||||
|
const secretApprovalPolicyEnvironmentOrm = ormify(db, TableName.SecretApprovalPolicyEnvironment);
|
||||||
|
|
||||||
|
const findAvailablePoliciesIds = async (envId: string, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const docs = await (tx || db.replicaNode())(TableName.SecretApprovalPolicyEnvironment)
|
||||||
|
.join(
|
||||||
|
TableName.SecretApprovalPolicy,
|
||||||
|
`${TableName.SecretApprovalPolicyEnvironment}.policyId`,
|
||||||
|
`${TableName.SecretApprovalPolicy}.id`
|
||||||
|
)
|
||||||
|
.where({ [`${TableName.SecretApprovalPolicyEnvironment}.envId` as "envId"]: envId })
|
||||||
|
.whereNull(`${TableName.SecretApprovalPolicy}.deletedAt`)
|
||||||
|
.select(selectAllTableCols(TableName.SecretApprovalPolicyEnvironment));
|
||||||
|
return docs;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "findAvailablePoliciesIds" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...secretApprovalPolicyEnvironmentOrm, findAvailablePoliciesIds };
|
||||||
|
};
|
||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
TSecretApprovalPolicyBypasserDALFactory
|
TSecretApprovalPolicyBypasserDALFactory
|
||||||
} from "./secret-approval-policy-approver-dal";
|
} from "./secret-approval-policy-approver-dal";
|
||||||
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
|
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
|
||||||
|
import { TSecretApprovalPolicyEnvironmentDALFactory } from "./secret-approval-policy-environment-dal";
|
||||||
import {
|
import {
|
||||||
TCreateSapDTO,
|
TCreateSapDTO,
|
||||||
TDeleteSapDTO,
|
TDeleteSapDTO,
|
||||||
@@ -35,12 +36,13 @@ const getPolicyScore = (policy: { secretPath?: string | null }) =>
|
|||||||
type TSecretApprovalPolicyServiceFactoryDep = {
|
type TSecretApprovalPolicyServiceFactoryDep = {
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory;
|
secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory;
|
||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne" | "find">;
|
||||||
userDAL: Pick<TUserDALFactory, "find">;
|
userDAL: Pick<TUserDALFactory, "find">;
|
||||||
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
|
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
|
||||||
secretApprovalPolicyBypasserDAL: TSecretApprovalPolicyBypasserDALFactory;
|
secretApprovalPolicyBypasserDAL: TSecretApprovalPolicyBypasserDALFactory;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "update">;
|
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "update">;
|
||||||
|
secretApprovalPolicyEnvironmentDAL: TSecretApprovalPolicyEnvironmentDALFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretApprovalPolicyServiceFactory = ReturnType<typeof secretApprovalPolicyServiceFactory>;
|
export type TSecretApprovalPolicyServiceFactory = ReturnType<typeof secretApprovalPolicyServiceFactory>;
|
||||||
@@ -50,27 +52,30 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
permissionService,
|
permissionService,
|
||||||
secretApprovalPolicyApproverDAL,
|
secretApprovalPolicyApproverDAL,
|
||||||
secretApprovalPolicyBypasserDAL,
|
secretApprovalPolicyBypasserDAL,
|
||||||
|
secretApprovalPolicyEnvironmentDAL,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
userDAL,
|
userDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
secretApprovalRequestDAL
|
secretApprovalRequestDAL
|
||||||
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
||||||
const $policyExists = async ({
|
const $policyExists = async ({
|
||||||
|
envIds,
|
||||||
envId,
|
envId,
|
||||||
secretPath,
|
secretPath,
|
||||||
policyId
|
policyId
|
||||||
}: {
|
}: {
|
||||||
envId: string;
|
envIds?: string[];
|
||||||
|
envId?: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
policyId?: string;
|
policyId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const policy = await secretApprovalPolicyDAL
|
if (!envIds && !envId) {
|
||||||
.findOne({
|
throw new BadRequestError({ message: "At least one environment should be provided" });
|
||||||
envId,
|
}
|
||||||
secretPath,
|
const policy = await secretApprovalPolicyDAL.findPoliciesByEnvIdAndSecretPath({
|
||||||
deletedAt: null
|
envIds: envId ? [envId] : envIds || [],
|
||||||
})
|
secretPath
|
||||||
.catch(() => null);
|
});
|
||||||
|
|
||||||
return policyId ? policy && policy.id !== policyId : Boolean(policy);
|
return policyId ? policy && policy.id !== policyId : Boolean(policy);
|
||||||
};
|
};
|
||||||
@@ -87,6 +92,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
projectId,
|
projectId,
|
||||||
secretPath,
|
secretPath,
|
||||||
environment,
|
environment,
|
||||||
|
environments,
|
||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals
|
allowedSelfApprovals
|
||||||
}: TCreateSapDTO) => {
|
}: TCreateSapDTO) => {
|
||||||
@@ -125,17 +131,23 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
const mergedEnvs = (environment ? [environment] : environments) || [];
|
||||||
if (!env) {
|
if (mergedEnvs.length === 0) {
|
||||||
throw new NotFoundError({
|
throw new BadRequestError({ message: "Must provide either environment or environments" });
|
||||||
message: `Environment with slug '${environment}' not found in project with ID ${projectId}`
|
}
|
||||||
});
|
const envs = await projectEnvDAL.find({ $in: { slug: mergedEnvs }, projectId });
|
||||||
|
if (!envs.length || envs.length !== mergedEnvs.length) {
|
||||||
|
const notFoundEnvs = mergedEnvs.filter((env) => !envs.find((el) => el.slug === env));
|
||||||
|
throw new NotFoundError({ message: `One or more environments not found: ${notFoundEnvs.join(", ")}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await $policyExists({ envId: env.id, secretPath })) {
|
for (const env of envs) {
|
||||||
throw new BadRequestError({
|
// eslint-disable-next-line no-await-in-loop
|
||||||
message: `A policy for secret path '${secretPath}' already exists in environment '${environment}'`
|
if (await $policyExists({ envId: env.id, secretPath })) {
|
||||||
});
|
throw new BadRequestError({
|
||||||
|
message: `A policy for secret path '${secretPath}' already exists in environment '${env.slug}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let groupBypassers: string[] = [];
|
let groupBypassers: string[] = [];
|
||||||
@@ -179,7 +191,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
const secretApproval = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
const secretApproval = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
||||||
const doc = await secretApprovalPolicyDAL.create(
|
const doc = await secretApprovalPolicyDAL.create(
|
||||||
{
|
{
|
||||||
envId: env.id,
|
envId: envs[0].id,
|
||||||
approvals,
|
approvals,
|
||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
@@ -188,6 +200,13 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
await secretApprovalPolicyEnvironmentDAL.insertMany(
|
||||||
|
envs.map((env) => ({
|
||||||
|
envId: env.id,
|
||||||
|
policyId: doc.id
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
let userApproverIds = userApprovers;
|
let userApproverIds = userApprovers;
|
||||||
if (userApproverNames.length) {
|
if (userApproverNames.length) {
|
||||||
@@ -251,12 +270,13 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
return doc;
|
return doc;
|
||||||
});
|
});
|
||||||
|
|
||||||
return { ...secretApproval, environment: env, projectId };
|
return { ...secretApproval, environments: envs, projectId, environment: envs[0] };
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateSecretApprovalPolicy = async ({
|
const updateSecretApprovalPolicy = async ({
|
||||||
approvers,
|
approvers,
|
||||||
bypassers,
|
bypassers,
|
||||||
|
environments,
|
||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -286,16 +306,22 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
message: `Secret approval policy with ID '${secretPolicyId}' not found`
|
message: `Secret approval policy with ID '${secretPolicyId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
let envs = secretApprovalPolicy.environments;
|
||||||
|
if (
|
||||||
|
environments &&
|
||||||
|
(environments.length !== envs.length || environments.some((env) => !envs.find((el) => el.slug === env)))
|
||||||
|
) {
|
||||||
|
envs = await projectEnvDAL.find({ $in: { slug: environments }, projectId: secretApprovalPolicy.projectId });
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
await $policyExists({
|
await $policyExists({
|
||||||
envId: secretApprovalPolicy.envId,
|
envIds: envs.map((env) => env.id),
|
||||||
secretPath: secretPath || secretApprovalPolicy.secretPath,
|
secretPath: secretPath || secretApprovalPolicy.secretPath,
|
||||||
policyId: secretApprovalPolicy.id
|
policyId: secretApprovalPolicy.id
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: `A policy for secret path '${secretPath}' already exists in environment '${secretApprovalPolicy.environment.slug}'`
|
message: `A policy for secret path '${secretPath}' already exists`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,6 +438,17 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (environments) {
|
||||||
|
await secretApprovalPolicyEnvironmentDAL.delete({ policyId: doc.id }, tx);
|
||||||
|
await secretApprovalPolicyEnvironmentDAL.insertMany(
|
||||||
|
envs.map((env) => ({
|
||||||
|
envId: env.id,
|
||||||
|
policyId: doc.id
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await secretApprovalPolicyBypasserDAL.delete({ policyId: doc.id }, tx);
|
await secretApprovalPolicyBypasserDAL.delete({ policyId: doc.id }, tx);
|
||||||
|
|
||||||
if (bypasserUserIds.length) {
|
if (bypasserUserIds.length) {
|
||||||
@@ -438,7 +475,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
...updatedSap,
|
...updatedSap,
|
||||||
environment: secretApprovalPolicy.environment,
|
environments: secretApprovalPolicy.environments,
|
||||||
|
environment: secretApprovalPolicy.environments[0],
|
||||||
projectId: secretApprovalPolicy.projectId
|
projectId: secretApprovalPolicy.projectId
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -483,7 +521,12 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
const updatedPolicy = await secretApprovalPolicyDAL.softDeleteById(secretPolicyId, tx);
|
const updatedPolicy = await secretApprovalPolicyDAL.softDeleteById(secretPolicyId, tx);
|
||||||
return updatedPolicy;
|
return updatedPolicy;
|
||||||
});
|
});
|
||||||
return { ...deletedPolicy, projectId: sapPolicy.projectId, environment: sapPolicy.environment };
|
return {
|
||||||
|
...deletedPolicy,
|
||||||
|
projectId: sapPolicy.projectId,
|
||||||
|
environments: sapPolicy.environments,
|
||||||
|
environment: sapPolicy.environments[0]
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSecretApprovalPolicyByProjectId = async ({
|
const getSecretApprovalPolicyByProjectId = async ({
|
||||||
@@ -515,7 +558,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const policies = await secretApprovalPolicyDAL.find({ envId: env.id, deletedAt: null });
|
const policies = await secretApprovalPolicyDAL.find({ deletedAt: null }, { envId: env.id });
|
||||||
if (!policies.length) return;
|
if (!policies.length) return;
|
||||||
// this will filter policies either without scoped to secret path or the one that matches with secret path
|
// this will filter policies either without scoped to secret path or the one that matches with secret path
|
||||||
const policiesFilteredByPath = policies.filter(
|
const policiesFilteredByPath = policies.filter(
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import { ApproverType, BypasserType } from "../access-approval-policy/access-app
|
|||||||
export type TCreateSapDTO = {
|
export type TCreateSapDTO = {
|
||||||
approvals: number;
|
approvals: number;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
environment: string;
|
environment?: string;
|
||||||
|
environments?: string[];
|
||||||
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
|
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
|
||||||
bypassers?: (
|
bypassers?: (
|
||||||
| { type: BypasserType.Group; id: string }
|
| { type: BypasserType.Group; id: string }
|
||||||
@@ -29,6 +30,7 @@ export type TUpdateSapDTO = {
|
|||||||
name?: string;
|
name?: string;
|
||||||
enforcementLevel?: EnforcementLevel;
|
enforcementLevel?: EnforcementLevel;
|
||||||
allowedSelfApprovals?: boolean;
|
allowedSelfApprovals?: boolean;
|
||||||
|
environments?: string[];
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TDeleteSapDTO = {
|
export type TDeleteSapDTO = {
|
||||||
|
|||||||
@@ -40,6 +40,13 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.SecretApprovalRequest}.policyId`,
|
`${TableName.SecretApprovalRequest}.policyId`,
|
||||||
`${TableName.SecretApprovalPolicy}.id`
|
`${TableName.SecretApprovalPolicy}.id`
|
||||||
)
|
)
|
||||||
|
.leftJoin(TableName.SecretApprovalPolicyEnvironment, (bd) => {
|
||||||
|
bd.on(
|
||||||
|
`${TableName.SecretApprovalPolicy}.id`,
|
||||||
|
"=",
|
||||||
|
`${TableName.SecretApprovalPolicyEnvironment}.policyId`
|
||||||
|
).andOn(`${TableName.SecretApprovalPolicyEnvironment}.envId`, "=", `${TableName.SecretFolder}.envId`);
|
||||||
|
})
|
||||||
.leftJoin<TUsers>(
|
.leftJoin<TUsers>(
|
||||||
db(TableName.Users).as("statusChangedByUser"),
|
db(TableName.Users).as("statusChangedByUser"),
|
||||||
`${TableName.SecretApprovalRequest}.statusChangedByUserId`,
|
`${TableName.SecretApprovalRequest}.statusChangedByUserId`,
|
||||||
@@ -146,7 +153,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
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("envId").withSchema(TableName.SecretApprovalPolicy).as("policyEnvId"),
|
tx.ref("envId").withSchema(TableName.SecretApprovalPolicyEnvironment).as("policyEnvId"),
|
||||||
tx.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
tx.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
||||||
tx.ref("allowedSelfApprovals").withSchema(TableName.SecretApprovalPolicy).as("policyAllowedSelfApprovals"),
|
tx.ref("allowedSelfApprovals").withSchema(TableName.SecretApprovalPolicy).as("policyAllowedSelfApprovals"),
|
||||||
tx.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
|
tx.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
|
||||||
|
|||||||
@@ -531,6 +531,11 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
message: "The policy associated with this secret approval request has been deleted."
|
message: "The policy associated with this secret approval request has been deleted."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (!policy.envId) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The policy associated with this secret approval request is not linked to the environment."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { hasRole } = await permissionService.getProjectPermission({
|
const { hasRole } = await permissionService.getProjectPermission({
|
||||||
actor: ActorType.USER,
|
actor: ActorType.USER,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
accessApprovalPolicyBypasserDALFactory
|
accessApprovalPolicyBypasserDALFactory
|
||||||
} from "@app/ee/services/access-approval-policy/access-approval-policy-approver-dal";
|
} from "@app/ee/services/access-approval-policy/access-approval-policy-approver-dal";
|
||||||
import { accessApprovalPolicyDALFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-dal";
|
import { accessApprovalPolicyDALFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-dal";
|
||||||
|
import { accessApprovalPolicyEnvironmentDALFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-environment-dal";
|
||||||
import { accessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service";
|
import { accessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service";
|
||||||
import { accessApprovalRequestDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-dal";
|
import { accessApprovalRequestDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-dal";
|
||||||
import { accessApprovalRequestReviewerDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-reviewer-dal";
|
import { accessApprovalRequestReviewerDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-reviewer-dal";
|
||||||
@@ -76,6 +77,7 @@ import {
|
|||||||
secretApprovalPolicyBypasserDALFactory
|
secretApprovalPolicyBypasserDALFactory
|
||||||
} from "@app/ee/services/secret-approval-policy/secret-approval-policy-approver-dal";
|
} from "@app/ee/services/secret-approval-policy/secret-approval-policy-approver-dal";
|
||||||
import { secretApprovalPolicyDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-dal";
|
import { secretApprovalPolicyDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-dal";
|
||||||
|
import { secretApprovalPolicyEnvironmentDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-environment-dal";
|
||||||
import { secretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
import { secretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
||||||
import { secretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
|
import { secretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
|
||||||
import { secretApprovalRequestReviewerDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-reviewer-dal";
|
import { secretApprovalRequestReviewerDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-reviewer-dal";
|
||||||
@@ -418,9 +420,11 @@ export const registerRoutes = async (
|
|||||||
const accessApprovalPolicyApproverDAL = accessApprovalPolicyApproverDALFactory(db);
|
const accessApprovalPolicyApproverDAL = accessApprovalPolicyApproverDALFactory(db);
|
||||||
const accessApprovalPolicyBypasserDAL = accessApprovalPolicyBypasserDALFactory(db);
|
const accessApprovalPolicyBypasserDAL = accessApprovalPolicyBypasserDALFactory(db);
|
||||||
const accessApprovalRequestReviewerDAL = accessApprovalRequestReviewerDALFactory(db);
|
const accessApprovalRequestReviewerDAL = accessApprovalRequestReviewerDALFactory(db);
|
||||||
|
const accessApprovalPolicyEnvironmentDAL = accessApprovalPolicyEnvironmentDALFactory(db);
|
||||||
|
|
||||||
const sapApproverDAL = secretApprovalPolicyApproverDALFactory(db);
|
const sapApproverDAL = secretApprovalPolicyApproverDALFactory(db);
|
||||||
const sapBypasserDAL = secretApprovalPolicyBypasserDALFactory(db);
|
const sapBypasserDAL = secretApprovalPolicyBypasserDALFactory(db);
|
||||||
|
const sapEnvironmentDAL = secretApprovalPolicyEnvironmentDALFactory(db);
|
||||||
const secretApprovalPolicyDAL = secretApprovalPolicyDALFactory(db);
|
const secretApprovalPolicyDAL = secretApprovalPolicyDALFactory(db);
|
||||||
const secretApprovalRequestDAL = secretApprovalRequestDALFactory(db);
|
const secretApprovalRequestDAL = secretApprovalRequestDALFactory(db);
|
||||||
const secretApprovalRequestReviewerDAL = secretApprovalRequestReviewerDALFactory(db);
|
const secretApprovalRequestReviewerDAL = secretApprovalRequestReviewerDALFactory(db);
|
||||||
@@ -554,6 +558,7 @@ export const registerRoutes = async (
|
|||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
secretApprovalPolicyApproverDAL: sapApproverDAL,
|
secretApprovalPolicyApproverDAL: sapApproverDAL,
|
||||||
secretApprovalPolicyBypasserDAL: sapBypasserDAL,
|
secretApprovalPolicyBypasserDAL: sapBypasserDAL,
|
||||||
|
secretApprovalPolicyEnvironmentDAL: sapEnvironmentDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
secretApprovalPolicyDAL,
|
secretApprovalPolicyDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
@@ -1141,7 +1146,9 @@ export const registerRoutes = async (
|
|||||||
keyStore,
|
keyStore,
|
||||||
licenseService,
|
licenseService,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
folderDAL
|
folderDAL,
|
||||||
|
accessApprovalPolicyEnvironmentDAL,
|
||||||
|
secretApprovalPolicyEnvironmentDAL: sapEnvironmentDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectRoleService = projectRoleServiceFactory({
|
const projectRoleService = projectRoleServiceFactory({
|
||||||
@@ -1300,6 +1307,7 @@ export const registerRoutes = async (
|
|||||||
accessApprovalPolicyDAL,
|
accessApprovalPolicyDAL,
|
||||||
accessApprovalPolicyApproverDAL,
|
accessApprovalPolicyApproverDAL,
|
||||||
accessApprovalPolicyBypasserDAL,
|
accessApprovalPolicyBypasserDAL,
|
||||||
|
accessApprovalPolicyEnvironmentDAL,
|
||||||
groupDAL,
|
groupDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
|
|||||||
@@ -93,6 +93,13 @@ export const sapPubSchema = SecretApprovalPoliciesSchema.merge(
|
|||||||
name: z.string(),
|
name: z.string(),
|
||||||
slug: z.string()
|
slug: z.string()
|
||||||
}),
|
}),
|
||||||
|
environments: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
slug: z.string()
|
||||||
|
})
|
||||||
|
),
|
||||||
projectId: z.string()
|
projectId: z.string()
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
|
import { TAccessApprovalPolicyEnvironmentDALFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-environment-dal";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
|
import { TSecretApprovalPolicyEnvironmentDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-environment-dal";
|
||||||
import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore";
|
import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
@@ -19,6 +21,8 @@ type TProjectEnvServiceFactoryDep = {
|
|||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "setItemWithExpiry" | "getItem" | "waitTillReady">;
|
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "setItemWithExpiry" | "getItem" | "waitTillReady">;
|
||||||
|
accessApprovalPolicyEnvironmentDAL: Pick<TAccessApprovalPolicyEnvironmentDALFactory, "findAvailablePoliciesIds">;
|
||||||
|
secretApprovalPolicyEnvironmentDAL: Pick<TSecretApprovalPolicyEnvironmentDALFactory, "findAvailablePoliciesIds">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TProjectEnvServiceFactory = ReturnType<typeof projectEnvServiceFactory>;
|
export type TProjectEnvServiceFactory = ReturnType<typeof projectEnvServiceFactory>;
|
||||||
@@ -29,7 +33,9 @@ export const projectEnvServiceFactory = ({
|
|||||||
licenseService,
|
licenseService,
|
||||||
keyStore,
|
keyStore,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
folderDAL
|
folderDAL,
|
||||||
|
accessApprovalPolicyEnvironmentDAL,
|
||||||
|
secretApprovalPolicyEnvironmentDAL
|
||||||
}: TProjectEnvServiceFactoryDep) => {
|
}: TProjectEnvServiceFactoryDep) => {
|
||||||
const createEnvironment = async ({
|
const createEnvironment = async ({
|
||||||
projectId,
|
projectId,
|
||||||
@@ -216,6 +222,20 @@ export const projectEnvServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const env = await projectEnvDAL.transaction(async (tx) => {
|
const env = await projectEnvDAL.transaction(async (tx) => {
|
||||||
|
const secretApprovalRequest = await secretApprovalPolicyEnvironmentDAL.findAvailablePoliciesIds(id, tx);
|
||||||
|
if (secretApprovalRequest.length > 0) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Environment is in use by a secret approval policy",
|
||||||
|
name: "DeleteEnvironment"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const accessApprovalPolicy = await accessApprovalPolicyEnvironmentDAL.findAvailablePoliciesIds(id, tx);
|
||||||
|
if (accessApprovalPolicy.length > 0) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Environment is in use by an access approval policy",
|
||||||
|
name: "DeleteEnvironment"
|
||||||
|
});
|
||||||
|
}
|
||||||
const [doc] = await projectEnvDAL.delete({ id, projectId }, tx);
|
const [doc] = await projectEnvDAL.delete({ id, projectId }, tx);
|
||||||
if (!doc)
|
if (!doc)
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const useCreateAccessApprovalPolicy = () => {
|
|||||||
|
|
||||||
return useMutation<object, object, TCreateAccessPolicyDTO>({
|
return useMutation<object, object, TCreateAccessPolicyDTO>({
|
||||||
mutationFn: async ({
|
mutationFn: async ({
|
||||||
environment,
|
environments,
|
||||||
projectSlug,
|
projectSlug,
|
||||||
approvals,
|
approvals,
|
||||||
approvers,
|
approvers,
|
||||||
@@ -29,7 +29,7 @@ export const useCreateAccessApprovalPolicy = () => {
|
|||||||
approvalsRequired
|
approvalsRequired
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = await apiRequest.post("/api/v1/access-approvals/policies", {
|
const { data } = await apiRequest.post("/api/v1/access-approvals/policies", {
|
||||||
environment,
|
environments,
|
||||||
projectSlug,
|
projectSlug,
|
||||||
approvals,
|
approvals,
|
||||||
bypassers,
|
bypassers,
|
||||||
@@ -63,7 +63,8 @@ export const useUpdateAccessApprovalPolicy = () => {
|
|||||||
secretPath,
|
secretPath,
|
||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals,
|
allowedSelfApprovals,
|
||||||
approvalsRequired
|
approvalsRequired,
|
||||||
|
environments
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = await apiRequest.patch(`/api/v1/access-approvals/policies/${id}`, {
|
const { data } = await apiRequest.patch(`/api/v1/access-approvals/policies/${id}`, {
|
||||||
approvals,
|
approvals,
|
||||||
@@ -73,7 +74,8 @@ export const useUpdateAccessApprovalPolicy = () => {
|
|||||||
name,
|
name,
|
||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals,
|
allowedSelfApprovals,
|
||||||
approvalsRequired
|
approvalsRequired,
|
||||||
|
environments
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export type TAccessApprovalPolicy = {
|
|||||||
secretPath: string;
|
secretPath: string;
|
||||||
envId: string;
|
envId: string;
|
||||||
workspace: string;
|
workspace: string;
|
||||||
environment: WorkspaceEnv;
|
environments: WorkspaceEnv[];
|
||||||
projectId: string;
|
projectId: string;
|
||||||
policyType: PolicyType;
|
policyType: PolicyType;
|
||||||
approversRequired: boolean;
|
approversRequired: boolean;
|
||||||
@@ -166,7 +166,7 @@ export type TGetSecretApprovalPolicyOfBoardDTO = {
|
|||||||
export type TCreateAccessPolicyDTO = {
|
export type TCreateAccessPolicyDTO = {
|
||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
environment: string;
|
environments: string[];
|
||||||
approvers?: Approver[];
|
approvers?: Approver[];
|
||||||
bypassers?: Bypasser[];
|
bypassers?: Bypasser[];
|
||||||
approvals?: number;
|
approvals?: number;
|
||||||
@@ -182,7 +182,7 @@ export type TUpdateAccessPolicyDTO = {
|
|||||||
approvers?: Approver[];
|
approvers?: Approver[];
|
||||||
bypassers?: Bypasser[];
|
bypassers?: Bypasser[];
|
||||||
secretPath?: string;
|
secretPath?: string;
|
||||||
environment?: string;
|
environments?: string[];
|
||||||
approvals?: number;
|
approvals?: number;
|
||||||
enforcementLevel?: EnforcementLevel;
|
enforcementLevel?: EnforcementLevel;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const useCreateSecretApprovalPolicy = () => {
|
|||||||
|
|
||||||
return useMutation<object, object, TCreateSecretPolicyDTO>({
|
return useMutation<object, object, TCreateSecretPolicyDTO>({
|
||||||
mutationFn: async ({
|
mutationFn: async ({
|
||||||
environment,
|
environments,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
approvals,
|
approvals,
|
||||||
approvers,
|
approvers,
|
||||||
@@ -21,7 +21,7 @@ export const useCreateSecretApprovalPolicy = () => {
|
|||||||
allowedSelfApprovals
|
allowedSelfApprovals
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = await apiRequest.post("/api/v1/secret-approvals", {
|
const { data } = await apiRequest.post("/api/v1/secret-approvals", {
|
||||||
environment,
|
environments,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
approvals,
|
approvals,
|
||||||
approvers,
|
approvers,
|
||||||
@@ -53,7 +53,8 @@ export const useUpdateSecretApprovalPolicy = () => {
|
|||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals
|
allowedSelfApprovals,
|
||||||
|
environments
|
||||||
}) => {
|
}) => {
|
||||||
const { data } = await apiRequest.patch(`/api/v1/secret-approvals/${id}`, {
|
const { data } = await apiRequest.patch(`/api/v1/secret-approvals/${id}`, {
|
||||||
approvals,
|
approvals,
|
||||||
@@ -62,7 +63,8 @@ export const useUpdateSecretApprovalPolicy = () => {
|
|||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
allowedSelfApprovals
|
allowedSelfApprovals,
|
||||||
|
environments
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export type TSecretApprovalPolicy = {
|
|||||||
workspace: string;
|
workspace: string;
|
||||||
name: string;
|
name: string;
|
||||||
envId: string;
|
envId: string;
|
||||||
environment: WorkspaceEnv;
|
environments: WorkspaceEnv[];
|
||||||
secretPath?: string;
|
secretPath?: string;
|
||||||
approvals: number;
|
approvals: number;
|
||||||
approvers: Approver[];
|
approvers: Approver[];
|
||||||
@@ -48,7 +48,7 @@ export type TGetSecretApprovalPolicyOfBoardDTO = {
|
|||||||
export type TCreateSecretPolicyDTO = {
|
export type TCreateSecretPolicyDTO = {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
environment: string;
|
environments: string[];
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
approvers?: Approver[];
|
approvers?: Approver[];
|
||||||
bypassers?: Bypasser[];
|
bypassers?: Bypasser[];
|
||||||
@@ -68,6 +68,7 @@ export type TUpdateSecretPolicyDTO = {
|
|||||||
enforcementLevel?: EnforcementLevel;
|
enforcementLevel?: EnforcementLevel;
|
||||||
// for invalidating list
|
// for invalidating list
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
|
environments?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TDeleteSecretPolicyDTO = {
|
export type TDeleteSecretPolicyDTO = {
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ export const usePathAccessPolicies = ({ secretPath, environment }: Params) => {
|
|||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const pathPolicies = policies?.filter(
|
const pathPolicies = policies?.filter(
|
||||||
(policy) =>
|
(policy) =>
|
||||||
policy.environment.slug === environment && matchesPath(secretPath, policy.secretPath)
|
policy.environments?.some((env) => env.slug === environment) &&
|
||||||
|
matchesPath(secretPath, policy.secretPath)
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
+2
-2
@@ -155,8 +155,8 @@ export const SpecificPrivilegeSecretForm = ({
|
|||||||
|
|
||||||
const selectablePaths = useMemo(() => {
|
const selectablePaths = useMemo(() => {
|
||||||
if (!policies) return [];
|
if (!policies) return [];
|
||||||
const environmentPolicies = policies.filter(
|
const environmentPolicies = policies.filter((policy) =>
|
||||||
(policy) => policy.environment.slug === selectedEnvironment
|
policy.environments.find((env) => env.slug === selectedEnvironment)
|
||||||
);
|
);
|
||||||
|
|
||||||
privilegeForm.setValue("secretPath", "", {
|
privilegeForm.setValue("secretPath", "", {
|
||||||
|
|||||||
+18
-6
@@ -166,17 +166,20 @@ export const ApprovalPolicyList = ({ workspaceId }: IProps) => {
|
|||||||
const filteredPolicies = useMemo(
|
const filteredPolicies = useMemo(
|
||||||
() =>
|
() =>
|
||||||
policies
|
policies
|
||||||
.filter(({ policyType, environment, name, secretPath }) => {
|
.filter(({ policyType, environments, name, secretPath }) => {
|
||||||
if (filters.type && policyType !== filters.type) return false;
|
if (filters.type && policyType !== filters.type) return false;
|
||||||
|
|
||||||
if (filters.environmentIds.length && !filters.environmentIds.includes(environment.id))
|
if (
|
||||||
|
filters.environmentIds.length &&
|
||||||
|
!environments.some((env) => filters.environmentIds.includes(env.id))
|
||||||
|
)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const searchValue = search.trim().toLowerCase();
|
const searchValue = search.trim().toLowerCase();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
name.toLowerCase().includes(searchValue) ||
|
name.toLowerCase().includes(searchValue) ||
|
||||||
environment.name.toLowerCase().includes(searchValue) ||
|
environments.some((env) => env.name.toLowerCase().includes(searchValue)) ||
|
||||||
(secretPath ?? "*").toLowerCase().includes(searchValue)
|
(secretPath ?? "*").toLowerCase().includes(searchValue)
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@@ -189,9 +192,18 @@ export const ApprovalPolicyList = ({ workspaceId }: IProps) => {
|
|||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.localeCompare(policyTwo.policyType.toLowerCase());
|
.localeCompare(policyTwo.policyType.toLowerCase());
|
||||||
case PolicyOrderBy.Environment:
|
case PolicyOrderBy.Environment:
|
||||||
return policyOne.environment.name
|
// eslint-disable-next-line no-case-declarations
|
||||||
.toLowerCase()
|
const getFirstEnvName = (policy: { environments: { name: string }[] }) => {
|
||||||
.localeCompare(policyTwo.environment.name.toLowerCase());
|
if (!policy.environments?.length) return "";
|
||||||
|
return (
|
||||||
|
policy.environments
|
||||||
|
.map((env) => env.name?.toLowerCase() || "")
|
||||||
|
.filter((name) => name)
|
||||||
|
.sort()[0] || ""
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return getFirstEnvName(policyOne).localeCompare(getFirstEnvName(policyTwo));
|
||||||
case PolicyOrderBy.SecretPath:
|
case PolicyOrderBy.SecretPath:
|
||||||
return (policyOne.secretPath ?? "*")
|
return (policyOne.secretPath ?? "*")
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
|
|||||||
+15
-14
@@ -54,7 +54,7 @@ type Props = {
|
|||||||
|
|
||||||
const formSchema = z
|
const formSchema = z
|
||||||
.object({
|
.object({
|
||||||
environment: z.object({ slug: z.string(), name: z.string() }),
|
environments: z.array(z.object({ slug: z.string(), name: z.string() })).min(1),
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
secretPath: z.string().trim().min(1),
|
secretPath: z.string().trim().min(1),
|
||||||
approvals: z.number().min(1).default(1),
|
approvals: z.number().min(1).default(1),
|
||||||
@@ -134,7 +134,7 @@ const Form = ({
|
|||||||
values: editValues
|
values: editValues
|
||||||
? ({
|
? ({
|
||||||
...editValues,
|
...editValues,
|
||||||
environment: editValues.environment,
|
environments: editValues.environments,
|
||||||
userApprovers:
|
userApprovers:
|
||||||
editValues?.approvers
|
editValues?.approvers
|
||||||
?.filter((approver) => approver.type === ApproverType.User)
|
?.filter((approver) => approver.type === ApproverType.User)
|
||||||
@@ -191,7 +191,7 @@ const Form = ({
|
|||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { data: groups } = useListWorkspaceGroups(projectId);
|
const { data: groups } = useListWorkspaceGroups(projectId);
|
||||||
|
|
||||||
const environments = currentWorkspace?.environments || [];
|
const availableEnvironments = currentWorkspace?.environments || [];
|
||||||
const isAccessPolicyType = watch("policyType") === PolicyType.AccessPolicy;
|
const isAccessPolicyType = watch("policyType") === PolicyType.AccessPolicy;
|
||||||
|
|
||||||
const { mutateAsync: createAccessApprovalPolicy } = useCreateAccessApprovalPolicy();
|
const { mutateAsync: createAccessApprovalPolicy } = useCreateAccessApprovalPolicy();
|
||||||
@@ -204,11 +204,11 @@ const Form = ({
|
|||||||
|
|
||||||
const formUserBypassers = watch("userBypassers");
|
const formUserBypassers = watch("userBypassers");
|
||||||
const formGroupBypassers = watch("groupBypassers");
|
const formGroupBypassers = watch("groupBypassers");
|
||||||
const formEnvironment = watch("environment")?.slug;
|
const formEnvironments = watch("environments");
|
||||||
const bypasserCount = (formUserBypassers || []).length + (formGroupBypassers || []).length;
|
const bypasserCount = (formUserBypassers || []).length + (formGroupBypassers || []).length;
|
||||||
|
|
||||||
const handleCreatePolicy = async ({
|
const handleCreatePolicy = async ({
|
||||||
environment,
|
environments,
|
||||||
groupApprovers,
|
groupApprovers,
|
||||||
userApprovers,
|
userApprovers,
|
||||||
groupBypassers,
|
groupBypassers,
|
||||||
@@ -226,7 +226,7 @@ const Form = ({
|
|||||||
...data,
|
...data,
|
||||||
approvers: [...userApprovers, ...groupApprovers],
|
approvers: [...userApprovers, ...groupApprovers],
|
||||||
bypassers: bypassers.length > 0 ? bypassers : undefined,
|
bypassers: bypassers.length > 0 ? bypassers : undefined,
|
||||||
environment: environment.slug,
|
environments: environments.map((env) => env.slug),
|
||||||
workspaceId: currentWorkspace?.id || ""
|
workspaceId: currentWorkspace?.id || ""
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -242,7 +242,7 @@ const Form = ({
|
|||||||
numberOfApprovals: el.approvals
|
numberOfApprovals: el.approvals
|
||||||
})),
|
})),
|
||||||
bypassers: bypassers.length > 0 ? bypassers : undefined,
|
bypassers: bypassers.length > 0 ? bypassers : undefined,
|
||||||
environment: environment.slug,
|
environments: environments.map((env) => env.slug),
|
||||||
projectSlug
|
projectSlug
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -261,7 +261,7 @@ const Form = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdatePolicy = async ({
|
const handleUpdatePolicy = async ({
|
||||||
environment,
|
environments,
|
||||||
userApprovers,
|
userApprovers,
|
||||||
groupApprovers,
|
groupApprovers,
|
||||||
userBypassers,
|
userBypassers,
|
||||||
@@ -281,7 +281,8 @@ const Form = ({
|
|||||||
...data,
|
...data,
|
||||||
approvers: [...userApprovers, ...groupApprovers],
|
approvers: [...userApprovers, ...groupApprovers],
|
||||||
bypassers: bypassers.length > 0 ? bypassers : undefined,
|
bypassers: bypassers.length > 0 ? bypassers : undefined,
|
||||||
workspaceId: currentWorkspace?.id || ""
|
workspaceId: currentWorkspace?.id || "",
|
||||||
|
environments: environments.map((env) => env.slug)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await updateAccessApprovalPolicy({
|
await updateAccessApprovalPolicy({
|
||||||
@@ -297,7 +298,7 @@ const Form = ({
|
|||||||
numberOfApprovals: el.approvals
|
numberOfApprovals: el.approvals
|
||||||
})),
|
})),
|
||||||
bypassers: bypassers.length > 0 ? bypassers : undefined,
|
bypassers: bypassers.length > 0 ? bypassers : undefined,
|
||||||
environment: environment.slug,
|
environments: environments.map((env) => env.slug),
|
||||||
projectSlug
|
projectSlug
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -479,14 +480,14 @@ const Form = ({
|
|||||||
<SecretPathInput
|
<SecretPathInput
|
||||||
{...field}
|
{...field}
|
||||||
value={field.value || ""}
|
value={field.value || ""}
|
||||||
environment={formEnvironment}
|
environment={formEnvironments?.[0]?.slug || ""}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="environment"
|
name="environments"
|
||||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Environment"
|
label="Environment"
|
||||||
@@ -496,11 +497,11 @@ const Form = ({
|
|||||||
className="flex-1"
|
className="flex-1"
|
||||||
>
|
>
|
||||||
<FilterableSelect
|
<FilterableSelect
|
||||||
isDisabled={isEditMode}
|
|
||||||
value={value}
|
value={value}
|
||||||
|
isMulti
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
placeholder="Select environment..."
|
placeholder="Select environment..."
|
||||||
options={environments}
|
options={availableEnvironments}
|
||||||
getOptionValue={(option) => option.slug}
|
getOptionValue={(option) => option.slug}
|
||||||
getOptionLabel={(option) => option.name}
|
getOptionLabel={(option) => option.name}
|
||||||
/>
|
/>
|
||||||
|
|||||||
+2
-2
@@ -37,7 +37,7 @@ import { TWorkspaceUser } from "@app/hooks/api/users/types";
|
|||||||
interface IPolicy {
|
interface IPolicy {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
environment: WorkspaceEnv;
|
environments: WorkspaceEnv[];
|
||||||
projectId?: string;
|
projectId?: string;
|
||||||
secretPath?: string;
|
secretPath?: string;
|
||||||
approvals: number;
|
approvals: number;
|
||||||
@@ -112,7 +112,7 @@ export const ApprovalPolicyRow = ({
|
|||||||
onClick={() => setIsExpanded.toggle()}
|
onClick={() => setIsExpanded.toggle()}
|
||||||
>
|
>
|
||||||
<Td>{policy.name || <span className="text-mineshaft-400">Unnamed Policy</span>}</Td>
|
<Td>{policy.name || <span className="text-mineshaft-400">Unnamed Policy</span>}</Td>
|
||||||
<Td>{policy.environment.name}</Td>
|
<Td>{policy.environments.map((env) => env.name).join(", ")}</Td>
|
||||||
<Td>{policy.secretPath || "*"}</Td>
|
<Td>{policy.secretPath || "*"}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<Badge
|
<Badge
|
||||||
|
|||||||
Reference in New Issue
Block a user