From 05408bc15156f273bb8252df96376d66e8f85f64 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Wed, 23 Jul 2025 09:54:41 -0300 Subject: [PATCH 01/34] Allow multiple environments on secret and access policies --- backend/src/@types/knex.d.ts | 21 +++ ...2152841_add-policies-environments-table.ts | 93 ++++++++++ .../access-approval-policies-environments.ts | 25 +++ backend/src/db/schemas/models.ts | 2 + .../secret-approval-policies-environments.ts | 25 +++ .../v1/access-approval-policy-router.ts | 107 +++++++----- .../v1/secret-approval-policy-router.ts | 72 ++++---- .../access-approval-policy-dal.ts | 159 ++++++++++++++++-- .../access-approval-policy-environment-dal.ts | 31 ++++ .../access-approval-policy-service.ts | 106 ++++++++---- .../access-approval-policy-types.ts | 36 +++- .../access-approval-request-dal.ts | 19 ++- .../access-approval-request-service.ts | 37 +++- .../secret-approval-policy-dal.ts | 105 +++++++++++- .../secret-approval-policy-environment-dal.ts | 31 ++++ .../secret-approval-policy-service.ts | 95 ++++++++--- .../secret-approval-policy-types.ts | 4 +- .../secret-approval-request-dal.ts | 9 +- .../secret-approval-request-service.ts | 5 + backend/src/server/routes/index.ts | 10 +- backend/src/server/routes/sanitizedSchemas.ts | 7 + .../project-env/project-env-service.ts | 22 ++- .../src/hooks/api/accessApproval/mutation.tsx | 10 +- .../src/hooks/api/accessApproval/types.ts | 6 +- .../src/hooks/api/secretApproval/mutation.tsx | 10 +- .../src/hooks/api/secretApproval/types.ts | 5 +- frontend/src/hooks/usePathAccessPolicies.tsx | 3 +- .../SpecificPrivilegeSection.tsx | 4 +- .../ApprovalPolicyList/ApprovalPolicyList.tsx | 24 ++- .../components/AccessPolicyModal.tsx | 29 ++-- .../components/ApprovalPolicyRow.tsx | 4 +- 31 files changed, 918 insertions(+), 198 deletions(-) create mode 100644 backend/src/db/migrations/20250722152841_add-policies-environments-table.ts create mode 100644 backend/src/db/schemas/access-approval-policies-environments.ts create mode 100644 backend/src/db/schemas/secret-approval-policies-environments.ts create mode 100644 backend/src/ee/services/access-approval-policy/access-approval-policy-environment-dal.ts create mode 100644 backend/src/ee/services/secret-approval-policy/secret-approval-policy-environment-dal.ts diff --git a/backend/src/@types/knex.d.ts b/backend/src/@types/knex.d.ts index 7ead9f84b..6f3e3029d 100644 --- a/backend/src/@types/knex.d.ts +++ b/backend/src/@types/knex.d.ts @@ -489,6 +489,11 @@ import { TWorkflowIntegrationsInsert, TWorkflowIntegrationsUpdate } from "@app/db/schemas"; +import { + TAccessApprovalPoliciesEnvironments, + TAccessApprovalPoliciesEnvironmentsInsert, + TAccessApprovalPoliciesEnvironmentsUpdate +} from "@app/db/schemas/access-approval-policies-environments"; import { TIdentityLdapAuths, TIdentityLdapAuthsInsert, @@ -504,6 +509,11 @@ import { TProjectMicrosoftTeamsConfigsInsert, TProjectMicrosoftTeamsConfigsUpdate } from "@app/db/schemas/project-microsoft-teams-configs"; +import { + TSecretApprovalPoliciesEnvironments, + TSecretApprovalPoliciesEnvironmentsInsert, + TSecretApprovalPoliciesEnvironmentsUpdate +} from "@app/db/schemas/secret-approval-policies-environments"; import { TSecretReminderRecipients, TSecretReminderRecipientsInsert, @@ -881,6 +891,12 @@ declare module "knex/types/tables" { TAccessApprovalPoliciesBypassersUpdate >; + [TableName.AccessApprovalPolicyEnvironment]: KnexOriginal.CompositeTableType< + TAccessApprovalPoliciesEnvironments, + TAccessApprovalPoliciesEnvironmentsInsert, + TAccessApprovalPoliciesEnvironmentsUpdate + >; + [TableName.AccessApprovalRequest]: KnexOriginal.CompositeTableType< TAccessApprovalRequests, TAccessApprovalRequestsInsert, @@ -929,6 +945,11 @@ declare module "knex/types/tables" { TSecretApprovalRequestSecretTagsInsert, TSecretApprovalRequestSecretTagsUpdate >; + [TableName.SecretApprovalPolicyEnvironment]: KnexOriginal.CompositeTableType< + TSecretApprovalPoliciesEnvironments, + TSecretApprovalPoliciesEnvironmentsInsert, + TSecretApprovalPoliciesEnvironmentsUpdate + >; [TableName.SecretRotation]: KnexOriginal.CompositeTableType< TSecretRotations, TSecretRotationsInsert, diff --git a/backend/src/db/migrations/20250722152841_add-policies-environments-table.ts b/backend/src/db/migrations/20250722152841_add-policies-environments-table.ts new file mode 100644 index 000000000..3c2edc365 --- /dev/null +++ b/backend/src/db/migrations/20250722152841_add-policies-environments-table.ts @@ -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 { + 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 { + 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"); + }); +} diff --git a/backend/src/db/schemas/access-approval-policies-environments.ts b/backend/src/db/schemas/access-approval-policies-environments.ts new file mode 100644 index 000000000..fa2a859c2 --- /dev/null +++ b/backend/src/db/schemas/access-approval-policies-environments.ts @@ -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; +export type TAccessApprovalPoliciesEnvironmentsInsert = Omit< + z.input, + TImmutableDBKeys +>; +export type TAccessApprovalPoliciesEnvironmentsUpdate = Partial< + Omit, TImmutableDBKeys> +>; diff --git a/backend/src/db/schemas/models.ts b/backend/src/db/schemas/models.ts index 75d36833b..8ea73cdf4 100644 --- a/backend/src/db/schemas/models.ts +++ b/backend/src/db/schemas/models.ts @@ -100,6 +100,7 @@ export enum TableName { AccessApprovalPolicyBypasser = "access_approval_policies_bypassers", AccessApprovalRequest = "access_approval_requests", AccessApprovalRequestReviewer = "access_approval_requests_reviewers", + AccessApprovalPolicyEnvironment = "access_approval_policies_environments", SecretApprovalPolicy = "secret_approval_policies", SecretApprovalPolicyApprover = "secret_approval_policies_approvers", SecretApprovalPolicyBypasser = "secret_approval_policies_bypassers", @@ -107,6 +108,7 @@ export enum TableName { SecretApprovalRequestReviewer = "secret_approval_requests_reviewers", SecretApprovalRequestSecret = "secret_approval_requests_secrets", SecretApprovalRequestSecretTag = "secret_approval_request_secret_tags", + SecretApprovalPolicyEnvironment = "secret_approval_policies_environments", SecretRotation = "secret_rotations", SecretRotationOutput = "secret_rotation_outputs", SamlConfig = "saml_configs", diff --git a/backend/src/db/schemas/secret-approval-policies-environments.ts b/backend/src/db/schemas/secret-approval-policies-environments.ts new file mode 100644 index 000000000..0420fe75d --- /dev/null +++ b/backend/src/db/schemas/secret-approval-policies-environments.ts @@ -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; +export type TSecretApprovalPoliciesEnvironmentsInsert = Omit< + z.input, + TImmutableDBKeys +>; +export type TSecretApprovalPoliciesEnvironmentsUpdate = Partial< + Omit, TImmutableDBKeys> +>; diff --git a/backend/src/ee/routes/v1/access-approval-policy-router.ts b/backend/src/ee/routes/v1/access-approval-policy-router.ts index 177f5e1fd..c01d2fc28 100644 --- a/backend/src/ee/routes/v1/access-approval-policy-router.ts +++ b/backend/src/ee/routes/v1/access-approval-policy-router.ts @@ -17,52 +17,66 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi rateLimit: writeLimit }, schema: { - body: z.object({ - projectSlug: z.string().trim(), - name: z.string().optional(), - secretPath: z.string().trim().min(1, { message: "Secret path cannot be empty" }).transform(removeTrailingSlash), - environment: z.string(), - approvers: z - .discriminatedUnion("type", [ - z.object({ - type: z.literal(ApproverType.Group), - id: z.string(), - 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) + body: z + .object({ + projectSlug: z.string().trim(), + name: z.string().optional(), + secretPath: z + .string() + .trim() + .min(1, { message: "Secret path cannot be empty" }) + .transform(removeTrailingSlash), + environment: z.string().optional(), + environments: z.string().array().optional(), + approvers: z + .discriminatedUnion("type", [ + z.object({ + type: z.literal(ApproverType.Group), + id: z.string(), + 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() - .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() - .optional(), - approvals: z.number().min(1).default(1), - enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard), - allowedSelfApprovals: z.boolean().default(true) - }), + .array() + .optional(), + approvals: z.number().min(1).default(1), + enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard), + allowedSelfApprovals: z.boolean().default(true) + }) + .refine( + (val) => Boolean(val.environment) || Boolean(val.environments), + "Must provide either environment or environments" + ), response: { 200: z.object({ approval: sapPubSchema @@ -78,7 +92,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi actorOrgId: req.permission.orgId, ...req.body, 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 }); return { approval }; @@ -211,6 +225,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi approvals: z.number().min(1).optional(), enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard), allowedSelfApprovals: z.boolean().default(true), + environments: z.array(z.string()).optional(), approvalsRequired: z .object({ numberOfApprovals: z.number().int(), diff --git a/backend/src/ee/routes/v1/secret-approval-policy-router.ts b/backend/src/ee/routes/v1/secret-approval-policy-router.ts index 46b2544b2..dc87b83f2 100644 --- a/backend/src/ee/routes/v1/secret-approval-policy-router.ts +++ b/backend/src/ee/routes/v1/secret-approval-policy-router.ts @@ -17,34 +17,45 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi rateLimit: writeLimit }, schema: { - body: z.object({ - workspaceId: z.string(), - name: z.string().optional(), - environment: z.string(), - secretPath: z - .string() - .min(1, { message: "Secret path cannot be empty" }) - .transform((val) => removeTrailingSlash(val)), - approvers: z - .discriminatedUnion("type", [ - z.object({ type: z.literal(ApproverType.Group), id: z.string() }), - z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), username: z.string().optional() }) - ]) - .array() - .min(1, { message: "At least one approver should be provided" }) - .max(100, "Cannot have more than 100 approvers"), - 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(), - approvals: z.number().min(1).default(1), - enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard), - allowedSelfApprovals: z.boolean().default(true) - }), + body: z + .object({ + workspaceId: z.string(), + name: z.string().optional(), + environment: z.string().optional(), + environments: z.string().array().optional(), + secretPath: z + .string() + .min(1, { message: "Secret path cannot be empty" }) + .transform((val) => removeTrailingSlash(val)), + approvers: z + .discriminatedUnion("type", [ + z.object({ type: z.literal(ApproverType.Group), id: z.string() }), + z.object({ + type: z.literal(ApproverType.User), + id: z.string().optional(), + username: z.string().optional() + }) + ]) + .array() + .min(1, { message: "At least one approver should be provided" }) + .max(100, "Cannot have more than 100 approvers"), + 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(), + 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: { 200: z.object({ approval: sapPubSchema @@ -60,7 +71,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi actorOrgId: req.permission.orgId, projectId: req.body.workspaceId, ...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 }); return { approval }; @@ -103,7 +114,8 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi .optional() .transform((val) => (val ? removeTrailingSlash(val) : undefined)), enforcementLevel: z.nativeEnum(EnforcementLevel).optional(), - allowedSelfApprovals: z.boolean().default(true) + allowedSelfApprovals: z.boolean().default(true), + environments: z.array(z.string()).optional() }), response: { 200: z.object({ diff --git a/backend/src/ee/services/access-approval-policy/access-approval-policy-dal.ts b/backend/src/ee/services/access-approval-policy/access-approval-policy-dal.ts index 995534f8f..e01b51a5d 100644 --- a/backend/src/ee/services/access-approval-policy/access-approval-policy-dal.ts +++ b/backend/src/ee/services/access-approval-policy/access-approval-policy-dal.ts @@ -26,6 +26,7 @@ export interface TAccessApprovalPolicyDALFactory >, customFilter?: { policyId?: string; + envId?: string; }, tx?: Knex ) => Promise< @@ -55,11 +56,6 @@ export interface TAccessApprovalPolicyDALFactory allowedSelfApprovals: boolean; secretPath: string; deletedAt?: Date | null | undefined; - environment: { - id: string; - name: string; - slug: string; - }; projectId: string; bypassers: ( | { @@ -72,6 +68,11 @@ export interface TAccessApprovalPolicyDALFactory type: BypasserType.Group; } )[]; + environments: { + id: string; + name: string; + slug: string; + }[]; }[] >; findById: ( @@ -95,11 +96,11 @@ export interface TAccessApprovalPolicyDALFactory allowedSelfApprovals: boolean; secretPath: string; deletedAt?: Date | null | undefined; - environment: { + environments: { id: string; name: string; slug: string; - }; + }[]; projectId: string; } | undefined @@ -143,6 +144,26 @@ export interface TAccessApprovalPolicyDALFactory } | 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 { @@ -367,6 +388,7 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo filter: TFindFilter, customFilter?: { policyId?: string; + envId?: string; } ) => { const result = await tx(TableName.AccessApprovalPolicy) @@ -377,7 +399,17 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo 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( TableName.AccessApprovalPolicyApprover, `${TableName.AccessApprovalPolicy}.id`, @@ -404,7 +436,7 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo .select(tx.ref("bypasserGroupId").withSchema(TableName.AccessApprovalPolicyBypasser)) .select(tx.ref("name").withSchema(TableName.Environment).as("envName")) .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(selectAllTableCols(TableName.AccessApprovalPolicy)); @@ -448,6 +480,15 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo sequence: approverSequence, 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, key: "id", parentMapper: (data) => ({ - environment: { - id: data.envId, - name: data.envName, - slug: data.envSlug - }, projectId: data.projectId, ...AccessApprovalPoliciesSchema.parse(data) // secretPath: data.secretPath || undefined, @@ -517,6 +553,15 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo id, 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 buildFindFilter( { - envId, secretPath }, TableName.AccessApprovalPolicy ) ) + .join( + TableName.AccessApprovalPolicyEnvironment, + `${TableName.AccessApprovalPolicyEnvironment}.policyId`, + `${TableName.AccessApprovalPolicy}.id` + ) + .where(`${TableName.AccessApprovalPolicyEnvironment}.envId`, "=", envId) .orderBy("deletedAt", "desc") .orderByRaw(`"deletedAt" IS NULL`) + .select(selectAllTableCols(TableName.AccessApprovalPolicy)) .first(); 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 + }; }; diff --git a/backend/src/ee/services/access-approval-policy/access-approval-policy-environment-dal.ts b/backend/src/ee/services/access-approval-policy/access-approval-policy-environment-dal.ts new file mode 100644 index 000000000..8485df036 --- /dev/null +++ b/backend/src/ee/services/access-approval-policy/access-approval-policy-environment-dal.ts @@ -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; + +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 }; +}; diff --git a/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts b/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts index fa487d0b7..693198f0e 100644 --- a/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts +++ b/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts @@ -20,6 +20,7 @@ import { TAccessApprovalPolicyBypasserDALFactory } from "./access-approval-policy-approver-dal"; import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal"; +import { TAccessApprovalPolicyEnvironmentDALFactory } from "./access-approval-policy-environment-dal"; import { ApproverType, BypasserType, @@ -44,12 +45,14 @@ type TAccessApprovalPolicyServiceFactoryDep = { additionalPrivilegeDAL: Pick; accessApprovalRequestReviewerDAL: Pick; orgMembershipDAL: Pick; + accessApprovalPolicyEnvironmentDAL: TAccessApprovalPolicyEnvironmentDALFactory; }; export const accessApprovalPolicyServiceFactory = ({ accessApprovalPolicyDAL, accessApprovalPolicyApproverDAL, accessApprovalPolicyBypasserDAL, + accessApprovalPolicyEnvironmentDAL, groupDAL, permissionService, projectEnvDAL, @@ -62,21 +65,22 @@ export const accessApprovalPolicyServiceFactory = ({ }: TAccessApprovalPolicyServiceFactoryDep): TAccessApprovalPolicyServiceFactory => { const $policyExists = async ({ envId, + envIds, secretPath, policyId }: { - envId: string; + envId?: string; + envIds?: string[]; secretPath: string; policyId?: string; }) => { - const policy = await accessApprovalPolicyDAL - .findOne({ - envId, - secretPath, - deletedAt: null - }) - .catch(() => null); - + if (!envId && !envIds) { + throw new BadRequestError({ message: "Must provide either envId or envIds" }); + } + const policy = await accessApprovalPolicyDAL.findPoliciesByEnvIdAndSecretPath({ + secretPath, + envIds: envId ? [envId] : envIds || [] + }); return policyId ? policy && policy.id !== policyId : Boolean(policy); }; @@ -92,6 +96,7 @@ export const accessApprovalPolicyServiceFactory = ({ bypassers, projectSlug, environment, + environments, enforcementLevel, allowedSelfApprovals, approvalsRequired @@ -123,13 +128,23 @@ export const accessApprovalPolicyServiceFactory = ({ ProjectPermissionActions.Create, ProjectPermissionSub.SecretApproval ); - const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id }); - if (!env) throw new NotFoundError({ message: `Environment with slug '${environment}' not found` }); + const mergedEnvs = (environment ? [environment] : environments) || []; + 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 })) { - throw new BadRequestError({ - message: `A policy for secret path '${secretPath}' already exists in environment '${environment}'` - }); + for (const env of envs) { + // eslint-disable-next-line no-await-in-loop + 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; @@ -197,7 +212,7 @@ export const accessApprovalPolicyServiceFactory = ({ const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => { const doc = await accessApprovalPolicyDAL.create( { - envId: env.id, + envId: envs[0].id, approvals, secretPath, name, @@ -206,6 +221,10 @@ export const accessApprovalPolicyServiceFactory = ({ }, tx ); + await accessApprovalPolicyEnvironmentDAL.insertMany( + envs.map((el) => ({ policyId: doc.id, envId: el.id })), + tx + ); if (approverUserIds.length) { await accessApprovalPolicyApproverDAL.insertMany( @@ -258,7 +277,7 @@ export const accessApprovalPolicyServiceFactory = ({ return doc; }); - return { ...accessApproval, environment: env, projectId: project.id }; + return { ...accessApproval, environments: envs, projectId: project.id, environment: envs[0] }; }; const getAccessApprovalPolicyByProjectSlug: TAccessApprovalPolicyServiceFactory["getAccessApprovalPolicyByProjectSlug"] = @@ -276,7 +295,10 @@ export const accessApprovalPolicyServiceFactory = ({ }); 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 ({ @@ -292,7 +314,8 @@ export const accessApprovalPolicyServiceFactory = ({ approvals, enforcementLevel, allowedSelfApprovals, - approvalsRequired + approvalsRequired, + environments }: TUpdateAccessApprovalPolicy) => { 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" }); } + 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 ( await $policyExists({ - envId: accessApprovalPolicy.envId, + envIds: envs.map((env) => env.id), secretPath: secretPath || accessApprovalPolicy.secretPath, policyId: accessApprovalPolicy.id }) ) { 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); if (bypasserUserIds.length) { @@ -513,7 +552,8 @@ export const accessApprovalPolicyServiceFactory = ({ return { ...updatedPolicy, - environment: accessApprovalPolicy.environment, + environments: accessApprovalPolicy.environments, + environment: accessApprovalPolicy.environments[0], projectId: accessApprovalPolicy.projectId }; }; @@ -563,7 +603,10 @@ export const accessApprovalPolicyServiceFactory = ({ } }); - return policy; + return { + ...policy, + environment: policy.environments[0] + }; }; const getAccessPolicyCountByEnvSlug: TAccessApprovalPolicyServiceFactory["getAccessPolicyCountByEnvSlug"] = async ({ @@ -592,11 +635,13 @@ export const accessApprovalPolicyServiceFactory = ({ const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug }); if (!environment) throw new NotFoundError({ message: `Environment with slug '${envSlug}' not found` }); - const policies = await accessApprovalPolicyDAL.find({ - envId: environment.id, - projectId: project.id, - deletedAt: null - }); + const policies = await accessApprovalPolicyDAL.find( + { + projectId: project.id, + deletedAt: null + }, + { envId: environment.id } + ); if (!policies) throw new NotFoundError({ message: `No policies found in environment with slug '${envSlug}'` }); return { count: policies.length }; @@ -627,7 +672,10 @@ export const accessApprovalPolicyServiceFactory = ({ ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); - return policy; + return { + ...policy, + environment: policy.environments[0] + }; }; return { diff --git a/backend/src/ee/services/access-approval-policy/access-approval-policy-types.ts b/backend/src/ee/services/access-approval-policy/access-approval-policy-types.ts index f3f195914..27ec228f7 100644 --- a/backend/src/ee/services/access-approval-policy/access-approval-policy-types.ts +++ b/backend/src/ee/services/access-approval-policy/access-approval-policy-types.ts @@ -26,7 +26,8 @@ export enum BypasserType { export type TCreateAccessApprovalPolicy = { approvals: number; secretPath: string; - environment: string; + environment?: string; + environments?: string[]; approvers: ( | { type: ApproverType.Group; id: string; sequence?: number } | { type: ApproverType.User; id?: string; username?: string; sequence?: number } @@ -58,6 +59,7 @@ export type TUpdateAccessApprovalPolicy = { enforcementLevel?: EnforcementLevel; allowedSelfApprovals: boolean; approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[]; + environments?: string[]; } & Omit; export type TDeleteAccessApprovalPolicy = { @@ -113,6 +115,15 @@ export interface TAccessApprovalPolicyServiceFactory { slug: string; position: number; }; + environments: { + name: string; + id: string; + createdAt: Date; + updatedAt: Date; + projectId: string; + slug: string; + position: number; + }[]; projectId: string; name: string; id: string; @@ -153,6 +164,11 @@ export interface TAccessApprovalPolicyServiceFactory { name: string; slug: string; }; + environments: { + id: string; + name: string; + slug: string; + }[]; projectId: string; }>; updateAccessApprovalPolicy: ({ @@ -168,13 +184,19 @@ export interface TAccessApprovalPolicyServiceFactory { approvals, enforcementLevel, allowedSelfApprovals, - approvalsRequired + approvalsRequired, + environments }: TUpdateAccessApprovalPolicy) => Promise<{ environment: { id: string; name: string; slug: string; }; + environments: { + id: string; + name: string; + slug: string; + }[]; projectId: string; name: string; id: string; @@ -225,6 +247,11 @@ export interface TAccessApprovalPolicyServiceFactory { name: string; slug: string; }; + environments: { + id: string; + name: string; + slug: string; + }[]; projectId: string; bypassers: ( | { @@ -276,6 +303,11 @@ export interface TAccessApprovalPolicyServiceFactory { name: string; slug: string; }; + environments: { + id: string; + name: string; + slug: string; + }[]; projectId: string; bypassers: ( | { diff --git a/backend/src/ee/services/access-approval-request/access-approval-request-dal.ts b/backend/src/ee/services/access-approval-request/access-approval-request-dal.ts index 671d2c1de..9872df067 100644 --- a/backend/src/ee/services/access-approval-request/access-approval-request-dal.ts +++ b/backend/src/ee/services/access-approval-request/access-approval-request-dal.ts @@ -65,7 +65,7 @@ export interface TAccessApprovalRequestDALFactory extends Omit environment } ] }); diff --git a/backend/src/ee/services/access-approval-request/access-approval-request-service.ts b/backend/src/ee/services/access-approval-request/access-approval-request-service.ts index 8b823ee91..03dd9e7de 100644 --- a/backend/src/ee/services/access-approval-request/access-approval-request-service.ts +++ b/backend/src/ee/services/access-approval-request/access-approval-request-service.ts @@ -86,6 +86,25 @@ export const accessApprovalRequestServiceFactory = ({ projectMicrosoftTeamsConfigDAL, projectSlackConfigDAL }: 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; + 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 ({ isTemporary, temporaryRange, @@ -323,13 +342,27 @@ export const accessApprovalRequestServiceFactory = ({ throw new NotFoundError({ message: `Secret approval request with ID '${requestId}' not found` }); } - const { policy, environment } = accessApprovalRequest; + const { policy, environments, permissions } = accessApprovalRequest; if (policy.deletedAt) { throw new BadRequestError({ 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({ actor, actorId, @@ -550,7 +583,7 @@ export const accessApprovalRequestServiceFactory = ({ requesterEmail: actingUser.email, bypassReason: bypassReason || "No reason provided", secretPath: policy.secretPath || "/", - environment, + environment: environment?.name || permissionEnvironment, approvalUrl: `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval`, requestType: "access" }, diff --git a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-dal.ts b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-dal.ts index fd8be93cf..c19286105 100644 --- a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-dal.ts +++ b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-dal.ts @@ -23,6 +23,7 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => { filter: TFindFilter, customFilter?: { sapId?: string; + envId?: string; } ) => tx(TableName.SecretApprovalPolicy) @@ -33,7 +34,17 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => { 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( TableName.SecretApprovalPolicyApprover, `${TableName.SecretApprovalPolicy}.id`, @@ -97,7 +108,7 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => { .select( tx.ref("name").withSchema(TableName.Environment).as("envName"), 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) ) .select(selectAllTableCols(TableName.SecretApprovalPolicy)) @@ -146,6 +157,15 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => { firstName, 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, customFilter?: { sapId?: string; + envId?: string; }, tx?: Knex ) => { @@ -221,6 +242,15 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => { mapper: ({ approverGroupUserId: 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 { ...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 }; }; diff --git a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-environment-dal.ts b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-environment-dal.ts new file mode 100644 index 000000000..58c2173de --- /dev/null +++ b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-environment-dal.ts @@ -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; + +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 }; +}; diff --git a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts index 80127c071..b6392d018 100644 --- a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts +++ b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts @@ -18,6 +18,7 @@ import { TSecretApprovalPolicyBypasserDALFactory } from "./secret-approval-policy-approver-dal"; import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal"; +import { TSecretApprovalPolicyEnvironmentDALFactory } from "./secret-approval-policy-environment-dal"; import { TCreateSapDTO, TDeleteSapDTO, @@ -35,12 +36,13 @@ const getPolicyScore = (policy: { secretPath?: string | null }) => type TSecretApprovalPolicyServiceFactoryDep = { permissionService: Pick; secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory; - projectEnvDAL: Pick; + projectEnvDAL: Pick; userDAL: Pick; secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory; secretApprovalPolicyBypasserDAL: TSecretApprovalPolicyBypasserDALFactory; licenseService: Pick; secretApprovalRequestDAL: Pick; + secretApprovalPolicyEnvironmentDAL: TSecretApprovalPolicyEnvironmentDALFactory; }; export type TSecretApprovalPolicyServiceFactory = ReturnType; @@ -50,27 +52,30 @@ export const secretApprovalPolicyServiceFactory = ({ permissionService, secretApprovalPolicyApproverDAL, secretApprovalPolicyBypasserDAL, + secretApprovalPolicyEnvironmentDAL, projectEnvDAL, userDAL, licenseService, secretApprovalRequestDAL }: TSecretApprovalPolicyServiceFactoryDep) => { const $policyExists = async ({ + envIds, envId, secretPath, policyId }: { - envId: string; + envIds?: string[]; + envId?: string; secretPath: string; policyId?: string; }) => { - const policy = await secretApprovalPolicyDAL - .findOne({ - envId, - secretPath, - deletedAt: null - }) - .catch(() => null); + if (!envIds && !envId) { + throw new BadRequestError({ message: "At least one environment should be provided" }); + } + const policy = await secretApprovalPolicyDAL.findPoliciesByEnvIdAndSecretPath({ + envIds: envId ? [envId] : envIds || [], + secretPath + }); return policyId ? policy && policy.id !== policyId : Boolean(policy); }; @@ -87,6 +92,7 @@ export const secretApprovalPolicyServiceFactory = ({ projectId, secretPath, environment, + environments, enforcementLevel, allowedSelfApprovals }: TCreateSapDTO) => { @@ -125,17 +131,23 @@ export const secretApprovalPolicyServiceFactory = ({ }); } - const env = await projectEnvDAL.findOne({ slug: environment, projectId }); - if (!env) { - throw new NotFoundError({ - message: `Environment with slug '${environment}' not found in project with ID ${projectId}` - }); + const mergedEnvs = (environment ? [environment] : environments) || []; + if (mergedEnvs.length === 0) { + throw new BadRequestError({ message: "Must provide either environment or environments" }); + } + 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 })) { - throw new BadRequestError({ - message: `A policy for secret path '${secretPath}' already exists in environment '${environment}'` - }); + for (const env of envs) { + // eslint-disable-next-line no-await-in-loop + 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[] = []; @@ -179,7 +191,7 @@ export const secretApprovalPolicyServiceFactory = ({ const secretApproval = await secretApprovalPolicyDAL.transaction(async (tx) => { const doc = await secretApprovalPolicyDAL.create( { - envId: env.id, + envId: envs[0].id, approvals, secretPath, name, @@ -188,6 +200,13 @@ export const secretApprovalPolicyServiceFactory = ({ }, tx ); + await secretApprovalPolicyEnvironmentDAL.insertMany( + envs.map((env) => ({ + envId: env.id, + policyId: doc.id + })), + tx + ); let userApproverIds = userApprovers; if (userApproverNames.length) { @@ -251,12 +270,13 @@ export const secretApprovalPolicyServiceFactory = ({ return doc; }); - return { ...secretApproval, environment: env, projectId }; + return { ...secretApproval, environments: envs, projectId, environment: envs[0] }; }; const updateSecretApprovalPolicy = async ({ approvers, bypassers, + environments, secretPath, name, actorId, @@ -286,16 +306,22 @@ export const secretApprovalPolicyServiceFactory = ({ 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 ( await $policyExists({ - envId: secretApprovalPolicy.envId, + envIds: envs.map((env) => env.id), secretPath: secretPath || secretApprovalPolicy.secretPath, policyId: secretApprovalPolicy.id }) ) { 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); if (bypasserUserIds.length) { @@ -438,7 +475,8 @@ export const secretApprovalPolicyServiceFactory = ({ }); return { ...updatedSap, - environment: secretApprovalPolicy.environment, + environments: secretApprovalPolicy.environments, + environment: secretApprovalPolicy.environments[0], projectId: secretApprovalPolicy.projectId }; }; @@ -483,7 +521,12 @@ export const secretApprovalPolicyServiceFactory = ({ const updatedPolicy = await secretApprovalPolicyDAL.softDeleteById(secretPolicyId, tx); 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 ({ @@ -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; // this will filter policies either without scoped to secret path or the one that matches with secret path const policiesFilteredByPath = policies.filter( diff --git a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-types.ts b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-types.ts index ba5334e5c..80369e638 100644 --- a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-types.ts +++ b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-types.ts @@ -5,7 +5,8 @@ import { ApproverType, BypasserType } from "../access-approval-policy/access-app export type TCreateSapDTO = { approvals: number; secretPath: string; - environment: string; + environment?: string; + environments?: string[]; approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[]; bypassers?: ( | { type: BypasserType.Group; id: string } @@ -29,6 +30,7 @@ export type TUpdateSapDTO = { name?: string; enforcementLevel?: EnforcementLevel; allowedSelfApprovals?: boolean; + environments?: string[]; } & Omit; export type TDeleteSapDTO = { diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts index c098d9b31..49f31bdf6 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts @@ -40,6 +40,13 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { `${TableName.SecretApprovalRequest}.policyId`, `${TableName.SecretApprovalPolicy}.id` ) + .leftJoin(TableName.SecretApprovalPolicyEnvironment, (bd) => { + bd.on( + `${TableName.SecretApprovalPolicy}.id`, + "=", + `${TableName.SecretApprovalPolicyEnvironment}.policyId` + ).andOn(`${TableName.SecretApprovalPolicyEnvironment}.envId`, "=", `${TableName.SecretFolder}.envId`); + }) .leftJoin( db(TableName.Users).as("statusChangedByUser"), `${TableName.SecretApprovalRequest}.statusChangedByUserId`, @@ -146,7 +153,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { tx.ref("projectId").withSchema(TableName.Environment), tx.ref("slug").withSchema(TableName.Environment).as("environment"), 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("allowedSelfApprovals").withSchema(TableName.SecretApprovalPolicy).as("policyAllowedSelfApprovals"), tx.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"), diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts index f7c1d4b1b..75efa948c 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts @@ -531,6 +531,11 @@ export const secretApprovalRequestServiceFactory = ({ 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({ actor: ActorType.USER, diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index a1e336844..f3f79260f 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -11,6 +11,7 @@ import { accessApprovalPolicyBypasserDALFactory } 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 { 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 { 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"; @@ -76,6 +77,7 @@ import { secretApprovalPolicyBypasserDALFactory } 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 { 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 { 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"; @@ -418,9 +420,11 @@ export const registerRoutes = async ( const accessApprovalPolicyApproverDAL = accessApprovalPolicyApproverDALFactory(db); const accessApprovalPolicyBypasserDAL = accessApprovalPolicyBypasserDALFactory(db); const accessApprovalRequestReviewerDAL = accessApprovalRequestReviewerDALFactory(db); + const accessApprovalPolicyEnvironmentDAL = accessApprovalPolicyEnvironmentDALFactory(db); const sapApproverDAL = secretApprovalPolicyApproverDALFactory(db); const sapBypasserDAL = secretApprovalPolicyBypasserDALFactory(db); + const sapEnvironmentDAL = secretApprovalPolicyEnvironmentDALFactory(db); const secretApprovalPolicyDAL = secretApprovalPolicyDALFactory(db); const secretApprovalRequestDAL = secretApprovalRequestDALFactory(db); const secretApprovalRequestReviewerDAL = secretApprovalRequestReviewerDALFactory(db); @@ -554,6 +558,7 @@ export const registerRoutes = async ( projectEnvDAL, secretApprovalPolicyApproverDAL: sapApproverDAL, secretApprovalPolicyBypasserDAL: sapBypasserDAL, + secretApprovalPolicyEnvironmentDAL: sapEnvironmentDAL, permissionService, secretApprovalPolicyDAL, licenseService, @@ -1141,7 +1146,9 @@ export const registerRoutes = async ( keyStore, licenseService, projectDAL, - folderDAL + folderDAL, + accessApprovalPolicyEnvironmentDAL, + secretApprovalPolicyEnvironmentDAL: sapEnvironmentDAL }); const projectRoleService = projectRoleServiceFactory({ @@ -1300,6 +1307,7 @@ export const registerRoutes = async ( accessApprovalPolicyDAL, accessApprovalPolicyApproverDAL, accessApprovalPolicyBypasserDAL, + accessApprovalPolicyEnvironmentDAL, groupDAL, permissionService, projectEnvDAL, diff --git a/backend/src/server/routes/sanitizedSchemas.ts b/backend/src/server/routes/sanitizedSchemas.ts index beef663b9..aba8663a3 100644 --- a/backend/src/server/routes/sanitizedSchemas.ts +++ b/backend/src/server/routes/sanitizedSchemas.ts @@ -93,6 +93,13 @@ export const sapPubSchema = SecretApprovalPoliciesSchema.merge( name: z.string(), slug: z.string() }), + environments: z.array( + z.object({ + id: z.string(), + name: z.string(), + slug: z.string() + }) + ), projectId: z.string() }) ); diff --git a/backend/src/services/project-env/project-env-service.ts b/backend/src/services/project-env/project-env-service.ts index 6773ee600..9d4fb86ee 100644 --- a/backend/src/services/project-env/project-env-service.ts +++ b/backend/src/services/project-env/project-env-service.ts @@ -1,8 +1,10 @@ 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 { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; 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 { BadRequestError, NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -19,6 +21,8 @@ type TProjectEnvServiceFactoryDep = { permissionService: Pick; licenseService: Pick; keyStore: Pick; + accessApprovalPolicyEnvironmentDAL: Pick; + secretApprovalPolicyEnvironmentDAL: Pick; }; export type TProjectEnvServiceFactory = ReturnType; @@ -29,7 +33,9 @@ export const projectEnvServiceFactory = ({ licenseService, keyStore, projectDAL, - folderDAL + folderDAL, + accessApprovalPolicyEnvironmentDAL, + secretApprovalPolicyEnvironmentDAL }: TProjectEnvServiceFactoryDep) => { const createEnvironment = async ({ projectId, @@ -216,6 +222,20 @@ export const projectEnvServiceFactory = ({ } 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); if (!doc) throw new NotFoundError({ diff --git a/frontend/src/hooks/api/accessApproval/mutation.tsx b/frontend/src/hooks/api/accessApproval/mutation.tsx index 3b05ff61b..ad7917a8f 100644 --- a/frontend/src/hooks/api/accessApproval/mutation.tsx +++ b/frontend/src/hooks/api/accessApproval/mutation.tsx @@ -17,7 +17,7 @@ export const useCreateAccessApprovalPolicy = () => { return useMutation({ mutationFn: async ({ - environment, + environments, projectSlug, approvals, approvers, @@ -29,7 +29,7 @@ export const useCreateAccessApprovalPolicy = () => { approvalsRequired }) => { const { data } = await apiRequest.post("/api/v1/access-approvals/policies", { - environment, + environments, projectSlug, approvals, bypassers, @@ -63,7 +63,8 @@ export const useUpdateAccessApprovalPolicy = () => { secretPath, enforcementLevel, allowedSelfApprovals, - approvalsRequired + approvalsRequired, + environments }) => { const { data } = await apiRequest.patch(`/api/v1/access-approvals/policies/${id}`, { approvals, @@ -73,7 +74,8 @@ export const useUpdateAccessApprovalPolicy = () => { name, enforcementLevel, allowedSelfApprovals, - approvalsRequired + approvalsRequired, + environments }); return data; }, diff --git a/frontend/src/hooks/api/accessApproval/types.ts b/frontend/src/hooks/api/accessApproval/types.ts index 70e9b883e..b07615372 100644 --- a/frontend/src/hooks/api/accessApproval/types.ts +++ b/frontend/src/hooks/api/accessApproval/types.ts @@ -10,7 +10,7 @@ export type TAccessApprovalPolicy = { secretPath: string; envId: string; workspace: string; - environment: WorkspaceEnv; + environments: WorkspaceEnv[]; projectId: string; policyType: PolicyType; approversRequired: boolean; @@ -166,7 +166,7 @@ export type TGetSecretApprovalPolicyOfBoardDTO = { export type TCreateAccessPolicyDTO = { projectSlug: string; name?: string; - environment: string; + environments: string[]; approvers?: Approver[]; bypassers?: Bypasser[]; approvals?: number; @@ -182,7 +182,7 @@ export type TUpdateAccessPolicyDTO = { approvers?: Approver[]; bypassers?: Bypasser[]; secretPath?: string; - environment?: string; + environments?: string[]; approvals?: number; enforcementLevel?: EnforcementLevel; allowedSelfApprovals: boolean; diff --git a/frontend/src/hooks/api/secretApproval/mutation.tsx b/frontend/src/hooks/api/secretApproval/mutation.tsx index e2d566e25..8370d0367 100644 --- a/frontend/src/hooks/api/secretApproval/mutation.tsx +++ b/frontend/src/hooks/api/secretApproval/mutation.tsx @@ -10,7 +10,7 @@ export const useCreateSecretApprovalPolicy = () => { return useMutation({ mutationFn: async ({ - environment, + environments, workspaceId, approvals, approvers, @@ -21,7 +21,7 @@ export const useCreateSecretApprovalPolicy = () => { allowedSelfApprovals }) => { const { data } = await apiRequest.post("/api/v1/secret-approvals", { - environment, + environments, workspaceId, approvals, approvers, @@ -53,7 +53,8 @@ export const useUpdateSecretApprovalPolicy = () => { secretPath, name, enforcementLevel, - allowedSelfApprovals + allowedSelfApprovals, + environments }) => { const { data } = await apiRequest.patch(`/api/v1/secret-approvals/${id}`, { approvals, @@ -62,7 +63,8 @@ export const useUpdateSecretApprovalPolicy = () => { secretPath, name, enforcementLevel, - allowedSelfApprovals + allowedSelfApprovals, + environments }); return data; }, diff --git a/frontend/src/hooks/api/secretApproval/types.ts b/frontend/src/hooks/api/secretApproval/types.ts index eeb734115..0f0b604f4 100644 --- a/frontend/src/hooks/api/secretApproval/types.ts +++ b/frontend/src/hooks/api/secretApproval/types.ts @@ -6,7 +6,7 @@ export type TSecretApprovalPolicy = { workspace: string; name: string; envId: string; - environment: WorkspaceEnv; + environments: WorkspaceEnv[]; secretPath?: string; approvals: number; approvers: Approver[]; @@ -48,7 +48,7 @@ export type TGetSecretApprovalPolicyOfBoardDTO = { export type TCreateSecretPolicyDTO = { workspaceId: string; name?: string; - environment: string; + environments: string[]; secretPath: string; approvers?: Approver[]; bypassers?: Bypasser[]; @@ -68,6 +68,7 @@ export type TUpdateSecretPolicyDTO = { enforcementLevel?: EnforcementLevel; // for invalidating list workspaceId: string; + environments?: string[]; }; export type TDeleteSecretPolicyDTO = { diff --git a/frontend/src/hooks/usePathAccessPolicies.tsx b/frontend/src/hooks/usePathAccessPolicies.tsx index 463e9638d..1fbc5fd52 100644 --- a/frontend/src/hooks/usePathAccessPolicies.tsx +++ b/frontend/src/hooks/usePathAccessPolicies.tsx @@ -49,7 +49,8 @@ export const usePathAccessPolicies = ({ secretPath, environment }: Params) => { return useMemo(() => { const pathPolicies = policies?.filter( (policy) => - policy.environment.slug === environment && matchesPath(secretPath, policy.secretPath) + policy.environments?.some((env) => env.slug === environment) && + matchesPath(secretPath, policy.secretPath) ); return { diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/SpecificPrivilegeSection.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/SpecificPrivilegeSection.tsx index 51a83c6e0..0256d3219 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/SpecificPrivilegeSection.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/SpecificPrivilegeSection.tsx @@ -155,8 +155,8 @@ export const SpecificPrivilegeSecretForm = ({ const selectablePaths = useMemo(() => { if (!policies) return []; - const environmentPolicies = policies.filter( - (policy) => policy.environment.slug === selectedEnvironment + const environmentPolicies = policies.filter((policy) => + policy.environments.find((env) => env.slug === selectedEnvironment) ); privilegeForm.setValue("secretPath", "", { diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/ApprovalPolicyList.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/ApprovalPolicyList.tsx index e84d228cd..3d292f9a2 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/ApprovalPolicyList.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/ApprovalPolicyList.tsx @@ -166,17 +166,20 @@ export const ApprovalPolicyList = ({ workspaceId }: IProps) => { const filteredPolicies = useMemo( () => policies - .filter(({ policyType, environment, name, secretPath }) => { + .filter(({ policyType, environments, name, secretPath }) => { 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; const searchValue = search.trim().toLowerCase(); return ( name.toLowerCase().includes(searchValue) || - environment.name.toLowerCase().includes(searchValue) || + environments.some((env) => env.name.toLowerCase().includes(searchValue)) || (secretPath ?? "*").toLowerCase().includes(searchValue) ); }) @@ -189,9 +192,18 @@ export const ApprovalPolicyList = ({ workspaceId }: IProps) => { .toLowerCase() .localeCompare(policyTwo.policyType.toLowerCase()); case PolicyOrderBy.Environment: - return policyOne.environment.name - .toLowerCase() - .localeCompare(policyTwo.environment.name.toLowerCase()); + // eslint-disable-next-line no-case-declarations + const getFirstEnvName = (policy: { environments: { name: string }[] }) => { + 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: return (policyOne.secretPath ?? "*") .toLowerCase() diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx index 183d8ab1c..e160a88f0 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx @@ -54,7 +54,7 @@ type Props = { const formSchema = z .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(), secretPath: z.string().trim().min(1), approvals: z.number().min(1).default(1), @@ -134,7 +134,7 @@ const Form = ({ values: editValues ? ({ ...editValues, - environment: editValues.environment, + environments: editValues.environments, userApprovers: editValues?.approvers ?.filter((approver) => approver.type === ApproverType.User) @@ -191,7 +191,7 @@ const Form = ({ const { currentWorkspace } = useWorkspace(); const { data: groups } = useListWorkspaceGroups(projectId); - const environments = currentWorkspace?.environments || []; + const availableEnvironments = currentWorkspace?.environments || []; const isAccessPolicyType = watch("policyType") === PolicyType.AccessPolicy; const { mutateAsync: createAccessApprovalPolicy } = useCreateAccessApprovalPolicy(); @@ -204,11 +204,11 @@ const Form = ({ const formUserBypassers = watch("userBypassers"); const formGroupBypassers = watch("groupBypassers"); - const formEnvironment = watch("environment")?.slug; + const formEnvironments = watch("environments"); const bypasserCount = (formUserBypassers || []).length + (formGroupBypassers || []).length; const handleCreatePolicy = async ({ - environment, + environments, groupApprovers, userApprovers, groupBypassers, @@ -226,7 +226,7 @@ const Form = ({ ...data, approvers: [...userApprovers, ...groupApprovers], bypassers: bypassers.length > 0 ? bypassers : undefined, - environment: environment.slug, + environments: environments.map((env) => env.slug), workspaceId: currentWorkspace?.id || "" }); } else { @@ -242,7 +242,7 @@ const Form = ({ numberOfApprovals: el.approvals })), bypassers: bypassers.length > 0 ? bypassers : undefined, - environment: environment.slug, + environments: environments.map((env) => env.slug), projectSlug }); } @@ -261,7 +261,7 @@ const Form = ({ }; const handleUpdatePolicy = async ({ - environment, + environments, userApprovers, groupApprovers, userBypassers, @@ -281,7 +281,8 @@ const Form = ({ ...data, approvers: [...userApprovers, ...groupApprovers], bypassers: bypassers.length > 0 ? bypassers : undefined, - workspaceId: currentWorkspace?.id || "" + workspaceId: currentWorkspace?.id || "", + environments: environments.map((env) => env.slug) }); } else { await updateAccessApprovalPolicy({ @@ -297,7 +298,7 @@ const Form = ({ numberOfApprovals: el.approvals })), bypassers: bypassers.length > 0 ? bypassers : undefined, - environment: environment.slug, + environments: environments.map((env) => env.slug), projectSlug }); } @@ -479,14 +480,14 @@ const Form = ({ )} /> ( option.slug} getOptionLabel={(option) => option.name} /> diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx index 408d718fa..b19ace7ea 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx @@ -37,7 +37,7 @@ import { TWorkspaceUser } from "@app/hooks/api/users/types"; interface IPolicy { id: string; name: string; - environment: WorkspaceEnv; + environments: WorkspaceEnv[]; projectId?: string; secretPath?: string; approvals: number; @@ -112,7 +112,7 @@ export const ApprovalPolicyRow = ({ onClick={() => setIsExpanded.toggle()} > {policy.name || Unnamed Policy} - {policy.environment.name} + {policy.environments.map((env) => env.name).join(", ")} {policy.secretPath || "*"} Date: Wed, 23 Jul 2025 10:37:23 -0300 Subject: [PATCH 02/34] Addressed PR suggestions --- ...22152841_add-policies-environments-table.ts | 2 ++ .../routes/v1/access-approval-policy-router.ts | 3 ++- .../access-approval-policy-dal.ts | 18 +++++++++--------- .../access-approval-policy-environment-dal.ts | 11 ++++++----- .../access-approval-policy-service.ts | 4 ++-- .../secret-approval-policy-dal.ts | 6 +++--- .../secret-approval-policy-environment-dal.ts | 11 ++++++----- .../secret-approval-policy-service.ts | 2 +- .../project-env/project-env-service.ts | 12 ++++++------ frontend/src/hooks/api/accessApproval/types.ts | 1 - frontend/src/hooks/api/secretApproval/types.ts | 1 - 11 files changed, 37 insertions(+), 34 deletions(-) diff --git a/backend/src/db/migrations/20250722152841_add-policies-environments-table.ts b/backend/src/db/migrations/20250722152841_add-policies-environments-table.ts index 3c2edc365..450a6c4d6 100644 --- a/backend/src/db/migrations/20250722152841_add-policies-environments-table.ts +++ b/backend/src/db/migrations/20250722152841_add-policies-environments-table.ts @@ -14,6 +14,7 @@ export async function up(knex: Knex): Promise { t.uuid("envId").notNullable(); t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE"); t.timestamps(true, true, true); + t.unique(["policyId", "envId"]); }); } if (!(await knex.schema.hasTable(TableName.SecretApprovalPolicyEnvironment))) { @@ -24,6 +25,7 @@ export async function up(knex: Knex): Promise { t.uuid("envId").notNullable(); t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE"); t.timestamps(true, true, true); + t.unique(["policyId", "envId"]); }); } diff --git a/backend/src/ee/routes/v1/access-approval-policy-router.ts b/backend/src/ee/routes/v1/access-approval-policy-router.ts index c01d2fc28..ef44344de 100644 --- a/backend/src/ee/routes/v1/access-approval-policy-router.ts +++ b/backend/src/ee/routes/v1/access-approval-policy-router.ts @@ -92,7 +92,8 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi actorOrgId: req.permission.orgId, ...req.body, projectSlug: req.body.projectSlug, - name: req.body.name ?? `${req.body.environment || req.body.environments?.join("-")}-${nanoid(3)}`, + name: + req.body.name ?? `${req.body.environment || req.body.environments?.join("-").substring(0, 250)}-${nanoid(3)}`, enforcementLevel: req.body.enforcementLevel }); return { approval }; diff --git a/backend/src/ee/services/access-approval-policy/access-approval-policy-dal.ts b/backend/src/ee/services/access-approval-policy/access-approval-policy-dal.ts index e01b51a5d..9baf762d6 100644 --- a/backend/src/ee/services/access-approval-policy/access-approval-policy-dal.ts +++ b/backend/src/ee/services/access-approval-policy/access-approval-policy-dal.ts @@ -144,7 +144,7 @@ export interface TAccessApprovalPolicyDALFactory } | undefined >; - findPoliciesByEnvIdAndSecretPath: ( + findPolicyByEnvIdAndSecretPath: ( { envIds, secretPath }: { envIds: string[]; secretPath: string }, tx?: Knex ) => Promise<{ @@ -399,17 +399,17 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo void qb.where(`${TableName.AccessApprovalPolicy}.id`, "=", customFilter.policyId); } }) - .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`) + .where((qb) => { + if (customFilter?.envId) { + void qb.where(`${TableName.AccessApprovalPolicyEnvironment}.envId`, "=", customFilter.envId); + } + }) .leftJoin( TableName.AccessApprovalPolicyApprover, `${TableName.AccessApprovalPolicy}.id`, @@ -612,7 +612,7 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo } }; - const findPoliciesByEnvIdAndSecretPath: TAccessApprovalPolicyDALFactory["findPoliciesByEnvIdAndSecretPath"] = async ( + const findPolicyByEnvIdAndSecretPath: TAccessApprovalPolicyDALFactory["findPolicyByEnvIdAndSecretPath"] = async ( { envIds, secretPath }, tx ) => { @@ -677,7 +677,7 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo }); return formattedDocs?.[0]; } catch (error) { - throw new DatabaseError({ error, name: "FindPoliciesByEnvIdAndSecretPath" }); + throw new DatabaseError({ error, name: "findPolicyByEnvIdAndSecretPath" }); } }; @@ -687,6 +687,6 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo findById, softDeleteById, findLastValidPolicy, - findPoliciesByEnvIdAndSecretPath + findPolicyByEnvIdAndSecretPath }; }; diff --git a/backend/src/ee/services/access-approval-policy/access-approval-policy-environment-dal.ts b/backend/src/ee/services/access-approval-policy/access-approval-policy-environment-dal.ts index 8485df036..f0d8079cf 100644 --- a/backend/src/ee/services/access-approval-policy/access-approval-policy-environment-dal.ts +++ b/backend/src/ee/services/access-approval-policy/access-approval-policy-environment-dal.ts @@ -3,14 +3,14 @@ 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"; +import { buildFindFilter, ormify, selectAllTableCols } from "@app/lib/knex"; export type TAccessApprovalPolicyEnvironmentDALFactory = ReturnType; export const accessApprovalPolicyEnvironmentDALFactory = (db: TDbClient) => { const accessApprovalPolicyEnvironmentOrm = ormify(db, TableName.AccessApprovalPolicyEnvironment); - const findAvailablePoliciesIds = async (envId: string, tx?: Knex) => { + const findAvailablePoliciesByEnvId = async (envId: string, tx?: Knex) => { try { const docs = await (tx || db.replicaNode())(TableName.AccessApprovalPolicyEnvironment) .join( @@ -18,14 +18,15 @@ export const accessApprovalPolicyEnvironmentDALFactory = (db: TDbClient) => { `${TableName.AccessApprovalPolicyEnvironment}.policyId`, `${TableName.AccessApprovalPolicy}.id` ) - .where({ [`${TableName.AccessApprovalPolicyEnvironment}.envId` as "envId"]: envId }) + // eslint-disable-next-line @typescript-eslint/no-misused-promises + .where(buildFindFilter({ envId }, TableName.AccessApprovalPolicyEnvironment)) .whereNull(`${TableName.AccessApprovalPolicy}.deletedAt`) .select(selectAllTableCols(TableName.AccessApprovalPolicyEnvironment)); return docs; } catch (error) { - throw new DatabaseError({ error, name: "findAvailablePoliciesIds" }); + throw new DatabaseError({ error, name: "findAvailablePoliciesByEnvId" }); } }; - return { ...accessApprovalPolicyEnvironmentOrm, findAvailablePoliciesIds }; + return { ...accessApprovalPolicyEnvironmentOrm, findAvailablePoliciesByEnvId }; }; diff --git a/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts b/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts index 693198f0e..0282175df 100644 --- a/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts +++ b/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts @@ -77,9 +77,9 @@ export const accessApprovalPolicyServiceFactory = ({ if (!envId && !envIds) { throw new BadRequestError({ message: "Must provide either envId or envIds" }); } - const policy = await accessApprovalPolicyDAL.findPoliciesByEnvIdAndSecretPath({ + const policy = await accessApprovalPolicyDAL.findPolicyByEnvIdAndSecretPath({ secretPath, - envIds: envId ? [envId] : envIds || [] + envIds: envId ? [envId] : (envIds as string[]) }); return policyId ? policy && policy.id !== policyId : Boolean(policy); }; diff --git a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-dal.ts b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-dal.ts index c19286105..3212fb902 100644 --- a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-dal.ts +++ b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-dal.ts @@ -265,7 +265,7 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => { return softDeletedPolicy; }; - const findPoliciesByEnvIdAndSecretPath = async ( + const findPolicyByEnvIdAndSecretPath = async ( { envIds, secretPath }: { envIds: string[]; secretPath: string }, tx?: Knex ) => { @@ -330,9 +330,9 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => { }); return formattedDocs?.[0]; } catch (error) { - throw new DatabaseError({ error, name: "FindPoliciesByEnvIdAndSecretPath" }); + throw new DatabaseError({ error, name: "findPolicyByEnvIdAndSecretPath" }); } }; - return { ...secretApprovalPolicyOrm, findById, find, softDeleteById, findPoliciesByEnvIdAndSecretPath }; + return { ...secretApprovalPolicyOrm, findById, find, softDeleteById, findPolicyByEnvIdAndSecretPath }; }; diff --git a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-environment-dal.ts b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-environment-dal.ts index 58c2173de..d12ace04c 100644 --- a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-environment-dal.ts +++ b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-environment-dal.ts @@ -3,14 +3,14 @@ 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"; +import { buildFindFilter, ormify, selectAllTableCols } from "@app/lib/knex"; export type TSecretApprovalPolicyEnvironmentDALFactory = ReturnType; export const secretApprovalPolicyEnvironmentDALFactory = (db: TDbClient) => { const secretApprovalPolicyEnvironmentOrm = ormify(db, TableName.SecretApprovalPolicyEnvironment); - const findAvailablePoliciesIds = async (envId: string, tx?: Knex) => { + const findAvailablePoliciesByEnvId = async (envId: string, tx?: Knex) => { try { const docs = await (tx || db.replicaNode())(TableName.SecretApprovalPolicyEnvironment) .join( @@ -18,14 +18,15 @@ export const secretApprovalPolicyEnvironmentDALFactory = (db: TDbClient) => { `${TableName.SecretApprovalPolicyEnvironment}.policyId`, `${TableName.SecretApprovalPolicy}.id` ) - .where({ [`${TableName.SecretApprovalPolicyEnvironment}.envId` as "envId"]: envId }) + // eslint-disable-next-line @typescript-eslint/no-misused-promises + .where(buildFindFilter({ envId }, TableName.SecretApprovalPolicyEnvironment)) .whereNull(`${TableName.SecretApprovalPolicy}.deletedAt`) .select(selectAllTableCols(TableName.SecretApprovalPolicyEnvironment)); return docs; } catch (error) { - throw new DatabaseError({ error, name: "findAvailablePoliciesIds" }); + throw new DatabaseError({ error, name: "findAvailablePoliciesByEnvId" }); } }; - return { ...secretApprovalPolicyEnvironmentOrm, findAvailablePoliciesIds }; + return { ...secretApprovalPolicyEnvironmentOrm, findAvailablePoliciesByEnvId }; }; diff --git a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts index b6392d018..e3d54bcf5 100644 --- a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts +++ b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts @@ -72,7 +72,7 @@ export const secretApprovalPolicyServiceFactory = ({ if (!envIds && !envId) { throw new BadRequestError({ message: "At least one environment should be provided" }); } - const policy = await secretApprovalPolicyDAL.findPoliciesByEnvIdAndSecretPath({ + const policy = await secretApprovalPolicyDAL.findPolicyByEnvIdAndSecretPath({ envIds: envId ? [envId] : envIds || [], secretPath }); diff --git a/backend/src/services/project-env/project-env-service.ts b/backend/src/services/project-env/project-env-service.ts index 9d4fb86ee..b76e93fed 100644 --- a/backend/src/services/project-env/project-env-service.ts +++ b/backend/src/services/project-env/project-env-service.ts @@ -21,8 +21,8 @@ type TProjectEnvServiceFactoryDep = { permissionService: Pick; licenseService: Pick; keyStore: Pick; - accessApprovalPolicyEnvironmentDAL: Pick; - secretApprovalPolicyEnvironmentDAL: Pick; + accessApprovalPolicyEnvironmentDAL: Pick; + secretApprovalPolicyEnvironmentDAL: Pick; }; export type TProjectEnvServiceFactory = ReturnType; @@ -222,15 +222,15 @@ export const projectEnvServiceFactory = ({ } const env = await projectEnvDAL.transaction(async (tx) => { - const secretApprovalRequest = await secretApprovalPolicyEnvironmentDAL.findAvailablePoliciesIds(id, tx); - if (secretApprovalRequest.length > 0) { + const secretApprovalPolicies = await secretApprovalPolicyEnvironmentDAL.findAvailablePoliciesByEnvId(id, tx); + if (secretApprovalPolicies.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) { + const accessApprovalPolicies = await accessApprovalPolicyEnvironmentDAL.findAvailablePoliciesByEnvId(id, tx); + if (accessApprovalPolicies.length > 0) { throw new BadRequestError({ message: "Environment is in use by an access approval policy", name: "DeleteEnvironment" diff --git a/frontend/src/hooks/api/accessApproval/types.ts b/frontend/src/hooks/api/accessApproval/types.ts index b07615372..bc569165b 100644 --- a/frontend/src/hooks/api/accessApproval/types.ts +++ b/frontend/src/hooks/api/accessApproval/types.ts @@ -8,7 +8,6 @@ export type TAccessApprovalPolicy = { name: string; approvals: number; secretPath: string; - envId: string; workspace: string; environments: WorkspaceEnv[]; projectId: string; diff --git a/frontend/src/hooks/api/secretApproval/types.ts b/frontend/src/hooks/api/secretApproval/types.ts index 0f0b604f4..8fd86624d 100644 --- a/frontend/src/hooks/api/secretApproval/types.ts +++ b/frontend/src/hooks/api/secretApproval/types.ts @@ -5,7 +5,6 @@ export type TSecretApprovalPolicy = { id: string; workspace: string; name: string; - envId: string; environments: WorkspaceEnv[]; secretPath?: string; approvals: number; From aec4ee905ee4bb0c6ee462a25002a8bb6e26c81a Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Thu, 24 Jul 2025 09:40:54 -0300 Subject: [PATCH 03/34] Add client secrets authentication on Azure CS app connection --- backend/src/lib/api-docs/constants.ts | 4 +- .../azure-client-secrets-connection-enums.ts | 3 +- .../azure-client-secrets-connection-fns.ts | 250 ++++++++++++------ ...azure-client-secrets-connection-schemas.ts | 55 +++- .../azure-client-secrets-connection-types.ts | 5 + frontend/src/helpers/appConnections.ts | 3 +- .../types/azure-client-secrets-connection.ts | 27 +- .../AppConnectionForm/AppConnectionForm.tsx | 4 +- .../AzureClientSecretsConnectionForm.tsx | 181 ++++++++++--- 9 files changed, 392 insertions(+), 140 deletions(-) diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index b6c00985a..a06df447d 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -2245,7 +2245,9 @@ export const AppConnections = { }, AZURE_CLIENT_SECRETS: { code: "The OAuth code to use to connect with Azure Client Secrets.", - tenantId: "The Tenant ID to use to connect with Azure Client Secrets." + tenantId: "The Tenant ID to use to connect with Azure Client Secrets.", + clientId: "The Client ID to use to connect with Azure Client Secrets.", + clientSecret: "The Client Secret to use to connect with Azure Client Secrets." }, AZURE_DEVOPS: { code: "The OAuth code to use to connect with Azure DevOps.", diff --git a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-enums.ts b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-enums.ts index 338126c1e..eb0521c64 100644 --- a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-enums.ts +++ b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-enums.ts @@ -1,3 +1,4 @@ export enum AzureClientSecretsConnectionMethod { - OAuth = "oauth" + OAuth = "oauth", + ClientSecret = "client-secret" } diff --git a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts index 463e40e7c..fa2563078 100644 --- a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts +++ b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-case-declarations */ import { AxiosError, AxiosResponse } from "axios"; import { getConfig } from "@app/lib/config/env"; @@ -16,6 +17,7 @@ import { AppConnection } from "../app-connection-enums"; import { AzureClientSecretsConnectionMethod } from "./azure-client-secrets-connection-enums"; import { ExchangeCodeAzureResponse, + TAzureClientSecretsConnectionAccessTokenCredentials, TAzureClientSecretsConnectionConfig, TAzureClientSecretsConnectionCredentials } from "./azure-client-secrets-connection-types"; @@ -26,7 +28,10 @@ export const getAzureClientSecretsConnectionListItem = () => { return { name: "Azure Client Secrets" as const, app: AppConnection.AzureClientSecrets as const, - methods: Object.values(AzureClientSecretsConnectionMethod) as [AzureClientSecretsConnectionMethod.OAuth], + methods: Object.values(AzureClientSecretsConnectionMethod) as [ + AzureClientSecretsConnectionMethod.OAuth, + AzureClientSecretsConnectionMethod.ClientSecret + ], oauthClientId: INF_APP_CONNECTION_AZURE_CLIENT_ID }; }; @@ -37,12 +42,6 @@ export const getAzureConnectionAccessToken = async ( kmsService: Pick ) => { const appCfg = getConfig(); - if (!appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID || !appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET) { - throw new BadRequestError({ - message: `Azure environment variables have not been configured` - }); - } - const appConnection = await appConnectionDAL.findById(connectionId); if (!appConnection) { @@ -63,34 +62,81 @@ export const getAzureConnectionAccessToken = async ( const { refreshToken } = credentials; const currentTime = Date.now(); + switch (appConnection.method) { + case AzureClientSecretsConnectionMethod.OAuth: + if (!appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID || !appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET) { + throw new BadRequestError({ + message: `Azure OAuth environment variables have not been configured` + }); + } + const { data } = await request.post( + IntegrationUrls.AZURE_TOKEN_URL.replace("common", credentials.tenantId || "common"), + new URLSearchParams({ + grant_type: "refresh_token", + scope: `openid offline_access https://graph.microsoft.com/.default`, + client_id: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID, + client_secret: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET, + refresh_token: refreshToken + }) + ); - const { data } = await request.post( - IntegrationUrls.AZURE_TOKEN_URL.replace("common", credentials.tenantId || "common"), - new URLSearchParams({ - grant_type: "refresh_token", - scope: `openid offline_access https://graph.microsoft.com/.default`, - client_id: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID, - client_secret: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET, - refresh_token: refreshToken - }) - ); + const updatedCredentials = { + ...credentials, + accessToken: data.access_token, + expiresAt: currentTime + data.expires_in * 1000, + refreshToken: data.refresh_token + }; - const updatedCredentials = { - ...credentials, - accessToken: data.access_token, - expiresAt: currentTime + data.expires_in * 1000, - refreshToken: data.refresh_token - }; + const encryptedCredentials = await encryptAppConnectionCredentials({ + credentials: updatedCredentials, + orgId: appConnection.orgId, + kmsService + }); - const encryptedCredentials = await encryptAppConnectionCredentials({ - credentials: updatedCredentials, - orgId: appConnection.orgId, - kmsService - }); + await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials }); - await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials }); + return data.access_token; + case AzureClientSecretsConnectionMethod.ClientSecret: + const accessTokenCredentials = (await decryptAppConnectionCredentials({ + orgId: appConnection.orgId, + kmsService, + encryptedCredentials: appConnection.encryptedCredentials + })) as TAzureClientSecretsConnectionAccessTokenCredentials; + const { accessToken, expiresAt, clientId, clientSecret, tenantId } = accessTokenCredentials; + if (accessToken && expiresAt && expiresAt > currentTime + 300000) { + return accessToken; + } - return data.access_token; + const { data: clientData } = await request.post( + IntegrationUrls.AZURE_TOKEN_URL.replace("common", tenantId || "common"), + new URLSearchParams({ + grant_type: "client_credentials", + scope: `https://graph.microsoft.com/.default`, + client_id: clientId, + client_secret: clientSecret + }) + ); + + const updatedClientCredentials = { + ...accessTokenCredentials, + accessToken: clientData.access_token, + expiresAt: currentTime + clientData.expires_in * 1000 + }; + + const encryptedClientCredentials = await encryptAppConnectionCredentials({ + credentials: updatedClientCredentials, + orgId: appConnection.orgId, + kmsService + }); + + await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials: encryptedClientCredentials }); + + return clientData.access_token; + default: + throw new InternalServerError({ + message: `Unhandled Azure connection method: ${appConnection.method as AzureClientSecretsConnectionMethod}` + }); + } }; export const validateAzureClientSecretsConnectionCredentials = async (config: TAzureClientSecretsConnectionConfig) => { @@ -98,69 +144,103 @@ export const validateAzureClientSecretsConnectionCredentials = async (config: TA const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_CLIENT_SECRET, SITE_URL } = getConfig(); - if (!SITE_URL) { - throw new InternalServerError({ message: "SITE_URL env var is required to complete Azure OAuth flow" }); - } - - if (!INF_APP_CONNECTION_AZURE_CLIENT_ID || !INF_APP_CONNECTION_AZURE_CLIENT_SECRET) { - throw new InternalServerError({ - message: `Azure ${getAppConnectionMethodName(method)} environment variables have not been configured` - }); - } - - let tokenResp: AxiosResponse | null = null; - let tokenError: AxiosError | null = null; - - try { - tokenResp = await request.post( - IntegrationUrls.AZURE_TOKEN_URL.replace("common", inputCredentials.tenantId || "common"), - new URLSearchParams({ - grant_type: "authorization_code", - code: inputCredentials.code, - scope: `openid offline_access https://graph.microsoft.com/.default`, - client_id: INF_APP_CONNECTION_AZURE_CLIENT_ID, - client_secret: INF_APP_CONNECTION_AZURE_CLIENT_SECRET, - redirect_uri: `${SITE_URL}/organization/app-connections/azure/oauth/callback` - }) - ); - } catch (e: unknown) { - if (e instanceof AxiosError) { - tokenError = e; - } else { - throw new BadRequestError({ - message: `Unable to validate connection: verify credentials` - }); - } - } - - if (tokenError) { - if (tokenError instanceof AxiosError) { - throw new BadRequestError({ - message: `Failed to get access token: ${ - (tokenError?.response?.data as { error_description?: string })?.error_description || "Unknown error" - }` - }); - } else { - throw new InternalServerError({ - message: "Failed to get access token" - }); - } - } - - if (!tokenResp) { - throw new InternalServerError({ - message: `Failed to get access token: Token was empty with no error` - }); - } - switch (method) { case AzureClientSecretsConnectionMethod.OAuth: + if (!SITE_URL) { + throw new InternalServerError({ message: "SITE_URL env var is required to complete Azure OAuth flow" }); + } + + if (!INF_APP_CONNECTION_AZURE_CLIENT_ID || !INF_APP_CONNECTION_AZURE_CLIENT_SECRET) { + throw new InternalServerError({ + message: `Azure ${getAppConnectionMethodName(method)} environment variables have not been configured` + }); + } + + let tokenResp: AxiosResponse | null = null; + let tokenError: AxiosError | null = null; + + try { + tokenResp = await request.post( + IntegrationUrls.AZURE_TOKEN_URL.replace("common", inputCredentials.tenantId || "common"), + new URLSearchParams({ + grant_type: "authorization_code", + code: inputCredentials.code, + scope: `openid offline_access https://graph.microsoft.com/.default`, + client_id: INF_APP_CONNECTION_AZURE_CLIENT_ID, + client_secret: INF_APP_CONNECTION_AZURE_CLIENT_SECRET, + redirect_uri: `${SITE_URL}/organization/app-connections/azure/oauth/callback` + }) + ); + } catch (e: unknown) { + if (e instanceof AxiosError) { + tokenError = e; + } else { + throw new BadRequestError({ + message: `Unable to validate connection: verify credentials` + }); + } + } + + if (tokenError) { + if (tokenError instanceof AxiosError) { + throw new BadRequestError({ + message: `Failed to get access token: ${ + (tokenError?.response?.data as { error_description?: string })?.error_description || "Unknown error" + }` + }); + } else { + throw new InternalServerError({ + message: "Failed to get access token" + }); + } + } + + if (!tokenResp) { + throw new InternalServerError({ + message: `Failed to get access token: Token was empty with no error` + }); + } + return { tenantId: inputCredentials.tenantId, accessToken: tokenResp.data.access_token, refreshToken: tokenResp.data.refresh_token, expiresAt: Date.now() + tokenResp.data.expires_in * 1000 }; + + case AzureClientSecretsConnectionMethod.ClientSecret: + const { tenantId, clientId, clientSecret } = inputCredentials; + try { + const { data: clientData } = await request.post( + IntegrationUrls.AZURE_TOKEN_URL.replace("common", tenantId || "common"), + new URLSearchParams({ + grant_type: "client_credentials", + scope: `https://graph.microsoft.com/.default`, + client_id: clientId, + client_secret: clientSecret + }) + ); + + return { + tenantId, + accessToken: clientData.access_token, + expiresAt: Date.now() + clientData.expires_in * 1000, + clientId, + clientSecret + }; + } catch (e: unknown) { + if (e instanceof AxiosError) { + throw new BadRequestError({ + message: `Failed to get access token: ${ + (e?.response?.data as { error_description?: string })?.error_description || "Unknown error" + }` + }); + } else { + throw new InternalServerError({ + message: "Failed to get access token" + }); + } + } default: throw new InternalServerError({ message: `Unhandled Azure connection method: ${method as AzureClientSecretsConnectionMethod}` diff --git a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts index 2b4e65a13..4d2c486d7 100644 --- a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts +++ b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts @@ -26,6 +26,32 @@ export const AzureClientSecretsConnectionOAuthOutputCredentialsSchema = z.object expiresAt: z.number() }); +export const AzureClientSecretsConnectionAccessTokenInputCredentialsSchema = z.object({ + clientId: z + .string() + .trim() + .min(1, "Client ID required") + .describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.clientId), + clientSecret: z + .string() + .trim() + .min(1, "Client Secret required") + .describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.clientSecret), + tenantId: z + .string() + .trim() + .min(1, "Tenant ID required") + .describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.tenantId) +}); + +export const AzureClientSecretsConnectionAccessTokenOutputCredentialsSchema = z.object({ + clientId: z.string(), + clientSecret: z.string(), + tenantId: z.string(), + accessToken: z.string(), + expiresAt: z.number() +}); + export const ValidateAzureClientSecretsConnectionCredentialsSchema = z.discriminatedUnion("method", [ z.object({ method: z @@ -34,6 +60,14 @@ export const ValidateAzureClientSecretsConnectionCredentialsSchema = z.discrimin credentials: AzureClientSecretsConnectionOAuthInputCredentialsSchema.describe( AppConnections.CREATE(AppConnection.AzureClientSecrets).credentials ) + }), + z.object({ + method: z + .literal(AzureClientSecretsConnectionMethod.ClientSecret) + .describe(AppConnections.CREATE(AppConnection.AzureClientSecrets).method), + credentials: AzureClientSecretsConnectionAccessTokenInputCredentialsSchema.describe( + AppConnections.CREATE(AppConnection.AzureClientSecrets).credentials + ) }) ]); @@ -43,9 +77,13 @@ export const CreateAzureClientSecretsConnectionSchema = ValidateAzureClientSecre export const UpdateAzureClientSecretsConnectionSchema = z .object({ - credentials: AzureClientSecretsConnectionOAuthInputCredentialsSchema.optional().describe( - AppConnections.UPDATE(AppConnection.AzureClientSecrets).credentials - ) + credentials: z + .union([ + AzureClientSecretsConnectionOAuthInputCredentialsSchema, + AzureClientSecretsConnectionAccessTokenInputCredentialsSchema + ]) + .optional() + .describe(AppConnections.UPDATE(AppConnection.AzureClientSecrets).credentials) }) .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.AzureClientSecrets)); @@ -59,6 +97,10 @@ export const AzureClientSecretsConnectionSchema = z.intersection( z.object({ method: z.literal(AzureClientSecretsConnectionMethod.OAuth), credentials: AzureClientSecretsConnectionOAuthOutputCredentialsSchema + }), + z.object({ + method: z.literal(AzureClientSecretsConnectionMethod.ClientSecret), + credentials: AzureClientSecretsConnectionAccessTokenOutputCredentialsSchema }) ]) ); @@ -69,6 +111,13 @@ export const SanitizedAzureClientSecretsConnectionSchema = z.discriminatedUnion( credentials: AzureClientSecretsConnectionOAuthOutputCredentialsSchema.pick({ tenantId: true }) + }), + BaseAzureClientSecretsConnectionSchema.extend({ + method: z.literal(AzureClientSecretsConnectionMethod.ClientSecret), + credentials: AzureClientSecretsConnectionAccessTokenOutputCredentialsSchema.pick({ + clientId: true, + tenantId: true + }) }) ]); diff --git a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-types.ts b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-types.ts index fb20fbadd..f6aa932d7 100644 --- a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-types.ts +++ b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-types.ts @@ -4,6 +4,7 @@ import { DiscriminativePick } from "@app/lib/types"; import { AppConnection } from "../app-connection-enums"; import { + AzureClientSecretsConnectionAccessTokenOutputCredentialsSchema, AzureClientSecretsConnectionOAuthOutputCredentialsSchema, AzureClientSecretsConnectionSchema, CreateAzureClientSecretsConnectionSchema, @@ -30,6 +31,10 @@ export type TAzureClientSecretsConnectionCredentials = z.infer< typeof AzureClientSecretsConnectionOAuthOutputCredentialsSchema >; +export type TAzureClientSecretsConnectionAccessTokenCredentials = z.infer< + typeof AzureClientSecretsConnectionAccessTokenOutputCredentialsSchema +>; + export interface ExchangeCodeAzureResponse { token_type: string; scope: string; diff --git a/frontend/src/helpers/appConnections.ts b/frontend/src/helpers/appConnections.ts index 1345cb493..8c8475767 100644 --- a/frontend/src/helpers/appConnections.ts +++ b/frontend/src/helpers/appConnections.ts @@ -171,7 +171,8 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) case RenderConnectionMethod.ApiKey: case ChecklyConnectionMethod.ApiKey: return { name: "API Key", icon: faKey }; - + case AzureClientSecretsConnectionMethod.ClientSecret: + return { name: "Client Secret", icon: faKey }; default: throw new Error(`Unhandled App Connection Method: ${method}`); } diff --git a/frontend/src/hooks/api/appConnections/types/azure-client-secrets-connection.ts b/frontend/src/hooks/api/appConnections/types/azure-client-secrets-connection.ts index 04ad167d2..220e3cdb2 100644 --- a/frontend/src/hooks/api/appConnections/types/azure-client-secrets-connection.ts +++ b/frontend/src/hooks/api/appConnections/types/azure-client-secrets-connection.ts @@ -2,15 +2,26 @@ import { AppConnection } from "@app/hooks/api/appConnections/enums"; import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection"; export enum AzureClientSecretsConnectionMethod { - OAuth = "oauth" + OAuth = "oauth", + ClientSecret = "client-secret" } export type TAzureClientSecretsConnection = TRootAppConnection & { app: AppConnection.AzureClientSecrets; -} & { - method: AzureClientSecretsConnectionMethod.OAuth; - credentials: { - code: string; - tenantId: string; - }; -}; +} & ( + | { + method: AzureClientSecretsConnectionMethod.OAuth; + credentials: { + code: string; + tenantId: string; + }; + } + | { + method: AzureClientSecretsConnectionMethod.ClientSecret; + credentials: { + clientSecret: string; + clientId: string; + tenantId: string; + }; + } + ); diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx index fa8c85b66..d4b87af81 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx @@ -114,7 +114,7 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => { case AppConnection.Camunda: return ; case AppConnection.AzureClientSecrets: - return ; + return ; case AppConnection.AzureDevOps: return ; case AppConnection.Windmill: @@ -222,7 +222,7 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => { case AppConnection.Camunda: return ; case AppConnection.AzureClientSecrets: - return ; + return ; case AppConnection.AzureDevOps: return ; case AppConnection.Windmill: diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureClientSecretsConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureClientSecretsConnectionForm.tsx index 9076c95ce..927189e6d 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureClientSecretsConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureClientSecretsConnectionForm.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-case-declarations */ import crypto from "crypto"; import { useState } from "react"; @@ -20,19 +21,83 @@ import { GenericAppConnectionsFields } from "./GenericAppConnectionFields"; +type ClientSecretForm = z.infer; + type Props = { appConnection?: TAzureClientSecretsConnection; + onSubmit: (formData: ClientSecretForm) => Promise; }; -const formSchema = genericAppConnectionFieldsSchema.extend({ +const baseSchema = genericAppConnectionFieldsSchema.extend({ app: z.literal(AppConnection.AzureClientSecrets), - method: z.nativeEnum(AzureClientSecretsConnectionMethod), - tenantId: z.string().trim().min(1, "Tenant ID is required") + method: z.nativeEnum(AzureClientSecretsConnectionMethod) }); +const oauthSchema = baseSchema.extend({ + tenantId: z.string().trim().min(1, "Tenant ID is required"), + method: z.literal(AzureClientSecretsConnectionMethod.OAuth) +}); + +const clientSecretSchema = baseSchema.extend({ + method: z.literal(AzureClientSecretsConnectionMethod.ClientSecret), + credentials: z.object({ + clientSecret: z.string().trim().min(1, "Client Secret is required"), + clientId: z.string().trim().min(1, "Client ID is required"), + tenantId: z.string().trim().min(1, "Tenant ID is required") + }) +}); + +const formSchema = z.discriminatedUnion("method", [oauthSchema, clientSecretSchema]); + type FormData = z.infer; -export const AzureClientSecretsConnectionForm = ({ appConnection }: Props) => { +const getDefaultValues = (appConnection?: TAzureClientSecretsConnection): Partial => { + if (!appConnection) { + return { + app: AppConnection.AzureClientSecrets, + method: AzureClientSecretsConnectionMethod.OAuth + }; + } + + const base = { + name: appConnection.name, + description: appConnection.description, + app: appConnection.app, + method: appConnection.method + }; + const { credentials } = appConnection; + + switch (appConnection.method) { + case AzureClientSecretsConnectionMethod.OAuth: + if ("tenantId" in credentials) { + return { + ...base, + method: AzureClientSecretsConnectionMethod.OAuth, + tenantId: credentials.tenantId + }; + } + break; + case AzureClientSecretsConnectionMethod.ClientSecret: + if ("clientSecret" in credentials && "clientId" in credentials) { + return { + ...base, + method: AzureClientSecretsConnectionMethod.ClientSecret, + credentials: { + clientSecret: credentials.clientSecret, + clientId: credentials.clientId, + tenantId: credentials.tenantId + } + }; + } + break; + default: + return base; + } + + return base; +}; + +export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit }: Props) => { const isUpdate = Boolean(appConnection); const [isRedirecting, setIsRedirecting] = useState(false); @@ -43,70 +108,51 @@ export const AzureClientSecretsConnectionForm = ({ appConnection }: Props) => { const form = useForm({ resolver: zodResolver(formSchema), - defaultValues: appConnection - ? { - ...appConnection, - tenantId: appConnection.credentials.tenantId - } - : { - app: AppConnection.AzureClientSecrets, - method: AzureClientSecretsConnectionMethod.OAuth - } + defaultValues: getDefaultValues(appConnection) }); const { handleSubmit, control, watch, + setValue, formState: { isSubmitting, isDirty } } = form; const selectedMethod = watch("method"); - const onSubmit = (formData: FormData) => { - setIsRedirecting(true); + const onSubmitHandler = (formData: FormData) => { const state = crypto.randomBytes(16).toString("hex"); - localStorage.setItem("latestCSRFToken", state); - localStorage.setItem( - "azureClientSecretsConnectionFormData", - JSON.stringify({ ...formData, connectionId: appConnection?.id }) - ); - switch (formData.method) { case AzureClientSecretsConnectionMethod.OAuth: + setIsRedirecting(true); + localStorage.setItem("latestCSRFToken", state); + localStorage.setItem( + "azureClientSecretsConnectionFormData", + JSON.stringify({ ...formData, connectionId: appConnection?.id }) + ); window.location.assign( `https://login.microsoftonline.com/${formData.tenantId || "common"}/oauth2/v2.0/authorize?client_id=${oauthClientId}&response_type=code&redirect_uri=${window.location.origin}/organization/app-connections/azure/oauth/callback&response_mode=query&scope=https://azconfig.io/.default%20openid%20offline_access&state=${state}<:>azure-client-secrets` ); break; + + case AzureClientSecretsConnectionMethod.ClientSecret: + onSubmit(formData); + break; default: throw new Error(`Unhandled Azure Connection method: ${(formData as FormData).method}`); } }; - const isMissingConfig = !oauthClientId; - + const isMissingConfig = + selectedMethod === AzureClientSecretsConnectionMethod.OAuth && !oauthClientId; const methodDetails = getAppConnectionMethodDetails(selectedMethod); return ( -
+ {!isUpdate && } - ( - - - - )} - /> - { )} /> + + ( + + { + field.onChange(e.target.value); + setValue("credentials.tenantId", e.target.value); + }} + /> + + )} + /> + + {/* Access Token-specific fields */} + {selectedMethod === AzureClientSecretsConnectionMethod.ClientSecret && ( + <> + ( + + + + )} + /> + ( + + + + )} + /> + + )} +
); } diff --git a/frontend/src/hoc/withProjectPermission/withProjectPermission.tsx b/frontend/src/hoc/withProjectPermission/withProjectPermission.tsx index 564d44adc..7ba6efc57 100644 --- a/frontend/src/hoc/withProjectPermission/withProjectPermission.tsx +++ b/frontend/src/hoc/withProjectPermission/withProjectPermission.tsx @@ -1,14 +1,12 @@ import { ComponentType } from "react"; import { AbilityTuple } from "@casl/ability"; -import { faLock } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { twMerge } from "tailwind-merge"; +import { AccessRestrictedBanner } from "@app/components/v2"; import { useProjectPermission } from "@app/context"; import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext"; type Props = { - className?: string; containerClassName?: string; action: T[0]; subject: T[1]; @@ -16,7 +14,7 @@ type Props = { export const withProjectPermission = ( Component: ComponentType, "action" | "subject"> & T>, - { action, subject, className, containerClassName }: Props + { action, subject, containerClassName }: Props ) => { const HOC = (hocProps: Omit, "action" | "subject"> & T) => { const { permission } = useProjectPermission(); @@ -31,23 +29,7 @@ export const withProjectPermission = ( containerClassName )} > -
-
- -
-
-
Permission Denied
-
- You do not have permission to this page.
Kindly contact your organization - administrator -
-
-
+ ); } diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx index c11ff454d..3c2c0c859 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx @@ -948,12 +948,12 @@ const Page = () => { /> )} {noAccessSecretCount > 0 && } - {!canReadSecret && - !canReadDynamicSecret && - !canReadSecretImports && - folders?.length === 0 && } + {!canReadSecret && + !canReadDynamicSecret && + !canReadSecretImports && + folders?.length === 0 && } {!isDetailsLoading && (totalCount > 0 || pendingChanges.secrets.length > 0 || diff --git a/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectScanningConfigTab/ProjectScanningConfigTab.tsx b/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectScanningConfigTab/ProjectScanningConfigTab.tsx index 73fd26c8b..e8d3206d8 100644 --- a/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectScanningConfigTab/ProjectScanningConfigTab.tsx +++ b/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectScanningConfigTab/ProjectScanningConfigTab.tsx @@ -1,6 +1,6 @@ import { faBan } from "@fortawesome/free-solid-svg-icons"; -import { ContentLoader, EmptyState } from "@app/components/v2"; +import { AccessRestrictedBanner, ContentLoader, EmptyState } from "@app/components/v2"; import { useSubscription, useWorkspace } from "@app/context"; import { useGetSecretScanningConfig } from "@app/hooks/api/secretScanningV2"; @@ -16,16 +16,14 @@ export const ProjectScanningConfigTab = () => { if (!subscription.secretScanning) { return ( -
- +
+ Your current plan doesn't support Secret Scanning.
Please contact Infisical Support or reach out through our Slack channel for assistance. - + } />
From 4e960445a4abcde9bd0d97ad14ead5430ee7cf13 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Thu, 24 Jul 2025 15:56:14 -0700 Subject: [PATCH 07/34] chore: remove unused tw css --- .../v2/AccessRestrictedBanner/AccessRestrictedBanner.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/v2/AccessRestrictedBanner/AccessRestrictedBanner.tsx b/frontend/src/components/v2/AccessRestrictedBanner/AccessRestrictedBanner.tsx index 27fa3c982..1979039dd 100644 --- a/frontend/src/components/v2/AccessRestrictedBanner/AccessRestrictedBanner.tsx +++ b/frontend/src/components/v2/AccessRestrictedBanner/AccessRestrictedBanner.tsx @@ -16,7 +16,7 @@ export const AccessRestrictedBanner = ({ ) }: Props) => { return ( -
+
{title}
{body}
From 7365f60835b971261492c2fe945e45c69150ad68 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Fri, 25 Jul 2025 01:23:01 -0300 Subject: [PATCH 08/34] Small code improvements --- .../azure-client-secrets-connection-fns.ts | 4 ++-- .../azure-client-secrets-connection-schemas.ts | 14 ++++++++------ .../azure-client-secrets-connection-types.ts | 6 +++--- .../AzureClientSecretsConnectionForm.tsx | 6 ++---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts index fa2563078..a28217320 100644 --- a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts +++ b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts @@ -17,7 +17,7 @@ import { AppConnection } from "../app-connection-enums"; import { AzureClientSecretsConnectionMethod } from "./azure-client-secrets-connection-enums"; import { ExchangeCodeAzureResponse, - TAzureClientSecretsConnectionAccessTokenCredentials, + TAzureClientSecretsConnectionClientSecretCredentials, TAzureClientSecretsConnectionConfig, TAzureClientSecretsConnectionCredentials } from "./azure-client-secrets-connection-types"; @@ -101,7 +101,7 @@ export const getAzureConnectionAccessToken = async ( orgId: appConnection.orgId, kmsService, encryptedCredentials: appConnection.encryptedCredentials - })) as TAzureClientSecretsConnectionAccessTokenCredentials; + })) as TAzureClientSecretsConnectionClientSecretCredentials; const { accessToken, expiresAt, clientId, clientSecret, tenantId } = accessTokenCredentials; if (accessToken && expiresAt && expiresAt > currentTime + 300000) { return accessToken; diff --git a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts index 4d2c486d7..41c9a1d36 100644 --- a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts +++ b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts @@ -26,16 +26,18 @@ export const AzureClientSecretsConnectionOAuthOutputCredentialsSchema = z.object expiresAt: z.number() }); -export const AzureClientSecretsConnectionAccessTokenInputCredentialsSchema = z.object({ +export const AzureClientSecretsConnectionClientSecretInputCredentialsSchema = z.object({ clientId: z .string() .trim() .min(1, "Client ID required") + .max(50, "Client ID must be at most 50 characters long") .describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.clientId), clientSecret: z .string() .trim() .min(1, "Client Secret required") + .max(50, "Client Secret must be at most 50 characters long") .describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.clientSecret), tenantId: z .string() @@ -44,7 +46,7 @@ export const AzureClientSecretsConnectionAccessTokenInputCredentialsSchema = z.o .describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.tenantId) }); -export const AzureClientSecretsConnectionAccessTokenOutputCredentialsSchema = z.object({ +export const AzureClientSecretsConnectionClientSecretOutputCredentialsSchema = z.object({ clientId: z.string(), clientSecret: z.string(), tenantId: z.string(), @@ -65,7 +67,7 @@ export const ValidateAzureClientSecretsConnectionCredentialsSchema = z.discrimin method: z .literal(AzureClientSecretsConnectionMethod.ClientSecret) .describe(AppConnections.CREATE(AppConnection.AzureClientSecrets).method), - credentials: AzureClientSecretsConnectionAccessTokenInputCredentialsSchema.describe( + credentials: AzureClientSecretsConnectionClientSecretInputCredentialsSchema.describe( AppConnections.CREATE(AppConnection.AzureClientSecrets).credentials ) }) @@ -80,7 +82,7 @@ export const UpdateAzureClientSecretsConnectionSchema = z credentials: z .union([ AzureClientSecretsConnectionOAuthInputCredentialsSchema, - AzureClientSecretsConnectionAccessTokenInputCredentialsSchema + AzureClientSecretsConnectionClientSecretInputCredentialsSchema ]) .optional() .describe(AppConnections.UPDATE(AppConnection.AzureClientSecrets).credentials) @@ -100,7 +102,7 @@ export const AzureClientSecretsConnectionSchema = z.intersection( }), z.object({ method: z.literal(AzureClientSecretsConnectionMethod.ClientSecret), - credentials: AzureClientSecretsConnectionAccessTokenOutputCredentialsSchema + credentials: AzureClientSecretsConnectionClientSecretOutputCredentialsSchema }) ]) ); @@ -114,7 +116,7 @@ export const SanitizedAzureClientSecretsConnectionSchema = z.discriminatedUnion( }), BaseAzureClientSecretsConnectionSchema.extend({ method: z.literal(AzureClientSecretsConnectionMethod.ClientSecret), - credentials: AzureClientSecretsConnectionAccessTokenOutputCredentialsSchema.pick({ + credentials: AzureClientSecretsConnectionClientSecretOutputCredentialsSchema.pick({ clientId: true, tenantId: true }) diff --git a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-types.ts b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-types.ts index f6aa932d7..1ad5a3411 100644 --- a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-types.ts +++ b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-types.ts @@ -4,7 +4,7 @@ import { DiscriminativePick } from "@app/lib/types"; import { AppConnection } from "../app-connection-enums"; import { - AzureClientSecretsConnectionAccessTokenOutputCredentialsSchema, + AzureClientSecretsConnectionClientSecretOutputCredentialsSchema, AzureClientSecretsConnectionOAuthOutputCredentialsSchema, AzureClientSecretsConnectionSchema, CreateAzureClientSecretsConnectionSchema, @@ -31,8 +31,8 @@ export type TAzureClientSecretsConnectionCredentials = z.infer< typeof AzureClientSecretsConnectionOAuthOutputCredentialsSchema >; -export type TAzureClientSecretsConnectionAccessTokenCredentials = z.infer< - typeof AzureClientSecretsConnectionAccessTokenOutputCredentialsSchema +export type TAzureClientSecretsConnectionClientSecretCredentials = z.infer< + typeof AzureClientSecretsConnectionClientSecretOutputCredentialsSchema >; export interface ExchangeCodeAzureResponse { diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureClientSecretsConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureClientSecretsConnectionForm.tsx index 927189e6d..6aa6ee63c 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureClientSecretsConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureClientSecretsConnectionForm.tsx @@ -205,7 +205,7 @@ export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit }: Pr > { field.onChange(e.target.value); setValue("credentials.tenantId", e.target.value); @@ -223,12 +223,11 @@ export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit }: Pr control={control} render={({ field, fieldState: { error } }) => ( - + )} /> @@ -237,7 +236,6 @@ export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit }: Pr control={control} render={({ field, fieldState: { error } }) => ( Date: Fri, 25 Jul 2025 01:37:25 -0300 Subject: [PATCH 09/34] Minor fixes on policies multi env migration --- ...2152841_add-policies-environments-table.ts | 11 +++++---- .../access-approval-policy-service.ts | 23 +++++++++++-------- .../secret-approval-policy-service.ts | 23 +++++++++++-------- 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/backend/src/db/migrations/20250722152841_add-policies-environments-table.ts b/backend/src/db/migrations/20250722152841_add-policies-environments-table.ts index c8ee3d524..57ec13203 100644 --- a/backend/src/db/migrations/20250722152841_add-policies-environments-table.ts +++ b/backend/src/db/migrations/20250722152841_add-policies-environments-table.ts @@ -12,11 +12,13 @@ export async function up(knex: Knex): Promise { 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.foreign("envId").references("id").inTable(TableName.Environment); t.timestamps(true, true, true); t.unique(["policyId", "envId"]); }); + await createOnUpdateTrigger(knex, TableName.AccessApprovalPolicyEnvironment); + const existingAccessApprovalPolicies = await knex(TableName.AccessApprovalPolicy) .select(selectAllTableCols(TableName.AccessApprovalPolicy)) .whereNotNull(`${TableName.AccessApprovalPolicy}.envId`); @@ -36,11 +38,13 @@ export async function up(knex: Knex): Promise { 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.foreign("envId").references("id").inTable(TableName.Environment); t.timestamps(true, true, true); t.unique(["policyId", "envId"]); }); + await createOnUpdateTrigger(knex, TableName.SecretApprovalPolicyEnvironment); + const existingSecretApprovalPolicies = await knex(TableName.SecretApprovalPolicy) .select(selectAllTableCols(TableName.SecretApprovalPolicy)) .whereNotNull(`${TableName.SecretApprovalPolicy}.envId`); @@ -68,9 +72,6 @@ export async function up(knex: Knex): Promise { // 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); } export async function down(knex: Knex): Promise { diff --git a/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts b/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts index d6187d418..0b3c4e128 100644 --- a/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts +++ b/backend/src/ee/services/access-approval-policy/access-approval-policy-service.ts @@ -354,16 +354,19 @@ export const accessApprovalPolicyServiceFactory = ({ envs = await projectEnvDAL.find({ $in: { slug: environments }, projectId: accessApprovalPolicy.projectId }); } - if ( - await $policyExists({ - envIds: envs.map((env) => env.id), - secretPath: secretPath || accessApprovalPolicy.secretPath, - policyId: accessApprovalPolicy.id - }) - ) { - throw new BadRequestError({ - message: `A policy for secret path '${secretPath}' already exists` - }); + for (const env of envs) { + if ( + // eslint-disable-next-line no-await-in-loop + await $policyExists({ + envId: env.id, + secretPath: secretPath || accessApprovalPolicy.secretPath, + policyId: accessApprovalPolicy.id + }) + ) { + throw new BadRequestError({ + message: `A policy for secret path '${secretPath || accessApprovalPolicy.secretPath}' already exists in environment '${env.slug}'` + }); + } } const { permission } = await permissionService.getProjectPermission({ diff --git a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts index 2bff6f440..96757dc22 100644 --- a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts +++ b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts @@ -315,16 +315,19 @@ export const secretApprovalPolicyServiceFactory = ({ ) { envs = await projectEnvDAL.find({ $in: { slug: environments }, projectId: secretApprovalPolicy.projectId }); } - if ( - await $policyExists({ - envIds: envs.map((env) => env.id), - secretPath: secretPath || secretApprovalPolicy.secretPath, - policyId: secretApprovalPolicy.id - }) - ) { - throw new BadRequestError({ - message: `A policy for secret path '${secretPath}' already exists` - }); + for (const env of envs) { + if ( + // eslint-disable-next-line no-await-in-loop + await $policyExists({ + envId: env.id, + secretPath: secretPath || secretApprovalPolicy.secretPath, + policyId: secretApprovalPolicy.id + }) + ) { + throw new BadRequestError({ + message: `A policy for secret path '${secretPath || secretApprovalPolicy.secretPath}' already exists in environment '${env.slug}'` + }); + } } const { permission } = await permissionService.getProjectPermission({ From 418aca8af067847631086e811f1a04fcac45bd7b Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 25 Jul 2025 19:50:28 +0400 Subject: [PATCH 10/34] feat(secret-sync/render): auto redeploy on sync --- backend/src/lib/api-docs/constants.ts | 4 ++ .../secret-sync/render/render-sync-fns.ts | 30 ++++++++++++++ .../secret-sync/render/render-sync-schemas.ts | 16 ++++++-- .../RenderSyncOptionsFields.tsx | 40 +++++++++++++++++++ .../SecretSyncOptionsFields.tsx | 5 ++- .../RenderSyncReviewFields.tsx | 21 ++++++++++ .../SecretSyncReviewFields.tsx | 3 +- .../schemas/render-sync-destination-schema.ts | 6 ++- .../src/hooks/api/secretSyncs/render-sync.ts | 4 ++ .../RenderSyncOptionsSection.tsx | 27 +++++++++++++ .../SecretSyncOptionsSection.tsx | 5 ++- 11 files changed, 154 insertions(+), 7 deletions(-) create mode 100644 frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/RenderSyncOptionsFields.tsx create mode 100644 frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/RenderSyncOptionsSection.tsx diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index b6c00985a..318c085a1 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -2373,6 +2373,10 @@ export const SecretSyncs = { keyId: "The AWS KMS key ID or alias to use when encrypting parameters synced by Infisical.", tags: "Optional tags to add to secrets synced by Infisical.", syncSecretMetadataAsTags: `Whether Infisical secret metadata should be added as tags to secrets synced by Infisical.` + }, + RENDER: { + autoRedeployServices: + "Whether Infisical should automatically redeploy the configured Render service upon secret changes." } }, DESTINATION_CONFIG: { diff --git a/backend/src/services/secret-sync/render/render-sync-fns.ts b/backend/src/services/secret-sync/render/render-sync-fns.ts index 36e97e620..8af3653ca 100644 --- a/backend/src/services/secret-sync/render/render-sync-fns.ts +++ b/backend/src/services/secret-sync/render/render-sync-fns.ts @@ -97,6 +97,28 @@ const batchUpdateEnvironmentSecrets = async ( ); }; +const redeployService = async (secretSync: TRenderSyncWithCredentials) => { + const { + destinationConfig, + connection: { + credentials: { apiKey } + } + } = secretSync; + + await makeRequestWithRetry(() => + request.post( + `${IntegrationUrls.RENDER_API_URL}/v1/services/${destinationConfig.serviceId}/deploys`, + {}, + { + headers: { + Authorization: `Bearer ${apiKey}`, + "Accept-Encoding": "application/json" + } + } + ) + ); +}; + export const RenderSyncFns = { syncSecrets: async (secretSync: TRenderSyncWithCredentials, secretMap: TSecretMap) => { const renderSecrets = await getRenderEnvironmentSecrets(secretSync); @@ -131,6 +153,10 @@ export const RenderSyncFns = { } await batchUpdateEnvironmentSecrets(secretSync, finalEnvVars); + + if (secretSync.syncOptions.autoRedeployServices) { + await redeployService(secretSync); + } }, getSecrets: async (secretSync: TRenderSyncWithCredentials): Promise => { @@ -151,5 +177,9 @@ export const RenderSyncFns = { } } await batchUpdateEnvironmentSecrets(secretSync, finalEnvVars); + + if (secretSync.syncOptions.autoRedeployServices) { + await redeployService(secretSync); + } } }; diff --git a/backend/src/services/secret-sync/render/render-sync-schemas.ts b/backend/src/services/secret-sync/render/render-sync-schemas.ts index 77414c17c..0d6e93987 100644 --- a/backend/src/services/secret-sync/render/render-sync-schemas.ts +++ b/backend/src/services/secret-sync/render/render-sync-schemas.ts @@ -20,23 +20,33 @@ const RenderSyncDestinationConfigSchema = z.discriminatedUnion("scope", [ }) ]); +const RenderSyncOptionsSchema = z.object({ + autoRedeployServices: z.boolean().optional().describe(SecretSyncs.ADDITIONAL_SYNC_OPTIONS.RENDER.autoRedeployServices) +}); + const RenderSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; -export const RenderSyncSchema = BaseSecretSyncSchema(SecretSync.Render, RenderSyncOptionsConfig).extend({ +export const RenderSyncSchema = BaseSecretSyncSchema( + SecretSync.Render, + RenderSyncOptionsConfig, + RenderSyncOptionsSchema +).extend({ destination: z.literal(SecretSync.Render), destinationConfig: RenderSyncDestinationConfigSchema }); export const CreateRenderSyncSchema = GenericCreateSecretSyncFieldsSchema( SecretSync.Render, - RenderSyncOptionsConfig + RenderSyncOptionsConfig, + RenderSyncOptionsSchema ).extend({ destinationConfig: RenderSyncDestinationConfigSchema }); export const UpdateRenderSyncSchema = GenericUpdateSecretSyncFieldsSchema( SecretSync.Render, - RenderSyncOptionsConfig + RenderSyncOptionsConfig, + RenderSyncOptionsSchema ).extend({ destinationConfig: RenderSyncDestinationConfigSchema.optional() }); diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/RenderSyncOptionsFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/RenderSyncOptionsFields.tsx new file mode 100644 index 000000000..6d4173bd0 --- /dev/null +++ b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/RenderSyncOptionsFields.tsx @@ -0,0 +1,40 @@ +import { Controller, useFormContext } from "react-hook-form"; +import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { FormControl, Switch, Tooltip } from "@app/components/v2"; +import { SecretSync } from "@app/hooks/api/secretSyncs"; + +import { TSecretSyncForm } from "../schemas"; + +export const RenderSyncOptionsFields = () => { + const { control } = useFormContext(); + + return ( + ( + + + Auto Redeploy Services On Sync + If enabled, services will be automatically redeployed upon secret changes.

+ } + > + +
+
+
+ )} + /> + ); +}; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx index 50ea46ac0..cb7a40559 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx @@ -14,6 +14,7 @@ import { SecretSync, useSecretSyncOption } from "@app/hooks/api/secretSyncs"; import { TSecretSyncForm } from "../schemas"; import { AwsParameterStoreSyncOptionsFields } from "./AwsParameterStoreSyncOptionsFields"; import { AwsSecretsManagerSyncOptionsFields } from "./AwsSecretsManagerSyncOptionsFields"; +import { RenderSyncOptionsFields } from "./RenderSyncOptionsFields"; type Props = { hideInitialSync?: boolean; @@ -38,6 +39,9 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => { case SecretSync.AWSSecretsManager: AdditionalSyncOptionsFieldsComponent = ; break; + case SecretSync.Render: + AdditionalSyncOptionsFieldsComponent = ; + break; case SecretSync.GitHub: case SecretSync.GCPSecretManager: case SecretSync.AzureKeyVault: @@ -54,7 +58,6 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => { case SecretSync.OnePass: case SecretSync.OCIVault: case SecretSync.Heroku: - case SecretSync.Render: case SecretSync.Flyio: case SecretSync.GitLab: case SecretSync.CloudflarePages: diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/RenderSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/RenderSyncReviewFields.tsx index becc46c1d..15f5b6625 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/RenderSyncReviewFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/RenderSyncReviewFields.tsx @@ -2,8 +2,29 @@ import { useFormContext } from "react-hook-form"; import { GenericFieldLabel } from "@app/components/secret-syncs"; import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas"; +import { Badge } from "@app/components/v2"; import { SecretSync } from "@app/hooks/api/secretSyncs"; +export const RenderSyncOptionsReviewFields = () => { + const { watch } = useFormContext(); + + const [{ autoRedeployServices }] = watch(["syncOptions"]); + + return ( +
+ {autoRedeployServices ? ( + + Enabled + + ) : ( + + Disabled + + )} +
+ ); +}; + export const RenderSyncReviewFields = () => { const { watch } = useFormContext(); const serviceName = watch("destinationConfig.serviceName"); diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx index c1194a4c1..5ee53f2e3 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx @@ -35,7 +35,7 @@ import { HumanitecSyncReviewFields } from "./HumanitecSyncReviewFields"; import { OCIVaultSyncReviewFields } from "./OCIVaultSyncReviewFields"; import { OnePassSyncReviewFields } from "./OnePassSyncReviewFields"; import { RailwaySyncReviewFields } from "./RailwaySyncReviewFields"; -import { RenderSyncReviewFields } from "./RenderSyncReviewFields"; +import { RenderSyncOptionsReviewFields, RenderSyncReviewFields } from "./RenderSyncReviewFields"; import { SupabaseSyncReviewFields } from "./SupabaseSyncReviewFields"; import { TeamCitySyncReviewFields } from "./TeamCitySyncReviewFields"; import { TerraformCloudSyncReviewFields } from "./TerraformCloudSyncReviewFields"; @@ -121,6 +121,7 @@ export const SecretSyncReviewFields = () => { break; case SecretSync.Render: DestinationFieldsComponent = ; + AdditionalSyncOptionsFieldsComponent = ; break; case SecretSync.Flyio: DestinationFieldsComponent = ; diff --git a/frontend/src/components/secret-syncs/forms/schemas/render-sync-destination-schema.ts b/frontend/src/components/secret-syncs/forms/schemas/render-sync-destination-schema.ts index 16b213421..da81e121d 100644 --- a/frontend/src/components/secret-syncs/forms/schemas/render-sync-destination-schema.ts +++ b/frontend/src/components/secret-syncs/forms/schemas/render-sync-destination-schema.ts @@ -4,7 +4,11 @@ import { BaseSecretSyncSchema } from "@app/components/secret-syncs/forms/schemas import { SecretSync } from "@app/hooks/api/secretSyncs"; import { RenderSyncScope, RenderSyncType } from "@app/hooks/api/secretSyncs/render-sync"; -export const RenderSyncDestinationSchema = BaseSecretSyncSchema().merge( +export const RenderSyncDestinationSchema = BaseSecretSyncSchema( + z.object({ + autoRedeployServices: z.boolean().optional() + }) +).merge( z.object({ destination: z.literal(SecretSync.Render), destinationConfig: z.discriminatedUnion("scope", [ diff --git a/frontend/src/hooks/api/secretSyncs/render-sync.ts b/frontend/src/hooks/api/secretSyncs/render-sync.ts index ecac7d077..61aecc53f 100644 --- a/frontend/src/hooks/api/secretSyncs/render-sync.ts +++ b/frontend/src/hooks/api/secretSyncs/render-sync.ts @@ -16,6 +16,10 @@ export type TRenderSync = TRootSecretSync & { name: string; id: string; }; + + syncOptions: { + autoRedeployServices?: boolean; + }; }; export enum RenderSyncScope { diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/RenderSyncOptionsSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/RenderSyncOptionsSection.tsx new file mode 100644 index 000000000..3dd3d2fd5 --- /dev/null +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/RenderSyncOptionsSection.tsx @@ -0,0 +1,27 @@ +import { GenericFieldLabel } from "@app/components/secret-syncs"; +import { Badge } from "@app/components/v2"; +import { TRenderSync } from "@app/hooks/api/secretSyncs/render-sync"; + +type Props = { + secretSync: TRenderSync; +}; + +export const RenderSyncOptionsSection = ({ secretSync }: Props) => { + const { + syncOptions: { autoRedeployServices } + } = secretSync; + + return ( +
+ {autoRedeployServices ? ( + + Enabled + + ) : ( + + Disabled + + )} +
+ ); +}; diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx index 843e02f63..32c46fca2 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx @@ -13,6 +13,7 @@ import { SecretSync, TSecretSync } from "@app/hooks/api/secretSyncs"; import { AwsParameterStoreSyncOptionsSection } from "./AwsParameterStoreSyncOptionsSection"; import { AwsSecretsManagerSyncOptionsSection } from "./AwsSecretsManagerSyncOptionsSection"; +import { RenderSyncOptionsSection } from "./RenderSyncOptionsSection"; type Props = { secretSync: TSecretSync; @@ -40,6 +41,9 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) = ); break; + case SecretSync.Render: + AdditionalSyncOptionsComponent = ; + break; case SecretSync.GitHub: case SecretSync.GCPSecretManager: case SecretSync.AzureKeyVault: @@ -56,7 +60,6 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) = case SecretSync.OCIVault: case SecretSync.OnePass: case SecretSync.Heroku: - case SecretSync.Render: case SecretSync.Flyio: case SecretSync.GitLab: case SecretSync.CloudflarePages: From 11ca76cccaa8a98beef9b882cfae78514fdaf0f2 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 25 Jul 2025 20:05:20 +0400 Subject: [PATCH 11/34] fix: restructure and requested changes --- .../secret-sync/render/render-sync-fns.ts | 2 +- .../RenderSyncFields.tsx | 2 +- .../schemas/render-sync-destination-schema.ts | 2 +- frontend/src/helpers/secretSyncs.ts | 2 +- .../src/hooks/api/secretSyncs/types/index.ts | 2 +- .../api/secretSyncs/{ => types}/render-sync.ts | 4 ++-- .../RenderSyncDestinationCol.tsx | 2 +- .../RenderSyncDestinationSection.tsx | 2 +- .../RenderSyncOptionsSection.tsx | 18 ++++++------------ 9 files changed, 15 insertions(+), 21 deletions(-) rename frontend/src/hooks/api/secretSyncs/{ => types}/render-sync.ts (82%) diff --git a/backend/src/services/secret-sync/render/render-sync-fns.ts b/backend/src/services/secret-sync/render/render-sync-fns.ts index 8af3653ca..71347f998 100644 --- a/backend/src/services/secret-sync/render/render-sync-fns.ts +++ b/backend/src/services/secret-sync/render/render-sync-fns.ts @@ -112,7 +112,7 @@ const redeployService = async (secretSync: TRenderSyncWithCredentials) => { { headers: { Authorization: `Bearer ${apiKey}`, - "Accept-Encoding": "application/json" + Accept: "application/json" } } ) diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/RenderSyncFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/RenderSyncFields.tsx index b5cb407cb..3d3f8df93 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/RenderSyncFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/RenderSyncFields.tsx @@ -9,7 +9,7 @@ import { useRenderConnectionListServices } from "@app/hooks/api/appConnections/render"; import { SecretSync } from "@app/hooks/api/secretSyncs"; -import { RenderSyncScope, RenderSyncType } from "@app/hooks/api/secretSyncs/render-sync"; +import { RenderSyncScope, RenderSyncType } from "@app/hooks/api/secretSyncs/types/render-sync"; import { TSecretSyncForm } from "../schemas"; diff --git a/frontend/src/components/secret-syncs/forms/schemas/render-sync-destination-schema.ts b/frontend/src/components/secret-syncs/forms/schemas/render-sync-destination-schema.ts index da81e121d..83e5347eb 100644 --- a/frontend/src/components/secret-syncs/forms/schemas/render-sync-destination-schema.ts +++ b/frontend/src/components/secret-syncs/forms/schemas/render-sync-destination-schema.ts @@ -2,7 +2,7 @@ import { z } from "zod"; import { BaseSecretSyncSchema } from "@app/components/secret-syncs/forms/schemas/base-secret-sync-schema"; import { SecretSync } from "@app/hooks/api/secretSyncs"; -import { RenderSyncScope, RenderSyncType } from "@app/hooks/api/secretSyncs/render-sync"; +import { RenderSyncScope, RenderSyncType } from "@app/hooks/api/secretSyncs/types/render-sync"; export const RenderSyncDestinationSchema = BaseSecretSyncSchema( z.object({ diff --git a/frontend/src/helpers/secretSyncs.ts b/frontend/src/helpers/secretSyncs.ts index 0eb41e119..b4c0df352 100644 --- a/frontend/src/helpers/secretSyncs.ts +++ b/frontend/src/helpers/secretSyncs.ts @@ -4,9 +4,9 @@ import { SecretSyncImportBehavior, SecretSyncInitialSyncBehavior } from "@app/hooks/api/secretSyncs"; -import { RenderSyncScope } from "@app/hooks/api/secretSyncs/render-sync"; import { GcpSyncScope } from "@app/hooks/api/secretSyncs/types/gcp-sync"; import { HumanitecSyncScope } from "@app/hooks/api/secretSyncs/types/humanitec-sync"; +import { RenderSyncScope } from "@app/hooks/api/secretSyncs/types/render-sync"; export const SECRET_SYNC_MAP: Record = { [SecretSync.AWSParameterStore]: { name: "AWS Parameter Store", image: "Amazon Web Services.png" }, diff --git a/frontend/src/hooks/api/secretSyncs/types/index.ts b/frontend/src/hooks/api/secretSyncs/types/index.ts index 7af01765e..2ab76341b 100644 --- a/frontend/src/hooks/api/secretSyncs/types/index.ts +++ b/frontend/src/hooks/api/secretSyncs/types/index.ts @@ -1,7 +1,6 @@ import { SecretSync, SecretSyncImportBehavior } from "@app/hooks/api/secretSyncs"; import { DiscriminativePick } from "@app/types"; -import { TRenderSync } from "../render-sync"; import { TOnePassSync } from "./1password-sync"; import { TAwsParameterStoreSync } from "./aws-parameter-store-sync"; import { TAwsSecretsManagerSync } from "./aws-secrets-manager-sync"; @@ -24,6 +23,7 @@ import { THerokuSync } from "./heroku-sync"; import { THumanitecSync } from "./humanitec-sync"; import { TOCIVaultSync } from "./oci-vault-sync"; import { TRailwaySync } from "./railway-sync"; +import { TRenderSync } from "./render-sync"; import { TSupabaseSync } from "./supabase"; import { TTeamCitySync } from "./teamcity-sync"; import { TTerraformCloudSync } from "./terraform-cloud-sync"; diff --git a/frontend/src/hooks/api/secretSyncs/render-sync.ts b/frontend/src/hooks/api/secretSyncs/types/render-sync.ts similarity index 82% rename from frontend/src/hooks/api/secretSyncs/render-sync.ts rename to frontend/src/hooks/api/secretSyncs/types/render-sync.ts index 61aecc53f..3d66de623 100644 --- a/frontend/src/hooks/api/secretSyncs/render-sync.ts +++ b/frontend/src/hooks/api/secretSyncs/types/render-sync.ts @@ -1,6 +1,6 @@ import { AppConnection } from "@app/hooks/api/appConnections/enums"; import { SecretSync } from "@app/hooks/api/secretSyncs"; -import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync"; +import { RootSyncOptions, TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync"; export type TRenderSync = TRootSecretSync & { destination: SecretSync.Render; @@ -17,7 +17,7 @@ export type TRenderSync = TRootSecretSync & { id: string; }; - syncOptions: { + syncOptions: RootSyncOptions & { autoRedeployServices?: boolean; }; }; diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/RenderSyncDestinationCol.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/RenderSyncDestinationCol.tsx index 43e9e35d2..00fcfe264 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/RenderSyncDestinationCol.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/RenderSyncDestinationCol.tsx @@ -1,5 +1,5 @@ import { useRenderConnectionListServices } from "@app/hooks/api/appConnections/render"; -import { TRenderSync } from "@app/hooks/api/secretSyncs/render-sync"; +import { TRenderSync } from "@app/hooks/api/secretSyncs/types/render-sync"; import { getSecretSyncDestinationColValues } from "../helpers"; import { SecretSyncTableCell } from "../SecretSyncTableCell"; diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/RenderSyncDestinationSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/RenderSyncDestinationSection.tsx index b661caf00..eda37a9cf 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/RenderSyncDestinationSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/RenderSyncDestinationSection.tsx @@ -1,6 +1,6 @@ import { GenericFieldLabel } from "@app/components/secret-syncs"; import { useRenderConnectionListServices } from "@app/hooks/api/appConnections/render"; -import { TRenderSync } from "@app/hooks/api/secretSyncs/render-sync"; +import { TRenderSync } from "@app/hooks/api/secretSyncs/types/render-sync"; type Props = { secretSync: TRenderSync; diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/RenderSyncOptionsSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/RenderSyncOptionsSection.tsx index 3dd3d2fd5..b23b3298b 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/RenderSyncOptionsSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/RenderSyncOptionsSection.tsx @@ -1,6 +1,6 @@ import { GenericFieldLabel } from "@app/components/secret-syncs"; import { Badge } from "@app/components/v2"; -import { TRenderSync } from "@app/hooks/api/secretSyncs/render-sync"; +import { TRenderSync } from "@app/hooks/api/secretSyncs/types/render-sync"; type Props = { secretSync: TRenderSync; @@ -12,16 +12,10 @@ export const RenderSyncOptionsSection = ({ secretSync }: Props) => { } = secretSync; return ( -
- {autoRedeployServices ? ( - - Enabled - - ) : ( - - Disabled - - )} -
+ + + {autoRedeployServices ? "Enabled" : "Disabled"} + + ); }; From 4afc7a19816512830f7c8cd87851ad6545100c86 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Fri, 25 Jul 2025 13:06:29 -0300 Subject: [PATCH 12/34] Add manual migration to secret imports rework --- ...25144940_fix-secret-reminders-migration.ts | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 backend/src/db/migrations/20250725144940_fix-secret-reminders-migration.ts diff --git a/backend/src/db/migrations/20250725144940_fix-secret-reminders-migration.ts b/backend/src/db/migrations/20250725144940_fix-secret-reminders-migration.ts new file mode 100644 index 000000000..81e8ccf31 --- /dev/null +++ b/backend/src/db/migrations/20250725144940_fix-secret-reminders-migration.ts @@ -0,0 +1,92 @@ +/* eslint-disable no-await-in-loop */ +import { Knex } from "knex"; + +import { chunkArray } from "@app/lib/fn"; +import { logger } from "@app/lib/logger"; + +import { TableName } from "../schemas"; +import { TReminders, TRemindersInsert } from "../schemas/reminders"; + +export async function up(knex: Knex): Promise { + logger.info("Initializing secret reminders migration"); + const hasReminderTable = await knex.schema.hasTable(TableName.Reminder); + + if (hasReminderTable) { + const secretsWithLatestVersions = await knex(TableName.SecretV2) + .whereNotNull(`${TableName.SecretV2}.reminderRepeatDays`) + .whereRaw(`"${TableName.SecretV2}"."reminderRepeatDays" > 0`) + .innerJoin(TableName.SecretVersionV2, (qb) => { + void qb + .on(`${TableName.SecretVersionV2}.secretId`, "=", `${TableName.SecretV2}.id`) + .andOn(`${TableName.SecretVersionV2}.reminderRepeatDays`, "=", `${TableName.SecretV2}.reminderRepeatDays`); + }) + .whereIn([`${TableName.SecretVersionV2}.secretId`, `${TableName.SecretVersionV2}.version`], (qb) => { + void qb + .select(["secretId", knex.raw("MAX(version) as version")]) + .from(`${TableName.SecretVersionV2} as v2`) + .whereNotNull("v2.reminderRepeatDays") + .whereRaw(`"v2"."reminderRepeatDays" > 0`) + .groupBy("v2.secretId"); + }) + .select( + knex.ref("id").withSchema(TableName.SecretV2).as("secretId"), + knex.ref("reminderRepeatDays").withSchema(TableName.SecretV2).as("reminderRepeatDays"), + knex.ref("reminderNote").withSchema(TableName.SecretV2).as("reminderNote"), + knex.ref("createdAt").withSchema(TableName.SecretVersionV2).as("createdAt") + ); + + logger.info(`Found ${secretsWithLatestVersions.length} reminders to migrate`); + + const reminderInserts: TRemindersInsert[] = []; + if (secretsWithLatestVersions.length > 0) { + secretsWithLatestVersions.forEach((secret) => { + if (!secret.reminderRepeatDays) return; + const nextReminderDate = new Date(secret.createdAt); + nextReminderDate.setDate(nextReminderDate.getDate() + secret.reminderRepeatDays); + + reminderInserts.push({ + secretId: secret.secretId, + message: secret.reminderNote, + repeatDays: secret.reminderRepeatDays, + nextReminderDate + }); + }); + + const commitBatches = chunkArray(reminderInserts, 9000); + for (const commitBatch of commitBatches) { + const insertedReminders = (await knex + .batchInsert(TableName.Reminder, commitBatch) + .returning("*")) as TReminders[]; + + const insertedReminderSecretIds = insertedReminders.map((reminder) => reminder.secretId).filter(Boolean); + + const recipients = await knex(TableName.SecretReminderRecipients) + .whereRaw(`??.?? IN (${insertedReminderSecretIds.map(() => "?").join(",")})`, [ + TableName.SecretReminderRecipients, + "secretId", + ...insertedReminderSecretIds + ]) + .select( + knex.ref("userId").withSchema(TableName.SecretReminderRecipients).as("userId"), + knex.ref("secretId").withSchema(TableName.SecretReminderRecipients).as("secretId") + ); + const reminderRecipients = recipients.map((recipient) => ({ + reminderId: insertedReminders.find((reminder) => reminder.secretId === recipient.secretId)?.id, + userId: recipient.userId + })); + + const filteredRecipients = reminderRecipients.filter((recipient) => !!recipient.reminderId); + await knex.batchInsert(TableName.ReminderRecipient, filteredRecipients); + } + logger.info(`Successfully migrated ${reminderInserts.length} secret reminders`); + } + + logger.info("Secret reminders migration completed"); + } else { + logger.warn("Reminder table does not exist, skipping migration"); + } +} + +export async function down(): Promise { + logger.info("Rollback not implemented for secret reminders fix migration"); +} From cd718488000e1c52309bac9d0d33e7657c387baf Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Fri, 25 Jul 2025 13:10:54 -0300 Subject: [PATCH 13/34] Avoid migrating existing reminders --- .../20250725144940_fix-secret-reminders-migration.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/src/db/migrations/20250725144940_fix-secret-reminders-migration.ts b/backend/src/db/migrations/20250725144940_fix-secret-reminders-migration.ts index 81e8ccf31..35612f2d7 100644 --- a/backend/src/db/migrations/20250725144940_fix-secret-reminders-migration.ts +++ b/backend/src/db/migrations/20250725144940_fix-secret-reminders-migration.ts @@ -28,6 +28,10 @@ export async function up(knex: Knex): Promise { .whereRaw(`"v2"."reminderRepeatDays" > 0`) .groupBy("v2.secretId"); }) + // Add LEFT JOIN with Reminder table to check for existing reminders + .leftJoin(TableName.Reminder, `${TableName.Reminder}.secretId`, `${TableName.SecretV2}.id`) + // Only include secrets that don't already have reminders + .whereNull(`${TableName.Reminder}.secretId`) .select( knex.ref("id").withSchema(TableName.SecretV2).as("secretId"), knex.ref("reminderRepeatDays").withSchema(TableName.SecretV2).as("reminderRepeatDays"), From af32948a05b472ba0cab4547e30796604922a75f Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Fri, 25 Jul 2025 13:35:06 -0300 Subject: [PATCH 14/34] Minor improvements on reminders migration --- .../20250725144940_fix-secret-reminders-migration.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/src/db/migrations/20250725144940_fix-secret-reminders-migration.ts b/backend/src/db/migrations/20250725144940_fix-secret-reminders-migration.ts index 35612f2d7..9a0394bf9 100644 --- a/backend/src/db/migrations/20250725144940_fix-secret-reminders-migration.ts +++ b/backend/src/db/migrations/20250725144940_fix-secret-reminders-migration.ts @@ -22,10 +22,12 @@ export async function up(knex: Knex): Promise { }) .whereIn([`${TableName.SecretVersionV2}.secretId`, `${TableName.SecretVersionV2}.version`], (qb) => { void qb - .select(["secretId", knex.raw("MAX(version) as version")]) + .select(["v2.secretId", knex.raw("MIN(v2.version) as version")]) .from(`${TableName.SecretVersionV2} as v2`) + .innerJoin(`${TableName.SecretV2} as s2`, "v2.secretId", "s2.id") + .whereRaw(`v2."reminderRepeatDays" = s2."reminderRepeatDays"`) .whereNotNull("v2.reminderRepeatDays") - .whereRaw(`"v2"."reminderRepeatDays" > 0`) + .whereRaw(`v2."reminderRepeatDays" > 0`) .groupBy("v2.secretId"); }) // Add LEFT JOIN with Reminder table to check for existing reminders @@ -56,7 +58,7 @@ export async function up(knex: Knex): Promise { }); }); - const commitBatches = chunkArray(reminderInserts, 9000); + const commitBatches = chunkArray(reminderInserts, 2000); for (const commitBatch of commitBatches) { const insertedReminders = (await knex .batchInsert(TableName.Reminder, commitBatch) @@ -79,7 +81,7 @@ export async function up(knex: Knex): Promise { userId: recipient.userId })); - const filteredRecipients = reminderRecipients.filter((recipient) => !!recipient.reminderId); + const filteredRecipients = reminderRecipients.filter((recipient) => Boolean(recipient.reminderId)); await knex.batchInsert(TableName.ReminderRecipient, filteredRecipients); } logger.info(`Successfully migrated ${reminderInserts.length} secret reminders`); From 7ce11cde9569c5c9dd3ac10144671d0c30c17dee Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Fri, 25 Jul 2025 14:47:57 -0300 Subject: [PATCH 15/34] Add cycle logic to next reminder migration --- ...250725144940_fix-secret-reminders-migration.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/backend/src/db/migrations/20250725144940_fix-secret-reminders-migration.ts b/backend/src/db/migrations/20250725144940_fix-secret-reminders-migration.ts index 9a0394bf9..b720c97ae 100644 --- a/backend/src/db/migrations/20250725144940_fix-secret-reminders-migration.ts +++ b/backend/src/db/migrations/20250725144940_fix-secret-reminders-migration.ts @@ -47,9 +47,22 @@ export async function up(knex: Knex): Promise { if (secretsWithLatestVersions.length > 0) { secretsWithLatestVersions.forEach((secret) => { if (!secret.reminderRepeatDays) return; - const nextReminderDate = new Date(secret.createdAt); + + const now = new Date(); + const createdAt = new Date(secret.createdAt); + let nextReminderDate = new Date(createdAt); nextReminderDate.setDate(nextReminderDate.getDate() + secret.reminderRepeatDays); + // If the next reminder date is in the past, calculate the proper next occurrence + if (nextReminderDate < now) { + const daysSinceCreation = Math.floor((now.getTime() - createdAt.getTime()) / (1000 * 60 * 60 * 24)); + const daysIntoCurrentCycle = daysSinceCreation % secret.reminderRepeatDays; + const daysUntilNextReminder = secret.reminderRepeatDays - daysIntoCurrentCycle; + + nextReminderDate = new Date(now); + nextReminderDate.setDate(now.getDate() + daysUntilNextReminder); + } + reminderInserts.push({ secretId: secret.secretId, message: secret.reminderNote, From d0ffae2c109c23e3138f89dc7d365b02a03212be Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Fri, 25 Jul 2025 14:53:46 -0300 Subject: [PATCH 16/34] Add uuid validation to Azure client secrets --- .../azure-client-secrets-connection-schemas.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts index 41c9a1d36..d9f178a06 100644 --- a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts +++ b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-schemas.ts @@ -29,6 +29,7 @@ export const AzureClientSecretsConnectionOAuthOutputCredentialsSchema = z.object export const AzureClientSecretsConnectionClientSecretInputCredentialsSchema = z.object({ clientId: z .string() + .uuid() .trim() .min(1, "Client ID required") .max(50, "Client ID must be at most 50 characters long") @@ -41,6 +42,7 @@ export const AzureClientSecretsConnectionClientSecretInputCredentialsSchema = z. .describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.clientSecret), tenantId: z .string() + .uuid() .trim() .min(1, "Tenant ID required") .describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.tenantId) From 72ee468208f8d1bf90c4c9020a7e1aa1037123df Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Fri, 25 Jul 2025 15:20:23 -0300 Subject: [PATCH 17/34] Remove previous queue running the migration --- backend/src/services/reminder/reminder-queue.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/backend/src/services/reminder/reminder-queue.ts b/backend/src/services/reminder/reminder-queue.ts index 1487a63f3..c038734bd 100644 --- a/backend/src/services/reminder/reminder-queue.ts +++ b/backend/src/services/reminder/reminder-queue.ts @@ -173,12 +173,6 @@ export const dailyReminderQueueServiceFactory = ({ { pattern: "0 */1 * * *", utc: true }, QueueName.SecretReminderMigration // just a job id ); - - await queueService.queue(QueueName.SecretReminderMigration, QueueJobs.SecretReminderMigration, undefined, { - delay: 5000, - jobId: QueueName.SecretReminderMigration, - repeat: { pattern: "0 */1 * * *", utc: true } - }); }; queueService.listen(QueueName.DailyReminders, "failed", (_, err) => { From 253c46f21d36e649dec638b843ae376eee0baaa7 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 25 Jul 2025 23:09:23 +0400 Subject: [PATCH 18/34] fips improvements --- Dockerfile.fips.standalone-infisical | 8 ++++++-- backend/Dockerfile.dev.fips | 6 +++++- backend/src/db/migrations/utils/env-config.ts | 2 +- backend/src/lib/crypto/cryptography/crypto.ts | 16 ++++++++-------- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Dockerfile.fips.standalone-infisical b/Dockerfile.fips.standalone-infisical index d2b2a2d87..dec41a36d 100644 --- a/Dockerfile.fips.standalone-infisical +++ b/Dockerfile.fips.standalone-infisical @@ -145,7 +145,11 @@ RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \ && cd openssl-3.1.2 \ && ./Configure enable-fips \ && make \ - && make install_fips + && make install_fips \ + && cd / \ + && rm -rf /openssl-build \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* # Install Infisical CLI RUN curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash \ @@ -186,7 +190,7 @@ ENV NODE_ENV production ENV STANDALONE_BUILD true ENV STANDALONE_MODE true ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/ -ENV NODE_OPTIONS="--max-old-space-size=1024" +ENV NODE_OPTIONS="--max-old-space-size=8192" # FIPS mode of operation: ENV OPENSSL_CONF=/backend/nodejs.fips.cnf diff --git a/backend/Dockerfile.dev.fips b/backend/Dockerfile.dev.fips index 977362e03..b954ccd50 100644 --- a/backend/Dockerfile.dev.fips +++ b/backend/Dockerfile.dev.fips @@ -59,7 +59,11 @@ RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \ && cd openssl-3.1.2 \ && ./Configure enable-fips \ && make \ - && make install_fips + && make install_fips \ + && cd / \ + && rm -rf /openssl-build \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* # ? App setup diff --git a/backend/src/db/migrations/utils/env-config.ts b/backend/src/db/migrations/utils/env-config.ts index debaea03f..de32f4db9 100644 --- a/backend/src/db/migrations/utils/env-config.ts +++ b/backend/src/db/migrations/utils/env-config.ts @@ -53,7 +53,7 @@ export const getMigrationEnvConfig = async (superAdminDAL: TSuperAdminDALFactory let envCfg = Object.freeze(parsedEnv.data); - const fipsEnabled = await crypto.initialize(superAdminDAL); + const fipsEnabled = await crypto.initialize(superAdminDAL, envCfg); // Fix for 128-bit entropy encryption key expansion issue: // In FIPS it is not ideal to expand a 128-bit key into 256-bit. We solved this issue in the past by creating the ROOT_ENCRYPTION_KEY. diff --git a/backend/src/lib/crypto/cryptography/crypto.ts b/backend/src/lib/crypto/cryptography/crypto.ts index 967e7e007..b8fc45645 100644 --- a/backend/src/lib/crypto/cryptography/crypto.ts +++ b/backend/src/lib/crypto/cryptography/crypto.ts @@ -14,7 +14,7 @@ import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal import { ADMIN_CONFIG_DB_UUID } from "@app/services/super-admin/super-admin-service"; import { isBase64 } from "../../base64"; -import { getConfig } from "../../config/env"; +import { getConfig, TEnvConfig } from "../../config/env"; import { CryptographyError } from "../../errors"; import { logger } from "../../logger"; import { asymmetricFipsValidated } from "./asymmetric-fips"; @@ -106,12 +106,12 @@ const cryptographyFactory = () => { } }; - const $setFipsModeEnabled = (enabled: boolean) => { + const $setFipsModeEnabled = (enabled: boolean, envCfg?: Pick) => { // If FIPS is enabled, we need to validate that the ENCRYPTION_KEY is in a base64 format, and is a 256-bit key. if (enabled) { crypto.setFips(true); - const appCfg = getConfig(); + const appCfg = envCfg || getConfig(); if (appCfg.ENCRYPTION_KEY) { // we need to validate that the ENCRYPTION_KEY is a base64 encoded 256-bit key @@ -141,14 +141,14 @@ const cryptographyFactory = () => { $isInitialized = true; }; - const initialize = async (superAdminDAL: TSuperAdminDALFactory) => { + const initialize = async (superAdminDAL: TSuperAdminDALFactory, envCfg?: Pick) => { if ($isInitialized) { return isFipsModeEnabled(); } if (process.env.FIPS_ENABLED !== "true") { logger.info("Cryptography module initialized in normal operation mode."); - $setFipsModeEnabled(false); + $setFipsModeEnabled(false, envCfg); return false; } @@ -158,11 +158,11 @@ const cryptographyFactory = () => { if (serverCfg) { if (serverCfg.fipsEnabled) { logger.info("[FIPS]: Instance is configured for FIPS mode of operation. Continuing startup with FIPS enabled."); - $setFipsModeEnabled(true); + $setFipsModeEnabled(true, envCfg); return true; } logger.info("[FIPS]: Instance age predates FIPS mode inception date. Continuing without FIPS."); - $setFipsModeEnabled(false); + $setFipsModeEnabled(false, envCfg); return false; } @@ -171,7 +171,7 @@ const cryptographyFactory = () => { // TODO(daniel): check if it's an enterprise deployment // if there is no server cfg, and FIPS_MODE is `true`, its a fresh FIPS deployment. We need to set the fipsEnabled to true. - $setFipsModeEnabled(true); + $setFipsModeEnabled(true, envCfg); return true; }; From d4f030110496036c4a0d0d3e111f79efb02bdbd2 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 25 Jul 2025 23:13:26 +0400 Subject: [PATCH 19/34] Update Dockerfile.fips.standalone-infisical --- Dockerfile.fips.standalone-infisical | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile.fips.standalone-infisical b/Dockerfile.fips.standalone-infisical index dec41a36d..b95794832 100644 --- a/Dockerfile.fips.standalone-infisical +++ b/Dockerfile.fips.standalone-infisical @@ -190,12 +190,11 @@ ENV NODE_ENV production ENV STANDALONE_BUILD true ENV STANDALONE_MODE true ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/ -ENV NODE_OPTIONS="--max-old-space-size=8192" +ENV NODE_OPTIONS="--max-old-space-size=8192 --force-fips" # FIPS mode of operation: ENV OPENSSL_CONF=/backend/nodejs.fips.cnf ENV OPENSSL_MODULES=/usr/local/lib/ossl-modules -ENV NODE_OPTIONS=--force-fips ENV FIPS_ENABLED=true From e6588b5d0e612fbdf0c7d0d6cbdbe8cf3bcb66ed Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Fri, 25 Jul 2025 17:00:11 -0300 Subject: [PATCH 20/34] Set correct environmentName on listApprovalRequests --- .../access-approval-request-service.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backend/src/ee/services/access-approval-request/access-approval-request-service.ts b/backend/src/ee/services/access-approval-request/access-approval-request-service.ts index e9b311905..dcbe717da 100644 --- a/backend/src/ee/services/access-approval-request/access-approval-request-service.ts +++ b/backend/src/ee/services/access-approval-request/access-approval-request-service.ts @@ -327,6 +327,15 @@ export const accessApprovalRequestServiceFactory = ({ requests = requests.filter((request) => request.environment === envSlug); } + requests = requests.map((request) => { + const permissionEnvironment = $getEnvironmentFromPermissions(request.permissions); + + if (permissionEnvironment) { + request.environmentName = permissionEnvironment; + } + return request; + }); + return { requests }; }; From 3400a8f911ec12b67338e9928d24b376fb7865a3 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Fri, 25 Jul 2025 17:24:15 -0300 Subject: [PATCH 21/34] Small UI fix for environments label --- .../ApprovalPolicyList/components/AccessPolicyModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx index e160a88f0..b254b8ac7 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx @@ -490,7 +490,7 @@ const Form = ({ name="environments" render={({ field: { value, onChange }, fieldState: { error } }) => ( option.slug} getOptionLabel={(option) => option.name} From 0adf2c830dc81137d3be3ab89f00e6ae2faa7582 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Fri, 25 Jul 2025 20:47:17 -0300 Subject: [PATCH 22/34] Fix azure client secrets OAuth URL to use graph instead of vault --- .../app-connections/azure-client-secrets.mdx | 18 ++---------------- .../AzureClientSecretsConnectionForm.tsx | 2 +- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/docs/integrations/app-connections/azure-client-secrets.mdx b/docs/integrations/app-connections/azure-client-secrets.mdx index fc4194c5e..1fb1c753f 100644 --- a/docs/integrations/app-connections/azure-client-secrets.mdx +++ b/docs/integrations/app-connections/azure-client-secrets.mdx @@ -43,12 +43,6 @@ Infisical currently only supports one method for connecting to Azure, which is O - `Application.ReadWrite.All` (Delegated) - `Directory.ReadWrite.All` (Delegated) - `User.Read` (Delegated) - - Azure App Configuration - - `KeyValue.Delete` (Delegated) - - `KeyValue.Read` (Delegated) - - `KeyValue.Write` (Delegated) - - Access Key Vault - - `user_impersonation` (Delegated) ![Azure client secrets](/images/integrations/azure-client-secrets/app-api-permissions.png) @@ -63,8 +57,8 @@ Infisical currently only supports one method for connecting to Azure, which is O Back in your Infisical instance, add two new environment variables for the credentials of your Azure application. - - `INF_APP_CONNECTION_AZURE_CLIENT_ID`: The **Application (Client) ID** of your Azure application. - - `INF_APP_CONNECTION_AZURE_CLIENT_SECRET`: The **Client Secret** of your Azure application. + - `INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID`: The **Application (Client) ID** of your Azure application. + - `INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET`: The **Client Secret** of your Azure application. Once added, restart your Infisical instance and use the Azure Client Secrets connection. @@ -91,14 +85,6 @@ Infisical currently only supports one method for connecting to Azure, which is O - `Directory.ReadWrite.All` (Delegated) - `User.Read` (Delegated) - **Azure App Configuration** - - `KeyValue.Delete` (Delegated) - - `KeyValue.Read` (Delegated) - - `KeyValue.Write` (Delegated) - - **Access Key Vault** - - `user_impersonation` (Delegated) - ![Azure client secrets](/images/integrations/azure-client-secrets/app-api-permissions.png) diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureClientSecretsConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureClientSecretsConnectionForm.tsx index 6aa6ee63c..f6c5788f4 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureClientSecretsConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureClientSecretsConnectionForm.tsx @@ -132,7 +132,7 @@ export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit }: Pr JSON.stringify({ ...formData, connectionId: appConnection?.id }) ); window.location.assign( - `https://login.microsoftonline.com/${formData.tenantId || "common"}/oauth2/v2.0/authorize?client_id=${oauthClientId}&response_type=code&redirect_uri=${window.location.origin}/organization/app-connections/azure/oauth/callback&response_mode=query&scope=https://azconfig.io/.default%20openid%20offline_access&state=${state}<:>azure-client-secrets` + `https://login.microsoftonline.com/${formData.tenantId || "common"}/oauth2/v2.0/authorize?client_id=${oauthClientId}&response_type=code&redirect_uri=${window.location.origin}/organization/app-connections/azure/oauth/callback&response_mode=query&scope=https://graph.microsoft.com/.default%20openid%20offline_access&state=${state}<:>azure-client-secrets` ); break; From 68401a799ef17c3c6737ac2be126f649016357b4 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Fri, 25 Jul 2025 20:48:18 -0300 Subject: [PATCH 23/34] Fix env variables name on doc --- docs/integrations/app-connections/azure-client-secrets.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/integrations/app-connections/azure-client-secrets.mdx b/docs/integrations/app-connections/azure-client-secrets.mdx index 1fb1c753f..82382733e 100644 --- a/docs/integrations/app-connections/azure-client-secrets.mdx +++ b/docs/integrations/app-connections/azure-client-secrets.mdx @@ -57,8 +57,8 @@ Infisical currently only supports one method for connecting to Azure, which is O Back in your Infisical instance, add two new environment variables for the credentials of your Azure application. - - `INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID`: The **Application (Client) ID** of your Azure application. - - `INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET`: The **Client Secret** of your Azure application. + - `INF_APP_CONNECTION_AZURE_CLIENT_ID`: The **Application (Client) ID** of your Azure application. + - `INF_APP_CONNECTION_AZURE_CLIENT_SECRET`: The **Client Secret** of your Azure application. Once added, restart your Infisical instance and use the Azure Client Secrets connection. From 484f34a25726be2eeacc2f4a0008bda50c10168c Mon Sep 17 00:00:00 2001 From: = Date: Mon, 28 Jul 2025 00:03:01 +0530 Subject: [PATCH 24/34] fix: potential fix for oracle db rotation failing --- .../sql-credentials-rotation-fns.ts | 28 +++++++++++++++---- .../secret-rotation-v2/shared/utils/index.ts | 2 +- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/backend/src/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-fns.ts b/backend/src/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-fns.ts index 3e6e5d265..15832fe99 100644 --- a/backend/src/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-fns.ts +++ b/backend/src/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-fns.ts @@ -7,12 +7,13 @@ import { TRotationFactoryRevokeCredentials, TRotationFactoryRotateCredentials } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { executeWithPotentialGateway, SQL_CONNECTION_ALTER_LOGIN_STATEMENT } from "@app/services/app-connection/shared/sql"; -import { generatePassword } from "../utils"; +import { DEFAULT_PASSWORD_REQUIREMENTS, generatePassword } from "../utils"; import { TSqlCredentialsRotationGeneratedCredentials, TSqlCredentialsRotationWithConnection @@ -32,6 +33,11 @@ const redactPasswords = (e: unknown, credentials: TSqlCredentialsRotationGenerat return redactedMessage; }; +const ORACLE_PASSWORD_REQUIREMENTS = { + ...DEFAULT_PASSWORD_REQUIREMENTS, + length: 30 +}; + export const sqlCredentialsRotationFactory: TRotationFactory< TSqlCredentialsRotationWithConnection, TSqlCredentialsRotationGeneratedCredentials @@ -43,6 +49,9 @@ export const sqlCredentialsRotationFactory: TRotationFactory< secretsMapping } = secretRotation; + const passwordRequirement = + connection.app === AppConnection.OracleDB ? ORACLE_PASSWORD_REQUIREMENTS : DEFAULT_PASSWORD_REQUIREMENTS; + const executeOperation = ( operation: (client: Knex) => Promise, credentialsOverride?: TSqlCredentialsRotationGeneratedCredentials[number] @@ -65,7 +74,7 @@ export const sqlCredentialsRotationFactory: TRotationFactory< const $validateCredentials = async (credentials: TSqlCredentialsRotationGeneratedCredentials[number]) => { try { await executeOperation(async (client) => { - await client.raw("SELECT 1"); + await client.raw(connection.app === AppConnection.OracleDB ? `SELECT 1 FROM DUAL` : `Select 1`); }, credentials); } catch (error) { throw new Error(redactPasswords(error, [credentials])); @@ -75,11 +84,12 @@ export const sqlCredentialsRotationFactory: TRotationFactory< const issueCredentials: TRotationFactoryIssueCredentials = async ( callback ) => { + // const connection.app === AppConnection.OracleDB ? ORACLE_PASSWORD_REQUIREMENTS : DEFAULT_PASSWORD_REQUIREMENTS // For SQL, since we get existing users, we change both their passwords // on issue to invalidate their existing passwords const credentialsSet = [ - { username: username1, password: generatePassword() }, - { username: username2, password: generatePassword() } + { username: username1, password: generatePassword(passwordRequirement) }, + { username: username2, password: generatePassword(passwordRequirement) } ]; try { @@ -105,7 +115,10 @@ export const sqlCredentialsRotationFactory: TRotationFactory< credentialsToRevoke, callback ) => { - const revokedCredentials = credentialsToRevoke.map(({ username }) => ({ username, password: generatePassword() })); + const revokedCredentials = credentialsToRevoke.map(({ username }) => ({ + username, + password: generatePassword(passwordRequirement) + })); try { await executeOperation(async (client) => { @@ -128,7 +141,10 @@ export const sqlCredentialsRotationFactory: TRotationFactory< callback ) => { // generate new password for the next active user - const credentials = { username: activeIndex === 0 ? username2 : username1, password: generatePassword() }; + const credentials = { + username: activeIndex === 0 ? username2 : username1, + password: generatePassword(passwordRequirement) + }; try { await executeOperation(async (client) => { diff --git a/backend/src/ee/services/secret-rotation-v2/shared/utils/index.ts b/backend/src/ee/services/secret-rotation-v2/shared/utils/index.ts index ef58687a1..4122abfda 100644 --- a/backend/src/ee/services/secret-rotation-v2/shared/utils/index.ts +++ b/backend/src/ee/services/secret-rotation-v2/shared/utils/index.ts @@ -11,7 +11,7 @@ type TPasswordRequirements = { allowedSymbols?: string; }; -const DEFAULT_PASSWORD_REQUIREMENTS: TPasswordRequirements = { +export const DEFAULT_PASSWORD_REQUIREMENTS: TPasswordRequirements = { length: 48, required: { lowercase: 1, From 8df461626539ab9d1fca130adfdc115635b77de6 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Mon, 28 Jul 2025 00:09:30 +0530 Subject: [PATCH 25/34] Update backend/src/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-fns.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .../shared/sql-credentials/sql-credentials-rotation-fns.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-fns.ts b/backend/src/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-fns.ts index 15832fe99..1da1db376 100644 --- a/backend/src/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-fns.ts +++ b/backend/src/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-fns.ts @@ -84,7 +84,8 @@ export const sqlCredentialsRotationFactory: TRotationFactory< const issueCredentials: TRotationFactoryIssueCredentials = async ( callback ) => { - // const connection.app === AppConnection.OracleDB ? ORACLE_PASSWORD_REQUIREMENTS : DEFAULT_PASSWORD_REQUIREMENTS + // For SQL, since we get existing users, we change both their passwords + // on issue to invalidate their existing passwords // For SQL, since we get existing users, we change both their passwords // on issue to invalidate their existing passwords const credentialsSet = [ From 0779091d1fcc1bb4d4035fbc118ed4ff477e326b Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Mon, 28 Jul 2025 09:14:43 -0300 Subject: [PATCH 26/34] Separate Azure OAuth env vars to different env variables for each app connection --- .env.example | 12 ++++++ backend/src/lib/config/env.ts | 40 +++++++++++++++++++ .../azure-app-configuration-connection-fns.ts | 22 +++++++--- .../azure-client-secrets-connection-fns.ts | 32 ++++++++++----- .../azure-devops/azure-devops-fns.ts | 30 +++++++++----- .../azure-key-vault-connection-fns.ts | 31 +++++++++----- .../azure-app-configuration.mdx | 4 +- .../app-connections/azure-client-secrets.mdx | 4 +- .../app-connections/azure-devops.mdx | 4 +- .../app-connections/azure-key-vault.mdx | 4 +- 10 files changed, 142 insertions(+), 41 deletions(-) diff --git a/.env.example b/.env.example index 05a888db0..dbaf1e633 100644 --- a/.env.example +++ b/.env.example @@ -126,6 +126,18 @@ INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL= INF_APP_CONNECTION_AZURE_CLIENT_ID= INF_APP_CONNECTION_AZURE_CLIENT_SECRET= +INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID= +INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET= + +INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID= +INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET= + +INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_ID= +INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_SECRET= + +INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID= +INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET= + # datadog SHOULD_USE_DATADOG_TRACER= DATADOG_PROFILING_ENABLED= diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 986963e47..d6eeba342 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -264,6 +264,14 @@ const envSchema = z // azure app INF_APP_CONNECTION_AZURE_CLIENT_ID: zpStr(z.string().optional()), INF_APP_CONNECTION_AZURE_CLIENT_SECRET: zpStr(z.string().optional()), + INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID: zpStr(z.string().optional()), + INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET: zpStr(z.string().optional()), + INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID: zpStr(z.string().optional()), + INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET: zpStr(z.string().optional()), + INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_ID: zpStr(z.string().optional()), + INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_SECRET: zpStr(z.string().optional()), + INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID: zpStr(z.string().optional()), + INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET: zpStr(z.string().optional()), // datadog SHOULD_USE_DATADOG_TRACER: zodStrBool.default("false"), @@ -461,6 +469,38 @@ export const overwriteSchema: { { key: "INF_APP_CONNECTION_AZURE_CLIENT_SECRET", description: "The Client Secret of your Azure application." + }, + { + key: "INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID", + description: "The Application (Client) ID of your Azure application." + }, + { + key: "INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET", + description: "The Client Secret of your Azure application." + }, + { + key: "INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID", + description: "The Application (Client) ID of your Azure application." + }, + { + key: "INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET", + description: "The Client Secret of your Azure application." + }, + { + key: "INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_ID", + description: "The Application (Client) ID of your Azure application." + }, + { + key: "INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_SECRET", + description: "The Client Secret of your Azure application." + }, + { + key: "INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID", + description: "The Application (Client) ID of your Azure application." + }, + { + key: "INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET", + description: "The Client Secret of your Azure application." } ] }, diff --git a/backend/src/services/app-connection/azure-app-configuration/azure-app-configuration-connection-fns.ts b/backend/src/services/app-connection/azure-app-configuration/azure-app-configuration-connection-fns.ts index 937a8a84f..9fd38be9d 100644 --- a/backend/src/services/app-connection/azure-app-configuration/azure-app-configuration-connection-fns.ts +++ b/backend/src/services/app-connection/azure-app-configuration/azure-app-configuration-connection-fns.ts @@ -14,13 +14,13 @@ import { } from "./azure-app-configuration-connection-types"; export const getAzureAppConfigurationConnectionListItem = () => { - const { INF_APP_CONNECTION_AZURE_CLIENT_ID } = getConfig(); + const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID } = getConfig(); return { name: "Azure App Configuration" as const, app: AppConnection.AzureAppConfiguration as const, methods: Object.values(AzureAppConfigurationConnectionMethod) as [AzureAppConfigurationConnectionMethod.OAuth], - oauthClientId: INF_APP_CONNECTION_AZURE_CLIENT_ID + oauthClientId: INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID || INF_APP_CONNECTION_AZURE_CLIENT_ID }; }; @@ -29,9 +29,19 @@ export const validateAzureAppConfigurationConnectionCredentials = async ( ) => { const { credentials: inputCredentials, method } = config; - const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_CLIENT_SECRET, SITE_URL } = getConfig(); + const { + INF_APP_CONNECTION_AZURE_CLIENT_ID, + INF_APP_CONNECTION_AZURE_CLIENT_SECRET, + INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID, + INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET, + SITE_URL + } = getConfig(); - if (!INF_APP_CONNECTION_AZURE_CLIENT_ID || !INF_APP_CONNECTION_AZURE_CLIENT_SECRET) { + const azureClientId = INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID || INF_APP_CONNECTION_AZURE_CLIENT_ID; + const azureClientSecret = + INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET || INF_APP_CONNECTION_AZURE_CLIENT_SECRET; + + if (!azureClientId || !azureClientSecret) { throw new InternalServerError({ message: `Azure ${getAppConnectionMethodName(method)} environment variables have not been configured` }); @@ -47,8 +57,8 @@ export const validateAzureAppConfigurationConnectionCredentials = async ( grant_type: "authorization_code", code: inputCredentials.code, scope: `openid offline_access https://azconfig.io/.default`, - client_id: INF_APP_CONNECTION_AZURE_CLIENT_ID, - client_secret: INF_APP_CONNECTION_AZURE_CLIENT_SECRET, + client_id: azureClientId, + client_secret: azureClientSecret, redirect_uri: `${SITE_URL}/organization/app-connections/azure/oauth/callback` }) ); diff --git a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts index a28217320..f6987bbe9 100644 --- a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts +++ b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts @@ -23,7 +23,7 @@ import { } from "./azure-client-secrets-connection-types"; export const getAzureClientSecretsConnectionListItem = () => { - const { INF_APP_CONNECTION_AZURE_CLIENT_ID } = getConfig(); + const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_ID } = getConfig(); return { name: "Azure Client Secrets" as const, @@ -32,7 +32,7 @@ export const getAzureClientSecretsConnectionListItem = () => { AzureClientSecretsConnectionMethod.OAuth, AzureClientSecretsConnectionMethod.ClientSecret ], - oauthClientId: INF_APP_CONNECTION_AZURE_CLIENT_ID + oauthClientId: INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_ID || INF_APP_CONNECTION_AZURE_CLIENT_ID }; }; @@ -64,7 +64,11 @@ export const getAzureConnectionAccessToken = async ( const currentTime = Date.now(); switch (appConnection.method) { case AzureClientSecretsConnectionMethod.OAuth: - if (!appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID || !appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET) { + const azureClientId = + appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_ID || appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID; + const azureClientSecret = + appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_SECRET || appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET; + if (!azureClientId || !azureClientSecret) { throw new BadRequestError({ message: `Azure OAuth environment variables have not been configured` }); @@ -74,8 +78,8 @@ export const getAzureConnectionAccessToken = async ( new URLSearchParams({ grant_type: "refresh_token", scope: `openid offline_access https://graph.microsoft.com/.default`, - client_id: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID, - client_secret: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET, + client_id: azureClientId, + client_secret: azureClientSecret, refresh_token: refreshToken }) ); @@ -142,7 +146,13 @@ export const getAzureConnectionAccessToken = async ( export const validateAzureClientSecretsConnectionCredentials = async (config: TAzureClientSecretsConnectionConfig) => { const { credentials: inputCredentials, method } = config; - const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_CLIENT_SECRET, SITE_URL } = getConfig(); + const { + INF_APP_CONNECTION_AZURE_CLIENT_ID, + INF_APP_CONNECTION_AZURE_CLIENT_SECRET, + INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_ID, + INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_SECRET, + SITE_URL + } = getConfig(); switch (method) { case AzureClientSecretsConnectionMethod.OAuth: @@ -150,7 +160,11 @@ export const validateAzureClientSecretsConnectionCredentials = async (config: TA throw new InternalServerError({ message: "SITE_URL env var is required to complete Azure OAuth flow" }); } - if (!INF_APP_CONNECTION_AZURE_CLIENT_ID || !INF_APP_CONNECTION_AZURE_CLIENT_SECRET) { + const azureClientId = INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_ID || INF_APP_CONNECTION_AZURE_CLIENT_ID; + const azureClientSecret = + INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_SECRET || INF_APP_CONNECTION_AZURE_CLIENT_SECRET; + + if (!azureClientId || !azureClientSecret) { throw new InternalServerError({ message: `Azure ${getAppConnectionMethodName(method)} environment variables have not been configured` }); @@ -166,8 +180,8 @@ export const validateAzureClientSecretsConnectionCredentials = async (config: TA grant_type: "authorization_code", code: inputCredentials.code, scope: `openid offline_access https://graph.microsoft.com/.default`, - client_id: INF_APP_CONNECTION_AZURE_CLIENT_ID, - client_secret: INF_APP_CONNECTION_AZURE_CLIENT_SECRET, + client_id: azureClientId, + client_secret: azureClientSecret, redirect_uri: `${SITE_URL}/organization/app-connections/azure/oauth/callback` }) ); diff --git a/backend/src/services/app-connection/azure-devops/azure-devops-fns.ts b/backend/src/services/app-connection/azure-devops/azure-devops-fns.ts index 644747353..2c0521081 100644 --- a/backend/src/services/app-connection/azure-devops/azure-devops-fns.ts +++ b/backend/src/services/app-connection/azure-devops/azure-devops-fns.ts @@ -23,7 +23,7 @@ import { } from "./azure-devops-types"; export const getAzureDevopsConnectionListItem = () => { - const { INF_APP_CONNECTION_AZURE_CLIENT_ID } = getConfig(); + const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID } = getConfig(); return { name: "Azure DevOps" as const, @@ -32,7 +32,7 @@ export const getAzureDevopsConnectionListItem = () => { AzureDevOpsConnectionMethod.OAuth, AzureDevOpsConnectionMethod.AccessToken ], - oauthClientId: INF_APP_CONNECTION_AZURE_CLIENT_ID + oauthClientId: INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID || INF_APP_CONNECTION_AZURE_CLIENT_ID }; }; @@ -63,7 +63,11 @@ export const getAzureDevopsConnection = async ( switch (appConnection.method) { case AzureDevOpsConnectionMethod.OAuth: const appCfg = getConfig(); - if (!appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID || !appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET) { + const azureClientId = + appCfg.INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID || appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID; + const azureClientSecret = + appCfg.INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET || appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET; + if (!azureClientId || !azureClientSecret) { throw new BadRequestError({ message: `Azure environment variables have not been configured` }); @@ -81,8 +85,8 @@ export const getAzureDevopsConnection = async ( new URLSearchParams({ grant_type: "refresh_token", scope: `https://app.vssps.visualstudio.com/.default`, - client_id: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID, - client_secret: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET, + client_id: azureClientId, + client_secret: azureClientSecret, refresh_token: refreshToken }) ); @@ -119,7 +123,13 @@ export const getAzureDevopsConnection = async ( export const validateAzureDevOpsConnectionCredentials = async (config: TAzureDevOpsConnectionConfig) => { const { credentials: inputCredentials, method } = config; - const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_CLIENT_SECRET, SITE_URL } = getConfig(); + const { + INF_APP_CONNECTION_AZURE_CLIENT_ID, + INF_APP_CONNECTION_AZURE_CLIENT_SECRET, + INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID, + INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET, + SITE_URL + } = getConfig(); switch (method) { case AzureDevOpsConnectionMethod.OAuth: @@ -127,7 +137,9 @@ export const validateAzureDevOpsConnectionCredentials = async (config: TAzureDev throw new InternalServerError({ message: "SITE_URL env var is required to complete Azure OAuth flow" }); } - if (!INF_APP_CONNECTION_AZURE_CLIENT_ID || !INF_APP_CONNECTION_AZURE_CLIENT_SECRET) { + const azureClientId = INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID || INF_APP_CONNECTION_AZURE_CLIENT_ID; + const azureClientSecret = INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET || INF_APP_CONNECTION_AZURE_CLIENT_SECRET; + if (!azureClientId || !azureClientSecret) { throw new InternalServerError({ message: `Azure ${getAppConnectionMethodName(method)} environment variables have not been configured` }); @@ -144,8 +156,8 @@ export const validateAzureDevOpsConnectionCredentials = async (config: TAzureDev grant_type: "authorization_code", code: oauthCredentials.code, scope: `https://app.vssps.visualstudio.com/.default`, - client_id: INF_APP_CONNECTION_AZURE_CLIENT_ID, - client_secret: INF_APP_CONNECTION_AZURE_CLIENT_SECRET, + client_id: azureClientId, + client_secret: azureClientSecret, redirect_uri: `${SITE_URL}/organization/app-connections/azure/oauth/callback` }) ); diff --git a/backend/src/services/app-connection/azure-key-vault/azure-key-vault-connection-fns.ts b/backend/src/services/app-connection/azure-key-vault/azure-key-vault-connection-fns.ts index 116597ec4..af8ec360c 100644 --- a/backend/src/services/app-connection/azure-key-vault/azure-key-vault-connection-fns.ts +++ b/backend/src/services/app-connection/azure-key-vault/azure-key-vault-connection-fns.ts @@ -26,7 +26,11 @@ export const getAzureConnectionAccessToken = async ( kmsService: Pick ) => { const appCfg = getConfig(); - if (!appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID || !appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET) { + const azureClientId = + appCfg.INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID || appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID; + const azureClientSecret = + appCfg.INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET || appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET; + if (!azureClientId || !azureClientSecret) { throw new BadRequestError({ message: `Azure environment variables have not been configured` }); @@ -57,8 +61,8 @@ export const getAzureConnectionAccessToken = async ( new URLSearchParams({ grant_type: "refresh_token", scope: `openid offline_access`, - client_id: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID, - client_secret: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET, + client_id: azureClientId, + client_secret: azureClientSecret, refresh_token: credentials.refreshToken }) ); @@ -92,22 +96,31 @@ export const getAzureConnectionAccessToken = async ( }; export const getAzureKeyVaultConnectionListItem = () => { - const { INF_APP_CONNECTION_AZURE_CLIENT_ID } = getConfig(); + const { INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID, INF_APP_CONNECTION_AZURE_CLIENT_ID } = getConfig(); return { name: "Azure Key Vault" as const, app: AppConnection.AzureKeyVault as const, methods: Object.values(AzureKeyVaultConnectionMethod) as [AzureKeyVaultConnectionMethod.OAuth], - oauthClientId: INF_APP_CONNECTION_AZURE_CLIENT_ID + oauthClientId: INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID || INF_APP_CONNECTION_AZURE_CLIENT_ID }; }; export const validateAzureKeyVaultConnectionCredentials = async (config: TAzureKeyVaultConnectionConfig) => { const { credentials: inputCredentials, method } = config; - const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_CLIENT_SECRET, SITE_URL } = getConfig(); + const { + INF_APP_CONNECTION_AZURE_CLIENT_ID, + INF_APP_CONNECTION_AZURE_CLIENT_SECRET, + INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID, + INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET, + SITE_URL + } = getConfig(); - if (!INF_APP_CONNECTION_AZURE_CLIENT_ID || !INF_APP_CONNECTION_AZURE_CLIENT_SECRET) { + const azureClientId = INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID || INF_APP_CONNECTION_AZURE_CLIENT_ID; + const azureClientSecret = INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET || INF_APP_CONNECTION_AZURE_CLIENT_SECRET; + + if (!azureClientId || !azureClientSecret) { throw new InternalServerError({ message: `Azure ${getAppConnectionMethodName(method)} environment variables have not been configured` }); @@ -123,8 +136,8 @@ export const validateAzureKeyVaultConnectionCredentials = async (config: TAzureK grant_type: "authorization_code", code: inputCredentials.code, scope: `openid offline_access https://vault.azure.net/.default`, - client_id: INF_APP_CONNECTION_AZURE_CLIENT_ID, - client_secret: INF_APP_CONNECTION_AZURE_CLIENT_SECRET, + client_id: azureClientId, + client_secret: azureClientSecret, redirect_uri: `${SITE_URL}/organization/app-connections/azure/oauth/callback` }) ); diff --git a/docs/integrations/app-connections/azure-app-configuration.mdx b/docs/integrations/app-connections/azure-app-configuration.mdx index 959a1812a..679839878 100644 --- a/docs/integrations/app-connections/azure-app-configuration.mdx +++ b/docs/integrations/app-connections/azure-app-configuration.mdx @@ -50,8 +50,8 @@ Infisical currently only supports one method for connecting to Azure, which is O Back in your Infisical instance, add two new environment variables for the credentials of your Azure application. - - `INF_APP_CONNECTION_AZURE_CLIENT_ID`: The **Application (Client) ID** of your Azure application. - - `INF_APP_CONNECTION_AZURE_CLIENT_SECRET`: The **Client Secret** of your Azure application. + - `INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID`: The **Application (Client) ID** of your Azure application. + - `INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET`: The **Client Secret** of your Azure application. Once added, restart your Infisical instance and use the Azure App Configuration connection. diff --git a/docs/integrations/app-connections/azure-client-secrets.mdx b/docs/integrations/app-connections/azure-client-secrets.mdx index 82382733e..1fb1c753f 100644 --- a/docs/integrations/app-connections/azure-client-secrets.mdx +++ b/docs/integrations/app-connections/azure-client-secrets.mdx @@ -57,8 +57,8 @@ Infisical currently only supports one method for connecting to Azure, which is O Back in your Infisical instance, add two new environment variables for the credentials of your Azure application. - - `INF_APP_CONNECTION_AZURE_CLIENT_ID`: The **Application (Client) ID** of your Azure application. - - `INF_APP_CONNECTION_AZURE_CLIENT_SECRET`: The **Client Secret** of your Azure application. + - `INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID`: The **Application (Client) ID** of your Azure application. + - `INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET`: The **Client Secret** of your Azure application. Once added, restart your Infisical instance and use the Azure Client Secrets connection. diff --git a/docs/integrations/app-connections/azure-devops.mdx b/docs/integrations/app-connections/azure-devops.mdx index 8fcc25427..6a9e71430 100644 --- a/docs/integrations/app-connections/azure-devops.mdx +++ b/docs/integrations/app-connections/azure-devops.mdx @@ -56,8 +56,8 @@ Infisical currently supports two methods for connecting to Azure DevOps, which a Back in your Infisical instance, add two new environment variables for the credentials of your Azure application. - - `INF_APP_CONNECTION_AZURE_CLIENT_ID`: The **Application (Client) ID** of your Azure application. - - `INF_APP_CONNECTION_AZURE_CLIENT_SECRET`: The **Client Secret** of your Azure application. + - `INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID`: The **Application (Client) ID** of your Azure application. + - `INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET`: The **Client Secret** of your Azure application. Once added, restart your Infisical instance and use the Azure Client Secrets connection. diff --git a/docs/integrations/app-connections/azure-key-vault.mdx b/docs/integrations/app-connections/azure-key-vault.mdx index f73dab834..22cdcf637 100644 --- a/docs/integrations/app-connections/azure-key-vault.mdx +++ b/docs/integrations/app-connections/azure-key-vault.mdx @@ -49,8 +49,8 @@ Infisical currently only supports one method for connecting to Azure, which is O Back in your Infisical instance, add two new environment variables for the credentials of your Azure application. - - `INF_APP_CONNECTION_AZURE_CLIENT_ID`: The **Application (Client) ID** of your Azure application. - - `INF_APP_CONNECTION_AZURE_CLIENT_SECRET`: The **Client Secret** of your Azure application. + - `INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID`: The **Application (Client) ID** of your Azure application. + - `INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET`: The **Client Secret** of your Azure application. Once added, restart your Infisical instance and use the Azure Key Vault connection. From cd4b9cd03a23a433d7357949f6f4d66aea751ebf Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Mon, 28 Jul 2025 09:30:37 -0300 Subject: [PATCH 27/34] Improve azure client secrets env var name --- .env.example | 4 ++-- backend/src/lib/config/env.ts | 8 ++++---- .../azure-client-secrets-connection-fns.ts | 16 ++++++++-------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.env.example b/.env.example index dbaf1e633..847b5e05c 100644 --- a/.env.example +++ b/.env.example @@ -132,8 +132,8 @@ INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET= INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID= INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET= -INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_ID= -INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_SECRET= +INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID= +INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET= INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID= INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET= diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index d6eeba342..551ff41eb 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -268,8 +268,8 @@ const envSchema = z INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET: zpStr(z.string().optional()), INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID: zpStr(z.string().optional()), INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET: zpStr(z.string().optional()), - INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_ID: zpStr(z.string().optional()), - INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_SECRET: zpStr(z.string().optional()), + INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID: zpStr(z.string().optional()), + INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET: zpStr(z.string().optional()), INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID: zpStr(z.string().optional()), INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET: zpStr(z.string().optional()), @@ -487,11 +487,11 @@ export const overwriteSchema: { description: "The Client Secret of your Azure application." }, { - key: "INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_ID", + key: "INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID", description: "The Application (Client) ID of your Azure application." }, { - key: "INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_SECRET", + key: "INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET", description: "The Client Secret of your Azure application." }, { diff --git a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts index f6987bbe9..3dc2f12d1 100644 --- a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts +++ b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts @@ -23,7 +23,7 @@ import { } from "./azure-client-secrets-connection-types"; export const getAzureClientSecretsConnectionListItem = () => { - const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_ID } = getConfig(); + const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID } = getConfig(); return { name: "Azure Client Secrets" as const, @@ -32,7 +32,7 @@ export const getAzureClientSecretsConnectionListItem = () => { AzureClientSecretsConnectionMethod.OAuth, AzureClientSecretsConnectionMethod.ClientSecret ], - oauthClientId: INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_ID || INF_APP_CONNECTION_AZURE_CLIENT_ID + oauthClientId: INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID || INF_APP_CONNECTION_AZURE_CLIENT_ID }; }; @@ -65,9 +65,9 @@ export const getAzureConnectionAccessToken = async ( switch (appConnection.method) { case AzureClientSecretsConnectionMethod.OAuth: const azureClientId = - appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_ID || appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID; + appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID || appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID; const azureClientSecret = - appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_SECRET || appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET; + appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET || appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET; if (!azureClientId || !azureClientSecret) { throw new BadRequestError({ message: `Azure OAuth environment variables have not been configured` @@ -149,8 +149,8 @@ export const validateAzureClientSecretsConnectionCredentials = async (config: TA const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_CLIENT_SECRET, - INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_ID, - INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_SECRET, + INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID, + INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET, SITE_URL } = getConfig(); @@ -160,9 +160,9 @@ export const validateAzureClientSecretsConnectionCredentials = async (config: TA throw new InternalServerError({ message: "SITE_URL env var is required to complete Azure OAuth flow" }); } - const azureClientId = INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_ID || INF_APP_CONNECTION_AZURE_CLIENT_ID; + const azureClientId = INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID || INF_APP_CONNECTION_AZURE_CLIENT_ID; const azureClientSecret = - INF_APP_CONNECTION_AZURE_CLIENT_SECRET_CLIENT_SECRET || INF_APP_CONNECTION_AZURE_CLIENT_SECRET; + INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET || INF_APP_CONNECTION_AZURE_CLIENT_SECRET; if (!azureClientId || !azureClientSecret) { throw new InternalServerError({ From 27da14df9dca84872d243cbfa5a5195ad603e787 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 28 Jul 2025 16:40:20 +0400 Subject: [PATCH 28/34] Fix CVE's --- backend/package-lock.json | 31 +-- backend/package.json | 3 +- frontend/package-lock.json | 40 ++-- frontend/package.json | 2 +- package-lock.json | 453 +++++++++++++++++++++++++++++++++++++ package.json | 1 + 6 files changed, 491 insertions(+), 39 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index a5c106540..cb9efa148 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "backend", "version": "1.0.0", + "hasInstallScript": true, "license": "ISC", "dependencies": { "@aws-sdk/client-elasticache": "^3.637.0", @@ -61,7 +62,7 @@ "ajv": "^8.12.0", "argon2": "^0.31.2", "aws-sdk": "^2.1553.0", - "axios": "^1.6.7", + "axios": "^1.11.0", "axios-retry": "^4.0.0", "bcrypt": "^5.1.1", "botbuilder": "^4.23.2", @@ -13699,14 +13700,16 @@ } }, "node_modules/@types/request/node_modules/form-data": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz", - "integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", + "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35", "safe-buffer": "^5.2.1" }, "engines": { @@ -15230,13 +15233,13 @@ } }, "node_modules/axios": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", - "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -18761,13 +18764,15 @@ } }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { diff --git a/backend/package.json b/backend/package.json index bd355dd22..dcd36ee0b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -25,6 +25,7 @@ "outputPath": "binary" }, "scripts": { + "preinstall": "npm-force-resolutions", "binary:build": "npm run binary:clean && npm run build:frontend && npm run build && npm run binary:babel-frontend && npm run binary:babel-backend && npm run binary:rename-imports", "binary:package": "pkg --no-bytecode --public-packages \"*\" --public --target host .", "binary:babel-backend": " babel ./dist -d ./dist", @@ -181,7 +182,7 @@ "ajv": "^8.12.0", "argon2": "^0.31.2", "aws-sdk": "^2.1553.0", - "axios": "^1.6.7", + "axios": "^1.11.0", "axios-retry": "^4.0.0", "bcrypt": "^5.1.1", "botbuilder": "^4.23.2", diff --git a/frontend/package-lock.json b/frontend/package-lock.json index edbf5673f..3c73d9fe3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -55,7 +55,7 @@ "@ucast/mongo2js": "^1.3.4", "@xyflow/react": "^12.4.4", "argon2-browser": "^1.18.0", - "axios": "^1.7.9", + "axios": "^1.11.0", "classnames": "^2.5.1", "cva": "npm:class-variance-authority@^0.7.1", "date-fns": "^4.1.0", @@ -5282,13 +5282,13 @@ } }, "node_modules/axios": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz", - "integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -5700,7 +5700,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -6665,7 +6664,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.0", @@ -6819,7 +6817,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6829,7 +6826,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -6867,7 +6863,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -6877,15 +6872,15 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dev": true, + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -7855,13 +7850,15 @@ } }, "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -7992,7 +7989,6 @@ "version": "1.2.6", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -8139,7 +8135,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8215,7 +8210,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -8228,7 +8222,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -9545,7 +9538,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" diff --git a/frontend/package.json b/frontend/package.json index 9a2cc2ba4..1bc55c1c7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -59,7 +59,7 @@ "@ucast/mongo2js": "^1.3.4", "@xyflow/react": "^12.4.4", "argon2-browser": "^1.18.0", - "axios": "^1.7.9", + "axios": "^1.11.0", "classnames": "^2.5.1", "cva": "npm:class-variance-authority@^0.7.1", "date-fns": "^4.1.0", diff --git a/package-lock.json b/package-lock.json index 4d72220af..5a6260a40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "license": "ISC", "dependencies": { "@radix-ui/react-radio-group": "^1.1.3", + "axios": "^1.11.0", "secrets.js-grempe": "^2.0.0" }, "devDependencies": { @@ -564,6 +565,23 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -580,6 +598,19 @@ "concat-map": "0.0.1" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -624,6 +655,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -670,6 +713,15 @@ "dev": true, "license": "MIT" }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -682,6 +734,65 @@ "node": ">=6.0.0" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -921,12 +1032,94 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -975,6 +1168,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -991,6 +1196,45 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/husky": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", @@ -1173,6 +1417,36 @@ "loose-envify": "cli.js" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1305,6 +1579,12 @@ "node": ">= 0.8.0" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -1894,6 +2174,21 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "axios": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "requires": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1910,6 +2205,15 @@ "concat-map": "0.0.1" } }, + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1941,6 +2245,14 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1973,6 +2285,11 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -1982,6 +2299,45 @@ "esutils": "^2.0.2" } }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, + "es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "requires": { + "es-errors": "^1.3.0" + } + }, + "es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "requires": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2153,12 +2509,60 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==" + }, + "form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + } + }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2191,6 +2595,11 @@ "type-fest": "^0.20.2" } }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + }, "graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -2203,6 +2612,27 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "requires": { + "has-symbols": "^1.0.3" + } + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, "husky": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", @@ -2335,6 +2765,24 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2430,6 +2878,11 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index db15de3fb..71d278fa3 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ }, "dependencies": { "@radix-ui/react-radio-group": "^1.1.3", + "axios": "^1.11.0", "secrets.js-grempe": "^2.0.0" } } From 17af33372cc08b90af2851400f98e7e0cda28b07 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 28 Jul 2025 16:40:58 +0400 Subject: [PATCH 29/34] uninstall axios in root --- package-lock.json | 453 ---------------------------------------------- package.json | 1 - 2 files changed, 454 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5a6260a40..4d72220af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,6 @@ "license": "ISC", "dependencies": { "@radix-ui/react-radio-group": "^1.1.3", - "axios": "^1.11.0", "secrets.js-grempe": "^2.0.0" }, "devDependencies": { @@ -565,23 +564,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", - "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -598,19 +580,6 @@ "concat-map": "0.0.1" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -655,18 +624,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -713,15 +670,6 @@ "dev": true, "license": "MIT" }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -734,65 +682,6 @@ "node": ">=6.0.0" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1032,94 +921,12 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -1168,18 +975,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -1196,45 +991,6 @@ "node": ">=8" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/husky": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", @@ -1417,36 +1173,6 @@ "loose-envify": "cli.js" } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1579,12 +1305,6 @@ "node": ">= 0.8.0" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2174,21 +1894,6 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "axios": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", - "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", - "requires": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" - } - }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2205,15 +1910,6 @@ "concat-map": "0.0.1" } }, - "call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "requires": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - } - }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2245,14 +1941,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2285,11 +1973,6 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -2299,45 +1982,6 @@ "esutils": "^2.0.2" } }, - "dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "requires": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - } - }, - "es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" - }, - "es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" - }, - "es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "requires": { - "es-errors": "^1.3.0" - } - }, - "es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "requires": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - } - }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2509,60 +2153,12 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, - "follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==" - }, - "form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" - }, - "get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "requires": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - } - }, - "get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "requires": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - } - }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2595,11 +2191,6 @@ "type-fest": "^0.20.2" } }, - "gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" - }, "graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -2612,27 +2203,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" - }, - "has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "requires": { - "has-symbols": "^1.0.3" - } - }, - "hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "requires": { - "function-bind": "^1.1.2" - } - }, "husky": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", @@ -2765,24 +2335,6 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, - "math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2878,11 +2430,6 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index 71d278fa3..db15de3fb 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ }, "dependencies": { "@radix-ui/react-radio-group": "^1.1.3", - "axios": "^1.11.0", "secrets.js-grempe": "^2.0.0" } } From 2a5593ea309e317f776782b75c55bb689f388a0b Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 28 Jul 2025 16:42:21 +0400 Subject: [PATCH 30/34] update axios in oidc sink server --- sink/oidc-server/package-lock.json | 17 +++++++++-------- sink/oidc-server/package.json | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/sink/oidc-server/package-lock.json b/sink/oidc-server/package-lock.json index 2be29633b..f1732704d 100644 --- a/sink/oidc-server/package-lock.json +++ b/sink/oidc-server/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "axios": "^1.8.3", + "axios": "^1.11.0", "dotenv": "^16.4.7", "express": "^4.21.2", "form-data": "^4.0.2", @@ -105,13 +105,13 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz", - "integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -571,14 +571,15 @@ } }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { diff --git a/sink/oidc-server/package.json b/sink/oidc-server/package.json index 514629d46..3dfa41224 100644 --- a/sink/oidc-server/package.json +++ b/sink/oidc-server/package.json @@ -11,7 +11,7 @@ "license": "ISC", "description": "", "dependencies": { - "axios": "^1.8.3", + "axios": "^1.11.0", "dotenv": "^16.4.7", "express": "^4.21.2", "form-data": "^4.0.2", From 8eebd7228f1487fac0588d7736ffe60643dc75f2 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 28 Jul 2025 16:43:13 +0400 Subject: [PATCH 31/34] Update package.json --- backend/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/package.json b/backend/package.json index dcd36ee0b..01bb0c42a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -25,7 +25,6 @@ "outputPath": "binary" }, "scripts": { - "preinstall": "npm-force-resolutions", "binary:build": "npm run binary:clean && npm run build:frontend && npm run build && npm run binary:babel-frontend && npm run binary:babel-backend && npm run binary:rename-imports", "binary:package": "pkg --no-bytecode --public-packages \"*\" --public --target host .", "binary:babel-backend": " babel ./dist -d ./dist", From f265fa6d374f4b3736e74e1626b5286be930049c Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Mon, 28 Jul 2025 10:14:21 -0300 Subject: [PATCH 32/34] Minor improvements to azure multi env variables --- .env.example | 3 - backend/src/lib/config/env.ts | 61 ++++++++++++++----- .../azure-app-configuration-connection-fns.ts | 19 +++--- .../azure-client-secrets-connection-fns.ts | 32 +++++----- .../azure-devops/azure-devops-fns.ts | 31 +++------- .../azure-key-vault-connection-fns.ts | 35 ++++------- 6 files changed, 91 insertions(+), 90 deletions(-) diff --git a/.env.example b/.env.example index 847b5e05c..059ec124f 100644 --- a/.env.example +++ b/.env.example @@ -123,9 +123,6 @@ INF_APP_CONNECTION_GITHUB_RADAR_APP_WEBHOOK_SECRET= INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL= # azure app connection -INF_APP_CONNECTION_AZURE_CLIENT_ID= -INF_APP_CONNECTION_AZURE_CLIENT_SECRET= - INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID= INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET= diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 551ff41eb..29f21ae20 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -261,15 +261,23 @@ const envSchema = z // gcp app INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL: zpStr(z.string().optional()), - // azure app + // Legacy Single Multi Purpose Azure App Connection INF_APP_CONNECTION_AZURE_CLIENT_ID: zpStr(z.string().optional()), INF_APP_CONNECTION_AZURE_CLIENT_SECRET: zpStr(z.string().optional()), + + // Azure App Configuration App Connection INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID: zpStr(z.string().optional()), INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET: zpStr(z.string().optional()), + + // Azure Key Vault App Connection INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID: zpStr(z.string().optional()), INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET: zpStr(z.string().optional()), + + // Azure Client Secrets App Connection INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID: zpStr(z.string().optional()), INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET: zpStr(z.string().optional()), + + // Azure DevOps App Connection INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID: zpStr(z.string().optional()), INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET: zpStr(z.string().optional()), @@ -349,7 +357,23 @@ const envSchema = z isHsmConfigured: Boolean(data.HSM_LIB_PATH) && Boolean(data.HSM_PIN) && Boolean(data.HSM_KEY_LABEL) && data.HSM_SLOT !== undefined, samlDefaultOrgSlug: data.DEFAULT_SAML_ORG_SLUG, - SECRET_SCANNING_ORG_WHITELIST: data.SECRET_SCANNING_ORG_WHITELIST?.split(",") + SECRET_SCANNING_ORG_WHITELIST: data.SECRET_SCANNING_ORG_WHITELIST?.split(","), + INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID: + data.INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID || data.INF_APP_CONNECTION_AZURE_CLIENT_ID, + INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET: + data.INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET || data.INF_APP_CONNECTION_AZURE_CLIENT_SECRET, + INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID: + data.INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID || data.INF_APP_CONNECTION_AZURE_CLIENT_ID, + INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET: + data.INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET || data.INF_APP_CONNECTION_AZURE_CLIENT_SECRET, + INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID: + data.INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID || data.INF_APP_CONNECTION_AZURE_CLIENT_ID, + INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET: + data.INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET || data.INF_APP_CONNECTION_AZURE_CLIENT_SECRET, + INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID: + data.INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID || data.INF_APP_CONNECTION_AZURE_CLIENT_ID, + INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET: + data.INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET || data.INF_APP_CONNECTION_AZURE_CLIENT_SECRET })); export type TEnvConfig = Readonly>; @@ -459,17 +483,9 @@ export const overwriteSchema: { } ] }, - azure: { - name: "Azure", + azureAppConfiguration: { + name: "Azure App Configuration", fields: [ - { - key: "INF_APP_CONNECTION_AZURE_CLIENT_ID", - description: "The Application (Client) ID of your Azure application." - }, - { - key: "INF_APP_CONNECTION_AZURE_CLIENT_SECRET", - description: "The Client Secret of your Azure application." - }, { key: "INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID", description: "The Application (Client) ID of your Azure application." @@ -477,7 +493,12 @@ export const overwriteSchema: { { key: "INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET", description: "The Client Secret of your Azure application." - }, + } + ] + }, + azureKeyVault: { + name: "Azure Key Vault", + fields: [ { key: "INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID", description: "The Application (Client) ID of your Azure application." @@ -485,7 +506,12 @@ export const overwriteSchema: { { key: "INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET", description: "The Client Secret of your Azure application." - }, + } + ] + }, + azureClientSecrets: { + name: "Azure Client Secrets", + fields: [ { key: "INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID", description: "The Application (Client) ID of your Azure application." @@ -493,7 +519,12 @@ export const overwriteSchema: { { key: "INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET", description: "The Client Secret of your Azure application." - }, + } + ] + }, + azureDevOps: { + name: "Azure DevOps", + fields: [ { key: "INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID", description: "The Application (Client) ID of your Azure application." diff --git a/backend/src/services/app-connection/azure-app-configuration/azure-app-configuration-connection-fns.ts b/backend/src/services/app-connection/azure-app-configuration/azure-app-configuration-connection-fns.ts index 9fd38be9d..114794dc5 100644 --- a/backend/src/services/app-connection/azure-app-configuration/azure-app-configuration-connection-fns.ts +++ b/backend/src/services/app-connection/azure-app-configuration/azure-app-configuration-connection-fns.ts @@ -14,13 +14,13 @@ import { } from "./azure-app-configuration-connection-types"; export const getAzureAppConfigurationConnectionListItem = () => { - const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID } = getConfig(); + const { INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID } = getConfig(); return { name: "Azure App Configuration" as const, app: AppConnection.AzureAppConfiguration as const, methods: Object.values(AzureAppConfigurationConnectionMethod) as [AzureAppConfigurationConnectionMethod.OAuth], - oauthClientId: INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID || INF_APP_CONNECTION_AZURE_CLIENT_ID + oauthClientId: INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID }; }; @@ -30,18 +30,15 @@ export const validateAzureAppConfigurationConnectionCredentials = async ( const { credentials: inputCredentials, method } = config; const { - INF_APP_CONNECTION_AZURE_CLIENT_ID, - INF_APP_CONNECTION_AZURE_CLIENT_SECRET, INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID, INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET, SITE_URL } = getConfig(); - const azureClientId = INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID || INF_APP_CONNECTION_AZURE_CLIENT_ID; - const azureClientSecret = - INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET || INF_APP_CONNECTION_AZURE_CLIENT_SECRET; - - if (!azureClientId || !azureClientSecret) { + if ( + !INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID || + !INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET + ) { throw new InternalServerError({ message: `Azure ${getAppConnectionMethodName(method)} environment variables have not been configured` }); @@ -57,8 +54,8 @@ export const validateAzureAppConfigurationConnectionCredentials = async ( grant_type: "authorization_code", code: inputCredentials.code, scope: `openid offline_access https://azconfig.io/.default`, - client_id: azureClientId, - client_secret: azureClientSecret, + client_id: INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID, + client_secret: INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET, redirect_uri: `${SITE_URL}/organization/app-connections/azure/oauth/callback` }) ); diff --git a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts index 3dc2f12d1..41dbb4392 100644 --- a/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts +++ b/backend/src/services/app-connection/azure-client-secrets/azure-client-secrets-connection-fns.ts @@ -23,7 +23,7 @@ import { } from "./azure-client-secrets-connection-types"; export const getAzureClientSecretsConnectionListItem = () => { - const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID } = getConfig(); + const { INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID } = getConfig(); return { name: "Azure Client Secrets" as const, @@ -32,7 +32,7 @@ export const getAzureClientSecretsConnectionListItem = () => { AzureClientSecretsConnectionMethod.OAuth, AzureClientSecretsConnectionMethod.ClientSecret ], - oauthClientId: INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID || INF_APP_CONNECTION_AZURE_CLIENT_ID + oauthClientId: INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID }; }; @@ -64,11 +64,10 @@ export const getAzureConnectionAccessToken = async ( const currentTime = Date.now(); switch (appConnection.method) { case AzureClientSecretsConnectionMethod.OAuth: - const azureClientId = - appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID || appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID; - const azureClientSecret = - appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET || appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET; - if (!azureClientId || !azureClientSecret) { + if ( + !appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID || + !appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET + ) { throw new BadRequestError({ message: `Azure OAuth environment variables have not been configured` }); @@ -78,8 +77,8 @@ export const getAzureConnectionAccessToken = async ( new URLSearchParams({ grant_type: "refresh_token", scope: `openid offline_access https://graph.microsoft.com/.default`, - client_id: azureClientId, - client_secret: azureClientSecret, + client_id: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID, + client_secret: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET, refresh_token: refreshToken }) ); @@ -147,8 +146,6 @@ export const validateAzureClientSecretsConnectionCredentials = async (config: TA const { credentials: inputCredentials, method } = config; const { - INF_APP_CONNECTION_AZURE_CLIENT_ID, - INF_APP_CONNECTION_AZURE_CLIENT_SECRET, INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID, INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET, SITE_URL @@ -160,11 +157,10 @@ export const validateAzureClientSecretsConnectionCredentials = async (config: TA throw new InternalServerError({ message: "SITE_URL env var is required to complete Azure OAuth flow" }); } - const azureClientId = INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID || INF_APP_CONNECTION_AZURE_CLIENT_ID; - const azureClientSecret = - INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET || INF_APP_CONNECTION_AZURE_CLIENT_SECRET; - - if (!azureClientId || !azureClientSecret) { + if ( + !INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID || + !INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET + ) { throw new InternalServerError({ message: `Azure ${getAppConnectionMethodName(method)} environment variables have not been configured` }); @@ -180,8 +176,8 @@ export const validateAzureClientSecretsConnectionCredentials = async (config: TA grant_type: "authorization_code", code: inputCredentials.code, scope: `openid offline_access https://graph.microsoft.com/.default`, - client_id: azureClientId, - client_secret: azureClientSecret, + client_id: INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID, + client_secret: INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET, redirect_uri: `${SITE_URL}/organization/app-connections/azure/oauth/callback` }) ); diff --git a/backend/src/services/app-connection/azure-devops/azure-devops-fns.ts b/backend/src/services/app-connection/azure-devops/azure-devops-fns.ts index 2c0521081..e9bb1f6bd 100644 --- a/backend/src/services/app-connection/azure-devops/azure-devops-fns.ts +++ b/backend/src/services/app-connection/azure-devops/azure-devops-fns.ts @@ -23,7 +23,7 @@ import { } from "./azure-devops-types"; export const getAzureDevopsConnectionListItem = () => { - const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID } = getConfig(); + const { INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID } = getConfig(); return { name: "Azure DevOps" as const, @@ -32,7 +32,7 @@ export const getAzureDevopsConnectionListItem = () => { AzureDevOpsConnectionMethod.OAuth, AzureDevOpsConnectionMethod.AccessToken ], - oauthClientId: INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID || INF_APP_CONNECTION_AZURE_CLIENT_ID + oauthClientId: INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID }; }; @@ -63,11 +63,7 @@ export const getAzureDevopsConnection = async ( switch (appConnection.method) { case AzureDevOpsConnectionMethod.OAuth: const appCfg = getConfig(); - const azureClientId = - appCfg.INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID || appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID; - const azureClientSecret = - appCfg.INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET || appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET; - if (!azureClientId || !azureClientSecret) { + if (!appCfg.INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID || !appCfg.INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET) { throw new BadRequestError({ message: `Azure environment variables have not been configured` }); @@ -85,8 +81,8 @@ export const getAzureDevopsConnection = async ( new URLSearchParams({ grant_type: "refresh_token", scope: `https://app.vssps.visualstudio.com/.default`, - client_id: azureClientId, - client_secret: azureClientSecret, + client_id: appCfg.INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID, + client_secret: appCfg.INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET, refresh_token: refreshToken }) ); @@ -123,13 +119,8 @@ export const getAzureDevopsConnection = async ( export const validateAzureDevOpsConnectionCredentials = async (config: TAzureDevOpsConnectionConfig) => { const { credentials: inputCredentials, method } = config; - const { - INF_APP_CONNECTION_AZURE_CLIENT_ID, - INF_APP_CONNECTION_AZURE_CLIENT_SECRET, - INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID, - INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET, - SITE_URL - } = getConfig(); + const { INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID, INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET, SITE_URL } = + getConfig(); switch (method) { case AzureDevOpsConnectionMethod.OAuth: @@ -137,9 +128,7 @@ export const validateAzureDevOpsConnectionCredentials = async (config: TAzureDev throw new InternalServerError({ message: "SITE_URL env var is required to complete Azure OAuth flow" }); } - const azureClientId = INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID || INF_APP_CONNECTION_AZURE_CLIENT_ID; - const azureClientSecret = INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET || INF_APP_CONNECTION_AZURE_CLIENT_SECRET; - if (!azureClientId || !azureClientSecret) { + if (!INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID || !INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET) { throw new InternalServerError({ message: `Azure ${getAppConnectionMethodName(method)} environment variables have not been configured` }); @@ -156,8 +145,8 @@ export const validateAzureDevOpsConnectionCredentials = async (config: TAzureDev grant_type: "authorization_code", code: oauthCredentials.code, scope: `https://app.vssps.visualstudio.com/.default`, - client_id: azureClientId, - client_secret: azureClientSecret, + client_id: INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID, + client_secret: INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET, redirect_uri: `${SITE_URL}/organization/app-connections/azure/oauth/callback` }) ); diff --git a/backend/src/services/app-connection/azure-key-vault/azure-key-vault-connection-fns.ts b/backend/src/services/app-connection/azure-key-vault/azure-key-vault-connection-fns.ts index af8ec360c..95102c5d1 100644 --- a/backend/src/services/app-connection/azure-key-vault/azure-key-vault-connection-fns.ts +++ b/backend/src/services/app-connection/azure-key-vault/azure-key-vault-connection-fns.ts @@ -26,11 +26,10 @@ export const getAzureConnectionAccessToken = async ( kmsService: Pick ) => { const appCfg = getConfig(); - const azureClientId = - appCfg.INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID || appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID; - const azureClientSecret = - appCfg.INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET || appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET; - if (!azureClientId || !azureClientSecret) { + if ( + !appCfg.INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID || + !appCfg.INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET + ) { throw new BadRequestError({ message: `Azure environment variables have not been configured` }); @@ -61,8 +60,8 @@ export const getAzureConnectionAccessToken = async ( new URLSearchParams({ grant_type: "refresh_token", scope: `openid offline_access`, - client_id: azureClientId, - client_secret: azureClientSecret, + client_id: appCfg.INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID, + client_secret: appCfg.INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET, refresh_token: credentials.refreshToken }) ); @@ -96,31 +95,23 @@ export const getAzureConnectionAccessToken = async ( }; export const getAzureKeyVaultConnectionListItem = () => { - const { INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID, INF_APP_CONNECTION_AZURE_CLIENT_ID } = getConfig(); + const { INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID } = getConfig(); return { name: "Azure Key Vault" as const, app: AppConnection.AzureKeyVault as const, methods: Object.values(AzureKeyVaultConnectionMethod) as [AzureKeyVaultConnectionMethod.OAuth], - oauthClientId: INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID || INF_APP_CONNECTION_AZURE_CLIENT_ID + oauthClientId: INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID }; }; export const validateAzureKeyVaultConnectionCredentials = async (config: TAzureKeyVaultConnectionConfig) => { const { credentials: inputCredentials, method } = config; - const { - INF_APP_CONNECTION_AZURE_CLIENT_ID, - INF_APP_CONNECTION_AZURE_CLIENT_SECRET, - INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID, - INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET, - SITE_URL - } = getConfig(); + const { INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID, INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET, SITE_URL } = + getConfig(); - const azureClientId = INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID || INF_APP_CONNECTION_AZURE_CLIENT_ID; - const azureClientSecret = INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET || INF_APP_CONNECTION_AZURE_CLIENT_SECRET; - - if (!azureClientId || !azureClientSecret) { + if (!INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID || !INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET) { throw new InternalServerError({ message: `Azure ${getAppConnectionMethodName(method)} environment variables have not been configured` }); @@ -136,8 +127,8 @@ export const validateAzureKeyVaultConnectionCredentials = async (config: TAzureK grant_type: "authorization_code", code: inputCredentials.code, scope: `openid offline_access https://vault.azure.net/.default`, - client_id: azureClientId, - client_secret: azureClientSecret, + client_id: INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID, + client_secret: INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET, redirect_uri: `${SITE_URL}/organization/app-connections/azure/oauth/callback` }) ); From 41ba7edba22cdec974ceaf7c971f2fb4d784e7a2 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Mon, 28 Jul 2025 09:50:18 -0700 Subject: [PATCH 33/34] improvement: remove click outside modal close disabling on sync/data source/rotation modals --- .../secret-rotations-v2/CreateSecretRotationV2Modal.tsx | 1 - .../secret-scanning/CreateSecretScanningDataSourceModal.tsx | 1 - frontend/src/components/secret-syncs/CreateSecretSyncModal.tsx | 1 - 3 files changed, 3 deletions(-) diff --git a/frontend/src/components/secret-rotations-v2/CreateSecretRotationV2Modal.tsx b/frontend/src/components/secret-rotations-v2/CreateSecretRotationV2Modal.tsx index 210257d7d..f5b9a39b3 100644 --- a/frontend/src/components/secret-rotations-v2/CreateSecretRotationV2Modal.tsx +++ b/frontend/src/components/secret-rotations-v2/CreateSecretRotationV2Modal.tsx @@ -76,7 +76,6 @@ export const CreateSecretRotationV2Modal = ({ onOpenChange, isOpen, ...props }:
) } - onPointerDownOutside={(e) => e.preventDefault()} className={selectedRotation ? "max-w-2xl" : "max-w-3xl"} subTitle={ selectedRotation ? undefined : "Select a provider to create a secret rotation for." diff --git a/frontend/src/components/secret-scanning/CreateSecretScanningDataSourceModal.tsx b/frontend/src/components/secret-scanning/CreateSecretScanningDataSourceModal.tsx index a3943cf35..c95baaae5 100644 --- a/frontend/src/components/secret-scanning/CreateSecretScanningDataSourceModal.tsx +++ b/frontend/src/components/secret-scanning/CreateSecretScanningDataSourceModal.tsx @@ -75,7 +75,6 @@ export const CreateSecretScanningDataSourceModal = ({ onOpenChange, isOpen, ...p
) } - onPointerDownOutside={(e) => e.preventDefault()} className={selectedDataSource ? "max-w-2xl" : "max-w-3xl"} subTitle={ selectedDataSource ? undefined : "Select a data source to configure secret scanning for." diff --git a/frontend/src/components/secret-syncs/CreateSecretSyncModal.tsx b/frontend/src/components/secret-syncs/CreateSecretSyncModal.tsx index 7bae0479f..5ff72e810 100644 --- a/frontend/src/components/secret-syncs/CreateSecretSyncModal.tsx +++ b/frontend/src/components/secret-syncs/CreateSecretSyncModal.tsx @@ -56,7 +56,6 @@ export const CreateSecretSyncModal = ({ onOpenChange, selectSync = null, ...prop "Add Sync" ) } - onPointerDownOutside={(e) => e.preventDefault()} className="max-w-2xl" bodyClassName="overflow-visible" subTitle={selectedSync ? undefined : "Select a third-party service to sync secrets to."} From 975b621bc8077b46ee2ad6297a38bdf522d75467 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Mon, 28 Jul 2025 10:26:22 -0700 Subject: [PATCH 34/34] fix: remove passthrough on banner guard for kms pages --- frontend/src/pages/kms/KmipPage/KmipPage.tsx | 1 - frontend/src/pages/kms/OverviewPage/OverviewPage.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/frontend/src/pages/kms/KmipPage/KmipPage.tsx b/frontend/src/pages/kms/KmipPage/KmipPage.tsx index 6297c5eef..9c337bba3 100644 --- a/frontend/src/pages/kms/KmipPage/KmipPage.tsx +++ b/frontend/src/pages/kms/KmipPage/KmipPage.tsx @@ -22,7 +22,6 @@ export const KmipPage = () => { description="Integrate with Infisical KMS via Key Management Interoperability Protocol." /> { description="Manage keys and perform cryptographic operations." />