mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 18:27:19 +00:00
Merge pull request #3923 from Infisical/daniel/approval-policy-improvements
fix(approval-policies): improve policies handling
This commit is contained in:
@@ -0,0 +1,55 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const existingSecretApprovalPolicies = await knex(TableName.SecretApprovalPolicy)
|
||||||
|
.whereNull("secretPath")
|
||||||
|
.orWhere("secretPath", "");
|
||||||
|
|
||||||
|
const existingAccessApprovalPolicies = await knex(TableName.AccessApprovalPolicy)
|
||||||
|
.whereNull("secretPath")
|
||||||
|
.orWhere("secretPath", "");
|
||||||
|
|
||||||
|
// update all the secret approval policies secretPath to be "/**"
|
||||||
|
if (existingSecretApprovalPolicies.length) {
|
||||||
|
await knex(TableName.SecretApprovalPolicy)
|
||||||
|
.whereIn(
|
||||||
|
"id",
|
||||||
|
existingSecretApprovalPolicies.map((el) => el.id)
|
||||||
|
)
|
||||||
|
.update({
|
||||||
|
secretPath: "/**"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// update all the access approval policies secretPath to be "/**"
|
||||||
|
if (existingAccessApprovalPolicies.length) {
|
||||||
|
await knex(TableName.AccessApprovalPolicy)
|
||||||
|
.whereIn(
|
||||||
|
"id",
|
||||||
|
existingAccessApprovalPolicies.map((el) => el.id)
|
||||||
|
)
|
||||||
|
.update({
|
||||||
|
secretPath: "/**"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.SecretApprovalPolicy, (table) => {
|
||||||
|
table.string("secretPath").notNullable().alter();
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (table) => {
|
||||||
|
table.string("secretPath").notNullable().alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.alterTable(TableName.SecretApprovalPolicy, (table) => {
|
||||||
|
table.string("secretPath").nullable().alter();
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (table) => {
|
||||||
|
table.string("secretPath").nullable().alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ export const AccessApprovalPoliciesSchema = z.object({
|
|||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
approvals: z.number().default(1),
|
approvals: z.number().default(1),
|
||||||
secretPath: z.string().nullable().optional(),
|
secretPath: z.string(),
|
||||||
envId: z.string().uuid(),
|
envId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { TImmutableDBKeys } from "./models";
|
|||||||
export const SecretApprovalPoliciesSchema = z.object({
|
export const SecretApprovalPoliciesSchema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
secretPath: z.string().nullable().optional(),
|
secretPath: z.string(),
|
||||||
approvals: z.number().default(1),
|
approvals: z.number().default(1),
|
||||||
envId: z.string().uuid(),
|
envId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { nanoid } from "nanoid";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ApproverType, BypasserType } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
|
import { ApproverType, BypasserType } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
|
||||||
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { EnforcementLevel } from "@app/lib/types";
|
import { EnforcementLevel } from "@app/lib/types";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
@@ -19,7 +20,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
body: z.object({
|
body: z.object({
|
||||||
projectSlug: z.string().trim(),
|
projectSlug: z.string().trim(),
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
secretPath: z.string().trim().default("/"),
|
secretPath: z.string().trim().min(1, { message: "Secret path cannot be empty" }).transform(removeTrailingSlash),
|
||||||
environment: z.string(),
|
environment: z.string(),
|
||||||
approvers: z
|
approvers: z
|
||||||
.discriminatedUnion("type", [
|
.discriminatedUnion("type", [
|
||||||
@@ -174,8 +175,9 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
secretPath: z
|
secretPath: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
|
.min(1, { message: "Secret path cannot be empty" })
|
||||||
.optional()
|
.optional()
|
||||||
.transform((val) => (val === "" ? "/" : val)),
|
.transform((val) => (val ? removeTrailingSlash(val) : val)),
|
||||||
approvers: z
|
approvers: z
|
||||||
.discriminatedUnion("type", [
|
.discriminatedUnion("type", [
|
||||||
z.object({
|
z.object({
|
||||||
|
|||||||
@@ -23,10 +23,8 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
environment: z.string(),
|
environment: z.string(),
|
||||||
secretPath: z
|
secretPath: z
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.min(1, { message: "Secret path cannot be empty" })
|
||||||
.nullable()
|
.transform((val) => removeTrailingSlash(val)),
|
||||||
.default("/")
|
|
||||||
.transform((val) => (val ? removeTrailingSlash(val) : val)),
|
|
||||||
approvers: z
|
approvers: z
|
||||||
.discriminatedUnion("type", [
|
.discriminatedUnion("type", [
|
||||||
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
||||||
@@ -100,10 +98,10 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
approvals: z.number().min(1).default(1),
|
approvals: z.number().min(1).default(1),
|
||||||
secretPath: z
|
secretPath: z
|
||||||
.string()
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, { message: "Secret path cannot be empty" })
|
||||||
.optional()
|
.optional()
|
||||||
.nullable()
|
.transform((val) => (val ? removeTrailingSlash(val) : undefined)),
|
||||||
.transform((val) => (val ? removeTrailingSlash(val) : val))
|
|
||||||
.transform((val) => (val === "" ? "/" : val)),
|
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).optional(),
|
enforcementLevel: z.nativeEnum(EnforcementLevel).optional(),
|
||||||
allowedSelfApprovals: z.boolean().default(true)
|
allowedSelfApprovals: z.boolean().default(true)
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export interface TAccessApprovalPolicyDALFactory
|
|||||||
envId: string;
|
envId: string;
|
||||||
enforcementLevel: string;
|
enforcementLevel: string;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath?: string | null | undefined;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
environment: {
|
environment: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -93,7 +93,7 @@ export interface TAccessApprovalPolicyDALFactory
|
|||||||
envId: string;
|
envId: string;
|
||||||
enforcementLevel: string;
|
enforcementLevel: string;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath?: string | null | undefined;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
environment: {
|
environment: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -116,7 +116,7 @@ export interface TAccessApprovalPolicyDALFactory
|
|||||||
envId: string;
|
envId: string;
|
||||||
enforcementLevel: string;
|
enforcementLevel: string;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath?: string | null | undefined;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
}>;
|
}>;
|
||||||
findLastValidPolicy: (
|
findLastValidPolicy: (
|
||||||
@@ -138,7 +138,7 @@ export interface TAccessApprovalPolicyDALFactory
|
|||||||
envId: string;
|
envId: string;
|
||||||
enforcementLevel: string;
|
enforcementLevel: string;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath?: string | null | undefined;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
}
|
}
|
||||||
| undefined
|
| undefined
|
||||||
@@ -190,7 +190,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
envId: string;
|
envId: string;
|
||||||
enforcementLevel: string;
|
enforcementLevel: string;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath?: string | null | undefined;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
}>;
|
}>;
|
||||||
deleteAccessApprovalPolicy: ({
|
deleteAccessApprovalPolicy: ({
|
||||||
@@ -214,7 +214,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
envId: string;
|
envId: string;
|
||||||
enforcementLevel: string;
|
enforcementLevel: string;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath?: string | null | undefined;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
environment: {
|
environment: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -252,7 +252,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
envId: string;
|
envId: string;
|
||||||
enforcementLevel: string;
|
enforcementLevel: string;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath?: string | null | undefined;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
}>;
|
}>;
|
||||||
getAccessApprovalPolicyByProjectSlug: ({
|
getAccessApprovalPolicyByProjectSlug: ({
|
||||||
@@ -286,7 +286,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
envId: string;
|
envId: string;
|
||||||
enforcementLevel: string;
|
enforcementLevel: string;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath?: string | null | undefined;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
environment: {
|
environment: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -337,7 +337,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
envId: string;
|
envId: string;
|
||||||
enforcementLevel: string;
|
enforcementLevel: string;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath?: string | null | undefined;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
environment: {
|
environment: {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -60,6 +60,26 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
accessApprovalRequestReviewerDAL,
|
accessApprovalRequestReviewerDAL,
|
||||||
orgMembershipDAL
|
orgMembershipDAL
|
||||||
}: TAccessApprovalPolicyServiceFactoryDep): TAccessApprovalPolicyServiceFactory => {
|
}: TAccessApprovalPolicyServiceFactoryDep): TAccessApprovalPolicyServiceFactory => {
|
||||||
|
const $policyExists = async ({
|
||||||
|
envId,
|
||||||
|
secretPath,
|
||||||
|
policyId
|
||||||
|
}: {
|
||||||
|
envId: string;
|
||||||
|
secretPath: string;
|
||||||
|
policyId?: string;
|
||||||
|
}) => {
|
||||||
|
const policy = await accessApprovalPolicyDAL
|
||||||
|
.findOne({
|
||||||
|
envId,
|
||||||
|
secretPath,
|
||||||
|
deletedAt: null
|
||||||
|
})
|
||||||
|
.catch(() => null);
|
||||||
|
|
||||||
|
return policyId ? policy && policy.id !== policyId : Boolean(policy);
|
||||||
|
};
|
||||||
|
|
||||||
const createAccessApprovalPolicy: TAccessApprovalPolicyServiceFactory["createAccessApprovalPolicy"] = async ({
|
const createAccessApprovalPolicy: TAccessApprovalPolicyServiceFactory["createAccessApprovalPolicy"] = async ({
|
||||||
name,
|
name,
|
||||||
actor,
|
actor,
|
||||||
@@ -106,6 +126,12 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
|
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
|
||||||
if (!env) throw new NotFoundError({ message: `Environment with slug '${environment}' not found` });
|
if (!env) throw new NotFoundError({ message: `Environment with slug '${environment}' not found` });
|
||||||
|
|
||||||
|
if (await $policyExists({ envId: env.id, secretPath })) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `A policy for secret path '${secretPath}' already exists in environment '${environment}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let approverUserIds = userApprovers;
|
let approverUserIds = userApprovers;
|
||||||
if (userApproverNames.length) {
|
if (userApproverNames.length) {
|
||||||
const approverUsersInDB = await userDAL.find({
|
const approverUsersInDB = await userDAL.find({
|
||||||
@@ -279,7 +305,11 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
) as { username: string; sequence?: number }[];
|
) as { username: string; sequence?: number }[];
|
||||||
|
|
||||||
const accessApprovalPolicy = await accessApprovalPolicyDAL.findById(policyId);
|
const accessApprovalPolicy = await accessApprovalPolicyDAL.findById(policyId);
|
||||||
if (!accessApprovalPolicy) throw new BadRequestError({ message: "Approval policy not found" });
|
if (!accessApprovalPolicy) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Access approval policy with ID '${policyId}' not found`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const currentApprovals = approvals || accessApprovalPolicy.approvals;
|
const currentApprovals = approvals || accessApprovalPolicy.approvals;
|
||||||
if (
|
if (
|
||||||
@@ -290,9 +320,18 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!accessApprovalPolicy) {
|
if (
|
||||||
throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
|
await $policyExists({
|
||||||
|
envId: accessApprovalPolicy.envId,
|
||||||
|
secretPath: secretPath || accessApprovalPolicy.secretPath,
|
||||||
|
policyId: accessApprovalPolicy.id
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `A policy for secret path '${secretPath}' already exists in environment '${accessApprovalPolicy.environment.slug}'`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
envId: string;
|
envId: string;
|
||||||
enforcementLevel: string;
|
enforcementLevel: string;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath?: string | null | undefined;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
}>;
|
}>;
|
||||||
deleteAccessApprovalPolicy: ({
|
deleteAccessApprovalPolicy: ({
|
||||||
@@ -146,7 +146,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
envId: string;
|
envId: string;
|
||||||
enforcementLevel: string;
|
enforcementLevel: string;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath?: string | null | undefined;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
environment: {
|
environment: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -218,7 +218,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
envId: string;
|
envId: string;
|
||||||
enforcementLevel: string;
|
enforcementLevel: string;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath?: string | null | undefined;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
environment: {
|
environment: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -269,7 +269,7 @@ export interface TAccessApprovalPolicyServiceFactory {
|
|||||||
envId: string;
|
envId: string;
|
||||||
enforcementLevel: string;
|
enforcementLevel: string;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
secretPath?: string | null | undefined;
|
secretPath: string;
|
||||||
deletedAt?: Date | null | undefined;
|
deletedAt?: Date | null | undefined;
|
||||||
environment: {
|
environment: {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -55,6 +55,26 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
licenseService,
|
licenseService,
|
||||||
secretApprovalRequestDAL
|
secretApprovalRequestDAL
|
||||||
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
||||||
|
const $policyExists = async ({
|
||||||
|
envId,
|
||||||
|
secretPath,
|
||||||
|
policyId
|
||||||
|
}: {
|
||||||
|
envId: string;
|
||||||
|
secretPath: string;
|
||||||
|
policyId?: string;
|
||||||
|
}) => {
|
||||||
|
const policy = await secretApprovalPolicyDAL
|
||||||
|
.findOne({
|
||||||
|
envId,
|
||||||
|
secretPath,
|
||||||
|
deletedAt: null
|
||||||
|
})
|
||||||
|
.catch(() => null);
|
||||||
|
|
||||||
|
return policyId ? policy && policy.id !== policyId : Boolean(policy);
|
||||||
|
};
|
||||||
|
|
||||||
const createSecretApprovalPolicy = async ({
|
const createSecretApprovalPolicy = async ({
|
||||||
name,
|
name,
|
||||||
actor,
|
actor,
|
||||||
@@ -106,10 +126,17 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
||||||
if (!env)
|
if (!env) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: `Environment with slug '${environment}' not found in project with ID ${projectId}`
|
message: `Environment with slug '${environment}' not found in project with ID ${projectId}`
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await $policyExists({ envId: env.id, secretPath })) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `A policy for secret path '${secretPath}' already exists in environment '${environment}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let groupBypassers: string[] = [];
|
let groupBypassers: string[] = [];
|
||||||
let bypasserUserIds: string[] = [];
|
let bypasserUserIds: string[] = [];
|
||||||
@@ -260,6 +287,18 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
await $policyExists({
|
||||||
|
envId: secretApprovalPolicy.envId,
|
||||||
|
secretPath: secretPath || secretApprovalPolicy.secretPath,
|
||||||
|
policyId: secretApprovalPolicy.id
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `A policy for secret path '${secretPath}' already exists in environment '${secretApprovalPolicy.environment.slug}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { ApproverType, BypasserType } from "../access-approval-policy/access-app
|
|||||||
|
|
||||||
export type TCreateSapDTO = {
|
export type TCreateSapDTO = {
|
||||||
approvals: number;
|
approvals: number;
|
||||||
secretPath?: string | null;
|
secretPath: string;
|
||||||
environment: string;
|
environment: 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?: (
|
||||||
@@ -20,7 +20,7 @@ export type TCreateSapDTO = {
|
|||||||
export type TUpdateSapDTO = {
|
export type TUpdateSapDTO = {
|
||||||
secretPolicyId: string;
|
secretPolicyId: string;
|
||||||
approvals?: number;
|
approvals?: number;
|
||||||
secretPath?: string | null;
|
secretPath?: 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 }
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ export type TCreateAccessPolicyDTO = {
|
|||||||
approvers?: Approver[];
|
approvers?: Approver[];
|
||||||
bypassers?: Bypasser[];
|
bypassers?: Bypasser[];
|
||||||
approvals?: number;
|
approvals?: number;
|
||||||
secretPath?: string;
|
secretPath: string;
|
||||||
enforcementLevel?: EnforcementLevel;
|
enforcementLevel?: EnforcementLevel;
|
||||||
allowedSelfApprovals: boolean;
|
allowedSelfApprovals: boolean;
|
||||||
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
|
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export type TCreateSecretPolicyDTO = {
|
|||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath?: string | null;
|
secretPath: string;
|
||||||
approvers?: Approver[];
|
approvers?: Approver[];
|
||||||
bypassers?: Bypasser[];
|
bypassers?: Bypasser[];
|
||||||
approvals?: number;
|
approvals?: number;
|
||||||
@@ -62,7 +62,7 @@ export type TUpdateSecretPolicyDTO = {
|
|||||||
name?: string;
|
name?: string;
|
||||||
approvers?: Approver[];
|
approvers?: Approver[];
|
||||||
bypassers?: Bypasser[];
|
bypassers?: Bypasser[];
|
||||||
secretPath?: string | null;
|
secretPath?: string;
|
||||||
approvals?: number;
|
approvals?: number;
|
||||||
allowedSelfApprovals?: boolean;
|
allowedSelfApprovals?: boolean;
|
||||||
enforcementLevel?: EnforcementLevel;
|
enforcementLevel?: EnforcementLevel;
|
||||||
|
|||||||
+21
-16
@@ -55,7 +55,7 @@ const formSchema = z
|
|||||||
.object({
|
.object({
|
||||||
environment: z.object({ slug: z.string(), name: z.string() }),
|
environment: z.object({ slug: z.string(), name: z.string() }),
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
secretPath: z.string().optional(),
|
secretPath: z.string().trim().min(1),
|
||||||
approvals: z.number().min(1).default(1),
|
approvals: z.number().min(1).default(1),
|
||||||
userApprovers: z
|
userApprovers: z
|
||||||
.object({ type: z.literal(ApproverType.User), id: z.string() })
|
.object({ type: z.literal(ApproverType.User), id: z.string() })
|
||||||
@@ -93,20 +93,19 @@ const formSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
})
|
})
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
if (
|
if (data.policyType === PolicyType.ChangePolicy) {
|
||||||
data.policyType === PolicyType.ChangePolicy &&
|
if (!(data.groupApprovers.length || data.userApprovers.length)) {
|
||||||
!(data.groupApprovers.length || data.userApprovers.length)
|
ctx.addIssue({
|
||||||
) {
|
path: ["userApprovers"],
|
||||||
ctx.addIssue({
|
code: z.ZodIssueCode.custom,
|
||||||
path: ["userApprovers"],
|
message: "At least one approver should be provided"
|
||||||
code: z.ZodIssueCode.custom,
|
});
|
||||||
message: "At least one approver should be provided"
|
ctx.addIssue({
|
||||||
});
|
path: ["groupApprovers"],
|
||||||
ctx.addIssue({
|
code: z.ZodIssueCode.custom,
|
||||||
path: ["groupApprovers"],
|
message: "At least one approver should be provided"
|
||||||
code: z.ZodIssueCode.custom,
|
});
|
||||||
message: "At least one approver should be provided"
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -127,6 +126,7 @@ const Form = ({
|
|||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
watch,
|
watch,
|
||||||
|
resetField,
|
||||||
formState: { isSubmitting }
|
formState: { isSubmitting }
|
||||||
} = useForm<TFormSchema>({
|
} = useForm<TFormSchema>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
@@ -177,6 +177,7 @@ const Form = ({
|
|||||||
: undefined,
|
: undefined,
|
||||||
defaultValues: !editValues
|
defaultValues: !editValues
|
||||||
? {
|
? {
|
||||||
|
secretPath: "/",
|
||||||
sequenceApprovers: [{ approvals: 1 }]
|
sequenceApprovers: [{ approvals: 1 }]
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
@@ -405,7 +406,10 @@ const Form = ({
|
|||||||
<Select
|
<Select
|
||||||
isDisabled={isEditMode}
|
isDisabled={isEditMode}
|
||||||
value={value}
|
value={value}
|
||||||
onValueChange={(val) => onChange(val as PolicyType)}
|
onValueChange={(val) => {
|
||||||
|
onChange(val as PolicyType);
|
||||||
|
resetField("secretPath");
|
||||||
|
}}
|
||||||
className="w-full border border-mineshaft-500"
|
className="w-full border border-mineshaft-500"
|
||||||
>
|
>
|
||||||
{Object.values(PolicyType).map((policyType) => {
|
{Object.values(PolicyType).map((policyType) => {
|
||||||
@@ -465,6 +469,7 @@ const Form = ({
|
|||||||
<FormControl
|
<FormControl
|
||||||
tooltipText="Secret paths support glob patterns. For example, '/**' will match all paths."
|
tooltipText="Secret paths support glob patterns. For example, '/**' will match all paths."
|
||||||
label="Secret Path"
|
label="Secret Path"
|
||||||
|
isRequired
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
|
|||||||
Reference in New Issue
Block a user