diff --git a/.infisicalignore b/.infisicalignore index b935763c8..6dc706c67 100644 --- a/.infisicalignore +++ b/.infisicalignore @@ -52,3 +52,4 @@ docs/integrations/app-connections/railway.mdx:generic-api-key:156 .github/workflows/validate-db-schemas.yml:generic-api-key:21 k8-operator/config/samples/universalAuthIdentitySecret.yaml:generic-api-key:8 docs/integrations/app-connections/redis.mdx:generic-api-key:80 +backend/src/ee/services/app-connections/chef/chef-connection-fns.ts:private-key:42 diff --git a/backend/src/@types/knex.d.ts b/backend/src/@types/knex.d.ts index bbc27ebc1..7ff31ed99 100644 --- a/backend/src/@types/knex.d.ts +++ b/backend/src/@types/knex.d.ts @@ -62,6 +62,9 @@ import { TCertificateSecretsUpdate, TCertificatesInsert, TCertificatesUpdate, + TCertificateSyncs, + TCertificateSyncsInsert, + TCertificateSyncsUpdate, TCertificateTemplateEstConfigs, TCertificateTemplateEstConfigsInsert, TCertificateTemplateEstConfigsUpdate, @@ -738,6 +741,11 @@ declare module "knex/types/tables" { TPkiSubscribersUpdate >; [TableName.PkiSync]: KnexOriginal.CompositeTableType; + [TableName.CertificateSync]: KnexOriginal.CompositeTableType< + TCertificateSyncs, + TCertificateSyncsInsert, + TCertificateSyncsUpdate + >; [TableName.UserGroupMembership]: KnexOriginal.CompositeTableType< TUserGroupMembership, TUserGroupMembershipInsert, diff --git a/backend/src/db/migrations/20251028120000_add-certificate-sync-table.ts b/backend/src/db/migrations/20251028120000_add-certificate-sync-table.ts new file mode 100644 index 000000000..14904e5bd --- /dev/null +++ b/backend/src/db/migrations/20251028120000_add-certificate-sync-table.ts @@ -0,0 +1,35 @@ +import { Knex } from "knex"; + +import { TableName } from "@app/db/schemas"; +import { createOnUpdateTrigger, dropOnUpdateTrigger } from "@app/db/utils"; +import { CertificateSyncStatus } from "@app/services/certificate-sync/certificate-sync-enums"; + +export async function up(knex: Knex): Promise { + if (!(await knex.schema.hasTable(TableName.CertificateSync))) { + await knex.schema.createTable(TableName.CertificateSync, (t) => { + t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); + t.uuid("pkiSyncId").notNullable(); + t.foreign("pkiSyncId").references("id").inTable(TableName.PkiSync).onDelete("CASCADE"); + t.uuid("certificateId").notNullable(); + t.foreign("certificateId").references("id").inTable(TableName.Certificate).onDelete("CASCADE"); + t.string("syncStatus").defaultTo(CertificateSyncStatus.Pending); + t.text("lastSyncMessage"); + t.datetime("lastSyncedAt"); + t.timestamps(true, true, true); + + // Ensure unique combination of pki sync and certificate + t.unique(["pkiSyncId", "certificateId"]); + + t.index("pkiSyncId"); + t.index("certificateId"); + t.index("syncStatus"); + }); + + await createOnUpdateTrigger(knex, TableName.CertificateSync); + } +} + +export async function down(knex: Knex): Promise { + await knex.schema.dropTableIfExists(TableName.CertificateSync); + await dropOnUpdateTrigger(knex, TableName.CertificateSync); +} diff --git a/backend/src/db/migrations/20251031044512_add-certificate-sync-external-identifier.ts b/backend/src/db/migrations/20251031044512_add-certificate-sync-external-identifier.ts new file mode 100644 index 000000000..69e15795a --- /dev/null +++ b/backend/src/db/migrations/20251031044512_add-certificate-sync-external-identifier.ts @@ -0,0 +1,21 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + if (!(await knex.schema.hasColumn(TableName.CertificateSync, "externalIdentifier"))) { + await knex.schema.alterTable(TableName.CertificateSync, (t) => { + t.text("externalIdentifier").nullable(); + t.index("externalIdentifier"); + }); + } +} + +export async function down(knex: Knex): Promise { + if (await knex.schema.hasColumn(TableName.CertificateSync, "externalIdentifier")) { + await knex.schema.alterTable(TableName.CertificateSync, (t) => { + t.dropIndex("externalIdentifier"); + t.dropColumn("externalIdentifier"); + }); + } +} diff --git a/backend/src/db/schemas/certificate-syncs.ts b/backend/src/db/schemas/certificate-syncs.ts new file mode 100644 index 000000000..241684db2 --- /dev/null +++ b/backend/src/db/schemas/certificate-syncs.ts @@ -0,0 +1,24 @@ +// 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 CertificateSyncsSchema = z.object({ + id: z.string().uuid(), + pkiSyncId: z.string().uuid(), + certificateId: z.string().uuid(), + syncStatus: z.string().default("pending").nullable().optional(), + lastSyncMessage: z.string().nullable().optional(), + lastSyncedAt: z.date().nullable().optional(), + createdAt: z.date(), + updatedAt: z.date(), + externalIdentifier: z.string().nullable().optional() +}); + +export type TCertificateSyncs = z.infer; +export type TCertificateSyncsInsert = Omit, TImmutableDBKeys>; +export type TCertificateSyncsUpdate = Partial, TImmutableDBKeys>>; diff --git a/backend/src/db/schemas/index.ts b/backend/src/db/schemas/index.ts index 4f0f221ff..fba195746 100644 --- a/backend/src/db/schemas/index.ts +++ b/backend/src/db/schemas/index.ts @@ -17,6 +17,7 @@ export * from "./certificate-authority-crl"; export * from "./certificate-authority-secret"; export * from "./certificate-bodies"; export * from "./certificate-secrets"; +export * from "./certificate-syncs"; export * from "./certificate-template-est-configs"; export * from "./certificate-templates"; export * from "./certificates"; diff --git a/backend/src/db/schemas/models.ts b/backend/src/db/schemas/models.ts index 86bc929b8..e10c6dcbe 100644 --- a/backend/src/db/schemas/models.ts +++ b/backend/src/db/schemas/models.ts @@ -161,6 +161,7 @@ export enum TableName { AppConnection = "app_connections", SecretSync = "secret_syncs", PkiSync = "pki_syncs", + CertificateSync = "certificate_syncs", KmipClient = "kmip_clients", KmipOrgConfig = "kmip_org_configs", KmipOrgServerCertificates = "kmip_org_server_certificates", diff --git a/backend/src/ee/routes/v1/app-connection-routers/chef-connection-router.ts b/backend/src/ee/routes/v1/app-connection-routers/chef-connection-router.ts new file mode 100644 index 000000000..2855d8b37 --- /dev/null +++ b/backend/src/ee/routes/v1/app-connection-routers/chef-connection-router.ts @@ -0,0 +1,84 @@ +import z from "zod"; + +import { + CreateChefConnectionSchema, + SanitizedChefConnectionSchema, + UpdateChefConnectionSchema +} from "@app/ee/services/app-connections/chef"; +import { readLimit } from "@app/server/config/rateLimiter"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { registerAppConnectionEndpoints } from "@app/server/routes/v1/app-connection-routers/app-connection-endpoints"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { AuthMode } from "@app/services/auth/auth-type"; + +export const registerChefConnectionRouter = async (server: FastifyZodProvider) => { + registerAppConnectionEndpoints({ + app: AppConnection.Chef, + server, + sanitizedResponseSchema: SanitizedChefConnectionSchema, + createSchema: CreateChefConnectionSchema, + updateSchema: UpdateChefConnectionSchema + }); + + server.route({ + method: "GET", + url: `/:connectionId/data-bags`, + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + connectionId: z.string().uuid() + }), + response: { + 200: z + .object({ + name: z.string() + }) + .array() + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const { connectionId } = req.params; + const dataBags = await server.services.appConnection.chef.listDataBags(connectionId, req.permission); + + return dataBags; + } + }); + + server.route({ + method: "GET", + url: `/:connectionId/data-bag-items`, + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + connectionId: z.string().uuid() + }), + querystring: z.object({ + dataBagName: z.string() + }), + response: { + 200: z + .object({ + name: z.string() + }) + .array() + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const { connectionId } = req.params; + const { dataBagName } = req.query; + const dataBagItems = await server.services.appConnection.chef.listDataBagItems( + connectionId, + dataBagName, + req.permission + ); + + return dataBagItems; + } + }); +}; diff --git a/backend/src/ee/routes/v1/pam-account-routers/pam-account-router.ts b/backend/src/ee/routes/v1/pam-account-routers/pam-account-router.ts index d2e0183ff..286e0896f 100644 --- a/backend/src/ee/routes/v1/pam-account-routers/pam-account-router.ts +++ b/backend/src/ee/routes/v1/pam-account-routers/pam-account-router.ts @@ -92,7 +92,8 @@ export const registerPamAccountRouter = async (server: FastifyZodProvider) => { gatewayClientCertificate: z.string(), gatewayClientPrivateKey: z.string(), gatewayServerCertificateChain: z.string(), - relayHost: z.string() + relayHost: z.string(), + metadata: z.record(z.string(), z.string()).optional() }) } }, diff --git a/backend/src/ee/routes/v1/pit-router.ts b/backend/src/ee/routes/v1/pit-router.ts index 14a82bce4..26909d294 100644 --- a/backend/src/ee/routes/v1/pit-router.ts +++ b/backend/src/ee/routes/v1/pit-router.ts @@ -468,7 +468,10 @@ export const registerPITRouter = async (server: FastifyZodProvider) => { .transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())) .optional(), secretComment: z.string().trim().optional().default(""), - skipMultilineEncoding: z.boolean().optional(), + skipMultilineEncoding: z + .boolean() + .nullish() + .transform((val) => (val === null ? false : val)), metadata: z.record(z.string()).optional(), secretMetadata: ResourceMetadataSchema.optional(), tagIds: z.string().array().optional() diff --git a/backend/src/ee/routes/v1/secret-sync-routers/chef-sync-router.ts b/backend/src/ee/routes/v1/secret-sync-routers/chef-sync-router.ts new file mode 100644 index 000000000..3bdd5bce6 --- /dev/null +++ b/backend/src/ee/routes/v1/secret-sync-routers/chef-sync-router.ts @@ -0,0 +1,12 @@ +import { ChefSyncSchema, CreateChefSyncSchema, UpdateChefSyncSchema } from "@app/ee/services/secret-sync/chef"; +import { registerSyncSecretsEndpoints } from "@app/server/routes/v1/secret-sync-routers/secret-sync-endpoints"; +import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; + +export const registerChefSyncRouter = async (server: FastifyZodProvider) => + registerSyncSecretsEndpoints({ + destination: SecretSync.Chef, + server, + responseSchema: ChefSyncSchema, + createSchema: CreateChefSyncSchema, + updateSchema: UpdateChefSyncSchema + }); diff --git a/backend/src/ee/services/app-connections/chef/chef-connection-enums.ts b/backend/src/ee/services/app-connections/chef/chef-connection-enums.ts new file mode 100644 index 000000000..58e59c3ba --- /dev/null +++ b/backend/src/ee/services/app-connections/chef/chef-connection-enums.ts @@ -0,0 +1,3 @@ +export enum ChefConnectionMethod { + UserKey = "user-key" +} diff --git a/backend/src/ee/services/app-connections/chef/chef-connection-fns.ts b/backend/src/ee/services/app-connections/chef/chef-connection-fns.ts new file mode 100644 index 000000000..6cef8373f --- /dev/null +++ b/backend/src/ee/services/app-connections/chef/chef-connection-fns.ts @@ -0,0 +1,288 @@ +import { AxiosError } from "axios"; +import crypto from "crypto"; + +import { request } from "@app/lib/config/request"; +import { BadRequestError } from "@app/lib/errors"; +import { removeTrailingSlash } from "@app/lib/fn"; +import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; + +import { TChefDataBagItemContent } from "../../secret-sync/chef/chef-sync-types"; +import { ChefConnectionMethod } from "./chef-connection-enums"; +import { + TChefConnection, + TChefConnectionConfig, + TChefDataBag, + TChefDataBagItem, + TGetChefDataBagItem, + TUpdateChefDataBagItem +} from "./chef-connection-types"; + +export const getChefServerUrl = async (serverUrl?: string) => { + const chefServerUrl = serverUrl ? removeTrailingSlash(serverUrl) : IntegrationUrls.CHEF_API_URL; + + await blockLocalAndPrivateIpAddresses(chefServerUrl); + + return chefServerUrl; +}; + +// Helper to ensure private key is in proper PEM format +const formatPrivateKey = (key: string): string => { + let formattedKey = key.trim(); + + // Ensure proper line breaks in PEM format (handle escaped newlines) + formattedKey = formattedKey.replace(/\\n/g, "\n"); + + // Remove any extra whitespace between lines + formattedKey = formattedKey.replace(/\n\s+/g, "\n"); + + // If key doesn't have headers, add PKCS#1 RSA headers + if (!formattedKey.includes("BEGIN")) { + formattedKey = `-----BEGIN RSA PRIVATE KEY-----\n${formattedKey}\n-----END RSA PRIVATE KEY-----`; + } + + // Ensure the key has proper line breaks after headers and before footers + formattedKey = formattedKey.replace(/(-----BEGIN[^-]+-----)\s*/g, "$1\n").replace(/\s*(-----END[^-]+-----)/g, "\n$1"); + + // Remove any duplicate newlines + formattedKey = formattedKey.replace(/\n{3,}/g, "\n\n"); + + return formattedKey; +}; + +const getChefAuthHeaders = ( + method: string, + path: string, + body: string, + userId: string, + privateKey: string, + apiVersion: "1.0" | "1.3" = "1.3" +) => { + const timestamp = new Date().toISOString().replace(/\.\d{3}Z$/, "Z"); // Remove milliseconds from timestamp + + // Calculate content hash based on version + let contentHash: string; + if (apiVersion === "1.3") { + contentHash = crypto.createHash("sha256").update(body).digest("base64"); + } else { + contentHash = crypto.createHash("sha1").update(body).digest("base64"); + } + + // Build canonical request based on version + let canonicalRequest: string; + if (apiVersion === "1.3") { + canonicalRequest = [ + `Method:${method}`, + `Path:${path}`, + `X-Ops-Content-Hash:${contentHash}`, + "X-Ops-Sign:version=1.3", + `X-Ops-Timestamp:${timestamp}`, + `X-Ops-UserId:${userId}`, + "X-Ops-Server-API-Version:1" + ].join("\n"); + } else { + const hashedPath = crypto.createHash("sha1").update(path).digest("base64"); + canonicalRequest = [ + `Method:${method}`, + `Hashed Path:${hashedPath}`, + `X-Ops-Content-Hash:${contentHash}`, + `X-Ops-Timestamp:${timestamp}`, + `X-Ops-UserId:${userId}` + ].join("\n"); + } + + // Format the private key properly + const formattedKey = formatPrivateKey(privateKey); + + // Sign the canonical request + const sign = crypto.createSign(apiVersion === "1.3" ? "RSA-SHA256" : "RSA-SHA1"); + sign.update(canonicalRequest); + const signature = sign.sign(formattedKey, "base64"); + + // Split signature into 60-character chunks + const authHeaders: Record = {}; + const signatureLines = signature.match(/.{1,60}/g) || []; + signatureLines.forEach((line, index) => { + authHeaders[`X-Ops-Authorization-${index + 1}`] = line; + }); + + return { + Accept: "application/json", + "Content-Type": "application/json", + "X-Chef-Version": "14.0.0", + "X-Ops-Timestamp": timestamp, + "X-Ops-UserId": userId, + "X-Ops-Sign": apiVersion === "1.3" ? "version=1.3" : "algorithm=sha1;version=1.0", + "X-Ops-Content-Hash": contentHash, + ...(apiVersion === "1.3" && { "X-Ops-Server-API-Version": "1" }), + ...authHeaders + }; +}; + +export const getChefConnectionListItem = () => { + return { + name: "Chef" as const, + app: AppConnection.Chef as const, + methods: Object.values(ChefConnectionMethod) as [ChefConnectionMethod.UserKey] + }; +}; + +export const validateChefConnectionCredentials = async (config: TChefConnectionConfig) => { + const { credentials: inputCredentials } = config; + + try { + const path = `/organizations/${inputCredentials.orgName}/users/${inputCredentials.userName}`; + + const hostServerUrl = await getChefServerUrl(inputCredentials.serverUrl); + + const headers = getChefAuthHeaders("GET", path, "", inputCredentials.userName, inputCredentials.privateKey); + + await request.get(`${hostServerUrl}${path}`, { + headers + }); + } catch (error: unknown) { + if (error instanceof AxiosError) { + throw new BadRequestError({ + message: `Failed to validate Chef credentials: ${error.message || "Unknown error"}` + }); + } + throw new BadRequestError({ + message: "Unable to validate Chef connection: verify credentials" + }); + } + + return inputCredentials; +}; + +export const listChefDataBags = async (appConnection: TChefConnection): Promise => { + const { + credentials: { serverUrl, userName, privateKey, orgName } + } = appConnection; + + try { + const path = `/organizations/${orgName}/data`; + const body = ""; + + const hostServerUrl = await getChefServerUrl(serverUrl); + + const headers = getChefAuthHeaders("GET", path, body, userName, privateKey); + + const res = await request.get>(`${hostServerUrl}${path}`, { + headers + }); + + return Object.keys(res.data).map((name) => ({ + name + })); + } catch (error) { + if (error instanceof AxiosError) { + throw new BadRequestError({ + message: `Failed to list Chef data bags: ${error.message || "Unknown error"}` + }); + } + throw new BadRequestError({ + message: "Unable to list Chef data bags" + }); + } +}; + +export const listChefDataBagItems = async ( + appConnection: TChefConnection, + dataBagName: string +): Promise => { + const { + credentials: { serverUrl, userName, privateKey, orgName } + } = appConnection; + + try { + const path = `/organizations/${orgName}/data/${dataBagName}`; + const body = ""; + + const hostServerUrl = await getChefServerUrl(serverUrl); + + const headers = getChefAuthHeaders("GET", path, body, userName, privateKey); + + const res = await request.get>(`${hostServerUrl}${path}`, { + headers + }); + + return Object.keys(res.data).map((name) => ({ + name + })); + } catch (error) { + if (error instanceof AxiosError) { + throw new BadRequestError({ + message: `Failed to list Chef data bag items: ${error.message || "Unknown error"}` + }); + } + throw new BadRequestError({ + message: "Unable to list Chef data bag items" + }); + } +}; + +export const getChefDataBagItem = async ({ + serverUrl, + userName, + privateKey, + orgName, + dataBagName, + dataBagItemName +}: TGetChefDataBagItem): Promise => { + try { + const path = `/organizations/${orgName}/data/${dataBagName}/${dataBagItemName}`; + const body = ""; + + const hostServerUrl = await getChefServerUrl(serverUrl); + + const headers = getChefAuthHeaders("GET", path, body, userName, privateKey); + + const res = await request.get(`${hostServerUrl}${path}`, { + headers + }); + + return res.data; + } catch (error) { + if (error instanceof AxiosError) { + throw new BadRequestError({ + message: `Failed to get Chef data bag item: ${error.message || "Unknown error"}` + }); + } + throw new BadRequestError({ + message: "Unable to get Chef data bag item" + }); + } +}; + +export const updateChefDataBagItem = async ({ + serverUrl, + userName, + privateKey, + orgName, + dataBagName, + dataBagItemName, + data +}: TUpdateChefDataBagItem): Promise => { + try { + const path = `/organizations/${orgName}/data/${dataBagName}/${dataBagItemName}`; + const body = JSON.stringify(data); + + const hostServerUrl = await getChefServerUrl(serverUrl); + + const headers = getChefAuthHeaders("PUT", path, body, userName, privateKey); + + await request.put(`${hostServerUrl}${path}`, data, { + headers + }); + } catch (error) { + if (error instanceof AxiosError) { + throw new BadRequestError({ + message: `Failed to update Chef data bag item: ${error.message || "Unknown error"}` + }); + } + throw new BadRequestError({ + message: "Unable to update Chef data bag item" + }); + } +}; diff --git a/backend/src/ee/services/app-connections/chef/chef-connection-schemas.ts b/backend/src/ee/services/app-connections/chef/chef-connection-schemas.ts new file mode 100644 index 000000000..e5a3687a2 --- /dev/null +++ b/backend/src/ee/services/app-connections/chef/chef-connection-schemas.ts @@ -0,0 +1,77 @@ +import z from "zod"; + +import { AppConnections } from "@app/lib/api-docs"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { + BaseAppConnectionSchema, + GenericCreateAppConnectionFieldsSchema, + GenericUpdateAppConnectionFieldsSchema +} from "@app/services/app-connection/app-connection-schemas"; + +import { ChefConnectionMethod } from "./chef-connection-enums"; + +export const ChefConnectionUserKeyCredentialsSchema = z.object({ + serverUrl: z + .string() + .trim() + .url("Valid Chef Server URL required") + .optional() + .describe(AppConnections.CREDENTIALS.CHEF.serverUrl), + orgName: z + .string() + .trim() + .min(1, "Organization name required") + .max(256, "Organization name cannot exceed 256 characters") + .describe(AppConnections.CREDENTIALS.CHEF.orgName), + userName: z + .string() + .trim() + .min(1, "User name required") + .max(256, "User name cannot exceed 256 characters") + .describe(AppConnections.CREDENTIALS.CHEF.userName), + privateKey: z + .string() + .trim() + .min(1, "Private key required") + .max(16384, "Private key cannot exceed 16384 characters") + .describe(AppConnections.CREDENTIALS.CHEF.privateKey) +}); + +const BaseChefConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.Chef) }); + +export const ChefConnectionSchema = BaseChefConnectionSchema.extend({ + method: z.literal(ChefConnectionMethod.UserKey), + credentials: ChefConnectionUserKeyCredentialsSchema +}); + +export const SanitizedChefConnectionSchema = z.discriminatedUnion("method", [ + BaseChefConnectionSchema.extend({ + method: z.literal(ChefConnectionMethod.UserKey), + credentials: ChefConnectionUserKeyCredentialsSchema.pick({ serverUrl: true, orgName: true, userName: true }) + }) +]); + +export const ValidateChefConnectionCredentialsSchema = z.discriminatedUnion("method", [ + z.object({ + method: z.literal(ChefConnectionMethod.UserKey).describe(AppConnections.CREATE(AppConnection.Chef).method), + credentials: ChefConnectionUserKeyCredentialsSchema.describe(AppConnections.CREATE(AppConnection.Chef).credentials) + }) +]); + +export const CreateChefConnectionSchema = ValidateChefConnectionCredentialsSchema.and( + GenericCreateAppConnectionFieldsSchema(AppConnection.Chef) +); + +export const UpdateChefConnectionSchema = z + .object({ + credentials: ChefConnectionUserKeyCredentialsSchema.optional().describe( + AppConnections.UPDATE(AppConnection.Chef).credentials + ) + }) + .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Chef)); + +export const ChefConnectionListItemSchema = z.object({ + name: z.literal("Chef"), + app: z.literal(AppConnection.Chef), + methods: z.nativeEnum(ChefConnectionMethod).array() +}); diff --git a/backend/src/ee/services/app-connections/chef/chef-connection-service.ts b/backend/src/ee/services/app-connections/chef/chef-connection-service.ts new file mode 100644 index 000000000..242b1fbf9 --- /dev/null +++ b/backend/src/ee/services/app-connections/chef/chef-connection-service.ts @@ -0,0 +1,57 @@ +import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors"; +import { OrgServiceActor } from "@app/lib/types"; + +import { AppConnection } from "../../../../services/app-connection/app-connection-enums"; +import { TLicenseServiceFactory } from "../../license/license-service"; +import { listChefDataBagItems, listChefDataBags } from "./chef-connection-fns"; +import { TChefConnection } from "./chef-connection-types"; + +type TGetAppConnectionFunc = ( + app: AppConnection, + connectionId: string, + actor: OrgServiceActor +) => Promise; + +// Enterprise check +export const checkPlan = async (licenseService: Pick, orgId: string) => { + const plan = await licenseService.getPlan(orgId); + if (!plan.enterpriseAppConnections) + throw new BadRequestError({ + message: + "Failed to use app connection due to plan restriction. Upgrade plan to access enterprise app connections." + }); +}; + +export const chefConnectionService = ( + getAppConnection: TGetAppConnectionFunc, + licenseService: Pick +) => { + const listDataBags = async (appConnectionId: string, actor: OrgServiceActor) => { + await checkPlan(licenseService, actor.orgId); + + const appConnection = await getAppConnection(AppConnection.Chef, appConnectionId, actor); + + if (!appConnection) { + throw new ForbiddenRequestError({ message: "App connection not found" }); + } + + return listChefDataBags(appConnection); + }; + + const listDataBagItems = async (appConnectionId: string, dataBagName: string, actor: OrgServiceActor) => { + await checkPlan(licenseService, actor.orgId); + + const appConnection = await getAppConnection(AppConnection.Chef, appConnectionId, actor); + + if (!appConnection) { + throw new ForbiddenRequestError({ message: "App connection not found" }); + } + + return listChefDataBagItems(appConnection, dataBagName); + }; + + return { + listDataBags, + listDataBagItems + }; +}; diff --git a/backend/src/ee/services/app-connections/chef/chef-connection-types.ts b/backend/src/ee/services/app-connections/chef/chef-connection-types.ts new file mode 100644 index 000000000..5673614e9 --- /dev/null +++ b/backend/src/ee/services/app-connections/chef/chef-connection-types.ts @@ -0,0 +1,50 @@ +import z from "zod"; + +import { TChefDataBagItemContent } from "@app/ee/services/secret-sync/chef"; +import { DiscriminativePick } from "@app/lib/types"; + +import { AppConnection } from "../../../../services/app-connection/app-connection-enums"; +import { + ChefConnectionSchema, + CreateChefConnectionSchema, + ValidateChefConnectionCredentialsSchema +} from "./chef-connection-schemas"; + +export type TChefConnection = z.infer; + +export type TChefConnectionInput = z.infer & { + app: AppConnection.Chef; +}; + +export type TValidateChefConnectionCredentialsSchema = typeof ValidateChefConnectionCredentialsSchema; + +export type TChefConnectionConfig = DiscriminativePick & { + orgName: string; +}; + +export type TChefDataBag = { + name: string; +}; + +export type TChefDataBagItem = { + name: string; +}; + +export type TGetChefDataBagItem = { + serverUrl?: string; + userName: string; + privateKey: string; + orgName: string; + dataBagName: string; + dataBagItemName: string; +}; + +export type TUpdateChefDataBagItem = { + serverUrl?: string; + userName: string; + privateKey: string; + orgName: string; + dataBagName: string; + dataBagItemName: string; + data: TChefDataBagItemContent; +}; diff --git a/backend/src/ee/services/app-connections/chef/index.ts b/backend/src/ee/services/app-connections/chef/index.ts new file mode 100644 index 000000000..e02479d0e --- /dev/null +++ b/backend/src/ee/services/app-connections/chef/index.ts @@ -0,0 +1,4 @@ +export * from "./chef-connection-enums"; +export * from "./chef-connection-fns"; +export * from "./chef-connection-schemas"; +export * from "./chef-connection-types"; diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index b58503110..c6ac6ff3b 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -426,6 +426,7 @@ export enum EventType { SECRET_SYNC_REMOVE_SECRETS = "secret-sync-remove-secrets", GET_PKI_SYNCS = "get-pki-syncs", GET_PKI_SYNC = "get-pki-sync", + GET_PKI_SYNC_CERTIFICATES = "get-pki-sync-certificates", CREATE_PKI_SYNC = "create-pki-sync", UPDATE_PKI_SYNC = "update-pki-sync", DELETE_PKI_SYNC = "delete-pki-sync", @@ -3161,6 +3162,16 @@ interface GetPkiSyncEvent { }; } +interface GetPkiSyncCertificatesEvent { + type: EventType.GET_PKI_SYNC_CERTIFICATES; + metadata: { + syncId: string; + count: number; + certificateIds: string[]; + destination: string; + }; +} + interface CreatePkiSyncEvent { type: EventType.CREATE_PKI_SYNC; metadata: { @@ -4329,6 +4340,7 @@ export type Event = | SecretSyncRemoveSecretsEvent | GetPkiSyncsEvent | GetPkiSyncEvent + | GetPkiSyncCertificatesEvent | CreatePkiSyncEvent | UpdatePkiSyncEvent | DeletePkiSyncEvent diff --git a/backend/src/ee/services/gateway-v2/gateway-v2-service.ts b/backend/src/ee/services/gateway-v2/gateway-v2-service.ts index fd4954a00..22daa1cd1 100644 --- a/backend/src/ee/services/gateway-v2/gateway-v2-service.ts +++ b/backend/src/ee/services/gateway-v2/gateway-v2-service.ts @@ -3,7 +3,7 @@ import net from "node:net"; import { ForbiddenError } from "@casl/ability"; import * as x509 from "@peculiar/x509"; -import { OrganizationActionScope, OrgMembershipRole, TRelays } from "@app/db/schemas"; +import { OrganizationActionScope, OrgMembershipRole, OrgMembershipStatus, TRelays } from "@app/db/schemas"; import { PgSqlLock } from "@app/keystore/keystore"; import { crypto } from "@app/lib/crypto"; import { DatabaseErrorCode } from "@app/lib/error-codes"; @@ -25,7 +25,7 @@ import { KmsDataKey } from "@app/services/kms/kms-types"; import { TNotificationServiceFactory } from "@app/services/notification/notification-service"; import { NotificationType } from "@app/services/notification/notification-types"; import { TOrgDALFactory } from "@app/services/org/org-dal"; -import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service"; +import { TSmtpService } from "@app/services/smtp/smtp-service"; import { TLicenseServiceFactory } from "../license/license-service"; import { PamResource } from "../pam-resource/pam-resource-enums"; @@ -61,8 +61,7 @@ export const gatewayV2ServiceFactory = ({ relayDAL, permissionService, orgDAL, - notificationService, - smtpService + notificationService }: TGatewayV2ServiceFactoryDep) => { const $validateIdentityAccessToGateway = async (orgId: string, actorId: string, actorAuthMethod: ActorAuthMethod) => { const orgLicensePlan = await licenseService.getPlan(orgId); @@ -910,7 +909,9 @@ export const gatewayV2ServiceFactory = ({ for await (const [orgId, gateways] of Object.entries(gatewaysByOrg)) { try { - const admins = await orgDAL.findOrgMembersByRole(orgId, OrgMembershipRole.Admin); + const admins = (await orgDAL.findOrgMembersByRole(orgId, OrgMembershipRole.Admin)).filter( + (admin) => admin.status !== OrgMembershipStatus.Invited + ); if (admins.length === 0) { logger.warn({ orgId }, "Organization has no admins to notify about unhealthy gateway."); // eslint-disable-next-line no-continue @@ -931,15 +932,17 @@ export const gatewayV2ServiceFactory = ({ })) ); - await smtpService.sendMail({ - recipients: admins.map((admin) => admin.user.email).filter((v): v is string => !!v), - subjectLine: "Gateway Health Alert", - substitutions: { - type: "gateway", - names: gatewayNames - }, - template: SmtpTemplates.HealthAlert - }); + // Temporarily disabled email notifications due to excessive noise. Will be revised later + // + // await smtpService.sendMail({ + // recipients: admins.map((admin) => admin.user.email).filter((v): v is string => !!v), + // subjectLine: "Gateway Health Alert", + // substitutions: { + // type: "gateway", + // names: gatewayNames + // }, + // template: SmtpTemplates.HealthAlert + // }); await Promise.all(gateways.map((gw) => gatewayV2DAL.updateById(gw.id, { healthAlertedAt: new Date() }))); } catch (error) { diff --git a/backend/src/ee/services/pam-account/pam-account-service.ts b/backend/src/ee/services/pam-account/pam-account-service.ts index 00b84943f..2f66d28d7 100644 --- a/backend/src/ee/services/pam-account/pam-account-service.ts +++ b/backend/src/ee/services/pam-account/pam-account-service.ts @@ -480,6 +480,36 @@ export const pamAccountServiceFactory = ({ throw new NotFoundError({ message: `Gateway connection details for gateway '${gatewayId}' not found.` }); } + let metadata; + + switch (resourceType) { + case PamResource.Postgres: + case PamResource.MySQL: + { + const connectionCredentials = await decryptResourceConnectionDetails({ + encryptedConnectionDetails: resource.encryptedConnectionDetails, + kmsService, + projectId: account.projectId + }); + + const credentials = await decryptAccountCredentials({ + encryptedCredentials: account.encryptedCredentials, + kmsService, + projectId: account.projectId + }); + + metadata = { + username: credentials.username, + database: connectionCredentials.database, + accountName: account.name, + accountPath + }; + } + break; + default: + break; + } + return { sessionId: session.id, resourceType, @@ -491,7 +521,8 @@ export const pamAccountServiceFactory = ({ gatewayServerCertificateChain: gatewayConnectionDetails.gateway.serverCertificateChain, relayHost: gatewayConnectionDetails.relayHost, projectId: account.projectId, - account + account, + metadata }; }; diff --git a/backend/src/ee/services/pam-resource/shared/sql/sql-resource-factory.ts b/backend/src/ee/services/pam-resource/shared/sql/sql-resource-factory.ts index 99ce2d25f..413990c5d 100644 --- a/backend/src/ee/services/pam-resource/shared/sql/sql-resource-factory.ts +++ b/backend/src/ee/services/pam-resource/shared/sql/sql-resource-factory.ts @@ -1,6 +1,5 @@ import knex from "knex"; import mysql, { Connection } from "mysql2/promise"; -import * as pg from "pg"; import tls, { PeerCertificate } from "tls"; import { verifyHostInputValidity } from "@app/ee/services/dynamic-secret/dynamic-secret-fns"; @@ -97,7 +96,7 @@ const makeSqlConnection = ( try { await client.raw(SIMPLE_QUERY); } catch (error) { - if (error instanceof pg.DatabaseError) { + if (error instanceof Error) { // Hacky way to know if we successfully hit the database. // TODO: potentially two approaches to solve the problem. // 1. change the work flow, add account first then resource diff --git a/backend/src/ee/services/permission/permission-dal.ts b/backend/src/ee/services/permission/permission-dal.ts index 95480a54a..88b52be22 100644 --- a/backend/src/ee/services/permission/permission-dal.ts +++ b/backend/src/ee/services/permission/permission-dal.ts @@ -201,11 +201,11 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => { .leftJoin(TableName.IdentityMetadata, (queryBuilder) => { if (actorType === ActorType.USER) { void queryBuilder - .on(`${TableName.Membership}.actorUserId`, `${TableName.IdentityMetadata}.userId`) + .on(`${TableName.IdentityMetadata}.userId`, db.raw("?", [actorId])) .andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`); } else if (actorType === ActorType.IDENTITY) { void queryBuilder - .on(`${TableName.Membership}.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`) + .on(`${TableName.IdentityMetadata}.identityId`, db.raw("?", [actorId])) .andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`); } }) @@ -488,7 +488,7 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => { }) .leftJoin(TableName.IdentityMetadata, (queryBuilder) => { void queryBuilder - .on(`${TableName.Membership}.actorUserId`, `${TableName.IdentityMetadata}.userId`) + .on(`${TableName.Users}.id`, `${TableName.IdentityMetadata}.userId`) .andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`); }) .where(`${TableName.Membership}.scopeOrgId`, orgId) diff --git a/backend/src/ee/services/relay/relay-service.ts b/backend/src/ee/services/relay/relay-service.ts index b2eb932ed..ae7758e67 100644 --- a/backend/src/ee/services/relay/relay-service.ts +++ b/backend/src/ee/services/relay/relay-service.ts @@ -3,7 +3,7 @@ import { isIP } from "node:net"; import { ForbiddenError } from "@casl/ability"; import * as x509 from "@peculiar/x509"; -import { OrganizationActionScope, OrgMembershipRole, TRelays } from "@app/db/schemas"; +import { OrganizationActionScope, OrgMembershipRole, OrgMembershipStatus, TRelays } from "@app/db/schemas"; import { PgSqlLock } from "@app/keystore/keystore"; import { crypto } from "@app/lib/crypto"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; @@ -996,7 +996,9 @@ export const relayServiceFactory = ({ ); if (existingRelay && (existingRelay.host !== host || existingRelay.name !== name)) { - return relayDAL.updateById(existingRelay.id, { host, name }, tx); + throw new BadRequestError({ + message: `Machine identity already has an existing relay with the name "${existingRelay.name}" and host "${existingRelay.host}". Delete the existing relay or use a different machine identity.` + }); } if (!existingRelay) { @@ -1248,7 +1250,9 @@ export const relayServiceFactory = ({ }); } } else { - const admins = await orgDAL.findOrgMembersByRole(orgId, OrgMembershipRole.Admin); + const admins = (await orgDAL.findOrgMembersByRole(orgId, OrgMembershipRole.Admin)).filter( + (admin) => admin.status !== OrgMembershipStatus.Invited + ); if (admins.length === 0) { // eslint-disable-next-line no-continue continue; @@ -1268,15 +1272,17 @@ export const relayServiceFactory = ({ })) ); - await smtpService.sendMail({ - recipients: admins.map((admin) => admin.user.email).filter((v): v is string => !!v), - subjectLine: "Relay Health Alert", - substitutions: { - type: "relay", - names: relayNames - }, - template: SmtpTemplates.HealthAlert - }); + // Temporarily disabled email notifications due to excessive noise. Will be revised later + // + // await smtpService.sendMail({ + // recipients: admins.map((admin) => admin.user.email).filter((v): v is string => !!v), + // subjectLine: "Relay Health Alert", + // substitutions: { + // type: "relay", + // names: relayNames + // }, + // template: SmtpTemplates.HealthAlert + // }); } await Promise.all(relays.map((r) => relayDAL.updateById(r.id, { healthAlertedAt: new Date() }))); 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 7597dcfd4..db300720f 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 @@ -670,6 +670,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { .select( db.ref("projectId").withSchema(TableName.Environment), db.ref("slug").withSchema(TableName.Environment).as("environment"), + db.ref("name").withSchema(TableName.Environment).as("environmentName"), db.ref("id").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerId"), db.ref("reviewerUserId").withSchema(TableName.SecretApprovalRequestReviewer), db.ref("status").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerStatus"), @@ -699,30 +700,30 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { ) .as("inner"); - const countQuery = (await (tx || db) - .select(db.raw("count(*) OVER() as total_count")) - .from(innerQuery.clone().distinctOn(`${TableName.SecretApprovalRequest}.id`))) as Array<{ - total_count: number; - }>; - const query = (tx || db).select("*").from(innerQuery).orderBy("createdAt", "desc") as typeof innerQuery; if (search) { void query.where((qb) => { void qb .whereRaw(`CONCAT_WS(' ', ??, ??) ilike ?`, [ - db.ref("firstName").withSchema("committerUser"), - db.ref("lastName").withSchema("committerUser"), + db.ref("committerUserFirstName"), + db.ref("committerUserLastName"), `%${search}%` ]) - .orWhereRaw(`?? ilike ?`, [db.ref("username").withSchema("committerUser"), `%${search}%`]) - .orWhereRaw(`?? ilike ?`, [db.ref("email").withSchema("committerUser"), `%${search}%`]) - .orWhereILike(`${TableName.Environment}.name`, `%${search}%`) - .orWhereILike(`${TableName.Environment}.slug`, `%${search}%`) - .orWhereILike(`${TableName.SecretApprovalPolicy}.secretPath`, `%${search}%`); + .orWhereRaw(`?? ilike ?`, [db.ref("committerUserUsername"), `%${search}%`]) + .orWhereRaw(`?? ilike ?`, [db.ref("committerUserEmail"), `%${search}%`]) + .orWhereILike(`environmentName`, `%${search}%`) + .orWhereILike(`environment`, `%${search}%`) + .orWhereILike(`policySecretPath`, `%${search}%`); }); } + const countQuery = (await (tx || db) + .select(db.raw("count(*) OVER() as total_count")) + .from(query.clone().as("outer"))) as Array<{ + total_count: number; + }>; + const rankOffset = offset + 1; const docs = await (tx || db) .with("w", query) diff --git a/backend/src/ee/services/secret-sync/chef/chef-sync-constants.ts b/backend/src/ee/services/secret-sync/chef/chef-sync-constants.ts new file mode 100644 index 000000000..8bbf0e12a --- /dev/null +++ b/backend/src/ee/services/secret-sync/chef/chef-sync-constants.ts @@ -0,0 +1,11 @@ +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; +import { TSecretSyncListItem } from "@app/services/secret-sync/secret-sync-types"; + +export const CHEF_SYNC_LIST_OPTION: TSecretSyncListItem = { + name: "Chef", + destination: SecretSync.Chef, + connection: AppConnection.Chef, + canImportSecrets: true, + enterprise: true +}; diff --git a/backend/src/ee/services/secret-sync/chef/chef-sync-fns.ts b/backend/src/ee/services/secret-sync/chef/chef-sync-fns.ts new file mode 100644 index 000000000..65a4fca9b --- /dev/null +++ b/backend/src/ee/services/secret-sync/chef/chef-sync-fns.ts @@ -0,0 +1,151 @@ +import { getChefDataBagItem, updateChefDataBagItem } from "@app/ee/services/app-connections/chef"; +import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns"; +import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; + +import { + ChefSecret, + TChefDataBagItemContent, + TChefSecret, + TChefSecrets, + TChefSyncWithCredentials, + TGetChefSecrets +} from "./chef-sync-types"; + +const getChefSecretsRaw = async ({ + serverUrl, + userName, + privateKey, + orgName, + dataBagName, + dataBagItemName +}: TGetChefSecrets): Promise => { + const dataBagItem = await getChefDataBagItem({ + serverUrl, + userName, + privateKey, + orgName, + dataBagName, + dataBagItemName + }); + + // Ensure the data bag item has an id field + if (!dataBagItem.id) { + dataBagItem.id = dataBagItemName; + } + + return dataBagItem; +}; + +const getChefSecrets = async (secretSync: TChefSyncWithCredentials): Promise => { + const { + connection, + destinationConfig: { dataBagName, dataBagItemName } + } = secretSync; + + const { serverUrl, userName, privateKey, orgName } = connection.credentials; + + const dataBagItem = await getChefSecretsRaw({ + serverUrl, + orgName, + userName, + privateKey, + dataBagName, + dataBagItemName + }); + + const { id, ...existingSecrets } = dataBagItem; + + // Convert data bag item to key-value pairs + const secrets: ChefSecret[] = []; + Object.entries(existingSecrets).forEach(([key, value]) => { + if (key !== "id" && value !== null && value !== undefined) { + secrets.push({ key, value: String(value) }); + } + }); + + return { id, secrets }; +}; + +const updateChefSecrets = async ( + secretSync: TChefSyncWithCredentials, + id: string, + secrets: Record +) => { + const { + connection, + destinationConfig: { dataBagName, dataBagItemName } + } = secretSync; + + const { serverUrl, userName, privateKey, orgName } = connection.credentials; + + // Chef data bag items must have an 'id' field + const dataBagItemContent: TChefDataBagItemContent = { + id, + ...secrets + }; + + await updateChefDataBagItem({ + serverUrl, + orgName, + userName, + privateKey, + dataBagName, + dataBagItemName, + data: dataBagItemContent + }); +}; + +export const ChefSyncFns = { + async syncSecrets(secretSync: TChefSyncWithCredentials, secretMap: TSecretMap) { + const { + environment, + syncOptions: { disableSecretDeletion, keySchema } + } = secretSync; + + const { id, secrets } = await getChefSecrets(secretSync); + + // Create a map of the existing secrets + const updatedSecretsMap = new Map(secrets.map((secret) => [secret.key, secret.value])); + + // Add/update new secrets + for (const [key, { value }] of Object.entries(secretMap)) { + updatedSecretsMap.set(key, value); + } + + // Delete secrets if not disabled + if (!disableSecretDeletion) { + secrets.forEach((secret) => { + if (!matchesSchema(secret.key, environment?.slug || "", keySchema)) return; + + if (!secretMap[secret.key]) { + updatedSecretsMap.delete(secret.key); + } + }); + } + + // Convert map to object for Chef API + const updatedSecrets = Object.fromEntries(updatedSecretsMap.entries()); + + await updateChefSecrets(secretSync, id, updatedSecrets); + }, + + async getSecrets(secretSync: TChefSyncWithCredentials): Promise { + const { secrets } = await getChefSecrets(secretSync); + + return Object.fromEntries(secrets.map((secret) => [secret.key, { value: secret.value }])); + }, + + async removeSecrets(secretSync: TChefSyncWithCredentials, secretMap: TSecretMap) { + const { id, secrets: existingSecrets } = await getChefSecrets(secretSync); + + const newSecrets = existingSecrets.filter((secret) => !Object.hasOwn(secretMap, secret.key)); + + if (newSecrets.length === existingSecrets.length) { + return; + } + + const updatedSecrets = Object.fromEntries(newSecrets.map((secret) => [secret.key, secret.value])); + + await updateChefSecrets(secretSync, id, updatedSecrets); + } +}; diff --git a/backend/src/ee/services/secret-sync/chef/chef-sync-schemas.ts b/backend/src/ee/services/secret-sync/chef/chef-sync-schemas.ts new file mode 100644 index 000000000..9702f97d3 --- /dev/null +++ b/backend/src/ee/services/secret-sync/chef/chef-sync-schemas.ts @@ -0,0 +1,47 @@ +import { z } from "zod"; + +import { SecretSyncs } from "@app/lib/api-docs"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; +import { + BaseSecretSyncSchema, + GenericCreateSecretSyncFieldsSchema, + GenericUpdateSecretSyncFieldsSchema +} from "@app/services/secret-sync/secret-sync-schemas"; +import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; + +const ChefSyncDestinationConfigSchema = z.object({ + dataBagName: z + .string() + .min(1, "Data Bag Name is required") + .max(256, "Data Bag Name cannot exceed 256 characters") + .describe(SecretSyncs.DESTINATION_CONFIG.CHEF.dataBagName), + dataBagItemName: z + .string() + .min(1, "Data Bag Item Name is required") + .max(256, "Data Bag Item Name cannot exceed 256 characters") + .describe(SecretSyncs.DESTINATION_CONFIG.CHEF.dataBagItemName) +}); + +const ChefSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; + +export const ChefSyncSchema = BaseSecretSyncSchema(SecretSync.Chef, ChefSyncOptionsConfig).extend({ + destination: z.literal(SecretSync.Chef), + destinationConfig: ChefSyncDestinationConfigSchema +}); + +export const CreateChefSyncSchema = GenericCreateSecretSyncFieldsSchema(SecretSync.Chef, ChefSyncOptionsConfig).extend({ + destinationConfig: ChefSyncDestinationConfigSchema +}); + +export const UpdateChefSyncSchema = GenericUpdateSecretSyncFieldsSchema(SecretSync.Chef, ChefSyncOptionsConfig).extend({ + destinationConfig: ChefSyncDestinationConfigSchema.optional() +}); + +export const ChefSyncListItemSchema = z.object({ + name: z.literal("Chef"), + connection: z.literal(AppConnection.Chef), + destination: z.literal(SecretSync.Chef), + canImportSecrets: z.literal(true), + enterprise: z.boolean() +}); diff --git a/backend/src/ee/services/secret-sync/chef/chef-sync-types.ts b/backend/src/ee/services/secret-sync/chef/chef-sync-types.ts new file mode 100644 index 000000000..0f70e7e1a --- /dev/null +++ b/backend/src/ee/services/secret-sync/chef/chef-sync-types.ts @@ -0,0 +1,41 @@ +import z from "zod"; + +import { TChefConnection } from "@app/ee/services/app-connections/chef"; + +import { ChefSyncListItemSchema, ChefSyncSchema, CreateChefSyncSchema } from "./chef-sync-schemas"; + +export type TChefSyncListItem = z.infer; + +export type TChefSync = z.infer; + +export type TChefSyncInput = z.infer; + +export type TChefSyncWithCredentials = TChefSync & { + connection: TChefConnection; +}; + +export type TGetChefSecrets = { + serverUrl?: string; + userName: string; + privateKey: string; + orgName: string; + dataBagName: string; + dataBagItemName: string; +}; + +export type TChefSecret = string | number | boolean | null; + +export type TChefDataBagItemContent = { + id: string; + [key: string]: TChefSecret; +}; + +export type TChefSecrets = { + id: string; + secrets: ChefSecret[]; +}; + +export type ChefSecret = { + key: string; + value: string; +}; diff --git a/backend/src/ee/services/secret-sync/chef/index.ts b/backend/src/ee/services/secret-sync/chef/index.ts new file mode 100644 index 000000000..c8599c867 --- /dev/null +++ b/backend/src/ee/services/secret-sync/chef/index.ts @@ -0,0 +1,4 @@ +export * from "./chef-sync-constants"; +export * from "./chef-sync-fns"; +export * from "./chef-sync-schemas"; +export * from "./chef-sync-types"; diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 6b032c6f0..0cb606cbf 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -2379,6 +2379,12 @@ export const AppConnections = { }, LARAVEL_FORGE: { apiToken: "The API token used to authenticate with Laravel Forge." + }, + CHEF: { + serverUrl: "The URL of the Chef server to connect to.", + orgName: "The short name of the Chef organization to connect to.", + userName: "The username used to access Chef.", + privateKey: "The private key used to access Chef." } } }; @@ -2624,6 +2630,10 @@ export const SecretSyncs = { siteId: "The ID of the Netlify site to sync secrets to.", context: "The Netlify context to sync secrets to." }, + CHEF: { + dataBagName: "The name of the Chef data bag to sync secrets to.", + dataBagItemName: "The name of the Chef data bag item to sync secrets to." + }, NORTHFLANK: { projectId: "The ID of the Northflank project to sync secrets to.", projectName: "The name of the Northflank project to sync secrets to.", diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index cf268b24e..6bd37e991 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -172,6 +172,7 @@ import { internalCertificateAuthorityServiceFactory } from "@app/services/certif import { certificateEstV3ServiceFactory } from "@app/services/certificate-est-v3/certificate-est-v3-service"; import { certificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal"; import { certificateProfileServiceFactory } from "@app/services/certificate-profile/certificate-profile-service"; +import { certificateSyncDALFactory } from "@app/services/certificate-sync/certificate-sync-dal"; import { certificateTemplateDALFactory } from "@app/services/certificate-template/certificate-template-dal"; import { certificateTemplateEstConfigDALFactory } from "@app/services/certificate-template/certificate-template-est-config-dal"; import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service"; @@ -1064,6 +1065,7 @@ export const registerRoutes = async ( const certificateDAL = certificateDALFactory(db); const certificateBodyDAL = certificateBodyDALFactory(db); const certificateSecretDAL = certificateSecretDALFactory(db); + const certificateSyncDAL = certificateSyncDALFactory(db); const pkiAlertDAL = pkiAlertDALFactory(db); const pkiCollectionDAL = pkiCollectionDALFactory(db); @@ -2027,7 +2029,8 @@ export const registerRoutes = async ( certificateBodyDAL, certificateSecretDAL, certificateAuthorityDAL, - certificateAuthorityCertDAL + certificateAuthorityCertDAL, + certificateSyncDAL }); const pkiSyncCleanup = pkiSyncCleanupQueueServiceFactory({ @@ -2138,6 +2141,7 @@ export const registerRoutes = async ( permissionService, pkiCollectionDAL, pkiCollectionItemDAL, + certificateSyncDAL, pkiSyncDAL, pkiSyncQueue }); @@ -2149,7 +2153,10 @@ export const registerRoutes = async ( certificateProfileDAL, certificateTemplateV2Service, internalCaService: internalCertificateAuthorityService, - permissionService + permissionService, + certificateSyncDAL, + pkiSyncDAL, + pkiSyncQueue }); const certificateV3Queue = certificateV3QueueServiceFactory({ @@ -2191,6 +2198,8 @@ export const registerRoutes = async ( const pkiSyncService = pkiSyncServiceFactory({ pkiSyncDAL, + certificateDAL, + certificateSyncDAL, pkiSubscriberDAL, appConnectionService, permissionService, diff --git a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts index a3250c6a9..5a3496750 100644 --- a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts +++ b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { ProjectType } from "@app/db/schemas"; +import { ChefConnectionListItemSchema, SanitizedChefConnectionSchema } from "@app/ee/services/app-connections/chef"; import { OCIConnectionListItemSchema, SanitizedOCIConnectionSchema } from "@app/ee/services/app-connections/oci"; import { OracleDBConnectionListItemSchema, @@ -168,7 +169,8 @@ const SanitizedAppConnectionSchema = z.union([ ...SanitizedOktaConnectionSchema.options, ...SanitizedAzureADCSConnectionSchema.options, ...SanitizedRedisConnectionSchema.options, - ...SanitizedLaravelForgeConnectionSchema.options + ...SanitizedLaravelForgeConnectionSchema.options, + ...SanitizedChefConnectionSchema.options ]); const AppConnectionOptionsSchema = z.discriminatedUnion("app", [ @@ -212,7 +214,8 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [ OktaConnectionListItemSchema, AzureADCSConnectionListItemSchema, RedisConnectionListItemSchema, - LaravelForgeConnectionListItemSchema + LaravelForgeConnectionListItemSchema, + ChefConnectionListItemSchema ]); export const registerAppConnectionRouter = async (server: FastifyZodProvider) => { diff --git a/backend/src/server/routes/v1/app-connection-routers/index.ts b/backend/src/server/routes/v1/app-connection-routers/index.ts index d8bcdce23..aa1d671b6 100644 --- a/backend/src/server/routes/v1/app-connection-routers/index.ts +++ b/backend/src/server/routes/v1/app-connection-routers/index.ts @@ -1,3 +1,4 @@ +import { registerChefConnectionRouter } from "@app/ee/routes/v1/app-connection-routers/chef-connection-router"; import { registerOCIConnectionRouter } from "@app/ee/routes/v1/app-connection-routers/oci-connection-router"; import { registerOracleDBConnectionRouter } from "@app/ee/routes/v1/app-connection-routers/oracledb-connection-router"; import { AppConnection } from "@app/services/app-connection/app-connection-enums"; @@ -86,5 +87,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record; description?: string; isAutoSyncEnabled?: boolean; - subscriberId?: string; + subscriberId?: string | null; }>; updateSchema: z.ZodType<{ connectionId?: string; @@ -35,7 +35,7 @@ export const registerSyncPkiEndpoints = ({ syncOptions?: Record; description?: string; isAutoSyncEnabled?: boolean; - subscriberId?: string; + subscriberId?: string | null; }>; responseSchema: z.ZodTypeAny; syncOptions: { diff --git a/backend/src/server/routes/v1/pki-sync-routers/pki-sync-router.ts b/backend/src/server/routes/v1/pki-sync-routers/pki-sync-router.ts index 158d9e4dc..0710a91fc 100644 --- a/backend/src/server/routes/v1/pki-sync-routers/pki-sync-router.ts +++ b/backend/src/server/routes/v1/pki-sync-routers/pki-sync-router.ts @@ -2,10 +2,11 @@ import { z } from "zod"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { ApiDocsTags } from "@app/lib/api-docs"; -import { readLimit } from "@app/server/config/rateLimiter"; +import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { AuthMode } from "@app/services/auth/auth-type"; +import { CertificateSyncStatus } from "@app/services/certificate-sync/certificate-sync-enums"; import { PkiSync } from "@app/services/pki-sync/pki-sync-enums"; const PkiSyncSchema = z.object({ @@ -60,7 +61,8 @@ const PkiSyncSchema = z.object({ name: z.string() }) .nullable() - .optional() + .optional(), + hasCertificate: z.boolean().optional() }); const PkiSyncOptionsSchema = z.object({ @@ -76,6 +78,27 @@ const PkiSyncOptionsSchema = z.object({ minCertificateNameLength: z.number().optional() }); +const PkiSyncCertificateSchema = z.object({ + id: z.string().uuid(), + pkiSyncId: z.string().uuid(), + certificateId: z.string().uuid(), + syncStatus: z.nativeEnum(CertificateSyncStatus), + lastSyncMessage: z.string().nullable().optional(), + lastSyncedAt: z.date().nullable().optional(), + createdAt: z.date(), + updatedAt: z.date(), + certificateSerialNumber: z.string().optional(), + certificateCommonName: z.string().optional(), + certificateAltNames: z.string().optional(), + certificateStatus: z.string().optional(), + certificateNotBefore: z.date().optional(), + certificateNotAfter: z.date().optional(), + certificateRenewBeforeDays: z.number().nullish(), + certificateRenewalError: z.string().nullish(), + pkiSyncName: z.string().optional(), + pkiSyncDestination: z.string().optional() +}); + export const registerPkiSyncRouter = async (server: FastifyZodProvider) => { server.route({ method: "GET", @@ -111,7 +134,8 @@ export const registerPkiSyncRouter = async (server: FastifyZodProvider) => { tags: [ApiDocsTags.PkiSyncs], description: "List all the PKI Syncs for the specified project.", querystring: z.object({ - projectId: z.string().trim().min(1) + projectId: z.string().trim().min(1), + certificateId: z.string().uuid().optional() }), response: { 200: z.object({ pkiSyncs: PkiSyncSchema.array() }) @@ -120,11 +144,11 @@ export const registerPkiSyncRouter = async (server: FastifyZodProvider) => { onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const { - query: { projectId }, + query: { projectId, certificateId }, permission } = req; - const pkiSyncs = await server.services.pkiSync.listPkiSyncsByProjectId({ projectId }, permission); + const pkiSyncs = await server.services.pkiSync.listPkiSyncsByProjectId({ projectId, certificateId }, permission); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, @@ -179,4 +203,163 @@ export const registerPkiSyncRouter = async (server: FastifyZodProvider) => { return pkiSync; } }); + + server.route({ + method: "GET", + url: "/:pkiSyncId/certificates", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiSyncs], + description: "List all certificates associated with a PKI Sync.", + params: z.object({ + pkiSyncId: z.string().uuid() + }), + querystring: z.object({ + offset: z.coerce.number().min(0).default(0), + limit: z.coerce.number().min(1).max(100).default(20) + }), + response: { + 200: z.object({ + certificates: PkiSyncCertificateSchema.array(), + totalCount: z.number() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { pkiSyncId } = req.params; + const { offset, limit } = req.query; + + const { certificates, totalCount, pkiSyncInfo } = await server.services.pkiSync.listPkiSyncCertificates( + { pkiSyncId, offset, limit }, + req.permission + ); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: pkiSyncInfo.projectId, + event: { + type: EventType.GET_PKI_SYNC_CERTIFICATES, + metadata: { + syncId: pkiSyncId, + destination: pkiSyncInfo.destination, + count: certificates.length, + certificateIds: certificates.map((c) => c.certificateId) + } + } + }); + + return { certificates, totalCount }; + } + }); + + server.route({ + method: "POST", + url: "/:pkiSyncId/certificates", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiSyncs], + description: "Add certificates to a PKI Sync.", + params: z.object({ + pkiSyncId: z.string().uuid() + }), + body: z.object({ + certificateIds: z.array(z.string().uuid()).min(1, "At least one certificate ID is required") + }), + response: { + 200: z.object({ + addedCertificates: z.array( + z.object({ + id: z.string().uuid(), + pkiSyncId: z.string().uuid(), + certificateId: z.string().uuid(), + syncStatus: z.string().default("pending").optional().nullable(), + lastSyncMessage: z.string().optional().nullable(), + lastSyncedAt: z.date().optional().nullable(), + createdAt: z.date(), + updatedAt: z.date() + }) + ) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { pkiSyncId } = req.params; + const { certificateIds } = req.body; + + const { addedCertificates, pkiSyncInfo } = await server.services.pkiSync.addCertificatesToPkiSync( + { pkiSyncId, certificateIds }, + req.permission + ); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: pkiSyncInfo.projectId, + event: { + type: EventType.UPDATE_PKI_SYNC, + metadata: { + pkiSyncId, + name: pkiSyncInfo.name + } + } + }); + + return { addedCertificates }; + } + }); + + server.route({ + method: "DELETE", + url: "/:pkiSyncId/certificates", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiSyncs], + description: "Remove certificates from a PKI Sync.", + params: z.object({ + pkiSyncId: z.string().uuid() + }), + body: z.object({ + certificateIds: z.array(z.string().uuid()).min(1, "At least one certificate ID is required") + }), + response: { + 200: z.object({ + removedCount: z.number() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { pkiSyncId } = req.params; + const { certificateIds } = req.body; + + const { removedCount, pkiSyncInfo } = await server.services.pkiSync.removeCertificatesFromPkiSync( + { pkiSyncId, certificateIds }, + req.permission + ); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: pkiSyncInfo.projectId, + event: { + type: EventType.UPDATE_PKI_SYNC, + metadata: { + pkiSyncId, + name: pkiSyncInfo.name + } + } + }); + + return { removedCount }; + } + }); }; diff --git a/backend/src/server/routes/v1/project-membership-router.ts b/backend/src/server/routes/v1/project-membership-router.ts index 57fdad031..dc76efa92 100644 --- a/backend/src/server/routes/v1/project-membership-router.ts +++ b/backend/src/server/routes/v1/project-membership-router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { AccessScope, + OrgMembershipRole, ProjectMembershipRole, ProjectMembershipsSchema, ProjectUserMembershipRolesSchema, @@ -266,6 +267,19 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const usernamesAndEmails = [...req.body.emails, ...req.body.usernames]; + + await server.services.membershipUser.createMembership({ + permission: req.permission, + scopeData: { + scope: AccessScope.Organization, + orgId: req.permission.orgId + }, + data: { + roles: [{ isTemporary: false, role: OrgMembershipRole.NoAccess }], + usernames: usernamesAndEmails + } + }); + const { memberships } = await server.services.membershipUser.createMembership({ permission: req.permission, scopeData: { diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index c3bffa2fc..1054d359b 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -1195,8 +1195,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { querystring: z.object({ friendlyName: z.string().optional().describe(PROJECTS.LIST_CERTIFICATES.friendlyName), commonName: z.string().optional().describe(PROJECTS.LIST_CERTIFICATES.commonName), - offset: z.coerce.number().min(0).max(100).default(0).describe(PROJECTS.LIST_CERTIFICATES.offset), - limit: z.coerce.number().min(1).max(100).default(25).describe(PROJECTS.LIST_CERTIFICATES.limit) + offset: z.coerce.number().min(0).default(0).describe(PROJECTS.LIST_CERTIFICATES.offset), + limit: z.coerce.number().min(1).max(100).default(25).describe(PROJECTS.LIST_CERTIFICATES.limit), + forPkiSync: z.coerce + .boolean() + .default(false) + .optional() + .describe("Retrieve only certificates available for PKI sync") }), response: { 200: z.object({ diff --git a/backend/src/server/routes/v1/secret-sync-routers/index.ts b/backend/src/server/routes/v1/secret-sync-routers/index.ts index 2acf2dfc9..810e5b7fa 100644 --- a/backend/src/server/routes/v1/secret-sync-routers/index.ts +++ b/backend/src/server/routes/v1/secret-sync-routers/index.ts @@ -1,3 +1,4 @@ +import { registerChefSyncRouter } from "@app/ee/routes/v1/secret-sync-routers/chef-sync-router"; import { registerOCIVaultSyncRouter } from "@app/ee/routes/v1/secret-sync-routers/oci-vault-sync-router"; import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; @@ -67,5 +68,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record { diff --git a/backend/src/services/app-connection/app-connection-enums.ts b/backend/src/services/app-connection/app-connection-enums.ts index 1e731ed77..1c184a436 100644 --- a/backend/src/services/app-connection/app-connection-enums.ts +++ b/backend/src/services/app-connection/app-connection-enums.ts @@ -39,6 +39,7 @@ export enum AppConnection { Okta = "okta", Redis = "redis", LaravelForge = "laravel-forge", + Chef = "chef", Northflank = "northflank" } diff --git a/backend/src/services/app-connection/app-connection-fns.ts b/backend/src/services/app-connection/app-connection-fns.ts index efc3deb99..863fa75f9 100644 --- a/backend/src/services/app-connection/app-connection-fns.ts +++ b/backend/src/services/app-connection/app-connection-fns.ts @@ -1,5 +1,10 @@ import { ProjectType } from "@app/db/schemas"; import { TAppConnections } from "@app/db/schemas/app-connections"; +import { + ChefConnectionMethod, + getChefConnectionListItem, + validateChefConnectionCredentials +} from "@app/ee/services/app-connections/chef"; import { getOCIConnectionListItem, OCIConnectionMethod, @@ -210,7 +215,8 @@ export const listAppConnectionOptions = (projectType?: ProjectType) => { getNetlifyConnectionListItem(), getNorthflankConnectionListItem(), getOktaConnectionListItem(), - getRedisConnectionListItem() + getRedisConnectionListItem(), + getChefConnectionListItem() ] .filter((option) => { switch (projectType) { @@ -341,6 +347,7 @@ export const validateAppConnectionCredentials = async ( [AppConnection.Netlify]: validateNetlifyConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Northflank]: validateNorthflankConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Okta]: validateOktaConnectionCredentials as TAppConnectionCredentialsValidator, + [AppConnection.Chef]: validateChefConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Redis]: validateRedisConnectionCredentials as TAppConnectionCredentialsValidator }; @@ -409,6 +416,8 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) => case RenderConnectionMethod.ApiKey: case ChecklyConnectionMethod.ApiKey: return "API Key"; + case ChefConnectionMethod.UserKey: + return "User Key"; case SupabaseConnectionMethod.AccessToken: return "Access Token"; default: @@ -483,7 +492,8 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record< [AppConnection.Northflank]: platformManagedCredentialsNotSupported, [AppConnection.Okta]: platformManagedCredentialsNotSupported, [AppConnection.Redis]: platformManagedCredentialsNotSupported, - [AppConnection.LaravelForge]: platformManagedCredentialsNotSupported + [AppConnection.LaravelForge]: platformManagedCredentialsNotSupported, + [AppConnection.Chef]: platformManagedCredentialsNotSupported }; export const enterpriseAppCheck = async ( diff --git a/backend/src/services/app-connection/app-connection-maps.ts b/backend/src/services/app-connection/app-connection-maps.ts index c684765bd..5b8cc3fc1 100644 --- a/backend/src/services/app-connection/app-connection-maps.ts +++ b/backend/src/services/app-connection/app-connection-maps.ts @@ -41,6 +41,7 @@ export const APP_CONNECTION_NAME_MAP: Record = { [AppConnection.Netlify]: "Netlify", [AppConnection.Okta]: "Okta", [AppConnection.Redis]: "Redis", + [AppConnection.Chef]: "Chef", [AppConnection.Northflank]: "Northflank" }; @@ -85,5 +86,6 @@ export const APP_CONNECTION_PLAN_MAP: Record>>; @@ -330,6 +337,7 @@ export type TAppConnectionInput = { id: string } & ( | TNorthflankConnectionInput | TOktaConnectionInput | TRedisConnectionInput + | TChefConnectionInput ); export type TSqlConnectionInput = @@ -395,7 +403,8 @@ export type TAppConnectionConfig = | TNetlifyConnectionConfig | TNorthflankConnectionConfig | TOktaConnectionConfig - | TRedisConnectionConfig; + | TRedisConnectionConfig + | TChefConnectionConfig; export type TValidateAppConnectionCredentialsSchema = | TValidateAwsConnectionCredentialsSchema @@ -438,7 +447,8 @@ export type TValidateAppConnectionCredentialsSchema = | TValidateNetlifyConnectionCredentialsSchema | TValidateNorthflankConnectionCredentialsSchema | TValidateOktaConnectionCredentialsSchema - | TValidateRedisConnectionCredentialsSchema; + | TValidateRedisConnectionCredentialsSchema + | TValidateChefConnectionCredentialsSchema; export type TListAwsConnectionKmsKeys = { connectionId: string; diff --git a/backend/src/services/certificate-authority/azure-ad-cs/azure-ad-cs-certificate-authority-fns.ts b/backend/src/services/certificate-authority/azure-ad-cs/azure-ad-cs-certificate-authority-fns.ts index 4937a5331..26f59a402 100644 --- a/backend/src/services/certificate-authority/azure-ad-cs/azure-ad-cs-certificate-authority-fns.ts +++ b/backend/src/services/certificate-authority/azure-ad-cs/azure-ad-cs-certificate-authority-fns.ts @@ -192,7 +192,7 @@ export const castDbEntryToAzureAdCsCertificateAuthority = ( ca: Awaited> ): TAzureAdCsCertificateAuthority & { credentials: unknown } => { if (!ca.externalCa?.id) { - throw new BadRequestError({ message: "Malformed Azure AD Certificate Service certificate authority" }); + throw new BadRequestError({ message: "Malformed Active Directory Certificate Service certificate authority" }); } if (!ca.externalCa.dnsAppConnectionId) { @@ -776,7 +776,7 @@ export const AzureAdCsCertificateAuthorityFns = ({ const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(subscriber.caId); if (!ca.externalCa || ca.externalCa.type !== CaType.AZURE_AD_CS) { - throw new BadRequestError({ message: "CA is not an Azure AD Certificate Service CA" }); + throw new BadRequestError({ message: "CA is not an Active Directory Certificate Service CA" }); } const azureCa = castDbEntryToAzureAdCsCertificateAuthority(ca); diff --git a/backend/src/services/certificate-authority/certificate-authority-maps.ts b/backend/src/services/certificate-authority/certificate-authority-maps.ts index ef844a1ed..bae444cae 100644 --- a/backend/src/services/certificate-authority/certificate-authority-maps.ts +++ b/backend/src/services/certificate-authority/certificate-authority-maps.ts @@ -2,8 +2,8 @@ import { CaCapability, CaType } from "./certificate-authority-enums"; export const CERTIFICATE_AUTHORITIES_TYPE_MAP: Record = { [CaType.INTERNAL]: "Internal", - [CaType.ACME]: "ACME", - [CaType.AZURE_AD_CS]: "Azure AD Certificate Service" + [CaType.ACME]: "ACME-compatible CA", + [CaType.AZURE_AD_CS]: "Active Directory Certificate Service" }; export const CERTIFICATE_AUTHORITIES_CAPABILITIES_MAP: Record = { diff --git a/backend/src/services/certificate-profile/certificate-profile-dal.ts b/backend/src/services/certificate-profile/certificate-profile-dal.ts index 1ffa3e295..b66475bbf 100644 --- a/backend/src/services/certificate-profile/certificate-profile-dal.ts +++ b/backend/src/services/certificate-profile/certificate-profile-dal.ts @@ -10,10 +10,8 @@ import { TCertificateProfile, TCertificateProfileCertificate, TCertificateProfileInsert, - TCertificateProfileMetrics, TCertificateProfileUpdate, - TCertificateProfileWithConfigs, - TCertificateProfileWithRawMetrics + TCertificateProfileWithConfigs } from "./certificate-profile-types"; export type TCertificateProfileDALFactory = ReturnType; @@ -203,21 +201,11 @@ export const certificateProfileDALFactory = (db: TDbClient) => { search?: string; enrollmentType?: EnrollmentType; caId?: string; - includeMetrics?: boolean; - expiringDays?: number; } = {}, tx?: Knex - ): Promise => { + ): Promise => { try { - const { - offset = 0, - limit = 20, - search, - enrollmentType, - caId, - includeMetrics = false, - expiringDays = 7 - } = options; + const { offset = 0, limit = 20, search, enrollmentType, caId } = options; let baseQuery = (tx || db)(TableName.PkiCertificateProfile).where( `${TableName.PkiCertificateProfile}.projectId`, @@ -242,7 +230,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => { baseQuery = baseQuery.where(`${TableName.PkiCertificateProfile}.caId`, caId); } - let query = baseQuery + const query = baseQuery .leftJoin( TableName.PkiEstEnrollmentConfig, `${TableName.PkiCertificateProfile}.estConfigId`, @@ -267,52 +255,6 @@ export const certificateProfileDALFactory = (db: TDbClient) => { db.ref("renewBeforeDays").withSchema(TableName.PkiApiEnrollmentConfig).as("apiRenewBeforeDays") ); - if (includeMetrics) { - query = query.leftJoin( - TableName.Certificate, - `${TableName.PkiCertificateProfile}.id`, - `${TableName.Certificate}.profileId` - ); - - const now = new Date(); - const expiringDate = new Date(); - expiringDate.setDate(now.getDate() + expiringDays); - - query = query - .select( - selectAllTableCols(TableName.PkiCertificateProfile), - db.ref("id").withSchema(TableName.PkiEstEnrollmentConfig).as("estId"), - db - .ref("disableBootstrapCaValidation") - .withSchema(TableName.PkiEstEnrollmentConfig) - .as("estDisableBootstrapCaValidation"), - db.ref("hashedPassphrase").withSchema(TableName.PkiEstEnrollmentConfig).as("estHashedPassphrase"), - db.ref("encryptedCaChain").withSchema(TableName.PkiEstEnrollmentConfig).as("estEncryptedCaChain"), - db.ref("id").withSchema(TableName.PkiApiEnrollmentConfig).as("apiId"), - db.ref("autoRenew").withSchema(TableName.PkiApiEnrollmentConfig).as("apiAutoRenew"), - db.ref("renewBeforeDays").withSchema(TableName.PkiApiEnrollmentConfig).as("apiRenewBeforeDays"), - db.raw("COUNT(certificates.id) as total_certificates"), - db.raw( - 'COUNT(CASE WHEN certificates."revokedAt" IS NULL AND certificates."notAfter" > ? THEN 1 END) as active_certificates', - [expiringDate] - ), - db.raw( - 'COUNT(CASE WHEN certificates."revokedAt" IS NULL AND certificates."notAfter" <= ? THEN 1 END) as expired_certificates', - [now] - ), - db.raw( - 'COUNT(CASE WHEN certificates."revokedAt" IS NULL AND certificates."notAfter" > ? AND certificates."notAfter" <= ? THEN 1 END) as expiring_certificates', - [now, expiringDate] - ), - db.raw('COUNT(CASE WHEN certificates."revokedAt" IS NOT NULL THEN 1 END) as revoked_certificates') - ) - .groupBy( - `${TableName.PkiCertificateProfile}.id`, - `${TableName.PkiEstEnrollmentConfig}.id`, - `${TableName.PkiApiEnrollmentConfig}.id` - ); - } - const results = (await query .orderBy(`${TableName.PkiCertificateProfile}.createdAt`, "desc") .offset(offset) @@ -353,17 +295,6 @@ export const certificateProfileDALFactory = (db: TDbClient) => { apiConfig }; - if (includeMetrics) { - return { - ...baseProfile, - total_certificates: result.total_certificates, - active_certificates: result.active_certificates, - expired_certificates: result.expired_certificates, - expiring_certificates: result.expiring_certificates, - revoked_certificates: result.revoked_certificates - } as TCertificateProfileWithRawMetrics & TCertificateProfileWithConfigs; - } - return baseProfile as TCertificateProfileWithConfigs; }); } catch (error) { @@ -485,45 +416,6 @@ export const certificateProfileDALFactory = (db: TDbClient) => { } }; - const getProfileMetrics = async ( - profileId: string, - expiringDays: number = 7, - tx?: Knex - ): Promise => { - try { - const now = new Date(); - const expiringDate = new Date(); - expiringDate.setDate(now.getDate() + expiringDays); - - const metrics = await (tx || db)(TableName.Certificate) - .where("profileId", profileId) - .select( - db.raw("COUNT(*) as total_certificates"), - db.raw('COUNT(CASE WHEN "revokedAt" IS NULL AND "notAfter" > ? THEN 1 END) as active_certificates', [ - expiringDate - ]), - db.raw('COUNT(CASE WHEN "revokedAt" IS NULL AND "notAfter" <= ? THEN 1 END) as expired_certificates', [now]), - db.raw( - 'COUNT(CASE WHEN "revokedAt" IS NULL AND "notAfter" > ? AND "notAfter" <= ? THEN 1 END) as expiring_certificates', - [now, expiringDate] - ), - db.raw('COUNT(CASE WHEN "revokedAt" IS NOT NULL THEN 1 END) as revoked_certificates') - ) - .first(); - - return { - profileId, - totalCertificates: parseInt(String((metrics as Record)?.total_certificates || 0), 10), - activeCertificates: parseInt(String((metrics as Record)?.active_certificates || 0), 10), - expiredCertificates: parseInt(String((metrics as Record)?.expired_certificates || 0), 10), - expiringCertificates: parseInt(String((metrics as Record)?.expiring_certificates || 0), 10), - revokedCertificates: parseInt(String((metrics as Record)?.revoked_certificates || 0), 10) - }; - } catch (error) { - throw new DatabaseError({ error, name: "Get certificate profile metrics" }); - } - }; - const isProfileInUse = async (profileId: string, tx?: Knex) => { try { const doc = await (tx || db)(TableName.Certificate).where("profileId", profileId).count("*").first(); @@ -546,7 +438,6 @@ export const certificateProfileDALFactory = (db: TDbClient) => { countByProjectId, findByNameAndProjectId, getCertificatesByProfile, - getProfileMetrics, isProfileInUse }; }; diff --git a/backend/src/services/certificate-profile/certificate-profile-schemas.ts b/backend/src/services/certificate-profile/certificate-profile-schemas.ts index a2c391c2a..8ac494fe6 100644 --- a/backend/src/services/certificate-profile/certificate-profile-schemas.ts +++ b/backend/src/services/certificate-profile/certificate-profile-schemas.ts @@ -127,8 +127,3 @@ export const listCertificatesByProfileSchema = z.object({ status: z.enum(["active", "expired", "revoked"]).optional(), search: z.string().optional() }); - -export const getCertificateProfileMetricsSchema = z.object({ - profileId: z.string().uuid(), - expiringDays: z.coerce.number().min(1).max(365).default(30) -}); diff --git a/backend/src/services/certificate-profile/certificate-profile-service.test.ts b/backend/src/services/certificate-profile/certificate-profile-service.test.ts index 26b1e976a..bb30b8d5c 100644 --- a/backend/src/services/certificate-profile/certificate-profile-service.test.ts +++ b/backend/src/services/certificate-profile/certificate-profile-service.test.ts @@ -47,7 +47,6 @@ describe("CertificateProfileService", () => { findByNameAndProjectId: vi.fn(), findByIdWithConfigs: vi.fn(), getCertificatesByProfile: vi.fn(), - getProfileMetrics: vi.fn(), isProfileInUse: vi.fn(), transaction: vi.fn(), find: vi.fn(), @@ -493,9 +492,7 @@ describe("CertificateProfileService", () => { limit: 20, search: undefined, enrollmentType: undefined, - caId: undefined, - includeMetrics: false, - expiringDays: 30 + caId: undefined }); }); @@ -515,51 +512,7 @@ describe("CertificateProfileService", () => { limit: 5, search: "test", enrollmentType: EnrollmentType.API, - caId: "ca-123", - includeMetrics: false, - expiringDays: 30 - }); - }); - - it("should list profiles with metrics when includeMetrics is true", async () => { - const mockProfilesWithMetrics = [ - { - ...sampleProfile, - total_certificates: 10, - active_certificates: 8, - expired_certificates: 1, - expiring_certificates: 1, - revoked_certificates: 0 - } - ]; - (mockCertificateProfileDAL.findByProjectId as any).mockResolvedValue(mockProfilesWithMetrics); - - const result = await service.listProfiles({ - ...mockActor, - projectId: "project-123", - includeMetrics: true, - expiringDays: 15 - }); - - expect(result.profiles).toHaveLength(1); - expect(result.profiles[0]).toHaveProperty("metrics"); - expect(result.profiles[0].metrics).toEqual({ - profileId: sampleProfile.id, - totalCertificates: 10, - activeCertificates: 8, - expiredCertificates: 1, - expiringCertificates: 1, - revokedCertificates: 0 - }); - - expect(mockCertificateProfileDAL.findByProjectId).toHaveBeenCalledWith("project-123", { - offset: 0, - limit: 20, - search: undefined, - enrollmentType: undefined, - caId: undefined, - includeMetrics: true, - expiringDays: 15 + caId: "ca-123" }); }); }); @@ -659,54 +612,6 @@ describe("CertificateProfileService", () => { }); }); - describe("getProfileMetrics", () => { - const mockMetrics = { - profileId: "profile-123", - totalCertificates: 10, - activeCertificates: 8, - expiredCertificates: 1, - expiringCertificates: 2, - revokedCertificates: 1 - }; - - beforeEach(() => { - (mockCertificateProfileDAL.findById as any).mockResolvedValue(sampleProfile); - (mockCertificateProfileDAL.getProfileMetrics as any).mockResolvedValue(mockMetrics); - }); - - it("should get profile metrics successfully", async () => { - const result = await service.getProfileMetrics({ - ...mockActor, - profileId: "profile-123" - }); - - expect(result).toEqual(mockMetrics); - expect(mockCertificateProfileDAL.findById).toHaveBeenCalledWith("profile-123"); - expect(mockCertificateProfileDAL.getProfileMetrics).toHaveBeenCalledWith("profile-123", 30); - }); - - it("should get profile metrics with custom expiring days", async () => { - await service.getProfileMetrics({ - ...mockActor, - profileId: "profile-123", - expiringDays: 60 - }); - - expect(mockCertificateProfileDAL.getProfileMetrics).toHaveBeenCalledWith("profile-123", 60); - }); - - it("should throw NotFoundError when profile not found", async () => { - (mockCertificateProfileDAL.findById as any).mockResolvedValue(null); - - await expect( - service.getProfileMetrics({ - ...mockActor, - profileId: "profile-123" - }) - ).rejects.toThrow(NotFoundError); - }); - }); - describe("comprehensive certificate profile scenarios", () => { describe("profile configuration validation", () => { it("should validate EST enrollment configuration", async () => { @@ -929,53 +834,6 @@ describe("CertificateProfileService", () => { }); }); - describe("metrics and monitoring", () => { - it("should calculate profile metrics correctly", async () => { - const detailedMetrics = { - profileId: "profile-123", - totalCertificates: 50, - activeCertificates: 40, - expiredCertificates: 5, - expiringCertificates: 3, - revokedCertificates: 2 - }; - - (mockCertificateProfileDAL.findById as any).mockResolvedValue(sampleProfile); - (mockCertificateProfileDAL.getProfileMetrics as any).mockResolvedValue(detailedMetrics); - - const result = await service.getProfileMetrics({ - ...mockActor, - profileId: "profile-123", - expiringDays: 14 - }); - - expect(result).toEqual(detailedMetrics); - expect(mockCertificateProfileDAL.getProfileMetrics).toHaveBeenCalledWith("profile-123", 14); - }); - - it("should handle zero certificate metrics", async () => { - const emptyMetrics = { - profileId: "profile-123", - totalCertificates: 0, - activeCertificates: 0, - expiredCertificates: 0, - expiringCertificates: 0, - revokedCertificates: 0 - }; - - (mockCertificateProfileDAL.findById as any).mockResolvedValue(sampleProfile); - (mockCertificateProfileDAL.getProfileMetrics as any).mockResolvedValue(emptyMetrics); - - const result = await service.getProfileMetrics({ - ...mockActor, - profileId: "profile-123" - }); - - expect(result.totalCertificates).toBe(0); - expect(result.activeCertificates).toBe(0); - }); - }); - describe("error scenarios", () => { it("should handle database connection errors gracefully", async () => { (mockCertificateProfileDAL.findById as any).mockRejectedValue(new Error("Database connection failed")); diff --git a/backend/src/services/certificate-profile/certificate-profile-service.ts b/backend/src/services/certificate-profile/certificate-profile-service.ts index 7b48af8f1..f858a8d4f 100644 --- a/backend/src/services/certificate-profile/certificate-profile-service.ts +++ b/backend/src/services/certificate-profile/certificate-profile-service.ts @@ -27,10 +27,8 @@ import { TCertificateProfile, TCertificateProfileCertificate, TCertificateProfileInsert, - TCertificateProfileMetrics, TCertificateProfileUpdate, - TCertificateProfileWithConfigs, - TCertificateProfileWithRawMetrics + TCertificateProfileWithConfigs } from "./certificate-profile-types"; const validateAndEncryptPemCaChain = async ( @@ -361,18 +359,14 @@ export const certificateProfileServiceFactory = ({ actorId, actorAuthMethod, actorOrgId, - profileId, - includeMetrics = false, - expiringDays = 30 + profileId }: { actor: ActorType; actorId: string; actorAuthMethod: ActorAuthMethod; actorOrgId: string; profileId: string; - includeMetrics?: boolean; - expiringDays?: number; - }): Promise => { + }): Promise => { const profile = await certificateProfileDAL.findById(profileId); if (!profile) { throw new NotFoundError({ message: "Certificate profile not found" }); @@ -393,14 +387,6 @@ export const certificateProfileServiceFactory = ({ const converted = convertDalToService(profile); - if (includeMetrics) { - const metrics = await certificateProfileDAL.getProfileMetrics(profileId, expiringDays); - return { - ...converted, - metrics - }; - } - return converted; }; @@ -506,9 +492,7 @@ export const certificateProfileServiceFactory = ({ limit = 20, search, enrollmentType, - caId, - includeMetrics = false, - expiringDays = 30 + caId }: { actor: ActorType; actorId: string; @@ -520,10 +504,8 @@ export const certificateProfileServiceFactory = ({ search?: string; enrollmentType?: EnrollmentType; caId?: string; - includeMetrics?: boolean; - expiringDays?: number; }): Promise<{ - profiles: (TCertificateProfileWithConfigs & { metrics?: TCertificateProfileMetrics })[]; + profiles: TCertificateProfileWithConfigs[]; totalCount: number; }> => { const { permission } = await permissionService.getProjectPermission({ @@ -544,9 +526,7 @@ export const certificateProfileServiceFactory = ({ limit, search, enrollmentType, - caId, - includeMetrics, - expiringDays + caId }); const totalCount = await certificateProfileDAL.countByProjectId(projectId, { @@ -591,27 +571,12 @@ export const certificateProfileServiceFactory = ({ } const converted = convertDalToService(profileWithConfigs); - let result: TCertificateProfileWithConfigs & { metrics?: TCertificateProfileMetrics } = { + const result: TCertificateProfileWithConfigs = { ...converted, estConfig: decryptedEstConfig, apiConfig: profileWithConfigs.apiConfig }; - if (includeMetrics) { - const profileWithMetrics = profile as TCertificateProfileWithRawMetrics; - result = { - ...result, - metrics: { - profileId: converted.id, - totalCertificates: parseInt(String(profileWithMetrics.total_certificates || 0), 10), - activeCertificates: parseInt(String(profileWithMetrics.active_certificates || 0), 10), - expiredCertificates: parseInt(String(profileWithMetrics.expired_certificates || 0), 10), - expiringCertificates: parseInt(String(profileWithMetrics.expiring_certificates || 0), 10), - revokedCertificates: parseInt(String(profileWithMetrics.revoked_certificates || 0), 10) - } - }; - } - return result; }) ); @@ -709,43 +674,6 @@ export const certificateProfileServiceFactory = ({ return certificates; }; - const getProfileMetrics = async ({ - actor, - actorId, - actorAuthMethod, - actorOrgId, - profileId, - expiringDays = 30 - }: { - actor: ActorType; - actorId: string; - actorAuthMethod: ActorAuthMethod; - actorOrgId: string; - profileId: string; - expiringDays?: number; - }): Promise => { - const profile = await certificateProfileDAL.findById(profileId); - if (!profile) { - throw new NotFoundError({ message: "Certificate profile not found" }); - } - - const { permission } = await permissionService.getProjectPermission({ - actor, - actorId, - projectId: profile.projectId, - actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.CertificateManager - }); - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionCertificateProfileActions.Read, - ProjectPermissionSub.CertificateProfiles - ); - - const metrics = await certificateProfileDAL.getProfileMetrics(profileId, expiringDays); - return metrics; - }; - const getEstConfigurationByProfile = async ( params: | { @@ -818,7 +746,6 @@ export const certificateProfileServiceFactory = ({ listProfiles, deleteProfile, getProfileCertificates, - getProfileMetrics, getEstConfigurationByProfile }; }; diff --git a/backend/src/services/certificate-profile/certificate-profile-types.ts b/backend/src/services/certificate-profile/certificate-profile-types.ts index 1c22a5e75..5dac470c8 100644 --- a/backend/src/services/certificate-profile/certificate-profile-types.ts +++ b/backend/src/services/certificate-profile/certificate-profile-types.ts @@ -54,18 +54,8 @@ export type TCertificateProfileWithConfigs = TCertificateProfile & { autoRenew: boolean; renewBeforeDays?: number; }; - metrics?: TCertificateProfileMetrics; }; -export interface TCertificateProfileMetrics { - profileId: string; - totalCertificates: number; - activeCertificates: number; - expiredCertificates: number; - expiringCertificates: number; - revokedCertificates: number; -} - export interface TCertificateProfileCertificate { id: string; serialNumber: string; @@ -76,11 +66,3 @@ export interface TCertificateProfileCertificate { revokedAt: Date | null; createdAt: Date; } - -export type TCertificateProfileWithRawMetrics = TCertificateProfile & { - total_certificates?: string; - active_certificates?: string; - expired_certificates?: string; - expiring_certificates?: string; - revoked_certificates?: string; -}; diff --git a/backend/src/services/certificate-sync/certificate-sync-dal.ts b/backend/src/services/certificate-sync/certificate-sync-dal.ts new file mode 100644 index 000000000..a46ae8e5a --- /dev/null +++ b/backend/src/services/certificate-sync/certificate-sync-dal.ts @@ -0,0 +1,272 @@ +import { Knex } from "knex"; + +import { TDbClient } from "@app/db"; +import { TableName, TCertificateSyncs } from "@app/db/schemas"; +import { DatabaseError } from "@app/lib/errors"; +import { buildFindFilter, ormify, selectAllTableCols } from "@app/lib/knex"; + +import { CertificateSyncStatus } from "./certificate-sync-enums"; + +export type TCertificateSyncDALFactory = ReturnType; + +type CertificateSyncFindFilter = Parameters>[0]; + +export const certificateSyncDALFactory = (db: TDbClient) => { + const certificateSyncOrm = ormify(db, TableName.CertificateSync); + + const findByPkiSyncId = async (pkiSyncId: string, tx?: Knex) => { + try { + const docs = await (tx || db.replicaNode())(TableName.CertificateSync) + .where({ pkiSyncId }) + .select(selectAllTableCols(TableName.CertificateSync)); + return docs; + } catch (error) { + throw new DatabaseError({ error, name: "FindByPkiSyncId" }); + } + }; + + const findByCertificateId = async (certificateId: string, tx?: Knex) => { + try { + const docs = await (tx || db.replicaNode())(TableName.CertificateSync) + .where({ certificateId }) + .select(selectAllTableCols(TableName.CertificateSync)); + return docs; + } catch (error) { + throw new DatabaseError({ error, name: "FindByCertificateId" }); + } + }; + + const findByPkiSyncAndCertificate = async (pkiSyncId: string, certificateId: string, tx?: Knex) => { + try { + const doc = await (tx || db.replicaNode())(TableName.CertificateSync) + .where({ pkiSyncId, certificateId }) + .select(selectAllTableCols(TableName.CertificateSync)) + .first(); + return doc; + } catch (error) { + throw new DatabaseError({ error, name: "FindByPkiSyncAndCertificate" }); + } + }; + + const findCertificateIdsByPkiSyncId = async (pkiSyncId: string, tx?: Knex): Promise => { + try { + const docs = (await (tx || db.replicaNode())(TableName.CertificateSync) + .where({ pkiSyncId }) + .select("certificateId")) as Array<{ certificateId: string }>; + return docs.map((doc) => doc.certificateId); + } catch (error) { + throw new DatabaseError({ error, name: "FindCertificateIdsByPkiSyncId" }); + } + }; + + const findPkiSyncIdsByCertificateId = async (certificateId: string, tx?: Knex): Promise => { + try { + const docs = (await (tx || db.replicaNode())(TableName.CertificateSync) + .where({ certificateId }) + .select("pkiSyncId")) as Array<{ pkiSyncId: string }>; + return docs.map((doc) => doc.pkiSyncId); + } catch (error) { + throw new DatabaseError({ error, name: "FindPkiSyncIdsByCertificateId" }); + } + }; + + const addCertificates = async ( + pkiSyncId: string, + certificateData: Array<{ certificateId: string; externalIdentifier?: string }>, + tx?: Knex + ): Promise => { + try { + const insertData = certificateData.map(({ certificateId, externalIdentifier }) => ({ + pkiSyncId, + certificateId, + syncStatus: CertificateSyncStatus.Pending, + externalIdentifier + })); + + const docs = await (tx || db)(TableName.CertificateSync).insert(insertData).returning("*"); + + return docs; + } catch (error) { + throw new DatabaseError({ error, name: "AddCertificates" }); + } + }; + + const removeCertificates = async (pkiSyncId: string, certificateIds: string[], tx?: Knex): Promise => { + try { + const deletedCount = await (tx || db)(TableName.CertificateSync) + .where({ pkiSyncId }) + .whereIn("certificateId", certificateIds) + .del(); + + return deletedCount; + } catch (error) { + throw new DatabaseError({ error, name: "RemoveCertificates" }); + } + }; + + const removeAllCertificatesFromSync = async (pkiSyncId: string, tx?: Knex): Promise => { + try { + const deletedCount = await (tx || db)(TableName.CertificateSync).where({ pkiSyncId }).del(); + return deletedCount; + } catch (error) { + throw new DatabaseError({ error, name: "RemoveAllCertificatesFromSync" }); + } + }; + + const updateSyncStatus = async ( + pkiSyncId: string, + certificateId: string, + status: string, + message?: string, + tx?: Knex + ): Promise => { + try { + const updateData: Partial = { + syncStatus: status, + lastSyncedAt: new Date() + }; + + if (message !== undefined) { + updateData.lastSyncMessage = message; + } + + const docs = await (tx || db)(TableName.CertificateSync) + .where({ pkiSyncId, certificateId }) + .update(updateData) + .returning("*"); + + return docs[0]; + } catch (error) { + throw new DatabaseError({ error, name: "UpdateSyncStatus" }); + } + }; + + const bulkUpdateSyncStatus = async ( + updates: Array<{ + pkiSyncId: string; + certificateId: string; + status: string; + message?: string; + }>, + tx?: Knex + ): Promise => { + try { + if (tx) { + for (const update of updates) { + // eslint-disable-next-line no-await-in-loop + await updateSyncStatus(update.pkiSyncId, update.certificateId, update.status, update.message, tx); + } + } else { + await certificateSyncOrm.transaction(async (trx) => { + for (const update of updates) { + // eslint-disable-next-line no-await-in-loop + await updateSyncStatus(update.pkiSyncId, update.certificateId, update.status, update.message, trx); + } + }); + } + } catch (error) { + throw new DatabaseError({ error, name: "BulkUpdateSyncStatus" }); + } + }; + + const findWithDetails = async ( + options: { + filter?: CertificateSyncFindFilter; + pkiSyncId?: string; + offset?: number; + limit?: number; + }, + tx?: Knex + ): Promise<{ + certificateDetails: (TCertificateSyncs & { + certificateSerialNumber?: string; + certificateCommonName?: string; + certificateAltNames?: string; + certificateStatus?: string; + certificateNotBefore?: Date; + certificateNotAfter?: Date; + certificateRenewBeforeDays?: number | null; + certificateRenewedByCertificateId?: string; + certificateRenewalError?: string; + pkiSyncName?: string; + pkiSyncDestination?: string; + })[]; + totalCount: number; + }> => { + try { + const { filter, pkiSyncId, offset, limit } = options; + + const baseQuery = (tx || db.replicaNode())(TableName.CertificateSync) + .leftJoin(TableName.Certificate, `${TableName.CertificateSync}.certificateId`, `${TableName.Certificate}.id`) + .leftJoin(TableName.PkiSync, `${TableName.CertificateSync}.pkiSyncId`, `${TableName.PkiSync}.id`); + + if (filter) { + // eslint-disable-next-line @typescript-eslint/no-misused-promises + void baseQuery.where(buildFindFilter(filter)); + } + if (pkiSyncId) { + void baseQuery.where(`${TableName.CertificateSync}.pkiSyncId`, pkiSyncId); + } + + const countResult = await baseQuery.clone().count("* as count"); + const totalCount = Number((countResult[0] as unknown as { count: string | number }).count); + + const query = baseQuery + .select(selectAllTableCols(TableName.CertificateSync)) + .select( + db.ref("serialNumber").withSchema(TableName.Certificate).as("certificateSerialNumber"), + db.ref("commonName").withSchema(TableName.Certificate).as("certificateCommonName"), + db.ref("altNames").withSchema(TableName.Certificate).as("certificateAltNames"), + db.ref("status").withSchema(TableName.Certificate).as("certificateStatus"), + db.ref("notBefore").withSchema(TableName.Certificate).as("certificateNotBefore"), + db.ref("notAfter").withSchema(TableName.Certificate).as("certificateNotAfter"), + db.ref("renewBeforeDays").withSchema(TableName.Certificate).as("certificateRenewBeforeDays"), + db.ref("renewedByCertificateId").withSchema(TableName.Certificate).as("certificateRenewedByCertificateId"), + db.ref("renewalError").withSchema(TableName.Certificate).as("certificateRenewalError"), + db.ref("name").withSchema(TableName.PkiSync).as("pkiSyncName"), + db.ref("destination").withSchema(TableName.PkiSync).as("pkiSyncDestination") + ) + .orderBy(`${TableName.CertificateSync}.createdAt`, "desc"); + + if (offset !== undefined) { + void query.offset(offset); + } + if (limit !== undefined) { + void query.limit(limit); + } + + const certificateDetails = (await query) as (TCertificateSyncs & { + certificateSerialNumber?: string; + certificateCommonName?: string; + certificateAltNames?: string; + certificateStatus?: string; + certificateNotBefore?: Date; + certificateNotAfter?: Date; + certificateRenewBeforeDays?: number; + certificateRenewedByCertificateId?: string; + certificateRenewalError?: string; + pkiSyncName?: string; + pkiSyncDestination?: string; + })[]; + + return { certificateDetails, totalCount }; + } catch (error) { + throw new DatabaseError({ error, name: "FindWithDetails" }); + } + }; + + return { + ...certificateSyncOrm, + findByPkiSyncId, + findByCertificateId, + findByPkiSyncAndCertificate, + findCertificateIdsByPkiSyncId, + findPkiSyncIdsByCertificateId, + addCertificates, + removeCertificates, + removeAllCertificatesFromSync, + updateSyncStatus, + bulkUpdateSyncStatus, + findWithDetails + }; +}; diff --git a/backend/src/services/certificate-sync/certificate-sync-enums.ts b/backend/src/services/certificate-sync/certificate-sync-enums.ts new file mode 100644 index 000000000..7b9eedafb --- /dev/null +++ b/backend/src/services/certificate-sync/certificate-sync-enums.ts @@ -0,0 +1,7 @@ +export enum CertificateSyncStatus { + Pending = "pending", + Syncing = "syncing", + Succeeded = "succeeded", + Failed = "failed", + Running = "running" +} diff --git a/backend/src/services/certificate-v3/certificate-v3-service.test.ts b/backend/src/services/certificate-v3/certificate-v3-service.test.ts index 0c70571dd..d11cce056 100644 --- a/backend/src/services/certificate-v3/certificate-v3-service.test.ts +++ b/backend/src/services/certificate-v3/certificate-v3-service.test.ts @@ -133,7 +133,18 @@ describe("CertificateV3Service", () => { certificateProfileDAL: mockCertificateProfileDAL, certificateTemplateV2Service: mockCertificateTemplateV2Service, internalCaService: mockInternalCaService, - permissionService: mockPermissionService + permissionService: mockPermissionService, + certificateSyncDAL: { + findPkiSyncIdsByCertificateId: vi.fn().mockResolvedValue([]), + addCertificates: vi.fn().mockResolvedValue([]), + findByPkiSyncAndCertificate: vi.fn().mockResolvedValue(null) + }, + pkiSyncDAL: { + find: vi.fn().mockResolvedValue([]) + }, + pkiSyncQueue: { + queuePkiSyncSyncCertificatesById: vi.fn().mockResolvedValue(undefined) + } }); }); diff --git a/backend/src/services/certificate-v3/certificate-v3-service.ts b/backend/src/services/certificate-v3/certificate-v3-service.ts index 0a721b2db..51c79f135 100644 --- a/backend/src/services/certificate-v3/certificate-v3-service.ts +++ b/backend/src/services/certificate-v3/certificate-v3-service.ts @@ -48,6 +48,10 @@ import { mapEnumsForValidation, normalizeDateForApi } from "../certificate-common/certificate-utils"; +import { TCertificateSyncDALFactory } from "../certificate-sync/certificate-sync-dal"; +import { TPkiSyncDALFactory } from "../pki-sync/pki-sync-dal"; +import { TPkiSyncQueueFactory } from "../pki-sync/pki-sync-queue"; +import { addRenewedCertificateToSyncs, triggerAutoSyncForCertificate } from "../pki-sync/pki-sync-utils"; import { TCertificateFromProfileResponse, TCertificateOrderResponse, @@ -72,6 +76,12 @@ type TCertificateV3ServiceFactoryDep = { >; internalCaService: Pick; permissionService: Pick; + certificateSyncDAL: Pick< + TCertificateSyncDALFactory, + "findPkiSyncIdsByCertificateId" | "addCertificates" | "findByPkiSyncAndCertificate" + >; + pkiSyncDAL: Pick; + pkiSyncQueue: Pick; }; export type TCertificateV3ServiceFactory = ReturnType; @@ -328,7 +338,10 @@ export const certificateV3ServiceFactory = ({ certificateProfileDAL, certificateTemplateV2Service, internalCaService, - permissionService + permissionService, + certificateSyncDAL, + pkiSyncDAL, + pkiSyncQueue }: TCertificateV3ServiceFactoryDep) => { const issueCertificateFromProfile = async ({ profileId, @@ -872,6 +885,8 @@ export const certificateV3ServiceFactory = ({ tx ); + await addRenewedCertificateToSyncs(originalCert.id, newCert.id, { certificateSyncDAL }, tx); + return { certificate, certificateChain, @@ -883,6 +898,12 @@ export const certificateV3ServiceFactory = ({ }; }); + await triggerAutoSyncForCertificate(renewalResult.newCert.id, { + certificateSyncDAL, + pkiSyncDAL, + pkiSyncQueue + }); + return { certificate: renewalResult.certificate, issuingCaCertificate: renewalResult.issuingCaCertificate, diff --git a/backend/src/services/certificate/certificate-dal.ts b/backend/src/services/certificate/certificate-dal.ts index eb40b85a5..7af79319b 100644 --- a/backend/src/services/certificate/certificate-dal.ts +++ b/backend/src/services/certificate/certificate-dal.ts @@ -1,3 +1,5 @@ +import RE2 from "re2"; + import { TDbClient } from "@app/db"; import { TableName, TCertificates } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; @@ -60,11 +62,13 @@ export const certificateDALFactory = (db: TDbClient) => { .where(`${TableName.Project}.id`, projectId); if (friendlyName) { - query = query.andWhere(`${TableName.Certificate}.friendlyName`, friendlyName); + const sanitizedValue = String(friendlyName).replace(new RE2("[%_\\\\]", "g"), "\\$&"); + query = query.andWhere(`${TableName.Certificate}.friendlyName`, "like", `%${sanitizedValue}%`); } if (commonName) { - query = query.andWhere(`${TableName.Certificate}.commonName`, commonName); + const sanitizedValue = String(commonName).replace(new RE2("[%_\\\\]", "g"), "\\$&"); + query = query.andWhere(`${TableName.Certificate}.commonName`, "like", `%${sanitizedValue}%`); } const count = await query.count("*").first(); @@ -114,6 +118,109 @@ export const certificateDALFactory = (db: TDbClient) => { } }; + const findActiveCertificatesByIds = async (certificateIds: string[]): Promise => { + try { + if (certificateIds.length === 0) { + return []; + } + + const certs = await db + .replicaNode()(TableName.Certificate) + .whereIn("id", certificateIds) + .where({ status: CertStatus.ACTIVE }) + .where("notAfter", ">", new Date()) + .orderBy("notBefore", "desc") + .select("*"); + + return certs; + } catch (error) { + throw new DatabaseError({ error, name: "Find active certificates by IDs" }); + } + }; + + const findActiveCertificatesForSync = async ( + filter: Partial, + options?: { limit?: number; offset?: number } + ): Promise<(TCertificates & { hasPrivateKey: boolean })[]> => { + try { + let query = db + .replicaNode()(TableName.Certificate) + .leftJoin(TableName.CertificateSecret, `${TableName.Certificate}.id`, `${TableName.CertificateSecret}.certId`) + .select(selectAllTableCols(TableName.Certificate)) + .select(db.ref(`${TableName.CertificateSecret}.certId`).as("privateKeyRef")) + .where({ status: CertStatus.ACTIVE }) + .where("notAfter", ">", new Date()) + .whereNull("renewedByCertificateId"); + + Object.entries(filter).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + if (key === "friendlyName" || key === "commonName") { + const sanitizedValue = String(value).replace(new RE2("[%_\\\\]", "g"), "\\$&"); + query = query.andWhere(`${TableName.Certificate}.${key}`, "like", `%${sanitizedValue}%`); + } else { + query = query.andWhere(`${TableName.Certificate}.${key}`, value); + } + } + }); + + if (options?.offset) { + query = query.offset(options.offset); + } + + if (options?.limit) { + query = query.limit(options.limit); + } + + query = query.orderBy("createdAt", "desc"); + + const certs = await query; + return certs.map((cert) => ({ ...cert, hasPrivateKey: Boolean(cert.privateKeyRef) })); + } catch (error) { + throw new DatabaseError({ error, name: "Find active certificates for sync" }); + } + }; + + const countActiveCertificatesForSync = async ({ + projectId, + friendlyName, + commonName + }: { + projectId: string; + friendlyName?: string; + commonName?: string; + }) => { + try { + interface CountResult { + count: string; + } + + let query = db + .replicaNode()(TableName.Certificate) + .join(TableName.CertificateAuthority, `${TableName.Certificate}.caId`, `${TableName.CertificateAuthority}.id`) + .join(TableName.Project, `${TableName.CertificateAuthority}.projectId`, `${TableName.Project}.id`) + .where(`${TableName.Project}.id`, projectId) + .where(`${TableName.Certificate}.status`, CertStatus.ACTIVE) + .where(`${TableName.Certificate}.notAfter`, ">", new Date()) + .whereNull(`${TableName.Certificate}.renewedByCertificateId`); + + if (friendlyName) { + const sanitizedValue = String(friendlyName).replace(new RE2("[%_\\\\]", "g"), "\\$&"); + query = query.andWhere(`${TableName.Certificate}.friendlyName`, "like", `%${sanitizedValue}%`); + } + + if (commonName) { + const sanitizedValue = String(commonName).replace(new RE2("[%_\\\\]", "g"), "\\$&"); + query = query.andWhere(`${TableName.Certificate}.commonName`, "like", `%${sanitizedValue}%`); + } + + const count = await query.count("*").first(); + + return parseInt((count as unknown as CountResult).count || "0", 10); + } catch (error) { + throw new DatabaseError({ error, name: "Count active certificates for sync" }); + } + }; + const findCertificatesEligibleForRenewal = async ({ limit, offset @@ -159,7 +266,7 @@ export const certificateDALFactory = (db: TDbClient) => { }; const findWithPrivateKeyInfo = async ( - filter: Partial, + filter: Partial, options?: { offset?: number; limit?: number; sort?: [string, "asc" | "desc"][] } ): Promise<(TCertificates & { hasPrivateKey: boolean })[]> => { try { @@ -167,8 +274,18 @@ export const certificateDALFactory = (db: TDbClient) => { .replicaNode()(TableName.Certificate) .leftJoin(TableName.CertificateSecret, `${TableName.Certificate}.id`, `${TableName.CertificateSecret}.certId`) .select(selectAllTableCols(TableName.Certificate)) - .select(db.ref(`${TableName.CertificateSecret}.certId`).as("privateKeyRef")) - .where(filter); + .select(db.ref(`${TableName.CertificateSecret}.certId`).as("privateKeyRef")); + + Object.entries(filter).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + if (key === "friendlyName" || key === "commonName") { + const sanitizedValue = String(value).replace(new RE2("[%_\\\\]", "g"), "\\$&"); + query = query.andWhere(`${TableName.Certificate}.${key}`, "like", `%${sanitizedValue}%`); + } else { + query = query.andWhere(`${TableName.Certificate}.${key}`, value); + } + } + }); if (options?.offset) { query = query.offset(options.offset); @@ -197,10 +314,13 @@ export const certificateDALFactory = (db: TDbClient) => { return { ...certificateOrm, countCertificatesInProject, + countActiveCertificatesForSync, countCertificatesForPkiSubscriber, findLatestActiveCertForSubscriber, findAllActiveCertsForSubscriber, findExpiredSyncedCertificates, + findActiveCertificatesByIds, + findActiveCertificatesForSync, findCertificatesEligibleForRenewal, findWithPrivateKeyInfo }; diff --git a/backend/src/services/certificate/certificate-service.ts b/backend/src/services/certificate/certificate-service.ts index 91731c387..eb8006f00 100644 --- a/backend/src/services/certificate/certificate-service.ts +++ b/backend/src/services/certificate/certificate-service.ts @@ -18,12 +18,13 @@ import { TCertificateAuthorityDALFactory } from "@app/services/certificate-autho import { CaCapability, CaType } from "@app/services/certificate-authority/certificate-authority-enums"; import { caSupportsCapability } from "@app/services/certificate-authority/certificate-authority-maps"; import { TCertificateAuthoritySecretDALFactory } from "@app/services/certificate-authority/certificate-authority-secret-dal"; +import { TCertificateSyncDALFactory } from "@app/services/certificate-sync/certificate-sync-dal"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TPkiCollectionDALFactory } from "@app/services/pki-collection/pki-collection-dal"; import { TPkiCollectionItemDALFactory } from "@app/services/pki-collection/pki-collection-item-dal"; import { TPkiSyncDALFactory } from "@app/services/pki-sync/pki-sync-dal"; import { TPkiSyncQueueFactory } from "@app/services/pki-sync/pki-sync-queue"; -import { triggerAutoSyncForSubscriber } from "@app/services/pki-sync/pki-sync-utils"; +import { triggerAutoSyncForCertificate } from "@app/services/pki-sync/pki-sync-utils"; import { TProjectDALFactory } from "@app/services/project/project-dal"; import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns"; @@ -57,6 +58,7 @@ type TCertificateServiceFactoryDep = { projectDAL: Pick; kmsService: Pick; permissionService: Pick; + certificateSyncDAL: Pick; pkiSyncDAL: Pick; pkiSyncQueue: Pick; }; @@ -76,6 +78,7 @@ export const certificateServiceFactory = ({ projectDAL, kmsService, permissionService, + certificateSyncDAL, pkiSyncDAL, pkiSyncQueue }: TCertificateServiceFactoryDep) => { @@ -166,10 +169,12 @@ export const certificateServiceFactory = ({ const deletedCert = await certificateDAL.deleteById(cert.id); - // Trigger auto sync for PKI syncs connected to this certificate's subscriber - if (cert.pkiSubscriberId) { - await triggerAutoSyncForSubscriber(cert.pkiSubscriberId, { pkiSyncDAL, pkiSyncQueue }); - } + // Trigger auto sync for PKI syncs connected to this certificate + await triggerAutoSyncForCertificate(cert.id, { + certificateSyncDAL, + pkiSyncDAL, + pkiSyncQueue + }); return { deletedCert @@ -235,10 +240,12 @@ export const certificateServiceFactory = ({ } ); - // Trigger auto sync for PKI syncs connected to this certificate's subscriber - if (cert.pkiSubscriberId) { - await triggerAutoSyncForSubscriber(cert.pkiSubscriberId, { pkiSyncDAL, pkiSyncQueue }); - } + // Trigger auto sync for PKI syncs connected to this certificate + await triggerAutoSyncForCertificate(cert.id, { + certificateSyncDAL, + pkiSyncDAL, + pkiSyncQueue + }); // Note: External CA revocation handling would go here for supported CA types // Currently, only internal CAs and ACME CAs support revocation diff --git a/backend/src/services/identity-token-auth/identity-token-auth-service.ts b/backend/src/services/identity-token-auth/identity-token-auth-service.ts index 2d3e11cd8..e6969da61 100644 --- a/backend/src/services/identity-token-auth/identity-token-auth-service.ts +++ b/backend/src/services/identity-token-auth/identity-token-auth-service.ts @@ -244,8 +244,8 @@ export const identityTokenAuthServiceFactory = ({ } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) { - throw new BadRequestError({ - message: "The identity does not have Token Auth attached" + throw new NotFoundError({ + message: "Token Auth configuration not found for identity" }); } diff --git a/backend/src/services/integration-auth/integration-list.ts b/backend/src/services/integration-auth/integration-list.ts index 0608bbd4b..e4e1d3126 100644 --- a/backend/src/services/integration-auth/integration-list.ts +++ b/backend/src/services/integration-auth/integration-list.ts @@ -104,7 +104,8 @@ export enum IntegrationUrls { GCP_SERVICE_USAGE_URL = "https://serviceusage.googleapis.com", GCP_CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform", - GITHUB_USER_INSTALLATIONS = "https://api.github.com/user/installations" + GITHUB_USER_INSTALLATIONS = "https://api.github.com/user/installations", + CHEF_API_URL = "https://api.chef.io" } export const getIntegrationOptions = async () => { diff --git a/backend/src/services/pki-sync/aws-certificate-manager/aws-certificate-manager-pki-sync-fns.ts b/backend/src/services/pki-sync/aws-certificate-manager/aws-certificate-manager-pki-sync-fns.ts index f78bd790e..3e07420b5 100644 --- a/backend/src/services/pki-sync/aws-certificate-manager/aws-certificate-manager-pki-sync-fns.ts +++ b/backend/src/services/pki-sync/aws-certificate-manager/aws-certificate-manager-pki-sync-fns.ts @@ -3,7 +3,9 @@ import * as AWS from "aws-sdk"; import RE2 from "re2"; import { z } from "zod"; +import { TCertificateSyncs } from "@app/db/schemas"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; +import { logger } from "@app/lib/logger"; import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal"; import { AppConnection, AWSRegion } from "@app/services/app-connection/app-connection-enums"; import { decryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns"; @@ -14,6 +16,9 @@ import { AwsConnectionAssumeRoleCredentialsSchema } from "@app/services/app-connection/aws/aws-connection-schemas"; import { TAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-types"; +import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; +import { TCertificateSyncDALFactory } from "@app/services/certificate-sync/certificate-sync-dal"; +import { CertificateSyncStatus } from "@app/services/certificate-sync/certificate-sync-enums"; import { createConnectionQueue, RateLimitConfig } from "@app/services/connection-queue"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TCertificateMap } from "@app/services/pki-sync/pki-sync-types"; @@ -88,39 +93,6 @@ const shouldSkipCertificateExport = (certificate: AWS.ACM.CertificateSummary): b return isAwsIssuedCertificate(certificate); }; -const findTagByKey = (tags: AWS.ACM.TagList | undefined, key: string): AWS.ACM.Tag | undefined => { - if (!tags || !Array.isArray(tags)) { - return undefined; - } - return tags.find((tag: AWS.ACM.Tag) => tag.Key === key && tag.Value); -}; - -const findInfisicalCertificateTag = (tags: AWS.ACM.TagList | undefined): AWS.ACM.Tag | undefined => { - return findTagByKey(tags, INFISICAL_CERTIFICATE_TAG); -}; - -const validateCertificateIdentification = ( - certName: string, - existingCert: { arn?: string; Tags?: AWS.ACM.TagList; cert?: string; privateKey?: string; certificateChain?: string } -): boolean => { - if (!existingCert?.arn || !existingCert?.Tags) { - return false; - } - - const certNameTag = findInfisicalCertificateTag(existingCert.Tags); - - if (!certNameTag || !certNameTag.Value) { - return false; - } - - return certNameTag.Value === certName; -}; - -type TAwsCertificateManagerPkiSyncFactoryDeps = { - appConnectionDAL: Pick; - kmsService: Pick; -}; - const validateCertificateNameSchema = (schema: string): void => { if (!schema.includes("{{certificateId}}")) { throw new Error( @@ -174,6 +146,21 @@ const generateCertificateName = (certificateName: string, pkiSync: TPkiSyncWithC return sanitizedCertificateName; }; +type TAwsCertificateManagerPkiSyncFactoryDeps = { + appConnectionDAL: Pick; + kmsService: Pick; + certificateSyncDAL: Pick< + TCertificateSyncDALFactory, + | "removeCertificates" + | "addCertificates" + | "findByPkiSyncAndCertificate" + | "updateSyncStatus" + | "updateById" + | "findByPkiSyncId" + >; + certificateDAL: Pick; +}; + const getAwsAcmClient = async ( connectionId: string, region: AWSRegion, @@ -230,7 +217,9 @@ const getAwsAcmClient = async ( export const awsCertificateManagerPkiSyncFactory = ({ kmsService, - appConnectionDAL + appConnectionDAL, + certificateSyncDAL, + certificateDAL }: TAwsCertificateManagerPkiSyncFactoryDeps) => { const deleteCertificateFromAcm = async ( acm: AWS.ACM, @@ -392,79 +381,201 @@ export const awsCertificateManagerPkiSyncFactory = ({ kmsService ); - const { acmCertificates } = await $getAwsAcmCertificates(acm, pkiSync.id); + const { + acmCertificates + }: { + acmCertificates: Record< + string, + { cert: string; privateKey: string; certificateChain?: string; arn?: string; Tags?: AWS.ACM.TagList } + >; + } = await $getAwsAcmCertificates(acm, pkiSync.id); + + const acmCertificatesByArn = new Map(); + Object.values(acmCertificates).forEach((acmCert) => { + if (acmCert.arn) { + acmCertificatesByArn.set(acmCert.arn, acmCert); + } + }); + + const existingSyncRecords = await certificateSyncDAL.findByPkiSyncId(pkiSync.id); + const syncRecordsByCertId = new Map(); + const syncRecordsByExternalId = new Map(); + + existingSyncRecords.forEach((record: TCertificateSyncs) => { + if (record.certificateId) { + syncRecordsByCertId.set(record.certificateId, record); + } + if (record.externalIdentifier) { + syncRecordsByExternalId.set(record.externalIdentifier, record); + } + }); const setCertificates: CertificateImportRequest[] = []; + const validationErrors: Array<{ name: string; error: string }> = []; - const activeCertificateNames = Object.keys(certificateMap); + const syncOptions = pkiSync.syncOptions as { preserveArn?: boolean; canRemoveCertificates?: boolean } | undefined; + const preserveArn = syncOptions?.preserveArn ?? true; + const canRemoveCertificates = syncOptions?.canRemoveCertificates ?? true; - Object.entries(certificateMap).forEach(([certName, certData]) => { - const { cert, privateKey, certificateChain } = certData; - const certificateName = generateCertificateName(certName, pkiSync); + const activeExternalIdentifiers = new Set(); - const existingCert = Object.values(acmCertificates).find((acmCert) => - validateCertificateIdentification(certName, acmCert) - ); - - const shouldUpdateCert = !existingCert || existingCert.cert !== cert; + for (const [certName, certData] of Object.entries(certificateMap)) { + const { cert, privateKey, certificateChain, certificateId } = certData; try { validateCertificateContent(cert, privateKey); } catch (validationError) { - throw new PkiSyncError({ - message: `Certificate validation failed for ${certName}: ${validationError instanceof Error ? validationError.message : String(validationError)}`, - shouldRetry: false, - context: { - certificateName, - certName - } + const errorMessage = validationError instanceof Error ? validationError.message : String(validationError); + validationErrors.push({ + name: certName, + error: `Certificate validation failed: ${errorMessage}` }); + // eslint-disable-next-line no-continue + continue; } - if (shouldUpdateCert) { + if (preserveArn && certificateId && typeof certificateId === "string") { + const certificate = await certificateDAL.findById(certificateId); + if (certificate?.renewedByCertificateId) { + // eslint-disable-next-line no-continue + continue; + } + } + + const certificateName = generateCertificateName(certName, pkiSync); + + let targetArn: string | undefined; + let shouldCreateNew = false; + + if (!certificateId || typeof certificateId !== "string") { + shouldCreateNew = true; + } else { + const currentCertificate = await certificateDAL.findById(certificateId); + const isRenewal = !!currentCertificate?.renewedFromCertificateId; + + if (isRenewal) { + const currentSyncRecord = syncRecordsByCertId.get(certificateId); + const oldCertificateId = currentCertificate.renewedFromCertificateId; + const oldSyncRecord = oldCertificateId ? syncRecordsByCertId.get(oldCertificateId) : undefined; + + if (currentSyncRecord?.externalIdentifier) { + const existingAcmCert = acmCertificatesByArn.get(currentSyncRecord.externalIdentifier); + + if (existingAcmCert) { + if (!preserveArn && oldSyncRecord?.externalIdentifier === currentSyncRecord.externalIdentifier) { + shouldCreateNew = true; + } else if (preserveArn && oldSyncRecord?.externalIdentifier === currentSyncRecord.externalIdentifier) { + targetArn = currentSyncRecord.externalIdentifier; + shouldCreateNew = true; + activeExternalIdentifiers.add(targetArn); + + if (oldCertificateId && oldSyncRecord) { + await certificateSyncDAL.removeCertificates(pkiSync.id, [oldCertificateId]); + } + } else { + targetArn = currentSyncRecord.externalIdentifier; + activeExternalIdentifiers.add(targetArn); + shouldCreateNew = false; + } + } else { + shouldCreateNew = true; + } + } else if (preserveArn && oldSyncRecord?.externalIdentifier) { + const existingAcmCert = acmCertificatesByArn.get(oldSyncRecord.externalIdentifier); + + if (existingAcmCert) { + targetArn = oldSyncRecord.externalIdentifier; + shouldCreateNew = true; + activeExternalIdentifiers.add(targetArn); + if (oldCertificateId) { + await certificateSyncDAL.removeCertificates(pkiSync.id, [oldCertificateId]); + } + } else { + shouldCreateNew = true; + } + } else { + shouldCreateNew = true; + } + } else { + const existingSyncRecord = syncRecordsByCertId.get(certificateId); + if (existingSyncRecord?.externalIdentifier) { + const existingAcmCert = acmCertificatesByArn.get(existingSyncRecord.externalIdentifier); + if (existingAcmCert) { + targetArn = existingSyncRecord.externalIdentifier; + activeExternalIdentifiers.add(targetArn); + shouldCreateNew = false; + } else { + shouldCreateNew = true; + } + } else { + shouldCreateNew = true; + } + } + } + + if (shouldCreateNew) { setCertificates.push({ key: certName, name: certificateName, cert, privateKey, certificateChain, - existingArn: existingCert?.arn + existingArn: targetArn, + certificateId: certificateId as string }); } - }); - // Identify expired/removed certificates that need to be cleaned up from ACM - const certificatesToRemove = Object.values(acmCertificates) - .filter((acmCert) => { - if (!acmCert.arn || !acmCert.Tags) { - return false; + if (targetArn) { + activeExternalIdentifiers.add(targetArn); + } + } + + const certificatesToRemove: string[] = []; + + if (canRemoveCertificates) { + existingSyncRecords.forEach((syncRecord) => { + if (syncRecord.externalIdentifier && !activeExternalIdentifiers.has(syncRecord.externalIdentifier)) { + const acmCert = acmCertificatesByArn.get(syncRecord.externalIdentifier); + if (acmCert?.arn) { + certificatesToRemove.push(acmCert.arn); + } } + }); - const certNameTag = findInfisicalCertificateTag(acmCert.Tags); - if (!certNameTag || !certNameTag.Value) { - return false; + Object.values(acmCertificates).forEach((acmCert) => { + if (acmCert.arn && acmCert.Tags) { + const hasInfisicalTag = acmCert.Tags.some((tag) => tag.Key === INFISICAL_CERTIFICATE_TAG && tag.Value); + + if (hasInfisicalTag) { + const isTrackedInSyncRecords = existingSyncRecords.some( + (record) => record.externalIdentifier === acmCert.arn + ); + const isInActiveSet = activeExternalIdentifiers.has(acmCert.arn); + if (!isTrackedInSyncRecords && !isInActiveSet && !certificatesToRemove.includes(acmCert.arn)) { + certificatesToRemove.push(acmCert.arn); + } + } } - - const isActive = activeCertificateNames.includes(certNameTag.Value); - return !isActive; - }) - .map((acmCert) => acmCert.arn!) - .filter((arn) => arn); + }); + } const uploadResults = await executeWithConcurrencyLimit( setCertificates, - async ({ key, name, cert, privateKey, certificateChain, existingArn }) => { + async ({ key, name, cert, privateKey, certificateChain, existingArn, certificateId }) => { try { const importParams: AWS.ACM.ImportCertificateRequest = { Certificate: cert, - PrivateKey: privateKey, - Tags: [ + PrivateKey: privateKey + }; + + if (!existingArn) { + importParams.Tags = [ { Key: INFISICAL_CERTIFICATE_TAG, Value: key } - ] - }; + ]; + } if (certificateChain && certificateChain.trim().length > 0) { importParams.CertificateChain = certificateChain; @@ -478,6 +589,57 @@ export const awsCertificateManagerPkiSyncFactory = ({ syncId: pkiSync.id }); + if (existingArn && response.CertificateArn) { + try { + // Small delay to ensure AWS ACM has processed the certificate import + await new Promise((resolve) => { + setTimeout(() => resolve(), 500); + }); + + await withRateLimitRetry( + () => + acm + .addTagsToCertificate({ + CertificateArn: response.CertificateArn!, + Tags: [ + { + Key: INFISICAL_CERTIFICATE_TAG, + Value: key + } + ] + }) + .promise(), + { + operation: "add-tags-to-certificate", + syncId: pkiSync.id + } + ); + } catch (tagError) { + const errorMessage = tagError instanceof Error ? tagError.message : "Unknown tagging error"; + logger.warn( + `Failed to add tags to certificate ${key} (ARN: ${response.CertificateArn}): ${errorMessage}` + ); + } + } + + if (response.CertificateArn && certificateId) { + const existingCertSync = await certificateSyncDAL.findByPkiSyncAndCertificate(pkiSync.id, certificateId); + if (existingCertSync) { + await certificateSyncDAL.updateById(existingCertSync.id, { + externalIdentifier: response.CertificateArn, + syncStatus: CertificateSyncStatus.Succeeded, + lastSyncedAt: new Date() + }); + } else { + await certificateSyncDAL.addCertificates(pkiSync.id, [ + { + certificateId, + externalIdentifier: response.CertificateArn + } + ]); + } + } + return { key, name, success: true, response }; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; @@ -520,15 +682,21 @@ export const awsCertificateManagerPkiSyncFactory = ({ const details: { failedUploads?: Array<{ name: string; error: string }>; failedRemovals?: Array<{ name: string; error: string }>; + validationErrors?: Array<{ name: string; error: string }>; } = {}; + if (validationErrors.length > 0) { + details.validationErrors = validationErrors; + } + if (failedUploads.length > 0) { details.failedUploads = failedUploads.map((failure, index) => { - const certificateName = setCertificates[index]?.name || "unknown"; + const certificateRequest = setCertificates[index]; + const certificateName = certificateRequest?.name || certificateRequest?.key || "unknown"; let errorMessage = "Unknown error"; if (failure.status === "rejected") { - errorMessage = failure.reason instanceof Error ? failure.reason.message : "Unknown error"; + errorMessage = failure.reason instanceof Error ? failure.reason.message : String(failure.reason); } return { @@ -567,7 +735,8 @@ export const awsCertificateManagerPkiSyncFactory = ({ const removeCertificates = async ( pkiSync: TPkiSyncWithCredentials, - certificateNames: string[] + certificateNames: string[], + deps?: { certificateSyncDAL?: TCertificateSyncDALFactory; certificateMap?: TCertificateMap } ): Promise => { const destinationConfig = pkiSync.destinationConfig as TAwsCertificateManagerPkiSyncConfig; const acm = await getAwsAcmClient( @@ -577,22 +746,33 @@ export const awsCertificateManagerPkiSyncFactory = ({ kmsService ); - const { acmCertificates } = await $getAwsAcmCertificates(acm, pkiSync.id); - + const existingSyncRecords = await certificateSyncDAL.findByPkiSyncId(pkiSync.id); const certificateArnsToRemove: string[] = []; - + const certificateIdToArnMap = new Map(); for (const certName of certificateNames) { - const matchingCerts = Object.values(acmCertificates).filter((acmCert) => - validateCertificateIdentification(certName, acmCert) - ); + const certificateData = deps?.certificateMap?.[certName]; + if (certificateData?.certificateId) { + const { certificateId } = certificateData; - for (const acmCert of matchingCerts) { - if (acmCert.arn) { - certificateArnsToRemove.push(acmCert.arn); + if (typeof certificateId === "string") { + const syncRecord = existingSyncRecords.find((record) => record.certificateId === certificateId); + + if (syncRecord?.externalIdentifier) { + certificateArnsToRemove.push(syncRecord.externalIdentifier); + certificateIdToArnMap.set(certificateId, syncRecord.externalIdentifier); + } } } } + if (certificateArnsToRemove.length === 0) { + return { + removed: 0, + failed: 0, + skipped: certificateNames.length + }; + } + const results = await executeWithConcurrencyLimit( certificateArnsToRemove, async (certificateArn) => @@ -602,6 +782,38 @@ export const awsCertificateManagerPkiSyncFactory = ({ const failedRemovals = results.filter((result) => result.status === "rejected"); + if (failedRemovals.length > 0 && deps?.certificateSyncDAL) { + for (const failure of failedRemovals) { + if (failure.status === "rejected") { + const failedArn = certificateArnsToRemove[results.indexOf(failure)]; + const certificateId = Array.from(certificateIdToArnMap.entries()).find(([, arn]) => arn === failedArn)?.[0]; + + if (certificateId) { + const errorMessage = failure.reason instanceof Error ? failure.reason.message : "Unknown error"; + await deps.certificateSyncDAL.updateSyncStatus( + pkiSync.id, + certificateId, + CertificateSyncStatus.Failed, + `Failed to remove from AWS: ${errorMessage}` + ); + } + } + } + } + + const successfulRemovals = results.filter((result) => result.status === "fulfilled"); + if (successfulRemovals.length > 0) { + const successfulArns = new Set(successfulRemovals.map((_, index) => certificateArnsToRemove[index])); + + const certificateIdsToRemove = Array.from(certificateIdToArnMap.entries()) + .filter(([, arn]) => successfulArns.has(arn)) + .map(([certificateId]) => certificateId); + + if (certificateIdsToRemove.length > 0) { + await certificateSyncDAL.removeCertificates(pkiSync.id, certificateIdsToRemove); + } + } + if (failedRemovals.length > 0) { const failedReasons = failedRemovals.map((failure) => { if (failure.status === "rejected") { diff --git a/backend/src/services/pki-sync/aws-certificate-manager/aws-certificate-manager-pki-sync-schemas.ts b/backend/src/services/pki-sync/aws-certificate-manager/aws-certificate-manager-pki-sync-schemas.ts index eb9ae5444..3b9f5c881 100644 --- a/backend/src/services/pki-sync/aws-certificate-manager/aws-certificate-manager-pki-sync-schemas.ts +++ b/backend/src/services/pki-sync/aws-certificate-manager/aws-certificate-manager-pki-sync-schemas.ts @@ -14,6 +14,7 @@ export const AwsCertificateManagerPkiSyncConfigSchema = z.object({ const AwsCertificateManagerPkiSyncOptionsSchema = z.object({ canImportCertificates: z.boolean().default(false), canRemoveCertificates: z.boolean().default(true), + preserveArn: z.boolean().default(true), certificateNameSchema: z .string() .optional() @@ -28,6 +29,9 @@ const AwsCertificateManagerPkiSyncOptionsSchema = z.object({ const testName = schema .replace(new RE2("\\{\\{certificateId\\}\\}", "g"), "test-cert-id") + .replace(new RE2("\\{\\{profileId\\}\\}", "g"), "test-profile-id") + .replace(new RE2("\\{\\{commonName\\}\\}", "g"), "test-common-name") + .replace(new RE2("\\{\\{friendlyName\\}\\}", "g"), "test-friendly-name") .replace(new RE2("\\{\\{environment\\}\\}", "g"), "test-env"); const hasForbiddenChars = AWS_CERTIFICATE_MANAGER_CERTIFICATE_NAMING.FORBIDDEN_CHARACTERS.split("").some( @@ -43,7 +47,7 @@ const AwsCertificateManagerPkiSyncOptionsSchema = z.object({ }, { message: - "Certificate name schema must include {{certificateId}} placeholder and result in names that contain only alphanumeric characters, spaces, hyphens, and underscores and be 1-256 characters long when compiled for AWS Certificate Manager" + "Certificate name schema must include {{certificateId}} placeholder and result in names that contain only alphanumeric characters, spaces, hyphens, and underscores and be 1-256 characters long when compiled for AWS Certificate Manager. Available placeholders: {{certificateId}}, {{profileId}}, {{commonName}}, {{friendlyName}}, {{environment}}" } ) }); @@ -60,9 +64,10 @@ export const CreateAwsCertificateManagerPkiSyncSchema = z.object({ isAutoSyncEnabled: z.boolean().default(true), destinationConfig: AwsCertificateManagerPkiSyncConfigSchema, syncOptions: AwsCertificateManagerPkiSyncOptionsSchema.optional().default({}), - subscriberId: z.string().optional(), + subscriberId: z.string().nullish(), connectionId: z.string(), - projectId: z.string().trim().min(1) + projectId: z.string().trim().min(1), + certificateIds: z.array(z.string().uuid()).optional() }); export const UpdateAwsCertificateManagerPkiSyncSchema = z.object({ @@ -71,7 +76,7 @@ export const UpdateAwsCertificateManagerPkiSyncSchema = z.object({ isAutoSyncEnabled: z.boolean().optional(), destinationConfig: AwsCertificateManagerPkiSyncConfigSchema.optional(), syncOptions: AwsCertificateManagerPkiSyncOptionsSchema.optional(), - subscriberId: z.string().optional(), + subscriberId: z.string().nullish(), connectionId: z.string().optional() }); diff --git a/backend/src/services/pki-sync/aws-certificate-manager/aws-certificate-manager-pki-sync-types.ts b/backend/src/services/pki-sync/aws-certificate-manager/aws-certificate-manager-pki-sync-types.ts index 717e86438..8b2b8b87e 100644 --- a/backend/src/services/pki-sync/aws-certificate-manager/aws-certificate-manager-pki-sync-types.ts +++ b/backend/src/services/pki-sync/aws-certificate-manager/aws-certificate-manager-pki-sync-types.ts @@ -39,6 +39,7 @@ export interface SyncCertificatesResult { details?: { failedUploads?: Array<{ name: string; error: string }>; failedRemovals?: Array<{ name: string; error: string }>; + validationErrors?: Array<{ name: string; error: string }>; }; } @@ -55,4 +56,5 @@ export interface CertificateImportRequest { privateKey: string; certificateChain?: string; existingArn?: string; + certificateId?: string; } diff --git a/backend/src/services/pki-sync/azure-key-vault/azure-key-vault-pki-sync-fns.ts b/backend/src/services/pki-sync/azure-key-vault/azure-key-vault-pki-sync-fns.ts index b423f42db..765d0b8ba 100644 --- a/backend/src/services/pki-sync/azure-key-vault/azure-key-vault-pki-sync-fns.ts +++ b/backend/src/services/pki-sync/azure-key-vault/azure-key-vault-pki-sync-fns.ts @@ -2,10 +2,14 @@ import { AxiosError } from "axios"; import * as crypto from "crypto"; +import { TCertificateSyncs } from "@app/db/schemas"; import { request } from "@app/lib/config/request"; import { logger } from "@app/lib/logger"; import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal"; import { getAzureConnectionAccessToken } from "@app/services/app-connection/azure-key-vault"; +import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; +import { TCertificateSyncDALFactory } from "@app/services/certificate-sync/certificate-sync-dal"; +import { CertificateSyncStatus } from "@app/services/certificate-sync/certificate-sync-enums"; import { createConnectionQueue, RateLimitConfig } from "@app/services/connection-queue"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { matchesCertificateNameSchema } from "@app/services/pki-sync/pki-sync-fns"; @@ -32,7 +36,9 @@ const extractCertificateNameFromId = (certificateId: string): string => { }; const isInfisicalManagedCertificate = (certificateName: string, pkiSync: TPkiSyncWithCredentials): boolean => { - const syncOptions = pkiSync.syncOptions as { certificateNameSchema?: string } | undefined; + const syncOptions = pkiSync.syncOptions as + | { certificateNameSchema?: string; canRemoveCertificates?: boolean } + | undefined; const certificateNameSchema = syncOptions?.certificateNameSchema; if (certificateNameSchema) { @@ -46,6 +52,16 @@ const isInfisicalManagedCertificate = (certificateName: string, pkiSync: TPkiSyn type TAzureKeyVaultPkiSyncFactoryDeps = { appConnectionDAL: Pick; kmsService: Pick; + certificateSyncDAL: Pick< + TCertificateSyncDALFactory, + | "removeCertificates" + | "addCertificates" + | "findByPkiSyncAndCertificate" + | "updateById" + | "findByPkiSyncId" + | "updateSyncStatus" + >; + certificateDAL: Pick; }; const parseCertificateX509Props = (certPem: string) => { @@ -188,7 +204,12 @@ const parseCertificateKeyProps = (certPem: string) => { } }; -export const azureKeyVaultPkiSyncFactory = ({ kmsService, appConnectionDAL }: TAzureKeyVaultPkiSyncFactoryDeps) => { +export const azureKeyVaultPkiSyncFactory = ({ + kmsService, + appConnectionDAL, + certificateSyncDAL, + certificateDAL +}: TAzureKeyVaultPkiSyncFactoryDeps) => { const $getAzureKeyVaultCertificates = async (accessToken: string, vaultBaseUrl: string, syncId = "unknown") => { const paginateAzureKeyVaultCertificates = async () => { let result: GetAzureKeyVaultCertificate[] = []; @@ -325,48 +346,126 @@ export const azureKeyVaultPkiSyncFactory = ({ kmsService, appConnectionDAL }: TA pkiSync.id ); + const existingSyncRecords = await certificateSyncDAL.findByPkiSyncId(pkiSync.id); + const syncRecordsByCertId = new Map(); + const syncRecordsByExternalId = new Map(); + + existingSyncRecords.forEach((record: TCertificateSyncs) => { + if (record.certificateId) { + syncRecordsByCertId.set(record.certificateId, record); + } + if (record.externalIdentifier) { + syncRecordsByExternalId.set(record.externalIdentifier, record); + } + }); + const setCertificates: { key: string; cert: string; privateKey: string; certificateChain?: string; + certificateId?: string; }[] = []; - // Track which certificates should exist in Azure Key Vault - const activeCertificateNames = Object.keys(certificateMap); + const syncOptions = pkiSync.syncOptions as + | { certificateNameSchema?: string; canRemoveCertificates?: boolean; enableVersioning?: boolean } + | undefined; + const canRemoveCertificates = syncOptions?.canRemoveCertificates ?? true; + const enableVersioning = syncOptions?.enableVersioning ?? true; + + const activeExternalIdentifiers = new Set(); // Iterate through certificates to sync to Azure Key Vault - Object.entries(certificateMap).forEach(([certName, { cert, privateKey, certificateChain }]) => { + for (const [certName, { cert, privateKey, certificateChain, certificateId }] of Object.entries(certificateMap)) { if (disabledAzureKeyVaultCertificateKeys.includes(certName)) { - return; + // eslint-disable-next-line no-continue + continue; } - const existingCert = vaultCertificates[certName]; - const shouldUpdateCert = !existingCert || existingCert.cert !== cert; + if (enableVersioning && typeof certificateId === "string") { + const certificate = await certificateDAL.findById(certificateId); + if (certificate?.renewedByCertificateId) { + // eslint-disable-next-line no-continue + continue; + } + } - if (shouldUpdateCert) { + let targetCertName = certName; + let shouldCreateNew = false; + + if (typeof certificateId === "string") { + const existingSyncRecord = syncRecordsByCertId.get(certificateId); + + if (existingSyncRecord?.externalIdentifier) { + const existingAzureCert = vaultCertificates[existingSyncRecord.externalIdentifier]; + + if (existingAzureCert && enableVersioning) { + targetCertName = existingSyncRecord.externalIdentifier; + activeExternalIdentifiers.add(targetCertName); + + const shouldUpdateCert = existingAzureCert.cert !== cert; + if (shouldUpdateCert) { + shouldCreateNew = true; + } + } else if (!existingAzureCert) { + shouldCreateNew = true; + } else if (!enableVersioning) { + shouldCreateNew = true; + } + } else { + shouldCreateNew = true; + } + } else { + shouldCreateNew = true; + } + + if (shouldCreateNew || !vaultCertificates[targetCertName] || vaultCertificates[targetCertName].cert !== cert) { setCertificates.push({ - key: certName, + key: targetCertName, cert, privateKey, - certificateChain + certificateChain, + certificateId }); } - }); - // Identify expired/removed certificates that need to be cleaned up from Azure Key Vault - // Only remove certificates that were managed by Infisical (match naming schema) - const certificatesToRemove = Object.keys(vaultCertificates).filter( - (vaultCertName) => - isInfisicalManagedCertificate(vaultCertName, pkiSync) && - !activeCertificateNames.includes(vaultCertName) && - !disabledAzureKeyVaultCertificateKeys.includes(vaultCertName) - ); + if (targetCertName) { + activeExternalIdentifiers.add(targetCertName); + } + } + + const certificatesToRemove: string[] = []; + + if (canRemoveCertificates) { + existingSyncRecords.forEach((syncRecord) => { + if (syncRecord.externalIdentifier && !activeExternalIdentifiers.has(syncRecord.externalIdentifier)) { + if (vaultCertificates[syncRecord.externalIdentifier]) { + certificatesToRemove.push(syncRecord.externalIdentifier); + } + } + }); + + Object.keys(vaultCertificates).forEach((certificateName) => { + const isInfisicalManaged = isInfisicalManagedCertificate(certificateName, pkiSync); + + if (isInfisicalManaged) { + const isTrackedInSyncRecords = existingSyncRecords.some( + (record) => record.externalIdentifier === certificateName + ); + + const isInActiveSet = activeExternalIdentifiers.has(certificateName); + + if (!isTrackedInSyncRecords && !isInActiveSet && !certificatesToRemove.includes(certificateName)) { + certificatesToRemove.push(certificateName); + } + } + }); + } // Upload certificates to Azure Key Vault with rate limiting const uploadResults = await executeWithConcurrencyLimit( setCertificates, - async ({ key, cert, privateKey, certificateChain }) => { + async ({ key, cert, privateKey, certificateChain, certificateId }) => { try { // Combine private key, certificate, and certificate chain in PEM format for Azure Key Vault let combinedPem = ""; @@ -428,6 +527,31 @@ export const azureKeyVaultPkiSyncFactory = ({ kmsService, appConnectionDAL }: TA } ); + if (certificateId) { + const existingCertSync = await certificateSyncDAL.findByPkiSyncAndCertificate(pkiSync.id, certificateId); + if (existingCertSync) { + await certificateSyncDAL.updateById(existingCertSync.id, { + externalIdentifier: key, + syncStatus: CertificateSyncStatus.Succeeded, + lastSyncedAt: new Date() + }); + } else { + await certificateSyncDAL.addCertificates(pkiSync.id, [ + { + certificateId, + externalIdentifier: key + } + ]); + } + + if (enableVersioning) { + const currentCertificate = await certificateDAL.findById(certificateId); + if (currentCertificate?.renewedFromCertificateId) { + await certificateSyncDAL.removeCertificates(pkiSync.id, [currentCertificate.renewedFromCertificateId]); + } + } + } + return { key, success: true, response: response.data as unknown }; } catch (error) { if (error instanceof AxiosError) { @@ -599,19 +723,43 @@ export const azureKeyVaultPkiSyncFactory = ({ kmsService, appConnectionDAL }: TA }; }; - const removeCertificates = async (pkiSync: TPkiSyncWithCredentials, certificateNames: string[]) => { + const removeCertificates = async ( + pkiSync: TPkiSyncWithCredentials, + certificateNames: string[], + deps?: { certificateSyncDAL?: TCertificateSyncDALFactory; certificateMap?: TCertificateMap } + ) => { const { accessToken } = await getAzureConnectionAccessToken(pkiSync.connection.id, appConnectionDAL, kmsService); // Cast destination config to Azure Key Vault config const destinationConfig = pkiSync.destinationConfig as TAzureKeyVaultPkiSyncConfig; - // Only remove certificates that are managed by Infisical (match naming schema) - const infisicalManagedCertNames = certificateNames.filter((certName) => - isInfisicalManagedCertificate(certName, pkiSync) - ); + const existingSyncRecords = await certificateSyncDAL.findByPkiSyncId(pkiSync.id); + const certificateNamesToRemove: string[] = []; + const certificateIdToNameMap = new Map(); + + for (const certName of certificateNames) { + if (deps?.certificateMap?.[certName]?.certificateId) { + const { certificateId } = deps.certificateMap[certName]; + + const syncRecord = existingSyncRecords.find((record) => record.certificateId === certificateId); + + if (syncRecord?.externalIdentifier && typeof certificateId === "string") { + certificateNamesToRemove.push(syncRecord.externalIdentifier); + certificateIdToNameMap.set(certificateId, syncRecord.externalIdentifier); + } + } + } + + if (certificateNamesToRemove.length === 0) { + return { + removed: 0, + failed: 0, + skipped: certificateNames.length + }; + } const results = await executeWithConcurrencyLimit( - infisicalManagedCertNames, + certificateNamesToRemove, async (certName) => { try { const response = await request.delete( @@ -646,8 +794,44 @@ export const azureKeyVaultPkiSyncFactory = ({ kmsService, appConnectionDAL }: TA }, { operation: "remove-specific-certificates", syncId: pkiSync.id } ); + const failedRemovals = results.filter((result) => result.status === "rejected"); + if (failedRemovals.length > 0 && deps?.certificateSyncDAL) { + for (const failure of failedRemovals) { + if (failure.status === "rejected") { + const failedCertName = certificateNamesToRemove[results.indexOf(failure)]; + + const certificateId = Array.from(certificateIdToNameMap.entries()).find( + ([, name]) => name === failedCertName + )?.[0]; + + if (certificateId) { + const errorMessage = (failure.reason as Error)?.message || "Unknown error"; + await deps.certificateSyncDAL.updateSyncStatus( + pkiSync.id, + certificateId, + CertificateSyncStatus.Failed, + `Failed to remove from Azure: ${errorMessage}` + ); + } + } + } + } + + const successfulRemovals = results.filter((result) => result.status === "fulfilled"); + if (successfulRemovals.length > 0) { + const successfulCertNames = new Set(successfulRemovals.map((_, index) => certificateNamesToRemove[index])); + + const certificateIdsToRemove = Array.from(certificateIdToNameMap.entries()) + .filter(([, name]) => successfulCertNames.has(name)) + .map(([certificateId]) => certificateId); + + if (certificateIdsToRemove.length > 0) { + await certificateSyncDAL.removeCertificates(pkiSync.id, certificateIdsToRemove); + } + } + if (failedRemovals.length > 0) { const failedReasons = failedRemovals.map((failure) => { if (failure.status === "rejected") { @@ -660,16 +844,16 @@ export const azureKeyVaultPkiSyncFactory = ({ kmsService, appConnectionDAL }: TA message: `Failed to remove ${failedRemovals.length} certificate(s) from Azure Key Vault`, context: { failedReasons, - totalCertificates: infisicalManagedCertNames.length, + totalCertificates: certificateNamesToRemove.length, failedCount: failedRemovals.length } }); } return { - removed: infisicalManagedCertNames.length - failedRemovals.length, + removed: certificateNamesToRemove.length - failedRemovals.length, failed: failedRemovals.length, - skipped: certificateNames.length - infisicalManagedCertNames.length + skipped: certificateNames.length - certificateNamesToRemove.length }; }; diff --git a/backend/src/services/pki-sync/azure-key-vault/azure-key-vault-pki-sync-schemas.ts b/backend/src/services/pki-sync/azure-key-vault/azure-key-vault-pki-sync-schemas.ts index ef6347e82..90f4a119b 100644 --- a/backend/src/services/pki-sync/azure-key-vault/azure-key-vault-pki-sync-schemas.ts +++ b/backend/src/services/pki-sync/azure-key-vault/azure-key-vault-pki-sync-schemas.ts @@ -14,6 +14,7 @@ export const AzureKeyVaultPkiSyncConfigSchema = z.object({ const AzureKeyVaultPkiSyncOptionsSchema = z.object({ canImportCertificates: z.boolean().default(false), canRemoveCertificates: z.boolean().default(true), + enableVersioning: z.boolean().default(true), certificateNameSchema: z .string() .optional() @@ -50,9 +51,10 @@ export const CreateAzureKeyVaultPkiSyncSchema = z.object({ isAutoSyncEnabled: z.boolean().default(true), destinationConfig: AzureKeyVaultPkiSyncConfigSchema, syncOptions: AzureKeyVaultPkiSyncOptionsSchema.optional().default({}), - subscriberId: z.string().optional(), + subscriberId: z.string().nullish(), connectionId: z.string(), - projectId: z.string().trim().min(1) + projectId: z.string().trim().min(1), + certificateIds: z.array(z.string().uuid()).optional() }); export const UpdateAzureKeyVaultPkiSyncSchema = z.object({ @@ -61,7 +63,7 @@ export const UpdateAzureKeyVaultPkiSyncSchema = z.object({ isAutoSyncEnabled: z.boolean().optional(), destinationConfig: AzureKeyVaultPkiSyncConfigSchema.optional(), syncOptions: AzureKeyVaultPkiSyncOptionsSchema.optional(), - subscriberId: z.string().optional(), + subscriberId: z.string().nullish(), connectionId: z.string().optional() }); diff --git a/backend/src/services/pki-sync/pki-sync-fns.ts b/backend/src/services/pki-sync/pki-sync-fns.ts index 75f312fff..961687f85 100644 --- a/backend/src/services/pki-sync/pki-sync-fns.ts +++ b/backend/src/services/pki-sync/pki-sync-fns.ts @@ -4,6 +4,8 @@ import { z, ZodSchema } from "zod"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { BadRequestError } from "@app/lib/errors"; import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal"; +import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; +import { TCertificateSyncDALFactory } from "@app/services/certificate-sync/certificate-sync-dal"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { AWS_CERTIFICATE_MANAGER_PKI_SYNC_LIST_OPTION } from "./aws-certificate-manager/aws-certificate-manager-pki-sync-constants"; @@ -184,6 +186,8 @@ export const PkiSyncFns = { dependencies: { appConnectionDAL: Pick; kmsService: Pick; + certificateDAL: TCertificateDALFactory; + certificateSyncDAL: TCertificateSyncDALFactory; } ): Promise<{ uploaded: number; @@ -194,17 +198,28 @@ export const PkiSyncFns = { failedUploads?: Array<{ name: string; error: string }>; failedRemovals?: Array<{ name: string; error: string }>; skippedCertificates?: Array<{ name: string; reason: string }>; + validationErrors?: Array<{ name: string; error: string }>; }; }> => { switch (pkiSync.destination) { case PkiSync.AzureKeyVault: { checkPkiSyncDestination(pkiSync, PkiSync.AzureKeyVault); - const azureKeyVaultPkiSync = azureKeyVaultPkiSyncFactory(dependencies); + const azureKeyVaultPkiSync = azureKeyVaultPkiSyncFactory({ + appConnectionDAL: dependencies.appConnectionDAL, + kmsService: dependencies.kmsService, + certificateDAL: dependencies.certificateDAL, + certificateSyncDAL: dependencies.certificateSyncDAL + }); return azureKeyVaultPkiSync.syncCertificates(pkiSync, certificateMap); } case PkiSync.AwsCertificateManager: { checkPkiSyncDestination(pkiSync, PkiSync.AwsCertificateManager); - const awsCertificateManagerPkiSync = awsCertificateManagerPkiSyncFactory(dependencies); + const awsCertificateManagerPkiSync = awsCertificateManagerPkiSyncFactory({ + appConnectionDAL: dependencies.appConnectionDAL, + kmsService: dependencies.kmsService, + certificateDAL: dependencies.certificateDAL, + certificateSyncDAL: dependencies.certificateSyncDAL + }); return awsCertificateManagerPkiSync.syncCertificates(pkiSync, certificateMap); } default: @@ -218,19 +233,38 @@ export const PkiSyncFns = { dependencies: { appConnectionDAL: Pick; kmsService: Pick; + certificateSyncDAL: TCertificateSyncDALFactory; + certificateDAL: TCertificateDALFactory; + certificateMap: TCertificateMap; } ): Promise => { switch (pkiSync.destination) { case PkiSync.AzureKeyVault: { checkPkiSyncDestination(pkiSync, PkiSync.AzureKeyVault); - const azureKeyVaultPkiSync = azureKeyVaultPkiSyncFactory(dependencies); - await azureKeyVaultPkiSync.removeCertificates(pkiSync, certificateNames); + const azureKeyVaultPkiSync = azureKeyVaultPkiSyncFactory({ + appConnectionDAL: dependencies.appConnectionDAL, + kmsService: dependencies.kmsService, + certificateDAL: dependencies.certificateDAL, + certificateSyncDAL: dependencies.certificateSyncDAL + }); + await azureKeyVaultPkiSync.removeCertificates(pkiSync, certificateNames, { + certificateSyncDAL: dependencies.certificateSyncDAL, + certificateMap: dependencies.certificateMap + }); break; } case PkiSync.AwsCertificateManager: { checkPkiSyncDestination(pkiSync, PkiSync.AwsCertificateManager); - const awsCertificateManagerPkiSync = awsCertificateManagerPkiSyncFactory(dependencies); - await awsCertificateManagerPkiSync.removeCertificates(pkiSync, certificateNames); + const awsCertificateManagerPkiSync = awsCertificateManagerPkiSyncFactory({ + appConnectionDAL: dependencies.appConnectionDAL, + kmsService: dependencies.kmsService, + certificateDAL: dependencies.certificateDAL, + certificateSyncDAL: dependencies.certificateSyncDAL + }); + await awsCertificateManagerPkiSync.removeCertificates(pkiSync, certificateNames, { + certificateSyncDAL: dependencies.certificateSyncDAL, + certificateMap: dependencies.certificateMap + }); break; } default: diff --git a/backend/src/services/pki-sync/pki-sync-queue.ts b/backend/src/services/pki-sync/pki-sync-queue.ts index 5967a6c97..608162ead 100644 --- a/backend/src/services/pki-sync/pki-sync-queue.ts +++ b/backend/src/services/pki-sync/pki-sync-queue.ts @@ -5,6 +5,7 @@ import { AxiosError } from "axios"; import { Job } from "bullmq"; import handlebars from "handlebars"; +import { TCertificates } from "@app/db/schemas"; import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore"; @@ -25,6 +26,8 @@ import { TCertificateSecretDALFactory } from "../certificate/certificate-secret- import { TCertificateAuthorityCertDALFactory } from "../certificate-authority/certificate-authority-cert-dal"; import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal"; import { getCaCertChain } from "../certificate-authority/certificate-authority-fns"; +import { TCertificateSyncDALFactory } from "../certificate-sync/certificate-sync-dal"; +import { CertificateSyncStatus } from "../certificate-sync/certificate-sync-enums"; import { TPkiSyncDALFactory } from "./pki-sync-dal"; import { PkiSyncStatus } from "./pki-sync-enums"; import { PkiSyncError } from "./pki-sync-errors"; @@ -55,14 +58,12 @@ type TPkiSyncQueueFactoryDep = { auditLogService: Pick; projectDAL: TProjectDALFactory; licenseService: Pick; - certificateDAL: Pick< - TCertificateDALFactory, - "findLatestActiveCertForSubscriber" | "findAllActiveCertsForSubscriber" | "create" - >; + certificateDAL: TCertificateDALFactory; certificateBodyDAL: Pick; certificateSecretDAL: Pick; certificateAuthorityDAL: Pick; certificateAuthorityCertDAL: Pick; + certificateSyncDAL: TCertificateSyncDALFactory; }; type PkiSyncActionJob = Job< @@ -93,7 +94,8 @@ export const pkiSyncQueueFactory = ({ certificateBodyDAL, certificateSecretDAL, certificateAuthorityDAL, - certificateAuthorityCertDAL + certificateAuthorityCertDAL, + certificateSyncDAL }: TPkiSyncQueueFactoryDep) => { const appCfg = getConfig(); @@ -153,25 +155,39 @@ export const pkiSyncQueueFactory = ({ const $getInfisicalCertificates = async ( pkiSync: TPkiSyncRaw | TPkiSyncWithCredentials - ): Promise => { - const { projectId, subscriberId } = pkiSync; - - if (!subscriberId) { - throw new PkiSyncError({ - message: "Invalid PKI Sync source configuration: subscriber no longer exists. Please update source subscriber.", - shouldRetry: false - }); - } + ): Promise<{ certificateMap: TCertificateMap; certificateMetadata: Map }> => { + const { projectId, subscriberId, id: pkiSyncId } = pkiSync; const certificateMap: TCertificateMap = {}; + const certificateMetadata = new Map(); + let certificates: Array<{ id: string; projectId: string; caCertId?: string | null }> = []; try { - // Get all active certificates for the subscriber (not just the latest) - const certificates = await certificateDAL.findAllActiveCertsForSubscriber({ - subscriberId - }); + if (subscriberId) { + const subscriberCertificates = await certificateDAL.findAllActiveCertsForSubscriber({ + subscriberId + }); + certificates.push(...subscriberCertificates); + } + + const certificateIds = await certificateSyncDAL.findCertificateIdsByPkiSyncId(pkiSyncId); + if (certificateIds.length > 0) { + const directCertificates = await certificateDAL.findActiveCertificatesByIds(certificateIds); + certificates.push(...directCertificates); + } + + const uniqueCertificates = certificates.filter( + (cert, index, self) => self.findIndex((c) => c.id === cert.id) === index + ); + + if (uniqueCertificates.length === 0) { + return { certificateMap, certificateMetadata }; + } + + certificates = uniqueCertificates; for (const certificate of certificates) { + const cert = certificate as TCertificates; try { // Get the certificate body and decrypt the certificate data const certBody = await certificateBodyDAL.findOne({ certId: certificate.id }); @@ -246,19 +262,45 @@ export const pkiSyncQueueFactory = ({ if (certificateNameSchema) { const environment = "global"; - certificateName = handlebars.compile(certificateNameSchema)({ + const templateData = { certificateId: certificate.id.replace(/-/g, ""), + profileId: cert.profileId?.replace(/-/g, "") || certificate.id.replace(/-/g, ""), + commonName: cert.commonName || "", + friendlyName: cert.friendlyName || "", environment - }); + }; + certificateName = handlebars.compile(certificateNameSchema)(templateData); } else { - certificateName = `Infisical-${certificate.id.replace(/-/g, "")}`; + const stableId = cert.profileId + ? `${cert.profileId.replace(/-/g, "")}-${(cert.commonName || "").replace(/[^a-zA-Z0-9]/g, "")}` + : certificate.id.replace(/-/g, ""); + certificateName = `Infisical-${stableId}`; + } + + const alternativeNames: string[] = []; + + const legacyName = `Infisical-${certificate.id.replace(/-/g, "")}`; + if (legacyName !== certificateName) { + alternativeNames.push(legacyName); + } + + if (cert.renewedFromCertificateId) { + const originalLegacyName = `Infisical-${cert.renewedFromCertificateId.replace(/-/g, "")}`; + alternativeNames.push(originalLegacyName); } certificateMap[certificateName] = { cert: certificatePem, privateKey: certPrivateKey || "", - certificateChain + certificateChain, + alternativeNames, + certificateId: certificate.id }; + + certificateMetadata.set(certificateName, { + id: certificate.id, + name: certificateName + }); } else { logger.warn({ certificateId: certificate.id, subscriberId }, "Certificate body not found for certificate"); } @@ -281,7 +323,7 @@ export const pkiSyncQueueFactory = ({ }); } - return certificateMap; + return { certificateMap, certificateMetadata }; }; const queuePkiSyncSyncCertificatesById = async (payload: TQueuePkiSyncSyncCertificatesByIdDTO) => @@ -348,12 +390,17 @@ export const pkiSyncQueueFactory = ({ try { const { - connection: { orgId, encryptedCredentials, projectId: appConnectionProjectId } + connection: { id: connectionId, orgId, projectId: appConnectionProjectId } } = pkiSync; + const appConnection = await appConnectionDAL.findById(connectionId); + if (!appConnection) { + throw new Error(`App connection not found: ${connectionId}`); + } + const credentials = await decryptAppConnectionCredentials({ orgId, - encryptedCredentials, + encryptedCredentials: appConnection.encryptedCredentials, kmsService, projectId: appConnectionProjectId }); @@ -366,11 +413,24 @@ export const pkiSyncQueueFactory = ({ } } as TPkiSyncWithCredentials; - const certificateMap = await $getInfisicalCertificates(pkiSync); + const { certificateMap, certificateMetadata } = await $getInfisicalCertificates(pkiSync); + + const statusUpdates = Array.from(certificateMetadata.entries()).map(([, metadata]) => ({ + pkiSyncId: pkiSync.id, + certificateId: metadata.id, + status: CertificateSyncStatus.Running, + message: "Syncing certificate to destination" + })); + + if (statusUpdates.length > 0) { + await certificateSyncDAL.bulkUpdateSyncStatus(statusUpdates); + } const syncResult = await PkiSyncFns.syncCertificates(pkiSyncWithCredentials, certificateMap, { appConnectionDAL, - kmsService + kmsService, + certificateDAL, + certificateSyncDAL }); logger.info( @@ -384,6 +444,60 @@ export const pkiSyncQueueFactory = ({ "PKI sync operation completed with certificate cleanup" ); + const postSyncUpdates: Array<{ + pkiSyncId: string; + certificateId: string; + status: string; + message?: string; + }> = []; + + for (const [, metadata] of certificateMetadata.entries()) { + postSyncUpdates.push({ + pkiSyncId: pkiSync.id, + certificateId: metadata.id, + status: CertificateSyncStatus.Succeeded, + message: "Certificate successfully synced to destination" + }); + } + + if (syncResult.details?.validationErrors) { + for (const validationError of syncResult.details.validationErrors) { + const metadata = certificateMetadata.get(validationError.name); + if (metadata) { + const updateIndex = postSyncUpdates.findIndex((u) => u.certificateId === metadata.id); + if (updateIndex >= 0) { + postSyncUpdates[updateIndex] = { + pkiSyncId: pkiSync.id, + certificateId: metadata.id, + status: CertificateSyncStatus.Failed, + message: `${validationError.error}` + }; + } + } + } + } + + if (syncResult.details?.failedUploads) { + for (const failure of syncResult.details.failedUploads) { + const metadata = certificateMetadata.get(failure.name); + if (metadata) { + const updateIndex = postSyncUpdates.findIndex((u) => u.certificateId === metadata.id); + if (updateIndex >= 0) { + postSyncUpdates[updateIndex] = { + pkiSyncId: pkiSync.id, + certificateId: metadata.id, + status: CertificateSyncStatus.Failed, + message: `Failed to sync certificate: ${failure.error}` + }; + } + } + } + } + + if (postSyncUpdates.length > 0) { + await certificateSyncDAL.bulkUpdateSyncStatus(postSyncUpdates); + } + isSynced = true; } catch (err) { logger.error( @@ -550,17 +664,22 @@ export const pkiSyncQueueFactory = ({ try { const { - connection: { orgId, encryptedCredentials, projectId: appConnectionProjectId } + connection: { id: connectionId, orgId, projectId: appConnectionProjectId } } = pkiSync; + const appConnection = await appConnectionDAL.findById(connectionId); + if (!appConnection) { + throw new Error(`App connection not found: ${connectionId}`); + } + const credentials = await decryptAppConnectionCredentials({ orgId, - encryptedCredentials, + encryptedCredentials: appConnection.encryptedCredentials, kmsService, projectId: appConnectionProjectId }); - const certificateMap = await $getInfisicalCertificates(pkiSync); + const { certificateMap } = await $getInfisicalCertificates(pkiSync); await PkiSyncFns.removeCertificates( { @@ -573,7 +692,10 @@ export const pkiSyncQueueFactory = ({ Object.keys(certificateMap), { appConnectionDAL, - kmsService + kmsService, + certificateSyncDAL, + certificateDAL, + certificateMap } ); diff --git a/backend/src/services/pki-sync/pki-sync-service.ts b/backend/src/services/pki-sync/pki-sync-service.ts index f92c9e19f..02a76db2a 100644 --- a/backend/src/services/pki-sync/pki-sync-service.ts +++ b/backend/src/services/pki-sync/pki-sync-service.ts @@ -1,6 +1,6 @@ import { ForbiddenError, subject } from "@casl/ability"; -import { ActionProjectType } from "@app/db/schemas"; +import { ActionProjectType, TCertificateSyncs } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { ProjectPermissionPkiSyncActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; @@ -10,17 +10,24 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service"; import { TPkiSubscriberDALFactory } from "@app/services/pki-subscriber/pki-subscriber-dal"; +import { TCertificateDALFactory } from "../certificate/certificate-dal"; +import { TCertificateSyncDALFactory } from "../certificate-sync/certificate-sync-dal"; +import { CertificateSyncStatus } from "../certificate-sync/certificate-sync-enums"; import { TPkiSyncDALFactory } from "./pki-sync-dal"; import { PkiSync, PkiSyncStatus } from "./pki-sync-enums"; import { enterprisePkiSyncCheck, getPkiSyncProviderCapabilities, listPkiSyncOptions } from "./pki-sync-fns"; import { PKI_SYNC_CONNECTION_MAP, PKI_SYNC_NAME_MAP } from "./pki-sync-maps"; import { TPkiSyncQueueFactory } from "./pki-sync-queue"; import { + TAddCertificatesToPkiSyncDTO, TCreatePkiSyncDTO, TDeletePkiSyncDTO, TFindPkiSyncByIdDTO, + TListPkiSyncCertificatesDTO, TListPkiSyncsByProjectId, TPkiSync, + TPkiSyncCertificate, + TRemoveCertificatesFromPkiSyncDTO, TTriggerPkiSyncImportCertificatesByIdDTO, TTriggerPkiSyncRemoveCertificatesByIdDTO, TTriggerPkiSyncSyncCertificatesByIdDTO, @@ -42,6 +49,17 @@ type TPkiSyncServiceFactoryDep = { TPkiSyncDALFactory, "findById" | "findByProjectIdWithSubscribers" | "findByNameAndProjectId" | "create" | "updateById" | "deleteById" >; + certificateDAL: Pick; + certificateSyncDAL: Pick< + TCertificateSyncDALFactory, + | "findByPkiSyncId" + | "findByCertificateId" + | "findCertificateIdsByPkiSyncId" + | "addCertificates" + | "removeCertificates" + | "removeAllCertificatesFromSync" + | "findWithDetails" + >; pkiSubscriberDAL: Pick; appConnectionService: Pick; permissionService: Pick; @@ -56,12 +74,41 @@ export type TPkiSyncServiceFactory = ReturnType; export const pkiSyncServiceFactory = ({ pkiSyncDAL, + certificateDAL, + certificateSyncDAL, pkiSubscriberDAL, appConnectionService, permissionService, licenseService, pkiSyncQueue }: TPkiSyncServiceFactoryDep) => { + const validateCertificatesProjectOwnership = async (certificateIds: string[], expectedProjectId: string) => { + if (certificateIds.length === 0) return; + + const certificates = await certificateDAL.findActiveCertificatesByIds(certificateIds); + + if (certificates.length !== certificateIds.length) { + const foundIds = certificates.map((cert) => cert.id); + const missingIds = certificateIds.filter((id) => !foundIds.includes(id)); + throw new NotFoundError({ + message: `Certificates not found or not active: ${missingIds.join(", ")}` + }); + } + + const invalidProjectCertificates = certificates.filter((cert) => cert.projectId !== expectedProjectId); + if (invalidProjectCertificates.length > 0) { + throw new BadRequestError({ + message: `Certificates do not belong to the same project: ${invalidProjectCertificates.map((cert) => cert.id).join(", ")}` + }); + } + + const invalidRenewedCertificates = certificates.filter((cert) => cert.renewedByCertificateId); + if (invalidRenewedCertificates.length > 0) { + throw new BadRequestError({ + message: `Cannot add renewed certificates to PKI sync: ${invalidRenewedCertificates.map((cert) => cert.id).join(", ")}` + }); + } + }; const createPkiSync = async ( { name, @@ -72,7 +119,8 @@ export const pkiSyncServiceFactory = ({ syncOptions = {}, subscriberId, connectionId, - projectId + projectId, + certificateIds = [] }: Omit, actor: OrgServiceActor ): Promise => { @@ -114,6 +162,10 @@ export const pkiSyncServiceFactory = ({ ...syncOptions }; + if (certificateIds.length > 0) { + await validateCertificatesProjectOwnership(certificateIds, projectId); + } + try { const pkiSync = await pkiSyncDAL.create({ name, @@ -128,6 +180,13 @@ export const pkiSyncServiceFactory = ({ ...(isAutoSyncEnabled && { syncStatus: PkiSyncStatus.Pending }) }); + if (certificateIds.length > 0) { + await certificateSyncDAL.addCertificates( + pkiSync.id, + certificateIds.map((id) => ({ certificateId: id })) + ); + } + if (pkiSync.isAutoSyncEnabled) { await pkiSyncQueue.queuePkiSyncSyncCertificatesById({ syncId: pkiSync.id }); } @@ -152,7 +211,8 @@ export const pkiSyncServiceFactory = ({ destinationConfig, syncOptions, subscriberId, - connectionId + connectionId, + certificateIds }: Omit, actor: OrgServiceActor ): Promise => { @@ -221,6 +281,20 @@ export const pkiSyncServiceFactory = ({ }; } + if (certificateIds !== undefined) { + if (certificateIds.length > 0) { + await validateCertificatesProjectOwnership(certificateIds, pkiSync.projectId); + } + + await certificateSyncDAL.removeAllCertificatesFromSync(id); + if (certificateIds.length > 0) { + await certificateSyncDAL.addCertificates( + id, + certificateIds.map((certId) => ({ certificateId: certId })) + ); + } + } + const updatedPkiSync = await pkiSyncDAL.updateById(id, { name, description, @@ -266,7 +340,7 @@ export const pkiSyncServiceFactory = ({ }; const listPkiSyncsByProjectId = async ( - { projectId }: TListPkiSyncsByProjectId, + { projectId, certificateId }: TListPkiSyncsByProjectId, actor: OrgServiceActor ): Promise => { const { permission } = await permissionService.getProjectPermission({ @@ -282,6 +356,29 @@ export const pkiSyncServiceFactory = ({ const pkiSyncsWithSubscribers = await pkiSyncDAL.findByProjectIdWithSubscribers(projectId); + if (certificateId) { + const syncsWithCertificateInfo = await Promise.all( + pkiSyncsWithSubscribers.map(async (sync) => { + try { + const certificateSyncs = await certificateSyncDAL.findByPkiSyncId(sync.id); + const hasCertificate = certificateSyncs.some((certSync) => certSync.certificateId === certificateId); + + return { + ...sync, + hasCertificate + }; + } catch (error) { + return { + ...sync, + hasCertificate: false + }; + } + }) + ); + + return syncsWithCertificateInfo as TPkiSync[]; + } + return pkiSyncsWithSubscribers as TPkiSync[]; }; @@ -433,6 +530,145 @@ export const pkiSyncServiceFactory = ({ return listPkiSyncOptions(); }; + const addCertificatesToPkiSync = async ( + { pkiSyncId, certificateIds }: Omit, + actor: OrgServiceActor + ): Promise<{ + addedCertificates: TCertificateSyncs[]; + pkiSyncInfo: { projectId: string; destination: string; name: string }; + }> => { + const pkiSync = await pkiSyncDAL.findById(pkiSyncId); + if (!pkiSync) throw new NotFoundError({ message: "PKI sync not found" }); + + const { permission } = await permissionService.getProjectPermission({ + actor: actor.type, + actorId: actor.id, + actorAuthMethod: actor.authMethod, + actorOrgId: actor.orgId, + actionProjectType: ActionProjectType.CertificateManager, + projectId: pkiSync.projectId + }); + + ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionPkiSyncActions.Edit, ProjectPermissionSub.PkiSyncs); + + await validateCertificatesProjectOwnership(certificateIds, pkiSync.projectId); + + const addedCertificates = await certificateSyncDAL.addCertificates( + pkiSyncId, + certificateIds.map((id) => ({ certificateId: id })) + ); + + if (pkiSync.isAutoSyncEnabled) { + await pkiSyncQueue.queuePkiSyncSyncCertificatesById({ syncId: pkiSyncId }); + } + + return { + addedCertificates, + pkiSyncInfo: { + projectId: pkiSync.projectId, + destination: pkiSync.destination, + name: pkiSync.name + } + }; + }; + + const removeCertificatesFromPkiSync = async ( + { pkiSyncId, certificateIds }: Omit, + actor: OrgServiceActor + ): Promise<{ removedCount: number; pkiSyncInfo: { projectId: string; destination: string; name: string } }> => { + const pkiSync = await pkiSyncDAL.findById(pkiSyncId); + if (!pkiSync) throw new NotFoundError({ message: "PKI sync not found" }); + + const { permission } = await permissionService.getProjectPermission({ + actor: actor.type, + actorId: actor.id, + actorAuthMethod: actor.authMethod, + actorOrgId: actor.orgId, + actionProjectType: ActionProjectType.CertificateManager, + projectId: pkiSync.projectId + }); + + ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionPkiSyncActions.Edit, ProjectPermissionSub.PkiSyncs); + + const removedCount = await certificateSyncDAL.removeCertificates(pkiSyncId, certificateIds); + + if (pkiSync.isAutoSyncEnabled) { + await pkiSyncQueue.queuePkiSyncSyncCertificatesById({ syncId: pkiSyncId }); + } + + return { + removedCount, + pkiSyncInfo: { + projectId: pkiSync.projectId, + destination: pkiSync.destination, + name: pkiSync.name + } + }; + }; + + const listPkiSyncCertificates = async ( + { pkiSyncId, offset = 0, limit = 20 }: Omit, + actor: OrgServiceActor + ): Promise<{ + certificates: TPkiSyncCertificate[]; + totalCount: number; + pkiSyncInfo: { projectId: string; destination: string; name: string }; + }> => { + const pkiSync = await pkiSyncDAL.findById(pkiSyncId); + if (!pkiSync) throw new NotFoundError({ message: "PKI sync not found" }); + + const { permission } = await permissionService.getProjectPermission({ + actor: actor.type, + actorId: actor.id, + actorAuthMethod: actor.authMethod, + actorOrgId: actor.orgId, + actionProjectType: ActionProjectType.CertificateManager, + projectId: pkiSync.projectId + }); + + ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionPkiSyncActions.Read, ProjectPermissionSub.PkiSyncs); + + const result = await certificateSyncDAL.findWithDetails({ + pkiSyncId, + offset, + limit + }); + const { certificateDetails, totalCount } = result; + + const certificates = certificateDetails.map((detail) => ({ + id: detail.id, + pkiSyncId: detail.pkiSyncId, + certificateId: detail.certificateId, + syncStatus: (detail.syncStatus as CertificateSyncStatus) || CertificateSyncStatus.Pending, + lastSyncMessage: detail.lastSyncMessage || undefined, + lastSyncedAt: detail.lastSyncedAt || undefined, + createdAt: detail.createdAt, + updatedAt: detail.updatedAt, + certificateSerialNumber: detail.certificateSerialNumber || undefined, + certificateCommonName: detail.certificateCommonName || undefined, + certificateAltNames: detail.certificateAltNames || undefined, + certificateStatus: detail.certificateStatus || undefined, + certificateNotBefore: detail.certificateNotBefore || undefined, + certificateNotAfter: detail.certificateNotAfter || undefined, + certificateRenewBeforeDays: !detail.certificateRenewedByCertificateId + ? detail.certificateRenewBeforeDays || undefined + : undefined, + certificateRenewalError: detail.certificateRenewalError || undefined, + pkiSyncName: detail.pkiSyncName || undefined, + pkiSyncDestination: detail.pkiSyncDestination || undefined + })); + + return { + certificates, + totalCount, + pkiSyncInfo: { + projectId: pkiSync.projectId, + destination: pkiSync.destination, + name: pkiSync.name + } + }; + }; + return { createPkiSync, updatePkiSync, @@ -442,6 +678,9 @@ export const pkiSyncServiceFactory = ({ triggerPkiSyncSyncCertificatesById, triggerPkiSyncImportCertificatesById, triggerPkiSyncRemoveCertificatesById, - getPkiSyncOptions + getPkiSyncOptions, + addCertificatesToPkiSync, + removeCertificatesFromPkiSync, + listPkiSyncCertificates }; }; diff --git a/backend/src/services/pki-sync/pki-sync-types.ts b/backend/src/services/pki-sync/pki-sync-types.ts index bf750beee..f42f64a1b 100644 --- a/backend/src/services/pki-sync/pki-sync-types.ts +++ b/backend/src/services/pki-sync/pki-sync-types.ts @@ -2,6 +2,7 @@ import { Job } from "bullmq"; import { AuditLogInfo } from "@app/ee/services/audit-log/audit-log-types"; import { QueueJobs } from "@app/queue"; +import { CertificateSyncStatus } from "@app/services/certificate-sync/certificate-sync-enums"; import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema"; import { TPkiSyncDALFactory } from "./pki-sync-dal"; @@ -70,7 +71,10 @@ export type TPkiSyncListItem = TPkiSync & { appConnectionApp: string; }; -export type TCertificateMap = Record; +export type TCertificateMap = Record< + string, + { cert: string; privateKey: string; certificateChain?: string; alternativeNames?: string[]; certificateId?: string } +>; export type TCreatePkiSyncDTO = { name: string; @@ -79,9 +83,10 @@ export type TCreatePkiSyncDTO = { isAutoSyncEnabled?: boolean; destinationConfig: Record; syncOptions?: Record; - subscriberId?: string; + subscriberId?: string | null; connectionId: string; projectId: string; + certificateIds?: string[]; auditLogInfo: AuditLogInfo; resourceMetadata?: ResourceMetadataDTO; }; @@ -94,8 +99,9 @@ export type TUpdatePkiSyncDTO = { isAutoSyncEnabled?: boolean; destinationConfig?: Record; syncOptions?: Record; - subscriberId?: string; + subscriberId?: string | null; connectionId?: string; + certificateIds?: string[]; auditLogInfo: AuditLogInfo; resourceMetadata?: ResourceMetadataDTO; }; @@ -108,6 +114,7 @@ export type TDeletePkiSyncDTO = { export type TListPkiSyncsByProjectId = { projectId: string; + certificateId?: string; }; export type TFindPkiSyncByIdDTO = { @@ -133,6 +140,48 @@ export type TTriggerPkiSyncRemoveCertificatesByIdDTO = { auditLogInfo: AuditLogInfo; }; +export type TAddCertificatesToPkiSyncDTO = { + pkiSyncId: string; + certificateIds: string[]; + projectId?: string; + auditLogInfo: AuditLogInfo; +}; + +export type TRemoveCertificatesFromPkiSyncDTO = { + pkiSyncId: string; + certificateIds: string[]; + projectId?: string; + auditLogInfo: AuditLogInfo; +}; + +export type TListPkiSyncCertificatesDTO = { + pkiSyncId: string; + projectId?: string; + offset?: number; + limit?: number; +}; + +export type TPkiSyncCertificate = { + id: string; + pkiSyncId: string; + certificateId: string; + syncStatus: CertificateSyncStatus; + lastSyncMessage?: string; + lastSyncedAt?: Date; + createdAt: Date; + updatedAt: Date; + certificateSerialNumber?: string; + certificateCommonName?: string; + certificateAltNames?: string; + certificateStatus?: string; + certificateNotBefore?: Date; + certificateNotAfter?: Date; + certificateRenewBeforeDays?: number; + certificateRenewalError?: string; + pkiSyncName?: string; + pkiSyncDestination?: string; +}; + export type TPkiSyncRaw = NonNullable>>; export type TQueuePkiSyncSyncCertificatesByIdDTO = { diff --git a/backend/src/services/pki-sync/pki-sync-utils.ts b/backend/src/services/pki-sync/pki-sync-utils.ts index a81864a17..d216ddd5a 100644 --- a/backend/src/services/pki-sync/pki-sync-utils.ts +++ b/backend/src/services/pki-sync/pki-sync-utils.ts @@ -1,5 +1,8 @@ +import { Knex } from "knex"; + import { logger } from "@app/lib/logger"; +import { TCertificateSyncDALFactory } from "../certificate-sync/certificate-sync-dal"; import { TPkiSyncDALFactory } from "./pki-sync-dal"; import { TPkiSyncQueueFactory } from "./pki-sync-queue"; @@ -25,3 +28,78 @@ export const triggerAutoSyncForSubscriber = async ( logger.error(error, `Failed to trigger auto sync for subscriber ${subscriberId}:`); } }; + +export const triggerAutoSyncForCertificate = async ( + certificateId: string, + dependencies: { + certificateSyncDAL: Pick; + pkiSyncDAL: Pick; + pkiSyncQueue: Pick; + } +) => { + try { + const pkiSyncIds = await dependencies.certificateSyncDAL.findPkiSyncIdsByCertificateId(certificateId); + + if (pkiSyncIds.length === 0) { + return; + } + + const allPkiSyncs = await dependencies.pkiSyncDAL.find({ + isAutoSyncEnabled: true, + $in: { + id: pkiSyncIds + } + }); + + const syncPromises = allPkiSyncs.map((pkiSync) => + dependencies.pkiSyncQueue.queuePkiSyncSyncCertificatesById({ syncId: pkiSync.id }) + ); + await Promise.all(syncPromises); + } catch (error) { + logger.error(error, `Failed to trigger auto sync for certificate ${certificateId}:`); + } +}; + +export const addRenewedCertificateToSyncs = async ( + oldCertificateId: string, + newCertificateId: string, + dependencies: { + certificateSyncDAL: Pick< + TCertificateSyncDALFactory, + "findPkiSyncIdsByCertificateId" | "addCertificates" | "findByPkiSyncAndCertificate" + >; + }, + tx?: Knex +) => { + try { + const pkiSyncIds = await dependencies.certificateSyncDAL.findPkiSyncIdsByCertificateId(oldCertificateId); + + if (pkiSyncIds.length === 0) { + return; + } + + const addPromises = pkiSyncIds.map(async (pkiSyncId) => { + const oldCertificateRecord = await dependencies.certificateSyncDAL.findByPkiSyncAndCertificate( + pkiSyncId, + oldCertificateId + ); + + await dependencies.certificateSyncDAL.addCertificates( + pkiSyncId, + [ + { + certificateId: newCertificateId, + externalIdentifier: oldCertificateRecord?.externalIdentifier || undefined + } + ], + tx + ); + }); + await Promise.all(addPromises); + + logger.info(`Successfully added renewed certificate ${newCertificateId} to PKI sync(s)`); + } catch (error) { + logger.error(error, `Failed to add renewed certificate ${newCertificateId} to syncs:`); + throw error; + } +}; diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index fb2eb70af..25052ba9a 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -156,7 +156,14 @@ type TProjectServiceFactoryDep = { >; pkiSubscriberDAL: Pick; certificateAuthorityDAL: Pick; - certificateDAL: Pick; + certificateDAL: Pick< + TCertificateDALFactory, + | "find" + | "countCertificatesInProject" + | "findWithPrivateKeyInfo" + | "findActiveCertificatesForSync" + | "countActiveCertificatesForSync" + >; certificateTemplateDAL: Pick; pkiAlertDAL: Pick; pkiCollectionDAL: Pick; @@ -929,6 +936,7 @@ export const projectServiceFactory = ({ offset = 0, friendlyName, commonName, + forPkiSync = false, actorId, actorOrgId, actorAuthMethod, @@ -952,20 +960,35 @@ export const projectServiceFactory = ({ ProjectPermissionSub.Certificates ); - const certificates = await certificateDAL.findWithPrivateKeyInfo( - { - projectId, - ...(friendlyName && { friendlyName }), - ...(commonName && { commonName }) - }, - { offset, limit, sort: [["notAfter", "desc"]] } - ); + const certificates = forPkiSync + ? await certificateDAL.findActiveCertificatesForSync( + { + projectId, + ...(friendlyName && { friendlyName }), + ...(commonName && { commonName }) + }, + { offset, limit } + ) + : await certificateDAL.findWithPrivateKeyInfo( + { + projectId, + ...(friendlyName && { friendlyName }), + ...(commonName && { commonName }) + }, + { offset, limit, sort: [["notAfter", "desc"]] } + ); - const count = await certificateDAL.countCertificatesInProject({ - projectId, - friendlyName, - commonName - }); + const count = forPkiSync + ? await certificateDAL.countActiveCertificatesForSync({ + projectId, + friendlyName, + commonName + }) + : await certificateDAL.countCertificatesInProject({ + projectId, + friendlyName, + commonName + }); return { certificates, diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 18ae74350..2b75b1bc7 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -142,6 +142,7 @@ export type TListProjectCertsDTO = { limit: number; friendlyName?: string; commonName?: string; + forPkiSync?: boolean; } & Omit; export type TListProjectAlertsDTO = TProjectPermission; diff --git a/backend/src/services/secret-folder/secret-folder-dal.ts b/backend/src/services/secret-folder/secret-folder-dal.ts index 7dfeaddcf..f2befbe1a 100644 --- a/backend/src/services/secret-folder/secret-folder-dal.ts +++ b/backend/src/services/secret-folder/secret-folder-dal.ts @@ -419,13 +419,14 @@ export const secretFolderDALFactory = (db: TDbClient) => { .select( selectAllTableCols(TableName.SecretFolder), db.raw( - `DENSE_RANK() OVER (ORDER BY ${TableName.SecretFolder}."name" ${ - orderDirection ?? OrderByDirection.ASC - }) as rank` + `DENSE_RANK() OVER (ORDER BY ${TableName.SecretFolder}."name" COLLATE "en-x-icu" ${orderDirection === OrderByDirection.ASC ? "ASC" : "DESC"}) as rank` ), db.ref("slug").withSchema(TableName.Environment).as("environment") ) - .orderBy(`${TableName.SecretFolder}.${orderBy}`, orderDirection); + .orderByRaw( + `${TableName.SecretFolder}.?? COLLATE "en-x-icu" ${orderDirection === OrderByDirection.ASC ? "ASC" : "DESC"}`, + [orderBy] + ); if (limit) { const rankOffset = offset + 1; // ranks start from 1 @@ -434,7 +435,10 @@ export const secretFolderDALFactory = (db: TDbClient) => { .select("*") .from[number]>("w") .where("w.rank", ">=", rankOffset) - .andWhere("w.rank", "<", rankOffset + limit); + .andWhere("w.rank", "<", rankOffset + limit) + .orderByRaw(`"w".?? COLLATE "en-x-icu" ${orderDirection === OrderByDirection.ASC ? "ASC" : "DESC"}`, [ + orderBy + ]); } const folders = await query; @@ -445,7 +449,10 @@ export const secretFolderDALFactory = (db: TDbClient) => { } }; - const findByEnvsDeep = async ({ parentIds }: TFindFoldersDeepByParentIdsDTO, tx?: Knex) => { + const findByEnvsDeep = async ( + { parentIds, orderBy = SecretsOrderBy.Name, orderDirection = OrderByDirection.ASC }: TFindFoldersDeepByParentIdsDTO, + tx?: Knex + ) => { try { const folders = await (tx || db.replicaNode()) .withRecursive("parents", (qb) => @@ -480,7 +487,9 @@ export const secretFolderDALFactory = (db: TDbClient) => { .select<(TSecretFolders & { path: string; depth: number; environment: string })[]>("*") .from("parents") .orderBy("depth") - .orderBy(`name`); + .orderByRaw(`"parents".?? COLLATE "en-x-icu" ${orderDirection === OrderByDirection.ASC ? "ASC" : "DESC"}`, [ + orderBy + ]); return folders; } catch (error) { diff --git a/backend/src/services/secret-folder/secret-folder-service.ts b/backend/src/services/secret-folder/secret-folder-service.ts index c216ea3a8..033efdb14 100644 --- a/backend/src/services/secret-folder/secret-folder-service.ts +++ b/backend/src/services/secret-folder/secret-folder-service.ts @@ -14,6 +14,7 @@ import { PgSqlLock } from "@app/keystore/keystore"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { OrderByDirection, OrgServiceActor } from "@app/lib/types"; import { ActorType } from "@app/services/auth/auth-type"; +import { SecretsOrderBy } from "@app/services/secret/secret-types"; import { buildFolderPath } from "@app/services/secret-folder/secret-folder-fns"; import { @@ -781,7 +782,11 @@ export const secretFolderServiceFactory = ({ if (!parentFolder) return []; if (recursive) { - const recursiveFolders = await folderDAL.findByEnvsDeep({ parentIds: [parentFolder.id] }); + const recursiveFolders = await folderDAL.findByEnvsDeep({ + parentIds: [parentFolder.id], + orderBy: orderBy || SecretsOrderBy.Name, + orderDirection: orderDirection || OrderByDirection.ASC + }); // remove the parent folder return recursiveFolders .filter((folder) => { @@ -800,19 +805,15 @@ export const secretFolderServiceFactory = ({ })); } - const folders = await folderDAL.find( - { - envId: env.id, - parentId: parentFolder.id, - isReserved: false, - $search: search ? { name: `%${search}%` } : undefined - }, - { - sort: orderBy ? [[orderBy, orderDirection ?? OrderByDirection.ASC]] : undefined, - limit, - offset - } - ); + const folders = await folderDAL.findByMultiEnv({ + environmentIds: [env.id], + parentIds: [parentFolder.id], + search, + orderBy: orderBy || SecretsOrderBy.Name, + orderDirection: orderDirection || OrderByDirection.ASC, + limit, + offset + }); if (lastSecretModified) { return folders.filter((el) => el.lastSecretModified ? el.lastSecretModified >= new Date(lastSecretModified) : false diff --git a/backend/src/services/secret-folder/secret-folder-types.ts b/backend/src/services/secret-folder/secret-folder-types.ts index da8be52a0..d220c29d2 100644 --- a/backend/src/services/secret-folder/secret-folder-types.ts +++ b/backend/src/services/secret-folder/secret-folder-types.ts @@ -64,6 +64,8 @@ export type TGetFoldersDeepByEnvsDTO = { export type TFindFoldersDeepByParentIdsDTO = { parentIds: string[]; + orderBy?: SecretsOrderBy; + orderDirection?: OrderByDirection; }; export type TCreateManyFoldersDTO = { diff --git a/backend/src/services/secret-sync/secret-sync-enums.ts b/backend/src/services/secret-sync/secret-sync-enums.ts index f04247684..835a314ca 100644 --- a/backend/src/services/secret-sync/secret-sync-enums.ts +++ b/backend/src/services/secret-sync/secret-sync-enums.ts @@ -30,7 +30,8 @@ export enum SecretSync { Netlify = "netlify", Northflank = "northflank", Bitbucket = "bitbucket", - LaravelForge = "laravel-forge" + LaravelForge = "laravel-forge", + Chef = "chef" } export enum SecretSyncInitialSyncBehavior { diff --git a/backend/src/services/secret-sync/secret-sync-fns.ts b/backend/src/services/secret-sync/secret-sync-fns.ts index 3068e803b..6ee9b91d3 100644 --- a/backend/src/services/secret-sync/secret-sync-fns.ts +++ b/backend/src/services/secret-sync/secret-sync-fns.ts @@ -4,6 +4,7 @@ import handlebars from "handlebars"; import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; +import { CHEF_SYNC_LIST_OPTION, ChefSyncFns } from "@app/ee/services/secret-sync/chef"; import { OCI_VAULT_SYNC_LIST_OPTION, OCIVaultSyncFns } from "@app/ee/services/secret-sync/oci-vault"; import { BadRequestError } from "@app/lib/errors"; import { @@ -49,8 +50,7 @@ import { HC_VAULT_SYNC_LIST_OPTION, HCVaultSyncFns } from "./hc-vault"; import { HEROKU_SYNC_LIST_OPTION, HerokuSyncFns } from "./heroku"; import { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec"; import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns"; -import { LARAVEL_FORGE_SYNC_LIST_OPTION } from "./laravel-forge"; -import { LaravelForgeSyncFns } from "./laravel-forge/laravel-forge-sync-fns"; +import { LARAVEL_FORGE_SYNC_LIST_OPTION, LaravelForgeSyncFns } from "./laravel-forge"; import { NETLIFY_SYNC_LIST_OPTION, NetlifySyncFns } from "./netlify"; import { NORTHFLANK_SYNC_LIST_OPTION, NorthflankSyncFns } from "./northflank"; import { RAILWAY_SYNC_LIST_OPTION } from "./railway/railway-sync-constants"; @@ -96,7 +96,8 @@ const SECRET_SYNC_LIST_OPTIONS: Record = { [SecretSync.Netlify]: NETLIFY_SYNC_LIST_OPTION, [SecretSync.Northflank]: NORTHFLANK_SYNC_LIST_OPTION, [SecretSync.Bitbucket]: BITBUCKET_SYNC_LIST_OPTION, - [SecretSync.LaravelForge]: LARAVEL_FORGE_SYNC_LIST_OPTION + [SecretSync.LaravelForge]: LARAVEL_FORGE_SYNC_LIST_OPTION, + [SecretSync.Chef]: CHEF_SYNC_LIST_OPTION }; export const listSecretSyncOptions = () => { @@ -286,6 +287,8 @@ export const SecretSyncFns = { return BitbucketSyncFns.syncSecrets(secretSync, schemaSecretMap); case SecretSync.LaravelForge: return LaravelForgeSyncFns.syncSecrets(secretSync, schemaSecretMap); + case SecretSync.Chef: + return ChefSyncFns.syncSecrets(secretSync, schemaSecretMap); default: throw new Error( `Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}` @@ -408,6 +411,9 @@ export const SecretSyncFns = { case SecretSync.LaravelForge: secretMap = await LaravelForgeSyncFns.getSecrets(secretSync); break; + case SecretSync.Chef: + secretMap = await ChefSyncFns.getSecrets(secretSync); + break; default: throw new Error( `Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}` @@ -505,6 +511,8 @@ export const SecretSyncFns = { return BitbucketSyncFns.removeSecrets(secretSync, schemaSecretMap); case SecretSync.LaravelForge: return LaravelForgeSyncFns.removeSecrets(secretSync, schemaSecretMap); + case SecretSync.Chef: + return ChefSyncFns.removeSecrets(secretSync, schemaSecretMap); default: throw new Error( `Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}` diff --git a/backend/src/services/secret-sync/secret-sync-maps.ts b/backend/src/services/secret-sync/secret-sync-maps.ts index 8110cced9..529634a6d 100644 --- a/backend/src/services/secret-sync/secret-sync-maps.ts +++ b/backend/src/services/secret-sync/secret-sync-maps.ts @@ -34,7 +34,8 @@ export const SECRET_SYNC_NAME_MAP: Record = { [SecretSync.Netlify]: "Netlify", [SecretSync.Northflank]: "Northflank", [SecretSync.Bitbucket]: "Bitbucket", - [SecretSync.LaravelForge]: "Laravel Forge" + [SecretSync.LaravelForge]: "Laravel Forge", + [SecretSync.Chef]: "Chef" }; export const SECRET_SYNC_CONNECTION_MAP: Record = { @@ -69,7 +70,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record = { [SecretSync.Netlify]: AppConnection.Netlify, [SecretSync.Northflank]: AppConnection.Northflank, [SecretSync.Bitbucket]: AppConnection.Bitbucket, - [SecretSync.LaravelForge]: AppConnection.LaravelForge + [SecretSync.LaravelForge]: AppConnection.LaravelForge, + [SecretSync.Chef]: AppConnection.Chef }; export const SECRET_SYNC_PLAN_MAP: Record = { @@ -104,7 +106,8 @@ export const SECRET_SYNC_PLAN_MAP: Record = { [SecretSync.Netlify]: SecretSyncPlanType.Regular, [SecretSync.Northflank]: SecretSyncPlanType.Regular, [SecretSync.Bitbucket]: SecretSyncPlanType.Regular, - [SecretSync.LaravelForge]: SecretSyncPlanType.Regular + [SecretSync.LaravelForge]: SecretSyncPlanType.Regular, + [SecretSync.Chef]: SecretSyncPlanType.Enterprise }; export const SECRET_SYNC_SKIP_FIELDS_MAP: Record = { @@ -148,7 +151,8 @@ export const SECRET_SYNC_SKIP_FIELDS_MAP: Record = { [SecretSync.Netlify]: ["accountName", "siteName"], [SecretSync.Northflank]: [], [SecretSync.Bitbucket]: [], - [SecretSync.LaravelForge]: [] + [SecretSync.LaravelForge]: [], + [SecretSync.Chef]: [] }; const defaultDuplicateCheck: DestinationDuplicateCheckFn = () => true; @@ -209,5 +213,6 @@ export const DESTINATION_DUPLICATE_CHECK_MAP: Record + Check out the configuration docs for [Chef + Connections](/integrations/app-connections/chef) to learn how to obtain the + required credentials. + diff --git a/docs/api-reference/endpoints/app-connections/chef/delete.mdx b/docs/api-reference/endpoints/app-connections/chef/delete.mdx new file mode 100644 index 000000000..43a67f01c --- /dev/null +++ b/docs/api-reference/endpoints/app-connections/chef/delete.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete" +openapi: "DELETE /api/v1/app-connections/chef/{connectionId}" +--- diff --git a/docs/api-reference/endpoints/app-connections/chef/get-by-id.mdx b/docs/api-reference/endpoints/app-connections/chef/get-by-id.mdx new file mode 100644 index 000000000..8461cc553 --- /dev/null +++ b/docs/api-reference/endpoints/app-connections/chef/get-by-id.mdx @@ -0,0 +1,4 @@ +--- +title: "Get by ID" +openapi: "GET /api/v1/app-connections/chef/{connectionId}" +--- diff --git a/docs/api-reference/endpoints/app-connections/chef/get-by-name.mdx b/docs/api-reference/endpoints/app-connections/chef/get-by-name.mdx new file mode 100644 index 000000000..f1042abdb --- /dev/null +++ b/docs/api-reference/endpoints/app-connections/chef/get-by-name.mdx @@ -0,0 +1,4 @@ +--- +title: "Get by Name" +openapi: "GET /api/v1/app-connections/chef/connection-name/{connectionName}" +--- diff --git a/docs/api-reference/endpoints/app-connections/chef/list.mdx b/docs/api-reference/endpoints/app-connections/chef/list.mdx new file mode 100644 index 000000000..dc18436a7 --- /dev/null +++ b/docs/api-reference/endpoints/app-connections/chef/list.mdx @@ -0,0 +1,4 @@ +--- +title: "List" +openapi: "GET /api/v1/app-connections/chef" +--- diff --git a/docs/api-reference/endpoints/app-connections/chef/update.mdx b/docs/api-reference/endpoints/app-connections/chef/update.mdx new file mode 100644 index 000000000..780bea960 --- /dev/null +++ b/docs/api-reference/endpoints/app-connections/chef/update.mdx @@ -0,0 +1,10 @@ +--- +title: "Update" +openapi: "PATCH /api/v1/app-connections/chef/{connectionId}" +--- + + + Check out the configuration docs for [Chef + Connections](/integrations/app-connections/chef) to learn how to obtain the + required credentials. + diff --git a/docs/api-reference/endpoints/secret-syncs/chef/create.mdx b/docs/api-reference/endpoints/secret-syncs/chef/create.mdx new file mode 100644 index 000000000..61b816d7d --- /dev/null +++ b/docs/api-reference/endpoints/secret-syncs/chef/create.mdx @@ -0,0 +1,4 @@ +--- +title: "Create" +openapi: "POST /api/v1/secret-syncs/chef" +--- diff --git a/docs/api-reference/endpoints/secret-syncs/chef/delete.mdx b/docs/api-reference/endpoints/secret-syncs/chef/delete.mdx new file mode 100644 index 000000000..a43d83be1 --- /dev/null +++ b/docs/api-reference/endpoints/secret-syncs/chef/delete.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete" +openapi: "DELETE /api/v1/secret-syncs/chef/{syncId}" +--- diff --git a/docs/api-reference/endpoints/secret-syncs/chef/get-by-id.mdx b/docs/api-reference/endpoints/secret-syncs/chef/get-by-id.mdx new file mode 100644 index 000000000..2efeb51e2 --- /dev/null +++ b/docs/api-reference/endpoints/secret-syncs/chef/get-by-id.mdx @@ -0,0 +1,4 @@ +--- +title: "Get by ID" +openapi: "GET /api/v1/secret-syncs/chef/{syncId}" +--- diff --git a/docs/api-reference/endpoints/secret-syncs/chef/get-by-name.mdx b/docs/api-reference/endpoints/secret-syncs/chef/get-by-name.mdx new file mode 100644 index 000000000..d6ac030d8 --- /dev/null +++ b/docs/api-reference/endpoints/secret-syncs/chef/get-by-name.mdx @@ -0,0 +1,4 @@ +--- +title: "Get by Name" +openapi: "GET /api/v1/secret-syncs/chef/sync-name/{syncName}" +--- diff --git a/docs/api-reference/endpoints/secret-syncs/chef/import-secrets.mdx b/docs/api-reference/endpoints/secret-syncs/chef/import-secrets.mdx new file mode 100644 index 000000000..734c89a95 --- /dev/null +++ b/docs/api-reference/endpoints/secret-syncs/chef/import-secrets.mdx @@ -0,0 +1,4 @@ +--- +title: "Import Secrets" +openapi: "POST /api/v1/secret-syncs/chef/{syncId}/import-secrets" +--- diff --git a/docs/api-reference/endpoints/secret-syncs/chef/list.mdx b/docs/api-reference/endpoints/secret-syncs/chef/list.mdx new file mode 100644 index 000000000..e38b35e43 --- /dev/null +++ b/docs/api-reference/endpoints/secret-syncs/chef/list.mdx @@ -0,0 +1,4 @@ +--- +title: "List" +openapi: "GET /api/v1/secret-syncs/chef" +--- diff --git a/docs/api-reference/endpoints/secret-syncs/chef/remove-secrets.mdx b/docs/api-reference/endpoints/secret-syncs/chef/remove-secrets.mdx new file mode 100644 index 000000000..e44df1c3e --- /dev/null +++ b/docs/api-reference/endpoints/secret-syncs/chef/remove-secrets.mdx @@ -0,0 +1,4 @@ +--- +title: "Remove Secrets" +openapi: "POST /api/v1/secret-syncs/chef/{syncId}/remove-secrets" +--- diff --git a/docs/api-reference/endpoints/secret-syncs/chef/sync-secrets.mdx b/docs/api-reference/endpoints/secret-syncs/chef/sync-secrets.mdx new file mode 100644 index 000000000..8f8eefa04 --- /dev/null +++ b/docs/api-reference/endpoints/secret-syncs/chef/sync-secrets.mdx @@ -0,0 +1,4 @@ +--- +title: "Sync Secrets" +openapi: "POST /api/v1/secret-syncs/chef/{syncId}/sync-secrets" +--- diff --git a/docs/api-reference/endpoints/secret-syncs/chef/update.mdx b/docs/api-reference/endpoints/secret-syncs/chef/update.mdx new file mode 100644 index 000000000..d5c39484d --- /dev/null +++ b/docs/api-reference/endpoints/secret-syncs/chef/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v1/secret-syncs/chef/{syncId}" +--- diff --git a/docs/docs.json b/docs/docs.json index 86aca8145..526e40b69 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -114,6 +114,7 @@ "integrations/app-connections/bitbucket", "integrations/app-connections/camunda", "integrations/app-connections/checkly", + "integrations/app-connections/chef", "integrations/app-connections/cloudflare", "integrations/app-connections/databricks", "integrations/app-connections/digital-ocean", @@ -540,6 +541,7 @@ "integrations/secret-syncs/bitbucket", "integrations/secret-syncs/camunda", "integrations/secret-syncs/checkly", + "integrations/secret-syncs/chef", "integrations/secret-syncs/cloudflare-pages", "integrations/secret-syncs/cloudflare-workers", "integrations/secret-syncs/databricks", @@ -814,7 +816,10 @@ "groups": [ { "group": "Infisical PAM", - "pages": ["documentation/platform/pam/overview"] + "pages": [ + "documentation/platform/pam/overview", + "documentation/platform/pam/session-recording" + ] } ] } @@ -1690,6 +1695,18 @@ "api-reference/endpoints/app-connections/checkly/delete" ] }, + { + "group": "Chef", + "pages": [ + "api-reference/endpoints/app-connections/chef/list", + "api-reference/endpoints/app-connections/chef/available", + "api-reference/endpoints/app-connections/chef/get-by-id", + "api-reference/endpoints/app-connections/chef/get-by-name", + "api-reference/endpoints/app-connections/chef/create", + "api-reference/endpoints/app-connections/chef/update", + "api-reference/endpoints/app-connections/chef/delete" + ] + }, { "group": "Cloudflare", "pages": [ @@ -2181,6 +2198,20 @@ "api-reference/endpoints/secret-syncs/checkly/remove-secrets" ] }, + { + "group": "Chef", + "pages": [ + "api-reference/endpoints/secret-syncs/chef/list", + "api-reference/endpoints/secret-syncs/chef/get-by-id", + "api-reference/endpoints/secret-syncs/chef/get-by-name", + "api-reference/endpoints/secret-syncs/chef/create", + "api-reference/endpoints/secret-syncs/chef/update", + "api-reference/endpoints/secret-syncs/chef/delete", + "api-reference/endpoints/secret-syncs/chef/sync-secrets", + "api-reference/endpoints/secret-syncs/chef/import-secrets", + "api-reference/endpoints/secret-syncs/chef/remove-secrets" + ] + }, { "group": "Cloudflare Pages", "pages": [ @@ -2336,6 +2367,7 @@ "api-reference/endpoints/secret-syncs/laravel-forge/update", "api-reference/endpoints/secret-syncs/laravel-forge/delete", "api-reference/endpoints/secret-syncs/laravel-forge/sync-secrets", + "api-reference/endpoints/secret-syncs/laravel-forge/import-secrets", "api-reference/endpoints/secret-syncs/laravel-forge/remove-secrets" ] }, diff --git a/docs/documentation/guides/organization-structure.mdx b/docs/documentation/guides/organization-structure.mdx index 3cba64678..752c06ccc 100644 --- a/docs/documentation/guides/organization-structure.mdx +++ b/docs/documentation/guides/organization-structure.mdx @@ -24,9 +24,11 @@ Infisical is designed to provide comprehensive, centralized, and efficient manag ### 2. Projects - **Definition and Role**: [Projects](/documentation/platform/project) are the highest-level construct within an [organization](/documentation/platform/organization) in Infisical. They serve as the primary container for all functionalities. -- **Correspondence to Code Repositories**: Projects typically align with specific code repositories. +- **Common Project Mappings**: Projects typically align with applications, services, or code repositories — each being a valid and common approach depending on your organizational structure. - **Functional Capabilities**: Each project encompasses features for managing secrets, certificates, and encryption keys, serving as the central hub for these resources. +Projects are isolated from one another. Secrets, certificates, and other resources cannot be shared or referenced across different projects. Each project maintains its own separate set of resources. + ### 3. Environments - **Purpose**: Environments are designed for organizing and compartmentalizing secrets within projects. @@ -40,8 +42,9 @@ Infisical is designed to provide comprehensive, centralized, and efficient manag ### 5. Imports -- **Purpose and Benefits**: To promote reusability and avoid redundancy, Infisical supports the use of imports. This allows secrets, folders, or entire environments to be referenced across multiple projects as needed. -- **Best Practice**: Utilizing [secret imports](/documentation/platform/secret-reference#secret-imports) or [references](/documentation/platform/secret-reference#secret-referencing) ensures consistency and minimizes manual overhead. +- **Purpose and Benefits**: To promote reusability and avoid redundancy within a project, Infisical supports the use of imports and references. This allows secrets, folders, or entire environments to be referenced within the same project as needed. +- **Project Isolation**: Imports and references only work within a single project. Secrets cannot be imported or referenced across different projects, as projects are isolated from one another. +- **Best Practice**: Utilizing [secret imports](/documentation/platform/secret-reference#secret-imports) or [references](/documentation/platform/secret-reference#secret-referencing) ensures consistency and minimizes manual overhead when managing secrets within a project. ### 6. Approval Workflows diff --git a/docs/documentation/platform/pam/session-recording.mdx b/docs/documentation/platform/pam/session-recording.mdx new file mode 100644 index 000000000..7e560d5c2 --- /dev/null +++ b/docs/documentation/platform/pam/session-recording.mdx @@ -0,0 +1,60 @@ +--- +title: "Session Recording" +sidebarTitle: "Session Recording" +description: "Learn how Infisical records and stores session activity for auditing and monitoring." +--- + +Infisical's Privileged Access Management (PAM) provides robust session recording capabilities to help you audit and monitor user activity across your infrastructure. + +## How It Works + +When a user initiates a session through the Infisical Gateway, a recording of the session begins. The gateway securely caches all recording data in temporary encrypted files on its local system. + +Once the session concludes, the gateway transmits the complete recording to the Infisical platform for long-term, centralized storage. This asynchronous process ensures that sessions remain operational even if the connection to the Infisical platform is temporarily lost. After the upload is complete, administrators can search and review the session logs in the Infisical UI. + +## What's Captured + +The content captured during a session depends on the type of resource being accessed. + +### Database Sessions + +For database connections, Infisical captures all queries executed and their corresponding responses. + + +Support for additional resource types like SSH and RDP is coming soon. + + +## Viewing Recordings + +To review session recordings: + +1. Navigate to the **PAM Sessions** page in your project. +2. Click on a session from the list to view its details. + +![PAM Sessions](/images/pam/session-recording/sessions-page.png) + +The session details page provides key information, including the complete session logs, connection status, the user who initiated it, and more. + +![PAM Individual Session](/images/pam/session-recording/individual-session-page.png) + +### Searching Logs + +You can use the search bar to quickly find relevant information: + +- **On the main Sessions page:** Search across all session logs to locate specific queries or outputs. +- **On an individual session page:** Search within that specific session's logs to pinpoint activity. + +![PAM Sessions Search](/images/pam/session-recording/sessions-page-search.png) + +![PAM Individual Session Search](/images/pam/session-recording/individual-session-page-search.png) + +## FAQ + + + + Yes. All session recordings are encrypted at rest by default, ensuring your audit data is always secure. + + + Currently, Infisical uses an asynchronous approach where the gateway records the entire session locally before uploading it. This design makes your PAM sessions more resilient, as they don't depend on a constant, active connection to the Infisical platform. We may introduce live streaming capabilities in a future release. + + diff --git a/docs/documentation/platform/pki/ca/azure-adcs.mdx b/docs/documentation/platform/pki/ca/azure-adcs.mdx index 1a0e2d505..6e913be06 100644 --- a/docs/documentation/platform/pki/ca/azure-adcs.mdx +++ b/docs/documentation/platform/pki/ca/azure-adcs.mdx @@ -28,46 +28,54 @@ This section walks you through the complete end-to-end process of setting up Azu **Certificate Authority** to access the external CAs page. ![External CA Page](/images/platform/pki/azure-adcs/azure-adcs-external-ca-page.png) - - Click **Create CA** and configure: - **Type**: Choose **Azure AD Certificate - Service** - **Name**: Friendly name for this CA (e.g., "Production ADCS CA") - - **App Connection**: Choose your ADCS connection from the dropdown ![External - CA Form](/images/platform/pki/azure-adcs/azure-adcs-external-ca-form.png) - - - Once created, your Azure ADCS Certificate Authority will appear in the list - and be ready for use. ![External CA - Created](/images/platform/pki/azure-adcs/azure-adcs-external-ca-created.png) - - - Go to **Subscribers** to access the subscribers page. ![Subscribers - Page](/images/platform/pki/azure-adcs/azure-adcs-subscribers-page.png) - - - Click **Add Subscriber** and configure: - **Name**: Unique subscriber name - (e.g., "web-server-certs") - **Certificate Authority**: Select your ADCS CA - - **Common Name**: Certificate CN (e.g., "api.example.com") - **Certificate - Template**: Select from dynamically loaded ADCS templates - **Subject - Alternative Names**: DNS names, IP addresses, or email addresses - **TTL**: - Certificate validity period (e.g., "1y" for 1 year) - **Additional Subject - Fields**: Organization, OU, locality, state, country, email (if required by - template) ![Subscribers - Form](/images/platform/pki/azure-adcs/azure-adcs-subscribers-form.png) - - - Your subscriber is now created and ready to issue certificates. ![Subscriber - Created](/images/platform/pki/azure-adcs/azure-adcs-subscribers-created.png) - - - Click into your subscriber and click **Order Certificate** to generate a new - certificate using your ADCS template. ![Issue New - Certificate](/images/platform/pki/azure-adcs/azure-adcs-subscriber-issue-new-certificate.png) - - - Your certificate has been successfully issued by the ADCS server and is ready - for use. ![Certificate - Created](/images/platform/pki/azure-adcs/azure-adcs-certificate-created.png) - + + + Click **Create CA** and configure: - **Type**: Choose **Active Directory + Certificate Services (AD CS)** - **Name**: Friendly name for this CA (e.g., + "Production ADCS CA") - **App Connection**: Choose your ADCS connection from + the dropdown ![External CA + Form](/images/platform/pki/azure-adcs/azure-adcs-external-ca-form.png) + + + + Once created, your Azure ADCS Certificate Authority will appear in the list + and be ready for use. ![External CA + Created](/images/platform/pki/azure-adcs/azure-adcs-external-ca-created.png) + + + + Go to **Subscribers** to access the subscribers page. ![Subscribers + Page](/images/platform/pki/azure-adcs/azure-adcs-subscribers-page.png) + + + + Click **Add Subscriber** and configure: - **Name**: Unique subscriber name + (e.g., "web-server-certs") - **Certificate Authority**: Select your ADCS CA + - **Common Name**: Certificate CN (e.g., "api.example.com") - **Certificate + Template**: Select from dynamically loaded ADCS templates - **Subject + Alternative Names**: DNS names, IP addresses, or email addresses - **TTL**: + Certificate validity period (e.g., "1y" for 1 year) - **Additional Subject + Fields**: Organization, OU, locality, state, country, email (if required by + template) ![Subscribers + Form](/images/platform/pki/azure-adcs/azure-adcs-subscribers-form.png) + + + + Your subscriber is now created and ready to issue certificates. ![Subscriber + Created](/images/platform/pki/azure-adcs/azure-adcs-subscribers-created.png) + + + + Click into your subscriber and click **Order Certificate** to generate a new + certificate using your ADCS template. ![Issue New + Certificate](/images/platform/pki/azure-adcs/azure-adcs-subscriber-issue-new-certificate.png) + + + + Your certificate has been successfully issued by the ADCS server and is + ready for use. ![Certificate + Created](/images/platform/pki/azure-adcs/azure-adcs-certificate-created.png) + Navigate to **Certificates** to view detailed information about all issued diff --git a/docs/images/app-connections/chef/app-connection-form.png b/docs/images/app-connections/chef/app-connection-form.png new file mode 100644 index 000000000..9ba5dc8e8 Binary files /dev/null and b/docs/images/app-connections/chef/app-connection-form.png differ diff --git a/docs/images/app-connections/chef/app-connection-generated.png b/docs/images/app-connections/chef/app-connection-generated.png new file mode 100644 index 000000000..c8ab79540 Binary files /dev/null and b/docs/images/app-connections/chef/app-connection-generated.png differ diff --git a/docs/images/app-connections/chef/app-connection-option.png b/docs/images/app-connections/chef/app-connection-option.png new file mode 100644 index 000000000..2a3e46920 Binary files /dev/null and b/docs/images/app-connections/chef/app-connection-option.png differ diff --git a/docs/images/app-connections/chef/chef-connection-details.png b/docs/images/app-connections/chef/chef-connection-details.png new file mode 100644 index 000000000..9fcb7ee0d Binary files /dev/null and b/docs/images/app-connections/chef/chef-connection-details.png differ diff --git a/docs/images/app-connections/chef/chef-dashboard.png b/docs/images/app-connections/chef/chef-dashboard.png new file mode 100644 index 000000000..65195a8d0 Binary files /dev/null and b/docs/images/app-connections/chef/chef-dashboard.png differ diff --git a/docs/images/app-connections/chef/chef-folder.png b/docs/images/app-connections/chef/chef-folder.png new file mode 100644 index 000000000..29f698080 Binary files /dev/null and b/docs/images/app-connections/chef/chef-folder.png differ diff --git a/docs/images/app-connections/chef/download-starter-kit.png b/docs/images/app-connections/chef/download-starter-kit.png new file mode 100644 index 000000000..27f9582ad Binary files /dev/null and b/docs/images/app-connections/chef/download-starter-kit.png differ diff --git a/docs/images/app-connections/chef/extract-starter-kit.png b/docs/images/app-connections/chef/extract-starter-kit.png new file mode 100644 index 000000000..8743dae9b Binary files /dev/null and b/docs/images/app-connections/chef/extract-starter-kit.png differ diff --git a/docs/images/app-connections/chef/private-key-file.png b/docs/images/app-connections/chef/private-key-file.png new file mode 100644 index 000000000..052103d0e Binary files /dev/null and b/docs/images/app-connections/chef/private-key-file.png differ diff --git a/docs/images/app-connections/chef/starter-kit.png b/docs/images/app-connections/chef/starter-kit.png new file mode 100644 index 000000000..b9b9621e4 Binary files /dev/null and b/docs/images/app-connections/chef/starter-kit.png differ diff --git a/docs/images/pam/session-recording/individual-session-page-search.png b/docs/images/pam/session-recording/individual-session-page-search.png new file mode 100644 index 000000000..ce369f515 Binary files /dev/null and b/docs/images/pam/session-recording/individual-session-page-search.png differ diff --git a/docs/images/pam/session-recording/individual-session-page.png b/docs/images/pam/session-recording/individual-session-page.png new file mode 100644 index 000000000..2926caf67 Binary files /dev/null and b/docs/images/pam/session-recording/individual-session-page.png differ diff --git a/docs/images/pam/session-recording/sessions-page-search.png b/docs/images/pam/session-recording/sessions-page-search.png new file mode 100644 index 000000000..a90cda587 Binary files /dev/null and b/docs/images/pam/session-recording/sessions-page-search.png differ diff --git a/docs/images/pam/session-recording/sessions-page.png b/docs/images/pam/session-recording/sessions-page.png new file mode 100644 index 000000000..8faab291d Binary files /dev/null and b/docs/images/pam/session-recording/sessions-page.png differ diff --git a/docs/images/secret-syncs/chef/select-option.png b/docs/images/secret-syncs/chef/select-option.png new file mode 100644 index 000000000..5fba1e415 Binary files /dev/null and b/docs/images/secret-syncs/chef/select-option.png differ diff --git a/docs/images/secret-syncs/chef/sync-created.png b/docs/images/secret-syncs/chef/sync-created.png new file mode 100644 index 000000000..c6383deb3 Binary files /dev/null and b/docs/images/secret-syncs/chef/sync-created.png differ diff --git a/docs/images/secret-syncs/chef/sync-destination.png b/docs/images/secret-syncs/chef/sync-destination.png new file mode 100644 index 000000000..073631956 Binary files /dev/null and b/docs/images/secret-syncs/chef/sync-destination.png differ diff --git a/docs/images/secret-syncs/chef/sync-details.png b/docs/images/secret-syncs/chef/sync-details.png new file mode 100644 index 000000000..3e8a92008 Binary files /dev/null and b/docs/images/secret-syncs/chef/sync-details.png differ diff --git a/docs/images/secret-syncs/chef/sync-options.png b/docs/images/secret-syncs/chef/sync-options.png new file mode 100644 index 000000000..75f507310 Binary files /dev/null and b/docs/images/secret-syncs/chef/sync-options.png differ diff --git a/docs/images/secret-syncs/chef/sync-review.png b/docs/images/secret-syncs/chef/sync-review.png new file mode 100644 index 000000000..de0f10f8a Binary files /dev/null and b/docs/images/secret-syncs/chef/sync-review.png differ diff --git a/docs/images/secret-syncs/chef/sync-source.png b/docs/images/secret-syncs/chef/sync-source.png new file mode 100644 index 000000000..877b104ef Binary files /dev/null and b/docs/images/secret-syncs/chef/sync-source.png differ diff --git a/docs/integrations/app-connections/chef.mdx b/docs/integrations/app-connections/chef.mdx new file mode 100644 index 000000000..fdb866557 --- /dev/null +++ b/docs/integrations/app-connections/chef.mdx @@ -0,0 +1,150 @@ +--- +title: "Chef Connection" +description: "Learn how to configure a Chef Connection for Infisical." +--- + + + Chef App Connection is a paid feature. + + If you're using Infisical Cloud, then it is available under the **Enterprise Tier**. If you're self-hosting Infisical, + then you should contact team@infisical.com to purchase an enterprise license to use it. + + + +Infisical supports the use of User Private Key to connect with Chef Server. + +Please access your **starter kit** to get all the required information to create a Chef Connection. + + + + If you download a new starter kit, your previous private key/user key will + no longer be valid. Please make sure to update all the places that use the + previous private key. + + + + ![Chef Server User Keys](/images/app-connections/chef/chef-dashboard.png) + + + ![Starter Kit](/images/app-connections/chef/starter-kit.png) + + + ![Download Starter + Kit](/images/app-connections/chef/download-starter-kit.png) + + + ![Extract Starter + Kit](/images/app-connections/chef/extract-starter-kit.png) + + + + + + + Open your starter kit's folder(or `chef-repo`) and navigate to the `.chef` + folder. + + Please make sure you have hidden files visible in your file explorer. + + ![.chef folder](/images/app-connections/chef/chef-folder.png) + + + In the `.chef` folder, you will find a `[your-username].pem` file. ![Private + Key File](/images/app-connections/chef/private-key-file.png) + + **Private Key:** Copy the content of the private key file. + + + + Open the `config.rb` file and copy the content of the file. + ![Config.rb File Content](/images/app-connections/chef/chef-connection-details.png) + + **User Name(1):** The user name of the chef user. + + **Server URL(2):** The server url of the chef server. + + **Organization Name(3):** The organization name of the chef server. + + + + +## Create a Chef Connection in Infisical via UI + + + + + + In your Infisical dashboard, navigate to the **App Connections** page in the desired project. + ![App Connections Tab](/images/app-connections/general/add-connection.png) + + + Click **+ Add Connection** and choose **Chef** Connection from the list of integrations. + ![Select Chef Connection](/images/app-connections/chef/app-connection-option.png) + + + Complete the form by providing: + - A descriptive name for the connection + - An optional description + - Server URL(optional): The URL of the Chef server to connect with (defaults to https://api.chef.io) + - Organization short name + - User name + - Private key: Your Chef user's private key (.pem file) + + ![Chef Connection Modal](/images/app-connections/chef/app-connection-form.png) + + + After submitting the form, your **Chef Connection** will be successfully created and ready to use with your Infisical project. + ![Chef Connection Created](/images/app-connections/chef/app-connection-generated.png) + + + + + + + To create a Chef Connection via API, send a request to the [Create Chef Connection](/api-reference/endpoints/app-connections/chef/create) endpoint. + + ### Sample request + + ```bash Request + curl --request POST \ + --url https://app.infisical.com/api/v1/app-connections/chef \ + --header 'Content-Type: application/json' \ + --data '{ + "name": "my-chef-connection", + "method": "user-key", + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", + "credentials": { + "orgName": "my-org", + "userName": "my-user", + "privateKey": "your-private-key" + } + }' + ``` + + ### Sample response + + ```bash Response + { + "appConnection": { + "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab", + "name": "my-chef-connection", + "description": null, + "projectId": "7ffbb072-2575-495a-b5b0-127f88caef78", + "version": 1, + "orgId": "abcdef12-3456-7890-abcd-ef1234567890", + "createdAt": "2025-10-13T10:15:00.000Z", + "updatedAt": "2025-10-13T10:15:00.000Z", + "isPlatformManagedCredentials": false, + "credentialsHash": "d41d8cd98f00b204e9800998ecf8427e", + "app": "chef", + "method": "user-key", + "credentials": { + "orgName": "my-org", + "userName": "my-user", + } + } + } + ``` + + + diff --git a/docs/integrations/secret-syncs/chef.mdx b/docs/integrations/secret-syncs/chef.mdx new file mode 100644 index 000000000..423d42b4b --- /dev/null +++ b/docs/integrations/secret-syncs/chef.mdx @@ -0,0 +1,162 @@ +--- +title: "Chef Sync" +description: "Learn how to configure a Chef Sync for Infisical." +--- + + + Chef Sync is a paid feature. + + If you're using Infisical Cloud, then it is available under the **Enterprise Tier**. If you're self-hosting Infisical, + then you should contact team@infisical.com to purchase an enterprise license to use it. + + + +**Prerequisites:** + +- Create a [Chef Connection](/integrations/app-connections/chef) + + + + + + Navigate to **Project** > **Integrations** and select the **Secret Syncs** tab. Click on the **Add Sync** button. + + ![Secret Syncs Tab](/images/secret-syncs/general/secret-sync-tab.png) + + + ![Select Chef](/images/secret-syncs/chef/select-option.png) + + + Configure the **Source** from where secrets should be retrieved, then click **Next**. + + ![Configure Source](/images/secret-syncs/chef/sync-source.png) + + - **Environment**: The project environment to retrieve secrets from. + - **Secret Path**: The folder path to retrieve secrets from. + + + If you need to sync secrets from multiple folder locations, check out [secret imports](/documentation/platform/secret-reference#secret-imports). + + + + Configure the **Destination** to where secrets should be deployed, then click **Next**. + + ![Configure Destination](/images/secret-syncs/chef/sync-destination.png) + + - **Chef Connection**: The Chef Connection to authenticate with. + - **Data Bag**: The Data Bag to sync secrets to. + - **Data Bag Item**: The Data Bag Item to sync secrets to. + + + Configure the **Sync Options** to specify how secrets should be synced, then click **Next**. + + ![Configure Options](/images/secret-syncs/chef/sync-options.png) + + - **Initial Sync Behavior**: Determines how Infisical should resolve the initial sync. + - **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical. + - **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Chef when keys conflict. + - **Import Secrets (Prioritize Chef)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Chef over Infisical when keys conflict. + - **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and `{{environment}}` for the environment. + + We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched. + + - **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only. + - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical. + + + Configure the **Details** of your Chef Sync, then click **Next**. + + ![Configure Details](/images/secret-syncs/chef/sync-details.png) + + - **Name**: The name of your sync. Must be slug-friendly. + - **Description**: An optional description for your sync. + + + Review your Chef Sync configuration, then click **Create Sync**. + + ![Review Configuration](/images/secret-syncs/chef/sync-review.png) + + + If enabled, your Chef Sync will begin syncing your secrets to the destination endpoint. + + ![Sync Created](/images/secret-syncs/chef/sync-created.png) + + + + + + + To create a **Chef Sync**, make an API request to the [Create Chef Sync](/api-reference/endpoints/secret-syncs/chef/create) API endpoint. + + ### Sample request + + ```bash Request + curl --request POST \ + --url https://app.infisical.com/api/v1/secret-syncs/chef \ + --header 'Content-Type: application/json' \ + --data '{ + "name": "my-chef-sync", + "projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "description": "sync to chef site", + "connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "environment": "dev", + "secretPath": "/", + "isEnabled": true, + "isAutoSyncEnabled": true, + "syncOptions": { + "initialSyncBehavior": "overwrite-destination", + "disableSecretDeletion": false + }, + "destinationConfig": { + "dataBagName": "my-data-bag", + "dataBagItemName": "my-data-bag-item" + } + }' + ``` + + ### Sample response + + ```bash Response + { + "secretSync": { + "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "name": "my-chef-sync", + "description": "sync to chef site", + "folderId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "createdAt": "2025-07-19T12:00:00Z", + "updatedAt": "2025-07-19T12:00:00Z", + "syncStatus": "succeeded", + "lastSyncJobId": "job-1234", + "lastSyncMessage": null, + "lastSyncedAt": "2025-07-19T12:00:00Z", + "syncOptions": { + "initialSyncBehavior": "overwrite-destination", + "disableSecretDeletion": false + }, + "projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "connection": { + "app": "chef", + "name": "my-chef-connection", + "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a" + }, + "environment": { + "slug": "dev", + "name": "Development", + "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a" + }, + "folder": { + "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", + "path": "/" + }, + "destination": "chef", + "destinationConfig": { + "dataBagName": "my-data-bag", + "dataBagItemName": "my-data-bag-item" + } + } + } + ``` + + + diff --git a/docs/self-hosting/guides/monitoring-telemetry.mdx b/docs/self-hosting/guides/monitoring-telemetry.mdx index b23c51b27..763826d4f 100644 --- a/docs/self-hosting/guides/monitoring-telemetry.mdx +++ b/docs/self-hosting/guides/monitoring-telemetry.mdx @@ -27,7 +27,9 @@ Both approaches provide the same metrics data in OTEL format, so you can choose - Access to deploy monitoring services (Prometheus, Grafana, etc.) - Basic understanding of Prometheus and Grafana -## Environment Variables +## Setup + +### Environment Variables Configure the following environment variables in your Infisical backend: @@ -37,287 +39,304 @@ OTEL_TELEMETRY_COLLECTION_ENABLED=true # Choose export type: "prometheus" or "otlp" OTEL_EXPORT_TYPE=prometheus - -# For OTLP push mode, also configure: -# OTEL_EXPORT_OTLP_ENDPOINT=http://otel-collector:4318/v1/metrics -# OTEL_COLLECTOR_BASIC_AUTH_USERNAME=your_collector_username -# OTEL_COLLECTOR_BASIC_AUTH_PASSWORD=your_collector_password -# OTEL_OTLP_PUSH_INTERVAL=30000 ``` -**Note**: The `OTEL_COLLECTOR_BASIC_AUTH_USERNAME` and `OTEL_COLLECTOR_BASIC_AUTH_PASSWORD` values must match the credentials configured in your OpenTelemetry Collector's `basicauth/server` extension. These are not hardcoded values - you configure them in your collector configuration file. + + + This approach exposes metrics on port 9464 at the `/metrics` endpoint, allowing Prometheus to scrape the data. The metrics are exposed in Prometheus format but originate from OpenTelemetry instrumentation. -## Option 1: Pull-based Monitoring (Prometheus) + ### Configuration -This approach exposes metrics on port 9464 at the `/metrics` endpoint, allowing Prometheus to scrape the data. The metrics are exposed in Prometheus format but originate from OpenTelemetry instrumentation. + + +```bash +OTEL_TELEMETRY_COLLECTION_ENABLED=true +OTEL_EXPORT_TYPE=prometheus +``` + -### Configuration + + Expose the metrics port in your Infisical backend: -1. **Enable Prometheus export in Infisical**: + - **Docker**: Expose port 9464 + - **Kubernetes**: Create a service exposing port 9464 + - **Other**: Ensure port 9464 is accessible to your monitoring stack + - ```bash - OTEL_TELEMETRY_COLLECTION_ENABLED=true - OTEL_EXPORT_TYPE=prometheus - ``` - -2. **Expose the metrics port** in your Infisical backend: - - - **Docker**: Expose port 9464 - - **Kubernetes**: Create a service exposing port 9464 - - **Other**: Ensure port 9464 is accessible to your monitoring stack - -3. **Create Prometheus configuration** (`prometheus.yml`): - - ```yaml - global: - scrape_interval: 30s - evaluation_interval: 30s - - scrape_configs: - - job_name: "infisical" - scrape_interval: 30s - static_configs: - - targets: ["infisical-backend:9464"] # Adjust hostname/port based on your deployment - metrics_path: "/metrics" - ``` - - **Note**: Replace `infisical-backend:9464` with the actual hostname and port where your Infisical backend is running. This could be: - - - **Docker Compose**: `infisical-backend:9464` (service name) - - **Kubernetes**: `infisical-backend.default.svc.cluster.local:9464` (service name) - - **Bare Metal**: `192.168.1.100:9464` (actual IP address) - - **Cloud**: `your-infisical.example.com:9464` (domain name) - -### Deployment Options - -#### Docker Compose + +Create `prometheus.yml`: ```yaml -services: +global: + scrape_interval: 30s + evaluation_interval: 30s + +scrape_configs: + - job_name: "infisical" + scrape_interval: 30s + static_configs: + - targets: ["infisical-backend:9464"] # Adjust hostname/port based on your deployment + metrics_path: "/metrics" +``` + + +Replace `infisical-backend:9464` with the actual hostname and port where your Infisical backend is running. This could be: + +- **Docker Compose**: `infisical-backend:9464` (service name) +- **Kubernetes**: `infisical-backend.default.svc.cluster.local:9464` (service name) +- **Bare Metal**: `192.168.1.100:9464` (actual IP address) +- **Cloud**: `your-infisical.example.com:9464` (domain name) + + + + + ### Deployment Options + + Once you've configured Infisical to expose metrics, you'll need to deploy Prometheus to scrape and store them. Below are examples for different deployment environments. Choose the option that matches your infrastructure. + + + + ```yaml + services: + prometheus: + image: prom/prometheus:latest + ports: + - "9090:9090" + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro + command: + - "--config.file=/etc/prometheus/prometheus.yml" + + grafana: + image: grafana/grafana:latest + ports: + - "3000:3000" + environment: + - GF_SECURITY_ADMIN_USER=admin + - GF_SECURITY_ADMIN_PASSWORD=admin + ``` + + + ```yaml + # prometheus-deployment.yaml + apiVersion: apps/v1 + kind: Deployment + metadata: + name: prometheus + spec: + replicas: 1 + selector: + matchLabels: + app: prometheus + template: + metadata: + labels: + app: prometheus + spec: + containers: + - name: prometheus + image: prom/prometheus:latest + ports: + - containerPort: 9090 + volumeMounts: + - name: config + mountPath: /etc/prometheus + volumes: + - name: config + configMap: + name: prometheus-config + + --- + # prometheus-service.yaml + apiVersion: v1 + kind: Service + metadata: + name: prometheus + spec: + selector: + app: prometheus + ports: + - port: 9090 + targetPort: 9090 + type: ClusterIP + ``` + + + ```bash + helm repo add prometheus-community https://prometheus-community.github.io/helm-charts + helm install prometheus prometheus-community/prometheus \ + --set server.config.global.scrape_interval=30s \ + --set server.config.scrape_configs[0].job_name=infisical \ + --set server.config.scrape_configs[0].static_configs[0].targets[0]=infisical-backend:9464 + ``` + + + + + + This approach sends metrics directly to an OpenTelemetry Collector via the OTLP protocol. This gives you the most flexibility as you can configure the collector to export to multiple backends simultaneously. + + ### Configuration + + + +```bash +OTEL_TELEMETRY_COLLECTION_ENABLED=true +OTEL_EXPORT_TYPE=otlp +OTEL_EXPORT_OTLP_ENDPOINT=http://otel-collector:4318/v1/metrics +OTEL_COLLECTOR_BASIC_AUTH_USERNAME=infisical +OTEL_COLLECTOR_BASIC_AUTH_PASSWORD=infisical +OTEL_OTLP_PUSH_INTERVAL=30000 +``` + + + +Create `otel-collector-config.yaml`: + +```yaml +extensions: + health_check: + pprof: + zpages: + basicauth/server: + htpasswd: + inline: | + your_username:your_password + +receivers: + otlp: + protocols: + http: + endpoint: 0.0.0.0:4318 + auth: + authenticator: basicauth/server + prometheus: - image: prom/prometheus:latest - ports: - - "9090:9090" - volumes: - - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro - command: - - "--config.file=/etc/prometheus/prometheus.yml" + config: + scrape_configs: + - job_name: otel-collector + scrape_interval: 30s + static_configs: + - targets: [infisical-backend:9464] + metric_relabel_configs: + - action: labeldrop + regex: "service_instance_id|service_name" - grafana: - image: grafana/grafana:latest - ports: - - "3000:3000" - environment: - - GF_SECURITY_ADMIN_USER=admin - - GF_SECURITY_ADMIN_PASSWORD=admin +processors: + batch: + +exporters: + prometheus: + endpoint: "0.0.0.0:8889" + auth: + authenticator: basicauth/server + resource_to_telemetry_conversion: + enabled: true + +service: + extensions: [basicauth/server, health_check, pprof, zpages] + pipelines: + metrics: + receivers: [otlp] + processors: [batch] + exporters: [prometheus] ``` -#### Kubernetes + +Replace `your_username:your_password` with your chosen credentials. These must match the values you set in Infisical's `OTEL_COLLECTOR_BASIC_AUTH_USERNAME` and `OTEL_COLLECTOR_BASIC_AUTH_PASSWORD` environment variables. + + + + +Create Prometheus configuration for the collector: ```yaml -# prometheus-deployment.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: prometheus -spec: - replicas: 1 - selector: - matchLabels: - app: prometheus - template: - metadata: - labels: - app: prometheus - spec: - containers: - - name: prometheus - image: prom/prometheus:latest - ports: - - containerPort: 9090 - volumeMounts: - - name: config - mountPath: /etc/prometheus - volumes: - - name: config - configMap: - name: prometheus-config +global: + scrape_interval: 30s + evaluation_interval: 30s ---- -# prometheus-service.yaml -apiVersion: v1 -kind: Service -metadata: - name: prometheus -spec: - selector: - app: prometheus - ports: - - port: 9090 - targetPort: 9090 - type: ClusterIP +scrape_configs: + - job_name: "otel-collector" + scrape_interval: 30s + static_configs: + - targets: ["otel-collector:8889"] # Adjust hostname/port based on your deployment + metrics_path: "/metrics" ``` -#### Helm + +Replace `otel-collector:8889` with the actual hostname and port where your OpenTelemetry Collector is running. This could be: -```bash -helm repo add prometheus-community https://prometheus-community.github.io/helm-charts -helm install prometheus prometheus-community/prometheus \ - --set server.config.global.scrape_interval=30s \ - --set server.config.scrape_configs[0].job_name=infisical \ - --set server.config.scrape_configs[0].static_configs[0].targets[0]=infisical-backend:9464 -``` +- **Docker Compose**: `otel-collector:8889` (service name) +- **Kubernetes**: `otel-collector.default.svc.cluster.local:8889` (service name) +- **Bare Metal**: `192.168.1.100:8889` (actual IP address) +- **Cloud**: `your-collector.example.com:8889` (domain name) + + + -## Option 2: Push-based Monitoring (OTLP) + ### Deployment Options -This approach sends metrics directly to an OpenTelemetry Collector via the OTLP protocol. This gives you the most flexibility as you can configure the collector to export to multiple backends simultaneously. + After configuring Infisical and the OpenTelemetry Collector, you'll need to deploy the collector to receive metrics from Infisical. Below are examples for different deployment environments. Choose the option that matches your infrastructure. -### Configuration + + + ```yaml + services: + otel-collector: + image: otel/opentelemetry-collector-contrib:latest + ports: + - 4318:4318 # OTLP http receiver + - 8889:8889 # Prometheus exporter metrics + volumes: + - ./otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml:ro + command: + - "--config=/etc/otelcol-contrib/config.yaml" + ``` + + + ```yaml + # otel-collector-deployment.yaml + apiVersion: apps/v1 + kind: Deployment + metadata: + name: otel-collector + spec: + replicas: 1 + selector: + matchLabels: + app: otel-collector + template: + metadata: + labels: + app: otel-collector + spec: + containers: + - name: otel-collector + image: otel/opentelemetry-collector-contrib:latest + ports: + - containerPort: 4318 + - containerPort: 8889 + volumeMounts: + - name: config + mountPath: /etc/otelcol-contrib + volumes: + - name: config + configMap: + name: otel-collector-config + ``` + + + ```bash + helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts + helm install otel-collector open-telemetry/opentelemetry-collector \ + --set config.receivers.otlp.protocols.http.endpoint=0.0.0.0:4318 \ + --set config.exporters.prometheus.endpoint=0.0.0.0:8889 + ``` + + -1. **Enable OTLP export in Infisical**: - - ```bash - OTEL_TELEMETRY_COLLECTION_ENABLED=true - OTEL_EXPORT_TYPE=otlp - OTEL_EXPORT_OTLP_ENDPOINT=http://otel-collector:4318/v1/metrics - OTEL_COLLECTOR_BASIC_AUTH_USERNAME=infisical - OTEL_COLLECTOR_BASIC_AUTH_PASSWORD=infisical - OTEL_OTLP_PUSH_INTERVAL=30000 - ``` - -2. **Create OpenTelemetry Collector configuration** (`otel-collector-config.yaml`): - - ```yaml - extensions: - health_check: - pprof: - zpages: - basicauth/server: - htpasswd: - inline: | - your_username:your_password - - receivers: - otlp: - protocols: - http: - endpoint: 0.0.0.0:4318 - auth: - authenticator: basicauth/server - - prometheus: - config: - scrape_configs: - - job_name: otel-collector - scrape_interval: 30s - static_configs: - - targets: [infisical-backend:9464] - metric_relabel_configs: - - action: labeldrop - regex: "service_instance_id|service_name" - - processors: - batch: - - exporters: - prometheus: - endpoint: "0.0.0.0:8889" - auth: - authenticator: basicauth/server - resource_to_telemetry_conversion: - enabled: true - - service: - extensions: [basicauth/server, health_check, pprof, zpages] - pipelines: - metrics: - receivers: [otlp] - processors: [batch] - exporters: [prometheus] - ``` - - **Important**: Replace `your_username:your_password` with your chosen credentials. These must match the values you set in Infisical's `OTEL_COLLECTOR_BASIC_AUTH_USERNAME` and `OTEL_COLLECTOR_BASIC_AUTH_PASSWORD` environment variables. - -3. **Create Prometheus configuration** for the collector: - - ```yaml - global: - scrape_interval: 30s - evaluation_interval: 30s - - scrape_configs: - - job_name: "otel-collector" - scrape_interval: 30s - static_configs: - - targets: ["otel-collector:8889"] # Adjust hostname/port based on your deployment - metrics_path: "/metrics" - ``` - - **Note**: Replace `otel-collector:8889` with the actual hostname and port where your OpenTelemetry Collector is running. This could be: - - - **Docker Compose**: `otel-collector:8889` (service name) - - **Kubernetes**: `otel-collector.default.svc.cluster.local:8889` (service name) - - **Bare Metal**: `192.168.1.100:8889` (actual IP address) - - **Cloud**: `your-collector.example.com:8889` (domain name) - -### Deployment Options - -#### Docker Compose - -```yaml -services: - otel-collector: - image: otel/opentelemetry-collector-contrib:latest - ports: - - 4318:4318 # OTLP http receiver - - 8889:8889 # Prometheus exporter metrics - volumes: - - ./otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml:ro - command: - - "--config=/etc/otelcol-contrib/config.yaml" -``` - -#### Kubernetes - -```yaml -# otel-collector-deployment.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: otel-collector -spec: - replicas: 1 - selector: - matchLabels: - app: otel-collector - template: - metadata: - labels: - app: otel-collector - spec: - containers: - - name: otel-collector - image: otel/opentelemetry-collector-contrib:latest - ports: - - containerPort: 4318 - - containerPort: 8889 - volumeMounts: - - name: config - mountPath: /etc/otelcol-contrib - volumes: - - name: config - configMap: - name: otel-collector-config -``` - -#### Helm - -```bash -helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts -helm install otel-collector open-telemetry/opentelemetry-collector \ - --set config.receivers.otlp.protocols.http.endpoint=0.0.0.0:4318 \ - --set config.exporters.prometheus.endpoint=0.0.0.0:8889 -``` + + ## Available Metrics @@ -327,166 +346,211 @@ Infisical exposes the following key metrics in OpenTelemetry format: These metrics track all HTTP API requests to Infisical, including request counts, latency, and errors. Use these to monitor overall API health, identify performance bottlenecks, and track usage patterns across users and machine identities. -#### Total API Requests + + + **Metric Name**: `infisical.http.server.request.count` -- **Metric Name**: `infisical.http.server.request.count` -- **Type**: Counter -- **Unit**: `{request}` -- **Description**: Total number of API requests to Infisical (covers both human users and machine identities) -- **Attributes**: - - `infisical.organization.id` (string): Organization ID - - `infisical.organization.name` (string): Organization name (e.g., "Platform Engineering Team") - - `infisical.user.id` (string, optional): User ID if human user - - `infisical.user.email` (string, optional): User email (e.g., "jane.doe@cisco.com") - - `infisical.identity.id` (string, optional): Machine identity ID - - `infisical.identity.name` (string, optional): Machine identity name (e.g., "prod-k8s-operator") - - `infisical.auth.method` (string, optional): Auth method used - - `http.request.method` (string): HTTP method (GET, POST, PUT, DELETE) - - `http.route` (string): API endpoint route pattern - - `http.response.status_code` (int): HTTP status code - - `infisical.project.id` (string, optional): Project ID - - `infisical.project.name` (string, optional): Project name - - `user_agent.original` (string, optional): User agent string - - `client.address` (string, optional): IP address + **Type**: Counter -#### Request Duration + **Unit**: `{request}` -- **Metric Name**: `infisical.http.server.request.duration` -- **Type**: Histogram -- **Unit**: `s` (seconds) -- **Description**: API request latency -- **Buckets**: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] -- **Attributes**: - - `infisical.organization.id` (string): Organization ID - - `infisical.organization.name` (string): Organization name - - `infisical.user.id` (string, optional): User ID if human user - - `infisical.user.email` (string, optional): User email - - `infisical.identity.id` (string, optional): Machine identity ID - - `infisical.identity.name` (string, optional): Machine identity name - - `http.request.method` (string): HTTP method - - `http.route` (string): API endpoint route pattern - - `http.response.status_code` (int): HTTP status code - - `infisical.project.id` (string, optional): Project ID - - `infisical.project.name` (string, optional): Project name + **Description**: Total number of API requests to Infisical (covers both human users and machine identities) -#### API Errors by Actor + **Attributes**: + - `infisical.organization.id` (string): Organization ID + - `infisical.organization.name` (string): Organization name (e.g., "Platform Engineering Team") + - `infisical.user.id` (string, optional): User ID if human user + - `infisical.user.email` (string, optional): User email (e.g., "jane.doe@cisco.com") + - `infisical.identity.id` (string, optional): Machine identity ID + - `infisical.identity.name` (string, optional): Machine identity name (e.g., "prod-k8s-operator") + - `infisical.auth.method` (string, optional): Auth method used + - `http.request.method` (string): HTTP method (GET, POST, PUT, DELETE) + - `http.route` (string): API endpoint route pattern + - `http.response.status_code` (int): HTTP status code + - `infisical.project.id` (string, optional): Project ID + - `infisical.project.name` (string, optional): Project name + - `user_agent.original` (string, optional): User agent string + - `client.address` (string, optional): IP address + -- **Metric Name**: `infisical.http.server.error.count` -- **Type**: Counter -- **Unit**: `{error}` -- **Description**: API errors grouped by actor (for identifying misconfigured services) -- **Attributes**: - - `infisical.organization.id` (string): Organization ID - - `infisical.organization.name` (string): Organization name - - `infisical.user.id` (string, optional): User ID if human - - `infisical.user.email` (string, optional): User email - - `infisical.identity.id` (string, optional): Identity ID if machine - - `infisical.identity.name` (string, optional): Identity name - - `http.route` (string): API endpoint where error occurred - - `http.request.method` (string): HTTP method - - `error.type` (string): Error category/type (client_error, server_error, auth_error, rate_limit_error, etc.) - - `infisical.project.id` (string, optional): Project ID - - `infisical.project.name` (string, optional): Project name - - `client.address` (string, optional): IP address - - `user_agent.original` (string, optional): User agent information + + **Metric Name**: `infisical.http.server.request.duration` + + **Type**: Histogram + + **Unit**: `s` (seconds) + + **Description**: API request latency + + **Buckets**: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] + + **Attributes**: + - `infisical.organization.id` (string): Organization ID + - `infisical.organization.name` (string): Organization name + - `infisical.user.id` (string, optional): User ID if human user + - `infisical.user.email` (string, optional): User email + - `infisical.identity.id` (string, optional): Machine identity ID + - `infisical.identity.name` (string, optional): Machine identity name + - `http.request.method` (string): HTTP method + - `http.route` (string): API endpoint route pattern + - `http.response.status_code` (int): HTTP status code + - `infisical.project.id` (string, optional): Project ID + - `infisical.project.name` (string, optional): Project name + + + + **Metric Name**: `infisical.http.server.error.count` + + **Type**: Counter + + **Unit**: `{error}` + + **Description**: API errors grouped by actor (for identifying misconfigured services) + + **Attributes**: + - `infisical.organization.id` (string): Organization ID + - `infisical.organization.name` (string): Organization name + - `infisical.user.id` (string, optional): User ID if human + - `infisical.user.email` (string, optional): User email + - `infisical.identity.id` (string, optional): Identity ID if machine + - `infisical.identity.name` (string, optional): Identity name + - `http.route` (string): API endpoint where error occurred + - `http.request.method` (string): HTTP method + - `error.type` (string): Error category/type (client_error, server_error, auth_error, rate_limit_error, etc.) + - `infisical.project.id` (string, optional): Project ID + - `infisical.project.name` (string, optional): Project name + - `client.address` (string, optional): IP address + - `user_agent.original` (string, optional): User agent information + + ### Secret Operations Metrics These metrics provide visibility into secret access patterns, helping you understand which secrets are being accessed, by whom, and from where. Essential for security auditing and access pattern analysis. -#### Secret Read Operations - -- **Metric Name**: `infisical.secret.read.count` -- **Type**: Counter -- **Unit**: `{operation}` -- **Description**: Number of secret read operations -- **Attributes**: - - `infisical.organization.id` (string): Organization ID - - `infisical.organization.name` (string): Organization name - - `infisical.project.id` (string): Project ID - - `infisical.project.name` (string): Project name (e.g., "payment-service-secrets") - - `infisical.environment` (string): Environment (dev, staging, prod) - - `infisical.secret.path` (string): Path to secrets (e.g., "/microservice-a/database") - - `infisical.secret.name` (string, optional): Name of secret - - `infisical.user.id` (string, optional): User ID if human - - `infisical.user.email` (string, optional): User email - - `infisical.identity.id` (string, optional): Machine identity ID - - `infisical.identity.name` (string, optional): Machine identity name - - `user_agent.original` (string, optional): User agent/SDK information - - `client.address` (string, optional): IP address + + + **Metric Name**: `infisical.secret.read.count` + + **Type**: Counter + + **Unit**: `{operation}` + + **Description**: Number of secret read operations + + **Attributes**: + - `infisical.organization.id` (string): Organization ID + - `infisical.organization.name` (string): Organization name + - `infisical.project.id` (string): Project ID + - `infisical.project.name` (string): Project name (e.g., "payment-service-secrets") + - `infisical.environment` (string): Environment (dev, staging, prod) + - `infisical.secret.path` (string): Path to secrets (e.g., "/microservice-a/database") + - `infisical.secret.name` (string, optional): Name of secret + - `infisical.user.id` (string, optional): User ID if human + - `infisical.user.email` (string, optional): User email + - `infisical.identity.id` (string, optional): Machine identity ID + - `infisical.identity.name` (string, optional): Machine identity name + - `user_agent.original` (string, optional): User agent/SDK information + - `client.address` (string, optional): IP address + + ### Authentication Metrics These metrics track authentication attempts and outcomes, enabling you to monitor login success rates, detect potential security threats, and identify authentication issues. -#### Login Attempts - -- **Metric Name**: `infisical.auth.attempt.count` -- **Type**: Counter -- **Unit**: `{attempt}` -- **Description**: Authentication attempts (both successful and failed) -- **Attributes**: - - `infisical.organization.id` (string): Organization ID - - `infisical.organization.name` (string): Organization name - - `infisical.user.id` (string, optional): User ID if human (if identifiable) - - `infisical.user.email` (string, optional): User email (if identifiable) - - `infisical.identity.id` (string, optional): Identity ID if machine (if identifiable) - - `infisical.identity.name` (string, optional): Identity name (if identifiable) - - `infisical.auth.method` (string): Authentication method attempted - - `infisical.auth.result` (string): success or failure - - `error.type` (string, optional): Reason for failure if failed (invalid_credentials, expired_token, invalid_token, etc.) - - `client.address` (string): IP address - - `user_agent.original` (string, optional): User agent/client information - - `infisical.auth.attempt.username` (string, optional): Attempted username/email (if available) - -### Legacy Metrics - -These metrics are from the previous instrumentation and may be deprecated in future versions. Consider migrating to the new Core API Metrics for more comprehensive observability. - -- `API_latency` - API request latency histogram in milliseconds (Labels: `route`, `method`, `statusCode`) -- `API_errors` - API error count histogram (Labels: `route`, `method`, `type`, `name`) + + + **Metric Name**: `infisical.auth.attempt.count` + + **Type**: Counter + + **Unit**: `{attempt}` + + **Description**: Authentication attempts (both successful and failed) + + **Attributes**: + - `infisical.organization.id` (string): Organization ID + - `infisical.organization.name` (string): Organization name + - `infisical.user.id` (string, optional): User ID if human (if identifiable) + - `infisical.user.email` (string, optional): User email (if identifiable) + - `infisical.identity.id` (string, optional): Identity ID if machine (if identifiable) + - `infisical.identity.name` (string, optional): Identity name (if identifiable) + - `infisical.auth.method` (string): Authentication method attempted + - `infisical.auth.result` (string): success or failure + - `error.type` (string, optional): Reason for failure if failed (invalid_credentials, expired_token, invalid_token, etc.) + - `client.address` (string): IP address + - `user_agent.original` (string, optional): User agent/client information + - `infisical.auth.attempt.username` (string, optional): Attempted username/email (if available) + + ### Integration & Secret Sync Metrics These metrics monitor secret synchronization operations between Infisical and external systems, helping you track sync health, identify integration failures, and troubleshoot connectivity issues. -- `integration_secret_sync_errors` - Integration secret sync error count + + + Integration secret sync error count - - **Labels**: `version`, `integration`, `integrationId`, `type`, `status`, `name`, `projectId` - - **Example**: Monitor integration sync failures across different services + - **Labels**: `version`, `integration`, `integrationId`, `type`, `status`, `name`, `projectId` + - **Example**: Monitor integration sync failures across different services + -- `secret_sync_sync_secrets_errors` - Secret sync operation error count + + Secret sync operation error count - - **Labels**: `version`, `destination`, `syncId`, `projectId`, `type`, `status`, `name` - - **Example**: Track secret sync failures to external systems + - **Labels**: `version`, `destination`, `syncId`, `projectId`, `type`, `status`, `name` + - **Example**: Track secret sync failures to external systems + -- `secret_sync_import_secrets_errors` - Secret import operation error count + + Secret import operation error count - - **Labels**: `version`, `destination`, `syncId`, `projectId`, `type`, `status`, `name` - - **Example**: Monitor secret import failures + - **Labels**: `version`, `destination`, `syncId`, `projectId`, `type`, `status`, `name` + - **Example**: Monitor secret import failures + -- `secret_sync_remove_secrets_errors` - Secret removal operation error count - - **Labels**: `version`, `destination`, `syncId`, `projectId`, `type`, `status`, `name` - - **Example**: Track secret removal operation failures + + Secret removal operation error count + + - **Labels**: `version`, `destination`, `syncId`, `projectId`, `type`, `status`, `name` + - **Example**: Track secret removal operation failures + + ### System Metrics These low-level HTTP metrics are automatically collected by OpenTelemetry's instrumentation layer, providing baseline performance data for all HTTP traffic. -- `http_server_duration` - HTTP server request duration metrics (histogram buckets, count, sum) -- `http_client_duration` - HTTP client request duration metrics (histogram buckets, count, sum) + + + HTTP server request duration metrics (histogram buckets, count, sum) + + + + HTTP client request duration metrics (histogram buckets, count, sum) + + ## Troubleshooting -### Common Issues + + If your metrics are not showing up in Prometheus or your monitoring system, check the following: -1. **Metrics not appearing**: + - Verify `OTEL_TELEMETRY_COLLECTION_ENABLED=true` is set in your Infisical environment variables + - Ensure the correct `OTEL_EXPORT_TYPE` is set (`prometheus` or `otlp`) + - Check network connectivity between Infisical and your monitoring services (Prometheus or OTLP collector) + - For pull-based monitoring: Verify port 9464 is exposed and accessible + - For push-based monitoring: Verify the OTLP endpoint URL is correct and reachable + - Check Infisical backend logs for any errors related to metrics export + - - Check if `OTEL_TELEMETRY_COLLECTION_ENABLED=true` - - Verify the correct `OTEL_EXPORT_TYPE` is set - - Check network connectivity between services + + If you're experiencing authentication errors with the OpenTelemetry Collector: -2. **Authentication errors**: - - - Verify basic auth credentials in OTLP configuration - - Check if credentials match between Infisical and collector + - Verify basic auth credentials in your OTLP configuration match between Infisical and the collector + - Check that `OTEL_COLLECTOR_BASIC_AUTH_USERNAME` and `OTEL_COLLECTOR_BASIC_AUTH_PASSWORD` match the credentials in your `otel-collector-config.yaml` + - Ensure the htpasswd format in the collector configuration is correct + - Test the collector endpoint manually using curl with the same credentials to verify they work + diff --git a/docs/snippets/AppConnectionsBrowser.jsx b/docs/snippets/AppConnectionsBrowser.jsx index 7761d4bfc..dfe574547 100644 --- a/docs/snippets/AppConnectionsBrowser.jsx +++ b/docs/snippets/AppConnectionsBrowser.jsx @@ -47,6 +47,7 @@ export const AppConnectionsBrowser = () => { {"name": "Auth0", "slug": "auth0", "path": "/integrations/app-connections/auth0", "description": "Learn how to connect your Auth0 to pull secrets from Infisical.", "category": "Identity & Auth"}, {"name": "Okta", "slug": "okta", "path": "/integrations/app-connections/okta", "description": "Learn how to connect your Okta to pull secrets from Infisical.", "category": "Identity & Auth"}, {"name": "Laravel Forge", "slug": "laravel-forge", "path": "/integrations/app-connections/laravel-forge", "description": "Learn how to connect your Laravel Forge to pull secrets from Infisical.", "category": "Hosting"}, + {"name": "Chef", "slug": "chef", "path": "/integrations/app-connections/chef", "description": "Learn how to connect your Chef to pull secrets from Infisical.", "category": "DevOps Tools"}, {"name": "Northflank", "slug": "northflank", "path": "/integrations/app-connections/northflank", "description": "Learn how to connect your Northflank projects to pull secrets from Infisical.", "category": "Hosting"} ].sort(function(a, b) { return a.name.toLowerCase().localeCompare(b.name.toLowerCase()); diff --git a/docs/snippets/SecretSyncsBrowser.jsx b/docs/snippets/SecretSyncsBrowser.jsx index d71ef1a80..17ff9e97b 100644 --- a/docs/snippets/SecretSyncsBrowser.jsx +++ b/docs/snippets/SecretSyncsBrowser.jsx @@ -38,6 +38,7 @@ export const SecretSyncsBrowser = () => { {"name": "OCI Vault", "slug": "oci-vault", "path": "/integrations/secret-syncs/oci-vault", "description": "Learn how to sync secrets from Infisical to OCI Vault.", "category": "Cloud Providers"}, {"name": "Zabbix", "slug": "zabbix", "path": "/integrations/secret-syncs/zabbix", "description": "Learn how to sync secrets from Infisical to Zabbix.", "category": "Monitoring"}, {"name": "Laravel Forge", "slug": "laravel-forge", "path": "/integrations/secret-syncs/laravel-forge", "description": "Learn how to sync secrets from Infisical to Laravel Forge.", "category": "Hosting"}, + {"name": "Chef", "slug": "chef", "path": "/integrations/secret-syncs/chef", "description": "Learn how to sync secrets from Infisical to Chef.", "category": "DevOps Tools"}, {"name": "Northflank", "slug": "northflank", "path": "/integrations/secret-syncs/northflank", "description": "Learn how to sync secrets from Infisical to Northflank projects.", "category": "Hosting"} ].sort(function(a, b) { return a.name.toLowerCase().localeCompare(b.name.toLowerCase()); diff --git a/frontend/public/images/integrations/Chef.png b/frontend/public/images/integrations/Chef.png new file mode 100644 index 000000000..8d8886c21 Binary files /dev/null and b/frontend/public/images/integrations/Chef.png differ diff --git a/frontend/src/components/features/WishForm.tsx b/frontend/src/components/features/WishForm.tsx index 118900bc9..28107809a 100644 --- a/frontend/src/components/features/WishForm.tsx +++ b/frontend/src/components/features/WishForm.tsx @@ -35,23 +35,16 @@ export const WishForm = () => { const [isOpen, setIsOpen] = useToggle(false); const createWish = async (data: TFormData) => { - try { - await mutateAsync({ - text: data.text - }); + await mutateAsync({ + text: data.text + }); - createNotification({ - text: "Your wish has been sent to the Infisical team!", - type: "success" - }); + createNotification({ + text: "Your wish has been sent to the Infisical team!", + type: "success" + }); - setIsOpen.off(); - } catch { - createNotification({ - text: "An error occured while sending your wish to the Infisical team.", - type: "error" - }); - } + setIsOpen.off(); }; return ( diff --git a/frontend/src/components/mfa/TotpRegistration.tsx b/frontend/src/components/mfa/TotpRegistration.tsx index 0b79bfd98..bcb0d3691 100644 --- a/frontend/src/components/mfa/TotpRegistration.tsx +++ b/frontend/src/components/mfa/TotpRegistration.tsx @@ -25,27 +25,20 @@ const TotpRegistration = ({ onComplete, shouldCenterQr }: Props) => { const handleTotpVerify = async (event: React.FormEvent) => { event.preventDefault(); - try { - const result = await verifyUserTotp({ - totp - }); + const result = await verifyUserTotp({ + totp + }); - createNotification({ - text: "Successfully configured mobile authenticator", - type: "success" - }); + createNotification({ + text: "Successfully configured mobile authenticator", + type: "success" + }); - if (result.recoveryCodes && result.recoveryCodes.length > 0) { - setRecoveryCodes(result.recoveryCodes); - setShowRecoveryModal(true); - } else if (onComplete) { - onComplete(); - } - } catch { - createNotification({ - text: "Failed to verify TOTP code", - type: "error" - }); + if (result.recoveryCodes && result.recoveryCodes.length > 0) { + setRecoveryCodes(result.recoveryCodes); + setShowRecoveryModal(true); + } else if (onComplete) { + onComplete(); } }; diff --git a/frontend/src/components/organization/CreateOrgModal/CreateOrgModal.tsx b/frontend/src/components/organization/CreateOrgModal/CreateOrgModal.tsx index 23c3fe6a5..84931f84a 100644 --- a/frontend/src/components/organization/CreateOrgModal/CreateOrgModal.tsx +++ b/frontend/src/components/organization/CreateOrgModal/CreateOrgModal.tsx @@ -43,35 +43,27 @@ export const CreateOrgModal: FC = ({ isOpen, onClose }) => const { mutateAsync: selectOrg } = useSelectOrganization(); const onFormSubmit = async ({ name }: FormData) => { - try { - const organization = await createOrg({ - name - }); + const organization = await createOrg({ + name + }); - await selectOrg({ - organizationId: organization.id - }); + await selectOrg({ + organizationId: organization.id + }); - createNotification({ - text: "Successfully created organization", - type: "success" - }); + createNotification({ + text: "Successfully created organization", + type: "success" + }); - navigate({ - to: "/organization/projects" - }); + navigate({ + to: "/organization/projects" + }); - localStorage.setItem("orgData.id", organization.id); + localStorage.setItem("orgData.id", organization.id); - reset(); - onClose(); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to created organization", - type: "error" - }); - } + reset(); + onClose(); }; return ( diff --git a/frontend/src/components/pki-syncs/CertificateManagementModal.tsx b/frontend/src/components/pki-syncs/CertificateManagementModal.tsx new file mode 100644 index 000000000..f5fd72309 --- /dev/null +++ b/frontend/src/components/pki-syncs/CertificateManagementModal.tsx @@ -0,0 +1,444 @@ +import React, { useEffect, useState } from "react"; +import { faSearch, faX } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { createNotification } from "@app/components/notifications"; +import { + Button, + Checkbox, + EmptyState, + Input, + Modal, + ModalContent, + Pagination, + Table, + TableContainer, + TBody, + Td, + Th, + THead, + Tooltip, + Tr +} from "@app/components/v2"; +import { useProject } from "@app/context"; +import { + CertStatus, + useAddCertificatesToPkiSync, + useListPkiSyncCertificates, + useRemoveCertificatesFromPkiSync +} from "@app/hooks/api"; +import { TPkiSync } from "@app/hooks/api/pkiSyncs"; +import { useListWorkspaceCertificates } from "@app/hooks/api/projects"; + +type Props = { + isOpen: boolean; + onClose: () => void; + pkiSync?: TPkiSync; + onCertificatesUpdated?: () => void; + selectedCertificateIds?: string[]; + onCertificateSelectionChange?: (certificateIds: string[]) => void; + title?: string; + subtitle?: string; + saveButtonText?: string; +}; + +export const CertificateManagementModal = ({ + isOpen, + onClose, + pkiSync, + onCertificatesUpdated, + selectedCertificateIds, + onCertificateSelectionChange, + title = "Manage Certificate Sync", + subtitle = "Select which certificates should be synced.", + saveButtonText = "Save Changes" +}: Props) => { + const { currentProject } = useProject(); + const [currentPage, setCurrentPage] = useState(1); + const [searchTerm, setSearchTerm] = useState(""); + const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(""); + const pageSize = 10; + + const isCreateMode = !pkiSync; + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedSearchTerm(searchTerm); + setCurrentPage(1); + }, 300); + + return () => { + clearTimeout(handler); + }; + }, [searchTerm]); + + const { data } = useListWorkspaceCertificates({ + projectId: currentProject?.id || "", + offset: (currentPage - 1) * pageSize, + limit: pageSize, + commonName: debouncedSearchTerm || undefined, + friendlyName: debouncedSearchTerm || undefined, + forPkiSync: true + }); + + const allCertificates = data?.certificates || []; + const totalCount = data?.totalCount || 0; + + const { data: syncData } = useListPkiSyncCertificates(pkiSync?.id || ""); + const syncCertificates = syncData?.certificates || []; + const addCertificatesToSync = useAddCertificatesToPkiSync(); + const removeCertificatesFromSync = useRemoveCertificatesFromPkiSync(); + + const syncedCertificateIds = isCreateMode + ? selectedCertificateIds || [] + : syncCertificates.map((sc) => sc.certificateId); + + const totalPages = Math.ceil(totalCount / pageSize); + + const [selectedIds, setSelectedIds] = useState([]); + + React.useEffect(() => { + setSelectedIds(syncedCertificateIds); + }, [JSON.stringify(syncedCertificateIds)]); + + const handleToggleSelection = (certId: string) => { + setSelectedIds((prev) => + prev.includes(certId) ? prev.filter((id) => id !== certId) : [...prev, certId] + ); + }; + + const handleSelectAll = () => { + const currentPageIds = allCertificates.map((cert) => cert.id); + const allCurrentPageSelected = currentPageIds.every((id) => selectedIds.includes(id)); + + if (allCurrentPageSelected) { + setSelectedIds((prev) => prev.filter((id) => !currentPageIds.includes(id))); + } else { + setSelectedIds((prev) => [...new Set([...prev, ...currentPageIds])]); + } + }; + + const clearSearch = () => { + setSearchTerm(""); + setCurrentPage(1); + }; + + React.useEffect(() => { + if (isOpen) { + setCurrentPage(1); + setSearchTerm(""); + } + }, [isOpen]); + + const handleSaveCertificates = async () => { + try { + if (isCreateMode) { + if (onCertificateSelectionChange) { + onCertificateSelectionChange(selectedIds); + onClose(); + } + return; + } + + if (!pkiSync) return; + + const certificatesToAdd = selectedIds.filter((id) => !syncedCertificateIds.includes(id)); + const certificatesToRemove = syncedCertificateIds.filter((id) => !selectedIds.includes(id)); + + const invalidCertificates = certificatesToAdd + .map((id) => allCertificates.find((cert) => cert.id === id)) + .filter((cert) => { + if (!cert) return false; + const isExpired = new Date(cert.notAfter) < new Date(); + const isRevoked = cert.status === CertStatus.REVOKED; + return isExpired || isRevoked; + }); + + if (invalidCertificates.length > 0) { + const invalidNames = invalidCertificates.map((cert) => cert?.commonName).join(", "); + createNotification({ + text: `Cannot add expired or revoked certificates: ${invalidNames}`, + type: "error" + }); + return; + } + + const operations = []; + + if (certificatesToAdd.length > 0) { + operations.push( + addCertificatesToSync + .mutateAsync({ + pkiSyncId: pkiSync.id, + certificateIds: certificatesToAdd + }) + .then(() => ({ + type: "add", + count: certificatesToAdd.length, + success: true + })) + .catch((error) => ({ + type: "add", + count: certificatesToAdd.length, + success: false, + error + })) + ); + } + + if (certificatesToRemove.length > 0) { + operations.push( + removeCertificatesFromSync + .mutateAsync({ + pkiSyncId: pkiSync.id, + certificateIds: certificatesToRemove + }) + .then(() => ({ + type: "remove", + count: certificatesToRemove.length, + success: true + })) + .catch((error) => ({ + type: "remove", + count: certificatesToRemove.length, + success: false, + error + })) + ); + } + + if (operations.length === 0) { + createNotification({ + text: "No changes to save", + type: "info" + }); + onClose(); + return; + } + + const results = await Promise.all(operations); + const failures = results.filter((r) => !r.success); + const successes = results.filter((r) => r.success); + + if (failures.length === 0) { + const addCount = successes.find((r) => r.type === "add")?.count || 0; + const removeCount = successes.find((r) => r.type === "remove")?.count || 0; + + let message = "Certificate selection updated successfully"; + if (addCount > 0 && removeCount > 0) { + message = `Added ${addCount} and removed ${removeCount} certificate(s)`; + } else if (addCount > 0) { + message = `Added ${addCount} certificate(s)`; + } else if (removeCount > 0) { + message = `Removed ${removeCount} certificate(s)`; + } + + createNotification({ + text: message, + type: "success" + }); + + if (onCertificatesUpdated) { + onCertificatesUpdated(); + } + onClose(); + } else { + const partialSuccess = successes.length > 0; + console.error("Certificate sync operation failures:", failures); + + createNotification({ + text: partialSuccess + ? "Some certificate changes failed. Check console for details." + : "Failed to update certificate selection", + type: partialSuccess ? "warning" : "error" + }); + + if (partialSuccess && onCertificatesUpdated) { + onCertificatesUpdated(); + } + } + } catch (error) { + console.error("Unexpected error during certificate sync operation:", error); + createNotification({ + text: "An unexpected error occurred while updating certificates", + type: "error" + }); + } + }; + + const isLoading = addCertificatesToSync.isPending || removeCertificatesFromSync.isPending; + + return ( + !open && onClose()}> + +
+
+
+ { + setSearchTerm(e.target.value); + setCurrentPage(1); + }} + className="pl-9" + /> + + {searchTerm && ( + + )} +
+
+ + + + + + + + + + + + + + {allCertificates.map((cert) => { + const isExpired = new Date(cert.notAfter) < new Date(); + const isRevoked = cert.status === CertStatus.REVOKED; + const cannotBeAdded = isExpired || isRevoked; + const isAlreadySynced = syncedCertificateIds.includes(cert.id); + + let originalDisplayName = "—"; + if (cert.altNames && cert.altNames.trim()) { + originalDisplayName = cert.altNames.trim(); + } else if (cert.commonName && cert.commonName.trim()) { + originalDisplayName = cert.commonName.trim(); + } + + let displayName = originalDisplayName; + let isTruncated = false; + if (originalDisplayName.length > 34) { + displayName = `${originalDisplayName.substring(0, 34)}...`; + isTruncated = true; + } + + const truncatedSerial = + cert.serialNumber.length > 8 + ? `${cert.serialNumber.slice(0, 4)}...${cert.serialNumber.slice(-4)}` + : cert.serialNumber; + + return ( + { + if (!cannotBeAdded || isAlreadySynced) { + handleToggleSelection(cert.id); + } + }} + > + + + + + + + ); + })} + +
+ 0 && + allCertificates.every((cert) => selectedIds.includes(cert.id)) + } + onCheckedChange={handleSelectAll} + /> + SAN / CNSerial NumberIssued AtExpires At
e.stopPropagation()}> + { + if (!cannotBeAdded || isAlreadySynced) { + handleToggleSelection(cert.id); + } + }} + isDisabled={cannotBeAdded && !isAlreadySynced} + /> + + {isTruncated ? ( + +
{displayName}
+
+ ) : ( +
{displayName}
+ )} +
+
+ {truncatedSerial} +
+
+ + {new Date(cert.notBefore).toLocaleDateString()} + + + + {new Date(cert.notAfter).toLocaleDateString()} + +
+ {allCertificates.length === 0 && ( + + {searchTerm + ? "No certificates match your search criteria." + : "No certificates available for sync."} + + )} +
+ + {totalPages > 1 && ( +
+ setCurrentPage(page)} + onChangePerPage={() => {}} + /> +
+ )} +
+ +
+ + +
+
+
+ ); +}; diff --git a/frontend/src/components/pki-syncs/CreatePkiSyncModal.tsx b/frontend/src/components/pki-syncs/CreatePkiSyncModal.tsx index 5b5e81ba3..0169b2296 100644 --- a/frontend/src/components/pki-syncs/CreatePkiSyncModal.tsx +++ b/frontend/src/components/pki-syncs/CreatePkiSyncModal.tsx @@ -11,21 +11,24 @@ type Props = { isOpen: boolean; onOpenChange: (isOpen: boolean) => void; selectSync?: PkiSync | null; + initialData?: any; }; type ContentProps = { onComplete: (pkiSync: TPkiSync) => void; selectedSync: PkiSync | null; setSelectedSync: (selectedSync: PkiSync | null) => void; + initialData?: any; }; -const Content = ({ onComplete, setSelectedSync, selectedSync }: ContentProps) => { +const Content = ({ onComplete, setSelectedSync, selectedSync, initialData }: ContentProps) => { if (selectedSync) { return ( setSelectedSync(null)} destination={selectedSync} + initialData={initialData} /> ); } @@ -33,7 +36,12 @@ const Content = ({ onComplete, setSelectedSync, selectedSync }: ContentProps) => return ; }; -export const CreatePkiSyncModal = ({ onOpenChange, selectSync = null, ...props }: Props) => { +export const CreatePkiSyncModal = ({ + onOpenChange, + selectSync = null, + initialData, + ...props +}: Props) => { const [selectedSync, setSelectedSync] = useState(selectSync); useEffect(() => { @@ -69,6 +77,7 @@ export const CreatePkiSyncModal = ({ onOpenChange, selectSync = null, ...props } }} selectedSync={selectedSync} setSelectedSync={setSelectedSync} + initialData={initialData} /> diff --git a/frontend/src/components/pki-syncs/DeletePkiSyncModal.tsx b/frontend/src/components/pki-syncs/DeletePkiSyncModal.tsx index e465d3f34..c01efcbf2 100644 --- a/frontend/src/components/pki-syncs/DeletePkiSyncModal.tsx +++ b/frontend/src/components/pki-syncs/DeletePkiSyncModal.tsx @@ -20,28 +20,19 @@ export const DeletePkiSyncModal = ({ isOpen, onOpenChange, pkiSync, onComplete } const handleDeletePkiSync = async () => { const destinationName = PKI_SYNC_MAP[destination].name; - try { - await deleteSync.mutateAsync({ - syncId, - projectId, - destination - }); + await deleteSync.mutateAsync({ + syncId, + projectId, + destination + }); - createNotification({ - text: `Successfully deleted ${destinationName} PKI Sync`, - type: "success" - }); + createNotification({ + text: `Successfully deleted ${destinationName} PKI Sync`, + type: "success" + }); - if (onComplete) onComplete(); - onOpenChange(false); - } catch (err) { - console.error(err); - - createNotification({ - text: `Failed to delete ${destinationName} PKI Sync`, - type: "error" - }); - } + if (onComplete) onComplete(); + onOpenChange(false); }; return ( diff --git a/frontend/src/components/pki-syncs/PkiSyncImportCertificatesModal.tsx b/frontend/src/components/pki-syncs/PkiSyncImportCertificatesModal.tsx index 33b06dbe4..43192c5e3 100644 --- a/frontend/src/components/pki-syncs/PkiSyncImportCertificatesModal.tsx +++ b/frontend/src/components/pki-syncs/PkiSyncImportCertificatesModal.tsx @@ -21,27 +21,18 @@ const Content = ({ pkiSync, onComplete }: ContentProps) => { const triggerImportCertificates = useTriggerPkiSyncImportCertificates(); const handleTriggerImportCertificates = async () => { - try { - await triggerImportCertificates.mutateAsync({ - syncId, - destination, - projectId - }); + await triggerImportCertificates.mutateAsync({ + syncId, + destination, + projectId + }); - createNotification({ - text: `Successfully triggered certificate import for ${destinationName} Sync`, - type: "success" - }); + createNotification({ + text: `Successfully triggered certificate import for ${destinationName} Sync`, + type: "success" + }); - onComplete(); - } catch (err) { - console.error(err); - - createNotification({ - text: `Failed to trigger certificate import for ${destinationName} Sync`, - type: "error" - }); - } + onComplete(); }; return ( diff --git a/frontend/src/components/pki-syncs/PkiSyncRemoveCertificatesModal.tsx b/frontend/src/components/pki-syncs/PkiSyncRemoveCertificatesModal.tsx index f17855289..cee845381 100644 --- a/frontend/src/components/pki-syncs/PkiSyncRemoveCertificatesModal.tsx +++ b/frontend/src/components/pki-syncs/PkiSyncRemoveCertificatesModal.tsx @@ -21,27 +21,18 @@ const Content = ({ pkiSync, onComplete }: ContentProps) => { const triggerRemoveCertificates = useTriggerPkiSyncRemoveCertificates(); const handleTriggerRemoveCertificates = async () => { - try { - await triggerRemoveCertificates.mutateAsync({ - syncId, - destination, - projectId - }); + await triggerRemoveCertificates.mutateAsync({ + syncId, + destination, + projectId + }); - createNotification({ - text: `Successfully triggered certificate removal for ${destinationName} Sync`, - type: "success" - }); + createNotification({ + text: `Successfully triggered certificate removal for ${destinationName} Sync`, + type: "success" + }); - onComplete(); - } catch (err) { - console.error(err); - - createNotification({ - text: `Failed to trigger certificate removal for ${destinationName} Sync`, - type: "error" - }); - } + onComplete(); }; return ( diff --git a/frontend/src/components/pki-syncs/PkiSyncSelect.tsx b/frontend/src/components/pki-syncs/PkiSyncSelect.tsx index aedfebf47..251f88ee2 100644 --- a/frontend/src/components/pki-syncs/PkiSyncSelect.tsx +++ b/frontend/src/components/pki-syncs/PkiSyncSelect.tsx @@ -71,7 +71,7 @@ export const PkiSyncSelect = ({ onSelect }: Props) => { enterprise && !subscription.enterpriseCertificateSyncs ? handlePopUpOpen("upgradePlan", { isEnterpriseFeature: true, - text: "You can use every Certificate Sync if you switch to Infisical's Enterprise plan." + text: "All Certificate Syncs can be unlocked if you switch to Infisical Enterprise plan." }) : onSelect(destination) } @@ -152,7 +152,7 @@ export const PkiSyncSelect = ({ onSelect }: Props) => { isOpen={popUp.upgradePlan.isOpen} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} - text="You can use every Certificate Sync if you switch to Infisical's Enterprise plan." + text={popUp.upgradePlan.data?.text} /> ); diff --git a/frontend/src/components/pki-syncs/forms/CreatePkiSyncForm.tsx b/frontend/src/components/pki-syncs/forms/CreatePkiSyncForm.tsx index 582518bd7..1dee6eaa7 100644 --- a/frontend/src/components/pki-syncs/forms/CreatePkiSyncForm.tsx +++ b/frontend/src/components/pki-syncs/forms/CreatePkiSyncForm.tsx @@ -13,27 +13,28 @@ import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs"; import { PkiSync, TPkiSync, useCreatePkiSync, usePkiSyncOption } from "@app/hooks/api/pkiSyncs"; import { PkiSyncFormSchema, TPkiSyncForm } from "./schemas/pki-sync-schema"; +import { PkiSyncCertificatesFields } from "./PkiSyncCertificatesFields"; import { PkiSyncDestinationFields } from "./PkiSyncDestinationFields"; import { PkiSyncDetailsFields } from "./PkiSyncDetailsFields"; import { PkiSyncOptionsFields } from "./PkiSyncOptionsFields"; import { PkiSyncReviewFields } from "./PkiSyncReviewFields"; -import { PkiSyncSourceFields } from "./PkiSyncSourceFields"; type Props = { onComplete: (pkiSync: TPkiSync) => void; destination: PkiSync; onCancel: () => void; + initialData?: any; }; const FORM_TABS: { name: string; key: string; fields: (keyof TPkiSyncForm)[] }[] = [ - { name: "Source", key: "source", fields: ["subscriberId"] }, { name: "Destination", key: "destination", fields: ["connection", "destinationConfig"] }, { name: "Sync Options", key: "options", fields: ["syncOptions"] }, { name: "Details", key: "details", fields: ["name", "description"] }, + { name: "Certificates", key: "certificates", fields: ["certificateIds"] }, { name: "Review", key: "review", fields: [] } ]; -export const CreatePkiSyncForm = ({ destination, onComplete, onCancel }: Props) => { +export const CreatePkiSyncForm = ({ destination, onComplete, onCancel, initialData }: Props) => { const createPkiSync = useCreatePkiSync(); const { currentProject } = useProject(); const { name: destinationName } = PKI_SYNC_MAP[destination]; @@ -49,37 +50,44 @@ export const CreatePkiSyncForm = ({ destination, onComplete, onCancel }: Props) defaultValues: { destination, isAutoSyncEnabled: false, + certificateIds: [], syncOptions: { canImportCertificates: false, canRemoveCertificates: false, + preserveArn: true, certificateNameSchema: syncOption?.defaultCertificateNameSchema - } + }, + ...initialData } as Partial, reValidateMode: "onChange" }); - const onSubmit = async ({ connection, destinationConfig, ...formData }: TPkiSyncForm) => { + const onSubmit = async ({ + connection, + destinationConfig, + certificateIds, + ...formData + }: TPkiSyncForm) => { try { const pkiSync = await createPkiSync.mutateAsync({ ...formData, connectionId: connection.id, projectId: currentProject.id, - destinationConfig + destinationConfig, + certificateIds: certificateIds || [] }); createNotification({ - text: `Successfully added ${destinationName} Certificate Sync`, + text: `Successfully created ${destinationName} Certificate Sync${ + certificateIds && certificateIds.length > 0 + ? ` with ${certificateIds.length} certificate(s)` + : "" + }`, type: "success" }); onComplete(pkiSync); - } catch (err: Error | unknown) { - console.error(err); + } catch { setShowConfirmation(false); - createNotification({ - title: `Failed to add ${destinationName} Certificate Sync`, - text: err instanceof Error ? err.message : "An unknown error occurred", - type: "error" - }); } }; @@ -184,9 +192,6 @@ export const CreatePkiSyncForm = ({ destination, onComplete, onCancel }: Props) ))} - - - @@ -200,8 +205,8 @@ export const CreatePkiSyncForm = ({ destination, onComplete, onCancel }: Props) + + + diff --git a/frontend/src/components/pki-syncs/forms/EditPkiSyncForm.tsx b/frontend/src/components/pki-syncs/forms/EditPkiSyncForm.tsx index 73f5bc2bc..9041d32f2 100644 --- a/frontend/src/components/pki-syncs/forms/EditPkiSyncForm.tsx +++ b/frontend/src/components/pki-syncs/forms/EditPkiSyncForm.tsx @@ -27,39 +27,34 @@ export const EditPkiSyncForm = ({ pkiSync, fields, onComplete }: Props) => { const formMethods = useForm({ resolver: zodResolver(UpdatePkiSyncFormSchema), defaultValues: { - ...pkiSync, + name: pkiSync.name, + destination: pkiSync.destination, description: pkiSync.description ?? "", connection: { id: pkiSync.connectionId, name: pkiSync.appConnectionName - } + }, + syncOptions: pkiSync.syncOptions, + destinationConfig: pkiSync.destinationConfig, + isAutoSyncEnabled: pkiSync.isAutoSyncEnabled } as Partial, reValidateMode: "onChange" }); const onSubmit = async ({ connection, ...formData }: TUpdatePkiSyncForm) => { - try { - const updatedPkiSync = await updatePkiSync.mutateAsync({ - syncId: pkiSync.id, - ...formData, - connectionId: connection.id, - projectId: pkiSync.projectId, - destination: pkiSync.destination - }); + const updatedPkiSync = await updatePkiSync.mutateAsync({ + syncId: pkiSync.id, + ...formData, + connectionId: connection.id, + projectId: pkiSync.projectId, + destination: pkiSync.destination + }); - createNotification({ - text: `Successfully updated ${destinationName} PKI Sync`, - type: "success" - }); - onComplete(updatedPkiSync); - } catch (err: any) { - console.error(err); - createNotification({ - title: `Failed to update ${destinationName} PKI Sync`, - text: err.message, - type: "error" - }); - } + createNotification({ + text: `Successfully updated ${destinationName} PKI Sync`, + type: "success" + }); + onComplete(updatedPkiSync); }; let Component: ReactNode; diff --git a/frontend/src/components/pki-syncs/forms/PkiSyncCertificatesFields.tsx b/frontend/src/components/pki-syncs/forms/PkiSyncCertificatesFields.tsx new file mode 100644 index 000000000..a5abcb60b --- /dev/null +++ b/frontend/src/components/pki-syncs/forms/PkiSyncCertificatesFields.tsx @@ -0,0 +1,189 @@ +import { useMemo, useState } from "react"; +import { Controller, useFormContext } from "react-hook-form"; +import { faCertificate, faEdit, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { + Button, + EmptyState, + FormControl, + Table, + TableContainer, + TBody, + Td, + Th, + THead, + Tooltip, + Tr +} from "@app/components/v2"; +import { useProject } from "@app/context"; +import { CertStatus } from "@app/hooks/api"; +import { useListWorkspaceCertificates } from "@app/hooks/api/projects"; + +import { CertificateManagementModal } from "../CertificateManagementModal"; +import { TPkiSyncForm } from "./schemas/pki-sync-schema"; + +export const PkiSyncCertificatesFields = () => { + const { control, watch, setValue } = useFormContext(); + const { currentProject } = useProject(); + const [isSelectionModalOpen, setIsSelectionModalOpen] = useState(false); + + const certificateIds = watch("certificateIds") || []; + + const { data, isLoading } = useListWorkspaceCertificates({ + projectId: currentProject?.id || "", + offset: 0, + limit: 100, + forPkiSync: true + }); + + const certificates = data?.certificates || []; + + const activeCertificates = useMemo( + () => certificates.filter((cert) => cert.status === CertStatus.ACTIVE), + [certificates] + ); + + const selectedCertificates = useMemo( + () => activeCertificates.filter((cert) => certificateIds.includes(cert.id)), + [activeCertificates, certificateIds] + ); + + if (isLoading) { + return ( +
+
Loading certificates...
+
+ ); + } + + return ( + <> +

+ Select certificates to sync with this integration. Only active certificates can be synced. + You can modify this selection after creating the sync. +

+ + ( + +
+ +
+ + + + + + + + + + + + + {selectedCertificates.map((cert) => { + let originalDisplayName = "—"; + if (cert.altNames && cert.altNames.trim()) { + originalDisplayName = cert.altNames.trim(); + } else if (cert.commonName && cert.commonName.trim()) { + originalDisplayName = cert.commonName.trim(); + } + + let displayName = originalDisplayName; + let isTruncated = false; + if (originalDisplayName.length > 34) { + displayName = `${originalDisplayName.substring(0, 34)}...`; + isTruncated = true; + } + + const truncatedSerial = + cert.serialNumber.length > 8 + ? `${cert.serialNumber.slice(0, 4)}...${cert.serialNumber.slice(-4)}` + : cert.serialNumber; + + const isExpired = new Date(cert.notAfter) < new Date(); + + return ( + + + + + + + + ); + })} + +
SAN / CNSerial NumberIssued AtExpires AtRemove
+ {isTruncated ? ( + +
{displayName}
+
+ ) : ( +
{displayName}
+ )} +
+
+ {truncatedSerial} +
+
+ + {new Date(cert.notBefore).toLocaleDateString()} + + + + {new Date(cert.notAfter).toLocaleDateString()} + + + +
+ {selectedCertificates.length === 0 && ( + + )} +
+
+
+
+ )} + /> + + setIsSelectionModalOpen(false)} + selectedCertificateIds={certificateIds} + onCertificateSelectionChange={(newCertificateIds) => { + setValue("certificateIds", newCertificateIds); + }} + title="Select Certificates for Sync" + subtitle="Choose which certificates you want to include in this sync. You can modify this selection after creating the sync." + saveButtonText="Update Selection" + /> + + ); +}; diff --git a/frontend/src/components/pki-syncs/forms/PkiSyncConnectionField.tsx b/frontend/src/components/pki-syncs/forms/PkiSyncConnectionField.tsx index 445f9169d..c8da83aac 100644 --- a/frontend/src/components/pki-syncs/forms/PkiSyncConnectionField.tsx +++ b/frontend/src/components/pki-syncs/forms/PkiSyncConnectionField.tsx @@ -1,14 +1,18 @@ import { Controller, useFormContext } from "react-hook-form"; +import { SingleValue } from "react-select"; import { faInfoCircle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Link } from "@tanstack/react-router"; +import { useRouterState } from "@tanstack/react-router"; +import { AppConnectionOption } from "@app/components/app-connections"; import { FilterableSelect, FormControl } from "@app/components/v2"; import { ProjectPermissionSub, useProject, useProjectPermission } from "@app/context"; import { ProjectPermissionAppConnectionActions } from "@app/context/ProjectPermissionContext/types"; import { APP_CONNECTION_MAP } from "@app/helpers/appConnections"; import { PKI_SYNC_CONNECTION_MAP } from "@app/helpers/pkiSyncs"; +import { usePopUp } from "@app/hooks"; import { useListAvailableAppConnections } from "@app/hooks/api/appConnections"; +import { AddAppConnectionModal } from "@app/pages/organization/AppConnections/AppConnectionsPage/components"; import { TPkiSyncForm } from "./schemas/pki-sync-schema"; @@ -18,12 +22,30 @@ type Props = { export const PkiSyncConnectionField = ({ onChange: callback }: Props) => { const { permission } = useProjectPermission(); - const { control, watch } = useFormContext(); - const { currentProject } = useProject(); + const { control, watch, setValue } = useFormContext(); + + const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp(["addConnection"] as const); const destination = watch("destination"); const app = PKI_SYNC_CONNECTION_MAP[destination]; + const { currentProject } = useProject(); + + const { + location: { pathname } + } = useRouterState(); + + const getPkiSyncReturnUrl = () => { + if (pathname.includes("selectedTab=secret-syncs")) { + return pathname.replace("selectedTab=secret-syncs", "selectedTab=pki-syncs"); + } + if (!pathname.includes("selectedTab=")) { + const separator = pathname.includes("?") ? "&" : "?"; + return `${pathname}${separator}selectedTab=pki-syncs`; + } + return pathname; + }; + const { data: availableConnections, isPending } = useListAvailableAppConnections( app, currentProject.id @@ -47,6 +69,7 @@ export const PkiSyncConnectionField = ({ onChange: callback }: Props) => { ( { { + if ((newValue as SingleValue<{ id: string; name: string }>)?.id === "_create") { + handlePopUpOpen("addConnection"); + onChange(null); + const formData = { ...watch(), returnUrl: getPkiSyncReturnUrl() }; + localStorage.setItem("pkiSyncFormData", JSON.stringify(formData)); + if (callback) callback(); + return; + } + onChange(newValue); if (callback) callback(); }} isLoading={isPending} - options={availableConnections} + options={[ + ...(canCreateConnection ? [{ id: "_create", name: "Create Connection" }] : []), + ...(availableConnections ?? []) + ]} placeholder="Select connection..." getOptionLabel={(option) => option.name} getOptionValue={(option) => option.id} + components={{ Option: AppConnectionOption }} /> )} control={control} name="connection" /> - {availableConnections?.length === 0 && ( + {!isPending && !availableConnections?.length && !canCreateConnection && (

- {canCreateConnection ? ( - <> - You do not have access to any {appName} Connections. Create one from the{" "} - - App Connections - {" "} - page. - - ) : ( - `You do not have access to any ${appName} Connections. Contact an admin to create one.` - )} + You do not have access to any {appName} Connections. Contact an admin to create one.

)} + { + localStorage.removeItem("pkiSyncFormData"); + handlePopUpToggle("addConnection", isOpen); + }} + projectType={currentProject.type} + projectId={currentProject.id} + app={app} + onComplete={(connection) => { + if (connection) { + setValue("connection", connection); + } + }} + /> ); }; diff --git a/frontend/src/components/pki-syncs/forms/PkiSyncOptionsFields/PkiSyncOptionsFields.tsx b/frontend/src/components/pki-syncs/forms/PkiSyncOptionsFields/PkiSyncOptionsFields.tsx index ce581bb88..c1313e684 100644 --- a/frontend/src/components/pki-syncs/forms/PkiSyncOptionsFields/PkiSyncOptionsFields.tsx +++ b/frontend/src/components/pki-syncs/forms/PkiSyncOptionsFields/PkiSyncOptionsFields.tsx @@ -71,14 +71,14 @@ export const PkiSyncOptionsFields = ({ destination }: Props) => { isChecked={value} >

- Enable Certificate Removal{" "} + Enable Removal of Expired/Revoked Certificates{" "}

When enabled, Infisical will remove certificates from the destination during - a sync if they are no longer managed by Infisical. + a sync if they are no longer active in Infisical.

Disable this option if you intend to manage some certificates manually @@ -95,6 +95,94 @@ export const PkiSyncOptionsFields = ({ destination }: Props) => { )} /> + {currentDestination === PkiSync.AwsCertificateManager && ( + ( + + +

+ Preserve ARN on Renewal{" "} + +

+ When enabled, Infisical will replace the contents of existing certificates + while preserving the same ARN during certificate renewal syncs. +

+

+ This allows consuming services like load balancers to continue using the + same ARN without requiring manual updates. +

+

+ When disabled, new certificates will be created with new ARNs, and old + certificates will be removed. +

+ + } + > + + +

+ +
+ )} + /> + )} + + {currentDestination === PkiSync.AzureKeyVault && ( + ( + + +

+ Enable Versioning on Renewal{" "} + +

+ When enabled, Infisical will create a new version of the existing + certificate in Azure Key Vault during certificate renewal syncs, + preserving the original certificate name. +

+

+ This allows consuming services to continue using the same certificate name + while automatically using the latest version without requiring manual + updates. +

+

+ When disabled, new certificates will be created with new names, and old + certificates will be removed. +

+ + } + > + + +

+
+
+ )} + /> + )} + { const { watch } = useFormContext(); const { currentProject } = useProject(); - const { data: pkiSubscribers = [] } = useListWorkspacePkiSubscribers(currentProject?.id || ""); + const { data } = useListWorkspaceCertificates({ + projectId: currentProject?.id || "", + offset: 0, + limit: 100 + }); - const getSubscriberName = (subscriberId?: string) => { - const subscriber = pkiSubscribers.find((sub) => sub.id === subscriberId); - return subscriber?.name || "Unknown"; + const certificates = data?.certificates || []; + + const getSelectedCertificates = (certificateIds?: string[]) => { + if (!certificateIds || certificateIds.length === 0) return []; + return certificates.filter((cert) => certificateIds.includes(cert.id)); }; const { name, description, connection, - subscriberId, + certificateIds, syncOptions, destination, destinationConfig, @@ -31,17 +47,79 @@ export const PkiSyncReviewFields = () => { } = watch(); const destinationName = PKI_SYNC_MAP[destination].name; + const selectedCertificates = getSelectedCertificates(certificateIds); return (
- Source + Certificates
-
- - {getSubscriberName(subscriberId)} - +
+ {selectedCertificates.length === 0 ? ( + No certificates selected + ) : ( + + + + + + + + + + + {selectedCertificates.map((cert) => { + let originalDisplayName = "—"; + if (cert.altNames && cert.altNames.trim()) { + originalDisplayName = cert.altNames.trim(); + } else if (cert.commonName && cert.commonName.trim()) { + originalDisplayName = cert.commonName.trim(); + } + + let displayName = originalDisplayName; + let isTruncated = false; + if (originalDisplayName.length > 34) { + displayName = `${originalDisplayName.substring(0, 34)}...`; + isTruncated = true; + } + + const truncatedSerial = + cert.serialNumber.length > 8 + ? `${cert.serialNumber.slice(0, 4)}...${cert.serialNumber.slice(-4)}` + : cert.serialNumber; + + return ( + + + + + + ); + })} + +
SAN / CNSerial NumberExpires At
+ {isTruncated ? ( + +
{displayName}
+
+ ) : ( +
{displayName}
+ )} +
+
+ {truncatedSerial} +
+
+ + {new Date(cert.notAfter).toLocaleDateString()} + +
+
+ )}
@@ -62,11 +140,13 @@ export const PkiSyncReviewFields = () => {
Sync Options
-
+
- - {isAutoSyncEnabled ? "Enabled" : "Disabled"} - +
+ + {isAutoSyncEnabled ? "Enabled" : "Disabled"} + +
{/* Hidden for now - Import certificates functionality disabled {syncOptions?.canImportCertificates !== undefined && ( @@ -79,9 +159,11 @@ export const PkiSyncReviewFields = () => { */} {syncOptions?.canRemoveCertificates !== undefined && ( - - {syncOptions.canRemoveCertificates ? "Enabled" : "Disabled"} - +
+ + {syncOptions.canRemoveCertificates ? "Enabled" : "Disabled"} + +
)}
diff --git a/frontend/src/components/pki-syncs/forms/schemas/aws-certificate-manager-pki-sync-destination-schema.ts b/frontend/src/components/pki-syncs/forms/schemas/aws-certificate-manager-pki-sync-destination-schema.ts index aa522ef7d..4333b6187 100644 --- a/frontend/src/components/pki-syncs/forms/schemas/aws-certificate-manager-pki-sync-destination-schema.ts +++ b/frontend/src/components/pki-syncs/forms/schemas/aws-certificate-manager-pki-sync-destination-schema.ts @@ -7,6 +7,7 @@ import { BasePkiSyncSchema } from "./base-pki-sync-schema"; const AwsCertificateManagerSyncOptionsSchema = z.object({ canImportCertificates: z.boolean().default(false), canRemoveCertificates: z.boolean().default(false), + preserveArn: z.boolean().default(true), certificateNameSchema: z .string() .optional() diff --git a/frontend/src/components/pki-syncs/forms/schemas/azure-key-vault-pki-sync-destination-schema.ts b/frontend/src/components/pki-syncs/forms/schemas/azure-key-vault-pki-sync-destination-schema.ts index e87e7a52c..2a8f3ee53 100644 --- a/frontend/src/components/pki-syncs/forms/schemas/azure-key-vault-pki-sync-destination-schema.ts +++ b/frontend/src/components/pki-syncs/forms/schemas/azure-key-vault-pki-sync-destination-schema.ts @@ -4,7 +4,46 @@ import { PkiSync } from "@app/hooks/api/pkiSyncs"; import { BasePkiSyncSchema } from "./base-pki-sync-schema"; -export const AzureKeyVaultPkiSyncDestinationSchema = BasePkiSyncSchema().merge( +const AzureKeyVaultSyncOptionsSchema = z.object({ + canImportCertificates: z.boolean().default(false), + canRemoveCertificates: z.boolean().default(true), + enableVersioning: z.boolean().default(true), + certificateNameSchema: z + .string() + .optional() + .refine( + (val) => { + if (!val) return true; + + const allowedOptionalPlaceholders = ["{{environment}}"]; + + const allowedPlaceholdersRegexPart = ["{{certificateId}}", ...allowedOptionalPlaceholders] + .map((p) => p.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")) + .join("|"); + + const allowedContentRegex = new RegExp( + `^([a-zA-Z0-9_\\-/]|${allowedPlaceholdersRegexPart})*$` + ); + const contentIsValid = allowedContentRegex.test(val); + + if (val.trim()) { + const certificateIdRegex = /\{\{certificateId\}\}/; + const certificateIdIsPresent = certificateIdRegex.test(val); + return contentIsValid && certificateIdIsPresent; + } + + return contentIsValid; + }, + { + message: + "Certificate name schema must include exactly one {{certificateId}} placeholder. It can also include {{environment}} placeholders. Only alphanumeric characters (a-z, A-Z, 0-9), dashes (-), underscores (_), and slashes (/) are allowed besides the placeholders." + } + ) +}); + +export const AzureKeyVaultPkiSyncDestinationSchema = BasePkiSyncSchema( + AzureKeyVaultSyncOptionsSchema +).merge( z.object({ destination: z.literal(PkiSync.AzureKeyVault), destinationConfig: z.object({ diff --git a/frontend/src/components/pki-syncs/forms/schemas/base-pki-sync-schema.ts b/frontend/src/components/pki-syncs/forms/schemas/base-pki-sync-schema.ts index f8c9d599f..73da1f6af 100644 --- a/frontend/src/components/pki-syncs/forms/schemas/base-pki-sync-schema.ts +++ b/frontend/src/components/pki-syncs/forms/schemas/base-pki-sync-schema.ts @@ -53,7 +53,8 @@ export const BasePkiSyncSchema = { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - await mutateAsync({ - projectId: currentProject.id, - newProjectName: data.name, - newProjectDescription: data.description, - ...(showSlugField && - "slug" in data && { - newSlug: data.slug !== currentProject.slug ? data.slug : undefined - }) - }); + await mutateAsync({ + projectId: currentProject.id, + newProjectName: data.name, + newProjectDescription: data.description, + ...(showSlugField && + "slug" in data && { + newSlug: data.slug !== currentProject.slug ? data.slug : undefined + }) + }); - createNotification({ - text: "Successfully updated project overview", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update project overview", - type: "error" - }); - } + createNotification({ + text: "Successfully updated project overview", + type: "success" + }); }; return ( diff --git a/frontend/src/components/projects/NewProjectModal.tsx b/frontend/src/components/projects/NewProjectModal.tsx index d71e7b957..663d6a0b7 100644 --- a/frontend/src/components/projects/NewProjectModal.tsx +++ b/frontend/src/components/projects/NewProjectModal.tsx @@ -141,29 +141,24 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => { // type check if (!currentOrg) return; if (!user) return; - try { - const { - data: { project } - } = await createWs.mutateAsync({ - projectName: name, - projectDescription: description, - kmsKeyId: kmsKeyId !== INTERNAL_KMS_KEY_ID ? kmsKeyId : undefined, - template, - type - }); - await refetchWorkspaces(); + const { + data: { project } + } = await createWs.mutateAsync({ + projectName: name, + projectDescription: description, + kmsKeyId: kmsKeyId !== INTERNAL_KMS_KEY_ID ? kmsKeyId : undefined, + template, + type + }); + await refetchWorkspaces(); - createNotification({ text: "Project created", type: "success" }); - reset(); - onOpenChange(false); - navigate({ - to: getProjectHomePage(project.type, project.environments), - params: { projectId: project.id } - }); - } catch (err) { - console.error(err); - createNotification({ text: "Failed to create project", type: "error" }); - } + createNotification({ text: "Project created", type: "success" }); + reset(); + onOpenChange(false); + navigate({ + to: getProjectHomePage(project.type, project.environments), + params: { projectId: project.id } + }); }; const onSubmit = handleSubmit((data) => { return onCreateProject(data); diff --git a/frontend/src/components/secret-rotations-v2/DeleteSecretRotationV2Modal.tsx b/frontend/src/components/secret-rotations-v2/DeleteSecretRotationV2Modal.tsx index 20b08eb49..524c66933 100644 --- a/frontend/src/components/secret-rotations-v2/DeleteSecretRotationV2Modal.tsx +++ b/frontend/src/components/secret-rotations-v2/DeleteSecretRotationV2Modal.tsx @@ -37,29 +37,22 @@ export const DeleteSecretRotationV2Modal = ({ const handleDeleteSecretRotation = async () => { const rotationType = SECRET_ROTATION_MAP[type].name; - try { - await deleteSecretRotation.mutateAsync({ - rotationId, - type, - revokeGeneratedCredentials, - deleteSecrets, - projectId, - secretPath: folder.path - }); + await deleteSecretRotation.mutateAsync({ + rotationId, + type, + revokeGeneratedCredentials, + deleteSecrets, + projectId, + secretPath: folder.path + }); - createNotification({ - text: `Successfully deleted ${rotationType} Rotation`, - type: "success" - }); + createNotification({ + text: `Successfully deleted ${rotationType} Rotation`, + type: "success" + }); - if (onComplete) onComplete(); - onOpenChange(false); - } catch { - createNotification({ - text: `Failed to delete ${rotationType} Rotation`, - type: "error" - }); - } + if (onComplete) onComplete(); + onOpenChange(false); }; return ( diff --git a/frontend/src/components/secret-rotations-v2/RotateSecretRotationV2Modal.tsx b/frontend/src/components/secret-rotations-v2/RotateSecretRotationV2Modal.tsx index e2c931d49..7ad9611be 100644 --- a/frontend/src/components/secret-rotations-v2/RotateSecretRotationV2Modal.tsx +++ b/frontend/src/components/secret-rotations-v2/RotateSecretRotationV2Modal.tsx @@ -22,28 +22,19 @@ const Content = ({ secretRotation, onComplete }: ContentProps) => { const rotationType = SECRET_ROTATION_MAP[type].name; const handleRotateSecrets = async () => { - try { - await rotateSecrets.mutateAsync({ - rotationId, - type, - projectId, - secretPath: folder.path - }); + await rotateSecrets.mutateAsync({ + rotationId, + type, + projectId, + secretPath: folder.path + }); - createNotification({ - text: `Successfully rotated ${rotationType} secrets`, - type: "success" - }); + createNotification({ + text: `Successfully rotated ${rotationType} secrets`, + type: "success" + }); - onComplete(); - } catch (err) { - console.error(err); - - createNotification({ - text: `Failed to rotate ${rotationType} secrets`, - type: "error" - }); - } + onComplete(); }; return ( diff --git a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2Form.tsx b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2Form.tsx index 9887da026..320793ed1 100644 --- a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2Form.tsx +++ b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2Form.tsx @@ -120,21 +120,13 @@ export const SecretRotationV2Form = ({ environment: environment.slug, projectId: currentProject.id }); - try { - const rotation = await mutation; + const rotation = await mutation; - createNotification({ - text: `Successfully ${secretRotation ? "updated" : "created"} ${rotationType} Rotation`, - type: "success" - }); - onComplete(rotation); - } catch (err: any) { - createNotification({ - title: `Failed to ${secretRotation ? "update" : "create"} ${rotationType} Rotation`, - text: err.message, - type: "error" - }); - } + createNotification({ + text: `Successfully ${secretRotation ? "updated" : "created"} ${rotationType} Rotation`, + type: "success" + }); + onComplete(rotation); }; const handlePrev = () => { diff --git a/frontend/src/components/secret-scanning/DeleteSecretScanningDataSourceModal.tsx b/frontend/src/components/secret-scanning/DeleteSecretScanningDataSourceModal.tsx index 9cf918dfb..091de4cc0 100644 --- a/frontend/src/components/secret-scanning/DeleteSecretScanningDataSourceModal.tsx +++ b/frontend/src/components/secret-scanning/DeleteSecretScanningDataSourceModal.tsx @@ -28,26 +28,19 @@ export const DeleteSecretScanningDataSourceModal = ({ const handleDeleteDataSource = async () => { const dataSourceType = SECRET_SCANNING_DATA_SOURCE_MAP[type].name; - try { - await deleteDataSource.mutateAsync({ - dataSourceId, - type, - projectId - }); + await deleteDataSource.mutateAsync({ + dataSourceId, + type, + projectId + }); - createNotification({ - text: `Successfully deleted ${dataSourceType} Data Source`, - type: "success" - }); + createNotification({ + text: `Successfully deleted ${dataSourceType} Data Source`, + type: "success" + }); - if (onComplete) onComplete(); - onOpenChange(false); - } catch { - createNotification({ - text: `Failed to delete ${dataSourceType} Data Source`, - type: "error" - }); - } + if (onComplete) onComplete(); + onOpenChange(false); }; return ( diff --git a/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceForm.tsx b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceForm.tsx index 4d5ccdc79..563f82d46 100644 --- a/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceForm.tsx +++ b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceForm.tsx @@ -73,21 +73,13 @@ export const SecretScanningDataSourceForm = ({ connectionId: connection?.id, projectId: currentProject.id }); - try { - const source = await mutation; + const source = await mutation; - createNotification({ - text: `Successfully ${source ? "updated" : "created"} ${sourceType} Data Source`, - type: "success" - }); - onComplete(source); - } catch (err: any) { - createNotification({ - title: `Failed to ${dataSource ? "update" : "create"} ${sourceType} Data Source`, - text: err.message, - type: "error" - }); - } + createNotification({ + text: `Successfully ${source ? "updated" : "created"} ${sourceType} Data Source`, + type: "success" + }); + onComplete(source); }; const handlePrev = () => { diff --git a/frontend/src/components/secret-syncs/DeleteSecretSyncModal.tsx b/frontend/src/components/secret-syncs/DeleteSecretSyncModal.tsx index 2b3903a9b..de8b2e79d 100644 --- a/frontend/src/components/secret-syncs/DeleteSecretSyncModal.tsx +++ b/frontend/src/components/secret-syncs/DeleteSecretSyncModal.tsx @@ -23,29 +23,20 @@ export const DeleteSecretSyncModal = ({ isOpen, onOpenChange, secretSync, onComp const handleDeleteSecretSync = async () => { const destinationName = SECRET_SYNC_MAP[destination].name; - try { - await deleteSync.mutateAsync({ - syncId, - destination, - removeSecrets, - projectId - }); + await deleteSync.mutateAsync({ + syncId, + destination, + removeSecrets, + projectId + }); - createNotification({ - text: `Successfully removed ${destinationName} Sync`, - type: "success" - }); + createNotification({ + text: `Successfully removed ${destinationName} Sync`, + type: "success" + }); - if (onComplete) onComplete(); - onOpenChange(false); - } catch (err) { - console.error(err); - - createNotification({ - text: `Failed to remove ${destinationName} Sync`, - type: "error" - }); - } + if (onComplete) onComplete(); + onOpenChange(false); }; return ( diff --git a/frontend/src/components/secret-syncs/SecretSyncImportSecretsModal.tsx b/frontend/src/components/secret-syncs/SecretSyncImportSecretsModal.tsx index c1b21a771..656faffeb 100644 --- a/frontend/src/components/secret-syncs/SecretSyncImportSecretsModal.tsx +++ b/frontend/src/components/secret-syncs/SecretSyncImportSecretsModal.tsx @@ -51,28 +51,19 @@ const Content = ({ secretSync, onComplete }: ContentProps) => { const triggerImportSecrets = useTriggerSecretSyncImportSecrets(); const handleTriggerImportSecrets = async ({ importBehavior }: TFormData) => { - try { - await triggerImportSecrets.mutateAsync({ - syncId, - destination, - importBehavior, - projectId - }); + await triggerImportSecrets.mutateAsync({ + syncId, + destination, + importBehavior, + projectId + }); - createNotification({ - text: `Successfully triggered secret import for ${destinationName} Sync`, - type: "success" - }); + createNotification({ + text: `Successfully triggered secret import for ${destinationName} Sync`, + type: "success" + }); - onComplete(); - } catch (err) { - console.error(err); - - createNotification({ - text: `Failed to trigger secret import for ${destinationName} Sync`, - type: "error" - }); - } + onComplete(); }; return ( diff --git a/frontend/src/components/secret-syncs/SecretSyncRemoveSecretsModal.tsx b/frontend/src/components/secret-syncs/SecretSyncRemoveSecretsModal.tsx index 718392b92..9c9c0e659 100644 --- a/frontend/src/components/secret-syncs/SecretSyncRemoveSecretsModal.tsx +++ b/frontend/src/components/secret-syncs/SecretSyncRemoveSecretsModal.tsx @@ -21,27 +21,18 @@ const Content = ({ secretSync, onComplete }: ContentProps) => { const triggerSyncImport = useTriggerSecretSyncRemoveSecrets(); const handleTriggerRemoveSecrets = async () => { - try { - await triggerSyncImport.mutateAsync({ - syncId, - destination, - projectId - }); + await triggerSyncImport.mutateAsync({ + syncId, + destination, + projectId + }); - createNotification({ - text: `Successfully triggered secret removal for ${destinationName} Sync`, - type: "success" - }); + createNotification({ + text: `Successfully triggered secret removal for ${destinationName} Sync`, + type: "success" + }); - onComplete(); - } catch (err) { - console.error(err); - - createNotification({ - text: `Failed to trigger secret removal for ${destinationName} Sync`, - type: "error" - }); - } + onComplete(); }; return ( diff --git a/frontend/src/components/secret-syncs/SecretSyncSelect.tsx b/frontend/src/components/secret-syncs/SecretSyncSelect.tsx index b3aeab962..881f16610 100644 --- a/frontend/src/components/secret-syncs/SecretSyncSelect.tsx +++ b/frontend/src/components/secret-syncs/SecretSyncSelect.tsx @@ -68,7 +68,8 @@ export const SecretSyncSelect = ({ onSelect }: Props) => { onClick={() => enterprise && !subscription.enterpriseSecretSyncs ? handlePopUpOpen("upgradePlan", { - isEnterpriseFeature: true + isEnterpriseFeature: true, + text: "All Secret Syncs can be unlocked if you switch to Infisical Enterprise plan." }) : onSelect(destination) } @@ -149,7 +150,7 @@ export const SecretSyncSelect = ({ onSelect }: Props) => { isOpen={popUp.upgradePlan.isOpen} isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} - text="You can use every Secret Sync if you switch to Infisical's Enterprise plan." + text={popUp.upgradePlan.data?.text} />
); diff --git a/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx b/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx index ebad86cbd..21a69b163 100644 --- a/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx +++ b/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx @@ -88,14 +88,8 @@ export const CreateSecretSyncForm = ({ type: "success" }); onComplete(secretSync); - } catch (err: any) { - console.error(err); + } catch { setShowConfirmation(false); - createNotification({ - title: `Failed to add ${destinationName} Sync`, - text: err.message, - type: "error" - }); } }; diff --git a/frontend/src/components/secret-syncs/forms/EditSecretSyncForm.tsx b/frontend/src/components/secret-syncs/forms/EditSecretSyncForm.tsx index 2085afe9c..207b72cd8 100644 --- a/frontend/src/components/secret-syncs/forms/EditSecretSyncForm.tsx +++ b/frontend/src/components/secret-syncs/forms/EditSecretSyncForm.tsx @@ -58,29 +58,20 @@ export const EditSecretSyncForm = ({ secretSync, fields, onComplete }: Props) => const performUpdate = useCallback( async (formData: TSecretSyncForm) => { - try { - const { environment, connection, ...updateData } = formData; - const updatedSecretSync = await updateSecretSync.mutateAsync({ - syncId: secretSync.id, - ...updateData, - environment: environment?.slug, - connectionId: connection.id, - projectId: secretSync.projectId - }); + const { environment, connection, ...updateData } = formData; + const updatedSecretSync = await updateSecretSync.mutateAsync({ + syncId: secretSync.id, + ...updateData, + environment: environment?.slug, + connectionId: connection.id, + projectId: secretSync.projectId + }); - createNotification({ - text: `Successfully updated ${destinationName} Sync`, - type: "success" - }); - onComplete(updatedSecretSync); - } catch (err: any) { - console.error(err); - createNotification({ - title: `Failed to update ${destinationName} Sync`, - text: err.message, - type: "error" - }); - } + createNotification({ + text: `Successfully updated ${destinationName} Sync`, + type: "success" + }); + onComplete(updatedSecretSync); }, [updateSecretSync, secretSync.id, secretSync.projectId, destinationName, onComplete] ); diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/ChefSyncFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/ChefSyncFields.tsx new file mode 100644 index 000000000..bf5e0908c --- /dev/null +++ b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/ChefSyncFields.tsx @@ -0,0 +1,93 @@ +import { Controller, useFormContext, useWatch } from "react-hook-form"; +import { SingleValue } from "react-select"; + +import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField"; +import { FilterableSelect, FormControl } from "@app/components/v2"; +import { + TChefDataBag, + TChefDataBagItem, + useChefConnectionListDataBagItems, + useChefConnectionListDataBags +} from "@app/hooks/api/appConnections/chef"; +import { SecretSync } from "@app/hooks/api/secretSyncs"; + +import { TSecretSyncForm } from "../schemas"; + +export const ChefSyncFields = () => { + const { control, setValue } = useFormContext< + TSecretSyncForm & { destination: SecretSync.Chef } + >(); + + const connectionId = useWatch({ name: "connection.id", control }); + const dataBagName = useWatch({ name: "destinationConfig.dataBagName", control }); + + const { data: dataBags, isLoading: isDataBagsLoading } = useChefConnectionListDataBags( + connectionId, + { + enabled: Boolean(connectionId) + } + ); + + const { data: dataBagItems, isLoading: isDataBagItemsLoading } = + useChefConnectionListDataBagItems(connectionId, dataBagName, { + enabled: Boolean(connectionId && dataBagName) + }); + + const handleChangeConnection = () => { + setValue("destinationConfig.dataBagName", ""); + setValue("destinationConfig.dataBagItemName", ""); + }; + + return ( + <> + + + ( + + dataBag.name === value) ?? null} + onChange={(option) => { + const selectedDataBag = option as SingleValue; + onChange(selectedDataBag?.name ?? ""); + setValue("destinationConfig.dataBagItemName", ""); + }} + options={dataBags} + placeholder="Select a data bag..." + getOptionLabel={(option) => option.name} + getOptionValue={(option) => option.name} + /> + + )} + /> + + ( + + dataBagItem.name === value) ?? null} + onChange={(option) => { + const selectedDataBagItem = option as SingleValue; + onChange(selectedDataBagItem?.name ?? ""); + }} + options={dataBagItems} + placeholder="Select a data bag item..." + getOptionLabel={(option) => option.name} + getOptionValue={(option) => option.name} + /> + + )} + /> + + ); +}; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx index ffad9aa42..50fee8b9b 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx @@ -12,6 +12,7 @@ import { AzureKeyVaultSyncFields } from "./AzureKeyVaultSyncFields"; import { BitbucketSyncFields } from "./BitbucketSyncFields"; import { CamundaSyncFields } from "./CamundaSyncFields"; import { ChecklySyncFields } from "./ChecklySyncFields"; +import { ChefSyncFields } from "./ChefSyncFields"; import { CloudflarePagesSyncFields } from "./CloudflarePagesSyncFields"; import { CloudflareWorkersSyncFields } from "./CloudflareWorkersSyncFields"; import { DatabricksSyncFields } from "./DatabricksSyncFields"; @@ -104,6 +105,8 @@ export const SecretSyncDestinationFields = () => { return ; case SecretSync.LaravelForge: return ; + case SecretSync.Chef: + return ; case SecretSync.Northflank: return ; default: diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx index a7fb037de..506c81a21 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx @@ -71,6 +71,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => { case SecretSync.Northflank: case SecretSync.Bitbucket: case SecretSync.LaravelForge: + case SecretSync.Chef: AdditionalSyncOptionsFieldsComponent = null; break; default: diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/ChefSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/ChefSyncReviewFields.tsx new file mode 100644 index 000000000..95f5310a2 --- /dev/null +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/ChefSyncReviewFields.tsx @@ -0,0 +1,18 @@ +import { useFormContext } from "react-hook-form"; + +import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas"; +import { GenericFieldLabel } from "@app/components/v2"; +import { SecretSync } from "@app/hooks/api/secretSyncs"; + +export const ChefSyncReviewFields = () => { + const { watch } = useFormContext(); + const dataBagName = watch("destinationConfig.dataBagName"); + const dataBagItemName = watch("destinationConfig.dataBagItemName"); + + return ( + <> + {dataBagName} + {dataBagItemName} + + ); +}; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx index 4cc9c7259..940b3c173 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx @@ -24,6 +24,7 @@ import { AzureKeyVaultSyncReviewFields } from "./AzureKeyVaultSyncReviewFields"; import { BitbucketSyncReviewFields } from "./BitbucketSyncReviewFields"; import { CamundaSyncReviewFields } from "./CamundaSyncReviewFields"; import { ChecklySyncReviewFields } from "./ChecklySyncReviewFields"; +import { ChefSyncReviewFields } from "./ChefSyncReviewFields"; import { CloudflarePagesSyncReviewFields } from "./CloudflarePagesReviewFields"; import { CloudflareWorkersSyncReviewFields } from "./CloudflareWorkersReviewFields"; import { DatabricksSyncReviewFields } from "./DatabricksSyncReviewFields"; @@ -177,6 +178,9 @@ export const SecretSyncReviewFields = () => { case SecretSync.LaravelForge: DestinationFieldsComponent = ; break; + case SecretSync.Chef: + DestinationFieldsComponent = ; + break; default: throw new Error(`Unhandled Destination Review Fields: ${destination}`); } diff --git a/frontend/src/components/secret-syncs/forms/schemas/chef-sync-destination-schema.ts b/frontend/src/components/secret-syncs/forms/schemas/chef-sync-destination-schema.ts new file mode 100644 index 000000000..8d27b616f --- /dev/null +++ b/frontend/src/components/secret-syncs/forms/schemas/chef-sync-destination-schema.ts @@ -0,0 +1,14 @@ +import { z } from "zod"; + +import { BaseSecretSyncSchema } from "@app/components/secret-syncs/forms/schemas/base-secret-sync-schema"; +import { SecretSync } from "@app/hooks/api/secretSyncs"; + +export const ChefSyncDestinationSchema = BaseSecretSyncSchema().merge( + z.object({ + destination: z.literal(SecretSync.Chef), + destinationConfig: z.object({ + dataBagName: z.string().trim().min(1, "Data Bag required"), + dataBagItemName: z.string().trim().min(1, "Data Bag Item required") + }) + }) +); diff --git a/frontend/src/components/secret-syncs/forms/schemas/secret-sync-schema.ts b/frontend/src/components/secret-syncs/forms/schemas/secret-sync-schema.ts index 146c862dc..561191da0 100644 --- a/frontend/src/components/secret-syncs/forms/schemas/secret-sync-schema.ts +++ b/frontend/src/components/secret-syncs/forms/schemas/secret-sync-schema.ts @@ -9,6 +9,7 @@ import { AzureKeyVaultSyncDestinationSchema } from "./azure-key-vault-sync-desti import { BitbucketSyncDestinationSchema } from "./bitbucket-sync-destination-schema"; import { CamundaSyncDestinationSchema } from "./camunda-sync-destination-schema"; import { ChecklySyncDestinationSchema } from "./checkly-sync-destination-schema"; +import { ChefSyncDestinationSchema } from "./chef-sync-destination-schema"; import { CloudflarePagesSyncDestinationSchema } from "./cloudflare-pages-sync-destination-schema"; import { CloudflareWorkersSyncDestinationSchema } from "./cloudflare-workers-sync-destination-schema"; import { DatabricksSyncDestinationSchema } from "./databricks-sync-destination-schema"; @@ -65,7 +66,8 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [ NetlifySyncDestinationSchema, NorthflankSyncDestinationSchema, BitbucketSyncDestinationSchema, - LaravelForgeSyncDestinationSchema + LaravelForgeSyncDestinationSchema, + ChefSyncDestinationSchema ]); export const SecretSyncFormSchema = SecretSyncUnionSchema; diff --git a/frontend/src/components/tags/CreateTagModal/CreateTagModal.tsx b/frontend/src/components/tags/CreateTagModal/CreateTagModal.tsx index 3c38926af..97e74cae4 100644 --- a/frontend/src/components/tags/CreateTagModal/CreateTagModal.tsx +++ b/frontend/src/components/tags/CreateTagModal/CreateTagModal.tsx @@ -130,26 +130,18 @@ export const CreateTagModal = ({ isOpen, onToggle, append, currentSecret }: Prop }, [isOpen]); const onFormSubmit = async ({ slug, color }: FormData) => { - try { - const data = await createWsTag({ - projectId, - tagColor: color, - tagSlug: slug - }); - append(data); - onToggle(false); - reset(); - createNotification({ - text: "Successfully created a tag", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to create a tag", - type: "error" - }); - } + const data = await createWsTag({ + projectId, + tagColor: color, + tagSlug: slug + }); + append(data); + onToggle(false); + reset(); + createNotification({ + text: "Successfully created a tag", + type: "success" + }); }; return ( diff --git a/frontend/src/components/utilities/certificateDisplayUtils.tsx b/frontend/src/components/utilities/certificateDisplayUtils.tsx new file mode 100644 index 000000000..4a2b2de26 --- /dev/null +++ b/frontend/src/components/utilities/certificateDisplayUtils.tsx @@ -0,0 +1,100 @@ +import { ReactNode } from "react"; + +import { Tooltip } from "@app/components/v2"; + +interface CertificateNameData { + altNames?: string | null; + commonName?: string | null; + certificateAltNames?: string | null; + certificateCommonName?: string | null; +} + +interface DisplayNameResult { + originalDisplayName: string; + displayName: string; + isTruncated: boolean; +} + +/** + * Extracts and formats the display name for a certificate from SAN/CN data + * @param cert - Certificate object with potential altNames/commonName fields + * @param maxLength - Maximum length before truncating (default: 64) + * @param fallback - Fallback text when no name is found (default: "—") + * @returns Object with original name, truncated name, and truncation flag + */ +export const getCertificateDisplayName = ( + cert: CertificateNameData, + maxLength: number = 64, + fallback: string = "—" +): DisplayNameResult => { + // Extract original display name - prioritize SAN over CN + let originalDisplayName = fallback; + + // Handle different property name variations + const altNames = cert.altNames || cert.certificateAltNames; + const commonName = cert.commonName || cert.certificateCommonName; + + if (altNames && altNames.trim()) { + originalDisplayName = altNames.trim(); + } else if (commonName && commonName.trim()) { + originalDisplayName = commonName.trim(); + } + + // Handle truncation + let displayName = originalDisplayName; + let isTruncated = false; + + if (originalDisplayName.length > maxLength) { + displayName = `${originalDisplayName.substring(0, maxLength)}...`; + isTruncated = true; + } + + return { + originalDisplayName, + displayName, + isTruncated + }; +}; + +/** + * Renders a certificate display name with optional tooltip for truncated names + * @param cert - Certificate object with potential altNames/commonName fields + * @param maxLength - Maximum length before truncating (default: 64) + * @param fallback - Fallback text when no name is found (default: "—") + * @param className - Optional CSS class for the display element + * @param tooltipClassName - Optional CSS class for the tooltip (default: "max-w-lg") + * @returns JSX element with certificate name and optional tooltip + */ +export const CertificateDisplayName = ({ + cert, + maxLength = 64, + fallback = "—", + className = "truncate", + tooltipClassName = "max-w-lg" +}: { + cert: CertificateNameData; + maxLength?: number; + fallback?: string; + className?: string; + tooltipClassName?: string; +}): ReactNode => { + const { originalDisplayName, displayName, isTruncated } = getCertificateDisplayName( + cert, + maxLength, + fallback + ); + + if (isTruncated) { + return ( + +
{displayName}
+
+ ); + } + + return ( +
+ {displayName} +
+ ); +}; diff --git a/frontend/src/components/v2/HighlightText/HighlightText.tsx b/frontend/src/components/v2/HighlightText/HighlightText.tsx index c81dab2df..92fdc1d6d 100644 --- a/frontend/src/components/v2/HighlightText/HighlightText.tsx +++ b/frontend/src/components/v2/HighlightText/HighlightText.tsx @@ -9,22 +9,10 @@ export const HighlightText = ({ }) => { if (!text) return null; - const renderTextWithNewlines = (input: string, baseKeyPrefix: string = ""): React.ReactNode[] => { - if (!input) return []; - const lines = input.split("\n"); - return lines.flatMap((line, index) => { - const nodes: React.ReactNode[] = [line]; - if (index < lines.length - 1) { - nodes.push(
); - } - return nodes; - }); - }; - const searchTerm = highlight.toLowerCase().trim(); if (!searchTerm) { - return {renderTextWithNewlines(text, "full-text")}; + return {text}; } const parts: React.ReactNode[] = []; @@ -36,16 +24,12 @@ export const HighlightText = ({ text.replace(regex, (match: string, offset: number) => { if (offset > lastIndex) { const preMatchText = text.substring(lastIndex, offset); - parts.push( - - {renderTextWithNewlines(preMatchText, `pre-${lastIndex}`)} - - ); + parts.push({preMatchText}); } parts.push( - {renderTextWithNewlines(match, `match-${offset}`)} + {match} ); @@ -56,11 +40,7 @@ export const HighlightText = ({ if (lastIndex < text.length) { const postMatchText = text.substring(lastIndex); - parts.push( - - {renderTextWithNewlines(postMatchText, `post-${lastIndex}`)} - - ); + parts.push({postMatchText}); } return parts; diff --git a/frontend/src/const/routes.ts b/frontend/src/const/routes.ts index c9a6b326d..5f199443d 100644 --- a/frontend/src/const/routes.ts +++ b/frontend/src/const/routes.ts @@ -301,10 +301,6 @@ export const ROUTE_PATHS = Object.freeze({ "/projects/cert-management/$projectId/subscribers", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/subscribers" ), - CertificatesPage: setRoute( - "/projects/cert-management/$projectId/certificates", - "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificates" - ), CertificateAuthoritiesPage: setRoute( "/projects/cert-management/$projectId/certificate-authorities", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-authorities" diff --git a/frontend/src/helpers/appConnections.ts b/frontend/src/helpers/appConnections.ts index 351357d20..b27da2441 100644 --- a/frontend/src/helpers/appConnections.ts +++ b/frontend/src/helpers/appConnections.ts @@ -46,6 +46,7 @@ import { } from "@app/hooks/api/appConnections/types"; import { BitbucketConnectionMethod } from "@app/hooks/api/appConnections/types/bitbucket-connection"; import { ChecklyConnectionMethod } from "@app/hooks/api/appConnections/types/checkly-connection"; +import { ChefConnectionMethod } from "@app/hooks/api/appConnections/types/chef-connection"; import { DigitalOceanConnectionMethod } from "@app/hooks/api/appConnections/types/digital-ocean"; import { HerokuConnectionMethod } from "@app/hooks/api/appConnections/types/heroku-connection"; import { LaravelForgeConnectionMethod } from "@app/hooks/api/appConnections/types/laravel-forge-connection"; @@ -129,7 +130,8 @@ export const APP_CONNECTION_MAP: Record< name: "Laravel Forge", image: "Laravel Forge.png", size: 65 - } + }, + [AppConnection.Chef]: { name: "Chef", image: "Chef.png", enterprise: true } }; export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => { @@ -202,6 +204,8 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) case RenderConnectionMethod.ApiKey: case ChecklyConnectionMethod.ApiKey: return { name: "API Key", icon: faKey }; + case ChefConnectionMethod.UserKey: + return { name: "User Key", icon: faKey }; case AzureClientSecretsConnectionMethod.ClientSecret: case AzureAppConfigurationConnectionMethod.ClientSecret: case AzureKeyVaultConnectionMethod.ClientSecret: diff --git a/frontend/src/helpers/secretSyncs.ts b/frontend/src/helpers/secretSyncs.ts index a96af15d4..393aff644 100644 --- a/frontend/src/helpers/secretSyncs.ts +++ b/frontend/src/helpers/secretSyncs.ts @@ -121,6 +121,10 @@ export const SECRET_SYNC_MAP: Record = { [SecretSync.Netlify]: AppConnection.Netlify, [SecretSync.Northflank]: AppConnection.Northflank, [SecretSync.Bitbucket]: AppConnection.Bitbucket, - [SecretSync.LaravelForge]: AppConnection.LaravelForge + [SecretSync.LaravelForge]: AppConnection.LaravelForge, + [SecretSync.Chef]: AppConnection.Chef }; export const SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP: Record< diff --git a/frontend/src/hooks/api/appConnections/chef/index.ts b/frontend/src/hooks/api/appConnections/chef/index.ts new file mode 100644 index 000000000..2c1906d36 --- /dev/null +++ b/frontend/src/hooks/api/appConnections/chef/index.ts @@ -0,0 +1,2 @@ +export * from "./queries"; +export * from "./types"; diff --git a/frontend/src/hooks/api/appConnections/chef/queries.tsx b/frontend/src/hooks/api/appConnections/chef/queries.tsx new file mode 100644 index 000000000..f24a39eed --- /dev/null +++ b/frontend/src/hooks/api/appConnections/chef/queries.tsx @@ -0,0 +1,68 @@ +import { useQuery, UseQueryOptions } from "@tanstack/react-query"; + +import { apiRequest } from "@app/config/request"; +import { appConnectionKeys } from "@app/hooks/api/appConnections"; + +import { TChefDataBag, TChefDataBagItem } from "./types"; + +const chefConnectionKeys = { + all: [...appConnectionKeys.all, "chef"] as const, + listDataBags: (connectionId: string) => + [...chefConnectionKeys.all, "data-bags", connectionId] as const, + listDataBagItems: (connectionId: string, dataBagName: string) => + [...chefConnectionKeys.all, "data-bag-items", connectionId, dataBagName] as const +}; + +export const useChefConnectionListDataBags = ( + connectionId: string, + options?: Omit< + UseQueryOptions< + TChefDataBag[], + unknown, + TChefDataBag[], + ReturnType + >, + "queryKey" | "queryFn" + > +) => { + return useQuery({ + queryKey: chefConnectionKeys.listDataBags(connectionId), + queryFn: async () => { + const { data } = await apiRequest.get( + `/api/v1/app-connections/chef/${connectionId}/data-bags` + ); + + return data; + }, + ...options + }); +}; + +export const useChefConnectionListDataBagItems = ( + connectionId: string, + dataBagName: string, + options?: Omit< + UseQueryOptions< + TChefDataBagItem[], + unknown, + TChefDataBagItem[], + ReturnType + >, + "queryKey" | "queryFn" + > +) => { + return useQuery({ + queryKey: chefConnectionKeys.listDataBagItems(connectionId, dataBagName), + queryFn: async () => { + const params = { dataBagName }; + const { data } = await apiRequest.get( + `/api/v1/app-connections/chef/${connectionId}/data-bag-items`, + { params } + ); + + return data; + }, + enabled: Boolean(connectionId && dataBagName), + ...options + }); +}; diff --git a/frontend/src/hooks/api/appConnections/chef/types.ts b/frontend/src/hooks/api/appConnections/chef/types.ts new file mode 100644 index 000000000..fd87d47c7 --- /dev/null +++ b/frontend/src/hooks/api/appConnections/chef/types.ts @@ -0,0 +1,7 @@ +export type TChefDataBag = { + name: string; +}; + +export type TChefDataBagItem = { + name: string; +}; diff --git a/frontend/src/hooks/api/appConnections/enums.ts b/frontend/src/hooks/api/appConnections/enums.ts index 4af1dbb27..fba0cbb4b 100644 --- a/frontend/src/hooks/api/appConnections/enums.ts +++ b/frontend/src/hooks/api/appConnections/enums.ts @@ -39,5 +39,6 @@ export enum AppConnection { Northflank = "northflank", Okta = "okta", Redis = "redis", - LaravelForge = "laravel-forge" + LaravelForge = "laravel-forge", + Chef = "chef" } diff --git a/frontend/src/hooks/api/appConnections/types/app-options.ts b/frontend/src/hooks/api/appConnections/types/app-options.ts index 4d4425ef6..1f553f605 100644 --- a/frontend/src/hooks/api/appConnections/types/app-options.ts +++ b/frontend/src/hooks/api/appConnections/types/app-options.ts @@ -148,6 +148,10 @@ export type TChecklyConnectionOption = TAppConnectionOptionBase & { app: AppConnection.Checkly; }; +export type TChefConnectionOption = TAppConnectionOptionBase & { + app: AppConnection.Chef; +}; + export type TSupabaseConnectionOption = TAppConnectionOptionBase & { app: AppConnection.Supabase; }; @@ -220,7 +224,8 @@ export type TAppConnectionOption = | TNorthflankConnectionOption | TOktaConnectionOption | TAzureAdCsConnectionOption - | TLaravelForgeConnectionOption; + | TLaravelForgeConnectionOption + | TChefConnectionOption; export type TAppConnectionOptionMap = { [AppConnection.AWS]: TAwsConnectionOption; @@ -264,4 +269,5 @@ export type TAppConnectionOptionMap = { [AppConnection.AzureADCS]: TAzureAdCsConnectionOption; [AppConnection.Redis]: TRedisConnectionOption; [AppConnection.LaravelForge]: TLaravelForgeConnectionOption; + [AppConnection.Chef]: TChefConnectionOption; }; diff --git a/frontend/src/hooks/api/appConnections/types/chef-connection.ts b/frontend/src/hooks/api/appConnections/types/chef-connection.ts new file mode 100644 index 000000000..371d42199 --- /dev/null +++ b/frontend/src/hooks/api/appConnections/types/chef-connection.ts @@ -0,0 +1,16 @@ +import { AppConnection } from "@app/hooks/api/appConnections/enums"; +import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection"; + +export enum ChefConnectionMethod { + UserKey = "user-key" +} + +export type TChefConnection = TRootAppConnection & { app: AppConnection.Chef } & { + method: ChefConnectionMethod.UserKey; + credentials: { + instanceUrl?: string; + orgName: string; + userName: string; + privateKey: string; + }; +}; diff --git a/frontend/src/hooks/api/appConnections/types/index.ts b/frontend/src/hooks/api/appConnections/types/index.ts index d82ad90ec..3e62031b3 100644 --- a/frontend/src/hooks/api/appConnections/types/index.ts +++ b/frontend/src/hooks/api/appConnections/types/index.ts @@ -11,6 +11,7 @@ import { TAzureKeyVaultConnection } from "./azure-key-vault-connection"; import { TBitbucketConnection } from "./bitbucket-connection"; import { TCamundaConnection } from "./camunda-connection"; import { TChecklyConnection } from "./checkly-connection"; +import { TChefConnection } from "./chef-connection"; import { TCloudflareConnection } from "./cloudflare-connection"; import { TDatabricksConnection } from "./databricks-connection"; import { TDigitalOceanConnection } from "./digital-ocean"; @@ -53,6 +54,7 @@ export * from "./azure-key-vault-connection"; export * from "./bitbucket-connection"; export * from "./camunda-connection"; export * from "./checkly-connection"; +export * from "./chef-connection"; export * from "./cloudflare-connection"; export * from "./databricks-connection"; export * from "./flyio-connection"; @@ -124,7 +126,8 @@ export type TAppConnection = | TNetlifyConnection | TNorthflankConnection | TOktaConnection - | TRedisConnection; + | TRedisConnection + | TChefConnection; export type TAvailableAppConnection = Pick; diff --git a/frontend/src/hooks/api/ca/mutations.tsx b/frontend/src/hooks/api/ca/mutations.tsx index fa422054c..49a53dce7 100644 --- a/frontend/src/hooks/api/ca/mutations.tsx +++ b/frontend/src/hooks/api/ca/mutations.tsx @@ -152,7 +152,7 @@ export const useCreateCertificate = () => { }); }; -export const useCreateCertificateV3 = () => { +export const useCreateCertificateV3 = (options?: { projectId?: string }) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (body) => { @@ -167,6 +167,12 @@ export const useCreateCertificateV3 = () => { queryKey: projectKeys.forProjectCertificates(projectSlug) }); + if (options?.projectId) { + queryClient.invalidateQueries({ + queryKey: projectKeys.forProjectCertificates(options.projectId) + }); + } + queryClient.invalidateQueries({ queryKey: ["certificate-profiles"] }); diff --git a/frontend/src/hooks/api/certificateProfiles/index.ts b/frontend/src/hooks/api/certificateProfiles/index.ts index dc5c17efa..e12e066c4 100644 --- a/frontend/src/hooks/api/certificateProfiles/index.ts +++ b/frontend/src/hooks/api/certificateProfiles/index.ts @@ -8,7 +8,6 @@ export { useGetCertificateProfileById, useGetCertificateProfileBySlug, useGetProfileCertificates, - useGetProfileMetrics, useListCertificateProfiles } from "./queries"; export type * from "./types"; diff --git a/frontend/src/hooks/api/certificateProfiles/queries.tsx b/frontend/src/hooks/api/certificateProfiles/queries.tsx index abdc93ddb..19859b02f 100644 --- a/frontend/src/hooks/api/certificateProfiles/queries.tsx +++ b/frontend/src/hooks/api/certificateProfiles/queries.tsx @@ -4,7 +4,6 @@ import { apiRequest } from "@app/config/request"; import { TCertificateProfile, - TCertificateProfileMetrics, TCertificateProfileWithDetails, TGetCertificateProfileByIdDTO, TGetCertificateProfileBySlugDTO, @@ -20,7 +19,6 @@ export const certificateProfileKeys = { limit?: number; offset?: number; search?: string; - includeMetrics?: boolean; includeConfigs?: boolean; enrollmentType?: string; expiringDays?: number; @@ -51,10 +49,8 @@ export const useListCertificateProfiles = ({ limit = 20, offset = 0, search, - includeMetrics = false, includeConfigs = false, - enrollmentType, - expiringDays = 7 + enrollmentType }: TListCertificateProfilesDTO) => { return useQuery({ queryKey: certificateProfileKeys.list({ @@ -62,10 +58,8 @@ export const useListCertificateProfiles = ({ limit, offset, search, - includeMetrics, includeConfigs, - enrollmentType, - expiringDays + enrollmentType }), queryFn: async () => { const { data } = await apiRequest.get<{ @@ -77,10 +71,8 @@ export const useListCertificateProfiles = ({ limit, offset, search, - includeMetrics, includeConfigs, - enrollmentType, - expiringDays + enrollmentType } }); return data; @@ -145,18 +137,3 @@ export const useGetProfileCertificates = ({ enabled: Boolean(profileId) }); }; - -export const useGetProfileMetrics = ({ profileId, expiringDays = 7 }: TGetProfileMetricsDTO) => { - return useQuery({ - queryKey: certificateProfileKeys.getMetrics(profileId, { expiringDays }), - queryFn: async () => { - const { data } = await apiRequest.get<{ - metrics: TCertificateProfileMetrics; - }>(`/api/v1/pki/certificate-profiles/${profileId}/metrics`, { - params: { expiringDays } - }); - return data.metrics; - }, - enabled: Boolean(profileId) - }); -}; diff --git a/frontend/src/hooks/api/certificateProfiles/types.ts b/frontend/src/hooks/api/certificateProfiles/types.ts index b5c53e11b..f3584b12d 100644 --- a/frontend/src/hooks/api/certificateProfiles/types.ts +++ b/frontend/src/hooks/api/certificateProfiles/types.ts @@ -10,7 +10,6 @@ export type TCertificateProfile = { apiConfigId?: string; createdAt: string; updatedAt: string; - metrics?: TCertificateProfileMetrics; }; export type TCertificateProfileWithDetails = TCertificateProfile & { @@ -81,10 +80,8 @@ export type TListCertificateProfilesDTO = { limit?: number; offset?: number; search?: string; - includeMetrics?: boolean; includeConfigs?: boolean; enrollmentType?: "api" | "est"; - expiringDays?: number; }; export type TGetCertificateProfileByIdDTO = { @@ -96,15 +93,6 @@ export type TGetCertificateProfileBySlugDTO = { slug: string; }; -export type TCertificateProfileMetrics = { - profileId: string; - totalCertificates: number; - activeCertificates: number; - expiredCertificates: number; - expiringCertificates: number; - revokedCertificates: number; -}; - export type TProfileCertificate = { id: string; serialNumber: string; @@ -126,5 +114,4 @@ export type TGetProfileCertificatesDTO = { export type TGetProfileMetricsDTO = { profileId: string; - expiringDays?: number; }; diff --git a/frontend/src/hooks/api/certificateTemplates/types.ts b/frontend/src/hooks/api/certificateTemplates/types.ts index 373bbef2a..a6653cbec 100644 --- a/frontend/src/hooks/api/certificateTemplates/types.ts +++ b/frontend/src/hooks/api/certificateTemplates/types.ts @@ -42,8 +42,8 @@ export type TCreateCertificateTemplateDTO = { subjectAlternativeName: string; ttl: string; projectId: string; - keyUsages: CertKeyUsage[]; - extendedKeyUsages: CertExtendedKeyUsage[]; + keyUsages: string[]; + extendedKeyUsages: string[]; }; export type TUpdateCertificateTemplateDTO = { @@ -55,8 +55,8 @@ export type TUpdateCertificateTemplateDTO = { subjectAlternativeName?: string; ttl?: string; projectId: string; - keyUsages?: CertKeyUsage[]; - extendedKeyUsages?: CertExtendedKeyUsage[]; + keyUsages?: string[]; + extendedKeyUsages?: string[]; }; export type TDeleteCertificateTemplateDTO = { @@ -71,8 +71,8 @@ export type TCreateCertificateTemplateV2DTO = { subjectAlternativeName: string; ttl: string; projectId: string; - keyUsages: CertKeyUsage[]; - extendedKeyUsages: CertExtendedKeyUsage[]; + keyUsages: string[]; + extendedKeyUsages: string[]; }; export type TUpdateCertificateTemplateV2DTO = { @@ -83,8 +83,8 @@ export type TUpdateCertificateTemplateV2DTO = { subjectAlternativeName?: string; ttl?: string; projectId: string; - keyUsages?: CertKeyUsage[]; - extendedKeyUsages?: CertExtendedKeyUsage[]; + keyUsages?: string[]; + extendedKeyUsages?: string[]; }; export type TDeleteCertificateTemplateV2DTO = { diff --git a/frontend/src/hooks/api/certificates/index.tsx b/frontend/src/hooks/api/certificates/index.tsx index a60ebf91e..7d9c5df08 100644 --- a/frontend/src/hooks/api/certificates/index.tsx +++ b/frontend/src/hooks/api/certificates/index.tsx @@ -1,3 +1,4 @@ +export { CertStatus } from "./enums"; export { useDeleteCert, useImportCertificate, diff --git a/frontend/src/hooks/api/certificates/types.ts b/frontend/src/hooks/api/certificates/types.ts index 622276e24..adfb815a6 100644 --- a/frontend/src/hooks/api/certificates/types.ts +++ b/frontend/src/hooks/api/certificates/types.ts @@ -9,6 +9,7 @@ export type TCertificate = { friendlyName: string; commonName: string; subjectAltNames: string; + altNames?: string; serialNumber: string; notBefore: string; notAfter: string; diff --git a/frontend/src/hooks/api/index.tsx b/frontend/src/hooks/api/index.tsx index e9e80feec..0216ae030 100644 --- a/frontend/src/hooks/api/index.tsx +++ b/frontend/src/hooks/api/index.tsx @@ -28,6 +28,7 @@ export * from "./organization"; export * from "./pkiAlerts"; export * from "./pkiCollections"; export * from "./pkiSubscriber"; +export * from "./pkiSyncs"; export * from "./projects"; export * from "./projectUserAdditionalPrivilege"; export * from "./rateLimit"; diff --git a/frontend/src/hooks/api/pkiSubscriber/types.ts b/frontend/src/hooks/api/pkiSubscriber/types.ts index 23ca6baf3..239d56148 100644 --- a/frontend/src/hooks/api/pkiSubscriber/types.ts +++ b/frontend/src/hooks/api/pkiSubscriber/types.ts @@ -47,8 +47,8 @@ export type TCreatePkiSubscriberDTO = { commonName: string; ttl?: string; subjectAlternativeNames: string[]; - keyUsages: CertKeyUsage[]; - extendedKeyUsages: CertExtendedKeyUsage[]; + keyUsages: string[]; + extendedKeyUsages: string[]; enableAutoRenewal?: boolean; autoRenewalPeriodInDays?: number; properties?: TPkiSubscriberProperties; @@ -63,8 +63,8 @@ export type TUpdatePkiSubscriberDTO = { status?: PkiSubscriberStatus; ttl?: string; subjectAlternativeNames?: string[]; - keyUsages?: CertKeyUsage[]; - extendedKeyUsages?: CertExtendedKeyUsage[]; + keyUsages?: string[]; + extendedKeyUsages?: string[]; enableAutoRenewal?: boolean; autoRenewalPeriodInDays?: number; properties?: TPkiSubscriberProperties; diff --git a/frontend/src/hooks/api/pkiSyncs/enums.ts b/frontend/src/hooks/api/pkiSyncs/enums.ts index 507f3336c..014516230 100644 --- a/frontend/src/hooks/api/pkiSyncs/enums.ts +++ b/frontend/src/hooks/api/pkiSyncs/enums.ts @@ -9,3 +9,10 @@ export enum PkiSyncStatus { Succeeded = "succeeded", Failed = "failed" } + +export enum CertificateSyncStatus { + Pending = "pending", + Syncing = "syncing", + Succeeded = "succeeded", + Failed = "failed" +} diff --git a/frontend/src/hooks/api/pkiSyncs/mutations.tsx b/frontend/src/hooks/api/pkiSyncs/mutations.tsx index 30cf18690..9aff79dd0 100644 --- a/frontend/src/hooks/api/pkiSyncs/mutations.tsx +++ b/frontend/src/hooks/api/pkiSyncs/mutations.tsx @@ -198,3 +198,47 @@ export const useTriggerPkiSyncRemoveCertificates = () => { } }); }; + +export const useAddCertificatesToPkiSync = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ + pkiSyncId, + certificateIds + }: { + pkiSyncId: string; + certificateIds: string[]; + }) => { + const { data } = await apiRequest.post(`/api/v1/pki/syncs/${pkiSyncId}/certificates`, { + certificateIds + }); + + return data; + }, + onSuccess: (_, { pkiSyncId }) => { + queryClient.invalidateQueries({ queryKey: pkiSyncKeys.certificates(pkiSyncId) }); + } + }); +}; + +export const useRemoveCertificatesFromPkiSync = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async ({ + pkiSyncId, + certificateIds + }: { + pkiSyncId: string; + certificateIds: string[]; + }) => { + const { data } = await apiRequest.delete(`/api/v1/pki/syncs/${pkiSyncId}/certificates`, { + data: { certificateIds } + }); + + return data; + }, + onSuccess: (_, { pkiSyncId }) => { + queryClient.invalidateQueries({ queryKey: pkiSyncKeys.certificates(pkiSyncId) }); + } + }); +}; diff --git a/frontend/src/hooks/api/pkiSyncs/queries.tsx b/frontend/src/hooks/api/pkiSyncs/queries.tsx index d0db913bd..6e7aabc42 100644 --- a/frontend/src/hooks/api/pkiSyncs/queries.tsx +++ b/frontend/src/hooks/api/pkiSyncs/queries.tsx @@ -2,14 +2,25 @@ import { useQuery, UseQueryOptions } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; import { PkiSync, TPkiSyncOption } from "@app/hooks/api/pkiSyncs"; -import { TListPkiSyncOptions, TListPkiSyncs, TPkiSync } from "@app/hooks/api/pkiSyncs/types"; +import { + TListPkiSyncOptions, + TListPkiSyncs, + TPkiSync, + TPkiSyncCertificate +} from "@app/hooks/api/pkiSyncs/types"; export const pkiSyncKeys = { all: ["pki-sync"] as const, options: () => [...pkiSyncKeys.all, "options"] as const, list: (projectId: string) => [...pkiSyncKeys.all, "list", projectId] as const, + listWithCertificate: (projectId: string, certificateId: string) => + [...pkiSyncKeys.all, "list", projectId, "with-certificate", certificateId] as const, byId: (syncId: string, projectId: string) => - [...pkiSyncKeys.all, "by-id", syncId, projectId] as const + [...pkiSyncKeys.all, "by-id", syncId, projectId] as const, + certificates: (syncId: string, pagination?: { offset: number; limit: number }) => + pagination + ? ([...pkiSyncKeys.all, "certificates", syncId, pagination] as const) + : ([...pkiSyncKeys.all, "certificates", syncId] as const) }; export const usePkiSyncOptions = ( @@ -41,9 +52,14 @@ export const usePkiSyncOption = (destination: PkiSync) => { return { syncOption, isPending }; }; -export const fetchPkiSyncsByProjectId = async (projectId: string) => { +export const fetchPkiSyncsByProjectId = async (projectId: string, certificateId?: string) => { + const params: { projectId: string; certificateId?: string } = { projectId }; + if (certificateId) { + params.certificateId = certificateId; + } + const { data } = await apiRequest.get("/api/v1/pki/syncs", { - params: { projectId } + params }); return data.pkiSyncs; @@ -63,6 +79,27 @@ export const useListPkiSyncs = ( }); }; +export const useListPkiSyncsWithCertificate = ( + projectId: string, + certificateId: string, + options?: Omit< + UseQueryOptions< + TPkiSync[], + unknown, + TPkiSync[], + ReturnType + >, + "queryKey" | "queryFn" + > +) => { + return useQuery({ + queryKey: pkiSyncKeys.listWithCertificate(projectId, certificateId), + queryFn: () => fetchPkiSyncsByProjectId(projectId, certificateId), + enabled: !!projectId && !!certificateId, + ...options + }); +}; + export const useGetPkiSync = ( { syncId, projectId }: { syncId: string; projectId: string }, options?: Omit< @@ -82,3 +119,33 @@ export const useGetPkiSync = ( ...options }); }; + +export const useListPkiSyncCertificates = ( + syncId: string, + pagination?: { offset?: number; limit?: number }, + options?: Omit< + UseQueryOptions< + { certificates: TPkiSyncCertificate[]; totalCount: number }, + unknown, + { certificates: TPkiSyncCertificate[]; totalCount: number }, + ReturnType + >, + "queryKey" | "queryFn" + > +) => { + const { offset = 0, limit = 20 } = pagination || {}; + + return useQuery({ + queryKey: pkiSyncKeys.certificates(syncId, { offset, limit }), + queryFn: async () => { + const { data } = await apiRequest.get(`/api/v1/pki/syncs/${syncId}/certificates`, { + params: { offset, limit } + }); + return { + certificates: data.certificates || [], + totalCount: data.totalCount || 0 + }; + }, + ...options + }); +}; diff --git a/frontend/src/hooks/api/pkiSyncs/types/common.ts b/frontend/src/hooks/api/pkiSyncs/types/common.ts index 8cd1aef96..77dc785ee 100644 --- a/frontend/src/hooks/api/pkiSyncs/types/common.ts +++ b/frontend/src/hooks/api/pkiSyncs/types/common.ts @@ -1,6 +1,6 @@ import { AppConnection } from "@app/hooks/api/appConnections/enums"; -import { PkiSyncStatus } from "../enums"; +import { CertificateSyncStatus, PkiSyncStatus } from "../enums"; export type RootPkiSyncOptions = { canImportCertificates: boolean; @@ -43,4 +43,26 @@ export type TRootPkiSync = { } | null; appConnectionName?: string; appConnectionApp?: string; + hasCertificate?: boolean; +}; + +export type TPkiSyncCertificate = { + id: string; + pkiSyncId: string; + certificateId: string; + syncStatus?: CertificateSyncStatus | null; + lastSyncMessage?: string | null; + lastSyncedAt?: string | null; + createdAt: string; + updatedAt: string; + certificateSerialNumber?: string; + certificateCommonName?: string; + certificateAltNames?: string; + certificateStatus?: string; + certificateNotBefore?: Date; + certificateNotAfter?: Date; + certificateRenewBeforeDays?: number; + certificateRenewalError?: string; + pkiSyncName?: string; + pkiSyncDestination?: string; }; diff --git a/frontend/src/hooks/api/pkiSyncs/types/index.ts b/frontend/src/hooks/api/pkiSyncs/types/index.ts index 899217f52..69ec3ae6f 100644 --- a/frontend/src/hooks/api/pkiSyncs/types/index.ts +++ b/frontend/src/hooks/api/pkiSyncs/types/index.ts @@ -33,7 +33,8 @@ type TCreatePkiSyncDTOBase = { certificateNameSchema?: string; }; isAutoSyncEnabled: boolean; - subscriberId?: string; + subscriberId?: string | null; + certificateIds?: string[]; projectId: string; }; diff --git a/frontend/src/hooks/api/projects/queries.tsx b/frontend/src/hooks/api/projects/queries.tsx index 0445d5984..1613e95a9 100644 --- a/frontend/src/hooks/api/projects/queries.tsx +++ b/frontend/src/hooks/api/projects/queries.tsx @@ -664,17 +664,26 @@ export const useListWorkspaceCas = ({ export const useListWorkspaceCertificates = ({ projectId, offset, - limit + limit, + friendlyName, + commonName, + forPkiSync }: { projectId: string; offset: number; limit: number; + friendlyName?: string; + commonName?: string; + forPkiSync?: boolean; }) => { return useQuery({ queryKey: projectKeys.specificProjectCertificates({ projectId, offset, - limit + limit, + friendlyName, + commonName, + forPkiSync }), queryFn: async () => { const params = new URLSearchParams({ @@ -682,6 +691,16 @@ export const useListWorkspaceCertificates = ({ limit: String(limit) }); + if (friendlyName) { + params.append("friendlyName", friendlyName); + } + if (commonName) { + params.append("commonName", commonName); + } + if (forPkiSync) { + params.append("forPkiSync", "true"); + } + const { data: { certificates, totalCount } } = await apiRequest.get<{ certificates: TCertificate[]; totalCount: number }>( @@ -693,7 +712,8 @@ export const useListWorkspaceCertificates = ({ return { certificates, totalCount }; }, - enabled: Boolean(projectId) + enabled: Boolean(projectId), + placeholderData: (previousData) => previousData }); }; diff --git a/frontend/src/hooks/api/projects/query-keys.tsx b/frontend/src/hooks/api/projects/query-keys.tsx index 51e3db71c..04f14f90d 100644 --- a/frontend/src/hooks/api/projects/query-keys.tsx +++ b/frontend/src/hooks/api/projects/query-keys.tsx @@ -39,12 +39,22 @@ export const projectKeys = { specificProjectCertificates: ({ projectId, offset, - limit + limit, + friendlyName, + commonName, + forPkiSync }: { projectId: string; offset: number; limit: number; - }) => [...projectKeys.forProjectCertificates(projectId), { offset, limit }] as const, + friendlyName?: string; + commonName?: string; + forPkiSync?: boolean; + }) => + [ + ...projectKeys.forProjectCertificates(projectId), + { offset, limit, friendlyName, commonName, forPkiSync } + ] as const, getProjectPkiAlerts: (projectId: string) => [{ projectId }, "project-pki-alerts"] as const, getProjectPkiSubscribers: (projectId: string) => [{ projectId }, "project-pki-subscribers"] as const, diff --git a/frontend/src/hooks/api/secretSyncs/enums.ts b/frontend/src/hooks/api/secretSyncs/enums.ts index 149759d33..be51805c8 100644 --- a/frontend/src/hooks/api/secretSyncs/enums.ts +++ b/frontend/src/hooks/api/secretSyncs/enums.ts @@ -30,7 +30,8 @@ export enum SecretSync { Netlify = "netlify", Northflank = "northflank", Bitbucket = "bitbucket", - LaravelForge = "laravel-forge" + LaravelForge = "laravel-forge", + Chef = "chef" } export enum SecretSyncStatus { diff --git a/frontend/src/hooks/api/secretSyncs/types/chef-sync.ts b/frontend/src/hooks/api/secretSyncs/types/chef-sync.ts new file mode 100644 index 000000000..6bd3d7ca2 --- /dev/null +++ b/frontend/src/hooks/api/secretSyncs/types/chef-sync.ts @@ -0,0 +1,16 @@ +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"; + +export type TChefSync = TRootSecretSync & { + destination: SecretSync.Chef; + destinationConfig: { + dataBagName: string; + dataBagItemName: string; + }; + connection: { + app: AppConnection.Chef; + name: string; + id: string; + }; +}; diff --git a/frontend/src/hooks/api/secretSyncs/types/index.ts b/frontend/src/hooks/api/secretSyncs/types/index.ts index bd2e5af4f..b4088728b 100644 --- a/frontend/src/hooks/api/secretSyncs/types/index.ts +++ b/frontend/src/hooks/api/secretSyncs/types/index.ts @@ -10,6 +10,7 @@ import { TAzureKeyVaultSync } from "./azure-key-vault-sync"; import { TBitbucketSync } from "./bitbucket-sync"; import { TCamundaSync } from "./camunda-sync"; import { TChecklySync } from "./checkly-sync"; +import { TChefSync } from "./chef-sync"; import { TCloudflarePagesSync } from "./cloudflare-pages-sync"; import { TCloudflareWorkersSync } from "./cloudflare-workers-sync"; import { TDatabricksSync } from "./databricks-sync"; @@ -73,7 +74,8 @@ export type TSecretSync = | TNetlifySync | TNorthflankSync | TBitbucketSync - | TLaravelForgeSync; + | TLaravelForgeSync + | TChefSync; export type TListSecretSyncs = { secretSyncs: TSecretSync[] }; diff --git a/frontend/src/layouts/OrganizationLayout/components/NavBar/NewSubOrganizationForm.tsx b/frontend/src/layouts/OrganizationLayout/components/NavBar/NewSubOrganizationForm.tsx index 75041a69e..bfea2e788 100644 --- a/frontend/src/layouts/OrganizationLayout/components/NavBar/NewSubOrganizationForm.tsx +++ b/frontend/src/layouts/OrganizationLayout/components/NavBar/NewSubOrganizationForm.tsx @@ -36,28 +36,21 @@ export const NewSubOrganizationForm = ({ onClose }: ContentProps) => { const router = useRouter(); const onSubmit = async ({ name }: FormData) => { - try { - const { organization } = await createSubOrg.mutateAsync({ - name - }); + const { organization } = await createSubOrg.mutateAsync({ + name + }); - createNotification({ - type: "success", - text: "Successfully created sub organization" - }); - onClose(); + createNotification({ + type: "success", + text: "Successfully created sub organization" + }); + onClose(); - navigate({ - to: "/organization/projects", - search: (prev) => ({ ...prev, subOrganization: organization.name }) - }); - await router.invalidate({ sync: true }).catch(() => null); - } catch { - createNotification({ - text: "Failed to create sub organization", - type: "error" - }); - } + navigate({ + to: "/organization/projects", + search: (prev) => ({ ...prev, subOrganization: organization.name }) + }); + await router.invalidate({ sync: true }).catch(() => null); }; return ( diff --git a/frontend/src/layouts/PamLayout/PamLayout.tsx b/frontend/src/layouts/PamLayout/PamLayout.tsx index 5b74703dc..c6e7e067a 100644 --- a/frontend/src/layouts/PamLayout/PamLayout.tsx +++ b/frontend/src/layouts/PamLayout/PamLayout.tsx @@ -19,7 +19,8 @@ export const PamLayout = () => { useEffect(() => { if (subscription && !subscription.pam) { handlePopUpOpen("upgradePlan", { - description: "You can use PAM if you switch to Infisical's Enterprise plan.", + description: + "Your current plan does not provide access to Infisical PAM. To unlock this feature, please upgrade to Infisical Enterprise plan.", isEnterpriseFeature: true }); } diff --git a/frontend/src/layouts/PkiManagerLayout/PkiManagerLayout.tsx b/frontend/src/layouts/PkiManagerLayout/PkiManagerLayout.tsx index 31c7aecf2..a2e4871de 100644 --- a/frontend/src/layouts/PkiManagerLayout/PkiManagerLayout.tsx +++ b/frontend/src/layouts/PkiManagerLayout/PkiManagerLayout.tsx @@ -52,25 +52,7 @@ export const PkiManagerLayout = () => { projectId: currentProject.id }} > - {({ isActive }) => Policies} - - - {({ isActive }) => ( - - Certificates - - )} + {({ isActive }) => Certificates} { const { mutateAsync: updateUserProjectFavorites } = useUpdateUserProjectFavorites(); const addProjectToFavorites = async (projectId: string) => { - try { - await updateUserProjectFavorites({ - orgId: currentOrg!.id, - projectFavorites: [...(projectFavorites || []), projectId] - }); - } catch { - createNotification({ - text: "Failed to add project to favorites.", - type: "error" - }); - } + await updateUserProjectFavorites({ + orgId: currentOrg!.id, + projectFavorites: [...(projectFavorites || []), projectId] + }); }; const removeProjectFromFavorites = async (projectId: string) => { - try { - await updateUserProjectFavorites({ - orgId: currentOrg!.id, - projectFavorites: [...(projectFavorites || []).filter((entry) => entry !== projectId)] - }); - } catch { - createNotification({ - text: "Failed to remove project from favorites.", - type: "error" - }); - } + await updateUserProjectFavorites({ + orgId: currentOrg!.id, + projectFavorites: [...(projectFavorites || []).filter((entry) => entry !== projectId)] + }); }; const isAddingProjectsAllowed = subscription?.workspaceLimit @@ -240,7 +225,7 @@ export const ProjectSelect = () => { handlePopUpToggle("upgradePlan", isOpen)} - text="You have exceeded the number of projects allowed on the free plan." + text="You’ve reached the maximum number of projects available on the Free plan. Upgrade to the Infisical Pro plan to create more projects." /> { const users = usersData.filter((user) => !user.superAdmin); const onSubmit = async ({ user }: FormData) => { - try { - await grantAdmin.mutateAsync(user.id); + await grantAdmin.mutateAsync(user.id); - createNotification({ - type: "success", - text: "Successfully granted server admin status" - }); - onClose(); - } catch { - createNotification({ - text: "Failed to grant server admin status", - type: "error" - }); - } + createNotification({ + type: "success", + text: "Successfully granted server admin status" + }); + onClose(); }; return ( diff --git a/frontend/src/pages/admin/AccessManagementPage/components/ServerAdminsTable.tsx b/frontend/src/pages/admin/AccessManagementPage/components/ServerAdminsTable.tsx index 7e207fbc5..58516be39 100644 --- a/frontend/src/pages/admin/AccessManagementPage/components/ServerAdminsTable.tsx +++ b/frontend/src/pages/admin/AccessManagementPage/components/ServerAdminsTable.tsx @@ -303,18 +303,11 @@ export const ServerAdminsTable = () => { const handleRemoveUser = async () => { const { id } = popUp?.removeUser?.data as { id: string; username: string }; - try { - await deleteUser(id); - createNotification({ - type: "success", - text: "Successfully deleted user" - }); - } catch { - createNotification({ - type: "error", - text: "Error deleting user" - }); - } + await deleteUser(id); + createNotification({ + type: "success", + text: "Successfully deleted user" + }); handlePopUpClose("removeUser"); }; @@ -322,39 +315,25 @@ export const ServerAdminsTable = () => { const handleRemoveServerAdminAccess = async () => { const { id } = popUp?.removeServerAdmin?.data as { id: string; username: string }; - try { - await removeAdminAccess(id); - createNotification({ - type: "success", - text: "Successfully removed server admin access from user" - }); - } catch { - createNotification({ - type: "error", - text: "Error removing server admin access from user" - }); - } + await removeAdminAccess(id); + createNotification({ + type: "success", + text: "Successfully removed server admin access from user" + }); handlePopUpClose("removeServerAdmin"); }; const handleRemoveUsers = async () => { - try { - await deleteUsers(selectedUsers.map((user) => user.id)); + await deleteUsers(selectedUsers.map((user) => user.id)); - createNotification({ - text: "Successfully removed users", - type: "success" - }); + createNotification({ + text: "Successfully removed users", + type: "success" + }); - setSelectedUsers([]); - handlePopUpClose("removeUsers"); - } catch { - createNotification({ - text: "Failed to remove users", - type: "error" - }); - } + setSelectedUsers([]); + handlePopUpClose("removeUsers"); }; return ( @@ -432,7 +411,7 @@ export const ServerAdminsTable = () => { handlePopUpToggle("upgradePlan", isOpen)} - text={`${popUp?.upgradePlan?.data?.message} is only available on Infisical's Pro plan and above.`} + text="Your current plan does not allow removing server admins. To unlock this feature, please upgrade to Infisical Pro plan." /> { }); const onAuthFormSubmit = async (formData: TAuthForm) => { - try { - const enabledMethods: LoginMethod[] = []; - if (formData.isEmailEnabled) { - enabledMethods.push(LoginMethod.EMAIL); - } + const enabledMethods: LoginMethod[] = []; + if (formData.isEmailEnabled) { + enabledMethods.push(LoginMethod.EMAIL); + } - if (formData.isGoogleEnabled) { - enabledMethods.push(LoginMethod.GOOGLE); - } + if (formData.isGoogleEnabled) { + enabledMethods.push(LoginMethod.GOOGLE); + } - if (formData.isGithubEnabled) { - enabledMethods.push(LoginMethod.GITHUB); - } + if (formData.isGithubEnabled) { + enabledMethods.push(LoginMethod.GITHUB); + } - if (formData.isGitlabEnabled) { - enabledMethods.push(LoginMethod.GITLAB); - } + if (formData.isGitlabEnabled) { + enabledMethods.push(LoginMethod.GITLAB); + } - if (formData.isSamlEnabled) { - enabledMethods.push(LoginMethod.SAML); - } + if (formData.isSamlEnabled) { + enabledMethods.push(LoginMethod.SAML); + } - if (formData.isLdapEnabled) { - enabledMethods.push(LoginMethod.LDAP); - } + if (formData.isLdapEnabled) { + enabledMethods.push(LoginMethod.LDAP); + } - if (formData.isOidcEnabled) { - enabledMethods.push(LoginMethod.OIDC); - } + if (formData.isOidcEnabled) { + enabledMethods.push(LoginMethod.OIDC); + } - if (!enabledMethods.length) { - createNotification({ - type: "error", - text: "At least one login method should be enabled." - }); - return; - } - - await updateServerConfig({ - enabledLoginMethods: enabledMethods - }); - - createNotification({ - text: "Login methods have been successfully updated.", - type: "success" - }); - } catch (e) { - console.error(e); + if (!enabledMethods.length) { createNotification({ type: "error", - text: "Failed to update login methods." + text: "At least one login method should be enabled." }); + return; } + + await updateServerConfig({ + enabledLoginMethods: enabledMethods + }); + + createNotification({ + text: "Login methods have been successfully updated.", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/admin/CachingPage/components/CachingPageForm.tsx b/frontend/src/pages/admin/CachingPage/components/CachingPageForm.tsx index fbf956b7b..e9bdc3d1e 100644 --- a/frontend/src/pages/admin/CachingPage/components/CachingPageForm.tsx +++ b/frontend/src/pages/admin/CachingPage/components/CachingPageForm.tsx @@ -31,15 +31,10 @@ export const CachingPageForm = () => { const handleInvalidateCacheSubmit = async () => { if (!type || isInvalidating) return; - try { - await invalidateCache({ type }); - createNotification({ text: `Began invalidating ${type} cache`, type: "success" }); - setShouldPoll(true); - handlePopUpClose("invalidateCache"); - } catch (err) { - console.error(err); - createNotification({ text: `Failed to invalidate ${type} cache`, type: "error" }); - } + await invalidateCache({ type }); + createNotification({ text: `Began invalidating ${type} cache`, type: "success" }); + setShouldPoll(true); + handlePopUpClose("invalidateCache"); }; useEffect(() => { diff --git a/frontend/src/pages/admin/EncryptionPage/components/EncryptionPageForm.tsx b/frontend/src/pages/admin/EncryptionPage/components/EncryptionPageForm.tsx index 8372dd557..5121face1 100644 --- a/frontend/src/pages/admin/EncryptionPage/components/EncryptionPageForm.tsx +++ b/frontend/src/pages/admin/EncryptionPage/components/EncryptionPageForm.tsx @@ -55,24 +55,17 @@ export const EncryptionPageForm = () => { if (!subscription.hsm) { handlePopUpOpen("upgradePlan", { isEnterpriseFeature: true, - description: "Hardware Security Module's (HSM's), are only available on Enterprise plans." + text: "Your current plan does not include access to Hardware Security Module (HSM). To unlock this feature, please upgrade to Infisical Enterprise plan." }); return; } - try { - await updateEncryptionStrategy(formData.encryptionStrategy); + await updateEncryptionStrategy(formData.encryptionStrategy); - createNotification({ - type: "success", - text: "Encryption strategy updated successfully" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update encryption strategy" - }); - } + createNotification({ + type: "success", + text: "Encryption strategy updated successfully" + }); }, []); return ( @@ -144,7 +137,7 @@ export const EncryptionPageForm = () => { handlePopUpToggle("upgradePlan", isOpen)} - text={(popUp.upgradePlan?.data as { description: string })?.description} + text={popUp.upgradePlan?.data?.text} isEnterpriseFeature={popUp.upgradePlan?.data?.isEnterpriseFeature} /> diff --git a/frontend/src/pages/admin/EnvironmentPage/components/EnvironmentPageForm.tsx b/frontend/src/pages/admin/EnvironmentPage/components/EnvironmentPageForm.tsx index 5f74593a3..8361aa944 100644 --- a/frontend/src/pages/admin/EnvironmentPage/components/EnvironmentPageForm.tsx +++ b/frontend/src/pages/admin/EnvironmentPage/components/EnvironmentPageForm.tsx @@ -176,31 +176,19 @@ export const EnvironmentPageForm = () => { const onSubmit = useCallback( async (formData: TForm) => { - try { - const filteredFormData = Object.fromEntries( - Object.entries(formData).filter(([, value]) => value !== "") - ); - await updateServerConfig({ - envOverrides: filteredFormData - }); + const filteredFormData = Object.fromEntries( + Object.entries(formData).filter(([, value]) => value !== "") + ); + await updateServerConfig({ + envOverrides: filteredFormData + }); - createNotification({ - type: "success", - text: "Environment overrides updated successfully. It can take up to 5 minutes to take effect." - }); + createNotification({ + type: "success", + text: "Environment overrides updated successfully. It can take up to 5 minutes to take effect." + }); - reset(formData); - } catch (error) { - const errorMessage = - (error as any)?.response?.data?.message || - (error as any)?.message || - "An unknown error occurred"; - createNotification({ - type: "error", - title: "Failed to update environment overrides", - text: errorMessage - }); - } + reset(formData); }, [reset, updateServerConfig] ); diff --git a/frontend/src/pages/admin/GeneralPage/components/GeneralPageForm.tsx b/frontend/src/pages/admin/GeneralPage/components/GeneralPageForm.tsx index be92dd679..eff3e3cf8 100644 --- a/frontend/src/pages/admin/GeneralPage/components/GeneralPageForm.tsx +++ b/frontend/src/pages/admin/GeneralPage/components/GeneralPageForm.tsx @@ -68,37 +68,29 @@ export const GeneralPageForm = () => { const organizations = useGetOrganizations(); const onFormSubmit = async (formData: TDashboardForm) => { - try { - const { - allowedSignUpDomain, - trustSamlEmails, - trustLdapEmails, - trustOidcEmails, - authConsentContent, - pageFrameContent - } = formData; + const { + allowedSignUpDomain, + trustSamlEmails, + trustLdapEmails, + trustOidcEmails, + authConsentContent, + pageFrameContent + } = formData; - await updateServerConfig({ - defaultAuthOrgId: defaultAuthOrgId || null, - allowSignUp: signUpMode !== SignUpModes.Disabled, - allowedSignUpDomain: signUpMode === SignUpModes.Anyone ? allowedSignUpDomain : null, - trustSamlEmails, - trustLdapEmails, - trustOidcEmails, - authConsentContent, - pageFrameContent - }); - createNotification({ - text: "Successfully changed sign up setting.", - type: "success" - }); - } catch (e) { - console.error(e); - createNotification({ - type: "error", - text: "Failed to update sign up setting." - }); - } + await updateServerConfig({ + defaultAuthOrgId: defaultAuthOrgId || null, + allowSignUp: signUpMode !== SignUpModes.Disabled, + allowedSignUpDomain: signUpMode === SignUpModes.Anyone ? allowedSignUpDomain : null, + trustSamlEmails, + trustLdapEmails, + trustOidcEmails, + authConsentContent, + pageFrameContent + }); + createNotification({ + text: "Successfully changed sign up setting.", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/admin/GeneralPage/components/UsageReportSection.tsx b/frontend/src/pages/admin/GeneralPage/components/UsageReportSection.tsx index d05800996..663aa18b7 100644 --- a/frontend/src/pages/admin/GeneralPage/components/UsageReportSection.tsx +++ b/frontend/src/pages/admin/GeneralPage/components/UsageReportSection.tsx @@ -10,23 +10,15 @@ export const UsageReportSection = () => { const generateUsageReport = useGenerateUsageReport(); const handleGenerateReport = async () => { - try { - const response = await generateUsageReport.mutateAsync(); - const { csvContent, filename } = response; + const response = await generateUsageReport.mutateAsync(); + const { csvContent, filename } = response; - downloadFile(csvContent, filename, "text/csv"); + downloadFile(csvContent, filename, "text/csv"); - createNotification({ - text: `Usage report downloaded: "${filename}"`, - type: "success" - }); - } catch (error) { - console.error("Failed to generate usage report:", error); - createNotification({ - text: "Failed to generate usage report. Please try again.", - type: "error" - }); - } + createNotification({ + text: `Usage report downloaded: "${filename}"`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/admin/ResourceOverviewPage/components/AddOrganizationModal.tsx b/frontend/src/pages/admin/ResourceOverviewPage/components/AddOrganizationModal.tsx index d577998b5..48983496f 100644 --- a/frontend/src/pages/admin/ResourceOverviewPage/components/AddOrganizationModal.tsx +++ b/frontend/src/pages/admin/ResourceOverviewPage/components/AddOrganizationModal.tsx @@ -81,25 +81,18 @@ const Content = ({ onClose }: ContentProps) => { const { users = [] } = data ?? {}; const onSubmit = async ({ name, invitees }: FormData) => { - try { - await createOrg.mutateAsync({ - name, - inviteAdminEmails: invitees - .filter((user) => Boolean(user.email)) - .map((user) => user.email) as string[] - }); + await createOrg.mutateAsync({ + name, + inviteAdminEmails: invitees + .filter((user) => Boolean(user.email)) + .map((user) => user.email) as string[] + }); - createNotification({ - type: "success", - text: "Successfully created organization" - }); - onClose(); - } catch { - createNotification({ - text: "Failed to create organization", - type: "error" - }); - } + createNotification({ + type: "success", + text: "Successfully created organization" + }); + onClose(); }; const { append } = useFieldArray({ control, name: "invitees" }); diff --git a/frontend/src/pages/admin/ResourceOverviewPage/components/MachineIdentitiesTable.tsx b/frontend/src/pages/admin/ResourceOverviewPage/components/MachineIdentitiesTable.tsx index 9929d7500..d17786712 100644 --- a/frontend/src/pages/admin/ResourceOverviewPage/components/MachineIdentitiesTable.tsx +++ b/frontend/src/pages/admin/ResourceOverviewPage/components/MachineIdentitiesTable.tsx @@ -185,18 +185,11 @@ export const MachineIdentitiesTable = () => { const handleRemoveServerAdmin = async () => { const { id } = popUp?.removeServerAdmin?.data as { id: string; name: string }; - try { - await deleteIdentitySuperAdminAccess(id); - createNotification({ - type: "success", - text: "Successfully removed server admin permissions" - }); - } catch { - createNotification({ - type: "error", - text: "Error removing server admin permissions" - }); - } + await deleteIdentitySuperAdminAccess(id); + createNotification({ + type: "success", + text: "Successfully removed server admin permissions" + }); handlePopUpClose("removeServerAdmin"); }; diff --git a/frontend/src/pages/admin/ResourceOverviewPage/components/OrganizationsTable.tsx b/frontend/src/pages/admin/ResourceOverviewPage/components/OrganizationsTable.tsx index fbb9462d0..93d0253ff 100644 --- a/frontend/src/pages/admin/ResourceOverviewPage/components/OrganizationsTable.tsx +++ b/frontend/src/pages/admin/ResourceOverviewPage/components/OrganizationsTable.tsx @@ -179,12 +179,6 @@ const ViewMembersModalContent = ({ text: "Successfully resent org invitation", type: "success" }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to resend org invitation", - type: "error" - }); } finally { setResendInviteId(null); } @@ -479,26 +473,19 @@ const OrganizationsPanelTable = ({ const { mutateAsync: accessOrganization } = useServerAdminAccessOrg(); const handleAccessOrg = async (orgId: string) => { - try { - await accessOrganization(orgId); + await accessOrganization(orgId); - navigate({ - to: "/login/select-organization", - search: { - org_id: orgId - } - }); + navigate({ + to: "/login/select-organization", + search: { + org_id: orgId + } + }); - createNotification({ - text: "Successfully joined organization", - type: "success" - }); - } catch { - createNotification({ - text: "Failed to join organization", - type: "error" - }); - } + createNotification({ + text: "Successfully joined organization", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/admin/ResourceOverviewPage/components/UserIdentitiesTable.tsx b/frontend/src/pages/admin/ResourceOverviewPage/components/UserIdentitiesTable.tsx index 05d07d426..c343a58eb 100644 --- a/frontend/src/pages/admin/ResourceOverviewPage/components/UserIdentitiesTable.tsx +++ b/frontend/src/pages/admin/ResourceOverviewPage/components/UserIdentitiesTable.tsx @@ -58,9 +58,6 @@ import { import { User } from "@app/hooks/api/users/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; -const addServerAdminUpgradePlanMessage = "Granting another user Server Admin permissions"; -const removeServerAdminUpgradePlanMessage = "Removing Server Admin permissions from user"; - const UserPanelTable = ({ handlePopUpOpen, users, @@ -84,7 +81,7 @@ const UserPanelTable = ({ data?: { username: string; id: string; - message?: string; + text?: string; } ) => void; isPending: boolean; @@ -260,7 +257,7 @@ const UserPanelTable = ({ handlePopUpOpen("upgradePlan", { username, id, - message: addServerAdminUpgradePlanMessage + text: "Your current plan does not allow setting additional server admins. To unlock this feature, please upgrade to Infisical Pro plan." }); return; } @@ -288,7 +285,7 @@ const UserPanelTable = ({ handlePopUpOpen("upgradePlan", { username, id, - message: removeServerAdminUpgradePlanMessage + text: "Your current plan does not allow removing server admins. To unlock this feature, please upgrade to Infisical Pro plan." }); return; } @@ -367,18 +364,11 @@ export const UserIdentitiesTable = () => { const handleRemoveUser = async () => { const { id } = popUp?.removeUser?.data as { id: string; username: string }; - try { - await deleteUser(id); - createNotification({ - type: "success", - text: "Successfully deleted user" - }); - } catch { - createNotification({ - type: "error", - text: "Error deleting user" - }); - } + await deleteUser(id); + createNotification({ + type: "success", + text: "Successfully deleted user" + }); handlePopUpClose("removeUser"); }; @@ -386,18 +376,11 @@ export const UserIdentitiesTable = () => { const handleGrantServerAdminAccess = async () => { const { id } = popUp?.upgradeToServerAdmin?.data as { id: string; username: string }; - try { - await grantAdminAccess(id); - createNotification({ - type: "success", - text: "Successfully granted server admin access to user" - }); - } catch { - createNotification({ - type: "error", - text: "Error granting server admin access to user" - }); - } + await grantAdminAccess(id); + createNotification({ + type: "success", + text: "Successfully granted server admin access to user" + }); handlePopUpClose("upgradeToServerAdmin"); }; @@ -405,39 +388,25 @@ export const UserIdentitiesTable = () => { const handleRemoveServerAdminAccess = async () => { const { id } = popUp?.removeServerAdmin?.data as { id: string; username: string }; - try { - await removeAdminAccess(id); - createNotification({ - type: "success", - text: "Successfully removed server admin access from user" - }); - } catch { - createNotification({ - type: "error", - text: "Error removing server admin access from user" - }); - } + await removeAdminAccess(id); + createNotification({ + type: "success", + text: "Successfully removed server admin access from user" + }); handlePopUpClose("removeServerAdmin"); }; const handleRemoveUsers = async () => { - try { - await deleteUsers(selectedUsers.map((user) => user.id)); + await deleteUsers(selectedUsers.map((user) => user.id)); - createNotification({ - text: "Successfully removed users", - type: "success" - }); + createNotification({ + text: "Successfully removed users", + type: "success" + }); - setSelectedUsers([]); - handlePopUpClose("removeUsers"); - } catch { - createNotification({ - text: "Failed to remove users", - type: "error" - }); - } + setSelectedUsers([]); + handlePopUpClose("removeUsers"); }; return ( @@ -530,7 +499,7 @@ export const UserIdentitiesTable = () => { handlePopUpToggle("upgradePlan", isOpen)} - text={`${popUp?.upgradePlan?.data?.message} is only available on Infisical's Pro plan and above.`} + text={popUp.upgradePlan.data?.text} /> { const handleFormSubmit = async ({ email, password, firstName, lastName }: TFormSchema) => { // avoid multi submission if (isSubmitting) return; - try { - const res = await createAdminUser({ - email, - password, - firstName, - lastName - }); + const res = await createAdminUser({ + email, + password, + firstName, + lastName + }); - SecurityClient.setToken(res.token); - await selectOrganization({ organizationId: res.organization.id }); + SecurityClient.setToken(res.token); + await selectOrganization({ organizationId: res.organization.id }); - // TODO(akhilmhdh): This is such a confusing pattern and too unreliable - // Will be refactored in next iteration to make it url based rather than local storage ones - // Part of migration to nextjs 14 - localStorage.setItem("orgData.id", res.organization.id); - navigate({ to: "/admin" }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to create admin" - }); - } + // TODO(akhilmhdh): This is such a confusing pattern and too unreliable + // Will be refactored in next iteration to make it url based rather than local storage ones + // Part of migration to nextjs 14 + localStorage.setItem("orgData.id", res.organization.id); + navigate({ to: "/admin" }); }; if (config?.initialized) return ; diff --git a/frontend/src/pages/auth/PasswordSetupPage/PasswordSetupPage.tsx b/frontend/src/pages/auth/PasswordSetupPage/PasswordSetupPage.tsx index a1dad3cc5..9626f4cc5 100644 --- a/frontend/src/pages/auth/PasswordSetupPage/PasswordSetupPage.tsx +++ b/frontend/src/pages/auth/PasswordSetupPage/PasswordSetupPage.tsx @@ -75,11 +75,7 @@ export const PasswordSetupPage = () => { setTimeout(() => { window.location.href = "/login"; }, 3000); - } catch (error) { - createNotification({ - type: "error", - text: (error as Error).message ?? "Error setting password" - }); + } catch { navigate({ to: "/personal-settings" }); } } diff --git a/frontend/src/pages/auth/SignUpSsoPage/components/EmailConfirmationStep/EmailConfirmationStep.tsx b/frontend/src/pages/auth/SignUpSsoPage/components/EmailConfirmationStep/EmailConfirmationStep.tsx index efa498749..2240fe263 100644 --- a/frontend/src/pages/auth/SignUpSsoPage/components/EmailConfirmationStep/EmailConfirmationStep.tsx +++ b/frontend/src/pages/auth/SignUpSsoPage/components/EmailConfirmationStep/EmailConfirmationStep.tsx @@ -73,67 +73,53 @@ export const EmailConfirmationStep = ({ const { mutateAsync: verifyEmailVerificationCode } = useVerifyEmailVerificationCode(); const checkCode = async () => { - try { - await verifyEmailVerificationCode({ username, code }); - setCodeError(false); + await verifyEmailVerificationCode({ username, code }); + setCodeError(false); - createNotification({ - text: "Successfully verified code", - type: "success" - }); + createNotification({ + text: "Successfully verified code", + type: "success" + }); - switch (authType) { - case UserAliasType.SAML: { - window.open(`/api/v1/sso/redirect/saml2/organizations/${organizationSlug}`); - window.close(); - break; - } - case UserAliasType.LDAP: { - navigate({ to: "/login/ldap", search: { organizationSlug } }); - break; - } - case UserAliasType.OIDC: { - window.open(`/api/v1/sso/oidc/login?orgSlug=${organizationSlug}`); - window.close(); - break; - } - default: { - setStep(1); - break; - } + switch (authType) { + case UserAliasType.SAML: { + window.open(`/api/v1/sso/redirect/saml2/organizations/${organizationSlug}`); + window.close(); + break; + } + case UserAliasType.LDAP: { + navigate({ to: "/login/ldap", search: { organizationSlug } }); + break; + } + case UserAliasType.OIDC: { + window.open(`/api/v1/sso/oidc/login?orgSlug=${organizationSlug}`); + window.close(); + break; + } + default: { + setStep(1); + break; } - } catch { - createNotification({ - text: "Failed to verify code", - type: "error" - }); } setCode(""); }; const resendCode = async () => { - try { - const queryParams = new URLSearchParams(window.location.search); - const token = queryParams.get("token"); - if (!token) { - createNotification({ - text: "Failed to resend code, no token found", - type: "error" - }); - return; - } - await sendEmailVerificationCode(token); + const queryParams = new URLSearchParams(window.location.search); + const token = queryParams.get("token"); + if (!token) { createNotification({ - text: "Successfully resent code", - type: "success" - }); - } catch { - createNotification({ - text: "Failed to resend code", + text: "Failed to resend code, no token found", type: "error" }); + return; } + await sendEmailVerificationCode(token); + createNotification({ + text: "Successfully resent code", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertModal.tsx b/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertModal.tsx index 5601e48db..af5bcb7ae 100644 --- a/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertModal.tsx +++ b/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertModal.tsx @@ -113,52 +113,44 @@ export const PkiAlertModal = ({ popUp, handlePopUpToggle }: Props) => { alertUnit, emails }: FormData) => { - try { - if (!projectId) return; + if (!projectId) return; - const emailArray = emails - .split(",") - .map((email) => email.trim()) - .filter((email) => email.length > 0); + const emailArray = emails + .split(",") + .map((email) => email.trim()) + .filter((email) => email.length > 0); - const alertBeforeDays = convertToDays(alertUnit, Number(alertBefore)); + const alertBeforeDays = convertToDays(alertUnit, Number(alertBefore)); - if (alert) { - // update - await updatePkiAlert({ - alertId: alert.id, - pkiCollectionId, - name, - projectId, - alertBeforeDays, - emails: emailArray - }); - } else { - // create - await createPkiAlert({ - name, - projectId, - pkiCollectionId, - alertBeforeDays, - emails: emailArray - }); - } - - handlePopUpToggle("pkiAlert", false); - - reset(); - - createNotification({ - text: `Successfully ${alert ? "updated" : "created"} alert`, - type: "success" + if (alert) { + // update + await updatePkiAlert({ + alertId: alert.id, + pkiCollectionId, + name, + projectId, + alertBeforeDays, + emails: emailArray }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${alert ? "updated" : "created"} alert`, - type: "error" + } else { + // create + await createPkiAlert({ + name, + projectId, + pkiCollectionId, + alertBeforeDays, + emails: emailArray }); } + + handlePopUpToggle("pkiAlert", false); + + reset(); + + createNotification({ + text: `Successfully ${alert ? "updated" : "created"} alert`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertsSection.tsx b/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertsSection.tsx index 329974f65..1529dfc19 100644 --- a/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertsSection.tsx +++ b/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertsSection.tsx @@ -22,27 +22,19 @@ export const PkiAlertsSection = () => { ] as const); const onRemoveAlertSubmit = async (alertId: string) => { - try { - if (!projectId) return; + if (!projectId) return; - await deletePkiAlert({ - alertId, - projectId - }); + await deletePkiAlert({ + alertId, + projectId + }); - createNotification({ - text: "Successfully deleted alert", - type: "success" - }); + createNotification({ + text: "Successfully deleted alert", + type: "success" + }); - handlePopUpClose("deletePkiAlert"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete alert", - type: "error" - }); - } + handlePopUpClose("deletePkiAlert"); }; return ( diff --git a/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionModal.tsx b/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionModal.tsx index 5900a1ba9..a29e9028b 100644 --- a/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionModal.tsx +++ b/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionModal.tsx @@ -62,49 +62,41 @@ export const PkiCollectionModal = ({ popUp, handlePopUpToggle }: Props) => { }, [pkiCollection]); const onFormSubmit = async ({ name, description }: FormData) => { - try { - if (!projectId) return; + if (!projectId) return; - if (pkiCollection) { - // update - await updatePkiCollection({ - collectionId: pkiCollection.id, - name, - description, - projectId - }); - } else { - // create - const { id: collectionId } = await createPkiCollection({ - name, - description, - projectId - }); - - navigate({ - to: "/projects/cert-management/$projectId/pki-collections/$collectionId", - params: { - projectId, - collectionId - } - }); - } - - handlePopUpToggle("pkiCollection", false); - - reset(); - - createNotification({ - text: `Successfully ${pkiCollection ? "updated" : "created"} PKI collection`, - type: "success" + if (pkiCollection) { + // update + await updatePkiCollection({ + collectionId: pkiCollection.id, + name, + description, + projectId }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${pkiCollection ? "updated" : "created"} PKI collection`, - type: "error" + } else { + // create + const { id: collectionId } = await createPkiCollection({ + name, + description, + projectId + }); + + navigate({ + to: "/projects/cert-management/$projectId/pki-collections/$collectionId", + params: { + projectId, + collectionId + } }); } + + handlePopUpToggle("pkiCollection", false); + + reset(); + + createNotification({ + text: `Successfully ${pkiCollection ? "updated" : "created"} PKI collection`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionSection.tsx b/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionSection.tsx index eecceaa7d..fa9cf7bea 100644 --- a/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionSection.tsx +++ b/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionSection.tsx @@ -22,27 +22,19 @@ export const PkiCollectionSection = () => { ] as const); const onRemovePkiCollectionSubmit = async (collectionId: string) => { - try { - if (!projectId) return; + if (!projectId) return; - await deletePkiCollection({ - collectionId, - projectId - }); + await deletePkiCollection({ + collectionId, + projectId + }); - createNotification({ - text: "Successfully deleted PKI collection", - type: "success" - }); + createNotification({ + text: "Successfully deleted PKI collection", + type: "success" + }); - handlePopUpClose("deletePkiCollection"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete PKI collection", - type: "error" - }); - } + handlePopUpClose("deletePkiCollection"); }; return ( diff --git a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/CertAuthDetailsByIDPage.tsx b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/CertAuthDetailsByIDPage.tsx index ca73abd50..b464a2230 100644 --- a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/CertAuthDetailsByIDPage.tsx +++ b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/CertAuthDetailsByIDPage.tsx @@ -57,33 +57,26 @@ const Page = () => { ] as const); const onRemoveCaSubmit = async () => { - try { - if (!currentProject?.slug) return; + if (!currentProject?.slug) return; - await deleteCa({ - caName, - projectId: currentProject.id, - type: CaType.INTERNAL - }); + await deleteCa({ + caName, + projectId: currentProject.id, + type: CaType.INTERNAL + }); - createNotification({ - text: "Successfully deleted CA", - type: "success" - }); + createNotification({ + text: "Successfully deleted CA", + type: "success" + }); - handlePopUpClose("deleteCa"); - navigate({ - to: "/projects/cert-management/$projectId/certificate-authorities", - params: { - projectId - } - }); - } catch { - createNotification({ - text: "Failed to delete CA", - type: "error" - }); - } + handlePopUpClose("deleteCa"); + navigate({ + to: "/projects/cert-management/$projectId/certificate-authorities", + params: { + projectId + } + }); }; return ( diff --git a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaRenewalModal.tsx b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaRenewalModal.tsx index 857586a58..c6283da37 100644 --- a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaRenewalModal.tsx +++ b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaRenewalModal.tsx @@ -84,27 +84,23 @@ export const CaRenewalModal = ({ popUp, handlePopUpToggle }: Props) => { // }, [ca, parentCa]); const onFormSubmit = async ({ type, notAfter }: FormData) => { - try { - if (!projectSlug || !popUpData.caId) return; + if (!projectSlug || !popUpData.caId) return; - await renewCa({ - projectSlug, - caId: popUpData.caId, - notAfter, - type - }); + await renewCa({ + projectSlug, + caId: popUpData.caId, + notAfter, + type + }); - handlePopUpToggle("renewCa", false); + handlePopUpToggle("renewCa", false); - createNotification({ - text: "Successfully renewed CA", - type: "success" - }); + createNotification({ + text: "Successfully renewed CA", + type: "success" + }); - reset(); - } catch (err) { - console.error(err); - } + reset(); }; return ( diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/ExternalCaInstallForm.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/ExternalCaInstallForm.tsx index f730322c9..8a7e9690e 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/ExternalCaInstallForm.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/ExternalCaInstallForm.tsx @@ -48,29 +48,22 @@ export const ExternalCaInstallForm = ({ caId, handlePopUpToggle }: Props) => { }, []); const onFormSubmit = async ({ certificate, certificateChain }: FormData) => { - try { - if (!csr || !caId || !currentProject?.slug) return; + if (!csr || !caId || !currentProject?.slug) return; - await importCaCertificate({ - caId, - projectSlug: currentProject?.slug, - certificate, - certificateChain - }); + await importCaCertificate({ + caId, + projectSlug: currentProject?.slug, + certificate, + certificateChain + }); - reset(); + reset(); - createNotification({ - text: "Successfully installed certificate for CA", - type: "success" - }); - handlePopUpToggle("installCaCert", false); - } catch { - createNotification({ - text: "Failed to install certificate for CA", - type: "error" - }); - } + createNotification({ + text: "Successfully installed certificate for CA", + type: "success" + }); + handlePopUpToggle("installCaCert", false); }; const downloadTxtFile = (filename: string, content: string) => { diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/InternalCaInstallForm.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/InternalCaInstallForm.tsx index 5979d9711..f80e21212 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/InternalCaInstallForm.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/InternalCaInstallForm.tsx @@ -101,37 +101,30 @@ export const InternalCaInstallForm = ({ caId, handlePopUpToggle }: Props) => { }, [parentCa]); const onFormSubmit = async ({ notAfter, maxPathLength }: FormData) => { - try { - if (!csr || !caId || !currentProject?.slug) return; + if (!csr || !caId || !currentProject?.slug) return; - const { certificate, certificateChain } = await signIntermediate({ - caId: parentCaId, - csr, - maxPathLength: Number(maxPathLength), - notAfter, - notBefore: new Date().toISOString() - }); + const { certificate, certificateChain } = await signIntermediate({ + caId: parentCaId, + csr, + maxPathLength: Number(maxPathLength), + notAfter, + notBefore: new Date().toISOString() + }); - await importCaCertificate({ - caId, - projectSlug: currentProject?.slug, - certificate, - certificateChain - }); + await importCaCertificate({ + caId, + projectSlug: currentProject?.slug, + certificate, + certificateChain + }); - reset(); + reset(); - createNotification({ - text: "Successfully installed certificate for CA", - type: "success" - }); - handlePopUpToggle("installCaCert", false); - } catch { - createNotification({ - text: "Failed to install certificate for CA", - type: "error" - }); - } + createNotification({ + text: "Successfully installed certificate for CA", + type: "success" + }); + handlePopUpToggle("installCaCert", false); }; function generatePathLengthOpts(parentCaMaxPathLength: number): number[] { diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaModal.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaModal.tsx index a19a3a9c7..53434b79d 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaModal.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaModal.tsx @@ -175,48 +175,40 @@ export const CaModal = ({ popUp, handlePopUpToggle }: Props) => { status, configuration }: FormData) => { - try { - if (!currentProject?.slug) return; + if (!currentProject?.slug) return; - if (ca) { - // update - await updateMutateAsync({ - caName: ca.name, - projectId: currentProject.id, - name, - type: CaType.INTERNAL, - status, - enableDirectIssuance - }); - } else { - // create - await createMutateAsync({ - projectId: currentProject.id, - name, - type, - status, - enableDirectIssuance, - configuration: { - ...configuration, - maxPathLength: Number(configuration.maxPathLength) - } - }); - } - - reset(); - handlePopUpToggle("ca", false); - - createNotification({ - text: `Successfully ${ca ? "updated" : "created"} CA`, - type: "success" + if (ca) { + // update + await updateMutateAsync({ + caName: ca.name, + projectId: currentProject.id, + name, + type: CaType.INTERNAL, + status, + enableDirectIssuance }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to create CA", - type: "error" + } else { + // create + await createMutateAsync({ + projectId: currentProject.id, + name, + type, + status, + enableDirectIssuance, + configuration: { + ...configuration, + maxPathLength: Number(configuration.maxPathLength) + } }); } + + reset(); + handlePopUpToggle("ca", false); + + createNotification({ + text: `Successfully ${ca ? "updated" : "created"} CA`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaSection.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaSection.tsx index e853dc801..918a844b1 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaSection.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaSection.tsx @@ -1,7 +1,6 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, DeleteActionModal } from "@app/components/v2"; @@ -24,49 +23,33 @@ export const CaSection = () => { "caCert", "installCaCert", "deleteCa", - "caStatus", // enable / disable - "upgradePlan" + "caStatus" // enable / disable ] as const); const onRemoveCaSubmit = async (caName: string) => { - try { - if (!currentProject?.slug) return; + if (!currentProject?.slug) return; - await deleteCa({ caName, projectId: currentProject.id, type: CaType.INTERNAL }); + await deleteCa({ caName, projectId: currentProject.id, type: CaType.INTERNAL }); - createNotification({ - text: "Successfully deleted CA", - type: "success" - }); + createNotification({ + text: "Successfully deleted CA", + type: "success" + }); - handlePopUpClose("deleteCa"); - } catch { - createNotification({ - text: "Failed to delete CA", - type: "error" - }); - } + handlePopUpClose("deleteCa"); }; const onUpdateCaStatus = async ({ caName, status }: { caName: string; status: CaStatus }) => { - try { - if (!currentProject?.slug) return; + if (!currentProject?.slug) return; - await updateCa({ caName, projectId: currentProject.id, type: CaType.INTERNAL, status }); + await updateCa({ caName, projectId: currentProject.id, type: CaType.INTERNAL, status }); - createNotification({ - text: `Successfully ${status === CaStatus.ACTIVE ? "enabled" : "disabled"} CA`, - type: "success" - }); + createNotification({ + text: `Successfully ${status === CaStatus.ACTIVE ? "enabled" : "disabled"} CA`, + type: "success" + }); - handlePopUpClose("caStatus"); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${status === CaStatus.ACTIVE ? "enabled" : "disabled"} CA`, - type: "error" - }); - } + handlePopUpClose("caStatus"); }; return ( @@ -124,11 +107,6 @@ export const CaSection = () => { onUpdateCaStatus(popUp?.caStatus?.data as { caName: string; status: CaStatus }) } /> - handlePopUpToggle("upgradePlan", isOpen)} - text={(popUp.upgradePlan?.data as { description: string })?.description} - />
); }; diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaTable.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaTable.tsx index 09fe39af6..b98475bc9 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaTable.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaTable.tsx @@ -34,15 +34,12 @@ import { UsePopUpState } from "@app/hooks/usePopUp"; type Props = { handlePopUpOpen: ( - popUpName: keyof UsePopUpState< - ["installCaCert", "caCert", "ca", "deleteCa", "caStatus", "upgradePlan"] - >, + popUpName: keyof UsePopUpState<["installCaCert", "caCert", "ca", "deleteCa", "caStatus"]>, data?: { caId?: string; caName?: string; dn?: string; status?: CaStatus; - description?: string; } ) => void; }; diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaModal.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaModal.tsx index 121c32e5f..be757e65b 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaModal.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaModal.tsx @@ -124,7 +124,7 @@ type Props = { const caTypes = [ { label: "ACME", value: CaType.ACME }, - { label: "Azure AD Certificate Service", value: CaType.AZURE_AD_CS } + { label: "Active Directory Certificate Services (AD CS)", value: CaType.AZURE_AD_CS } ]; export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => { @@ -297,63 +297,55 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => { status, configuration: formConfiguration }: FormData) => { - try { - if (!currentProject?.slug) return; + if (!currentProject?.slug) return; - let configPayload: any; + let configPayload: any; - if (type === CaType.ACME && "dnsAppConnection" in formConfiguration) { - configPayload = { - dnsProviderConfig: formConfiguration.dnsProviderConfig, - directoryUrl: formConfiguration.directoryUrl, - accountEmail: formConfiguration.accountEmail, - dnsAppConnectionId: formConfiguration.dnsAppConnection.id, - eabKid: formConfiguration.eabKid, - eabHmacKey: formConfiguration.eabHmacKey - }; - } else if (type === CaType.AZURE_AD_CS && "azureAdcsConnection" in formConfiguration) { - configPayload = { - azureAdcsConnectionId: formConfiguration.azureAdcsConnection.id - }; - } else { - throw new Error("Invalid certificate authority configuration"); - } + if (type === CaType.ACME && "dnsAppConnection" in formConfiguration) { + configPayload = { + dnsProviderConfig: formConfiguration.dnsProviderConfig, + directoryUrl: formConfiguration.directoryUrl, + accountEmail: formConfiguration.accountEmail, + dnsAppConnectionId: formConfiguration.dnsAppConnection.id, + eabKid: formConfiguration.eabKid, + eabHmacKey: formConfiguration.eabHmacKey + }; + } else if (type === CaType.AZURE_AD_CS && "azureAdcsConnection" in formConfiguration) { + configPayload = { + azureAdcsConnectionId: formConfiguration.azureAdcsConnection.id + }; + } else { + throw new Error("Invalid certificate authority configuration"); + } - if (ca) { - await updateMutateAsync({ - caName: ca.name, - projectId: currentProject.id, - name, - type, - status, - enableDirectIssuance: type === CaType.AZURE_AD_CS ? false : enableDirectIssuance, - configuration: configPayload - }); - } else { - await createMutateAsync({ - projectId: currentProject.id, - name, - type, - status, - enableDirectIssuance: type === CaType.AZURE_AD_CS ? false : enableDirectIssuance, - configuration: configPayload - }); - } - - reset(); - handlePopUpToggle("ca", false); - - createNotification({ - text: `Successfully ${ca ? "updated" : "created"} CA`, - type: "success" + if (ca) { + await updateMutateAsync({ + caName: ca.name, + projectId: currentProject.id, + name, + type, + status, + enableDirectIssuance: type === CaType.AZURE_AD_CS ? false : enableDirectIssuance, + configuration: configPayload }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to create CA", - type: "error" + } else { + await createMutateAsync({ + projectId: currentProject.id, + name, + type, + status, + enableDirectIssuance: type === CaType.AZURE_AD_CS ? false : enableDirectIssuance, + configuration: configPayload }); } + + reset(); + handlePopUpToggle("ca", false); + + createNotification({ + text: `Successfully ${ca ? "updated" : "created"} CA`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaSection.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaSection.tsx index 14ce122ec..31894b855 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaSection.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaSection.tsx @@ -1,7 +1,6 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, DeleteActionModal } from "@app/components/v2"; @@ -20,28 +19,20 @@ export const ExternalCaSection = () => { const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "ca", "deleteCa", - "caStatus", // enable / disable - "upgradePlan" + "caStatus" // enable / disable ] as const); const onRemoveCaSubmit = async (caName: string, type: CaType) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - await deleteCa({ caName, type, projectId: currentProject.id }); + await deleteCa({ caName, type, projectId: currentProject.id }); - createNotification({ - text: "Successfully deleted CA", - type: "success" - }); + createNotification({ + text: "Successfully deleted CA", + type: "success" + }); - handlePopUpClose("deleteCa"); - } catch { - createNotification({ - text: "Failed to delete CA", - type: "error" - }); - } + handlePopUpClose("deleteCa"); }; const onUpdateCaStatus = async ({ @@ -53,24 +44,16 @@ export const ExternalCaSection = () => { type: CaType; status: CaStatus; }) => { - try { - if (!currentProject?.slug) return; + if (!currentProject?.slug) return; - await updateCa({ caName: name, type, status, projectId: currentProject.id }); + await updateCa({ caName: name, type, status, projectId: currentProject.id }); - createNotification({ - text: `Successfully ${status === CaStatus.ACTIVE ? "enabled" : "disabled"} CA`, - type: "success" - }); + createNotification({ + text: `Successfully ${status === CaStatus.ACTIVE ? "enabled" : "disabled"} CA`, + type: "success" + }); - handlePopUpClose("caStatus"); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${status === CaStatus.ACTIVE ? "enable" : "disable"} CA`, - type: "error" - }); - } + handlePopUpClose("caStatus"); }; return ( @@ -131,11 +114,6 @@ export const ExternalCaSection = () => { ) } /> - handlePopUpToggle("upgradePlan", isOpen)} - text={(popUp.upgradePlan?.data as { description: string })?.description} - />
); }; diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaTable.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaTable.tsx index 0f3fca6cd..379a091c7 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaTable.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaTable.tsx @@ -33,12 +33,11 @@ import { UsePopUpState } from "@app/hooks/usePopUp"; type Props = { handlePopUpOpen: ( - popUpName: keyof UsePopUpState<["ca", "deleteCa", "caStatus", "upgradePlan"]>, + popUpName: keyof UsePopUpState<["ca", "deleteCa", "caStatus"]>, data?: { name?: string; type?: CaType; status?: CaStatus; - description?: string; } ) => void; }; diff --git a/frontend/src/pages/cert-manager/CertificatesPage/CertificatesPage.tsx b/frontend/src/pages/cert-manager/CertificatesPage/CertificatesPage.tsx deleted file mode 100644 index 984a3b398..000000000 --- a/frontend/src/pages/cert-manager/CertificatesPage/CertificatesPage.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { Helmet } from "react-helmet"; -import { useTranslation } from "react-i18next"; - -import { ProjectPermissionCan } from "@app/components/permissions"; -import { PageHeader } from "@app/components/v2"; -import { - ProjectPermissionActions, - ProjectPermissionCertificateActions, - ProjectPermissionSub, - useProjectPermission -} from "@app/context"; -import { ProjectType } from "@app/hooks/api/projects/types"; - -import { PkiCollectionSection } from "../AlertingPage/components"; -import { CertificatesSection } from "./components"; - -export const CertificatesPage = () => { - const { t } = useTranslation(); - const { permission } = useProjectPermission(); - - const canAccessPkiColl = permission.can( - ProjectPermissionActions.Read, - ProjectPermissionSub.PkiCollections - ); - const canAccessCerts = permission.can( - ProjectPermissionCertificateActions.Read, - ProjectPermissionSub.Certificates - ); - - return ( -
- - {t("common.head-title", { title: "Certificates" })} - -
- - {/* If both are false, the section does not render. This is to prevent duplicate banners. */} - {(canAccessCerts || canAccessPkiColl) && ( - - - - )} - - - -
-
- ); -}; diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateImportModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateImportModal.tsx index bb0bbda0b..d0434b79a 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateImportModal.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateImportModal.tsx @@ -71,38 +71,30 @@ export const CertificateImportModal = ({ popUp, handlePopUpToggle }: Props) => { chainPem, collectionId }: FormData) => { - try { - if (!currentProject?.slug) return; + if (!currentProject?.slug) return; - const { serialNumber, certificate, certificateChain, privateKey } = await importCertificate({ - projectSlug: currentProject.slug, + const { serialNumber, certificate, certificateChain, privateKey } = await importCertificate({ + projectSlug: currentProject.slug, - certificatePem, - privateKeyPem, - chainPem, - pkiCollectionId: collectionId - }); + certificatePem, + privateKeyPem, + chainPem, + pkiCollectionId: collectionId + }); - reset(); + reset(); - setCertificateDetails({ - serialNumber, - certificate, - certificateChain, - privateKey - }); + setCertificateDetails({ + serialNumber, + certificate, + certificateChain, + privateKey + }); - createNotification({ - text: "Successfully imported certificate", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to import certificate", - type: "error" - }); - } + createNotification({ + text: "Successfully imported certificate", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateIssuanceModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateIssuanceModal.tsx index dc0dffb0d..b854bc899 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateIssuanceModal.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateIssuanceModal.tsx @@ -122,11 +122,12 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId } const { data: profilesData } = useListCertificateProfiles({ projectId: currentProject?.id || "", - includeMetrics: false, enrollmentType: "api" }); - const { mutateAsync: createCertificate } = useCreateCertificateV3(); + const { mutateAsync: createCertificate } = useCreateCertificateV3({ + projectId: currentProject?.id + }); const formResolver = useMemo(() => { return zodResolver(createSchema(shouldShowSubjectSection)); @@ -242,84 +243,72 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId } keyUsages, extendedKeyUsages }: FormData) => { - try { - if (!currentProject?.slug) { - createNotification({ - text: "Project not found. Please refresh and try again.", - type: "error" - }); - return; - } - - if (!formProfileId) { - createNotification({ - text: "Please select a certificate profile.", - type: "error" - }); - return; - } - - let commonName = ""; - if ( - constraints.shouldShowSubjectSection && - subjectAttributes && - subjectAttributes.length > 0 - ) { - commonName = getAttributeValue(subjectAttributes, "common_name"); - if (!commonName.trim()) { - createNotification({ - text: "Common name is required.", - type: "error" - }); - return; - } - } - - const certificateRequest: any = { - profileId: formProfileId, - projectSlug: currentProject.slug, - ttl, - signatureAlgorithm, - keyAlgorithm, - keyUsages: filterUsages(keyUsages) as CertKeyUsage[], - extendedKeyUsages: filterUsages(extendedKeyUsages) as CertExtendedKeyUsage[] - }; - - if (constraints.shouldShowSubjectSection && commonName) { - certificateRequest.commonName = commonName; - } - if (constraints.shouldShowSanSection && subjectAltNames && subjectAltNames.length > 0) { - const formattedSans = formatSubjectAltNames(subjectAltNames); - if (formattedSans && formattedSans.length > 0) { - certificateRequest.altNames = formattedSans; - } - } - - const { serialNumber, certificate, certificateChain, privateKey } = - await createCertificate(certificateRequest); - - setCertificateDetails({ - serialNumber, - certificate, - certificateChain, - privateKey - }); - + if (!currentProject?.slug) { createNotification({ - text: "Successfully created certificate", - type: "success" - }); - } catch (err) { - console.error("Certificate creation failed:", err); - const errorMessage = - err instanceof Error - ? err.message - : "An unexpected error occurred while creating the certificate"; - createNotification({ - text: `Failed to create certificate: ${errorMessage}`, + text: "Project not found. Please refresh and try again.", type: "error" }); + return; } + + if (!formProfileId) { + createNotification({ + text: "Please select a certificate profile.", + type: "error" + }); + return; + } + + let commonName = ""; + if ( + constraints.shouldShowSubjectSection && + subjectAttributes && + subjectAttributes.length > 0 + ) { + commonName = getAttributeValue(subjectAttributes, "common_name"); + if (!commonName.trim()) { + createNotification({ + text: "Common name is required.", + type: "error" + }); + return; + } + } + + const certificateRequest: any = { + profileId: formProfileId, + projectSlug: currentProject.slug, + ttl, + signatureAlgorithm, + keyAlgorithm, + keyUsages: filterUsages(keyUsages) as CertKeyUsage[], + extendedKeyUsages: filterUsages(extendedKeyUsages) as CertExtendedKeyUsage[] + }; + + if (constraints.shouldShowSubjectSection && commonName) { + certificateRequest.commonName = commonName; + } + if (constraints.shouldShowSanSection && subjectAltNames && subjectAltNames.length > 0) { + const formattedSans = formatSubjectAltNames(subjectAltNames); + if (formattedSans && formattedSans.length > 0) { + certificateRequest.altNames = formattedSans; + } + } + + const { serialNumber, certificate, certificateChain, privateKey } = + await createCertificate(certificateRequest); + + setCertificateDetails({ + serialNumber, + certificate, + certificateChain, + privateKey + }); + + createNotification({ + text: "Successfully created certificate", + type: "success" + }); }, [ currentProject?.slug, diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateManagePkiSyncsModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateManagePkiSyncsModal.tsx new file mode 100644 index 000000000..db3e6c197 --- /dev/null +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateManagePkiSyncsModal.tsx @@ -0,0 +1,296 @@ +import { useEffect, useMemo, useState } from "react"; +import { faPlus, faSearch } from "@fortawesome/free-solid-svg-icons"; +import { useNavigate } from "@tanstack/react-router"; + +import { createNotification } from "@app/components/notifications"; +import { + Button, + Checkbox, + EmptyState, + Input, + Modal, + ModalContent, + Pagination, + Table, + TableContainer, + TBody, + Td, + Th, + THead, + Tr +} from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { useProject } from "@app/context"; +import { + PkiSync, + useAddCertificatesToPkiSync, + useListPkiSyncsWithCertificate, + useRemoveCertificatesFromPkiSync +} from "@app/hooks/api/pkiSyncs"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + +type Props = { + popUp: { + isOpen: boolean; + data?: { + certificateId?: string; + commonName?: string; + }; + }; + handlePopUpToggle: (popUpName: "managePkiSyncs", state?: boolean) => void; +}; + +const PER_PAGE = 10; + +export const CertificateManagePkiSyncsModal = ({ popUp, handlePopUpToggle }: Props) => { + const [selectedSyncIds, setSelectedSyncIds] = useState>(new Set()); + const [initialSyncIds, setInitialSyncIds] = useState>(new Set()); + const [isSubmitting, setIsSubmitting] = useState(false); + const [currentPage, setCurrentPage] = useState(1); + const [searchTerm, setSearchTerm] = useState(""); + + const { currentProject } = useProject(); + const navigate = useNavigate(); + const { certificateId, commonName } = popUp.data || {}; + + const { data: pkiSyncs = [], isPending } = useListPkiSyncsWithCertificate( + currentProject?.id || "", + certificateId || "", + { + enabled: !!currentProject?.id && !!certificateId + } + ); + const addCertificatesToSync = useAddCertificatesToPkiSync(); + const removeCertificatesFromSync = useRemoveCertificatesFromPkiSync(); + + const filteredSyncs = useMemo(() => { + if (!searchTerm.trim()) return pkiSyncs; + + const searchLower = searchTerm.toLowerCase(); + return pkiSyncs.filter((sync) => sync.name.toLowerCase().includes(searchLower)); + }, [pkiSyncs, searchTerm]); + + const startIndex = (currentPage - 1) * PER_PAGE; + const endIndex = startIndex + PER_PAGE; + const paginatedSyncs = filteredSyncs.slice(startIndex, endIndex); + + useEffect(() => { + setCurrentPage(1); + }, [searchTerm]); + + const handleClose = () => { + handlePopUpToggle("managePkiSyncs", false); + setSelectedSyncIds(new Set()); + setInitialSyncIds(new Set()); + setSearchTerm(""); + setCurrentPage(1); + }; + + const handleNavigateToPkiSyncs = () => { + if (!currentProject?.id) return; + + navigate({ + to: ROUTE_PATHS.CertManager.IntegrationsListPage.path, + params: { + projectId: currentProject.id + }, + search: { + selectedTab: IntegrationsListPageTabs.PkiSyncs + } + }); + handleClose(); + }; + + const getDestinationDisplayName = (destination: string) => { + switch (destination) { + case PkiSync.AzureKeyVault: + return "Azure Key Vault"; + case PkiSync.AwsCertificateManager: + return "AWS Certificate Manager"; + default: + return destination; + } + }; + + useEffect(() => { + if (!certificateId || !pkiSyncs || pkiSyncs.length === 0) return; + + const currentSyncIds = new Set( + pkiSyncs.filter((sync) => sync.hasCertificate).map((sync) => sync.id) + ); + setSelectedSyncIds(currentSyncIds); + setInitialSyncIds(new Set(currentSyncIds)); + }, [certificateId, pkiSyncs]); + + const handleSyncToggle = (syncId: string) => { + setSelectedSyncIds((prev) => { + const newSet = new Set(prev); + if (newSet.has(syncId)) { + newSet.delete(syncId); + } else { + newSet.add(syncId); + } + return newSet; + }); + }; + + const handleSaveChanges = async () => { + if (!certificateId) return; + + try { + setIsSubmitting(true); + + const syncsToAdd = Array.from(selectedSyncIds).filter((id) => !initialSyncIds.has(id)); + const syncsToRemove = Array.from(initialSyncIds).filter((id) => !selectedSyncIds.has(id)); + + await Promise.all( + syncsToAdd.map((syncId) => + addCertificatesToSync.mutateAsync({ + pkiSyncId: syncId, + certificateIds: [certificateId] + }) + ) + ); + + await Promise.all( + syncsToRemove.map((syncId) => + removeCertificatesFromSync.mutateAsync({ + pkiSyncId: syncId, + certificateIds: [certificateId] + }) + ) + ); + + createNotification({ + text: `PKI sync settings updated for certificate "${commonName}"`, + type: "success" + }); + + handleClose(); + } catch (error) { + console.error(error); + createNotification({ + text: "Failed to update PKI sync settings", + type: "error" + }); + } finally { + setIsSubmitting(false); + } + }; + + return ( + + +
+ setSearchTerm(e.target.value)} + placeholder="Search PKI syncs by name..." + /> +
+
+ {isPending && ( +
+
Loading PKI syncs...
+
+ )} + {!isPending && pkiSyncs.length === 0 && ( + +
+ Create a{" "} + {" "} + first to manage certificate syncing. +
+
+ )} + {!isPending && pkiSyncs.length > 0 && filteredSyncs.length === 0 && searchTerm && ( + +
+ No PKI syncs match your search criteria. Try a different search term. +
+
+ )} + {!isPending && filteredSyncs.length > 0 && ( + + + + + + + + + + {paginatedSyncs.map((sync) => ( + handleSyncToggle(sync.id)} + > + + + + + ))} + +
+ NameDestination
+ handleSyncToggle(sync.id)} + id={`sync-${sync.id}`} + /> + +
+ {sync.name} +
+
+
+ {getDestinationDisplayName(sync.destination)} +
+
+
+ )} + {!isPending && filteredSyncs.length > PER_PAGE && ( +
+ {}} + /> +
+ )} +
+ +
+ + +
+
+
+ ); +}; diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateManageRenewalModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateManageRenewalModal.tsx index d6199678a..96eb2654a 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateManageRenewalModal.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateManageRenewalModal.tsx @@ -163,38 +163,28 @@ export const CertificateManageRenewalModal = ({ popUp, handlePopUpToggle }: Prop }, [popUp.manageRenewal.isOpen, defaultRenewalDays, reset]); const onUpdateRenewal = async (data: FormData) => { - try { - if (!currentProject?.slug) { - createNotification({ - text: "Unable to update auto-renewal: Project not found. Please refresh the page and try again.", - type: "error" - }); - return; - } - - await updateRenewalConfig({ - certificateId: certificateData.certificateId, - renewBeforeDays: data.renewBeforeDays, - projectSlug: currentProject.slug - }); - + if (!currentProject?.slug) { createNotification({ - text: isAutoRenewalEnabled - ? "Auto-renewal configuration updated successfully" - : "Auto-renewal enabled successfully", - type: "success" - }); - - handlePopUpToggle("manageRenewal", false); - } catch (err) { - console.error(err); - createNotification({ - text: isAutoRenewalEnabled - ? "Failed to update auto-renewal configuration. Please check your inputs and try again." - : "Failed to enable auto-renewal. Please check your inputs and try again.", + text: "Unable to update auto-renewal: Project not found. Please refresh the page and try again.", type: "error" }); + return; } + + await updateRenewalConfig({ + certificateId: certificateData.certificateId, + renewBeforeDays: data.renewBeforeDays, + projectSlug: currentProject.slug + }); + + createNotification({ + text: isAutoRenewalEnabled + ? "Auto-renewal configuration updated successfully" + : "Auto-renewal enabled successfully", + type: "success" + }); + + handlePopUpToggle("manageRenewal", false); }; const getModalTitle = () => { diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateModal.tsx index 22718222a..f56f0fde8 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateModal.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateModal.tsx @@ -186,45 +186,37 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => { keyUsages, extendedKeyUsages }: FormData) => { - try { - if (!currentProject?.slug) return; + if (!currentProject?.slug) return; - const { serialNumber, certificate, certificateChain, privateKey } = await createCertificate({ - caId: !selectedCertTemplate ? caId : undefined, - certificateTemplateId: selectedCertTemplate ? selectedCertTemplateId : undefined, - projectSlug: currentProject.slug, - pkiCollectionId: collectionId, - commonName, - subjectAltNames, - ttl, - keyUsages: Object.entries(keyUsages) - .filter(([, value]) => value) - .map(([key]) => key as CertKeyUsage), - extendedKeyUsages: Object.entries(extendedKeyUsages) - .filter(([, value]) => value) - .map(([key]) => key as CertExtendedKeyUsage) - }); + const { serialNumber, certificate, certificateChain, privateKey } = await createCertificate({ + caId: !selectedCertTemplate ? caId : undefined, + certificateTemplateId: selectedCertTemplate ? selectedCertTemplateId : undefined, + projectSlug: currentProject.slug, + pkiCollectionId: collectionId, + commonName, + subjectAltNames, + ttl, + keyUsages: Object.entries(keyUsages) + .filter(([, value]) => value) + .map(([key]) => key as CertKeyUsage), + extendedKeyUsages: Object.entries(extendedKeyUsages) + .filter(([, value]) => value) + .map(([key]) => key as CertExtendedKeyUsage) + }); - reset(); + reset(); - setCertificateDetails({ - serialNumber, - certificate, - certificateChain, - privateKey - }); + setCertificateDetails({ + serialNumber, + certificate, + certificateChain, + privateKey + }); - createNotification({ - text: "Successfully created certificate", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to create certificate", - type: "error" - }); - } + createNotification({ + text: "Successfully created certificate", + type: "success" + }); }; useEffect(() => { diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalConfigModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalConfigModal.tsx index c952f1e54..039151866 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalConfigModal.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalConfigModal.tsx @@ -61,34 +61,26 @@ export const CertificateRenewalConfigModal = ({ popUp, handlePopUpToggle }: Prop const renewBeforeDays = watch("renewBeforeDays"); const onSubmit = async (data: FormData) => { - try { - if (!currentProject?.slug) { - createNotification({ - text: "Project not found", - type: "error" - }); - return; - } - - await updateRenewalConfig({ - certificateId: certificateData.certificateId, - renewBeforeDays: data.renewBeforeDays, - projectSlug: currentProject.slug - }); - + if (!currentProject?.slug) { createNotification({ - text: "Successfully updated auto-renewal configuration", - type: "success" - }); - - handlePopUpToggle("configureRenewal", false); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update auto-renewal configuration", + text: "Project not found", type: "error" }); + return; } + + await updateRenewalConfig({ + certificateId: certificateData.certificateId, + renewBeforeDays: data.renewBeforeDays, + projectSlug: currentProject.slug + }); + + createNotification({ + text: "Successfully updated auto-renewal configuration", + type: "success" + }); + + handlePopUpToggle("configureRenewal", false); }; return ( diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalDisableModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalDisableModal.tsx index 613080cd7..e44d7e774 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalDisableModal.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalDisableModal.tsx @@ -19,34 +19,26 @@ export const CertificateRenewalDisableModal = ({ popUp, handlePopUpToggle }: Pro }; const onDisableConfirm = async () => { - try { - if (!currentProject?.slug) { - createNotification({ - text: "Project not found", - type: "error" - }); - return; - } - - await updateRenewalConfig({ - certificateId: certificateData.certificateId, - projectSlug: currentProject.slug, - enableAutoRenewal: false - }); - + if (!currentProject?.slug) { createNotification({ - text: "Successfully disabled auto-renewal", - type: "success" - }); - - handlePopUpToggle("disableRenewal", false); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to disable auto-renewal", + text: "Project not found", type: "error" }); + return; } + + await updateRenewalConfig({ + certificateId: certificateData.certificateId, + projectSlug: currentProject.slug, + enableAutoRenewal: false + }); + + createNotification({ + text: "Successfully disabled auto-renewal", + type: "success" + }); + + handlePopUpToggle("disableRenewal", false); }; return ( diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalModal.tsx index 0e2b1c17d..87906e5a5 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalModal.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRenewalModal.tsx @@ -18,22 +18,18 @@ export const CertificateRenewalModal = ({ popUp, handlePopUpToggle }: Props) => const { mutateAsync: renewCertificate, isPending: isRenewing } = useRenewCertificate(); const onRenewConfirm = async () => { - try { - const { certificateId } = popUp.renewCertificate.data as { certificateId: string }; + const { certificateId } = popUp.renewCertificate.data as { certificateId: string }; - await renewCertificate({ - certificateId - }); + await renewCertificate({ + certificateId + }); - createNotification({ - text: "Certificate renewed successfully", - type: "success" - }); + createNotification({ + text: "Certificate renewed successfully", + type: "success" + }); - handlePopUpToggle("renewCertificate", false); - } catch (err) { - console.error(err); - } + handlePopUpToggle("renewCertificate", false); }; const certificateData = popUp.renewCertificate.data as { diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRevocationModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRevocationModal.tsx index 1d1539296..d9a564fb1 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRevocationModal.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateRevocationModal.tsx @@ -48,31 +48,23 @@ export const CertificateRevocationModal = ({ popUp, handlePopUpToggle }: Props) }); const onFormSubmit = async ({ revocationReason }: FormData) => { - try { - if (!currentProject?.slug) return; + if (!currentProject?.slug) return; - const { serialNumber } = popUp.revokeCertificate.data as { serialNumber: string }; + const { serialNumber } = popUp.revokeCertificate.data as { serialNumber: string }; - await revokeCertificate({ - projectSlug: currentProject.slug, - serialNumber, - revocationReason - }); + await revokeCertificate({ + projectSlug: currentProject.slug, + serialNumber, + revocationReason + }); - reset(); - handlePopUpToggle("revokeCertificate", false); + reset(); + handlePopUpToggle("revokeCertificate", false); - createNotification({ - text: "Successfully revoked certificate", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to revoke certificate", - type: "error" - }); - } + createNotification({ + text: "Successfully revoked certificate", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplateModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplateModal.tsx index 9d5d9355f..0ba3721e3 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplateModal.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplateModal.tsx @@ -159,61 +159,61 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Pro return; } - try { - if (certTemplate) { - await updateCertTemplate({ - id: certTemplate.id, - projectId: currentProject.id, - pkiCollectionId: collectionId, - caId, - name, - commonName, - subjectAlternativeName, - ttl, - keyUsages: Object.entries(keyUsages) - .filter(([, value]) => value) - .map(([key]) => key as CertKeyUsage), - extendedKeyUsages: Object.entries(extendedKeyUsages) - .filter(([, value]) => value) - .map(([key]) => key as CertExtendedKeyUsage) - }); + if (certTemplate) { + await updateCertTemplate({ + id: certTemplate.id, + projectId: currentProject.id, + pkiCollectionId: collectionId, + caId, + name, + commonName, + subjectAlternativeName, + ttl, + keyUsages: Object.entries(keyUsages) + .filter(([, value]) => value) + .map(([key]) => + key === CertKeyUsage.CRL_SIGN + ? "cRLSign" + : key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()) + ), + extendedKeyUsages: Object.entries(extendedKeyUsages) + .filter(([, value]) => value) + .map(([key]) => key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())) + }); - createNotification({ - text: "Successfully updated certificate template", - type: "success" - }); - } else { - await createCertTemplate({ - projectId: currentProject.id, - pkiCollectionId: collectionId, - caId, - name, - commonName, - subjectAlternativeName, - ttl, - keyUsages: Object.entries(keyUsages) - .filter(([, value]) => value) - .map(([key]) => key as CertKeyUsage), - extendedKeyUsages: Object.entries(extendedKeyUsages) - .filter(([, value]) => value) - .map(([key]) => key as CertExtendedKeyUsage) - }); - - createNotification({ - text: "Successfully created certificate template", - type: "success" - }); - } - - reset(); - handlePopUpToggle("certificateTemplate", false); - } catch (err) { - console.error(err); createNotification({ - text: "Failed to save changes", - type: "error" + text: "Successfully updated certificate template", + type: "success" + }); + } else { + await createCertTemplate({ + projectId: currentProject.id, + pkiCollectionId: collectionId, + caId, + name, + commonName, + subjectAlternativeName, + ttl, + keyUsages: Object.entries(keyUsages) + .filter(([, value]) => value) + .map(([key]) => + key === CertKeyUsage.CRL_SIGN + ? "cRLSign" + : key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()) + ), + extendedKeyUsages: Object.entries(extendedKeyUsages) + .filter(([, value]) => value) + .map(([key]) => key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())) + }); + + createNotification({ + text: "Successfully created certificate template", + type: "success" }); } + + reset(); + handlePopUpToggle("certificateTemplate", false); }; return ( diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplatesSection.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplatesSection.tsx index 629553200..0d575d164 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplatesSection.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateTemplatesSection.tsx @@ -41,25 +41,17 @@ export const CertificateTemplatesSection = ({ caId }: Props) => { return; } - try { - await deleteCertTemplate({ - id, - projectId: currentProject.id - }); + await deleteCertTemplate({ + id, + projectId: currentProject.id + }); - createNotification({ - text: "Successfully deleted certificate template", - type: "success" - }); + createNotification({ + text: "Successfully deleted certificate template", + type: "success" + }); - handlePopUpClose("deleteCertificateTemplate"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete certificate template", - type: "error" - }); - } + handlePopUpClose("deleteCertificateTemplate"); }; return ( @@ -105,7 +97,7 @@ export const CertificateTemplatesSection = ({ caId }: Props) => { isOpen={popUp.upgradePlan.isOpen} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} - text="Managing template enrollment options for EST is only available on Infisical's Enterprise plan." + text="Your current plan does not include access to managing template enrollment options for EST. To unlock this feature, please upgrade to Infisical Enterprise plan." />
); diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesSection.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesSection.tsx index 4102d8ea5..7eb1fbaff 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesSection.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificatesSection.tsx @@ -7,8 +7,7 @@ import { Button, DeleteActionModal } from "@app/components/v2"; import { ProjectPermissionCertificateActions, ProjectPermissionSub, - useProject, - useSubscription + useProject } from "@app/context"; import { useDeleteCert } from "@app/hooks/api"; import { usePopUp } from "@app/hooks/usePopUp"; @@ -16,6 +15,7 @@ import { usePopUp } from "@app/hooks/usePopUp"; import { CertificateCertModal } from "./CertificateCertModal"; import { CertificateImportModal } from "./CertificateImportModal"; import { CertificateIssuanceModal } from "./CertificateIssuanceModal"; +import { CertificateManagePkiSyncsModal } from "./CertificateManagePkiSyncsModal"; import { CertificateManageRenewalModal } from "./CertificateManageRenewalModal"; import { CertificateModal } from "./CertificateModal"; import { CertificateRenewalModal } from "./CertificateRenewalModal"; @@ -24,10 +24,10 @@ import { CertificatesTable } from "./CertificatesTable"; export const CertificatesSection = () => { const { currentProject } = useProject(); - const { subscription } = useSubscription(); const { mutateAsync: deleteCert } = useDeleteCert(); - const isLegacyTemplatesEnabled = subscription.pkiLegacyTemplates; + // TODO: Use subscription.pkiLegacyTemplates to block legacy templates creation + const isLegacyTemplatesEnabled = true; const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "certificateIssuance", @@ -37,28 +37,21 @@ export const CertificatesSection = () => { "deleteCertificate", "revokeCertificate", "manageRenewal", - "renewCertificate" + "renewCertificate", + "managePkiSyncs" ] as const); const onRemoveCertificateSubmit = async (serialNumber: string) => { - try { - if (!currentProject?.slug) return; + if (!currentProject?.slug) return; - await deleteCert({ serialNumber, projectSlug: currentProject.slug }); + await deleteCert({ serialNumber, projectSlug: currentProject.slug }); - createNotification({ - text: "Successfully deleted certificate", - type: "success" - }); + createNotification({ + text: "Successfully deleted certificate", + type: "success" + }); - handlePopUpClose("deleteCertificate"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete certificate", - type: "error" - }); - } + handlePopUpClose("deleteCertificate"); }; return ( @@ -105,6 +98,10 @@ export const CertificatesSection = () => { + { return expiryDate <= oneDayFromNow; }; -const getAutoRenewalInfo = (certificate: TCertificate) => { - if (certificate.renewedByCertificateId) { - return { text: "Renewed", variant: "neutral" as const }; - } - - const isRevoked = certificate.status === CertStatus.REVOKED; - const isExpired = new Date(certificate.notAfter) < new Date(); - const hasNoProfile = !certificate.profileId; - const isExpiringWithinDay = isExpiringWithinOneDay(certificate.notAfter); - - if (isRevoked) { - return { - text: "Not Available", - variant: "neutral" as const, - tooltip: "Renewal is not available for revoked certificates" - }; - } - - if (isExpired) { - return { - text: "Not Available", - variant: "neutral" as const, - tooltip: "Renewal is not available for expired certificates" - }; - } - - if (hasNoProfile) { - return { - text: "Not Available", - variant: "neutral" as const, - tooltip: "Renewal requires a certificate profile" - }; - } - - if (certificate.hasPrivateKey === false) { - return { - text: "Not Available", - variant: "neutral" as const, - tooltip: "Renewal is not available for certificates with externally generated private keys" - }; - } - - if (isExpiringWithinDay) { - return { - text: "Not Available", - variant: "neutral" as const, - tooltip: "Auto-renewal is not available for certificates expiring within 24 hours" - }; - } - - if (certificate.renewalError) { - return { - text: "Failed", - variant: "danger" as const, - tooltip: certificate.renewalError - }; - } - - if (!certificate.renewBeforeDays) { - return { text: "Auto-Renewal Disabled", variant: "warning" as const }; - } - - const notAfterDate = new Date(certificate.notAfter); - const renewalDate = new Date( - notAfterDate.getTime() - certificate.renewBeforeDays * 24 * 60 * 60 * 1000 - ); - const now = new Date(); - - if (renewalDate <= now) { - return { text: "Due Now", variant: "danger" as const }; - } - - const daysUntilRenewal = Math.floor( - (renewalDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000) - ); - - if (daysUntilRenewal === 0) { - return { text: "Renews today", variant: "warning" as const }; - } - - if (daysUntilRenewal <= 7) { - return { text: `Renews in ${daysUntilRenewal}d`, variant: "warning" as const }; - } - - return { text: `Renews in ${daysUntilRenewal}d`, variant: "success" as const }; -}; - type Props = { handlePopUpOpen: ( popUpName: keyof UsePopUpState< @@ -152,7 +69,8 @@ type Props = { "revokeCertificate", "certificateCert", "manageRenewal", - "renewCertificate" + "renewCertificate", + "managePkiSyncs" ] >, data?: { @@ -175,7 +93,6 @@ const PER_PAGE_INIT = 25; export const CertificatesTable = ({ handlePopUpOpen }: Props) => { const [page, setPage] = useState(1); const [perPage, setPerPage] = useState(PER_PAGE_INIT); - const { subscription } = useSubscription(); const { currentProject } = useProject(); const { data, isPending } = useListWorkspaceCertificates({ @@ -185,7 +102,8 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { }); const { mutateAsync: updateRenewalConfig } = useUpdateRenewalConfig(); - const isLegacyTemplatesEnabled = subscription.pkiLegacyTemplates; + // TODO: Use subscription.pkiLegacyTemplates to block legacy templates creation + const isLegacyTemplatesEnabled = true; const { data: caData } = useListCasByProjectId(currentProject?.id ?? ""); @@ -200,32 +118,24 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { }, [caData]); const handleDisableAutoRenewal = async (certificateId: string, commonName: string) => { - try { - if (!currentProject?.slug) { - createNotification({ - text: "Unable to disable auto-renewal: Project not found. Please refresh the page and try again.", - type: "error" - }); - return; - } - - await updateRenewalConfig({ - certificateId, - projectSlug: currentProject.slug, - enableAutoRenewal: false - }); - + if (!currentProject?.slug) { createNotification({ - text: `Auto-renewal disabled for ${commonName}`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to disable auto-renewal. Please try again or contact support if the issue persists.", + text: "Unable to disable auto-renewal: Project not found. Please refresh the page and try again.", type: "error" }); + return; } + + await updateRenewalConfig({ + certificateId, + projectSlug: currentProject.slug, + enableAutoRenewal: false + }); + + createNotification({ + text: `Auto-renewal disabled for ${commonName}`, + type: "success" + }); }; return ( @@ -233,20 +143,18 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { - - - - - - + + + + - {isPending && } + {isPending && } {!isPending && data?.certificates.map((certificate) => { const { variant, label } = getCertValidUntilBadgeDetails(certificate.notAfter); - const autoRenewalInfo = getAutoRenewalInfo(certificate); const isRevoked = certificate.status === CertStatus.REVOKED; const isExpired = new Date(certificate.notAfter) < new Date(); @@ -255,9 +163,24 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { const isAutoRenewalEnabled = Boolean( certificate.renewBeforeDays && certificate.renewBeforeDays > 0 ); + + const canShowAutoRenewalIcon = Boolean( + certificate.profileId && + certificate.hasPrivateKey !== false && + !certificate.renewedByCertificateId && + !isRevoked && + !isExpired && + !isExpiringWithinDay + ); + + // Still need originalDisplayName for other uses in the component + const { originalDisplayName } = getCertificateDisplayName(certificate, 64, "—"); + return ( - - + + - - - {subscriberId ? ( - - ) : ( - - )} - - -
Common NameStatusNot BeforeNot AfterRenewal Status + SAN / CNStatusNot BeforeNot After
{certificate.commonName}
+ + {certificate.status === CertStatus.REVOKED ? ( Revoked @@ -275,22 +198,64 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { ? format(new Date(certificate.notAfter), "yyyy-MM-dd") : "-"} - {autoRenewalInfo && - (autoRenewalInfo.tooltip ? ( -
- - - {autoRenewalInfo.text} - - - -
- ) : ( - {autoRenewalInfo.text} - ))} -
+ +
{ + if (!canShowAutoRenewalIcon) return ""; + if (isAutoRenewalEnabled) return "opacity-100"; + return "opacity-0 group-hover:opacity-100"; + })()}`} + > + {canShowAutoRenewalIcon && ( + { + if (hasFailed && certificate.renewalError) { + return `Auto-renewal failed: ${certificate.renewalError}`; + } + if (isAutoRenewalEnabled) { + const expiryDate = new Date(certificate.notAfter); + const now = new Date(); + const daysUntilExpiry = Math.ceil( + (expiryDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000) + ); + const daysUntilRenewal = Math.max( + 0, + daysUntilExpiry - (certificate.renewBeforeDays || 0) + ); + return `Auto-renews in ${daysUntilRenewal}d`; + } + return "Set auto renewal"; + })()} + > + + + )} +
@@ -484,6 +449,33 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => { ); })()} + {/* PKI Sync management - only for active certificates that are not renewed */} + {certificate.status === CertStatus.ACTIVE && + !certificate.renewedByCertificateId && ( + + {(isAllowed) => ( + + handlePopUpOpen("managePkiSyncs", { + certificateId: certificate.id, + commonName: certificate.commonName + }) + } + disabled={!isAllowed} + icon={} + > + Manage PKI Syncs + + )} + + )} {/* Only show revoke button if CA supports revocation */} {(() => { const caType = caCapabilityMap[certificate.caId]; diff --git a/frontend/src/pages/cert-manager/CertificatesPage/route.tsx b/frontend/src/pages/cert-manager/CertificatesPage/route.tsx deleted file mode 100644 index 68deb41b9..000000000 --- a/frontend/src/pages/cert-manager/CertificatesPage/route.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { createFileRoute } from "@tanstack/react-router"; - -import { CertificatesPage } from "./CertificatesPage"; - -export const Route = createFileRoute( - "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificates" -)({ - component: CertificatesPage, - beforeLoad: ({ context }) => { - return { - breadcrumbs: [ - ...context.breadcrumbs, - { - label: "Certificates" - } - ] - }; - } -}); diff --git a/frontend/src/pages/cert-manager/IntegrationsListPage/components/PkiSyncsTab/PkiSyncTable/PkiSyncRow.tsx b/frontend/src/pages/cert-manager/IntegrationsListPage/components/PkiSyncsTab/PkiSyncTable/PkiSyncRow.tsx index 1c5bff98c..0b2ce65c1 100644 --- a/frontend/src/pages/cert-manager/IntegrationsListPage/components/PkiSyncsTab/PkiSyncTable/PkiSyncRow.tsx +++ b/frontend/src/pages/cert-manager/IntegrationsListPage/components/PkiSyncsTab/PkiSyncTable/PkiSyncRow.tsx @@ -12,7 +12,6 @@ import { faToggleOff, faToggleOn, faTrash, - faTriangleExclamation, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -47,7 +46,6 @@ import { useToggle } from "@app/hooks"; import { PkiSyncStatus, TPkiSync, usePkiSyncOption } from "@app/hooks/api/pkiSyncs"; import { PkiSyncDestinationCol } from "./PkiSyncDestinationCol"; -import { PkiSyncTableCell } from "./PkiSyncTableCell"; type Props = { pkiSync: TPkiSync; @@ -163,23 +161,6 @@ export const PkiSyncRow = ({

{destinationDetails.name}

- -
- - - Source Deleted - -
-
-
diff --git a/frontend/src/pages/cert-manager/IntegrationsListPage/components/PkiSyncsTab/PkiSyncTable/PkiSyncsTable.tsx b/frontend/src/pages/cert-manager/IntegrationsListPage/components/PkiSyncsTab/PkiSyncTable/PkiSyncsTable.tsx index ca4aa7473..68e7cf294 100644 --- a/frontend/src/pages/cert-manager/IntegrationsListPage/components/PkiSyncsTab/PkiSyncTable/PkiSyncsTable.tsx +++ b/frontend/src/pages/cert-manager/IntegrationsListPage/components/PkiSyncsTab/PkiSyncTable/PkiSyncsTable.tsx @@ -57,7 +57,6 @@ import { PkiSyncRow } from "./PkiSyncRow"; enum PkiSyncsOrderBy { Destination = "destination", - Source = "source", Name = "name", Status = "status" } @@ -160,14 +159,6 @@ export const PkiSyncsTable = ({ pkiSyncs }: Props) => { const [syncOne, syncTwo] = orderDirection === OrderByDirection.ASC ? [a, b] : [b, a]; switch (orderBy) { - case PkiSyncsOrderBy.Source: - return (syncOne.subscriber?.name ?? syncOne.subscriberId ?? "") - .toLowerCase() - .localeCompare( - syncTwo.subscriber?.name?.toLowerCase() ?? - syncTwo.subscriberId?.toLowerCase() ?? - "" - ); case PkiSyncsOrderBy.Destination: return getPkiSyncDestinationColValues(syncOne) .primaryText.toLowerCase() @@ -226,46 +217,32 @@ export const PkiSyncsTable = ({ pkiSyncs }: Props) => { const isAutoSyncEnabled = !pkiSync.isAutoSyncEnabled; - try { - await updateSync.mutateAsync({ - syncId: pkiSync.id, - projectId: pkiSync.projectId, - destination: pkiSync.destination, - isAutoSyncEnabled - }); + await updateSync.mutateAsync({ + syncId: pkiSync.id, + projectId: pkiSync.projectId, + destination: pkiSync.destination, + isAutoSyncEnabled + }); - createNotification({ - text: `Successfully ${isAutoSyncEnabled ? "enabled" : "disabled"} auto-sync for ${destinationName} Sync`, - type: "success" - }); - } catch { - createNotification({ - text: `Failed to ${isAutoSyncEnabled ? "enable" : "disable"} auto-sync for ${destinationName} Sync`, - type: "error" - }); - } + createNotification({ + text: `Successfully ${isAutoSyncEnabled ? "enabled" : "disabled"} auto-sync for ${destinationName} Sync`, + type: "success" + }); }; const handleTriggerSync = async (pkiSync: TPkiSync) => { const destinationName = PKI_SYNC_MAP[pkiSync.destination].name; - try { - await triggerSync.mutateAsync({ - syncId: pkiSync.id, - destination: pkiSync.destination, - projectId: pkiSync.projectId - }); + await triggerSync.mutateAsync({ + syncId: pkiSync.id, + destination: pkiSync.destination, + projectId: pkiSync.projectId + }); - createNotification({ - text: `Successfully triggered ${destinationName} Sync`, - type: "success" - }); - } catch { - createNotification({ - text: `Failed to trigger ${destinationName} Sync`, - type: "error" - }); - } + createNotification({ + text: `Successfully triggered ${destinationName} Sync`, + type: "success" + }); }; return ( @@ -370,7 +347,7 @@ export const PkiSyncsTable = ({ pkiSyncs }: Props) => {
- +
Name {
-
- Source - handleSort(PkiSyncsOrderBy.Source)} - > - - -
-
+
Destination {
+
Status { const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["addSync"] as const); - const { addSync, ...search } = useSearch({ + const { addSync, connectionId, connectionName, ...search } = useSearch({ from: ROUTE_PATHS.CertManager.IntegrationsListPage.id }); @@ -45,6 +46,42 @@ export const PkiSyncsTab = () => { navigateToBase(); }, [addSync, handlePopUpOpen, navigateToBase]); + useEffect(() => { + const storedFormData = localStorage.getItem("pkiSyncFormData"); + if (storedFormData && !popUp.addSync.isOpen) { + try { + const parsedData = JSON.parse(storedFormData); + if (connectionId && connectionName) { + const initialData = { + ...parsedData, + connection: { id: connectionId, name: connectionName } + }; + handlePopUpOpen("addSync", { destination: parsedData.destination, initialData }); + navigate({ + to: ROUTE_PATHS.CertManager.IntegrationsListPage.path, + params: { projectId: currentProject?.id }, + search: { selectedTab: IntegrationsListPageTabs.PkiSyncs }, + replace: true + }); + } else { + handlePopUpOpen("addSync", { destination: parsedData.destination }); + } + localStorage.removeItem("pkiSyncFormData"); + } catch (error) { + console.error("Failed to parse stored PKI sync form data:", error); + localStorage.removeItem("pkiSyncFormData"); + handlePopUpOpen("addSync"); + } + } + }, [ + handlePopUpOpen, + popUp.addSync.isOpen, + connectionId, + connectionName, + navigate, + currentProject?.id + ]); + const { data: pkiSyncs = [], isPending: isPkiSyncsPending } = useListPkiSyncs( currentProject?.id || "", { @@ -94,7 +131,8 @@ export const PkiSyncsTab = () => {
handlePopUpToggle("addSync", isOpen)} /> diff --git a/frontend/src/pages/cert-manager/IntegrationsListPage/route.tsx b/frontend/src/pages/cert-manager/IntegrationsListPage/route.tsx index 0a0868a07..a996a23b5 100644 --- a/frontend/src/pages/cert-manager/IntegrationsListPage/route.tsx +++ b/frontend/src/pages/cert-manager/IntegrationsListPage/route.tsx @@ -9,7 +9,9 @@ import { IntegrationsListPage } from "./IntegrationsListPage"; const IntegrationsListPageQuerySchema = z.object({ selectedTab: z.nativeEnum(IntegrationsListPageTabs).optional(), - addSync: z.nativeEnum(PkiSync).optional() + addSync: z.nativeEnum(PkiSync).optional(), + connectionId: z.string().optional(), + connectionName: z.string().optional() }); export const Route = createFileRoute( diff --git a/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/PkiCollectionDetailsByIDPage.tsx b/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/PkiCollectionDetailsByIDPage.tsx index b8235f488..319323545 100644 --- a/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/PkiCollectionDetailsByIDPage.tsx +++ b/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/PkiCollectionDetailsByIDPage.tsx @@ -45,31 +45,24 @@ export const PkiCollectionPage = () => { ] as const); const onDeletePkiCollectionSubmit = async (collectionIdToDelete: string) => { - try { - if (!projectId) return; + if (!projectId) return; - await deletePkiCollection({ - projectId, - collectionId: collectionIdToDelete - }); + await deletePkiCollection({ + projectId, + collectionId: collectionIdToDelete + }); - createNotification({ - text: "Successfully deleted PKI collection", - type: "success" - }); - handlePopUpClose("deletePkiCollection"); - navigate({ - to: "/projects/cert-management/$projectId/certificates", - params: { - projectId - } - }); - } catch { - createNotification({ - text: "Failed to delete PKI collection", - type: "error" - }); - } + createNotification({ + text: "Successfully deleted PKI collection", + type: "success" + }); + handlePopUpClose("deletePkiCollection"); + navigate({ + to: "/projects/cert-management/$projectId/policies", + params: { + projectId: params.projectId + } + }); }; return ( @@ -77,9 +70,9 @@ export const PkiCollectionPage = () => { {data && (
diff --git a/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/components/AddPkiCollectionItemModal.tsx b/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/components/AddPkiCollectionItemModal.tsx index 3bcc3dc31..1fd4d7d99 100644 --- a/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/components/AddPkiCollectionItemModal.tsx +++ b/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/components/AddPkiCollectionItemModal.tsx @@ -5,13 +5,9 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Modal, ModalContent, Select, SelectItem } from "@app/components/v2"; import { useProject } from "@app/context"; -import { - CaStatus, - useAddItemToPkiCollection, - useListWorkspaceCas, - useListWorkspaceCertificates -} from "@app/hooks/api"; +import { CaStatus, useAddItemToPkiCollection, useListWorkspaceCas } from "@app/hooks/api"; import { PkiItemType, pkiItemTypeToNameMap } from "@app/hooks/api/pkiCollections/constants"; +import { useListWorkspaceCertificates } from "@app/hooks/api/projects"; import { UsePopUpState } from "@app/hooks/usePopUp"; const schema = z diff --git a/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/routes.tsx b/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/routes.tsx index 7fef38221..712f3dedf 100644 --- a/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/routes.tsx +++ b/frontend/src/pages/cert-manager/PkiCollectionDetailsByIDPage/routes.tsx @@ -13,7 +13,7 @@ export const Route = createFileRoute( { label: "Certificate Collections", link: linkOptions({ - to: "/projects/cert-management/$projectId/certificates", + to: "/projects/cert-management/$projectId/policies", params: { projectId: params.projectId } diff --git a/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/PkiSubscriberDetailsByIDPage.tsx b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/PkiSubscriberDetailsByIDPage.tsx index 1f723b395..572ee998e 100644 --- a/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/PkiSubscriberDetailsByIDPage.tsx +++ b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/PkiSubscriberDetailsByIDPage.tsx @@ -51,30 +51,22 @@ const Page = () => { ] as const); const onRemoveSubscriberSubmit = async (subscriberNameToDelete: string) => { - try { - if (!projectId) return; + if (!projectId) return; - await deletePkiSubscriber({ subscriberName: subscriberNameToDelete, projectId }); + await deletePkiSubscriber({ subscriberName: subscriberNameToDelete, projectId }); - createNotification({ - text: "Successfully deleted subscriber", - type: "success" - }); + createNotification({ + text: "Successfully deleted subscriber", + type: "success" + }); - handlePopUpClose("deletePkiSubscriber"); - navigate({ - to: "/projects/cert-management/$projectId/subscribers", - params: { - projectId - } - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete subscriber", - type: "error" - }); - } + handlePopUpClose("deletePkiSubscriber"); + navigate({ + to: "/projects/cert-management/$projectId/subscribers", + params: { + projectId + } + }); }; return ( diff --git a/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberDetailsSection.tsx b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberDetailsSection.tsx index f9c94740a..5da5be55a 100644 --- a/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberDetailsSection.tsx +++ b/frontend/src/pages/cert-manager/PkiSubscriberDetailsByIDPage/components/PkiSubscriberDetailsSection.tsx @@ -70,36 +70,28 @@ export const PkiSubscriberDetailsSection = ({ subscriberName, handlePopUpOpen }: useOrderPkiSubscriberCert(); const onIssuePkiSubscriberCert = async () => { - try { - if (pkiSubscriber?.supportsImmediateCertIssuance) { - const response = await issuePkiSubscriberCert({ subscriberName, projectId }); + if (pkiSubscriber?.supportsImmediateCertIssuance) { + const response = await issuePkiSubscriberCert({ subscriberName, projectId }); - setCertificateDetails({ - serialNumber: response.serialNumber, - certificate: response.certificate, - certificateChain: response.certificateChain, - privateKey: response.privateKey - }); + setCertificateDetails({ + serialNumber: response.serialNumber, + certificate: response.certificate, + certificateChain: response.certificateChain, + privateKey: response.privateKey + }); - setIsModalOpen(true); + setIsModalOpen(true); - createNotification({ - text: "Successfully issued certificate", - type: "success" - }); - } else { - await orderPkiSubscriberCert({ subscriberName, projectId }); - - createNotification({ - text: "Successfully ordered certificate. It will be issued after CA processing which could take a few minutes.", - type: "info" - }); - } - } catch (err) { - console.error(err); createNotification({ - text: "Failed to issue certificate", - type: "error" + text: "Successfully issued certificate", + type: "success" + }); + } else { + await orderPkiSubscriberCert({ subscriberName, projectId }); + + createNotification({ + text: "Successfully ordered certificate. It will be issued after CA processing which could take a few minutes.", + type: "info" }); } }; diff --git a/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscriberModal.tsx b/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscriberModal.tsx index 4bc427e48..27c91851c 100644 --- a/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscriberModal.tsx +++ b/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscriberModal.tsx @@ -276,117 +276,121 @@ export const PkiSubscriberModal = ({ popUp, handlePopUpToggle }: Props) => { locality, emailAddress }: FormData) => { - try { - if (!projectId) return; - - if (!caId) { - createNotification({ - text: "Please select an Issuing CA", - type: "error" - }); - return; - } - - // Check if there is already a different subscriber with the same name - const existingNames = - subscribers?.filter((s) => s.id !== pkiSubscriber?.id).map((s) => s.name) || []; - - if (existingNames.includes(name.trim())) { - createNotification({ - text: "A subscriber with this name already exists.", - type: "error" - }); - return; - } - - // Validate Azure template for Azure ADCS CA - if (selectedCa?.type === CaType.AZURE_AD_CS && !azureTemplateType) { - createNotification({ - text: "Please select an Azure certificate template", - type: "error" - }); - return; - } - - const keyUsagesList = - selectedCa?.type === CaType.AZURE_AD_CS - ? [] - : Object.entries(keyUsages) - .filter(([, value]) => value) - .map(([key]) => key as CertKeyUsage); - - const extendedKeyUsagesList = - selectedCa?.type === CaType.AZURE_AD_CS - ? [] - : Object.entries(extendedKeyUsages) - .filter(([, value]) => value) - .map(([key]) => key as CertExtendedKeyUsage); - - const subjectAlternativeNamesList = subjectAlternativeNames - .split(",") - .map((san) => san.trim()) - .filter(Boolean); - - const autoRenewalPeriodInDays = enableAutoRenewal - ? convertTimeUnitValueToDays(renewalUnit, renewalBefore) - : undefined; - - // Build properties object - const properties = { - ...(selectedCa?.type === CaType.AZURE_AD_CS && azureTemplateType && { azureTemplateType }), - ...(organization && { organization }), - ...(organizationalUnit && { organizationalUnit }), - ...(country && { country }), - ...(state && { state }), - ...(locality && { locality }), - ...(emailAddress && { emailAddress }) - }; - - if (pkiSubscriber) { - await updateMutateAsync({ - subscriberName: pkiSubscriber.name, - projectId, - name, - caId, - commonName, - subjectAlternativeNames: subjectAlternativeNamesList, - ttl, - keyUsages: keyUsagesList, - extendedKeyUsages: extendedKeyUsagesList, - enableAutoRenewal, - autoRenewalPeriodInDays, - properties: Object.keys(properties).length > 0 ? properties : undefined - }); - } else { - await createMutateAsync({ - projectId, - name, - caId, - commonName, - subjectAlternativeNames: subjectAlternativeNamesList, - ttl, - keyUsages: keyUsagesList, - extendedKeyUsages: extendedKeyUsagesList, - enableAutoRenewal, - autoRenewalPeriodInDays, - properties: Object.keys(properties).length > 0 ? properties : undefined - }); - } - - reset(); - handlePopUpToggle("pkiSubscriber", false); + if (!projectId) return; + if (!caId) { createNotification({ - text: `Successfully ${pkiSubscriber ? "updated" : "added"} PKI subscriber`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${pkiSubscriber ? "update" : "add"} PKI subscriber`, + text: "Please select an Issuing CA", type: "error" }); + return; } + + // Check if there is already a different subscriber with the same name + const existingNames = + subscribers?.filter((s) => s.id !== pkiSubscriber?.id).map((s) => s.name) || []; + + if (existingNames.includes(name.trim())) { + createNotification({ + text: "A subscriber with this name already exists.", + type: "error" + }); + return; + } + + // Validate Azure template for Azure ADCS CA + if (selectedCa?.type === CaType.AZURE_AD_CS && !azureTemplateType) { + createNotification({ + text: "Please select an Azure certificate template", + type: "error" + }); + return; + } + + const keyUsagesList = + selectedCa?.type === CaType.AZURE_AD_CS + ? [] + : Object.entries(keyUsages) + .filter(([, value]) => value) + .map(([key]) => key as CertKeyUsage); + + const extendedKeyUsagesList = + selectedCa?.type === CaType.AZURE_AD_CS + ? [] + : Object.entries(extendedKeyUsages) + .filter(([, value]) => value) + .map(([key]) => key as CertExtendedKeyUsage); + + const subjectAlternativeNamesList = subjectAlternativeNames + .split(",") + .map((san) => san.trim()) + .filter(Boolean); + + const autoRenewalPeriodInDays = enableAutoRenewal + ? convertTimeUnitValueToDays(renewalUnit, renewalBefore) + : undefined; + + // Build properties object + const properties = { + ...(selectedCa?.type === CaType.AZURE_AD_CS && azureTemplateType && { azureTemplateType }), + ...(organization && { organization }), + ...(organizationalUnit && { organizationalUnit }), + ...(country && { country }), + ...(state && { state }), + ...(locality && { locality }), + ...(emailAddress && { emailAddress }) + }; + + if (pkiSubscriber) { + await updateMutateAsync({ + subscriberName: pkiSubscriber.name, + projectId, + name, + caId, + commonName, + subjectAlternativeNames: subjectAlternativeNamesList, + ttl, + keyUsages: keyUsagesList.map((key) => + key === CertKeyUsage.CRL_SIGN + ? "cRLSign" + : key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()) + ), + extendedKeyUsages: extendedKeyUsagesList.map((key) => + key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()) + ), + enableAutoRenewal, + autoRenewalPeriodInDays, + properties: Object.keys(properties).length > 0 ? properties : undefined + }); + } else { + await createMutateAsync({ + projectId, + name, + caId, + commonName, + subjectAlternativeNames: subjectAlternativeNamesList, + ttl, + keyUsages: keyUsagesList.map((key) => + key === CertKeyUsage.CRL_SIGN + ? "cRLSign" + : key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()) + ), + extendedKeyUsages: extendedKeyUsagesList.map((key) => + key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()) + ), + enableAutoRenewal, + autoRenewalPeriodInDays, + properties: Object.keys(properties).length > 0 ? properties : undefined + }); + } + + reset(); + handlePopUpToggle("pkiSubscriber", false); + + createNotification({ + text: `Successfully ${pkiSubscriber ? "updated" : "added"} PKI subscriber`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscriberSection.tsx b/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscriberSection.tsx index cb4b9ef39..ae09cde2e 100644 --- a/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscriberSection.tsx +++ b/frontend/src/pages/cert-manager/PkiSubscribersPage/components/PkiSubscriberSection.tsx @@ -7,8 +7,7 @@ import { Button, DeleteActionModal } from "@app/components/v2"; import { ProjectPermissionPkiSubscriberActions, ProjectPermissionSub, - useProject, - useSubscription + useProject } from "@app/context"; import { useDeletePkiSubscriber, useUpdatePkiSubscriber } from "@app/hooks/api"; import { PkiSubscriberStatus } from "@app/hooks/api/pkiSubscriber/types"; @@ -19,10 +18,10 @@ import { PkiSubscribersTable } from "./PkiSubscribersTable"; export const PkiSubscriberSection = () => { const { currentProject } = useProject(); - const { subscription } = useSubscription(); const projectId = currentProject.id; - const canCreateLegacySubscribers = subscription.pkiLegacyTemplates; + // TODO: Use subscription.pkiLegacyTemplates to block legacy templates creation + const canCreateLegacySubscribers = true; const { mutateAsync: deletePkiSubscriber } = useDeletePkiSubscriber(); const { mutateAsync: updatePkiSubscriber } = useUpdatePkiSubscriber(); @@ -33,22 +32,14 @@ export const PkiSubscriberSection = () => { ] as const); const onRemovePkiSubscriberSubmit = async (subscriberName: string) => { - try { - const subscriber = await deletePkiSubscriber({ subscriberName, projectId }); + const subscriber = await deletePkiSubscriber({ subscriberName, projectId }); - createNotification({ - text: `Successfully deleted PKI subscriber: ${subscriber.name}`, - type: "success" - }); + createNotification({ + text: `Successfully deleted PKI subscriber: ${subscriber.name}`, + type: "success" + }); - handlePopUpClose("deletePkiSubscriber"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete PKI subscriber", - type: "error" - }); - } + handlePopUpClose("deletePkiSubscriber"); }; const onUpdatePkiSubscriberStatus = async ({ @@ -58,24 +49,16 @@ export const PkiSubscriberSection = () => { subscriberName: string; status: PkiSubscriberStatus; }) => { - try { - if (!currentProject?.slug) return; + if (!currentProject?.slug) return; - await updatePkiSubscriber({ subscriberName, projectId, status }); + await updatePkiSubscriber({ subscriberName, projectId, status }); - createNotification({ - text: `Successfully ${status === PkiSubscriberStatus.ACTIVE ? "enabled" : "disabled"} subscriber`, - type: "success" - }); + createNotification({ + text: `Successfully ${status === PkiSubscriberStatus.ACTIVE ? "enabled" : "disabled"} subscriber`, + type: "success" + }); - handlePopUpClose("pkiSubscriberStatus"); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${status === PkiSubscriberStatus.ACTIVE ? "enable" : "disable"} subscriber`, - type: "error" - }); - } + handlePopUpClose("pkiSubscriberStatus"); }; const subscriberStatusData = popUp?.pkiSubscriberStatus?.data as { diff --git a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/PkiSyncDetailsByIDPage.tsx b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/PkiSyncDetailsByIDPage.tsx index 76cc0ec7b..b1b1b2836 100644 --- a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/PkiSyncDetailsByIDPage.tsx +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/PkiSyncDetailsByIDPage.tsx @@ -18,10 +18,10 @@ import { IntegrationsListPageTabs } from "@app/types/integrations"; import { PkiSyncActionTriggers, PkiSyncAuditLogsSection, + PkiSyncCertificatesSection, PkiSyncDestinationSection, PkiSyncDetailsSection, - PkiSyncOptionsSection, - PkiSyncSourceSection + PkiSyncOptionsSection } from "./components"; const PageContent = () => { @@ -62,7 +62,6 @@ const PageContent = () => { const destinationDetails = PKI_SYNC_MAP[pkiSync.destination]; const handleEditDetails = () => handlePopUpOpen("editSync", PkiSyncEditFields.Details); - const handleEditSource = () => handlePopUpOpen("editSync", PkiSyncEditFields.Source); const handleEditOptions = () => handlePopUpOpen("editSync", PkiSyncEditFields.Options); const handleEditDestination = () => handlePopUpOpen("editSync", PkiSyncEditFields.Destination); @@ -103,7 +102,6 @@ const PageContent = () => {
-
@@ -111,6 +109,7 @@ const PageContent = () => { pkiSync={pkiSync} onEditDestination={handleEditDestination} /> +
diff --git a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncActionTriggers.tsx b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncActionTriggers.tsx index bb8e9d02a..5171b4d95 100644 --- a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncActionTriggers.tsx +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncActionTriggers.tsx @@ -86,44 +86,28 @@ export const PkiSyncActionTriggers = ({ pkiSync }: Props) => { }, [pkiSync.id, setIsIdCopied]); const handleTriggerSync = useCallback(async () => { - try { - await triggerSyncMutation.mutateAsync({ - syncId: id, - destination, - projectId - }); - createNotification({ - text: "PKI sync job queued successfully", - type: "success" - }); - } catch (error) { - console.error("Failed to trigger sync:", error); - createNotification({ - text: "Failed to trigger PKI sync", - type: "error" - }); - } + await triggerSyncMutation.mutateAsync({ + syncId: id, + destination, + projectId + }); + createNotification({ + text: "PKI sync job queued successfully", + type: "success" + }); }, [triggerSyncMutation, id, destination, projectId]); const handleToggleAutoSync = useCallback(async () => { - try { - await updatePkiSyncMutation.mutateAsync({ - syncId: id, - projectId, - destination, - isAutoSyncEnabled: !pkiSync.isAutoSyncEnabled - }); - createNotification({ - text: `Auto-sync ${pkiSync.isAutoSyncEnabled ? "disabled" : "enabled"} successfully`, - type: "success" - }); - } catch (error) { - console.error("Failed to toggle auto-sync:", error); - createNotification({ - text: "Failed to toggle auto-sync", - type: "error" - }); - } + await updatePkiSyncMutation.mutateAsync({ + syncId: id, + projectId, + destination, + isAutoSyncEnabled: !pkiSync.isAutoSyncEnabled + }); + createNotification({ + text: `Auto-sync ${pkiSync.isAutoSyncEnabled ? "disabled" : "enabled"} successfully`, + type: "success" + }); }, [updatePkiSyncMutation, id, projectId, pkiSync.isAutoSyncEnabled]); const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, { diff --git a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncAuditLogsSection.tsx b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncAuditLogsSection.tsx index 0f9f4a90c..6ef633091 100644 --- a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncAuditLogsSection.tsx +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncAuditLogsSection.tsx @@ -26,7 +26,7 @@ export const PkiSyncAuditLogsSection = ({ pkiSync }: Props) => { return (
-

Sync Logs

+

Sync Logs

{subscription.auditLogs && (

Displaying audit logs from the last {Math.min(auditLogsRetentionDays, 60)} days diff --git a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncCertificatesSection.tsx b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncCertificatesSection.tsx new file mode 100644 index 000000000..aafff85cd --- /dev/null +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncCertificatesSection.tsx @@ -0,0 +1,328 @@ +import { useState } from "react"; +import { subject } from "@casl/ability"; +import { + faCertificate, + faClockRotateLeft, + faEdit, + faTrash +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { createNotification } from "@app/components/notifications"; +import { ProjectPermissionCan } from "@app/components/permissions"; +import { CertificateManagementModal } from "@app/components/pki-syncs/CertificateManagementModal"; +import { + CertificateDisplayName, + getCertificateDisplayName +} from "@app/components/utilities/certificateDisplayUtils"; +import { + DeleteActionModal, + EmptyState, + IconButton, + Pagination, + Table, + TableContainer, + TBody, + Td, + Th, + THead, + Tooltip, + Tr +} from "@app/components/v2"; +import { Badge } from "@app/components/v3"; +import { ProjectPermissionSub } from "@app/context"; +import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types"; +import { useListPkiSyncCertificates, useRemoveCertificatesFromPkiSync } from "@app/hooks/api"; +import { CertificateSyncStatus, TPkiSync } from "@app/hooks/api/pkiSyncs"; + +type Props = { + pkiSync: TPkiSync; +}; + +const getSyncStatusVariant = (status?: CertificateSyncStatus | null) => { + if (status === CertificateSyncStatus.Succeeded) return "success"; + if (status === CertificateSyncStatus.Failed) return "danger"; + if (status === CertificateSyncStatus.Syncing) return "neutral"; + return "project"; +}; + +const getSyncStatusText = (status?: CertificateSyncStatus | null) => { + if (status === CertificateSyncStatus.Succeeded) return "Synced"; + if (status === CertificateSyncStatus.Failed) return "Failed"; + if (status === CertificateSyncStatus.Syncing) return "Syncing"; + if (status === CertificateSyncStatus.Pending) return "Pending"; + return "Unknown"; +}; + +const getCertificateStatusVariant = (isExpired: boolean, isRevoked: boolean) => { + if (isRevoked) return "danger"; + if (isExpired) return "danger"; + return "success"; +}; + +const getCertificateStatusText = (isExpired: boolean, isRevoked: boolean) => { + if (isRevoked) return "Revoked"; + if (isExpired) return "Expired"; + return "Active"; +}; + +export const PkiSyncCertificatesSection = ({ pkiSync }: Props) => { + const [isManageModalOpen, setIsManageModalOpen] = useState(false); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [certificateToDelete, setCertificateToDelete] = useState<{ + id: string; + displayName: string; + } | null>(null); + const [currentPage, setCurrentPage] = useState(1); + const pageSize = 10; + + const { data, refetch: refetchSyncCertificates } = useListPkiSyncCertificates(pkiSync.id, { + offset: (currentPage - 1) * pageSize, + limit: pageSize + }); + const syncCertificates = data?.certificates || []; + const totalCount = data?.totalCount || 0; + const removeCertificatesFromSync = useRemoveCertificatesFromPkiSync(); + + const permissionSubject = subject(ProjectPermissionSub.PkiSyncs, { + subscriberId: pkiSync.subscriberId || "" + }); + + const handleRemoveCertificate = async (certificateId: string) => { + try { + await removeCertificatesFromSync.mutateAsync({ + pkiSyncId: pkiSync.id, + certificateIds: [certificateId] + }); + + await refetchSyncCertificates(); + + createNotification({ + text: "Certificate removed from sync", + type: "success" + }); + + setIsDeleteModalOpen(false); + setCertificateToDelete(null); + } catch { + createNotification({ + text: "Failed to remove certificate from sync", + type: "error" + }); + } + }; + + const handleDeleteClick = (certificateId: string, displayName: string) => { + setCertificateToDelete({ id: certificateId, displayName }); + setIsDeleteModalOpen(true); + }; + + const totalPages = Math.ceil(totalCount / pageSize); + + return ( +

+
+
+

Certificates

+ + {(isAllowed) => ( + setIsManageModalOpen(true)} + > + + + )} + +
+ +
+
+ + + + + + + + + + + + + {syncCertificates.map((syncCert) => { + const isExpired = syncCert.certificateNotAfter + ? new Date(syncCert.certificateNotAfter) < new Date() + : false; + const isRevoked = syncCert.certificateStatus === "revoked"; + + // Calculate auto-renewal timeline + const hasAutoRenewal = Boolean( + syncCert.certificateRenewBeforeDays && + syncCert.certificateRenewBeforeDays > 0 && + !syncCert.certificateRenewalError && + syncCert.certificateNotAfter + ); + + const daysUntilRenewal = + hasAutoRenewal && syncCert.certificateNotAfter + ? (() => { + const expiryDate = new Date(syncCert.certificateNotAfter); + const renewalDate = new Date( + expiryDate.getTime() - + syncCert.certificateRenewBeforeDays! * 24 * 60 * 60 * 1000 + ); + const now = new Date(); + const diffInMs = renewalDate.getTime() - now.getTime(); + return Math.max(0, Math.ceil(diffInMs / (24 * 60 * 60 * 1000))); + })() + : null; + + const { originalDisplayName } = getCertificateDisplayName( + { + altNames: syncCert.certificateAltNames, + commonName: syncCert.certificateCommonName + }, + 34, + "Unknown" + ); + + return ( + + + + + + + + + ); + })} + +
SAN / CNCertificate StatusSerial NumberSync StatusExpires At +
+ + + + {getCertificateStatusText(isExpired, isRevoked)} + + +
+ {(() => { + const serial = syncCert.certificateSerialNumber; + if (!serial || serial === "Unknown") return "Unknown"; + if (serial.length <= 8) return serial; + return `${serial.substring(0, 4)}...${serial.substring(serial.length - 4)}`; + })()} +
+
+ {syncCert.lastSyncMessage && + syncCert.syncStatus === CertificateSyncStatus.Failed ? ( + + Failed + + ) : ( + + {getSyncStatusText(syncCert.syncStatus)} + + )} + + + {syncCert.certificateNotAfter + ? new Date(syncCert.certificateNotAfter).toLocaleDateString() + : "Unknown"} + + + {hasAutoRenewal && daysUntilRenewal !== null && ( + +
+ +
+
+ )} + + {(isAllowed) => ( + + handleDeleteClick(syncCert.certificateId, originalDisplayName) + } + > + + + )} + +
+ {syncCertificates.length === 0 && ( + + )} +
+ {/* Pagination */} + {totalPages > 1 && ( +
+ setCurrentPage(page)} + onChangePerPage={() => {}} + /> +
+ )} +
+
+
+ + setIsManageModalOpen(false)} + onCertificatesUpdated={() => { + refetchSyncCertificates(); + }} + /> + + { + setIsDeleteModalOpen(false); + setCertificateToDelete(null); + }} + title="Remove Certificate from Sync" + subTitle={`Are you sure you want to remove "${certificateToDelete?.displayName}" from this PKI sync?`} + deleteKey="confirm" + onDeleteApproved={async () => { + if (certificateToDelete) { + await handleRemoveCertificate(certificateToDelete.id); + } + }} + buttonText="Remove Certificate" + /> +
+ ); +}; diff --git a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection.tsx b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection.tsx index af16fbffd..1c6bb8a04 100644 --- a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection.tsx +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection.tsx @@ -11,12 +11,15 @@ import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionC import { PKI_SYNC_MAP } from "@app/helpers/pkiSyncs"; import { PkiSync, TPkiSync } from "@app/hooks/api/pkiSyncs"; -import { AzureKeyVaultPkiSyncDestinationSection } from "./PkiSyncDestinationSection/index"; +import { + AwsCertificateManagerPkiSyncDestinationSection, + AzureKeyVaultPkiSyncDestinationSection +} from "./PkiSyncDestinationSection/index"; const GenericFieldLabel = ({ label, children }: { label: string; children: React.ReactNode }) => ( -
- -
{children}
+
+

{label}

+
{children}
); @@ -32,6 +35,9 @@ export const PkiSyncDestinationSection = ({ pkiSync, onEditDestination }: Props) let DestinationComponents: ReactNode; switch (destination) { + case PkiSync.AwsCertificateManager: + DestinationComponents = ; + break; case PkiSync.AzureKeyVault: DestinationComponents = ; break; @@ -47,7 +53,7 @@ export const PkiSyncDestinationSection = ({ pkiSync, onEditDestination }: Props) return (
-

Destination Configuration

+

Destination Configuration

{(isAllowed) => (
-
+
{pkiSync.appConnectionName || "Default Connection"} diff --git a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/AwsCertificateManagerPkiSyncDestinationSection.tsx b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/AwsCertificateManagerPkiSyncDestinationSection.tsx new file mode 100644 index 000000000..88ffbbb93 --- /dev/null +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/AwsCertificateManagerPkiSyncDestinationSection.tsx @@ -0,0 +1,21 @@ +import { TPkiSync } from "@app/hooks/api/pkiSyncs"; + +const GenericFieldLabel = ({ label, children }: { label: string; children: React.ReactNode }) => ( +
+

{label}

+
{children}
+
+); + +type Props = { + pkiSync: TPkiSync; +}; + +export const AwsCertificateManagerPkiSyncDestinationSection = ({ pkiSync }: Props) => { + const region = + pkiSync.destinationConfig && "region" in pkiSync.destinationConfig + ? pkiSync.destinationConfig.region + : undefined; + + return {region || "Not specified"}; +}; diff --git a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/AzureKeyVaultPkiSyncDestinationSection.tsx b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/AzureKeyVaultPkiSyncDestinationSection.tsx index 7c1cc8b14..b9bc7462c 100644 --- a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/AzureKeyVaultPkiSyncDestinationSection.tsx +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/AzureKeyVaultPkiSyncDestinationSection.tsx @@ -2,9 +2,9 @@ import { TAzureKeyVaultPkiSync } from "@app/hooks/api/pkiSyncs/types/azure-key-vault-sync"; const GenericFieldLabel = ({ label, children }: { label: string; children: React.ReactNode }) => ( -
- -
{children}
+
+

{label}

+
{children}
); diff --git a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/index.ts b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/index.ts index 5c8823388..4a1728f42 100644 --- a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/index.ts +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDestinationSection/index.ts @@ -1 +1,2 @@ +export { AwsCertificateManagerPkiSyncDestinationSection } from "./AwsCertificateManagerPkiSyncDestinationSection"; export { AzureKeyVaultPkiSyncDestinationSection } from "./AzureKeyVaultPkiSyncDestinationSection"; diff --git a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDetailsSection.tsx b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDetailsSection.tsx index 7514bab50..a3d95d5c3 100644 --- a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDetailsSection.tsx +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncDetailsSection.tsx @@ -21,9 +21,9 @@ const GenericFieldLabel = ({ children: React.ReactNode; labelClassName?: string; }) => ( -
- -
{children}
+
+

{label}

+
{children}
); @@ -57,7 +57,7 @@ export const PkiSyncDetailsSection = ({ pkiSync, onEditDetails }: Props) => { return (
-

Details

+

Details

{(isAllowed) => ( { )}
-
-
- {name} - {description || "None"} - - {subscriber ? subscriber.name : "Subscriber deleted"} +
+ {name} + {description || "None"} + {subscriber && ( + {subscriber.name} + )} + {syncStatus && ( + + - {syncStatus && ( - - - - )} - {lastSyncedAt && ( - - {format(new Date(lastSyncedAt), "yyyy-MM-dd, h:mm aaa")} - - )} - {syncStatus === PkiSyncStatus.Failed && failureMessage && ( - -

- {failureMessage} -

-
- )} -
+ )} + {lastSyncedAt && ( + + {format(new Date(lastSyncedAt), "yyyy-MM-dd, h:mm aaa")} + + )} + {syncStatus === PkiSyncStatus.Failed && failureMessage && ( + +

{failureMessage}

+
+ )}
); diff --git a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncOptionsSection/PkiSyncOptionsSection.tsx b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncOptionsSection/PkiSyncOptionsSection.tsx index b6365f1cd..e9a509ebb 100644 --- a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncOptionsSection/PkiSyncOptionsSection.tsx +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/PkiSyncOptionsSection/PkiSyncOptionsSection.tsx @@ -3,13 +3,27 @@ import { faEdit } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { ProjectPermissionCan } from "@app/components/permissions"; -import { GenericFieldLabel } from "@app/components/secret-syncs"; import { IconButton } from "@app/components/v2"; import { Badge } from "@app/components/v3"; import { ProjectPermissionSub } from "@app/context"; import { ProjectPermissionPkiSyncActions } from "@app/context/ProjectPermissionContext/types"; import { TPkiSync } from "@app/hooks/api/pkiSyncs"; +const GenericFieldLabel = ({ + label, + children, + labelClassName +}: { + label: string; + children: React.ReactNode; + labelClassName?: string; +}) => ( +
+

{label}

+
{children}
+
+); + type Props = { pkiSync: TPkiSync; onEditOptions: VoidFunction; @@ -28,7 +42,7 @@ export const PkiSyncOptionsSection = ({ pkiSync, onEditOptions }: Props) => {
-

Sync Options

+

Sync Options

{(isAllowed) => ( { )}
-
-
- {/* Hidden for now - Import certificates functionality disabled +
+ {/* Hidden for now - Import certificates functionality disabled {canImportCertificates ? "Enabled" : "Disabled"} */} - - - {canRemoveCertificates ? "Enabled" : "Disabled"} - - -
+ + + {canRemoveCertificates ? "Enabled" : "Disabled"} + +
diff --git a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/index.ts b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/index.ts index 06fe5181e..55a877bff 100644 --- a/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/index.ts +++ b/frontend/src/pages/cert-manager/PkiSyncDetailsByIDPage/components/index.ts @@ -1,5 +1,6 @@ export { PkiSyncActionTriggers } from "./PkiSyncActionTriggers"; export { PkiSyncAuditLogsSection } from "./PkiSyncAuditLogsSection"; +export { PkiSyncCertificatesSection } from "./PkiSyncCertificatesSection"; export { PkiSyncDestinationSection } from "./PkiSyncDestinationSection"; export { PkiSyncDetailsSection } from "./PkiSyncDetailsSection"; export { PkiSyncOptionsSection } from "./PkiSyncOptionsSection"; diff --git a/frontend/src/pages/cert-manager/PkiTemplateListPage/PkiTemplateListPage.tsx b/frontend/src/pages/cert-manager/PkiTemplateListPage/PkiTemplateListPage.tsx index b395d0ec2..41574ce5e 100644 --- a/frontend/src/pages/cert-manager/PkiTemplateListPage/PkiTemplateListPage.tsx +++ b/frontend/src/pages/cert-manager/PkiTemplateListPage/PkiTemplateListPage.tsx @@ -78,25 +78,17 @@ export const PkiTemplateListPage = () => { const deleteCertTemplate = useDeleteCertTemplateV2(); const onRemovePkiSubscriberSubmit = async () => { - try { - const pkiTemplate = await deleteCertTemplate.mutateAsync({ - projectId: currentProject.id, - templateName: popUp?.deleteTemplate?.data?.name - }); + const pkiTemplate = await deleteCertTemplate.mutateAsync({ + projectId: currentProject.id, + templateName: popUp?.deleteTemplate?.data?.name + }); - createNotification({ - text: `Successfully deleted PKI template: ${pkiTemplate.name}`, - type: "success" - }); + createNotification({ + text: `Successfully deleted PKI template: ${pkiTemplate.name}`, + type: "success" + }); - handlePopUpClose("deleteTemplate"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete PKI subscriber", - type: "error" - }); - } + handlePopUpClose("deleteTemplate"); }; return ( @@ -114,30 +106,29 @@ export const PkiTemplateListPage = () => { />
- {subscription?.pkiLegacyTemplates && ( -
-

Templates

-
- - {(isAllowed) => ( - - )} - -
+ {/* TODO: Use subscription.pkiLegacyTemplates to block legacy templates creation */} +
+

Templates

+
+ + {(isAllowed) => ( + + )} +
- )} +
@@ -297,7 +288,7 @@ export const PkiTemplateListPage = () => { handlePopUpToggle("estUpgradePlan", isOpen)} - text="You can only configure template enrollment methods if you switch to Infisical's Enterprise plan." + text="Your current plan does not include access to configuring template enrollment methods. To unlock this feature, please upgrade to Infisical Enterprise plan." isEnterpriseFeature={popUp.estUpgradePlan.data?.isEnterpriseFeature} /> diff --git a/frontend/src/pages/cert-manager/PkiTemplateListPage/components/PkiTemplateForm.tsx b/frontend/src/pages/cert-manager/PkiTemplateListPage/components/PkiTemplateForm.tsx index 093a2fcc1..d0decf3f6 100644 --- a/frontend/src/pages/cert-manager/PkiTemplateListPage/components/PkiTemplateForm.tsx +++ b/frontend/src/pages/cert-manager/PkiTemplateListPage/components/PkiTemplateForm.tsx @@ -128,59 +128,59 @@ export const PkiTemplateForm = ({ certTemplate, handlePopUpToggle }: Props) => { return; } - try { - if (certTemplate) { - await updateCertTemplate({ - templateName: certTemplate.name, - projectId: currentProject.id, - caName: ca.name, - name, - commonName, - subjectAlternativeName, - ttl, - keyUsages: Object.entries(keyUsages) - .filter(([, value]) => value) - .map(([key]) => key as CertKeyUsage), - extendedKeyUsages: Object.entries(extendedKeyUsages) - .filter(([, value]) => value) - .map(([key]) => key as CertExtendedKeyUsage) - }); + if (certTemplate) { + await updateCertTemplate({ + templateName: certTemplate.name, + projectId: currentProject.id, + caName: ca.name, + name, + commonName, + subjectAlternativeName, + ttl, + keyUsages: Object.entries(keyUsages) + .filter(([, value]) => value) + .map(([key]) => + key === CertKeyUsage.CRL_SIGN + ? "cRLSign" + : key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()) + ), + extendedKeyUsages: Object.entries(extendedKeyUsages) + .filter(([, value]) => value) + .map(([key]) => key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())) + }); - createNotification({ - text: "Successfully updated certificate template", - type: "success" - }); - } else { - await createCertTemplate({ - projectId: currentProject.id, - caName: ca.name, - name, - commonName, - subjectAlternativeName, - ttl, - keyUsages: Object.entries(keyUsages) - .filter(([, value]) => value) - .map(([key]) => key as CertKeyUsage), - extendedKeyUsages: Object.entries(extendedKeyUsages) - .filter(([, value]) => value) - .map(([key]) => key as CertExtendedKeyUsage) - }); - - createNotification({ - text: "Successfully created certificate template", - type: "success" - }); - } - - reset(); - handlePopUpToggle(false); - } catch (err) { - console.error(err); createNotification({ - text: "Failed to save changes", - type: "error" + text: "Successfully updated certificate template", + type: "success" + }); + } else { + await createCertTemplate({ + projectId: currentProject.id, + caName: ca.name, + name, + commonName, + subjectAlternativeName, + ttl, + keyUsages: Object.entries(keyUsages) + .filter(([, value]) => value) + .map(([key]) => + key === CertKeyUsage.CRL_SIGN + ? "cRLSign" + : key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()) + ), + extendedKeyUsages: Object.entries(extendedKeyUsages) + .filter(([, value]) => value) + .map(([key]) => key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())) + }); + + createNotification({ + text: "Successfully created certificate template", + type: "success" }); } + + reset(); + handlePopUpToggle(false); }; return ( diff --git a/frontend/src/pages/cert-manager/PoliciesPage/PoliciesPage.tsx b/frontend/src/pages/cert-manager/PoliciesPage/PoliciesPage.tsx index f6bc792fe..5b2988775 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/PoliciesPage.tsx +++ b/frontend/src/pages/cert-manager/PoliciesPage/PoliciesPage.tsx @@ -2,17 +2,20 @@ import { useState } from "react"; import { Helmet } from "react-helmet"; import { useTranslation } from "react-i18next"; -import { ProjectPermissionCan } from "@app/components/permissions"; import { ContentLoader, PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context"; +import { useProject } from "@app/context"; import { ProjectType } from "@app/hooks/api/projects/types"; import { CertificateProfilesTab } from "./components/CertificateProfilesTab"; +import { CertificatesTab } from "./components/CertificatesTab"; import { CertificateTemplatesV2Tab } from "./components/CertificateTemplatesV2Tab"; +import { PkiCollectionsTab } from "./components/PkiCollectionsTab"; enum TabSections { CertificateProfiles = "profiles", - CertificateTemplatesV2 = "templates-v2" + CertificateTemplatesV2 = "templates-v2", + Certificates = "certificates", + PkiCollections = "pki-collections" } export const PoliciesPage = () => { @@ -25,59 +28,54 @@ export const PoliciesPage = () => { } return ( - - {(isAllowed) => { - if (!isAllowed) { - return ( -
-
-

You don't have permission to access certificate policies.

-
-
- ); - } +
+ + {t("common.head-title", { title: "Certificate Management" })} + +
+ - return ( -
- - {t("common.head-title", { title: "Certificate Policies" })} - -
- + setActiveTab(value as TabSections)} + > + + + Certificate Profiles + + + Certificate Templates + + + Certificates + + + Certificate Collections + + - setActiveTab(value as TabSections)} - > - - - Certificate Profiles - - - Certificate Templates - - + + + - - - + + + - - - - -
-
- ); - }} - + + + + + + + + +
+
); }; diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CertificateProfilesTab.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CertificateProfilesTab.tsx index d034aeda1..7938d04f5 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CertificateProfilesTab.tsx +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CertificateProfilesTab.tsx @@ -51,22 +51,15 @@ export const CertificateProfilesTab = () => { const handleDeleteConfirm = async () => { if (!selectedProfile) return; - try { - await deleteProfile.mutateAsync({ - profileId: selectedProfile.id - }); - setIsDeleteModalOpen(false); - setSelectedProfile(null); - createNotification({ - text: `Certificate profile "${selectedProfile.slug}" deleted successfully`, - type: "success" - }); - } catch (error) { - console.error( - `Failed to delete profile "${selectedProfile.slug}" (ID: ${selectedProfile.id}):`, - error - ); - } + await deleteProfile.mutateAsync({ + profileId: selectedProfile.id + }); + setIsDeleteModalOpen(false); + setSelectedProfile(null); + createNotification({ + text: `Certificate profile "${selectedProfile.slug}" deleted successfully`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CreateProfileModal.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CreateProfileModal.tsx index ea82fd843..9f13acb63 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CreateProfileModal.tsx +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CreateProfileModal.tsx @@ -236,64 +236,56 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" } }, [isEdit, profile, reset]); const onFormSubmit = async (data: FormData) => { - try { - if (!currentProject?.id && !isEdit) return; + if (!currentProject?.id && !isEdit) return; - if (isEdit) { - const updateData: TUpdateCertificateProfileDTO = { - profileId: profile.id, - slug: data.slug, - description: data.description - }; + if (isEdit) { + const updateData: TUpdateCertificateProfileDTO = { + profileId: profile.id, + slug: data.slug, + description: data.description + }; - if (data.enrollmentType === "est" && data.estConfig) { - updateData.estConfig = data.estConfig; - } else if (data.enrollmentType === "api" && data.apiConfig) { - updateData.apiConfig = data.apiConfig; - } - - await updateProfile.mutateAsync(updateData); - } else { - if (!currentProject?.id) { - throw new Error("Project ID is required for creating a profile"); - } - - const createData: TCreateCertificateProfileDTO = { - projectId: currentProject.id, - slug: data.slug, - description: data.description, - enrollmentType: data.enrollmentType, - caId: data.certificateAuthorityId, - certificateTemplateId: data.certificateTemplateId - }; - - if (data.enrollmentType === "est" && data.estConfig) { - createData.estConfig = { - passphrase: data.estConfig.passphrase, - caChain: data.estConfig.caChain || undefined, - disableBootstrapCaValidation: data.estConfig.disableBootstrapCaValidation - }; - } else if (data.enrollmentType === "api" && data.apiConfig) { - createData.apiConfig = data.apiConfig; - } - - await createProfile.mutateAsync(createData); + if (data.enrollmentType === "est" && data.estConfig) { + updateData.estConfig = data.estConfig; + } else if (data.enrollmentType === "api" && data.apiConfig) { + updateData.apiConfig = data.apiConfig; } - createNotification({ - text: `Certificate profile ${isEdit ? "updated" : "created"} successfully`, - type: "success" - }); + await updateProfile.mutateAsync(updateData); + } else { + if (!currentProject?.id) { + throw new Error("Project ID is required for creating a profile"); + } - reset(); - onClose(); - } catch (error) { - console.error(`Error ${isEdit ? "updating" : "creating"} profile:`, error); - createNotification({ - text: `Failed to ${isEdit ? "update" : "create"} certificate profile`, - type: "error" - }); + const createData: TCreateCertificateProfileDTO = { + projectId: currentProject.id, + slug: data.slug, + description: data.description, + enrollmentType: data.enrollmentType, + caId: data.certificateAuthorityId, + certificateTemplateId: data.certificateTemplateId + }; + + if (data.enrollmentType === "est" && data.estConfig) { + createData.estConfig = { + passphrase: data.estConfig.passphrase, + caChain: data.estConfig.caChain || undefined, + disableBootstrapCaValidation: data.estConfig.disableBootstrapCaValidation + }; + } else if (data.enrollmentType === "api" && data.apiConfig) { + createData.apiConfig = data.apiConfig; + } + + await createProfile.mutateAsync(createData); } + + createNotification({ + text: `Certificate profile ${isEdit ? "updated" : "created"} successfully`, + type: "success" + }); + + reset(); + onClose(); }; return ( @@ -418,7 +410,7 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" } name="enrollmentType" render={({ field: { onChange, ...field }, fieldState: { error } }) => ( { projectId: currentProject?.id || "", limit: 100, offset: 0, - includeConfigs: true, - includeMetrics: true + includeConfigs: true }); const profiles = data?.certificateProfiles || []; @@ -42,10 +41,9 @@ export const ProfileList = ({ onEditProfile, onDeleteProfile }: Props) => {
- + - @@ -67,10 +65,9 @@ export const ProfileList = ({ onEditProfile, onDeleteProfile }: Props) => { - + - diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/ProfileRow.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/ProfileRow.tsx index ab7eb549e..e3bdea3c4 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/ProfileRow.tsx +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/ProfileRow.tsx @@ -33,43 +33,6 @@ import { TCertificateProfile } from "@app/hooks/api/certificateProfiles"; import { useGetCertificateTemplateV2ById } from "@app/hooks/api/certificateTemplates/queries"; import { CertificateIssuanceModal } from "@app/pages/cert-manager/CertificatesPage/components/CertificateIssuanceModal"; -const MetricsBadges = ({ - metrics -}: { - metrics?: { - totalCertificates: number; - activeCertificates: number; - expiringCertificates: number; - expiredCertificates: number; - revokedCertificates: number; - }; -}) => { - if (!metrics) { - return No metrics; - } - - if (metrics.totalCertificates === 0) { - return No certificates; - } - - return ( - <> - {metrics.activeCertificates > 0 && ( - {metrics.activeCertificates} active - )} - {metrics.expiringCertificates > 0 && ( - {metrics.expiringCertificates} expiring - )} - {metrics.expiredCertificates > 0 && ( - {metrics.expiredCertificates} expired - )} - {metrics.revokedCertificates > 0 && ( - {metrics.revokedCertificates} revoked - )} - - ); -}; - interface Props { profile: TCertificateProfile; onEditProfile: (profile: TCertificateProfile) => void; @@ -118,8 +81,8 @@ export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) = const getEnrollmentTypeBadge = (enrollmentType: string) => { const config = { - api: { variant: "success" as const, label: "API" }, - est: { variant: "warning" as const, label: "EST" } + api: { variant: "ghost" as const, label: "API" }, + est: { variant: "ghost" as const, label: "EST" } } as const; const configKey = Object.keys(config).includes(enrollmentType) @@ -153,11 +116,6 @@ export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) = {templateData?.name || profile.certificateTemplateId} -
NameEnrollment TypeEnrollment Method Issuing CA Certificate TemplateCertificates
NameEnrollment TypeEnrollment Method Issuing CA Certificate TemplateCertificates
-
- -
-
diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CertificateTemplatesV2Tab.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CertificateTemplatesV2Tab.tsx index ae14f2dea..ec660b339 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CertificateTemplatesV2Tab.tsx +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CertificateTemplatesV2Tab.tsx @@ -48,23 +48,15 @@ export const CertificateTemplatesV2Tab = () => { const handleDeleteConfirm = async () => { if (!selectedTemplate) return; - try { - await deleteTemplateV2.mutateAsync({ - templateId: selectedTemplate.id - }); - setIsDeleteModalOpen(false); - setSelectedTemplate(null); - createNotification({ - text: `Certificate template "${selectedTemplate.name}" deleted successfully`, - type: "success" - }); - } catch (error) { - console.error("Failed to delete template:", error); - createNotification({ - text: "Failed to delete certificate template", - type: "error" - }); - } + await deleteTemplateV2.mutateAsync({ + templateId: selectedTemplate.id + }); + setIsDeleteModalOpen(false); + setSelectedTemplate(null); + createNotification({ + text: `Certificate template "${selectedTemplate.name}" deleted successfully`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx index a96beccad..1387171fb 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx @@ -407,59 +407,51 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" }; const onFormSubmit = async (data: FormData) => { - try { - if (!currentProject?.id && !isEdit) return; + if (!currentProject?.id && !isEdit) return; - const hasEmptyAttributeValues = data.attributes?.some( - (attr) => !attr.value || attr.value.length === 0 || attr.value.some((v) => !v.trim()) - ); + const hasEmptyAttributeValues = data.attributes?.some( + (attr) => !attr.value || attr.value.length === 0 || attr.value.some((v) => !v.trim()) + ); - const hasEmptySanValues = data.subjectAlternativeNames?.some( - (san) => !san.value || san.value.length === 0 || san.value.some((v) => !v.trim()) - ); - - if (hasEmptyAttributeValues || hasEmptySanValues) { - createNotification({ - text: "All values must be non-empty. Use wildcards (*) if needed.", - type: "error" - }); - return; - } - - const transformedData = transformToApiFormat(data); - - if (isEdit) { - const updateData = { - templateId: template.id, - ...transformedData - }; - await updateTemplate.mutateAsync(updateData); - } else { - if (!currentProject?.id) { - throw new Error("Project ID is required for creating a template"); - } - - const createData = { - projectId: currentProject.id, - ...transformedData - }; - await createTemplate.mutateAsync(createData); - } + const hasEmptySanValues = data.subjectAlternativeNames?.some( + (san) => !san.value || san.value.length === 0 || san.value.some((v) => !v.trim()) + ); + if (hasEmptyAttributeValues || hasEmptySanValues) { createNotification({ - text: `Certificate template ${isEdit ? "updated" : "created"} successfully`, - type: "success" - }); - - reset(); - onClose(); - } catch (error) { - console.error(`Error ${isEdit ? "updating" : "creating"} template:`, error); - createNotification({ - text: `Failed to ${isEdit ? "update" : "create"} certificate template`, + text: "All values must be non-empty. Use wildcards (*) if needed.", type: "error" }); + return; } + + const transformedData = transformToApiFormat(data); + + if (isEdit) { + const updateData = { + templateId: template.id, + ...transformedData + }; + await updateTemplate.mutateAsync(updateData); + } else { + if (!currentProject?.id) { + throw new Error("Project ID is required for creating a template"); + } + + const createData = { + projectId: currentProject.id, + ...transformedData + }; + await createTemplate.mutateAsync(createData); + } + + createNotification({ + text: `Certificate template ${isEdit ? "updated" : "created"} successfully`, + type: "success" + }); + + reset(); + onClose(); }; const addAttribute = () => { diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificatesTab/CertificatesTab.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificatesTab/CertificatesTab.tsx new file mode 100644 index 000000000..caf9cdb2f --- /dev/null +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificatesTab/CertificatesTab.tsx @@ -0,0 +1,5 @@ +import { CertificatesSection } from "../../../CertificatesPage/components/CertificatesSection"; + +export const CertificatesTab = () => { + return ; +}; diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificatesTab/index.ts b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificatesTab/index.ts new file mode 100644 index 000000000..277134d56 --- /dev/null +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificatesTab/index.ts @@ -0,0 +1 @@ +export { CertificatesTab } from "./CertificatesTab"; diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/PkiCollectionsTab/PkiCollectionsTab.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/PkiCollectionsTab/PkiCollectionsTab.tsx new file mode 100644 index 000000000..29bd82559 --- /dev/null +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/PkiCollectionsTab/PkiCollectionsTab.tsx @@ -0,0 +1,5 @@ +import { PkiCollectionSection } from "../../../AlertingPage/components/PkiCollectionSection"; + +export const PkiCollectionsTab = () => { + return ; +}; diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/PkiCollectionsTab/index.ts b/frontend/src/pages/cert-manager/PoliciesPage/components/PkiCollectionsTab/index.ts new file mode 100644 index 000000000..4297b3d5e --- /dev/null +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/PkiCollectionsTab/index.ts @@ -0,0 +1 @@ +export { PkiCollectionsTab } from "./PkiCollectionsTab"; diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/index.ts b/frontend/src/pages/cert-manager/PoliciesPage/components/index.ts index 62809f571..a9b24b1db 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/index.ts +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/index.ts @@ -1,2 +1,4 @@ export { CertificateProfilesTab } from "./CertificateProfilesTab"; +export { CertificatesTab } from "./CertificatesTab"; export { CertificateTemplatesV2Tab } from "./CertificateTemplatesV2Tab"; +export { PkiCollectionsTab } from "./PkiCollectionsTab"; diff --git a/frontend/src/pages/kms/KmipPage/components/DeleteKmipClientModal.tsx b/frontend/src/pages/kms/KmipPage/components/DeleteKmipClientModal.tsx index fe9c956ab..2d6accf76 100644 --- a/frontend/src/pages/kms/KmipPage/components/DeleteKmipClientModal.tsx +++ b/frontend/src/pages/kms/KmipPage/components/DeleteKmipClientModal.tsx @@ -17,28 +17,17 @@ export const DeleteKmipClientModal = ({ isOpen, onOpenChange, kmipClient }: Prop const { id, projectId, name } = kmipClient; const handleDeleteKmipClient = async () => { - try { - await deleteKmipClients.mutateAsync({ - id, - projectId - }); + await deleteKmipClients.mutateAsync({ + id, + projectId + }); - createNotification({ - text: "KMIP client successfully deleted", - type: "success" - }); + createNotification({ + text: "KMIP client successfully deleted", + type: "success" + }); - onOpenChange(false); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to delete KMIP client"; - - createNotification({ - text, - type: "error" - }); - } + onOpenChange(false); }; return ( diff --git a/frontend/src/pages/kms/KmipPage/components/KmipClientModal.tsx b/frontend/src/pages/kms/KmipPage/components/KmipClientModal.tsx index b3f3fb380..f10d077a4 100644 --- a/frontend/src/pages/kms/KmipPage/components/KmipClientModal.tsx +++ b/frontend/src/pages/kms/KmipPage/components/KmipClientModal.tsx @@ -98,20 +98,12 @@ const KmipClientForm = ({ onComplete, kmipClient }: FormProps) => { .map(([key]) => key as KmipPermission) }); - try { - await mutation; - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "added"} KMIP client`, - type: "success" - }); - onComplete(); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${isUpdate ? "update" : "add"} KMIP client`, - type: "error" - }); - } + await mutation; + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "added"} KMIP client`, + type: "success" + }); + onComplete(); }; return ( diff --git a/frontend/src/pages/kms/KmipPage/components/KmipClientTable.tsx b/frontend/src/pages/kms/KmipPage/components/KmipClientTable.tsx index 474ad158a..e3cdafbc2 100644 --- a/frontend/src/pages/kms/KmipPage/components/KmipClientTable.tsx +++ b/frontend/src/pages/kms/KmipPage/components/KmipClientTable.tsx @@ -344,7 +344,7 @@ export const KmipClientTable = () => { handlePopUpToggle("upgradePlan", isOpen)} - text="KMIP requires an enterprise plan." + text="Your current plan does not include access to KMIP. To unlock this feature, please upgrade to Infisical Enterprise plan." isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} /> diff --git a/frontend/src/pages/kms/OverviewPage/components/CmekDecryptModal.tsx b/frontend/src/pages/kms/OverviewPage/components/CmekDecryptModal.tsx index 4c9cf685b..4fd6b3419 100644 --- a/frontend/src/pages/kms/OverviewPage/components/CmekDecryptModal.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/CmekDecryptModal.tsx @@ -52,23 +52,15 @@ const DecryptForm = ({ cmek }: FormProps) => { }); const handleDecryptData = async (formData: FormData) => { - try { - const data = await cmekDecrypt.mutateAsync({ ...formData, keyId: cmek.id }); - createNotification({ - text: "Successfully decrypted data", - type: "success" - }); + const data = await cmekDecrypt.mutateAsync({ ...formData, keyId: cmek.id }); + createNotification({ + text: "Successfully decrypted data", + type: "success" + }); - setPlaintext( - shouldDecode ? Buffer.from(decodeBase64(data.plaintext)).toString("utf8") : data.plaintext - ); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to decrypt data", - type: "error" - }); - } + setPlaintext( + shouldDecode ? Buffer.from(decodeBase64(data.plaintext)).toString("utf8") : data.plaintext + ); }; useEffect(() => { diff --git a/frontend/src/pages/kms/OverviewPage/components/CmekEncryptModal.tsx b/frontend/src/pages/kms/OverviewPage/components/CmekEncryptModal.tsx index 4fb09cf4e..a5bf01d5a 100644 --- a/frontend/src/pages/kms/OverviewPage/components/CmekEncryptModal.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/CmekEncryptModal.tsx @@ -53,19 +53,11 @@ const EncryptForm = ({ cmek }: FormProps) => { }); const handleEncryptData = async (formData: FormData) => { - try { - await cmekEncrypt.mutateAsync({ ...formData, keyId: cmek.id }); - createNotification({ - text: "Successfully encrypted data", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to encrypt data", - type: "error" - }); - } + await cmekEncrypt.mutateAsync({ ...formData, keyId: cmek.id }); + createNotification({ + text: "Successfully encrypted data", + type: "success" + }); }; const ciphertext = cmekEncrypt.data?.ciphertext; diff --git a/frontend/src/pages/kms/OverviewPage/components/CmekModal.tsx b/frontend/src/pages/kms/OverviewPage/components/CmekModal.tsx index c3cccd678..e79d91e15 100644 --- a/frontend/src/pages/kms/OverviewPage/components/CmekModal.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/CmekModal.tsx @@ -86,20 +86,12 @@ const CmekForm = ({ onComplete, cmek }: FormProps) => { encryptionAlgorithm: encryptionAlgorithm as AsymmetricKeyAlgorithm | SymmetricKeyAlgorithm }); - try { - await mutation; - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "added"} key`, - type: "success" - }); - onComplete(); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${isUpdate ? "update" : "add"} key`, - type: "error" - }); - } + await mutation; + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "added"} key`, + type: "success" + }); + onComplete(); }; const selectedKeyUsage = watch("keyUsage"); diff --git a/frontend/src/pages/kms/OverviewPage/components/CmekSignModal.tsx b/frontend/src/pages/kms/OverviewPage/components/CmekSignModal.tsx index fec66a31f..e52c03dbe 100644 --- a/frontend/src/pages/kms/OverviewPage/components/CmekSignModal.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/CmekSignModal.tsx @@ -59,19 +59,11 @@ const SignForm = ({ cmek }: FormProps) => { }); const handleSignData = async (formData: FormData) => { - try { - await cmekSign.mutateAsync({ ...formData, keyId: cmek.id }); - createNotification({ - text: "Successfully signed data", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to sign data", - type: "error" - }); - } + await cmekSign.mutateAsync({ ...formData, keyId: cmek.id }); + createNotification({ + text: "Successfully signed data", + type: "success" + }); }; const signature = cmekSign.data?.signature; diff --git a/frontend/src/pages/kms/OverviewPage/components/CmekTable.tsx b/frontend/src/pages/kms/OverviewPage/components/CmekTable.tsx index a6675ea88..c9a0054c5 100644 --- a/frontend/src/pages/kms/OverviewPage/components/CmekTable.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/CmekTable.tsx @@ -152,28 +152,16 @@ export const CmekTable = () => { const updateCmek = useUpdateCmek(); const handleDisableCmek = async ({ id: keyId, isDisabled }: TCmek) => { - try { - await updateCmek.mutateAsync({ - keyId, - projectId, - isDisabled: !isDisabled - }); + await updateCmek.mutateAsync({ + keyId, + projectId, + isDisabled: !isDisabled + }); - createNotification({ - text: `Key successfully ${isDisabled ? "enabled" : "disabled"}`, - type: "success" - }); - } catch (err) { - console.error(err); - const error = err as any; - const text = - error?.response?.data?.message ?? `Failed to ${isDisabled ? "enable" : "disable"} key`; - - createNotification({ - text, - type: "error" - }); - } + createNotification({ + text: `Key successfully ${isDisabled ? "enabled" : "disabled"}`, + type: "success" + }); }; const cannotEditKey = permission.cannot( diff --git a/frontend/src/pages/kms/OverviewPage/components/CmekVerifyModal.tsx b/frontend/src/pages/kms/OverviewPage/components/CmekVerifyModal.tsx index 5c9eac072..d8203e373 100644 --- a/frontend/src/pages/kms/OverviewPage/components/CmekVerifyModal.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/CmekVerifyModal.tsx @@ -69,25 +69,17 @@ const VerifyForm = ({ cmek }: FormProps) => { }); const handleVerifyData = async (formData: FormData) => { - try { - const result = await cmekVerify.mutateAsync({ ...formData, keyId: cmek.id }); + const result = await cmekVerify.mutateAsync({ ...formData, keyId: cmek.id }); - if (result.signatureValid) { - createNotification({ - text: "Successfully verified signature", - type: "success" - }); - } else { - createNotification({ - title: "Signature Verification Failed", - text: "The signature is invalid. The signature was not created using the same signing algorithm and key as the one used to sign the data. The data and signature may have been tampered with.", - type: "error" - }); - } - } catch (err) { - console.error(err); + if (result.signatureValid) { createNotification({ - text: "Failed to sign data", + text: "Successfully verified signature", + type: "success" + }); + } else { + createNotification({ + title: "Signature Verification Failed", + text: "The signature is invalid. The signature was not created using the same signing algorithm and key as the one used to sign the data. The data and signature may have been tampered with.", type: "error" }); } diff --git a/frontend/src/pages/kms/OverviewPage/components/DeleteCmekModal.tsx b/frontend/src/pages/kms/OverviewPage/components/DeleteCmekModal.tsx index 4c5528c39..62389d1ba 100644 --- a/frontend/src/pages/kms/OverviewPage/components/DeleteCmekModal.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/DeleteCmekModal.tsx @@ -16,28 +16,17 @@ export const DeleteCmekModal = ({ isOpen, onOpenChange, cmek }: Props) => { const { id: keyId, projectId, name } = cmek; const handleDeleteCmek = async () => { - try { - await deleteCmek.mutateAsync({ - keyId, - projectId - }); + await deleteCmek.mutateAsync({ + keyId, + projectId + }); - createNotification({ - text: "Key successfully deleted", - type: "success" - }); + createNotification({ + text: "Key successfully deleted", + type: "success" + }); - onOpenChange(false); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to delete key"; - - createNotification({ - text, - type: "error" - }); - } + onOpenChange(false); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupModal.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupModal.tsx index 908be4935..4b6ea0858 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupModal.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupModal.tsx @@ -74,43 +74,36 @@ export const OrgGroupModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Pr }, [popUp?.group?.data, roles]); const onGroupModalSubmit = async ({ name, slug, role }: TGroupFormData) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - const group = popUp?.group?.data as { - groupId: string; - name: string; - slug: string; - }; + const group = popUp?.group?.data as { + groupId: string; + name: string; + slug: string; + }; - if (group) { - await updateMutateAsync({ - id: group.groupId, - name, - slug, - role: role.slug || undefined - }); - } else { - await createMutateAsync({ - name, - slug, - organizationId: currentOrg.id, - role: role.slug || undefined - }); - } - handlePopUpToggle("group", false); - reset(); - - createNotification({ - text: `Successfully ${popUp?.group?.data ? "updated" : "created"} group`, - type: "success" + if (group) { + await updateMutateAsync({ + id: group.groupId, + name, + slug, + role: role.slug || undefined }); - } catch { - createNotification({ - text: `Failed to ${popUp?.group?.data ? "updated" : "created"} group`, - type: "error" + } else { + await createMutateAsync({ + name, + slug, + organizationId: currentOrg.id, + role: role.slug || undefined }); } + handlePopUpToggle("group", false); + reset(); + + createNotification({ + text: `Successfully ${popUp?.group?.data ? "updated" : "created"} group`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx index 88cbc17f0..c9a0a23f4 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx @@ -27,8 +27,7 @@ export const OrgGroupsSection = () => { const handleAddGroupModal = () => { if (!subscription?.groups) { handlePopUpOpen("upgradePlan", { - description: - "You can manage users more efficiently with groups if you upgrade your Infisical plan to an Enterprise license.", + text: "Your current plan does not allow adding groups. To unlock this feature, please upgrade to Infisical Enterprise plan.", isEnterpriseFeature: true }); } else { @@ -37,21 +36,13 @@ export const OrgGroupsSection = () => { }; const onDeleteGroupSubmit = async ({ name, groupId }: { name: string; groupId: string }) => { - try { - await deleteMutateAsync({ - id: groupId - }); - createNotification({ - text: `Successfully deleted the group named ${name}`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to delete the group named ${name}`, - type: "error" - }); - } + await deleteMutateAsync({ + id: groupId + }); + createNotification({ + text: `Successfully deleted the group named ${name}`, + type: "success" + }); handlePopUpClose("deleteGroup"); }; @@ -98,7 +89,7 @@ export const OrgGroupsSection = () => { isOpen={popUp.upgradePlan.isOpen} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} - text={(popUp.upgradePlan?.data as { description: string })?.description} + text={popUp.upgradePlan?.data?.text} /> ); diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsTable.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsTable.tsx index 61119fce1..cc6d7c7aa 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsTable.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsTable.tsx @@ -79,23 +79,15 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => { const { data: roles } = useGetOrgRoles(orgId); const handleChangeRole = async ({ id, role }: { id: string; role: string }) => { - try { - await updateMutateAsync({ - id, - role - }); + await updateMutateAsync({ + id, + role + }); - createNotification({ - text: "Successfully updated group role", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update group role", - type: "error" - }); - } + createNotification({ + text: "Successfully updated group role", + type: "success" + }); }; const { diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAliCloudAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAliCloudAuthForm.tsx index 4ddcf964e..94c9c49a3 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAliCloudAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAliCloudAuthForm.tsx @@ -55,7 +55,10 @@ const schema = z export type FormData = z.infer; type Props = { - handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; + handlePopUpOpen: ( + popUpName: keyof UsePopUpState<["upgradePlan"]>, + data?: { featureName?: string } + ) => void; handlePopUpToggle: ( popUpName: keyof UsePopUpState<["identityAuthMethod"]>, state?: boolean @@ -137,45 +140,38 @@ export const IdentityAliCloudAuthForm = ({ accessTokenNumUsesLimit, accessTokenTrustedIps }: FormData) => { - try { - if (!identityId) return; + if (!identityId) return; - if (data) { - await updateMutateAsync({ - organizationId: orgId, - allowedArns, - identityId, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } else { - await addMutateAsync({ - organizationId: orgId, - identityId, - allowedArns, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" + if (data) { + await updateMutateAsync({ + organizationId: orgId, + allowedArns, + identityId, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" + } else { + await addMutateAsync({ + organizationId: orgId, + identityId, + allowedArns, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; return ( @@ -277,7 +273,9 @@ export const IdentityAliCloudAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} placeholder="123.456.789.0" /> @@ -292,7 +290,9 @@ export const IdentityAliCloudAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} size="lg" colorSchema="danger" @@ -315,7 +315,9 @@ export const IdentityAliCloudAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} leftIcon={} size="xs" diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModalContent.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModalContent.tsx index 752ab1a34..1bfb904a5 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModalContent.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModalContent.tsx @@ -292,7 +292,7 @@ export const IdentityAuthMethodModalContent = ({ handlePopUpToggle("upgradePlan", isOpen)} - text={`You can use ${popUp.upgradePlan.data?.featureName ?? "IP allowlisting"} if you switch to Infisical's ${popUp.upgradePlan.data?.isEnterpriseFeature ? "Enterprise" : "Pro"} plan.`} + text={`Your current plan does not include access to ${popUp.upgradePlan.data?.featureName}. To unlock this feature, please upgrade to Infisical ${popUp.upgradePlan.data?.isEnterpriseFeature ? "Enterprise" : "Pro"} plan.`} isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} /> diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthTemplateModal.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthTemplateModal.tsx index 3bd423982..6db81f19e 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthTemplateModal.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthTemplateModal.tsx @@ -103,56 +103,44 @@ export const IdentityAuthTemplateModal = ({ popUp, handlePopUpToggle }: Props) = const selectedMethod = watch("method"); const onFormSubmit = async (data: FormData) => { - try { - if (isEdit && template) { - await updateTemplate({ - templateId: template.id, - organizationId: orgId, - name: data.name, - templateFields: { - url: data.url, - bindDN: data.bindDN, - bindPass: data.bindPass, - searchBase: data.searchBase, - ldapCaCertificate: data.ldapCaCertificate - } - }); - createNotification({ - text: "Successfully updated auth template", - type: "success" - }); - } else { - await createTemplate({ - organizationId: orgId, - name: data.name, - authMethod: data.method, - templateFields: { - url: data.url, - bindDN: data.bindDN, - bindPass: data.bindPass, - searchBase: data.searchBase, - ldapCaCertificate: data.ldapCaCertificate - } - }); - createNotification({ - text: "Successfully created auth template", - type: "success" - }); - } - - handlePopUpToggle(isEdit ? "editTemplate" : "createTemplate", false); - reset(); - } catch (err) { - console.error(err); - const error = err as any; - const text = - error?.response?.data?.message ?? `Failed to ${isEdit ? "update" : "create"} auth template`; - + if (isEdit && template) { + await updateTemplate({ + templateId: template.id, + organizationId: orgId, + name: data.name, + templateFields: { + url: data.url, + bindDN: data.bindDN, + bindPass: data.bindPass, + searchBase: data.searchBase, + ldapCaCertificate: data.ldapCaCertificate + } + }); createNotification({ - text, - type: "error" + text: "Successfully updated auth template", + type: "success" + }); + } else { + await createTemplate({ + organizationId: orgId, + name: data.name, + authMethod: data.method, + templateFields: { + url: data.url, + bindDN: data.bindDN, + bindPass: data.bindPass, + searchBase: data.searchBase, + ldapCaCertificate: data.ldapCaCertificate + } + }); + createNotification({ + text: "Successfully created auth template", + type: "success" }); } + + handlePopUpToggle(isEdit ? "editTemplate" : "createTemplate", false); + reset(); }; const handleClose = () => { diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm.tsx index 7e3292c18..a05dcf8df 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm.tsx @@ -57,7 +57,10 @@ const schema = z export type FormData = z.infer; type Props = { - handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; + handlePopUpOpen: ( + popUpName: keyof UsePopUpState<["upgradePlan"]>, + data?: { featureName?: string } + ) => void; handlePopUpToggle: ( popUpName: keyof UsePopUpState<["identityAuthMethod"]>, state?: boolean @@ -147,49 +150,42 @@ export const IdentityAwsAuthForm = ({ accessTokenNumUsesLimit, accessTokenTrustedIps }: FormData) => { - try { - if (!identityId) return; + if (!identityId) return; - if (data) { - await updateMutateAsync({ - organizationId: orgId, - stsEndpoint, - allowedPrincipalArns, - allowedAccountIds, - identityId, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } else { - await addMutateAsync({ - organizationId: orgId, - identityId, - stsEndpoint: stsEndpoint || "", - allowedPrincipalArns: allowedPrincipalArns || "", - allowedAccountIds: allowedAccountIds || "", - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" + if (data) { + await updateMutateAsync({ + organizationId: orgId, + stsEndpoint, + allowedPrincipalArns, + allowedAccountIds, + identityId, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" + } else { + await addMutateAsync({ + organizationId: orgId, + identityId, + stsEndpoint: stsEndpoint || "", + allowedPrincipalArns: allowedPrincipalArns || "", + allowedAccountIds: allowedAccountIds || "", + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; return ( @@ -315,7 +311,9 @@ export const IdentityAwsAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} placeholder="123.456.789.0" /> @@ -330,7 +328,9 @@ export const IdentityAwsAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} size="lg" colorSchema="danger" @@ -353,7 +353,9 @@ export const IdentityAwsAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} leftIcon={} size="xs" diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm.tsx index 518d97e7b..ada799d13 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm.tsx @@ -52,7 +52,10 @@ const schema = z export type FormData = z.infer; type Props = { - handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; + handlePopUpOpen: ( + popUpName: keyof UsePopUpState<["upgradePlan"]>, + data?: { featureName?: string } + ) => void; handlePopUpToggle: ( popUpName: keyof UsePopUpState<["identityAuthMethod"]>, state?: boolean @@ -143,49 +146,42 @@ export const IdentityAzureAuthForm = ({ accessTokenNumUsesLimit, accessTokenTrustedIps }: FormData) => { - try { - if (!identityId) return; + if (!identityId) return; - if (data) { - await updateMutateAsync({ - organizationId: orgId, - identityId, - tenantId, - resource, - allowedServicePrincipalIds, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } else { - await addMutateAsync({ - organizationId: orgId, - identityId, - tenantId: tenantId || "", - resource: resource || "", - allowedServicePrincipalIds: allowedServicePrincipalIds || "", - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" + if (data) { + await updateMutateAsync({ + organizationId: orgId, + identityId, + tenantId, + resource, + allowedServicePrincipalIds, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" + } else { + await addMutateAsync({ + organizationId: orgId, + identityId, + tenantId: tenantId || "", + resource: resource || "", + allowedServicePrincipalIds: allowedServicePrincipalIds || "", + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; return ( @@ -311,7 +307,9 @@ export const IdentityAzureAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} placeholder="123.456.789.0" /> @@ -326,7 +324,9 @@ export const IdentityAzureAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} size="lg" colorSchema="danger" @@ -349,7 +349,9 @@ export const IdentityAzureAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} leftIcon={} size="xs" diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm.tsx index 7175e8b62..960d4b561 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm.tsx @@ -55,7 +55,10 @@ const schema = z export type FormData = z.infer; type Props = { - handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; + handlePopUpOpen: ( + popUpName: keyof UsePopUpState<["upgradePlan"]>, + data?: { featureName?: string } + ) => void; handlePopUpToggle: ( popUpName: keyof UsePopUpState<["identityAuthMethod"]>, state?: boolean @@ -152,51 +155,44 @@ export const IdentityGcpAuthForm = ({ accessTokenNumUsesLimit, accessTokenTrustedIps }: FormData) => { - try { - if (!identityId) return; + if (!identityId) return; - if (data) { - await updateMutateAsync({ - identityId, - organizationId: orgId, - type, - allowedServiceAccounts, - allowedProjects, - allowedZones, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } else { - await addMutateAsync({ - identityId, - organizationId: orgId, - type, - allowedServiceAccounts: allowedServiceAccounts || "", - allowedProjects: allowedProjects || "", - allowedZones: allowedZones || "", - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" + if (data) { + await updateMutateAsync({ + identityId, + organizationId: orgId, + type, + allowedServiceAccounts, + allowedProjects, + allowedZones, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" + } else { + await addMutateAsync({ + identityId, + organizationId: orgId, + type, + allowedServiceAccounts: allowedServiceAccounts || "", + allowedProjects: allowedProjects || "", + allowedZones: allowedZones || "", + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; return ( @@ -350,7 +346,9 @@ export const IdentityGcpAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} placeholder="123.456.789.0" /> @@ -365,7 +363,9 @@ export const IdentityGcpAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} size="lg" colorSchema="danger" @@ -388,7 +388,9 @@ export const IdentityGcpAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} leftIcon={} size="xs" diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm.tsx index f62045810..10eab486d 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm.tsx @@ -88,7 +88,10 @@ const schema = z.discriminatedUnion("configurationType", [ export type FormData = z.infer; type Props = { - handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; + handlePopUpOpen: ( + popUpName: keyof UsePopUpState<["upgradePlan"]>, + data?: { featureName?: string } + ) => void; handlePopUpToggle: ( popUpName: keyof UsePopUpState<["identityAuthMethod"]>, state?: boolean @@ -217,61 +220,54 @@ export const IdentityJwtAuthForm = ({ boundClaims, boundSubject }: FormData) => { - try { - if (!identityId) { - return; - } + if (!identityId) { + return; + } - if (data) { - await updateMutateAsync({ - identityId, - organizationId: orgId, - configurationType, - jwksUrl, - jwksCaCert, - publicKeys: publicKeys?.map((field) => field.value).filter(Boolean), - boundIssuer, - boundAudiences, - boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])), - boundSubject, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } else { - await addMutateAsync({ - identityId, - configurationType, - jwksUrl, - jwksCaCert, - publicKeys: publicKeys?.map((field) => field.value).filter(Boolean), - boundIssuer, - boundAudiences, - boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])), - boundSubject, - organizationId: orgId, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" + if (data) { + await updateMutateAsync({ + identityId, + organizationId: orgId, + configurationType, + jwksUrl, + jwksCaCert, + publicKeys: publicKeys?.map((field) => field.value).filter(Boolean), + boundIssuer, + boundAudiences, + boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])), + boundSubject, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" + } else { + await addMutateAsync({ + identityId, + configurationType, + jwksUrl, + jwksCaCert, + publicKeys: publicKeys?.map((field) => field.value).filter(Boolean), + boundIssuer, + boundAudiences, + boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])), + boundSubject, + organizationId: orgId, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; return ( @@ -619,7 +615,9 @@ export const IdentityJwtAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} placeholder="123.456.789.0" /> @@ -634,7 +632,9 @@ export const IdentityJwtAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} size="lg" colorSchema="danger" @@ -657,7 +657,9 @@ export const IdentityJwtAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} leftIcon={} size="xs" diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx index 9b8624990..c4ae8bac0 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx @@ -96,7 +96,10 @@ const schema = z export type FormData = z.infer; type Props = { - handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; + handlePopUpOpen: ( + popUpName: keyof UsePopUpState<["upgradePlan"]>, + data?: { featureName?: string } + ) => void; handlePopUpToggle: ( popUpName: keyof UsePopUpState<["identityAuthMethod"]>, state?: boolean @@ -311,71 +314,64 @@ export const IdentityKubernetesAuthForm = ({ tokenReviewMode, accessTokenTrustedIps }: FormData) => { - try { - if (!identityId) return; + if (!identityId) return; - if (data) { - await updateMutateAsync({ - organizationId: orgId, - ...(tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Api - ? { - kubernetesHost: kubernetesHost || "" - } - : { - kubernetesHost: null - }), - tokenReviewerJwt: tokenReviewerJwt || null, - allowedNames, - allowedNamespaces, - allowedAudience, - caCert, - identityId, - gatewayId: gatewayId || null, - tokenReviewMode, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } else { - await addMutateAsync({ - organizationId: orgId, - identityId, - ...(tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Api - ? { - kubernetesHost: kubernetesHost || "" - } - : { - kubernetesHost: null - }), - tokenReviewerJwt: tokenReviewerJwt || undefined, - allowedNames: allowedNames || "", - allowedNamespaces: allowedNamespaces || "", - allowedAudience: allowedAudience || "", - gatewayId: gatewayId || null, - caCert: caCert || "", - tokenReviewMode, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" + if (data) { + await updateMutateAsync({ + organizationId: orgId, + ...(tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Api + ? { + kubernetesHost: kubernetesHost || "" + } + : { + kubernetesHost: null + }), + tokenReviewerJwt: tokenReviewerJwt || null, + allowedNames, + allowedNamespaces, + allowedAudience, + caCert, + identityId, + gatewayId: gatewayId || null, + tokenReviewMode, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" + } else { + await addMutateAsync({ + organizationId: orgId, + identityId, + ...(tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Api + ? { + kubernetesHost: kubernetesHost || "" + } + : { + kubernetesHost: null + }), + tokenReviewerJwt: tokenReviewerJwt || undefined, + allowedNames: allowedNames || "", + allowedNamespaces: allowedNamespaces || "", + allowedAudience: allowedAudience || "", + gatewayId: gatewayId || null, + caCert: caCert || "", + tokenReviewMode, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; const tokenReviewMode = watch("tokenReviewMode"); @@ -707,7 +703,9 @@ export const IdentityKubernetesAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} placeholder="123.456.789.0" /> @@ -722,7 +720,9 @@ export const IdentityKubernetesAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} size="lg" colorSchema="danger" @@ -745,7 +745,9 @@ export const IdentityKubernetesAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} leftIcon={} size="xs" diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm.tsx index b2096fa06..a3ab70de0 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm.tsx @@ -316,83 +316,76 @@ export const IdentityLdapAuthForm = ({ }, [subscription, handlePopUpOpen, handlePopUpToggle]); const onFormSubmit = async (formData: FormData) => { - try { - if (!identityId) return; + if (!identityId) return; - const { - scope: submissionScope, - templateId: submissionTemplateId, - url: submissionUrl, - bindDN: submissionBindDN, - bindPass: submissionBindPass, - searchBase: submissionSearchBase, - searchFilter, - ldapCaCertificate, - allowedFields, - accessTokenTTL, - accessTokenMaxTTL, - accessTokenNumUsesLimit, - accessTokenTrustedIps, - lockoutEnabled, - lockoutThreshold, - lockoutDurationValue, - lockoutDurationUnit, - lockoutCounterResetValue, - lockoutCounterResetUnit - } = formData; + const { + scope: submissionScope, + templateId: submissionTemplateId, + url: submissionUrl, + bindDN: submissionBindDN, + bindPass: submissionBindPass, + searchBase: submissionSearchBase, + searchFilter, + ldapCaCertificate, + allowedFields, + accessTokenTTL, + accessTokenMaxTTL, + accessTokenNumUsesLimit, + accessTokenTrustedIps, + lockoutEnabled, + lockoutThreshold, + lockoutDurationValue, + lockoutDurationUnit, + lockoutCounterResetValue, + lockoutCounterResetUnit + } = formData; - const lockoutDurationSeconds = ms(`${lockoutDurationValue}${lockoutDurationUnit}`) / 1000; - const lockoutCounterResetSeconds = - ms(`${lockoutCounterResetValue}${lockoutCounterResetUnit}`) / 1000; + const lockoutDurationSeconds = ms(`${lockoutDurationValue}${lockoutDurationUnit}`) / 1000; + const lockoutCounterResetSeconds = + ms(`${lockoutCounterResetValue}${lockoutCounterResetUnit}`) / 1000; - const basePayload = { - organizationId: orgId, - identityId, - searchFilter, - ldapCaCertificate, - allowedFields, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps, - lockoutEnabled, - lockoutThreshold: Number(lockoutThreshold), - lockoutDurationSeconds, - lockoutCounterResetSeconds - }; + const basePayload = { + organizationId: orgId, + identityId, + searchFilter, + ldapCaCertificate, + allowedFields, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps, + lockoutEnabled, + lockoutThreshold: Number(lockoutThreshold), + lockoutDurationSeconds, + lockoutCounterResetSeconds + }; - // Add scope-specific fields - const payload = - submissionScope === "template" - ? { ...basePayload, templateId: submissionTemplateId } - : { - ...basePayload, - url: submissionUrl, - bindDN: submissionBindDN, - bindPass: submissionBindPass, - searchBase: submissionSearchBase - }; + // Add scope-specific fields + const payload = + submissionScope === "template" + ? { ...basePayload, templateId: submissionTemplateId } + : { + ...basePayload, + url: submissionUrl, + bindDN: submissionBindDN, + bindPass: submissionBindPass, + searchBase: submissionSearchBase + }; - if (data) { - await updateMutateAsync(payload); - } else { - await addMutateAsync(payload); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" - }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" - }); + if (data) { + await updateMutateAsync(payload); + } else { + await addMutateAsync(payload); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; return ( @@ -837,7 +830,9 @@ export const IdentityLdapAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} placeholder="123.456.789.0" /> @@ -852,7 +847,9 @@ export const IdentityLdapAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} size="lg" colorSchema="danger" @@ -875,7 +872,9 @@ export const IdentityLdapAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} leftIcon={} size="xs" diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLinkForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLinkForm.tsx index b0977437b..545aea5aa 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLinkForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLinkForm.tsx @@ -42,31 +42,20 @@ export const IdentityLinkForm = ({ onClose }: Props) => { }); const onFormSubmit = async ({ identity, role }: FormData) => { - try { - await createMutateAsync({ - identityId: identity.id, - roles: [{ role: role.slug, isTemporary: false }] - }); - createNotification({ - text: "Successfully linked identity", - type: "success" - }); - navigate({ - to: "/organization/identities/$identityId", - params: { - identityId: identity.id - } - }); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to link identity"; - - createNotification({ - text, - type: "error" - }); - } + await createMutateAsync({ + identityId: identity.id, + roles: [{ role: role.slug, isTemporary: false }] + }); + createNotification({ + text: "Successfully linked identity", + type: "success" + }); + navigate({ + to: "/organization/identities/$identityId", + params: { + identityId: identity.id + } + }); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal.tsx index dacbba428..09e779fa9 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal.tsx @@ -108,81 +108,68 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => { }, [popUp?.identity?.data, roles]); const onFormSubmit = async ({ name, role, metadata, hasDeleteProtection }: FormData) => { - try { - const identity = popUp?.identity?.data as { - identityId: string; - name: string; - role: string; - hasDeleteProtection: boolean; - orgId: string; - }; + const identity = popUp?.identity?.data as { + identityId: string; + name: string; + role: string; + hasDeleteProtection: boolean; + orgId: string; + }; - if (identity) { - // update + if (identity) { + // update - await updateMutateAsync({ - identityId: identity.identityId, - name, - role: role.slug || undefined, - hasDeleteProtection, - organizationId: orgId, - metadata - }); - - handlePopUpToggle("identity", false); - } else { - // create - - const { id: createdId } = await createMutateAsync({ - name, - role: role.slug || undefined, - hasDeleteProtection, - organizationId: orgId, - metadata - }); - - await addMutateAsync({ - organizationId: orgId, - identityId: createdId, - clientSecretTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }], - accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }], - accessTokenTTL: 2592000, - accessTokenMaxTTL: 2592000, - accessTokenNumUsesLimit: 0, - accessTokenPeriod: 0, - lockoutEnabled: true, - lockoutThreshold: 3, - lockoutDurationSeconds: 300, - lockoutCounterResetSeconds: 30 - }); - - handlePopUpToggle("identity", false); - navigate({ - to: "/organization/identities/$identityId", - params: { - identityId: createdId - } - }); - } - - createNotification({ - text: `Successfully ${popUp?.identity?.data ? "updated" : "created"} identity`, - type: "success" + await updateMutateAsync({ + identityId: identity.identityId, + name, + role: role.slug || undefined, + hasDeleteProtection, + organizationId: orgId, + metadata }); - reset(); - } catch (err) { - console.error(err); - const error = err as any; - const text = - error?.response?.data?.message ?? - `Failed to ${popUp?.identity?.data ? "update" : "create"} identity`; + handlePopUpToggle("identity", false); + } else { + // create - createNotification({ - text, - type: "error" + const { id: createdId } = await createMutateAsync({ + name, + role: role.slug || undefined, + hasDeleteProtection, + organizationId: orgId, + metadata + }); + + await addMutateAsync({ + organizationId: orgId, + identityId: createdId, + clientSecretTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }], + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }], + accessTokenTTL: 2592000, + accessTokenMaxTTL: 2592000, + accessTokenNumUsesLimit: 0, + accessTokenPeriod: 0, + lockoutEnabled: true, + lockoutThreshold: 3, + lockoutDurationSeconds: 300, + lockoutCounterResetSeconds: 30 + }); + + handlePopUpToggle("identity", false); + navigate({ + to: "/organization/identities/$identityId", + params: { + identityId: createdId + } }); } + + createNotification({ + text: `Successfully ${popUp?.identity?.data ? "updated" : "created"} identity`, + type: "success" + }); + + reset(); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOciAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOciAuthForm.tsx index 5e33c71bb..3ebfceb4a 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOciAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOciAuthForm.tsx @@ -63,7 +63,10 @@ const schema = z export type FormData = z.infer; type Props = { - handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; + handlePopUpOpen: ( + popUpName: keyof UsePopUpState<["upgradePlan"]>, + data?: { featureName?: string } + ) => void; handlePopUpToggle: ( popUpName: keyof UsePopUpState<["identityAuthMethod"]>, state?: boolean @@ -149,47 +152,40 @@ export const IdentityOciAuthForm = ({ accessTokenNumUsesLimit, accessTokenTrustedIps }: FormData) => { - try { - if (!identityId) return; + if (!identityId) return; - if (data) { - await updateMutateAsync({ - organizationId: orgId, - tenancyOcid, - allowedUsernames, - identityId, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } else { - await addMutateAsync({ - organizationId: orgId, - identityId, - tenancyOcid, - allowedUsernames: allowedUsernames || undefined, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" + if (data) { + await updateMutateAsync({ + organizationId: orgId, + tenancyOcid, + allowedUsernames, + identityId, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" + } else { + await addMutateAsync({ + organizationId: orgId, + identityId, + tenancyOcid, + allowedUsernames: allowedUsernames || undefined, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; return ( @@ -297,7 +293,9 @@ export const IdentityOciAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} placeholder="123.456.789.0" /> @@ -312,7 +310,9 @@ export const IdentityOciAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} size="lg" colorSchema="danger" @@ -335,7 +335,9 @@ export const IdentityOciAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} leftIcon={} size="xs" diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx index 15fbc4b72..7503e6f45 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx @@ -75,7 +75,10 @@ const schema = z.object({ export type FormData = z.infer; type Props = { - handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; + handlePopUpOpen: ( + popUpName: keyof UsePopUpState<["upgradePlan"]>, + data?: { featureName?: string } + ) => void; handlePopUpToggle: ( popUpName: keyof UsePopUpState<["identityAuthMethod"]>, state?: boolean @@ -201,63 +204,56 @@ export const IdentityOidcAuthForm = ({ claimMetadataMapping, boundSubject }: FormData) => { - try { - if (!identityId) { - return; - } + if (!identityId) { + return; + } - if (data) { - await updateMutateAsync({ - identityId, - organizationId: orgId, - oidcDiscoveryUrl, - caCert, - boundIssuer, - boundAudiences, - boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])), - claimMetadataMapping: claimMetadataMapping - ? Object.fromEntries(claimMetadataMapping.map((entry) => [entry.key, entry.value])) - : undefined, - boundSubject, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } else { - await addMutateAsync({ - identityId, - oidcDiscoveryUrl, - caCert, - boundIssuer, - boundAudiences, - boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])), - claimMetadataMapping: claimMetadataMapping - ? Object.fromEntries(claimMetadataMapping.map((entry) => [entry.key, entry.value])) - : undefined, - boundSubject, - organizationId: orgId, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" + if (data) { + await updateMutateAsync({ + identityId, + organizationId: orgId, + oidcDiscoveryUrl, + caCert, + boundIssuer, + boundAudiences, + boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])), + claimMetadataMapping: claimMetadataMapping + ? Object.fromEntries(claimMetadataMapping.map((entry) => [entry.key, entry.value])) + : undefined, + boundSubject, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" + } else { + await addMutateAsync({ + identityId, + oidcDiscoveryUrl, + caCert, + boundIssuer, + boundAudiences, + boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])), + claimMetadataMapping: claimMetadataMapping + ? Object.fromEntries(claimMetadataMapping.map((entry) => [entry.key, entry.value])) + : undefined, + boundSubject, + organizationId: orgId, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; return ( @@ -606,7 +602,9 @@ export const IdentityOidcAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} placeholder="123.456.789.0" /> @@ -621,7 +619,9 @@ export const IdentityOidcAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} size="lg" colorSchema="danger" @@ -644,7 +644,9 @@ export const IdentityOidcAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} leftIcon={} size="xs" diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx index 355dec803..8380836cb 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx @@ -56,53 +56,31 @@ export const IdentitySection = withPermission( const isEnterprise = subscription?.slug === "enterprise"; const onDeleteIdentitySubmit = async (identityId: string) => { - try { - await deleteMutateAsync({ - identityId, - organizationId: orgId - }); + await deleteMutateAsync({ + identityId, + organizationId: orgId + }); - createNotification({ - text: "Successfully deleted identity", - type: "success" - }); + createNotification({ + text: "Successfully deleted identity", + type: "success" + }); - handlePopUpClose("deleteIdentity"); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to delete identity"; - - createNotification({ - text, - type: "error" - }); - } + handlePopUpClose("deleteIdentity"); }; const onDeleteTemplateSubmit = async (templateId: string) => { - try { - await deleteTemplateMutateAsync({ - templateId, - organizationId: orgId - }); + await deleteTemplateMutateAsync({ + templateId, + organizationId: orgId + }); - createNotification({ - text: "Successfully deleted template", - type: "success" - }); + createNotification({ + text: "Successfully deleted template", + type: "success" + }); - handlePopUpClose("deleteTemplate"); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to delete template"; - - createNotification({ - text, - type: "error" - }); - } + handlePopUpClose("deleteTemplate"); }; return ( @@ -145,8 +123,7 @@ export const IdentitySection = withPermission( onClick={() => { if (!isMoreIdentitiesAllowed && !isEnterprise) { handlePopUpOpen("upgradePlan", { - description: - "You can add more identities if you upgrade your Infisical Pro plan." + text: "You have reached the maximum number of identities allowed on your current plan. Upgrade to Infisical Pro plan to add more identities." }); return; } @@ -181,8 +158,7 @@ export const IdentitySection = withPermission( if (subscription && !subscription.machineIdentityAuthTemplates) { handlePopUpOpen("upgradePlan", { isEnterpriseFeature: true, - description: - "You can use Identity Auth Templates if you switch to Infisical's Enterprise plan." + text: "Your current plan does not include access to creating Identity Auth Templates. To unlock this feature, please upgrade to Infisical Enterprise plan." }); return; } @@ -253,7 +229,7 @@ export const IdentitySection = withPermission( handlePopUpToggle("upgradePlan", isOpen)} - text={popUp.upgradePlan.data?.description} + text={popUp.upgradePlan.data?.text} isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} /> diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx index c4c1561e7..d202b909a 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx @@ -145,27 +145,16 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => { }; const handleChangeRole = async ({ identityId, role }: { identityId: string; role: string }) => { - try { - await updateMutateAsync({ - identityId, - role, - organizationId - }); + await updateMutateAsync({ + identityId, + role, + organizationId + }); - createNotification({ - text: "Successfully updated identity role", - type: "success" - }); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to update identity role"; - - createNotification({ - text, - type: "error" - }); - } + createNotification({ + text: "Successfully updated identity role", + type: "success" + }); }; const handleRoleToggle = useCallback( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTlsCertAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTlsCertAuthForm.tsx index 19a0499fc..5666be39f 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTlsCertAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTlsCertAuthForm.tsx @@ -50,7 +50,10 @@ const schema = z.object({ export type FormData = z.infer; type Props = { - handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; + handlePopUpOpen: ( + popUpName: keyof UsePopUpState<["upgradePlan"]>, + data?: { featureName?: string } + ) => void; handlePopUpToggle: ( popUpName: keyof UsePopUpState<["identityAuthMethod"]>, state?: boolean @@ -134,47 +137,40 @@ export const IdentityTlsCertAuthForm = ({ accessTokenNumUsesLimit, accessTokenTrustedIps }: FormData) => { - try { - if (!identityId) return; + if (!identityId) return; - if (data) { - await updateMutateAsync({ - organizationId: orgId, - caCertificate, - allowedCommonNames: allowedCommonNames || null, - identityId, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } else { - await addMutateAsync({ - organizationId: orgId, - identityId, - caCertificate, - allowedCommonNames: allowedCommonNames || undefined, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" + if (data) { + await updateMutateAsync({ + organizationId: orgId, + caCertificate, + allowedCommonNames: allowedCommonNames || null, + identityId, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" + } else { + await addMutateAsync({ + organizationId: orgId, + identityId, + caCertificate, + allowedCommonNames: allowedCommonNames || undefined, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; return ( @@ -286,7 +282,9 @@ export const IdentityTlsCertAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} placeholder="123.456.789.0" /> @@ -301,7 +299,9 @@ export const IdentityTlsCertAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} size="lg" colorSchema="danger" @@ -324,7 +324,9 @@ export const IdentityTlsCertAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} leftIcon={} size="xs" diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTokenAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTokenAuthForm.tsx index b819202fc..af2404c5c 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTokenAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTokenAuthForm.tsx @@ -49,7 +49,10 @@ const schema = z export type FormData = z.infer; type Props = { - handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; + handlePopUpOpen: ( + popUpName: keyof UsePopUpState<["upgradePlan"]>, + data?: { featureName?: string } + ) => void; handlePopUpToggle: ( popUpName: keyof UsePopUpState<["identityAuthMethod"]>, state?: boolean @@ -127,43 +130,36 @@ export const IdentityTokenAuthForm = ({ accessTokenNumUsesLimit, accessTokenTrustedIps }: FormData) => { - try { - if (!identityId) return; + if (!identityId) return; - if (data) { - await updateMutateAsync({ - organizationId: orgId, - identityId, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } else { - await addMutateAsync({ - organizationId: orgId, - identityId, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" + if (data) { + await updateMutateAsync({ + organizationId: orgId, + identityId, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" + } else { + await addMutateAsync({ + organizationId: orgId, + identityId, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; return ( @@ -248,7 +244,9 @@ export const IdentityTokenAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} placeholder="123.456.789.0" /> @@ -263,7 +261,9 @@ export const IdentityTokenAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} size="lg" colorSchema="danger" @@ -286,7 +286,9 @@ export const IdentityTokenAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} leftIcon={} size="xs" diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx index d43d84c86..d4de4bd0e 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx @@ -87,7 +87,10 @@ const schema = z export type FormData = z.infer; type Props = { - handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; + handlePopUpOpen: ( + popUpName: keyof UsePopUpState<["upgradePlan"]>, + data?: { featureName?: string } + ) => void; handlePopUpToggle: ( popUpName: keyof UsePopUpState<["identityAuthMethod"]>, state?: boolean @@ -220,64 +223,55 @@ export const IdentityUniversalAuthForm = ({ lockoutCounterResetValue, lockoutCounterResetUnit }: FormData) => { - try { - if (!identityId) return; + if (!identityId) return; - const lockoutDurationSeconds = ms(`${lockoutDurationValue}${lockoutDurationUnit}`) / 1000; - const lockoutCounterResetSeconds = - ms(`${lockoutCounterResetValue}${lockoutCounterResetUnit}`) / 1000; + const lockoutDurationSeconds = ms(`${lockoutDurationValue}${lockoutDurationUnit}`) / 1000; + const lockoutCounterResetSeconds = + ms(`${lockoutCounterResetValue}${lockoutCounterResetUnit}`) / 1000; - if (data) { - // update universal auth configuration - await updateMutateAsync({ - organizationId: orgId, - identityId, - clientSecretTrustedIps, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps, - accessTokenPeriod: Number(accessTokenPeriod), - lockoutEnabled, - lockoutThreshold: Number(lockoutThreshold), - lockoutDurationSeconds, - lockoutCounterResetSeconds - }); - } else { - // create new universal auth configuration - - await addMutateAsync({ - organizationId: orgId, - identityId, - clientSecretTrustedIps, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps, - accessTokenPeriod: Number(accessTokenPeriod), - lockoutEnabled, - lockoutThreshold: Number(lockoutThreshold), - lockoutDurationSeconds: Number(lockoutDurationSeconds), - lockoutCounterResetSeconds: Number(lockoutCounterResetSeconds) - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "created"} auth method`, - type: "success" + if (data) { + // update universal auth configuration + await updateMutateAsync({ + organizationId: orgId, + identityId, + clientSecretTrustedIps, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps, + accessTokenPeriod: Number(accessTokenPeriod), + lockoutEnabled, + lockoutThreshold: Number(lockoutThreshold), + lockoutDurationSeconds, + lockoutCounterResetSeconds }); + } else { + // create new universal auth configuration - reset(); - } catch { - const text = `Failed to ${isUpdate ? "update" : "configure"} identity`; - - createNotification({ - text, - type: "error" + await addMutateAsync({ + organizationId: orgId, + identityId, + clientSecretTrustedIps, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps, + accessTokenPeriod: Number(accessTokenPeriod), + lockoutEnabled, + lockoutThreshold: Number(lockoutThreshold), + lockoutDurationSeconds: Number(lockoutDurationSeconds), + lockoutCounterResetSeconds: Number(lockoutCounterResetSeconds) }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "created"} auth method`, + type: "success" + }); + + reset(); }; return ( @@ -409,7 +403,9 @@ export const IdentityUniversalAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} placeholder="123.456.789.0" /> @@ -424,7 +420,9 @@ export const IdentityUniversalAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} size="lg" colorSchema="danger" @@ -447,7 +445,9 @@ export const IdentityUniversalAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} leftIcon={} size="xs" @@ -477,7 +477,9 @@ export const IdentityUniversalAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} placeholder="123.456.789.0" /> @@ -492,7 +494,9 @@ export const IdentityUniversalAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} size="lg" colorSchema="danger" @@ -515,7 +519,9 @@ export const IdentityUniversalAuthForm = ({ return; } - handlePopUpOpen("upgradePlan"); + handlePopUpOpen("upgradePlan", { + featureName: "IP allowlisting" + }); }} leftIcon={} size="xs" diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx index e49beadae..da88603fc 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx @@ -122,64 +122,55 @@ export const AddOrgMemberModal = ({ } } - try { - const parsedEmails = emails - .replace(/\s/g, "") - .split(",") - .map((email) => { - if (EmailSchema.safeParse(email).success) { - return email.trim(); - } + const parsedEmails = emails + .replace(/\s/g, "") + .split(",") + .map((email) => { + if (EmailSchema.safeParse(email).success) { + return email.trim(); + } - return null; - }); - - if (parsedEmails.includes(null)) { - createNotification({ - text: "Invalid email addresses provided.", - type: "error" - }); - return; - } - - const usernames = emails.split(",").map((email) => email.trim()); - const { data } = await addUsersMutateAsync({ - organizationId: currentOrg?.id, - inviteeEmails: usernames, - organizationRoleSlug: organizationRole.slug + return null; }); - await Promise.allSettled( - selectedProjects.map((el) => - addUserToProject({ - orgId: currentOrg.id, - projectId: el.id, - roleSlugs: [projectRoleSlug], - usernames - }) - ) - ); - - setCompleteInviteLinks(data?.completeInviteLinks ?? null); - - // only show this notification when email is configured. - // A [completeInviteLink] will not be sent if smtp is configured - - if (!data.completeInviteLinks) { - createNotification({ - text: "Successfully invited user to the organization.", - type: "success" - }); - } - } catch (error) { - console.error(error); + if (parsedEmails.includes(null)) { createNotification({ - text: "Failed to invite user to org", + text: "Invalid email addresses provided.", type: "error" }); return; } + const usernames = emails.split(",").map((email) => email.trim()); + const { data } = await addUsersMutateAsync({ + organizationId: currentOrg?.id, + inviteeEmails: usernames, + organizationRoleSlug: organizationRole.slug + }); + + await Promise.allSettled( + selectedProjects.map((el) => + addUserToProject({ + orgId: currentOrg.id, + projectId: el.id, + roleSlugs: [projectRoleSlug], + usernames + }) + ) + ); + + setCompleteInviteLinks(data?.completeInviteLinks ?? null); + + // only show this notification when email is configured. + // A [completeInviteLink] will not be sent if smtp is configured + + if (!data.completeInviteLinks) { + createNotification({ + text: "Successfully invited user to the organization.", + type: "success" + }); + } + if (serverDetails?.emailConfigured) { handlePopUpToggle("addMember", false); } diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddSubOrgMemberModal.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddSubOrgMemberModal.tsx index 290f8db4b..5edba7cde 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddSubOrgMemberModal.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddSubOrgMemberModal.tsx @@ -106,32 +106,24 @@ export const AddSubOrgMemberModal = ({ onClose }: Props) => { } } - try { - const usernames = users.map((el) => el.username); - await addUsersMutateAsync({ - organizationId: currentOrg?.id, - inviteeEmails: usernames, - organizationRoleSlug: organizationRole.slug - }); + const usernames = users.map((el) => el.username); + await addUsersMutateAsync({ + organizationId: currentOrg?.id, + inviteeEmails: usernames, + organizationRoleSlug: organizationRole.slug + }); - await Promise.allSettled( - selectedProjects.map((el) => - addUserToProject({ - orgId: currentOrg.id, - projectId: el.id, - roleSlugs: [projectRoleSlug], - usernames - }) - ) - ); - onClose(); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to add user to suborganization", - type: "error" - }); - } + await Promise.allSettled( + selectedProjects.map((el) => + addUserToProject({ + orgId: currentOrg.id, + projectId: el.id, + roleSlugs: [projectRoleSlug], + usernames + }) + ) + ); + onClose(); }; const getGroupHeaderLabel = (type: ProjectType) => { diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx index adb729d75..0b71dea25 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx @@ -76,7 +76,7 @@ export const OrgMembersSection = () => { if (!isMoreIdentitiesAllowed && !isEnterprise) { handlePopUpOpen("upgradePlan", { - description: "You can add more members if you switch to Infisical's Pro plan." + text: "You have reached the maximum number of members allowed on your current plan. Upgrade to Infisical Pro plan to add more members." }); return; } @@ -85,46 +85,30 @@ export const OrgMembersSection = () => { }; const onDeactivateMemberSubmit = async (orgMembershipId: string) => { - try { - await updateOrgMembership({ - organizationId: orgId, - membershipId: orgMembershipId, - isActive: false - }); + await updateOrgMembership({ + organizationId: orgId, + membershipId: orgMembershipId, + isActive: false + }); - createNotification({ - text: "Successfully deactivated user in organization", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to deactivate user in organization", - type: "error" - }); - } + createNotification({ + text: "Successfully deactivated user in organization", + type: "success" + }); handlePopUpClose("deactivateMember"); }; const onRemoveMemberSubmit = async (orgMembershipId: string) => { - try { - await deleteMutateAsync({ - orgId, - membershipId: orgMembershipId - }); + await deleteMutateAsync({ + orgId, + membershipId: orgMembershipId + }); - createNotification({ - text: "Successfully removed user from org", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to remove user from the organization", - type: "error" - }); - } + createNotification({ + text: "Successfully removed user from org", + type: "success" + }); handlePopUpClose("removeMember"); }; @@ -132,27 +116,20 @@ export const OrgMembersSection = () => { const { data: members = [] } = useGetOrgUsers(orgId); const handleRemoveMembers = async (selectedMembers: OrgUser[]) => { - try { - await deleteBatchMutateAsync({ - orgId, - membershipIds: selectedMembers - .filter((member) => member.user.id !== userId) - .map((member) => member.id) - }); + await deleteBatchMutateAsync({ + orgId, + membershipIds: selectedMembers + .filter((member) => member.user.id !== userId) + .map((member) => member.id) + }); - createNotification({ - text: "Successfully removed users from organization", - type: "success" - }); + createNotification({ + text: "Successfully removed users from organization", + type: "success" + }); - setSelectedMemberIds([]); - handlePopUpClose("removeMembers"); - } catch { - createNotification({ - text: "Failed to remove users from the organization", - type: "error" - }); - } + setSelectedMemberIds([]); + handlePopUpClose("removeMembers"); }; return ( @@ -323,7 +300,7 @@ export const OrgMembersSection = () => { handlePopUpToggle("upgradePlan", isOpen)} - text={(popUp.upgradePlan?.data as { description: string })?.description} + text={popUp.upgradePlan?.data?.text} /> void; @@ -127,35 +127,26 @@ export const OrgMembersTable = ({ const onRoleChange = async (membershipId: string, role: string) => { if (!currentOrg?.id) return; - try { - // TODO: replace hardcoding default role - const isCustomRole = !["admin", "member", "no-access"].includes(role); + // TODO: replace hardcoding default role + const isCustomRole = !["admin", "member", "no-access"].includes(role); - if (isCustomRole && subscription && !subscription?.rbac) { - handlePopUpOpen("upgradePlan", { - description: - "You can assign custom roles to members if you switch to Infisical's Pro plan." - }); - return; - } - - await updateOrgMembership({ - organizationId: currentOrg?.id, - membershipId, - role - }); - - createNotification({ - text: "Successfully updated user role", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to update user role", - type: "error" + if (isCustomRole && subscription && !subscription?.rbac) { + handlePopUpOpen("upgradePlan", { + text: "Your current plan does not include access to assigning custom roles to members. To unlock this feature, please upgrade to Infisical Pro plan." }); + return; } + + await updateOrgMembership({ + organizationId: currentOrg?.id, + membershipId, + role + }); + + createNotification({ + text: "Successfully updated user role", + type: "success" + }); }; const onResendInvite = async (membershipId: string) => { @@ -174,12 +165,6 @@ export const OrgMembersTable = ({ text: "Successfully resent org invitation", type: "success" }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to resend org invitation", - type: "error" - }); } finally { setResendInviteId(null); } diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgRoleTabSection/OrgRoleTable.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgRoleTabSection/OrgRoleTable.tsx index d6dae6ae2..aa14ac130 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgRoleTabSection/OrgRoleTable.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgRoleTabSection/OrgRoleTable.tsx @@ -86,17 +86,12 @@ export const OrgRoleTable = () => { const handleRoleDelete = async () => { const { id } = popUp?.deleteRole?.data as TOrgRole; - try { - await deleteRole({ - orgId, - id - }); - createNotification({ type: "success", text: "Successfully removed the role" }); - handlePopUpClose("deleteRole"); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to delete role" }); - } + await deleteRole({ + orgId, + id + }); + createNotification({ type: "success", text: "Successfully removed the role" }); + handlePopUpClose("deleteRole"); }; const handleSetRoleAsDefault = async (defaultMembershipRoleSlug: string) => { @@ -104,23 +99,17 @@ export const OrgRoleTable = () => { if (isCustomRole && subscription && !subscription?.rbac) { handlePopUpOpen("upgradePlan", { - description: - "You can set the default org role to a custom role if you switch to Infisical's Pro plan." + text: "Your current plan does not include access to set a custom default organization role. To unlock this feature, please upgrade to Infisical Pro plan." }); return; } - try { - await updateOrg({ - orgId, - defaultMembershipRoleSlug - }); - createNotification({ type: "success", text: "Successfully updated default membership role" }); - handlePopUpClose("deleteRole"); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to update default membership role" }); - } + await updateOrg({ + orgId, + defaultMembershipRoleSlug + }); + createNotification({ type: "success", text: "Successfully updated default membership role" }); + handlePopUpClose("deleteRole"); }; const { @@ -471,7 +460,7 @@ export const OrgRoleTable = () => { handlePopUpToggle("upgradePlan", isOpen)} - text={(popUp.upgradePlan?.data as { description: string })?.description} + text={popUp.upgradePlan?.data?.text} /> acknowledgesPermanentChange; const handlePrivilegeSystemUpgrade = async () => { - try { - await upgradePrivilegeSystem(); + await upgradePrivilegeSystem(); - createNotification({ - text: "Privilege system upgrade completed", - type: "success" - }); + createNotification({ + text: "Privilege system upgrade completed", + type: "success" + }); - onOpenChange(false); - } catch { - createNotification({ - text: "Failed to upgrade privilege system", - type: "error" - }); - } + onOpenChange(false); }; const handleClose = () => { 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 c09a58960..aca33ffa2 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx @@ -20,6 +20,7 @@ import { AzureKeyVaultConnectionForm } from "./AzureKeyVaultConnectionForm"; import { BitbucketConnectionForm } from "./BitbucketConnectionForm"; import { CamundaConnectionForm } from "./CamundaConnectionForm"; import { ChecklyConnectionForm } from "./ChecklyConnectionForm"; +import { ChefConnectionForm } from "./ChefConnectionForm"; import { CloudflareConnectionForm } from "./CloudflareConnectionForm"; import { DatabricksConnectionForm } from "./DatabricksConnectionForm"; import { DigitalOceanConnectionForm } from "./DigitalOceanConnectionForm"; @@ -73,24 +74,15 @@ const CreateForm = ({ app, onComplete, projectId }: CreateFormProps) => { "method" | "name" | "app" | "credentials" | "isPlatformManagedCredentials" > ) => { - try { - const connection = await createAppConnection.mutateAsync({ - ...formData, - projectId - }); - createNotification({ - text: `Successfully added ${appName} Connection`, - type: "success" - }); - onComplete(connection); - } catch (err: any) { - console.error(err); - createNotification({ - title: `Failed to add ${appName} Connection`, - text: err.message, - type: "error" - }); - } + const connection = await createAppConnection.mutateAsync({ + ...formData, + projectId + }); + createNotification({ + text: `Successfully added ${appName} Connection`, + type: "success" + }); + onComplete(connection); }; switch (app) { @@ -164,6 +156,8 @@ const CreateForm = ({ app, onComplete, projectId }: CreateFormProps) => { return ; case AppConnection.Checkly: return ; + case AppConnection.Chef: + return ; case AppConnection.Supabase: return ; case AppConnection.DigitalOcean: @@ -191,24 +185,15 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => { "method" | "name" | "app" | "credentials" | "isPlatformManagedCredentials" > ) => { - try { - const connection = await updateAppConnection.mutateAsync({ - connectionId: appConnection.id, - ...formData - }); - createNotification({ - text: `Successfully updated ${appName} Connection`, - type: "success" - }); - onComplete(connection); - } catch (err: any) { - console.error(err); - createNotification({ - title: `Failed to update ${appName} Connection`, - text: err.message, - type: "error" - }); - } + const connection = await updateAppConnection.mutateAsync({ + connectionId: appConnection.id, + ...formData + }); + createNotification({ + text: `Successfully updated ${appName} Connection`, + type: "success" + }); + onComplete(connection); }; switch (appConnection.app) { @@ -329,6 +314,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => { return ; case AppConnection.Checkly: return ; + case AppConnection.Chef: + return ; case AppConnection.Supabase: return ; case AppConnection.DigitalOcean: diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/ChefConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/ChefConnectionForm.tsx new file mode 100644 index 000000000..862b4e2bd --- /dev/null +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/ChefConnectionForm.tsx @@ -0,0 +1,195 @@ +import { Controller, FormProvider, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { + Button, + FormControl, + Input, + ModalClose, + SecretInput, + Select, + SelectItem +} from "@app/components/v2"; +import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; +import { ChefConnectionMethod, TChefConnection } from "@app/hooks/api/appConnections"; +import { AppConnection } from "@app/hooks/api/appConnections/enums"; + +import { + genericAppConnectionFieldsSchema, + GenericAppConnectionsFields +} from "./GenericAppConnectionFields"; + +type Props = { + appConnection?: TChefConnection; + onSubmit: (formData: FormData) => Promise; +}; + +const rootSchema = genericAppConnectionFieldsSchema.extend({ + app: z.literal(AppConnection.Chef) +}); + +const formSchema = z.discriminatedUnion("method", [ + rootSchema.extend({ + method: z.literal(ChefConnectionMethod.UserKey), + credentials: z.object({ + serverUrl: z.string().trim().url("Valid Chef Server URL required").optional(), + orgName: z.string().trim().min(1, "Organization name required"), + userName: z.string().trim().min(1, "User name required"), + privateKey: z.string().trim().min(1, "Private key required") + }) + }) +]); + +type FormData = z.infer; + +export const ChefConnectionForm = ({ appConnection, onSubmit }: Props) => { + const isUpdate = Boolean(appConnection); + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: appConnection ?? { + app: AppConnection.Chef, + method: ChefConnectionMethod.UserKey + } + }); + + const { + handleSubmit, + control, + formState: { isSubmitting, isDirty } + } = form; + + return ( + +
+ {!isUpdate && } + ( + + onChange(e.target.value)} + /> + + )} + /> + ( + + + + )} + /> + + ( + + onChange(e.target.value)} + /> + + )} + /> + ( + + onChange(e.target.value)} + /> + + )} + /> + ( + + onChange(e.target.value)} + /> + + )} + /> +
+ + + + +
+ +
+ ); +}; diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx index 9b8fdddf1..bdea10d7a 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx @@ -169,7 +169,7 @@ export const AppConnectionsSelect = ({ onSelect, projectType }: Props) => { handlePopUpToggle("upgradePlan", isOpen)} - text="You can use every App Connection if you switch to Infisical's Enterprise plan." + text="All App Connections can be unlocked if you switch to Infisical Enterprise plan." isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} /> diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/DeleteAppConnectionModal.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/DeleteAppConnectionModal.tsx index e5f6b3684..f5782adbb 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/DeleteAppConnectionModal.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/DeleteAppConnectionModal.tsx @@ -18,26 +18,17 @@ export const DeleteAppConnectionModal = ({ isOpen, onOpenChange, appConnection } const { id: connectionId, name, app } = appConnection; const handleDeleteAppConnection = async () => { - try { - await deleteAppConnection.mutateAsync({ - connectionId, - app - }); + await deleteAppConnection.mutateAsync({ + connectionId, + app + }); - createNotification({ - text: `Successfully removed ${APP_CONNECTION_MAP[app].name} connection`, - type: "success" - }); + createNotification({ + text: `Successfully removed ${APP_CONNECTION_MAP[app].name} connection`, + type: "success" + }); - onOpenChange(false); - } catch (err) { - console.error(err); - - createNotification({ - text: `Failed to remove ${APP_CONNECTION_MAP[app].name} connection`, - type: "error" - }); - } + onOpenChange(false); }; return ( diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/EditAppConnectionDetailsModal.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/EditAppConnectionDetailsModal.tsx index f46a3b3cf..7d1a542c0 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/EditAppConnectionDetailsModal.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/EditAppConnectionDetailsModal.tsx @@ -43,24 +43,15 @@ const Content = ({ appConnection, onComplete }: ContentProps) => { } = form; const onSubmit = async (formData: FormData) => { - try { - await updateAppConnection.mutateAsync({ - connectionId: appConnection.id, - ...formData - }); - createNotification({ - text: `Successfully updated ${appName} Connection`, - type: "success" - }); - onComplete(); - } catch (err: any) { - console.error(err); - createNotification({ - title: `Failed to update ${appName} Connection`, - text: err.message, - type: "error" - }); - } + await updateAppConnection.mutateAsync({ + connectionId: appConnection.id, + ...formData + }); + createNotification({ + text: `Successfully updated ${appName} Connection`, + type: "success" + }); + onComplete(); }; return ( diff --git a/frontend/src/pages/organization/AppConnections/OauthCallbackPage/OauthCallbackPage.tsx b/frontend/src/pages/organization/AppConnections/OauthCallbackPage/OauthCallbackPage.tsx index df3d1327a..6575a0780 100644 --- a/frontend/src/pages/organization/AppConnections/OauthCallbackPage/OauthCallbackPage.tsx +++ b/frontend/src/pages/organization/AppConnections/OauthCallbackPage/OauthCallbackPage.tsx @@ -127,12 +127,7 @@ export const OAuthCallbackPage = () => { projectId, connection }; - } catch (err: any) { - createNotification({ - title: `Failed to ${connectionId ? "update" : "add"} GitLab Connection`, - text: err?.message, - type: "error" - }); + } catch { navigate({ to: returnUrl, params: { @@ -176,12 +171,7 @@ export const OAuthCallbackPage = () => { } }); } - } catch (err: any) { - createNotification({ - title: `Failed to ${connectionId ? "update" : "add"} Azure Key Vault Connection`, - text: err?.message, - type: "error" - }); + } catch { navigate({ to: returnUrl, params: { @@ -233,12 +223,7 @@ export const OAuthCallbackPage = () => { } }); } - } catch (err: any) { - createNotification({ - title: `Failed to ${connectionId ? "update" : "add"} Azure App Configuration Connection`, - text: err?.message, - type: "error" - }); + } catch { navigate({ to: returnUrl, params: { @@ -290,12 +275,7 @@ export const OAuthCallbackPage = () => { } }); } - } catch (err: any) { - createNotification({ - title: `Failed to ${connectionId ? "update" : "add"} Azure Client Secrets Connection`, - text: err?.message, - type: "error" - }); + } catch { navigate({ to: returnUrl, params: { @@ -353,12 +333,7 @@ export const OAuthCallbackPage = () => { } }); } - } catch (err: any) { - createNotification({ - title: `Failed to ${connectionId ? "update" : "add"} Azure DevOps Connection`, - text: err?.message, - type: "error" - }); + } catch { navigate({ to: returnUrl, params: { @@ -441,12 +416,7 @@ export const OAuthCallbackPage = () => { }) }); } - } catch (e: any) { - createNotification({ - title: `Failed to ${connectionId ? "update" : "add"} GitHub Connection`, - text: e.message, - type: "error" - }); + } catch { navigate({ to: returnUrl, params: { @@ -498,12 +468,7 @@ export const OAuthCallbackPage = () => { } }); } - } catch (e: any) { - createNotification({ - title: `Failed to ${connectionId ? "update" : "add"} GitHub Radar Connection`, - text: e.message, - type: "error" - }); + } catch { navigate({ to: returnUrl, params: { @@ -553,12 +518,7 @@ export const OAuthCallbackPage = () => { } }); } - } catch (e: any) { - createNotification({ - title: `Failed to ${connectionId ? "update" : "add"} Heroku Connection`, - text: e.message, - type: "error" - }); + } catch { navigate({ to: returnUrl, params: { @@ -633,7 +593,9 @@ export const OAuthCallbackPage = () => { connectionName: data.connection.name, ...(data.returnUrl.includes("integrations") ? { - selectedTab: IntegrationsListPageTabs.SecretSyncs + selectedTab: localStorage.getItem("pkiSyncFormData") + ? IntegrationsListPageTabs.PkiSyncs + : IntegrationsListPageTabs.SecretSyncs } : {}) } diff --git a/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx b/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx index 72561b90d..ece7b522a 100644 --- a/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx +++ b/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx @@ -1,7 +1,8 @@ /* eslint-disable no-nested-ternary */ import { useMemo } from "react"; import { Controller, useForm } from "react-hook-form"; -import { faCaretDown, faCheckCircle, faFilterCircleXmark } from "@fortawesome/free-solid-svg-icons"; +import { MultiValue, SingleValue } from "react-select"; +import { faFilterCircleXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { AnimatePresence, motion } from "framer-motion"; @@ -11,13 +12,10 @@ import { Button, DropdownMenu, DropdownMenuContent, - DropdownMenuItem, DropdownMenuTrigger, FilterableSelect, FormControl, - Input, - Select, - SelectItem + Input } from "@app/components/v2"; import { Badge } from "@app/components/v3"; import { useOrganization } from "@app/context"; @@ -132,7 +130,7 @@ export const LogsFilter = ({ presets, setFilter, filter, project }: Props) => {
-
+
@@ -165,7 +163,7 @@ export const LogsFilter = ({ presets, setFilter, filter, project }: Props) => { { - resetField("eventType"); + setValue("eventType", [], { shouldDirty: true }); }} > { name="eventType" render={({ field }) => ( - - -
- {selectedEventTypes?.length === 1 - ? filteredEventTypes.find( - (eventType) => eventType.value === selectedEventTypes[0] - )?.label - : selectedEventTypes?.length === 0 - ? "All events" - : `${selectedEventTypes?.length} events selected`} - -
-
- -
- {filteredEventTypes.length > 0 ? ( - filteredEventTypes.map((eventType) => { - const isSelected = selectedEventTypes?.includes( - eventType.value as EventType - ); - - return ( - - filteredEventTypes.length > 1 && event.preventDefault() - } - onClick={() => { - if ( - selectedEventTypes?.includes(eventType.value as EventType) - ) { - field.onChange( - selectedEventTypes?.filter( - (e: string) => e !== eventType.value - ) - ); - } else { - field.onChange([ - ...(selectedEventTypes || []), - eventType.value - ]); - } - }} - key={`event-type-${eventType.value}`} - icon={ - isSelected ? ( - - ) : ( -
- ) - } - iconPos="left" - className="w-[28.4rem] text-sm" - > - {eventType.label} - - ); - }) - ) : ( -
- )} -
- - + + field.value.includes(eventType.value as EventType) + )} + isMulti + isClearable + onChange={(options) => + field.onChange( + (options as MultiValue<(typeof filteredEventTypes)[number]>).map( + (option) => option.value + ) + ) + } + placeholder="All events" + options={filteredEventTypes} + getOptionValue={(option) => option.value} + getOptionLabel={(option) => option.label} + /> )} /> @@ -250,37 +196,33 @@ export const LogsFilter = ({ presets, setFilter, filter, project }: Props) => { { - resetField("userAgentType"); + setValue("userAgentType", undefined, { shouldDirty: true }); }} > ( + render={({ field: { onChange, value }, fieldState: { error } }) => ( - + value === (userAgentType.value as UserAgentType) + ) ?? null + } + isClearable + onChange={(option) => + onChange((option as SingleValue<(typeof userAgentTypes)[number]>)?.value) + } + placeholder="All sources" + options={userAgentTypes} + getOptionValue={(option) => option.value} + getOptionLabel={(option) => option.label} + /> )} /> @@ -289,10 +231,10 @@ export const LogsFilter = ({ presets, setFilter, filter, project }: Props) => { { - resetField("project"); - resetField("environment"); - setValue("secretPath", ""); - setValue("secretKey", ""); + setValue("project", null, { shouldDirty: true }); + setValue("environment", undefined, { shouldDirty: true }); + setValue("secretPath", "", { shouldDirty: true }); + setValue("secretKey", "", { shouldDirty: true }); }} > { } className={twMerge(!selectedProject && "opacity-50")} onClear={() => { - resetField("environment"); + setValue("environment", undefined, { shouldDirty: true }); }} > { } className={twMerge(!selectedProject && "opacity-50")} onClear={() => { - setValue("secretPath", ""); + setValue("secretPath", "", { shouldDirty: true }); }} > { className={twMerge(!selectedProject && "opacity-50")} label="Secret Key" onClear={() => { - setValue("secretKey", ""); + setValue("secretKey", "", { shouldDirty: true }); }} > { handlePopUpToggle("upgradePlan", isOpen); }} - text="You can use audit logs if you switch to Infisical's Pro plan." + text="Your current plan does not include access to audit logs. To unlock this feature, please upgrade to Infisical Pro plan." />
@@ -167,7 +167,7 @@ const LogsSectionComponent = ({ onOpenChange={(isOpen) => { handlePopUpToggle("upgradePlan", isOpen); }} - text="You can use audit logs if you switch to Infisical's Pro plan." + text="Your current plan does not include access to audit logs. To unlock this feature, please upgrade to Infisical Pro plan." />
); diff --git a/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/CompanyNameSection.tsx b/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/CompanyNameSection.tsx index fce3978c7..f63b5d6d1 100644 --- a/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/CompanyNameSection.tsx +++ b/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/CompanyNameSection.tsx @@ -35,25 +35,17 @@ export const CompanyNameSection = () => { }, [data]); const onFormSubmit = async ({ name }: { name: string }) => { - try { - if (!currentOrg?.id) return; - if (name === "") return; - await mutateAsync({ - name, - organizationId: currentOrg.id - }); + if (!currentOrg?.id) return; + if (name === "") return; + await mutateAsync({ + name, + organizationId: currentOrg.id + }); - createNotification({ - text: "Successfully updated business name", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update business name", - type: "error" - }); - } + createNotification({ + text: "Successfully updated business name", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/InvoiceEmailSection.tsx b/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/InvoiceEmailSection.tsx index 8b471346f..dc05a7208 100644 --- a/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/InvoiceEmailSection.tsx +++ b/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/InvoiceEmailSection.tsx @@ -35,26 +35,18 @@ export const InvoiceEmailSection = () => { }, [data]); const onFormSubmit = async ({ email }: { email: string }) => { - try { - if (!currentOrg?.id) return; - if (email === "") return; + if (!currentOrg?.id) return; + if (email === "") return; - await mutateAsync({ - email, - organizationId: currentOrg.id - }); + await mutateAsync({ + email, + organizationId: currentOrg.id + }); - createNotification({ - text: "Successfully updated invoice email recipient", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update invoice email recipient", - type: "error" - }); - } + createNotification({ + text: "Successfully updated invoice email recipient", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/PmtMethodsTable.tsx b/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/PmtMethodsTable.tsx index bf0851e11..15a6bc8e9 100644 --- a/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/PmtMethodsTable.tsx +++ b/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/PmtMethodsTable.tsx @@ -39,22 +39,15 @@ export const PmtMethodsTable = () => { }); return; } - try { - await deleteOrgPmtMethod.mutateAsync({ - organizationId: currentOrg.id, - pmtMethodId: pmtMethodToRemove.id - }); - createNotification({ - type: "success", - text: "Successfully removed payment method" - }); - handlePopUpClose("removeCard"); - } catch (error: any) { - createNotification({ - type: "error", - text: error.message ?? "Error removing payment method" - }); - } + await deleteOrgPmtMethod.mutateAsync({ + organizationId: currentOrg.id, + pmtMethodId: pmtMethodToRemove.id + }); + createNotification({ + type: "success", + text: "Successfully removed payment method" + }); + handlePopUpClose("removeCard"); }; return ( diff --git a/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/TaxIDModal.tsx b/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/TaxIDModal.tsx index 01b11e81a..23542ac8d 100644 --- a/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/TaxIDModal.tsx +++ b/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/TaxIDModal.tsx @@ -98,26 +98,18 @@ export const TaxIDModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props }); const onTaxIDModalSubmit = async ({ type, value }: AddTaxIDFormData) => { - try { - if (!currentOrg?.id) return; - await addOrgTaxId.mutateAsync({ - organizationId: currentOrg.id, - type, - value - }); + if (!currentOrg?.id) return; + await addOrgTaxId.mutateAsync({ + organizationId: currentOrg.id, + type, + value + }); - createNotification({ - text: "Successfully added Tax ID", - type: "success" - }); - handlePopUpClose("addTaxID"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to add Tax ID", - type: "error" - }); - } + createNotification({ + text: "Successfully added Tax ID", + type: "success" + }); + handlePopUpClose("addTaxID"); }; return ( diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx index b33ce0ef2..e6d3736fb 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx @@ -5,7 +5,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Link, useNavigate, useParams } from "@tanstack/react-router"; import { twMerge } from "tailwind-merge"; -import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { @@ -51,32 +50,23 @@ const Page = () => { const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "groupCreateUpdate", - "deleteGroup", - "upgradePlan" + "deleteGroup" ] as const); const onDeleteGroupSubmit = async ({ name, id }: { name: string; id: string }) => { - try { - await deleteMutateAsync({ - id - }); - createNotification({ - text: `Successfully deleted the ${name} group`, - type: "success" - }); - navigate({ - to: "/organization/access-management" as const, - search: { - selectedTab: TabSections.Groups - } - }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to delete the ${name} group`, - type: "error" - }); - } + await deleteMutateAsync({ + id + }); + createNotification({ + text: `Successfully deleted the ${name} group`, + type: "success" + }); + navigate({ + to: "/organization/access-management" as const, + search: { + selectedTab: TabSections.Groups + } + }); handlePopUpClose("deleteGroup"); }; @@ -184,11 +174,6 @@ const Page = () => { onDeleteGroupSubmit(popUp?.deleteGroup?.data as { name: string; id: string }) } /> - handlePopUpToggle("upgradePlan", isOpen)} - text={(popUp.upgradePlan?.data as { description: string })?.description} - />
); }; diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/components/AddGroupMemberModal.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/components/AddGroupMemberModal.tsx index 0995830e5..b9d1b184c 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/components/AddGroupMemberModal.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/components/AddGroupMemberModal.tsx @@ -63,31 +63,24 @@ export const AddGroupMembersModal = ({ popUp, handlePopUpToggle }: Props) => { const { mutateAsync: addUserToGroupMutateAsync } = useAddUserToGroup(); const handleAddMember = async (username: string) => { - try { - if (!popUpData?.slug) { - createNotification({ - text: "Some data is missing, please refresh the page and try again", - type: "error" - }); - return; - } - - await addUserToGroupMutateAsync({ - groupId: popUpData.groupId, - username, - slug: popUpData.slug - }); - + if (!popUpData?.slug) { createNotification({ - text: "Successfully assigned user to the group", - type: "success" - }); - } catch { - createNotification({ - text: "Failed to assign user to the group", + text: "Some data is missing, please refresh the page and try again", type: "error" }); + return; } + + await addUserToGroupMutateAsync({ + groupId: popUpData.groupId, + username, + slug: popUpData.slug + }); + + createNotification({ + text: "Successfully assigned user to the group", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupCreateUpdateModal.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupCreateUpdateModal.tsx index e4364e874..bc6e3fab5 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupCreateUpdateModal.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupCreateUpdateModal.tsx @@ -77,43 +77,36 @@ export const GroupCreateUpdateModal = ({ popUp, handlePopUpClose, handlePopUpTog }, [popUp?.groupCreateUpdate?.data, roles]); const onGroupModalSubmit = async ({ name, slug, role }: TGroupFormData) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - const group = popUp?.groupCreateUpdate?.data as { - groupId: string; - name: string; - slug: string; - }; + const group = popUp?.groupCreateUpdate?.data as { + groupId: string; + name: string; + slug: string; + }; - if (group) { - await updateMutateAsync({ - id: group.groupId, - name, - slug, - role: role.slug || undefined - }); - } else { - await createMutateAsync({ - name, - slug, - organizationId: currentOrg.id, - role: role.slug || undefined - }); - } - handlePopUpToggle("groupCreateUpdate", false); - reset(); - - createNotification({ - text: `Successfully ${popUp?.groupCreateUpdate?.data ? "updated" : "created"} group`, - type: "success" + if (group) { + await updateMutateAsync({ + id: group.groupId, + name, + slug, + role: role.slug || undefined }); - } catch { - createNotification({ - text: `Failed to ${popUp?.groupCreateUpdate?.data ? "updated" : "created"} group`, - type: "error" + } else { + await createMutateAsync({ + name, + slug, + organizationId: currentOrg.id, + role: role.slug || undefined }); } + handlePopUpToggle("groupCreateUpdate", false); + reset(); + + createNotification({ + text: `Successfully ${popUp?.groupCreateUpdate?.data ? "updated" : "created"} group`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersSection.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersSection.tsx index 2ca6859b0..c78b5404e 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersSection.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersSection.tsx @@ -29,25 +29,18 @@ export const GroupMembersSection = ({ groupId, groupSlug }: Props) => { const { mutateAsync: removeUserFromGroupMutateAsync } = useRemoveUserFromGroup(); const handleRemoveUserFromGroup = async (username: string) => { - try { - await removeUserFromGroupMutateAsync({ - groupId, - username, - slug: groupSlug - }); + await removeUserFromGroupMutateAsync({ + groupId, + username, + slug: groupSlug + }); - createNotification({ - text: `Successfully removed user ${username} from the group`, - type: "success" - }); + createNotification({ + text: `Successfully removed user ${username} from the group`, + type: "success" + }); - handlePopUpToggle("removeMemberFromGroup", false); - } catch { - createNotification({ - text: `Failed to remove user ${username} from the group`, - type: "error" - }); - } + handlePopUpToggle("removeMemberFromGroup", false); }; return ( diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx index f3bd05eeb..33753da39 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx @@ -44,34 +44,23 @@ const Page = () => { ] as const); const onDeleteIdentitySubmit = async (id: string) => { - try { - await deleteIdentity({ - identityId: id, - organizationId: orgId - }); + await deleteIdentity({ + identityId: id, + organizationId: orgId + }); - createNotification({ - text: "Successfully deleted identity", - type: "success" - }); + createNotification({ + text: "Successfully deleted identity", + type: "success" + }); - handlePopUpClose("deleteIdentity"); - navigate({ - to: "/organization/access-management", - search: { - selectedTab: OrgAccessControlTabSections.Identities - } - }); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to delete identity"; - - createNotification({ - text, - type: "error" - }); - } + handlePopUpClose("deleteIdentity"); + navigate({ + to: "/organization/access-management", + search: { + selectedTab: OrgAccessControlTabSections.Identities + } + }); }; return ( @@ -120,7 +109,7 @@ const Page = () => { handlePopUpToggle("upgradePlan", isOpen)} - text={(popUp.upgradePlan?.data as { description: string })?.description} + text={`Your current plan does not include access to ${popUp.upgradePlan.data?.featureName}. To unlock this feature, please upgrade to Infisical ${popUp.upgradePlan.data?.isEnterpriseFeature ? "Enterprise" : "Pro"} plan.`} isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} /> ) => { }, [workspaces, projectMemberships]); const onFormSubmit = async ({ project: selectedProject, role }: FormData) => { - try { - await addIdentityToWorkspace({ - projectId: selectedProject.id, - identityId, - role: role.slug || undefined - }); + await addIdentityToWorkspace({ + projectId: selectedProject.id, + identityId, + role: role.slug || undefined + }); - createNotification({ - text: "Successfully added identity to project", - type: "success" - }); + createNotification({ + text: "Successfully added identity to project", + type: "success" + }); - reset(); - handlePopUpToggle("addIdentityToProject", false); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to add identity to project"; - - createNotification({ - text, - type: "error" - }); - } + reset(); + handlePopUpToggle("addIdentityToProject", false); }; const isProjectSelected = Boolean(projectId); diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectsSection.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectsSection.tsx index 6530b9ae5..8d67ef6a4 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectsSection.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectsSection.tsx @@ -22,28 +22,17 @@ export const IdentityProjectsSection = ({ identityId }: Props) => { ] as const); const onRemoveIdentitySubmit = async (id: string, projectId: string) => { - try { - await deleteMutateAsync({ - identityId: id, - projectId - }); + await deleteMutateAsync({ + identityId: id, + projectId + }); - createNotification({ - text: "Successfully removed identity from project", - type: "success" - }); + createNotification({ + text: "Successfully removed identity from project", + type: "success" + }); - handlePopUpClose("removeIdentityFromProject"); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to remove identity from project"; - - createNotification({ - text, - type: "error" - }); - } + handlePopUpClose("removeIdentityFromProject"); }; return ( diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityTokenModal.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityTokenModal.tsx index d2b865a5a..d5c1aa2ac 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityTokenModal.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityTokenModal.tsx @@ -72,46 +72,33 @@ export const IdentityTokenModal = ({ popUp, handlePopUpToggle }: Props) => { }, [popUp?.token?.data]); const onFormSubmit = async ({ name }: FormData) => { - try { - if (tokenData?.tokenId) { - // update + if (tokenData?.tokenId) { + // update - await updateToken({ - identityId: tokenData.identityId, - tokenId: tokenData.tokenId, - name - }); - - handlePopUpToggle("token", false); - } else { - // create - - const newTokenData = await createToken({ - identityId: tokenData.identityId, - name - }); - - setToken(newTokenData.accessToken); - } - - createNotification({ - text: `Successfully ${popUp?.token?.data ? "updated" : "created"} token`, - type: "success" + await updateToken({ + identityId: tokenData.identityId, + tokenId: tokenData.tokenId, + name }); - reset(); - } catch (err) { - console.error(err); - const error = err as any; - const text = - error?.response?.data?.message ?? - `Failed to ${popUp?.token?.data ? "update" : "create"} token`; + handlePopUpToggle("token", false); + } else { + // create - createNotification({ - text, - type: "error" + const newTokenData = await createToken({ + identityId: tokenData.identityId, + name }); + + setToken(newTokenData.accessToken); } + + createNotification({ + text: `Successfully ${popUp?.token?.data ? "updated" : "created"} token`, + type: "success" + }); + + reset(); }; return ( diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityAuthLockoutFields.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityAuthLockoutFields.tsx index b5cb2d901..4ec5871a2 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityAuthLockoutFields.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityAuthLockoutFields.tsx @@ -31,23 +31,15 @@ export const LockoutFields = ({ const [lockedOutState, setLockedOutState] = useState(lockedOut); - async function clearLockouts() { - try { - const deleted = await mutateAsync({ identityId }); - createNotification({ - text: `Successfully cleared ${deleted} lockout${deleted === 1 ? "" : "s"}`, - type: "success" - }); - setLockedOutState(false); - onResetAllLockouts(); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to clear lockouts. Please try again.", - type: "error" - }); - } - } + const clearLockouts = async () => { + const deleted = await mutateAsync({ identityId }); + createNotification({ + text: `Successfully cleared ${deleted} lockout${deleted === 1 ? "" : "s"}`, + type: "success" + }); + setLockedOutState(false); + onResetAllLockouts(); + }; return ( <> diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityTokenAuthTokensTable.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityTokenAuthTokensTable.tsx index be9c166b3..cc11d9d10 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityTokenAuthTokensTable.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityTokenAuthTokensTable.tsx @@ -51,28 +51,17 @@ export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => { tokenId: string; name: string; }) => { - try { - await revokeToken({ - identityId: parentIdentityId, - tokenId - }); + await revokeToken({ + identityId: parentIdentityId, + tokenId + }); - handlePopUpClose("revokeToken"); + handlePopUpClose("revokeToken"); - createNotification({ - text: `Successfully revoked token ${name ?? ""}`, - type: "success" - }); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to revoke token"; - - createNotification({ - text, - type: "error" - }); - } + createNotification({ + text: `Successfully revoked token ${name ?? ""}`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityUniversalAuthClientSecretsTable.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityUniversalAuthClientSecretsTable.tsx index b034b5367..017f30719 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityUniversalAuthClientSecretsTable.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityUniversalAuthClientSecretsTable.tsx @@ -43,25 +43,17 @@ export const IdentityUniversalAuthClientSecretsTable = ({ clientSecrets, identit const { mutateAsync: revokeClientSecret } = useRevokeIdentityUniversalAuthClientSecret(); const onDeleteClientSecretSubmit = async (clientSecretId: string) => { - try { - await revokeClientSecret({ - identityId, - clientSecretId - }); + await revokeClientSecret({ + identityId, + clientSecretId + }); - handlePopUpToggle("revokeClientSecret", false); + handlePopUpToggle("revokeClientSecret", false); - createNotification({ - text: "Successfully deleted client secret", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete client secret", - type: "error" - }); - } + createNotification({ + text: "Successfully deleted client secret", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal.tsx index f95a20789..be5ae2cc9 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal.tsx @@ -140,25 +140,17 @@ export const Content = ({ } const handleDeleteAuthMethod = async () => { - try { - await revokeMethod({ - identityId, - organizationId: orgId - }); + await revokeMethod({ + identityId, + organizationId: orgId + }); - createNotification({ - text: "Successfully removed auth method", - type: "success" - }); - - handlePopUpToggle("revokeAuthMethod", false); - onDeleteAuthMethod(); - } catch { - createNotification({ - text: "Failed to remove auth method", - type: "error" - }); - } + createNotification({ + text: "Successfully removed auth method", + type: "success" + }); + handlePopUpToggle("revokeAuthMethod", false); + onDeleteAuthMethod(); }; return ( @@ -183,7 +175,7 @@ export const Content = ({ handlePopUpToggle("upgradePlan", isOpen)} - text={(popUp.upgradePlan?.data as { description: string })?.description} + text={`Your current plan does not include access to ${popUp.upgradePlan.data?.featureName}. To unlock this feature, please upgrade to Infisical ${popUp.upgradePlan.data?.isEnterpriseFeature ? "Enterprise" : "Pro"} plan.`} /> ); diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx index 7537d95a3..ae44cdd1e 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx @@ -36,7 +36,6 @@ import { RelayOption } from "./RelayOption"; const baseFormSchema = z.object({ name: slugSchema({ field: "name" }), - instanceDomain: z.string().url("Must be a valid URL").or(z.literal("")), relay: z .object( { @@ -78,7 +77,6 @@ export const GatewayCliDeploymentMethod = () => { const [autogenerateToken, setAutogenerateToken] = useState(true); const [step, setStep] = useState<"form" | "command">("form"); const [name, setName] = useState(""); - const [instanceDomain, setInstanceDomain] = useState(siteURL); const [relay, setRelay] = useState { const validation = formSchemaWithIdentity.safeParse({ name, relay, - identity, - instanceDomain + identity }); if (!validation.success) { setFormErrors(validation.error.issues); @@ -168,20 +165,14 @@ export const GatewayCliDeploymentMethod = () => { type: "info" }); setStep("command"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to generate token for the selected identity", - type: "error" - }); + } catch { setIdentityToken(""); } } else { const validation = formSchemaWithToken.safeParse({ name, relay, - identityToken, - instanceDomain + identityToken }); if (!validation.success) { setFormErrors(validation.error.issues); @@ -192,11 +183,10 @@ export const GatewayCliDeploymentMethod = () => { }; const command = useMemo(() => { - const domainFlag = instanceDomain ? ` --domain=${instanceDomain}` : ""; return `infisical gateway start --name=${name} --relay=${ relay?.name || "" - }${domainFlag} --token=${identityToken}`; - }, [name, relay, identityToken, instanceDomain]); + } --domain=${siteURL} --token=${identityToken}`; + }, [name, relay, identityToken, siteURL]); if (step === "command") { return ( @@ -279,19 +269,6 @@ export const GatewayCliDeploymentMethod = () => { /> {errors.relay &&

{errors.relay}

} - - setInstanceDomain(e.target.value)} - placeholder="https://app.infisical.com" - isError={Boolean(errors.instanceDomain)} - /> - {errors.instanceDomain &&

{errors.instanceDomain}

} - {canCreateToken && autogenerateToken ? ( <> { const [name, setName] = useState(""); const [host, setHost] = useState(""); - const [instanceDomain, setInstanceDomain] = useState(siteURL); const [identity, setIdentity] = useState { setFormErrors([]); if (canCreateToken && autogenerateToken) { - const validation = formSchemaWithIdentity.safeParse({ name, host, instanceDomain, identity }); + const validation = formSchemaWithIdentity.safeParse({ name, host, identity }); if (!validation.success) { setFormErrors(validation.error.issues); return; @@ -141,19 +139,13 @@ export const RelayCliDeploymentMethod = () => { type: "info" }); setStep("command"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to generate token for the selected identity", - type: "error" - }); + } catch { setIdentityToken(""); } } else { const validation = formSchemaWithToken.safeParse({ name, host, - instanceDomain, identityToken }); if (!validation.success) { @@ -174,9 +166,8 @@ export const RelayCliDeploymentMethod = () => { }; const command = useMemo(() => { - const domainFlag = instanceDomain ? ` --domain=${instanceDomain}` : ""; - return `infisical relay start --name=${name}${domainFlag} --host=${host} --token=${identityToken}`; - }, [name, instanceDomain, host, identityToken]); + return `infisical relay start --name=${name} --domain=${siteURL} --host=${host} --token=${identityToken}`; + }, [name, siteURL, host, identityToken]); if (step === "command") { return ( @@ -244,19 +235,6 @@ export const RelayCliDeploymentMethod = () => { /> {errors.host &&

{errors.host}

} - - setInstanceDomain(e.target.value)} - placeholder="https://app.infisical.com" - isError={Boolean(errors.instanceDomain)} - /> - {errors.instanceDomain &&

{errors.instanceDomain}

} - {canCreateToken && autogenerateToken ? ( <> val !== null, { message: "Identity is required" }) +}); + +const formSchemaWithToken = baseFormSchema.extend({ + identityToken: z.string().min(1, "Token is required") +}); + +const ec2FormSchema = z.object({ + awsRegion: z.string().min(1, "AWS Region is required"), + vpcId: z.string().min(1, "VPC ID is required"), + ami: z.string().min(1, "AMI ID is required"), + subnetId: z.string().min(1, "Subnet ID is required") +}); + +export const RelayTerraformDeploymentMethod = () => { + const { protocol, hostname, port } = window.location; + const portSuffix = port && port !== "80" ? `:${port}` : ""; + const siteURL = `${protocol}//${hostname}${portSuffix}`; + + const [selectedTabIndex, setSelectedTabIndex] = useState(0); + + const [autogenerateToken, setAutogenerateToken] = useState(true); + const [step, setStep] = useState<"form" | "command">("form"); + const [name, setName] = useState(""); + + const [identity, setIdentity] = useState(null); + const [identityToken, setIdentityToken] = useState(""); + const [formErrors, setFormErrors] = useState([]); + + const [awsRegion, setAwsRegion] = useState("us-east-1"); + const [vpcId, setVpcId] = useState(""); + const [ami, setAmi] = useState("ami-01b2110eef525172b"); + const [subnetId, setSubnetId] = useState(""); + + const errors = useMemo(() => { + const errorMap: Record = {}; + formErrors.forEach((issue) => { + if (issue.path.length > 0) { + errorMap[String(issue.path[0])] = issue.message; + } + }); + return errorMap; + }, [formErrors]); + + const { currentOrg } = useOrganization(); + const organizationId = currentOrg?.id || ""; + + const { permission } = useOrgPermission(); + const canCreateToken = permission.can( + OrgPermissionIdentityActions.CreateToken, + OrgPermissionSubjects.Identity + ); + + const { data: identityMembershipOrgsData, isPending: isIdentitiesLoading } = + useGetIdentityMembershipOrgs({ + organizationId, + limit: 20000 + }); + const identityMembershipOrgs = identityMembershipOrgsData?.identityMemberships || []; + + const { mutateAsync: createToken, isPending: isCreatingToken } = + useCreateTokenIdentityTokenAuth(); + const { mutateAsync: addIdentityTokenAuth, isPending: isAddingTokenAuth } = + useAddIdentityTokenAuth(); + const { refetch } = useGetIdentityTokenAuth(identity?.id ?? ""); + + const handleGenerateCommand = async () => { + setFormErrors([]); + + if (canCreateToken && autogenerateToken) { + const validation = formSchemaWithIdentity.safeParse({ name, identity }); + if (!validation.success) { + setFormErrors(validation.error.issues); + return; + } + + if (selectedTabIndex === 0) { + const ec2Validation = ec2FormSchema.safeParse({ awsRegion, vpcId, ami, subnetId }); + if (!ec2Validation.success) { + setFormErrors(ec2Validation.error.issues); + return; + } + } + + const validatedIdentity = validation.data.identity; + + try { + const { data: identityTokenAuth } = await refetch(); + if (!identityTokenAuth) { + await addIdentityTokenAuth({ + identityId: validatedIdentity.id, + organizationId, + accessTokenTTL: 2592000, + accessTokenMaxTTL: 2592000, + accessTokenNumUsesLimit: 0, + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + }); + createNotification({ + text: "Token authentication has been automatically enabled for the selected identity. By default, it is configured to allow all IP addresses with a default token TTL of 30 days. You can manage these settings in Access Control.", + type: "warning" + }); + } + + const token = await createToken({ + identityId: validatedIdentity.id, + name: `relay token for ${name} (autogenerated)` + }); + setIdentityToken(token.accessToken); + createNotification({ + text: "Automatically generated a token for the selected identity.", + type: "info" + }); + setStep("command"); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to generate token for the selected identity", + type: "error" + }); + setIdentityToken(""); + } + } else { + const validation = formSchemaWithToken.safeParse({ + name, + identityToken + }); + if (!validation.success) { + setFormErrors(validation.error.issues); + return; + } + + if (selectedTabIndex === 0) { + const ec2Validation = ec2FormSchema.safeParse({ awsRegion, vpcId, ami, subnetId }); + if (!ec2Validation.success) { + setFormErrors(ec2Validation.error.issues); + return; + } + } + setStep("command"); + } + }; + + const handleIdentityChange = ( + selectedIdentity: SingleValue<{ + id: string; + name: string; + }> + ) => { + setIdentity(selectedIdentity); + }; + + const terraformCommand = useMemo(() => { + return `terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +provider "aws" { + region = "${awsRegion}" +} + +# Security Group for the Infisical Relay instance +resource "aws_security_group" "infisical_relay_sg" { + name = "${name}-relay-sg" + description = "Allows inbound traffic for Infisical Relay and SSH" + vpc_id = "${vpcId}" + + # Inbound: Allows the Infisical platform to securely communicate with the Relay server. + ingress { + from_port = 8443 + to_port = 8443 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + # Inbound: Allows Infisical Gateway to securely communicate via the Relay. + ingress { + from_port = 2222 + to_port = 2222 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + # Inbound: Allows secure shell (SSH) access for administration. + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] # Restrict this to your IP in production + } + + # Outbound: Allows the Relay server to make necessary outbound connections to the Infisical platform. + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "${name}-relay-sg" + } +} + +# Elastic IP for a static public IP address +resource "aws_eip" "infisical_relay_eip" { + tags = { + Name = "${name}-relay-eip" + } +} + +# EC2 instance to run Infisical Relay +module "infisical_relay_instance" { + source = "terraform-aws-modules/ec2-instance/aws" + version = "~> 5.6" + + name = "${name}-relay-instance" + ami = "${ami}" + instance_type = "t3.micro" + subnet_id = "${subnetId}" + + vpc_security_group_ids = [aws_security_group.infisical_relay_sg.id] + associate_public_ip_address = false # We are using an Elastic IP instead + + user_data = <<-EOT + #!/bin/bash + set -e + # Install Infisical CLI + curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash + apt-get update && apt-get install -y infisical + + # Install the relay as a systemd service. + # This example uses a Machine Identity token for authentication via the INFISICAL_TOKEN environment variable. + # + # Note: For production environments, you might consider fetching the token from AWS Parameter Store or AWS Secrets Manager. + export INFISICAL_TOKEN="${identityToken}" + sudo -E infisical relay systemd install \\ + --name "${name}" \\ + --domain "${siteURL}" \\ + --host "\${aws_eip.infisical_relay_eip.public_ip}" + + # Start and enable the service to run on boot + sudo systemctl start infisical-relay + sudo systemctl enable infisical-relay + EOT +} + +# Associate the Elastic IP with the EC2 instance +resource "aws_eip_association" "eip_assoc" { + instance_id = module.infisical_relay_instance.id + allocation_id = aws_eip.infisical_relay_eip.id +} +`; + }, [name, siteURL, identityToken, awsRegion, vpcId, ami, subnetId]); + + if (step === "command") { + return ( + <> +
+ Terraform Configuration + { + navigator.clipboard.writeText(terraformCommand); + createNotification({ + text: "Terraform configuration copied to clipboard", + type: "info" + }); + }} + className="w-10" + > + + +
+
+
+            {terraformCommand}
+          
+
+
+ + + +
+ + ); + } + + return ( + <> + + setName(e.target.value)} + placeholder="Enter relay name..." + isError={Boolean(errors.name)} + /> + {errors.name &&

{errors.name}

} + + {canCreateToken && autogenerateToken ? ( + <> + + + handleIdentityChange( + e as SingleValue<{ + id: string; + name: string; + }> + ) + } + isLoading={isIdentitiesLoading} + placeholder="Select identity..." + options={identityMembershipOrgs.map((membership) => membership.identity)} + getOptionValue={(option) => option.id} + getOptionLabel={(option) => option.name} + /> + {errors.identity &&

{errors.identity}

} + + ) : ( + <> + + setIdentityToken(e.target.value)} + placeholder="Enter identity token..." + isError={Boolean(errors.identityToken)} + /> + {errors.identityToken &&

{errors.identityToken}

} + + )} + + {canCreateToken && ( +
+ { + setAutogenerateToken(Boolean(e)); + }} + id="autogenerate-token" + className="mr-2" + > +
+ Automatically enable token auth and generate a token for identity + + Token authentication will be automatically enabled for the selected identity if + it isn't already configured. By default, it will be configured to allow all + IP addresses with a token TTL of 30 days. You can manage these settings in + Access Control. +
+
A token will automatically be generated to be used with the CLI command. + + } + > + +
+
+
+
+ )} + + + + + `-mb-[0.14rem] px-4 py-2 text-sm font-medium whitespace-nowrap outline-hidden disabled:opacity-60 ${ + selected ? "border-b-2 border-mineshaft-300 text-mineshaft-200" : "text-bunker-300" + }` + } + > + EC2 + + + + + + r.slug === awsRegion)} + onChange={(selected) => { + if (selected) { + setAwsRegion((selected as SingleValue<{ slug: string; name: string }>)!.slug); + } + }} + options={AWS_REGIONS} + getOptionLabel={(option) => option.name} + getOptionValue={(option) => option.slug} + /> + {errors.awsRegion &&

{errors.awsRegion}

} + + setVpcId(e.target.value)} + placeholder="vpc-..." + isError={Boolean(errors.vpcId)} + /> + {errors.vpcId &&

{errors.vpcId}

} + + setAmi(e.target.value)} + placeholder="ami-..." + isError={Boolean(errors.ami)} + /> + {errors.ami &&

{errors.ami}

} + + setSubnetId(e.target.value)} + placeholder="subnet-..." + isError={Boolean(errors.subnetId)} + /> + {errors.subnetId &&

{errors.subnetId}

} +
+
+
+ +
+ + + + +
+ + ); +}; diff --git a/frontend/src/pages/organization/ProjectsPage/ProjectsPage.tsx b/frontend/src/pages/organization/ProjectsPage/ProjectsPage.tsx index b916c7867..aba2e752d 100644 --- a/frontend/src/pages/organization/ProjectsPage/ProjectsPage.tsx +++ b/frontend/src/pages/organization/ProjectsPage/ProjectsPage.tsx @@ -92,7 +92,7 @@ export const ProjectsPage = () => { handlePopUpToggle("upgradePlan", isOpen)} - text="You have exceeded the number of projects allowed on the free plan. You can upgrade to Infisical's Pro plan to add more projects." + text="You have reached the maximum number of projects allowed on your current plan. Upgrade to Infisical Pro plan to add more projects." />
); diff --git a/frontend/src/pages/organization/ProjectsPage/components/AllProjectView.tsx b/frontend/src/pages/organization/ProjectsPage/components/AllProjectView.tsx index cf60ffa59..a1bd8c9e4 100644 --- a/frontend/src/pages/organization/ProjectsPage/components/AllProjectView.tsx +++ b/frontend/src/pages/organization/ProjectsPage/components/AllProjectView.tsx @@ -13,7 +13,6 @@ import { useNavigate } from "@tanstack/react-router"; import { CheckIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; -import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { RequestProjectAccessModal } from "@app/components/projects/RequestProjectAccessModal"; import { @@ -103,22 +102,15 @@ export const AllProjectView = ({ projectId: string, environments: ProjectEnv[] ) => { - try { - await orgAdminAccessProject.mutateAsync({ + await orgAdminAccessProject.mutateAsync({ + projectId + }); + await navigate({ + to: getProjectHomePage(type, environments), + params: { projectId - }); - await navigate({ - to: getProjectHomePage(type, environments), - params: { - projectId - } - }); - } catch { - createNotification({ - text: "Failed to access project", - type: "error" - }); - } + } + }); }; useResetPageHelper({ diff --git a/frontend/src/pages/organization/ProjectsPage/components/MyProjectView.tsx b/frontend/src/pages/organization/ProjectsPage/components/MyProjectView.tsx index a86023b6c..32301923b 100644 --- a/frontend/src/pages/organization/ProjectsPage/components/MyProjectView.tsx +++ b/frontend/src/pages/organization/ProjectsPage/components/MyProjectView.tsx @@ -15,7 +15,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate } from "@tanstack/react-router"; import { twMerge } from "tailwind-merge"; -import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { Button, @@ -158,32 +157,18 @@ export const MyProjectView = ({ }; const addProjectToFavorites = async (projectId: string) => { - try { - if (currentOrg?.id) { - await updateUserProjectFavorites({ - orgId: currentOrg?.id, - projectFavorites: [...(projectFavorites || []), projectId] - }); - } - } catch { - createNotification({ - text: "Failed to add project to favorites.", - type: "error" + if (currentOrg?.id) { + await updateUserProjectFavorites({ + orgId: currentOrg?.id, + projectFavorites: [...(projectFavorites || []), projectId] }); } }; const removeProjectFromFavorites = async (projectId: string) => { - try { - if (currentOrg?.id) { - await updateUserProjectFavorites({ - orgId: currentOrg?.id, - projectFavorites: [...(projectFavorites || []).filter((entry) => entry !== projectId)] - }); - } - } catch { - createNotification({ - text: "Failed to remove project from favorites.", - type: "error" + if (currentOrg?.id) { + await updateUserProjectFavorites({ + orgId: currentOrg?.id, + projectFavorites: [...(projectFavorites || []).filter((entry) => entry !== projectId)] }); } }; diff --git a/frontend/src/pages/organization/RoleByIDPage/RoleByIDPage.tsx b/frontend/src/pages/organization/RoleByIDPage/RoleByIDPage.tsx index 0d30dd2f8..78c5393a7 100644 --- a/frontend/src/pages/organization/RoleByIDPage/RoleByIDPage.tsx +++ b/frontend/src/pages/organization/RoleByIDPage/RoleByIDPage.tsx @@ -42,36 +42,25 @@ export const Page = () => { ] as const); const onDeleteOrgRoleSubmit = async () => { - try { - if (!orgId || !roleId) return; + if (!orgId || !roleId) return; - await deleteOrgRole({ - orgId, - id: roleId - }); + await deleteOrgRole({ + orgId, + id: roleId + }); - createNotification({ - text: "Successfully deleted organization role", - type: "success" - }); + createNotification({ + text: "Successfully deleted organization role", + type: "success" + }); - handlePopUpClose("deleteOrgRole"); - navigate({ - to: "/organization/access-management" as const, - search: { - selectedTab: OrgAccessControlTabSections.Roles - } - }); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to delete organization role"; - - createNotification({ - text, - type: "error" - }); - } + handlePopUpClose("deleteOrgRole"); + navigate({ + to: "/organization/access-management" as const, + search: { + selectedTab: OrgAccessControlTabSections.Roles + } + }); }; const isCustomRole = !["admin", "member", "no-access"].includes(data?.slug ?? ""); diff --git a/frontend/src/pages/organization/RoleByIDPage/components/RoleModal.tsx b/frontend/src/pages/organization/RoleByIDPage/components/RoleModal.tsx index 88f568c4a..73da4f161 100644 --- a/frontend/src/pages/organization/RoleByIDPage/components/RoleModal.tsx +++ b/frontend/src/pages/organization/RoleByIDPage/components/RoleModal.tsx @@ -70,55 +70,46 @@ export const RoleModal = ({ popUp, handlePopUpToggle }: Props) => { }, [role]); const onFormSubmit = async ({ name, description, slug }: FormData) => { - try { - if (!orgId) return; + if (!orgId) return; - if (role) { - // update + if (role) { + // update - await updateOrgRole({ - orgId, - id: role.id, - name, - description, - slug - }); - - handlePopUpToggle("role", false); - } else { - // create - - const newRole = await createOrgRole({ - orgId, - name, - description, - slug, - permissions: [] - }); - - handlePopUpToggle("role", false); - navigate({ - to: "/organization/roles/$roleId", - params: { - roleId: newRole.id - } - }); - } - - createNotification({ - text: `Successfully ${popUp?.role?.data ? "updated" : "created"} role`, - type: "success" + await updateOrgRole({ + orgId, + id: role.id, + name, + description, + slug }); - reset(); - } catch { - const text = `Failed to ${popUp?.role?.data ? "update" : "create"} role`; + handlePopUpToggle("role", false); + } else { + // create - createNotification({ - text, - type: "error" + const newRole = await createOrgRole({ + orgId, + name, + description, + slug, + permissions: [] + }); + + handlePopUpToggle("role", false); + navigate({ + to: "/organization/roles/$roleId", + params: { + roleId: newRole.id + } }); } + + createNotification({ + text: `Successfully ${popUp?.role?.data ? "updated" : "created"} role`, + type: "success" + }); + + reset(); }; return ( diff --git a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx index 0d6b269a8..c25d11cb7 100644 --- a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx +++ b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx @@ -107,18 +107,13 @@ export const RolePermissionsSection = ({ roleId }: Props) => { const { mutateAsync: updateRole } = useUpdateOrgRole(); const onSubmit = async (el: TFormSchema) => { - try { - await updateRole({ - orgId, - id: roleId, - ...el, - permissions: formRolePermission2API(el.permissions) - }); - createNotification({ type: "success", text: "Successfully updated role" }); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to update role" }); - } + await updateRole({ + orgId, + id: roleId, + ...el, + permissions: formRolePermission2API(el.permissions) + }); + createNotification({ type: "success", text: "Successfully updated role" }); }; const isCustomRole = !["admin", "member", "no-access"].includes(role?.slug ?? ""); diff --git a/frontend/src/pages/organization/SecretSharingPage/components/RequestSecret/RequestSecretForm.tsx b/frontend/src/pages/organization/SecretSharingPage/components/RequestSecret/RequestSecretForm.tsx index 50a9e0f58..d50873339 100644 --- a/frontend/src/pages/organization/SecretSharingPage/components/RequestSecret/RequestSecretForm.tsx +++ b/frontend/src/pages/organization/SecretSharingPage/components/RequestSecret/RequestSecretForm.tsx @@ -56,35 +56,27 @@ export const RequestSecretForm = () => { const onFormSubmit = async ({ name, accessType, expiresIn }: FormData) => { const expiresAt = new Date(new Date().getTime() + Number(expiresIn)); - try { - const { id } = await createSecretRequest({ - name, - accessType, - expiresAt - }); + const { id } = await createSecretRequest({ + name, + accessType, + expiresAt + }); - const link = new URL(`${window.location.origin}/secret-request/secret/${id}`); - if (subOrganization) { - link.searchParams.set("subOrganization", subOrganization); - } - - setSecretLink(link.toString()); - reset(); - - navigator.clipboard.writeText(link.toString()); - setCopyTextSecret("secret"); - - createNotification({ - text: "Shared secret link copied to clipboard.", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to create a shared secret.", - type: "error" - }); + const link = new URL(`${window.location.origin}/secret-request/secret/${id}`); + if (subOrganization) { + link.searchParams.set("subOrganization", subOrganization); } + + setSecretLink(link.toString()); + reset(); + + navigator.clipboard.writeText(link.toString()); + setCopyTextSecret("secret"); + + createNotification({ + text: "Shared secret link copied to clipboard.", + type: "success" + }); }; const hasSecretLink = Boolean(secretLink); diff --git a/frontend/src/pages/organization/SecretSharingPage/components/RequestSecret/RequestSecretTab.tsx b/frontend/src/pages/organization/SecretSharingPage/components/RequestSecret/RequestSecretTab.tsx index fd3b1d5b5..1253cdbc0 100644 --- a/frontend/src/pages/organization/SecretSharingPage/components/RequestSecret/RequestSecretTab.tsx +++ b/frontend/src/pages/organization/SecretSharingPage/components/RequestSecret/RequestSecretTab.tsx @@ -22,23 +22,15 @@ export const RequestSecretTab = () => { const { mutateAsync: deleteSecretRequest } = useDeleteSecretRequest(); const onDeleteApproved = async () => { - try { - await deleteSecretRequest({ - secretRequestId: popUp.deleteSecretRequestConfirmation.data?.id - }); - createNotification({ - text: "Successfully deleted secret request", - type: "success" - }); + await deleteSecretRequest({ + secretRequestId: popUp.deleteSecretRequestConfirmation.data?.id + }); + createNotification({ + text: "Successfully deleted secret request", + type: "success" + }); - handlePopUpClose("deleteSecretRequestConfirmation"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete shared secret", - type: "error" - }); - } + handlePopUpClose("deleteSecretRequestConfirmation"); }; return ( diff --git a/frontend/src/pages/organization/SecretSharingPage/components/SecretSharingSettings/OrgSecretShareLimitSection.tsx b/frontend/src/pages/organization/SecretSharingPage/components/SecretSharingSettings/OrgSecretShareLimitSection.tsx index 034c7d8fe..d7c337332 100644 --- a/frontend/src/pages/organization/SecretSharingPage/components/SecretSharingSettings/OrgSecretShareLimitSection.tsx +++ b/frontend/src/pages/organization/SecretSharingPage/components/SecretSharingSettings/OrgSecretShareLimitSection.tsx @@ -90,28 +90,21 @@ export const OrgSecretShareLimitSection = () => { }, [currentOrg, reset]); const handleFormSubmit = async (formData: TForm) => { - try { - const maxSharedSecretLifetimeSeconds = - ms(`${formData.maxLifetimeValue}${formData.maxLifetimeUnit}`) / 1000; + const maxSharedSecretLifetimeSeconds = + ms(`${formData.maxLifetimeValue}${formData.maxLifetimeUnit}`) / 1000; - await mutateAsync({ - orgId: currentOrg.id, - maxSharedSecretViewLimit: formData.shouldLimitView ? Number(formData.maxViewLimit) : null, - maxSharedSecretLifetime: maxSharedSecretLifetimeSeconds - }); + await mutateAsync({ + orgId: currentOrg.id, + maxSharedSecretViewLimit: formData.shouldLimitView ? Number(formData.maxViewLimit) : null, + maxSharedSecretLifetime: maxSharedSecretLifetimeSeconds + }); - createNotification({ - text: "Successfully updated secret share limits", - type: "success" - }); + createNotification({ + text: "Successfully updated secret share limits", + type: "success" + }); - reset(formData); - } catch { - createNotification({ - text: "Failed to update secret share limits", - type: "error" - }); - } + reset(formData); }; // Units for the dropdown with readable labels diff --git a/frontend/src/pages/organization/SecretSharingPage/components/SecretSharingSettings/SecretSharingAllowShareToAnyone.tsx b/frontend/src/pages/organization/SecretSharingPage/components/SecretSharingSettings/SecretSharingAllowShareToAnyone.tsx index 1ea62c187..31b2221ce 100644 --- a/frontend/src/pages/organization/SecretSharingPage/components/SecretSharingSettings/SecretSharingAllowShareToAnyone.tsx +++ b/frontend/src/pages/organization/SecretSharingPage/components/SecretSharingSettings/SecretSharingAllowShareToAnyone.tsx @@ -9,25 +9,17 @@ export const SecretSharingAllowShareToAnyone = () => { const { mutateAsync } = useUpdateOrg(); const handleSecretSharingToggle = async (value: boolean) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - await mutateAsync({ - orgId: currentOrg.id, - allowSecretSharingOutsideOrganization: value - }); + await mutateAsync({ + orgId: currentOrg.id, + allowSecretSharingOutsideOrganization: value + }); - createNotification({ - text: `Successfully ${value ? "enabled" : "disabled"} secret sharing to members outside of this organization`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: (err as { response: { data: { message: string } } }).response.data.message, - type: "error" - }); - } + createNotification({ + text: `Successfully ${value ? "enabled" : "disabled"} secret sharing to members outside of this organization`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/SecretSharingPage/components/ShareSecret/ShareSecretTab.tsx b/frontend/src/pages/organization/SecretSharingPage/components/ShareSecret/ShareSecretTab.tsx index d4f2f0a1f..2ea61ca98 100644 --- a/frontend/src/pages/organization/SecretSharingPage/components/ShareSecret/ShareSecretTab.tsx +++ b/frontend/src/pages/organization/SecretSharingPage/components/ShareSecret/ShareSecretTab.tsx @@ -20,23 +20,15 @@ export const ShareSecretTab = () => { const deleteSecretShare = useDeleteSharedSecret(); const onDeleteApproved = async () => { - try { - deleteSecretShare.mutateAsync({ - sharedSecretId: (popUp?.deleteSharedSecretConfirmation?.data as DeleteModalData)?.id - }); - createNotification({ - text: "Successfully deleted shared secret", - type: "success" - }); + deleteSecretShare.mutateAsync({ + sharedSecretId: (popUp?.deleteSharedSecretConfirmation?.data as DeleteModalData)?.id + }); + createNotification({ + text: "Successfully deleted shared secret", + type: "success" + }); - handlePopUpClose("deleteSharedSecretConfirmation"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete shared secret", - type: "error" - }); - } + handlePopUpClose("deleteSharedSecretConfirmation"); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/OauthCallbackPage/OauthCallbackPage.tsx b/frontend/src/pages/organization/SettingsPage/OauthCallbackPage/OauthCallbackPage.tsx index df5bf4f60..5efec93d9 100644 --- a/frontend/src/pages/organization/SettingsPage/OauthCallbackPage/OauthCallbackPage.tsx +++ b/frontend/src/pages/organization/SettingsPage/OauthCallbackPage/OauthCallbackPage.tsx @@ -72,20 +72,12 @@ export const OAuthCallbackPage = () => { if (!isReady) return; (async () => { - try { - await handleMicrosoftTeams(); + await handleMicrosoftTeams(); - createNotification({ - text: "Successfully created Microsoft Teams workflow integration", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to create Microsoft Teams workflow integration", - type: "error" - }); - } + createNotification({ + text: "Successfully created Microsoft Teams workflow integration", + type: "success" + }); })(); }, [isReady]); diff --git a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamForm/AuditLogStreamForm.tsx b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamForm/AuditLogStreamForm.tsx index f59ae23aa..5ea16050a 100644 --- a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamForm/AuditLogStreamForm.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamForm/AuditLogStreamForm.tsx @@ -28,21 +28,12 @@ const CreateForm = ({ provider, onComplete }: CreateFormProps) => { const onSubmit = async ( formData: DiscriminativePick ) => { - try { - const logStream = await createAuditLogStream.mutateAsync(formData); - createNotification({ - text: `Successfully created ${providerName} Log Stream`, - type: "success" - }); - onComplete(logStream); - } catch (err: any) { - console.error(err); - createNotification({ - title: `Failed to create ${providerName} Log Stream`, - text: err.message, - type: "error" - }); - } + const logStream = await createAuditLogStream.mutateAsync(formData); + createNotification({ + text: `Successfully created ${providerName} Log Stream`, + type: "success" + }); + onComplete(logStream); }; switch (provider) { @@ -68,24 +59,15 @@ const UpdateForm = ({ auditLogStream, onComplete }: UpdateFormProps) => { const onSubmit = async ( formData: DiscriminativePick ) => { - try { - const connection = await updateAuditLogStream.mutateAsync({ - auditLogStreamId: auditLogStream.id, - ...formData - }); - createNotification({ - text: `Successfully updated ${providerName} Log Stream`, - type: "success" - }); - onComplete(connection); - } catch (err: any) { - console.error(err); - createNotification({ - title: `Failed to update ${providerName} Log Stream`, - text: err.message, - type: "error" - }); - } + const connection = await updateAuditLogStream.mutateAsync({ + auditLogStreamId: auditLogStream.id, + ...formData + }); + createNotification({ + text: `Successfully updated ${providerName} Log Stream`, + type: "success" + }); + onComplete(connection); }; switch (auditLogStream.provider) { diff --git a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamTab.tsx b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamTab.tsx index 2ccdfad61..d8dca7401 100644 --- a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamTab.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamTab.tsx @@ -57,7 +57,7 @@ export const AuditLogStreamsTab = withPermission( handlePopUpToggle("upgradePlan", isOpen)} - text="You can add audit log streams if you switch to Infisical's Enterprise plan." + text="Your current plan does not include access to audit log streams. To unlock this feature, please upgrade to Infisical Enterprise plan." isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} />
diff --git a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/DeleteAuditLogStreamModal.tsx b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/DeleteAuditLogStreamModal.tsx index 1a65e6c67..74eb19af3 100644 --- a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/DeleteAuditLogStreamModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/DeleteAuditLogStreamModal.tsx @@ -20,26 +20,17 @@ export const DeleteAuditLogStreamModal = ({ isOpen, onOpenChange, auditLogStream const providerDetails = AUDIT_LOG_STREAM_PROVIDER_MAP[provider]; const handleDelete = async () => { - try { - await deleteAuditLogStream.mutateAsync({ - auditLogStreamId, - provider - }); + await deleteAuditLogStream.mutateAsync({ + auditLogStreamId, + provider + }); - createNotification({ - text: `Successfully deleted ${providerDetails.name} stream`, - type: "success" - }); + createNotification({ + text: `Successfully deleted ${providerDetails.name} stream`, + type: "success" + }); - onOpenChange(false); - } catch (err) { - console.error(err); - - createNotification({ - text: `Failed to delete ${providerDetails.name} stream`, - type: "error" - }); - } + onOpenChange(false); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/LogStreamProviderSelect.tsx b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/LogStreamProviderSelect.tsx index df57dab88..6d1be6531 100644 --- a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/LogStreamProviderSelect.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/LogStreamProviderSelect.tsx @@ -111,7 +111,7 @@ export const LogStreamProviderSelect = ({ onSelect }: Props) => { handlePopUpToggle("upgradePlan", isOpen)} - text="This audit log stream provider requires an enterprise license." + text="Your current plan does not include access to this audit log stream provider. To unlock this feature, please upgrade to Infisical Enterprise plan." isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} />
diff --git a/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/components/VaultConnectionSection.tsx b/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/components/VaultConnectionSection.tsx index c91e9c24c..e802e250b 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/components/VaultConnectionSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/components/VaultConnectionSection.tsx @@ -54,21 +54,13 @@ export const VaultConnectionSection = () => { const handleDeleteConfirm = async () => { if (!configToDelete) return; - try { - await deleteConfig({ id: configToDelete.id }); - createNotification({ - type: "success", - text: "Namespace configuration deleted successfully" - }); - setIsDeleteModalOpen(false); - setConfigToDelete(null); - } catch (error) { - console.error("Failed to delete namespace config:", error); - createNotification({ - type: "error", - text: "Failed to delete namespace configuration" - }); - } + await deleteConfig({ id: configToDelete.id }); + createNotification({ + type: "success", + text: "Namespace configuration deleted successfully" + }); + setIsDeleteModalOpen(false); + setConfigToDelete(null); }; const getConnectionName = (connectionId: string | null) => { diff --git a/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/components/VaultNamespaceConfigModal.tsx b/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/components/VaultNamespaceConfigModal.tsx index 62fb78b48..f08aae204 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/components/VaultNamespaceConfigModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/components/VaultNamespaceConfigModal.tsx @@ -74,36 +74,28 @@ export const VaultNamespaceConfigModal = ({ isOpen, onOpenChange, editConfig }: }, [isOpen, editConfig, reset]); const onFormSubmit = async (data: FormData) => { - try { - if (isEdit && editConfig) { - await updateConfig({ - id: editConfig.id, - namespace: data.namespace, - connectionId: data.connectionId - }); - createNotification({ - type: "success", - text: "Namespace configuration updated successfully" - }); - } else { - await createConfig({ - namespace: data.namespace, - connectionId: data.connectionId - }); - createNotification({ - type: "success", - text: "Namespace configuration created successfully" - }); - } - reset(); - onOpenChange(false); - } catch (error) { - console.error("Failed to save namespace config:", error); + if (isEdit && editConfig) { + await updateConfig({ + id: editConfig.id, + namespace: data.namespace, + connectionId: data.connectionId + }); createNotification({ - type: "error", - text: `Failed to ${isEdit ? "update" : "create"} namespace configuration` + type: "success", + text: "Namespace configuration updated successfully" + }); + } else { + await createConfig({ + namespace: data.namespace, + connectionId: data.connectionId + }); + createNotification({ + type: "success", + text: "Namespace configuration created successfully" }); } + reset(); + onOpenChange(false); }; const handleClose = () => { diff --git a/frontend/src/pages/organization/SettingsPage/components/KmipTab/OrgKmipTab.tsx b/frontend/src/pages/organization/SettingsPage/components/KmipTab/OrgKmipTab.tsx index 7203cb9ef..1ab529ea3 100644 --- a/frontend/src/pages/organization/SettingsPage/components/KmipTab/OrgKmipTab.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/KmipTab/OrgKmipTab.tsx @@ -252,7 +252,7 @@ const OrgConfigSection = ({ handlePopUpToggle("upgradePlan", isOpen)} - text="KMIP requires an enterprise plan." + text="Your current plan does not include access to KMIP. To unlock this feature, please upgrade to Infisical Enterprise plan." isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} /> diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgDeleteSection/OrgDeleteSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgDeleteSection/OrgDeleteSection.tsx index 4dfe89f71..6e5c0e1cb 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgDeleteSection/OrgDeleteSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgDeleteSection/OrgDeleteSection.tsx @@ -19,27 +19,19 @@ export const OrgDeleteSection = () => { const { mutateAsync, isPending } = useDeleteOrgById(); const handleDeleteOrgSubmit = async () => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - await mutateAsync({ - organizationId: currentOrg?.id - }); + await mutateAsync({ + organizationId: currentOrg?.id + }); - createNotification({ - text: "Successfully deleted organization", - type: "success" - }); + createNotification({ + text: "Successfully deleted organization", + type: "success" + }); - clearSession(); - navigate({ to: "/login" }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete organization", - type: "error" - }); - } + clearSession(); + navigate({ to: "/login" }); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/OrgEncryptionTab.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/OrgEncryptionTab.tsx index a0b09b573..81649b604 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/OrgEncryptionTab.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/OrgEncryptionTab.tsx @@ -51,18 +51,14 @@ export const OrgEncryptionTab = withPermission( kmsId: string; }; - try { - await removeExternalKms(kmsId); + await removeExternalKms(kmsId); - createNotification({ - text: "Successfully deleted external KMS", - type: "success" - }); + createNotification({ + text: "Successfully deleted external KMS", + type: "success" + }); - handlePopUpToggle("removeExternalKms", false); - } catch (err) { - console.error(err); - } + handlePopUpToggle("removeExternalKms", false); }; return ( @@ -125,7 +121,7 @@ export const OrgEncryptionTab = withPermission( handlePopUpToggle("upgradePlan", isOpen)} - text="You can configure external KMS if you switch to Infisical's Enterprise plan." + text="Your current plan does not include access to external KMS. To unlock this feature, please upgrade to Infisical Enterprise plan." isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} /> { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - await mutateAsync({ - orgId: currentOrg.id, - email - }); + await mutateAsync({ + orgId: currentOrg.id, + email + }); - createNotification({ - text: "Successfully added incident contact", - type: "success" - }); + createNotification({ + text: "Successfully added incident contact", + type: "success" + }); - if (serverDetails?.emailConfigured) { - handlePopUpClose("addContact"); - } - - reset(); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to add incident contact", - type: "error" - }); + if (serverDetails?.emailConfigured) { + handlePopUpClose("addContact"); } + + reset(); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgIncidentContactsSection/OrgIncidentContactsTable.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgIncidentContactsSection/OrgIncidentContactsTable.tsx index d0723fdaf..c16431b9c 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgIncidentContactsSection/OrgIncidentContactsTable.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgIncidentContactsSection/OrgIncidentContactsTable.tsx @@ -33,28 +33,20 @@ export const OrgIncidentContactsTable = () => { const { mutateAsync } = useDeleteIncidentContact(); const onRemoveIncidentContact = async () => { - try { - const incidentContactId = (popUp?.removeContact?.data as { id: string })?.id; + const incidentContactId = (popUp?.removeContact?.data as { id: string })?.id; - if (!currentOrg?.id) return; - await mutateAsync({ - orgId: currentOrg.id, - incidentContactId - }); + if (!currentOrg?.id) return; + await mutateAsync({ + orgId: currentOrg.id, + incidentContactId + }); - createNotification({ - text: "Successfully removed incident contact", - type: "success" - }); + createNotification({ + text: "Successfully removed incident contact", + type: "success" + }); - handlePopUpClose("removeContact"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to remove incident contact", - type: "error" - }); - } + handlePopUpClose("removeContact"); }; const filteredContacts = contacts diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx index 766a22158..03fb14dc7 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx @@ -56,27 +56,19 @@ export const OrgNameChangeSection = (): JSX.Element => { }, [roles]); const onFormSubmit = async ({ name, slug, defaultMembershipRole }: FormData) => { - try { - if (!currentOrg?.id || !roles?.length) return; + if (!currentOrg?.id || !roles?.length) return; - await mutateAsync({ - orgId: currentOrg?.id, - name, - slug, - defaultMembershipRoleSlug: defaultMembershipRole - }); + await mutateAsync({ + orgId: currentOrg?.id, + name, + slug, + defaultMembershipRoleSlug: defaultMembershipRole + }); - createNotification({ - text: "Successfully updated organization details", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to update organization details", - type: "error" - }); - } + createNotification({ + text: "Successfully updated organization details", + type: "success" + }); }; if (!isFormInitialized) { diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgNameChangeSection/SubOrgNameChangeSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgNameChangeSection/SubOrgNameChangeSection.tsx index ca625be6c..e08d4d1b5 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgNameChangeSection/SubOrgNameChangeSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgNameChangeSection/SubOrgNameChangeSection.tsx @@ -39,26 +39,18 @@ export const SubOrgNameChangeSection = (): JSX.Element => { const { mutateAsync, isPending } = useUpdateSubOrganization(); const onFormSubmit = async ({ name }: FormData) => { - try { - await mutateAsync({ - name, - subOrgId: currentOrg.id - }); + await mutateAsync({ + name, + subOrgId: currentOrg.id + }); - navigate({ to: "/organization/settings", search: { subOrganization: name } }); - queryClient.invalidateQueries(); - await router.invalidate({ sync: true }); - createNotification({ - text: "Successfully updated sub-organization details", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to update sub-organization details", - type: "error" - }); - } + navigate({ to: "/organization/settings", search: { subOrganization: name } }); + queryClient.invalidateQueries(); + await router.invalidate({ sync: true }); + createNotification({ + text: "Successfully updated sub-organization details", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx index 5be15edad..e1fc2712d 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx @@ -1,7 +1,5 @@ import { useEffect, useState } from "react"; -import axios from "axios"; -import { createNotification } from "@app/components/notifications"; import { Switch } from "@app/components/v2"; import { useOrganization } from "@app/context"; import { useUpdateOrg } from "@app/hooks/api"; @@ -60,20 +58,10 @@ export const OrgProductSelectSection = () => { [key]: { ...products[key], enabled: value } })); - try { - await mutateAsync({ - orgId: currentOrg.id, - [key]: value - }); - } catch (e) { - if (axios.isAxiosError(e)) { - const { message = "Something went wrong" } = e.response?.data as { message: string }; - createNotification({ - type: "error", - text: message - }); - } - } + await mutateAsync({ + orgId: currentOrg.id, + [key]: value + }); setIsLoading(false); }; diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgProductSettingsTab/OrgProductSettingsTab.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgProductSettingsTab/OrgProductSettingsTab.tsx index e4d935996..95cc7b870 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgProductSettingsTab/OrgProductSettingsTab.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgProductSettingsTab/OrgProductSettingsTab.tsx @@ -30,12 +30,6 @@ export const OrgProductSettingsTab = () => { text: `Successfully ${state ? "enabled" : "disabled"} blocking duplicate secret sync destinations for this organization`, type: "success" }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update blocking duplicate secret sync destinations setting for this organization", - type: "error" - }); } finally { setIsLoading(false); } diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/ExternalGroupOrgRoleMappings.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/ExternalGroupOrgRoleMappings.tsx index 8e181e8c4..bc9a02921 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/ExternalGroupOrgRoleMappings.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/ExternalGroupOrgRoleMappings.tsx @@ -75,19 +75,11 @@ export const ExternalGroupOrgRoleMappings = () => { const mappingField = useFieldArray({ control, name: "mappings" }); const handleUpdateMappings = async (form: TForm) => { - try { - await updateMappings.mutateAsync(form); - createNotification({ - text: "Group organization role mappings updated.", - type: "success" - }); - } catch (e) { - console.error(e); - createNotification({ - text: "Failed to update group organization role mappings.", - type: "error" - }); - } + await updateMappings.mutateAsync(form); + createNotification({ + text: "Group organization role mappings updated.", + type: "success" + }); }; const disableScimEdit = permission.cannot(OrgPermissionActions.Edit, OrgPermissionSubjects.Scim); diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/GithubOrgSyncConfigModal.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/GithubOrgSyncConfigModal.tsx index 180b63278..868dd17f7 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/GithubOrgSyncConfigModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/GithubOrgSyncConfigModal.tsx @@ -55,55 +55,40 @@ export const GithubOrgSyncConfigModal = ({ }); const onFormSubmit = async ({ githubOrgName, githubOrgAccessToken }: FormData) => { - try { - if (isUpdate) { - await updateGithubSyncOrgConfig({ - githubOrgName, - githubOrgAccessToken - }); + if (isUpdate) { + await updateGithubSyncOrgConfig({ + githubOrgName, + githubOrgAccessToken + }); - createNotification({ - text: "Successfully updated GitHub Organization Sync", - type: "success" - }); - } else { - await createGithubSyncOrgConfig({ - githubOrgName, - githubOrgAccessToken, - isActive: false - }); - - createNotification({ - text: "Successfully created GitHub Organization Sync", - type: "success" - }); - } - handlePopUpToggle("githubOrgSyncConfig"); - } catch { createNotification({ - text: "Failed to setup GitHub Organization Sync", - type: "error" + text: "Successfully updated GitHub Organization Sync", + type: "success" + }); + } else { + await createGithubSyncOrgConfig({ + githubOrgName, + githubOrgAccessToken, + isActive: false + }); + + createNotification({ + text: "Successfully created GitHub Organization Sync", + type: "success" }); } + handlePopUpToggle("githubOrgSyncConfig"); }; const onDelete = async () => { - try { - await deleteGithubSyncOrgConfig(); + await deleteGithubSyncOrgConfig(); - handlePopUpToggle("deleteGithubOrgSyncConfig", false); - handlePopUpToggle("githubOrgSyncConfig", false); - createNotification({ - text: "Successfully deleted GitHub Organization Sync", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete GitHub Organization Sync", - type: "error" - }); - } + handlePopUpToggle("deleteGithubOrgSyncConfig", false); + handlePopUpToggle("githubOrgSyncConfig", false); + createNotification({ + text: "Successfully deleted GitHub Organization Sync", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/OrgGithubSyncSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/OrgGithubSyncSection.tsx index 188a83354..f2115df96 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/OrgGithubSyncSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/OrgGithubSyncSection.tsx @@ -35,64 +35,41 @@ export const OrgGithubSyncSection = () => { const data = !isPending && !githubOrgSyncConfig?.isError ? githubOrgSyncConfig?.data : undefined; const handleBulkSync = async () => { - try { - const result = await syncAllTeamsMutation.mutateAsync(); - let message = "Successfully synced teams"; + const result = await syncAllTeamsMutation.mutateAsync(); + let message = "Successfully synced teams"; - const details = []; - if (result.createdTeams.length > 0) { - details.push( - `${result.createdTeams.length} new team${result.createdTeams.length === 1 ? "" : "s"} created` - ); - } - if (result.updatedTeams.length > 0) { - details.push( - `${result.updatedTeams.length} team${result.updatedTeams.length === 1 ? "" : "s"} updated` - ); - } - if (result.removedMemberships > 0) { - details.push( - `${result.removedMemberships} membership${result.removedMemberships === 1 ? "" : "s"} removed` - ); - } + const details = []; + if (result.createdTeams.length > 0) { + details.push( + `${result.createdTeams.length} new team${result.createdTeams.length === 1 ? "" : "s"} created` + ); + } + if (result.updatedTeams.length > 0) { + details.push( + `${result.updatedTeams.length} team${result.updatedTeams.length === 1 ? "" : "s"} updated` + ); + } + if (result.removedMemberships > 0) { + details.push( + `${result.removedMemberships} membership${result.removedMemberships === 1 ? "" : "s"} removed` + ); + } - if (details.length > 0) { - message += `. ${details.join(", ")}`; - } + if (details.length > 0) { + message += `. ${details.join(", ")}`; + } + createNotification({ + text: message, + type: "success" + }); + + if (result.errors && result.errors.length > 0) { createNotification({ - text: message, - type: "success" + text: `Sync completed with ${result.errors.length} warnings. Check the console for details.`, + type: "warning" }); - - if (result.errors && result.errors.length > 0) { - createNotification({ - text: `Sync completed with ${result.errors.length} warnings. Check the console for details.`, - type: "warning" - }); - console.warn("Sync errors:", result.errors); - } - } catch (error) { - const errorMessage = - (error as any)?.response?.data?.message || (error as Error)?.message || "Unknown error"; - - if ( - errorMessage.includes("token") && - (errorMessage.includes("required") || - errorMessage.includes("invalid") || - errorMessage.includes("expired") || - errorMessage.includes("set a token first")) - ) { - createNotification({ - text: "Please set a GitHub access token in the configuration modal to continue with the sync", - type: "error" - }); - } else { - createNotification({ - text: `Failed to sync GitHub teams: ${errorMessage}`, - type: "error" - }); - } + console.warn("Sync errors:", result.errors); } }; @@ -204,7 +181,7 @@ export const OrgGithubSyncSection = () => { handlePopUpToggle("upgradePlan", isOpen)} - text="You can use GitHub Organization Plan if you switch to Infisical's Enterprise plan." + text="Your current plan does not include access to GitHub Organization Sync. To unlock this feature, please upgrade to Infisical Enterprise plan." isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} /> diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/OrgSCIMSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/OrgSCIMSection.tsx index 3936a20ee..f7551d1e4 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/OrgSCIMSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/OrgSCIMSection.tsx @@ -36,30 +36,23 @@ export const OrgScimSection = () => { }; const handleEnableSCIMToggle = async (value: boolean) => { - try { - if (!currentOrg?.id) return; - if (!subscription?.scim) { - handlePopUpOpen("upgradePlan", { - isEnterpriseFeature: true - }); - return; - } - - await mutateAsync({ - orgId: currentOrg?.id, - scimEnabled: value - }); - - createNotification({ - text: `Successfully ${value ? "enabled" : "disabled"} SCIM provisioning`, - type: "success" - }); - } catch (err) { - createNotification({ - text: (err as { response: { data: { message: string } } }).response.data.message, - type: "error" + if (!currentOrg?.id) return; + if (!subscription?.scim) { + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true }); + return; } + + await mutateAsync({ + orgId: currentOrg?.id, + scimEnabled: value + }); + + createNotification({ + text: `Successfully ${value ? "enabled" : "disabled"} SCIM provisioning`, + type: "success" + }); }; return ( @@ -117,7 +110,7 @@ export const OrgScimSection = () => { handlePopUpToggle("upgradePlan", isOpen)} - text="You can use SCIM Provisioning if you switch to Infisical's Enterprise plan." + text="Your current plan does not include access to SCIM Provisioning. To unlock this feature, please upgrade to Infisical Enterprise plan." isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} /> diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/ScimTokenModal.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/ScimTokenModal.tsx index c542a88bb..5d6007ac0 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/ScimTokenModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/ScimTokenModal.tsx @@ -92,52 +92,36 @@ export const ScimTokenModal = ({ popUp, handlePopUpOpen, handlePopUpToggle }: Pr }, [isScimTokenCopied, isScimUrlCopied]); const onFormSubmit = async ({ description, ttlDays }: FormData) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - const { scimToken } = await createScimTokenMutateAsync({ - organizationId: currentOrg.id, - description, - ttlDays: Number(ttlDays) - }); + const { scimToken } = await createScimTokenMutateAsync({ + organizationId: currentOrg.id, + description, + ttlDays: Number(ttlDays) + }); - setToken(scimToken); + setToken(scimToken); - createNotification({ - text: "Successfully created SCIM token", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to create SCIM token", - type: "error" - }); - } + createNotification({ + text: "Successfully created SCIM token", + type: "success" + }); }; const onDeleteScimTokenSubmit = async (scimTokenId: string) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - await deleteScimTokenMutateAsync({ - organizationId: currentOrg.id, - scimTokenId - }); + await deleteScimTokenMutateAsync({ + organizationId: currentOrg.id, + scimTokenId + }); - handlePopUpToggle("deleteScimToken", false); + handlePopUpToggle("deleteScimToken", false); - createNotification({ - text: "Successfully deleted SCIM token", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete SCIM token", - type: "error" - }); - } + createNotification({ + text: "Successfully deleted SCIM token", + type: "success" + }); }; const hasToken = Boolean(token); diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSecurityTab/OrgGenericAuthSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSecurityTab/OrgGenericAuthSection.tsx index 7e2bd522b..7e6922318 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSecurityTab/OrgGenericAuthSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSecurityTab/OrgGenericAuthSection.tsx @@ -20,55 +20,39 @@ export const OrgGenericAuthSection = () => { const { mutateAsync } = useUpdateOrg(); const handleEnforceMfaToggle = async (value: boolean) => { - try { - if (!currentOrg?.id) return; - if (!subscription?.enforceMfa) { - handlePopUpOpen("upgradePlan"); - return; - } - - await mutateAsync({ - orgId: currentOrg?.id, - enforceMfa: value - }); - - createNotification({ - text: `Successfully ${value ? "enforced" : "un-enforced"} MFA`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: (err as { response: { data: { message: string } } }).response.data.message, - type: "error" - }); + if (!currentOrg?.id) return; + if (!subscription?.enforceMfa) { + handlePopUpOpen("upgradePlan"); + return; } + + await mutateAsync({ + orgId: currentOrg?.id, + enforceMfa: value + }); + + createNotification({ + text: `Successfully ${value ? "enforced" : "un-enforced"} MFA`, + type: "success" + }); }; const handleUpdateSelectedMfa = async (selectedMfaMethod: MfaMethod) => { - try { - if (!currentOrg?.id) return; - if (!subscription?.enforceMfa) { - handlePopUpOpen("upgradePlan"); - return; - } - - await mutateAsync({ - orgId: currentOrg?.id, - selectedMfaMethod - }); - - createNotification({ - text: "Successfully updated selected MFA method", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: (err as { response: { data: { message: string } } }).response.data.message, - type: "error" - }); + if (!currentOrg?.id) return; + if (!subscription?.enforceMfa) { + handlePopUpOpen("upgradePlan"); + return; } + + await mutateAsync({ + orgId: currentOrg?.id, + selectedMfaMethod + }); + + createNotification({ + text: "Successfully updated selected MFA method", + type: "success" + }); }; return ( @@ -110,7 +94,7 @@ export const OrgGenericAuthSection = () => { handlePopUpToggle("upgradePlan", isOpen)} - text="You can enforce user MFA if you switch to Infisical's Pro plan." + text="Your current plan does not include access to enforce user MFA. To unlock this feature, please upgrade to Infisical Pro plan." /> ); diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSecurityTab/OrgUserAccessTokenLimitSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSecurityTab/OrgUserAccessTokenLimitSection.tsx index 6e9865532..c78b5475e 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSecurityTab/OrgUserAccessTokenLimitSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSecurityTab/OrgUserAccessTokenLimitSection.tsx @@ -57,24 +57,17 @@ export const OrgUserAccessTokenLimitSection = () => { if (!currentOrg) return null; const handleUserTokenExpirationSubmit = async (formData: TForm) => { - try { - const userTokenExpiration = formatDuration(formData.expirationValue, formData.expirationUnit); + const userTokenExpiration = formatDuration(formData.expirationValue, formData.expirationUnit); - await updateUserTokenExpiration({ - userTokenExpiration, - orgId: currentOrg.id - }); + await updateUserTokenExpiration({ + userTokenExpiration, + orgId: currentOrg.id + }); - createNotification({ - text: "Successfully updated user token expiration", - type: "success" - }); - } catch { - createNotification({ - text: "Failed updating user token expiration", - type: "error" - }); - } + createNotification({ + text: "Successfully updated user token expiration", + type: "success" + }); }; // Units for the dropdown with readable labels diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/LDAPGroupMapModal.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/LDAPGroupMapModal.tsx index aee845187..31e96a1cb 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/LDAPGroupMapModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/LDAPGroupMapModal.tsx @@ -79,28 +79,20 @@ export const LDAPGroupMapModal = ({ popUp, handlePopUpOpen, handlePopUpToggle }: }); const onFormSubmit = async ({ groupSlug, ldapGroupCN }: TFormData) => { - try { - if (!ldapConfig) return; + if (!ldapConfig) return; - await createLDAPGroupMapping({ - ldapConfigId: ldapConfig.id, - groupSlug, - ldapGroupCN - }); + await createLDAPGroupMapping({ + ldapConfigId: ldapConfig.id, + groupSlug, + ldapGroupCN + }); - reset(); + reset(); - createNotification({ - text: `Successfully added LDAP group mapping for ${ldapGroupCN}`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to add LDAP group mapping for ${ldapGroupCN}`, - type: "error" - }); - } + createNotification({ + text: `Successfully added LDAP group mapping for ${ldapGroupCN}`, + type: "success" + }); }; const onDeleteGroupMapSubmit = async ({ @@ -112,25 +104,17 @@ export const LDAPGroupMapModal = ({ popUp, handlePopUpOpen, handlePopUpToggle }: ldapGroupMapId: string; ldapGroupCN: string; }) => { - try { - await deleteLDAPGroupMapping({ - ldapConfigId, - ldapGroupMapId - }); + await deleteLDAPGroupMapping({ + ldapConfigId, + ldapGroupMapId + }); - handlePopUpToggle("deleteLdapGroupMap", false); + handlePopUpToggle("deleteLdapGroupMap", false); - createNotification({ - text: `Successfully deleted LDAP group mapping ${ldapGroupCN}`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to delete LDAP group mapping ${ldapGroupCN}`, - type: "error" - }); - } + createNotification({ + text: `Successfully deleted LDAP group mapping ${ldapGroupCN}`, + type: "success" + }); }; useEffect(() => { diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/LDAPModal.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/LDAPModal.tsx index 580ecdeb1..01429f512 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/LDAPModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/LDAPModal.tsx @@ -62,31 +62,24 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele if (!currentOrg) { return; } - try { - await updateMutateAsync({ - organizationId: currentOrg.id, - isActive: false, - url: "", - bindDN: "", - bindPass: "", - searchBase: "", - searchFilter: "", - uniqueUserAttribute: "", - groupSearchBase: "", - groupSearchFilter: "", - caCert: "" - }); + await updateMutateAsync({ + organizationId: currentOrg.id, + isActive: false, + url: "", + bindDN: "", + bindPass: "", + searchBase: "", + searchFilter: "", + uniqueUserAttribute: "", + groupSearchBase: "", + groupSearchFilter: "", + caCert: "" + }); - createNotification({ - text: "Successfully deleted OIDC configuration.", - type: "success" - }); - } catch { - createNotification({ - text: "Failed deleting OIDC configuration.", - type: "error" - }); - } + createNotification({ + text: "Successfully deleted OIDC configuration.", + type: "success" + }); }; const watchUrl = watch("url"); @@ -122,83 +115,59 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele caCert, shouldCloseModal = true }: TLDAPFormData & { shouldCloseModal?: boolean }) => { - try { - if (!currentOrg) return; + if (!currentOrg) return; - if (!data) { - await createMutateAsync({ - organizationId: currentOrg.id, - isActive: false, - url, - bindDN, - bindPass, - searchBase, - searchFilter, - uniqueUserAttribute, - groupSearchBase, - groupSearchFilter, - caCert - }); - } else { - await updateMutateAsync({ - organizationId: currentOrg.id, - url, - bindDN, - bindPass, - searchBase, - searchFilter, - uniqueUserAttribute, - groupSearchBase, - groupSearchFilter, - caCert - }); - } - - if (shouldCloseModal) { - handlePopUpClose("addLDAP"); - } - - createNotification({ - text: `Successfully ${!data ? "added" : "updated"} LDAP configuration`, - type: "success" + if (!data) { + await createMutateAsync({ + organizationId: currentOrg.id, + isActive: false, + url, + bindDN, + bindPass, + searchBase, + searchFilter, + uniqueUserAttribute, + groupSearchBase, + groupSearchFilter, + caCert }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${!data ? "add" : "update"} LDAP configuration`, - type: "error" + } else { + await updateMutateAsync({ + organizationId: currentOrg.id, + url, + bindDN, + bindPass, + searchBase, + searchFilter, + uniqueUserAttribute, + groupSearchBase, + groupSearchFilter, + caCert }); } + + if (shouldCloseModal) { + handlePopUpClose("addLDAP"); + } + + createNotification({ + text: `Successfully ${!data ? "added" : "updated"} LDAP configuration`, + type: "success" + }); }; const handleTestLDAPConnection = async () => { - try { - const result = await testLDAPConnection({ - url: watchUrl, - bindDN: watchBindDN, - bindPass: watchBindPass, - caCert: watchCaCert ?? "" - }); + await testLDAPConnection({ + url: watchUrl, + bindDN: watchBindDN, + bindPass: watchBindPass, + caCert: watchCaCert ?? "" + }); - if (!result) { - createNotification({ - text: "Failed to test the LDAP connection: Bind operation was unsuccessful", - type: "error" - }); - return; - } - - createNotification({ - text: "Successfully tested the LDAP connection: Bind operation was successful", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to test the LDAP connection", - type: "error" - }); - } + createNotification({ + text: "Successfully tested the LDAP connection: Bind operation was successful", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OIDCModal.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OIDCModal.tsx index 03f946ace..f980b2e4f 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OIDCModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OIDCModal.tsx @@ -122,31 +122,24 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele if (!currentOrg) { return; } - try { - await updateMutateAsync({ - issuer: "", - discoveryURL: "", - authorizationEndpoint: "", - allowedEmailDomains: "", - jwksUri: "", - tokenEndpoint: "", - userinfoEndpoint: "", - clientId: "", - clientSecret: "", - isActive: false, - organizationId: currentOrg.id - }); + await updateMutateAsync({ + issuer: "", + discoveryURL: "", + authorizationEndpoint: "", + allowedEmailDomains: "", + jwksUri: "", + tokenEndpoint: "", + userinfoEndpoint: "", + clientId: "", + clientSecret: "", + isActive: false, + organizationId: currentOrg.id + }); - createNotification({ - text: "Successfully deleted OIDC configuration.", - type: "success" - }); - } catch { - createNotification({ - text: "Failed deleting OIDC configuration.", - type: "error" - }); - } + createNotification({ + text: "Successfully deleted OIDC configuration.", + type: "success" + }); }; useEffect(() => { @@ -178,58 +171,50 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele clientSecret, jwtSignatureAlgorithm }: OIDCFormData) => { - try { - if (!currentOrg) { - return; - } + if (!currentOrg) { + return; + } - if (!data) { - await createMutateAsync({ - issuer, - configurationType, - discoveryURL, - authorizationEndpoint, - allowedEmailDomains, - jwksUri, - tokenEndpoint, - userinfoEndpoint, - clientId, - clientSecret, - isActive: true, - organizationId: currentOrg.id, - jwtSignatureAlgorithm - }); - } else { - await updateMutateAsync({ - issuer, - configurationType, - discoveryURL, - authorizationEndpoint, - allowedEmailDomains, - jwksUri, - tokenEndpoint, - userinfoEndpoint, - clientId, - clientSecret, - isActive: true, - organizationId: currentOrg.id, - jwtSignatureAlgorithm - }); - } - - handlePopUpClose("addOIDC"); - - createNotification({ - text: `Successfully ${!data ? "added" : "updated"} OIDC SSO configuration`, - type: "success" + if (!data) { + await createMutateAsync({ + issuer, + configurationType, + discoveryURL, + authorizationEndpoint, + allowedEmailDomains, + jwksUri, + tokenEndpoint, + userinfoEndpoint, + clientId, + clientSecret, + isActive: true, + organizationId: currentOrg.id, + jwtSignatureAlgorithm }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${!data ? "add" : "update"} OIDC SSO configuration`, - type: "error" + } else { + await updateMutateAsync({ + issuer, + configurationType, + discoveryURL, + authorizationEndpoint, + allowedEmailDomains, + jwksUri, + tokenEndpoint, + userinfoEndpoint, + clientId, + clientSecret, + isActive: true, + organizationId: currentOrg.id, + jwtSignatureAlgorithm }); } + + handlePopUpClose("addOIDC"); + + createNotification({ + text: `Successfully ${!data ? "added" : "updated"} OIDC SSO configuration`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgGeneralAuthSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgGeneralAuthSection.tsx index 527239898..d35a04323 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgGeneralAuthSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgGeneralAuthSection.tsx @@ -45,69 +45,61 @@ export const OrgGeneralAuthSection = ({ const logout = useLogoutUser(); const handleEnforceOrgAuthToggle = async (value: boolean, type: EnforceAuthType) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - if (type === EnforceAuthType.SAML) { - if (!subscription?.samlSSO) { - handlePopUpOpen("upgradePlan"); - return; - } - - await mutateAsync({ - orgId: currentOrg?.id, - authEnforced: value - }); - } else if (type === EnforceAuthType.GOOGLE) { - if (!subscription?.enforceGoogleSSO) { - handlePopUpOpen("upgradePlan"); - return; - } - - await mutateAsync({ - orgId: currentOrg?.id, - googleSsoAuthEnforced: value - }); - } else if (type === EnforceAuthType.OIDC) { - if (!subscription?.oidcSSO) { - handlePopUpOpen("upgradePlan"); - return; - } - - await mutateAsync({ - orgId: currentOrg?.id, - authEnforced: value - }); - } else { - createNotification({ - text: `Invalid auth enforcement type ${type}`, - type: "error" - }); + if (type === EnforceAuthType.SAML) { + if (!subscription?.samlSSO) { + handlePopUpOpen("upgradePlan"); + return; } - createNotification({ - text: `Successfully ${value ? "enabled" : "disabled"} org-level auth`, - type: "success" + await mutateAsync({ + orgId: currentOrg?.id, + authEnforced: value }); - - if (value) { - await logout.mutateAsync(); - - if (type === EnforceAuthType.SAML) { - window.open(`/api/v1/sso/redirect/saml2/organizations/${currentOrg.slug}`); - } else if (type === EnforceAuthType.GOOGLE) { - window.open(`/api/v1/sso/redirect/google?org_slug=${currentOrg.slug}`); - } - - window.close(); + } else if (type === EnforceAuthType.GOOGLE) { + if (!subscription?.enforceGoogleSSO) { + handlePopUpOpen("upgradePlan"); + return; } - } catch (err) { - console.error(err); + + await mutateAsync({ + orgId: currentOrg?.id, + googleSsoAuthEnforced: value + }); + } else if (type === EnforceAuthType.OIDC) { + if (!subscription?.oidcSSO) { + handlePopUpOpen("upgradePlan"); + return; + } + + await mutateAsync({ + orgId: currentOrg?.id, + authEnforced: value + }); + } else { createNotification({ - text: (err as { response: { data: { message: string } } }).response.data.message, + text: `Invalid auth enforcement type ${type}`, type: "error" }); } + + createNotification({ + text: `Successfully ${value ? "enabled" : "disabled"} org-level auth`, + type: "success" + }); + + if (value) { + await logout.mutateAsync(); + + if (type === EnforceAuthType.SAML) { + window.open(`/api/v1/sso/redirect/saml2/organizations/${currentOrg.slug}`); + } else if (type === EnforceAuthType.GOOGLE) { + window.open(`/api/v1/sso/redirect/google?org_slug=${currentOrg.slug}`); + } + + window.close(); + } }; const handleEnableBypassOrgAuthToggle = async (value: boolean) => { @@ -312,7 +304,7 @@ export const OrgGeneralAuthSection = ({ handlePopUpToggle("upgradePlan", isOpen)} - text="You can enforce SAML SSO if you switch to Infisical's Pro plan." + text="Your current plan does not include access to enforce SAML SSO. To unlock this feature, please upgrade to Infisical Pro plan." /> ); diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgLDAPSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgLDAPSection.tsx index 9d94d595c..311a7ea4b 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgLDAPSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgLDAPSection.tsx @@ -31,31 +31,23 @@ export const OrgLDAPSection = (): JSX.Element => { const { mutateAsync: createMutateAsync } = useCreateLDAPConfig(); const handleLDAPToggle = async (value: boolean) => { - try { - if (!currentOrg?.id) return; - if (!subscription?.ldap) { - handlePopUpOpen("upgradePlan", { - isEnterpriseFeature: true - }); - return; - } - - await mutateAsync({ - organizationId: currentOrg?.id, - isActive: value - }); - - createNotification({ - text: `Successfully ${value ? "enabled" : "disabled"} LDAP`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${value ? "enable" : "disable"} LDAP`, - type: "error" + if (!currentOrg?.id) return; + if (!subscription?.ldap) { + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true }); + return; } + + await mutateAsync({ + organizationId: currentOrg?.id, + isActive: value + }); + + createNotification({ + text: `Successfully ${value ? "enabled" : "disabled"} LDAP`, + type: "success" + }); }; const addLDAPBtnClick = async () => { @@ -191,7 +183,7 @@ export const OrgLDAPSection = (): JSX.Element => { handlePopUpToggle("upgradePlan", isOpen)} - text="You can use LDAP authentication if you switch to Infisical's Enterprise plan." + text="Your current plan does not include access to LDAP authentication. To unlock this feature, please upgrade to Infisical Enterprise plan." isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} /> diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgOIDCSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgOIDCSection.tsx index 7e4b2b3ef..b722762e6 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgOIDCSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgOIDCSection.tsx @@ -30,49 +30,41 @@ export const OrgOIDCSection = (): JSX.Element => { ] as const); const handleOIDCToggle = async (value: boolean) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - if (!subscription?.oidcSSO) { - handlePopUpOpen("upgradePlan"); - return; - } - - await mutateAsync({ - organizationId: currentOrg?.id, - isActive: value - }); - - createNotification({ - text: `Successfully ${value ? "enabled" : "disabled"} OIDC SSO`, - type: "success" - }); - } catch (err) { - console.error(err); + if (!subscription?.oidcSSO) { + handlePopUpOpen("upgradePlan"); + return; } + + await mutateAsync({ + organizationId: currentOrg?.id, + isActive: value + }); + + createNotification({ + text: `Successfully ${value ? "enabled" : "disabled"} OIDC SSO`, + type: "success" + }); }; const handleOIDCGroupManagement = async (value: boolean) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - if (!subscription?.oidcSSO) { - handlePopUpOpen("upgradePlan"); - return; - } - - await mutateAsync({ - organizationId: currentOrg?.id, - manageGroupMemberships: value - }); - - createNotification({ - text: `Successfully ${value ? "enabled" : "disabled"} OIDC group membership mapping`, - type: "success" - }); - } catch (err) { - console.error(err); + if (!subscription?.oidcSSO) { + handlePopUpOpen("upgradePlan"); + return; } + + await mutateAsync({ + organizationId: currentOrg?.id, + manageGroupMemberships: value + }); + + createNotification({ + text: `Successfully ${value ? "enabled" : "disabled"} OIDC group membership mapping`, + type: "success" + }); }; const addOidcButtonClick = async () => { @@ -207,7 +199,7 @@ export const OrgOIDCSection = (): JSX.Element => { handlePopUpToggle("upgradePlan", isOpen)} - text="You can use OIDC SSO if you switch to Infisical's Pro plan." + text="Your current plan does not include access to OIDC SSO. To unlock this feature, please upgrade to Infisical Pro plan." /> ); diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgSSOSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgSSOSection.tsx index 5e9d4c211..ba2d23abd 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgSSOSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgSSOSection.tsx @@ -27,70 +27,52 @@ export const OrgSSOSection = (): JSX.Element => { const { mutateAsync } = useUpdateSSOConfig(); const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "upgradePlan", - // "upgradeEnterprisePlan", "addSSO" ] as const); const { mutateAsync: createMutateAsync } = useCreateSSOConfig(); const handleSamlSSOToggle = async (value: boolean) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - if (!subscription?.samlSSO) { - handlePopUpOpen("upgradePlan", { - description: "You can use SAML SSO if you switch to Infisical's Pro plan." - }); - return; - } - - await mutateAsync({ - organizationId: currentOrg?.id, - isActive: value - }); - - createNotification({ - text: `Successfully ${value ? "enabled" : "disabled"} SAML SSO`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${value ? "enable" : "disable"} SAML SSO`, - type: "error" + if (!subscription?.samlSSO) { + handlePopUpOpen("upgradePlan", { + text: "Your current plan does not include access to SAML SSO. To unlock this feature, please upgrade to Infisical Pro plan." }); + return; } + + await mutateAsync({ + organizationId: currentOrg?.id, + isActive: value + }); + + createNotification({ + text: `Successfully ${value ? "enabled" : "disabled"} SAML SSO`, + type: "success" + }); }; const handleSamlGroupManagement = async (value: boolean) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - if (!subscription?.samlSSO || !subscription?.groups) { - handlePopUpOpen("upgradePlan", { - isEnterpriseFeature: true, - description: - "You can use SAML group mapping if you switch to Infisical's Enterprise plan." - }); - return; - } - - await mutateAsync({ - organizationId: currentOrg?.id, - enableGroupSync: value - }); - - createNotification({ - text: `Successfully ${value ? "enabled" : "disabled"} SAML group membership mapping`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${value ? "enable" : "disable"} SAML group membership mapping`, - type: "error" + if (!subscription?.samlSSO || !subscription?.groups) { + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true, + text: "Your current plan does not include access to SAML group mapping. To unlock this feature, please upgrade to Infisical Enterprise plan." }); + return; } + + await mutateAsync({ + organizationId: currentOrg?.id, + enableGroupSync: value + }); + + createNotification({ + text: `Successfully ${value ? "enabled" : "disabled"} SAML group membership mapping`, + type: "success" + }); }; const addSSOBtnClick = async () => { @@ -112,7 +94,7 @@ export const OrgSSOSection = (): JSX.Element => { handlePopUpOpen("addSSO"); } else { handlePopUpOpen("upgradePlan", { - description: "You can use SAML SSO if you switch to Infisical's Pro plan." + text: "Your current plan does not include access to SAML SSO. To unlock this feature, please upgrade to Infisical Pro plan." }); } } catch (err) { @@ -242,7 +224,7 @@ export const OrgSSOSection = (): JSX.Element => { handlePopUpToggle("upgradePlan", isOpen)} - text={popUp.upgradePlan.data?.description} + text={popUp.upgradePlan.data?.text} isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} /> diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgSsoTab.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgSsoTab.tsx index 7700d5c86..9469242b1 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgSsoTab.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgSsoTab.tsx @@ -93,7 +93,7 @@ export const OrgSsoTab = withPermission( colorSchema="secondary" onClick={() => { if (!subscription?.samlSSO) { - handlePopUpOpen("upgradePlan", { feature: "SAML SSO" }); + handlePopUpOpen("upgradePlan", { featureName: "SAML SSO" }); return; } @@ -116,7 +116,7 @@ export const OrgSsoTab = withPermission( colorSchema="secondary" onClick={() => { if (!subscription?.oidcSSO) { - handlePopUpOpen("upgradePlan", { feature: "OIDC SSO" }); + handlePopUpOpen("upgradePlan", { featureName: "OIDC SSO" }); return; } @@ -135,7 +135,7 @@ export const OrgSsoTab = withPermission( onClick={() => { if (!subscription?.ldap) { handlePopUpOpen("upgradePlan", { - feature: "LDAP", + featureName: "LDAP", isEnterpriseFeature: true }); return; @@ -208,16 +208,8 @@ export const OrgSsoTab = withPermission( handlePopUpToggle("upgradePlan", isOpen)} - text={`You can use ${ - (popUp.upgradePlan.data as { feature: string })?.feature - } if you switch to Infisical's ${ - (popUp.upgradePlan.data as { isEnterpriseFeature: boolean })?.isEnterpriseFeature - ? "Enterprise" - : "Pro" - } plan.`} - isEnterpriseFeature={ - (popUp.upgradePlan.data as { isEnterpriseFeature: boolean })?.isEnterpriseFeature - } + text={`Your current plan does not include access to ${popUp.upgradePlan.data?.featureName}. To unlock this feature, please upgrade to Infisical ${popUp.upgradePlan.data?.isEnterpriseFeature ? "Enterprise" : "Pro"} plan.`} + isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} /> ); diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/SSOModal.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/SSOModal.tsx index df5fca59f..558f37dcb 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/SSOModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/SSOModal.tsx @@ -108,64 +108,49 @@ export const SSOModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDelet if (!currentOrg) { return; } - try { - await updateMutateAsync({ - organizationId: currentOrg.id, - isActive: false, - entryPoint: "", - issuer: "", - cert: "" - }); + await updateMutateAsync({ + organizationId: currentOrg.id, + isActive: false, + entryPoint: "", + issuer: "", + cert: "" + }); - createNotification({ - text: "Successfully deleted SAML SSO configuration.", - type: "success" - }); - } catch { - createNotification({ - text: "Failed deleting SAML SSO configuration.", - type: "error" - }); - } + createNotification({ + text: "Successfully deleted SAML SSO configuration.", + type: "success" + }); }; const onSSOModalSubmit = async ({ authProvider, entryPoint, issuer, cert }: AddSSOFormData) => { - try { - if (!currentOrg) return; + if (!currentOrg) return; - if (!data) { - await createMutateAsync({ - organizationId: currentOrg.id, - authProvider, - isActive: false, - entryPoint, - issuer, - cert - }); - } else { - await updateMutateAsync({ - organizationId: currentOrg.id, - authProvider, - isActive: false, - entryPoint, - issuer, - cert - }); - } - - handlePopUpClose("addSSO"); - - createNotification({ - text: `Successfully ${!data ? "added" : "updated"} SAML SSO configuration`, - type: "success" + if (!data) { + await createMutateAsync({ + organizationId: currentOrg.id, + authProvider, + isActive: false, + entryPoint, + issuer, + cert }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${!data ? "add" : "update"} SAML SSO configuration`, - type: "error" + } else { + await updateMutateAsync({ + organizationId: currentOrg.id, + authProvider, + isActive: false, + entryPoint, + issuer, + cert }); } + + handlePopUpClose("addSSO"); + + createNotification({ + text: `Successfully ${!data ? "added" : "updated"} SAML SSO configuration`, + type: "success" + }); }; const renderLabels = (authProvider: string) => { diff --git a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/DeleteProjectTemplateModal.tsx b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/DeleteProjectTemplateModal.tsx index 8ccdb0191..33b54afca 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/DeleteProjectTemplateModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/DeleteProjectTemplateModal.tsx @@ -16,25 +16,16 @@ export const DeleteProjectTemplateModal = ({ isOpen, onOpenChange, template }: P const { id: templateId, name } = template; const handleDeleteProjectTemplate = async () => { - try { - await deleteTemplate.mutateAsync({ - templateId - }); + await deleteTemplate.mutateAsync({ + templateId + }); - createNotification({ - text: "Successfully removed project template", - type: "success" - }); + createNotification({ + text: "Successfully removed project template", + type: "success" + }); - onOpenChange(false); - } catch (err) { - console.error(err); - - createNotification({ - text: "Failed remove project template", - type: "error" - }); - } + onOpenChange(false); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/EditProjectTemplate.tsx b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/EditProjectTemplate.tsx index 7a99af1d6..afa8261b3 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/EditProjectTemplate.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/EditProjectTemplate.tsx @@ -31,22 +31,14 @@ export const EditProjectTemplate = ({ isInfisicalTemplate, projectTemplate, onBa const deleteProjectTemplate = useDeleteProjectTemplate(); const handleRemoveTemplate = async () => { - try { - await deleteProjectTemplate.mutateAsync({ - templateId - }); - createNotification({ - text: "Successfully removed project template", - type: "success" - }); - onBack(); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to remove project template", - type: "error" - }); - } + await deleteProjectTemplate.mutateAsync({ + templateId + }); + createNotification({ + text: "Successfully removed project template", + type: "success" + }); + onBack(); handlePopUpClose("removeTemplate"); }; diff --git a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEditRoleForm.tsx b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEditRoleForm.tsx index 09a2a69af..e54eaf19d 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEditRoleForm.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEditRoleForm.tsx @@ -57,31 +57,23 @@ export const ProjectTemplateEditRoleForm = ({ const updateProjectTemplate = useUpdateProjectTemplate(); const onSubmit = async (form: TFormSchema) => { - try { - await updateProjectTemplate.mutateAsync({ - templateId: projectTemplate.id, - roles: [ - ...projectTemplate.roles.filter( - (r) => r.slug !== role?.slug && isCustomProjectRole(r.slug) // filter out default roles as well - ), - { - ...form, - permissions: formRolePermission2API(form.permissions) - } - ] - }); - onGoBack(); - createNotification({ - text: "Template roles successfully updated", - type: "success" - }); - } catch (e: any) { - console.error(e); - createNotification({ - text: "Failed to update template roles", - type: "error" - }); - } + await updateProjectTemplate.mutateAsync({ + templateId: projectTemplate.id, + roles: [ + ...projectTemplate.roles.filter( + (r) => r.slug !== role?.slug && isCustomProjectRole(r.slug) // filter out default roles as well + ), + { + ...form, + permissions: formRolePermission2API(form.permissions) + } + ] + }); + onGoBack(); + createNotification({ + text: "Template roles successfully updated", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEnvironmentsForm.tsx b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEnvironmentsForm.tsx index ec1899369..f11e9ecc8 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEnvironmentsForm.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEnvironmentsForm.tsx @@ -68,28 +68,20 @@ export const ProjectTemplateEnvironmentsForm = ({ const updateProjectTemplate = useUpdateProjectTemplate(); const onFormSubmit = async (form: TFormSchema) => { - try { - const { environments: updatedEnvs } = await updateProjectTemplate.mutateAsync({ - environments: form.environments?.map((env, index) => ({ - ...env, - position: index + 1 - })), - templateId: projectTemplate.id - }); + const { environments: updatedEnvs } = await updateProjectTemplate.mutateAsync({ + environments: form.environments?.map((env, index) => ({ + ...env, + position: index + 1 + })), + templateId: projectTemplate.id + }); - reset({ environments: updatedEnvs }); + reset({ environments: updatedEnvs }); - createNotification({ - text: "Project template updated successfully", - type: "success" - }); - } catch (e: any) { - console.error(e); - createNotification({ - text: e.message ?? "Failed to update project template", - type: "error" - }); - } + createNotification({ + text: "Project template updated successfully", + type: "success" + }); }; const isEnvironmentLimitExceeded = diff --git a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateRolesSection.tsx b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateRolesSection.tsx index d65f7d785..cd5e41d2a 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateRolesSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateRolesSection.tsx @@ -42,26 +42,18 @@ export const ProjectTemplateRolesSection = ({ projectTemplate, isInfisicalTempla const updateProjectTemplate = useUpdateProjectTemplate(); const handleRemoveRole = async (slug: string) => { - try { - await updateProjectTemplate.mutateAsync({ - templateId: projectTemplate.id, - roles: projectTemplate.roles.filter( - (role) => role.slug !== slug && isCustomProjectRole(role.slug) // filter out default roles as well - ) - }); + await updateProjectTemplate.mutateAsync({ + templateId: projectTemplate.id, + roles: projectTemplate.roles.filter( + (role) => role.slug !== slug && isCustomProjectRole(role.slug) // filter out default roles as well + ) + }); - createNotification({ - text: "Successfully removed role from template", - type: "success" - }); - handlePopUpClose("removeRole"); - } catch (e) { - console.error(e); - createNotification({ - text: "Error removing role from template", - type: "error" - }); - } + createNotification({ + text: "Successfully removed role from template", + type: "success" + }); + handlePopUpClose("removeRole"); }; const editRole = popUp?.editRole?.data as TProjectRole; diff --git a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplateDetailsModal.tsx b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplateDetailsModal.tsx index 6010d61f1..17224536d 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplateDetailsModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplateDetailsModal.tsx @@ -93,25 +93,15 @@ const ProjectTemplateForm = ({ onComplete, projectTemplate }: FormProps) => { ? updateProjectTemplate.mutateAsync({ templateId: projectTemplate.id, ...data }) : createProjectTemplate.mutateAsync({ ...data }); - try { - const template = await mutation; - createNotification({ - text: `Successfully ${ - projectTemplate ? "updated template details" : "created project template" - }`, - type: "success" - }); + const template = await mutation; + createNotification({ + text: `Successfully ${ + projectTemplate ? "updated template details" : "created project template" + }`, + type: "success" + }); - onComplete(template); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${ - projectTemplate ? "update template details" : "create project template" - }`, - type: "error" - }); - } + onComplete(template); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplatesSection.tsx b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplatesSection.tsx index e76fea0a3..4fe5225c4 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplatesSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplatesSection.tsx @@ -73,7 +73,7 @@ export const ProjectTemplatesSection = () => { handlePopUpToggle("upgradePlan", isOpen)} - text="You can create project templates if you switch to Infisical's Enterprise plan." + text="Your current plan does not include access to project templates. To unlock this feature, please upgrade to Infisical Enterprise plan." isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} /> diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/UserDetailsByIDPage.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/UserDetailsByIDPage.tsx index 2d433bba8..d607bc100 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/UserDetailsByIDPage.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/UserDetailsByIDPage.tsx @@ -64,56 +64,38 @@ const Page = withPermission( ] as const); const onDeactivateMemberSubmit = async (orgMembershipId: string) => { - try { - await updateOrgMembership({ - organizationId: orgId, - membershipId: orgMembershipId, - isActive: false - }); + await updateOrgMembership({ + organizationId: orgId, + membershipId: orgMembershipId, + isActive: false + }); - createNotification({ - text: "Successfully deactivated user in organization", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to deactivate user in organization", - type: "error" - }); - } + createNotification({ + text: "Successfully deactivated user in organization", + type: "success" + }); handlePopUpClose("deactivateMember"); }; const onRemoveMemberSubmit = async (orgMembershipId: string) => { - try { - await deleteOrgMembership({ - orgId, - membershipId: orgMembershipId - }); + await deleteOrgMembership({ + orgId, + membershipId: orgMembershipId + }); - createNotification({ - text: "Successfully removed user from org", - type: "success" - }); - - handlePopUpClose("removeMember"); - navigate({ - to: "/organization/access-management" as const, - search: { - selectedTab: OrgAccessControlTabSections.Member - } - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to remove user from the organization", - type: "error" - }); - } + createNotification({ + text: "Successfully removed user from org", + type: "success" + }); handlePopUpClose("removeMember"); + navigate({ + to: "/organization/access-management" as const, + search: { + selectedTab: OrgAccessControlTabSections.Member + } + }); }; return ( @@ -305,7 +287,7 @@ const Page = withPermission( handlePopUpToggle("upgradePlan", isOpen)} - text={(popUp.upgradePlan?.data as { description: string })?.description} + text={popUp.upgradePlan?.data?.text} /> const { mutateAsync: resendOrgMemberInvitation, isPending } = useResendOrgMemberInvitation(); const onResendInvite = async () => { - try { - const signupToken = await resendOrgMemberInvitation({ - membershipId - }); + const signupToken = await resendOrgMemberInvitation({ + membershipId + }); - if (signupToken) { - return; - } - - createNotification({ - text: "Successfully resent org invitation", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to resend org invitation", - type: "error" - }); + if (signupToken) { + return; } + + createNotification({ + text: "Successfully resent org invitation", + type: "success" + }); }; const getStatus = (m: OrgUser) => { diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserOrgMembershipModal.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserOrgMembershipModal.tsx index 59d83749c..e8b976734 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserOrgMembershipModal.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserOrgMembershipModal.tsx @@ -87,34 +87,23 @@ export const UserOrgMembershipModal = ({ popUp, handlePopUpOpen, handlePopUpTogg }, [popUp?.orgMembership?.data, roles]); const onFormSubmit = async ({ role, metadata }: FormData) => { - try { - if (!orgId) return; + if (!orgId) return; - await updateOrgMembership({ - organizationId: orgId, - membershipId: popUpData.membershipId, - role: role.slug, - metadata - }); + await updateOrgMembership({ + organizationId: orgId, + membershipId: popUpData.membershipId, + role: role.slug, + metadata + }); - handlePopUpToggle("orgMembership", false); + handlePopUpToggle("orgMembership", false); - createNotification({ - text: "Successfully updated user organization role", - type: "success" - }); + createNotification({ + text: "Successfully updated user organization role", + type: "success" + }); - reset(); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to update user organization role"; - - createNotification({ - text, - type: "error" - }); - } + reset(); }; return ( @@ -148,8 +137,7 @@ export const UserOrgMembershipModal = ({ popUp, handlePopUpOpen, handlePopUpTogg if (isCustomRole && subscription && !subscription?.rbac) { handlePopUpOpen("upgradePlan", { - description: - "You can assign custom roles to members if you switch to Infisical's Pro plan." + text: "Your current plan does not include access to assig custom roles to members. To unlock this feature, please upgrade to Infisical Pro plan." }); return; } diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserAddToProjectModal.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserAddToProjectModal.tsx index 572be1d81..013358c5f 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserAddToProjectModal.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserAddToProjectModal.tsx @@ -67,30 +67,19 @@ const UserAddToProjectModalChild = ({ membershipId, popUp, handlePopUpToggle }: }, [workspaces, projectMemberships]); const onFormSubmit = async ({ projectId }: FormData) => { - try { - await addUserToWorkspaceNonE2EE({ - projectId, - usernames: [popupData.username], - orgId - }); + await addUserToWorkspaceNonE2EE({ + projectId, + usernames: [popupData.username], + orgId + }); - createNotification({ - text: "Successfully added user to project", - type: "success" - }); + createNotification({ + text: "Successfully added user to project", + type: "success" + }); - reset(); - handlePopUpToggle("addUserToProject", false); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to add identity to project"; - - createNotification({ - text, - type: "error" - }); - } + reset(); + handlePopUpToggle("addUserToProject", false); }; return ( diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserGroupsSection.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserGroupsSection.tsx index 8e7cf3ce8..fafa7aff3 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserGroupsSection.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserGroupsSection.tsx @@ -20,25 +20,18 @@ export const UserGroupsSection = ({ orgMembership }: Props) => { const { mutateAsync: removeUserFromGroup } = useRemoveUserFromGroup(); const handleRemoveUserFromGroup = useCallback(async (groupId: string, groupSlug: string) => { - try { - await removeUserFromGroup({ - groupId, - slug: groupSlug, - username: orgMembership.user.username - }); + await removeUserFromGroup({ + groupId, + slug: groupSlug, + username: orgMembership.user.username + }); - createNotification({ - type: "success", - text: "User removed from group successfully" - }); + createNotification({ + type: "success", + text: "User removed from group successfully" + }); - handlePopUpClose("removeUserFromGroup"); - } catch { - createNotification({ - type: "error", - text: "Failed to remove user from group" - }); - } + handlePopUpClose("removeUserFromGroup"); }, []); return ( diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsSection.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsSection.tsx index 843b5a63c..6e128036b 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsSection.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsSection.tsx @@ -31,19 +31,11 @@ export const UserProjectsSection = ({ membershipId }: Props) => { ] as const); const handleRemoveUser = async (projectId: string, username: string) => { - try { - await removeUserFromWorkspace({ projectId, usernames: [username], orgId }); - createNotification({ - text: "Successfully removed user from project", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to remove user from the project", - type: "error" - }); - } + await removeUserFromWorkspace({ projectId, usernames: [username], orgId }); + createNotification({ + text: "Successfully removed user from project", + type: "success" + }); handlePopUpClose("removeUserFromProject"); }; diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx index b9599be2d..346195e11 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx @@ -38,27 +38,18 @@ const CreateForm = ({ const onSubmit = async ( formData: DiscriminativePick ) => { - try { - const account = await createPamAccount.mutateAsync({ - ...formData, - folderId, - resourceId, - resourceType, - projectId - }); - createNotification({ - text: "Successfully created account", - type: "success" - }); - onComplete(account); - } catch (err: any) { - console.error(err); - createNotification({ - title: "Failed to create account", - text: err.message, - type: "error" - }); - } + const account = await createPamAccount.mutateAsync({ + ...formData, + folderId, + resourceId, + resourceType, + projectId + }); + createNotification({ + text: "Successfully created account", + type: "success" + }); + onComplete(account); }; switch (resourceType) { @@ -85,25 +76,16 @@ const UpdateForm = ({ account, onComplete }: UpdateFormProps) => { const onSubmit = async ( formData: DiscriminativePick ) => { - try { - const updatedAccount = await updatePamAccount.mutateAsync({ - accountId: account.id, - resourceType: account.resource.resourceType, - ...formData - }); - createNotification({ - text: "Successfully updated account", - type: "success" - }); - onComplete(updatedAccount); - } catch (err: any) { - console.error(err); - createNotification({ - title: "Failed to update account", - text: err.message, - type: "error" - }); - } + const updatedAccount = await updatePamAccount.mutateAsync({ + accountId: account.id, + resourceType: account.resource.resourceType, + ...formData + }); + createNotification({ + text: "Successfully updated account", + type: "success" + }); + onComplete(updatedAccount); }; switch (account.resource.resourceType) { diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAddFolderModal.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAddFolderModal.tsx index 7b057a648..266a724cd 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAddFolderModal.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAddFolderModal.tsx @@ -17,25 +17,16 @@ export const PamAddFolderModal = ({ isOpen, onOpenChange, projectId, currentFold console.log({ currentFolderId }); const onSubmit = async (formData: Pick) => { - try { - await createPamFolder.mutateAsync({ - ...formData, - parentId: currentFolderId, - projectId - }); - createNotification({ - text: "Successfully created folder", - type: "success" - }); - onOpenChange(false); - } catch (err: any) { - console.error(err); - createNotification({ - title: "Failed to create folder", - text: err.message, - type: "error" - }); - } + await createPamFolder.mutateAsync({ + ...formData, + parentId: currentFolderId, + projectId + }); + createNotification({ + text: "Successfully created folder", + type: "success" + }); + onOpenChange(false); }; return ( diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamDeleteAccountModal.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamDeleteAccountModal.tsx index 45ac03082..c094d3c53 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamDeleteAccountModal.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamDeleteAccountModal.tsx @@ -20,25 +20,17 @@ export const PamDeleteAccountModal = ({ isOpen, onOpenChange, account }: Props) } = account; const handleDelete = async () => { - try { - await deletePamAccount.mutateAsync({ - accountId, - resourceType - }); + await deletePamAccount.mutateAsync({ + accountId, + resourceType + }); - createNotification({ - text: "Successfully deleted account", - type: "success" - }); + createNotification({ + text: "Successfully deleted account", + type: "success" + }); - onOpenChange(false); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete account", - type: "error" - }); - } + onOpenChange(false); }; return ( diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamDeleteFolderModal.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamDeleteFolderModal.tsx index f0e2476c2..d901d0ba0 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamDeleteFolderModal.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamDeleteFolderModal.tsx @@ -16,24 +16,16 @@ export const PamDeleteFolderModal = ({ isOpen, onOpenChange, folder }: Props) => const { id: folderId, name } = folder; const handleDelete = async () => { - try { - await deletePamFolder.mutateAsync({ - folderId - }); + await deletePamFolder.mutateAsync({ + folderId + }); - createNotification({ - text: "Successfully deleted folder", - type: "success" - }); + createNotification({ + text: "Successfully deleted folder", + type: "success" + }); - onOpenChange(false); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete folder", - type: "error" - }); - } + onOpenChange(false); }; return ( diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamUpdateFolderModal.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamUpdateFolderModal.tsx index b4bcc46a4..4162ce25f 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamUpdateFolderModal.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamUpdateFolderModal.tsx @@ -16,24 +16,15 @@ export const PamUpdateFolderModal = ({ isOpen, onOpenChange, folder }: Props) => if (!folder) return null; const onSubmit = async (formData: Pick) => { - try { - await updatePamFolder.mutateAsync({ - ...formData, - folderId: folder.id - }); - createNotification({ - text: "Successfully updated folder", - type: "success" - }); - onOpenChange(false); - } catch (err: any) { - console.error(err); - createNotification({ - title: "Failed to updated folder", - text: err.message, - type: "error" - }); - } + await updatePamFolder.mutateAsync({ + ...formData, + folderId: folder.id + }); + createNotification({ + text: "Successfully updated folder", + type: "success" + }); + onOpenChange(false); }; return ( diff --git a/frontend/src/pages/pam/PamResourcesPage/components/PamDeleteResourceModal.tsx b/frontend/src/pages/pam/PamResourcesPage/components/PamDeleteResourceModal.tsx index efda53466..7f0d2bd19 100644 --- a/frontend/src/pages/pam/PamResourcesPage/components/PamDeleteResourceModal.tsx +++ b/frontend/src/pages/pam/PamResourcesPage/components/PamDeleteResourceModal.tsx @@ -16,25 +16,17 @@ export const PamDeleteResourceModal = ({ isOpen, onOpenChange, resource }: Props const { id: resourceId, name, resourceType } = resource; const handleDelete = async () => { - try { - await deletePamResource.mutateAsync({ - resourceId, - resourceType - }); + await deletePamResource.mutateAsync({ + resourceId, + resourceType + }); - createNotification({ - text: `Successfully removed ${PAM_RESOURCE_TYPE_MAP[resourceType].name} resource`, - type: "success" - }); + createNotification({ + text: `Successfully removed ${PAM_RESOURCE_TYPE_MAP[resourceType].name} resource`, + type: "success" + }); - onOpenChange(false); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to remove ${PAM_RESOURCE_TYPE_MAP[resourceType].name} resource`, - type: "error" - }); - } + onOpenChange(false); }; return ( diff --git a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PamResourceForm.tsx b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PamResourceForm.tsx index 2bc54e7cd..3dd3aeec8 100644 --- a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PamResourceForm.tsx +++ b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PamResourceForm.tsx @@ -35,24 +35,15 @@ const CreateForm = ({ resourceType, onComplete, projectId }: CreateFormProps) => "name" | "resourceType" | "connectionDetails" | "gatewayId" > ) => { - try { - const resource = await createPamResource.mutateAsync({ - ...formData, - projectId - }); - createNotification({ - text: `Successfully created ${resourceName} resource`, - type: "success" - }); - onComplete(resource); - } catch (err: any) { - console.error(err); - createNotification({ - title: `Failed to create ${resourceName} resource`, - text: err.message, - type: "error" - }); - } + const resource = await createPamResource.mutateAsync({ + ...formData, + projectId + }); + createNotification({ + text: `Successfully created ${resourceName} resource`, + type: "success" + }); + onComplete(resource); }; switch (resourceType) { @@ -72,24 +63,15 @@ const UpdateForm = ({ resource, onComplete }: UpdateFormProps) => { const onSubmit = async ( formData: DiscriminativePick ) => { - try { - const updatedResource = await updatePamResource.mutateAsync({ - resourceId: resource.id, - ...formData - }); - createNotification({ - text: `Successfully updated ${resourceName} resource`, - type: "success" - }); - onComplete(updatedResource); - } catch (err: any) { - console.error(err); - createNotification({ - title: `Failed to update ${resourceName} resource`, - text: err.message, - type: "error" - }); - } + const updatedResource = await updatePamResource.mutateAsync({ + resourceId: resource.id, + ...formData + }); + createNotification({ + text: `Successfully updated ${resourceName} resource`, + type: "success" + }); + onComplete(updatedResource); }; switch (resource.resourceType) { diff --git a/frontend/src/pages/pam/PamResourcesPage/components/ResourceTypeSelect.tsx b/frontend/src/pages/pam/PamResourcesPage/components/ResourceTypeSelect.tsx index b0bba77bc..b34f33721 100644 --- a/frontend/src/pages/pam/PamResourcesPage/components/ResourceTypeSelect.tsx +++ b/frontend/src/pages/pam/PamResourcesPage/components/ResourceTypeSelect.tsx @@ -55,7 +55,7 @@ export const ResourceTypeSelect = ({ onSelect }: Props) => { const handleResourceSelect = (resource: PamResourceType) => { if (!subscription.pam) { handlePopUpOpen("upgradePlan", { - description: "PAM (Privileged Access Management) requires an enterprise plan.", + text: "Your current plan does not include access to Infisical PAM. To unlock this feature, please upgrade to Infisical Enterprise plan.", isEnterpriseFeature: true }); return; @@ -68,8 +68,7 @@ export const ResourceTypeSelect = ({ onSelect }: Props) => { resource === PamResourceType.Kubernetes ) { handlePopUpOpen("upgradePlan", { - description: - "This resource type requires a special license add-on to be enabled in your enterprise plan.", + text: "Your current plan does not include access to this resource type. To unlock this feature, please upgrade to Infisical Enterprise plan.", isEnterpriseFeature: true }); return; @@ -180,7 +179,7 @@ export const ResourceTypeSelect = ({ onSelect }: Props) => { handlePopUpToggle("upgradePlan", isOpen)} - text={popUp.upgradePlan.data?.description} + text={popUp.upgradePlan.data?.text} isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} /> diff --git a/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogsSection.tsx b/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogsSection.tsx index 5f06a64e2..7a4945540 100644 --- a/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogsSection.tsx +++ b/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogsSection.tsx @@ -1,42 +1,75 @@ -import { useState } from "react"; -import { faChevronDown, faChevronRight } from "@fortawesome/free-solid-svg-icons"; +import { useMemo, useState } from "react"; +import { faChevronRight, faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { twMerge } from "tailwind-merge"; +import { Input } from "@app/components/v2"; +import { HighlightText } from "@app/components/v2/HighlightText"; import { TPamSession } from "@app/hooks/api/pam"; +import { formatLogContent } from "./PamSessionLogsSection.utils"; + type Props = { session: TPamSession; }; export const PamSessionLogsSection = ({ session }: Props) => { const [expandedLogTimestamps, setExpandedLogTimestamps] = useState>(new Set()); + const [search, setSearch] = useState(""); const toggleExpand = (timestamp: string) => { setExpandedLogTimestamps((prev) => { - const newSet = new Set(prev); - if (newSet.has(timestamp)) { - newSet.delete(timestamp); - } else { - newSet.add(timestamp); + if (prev.has(timestamp)) { + return new Set(); } - return newSet; + return new Set([timestamp]); }); }; + const filteredLogs = useMemo( + () => + session.commandLogs.filter((log) => { + const { input, output } = log; + + const searchValue = search.trim().toLowerCase(); + + return ( + input.toLowerCase().includes(searchValue) || output.toLowerCase().includes(searchValue) + ); + }), + [session.commandLogs, search] + ); + return (

Session Logs

-
- {session.commandLogs.length > 0 ? ( - session.commandLogs.map((log) => { - const isExpanded = expandedLogTimestamps.has(log.timestamp); + +
+ { + const newSearch = e.target.value; + setSearch(newSearch); + }} + leftIcon={} + placeholder="Search logs..." + className="flex-1 bg-mineshaft-800" + containerClassName="bg-transparent" + /> +
+
+ {filteredLogs.length > 0 ? ( + filteredLogs.map((log) => { + const isExpanded = search.length || expandedLogTimestamps.has(log.timestamp); + const formattedInput = formatLogContent(log.input); + return ( ); }) ) : ( -
- {session.startedAt && session.endedAt ? ( +
+ {search.length ? ( +
+
No logs match search criteria
+
+ ) : (
Session logs are not yet available
@@ -78,8 +127,6 @@ export const PamSessionLogsSection = ({ session }: Props) => { If logs do not appear after some time, please contact your Gateway administrators.
- ) : ( - "No session logs" )}
)} diff --git a/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogsSection.utils.ts b/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogsSection.utils.ts new file mode 100644 index 000000000..d76145655 --- /dev/null +++ b/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogsSection.utils.ts @@ -0,0 +1,46 @@ +// This function trims top and bottom empty padding, as well as moves all relative text to the left while still respecting indentation +export const formatLogContent = (text: string | null | undefined): string => { + if (!text) return ""; + + let lines = text.split("\n"); + + // Find the first and last non-empty lines to trim vertical padding + let firstLineIndex = -1; + for (let i = 0; i < lines.length; i += 1) { + if (lines[i].trim() !== "") { + firstLineIndex = i; + break; + } + } + + if (firstLineIndex === -1) { + return ""; + } + + let lastLineIndex = -1; + for (let i = lines.length - 1; i >= 0; i -= 1) { + if (lines[i].trim() !== "") { + lastLineIndex = i; + break; + } + } + + lines = lines.slice(firstLineIndex, lastLineIndex + 1); + + // Determine the minimum indentation of non-empty lines + const indentations = lines + .filter((line) => line.trim() !== "") + .map((line) => { + const match = line.match(/^\s*/); + return match ? match[0].length : 0; + }); + + const minIndentation = indentations.length > 0 ? Math.min(...indentations) : 0; + + // Remove the common indentation from all lines + if (minIndentation > 0) { + lines = lines.map((line) => line.substring(minIndentation)); + } + + return lines.join("\n"); +}; diff --git a/frontend/src/pages/pam/PamSessionsPage/components/PamSessionRow.tsx b/frontend/src/pages/pam/PamSessionsPage/components/PamSessionRow.tsx index f730e331d..8721560c4 100644 --- a/frontend/src/pages/pam/PamSessionsPage/components/PamSessionRow.tsx +++ b/frontend/src/pages/pam/PamSessionsPage/components/PamSessionRow.tsx @@ -27,6 +27,7 @@ import { HighlightText } from "@app/components/v2/HighlightText"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { PAM_RESOURCE_TYPE_MAP, TPamSession } from "@app/hooks/api/pam"; +import { formatLogContent } from "../../PamSessionsByIDPage/components/PamSessionLogsSection.utils"; import { PamSessionStatusBadge } from "./PamSessionStatusBadge"; type Props = { @@ -159,21 +160,28 @@ export const PamSessionRow = ({ session, search, filteredCommandLogs }: Props) = {filteredCommandLogs.length > 0 && (
- {logsToShow.map((log) => ( -
-
- - {new Date(log.timestamp).toLocaleString()} -
+ {logsToShow.map((log) => { + const formattedInput = formatLogContent(log.input); -
- + return ( +
+
+ + {new Date(log.timestamp).toLocaleString()} +
+ +
+ +
+
+ +
-
- -
-
- ))} + ); + })} {filteredCommandLogs.length > LOGS_TO_SHOW && (
); diff --git a/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx b/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx index 17dbdca27..48e01e2aa 100644 --- a/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx @@ -119,28 +119,17 @@ export const IdentityTab = withProjectPermission( ] as const); const onRemoveIdentitySubmit = async (identityId: string) => { - try { - await deleteMutateAsync({ - identityId, - projectId - }); + await deleteMutateAsync({ + identityId, + projectId + }); - createNotification({ - text: "Successfully removed identity from project", - type: "success" - }); + createNotification({ + text: "Successfully removed identity from project", + type: "success" + }); - handlePopUpClose("deleteIdentity"); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to remove identity from project"; - - createNotification({ - text, - type: "error" - }); - } + handlePopUpClose("deleteIdentity"); }; const handleSort = (column: ProjectIdentityOrderBy) => { diff --git a/frontend/src/pages/project/AccessControlPage/components/IdentityTab/components/IdentityModal.tsx b/frontend/src/pages/project/AccessControlPage/components/IdentityTab/components/IdentityModal.tsx index 55f22c5ae..3274185b1 100644 --- a/frontend/src/pages/project/AccessControlPage/components/IdentityTab/components/IdentityModal.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/IdentityTab/components/IdentityModal.tsx @@ -104,40 +104,29 @@ const Content = ({ popUp, handlePopUpToggle }: Props) => { }); const onFormSubmit = async ({ identity, role }: FormData) => { - try { - await addIdentityToWorkspaceMutateAsync({ - projectId, - identityId: identity.id, - role: role.slug || undefined - }); + await addIdentityToWorkspaceMutateAsync({ + projectId, + identityId: identity.id, + role: role.slug || undefined + }); - createNotification({ - text: "Successfully added identity to project", - type: "success" - }); + createNotification({ + text: "Successfully added identity to project", + type: "success" + }); - const nextAvailableMembership = filteredIdentityMembershipOrgs.filter( - (membership) => membership.identity.id !== identity.id - )[0]; + const nextAvailableMembership = filteredIdentityMembershipOrgs.filter( + (membership) => membership.identity.id !== identity.id + )[0]; - // prevents combobox from displaying previously added identity - reset({ - identity: { - name: nextAvailableMembership?.identity.name, - id: nextAvailableMembership?.identity.id - } - }); - handlePopUpToggle("identity", false); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to add identity to project"; - - createNotification({ - text, - type: "error" - }); - } + // prevents combobox from displaying previously added identity + reset({ + identity: { + name: nextAvailableMembership?.identity.name, + id: nextAvailableMembership?.identity.id + } + }); + handlePopUpToggle("identity", false); }; if (isMembershipsLoading || isRolesLoading) diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx index 3477440fe..7498b3181 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx @@ -25,14 +25,12 @@ import { useProject } from "@app/context"; import { - useAddUsersToOrg, useAddUserToWsNonE2EE, useGetOrgUsers, useGetProjectRoles, useGetWorkspaceUsers } from "@app/hooks/api"; import { ProjectVersion } from "@app/hooks/api/projects/types"; -import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; const addMemberFormSchema = z.object({ @@ -86,7 +84,6 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => { defaultValues: { orgMemberships: [], projectRoleSlugs: [] } }); - const { mutateAsync: addMemberToOrg } = useAddUsersToOrg(); const { mutateAsync: addUserToProject } = useAddUserToWsNonE2EE(); useEffect(() => { @@ -110,65 +107,49 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => { if (!selectedMembers) return; - try { - if (currentProject.version === ProjectVersion.V1) { + if (currentProject.version === ProjectVersion.V1) { + createNotification({ + type: "error", + text: "Please upgrade your project to invite new members to the project." + }); + } else { + const inviteeEmails = selectedMembers + .map((member) => { + if (!member) return null; + + if (member.user.username) { + return member.user.username; + } + + if (member.user.email) { + return member.user.email; + } + + return null; + }) + .filter(Boolean) as string[]; + + if (inviteeEmails.length !== selectedMembers.length) { createNotification({ - type: "error", - text: "Please upgrade your project to invite new members to the project." + text: "Failed to add users to project. One or more users were invalid.", + type: "error" + }); + return; + } + + if (newInvitees.length || inviteeEmails.length) { + await addUserToProject({ + usernames: [...inviteeEmails, ...newInvitees], + orgId, + projectId: currentProject.id, + roleSlugs: projectRoleSlugs.map((role) => role.slug) }); - } else { - const inviteeEmails = selectedMembers - .map((member) => { - if (!member) return null; - - if (member.user.username) { - return member.user.username; - } - - if (member.user.email) { - return member.user.email; - } - - return null; - }) - .filter(Boolean) as string[]; - - if (inviteeEmails.length !== selectedMembers.length) { - createNotification({ - text: "Failed to add users to project. One or more users were invalid.", - type: "error" - }); - return; - } - - if (newInvitees.length) { - await addMemberToOrg({ - inviteeEmails: newInvitees, - organizationId: orgId, - organizationRoleSlug: ProjectMembershipRole.Member // only applies to new invites - }); - } - if (newInvitees.length || inviteeEmails.length) { - await addUserToProject({ - usernames: [...inviteeEmails, ...newInvitees], - orgId, - projectId: currentProject.id, - roleSlugs: projectRoleSlugs.map((role) => role.slug) - }); - } } - createNotification({ - text: "Successfully added user to the project", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to add user to project", - type: "error" - }); - return; } + createNotification({ + text: "Successfully added user to the project", + type: "success" + }); handlePopUpToggle("addMember", false); reset(); }; diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/MemberRbacSection.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/MemberRbacSection.tsx index 291a5b58b..5be239042 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/MemberRbacSection.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/MemberRbacSection.tsx @@ -127,17 +127,13 @@ export const MemberRbacSection = ({ projectMember, onOpenUpgradeModal }: Props) return; } - try { - await updateMembershipRole.mutateAsync({ - projectId, - membershipId: projectMember.id, - roles: sanitizedRoles - }); - createNotification({ text: "Successfully updated roles", type: "success" }); - roleForm.reset(undefined, { keepValues: true }); - } catch { - createNotification({ text: "Failed to update role", type: "error" }); - } + await updateMembershipRole.mutateAsync({ + projectId, + membershipId: projectMember.id, + roles: sanitizedRoles + }); + createNotification({ text: "Successfully updated roles", type: "success" }); + roleForm.reset(undefined, { keepValues: true }); }; if (isRolesLoading) 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 33c0d1077..c3eb0c9aa 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 @@ -182,21 +182,14 @@ export const SpecificPrivilegeSecretForm = ({ } if (deleteUserPrivilege.isPending) return; - try { - await deleteUserPrivilege.mutateAsync({ - privilegeId: privilege.id, - projectMembershipId: privilege.projectMembershipId - }); - createNotification({ - type: "success", - text: "Successfully deleted privilege" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to delete privilege" - }); - } + await deleteUserPrivilege.mutateAsync({ + privilegeId: privilege.id, + projectMembershipId: privilege.projectMembershipId + }); + createNotification({ + type: "success", + text: "Successfully deleted privilege" + }); }; // This is used for requesting access additional privileges, not directly creating a privilege! diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersSection.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersSection.tsx index 5904f12fb..38dc5e47e 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersSection.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersSection.tsx @@ -1,7 +1,6 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, DeleteActionModal } from "@app/components/v2"; @@ -26,8 +25,7 @@ export const MembersSection = () => { const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ "addMember", - "removeMember", - "upgradePlan" + "removeMember" ] as const); const handleRemoveUser = async () => { @@ -35,23 +33,15 @@ export const MembersSection = () => { if (!currentOrg?.id) return; if (!currentProject?.id) return; - try { - await removeUserFromWorkspace({ - projectId: currentProject.id, - usernames: [username], - orgId: currentOrg.id - }); - createNotification({ - text: "Successfully removed user from project", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to remove user from the project", - type: "error" - }); - } + await removeUserFromWorkspace({ + projectId: currentProject.id, + usernames: [username], + orgId: currentOrg.id + }); + createNotification({ + text: "Successfully removed user from project", + type: "success" + }); handlePopUpClose("removeMember"); }; @@ -85,11 +75,6 @@ export const MembersSection = () => { onChange={(isOpen) => handlePopUpToggle("removeMember", isOpen)} onDeleteApproved={handleRemoveUser} /> - handlePopUpToggle("upgradePlan", isOpen)} - text={(popUp.upgradePlan?.data as { description: string })?.description} - />
); }; diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersTable.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersTable.tsx index e8cb43db9..d554f12fb 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersTable.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersTable.tsx @@ -60,10 +60,7 @@ import { UsePopUpState } from "@app/hooks/usePopUp"; const MAX_ROLES_TO_BE_SHOWN_IN_TABLE = 2; type Props = { - handlePopUpOpen: ( - popUpName: keyof UsePopUpState<["removeMember", "upgradePlan"]>, - data?: object - ) => void; + handlePopUpOpen: (popUpName: keyof UsePopUpState<["removeMember"]>, data?: object) => void; }; enum MembersOrderBy { diff --git a/frontend/src/pages/project/AccessControlPage/components/ProjectRoleListTab/components/ProjectRoleList/ProjectRoleList.tsx b/frontend/src/pages/project/AccessControlPage/components/ProjectRoleListTab/components/ProjectRoleList/ProjectRoleList.tsx index fca53fa06..eeb29725f 100644 --- a/frontend/src/pages/project/AccessControlPage/components/ProjectRoleListTab/components/ProjectRoleList/ProjectRoleList.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/ProjectRoleListTab/components/ProjectRoleList/ProjectRoleList.tsx @@ -77,17 +77,12 @@ export const ProjectRoleList = () => { const handleRoleDelete = async () => { const { id } = popUp?.deleteRole?.data as TProjectRole; - try { - await deleteRole({ - projectId, - id - }); - createNotification({ type: "success", text: "Successfully removed the role" }); - handlePopUpClose("deleteRole"); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to delete role" }); - } + await deleteRole({ + projectId, + id + }); + createNotification({ type: "success", text: "Successfully removed the role" }); + handlePopUpClose("deleteRole"); }; const { diff --git a/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/AddServiceTokenModal.tsx b/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/AddServiceTokenModal.tsx index 873422f3c..25d943f4a 100644 --- a/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/AddServiceTokenModal.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/AddServiceTokenModal.tsx @@ -6,7 +6,6 @@ import { useTranslation } from "react-i18next"; import { faCheck, faCopy, faPlus, faTrashCan } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; -import { AxiosError } from "axios"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; @@ -110,45 +109,29 @@ const ServiceTokenForm = () => { }; const onFormSubmit = async ({ name, scopes, expiresIn, permissions }: FormData) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - const randomBytes = crypto.randomBytes(16).toString("hex"); + const randomBytes = crypto.randomBytes(16).toString("hex"); - const { serviceToken } = await createServiceToken.mutateAsync({ - encryptedKey: "", - iv: "", - tag: "", - scopes, - expiresIn: Number(expiresIn), - name, - workspaceId: currentProject.id, - randomBytes, - permissions: Object.entries(permissions) - .filter(([, permissionsValue]) => permissionsValue) - .map(([permissionsKey]) => permissionsKey) - }); + const { serviceToken } = await createServiceToken.mutateAsync({ + encryptedKey: "", + iv: "", + tag: "", + scopes, + expiresIn: Number(expiresIn), + name, + workspaceId: currentProject.id, + randomBytes, + permissions: Object.entries(permissions) + .filter(([, permissionsValue]) => permissionsValue) + .map(([permissionsKey]) => permissionsKey) + }); - setToken(serviceToken); - createNotification({ - text: "Successfully created a service token", - type: "success" - }); - } catch (err) { - console.error(err); - const axiosError = err as AxiosError; - if (axiosError?.response?.status === 401) { - createNotification({ - text: "You do not have access to the selected environment/path", - type: "error" - }); - } else { - createNotification({ - text: "Failed to create a service token", - type: "error" - }); - } - } + setToken(serviceToken); + createNotification({ + text: "Successfully created a service token", + type: "success" + }); }; return !hasServiceToken ? ( diff --git a/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/ServiceTokenSection.tsx b/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/ServiceTokenSection.tsx index 64b042c35..6f2e82e02 100644 --- a/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/ServiceTokenSection.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/ServiceTokenSection.tsx @@ -28,23 +28,15 @@ export const ServiceTokenSection = withProjectPermission( ] as const); const onDeleteApproved = async () => { - try { - deleteServiceToken.mutateAsync( - (popUp?.deleteAPITokenConfirmation?.data as DeleteModalData)?.id - ); - createNotification({ - text: "Successfully deleted service token", - type: "success" - }); + await deleteServiceToken.mutateAsync( + (popUp?.deleteAPITokenConfirmation?.data as DeleteModalData)?.id + ); + createNotification({ + text: "Successfully deleted service token", + type: "success" + }); - handlePopUpClose("deleteAPITokenConfirmation"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete service token", - type: "error" - }); - } + handlePopUpClose("deleteAPITokenConfirmation"); }; return ( diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupDetailsSection.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupDetailsSection.tsx index cca6cabfd..368a740c2 100644 --- a/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupDetailsSection.tsx +++ b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupDetailsSection.tsx @@ -35,38 +35,27 @@ export const GroupDetailsSection = ({ groupMembership }: Props) => { const navigate = useNavigate(); const onRemoveGroupSubmit = async () => { - try { - await deleteMutateAsync({ - groupId: groupMembership.group.id, + await deleteMutateAsync({ + groupId: groupMembership.group.id, + projectId: currentProject.id + }); + + createNotification({ + text: "Successfully removed group from project", + type: "success" + }); + + navigate({ + to: `${getProjectBaseURL(currentProject.type)}/access-management`, + params: { projectId: currentProject.id - }); + }, + search: { + selectedTab: "groups" + } + }); - createNotification({ - text: "Successfully removed group from project", - type: "success" - }); - - navigate({ - to: `${getProjectBaseURL(currentProject.type)}/access-management`, - params: { - projectId: currentProject.id - }, - search: { - selectedTab: "groups" - } - }); - - handlePopUpClose("deleteGroup"); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to remove group from project"; - - createNotification({ - text, - type: "error" - }); - } + handlePopUpClose("deleteGroup"); }; return ( diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx index e49e05022..65a2a5710 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx @@ -76,35 +76,24 @@ const Page = () => { }; const onRemoveIdentitySubmit = async () => { - try { - await deleteMutateAsync({ - identityId, + await deleteMutateAsync({ + identityId, + projectId + }); + createNotification({ + text: "Successfully removed identity from project", + type: "success" + }); + handlePopUpClose("deleteIdentity"); + navigate({ + to: `${getProjectBaseURL(currentProject.type)}/access-management` as const, + params: { projectId - }); - createNotification({ - text: "Successfully removed identity from project", - type: "success" - }); - handlePopUpClose("deleteIdentity"); - navigate({ - to: `${getProjectBaseURL(currentProject.type)}/access-management` as const, - params: { - projectId - }, - search: { - selectedTab: "identities" - } - }); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to remove identity from project"; - - createNotification({ - text, - type: "error" - }); - } + }, + search: { + selectedTab: "identities" + } + }); }; if (isMembershipDetailsLoading) { diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx index a5d74271c..956c01d69 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx @@ -131,33 +131,28 @@ export const IdentityProjectAdditionalPrivilegeModifySection = ({ temporaryAccessStartTime: el.temporaryAccess.temporaryAccessStartTime }; - try { - if (isCreate) { - await createIdentityProjectAdditionalPrivilege({ - permissions: formRolePermission2API(el.permissions), - identityId, - projectId, - slug: el.slug || undefined, - type: accessType - }); - createNotification({ type: "success", text: "Successfully created privilege" }); - } else { - if (!projectId || !privilegeDetails?.id) return; - await updateIdentityProjectAdditionalPrivilege({ - privilegeId: privilegeDetails.id, - permissions: formRolePermission2API(el.permissions), - projectId, - identityId, - slug: el.slug || undefined, - type: accessType - }); - createNotification({ type: "success", text: "Successfully updated privilege" }); - } - onGoBack(); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to update privilege" }); + if (isCreate) { + await createIdentityProjectAdditionalPrivilege({ + permissions: formRolePermission2API(el.permissions), + identityId, + projectId, + slug: el.slug || undefined, + type: accessType + }); + createNotification({ type: "success", text: "Successfully created privilege" }); + } else { + if (!projectId || !privilegeDetails?.id) return; + await updateIdentityProjectAdditionalPrivilege({ + privilegeId: privilegeDetails.id, + permissions: formRolePermission2API(el.permissions), + projectId, + identityId, + slug: el.slug || undefined, + type: accessType + }); + createNotification({ type: "success", text: "Successfully updated privilege" }); } + onGoBack(); }; const privilegeTemporaryAccess = form.watch("temporaryAccess"); diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeSection.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeSection.tsx index f98f6ebf8..431875b3c 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeSection.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeSection.tsx @@ -57,18 +57,13 @@ export const IdentityProjectAdditionalPrivilegeSection = ({ identityMembershipDe const handlePrivilegeDelete = async () => { const { id } = popUp?.deletePrivilege?.data as { id: string }; - try { - await deletePrivilege({ - privilegeId: id, - projectId, - identityId - }); - createNotification({ type: "success", text: "Successfully removed the privilege" }); - handlePopUpClose("deletePrivilege"); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to delete privilege" }); - } + await deletePrivilege({ + privilegeId: id, + projectId, + identityId + }); + createNotification({ type: "success", text: "Successfully removed the privilege" }); + handlePopUpClose("deletePrivilege"); }; return ( diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleDetailsSection.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleDetailsSection.tsx index 34b5229d6..dce632121 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleDetailsSection.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleDetailsSection.tsx @@ -50,42 +50,37 @@ export const IdentityRoleDetailsSection = ({ const handleRoleDelete = async () => { const { id } = popUp?.deleteRole?.data as TProjectRole; - try { - const updatedRoles = identityMembershipDetails?.roles?.filter((el) => el.id !== id); - await updateIdentityWorkspaceRole({ - projectId: currentProject?.id || "", - identityId: identityMembershipDetails.identity.id, - roles: updatedRoles.map( - ({ - role, - customRoleSlug, - isTemporary, - temporaryMode, - temporaryRange, - temporaryAccessStartTime, - temporaryAccessEndTime - }) => ({ - role: role === "custom" ? customRoleSlug : role, - ...(isTemporary - ? { - isTemporary, - temporaryMode, - temporaryRange, - temporaryAccessStartTime, - temporaryAccessEndTime - } - : { - isTemporary - }) - }) - ) - }); - createNotification({ type: "success", text: "Successfully removed role" }); - handlePopUpClose("deleteRole"); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to delete role" }); - } + const updatedRoles = identityMembershipDetails?.roles?.filter((el) => el.id !== id); + await updateIdentityWorkspaceRole({ + projectId: currentProject?.id || "", + identityId: identityMembershipDetails.identity.id, + roles: updatedRoles.map( + ({ + role, + customRoleSlug, + isTemporary, + temporaryMode, + temporaryRange, + temporaryAccessStartTime, + temporaryAccessEndTime + }) => ({ + role: role === "custom" ? customRoleSlug : role, + ...(isTemporary + ? { + isTemporary, + temporaryMode, + temporaryRange, + temporaryAccessStartTime, + temporaryAccessEndTime + } + : { + isTemporary + }) + }) + ) + }); + createNotification({ type: "success", text: "Successfully removed role" }); + handlePopUpClose("deleteRole"); }; return ( diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleModify.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleModify.tsx index a74428e98..8bc451151 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleModify.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleModify.tsx @@ -114,16 +114,12 @@ export const IdentityRoleModify = ({ identityProjectMembership }: Props) => { }; }); - try { - await updateIdentityWorkspaceRole.mutateAsync({ - projectId, - identityId: identityProjectMembership.identity.id, - roles: sanitizedRoles - }); - createNotification({ text: "Successfully updated roles", type: "success" }); - } catch { - createNotification({ text: "Failed to update roles", type: "error" }); - } + await updateIdentityWorkspaceRole.mutateAsync({ + projectId, + identityId: identityProjectMembership.identity.id, + roles: sanitizedRoles + }); + createNotification({ text: "Successfully updated roles", type: "success" }); }; if (isRolesLoading) diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx index 2b85291d0..5d8ae812c 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx @@ -83,29 +83,21 @@ export const Page = () => { const handleRemoveUser = async () => { if (!currentOrg?.id || !currentProject?.id || !membershipDetails?.user?.username) return; - try { - await removeUserFromWorkspace({ - projectId, - usernames: [membershipDetails?.user?.username], - orgId: currentOrg.id - }); - createNotification({ - text: "Successfully removed user from project", - type: "success" - }); - navigate({ - to: `${getProjectBaseURL(currentProject.type)}/access-management` as const, - params: { - projectId: currentProject.id - } - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to remove user from the project", - type: "error" - }); - } + await removeUserFromWorkspace({ + projectId, + usernames: [membershipDetails?.user?.username], + orgId: currentOrg.id + }); + createNotification({ + text: "Successfully removed user from project", + type: "success" + }); + navigate({ + to: `${getProjectBaseURL(currentProject.type)}/access-management` as const, + params: { + projectId: currentProject.id + } + }); handlePopUpClose("removeMember"); }; @@ -190,8 +182,7 @@ export const Page = () => { isMembershipDetailsLoading={isMembershipDetailsLoading} onOpenUpgradeModal={() => handlePopUpOpen("upgradePlan", { - description: - "You can assign custom roles to members if you switch to Infisical's Pro plan." + text: "Assigning custom roles to members can be unlocked if you upgrade to Infisical Pro plan." }) } /> @@ -215,7 +206,7 @@ export const Page = () => { handlePopUpToggle("upgradePlan", isOpen)} - text={(popUp.upgradePlan?.data as { description: string })?.description} + text={popUp.upgradePlan?.data?.text} /> ) : ( diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MemberProjectAdditionalPrivilegeSection.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MemberProjectAdditionalPrivilegeSection.tsx index 7094c9ca8..28c3c9b5f 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MemberProjectAdditionalPrivilegeSection.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MemberProjectAdditionalPrivilegeSection.tsx @@ -60,17 +60,12 @@ export const MemberProjectAdditionalPrivilegeSection = ({ membershipDetails }: P const handlePrivilegeDelete = async () => { const { id } = popUp?.deletePrivilege?.data as { id: string }; - try { - await deletePrivilege({ - privilegeId: id, - projectMembershipId: membershipDetails.id - }); - createNotification({ type: "success", text: "Successfully removed the privilege" }); - handlePopUpClose("deletePrivilege"); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to delete privilege" }); - } + await deletePrivilege({ + privilegeId: id, + projectMembershipId: membershipDetails.id + }); + createNotification({ type: "success", text: "Successfully removed the privilege" }); + handlePopUpClose("deletePrivilege"); }; return ( diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MembershipProjectAdditionalPrivilegeModifySection.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MembershipProjectAdditionalPrivilegeModifySection.tsx index 1a217a509..bc5bd3a6f 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MembershipProjectAdditionalPrivilegeModifySection.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MembershipProjectAdditionalPrivilegeModifySection.tsx @@ -129,31 +129,26 @@ export const MembershipProjectAdditionalPrivilegeModifySection = ({ temporaryAccessStartTime: el.temporaryAccess.temporaryAccessStartTime }; - try { - if (isCreate) { - await createUserProjectAdditionalPrivilege({ - permissions: formRolePermission2API(el.permissions), - projectMembershipId, - slug: el.slug || undefined, - type: accessType - }); - createNotification({ type: "success", text: "Successfully created privilege" }); - } else { - if (!projectId || !privilegeDetails?.id) return; - await updateUserProjectAdditionalPrivilege({ - privilegeId: privilegeDetails.id, - permissions: formRolePermission2API(el.permissions), - projectMembershipId, - slug: el.slug || undefined, - type: accessType - }); - createNotification({ type: "success", text: "Successfully updated privilege" }); - } - onGoBack(); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to update privilege" }); + if (isCreate) { + await createUserProjectAdditionalPrivilege({ + permissions: formRolePermission2API(el.permissions), + projectMembershipId, + slug: el.slug || undefined, + type: accessType + }); + createNotification({ type: "success", text: "Successfully created privilege" }); + } else { + if (!projectId || !privilegeDetails?.id) return; + await updateUserProjectAdditionalPrivilege({ + privilegeId: privilegeDetails.id, + permissions: formRolePermission2API(el.permissions), + projectMembershipId, + slug: el.slug || undefined, + type: accessType + }); + createNotification({ type: "success", text: "Successfully updated privilege" }); } + onGoBack(); }; const privilegeTemporaryAccess = form.watch("temporaryAccess"); diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleDetailsSection.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleDetailsSection.tsx index 703511c84..2654e237e 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleDetailsSection.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleDetailsSection.tsx @@ -55,42 +55,37 @@ export const MemberRoleDetailsSection = ({ const handleRoleDelete = async () => { const { id } = popUp?.deleteRole?.data as TProjectRole; - try { - const updatedRoles = membershipDetails?.roles?.filter((el) => el.id !== id); - await updateUserWorkspaceRole({ - projectId, - roles: updatedRoles.map( - ({ - role, - customRoleSlug, - isTemporary, - temporaryMode, - temporaryRange, - temporaryAccessStartTime, - temporaryAccessEndTime - }) => ({ - role: role === "custom" ? customRoleSlug : role, - ...(isTemporary - ? { - isTemporary, - temporaryMode, - temporaryRange, - temporaryAccessStartTime, - temporaryAccessEndTime - } - : { - isTemporary - }) - }) - ), - membershipId: membershipDetails.id - }); - createNotification({ type: "success", text: "Successfully removed role" }); - handlePopUpClose("deleteRole"); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to delete role" }); - } + const updatedRoles = membershipDetails?.roles?.filter((el) => el.id !== id); + await updateUserWorkspaceRole({ + projectId, + roles: updatedRoles.map( + ({ + role, + customRoleSlug, + isTemporary, + temporaryMode, + temporaryRange, + temporaryAccessStartTime, + temporaryAccessEndTime + }) => ({ + role: role === "custom" ? customRoleSlug : role, + ...(isTemporary + ? { + isTemporary, + temporaryMode, + temporaryRange, + temporaryAccessStartTime, + temporaryAccessEndTime + } + : { + isTemporary + }) + }) + ), + membershipId: membershipDetails.id + }); + createNotification({ type: "success", text: "Successfully removed role" }); + handlePopUpClose("deleteRole"); }; return ( diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleModify.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleModify.tsx index 287ea3ad7..947424191 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleModify.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleModify.tsx @@ -126,16 +126,12 @@ export const MemberRoleModify = ({ projectMember, onOpenUpgradeModal }: Props) = return; } - try { - await updateMembershipRole.mutateAsync({ - projectId, - membershipId: projectMember.id, - roles: sanitizedRoles - }); - createNotification({ text: "Successfully updated roles", type: "success" }); - } catch { - createNotification({ text: "Failed to update roles", type: "error" }); - } + await updateMembershipRole.mutateAsync({ + projectId, + membershipId: projectMember.id, + roles: sanitizedRoles + }); + createNotification({ text: "Successfully updated roles", type: "success" }); }; if (isRolesLoading) diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/RoleDetailsBySlugPage.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/RoleDetailsBySlugPage.tsx index 9650b3fb8..9a87266c4 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/RoleDetailsBySlugPage.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/RoleDetailsBySlugPage.tsx @@ -53,38 +53,27 @@ const Page = () => { ] as const); const onDeleteRoleSubmit = async () => { - try { - if (!currentProject?.slug || !data?.id) return; + if (!currentProject?.slug || !data?.id) return; - await deleteProjectRole({ - projectId, - id: data.id - }); + await deleteProjectRole({ + projectId, + id: data.id + }); - createNotification({ - text: "Successfully deleted project role", - type: "success" - }); - handlePopUpClose("deleteRole"); - navigate({ - to: `${getProjectBaseURL(currentProject.type)}/access-management` as const, - params: { - projectId - }, - search: { - selectedTab: ProjectAccessControlTabs.Roles - } - }); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to delete project role"; - - createNotification({ - text, - type: "error" - }); - } + createNotification({ + text: "Successfully deleted project role", + type: "success" + }); + handlePopUpClose("deleteRole"); + navigate({ + to: `${getProjectBaseURL(currentProject.type)}/access-management` as const, + params: { + projectId + }, + search: { + selectedTab: ProjectAccessControlTabs.Roles + } + }); }; const isCustomRole = !Object.values(ProjectMembershipRole).includes( diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RoleModal.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RoleModal.tsx index 43467876b..03525eac5 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RoleModal.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RoleModal.tsx @@ -76,63 +76,54 @@ export const RoleModal = ({ popUp, handlePopUpToggle }: Props) => { }, [role]); const onFormSubmit = async ({ name, description, slug }: FormData) => { - try { - if (!projectId) return; + if (!projectId) return; - if (role) { - // update - await updateProjectRole({ - id: role.id, - projectId, - name, - description, - slug - }); - - handlePopUpToggle("role", false); - if (slug) { - navigate({ - to: `${getProjectBaseURL(currentProject.type)}/roles/$roleSlug` as const, - params: { - roleSlug: slug, - projectId - } - }); - } - } else { - // create - const newRole = await createProjectRole({ - projectId, - name, - description, - slug, - permissions: [] - }); + if (role) { + // update + await updateProjectRole({ + id: role.id, + projectId, + name, + description, + slug + }); + handlePopUpToggle("role", false); + if (slug) { navigate({ to: `${getProjectBaseURL(currentProject.type)}/roles/$roleSlug` as const, params: { - roleSlug: newRole.slug, + roleSlug: slug, projectId } }); - handlePopUpToggle("role", false); } - - createNotification({ - text: `Successfully ${popUp?.role?.data ? "updated" : "created"} role`, - type: "success" + } else { + // create + const newRole = await createProjectRole({ + projectId, + name, + description, + slug, + permissions: [] }); - reset(); - } catch { - const text = `Failed to ${popUp?.role?.data ? "update" : "create"} role`; - - createNotification({ - text, - type: "error" + navigate({ + to: `${getProjectBaseURL(currentProject.type)}/roles/$roleSlug` as const, + params: { + roleSlug: newRole.slug, + projectId + } }); + handlePopUpToggle("role", false); } + + createNotification({ + text: `Successfully ${popUp?.role?.data ? "updated" : "created"} role`, + type: "success" + }); + + reset(); }; return ( diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx index fd95d2659..c93cea534 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx @@ -122,19 +122,14 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => { const { mutateAsync: updateRole } = useUpdateProjectRole(); const onSubmit = async (el: TFormSchema) => { - try { - if (!projectId || !role?.id) return; - await updateRole({ - id: role?.id as string, - projectId, - ...el, - permissions: formRolePermission2API(el.permissions) - }); - createNotification({ type: "success", text: "Successfully updated role" }); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to update role" }); - } + if (!projectId || !role?.id) return; + await updateRole({ + id: role?.id as string, + projectId, + ...el, + permissions: formRolePermission2API(el.permissions) + }); + createNotification({ type: "success", text: "Successfully updated role" }); }; const isCustomRole = !Object.values(ProjectMembershipRole).includes( diff --git a/frontend/src/pages/project/SettingsPage/components/AuditLogsRetentionSection/AuditLogsRetentionSection.tsx b/frontend/src/pages/project/SettingsPage/components/AuditLogsRetentionSection/AuditLogsRetentionSection.tsx index d9bbc80d8..cf093c21a 100644 --- a/frontend/src/pages/project/SettingsPage/components/AuditLogsRetentionSection/AuditLogsRetentionSection.tsx +++ b/frontend/src/pages/project/SettingsPage/components/AuditLogsRetentionSection/AuditLogsRetentionSection.tsx @@ -39,40 +39,31 @@ export const AuditLogsRetentionSection = () => { if (!currentProject) return null; const handleAuditLogsRetentionSubmit = async ({ auditLogsRetentionDays }: TForm) => { - try { - if (!subscription?.auditLogs) { - handlePopUpOpen("upgradePlan", { - description: - "You can only configure audit logs retention if you switch to Infisical's Pro plan." - }); - - return; - } - - if (subscription && auditLogsRetentionDays > subscription?.auditLogsRetentionDays) { - handlePopUpOpen("upgradePlan", { - description: - "To update your audit logs retention period to a higher value, switch to Infisical's Pro plan." - }); - - return; - } - - await updateAuditLogsRetention({ - auditLogsRetentionDays, - projectSlug: currentProject.slug + if (!subscription?.auditLogs) { + handlePopUpOpen("upgradePlan", { + text: "Configuring audit logs retention can be unlocked if you upgrade to Infisical Pro plan." }); - createNotification({ - text: "Successfully updated audit logs retention period", - type: "success" - }); - } catch { - createNotification({ - text: "Failed updating audit logs retention period", - type: "error" - }); + return; } + + if (subscription && auditLogsRetentionDays > subscription?.auditLogsRetentionDays) { + handlePopUpOpen("upgradePlan", { + text: "Updating audit logs retention period to a higher value can be unlocked if you upgrade to Infisical Pro plan." + }); + + return; + } + + await updateAuditLogsRetention({ + auditLogsRetentionDays, + projectSlug: currentProject.slug + }); + + createNotification({ + text: "Successfully updated audit logs retention period", + type: "success" + }); }; // render only for dedicated/self-hosted instances of Infisical @@ -123,7 +114,7 @@ export const AuditLogsRetentionSection = () => { handlePopUpToggle("upgradePlan", isOpen)} - text={(popUp.upgradePlan?.data as { description: string })?.description} + text={popUp.upgradePlan?.data?.text} /> ); diff --git a/frontend/src/pages/project/SettingsPage/components/DeleteProjectProtection/DeleteProjectProtection.tsx b/frontend/src/pages/project/SettingsPage/components/DeleteProjectProtection/DeleteProjectProtection.tsx index 7160e9adf..3cb0c9371 100644 --- a/frontend/src/pages/project/SettingsPage/components/DeleteProjectProtection/DeleteProjectProtection.tsx +++ b/frontend/src/pages/project/SettingsPage/components/DeleteProjectProtection/DeleteProjectProtection.tsx @@ -10,24 +10,16 @@ export const DeleteProjectProtection = () => { const { mutateAsync } = useUpdateProject(); const handleToggleDeleteProjectProtection = async (state: boolean) => { - try { - await mutateAsync({ - projectId, - hasDeleteProtection: state - }); + await mutateAsync({ + projectId, + hasDeleteProtection: state + }); - const text = `Successfully ${state ? "enabled" : "disabled"} delete protection`; - createNotification({ - text, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update delete protection", - type: "error" - }); - } + const text = `Successfully ${state ? "enabled" : "disabled"} delete protection`; + createNotification({ + text, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/project/SettingsPage/components/DeleteProjectSection/DeleteProjectSection.tsx b/frontend/src/pages/project/SettingsPage/components/DeleteProjectSection/DeleteProjectSection.tsx index fb66f0a74..58db07484 100644 --- a/frontend/src/pages/project/SettingsPage/components/DeleteProjectSection/DeleteProjectSection.tsx +++ b/frontend/src/pages/project/SettingsPage/components/DeleteProjectSection/DeleteProjectSection.tsx @@ -68,12 +68,6 @@ export const DeleteProjectSection = () => { to: "/organization/projects" }); handlePopUpClose("deleteWorkspace"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete project", - type: "error" - }); } finally { setIsDeleting.off(); } @@ -118,12 +112,6 @@ export const DeleteProjectSection = () => { navigate({ to: "/organization/projects" }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to leave project", - type: "error" - }); } finally { setIsLeaving.off(); } diff --git a/frontend/src/pages/public/ErrorPage/components/ProjectAccessError.tsx b/frontend/src/pages/public/ErrorPage/components/ProjectAccessError.tsx index d6fd57d56..a07dd1674 100644 --- a/frontend/src/pages/public/ErrorPage/components/ProjectAccessError.tsx +++ b/frontend/src/pages/public/ErrorPage/components/ProjectAccessError.tsx @@ -2,7 +2,6 @@ import { faHome } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Link, useNavigate, useParams } from "@tanstack/react-router"; -import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { RequestProjectAccessModal } from "@app/components/projects"; import { AccessRestrictedBanner, Button } from "@app/components/v2"; @@ -35,19 +34,12 @@ export const ProjectAccessError = () => { const handleAccessProject = async () => { if (!project) return; - try { - await orgAdminAccessProject.mutateAsync({ - projectId: project.id - }); - await navigate({ - to: "." - }); - } catch { - createNotification({ - text: "Failed to access project", - type: "error" - }); - } + await orgAdminAccessProject.mutateAsync({ + projectId: project.id + }); + await navigate({ + to: "." + }); }; return ( diff --git a/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx b/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx index a34c08f9d..b8e93f0fe 100644 --- a/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx +++ b/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx @@ -131,52 +131,44 @@ export const ShareSecretForm = ({ emails, shouldLimitView }: FormData) => { - try { - const expiresAt = new Date(new Date().getTime() + Number(expiresIn)); + const expiresAt = new Date(new Date().getTime() + Number(expiresIn)); - const processedEmails = emails ? emails.split(",").map((e) => e.trim()) : undefined; + const processedEmails = emails ? emails.split(",").map((e) => e.trim()) : undefined; - const { id } = await createSharedSecret.mutateAsync({ - name, - password, - secretValue: secret, - expiresAt, - expiresAfterViews: shouldLimitView ? Number(viewLimit) : undefined, - accessType, - emails: processedEmails + const { id } = await createSharedSecret.mutateAsync({ + name, + password, + secretValue: secret, + expiresAt, + expiresAfterViews: shouldLimitView ? Number(viewLimit) : undefined, + accessType, + emails: processedEmails + }); + + if (processedEmails && processedEmails.length > 0) { + setSecretLink(""); + createNotification({ + text: `Shared secret link emailed to ${processedEmails.length} user(s).`, + type: "success" }); - - if (processedEmails && processedEmails.length > 0) { - setSecretLink(""); - createNotification({ - text: `Shared secret link emailed to ${processedEmails.length} user(s).`, - type: "success" - }); - } else { - const link = new URL(`${window.location.origin}/shared/secret/${id}`); - if (subOrganization) { - link.searchParams.set("subOrganization", subOrganization); - } - - setSecretLink(link.toString()); - - navigator.clipboard.writeText(link.toString()); - setCopyTextSecret("secret"); - - createNotification({ - text: "Shared secret link copied to clipboard.", - type: "success" - }); + } else { + const link = new URL(`${window.location.origin}/shared/secret/${id}`); + if (subOrganization) { + link.searchParams.set("subOrganization", subOrganization); } - reset(); - } catch (error) { - console.error(error); + setSecretLink(link.toString()); + + navigator.clipboard.writeText(link.toString()); + setCopyTextSecret("secret"); + createNotification({ - text: "Failed to create a shared secret.", - type: "error" + text: "Shared secret link copied to clipboard.", + type: "success" }); } + + reset(); }; if (secretLink === null) diff --git a/frontend/src/pages/secret-manager/CommitDetailsPage/components/RollbackPreviewTab/RollbackPreviewTab.tsx b/frontend/src/pages/secret-manager/CommitDetailsPage/components/RollbackPreviewTab/RollbackPreviewTab.tsx index 43992d73a..baf2b015b 100644 --- a/frontend/src/pages/secret-manager/CommitDetailsPage/components/RollbackPreviewTab/RollbackPreviewTab.tsx +++ b/frontend/src/pages/secret-manager/CommitDetailsPage/components/RollbackPreviewTab/RollbackPreviewTab.tsx @@ -140,22 +140,15 @@ export const RollbackPreviewTab = (): JSX.Element => { ); const handleRollback = async (): Promise => { - try { - await rollback(message); + await rollback(message); - createNotification({ - type: "success", - text: "Rollback completed successfully" - }); + createNotification({ + type: "success", + text: "Rollback completed successfully" + }); - handlePopUpClose("rollbackConfirm"); - goBackToHistory(); - } catch (error) { - createNotification({ - type: "error", - text: error instanceof Error ? error.message : "Failed to rollback changes" - }); - } + handlePopUpClose("rollbackConfirm"); + goBackToHistory(); }; const folderChanges: FolderChanges[] = rollbackChangesNested || []; diff --git a/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistModal.tsx b/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistModal.tsx index 36f585850..ea6b604dc 100644 --- a/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistModal.tsx +++ b/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistModal.tsx @@ -64,39 +64,32 @@ export const IPAllowlistModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: }, [popUp?.trustedIp?.data]); const onIPAllowlistModalSubmit = async ({ ipAddress, comment }: FormData) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - if (popUp?.trustedIp?.data) { - await updateTrustedIp.mutateAsync({ - projectId: currentProject.id, - trustedIpId: (popUp?.trustedIp?.data as { trustedIpId: string })?.trustedIpId, - ipAddress, - comment, - isActive: true - }); - } else { - await addTrustedIp.mutateAsync({ - projectId: currentProject.id, - ipAddress, - comment, - isActive: true - }); - } - - createNotification({ - text: `Successfully ${popUp?.trustedIp?.data ? "updated" : "added"} trusted IP`, - type: "success" + if (popUp?.trustedIp?.data) { + await updateTrustedIp.mutateAsync({ + projectId: currentProject.id, + trustedIpId: (popUp?.trustedIp?.data as { trustedIpId: string })?.trustedIpId, + ipAddress, + comment, + isActive: true }); - - reset(); - handlePopUpClose("trustedIp"); - } catch { - createNotification({ - text: `Failed to ${popUp?.trustedIp?.data ? "update" : "add"} trusted IP`, - type: "error" + } else { + await addTrustedIp.mutateAsync({ + projectId: currentProject.id, + ipAddress, + comment, + isActive: true }); } + + createNotification({ + text: `Successfully ${popUp?.trustedIp?.data ? "updated" : "added"} trusted IP`, + type: "success" + }); + + reset(); + handlePopUpClose("trustedIp"); }; return ( diff --git a/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistSection.tsx b/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistSection.tsx index 470f09aef..11d6ca8d9 100644 --- a/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistSection.tsx +++ b/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistSection.tsx @@ -29,27 +29,19 @@ export const IPAllowlistSection = () => { ] as const); const onDeleteTrustedIpSubmit = async (trustedIpId: string) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - await mutateAsync({ - projectId: currentProject.id, - trustedIpId - }); + await mutateAsync({ + projectId: currentProject.id, + trustedIpId + }); - createNotification({ - text: "Successfully deleted IP access range", - type: "success" - }); + createNotification({ + text: "Successfully deleted IP access range", + type: "success" + }); - handlePopUpClose("deleteTrustedIp"); - } catch (err) { - console.log(err); - createNotification({ - text: "Failed to delete IP access range", - type: "error" - }); - } + handlePopUpClose("deleteTrustedIp"); }; return ( @@ -105,7 +97,7 @@ export const IPAllowlistSection = () => { handlePopUpToggle("upgradePlan", isOpen)} - text="You can use IP allowlisting if you switch to Infisical's Pro plan." + text="Your current plan does not include access to IP allowlisting. To unlock this feature, please upgrade to Infisical Pro plan." /> ); diff --git a/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistTable.tsx b/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistTable.tsx index 8f3336be8..7fc696be3 100644 --- a/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistTable.tsx +++ b/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistTable.tsx @@ -159,7 +159,7 @@ export const IPAllowlistTable = ({ popUp, handlePopUpOpen, handlePopUpToggle }: handlePopUpToggle("upgradePlan", isOpen)} - text="You can use IP allowlisting if you switch to Infisical's Pro plan." + text="Your current plan does not include access to IP allowlisting. To unlock this feature, please upgrade to Infisical Pro plan." /> ); diff --git a/frontend/src/pages/secret-manager/IntegrationsDetailsByIDPage/IntegrationsDetailsByIDPage.tsx b/frontend/src/pages/secret-manager/IntegrationsDetailsByIDPage/IntegrationsDetailsByIDPage.tsx index 3b5600665..9043c35ef 100644 --- a/frontend/src/pages/secret-manager/IntegrationsDetailsByIDPage/IntegrationsDetailsByIDPage.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsDetailsByIDPage/IntegrationsDetailsByIDPage.tsx @@ -53,28 +53,20 @@ export const IntegrationDetailsByIDPage = () => { const navigate = useNavigate(); const handleIntegrationDelete = async (shouldDeleteIntegrationSecrets: boolean) => { - try { - await deleteIntegration({ - id: integrationId, - workspaceId: currentProject.id, - shouldDeleteIntegrationSecrets - }); + await deleteIntegration({ + id: integrationId, + workspaceId: currentProject.id, + shouldDeleteIntegrationSecrets + }); - createNotification({ - type: "success", - text: "Deleted integration" - }); + createNotification({ + type: "success", + text: "Deleted integration" + }); - await navigate({ - to: `/${ProjectType.SecretManager}/${projectId}/integrations` - }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to delete integration" - }); - } + await navigate({ + to: `/${ProjectType.SecretManager}/${projectId}/integrations` + }); }; const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/NativeIntegrationsTab.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/NativeIntegrationsTab.tsx index 97ebfbee5..db4c6356e 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/NativeIntegrationsTab.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/NativeIntegrationsTab.tsx @@ -108,43 +108,27 @@ export const NativeIntegrationsTab = () => { shouldDeleteIntegrationSecrets: boolean, cb: () => void ) => { - try { - await deleteIntegration({ id: integrationId, workspaceId, shouldDeleteIntegrationSecrets }); - if (cb) cb(); - createNotification({ - type: "success", - text: "Deleted integration" - }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to delete integration" - }); - } + await deleteIntegration({ id: integrationId, workspaceId, shouldDeleteIntegrationSecrets }); + if (cb) cb(); + createNotification({ + type: "success", + text: "Deleted integration" + }); }; const handleIntegrationAuthRevoke = async (provider: string, cb?: () => void) => { const integrationAuthForProvider = integrationAuths?.[provider]; if (!integrationAuthForProvider) return; - try { - await deleteIntegrationAuths({ - integration: provider, - workspaceId - }); - if (cb) cb(); - createNotification({ - type: "success", - text: "Revoked provider authentication" - }); - } catch (err) { - console.error(err); - createNotification({ - type: "error", - text: "Failed to revoke provider authentication" - }); - } + await deleteIntegrationAuths({ + integration: provider, + workspaceId + }); + if (cb) cb(); + createNotification({ + type: "success", + text: "Revoked provider authentication" + }); }; const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/ChefSyncDestinationCol.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/ChefSyncDestinationCol.tsx new file mode 100644 index 000000000..cd1b2742e --- /dev/null +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/ChefSyncDestinationCol.tsx @@ -0,0 +1,14 @@ +import { TChefSync } from "@app/hooks/api/secretSyncs/types/chef-sync"; + +import { getSecretSyncDestinationColValues } from "../helpers"; +import { SecretSyncTableCell } from "../SecretSyncTableCell"; + +type Props = { + secretSync: TChefSync; +}; + +export const ChefSyncDestinationCol = ({ secretSync }: Props) => { + const { primaryText, secondaryText } = getSecretSyncDestinationColValues(secretSync); + + return ; +}; diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/SecretSyncDestinationCol.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/SecretSyncDestinationCol.tsx index 4ec6d15ff..985794144 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/SecretSyncDestinationCol.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/SecretSyncDestinationCol.tsx @@ -9,6 +9,7 @@ import { AzureKeyVaultDestinationSyncCol } from "./AzureKeyVaultDestinationSyncC import { BitbucketSyncDestinationCol } from "./BitbucketSyncDestinationCol"; import { CamundaSyncDestinationCol } from "./CamundaSyncDestinationCol"; import { ChecklySyncDestinationCol } from "./ChecklySyncDestinationCol"; +import { ChefSyncDestinationCol } from "./ChefSyncDestinationCol"; import { CloudflarePagesSyncDestinationCol } from "./CloudflarePagesSyncDestinationCol"; import { CloudflareWorkersSyncDestinationCol } from "./CloudflareWorkersSyncDestinationCol"; import { DatabricksSyncDestinationCol } from "./DatabricksSyncDestinationCol"; @@ -103,6 +104,8 @@ export const SecretSyncDestinationCol = ({ secretSync }: Props) => { return ; case SecretSync.LaravelForge: return ; + case SecretSync.Chef: + return ; default: throw new Error( `Unhandled Secret Sync Destination Col: ${(secretSync as TSecretSync).destination}` diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx index bdf92ef7f..93a22feb8 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx @@ -235,46 +235,32 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => { const isAutoSyncEnabled = !secretSync.isAutoSyncEnabled; - try { - await updateSync.mutateAsync({ - syncId: secretSync.id, - destination: secretSync.destination, - isAutoSyncEnabled, - projectId: secretSync.projectId - }); + await updateSync.mutateAsync({ + syncId: secretSync.id, + destination: secretSync.destination, + isAutoSyncEnabled, + projectId: secretSync.projectId + }); - createNotification({ - text: `Successfully ${isAutoSyncEnabled ? "enabled" : "disabled"} auto-sync for ${destinationName} Sync`, - type: "success" - }); - } catch { - createNotification({ - text: `Failed to ${isAutoSyncEnabled ? "enable" : "disable"} auto-sync for ${destinationName} Sync`, - type: "error" - }); - } + createNotification({ + text: `Successfully ${isAutoSyncEnabled ? "enabled" : "disabled"} auto-sync for ${destinationName} Sync`, + type: "success" + }); }; const handleTriggerSync = async (secretSync: TSecretSync) => { const destinationName = SECRET_SYNC_MAP[secretSync.destination].name; - try { - await triggerSync.mutateAsync({ - syncId: secretSync.id, - destination: secretSync.destination, - projectId: secretSync.projectId - }); + await triggerSync.mutateAsync({ + syncId: secretSync.id, + destination: secretSync.destination, + projectId: secretSync.projectId + }); - createNotification({ - text: `Successfully triggered ${destinationName} Sync`, - type: "success" - }); - } catch { - createNotification({ - text: `Failed to trigger ${destinationName} Sync`, - type: "error" - }); - } + createNotification({ + text: `Successfully triggered ${destinationName} Sync`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/helpers/index.ts b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/helpers/index.ts index 48422c41e..de0aad100 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/helpers/index.ts +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/helpers/index.ts @@ -202,6 +202,10 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => { primaryText = destinationConfig.siteName || destinationConfig.siteId; secondaryText = destinationConfig.orgName || destinationConfig.orgSlug; break; + case SecretSync.Chef: + primaryText = destinationConfig.dataBagName; + secondaryText = destinationConfig.dataBagItemName; + break; default: throw new Error(`Unhandled Destination Col Values ${destination}`); } diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx index 2dc370321..591b7cf3d 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx @@ -488,55 +488,47 @@ export const OverviewPage = () => { }; const handleSecretCreate = async (env: string, key: string, value: string) => { - try { - // create folder if not existing - if (secretPath !== "/") { - // /hello/world -> [hello","world"] - const pathSegment = secretPath.split("/").filter(Boolean); - const parentPath = `/${pathSegment.slice(0, -1).join("/")}`; - const folderName = pathSegment.at(-1); - const canCreateFolder = permission.can( - ProjectPermissionActions.Create, - subject(ProjectPermissionSub.SecretFolders, { - environment: env, - secretPath: parentPath - }) - ); - if (folderName && parentPath && canCreateFolder) { - await getOrCreateFolder({ - projectId, - path: parentPath, - environment: env, - name: folderName - }); - } + // create folder if not existing + if (secretPath !== "/") { + // /hello/world -> [hello","world"] + const pathSegment = secretPath.split("/").filter(Boolean); + const parentPath = `/${pathSegment.slice(0, -1).join("/")}`; + const folderName = pathSegment.at(-1); + const canCreateFolder = permission.can( + ProjectPermissionActions.Create, + subject(ProjectPermissionSub.SecretFolders, { + environment: env, + secretPath: parentPath + }) + ); + if (folderName && parentPath && canCreateFolder) { + await getOrCreateFolder({ + projectId, + path: parentPath, + environment: env, + name: folderName + }); } - const result = await createSecretV3({ - environment: env, - projectId, - secretPath, - secretKey: key, - secretValue: value, - secretComment: "", - type: SecretType.Shared - }); + } + const result = await createSecretV3({ + environment: env, + projectId, + secretPath, + secretKey: key, + secretValue: value, + secretComment: "", + type: SecretType.Shared + }); - if ("approval" in result) { - createNotification({ - type: "info", - text: "Requested change has been sent for review" - }); - } else { - createNotification({ - type: "success", - text: "Successfully created secret" - }); - } - } catch (error) { - console.log(error); + if ("approval" in result) { createNotification({ - type: "error", - text: "Failed to create secret" + type: "info", + text: "Requested change has been sent for review" + }); + } else { + createNotification({ + type: "success", + text: "Successfully created secret" }); } }; @@ -565,63 +557,47 @@ export const OverviewPage = () => { secretValue = undefined; } - try { - const result = await updateSecretV3({ - environment: env, - projectId, - secretPath, - secretKey: key, - secretValue, - type - }); + const result = await updateSecretV3({ + environment: env, + projectId, + secretPath, + secretKey: key, + secretValue, + type + }); - if ("approval" in result) { - createNotification({ - type: "info", - text: "Requested change has been sent for review" - }); - } else { - createNotification({ - type: "success", - text: "Successfully updated secret" - }); - } - } catch (error) { - console.log(error); + if ("approval" in result) { createNotification({ - type: "error", - text: "Failed to update secret" + type: "info", + text: "Requested change has been sent for review" + }); + } else { + createNotification({ + type: "success", + text: "Successfully updated secret" }); } }; const handleSecretDelete = async (env: string, key: string, secretId?: string) => { - try { - const result = await deleteSecretV3({ - environment: env, - projectId, - secretPath, - secretKey: key, - secretId, - type: SecretType.Shared - }); + const result = await deleteSecretV3({ + environment: env, + projectId, + secretPath, + secretKey: key, + secretId, + type: SecretType.Shared + }); - if ("approval" in result) { - createNotification({ - type: "info", - text: "Requested change has been sent for review" - }); - } else { - createNotification({ - type: "success", - text: "Successfully deleted secret" - }); - } - } catch (error) { - console.log(error); + if ("approval" in result) { createNotification({ - type: "error", - text: "Failed to delete secret" + type: "info", + text: "Requested change has been sent for review" + }); + } else { + createNotification({ + type: "success", + text: "Successfully deleted secret" }); } }; @@ -1180,8 +1156,7 @@ export const OverviewPage = () => { } handlePopUpOpen("upgradePlan", { isEnterpriseFeature: true, - description: - "You can add dynamic secrets if you switch to Infisical's Enterprise plan." + text: "Adding dynamic secrets can be unlocked if you upgrade to Infisical Enterprise plan." }); }} isDisabled={userAvailableDynamicSecretEnvs.length === 0} @@ -1206,8 +1181,7 @@ export const OverviewPage = () => { return; } handlePopUpOpen("upgradePlan", { - description: - "You can add secret rotations if you switch to Infisical's Pro plan." + text: "Adding secret rotations can be unlocked if you upgrade to Infisical Pro plan." }); }} isDisabled={userAvailableSecretRotationEnvs.length === 0} @@ -1693,7 +1667,7 @@ export const OverviewPage = () => { isOpen={popUp.upgradePlan.isOpen} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} - text={popUp.upgradePlan.data?.description} + text={popUp.upgradePlan.data?.text} /> )} { const slugSchema = z.string().trim().toLowerCase().min(1); const createNewTag = async (slug: string) => { // TODO: Replace with slugSchema generic - try { - const parsedSlug = slugSchema.parse(slug); - await createWsTag.mutateAsync({ - projectId, - tagSlug: parsedSlug, - tagColor: "" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to create new tag" - }); - } + const parsedSlug = slugSchema.parse(slug); + await createWsTag.mutateAsync({ + projectId, + tagSlug: parsedSlug, + tagColor: "" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/OverviewPage/components/SecretV2MigrationSection/SecretV2MigrationSection.tsx b/frontend/src/pages/secret-manager/OverviewPage/components/SecretV2MigrationSection/SecretV2MigrationSection.tsx index 98feebb7e..79a658717 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/components/SecretV2MigrationSection/SecretV2MigrationSection.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/components/SecretV2MigrationSection/SecretV2MigrationSection.tsx @@ -65,20 +65,13 @@ export const SecretV2MigrationSection = () => { const didProjectUpgradeFailed = workspaceDetails?.upgradeStatus === ProjectUpgradeStatus.Failed; const handleMigrationSecretV2 = async () => { - try { - handlePopUpToggle("migrationInfo"); - await migrateProjectToV3.mutateAsync({ projectId: currentProject?.id || "" }); - refetch(); - createNotification({ - text: "Project upgrade started", - type: "success" - }); - } catch { - createNotification({ - text: "Failed to upgrade project", - type: "error" - }); - } + handlePopUpToggle("migrationInfo"); + await migrateProjectToV3.mutateAsync({ projectId: currentProject?.id || "" }); + refetch(); + createNotification({ + text: "Project upgrade started", + type: "success" + }); }; const isAdmin = hasProjectRole(ProjectMembershipRole.Admin); diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/AccessApprovalRequest.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/AccessApprovalRequest.tsx index dcb5c453b..24e43224a 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/AccessApprovalRequest.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/AccessApprovalRequest.tsx @@ -312,8 +312,7 @@ export const AccessApprovalRequest = ({ onClick={() => { if (subscription && !subscription?.secretApproval) { handlePopUpOpen("upgradePlan", { - description: - "You can use request access feature if you switch to Infisical's Pro plan." + text: "Access requests feature can be unlocked if you upgrade to Infisical Pro plan." }); return; } @@ -573,7 +572,7 @@ export const AccessApprovalRequest = ({ )} handlePopUpClose("upgradePlan")} /> diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/EditAccessRequestModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/EditAccessRequestModal.tsx index 430ba080e..922a5b8b1 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/EditAccessRequestModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/EditAccessRequestModal.tsx @@ -65,28 +65,20 @@ const Content = ({ accessRequest, onComplete, projectSlug }: ContentProps) => { }); const onSubmit = async (form: FormData) => { - try { - const request = await update.mutateAsync({ - requestId: accessRequest.id, - projectSlug, - ...form - }); - await queryClient.refetchQueries({ - queryKey: accessApprovalKeys.getAccessApprovalPolicies(projectSlug) - }); + const request = await update.mutateAsync({ + requestId: accessRequest.id, + projectSlug, + ...form + }); + await queryClient.refetchQueries({ + queryKey: accessApprovalKeys.getAccessApprovalPolicies(projectSlug) + }); - createNotification({ - type: "success", - text: "Access request updated successfully." - }); - onComplete(request); - } catch (e) { - console.error(e); - createNotification({ - type: "error", - text: "Failed to update access request" - }); - } + createNotification({ + type: "success", + text: "Access request updated successfully." + }); + onComplete(request); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx index 6a04225b4..a86ca8ebb 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx @@ -178,8 +178,7 @@ export const ReviewAccessRequestModal = ({ text: `The request has been ${status}`, type: status === "approved" ? "success" : "info" }); - } catch (error) { - console.error(error); + } catch { setIsLoading(null); return; } 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 a73786582..c7090eee9 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/ApprovalPolicyList.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/ApprovalPolicyList.tsx @@ -491,7 +491,7 @@ export const ApprovalPolicyList = ({ projectId }: IProps) => { handlePopUpToggle("upgradePlan", isOpen)} - text="You can add secret approval policy if you switch to Infisical's Pro plan." + text="Adding secret approval policies can be unlocked if you upgrade to Infisical Pro plan." /> ); 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 30998aff4..67385a58b 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 @@ -237,48 +237,40 @@ const Form = ({ }: TFormSchema) => { if (!projectId) return; - try { - const bypassers = [...userBypassers, ...groupBypassers]; + const bypassers = [...userBypassers, ...groupBypassers]; - if (data.policyType === PolicyType.ChangePolicy) { - await createSecretApprovalPolicy({ - ...data, - approvers: [...userApprovers, ...groupApprovers], - bypassers: bypassers.length > 0 ? bypassers : undefined, - environments: environments.map((env) => env.slug), - projectId: currentProject?.id || "" - }); - } else { - await createAccessApprovalPolicy({ - ...data, - approvers: sequenceApprovers?.flatMap((approvers, index) => - approvers.user - .map( - (el) => ({ ...el, sequence: index + 1 }) as Omit - ) - .concat(approvers.group.map((el) => ({ ...el, sequence: index + 1 }))) - ), - approvalsRequired: sequenceApprovers?.map((el, index) => ({ - stepNumber: index + 1, - numberOfApprovals: el.approvals - })), - bypassers: bypassers.length > 0 ? bypassers : undefined, - environments: environments.map((env) => env.slug), - projectSlug - }); - } - createNotification({ - type: "success", - text: "Successfully created policy" + if (data.policyType === PolicyType.ChangePolicy) { + await createSecretApprovalPolicy({ + ...data, + approvers: [...userApprovers, ...groupApprovers], + bypassers: bypassers.length > 0 ? bypassers : undefined, + environments: environments.map((env) => env.slug), + projectId: currentProject?.id || "" }); - onToggle(false); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to create policy" + } else { + await createAccessApprovalPolicy({ + ...data, + approvers: sequenceApprovers?.flatMap((approvers, index) => + approvers.user + .map( + (el) => ({ ...el, sequence: index + 1 }) as Omit + ) + .concat(approvers.group.map((el) => ({ ...el, sequence: index + 1 }))) + ), + approvalsRequired: sequenceApprovers?.map((el, index) => ({ + stepNumber: index + 1, + numberOfApprovals: el.approvals + })), + bypassers: bypassers.length > 0 ? bypassers : undefined, + environments: environments.map((env) => env.slug), + projectSlug }); } + createNotification({ + type: "success", + text: "Successfully created policy" + }); + onToggle(false); }; const handleUpdatePolicy = async ({ @@ -293,50 +285,42 @@ const Form = ({ if (!projectId || !projectSlug) return; if (!editValues?.id) return; - try { - const bypassers = [...userBypassers, ...groupBypassers]; + const bypassers = [...userBypassers, ...groupBypassers]; - if (data.policyType === PolicyType.ChangePolicy) { - await updateSecretApprovalPolicy({ - id: editValues?.id, - ...data, - approvers: [...userApprovers, ...groupApprovers], - bypassers: bypassers.length > 0 ? bypassers : undefined, - projectId: currentProject?.id || "", - environments: environments.map((env) => env.slug) - }); - } else { - await updateAccessApprovalPolicy({ - id: editValues?.id, - ...data, - approvers: sequenceApprovers?.flatMap((approvers, index) => - approvers.user - .map( - (el) => ({ ...el, sequence: index + 1 }) as Omit - ) - .concat(approvers.group.map((el) => ({ ...el, sequence: index + 1 }))) - ), - approvalsRequired: sequenceApprovers?.map((el, index) => ({ - stepNumber: index + 1, - numberOfApprovals: el.approvals - })), - bypassers: bypassers.length > 0 ? bypassers : undefined, - environments: environments.map((env) => env.slug), - projectSlug - }); - } - createNotification({ - type: "success", - text: "Successfully updated policy" + if (data.policyType === PolicyType.ChangePolicy) { + await updateSecretApprovalPolicy({ + id: editValues?.id, + ...data, + approvers: [...userApprovers, ...groupApprovers], + bypassers: bypassers.length > 0 ? bypassers : undefined, + projectId: currentProject?.id || "", + environments: environments.map((env) => env.slug) }); - onToggle(false); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "failed to update policy" + } else { + await updateAccessApprovalPolicy({ + id: editValues?.id, + ...data, + approvers: sequenceApprovers?.flatMap((approvers, index) => + approvers.user + .map( + (el) => ({ ...el, sequence: index + 1 }) as Omit + ) + .concat(approvers.group.map((el) => ({ ...el, sequence: index + 1 }))) + ), + approvalsRequired: sequenceApprovers?.map((el, index) => ({ + stepNumber: index + 1, + numberOfApprovals: el.approvals + })), + bypassers: bypassers.length > 0 ? bypassers : undefined, + environments: environments.map((env) => env.slug), + projectSlug }); } + createNotification({ + type: "success", + text: "Successfully updated policy" + }); + onToggle(false); }; const handleFormSubmit = async (data: TFormSchema) => { diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/RemoveApprovalPolicyModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/RemoveApprovalPolicyModal.tsx index db2406335..96866ad31 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/RemoveApprovalPolicyModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/RemoveApprovalPolicyModal.tsx @@ -32,29 +32,22 @@ export const RemoveApprovalPolicyModal = ({ const { currentProject } = useProject(); const handleDeletePolicy = async () => { - try { - if (policyType === PolicyType.ChangePolicy) { - await deleteSecretApprovalPolicy({ - projectId: currentProject.id, - id: policyId - }); - } else { - await deleteAccessApprovalPolicy({ - projectSlug: currentProject.slug, - id: policyId - }); - } - createNotification({ - type: "success", - text: "Successfully deleted policy" + if (policyType === PolicyType.ChangePolicy) { + await deleteSecretApprovalPolicy({ + projectId: currentProject.id, + id: policyId }); - onOpenChange(false); - } catch { - createNotification({ - type: "error", - text: "Failed to delete policy" + } else { + await deleteAccessApprovalPolicy({ + projectSlug: currentProject.slug, + id: policyId }); } + createNotification({ + type: "success", + text: "Successfully deleted policy" + }); + onOpenChange(false); }; const deleteSecretApprovalData = useGetSecretApprovalRequestCount({ diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/SecretApprovalRequest.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/SecretApprovalRequest.tsx index 0b8e6f03f..c730e4622 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/SecretApprovalRequest.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/SecretApprovalRequest.tsx @@ -40,7 +40,7 @@ import { PreferenceKey, setUserTablePreference } from "@app/helpers/userTablePreferences"; -import { usePagination } from "@app/hooks"; +import { usePagination, useResetPageHelper } from "@app/hooks"; import { useGetSecretApprovalRequestCount, useGetSecretApprovalRequests, @@ -99,6 +99,12 @@ export const SecretApprovalRequest = () => { const totalApprovalCount = data?.totalCount ?? 0; const secretApprovalRequests = data?.approvals ?? []; + useResetPageHelper({ + totalCount: totalApprovalCount, + offset, + setPage + }); + const { data: secretApprovalRequestCount, isSuccess: isSecretApprovalReqCountSuccess } = useGetSecretApprovalRequestCount({ projectId }); const { user: userSession } = useUser(); @@ -107,7 +113,7 @@ export const SecretApprovalRequest = () => { }); const { permission } = useProjectPermission(); - const { data: members } = useGetWorkspaceUsers(projectId); + const { data: members } = useGetWorkspaceUsers(projectId, true); const isSecretApprovalScreen = Boolean(selectedApprovalId); const { requestId } = search; diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestAction.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestAction.tsx index c2eb4c61f..709e312bb 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestAction.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestAction.tsx @@ -59,43 +59,27 @@ export const SecretApprovalRequestAction = ({ }; const handleSecretApprovalRequestMerge = async () => { - try { - await performSecretApprovalMerge({ - id: approvalRequestId, - projectId, - bypassReason: byPassApproval ? bypassReason : undefined - }); - createNotification({ - type: "success", - text: "Successfully merged the request" - }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to update the request status" - }); - } + await performSecretApprovalMerge({ + id: approvalRequestId, + projectId, + bypassReason: byPassApproval ? bypassReason : undefined + }); + createNotification({ + type: "success", + text: "Successfully merged the request" + }); }; const handleSecretApprovalStatusChange = async (reqState: "open" | "close") => { - try { - await updateSecretStatusChange({ - id: approvalRequestId, - status: reqState, - projectId - }); - createNotification({ - type: "success", - text: "Successfully updated the request" - }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to update the request status" - }); - } + await updateSecretStatusChange({ + id: approvalRequestId, + status: reqState, + projectId + }); + createNotification({ + type: "success", + text: "Successfully updated the request" + }); }; const isSoftEnforcement = enforcementLevel === EnforcementLevel.Soft; diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChangeItem.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChangeItem.tsx index c54ee8502..cbab034ed 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChangeItem.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChangeItem.tsx @@ -221,9 +221,7 @@ export const SecretApprovalRequestChangeItem = ({
Multi-line Encoding
- {secretVersion?.skipMultilineEncoding?.toString() || ( - - - )}{" "} + {secretVersion?.skipMultilineEncoding?.toString() || "false"}
@@ -366,9 +364,8 @@ export const SecretApprovalRequestChangeItem = ({
Multi-line Encoding
{newVersion?.skipMultilineEncoding?.toString() ?? - secretVersion?.skipMultilineEncoding?.toString() ?? ( - - - )}{" "} + secretVersion?.skipMultilineEncoding?.toString() ?? + "false"}
diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx index 24d306314..bd4a04fa3 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx @@ -174,23 +174,15 @@ export const SecretApprovalRequestChanges = ({ approvalRequestId, onGoBack }: Pr ); const handleSecretApprovalStatusUpdate = async (status: ApprovalStatus, comment: string) => { - try { - await updateSecretApprovalRequestStatus({ - id: approvalRequestId, - status, - comment - }); - createNotification({ - type: "success", - text: `Successfully ${status} the request` - }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to update the request status" - }); - } + await updateSecretApprovalRequestStatus({ + id: approvalRequestId, + status, + comment + }); + createNotification({ + type: "success", + text: `Successfully ${status} the request` + }); handlePopUpToggle("reviewChanges", false); reset({ diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx index f1a7ffba8..a2ee814f8 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx @@ -338,45 +338,37 @@ const Page = () => { const isProtectedBranch = Boolean(boardPolicy); const handleCreateCommit = async (changes: PendingChanges, message: string) => { - try { - await createCommit({ - projectId, - environment, - secretPath, - pendingChanges: changes, - message - }); + await createCommit({ + projectId, + environment, + secretPath, + pendingChanges: changes, + message + }); - if (!isProtectedBranch) { - pendingChanges.secrets.forEach((secret) => { - if (secret.type === "update" && secret.secretValue !== undefined) { - queryClient.setQueryData( - dashboardKeys.getSecretValue({ - projectId, - environment, - secretPath, - secretKey: secret.newSecretName ?? secret.secretKey, - isOverride: false - }), - { value: secret.secretValue } - ); - } - }); - } - - createNotification({ - text: isProtectedBranch - ? "Requested changes have been sent for review" - : "Changes saved successfully", - type: "success" + if (!isProtectedBranch) { + pendingChanges.secrets.forEach((secret) => { + if (secret.type === "update" && secret.secretValue !== undefined) { + queryClient.setQueryData( + dashboardKeys.getSecretValue({ + projectId, + environment, + secretPath, + secretKey: secret.newSecretName ?? secret.secretKey, + isOverride: false + }), + { value: secret.secretValue } + ); + } }); - } catch (error) { - createNotification({ - text: "Failed to save changes", - type: "error" - }); - console.error(error); } + + createNotification({ + text: isProtectedBranch + ? "Requested changes have been sent for review" + : "Changes saved successfully", + type: "success" + }); }; const { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx index 0b8210f0d..727903999 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx @@ -209,48 +209,40 @@ export const ActionBar = ({ const isOrgAdmin = hasOrgRole(OrgMembershipRole.Admin); const handleFolderCreate = async (folderName: string, description: string | null) => { - try { - if (isBatchMode) { - const folderId = `${folderName}`; - const pendingFolderCreate: PendingFolderCreate = { - id: folderId, - resourceType: "folder", - type: PendingAction.Create, - folderName, - description: description || undefined, - parentPath: secretPath, - timestamp: Date.now() - }; + if (isBatchMode) { + const folderId = `${folderName}`; + const pendingFolderCreate: PendingFolderCreate = { + id: folderId, + resourceType: "folder", + type: PendingAction.Create, + folderName, + description: description || undefined, + parentPath: secretPath, + timestamp: Date.now() + }; - addPendingChange(pendingFolderCreate, { - projectId, - environment, - secretPath - }); - - handlePopUpClose("addFolder"); - return; - } - - await createFolder({ - name: folderName, - path: secretPath, - environment, + addPendingChange(pendingFolderCreate, { projectId, - description + environment, + secretPath }); + handlePopUpClose("addFolder"); - createNotification({ - type: "success", - text: "Successfully created folder" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to create folder" - }); + return; } + + await createFolder({ + name: folderName, + path: secretPath, + environment, + projectId, + description + }); + handlePopUpClose("addFolder"); + createNotification({ + type: "success", + text: "Successfully created folder" + }); }; const handleSecretDownload = async () => { @@ -323,26 +315,18 @@ export const ActionBar = ({ const handleSecretBulkDelete = async () => { const bulkDeletedSecrets = Object.values(selectedSecrets); - try { - await deleteBatchSecretV3({ - secretPath, - projectId, - environment, - secrets: bulkDeletedSecrets.map(({ key }) => ({ secretKey: key, type: SecretType.Shared })) - }); - resetSelectedSecret(); - handlePopUpClose("bulkDeleteSecrets"); - createNotification({ - type: "success", - text: "Successfully deleted secrets" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to delete secrets" - }); - } + await deleteBatchSecretV3({ + secretPath, + projectId, + environment, + secrets: bulkDeletedSecrets.map(({ key }) => ({ secretKey: key, type: SecretType.Shared })) + }); + resetSelectedSecret(); + handlePopUpClose("bulkDeleteSecrets"); + createNotification({ + type: "success", + text: "Successfully deleted secrets" + }); }; const handleSecretsMove = async ({ @@ -678,35 +662,23 @@ export const ActionBar = ({ }; const handleVaultImport = async (vaultPath: string, namespace: string) => { - try { - const result = await importVaultSecrets({ - projectId, - environment, - secretPath, - vaultNamespace: namespace, - vaultSecretPath: vaultPath - }); - - if (result.status === VaultImportStatus.ApprovalRequired) { - createNotification({ - type: "info", - text: "Secret change request created successfully. Awaiting approval." - }); - } else { - createNotification({ - type: "success", - text: "Successfully imported secrets from HashiCorp Vault" - }); - } - } catch (err) { - console.error("Vault import error:", err); - const error = err as AxiosError<{ message?: string }>; - const errorMessage = - error.response?.data?.message || "Failed to import secrets from Vault. Please try again."; + const result = await importVaultSecrets({ + projectId, + environment, + secretPath, + vaultNamespace: namespace, + vaultSecretPath: vaultPath + }); + if (result.status === VaultImportStatus.ApprovalRequired) { createNotification({ - type: "error", - text: errorMessage + type: "info", + text: "Secret change request created successfully. Awaiting approval." + }); + } else { + createNotification({ + type: "success", + text: "Successfully imported secrets from HashiCorp Vault" }); } }; @@ -912,7 +884,7 @@ export const ActionBar = ({ } handlePopUpOpen("upgradePlan", { - feature: "PIT Recovery" + featureName: "PIT Recovery" }); }} leftIcon={} @@ -1028,7 +1000,7 @@ export const ActionBar = ({ return; } handlePopUpOpen("upgradePlan", { - feature: "Dynamic Secrets", + featureName: "Dynamic Secrets", isEnterpriseFeature: true }); }} @@ -1058,7 +1030,7 @@ export const ActionBar = ({ return; } handlePopUpOpen("upgradePlan", { - feature: "Secret Rotation" + featureName: "Secret Rotation" }); }} variant="outline_bg" @@ -1235,7 +1207,7 @@ export const ActionBar = ({ projectId={projectId} onUpgradePlan={() => handlePopUpOpen("upgradePlan", { - feature: "Secret Imports" + featureName: "Secret Imports" }) } isOpen={popUp.addSecretImport.isOpen} @@ -1300,7 +1272,7 @@ export const ActionBar = ({ isOpen={popUp.upgradePlan.isOpen} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} - text={`You can use ${popUp.upgradePlan.data?.feature} if you switch to Infisical's ${popUp.upgradePlan.data?.isEnterpriseFeature ? "Enterprise" : "Pro"} plan.`} + text={`Your current plan does not include access to ${popUp.upgradePlan.data?.featureName}. To unlock this feature, please upgrade to Infisical ${popUp.upgradePlan.data?.isEnterpriseFeature ? "Enterprise" : "Pro"} plan.`} /> )} { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - selectedUsers.map(async (user: { id: string; name: string; email: string }) => { - await createDynamicSecret.mutateAsync({ - provider: { - type: DynamicSecretProviders.AzureEntraId, - inputs: { - userId: user.id, - tenantId: provider.tenantId, - email: user.email, - applicationId: provider.applicationId, - clientSecret: provider.clientSecret - } - }, - maxTTL, - name: `${name}-${user.name}`, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug - }); + selectedUsers.map(async (user: { id: string; name: string; email: string }) => { + await createDynamicSecret.mutateAsync({ + provider: { + type: DynamicSecretProviders.AzureEntraId, + inputs: { + userId: user.id, + tenantId: provider.tenantId, + email: user.email, + applicationId: provider.applicationId, + clientSecret: provider.clientSecret + } + }, + maxTTL, + name: `${name}-${user.name}`, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureSqlDatabaseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureSqlDatabaseInputForm.tsx index aece11f38..954aed3ba 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureSqlDatabaseInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureSqlDatabaseInputForm.tsx @@ -5,7 +5,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { Accordion, @@ -171,29 +170,22 @@ export const AzureSqlDatabaseInputForm = ({ if (createDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await createDynamicSecret.mutateAsync({ - provider: { - type: DynamicSecretProviders.AzureSqlDatabase, - inputs: { ...provider, masterDatabase: "master" } - }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - metadata, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { + type: DynamicSecretProviders.AzureSqlDatabase, + inputs: { ...provider, masterDatabase: "master" } + }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + metadata, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx index 15f9387df..dd0c3e879 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx @@ -4,7 +4,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Accordion, AccordionContent, @@ -112,25 +111,18 @@ export const CassandraInputForm = ({ if (createDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.Cassandra, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.Cassandra, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CouchbaseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CouchbaseInputForm.tsx index 17bfeddca..894b31c1c 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CouchbaseInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CouchbaseInputForm.tsx @@ -7,7 +7,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Accordion, AccordionContent, @@ -386,25 +385,18 @@ export const CouchbaseInputForm = ({ const { useAdvancedBuckets, ...finalProvider } = transformedProvider; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.Couchbase, inputs: finalProvider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.Couchbase, inputs: finalProvider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx index 1d0b6c268..d6477862b 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx @@ -6,7 +6,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Button, FilterableSelect, @@ -131,25 +130,18 @@ export const ElasticSearchInputForm = ({ // wait till previous request is finished if (createDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.ElasticSearch, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.ElasticSearch, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; const selectedAuthType = watch("provider.auth.type"); diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GcpIamInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GcpIamInputForm.tsx index a208b1973..a0134bfde 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GcpIamInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GcpIamInputForm.tsx @@ -4,7 +4,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Button, FilterableSelect, FormControl, Input } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; @@ -83,28 +82,21 @@ export const GcpIamInputForm = ({ }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - await createDynamicSecret.mutateAsync({ - provider: { - type: DynamicSecretProviders.GcpIam, - inputs: { - ...provider - } - }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { + type: DynamicSecretProviders.GcpIam, + inputs: { + ...provider + } + }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GithubInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GithubInputForm.tsx index 776511ac4..31256f5a1 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GithubInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GithubInputForm.tsx @@ -4,7 +4,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; -import { createNotification } from "@app/components/notifications"; import { Button, FilterableSelect, @@ -71,27 +70,20 @@ export const GithubInputForm = ({ const handleCreateDynamicSecret = async ({ name, provider, environment }: TForm) => { if (createDynamicSecret.isPending) return; - try { - await createDynamicSecret.mutateAsync({ - provider: { - type: DynamicSecretProviders.Github, - inputs: { - ...provider - } - }, - defaultTTL: "1h", // Github is limited to 1 hour tokens - name, - path: secretPath, - projectSlug, - environmentSlug: environment.slug - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { + type: DynamicSecretProviders.Github, + inputs: { + ...provider + } + }, + defaultTTL: "1h", // Github is limited to 1 hour tokens + name, + path: secretPath, + projectSlug, + environmentSlug: environment.slug + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx index d15bcc9b8..a439f1af2 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx @@ -283,33 +283,26 @@ export const KubernetesInputForm = ({ // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - await createDynamicSecret.mutateAsync({ - provider: { - type: DynamicSecretProviders.Kubernetes, - inputs: { - ...provider, - url: provider.url || undefined - } - }, - maxTTL: rest.maxTTL, - name: rest.name, - path: secretPath, - defaultTTL: rest.defaultTTL, - projectSlug, - environmentSlug: rest.environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; + await createDynamicSecret.mutateAsync({ + provider: { + type: DynamicSecretProviders.Kubernetes, + inputs: { + ...provider, + url: provider.url || undefined + } + }, + maxTTL: rest.maxTTL, + name: rest.name, + path: secretPath, + defaultTTL: rest.defaultTTL, + projectSlug, + environmentSlug: rest.environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx index 7b7397625..61273fc41 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx @@ -4,7 +4,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Button, FilterableSelect, @@ -139,25 +138,18 @@ export const LdapInputForm = ({ if (createDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.Ldap, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate, - environmentSlug: environment.slug - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.Ldap, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate, + environmentSlug: environment.slug + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx index 3b0c7bb3b..317febae8 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx @@ -6,7 +6,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Accordion, AccordionContent, @@ -143,25 +142,18 @@ export const MongoAtlasInputForm = ({ if (createDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.MongoAtlas, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.MongoAtlas, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx index ac60a77b0..8099b123c 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx @@ -6,7 +6,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Button, FilterableSelect, @@ -113,32 +112,25 @@ export const MongoDBDatabaseInputForm = ({ if (createDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await createDynamicSecret.mutateAsync({ - provider: { - type: DynamicSecretProviders.MongoDB, - inputs: { - ...provider, - port: provider?.port ? provider.port : undefined, - roles: provider.roles.map((el) => el.roleName) - } - }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { + type: DynamicSecretProviders.MongoDB, + inputs: { + ...provider, + port: provider?.port ? provider.port : undefined, + roles: provider.roles.map((el) => el.roleName) + } + }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx index 7b2e56464..b142682dd 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx @@ -6,7 +6,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Button, FilterableSelect, @@ -121,25 +120,18 @@ export const RabbitMqInputForm = ({ if (createDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.RabbitMq, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.RabbitMq, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; const selectedTags = watch("provider.tags"); diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx index 472d30783..31745ae79 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx @@ -4,7 +4,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Accordion, AccordionContent, @@ -104,26 +103,19 @@ export const RedisInputForm = ({ }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.Redis, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.Redis, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx index 621451fda..42c06ebea 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx @@ -4,7 +4,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Accordion, AccordionContent, @@ -105,26 +104,19 @@ sp_droplogin '{{username}}';` }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.SapAse, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.SapAse, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx index 034afee2c..8984d1f49 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx @@ -4,7 +4,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Accordion, AccordionContent, @@ -105,26 +104,19 @@ DROP USER {{username}};`, }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.SapHana, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate, - environmentSlug: environment.slug - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.SapHana, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate, + environmentSlug: environment.slug + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx index 11357e26a..91f638f39 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx @@ -4,7 +4,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Accordion, AccordionContent, @@ -102,26 +101,19 @@ export const SnowflakeInputForm = ({ }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.Snowflake, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch (err) { - createNotification({ - type: "error", - text: err instanceof Error ? err.message : "Failed to create dynamic secret" - }); - } + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.Snowflake, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx index 0a836fec2..aaf2e43d1 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx @@ -5,7 +5,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { Accordion, @@ -215,26 +214,19 @@ export const SqlDatabaseInputForm = ({ if (createDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.SqlDatabase, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - metadata, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.SqlDatabase, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + metadata, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; const handleDatabaseChange = (type: SqlProviders) => { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx index 7f5693e33..f86ac4017 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx @@ -2,7 +2,6 @@ import { Controller, useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; -import { createNotification } from "@app/components/notifications"; import { Button, FilterableSelect, @@ -100,23 +99,16 @@ export const TotpInputForm = ({ const handleCreateDynamicSecret = async ({ name, provider, environment }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.Totp, inputs: provider }, - maxTTL: "24h", - name, - path: secretPath, - defaultTTL: "1m", - projectSlug, - environmentSlug: environment.slug - }); - onCompleted(); - } catch (err) { - createNotification({ - type: "error", - text: err instanceof Error ? err.message : "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.Totp, inputs: provider }, + maxTTL: "24h", + name, + path: secretPath, + defaultTTL: "1m", + projectSlug, + environmentSlug: environment.slug + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VerticaInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VerticaInputForm.tsx index b48eab850..7bd66a466 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VerticaInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VerticaInputForm.tsx @@ -5,7 +5,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { Accordion, @@ -145,26 +144,19 @@ GRANT CREATE ON SCHEMA public TO {{username}};`, usernameTemplate }: TForm) => { if (createDynamicSecret.isPending) return; - try { - const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.Vertica, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.Vertica, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateSecretImportForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateSecretImportForm.tsx index 68d2d8cd0..73a9d938e 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateSecretImportForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateSecretImportForm.tsx @@ -101,11 +101,6 @@ export const CreateSecretImportForm = ({ text: "You do not have access to the selected environment/path", type: "error" }); - } else { - createNotification({ - type: "error", - text: "Failed to link secrets" - }); } } }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CreateSecretForm/CreateSecretForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CreateSecretForm/CreateSecretForm.tsx index dd0561495..858fcde77 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CreateSecretForm/CreateSecretForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CreateSecretForm/CreateSecretForm.tsx @@ -77,70 +77,55 @@ export const CreateSecretForm = ({ const slugSchema = z.string().trim().toLowerCase().min(1); const createNewTag = async (slug: string) => { // TODO: Replace with slugSchema generic - try { - const parsedSlug = slugSchema.parse(slug); - await createWsTag.mutateAsync({ - projectId, - tagSlug: parsedSlug, - tagColor: "" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to create new tag" - }); - } + const parsedSlug = slugSchema.parse(slug); + await createWsTag.mutateAsync({ + projectId, + tagSlug: parsedSlug, + tagColor: "" + }); }; const handleFormSubmit = async ({ key, value, tags }: TFormSchema) => { - try { - if (isBatchMode) { - const pendingSecretCreate: PendingSecretCreate = { - id: key, - type: PendingAction.Create, - secretKey: key, - secretValue: value || "", - secretComment: "", - tags: tags?.map((el) => ({ id: el.value, slug: el.label })), - timestamp: Date.now(), - resourceType: "secret" - }; - addPendingChange(pendingSecretCreate, { - projectId, - - environment, - secretPath - }); - closePopUp(PopUpNames.CreateSecretForm); - reset(); - return; - } - await createSecretV3({ - environment, - projectId, - secretPath, + if (isBatchMode) { + const pendingSecretCreate: PendingSecretCreate = { + id: key, + type: PendingAction.Create, secretKey: key, secretValue: value || "", secretComment: "", - type: SecretType.Shared, - tagIds: tags?.map((el) => el.value) + tags: tags?.map((el) => ({ id: el.value, slug: el.label })), + timestamp: Date.now(), + resourceType: "secret" + }; + addPendingChange(pendingSecretCreate, { + projectId, + + environment, + secretPath }); closePopUp(PopUpNames.CreateSecretForm); reset(); - - createNotification({ - type: isProtectedBranch ? "info" : "success", - text: isProtectedBranch - ? "Requested changes have been sent for review" - : "Successfully created secret" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to create secret" - }); + return; } + await createSecretV3({ + environment, + projectId, + secretPath, + secretKey: key, + secretValue: value || "", + secretComment: "", + type: SecretType.Shared, + tagIds: tags?.map((el) => el.value) + }); + closePopUp(PopUpNames.CreateSecretForm); + reset(); + + createNotification({ + type: isProtectedBranch ? "info" : "success", + text: isProtectedBranch + ? "Requested changes have been sent for review" + : "Successfully created secret" + }); }; const handlePaste = (e: ClipboardEvent) => { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/CreateDynamicSecretLease.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/CreateDynamicSecretLease.tsx index 1a16e6e4e..99e66e8b7 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/CreateDynamicSecretLease.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/CreateDynamicSecretLease.tsx @@ -441,30 +441,22 @@ export const CreateKubernetesDynamicSecretLease = ({ const handleDynamicSecretLeaseCreate = async ({ ttl, namespace }: TKubernetesForm) => { if (createDynamicSecretLease.isPending) return; - try { - await createDynamicSecretLease.mutateAsync({ - environmentSlug: environment, - projectSlug, - path: secretPath, - ttl, - dynamicSecretName, - config: { - namespace: namespace || undefined - }, - provider - }); + await createDynamicSecretLease.mutateAsync({ + environmentSlug: environment, + projectSlug, + path: secretPath, + ttl, + dynamicSecretName, + config: { + namespace: namespace || undefined + }, + provider + }); - createNotification({ - type: "success", - text: "Successfully leased dynamic secret" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to lease dynamic secret" - }); - } + createNotification({ + type: "success", + text: "Successfully leased dynamic secret" + }); }; const handleLeaseRegeneration = async (data: { ttl?: string }) => { @@ -590,29 +582,21 @@ export const CreateDynamicSecretLease = ({ const handleDynamicSecretLeaseCreate = async ({ ttl }: TForm) => { if (createDynamicSecretLease.isPending) return; - try { - await createDynamicSecretLease.mutateAsync({ - environmentSlug: environment, - projectSlug, - path: secretPath, - ttl, - dynamicSecretName, - provider - }); + await createDynamicSecretLease.mutateAsync({ + environmentSlug: environment, + projectSlug, + path: secretPath, + ttl, + dynamicSecretName, + provider + }); - createNotification({ - type: "success", - text: "Successfully leased dynamic secret" - }); + createNotification({ + type: "success", + text: "Successfully leased dynamic secret" + }); - setIsPreloading.off(); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to lease dynamic secret" - }); - } + setIsPreloading.off(); }; const handleLeaseRegeneration = async (data: { ttl?: string }) => { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/DynamicSecretLease.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/DynamicSecretLease.tsx index 085a06169..1f734de32 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/DynamicSecretLease.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/DynamicSecretLease.tsx @@ -69,31 +69,23 @@ export const DynamicSecretLease = ({ const deleteDynamicSecretLease = useRevokeDynamicSecretLease(); const handleDynamicSecretDeleteLease = async () => { - try { - const { leaseId, isForced } = popUp.deleteSecret.data as { - leaseId: string; - isForced?: boolean; - }; - await deleteDynamicSecretLease.mutateAsync({ - environmentSlug: environment, - projectSlug, - path: secretPath, - dynamicSecretName, - leaseId, - isForced - }); - handlePopUpClose("deleteSecret"); - createNotification({ - type: "success", - text: "Successfully deleted lease" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to delete lease" - }); - } + const { leaseId, isForced } = popUp.deleteSecret.data as { + leaseId: string; + isForced?: boolean; + }; + await deleteDynamicSecretLease.mutateAsync({ + environmentSlug: environment, + projectSlug, + path: secretPath, + dynamicSecretName, + leaseId, + isForced + }); + handlePopUpClose("deleteSecret"); + createNotification({ + type: "success", + text: "Successfully deleted lease" + }); }; const canRenew = !DYNAMIC_SECRETS_WITHOUT_RENEWAL.includes(dynamicSecret.type); diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/DynamicSecretListView.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/DynamicSecretListView.tsx index 1ef140193..d444f8390 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/DynamicSecretListView.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/DynamicSecretListView.tsx @@ -54,29 +54,21 @@ export const DynamicSecretListView = ({ const deleteDynamicSecret = useDeleteDynamicSecret(); const handleDynamicSecretDelete = async () => { - try { - const { name, isForced } = popUp.deleteDynamicSecret.data as TDynamicSecret & { - isForced?: boolean; - }; - await deleteDynamicSecret.mutateAsync({ - environmentSlug: environment, - projectSlug, - path: secretPath, - name, - isForced - }); - handlePopUpClose("deleteDynamicSecret"); - createNotification({ - type: "success", - text: "Successfully deleted dynamic secret" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to delete dynamic secret" - }); - } + const { name, isForced } = popUp.deleteDynamicSecret.data as TDynamicSecret & { + isForced?: boolean; + }; + await deleteDynamicSecret.mutateAsync({ + environmentSlug: environment, + projectSlug, + path: secretPath, + name, + isForced + }); + handlePopUpClose("deleteDynamicSecret"); + createNotification({ + type: "success", + text: "Successfully deleted dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsElastiCacheProviderForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsElastiCacheProviderForm.tsx index 5740e2061..0015c0c0c 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsElastiCacheProviderForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsElastiCacheProviderForm.tsx @@ -101,31 +101,24 @@ export const EditDynamicSecretAwsElastiCacheProviderForm = ({ // wait till previous request is finished if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsIamForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsIamForm.tsx index 555ed9eae..c9c9afb71 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsIamForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsIamForm.tsx @@ -141,31 +141,24 @@ export const EditDynamicSecretAwsIamForm = ({ // wait till previous request is finished if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureEntraIdForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureEntraIdForm.tsx index c9bfc92cc..2799a95ed 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureEntraIdForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureEntraIdForm.tsx @@ -76,30 +76,23 @@ export const EditDynamicSecretAzureEntraIdForm = ({ const handleUpdateDynamicSecret = async ({ maxTTL, defaultTTL, newName, inputs }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - newName: newName === dynamicSecret.name ? undefined : newName, - inputs - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + newName: newName === dynamicSecret.name ? undefined : newName, + inputs + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureSqlDatabaseForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureSqlDatabaseForm.tsx index 77bb7aa1f..fd370d755 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureSqlDatabaseForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureSqlDatabaseForm.tsx @@ -162,33 +162,26 @@ export const EditDynamicSecretAzureSqlDatabaseForm = ({ }: TForm) => { if (updateDynamicSecret.isPending) return; - try { - const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - await updateDynamicSecret.mutateAsync({ - projectSlug, - environmentSlug: environment, - path: secretPath, - name: dynamicSecret.name, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs: inputs ? { ...inputs, masterDatabase: "master" } : undefined, - newName: newName === dynamicSecret.name ? undefined : newName, - metadata, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; + await updateDynamicSecret.mutateAsync({ + projectSlug, + environmentSlug: environment, + path: secretPath, + name: dynamicSecret.name, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs: inputs ? { ...inputs, masterDatabase: "master" } : undefined, + newName: newName === dynamicSecret.name ? undefined : newName, + metadata, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCassandraForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCassandraForm.tsx index 515ee19e3..fe1212330 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCassandraForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCassandraForm.tsx @@ -103,31 +103,24 @@ export const EditDynamicSecretCassandraForm = ({ // wait till previous request is finished if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCouchbaseForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCouchbaseForm.tsx index e69b3460f..de38358f3 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCouchbaseForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCouchbaseForm.tsx @@ -386,37 +386,30 @@ export const EditDynamicSecretCouchbaseForm = ({ })() : transformedInputs; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - defaultTTL, - maxTTL: maxTTL || undefined, - newName: newName === dynamicSecret.name ? undefined : newName, - metadata, - usernameTemplate: - !usernameTemplate || usernameTemplate === "{{randomUsername}}" - ? undefined - : usernameTemplate, - inputs: finalInputs - } - }); + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + defaultTTL, + maxTTL: maxTTL || undefined, + newName: newName === dynamicSecret.name ? undefined : newName, + metadata, + usernameTemplate: + !usernameTemplate || usernameTemplate === "{{randomUsername}}" + ? undefined + : usernameTemplate, + inputs: finalInputs + } + }); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); - onClose(); - } catch (err) { - createNotification({ - type: "error", - text: `Failed to update dynamic secret: ${err}` - }); - } + onClose(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretElasticSearchForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretElasticSearchForm.tsx index 000762bc5..015f394ea 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretElasticSearchForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretElasticSearchForm.tsx @@ -123,31 +123,24 @@ export const EditDynamicSecretElasticSearchForm = ({ // wait till previous request is finished if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; const selectedAuthType = watch("inputs.auth.type"); diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretGcpIamForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretGcpIamForm.tsx index fb313f730..a2755e9c8 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretGcpIamForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretGcpIamForm.tsx @@ -77,30 +77,23 @@ export const EditDynamicSecretGcpIamForm = ({ const handleUpdateDynamicSecret = async ({ inputs, maxTTL, defaultTTL, newName }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretGithubForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretGithubForm.tsx index 434bc9465..b5539d85d 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretGithubForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretGithubForm.tsx @@ -61,28 +61,21 @@ export const EditDynamicSecretGithubForm = ({ const handleUpdateDynamicSecret = async ({ inputs, newName }: TForm) => { if (updateDynamicSecret.isPending) return; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - inputs, - newName: newName === dynamicSecret.name ? undefined : newName - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + inputs, + newName: newName === dynamicSecret.name ? undefined : newName + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretKubernetesForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretKubernetesForm.tsx index 95f40f9f5..e599c2d6a 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretKubernetesForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretKubernetesForm.tsx @@ -189,38 +189,29 @@ export const EditDynamicSecretKubernetesForm = ({ // wait till previous request is finished if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = formData.usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - inputs: { - ...formData.inputs, - url: formData.inputs.url || undefined - }, - newName: formData.newName === dynamicSecret.name ? undefined : formData.newName, - defaultTTL: formData.defaultTTL, - maxTTL: formData.maxTTL, - usernameTemplate: - !formData.usernameTemplate || isDefaultUsernameTemplate - ? null - : formData.usernameTemplate - } - }); + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + inputs: { + ...formData.inputs, + url: formData.inputs.url || undefined + }, + newName: formData.newName === dynamicSecret.name ? undefined : formData.newName, + defaultTTL: formData.defaultTTL, + maxTTL: formData.maxTTL, + usernameTemplate: + !formData.usernameTemplate || isDefaultUsernameTemplate ? null : formData.usernameTemplate + } + }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch (err) { - createNotification({ - type: "error", - text: err instanceof Error ? err.message : "Failed to update dynamic secret" - }); - } + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretLdapForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretLdapForm.tsx index 09d345d3d..e95d89561 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretLdapForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretLdapForm.tsx @@ -119,31 +119,24 @@ export const EditDynamicSecretLdapForm = ({ if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoAtlasForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoAtlasForm.tsx index 5b577192a..905e00660 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoAtlasForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoAtlasForm.tsx @@ -142,31 +142,24 @@ export const EditDynamicSecretMongoAtlasForm = ({ if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoDBForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoDBForm.tsx index 90c31e1f0..d5b63a1cf 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoDBForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoDBForm.tsx @@ -108,36 +108,28 @@ export const EditDynamicSecretMongoDBForm = ({ if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs: { - ...inputs, - port: inputs?.port ? inputs.port : undefined, - roles: inputs?.roles?.map((el) => el.roleName) - }, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate, - newName: newName === dynamicSecret.name ? undefined : newName - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs: { + ...inputs, + port: inputs?.port ? inputs.port : undefined, + roles: inputs?.roles?.map((el) => el.roleName) + }, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate, + newName: newName === dynamicSecret.name ? undefined : newName + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRabbitMqForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRabbitMqForm.tsx index 984e68831..dbf0d90d0 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRabbitMqForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRabbitMqForm.tsx @@ -100,31 +100,24 @@ export const EditDynamicSecretRabbitMqForm = ({ if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; const selectedTags = watch("inputs.tags"); diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRedisProviderForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRedisProviderForm.tsx index bc21a17d7..add8d6042 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRedisProviderForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRedisProviderForm.tsx @@ -102,32 +102,24 @@ export const EditDynamicSecretRedisProviderForm = ({ // wait till previous request is finished if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate, - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate, + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapAseForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapAseForm.tsx index 37dbae5ee..2f55cca2b 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapAseForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapAseForm.tsx @@ -101,31 +101,24 @@ export const EditDynamicSecretSapAseForm = ({ if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapHanaForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapHanaForm.tsx index 430f13e83..d0f6a9d90 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapHanaForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapHanaForm.tsx @@ -101,31 +101,24 @@ export const EditDynamicSecretSapHanaForm = ({ if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSnowflakeForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSnowflakeForm.tsx index 1cbc20cfb..384278494 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSnowflakeForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSnowflakeForm.tsx @@ -97,32 +97,25 @@ export const EditDynamicSecretSnowflakeForm = ({ }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; - try { - const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch (err) { - createNotification({ - type: "error", - text: err instanceof Error ? err.message : "Failed to update dynamic secret" - }); - } + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSqlProviderForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSqlProviderForm.tsx index 929a1485d..d6da25f80 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSqlProviderForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSqlProviderForm.tsx @@ -167,36 +167,29 @@ export const EditDynamicSecretSqlProviderForm = ({ }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; - try { - const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs: { - ...inputs, - gatewayId: isGatewayInActive ? null : inputs.gatewayId - }, - newName: newName === dynamicSecret.name ? undefined : newName, - metadata, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs: { + ...inputs, + gatewayId: isGatewayInActive ? null : inputs.gatewayId + }, + newName: newName === dynamicSecret.name ? undefined : newName, + metadata, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretTotpForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretTotpForm.tsx index 7b4a4dac3..15dead8d0 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretTotpForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretTotpForm.tsx @@ -87,28 +87,21 @@ export const EditDynamicSecretTotpForm = ({ const handleUpdateDynamicSecret = async ({ inputs, newName }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - inputs, - newName: newName === dynamicSecret.name ? undefined : newName - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch (err) { - createNotification({ - type: "error", - text: err instanceof Error ? err.message : "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + inputs, + newName: newName === dynamicSecret.name ? undefined : newName + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretVertica.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretVertica.tsx index e4ccda943..0579ba3b9 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretVertica.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretVertica.tsx @@ -146,34 +146,28 @@ export const EditDynamicSecretVerticaForm = ({ if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs: { - ...inputs, - gatewayId: isGatewayInActive ? null : inputs.gatewayId - }, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs: { + ...inputs, + gatewayId: isGatewayInActive ? null : inputs.gatewayId + }, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/RenewDynamicSecretLease.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/RenewDynamicSecretLease.tsx index 85528a5bc..bdb5f9add 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/RenewDynamicSecretLease.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/RenewDynamicSecretLease.tsx @@ -64,27 +64,19 @@ export const RenewDynamicSecretLease = ({ const handleDynamicSecretLeaseCreate = async ({ ttl }: TForm) => { if (renewDynamicSecretLease.isPending) return; - try { - await renewDynamicSecretLease.mutateAsync({ - environmentSlug: environment, - projectSlug, - path: secretPath, - ttl, - dynamicSecretName, - leaseId - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully renewed lease" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to renew lease" - }); - } + await renewDynamicSecretLease.mutateAsync({ + environmentSlug: environment, + projectSlug, + path: secretPath, + ttl, + dynamicSecretName, + leaseId + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully renewed lease" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/EnvironmentTabs/EnvironmentTabs.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/EnvironmentTabs/EnvironmentTabs.tsx index ffdd8c6c8..f8a201717 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/EnvironmentTabs/EnvironmentTabs.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/EnvironmentTabs/EnvironmentTabs.tsx @@ -226,7 +226,7 @@ export const EnvironmentTabs = ({ secretPath }: Props) => { handlePopUpToggle("upgradePlan", isOpen)} - text="You can add custom environments if you switch to Infisical's Pro plan." + text="Your current plan does not include access to adding custom environments. To unlock this feature, please upgrade to Infisical Pro plan." /> { - try { - const updateFolderData = popUp.updateFolder.data; - if (!updateFolderData) throw new Error("Update folder data is required"); - const { id: folderId, pendingAction, isPending } = updateFolderData as TSecretFolder; + const updateFolderData = popUp.updateFolder.data; + if (!updateFolderData) throw new Error("Update folder data is required"); + const { id: folderId, pendingAction, isPending } = updateFolderData as TSecretFolder; - if (isBatchMode) { - const isEditingPendingCreation = isPending && pendingAction === PendingAction.Create; + if (isBatchMode) { + const isEditingPendingCreation = isPending && pendingAction === PendingAction.Create; - if (isEditingPendingCreation) { - const updatedCreate: PendingFolderCreate = { - id: folderId, - type: PendingAction.Create, - folderName: newFolderName, - description: newFolderDescription || undefined, - parentPath: secretPath, - timestamp: Date.now(), - resourceType: "folder" - }; + if (isEditingPendingCreation) { + const updatedCreate: PendingFolderCreate = { + id: folderId, + type: PendingAction.Create, + folderName: newFolderName, + description: newFolderDescription || undefined, + parentPath: secretPath, + timestamp: Date.now(), + resourceType: "folder" + }; - addPendingChange(updatedCreate, { - projectId, - environment, - secretPath - }); - } else { - const updateChange: PendingFolderUpdate = { - id: folderId, - type: PendingAction.Update, - originalFolderName: oldFolderName || "", - folderName: newFolderName, - originalDescription: oldFolderDescription, - description: newFolderDescription || undefined, - timestamp: Date.now(), - resourceType: "folder" - }; + addPendingChange(updatedCreate, { + projectId, + environment, + secretPath + }); + } else { + const updateChange: PendingFolderUpdate = { + id: folderId, + type: PendingAction.Update, + originalFolderName: oldFolderName || "", + folderName: newFolderName, + originalDescription: oldFolderDescription, + description: newFolderDescription || undefined, + timestamp: Date.now(), + resourceType: "folder" + }; - addPendingChange(updateChange, { - projectId, - environment, - secretPath - }); - } - - handlePopUpClose("updateFolder"); - return; + addPendingChange(updateChange, { + projectId, + environment, + secretPath + }); } - await updateFolder({ - folderId, - name: newFolderName, - path: secretPath, - environment, - projectId, - description: newFolderDescription - }); handlePopUpClose("updateFolder"); - createNotification({ - type: "success", - text: "Successfully saved folder" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to save folder" - }); + return; } + + await updateFolder({ + folderId, + name: newFolderName, + path: secretPath, + environment, + projectId, + description: newFolderDescription + }); + handlePopUpClose("updateFolder"); + createNotification({ + type: "success", + text: "Successfully saved folder" + }); }; const handleDeletePending = (id: string) => { @@ -147,48 +139,40 @@ export const FolderListView = ({ }; const handleFolderDelete = async () => { - try { - const folderData = popUp.deleteFolder?.data as TSecretFolder; + const folderData = popUp.deleteFolder?.data as TSecretFolder; - if (isBatchMode) { - const pendingFolderDelete: PendingFolderDelete = { - id: folderData.id, - folderName: folderData.name, - folderPath: secretPath, - resourceType: "folder", - type: PendingAction.Delete, - timestamp: Date.now() - }; + if (isBatchMode) { + const pendingFolderDelete: PendingFolderDelete = { + id: folderData.id, + folderName: folderData.name, + folderPath: secretPath, + resourceType: "folder", + type: PendingAction.Delete, + timestamp: Date.now() + }; - addPendingChange(pendingFolderDelete, { - projectId, - environment, - secretPath - }); - - handlePopUpClose("deleteFolder"); - return; - } - - await deleteFolder({ - folderId: folderData.id, - path: secretPath, + addPendingChange(pendingFolderDelete, { + projectId, environment, - projectId + secretPath }); handlePopUpClose("deleteFolder"); - createNotification({ - type: "success", - text: "Successfully deleted folder" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to delete folder" - }); + return; } + + await deleteFolder({ + folderId: folderData.id, + path: secretPath, + environment, + projectId + }); + + handlePopUpClose("deleteFolder"); + createNotification({ + type: "success", + text: "Successfully deleted folder" + }); }; const handleFolderClick = (name: string, isPending?: boolean) => { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportItem.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportItem.tsx index d4e7d42d5..6a127d6b5 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportItem.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportItem.tsx @@ -118,24 +118,16 @@ export const SecretImportItem = ({ const handleResyncSecretReplication = async () => { if (resyncSecretReplication.isPending) return; - try { - await resyncSecretReplication.mutateAsync({ - id, - environment, - path: secretPath, - projectId: currentProject?.id || "" - }); - createNotification({ - text: "Please refresh the dashboard to view changes", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to resync replication", - type: "error" - }); - } + await resyncSecretReplication.mutateAsync({ + id, + environment, + path: secretPath, + projectId: currentProject?.id || "" + }); + createNotification({ + text: "Please refresh the dashboard to view changes", + type: "success" + }); }; const handleRowClick = () => { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView.tsx index 763872e9e..794fce88f 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView.tsx @@ -147,25 +147,17 @@ export const SecretImportListView = ({ const handleSecretImportDelete = async () => { const { id: secretImportId } = popUp.deleteSecretImport?.data as { id: string }; - try { - await deleteSecretImport({ - projectId, - environment, - path: secretPath, - id: secretImportId - }); - handlePopUpClose("deleteSecretImport"); - createNotification({ - type: "success", - text: "Successfully removed secret link" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to remove secret link", - type: "error" - }); - } + await deleteSecretImport({ + projectId, + environment, + path: secretPath, + id: secretImportId + }); + handlePopUpClose("deleteSecretImport"); + createNotification({ + type: "success", + text: "Successfully removed secret link" + }); }; const handleSecretImportReorder = ({ over, active }: DragEndEvent) => { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/CreateReminderForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/CreateReminderForm.tsx index 3f7727290..531401998 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/CreateReminderForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/CreateReminderForm.tsx @@ -199,53 +199,37 @@ export const CreateReminderForm = ({ // Form submission handler const handleFormSubmit = async (data: TReminderFormSchema) => { - try { - await createReminder({ - repeatDays: data.repeatDays, - message: data.message, - recipients: data.recipients?.map((r) => r.value) || [], - secretId, - nextReminderDate: data.nextReminderDate, - fromDate: data.fromDate - }); + await createReminder({ + repeatDays: data.repeatDays, + message: data.message, + recipients: data.recipients?.map((r) => r.value) || [], + secretId, + nextReminderDate: data.nextReminderDate, + fromDate: data.fromDate + }); - invalidateQueries(); + invalidateQueries(); - createNotification({ - type: "success", - text: `Successfully ${isEditMode ? "updated" : "created"} secret reminder` - }); + createNotification({ + type: "success", + text: `Successfully ${isEditMode ? "updated" : "created"} secret reminder` + }); - reset(); - onOpenChange(); - } catch (error) { - console.error("Failed to save reminder:", error); - createNotification({ - type: "error", - text: "Failed to save reminder. Please try again." - }); - } + reset(); + onOpenChange(); }; // Delete reminder handler const handleDeleteReminder = async () => { - try { - await deleteReminder({ reminderId: reminder?.id || "", secretId }); - invalidateQueries(); - reset(); - onOpenChange(); + await deleteReminder({ reminderId: reminder?.id || "", secretId }); + invalidateQueries(); + reset(); + onOpenChange(); - createNotification({ - type: "success", - text: "Successfully deleted reminder" - }); - } catch (error) { - console.error("Failed to delete reminder:", error); - createNotification({ - type: "error", - text: "Failed to delete reminder. Please try again." - }); - } + createNotification({ + type: "success", + text: "Successfully deleted reminder" + }); }; // Handle reminder type change diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx index 927bec31e..3cb2a59ca 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx @@ -321,7 +321,7 @@ export const SecretDetailSidebar = ({ onOpenChange={(isUpgradeModalOpen) => handlePopUpToggle("secretAccessUpgradePlan", isUpgradeModalOpen) } - text="You can access secret access analysis if you switch to Infisical's Pro plan." + text="Secret access analysis feature can be unlocked if you upgrade to Infisical Pro plan." /> ({ id: tag.id, slug: tag.name || tag.slug || "" })) || [], - secretMetadata: secretMetadata || [], - timestamp: Date.now(), - resourceType: "secret", - originalKey: oldKey - }; + if (isEditingPendingCreation) { + const updatedCreate: PendingSecretCreate = { + id: orgSecret.id, + type: PendingAction.Create, + secretKey: key, + secretValue: value || "", + secretComment: comment || "", + skipMultilineEncoding: modSecret.skipMultilineEncoding || false, + tags: tags?.map((tag) => ({ id: tag.id, slug: tag.name || tag.slug || "" })) || [], + secretMetadata: secretMetadata || [], + timestamp: Date.now(), + resourceType: "secret", + originalKey: oldKey + }; - addPendingChange(updatedCreate, { - projectId, - environment, - secretPath - }); - } else { - const trueOriginalSecret = getTrueOriginalSecret( - orgSecret, - pendingChangesRef.current.secrets - ); + addPendingChange(updatedCreate, { + projectId, + environment, + secretPath + }); + } else { + const trueOriginalSecret = getTrueOriginalSecret( + orgSecret, + pendingChangesRef.current.secrets + ); - const updateChange: PendingSecretUpdate = { - id: orgSecret.id, - type: PendingAction.Update, - secretKey: trueOriginalSecret.key, - newSecretName: key, - originalValue: trueOriginalSecret.value, - secretValue: value, - originalComment: trueOriginalSecret.comment, - secretComment: comment, - originalSkipMultilineEncoding: trueOriginalSecret.skipMultilineEncoding, - skipMultilineEncoding: modSecret.skipMultilineEncoding, - originalTags: - trueOriginalSecret.tags?.map((tag) => ({ id: tag.id, slug: tag.slug })) || [], - tags: tags?.map((tag) => ({ id: tag.id, slug: tag.name || tag.slug || "" })) || [], - originalSecretMetadata: trueOriginalSecret.secretMetadata || [], - secretMetadata: secretMetadata || [], - timestamp: Date.now(), - resourceType: "secret", - existingSecret: orgSecret - }; + const updateChange: PendingSecretUpdate = { + id: orgSecret.id, + type: PendingAction.Update, + secretKey: trueOriginalSecret.key, + newSecretName: key, + originalValue: trueOriginalSecret.value, + secretValue: value, + originalComment: trueOriginalSecret.comment, + secretComment: comment, + originalSkipMultilineEncoding: trueOriginalSecret.skipMultilineEncoding, + skipMultilineEncoding: modSecret.skipMultilineEncoding, + originalTags: + trueOriginalSecret.tags?.map((tag) => ({ id: tag.id, slug: tag.slug })) || [], + tags: tags?.map((tag) => ({ id: tag.id, slug: tag.name || tag.slug || "" })) || [], + originalSecretMetadata: trueOriginalSecret.secretMetadata || [], + secretMetadata: secretMetadata || [], + timestamp: Date.now(), + resourceType: "secret", + existingSecret: orgSecret + }; - addPendingChange(updateChange, { - projectId, - environment, - secretPath - }); - } - - if (!isReminderEvent) { - handlePopUpClose("secretDetail"); - } - if (cb) cb(); - return; + addPendingChange(updateChange, { + projectId, + environment, + secretPath + }); } - await handleSecretOperation("update", SecretType.Shared, oldKey, { - value, - tags: tagIds, - comment, - reminderRepeatDays, - reminderNote, - reminderRecipients, - secretId: orgSecret.id, - newKey: hasKeyChanged ? key : undefined, - skipMultilineEncoding: modSecret.skipMultilineEncoding, - secretMetadata, - isRotatedSecret: orgSecret.isRotatedSecret, - secretValueHidden - }); + if (!isReminderEvent) { + handlePopUpClose("secretDetail"); + } if (cb) cb(); - } - queryClient.invalidateQueries({ - queryKey: dashboardKeys.getDashboardSecrets({ - projectId, - secretPath - }) - }); - queryClient.invalidateQueries({ - queryKey: secretKeys.getProjectSecret({ projectId, environment, secretPath }) - }); - queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.list({ - projectId, - environment, - directory: secretPath - }) - }); - queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.count({ - projectId, - environment, - directory: secretPath - }) - }); - queryClient.invalidateQueries({ - queryKey: commitKeys.count({ projectId, environment, directory: secretPath }) - }); - queryClient.invalidateQueries({ - queryKey: commitKeys.history({ - projectId, - environment, - directory: secretPath - }) - }); - queryClient.invalidateQueries({ - queryKey: secretApprovalRequestKeys.count({ projectId }) - }); - if (!isReminderEvent) { - handlePopUpClose("secretDetail"); + return; } - let successMessage; - if (isReminderEvent) { - successMessage = reminderRepeatDays - ? "Successfully saved secret reminder" - : "Successfully deleted secret reminder"; - } else { - successMessage = "Successfully saved secrets"; - } - - createNotification({ - type: isProtectedBranch && !personalAction ? "info" : "success", - text: - isProtectedBranch && !personalAction - ? "Requested changes have been sent for review" - : successMessage - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to save secret" + await handleSecretOperation("update", SecretType.Shared, oldKey, { + value, + tags: tagIds, + comment, + reminderRepeatDays, + reminderNote, + reminderRecipients, + secretId: orgSecret.id, + newKey: hasKeyChanged ? key : undefined, + skipMultilineEncoding: modSecret.skipMultilineEncoding, + secretMetadata, + isRotatedSecret: orgSecret.isRotatedSecret, + secretValueHidden }); + if (cb) cb(); } + queryClient.invalidateQueries({ + queryKey: dashboardKeys.getDashboardSecrets({ + projectId, + secretPath + }) + }); + queryClient.invalidateQueries({ + queryKey: secretKeys.getProjectSecret({ projectId, environment, secretPath }) + }); + queryClient.invalidateQueries({ + queryKey: secretSnapshotKeys.list({ + projectId, + environment, + directory: secretPath + }) + }); + queryClient.invalidateQueries({ + queryKey: secretSnapshotKeys.count({ + projectId, + environment, + directory: secretPath + }) + }); + queryClient.invalidateQueries({ + queryKey: commitKeys.count({ projectId, environment, directory: secretPath }) + }); + queryClient.invalidateQueries({ + queryKey: commitKeys.history({ + projectId, + environment, + directory: secretPath + }) + }); + queryClient.invalidateQueries({ + queryKey: secretApprovalRequestKeys.count({ projectId }) + }); + if (!isReminderEvent) { + handlePopUpClose("secretDetail"); + } + + let successMessage; + if (isReminderEvent) { + successMessage = reminderRepeatDays + ? "Successfully saved secret reminder" + : "Successfully deleted secret reminder"; + } else { + successMessage = "Successfully saved secrets"; + } + + createNotification({ + type: isProtectedBranch && !personalAction ? "info" : "success", + text: + isProtectedBranch && !personalAction + ? "Requested changes have been sent for review" + : successMessage + }); }, [environment, secretPath, isProtectedBranch, isBatchMode, projectId, addPendingChange] ); @@ -488,75 +480,67 @@ export const SecretListView = ({ value, secretValueHidden } = popUp.deleteSecret?.data as SecretV3RawSanitized; - try { - if (isBatchMode) { - const deleteChange: PendingSecretDelete = { - id: `${secretId}`, - type: PendingAction.Delete, - secretKey: key, - secretValue: value || "", - timestamp: Date.now(), - resourceType: "secret", - secretValueHidden - }; + if (isBatchMode) { + const deleteChange: PendingSecretDelete = { + id: `${secretId}`, + type: PendingAction.Delete, + secretKey: key, + secretValue: value || "", + timestamp: Date.now(), + resourceType: "secret", + secretValueHidden + }; - addPendingChange(deleteChange, { - projectId, - environment, - secretPath - }); + addPendingChange(deleteChange, { + projectId, + environment, + secretPath + }); - handlePopUpClose("deleteSecret"); - handlePopUpClose("secretDetail"); - return; - } - - await handleSecretOperation("delete", SecretType.Shared, key, { secretId }); - // wrap this in another function and then reuse - queryClient.invalidateQueries({ - queryKey: dashboardKeys.getDashboardSecrets({ projectId, secretPath }) - }); - queryClient.invalidateQueries({ - queryKey: secretKeys.getProjectSecret({ projectId, environment, secretPath }) - }); - queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.list({ - projectId, - environment, - directory: secretPath - }) - }); - queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.count({ - projectId, - environment, - directory: secretPath - }) - }); - queryClient.invalidateQueries({ - queryKey: commitKeys.count({ projectId, environment, directory: secretPath }) - }); - queryClient.invalidateQueries({ - queryKey: commitKeys.history({ projectId, environment, directory: secretPath }) - }); - queryClient.invalidateQueries({ - queryKey: secretApprovalRequestKeys.count({ projectId }) - }); handlePopUpClose("deleteSecret"); handlePopUpClose("secretDetail"); - createNotification({ - type: isProtectedBranch ? "info" : "success", - text: isProtectedBranch - ? "Requested changes have been sent for review" - : "Successfully deleted secret" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to delete secret" - }); + return; } + + await handleSecretOperation("delete", SecretType.Shared, key, { secretId }); + // wrap this in another function and then reuse + queryClient.invalidateQueries({ + queryKey: dashboardKeys.getDashboardSecrets({ projectId, secretPath }) + }); + queryClient.invalidateQueries({ + queryKey: secretKeys.getProjectSecret({ projectId, environment, secretPath }) + }); + queryClient.invalidateQueries({ + queryKey: secretSnapshotKeys.list({ + projectId, + environment, + directory: secretPath + }) + }); + queryClient.invalidateQueries({ + queryKey: secretSnapshotKeys.count({ + projectId, + environment, + directory: secretPath + }) + }); + queryClient.invalidateQueries({ + queryKey: commitKeys.count({ projectId, environment, directory: secretPath }) + }); + queryClient.invalidateQueries({ + queryKey: commitKeys.history({ projectId, environment, directory: secretPath }) + }); + queryClient.invalidateQueries({ + queryKey: secretApprovalRequestKeys.count({ projectId }) + }); + handlePopUpClose("deleteSecret"); + handlePopUpClose("secretDetail"); + createNotification({ + type: isProtectedBranch ? "info" : "success", + text: isProtectedBranch + ? "Requested changes have been sent for review" + : "Successfully deleted secret" + }); }, [ (popUp.deleteSecret?.data as SecretV3RawSanitized)?.key, environment, diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SnapshotView/SnapshotView.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SnapshotView/SnapshotView.tsx index eed335fa8..ca12e76a4 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SnapshotView/SnapshotView.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SnapshotView/SnapshotView.tsx @@ -128,25 +128,17 @@ export const SnapshotView = ({ }); return; } - try { - await performRollback({ - projectId, - snapshotId: snapshotData.id, - environment, - directory: secretPath - }); - createNotification({ - text: "Successfully rollback secrets", - type: "success" - }); - onGoBack(); - } catch (error) { - console.log(error); - createNotification({ - text: "Failed to rollback secrets", - type: "error" - }); - } + await performRollback({ + projectId, + snapshotId: snapshotData.id, + environment, + directory: secretPath + }); + createNotification({ + text: "Successfully rollback secrets", + type: "success" + }); + onGoBack(); }; if (isSnapshotLoading) { diff --git a/frontend/src/pages/secret-manager/SecretRotationPage/SecretRotationPage.tsx b/frontend/src/pages/secret-manager/SecretRotationPage/SecretRotationPage.tsx index 8efa28a63..12c264004 100644 --- a/frontend/src/pages/secret-manager/SecretRotationPage/SecretRotationPage.tsx +++ b/frontend/src/pages/secret-manager/SecretRotationPage/SecretRotationPage.tsx @@ -95,42 +95,26 @@ const Page = () => { const handleDeleteRotation = async () => { const { id } = popUp.deleteRotation.data as { id: string }; - try { - await deleteSecretRotation({ - id, - workspaceId - }); - handlePopUpClose("deleteRotation"); - createNotification({ - type: "success", - text: "Successfully removed rotation" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to remove rotation" - }); - } + await deleteSecretRotation({ + id, + workspaceId + }); + handlePopUpClose("deleteRotation"); + createNotification({ + type: "success", + text: "Successfully removed rotation" + }); }; const handleRestartRotation = async (id: string) => { - try { - await restartSecretRotation({ - id, - workspaceId - }); - createNotification({ - type: "success", - text: "Secret rotation initiated" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to restart rotation" - }); - } + await restartSecretRotation({ + id, + workspaceId + }); + createNotification({ + type: "success", + text: "Secret rotation initiated" + }); }; const handleCreateRotation = (provider: TSecretRotationProviderTemplate) => { @@ -397,7 +381,7 @@ const Page = () => { handlePopUpToggle("upgradePlan", isOpen)} - text="You can add secret rotation if you switch to Infisical's Pro plan." + text="Adding secret rotations can be unlocked if you upgrade to Infisical Pro plan." /> { if (!wizardData.current.input || !wizardData.current.output) return; - try { - await createSecretRotation({ - workspaceId, - provider: provider.name, - customProvider, - secretPath: wizardData.current.output.secretPath, - environment: wizardData.current.output.environment, - interval: wizardData.current.output.interval, - inputs: wizardData.current.input, - outputs: wizardData.current.output.secrets - }); - setWizardStep(0); - onToggle(false); - wizardData.current = {}; - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to create secret rotation" - }); - } + await createSecretRotation({ + workspaceId, + provider: provider.name, + customProvider, + secretPath: wizardData.current.output.secretPath, + environment: wizardData.current.output.environment, + interval: wizardData.current.output.interval, + inputs: wizardData.current.input, + outputs: wizardData.current.output.secrets + }); + setWizardStep(0); + onToggle(false); + wizardData.current = {}; }; return ( diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncActionTriggers.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncActionTriggers.tsx index 78cdf899e..ad82460eb 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncActionTriggers.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncActionTriggers.tsx @@ -89,44 +89,30 @@ export const SecretSyncActionTriggers = ({ secretSync }: Props) => { const handleToggleEnableSync = async () => { const isAutoSyncEnabled = !secretSync.isAutoSyncEnabled; - try { - await updateSync.mutateAsync({ - syncId: secretSync.id, - destination: secretSync.destination, - isAutoSyncEnabled, - projectId: secretSync.projectId - }); + await updateSync.mutateAsync({ + syncId: secretSync.id, + destination: secretSync.destination, + isAutoSyncEnabled, + projectId: secretSync.projectId + }); - createNotification({ - text: `Successfully ${isAutoSyncEnabled ? "enabled" : "disabled"} auto-sync for ${destinationName} Sync`, - type: "success" - }); - } catch { - createNotification({ - text: `Failed to ${isAutoSyncEnabled ? "enable" : "disable"} auto-sync for ${destinationName} Sync`, - type: "error" - }); - } + createNotification({ + text: `Successfully ${isAutoSyncEnabled ? "enabled" : "disabled"} auto-sync for ${destinationName} Sync`, + type: "success" + }); }; const handleTriggerSync = async () => { - try { - await triggerSyncSecrets.mutateAsync({ - syncId: secretSync.id, - destination: secretSync.destination, - projectId: secretSync.projectId - }); + await triggerSyncSecrets.mutateAsync({ + syncId: secretSync.id, + destination: secretSync.destination, + projectId: secretSync.projectId + }); - createNotification({ - text: `Successfully triggered ${destinationName} Sync`, - type: "success" - }); - } catch { - createNotification({ - text: `Failed to trigger ${destinationName} Sync`, - type: "error" - }); - } + createNotification({ + text: `Successfully triggered ${destinationName} Sync`, + type: "success" + }); }; const permissionSubject = diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/ChefSyncDestinationSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/ChefSyncDestinationSection.tsx new file mode 100644 index 000000000..c5e230878 --- /dev/null +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/ChefSyncDestinationSection.tsx @@ -0,0 +1,19 @@ +import { GenericFieldLabel } from "@app/components/secret-syncs"; +import { TChefSync } from "@app/hooks/api/secretSyncs/types/chef-sync"; + +type Props = { + secretSync: TChefSync; +}; + +export const ChefSyncDestinationSection = ({ secretSync }: Props) => { + const { destinationConfig } = secretSync; + + return ( + <> + {destinationConfig.dataBagName} + + {destinationConfig.dataBagItemName} + + + ); +}; diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx index 31bc6d4fd..de527d6c8 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx @@ -20,6 +20,7 @@ import { AzureKeyVaultSyncDestinationSection } from "./AzureKeyVaultSyncDestinat import { BitbucketSyncDestinationSection } from "./BitbucketSyncDestinationSection"; import { CamundaSyncDestinationSection } from "./CamundaSyncDestinationSection"; import { ChecklySyncDestinationSection } from "./ChecklySyncDestinationSection"; +import { ChefSyncDestinationSection } from "./ChefSyncDestinationSection"; import { CloudflarePagesSyncDestinationSection } from "./CloudflarePagesSyncDestinationSection"; import { CloudflareWorkersSyncDestinationSection } from "./CloudflareWorkersSyncDestinationSection"; import { DatabricksSyncDestinationSection } from "./DatabricksSyncDestinationSection"; @@ -156,6 +157,9 @@ export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }: case SecretSync.LaravelForge: DestinationComponents = ; break; + case SecretSync.Chef: + DestinationComponents = ; + break; default: throw new Error(`Unhandled Destination Section components: ${destination}`); } 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 7e6ac8459..a8de5b379 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx @@ -74,6 +74,7 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) = case SecretSync.Northflank: case SecretSync.Bitbucket: case SecretSync.LaravelForge: + case SecretSync.Chef: AdditionalSyncOptionsComponent = null; break; default: diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/AutoCapitalizationSection/AutoCapitalizationSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/AutoCapitalizationSection/AutoCapitalizationSection.tsx index 73bec81b9..7c9d03904 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/AutoCapitalizationSection/AutoCapitalizationSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/AutoCapitalizationSection/AutoCapitalizationSection.tsx @@ -13,26 +13,18 @@ export const AutoCapitalizationSection = () => { const { mutateAsync } = useUpdateProject(); const handleToggleCapitalizationToggle = async (state: boolean) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - await mutateAsync({ - projectId: currentProject.id, - autoCapitalization: state - }); + await mutateAsync({ + projectId: currentProject.id, + autoCapitalization: state + }); - const text = `Successfully ${state ? "enabled" : "disabled"} auto capitalization`; - createNotification({ - text, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update auto capitalization", - type: "error" - }); - } + const text = `Successfully ${state ? "enabled" : "disabled"} auto capitalization`; + createNotification({ + text, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/BackfillSecretReferenceSection/BackfillSecretReferenceSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/BackfillSecretReferenceSection/BackfillSecretReferenceSection.tsx index 26f5c7fc9..9350698dc 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/BackfillSecretReferenceSection/BackfillSecretReferenceSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/BackfillSecretReferenceSection/BackfillSecretReferenceSection.tsx @@ -13,12 +13,8 @@ export const BackfillSecretReferenceSecretion = () => { const handleBackfill = async () => { if (backfillSecretReferences.isPending) return; - try { - await backfillSecretReferences.mutateAsync({ projectId: currentProject.id || "" }); - createNotification({ text: "Successfully re-indexed secret references", type: "success" }); - } catch { - createNotification({ text: "Failed to re-index secret references", type: "error" }); - } + await backfillSecretReferences.mutateAsync({ projectId: currentProject.id || "" }); + createNotification({ text: "Successfully re-indexed secret references", type: "success" }); }; const isAdmin = hasProjectRole(ProjectMembershipRole.Admin); diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/EncryptionTab/EncryptionTab.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/EncryptionTab/EncryptionTab.tsx index 9e4627d26..e22719c83 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/EncryptionTab/EncryptionTab.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/EncryptionTab/EncryptionTab.tsx @@ -124,17 +124,13 @@ const LoadBackupModal = ({ return; } - try { - await loadKmsBackup(backupContent); - createNotification({ - text: "Successfully loaded KMS backup", - type: "success" - }); + await loadKmsBackup(backupContent); + createNotification({ + text: "Successfully loaded KMS backup", + type: "success" + }); - onOpenChange(false); - } catch (err) { - console.error(err); - } + onOpenChange(false); }; const parseFile = (file?: File) => { @@ -245,20 +241,16 @@ export const EncryptionTab = () => { }); const onUpdateProjectKms = async (data: TForm) => { - try { - await updateProjectKms( - data.kmsKeyId === INTERNAL_KMS_KEY_ID - ? { type: KmsType.Internal } - : { type: KmsType.External, kmsId: data.kmsKeyId } - ); + await updateProjectKms( + data.kmsKeyId === INTERNAL_KMS_KEY_ID + ? { type: KmsType.Internal } + : { type: KmsType.External, kmsId: data.kmsKeyId } + ); - createNotification({ - text: "Successfully updated project KMS", - type: "success" - }); - } catch (err) { - console.error(err); - } + createNotification({ + text: "Successfully updated project KMS", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/AddEnvironmentModal.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/AddEnvironmentModal.tsx index 611504a14..70c486616 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/AddEnvironmentModal.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/AddEnvironmentModal.tsx @@ -36,28 +36,20 @@ const Content = ({ onComplete }: ContentProps) => { }); const onFormSubmit = async ({ environmentName, environmentSlug }: FormData) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - const env = await mutateAsync({ - projectId: currentProject.id, - name: environmentName, - slug: environmentSlug - }); + const env = await mutateAsync({ + projectId: currentProject.id, + name: environmentName, + slug: environmentSlug + }); - createNotification({ - text: "Successfully created environment", - type: "success" - }); + createNotification({ + text: "Successfully created environment", + type: "success" + }); - onComplete(env); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to create environment", - type: "error" - }); - } + onComplete(env); }; return ( diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentSection.tsx index 1105d60a2..b6276a8c0 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentSection.tsx @@ -39,27 +39,19 @@ export const EnvironmentSection = () => { ] as const); const onEnvDeleteSubmit = async (id: string) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - await deleteWsEnvironment.mutateAsync({ - projectId: currentProject.id, - id - }); + await deleteWsEnvironment.mutateAsync({ + projectId: currentProject.id, + id + }); - createNotification({ - text: "Successfully deleted environment", - type: "success" - }); + createNotification({ + text: "Successfully deleted environment", + type: "success" + }); - handlePopUpClose("deleteEnv"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete environment", - type: "error" - }); - } + handlePopUpClose("deleteEnv"); }; return ( @@ -123,7 +115,7 @@ export const EnvironmentSection = () => { handlePopUpToggle("upgradePlan", isOpen)} - text="You can add custom environments if you switch to Infisical's Pro plan." + text="You have reached the maximum number of environments allowed on the free plan. Upgrade to Infisical Pro plan to add more environments." /> ); diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentTable.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentTable.tsx index 0908d8377..6b5b3a8d2 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentTable.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentTable.tsx @@ -46,26 +46,18 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => { const updateEnvironment = useUpdateWsEnvironment(); const handleReorderEnv = async (id: string, position: number) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - await updateEnvironment.mutateAsync({ - projectId: currentProject.id, - id, - position - }); + await updateEnvironment.mutateAsync({ + projectId: currentProject.id, + id, + position + }); - createNotification({ - text: "Successfully re-ordered environments", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to re-order environments", - type: "error" - }); - } + createNotification({ + text: "Successfully re-ordered environments", + type: "success" + }); }; const isMoreEnvironmentsAllowed = diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/UpdateEnvironmentModal.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/UpdateEnvironmentModal.tsx index e2d64ee72..1c9d09101 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/UpdateEnvironmentModal.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/UpdateEnvironmentModal.tsx @@ -33,29 +33,21 @@ export const UpdateEnvironmentModal = ({ popUp, handlePopUpClose, handlePopUpTog const oldEnvId = (popUp?.updateEnv?.data as { id: string })?.id; const onFormSubmit = async ({ name, slug }: FormData) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - await mutateAsync({ - projectId: currentProject.id, - name, - slug, - id: oldEnvId - }); + await mutateAsync({ + projectId: currentProject.id, + name, + slug, + id: oldEnvId + }); - createNotification({ - text: "Successfully updated environment", - type: "success" - }); + createNotification({ + text: "Successfully updated environment", + type: "success" + }); - handlePopUpClose("updateEnv"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update environment", - type: "error" - }); - } + handlePopUpClose("updateEnv"); }; return ( diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/PointInTimeVersionLimitSection/PointInTimeVersionLimitSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/PointInTimeVersionLimitSection/PointInTimeVersionLimitSection.tsx index e647ff481..6345301a0 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/PointInTimeVersionLimitSection/PointInTimeVersionLimitSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/PointInTimeVersionLimitSection/PointInTimeVersionLimitSection.tsx @@ -34,22 +34,15 @@ export const PointInTimeVersionLimitSection = () => { if (!currentProject) return null; const handleVersionLimitSubmit = async ({ pitVersionLimit }: TForm) => { - try { - await updateProject({ - pitVersionLimit, - projectId - }); + await updateProject({ + pitVersionLimit, + projectId + }); - createNotification({ - text: "Successfully updated version limit", - type: "success" - }); - } catch { - createNotification({ - text: "Failed updating project's version limit", - type: "error" - }); - } + createNotification({ + text: "Successfully updated version limit", + type: "success" + }); }; const isAdmin = hasProjectRole(ProjectMembershipRole.Admin); diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/SecretDetectionIgnoreValuesSection/SecretDetectionIgnoreValuesSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/SecretDetectionIgnoreValuesSection/SecretDetectionIgnoreValuesSection.tsx index e086834a1..5667afa76 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/SecretDetectionIgnoreValuesSection/SecretDetectionIgnoreValuesSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/SecretDetectionIgnoreValuesSection/SecretDetectionIgnoreValuesSection.tsx @@ -55,22 +55,15 @@ export const SecretDetectionIgnoreValuesSection = () => { }, [currentProject?.secretDetectionIgnoreValues, reset]); const handleIgnoreValuesSubmit = async ({ ignoreValues }: TForm) => { - try { - await updateProject({ - projectId: currentProject.id, - secretDetectionIgnoreValues: ignoreValues.map((item) => item.value) - }); + await updateProject({ + projectId: currentProject.id, + secretDetectionIgnoreValues: ignoreValues.map((item) => item.value) + }); - createNotification({ - text: "Successfully updated secret detection ignore values", - type: "success" - }); - } catch { - createNotification({ - text: "Failed updating secret detection ignore values", - type: "error" - }); - } + createNotification({ + text: "Successfully updated secret detection ignore values", + type: "success" + }); }; const isAdmin = hasProjectRole(ProjectMembershipRole.Admin); diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/SecretSharingSection/SecretSharingSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/SecretSharingSection/SecretSharingSection.tsx index bd8928823..ac1f735f9 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/SecretSharingSection/SecretSharingSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/SecretSharingSection/SecretSharingSection.tsx @@ -15,12 +15,12 @@ export const SecretSharingSection = () => { const handleToggle = async (state: boolean) => { setIsLoading(true); - try { - if (!currentProject?.id) { - setIsLoading(false); - return; - } + if (!currentProject?.id) { + setIsLoading(false); + return; + } + try { await updateProject({ projectId: currentProject.id, secretSharing: state @@ -30,12 +30,6 @@ export const SecretSharingSection = () => { text: `Successfully ${state ? "enabled" : "disabled"} secret sharing for this project`, type: "success" }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update secret sharing for this project", - type: "error" - }); } finally { setIsLoading(false); } diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/SecretSnapshotsLegacySection/SecretSnapshotsLegacySection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/SecretSnapshotsLegacySection/SecretSnapshotsLegacySection.tsx index 553f17d54..466b950f1 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/SecretSnapshotsLegacySection/SecretSnapshotsLegacySection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/SecretSnapshotsLegacySection/SecretSnapshotsLegacySection.tsx @@ -30,12 +30,6 @@ export const SecretSnapshotsLegacySection = () => { text: `Successfully ${state ? "enabled" : "disabled"} secret snapshots legacy for this project`, type: "success" }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update secret snapshots legacy for this project", - type: "error" - }); } finally { setIsLoading(false); } diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/AddSecretTagModal.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/AddSecretTagModal.tsx index 1d4c8e492..cfe7dff8e 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/AddSecretTagModal.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/AddSecretTagModal.tsx @@ -39,29 +39,21 @@ export const AddSecretTagModal = ({ popUp, handlePopUpClose, handlePopUpToggle } }); const onFormSubmit = async ({ slug }: FormData) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - await createWsTag.mutateAsync({ - projectId: currentProject?.id, - tagSlug: slug, - tagColor: "" - }); + await createWsTag.mutateAsync({ + projectId: currentProject?.id, + tagSlug: slug, + tagColor: "" + }); - handlePopUpClose("CreateSecretTag"); + handlePopUpClose("CreateSecretTag"); - createNotification({ - text: "Successfully created a tag", - type: "success" - }); - reset(); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to create a tag", - type: "error" - }); - } + createNotification({ + text: "Successfully created a tag", + type: "success" + }); + reset(); }; return ( diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/SecretTagsSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/SecretTagsSection.tsx index 508ef2867..2964467ad 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/SecretTagsSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/SecretTagsSection.tsx @@ -29,25 +29,17 @@ export const SecretTagsSection = (): JSX.Element => { const deleteWsTag = useDeleteWsTag(); const onDeleteApproved = async () => { - try { - await deleteWsTag.mutateAsync({ - projectId: currentProject?.id || "", - tagID: (popUp?.deleteTagConfirmation?.data as DeleteModalData)?.id - }); + await deleteWsTag.mutateAsync({ + projectId: currentProject?.id || "", + tagID: (popUp?.deleteTagConfirmation?.data as DeleteModalData)?.id + }); - createNotification({ - text: "Successfully deleted tag", - type: "success" - }); + createNotification({ + text: "Successfully deleted tag", + type: "success" + }); - handlePopUpClose("deleteTagConfirmation"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete the tag", - type: "error" - }); - } + handlePopUpClose("deleteTagConfirmation"); }; return ( diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/WebhooksTab/WebhooksTab.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/WebhooksTab/WebhooksTab.tsx index 517f9e624..5296bc4ad 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/WebhooksTab/WebhooksTab.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/WebhooksTab/WebhooksTab.tsx @@ -59,83 +59,51 @@ export const WebhooksTab = withProjectPermission( const { mutateAsync: deleteWebhook } = useDeleteWebhook(); const handleWebhookCreate = async (data: TFormSchema) => { - try { - await createWebhook({ - ...data, - projectId - }); - handlePopUpClose("addWebhook"); - createNotification({ - type: "success", - text: "Successfully created webhook" - }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to create webhook" - }); - } + await createWebhook({ + ...data, + projectId + }); + handlePopUpClose("addWebhook"); + createNotification({ + type: "success", + text: "Successfully created webhook" + }); }; const handleWebhookDisable = async (webhookId: string, isDisabled: boolean) => { - try { - await updateWebhook({ - webhookId, - projectId, - isDisabled - }); - createNotification({ - type: "success", - text: "Successfully updated webhook" - }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to update webhook" - }); - } + await updateWebhook({ + webhookId, + projectId, + isDisabled + }); + createNotification({ + type: "success", + text: "Successfully updated webhook" + }); }; const handleWebhookDelete = async () => { - try { - const webhookId = popUp?.deleteWebhook?.data as string; - await deleteWebhook({ - webhookId, - projectId - }); - handlePopUpClose("deleteWebhook"); - createNotification({ - type: "success", - text: "Successfully deleted webhook" - }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to delete webhook" - }); - } + const webhookId = popUp?.deleteWebhook?.data as string; + await deleteWebhook({ + webhookId, + projectId + }); + handlePopUpClose("deleteWebhook"); + createNotification({ + type: "success", + text: "Successfully deleted webhook" + }); }; const handleWebhookTest = async (webhookId: string) => { - try { - await testWebhook({ - webhookId, - projectId - }); - createNotification({ - type: "success", - text: "Successfully triggered webhook" - }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to trigger webhook" - }); - } + await testWebhook({ + webhookId, + projectId + }); + createNotification({ + type: "success", + text: "Successfully triggered webhook" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/MicrosoftTeamsIntegrationForm.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/MicrosoftTeamsIntegrationForm.tsx index c638b65a7..707322264 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/MicrosoftTeamsIntegrationForm.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/MicrosoftTeamsIntegrationForm.tsx @@ -130,37 +130,30 @@ export const MicrosoftTeamsIntegrationForm = ({ onClose }: Props) => { }); const handleIntegrationSave = async (data: TMicrosoftTeamsConfigForm) => { - try { - if (!currentProject) { - return; - } - - await updateProjectMicrosoftTeamsConfig({ - projectId: currentProject.id, - isAccessRequestNotificationEnabled: data.isAccessRequestNotificationEnabled, - isSecretRequestNotificationEnabled: data.isSecretRequestNotificationEnabled, - ...(data.isAccessRequestNotificationEnabled && { - accessRequestChannels: data.accessRequestChannels - }), - ...(data.isSecretRequestNotificationEnabled && { - secretRequestChannels: data.secretRequestChannels - }), - integration: WorkflowIntegrationPlatform.MICROSOFT_TEAMS, - integrationId: data.microsoftTeamsIntegrationId - }); - - createNotification({ - type: "success", - text: "Successfully created microsoft teams integration" - }); - - onClose(); - } catch { - createNotification({ - type: "error", - text: "Failed to create microsoft teams integration" - }); + if (!currentProject) { + return; } + + await updateProjectMicrosoftTeamsConfig({ + projectId: currentProject.id, + isAccessRequestNotificationEnabled: data.isAccessRequestNotificationEnabled, + isSecretRequestNotificationEnabled: data.isSecretRequestNotificationEnabled, + ...(data.isAccessRequestNotificationEnabled && { + accessRequestChannels: data.accessRequestChannels + }), + ...(data.isSecretRequestNotificationEnabled && { + secretRequestChannels: data.secretRequestChannels + }), + integration: WorkflowIntegrationPlatform.MICROSOFT_TEAMS, + integrationId: data.microsoftTeamsIntegrationId + }); + + createNotification({ + type: "success", + text: "Successfully created microsoft teams integration" + }); + + onClose(); }; const selectedAccessRequestTeamId = watch("accessRequestChannels.teamId"); diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/SlackIntegrationForm.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/SlackIntegrationForm.tsx index bcc2d81b5..4af602e12 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/SlackIntegrationForm.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/SlackIntegrationForm.tsx @@ -76,32 +76,25 @@ export const SlackIntegrationForm = ({ onClose }: Props) => { }); const handleIntegrationSave = async (data: TSlackConfigForm) => { - try { - if (!currentProject) { - return; - } - - await updateProjectSlackConfig({ - ...data, - projectId: currentProject.id, - integration: WorkflowIntegrationPlatform.SLACK, - integrationId: data.slackIntegrationId, - accessRequestChannels: data.accessRequestChannels.filter(Boolean).join(", "), - secretRequestChannels: data.secretRequestChannels.filter(Boolean).join(", ") - }); - - createNotification({ - type: "success", - text: "Successfully created slack integration" - }); - - onClose(); - } catch { - createNotification({ - type: "error", - text: "Failed to create slack integration" - }); + if (!currentProject) { + return; } + + await updateProjectSlackConfig({ + ...data, + projectId: currentProject.id, + integration: WorkflowIntegrationPlatform.SLACK, + integrationId: data.slackIntegrationId, + accessRequestChannels: data.accessRequestChannels.filter(Boolean).join(", "), + secretRequestChannels: data.secretRequestChannels.filter(Boolean).join(", ") + }); + + createNotification({ + type: "success", + text: "Successfully created slack integration" + }); + + onClose(); }; const secretRequestNotifState = watch("isSecretRequestNotificationEnabled"); diff --git a/frontend/src/pages/secret-manager/integrations/BitbucketConfigurePage/BitbucketConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/BitbucketConfigurePage/BitbucketConfigurePage.tsx index 8d855f284..b66644bdc 100644 --- a/frontend/src/pages/secret-manager/integrations/BitbucketConfigurePage/BitbucketConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/BitbucketConfigurePage/BitbucketConfigurePage.tsx @@ -126,43 +126,35 @@ export const BitbucketConfigurePage = () => { }: TFormData) => { if (!targetRepo || !targetWorkspace) return; - try { - await createIntegration.mutateAsync({ - integrationAuthId, - isActive: true, - app: targetRepo.name, - appId: targetRepo.appId, - sourceEnvironment: sourceEnvironment.slug, - targetEnvironment: targetWorkspace.name, - targetEnvironmentId: targetWorkspace.slug, - ...(scope.value === BitbucketScope.Env && - targetEnvironment && { - targetService: targetEnvironment.name, - targetServiceId: targetEnvironment.uuid - }), - secretPath - }); + await createIntegration.mutateAsync({ + integrationAuthId, + isActive: true, + app: targetRepo.name, + appId: targetRepo.appId, + sourceEnvironment: sourceEnvironment.slug, + targetEnvironment: targetWorkspace.name, + targetEnvironmentId: targetWorkspace.slug, + ...(scope.value === BitbucketScope.Env && + targetEnvironment && { + targetService: targetEnvironment.name, + targetServiceId: targetEnvironment.uuid + }), + secretPath + }); - createNotification({ - type: "success", - text: "Successfully created integration" - }); - navigate({ - to: "/projects/secret-management/$projectId/integrations", - params: { - projectId: currentProject.id - }, - search: { - selectedTab: IntegrationsListPageTabs.NativeIntegrations - } - }); - } catch (err) { - createNotification({ - type: "error", - text: "Failed to create integration" - }); - console.error(err); - } + createNotification({ + type: "success", + text: "Successfully created integration" + }); + navigate({ + to: "/projects/secret-management/$projectId/integrations", + params: { + projectId: currentProject.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } + }); }; useEffect(() => { diff --git a/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx index 7df2ae35c..a7ecbca6a 100644 --- a/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx @@ -73,51 +73,43 @@ export const CircleCIConfigurePage = () => { : undefined; const onSubmit = async (data: TFormData) => { - try { - if (data.scope === CircleCiScope.Context) { - await mutateAsync({ - scope: data.scope, - integrationAuthId, - isActive: true, - sourceEnvironment: data.sourceEnvironment.slug, - app: data.targetContext.name, - appId: data.targetContext.id, - owner: data.targetOrg.name, - secretPath: data.secretPath - }); - } else { - await mutateAsync({ - scope: data.scope, - integrationAuthId, - isActive: true, - app: data.targetProject.name, // project name - owner: data.targetOrg.name, // organization name - appId: data.targetProject.id, // project id (used for syncing) - sourceEnvironment: data.sourceEnvironment.slug, - secretPath: data.secretPath - }); - } - - createNotification({ - type: "success", - text: "Successfully created integration" + if (data.scope === CircleCiScope.Context) { + await mutateAsync({ + scope: data.scope, + integrationAuthId, + isActive: true, + sourceEnvironment: data.sourceEnvironment.slug, + app: data.targetContext.name, + appId: data.targetContext.id, + owner: data.targetOrg.name, + secretPath: data.secretPath }); - navigate({ - to: "/projects/secret-management/$projectId/integrations", - params: { - projectId: currentProject.id - }, - search: { - selectedTab: IntegrationsListPageTabs.NativeIntegrations - } + } else { + await mutateAsync({ + scope: data.scope, + integrationAuthId, + isActive: true, + app: data.targetProject.name, // project name + owner: data.targetOrg.name, // organization name + appId: data.targetProject.id, // project id (used for syncing) + sourceEnvironment: data.sourceEnvironment.slug, + secretPath: data.secretPath }); - } catch (err) { - createNotification({ - type: "error", - text: "Failed to create integration" - }); - console.error(err); } + + createNotification({ + type: "success", + text: "Successfully created integration" + }); + navigate({ + to: "/projects/secret-management/$projectId/integrations", + params: { + projectId: currentProject.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } + }); }; if (isCircleCIOrganizationsLoading) diff --git a/frontend/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/CloudflarePagesConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/CloudflarePagesConfigurePage.tsx index 0a0d08863..3948351c3 100644 --- a/frontend/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/CloudflarePagesConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/CloudflarePagesConfigurePage.tsx @@ -1,8 +1,6 @@ import { useEffect, useState } from "react"; import { useNavigate, useSearch } from "@tanstack/react-router"; -import axios from "axios"; -import { createNotification } from "@app/components/notifications"; import { Button, Card, @@ -102,19 +100,7 @@ export const CloudflarePagesConfigurePage = () => { selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); - } catch (err) { - console.error(err); - - let errorMessage: string = "Something went wrong!"; - if (axios.isAxiosError(err)) { - const { message } = err?.response?.data as { message: string }; - errorMessage = message; - } - - createNotification({ - text: errorMessage, - type: "error" - }); + } catch { setIsLoading(false); } }; diff --git a/frontend/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/CloudflareWorkersConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/CloudflareWorkersConfigurePage.tsx index 58bc8596b..417f0fe84 100644 --- a/frontend/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/CloudflareWorkersConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/CloudflareWorkersConfigurePage.tsx @@ -1,8 +1,6 @@ import { useEffect, useState } from "react"; import { useNavigate, useSearch } from "@tanstack/react-router"; -import axios from "axios"; -import { createNotification } from "@app/components/notifications"; import { Button, Card, CardTitle, FormControl, Select, SelectItem } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { ROUTE_PATHS } from "@app/const/routes"; @@ -75,19 +73,7 @@ export const CloudflareWorkersConfigurePage = () => { selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); - } catch (err) { - console.error(err); - - let errorMessage: string = "Something went wrong!"; - if (axios.isAxiosError(err)) { - const { message } = err?.response?.data as { message: string }; - errorMessage = message; - } - - createNotification({ - text: errorMessage, - type: "error" - }); + } catch { setIsLoading(false); } }; diff --git a/frontend/src/pages/secret-manager/integrations/DatabricksConfigurePage/DatabricksConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/DatabricksConfigurePage/DatabricksConfigurePage.tsx index 1f5a3c40a..05c943fc5 100644 --- a/frontend/src/pages/secret-manager/integrations/DatabricksConfigurePage/DatabricksConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/DatabricksConfigurePage/DatabricksConfigurePage.tsx @@ -55,49 +55,45 @@ export const DatabricksConfigurePage = () => { const [secretPath, setSecretPath] = useState("/"); const handleButtonClick = async () => { - try { - if (!integrationAuth?.id) return; + if (!integrationAuth?.id) return; - if (!targetScope) { - createNotification({ - type: "error", - text: "Please select a scope" - }); - return; - } - - const selectedScope = integrationAuthScopes?.find( - (integrationAuthScope) => integrationAuthScope.name === targetScope - ); - - if (!selectedScope) { - createNotification({ - type: "error", - text: "Invalid scope selected" - }); - return; - } - - await mutateAsync({ - integrationAuthId: integrationAuth?.id, - isActive: true, - app: selectedScope.name, // scope name - sourceEnvironment: selectedSourceEnvironment, - secretPath + if (!targetScope) { + createNotification({ + type: "error", + text: "Please select a scope" }); - - navigate({ - to: "/projects/secret-management/$projectId/integrations", - params: { - projectId: currentProject.id - }, - search: { - selectedTab: IntegrationsListPageTabs.NativeIntegrations - } - }); - } catch (err) { - console.error(err); + return; } + + const selectedScope = integrationAuthScopes?.find( + (integrationAuthScope) => integrationAuthScope.name === targetScope + ); + + if (!selectedScope) { + createNotification({ + type: "error", + text: "Invalid scope selected" + }); + return; + } + + await mutateAsync({ + integrationAuthId: integrationAuth?.id, + isActive: true, + app: selectedScope.name, // scope name + sourceEnvironment: selectedSourceEnvironment, + secretPath + }); + + navigate({ + to: "/projects/secret-management/$projectId/integrations", + params: { + projectId: currentProject.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } + }); }; return integrationAuth && selectedSourceEnvironment && integrationAuthScopes ? ( diff --git a/frontend/src/pages/secret-manager/integrations/GithubConfigurePage/GithubConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/GithubConfigurePage/GithubConfigurePage.tsx index 5e9871ade..741c03032 100644 --- a/frontend/src/pages/secret-manager/integrations/GithubConfigurePage/GithubConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/GithubConfigurePage/GithubConfigurePage.tsx @@ -12,12 +12,10 @@ import { import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { useNavigate, useSearch } from "@tanstack/react-router"; -import axios from "axios"; import { motion } from "framer-motion"; import { twMerge } from "tailwind-merge"; import { z, ZodIssueCode } from "zod"; -import { createNotification } from "@app/components/notifications"; import { Button, Card, @@ -275,19 +273,7 @@ export const GithubConfigurePage = () => { selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); - } catch (err) { - console.error(err); - - let errorMessage: string = "Something went wrong!"; - if (axios.isAxiosError(err)) { - const { message } = err?.response?.data as { message: string }; - errorMessage = message; - } - - createNotification({ - text: errorMessage, - type: "error" - }); + } catch { setIsLoading(false); } }; diff --git a/frontend/src/pages/secret-manager/integrations/HashicorpVaultAuthorizePage/HashicorpVaultAuthorizePage.tsx b/frontend/src/pages/secret-manager/integrations/HashicorpVaultAuthorizePage/HashicorpVaultAuthorizePage.tsx index 4b922a9bc..93d892241 100644 --- a/frontend/src/pages/secret-manager/integrations/HashicorpVaultAuthorizePage/HashicorpVaultAuthorizePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/HashicorpVaultAuthorizePage/HashicorpVaultAuthorizePage.tsx @@ -4,10 +4,8 @@ import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-sv import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { useNavigate } from "@tanstack/react-router"; -import axios from "axios"; import { z } from "zod"; -import { createNotification } from "@app/components/notifications"; import { Button, Card, CardBody, CardTitle, FormControl, Input } from "@app/components/v2"; import { useProject } from "@app/context"; import { useSaveIntegrationAccessToken } from "@app/hooks/api"; @@ -41,37 +39,23 @@ export const HashicorpVaultAuthorizePage = () => { }); const handleFormSubmit = async (formData: TForm) => { - try { - const integrationAuth = await mutateAsync({ - workspaceId: currentProject.id, - integration: "hashicorp-vault", - accessId: formData.vaultRoleID, - accessToken: formData.vaultSecretID, - url: formData.vaultURL, - namespace: formData.vaultNamespace - }); - navigate({ - to: "/projects/secret-management/$projectId/integrations/hashicorp-vault/create", - params: { - projectId: currentProject.id - }, - search: { - integrationAuthId: integrationAuth.id - } - }); - } catch (err) { - console.error(err); - let errorMessage: string = "Something went wrong!"; - if (axios.isAxiosError(err)) { - const { message } = err?.response?.data as { message: string }; - errorMessage = message; + const integrationAuth = await mutateAsync({ + workspaceId: currentProject.id, + integration: "hashicorp-vault", + accessId: formData.vaultRoleID, + accessToken: formData.vaultSecretID, + url: formData.vaultURL, + namespace: formData.vaultNamespace + }); + navigate({ + to: "/projects/secret-management/$projectId/integrations/hashicorp-vault/create", + params: { + projectId: currentProject.id + }, + search: { + integrationAuthId: integrationAuth.id } - - createNotification({ - text: errorMessage, - type: "error" - }); - } + }); }; return ( diff --git a/frontend/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/HashicorpVaultConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/HashicorpVaultConfigurePage.tsx index 0dfcbb0bf..0636625df 100644 --- a/frontend/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/HashicorpVaultConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/HashicorpVaultConfigurePage.tsx @@ -10,10 +10,8 @@ import { import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { useNavigate, useSearch } from "@tanstack/react-router"; -import axios from "axios"; import { z } from "zod"; -import { createNotification } from "@app/components/notifications"; import { Button, Card, @@ -90,38 +88,24 @@ export const HashicorpVaultConfigurePage = () => { }); const handleFormSubmit = async (formData: TForm) => { - try { - if (!integrationAuth?.id) return; - await mutateAsync({ - integrationAuthId: integrationAuth?.id, - isActive: true, - app: formData.vaultEnginePath, - sourceEnvironment: formData.selectedSourceEnvironment, - path: formData.vaultSecretPath, - secretPath: formData.secretPath - }); - navigate({ - to: "/projects/secret-management/$projectId/integrations", - params: { - projectId: currentProject.id - }, - search: { - selectedTab: IntegrationsListPageTabs.NativeIntegrations - } - }); - } catch (err) { - console.error(err); - let errorMessage: string = "Something went wrong!"; - if (axios.isAxiosError(err)) { - const { message } = err?.response?.data as { message: string }; - errorMessage = message; + if (!integrationAuth?.id) return; + await mutateAsync({ + integrationAuthId: integrationAuth?.id, + isActive: true, + app: formData.vaultEnginePath, + sourceEnvironment: formData.selectedSourceEnvironment, + path: formData.vaultSecretPath, + secretPath: formData.secretPath + }); + navigate({ + to: "/projects/secret-management/$projectId/integrations", + params: { + projectId: currentProject.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } - - createNotification({ - text: errorMessage, - type: "error" - }); - } + }); }; return integrationAuth ? ( diff --git a/frontend/src/pages/secret-manager/integrations/OctopusDeployAuthorizePage/OctopusDeployAuthorizePage.tsx b/frontend/src/pages/secret-manager/integrations/OctopusDeployAuthorizePage/OctopusDeployAuthorizePage.tsx index 21851e4cd..9e50fd43d 100644 --- a/frontend/src/pages/secret-manager/integrations/OctopusDeployAuthorizePage/OctopusDeployAuthorizePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/OctopusDeployAuthorizePage/OctopusDeployAuthorizePage.tsx @@ -6,7 +6,6 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useNavigate } from "@tanstack/react-router"; import { z } from "zod"; -import { createNotification } from "@app/components/notifications"; import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; import { useProject } from "@app/context"; import { removeTrailingSlash } from "@app/helpers/string"; @@ -29,30 +28,22 @@ export const OctopusDeployAuthorizePage = () => { }); const onSubmit = async ({ instanceUrl, apiKey }: TForm) => { - try { - const integrationAuth = await mutateAsync({ - workspaceId: currentProject.id, - integration: "octopus-deploy", - url: removeTrailingSlash(instanceUrl), - accessToken: apiKey - }); + const integrationAuth = await mutateAsync({ + workspaceId: currentProject.id, + integration: "octopus-deploy", + url: removeTrailingSlash(instanceUrl), + accessToken: apiKey + }); - navigate({ - to: "/projects/secret-management/$projectId/integrations/octopus-deploy/create", - params: { - projectId: currentProject.id - }, - search: { - integrationAuthId: integrationAuth.id - } - }); - } catch (err: any) { - createNotification({ - type: "error", - text: err.message ?? "Error authorizing integration" - }); - console.error(err); - } + navigate({ + to: "/projects/secret-management/$projectId/integrations/octopus-deploy/create", + params: { + projectId: currentProject.id + }, + search: { + integrationAuthId: integrationAuth.id + } + }); }; return ( diff --git a/frontend/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/OctopusDeployConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/OctopusDeployConfigurePage.tsx index 5b531ac2d..75f8f5031 100644 --- a/frontend/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/OctopusDeployConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/OctopusDeployConfigurePage.tsx @@ -107,49 +107,41 @@ export const OctopusDeployConfigurePage = () => { targetRoles, scope }: TFormData) => { - try { - await createIntegration.mutateAsync({ - integrationAuthId, - isActive: true, - scope, - app: targetResource.name, - appId: targetResource.appId, - targetEnvironment: targetSpace.Name, - targetEnvironmentId: targetSpace.Id, - metadata: { - octopusDeployScopeValues: { - Environment: targetEnvironments?.map(({ Id }) => Id), - Action: targetActions?.map(({ Id }) => Id), - Channel: targetChannels?.map(({ Id }) => Id), - ProcessOwner: targetProcesses?.map(({ Id }) => Id), - Role: targetRoles?.map(({ Id }) => Id), - Machine: targetMachines?.map(({ Id }) => Id) - } - }, - sourceEnvironment: sourceEnvironment.slug, - secretPath - }); - - createNotification({ - type: "success", - text: "Successfully created integration" - }); - navigate({ - to: "/projects/secret-management/$projectId/integrations", - params: { - projectId: currentProject.id - }, - search: { - selectedTab: IntegrationsListPageTabs.NativeIntegrations + await createIntegration.mutateAsync({ + integrationAuthId, + isActive: true, + scope, + app: targetResource.name, + appId: targetResource.appId, + targetEnvironment: targetSpace.Name, + targetEnvironmentId: targetSpace.Id, + metadata: { + octopusDeployScopeValues: { + Environment: targetEnvironments?.map(({ Id }) => Id), + Action: targetActions?.map(({ Id }) => Id), + Channel: targetChannels?.map(({ Id }) => Id), + ProcessOwner: targetProcesses?.map(({ Id }) => Id), + Role: targetRoles?.map(({ Id }) => Id), + Machine: targetMachines?.map(({ Id }) => Id) } - }); - } catch (err) { - createNotification({ - type: "error", - text: "Failed to create integration" - }); - console.error(err); - } + }, + sourceEnvironment: sourceEnvironment.slug, + secretPath + }); + + createNotification({ + type: "success", + text: "Successfully created integration" + }); + navigate({ + to: "/projects/secret-management/$projectId/integrations", + params: { + projectId: currentProject.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } + }); }; useEffect(() => { diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceRow.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceRow.tsx index 740cf6d71..b2021bf5c 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceRow.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceRow.tsx @@ -81,24 +81,17 @@ export const SecretScanningResourceRow = ({ resource, dataSource }: Props) => { const navigate = useNavigate(); const handleTriggerScan = async () => { - try { - await triggerDataSourceScan.mutateAsync({ - dataSourceId: dataSource.id, - type: dataSource.type, - projectId: dataSource.projectId, - resourceId: id - }); + await triggerDataSourceScan.mutateAsync({ + dataSourceId: dataSource.id, + type: dataSource.type, + projectId: dataSource.projectId, + resourceId: id + }); - createNotification({ - text: `Successfully triggered scan for ${name}`, - type: "success" - }); - } catch { - createNotification({ - text: `Failed to trigger scan for ${name}`, - type: "error" - }); - } + createNotification({ + text: `Successfully triggered scan for ${name}`, + type: "success" + }); }; const [isIdCopied, setIsIdCopied] = useToggle(false); diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceSection.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceSection.tsx index 5616c956a..1e81357b8 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceSection.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceSection.tsx @@ -22,23 +22,16 @@ export const SecretScanningResourceSection = ({ dataSource }: Props) => { const triggerDataSourceScan = useTriggerSecretScanningDataSource(); const handleTriggerScan = async () => { - try { - await triggerDataSourceScan.mutateAsync({ - dataSourceId: dataSource.id, - type: dataSource.type, - projectId: dataSource.projectId - }); + await triggerDataSourceScan.mutateAsync({ + dataSourceId: dataSource.id, + type: dataSource.type, + projectId: dataSource.projectId + }); - createNotification({ - text: `Successfully triggered scan for ${dataSource.name}`, - type: "success" - }); - } catch { - createNotification({ - text: `Failed to trigger scan for ${dataSource.name}`, - type: "error" - }); - } + createNotification({ + text: `Successfully triggered scan for ${dataSource.name}`, + type: "success" + }); }; const resourceDetails = RESOURCE_DESCRIPTION_HELPER[dataSource.type]; diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesSection.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesSection.tsx index a786d79bc..3ba6009e1 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesSection.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesSection.tsx @@ -84,7 +84,7 @@ export const SecretScanningDataSourcesSection = () => { handlePopUpToggle("upgradePlan", isOpen)} - text="You can create Data Sources by upgrading to Infisical's Enterprise plan." + text="Creating data sources can be unlocked if you upgrade to Infisical Enterprise plan." isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature} /> diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesTable.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesTable.tsx index 8e879acce..ac61c8f54 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesTable.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesTable.tsx @@ -184,44 +184,30 @@ export const SecretScanningDataSourcesTable = ({ dataSources }: Props) => { const isAutoScanEnabled = !dataSource.isAutoScanEnabled; - try { - await updateDataSource.mutateAsync({ - dataSourceId: dataSource.id, - type: dataSource.type, - isAutoScanEnabled, - projectId: dataSource.projectId - }); + await updateDataSource.mutateAsync({ + dataSourceId: dataSource.id, + type: dataSource.type, + isAutoScanEnabled, + projectId: dataSource.projectId + }); - createNotification({ - text: `Successfully ${isAutoScanEnabled ? "enabled" : "disabled"} auto-scan for ${destinationName} Data Source`, - type: "success" - }); - } catch { - createNotification({ - text: `Failed to ${isAutoScanEnabled ? "enable" : "disable"} auto-scan for ${destinationName} Data Source`, - type: "error" - }); - } + createNotification({ + text: `Successfully ${isAutoScanEnabled ? "enabled" : "disabled"} auto-scan for ${destinationName} Data Source`, + type: "success" + }); }; const handleTriggerScan = async (dataSource: TSecretScanningDataSource) => { - try { - await triggerDataSourceScan.mutateAsync({ - dataSourceId: dataSource.id, - type: dataSource.type, - projectId: dataSource.projectId - }); + await triggerDataSourceScan.mutateAsync({ + dataSourceId: dataSource.id, + type: dataSource.type, + projectId: dataSource.projectId + }); - createNotification({ - text: "Successfully triggered scan", - type: "success" - }); - } catch { - createNotification({ - text: "Failed to trigger scan", - type: "error" - }); - } + createNotification({ + text: "Successfully triggered scan", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningUpdateFindingModal.tsx b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningUpdateFindingModal.tsx index 484cbcf36..c4838673b 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningUpdateFindingModal.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningUpdateFindingModal.tsx @@ -55,39 +55,32 @@ const Content = ({ findings, onComplete }: ContentProps) => { const onSubmit = async (data: FormType) => { if (!data.status) return; - try { - if (findings.length > 1) { - await updateMultipleFindings.mutateAsync( - findings.map((f) => ({ - ...data, - status: data.status!, - findingId: f.id, - projectId: f.projectId - })) - ); - } else { - await updateMultipleFindings.mutateAsync([ - { - ...data, - status: data.status, - findingId: findings[0].id, - projectId: findings[0].projectId - } - ]); - } - - createNotification({ - type: "success", - text: `Finding status${single ? "" : "es"} successfully updated` - }); - - onComplete(); - } catch { - createNotification({ - type: "error", - text: `Failed to update finding status${single ? "" : "es"}` - }); + if (findings.length > 1) { + await updateMultipleFindings.mutateAsync( + findings.map((f) => ({ + ...data, + status: data.status!, + findingId: f.id, + projectId: f.projectId + })) + ); + } else { + await updateMultipleFindings.mutateAsync([ + { + ...data, + status: data.status, + findingId: findings[0].id, + projectId: findings[0].projectId + } + ]); } + + createNotification({ + type: "success", + text: `Finding status${single ? "" : "es"} successfully updated` + }); + + onComplete(); }; return ( diff --git a/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectScanningConfigTab/SecretScanningConfigForm.tsx b/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectScanningConfigTab/SecretScanningConfigForm.tsx index c9df333c4..7ff36964d 100644 --- a/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectScanningConfigTab/SecretScanningConfigForm.tsx +++ b/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectScanningConfigTab/SecretScanningConfigForm.tsx @@ -38,22 +38,15 @@ export const SecretScanningConfigForm = ({ config }: Props) => { }); const onSubmit = async ({ content }: FormType) => { - try { - await updateConfig.mutateAsync({ - projectId: config.projectId, - content: content || null - }); + await updateConfig.mutateAsync({ + projectId: config.projectId, + content: content || null + }); - createNotification({ - type: "success", - text: "Configuration successfully updated" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update Configuration" - }); - } + createNotification({ + type: "success", + text: "Configuration successfully updated" + }); }; return ( diff --git a/frontend/src/pages/ssh/SettingsPage/components/ProjectSshTab/components/ProjectSshConfigCasSection.tsx b/frontend/src/pages/ssh/SettingsPage/components/ProjectSshTab/components/ProjectSshConfigCasSection.tsx index 86bdf3c34..aed25d46f 100644 --- a/frontend/src/pages/ssh/SettingsPage/components/ProjectSshTab/components/ProjectSshConfigCasSection.tsx +++ b/frontend/src/pages/ssh/SettingsPage/components/ProjectSshTab/components/ProjectSshConfigCasSection.tsx @@ -47,24 +47,16 @@ export const ProjectSshConfigCasSection = () => { }, [sshConfig]); const onFormSubmit = async ({ defaultUserSshCaId, defaultHostSshCaId }: FormData) => { - try { - await updateProjectSshConfig({ - projectId: currentProject.id, - defaultUserSshCaId: defaultUserSshCaId || undefined, - defaultHostSshCaId: defaultHostSshCaId || undefined - }); + await updateProjectSshConfig({ + projectId: currentProject.id, + defaultUserSshCaId: defaultUserSshCaId || undefined, + defaultHostSshCaId: defaultHostSshCaId || undefined + }); - createNotification({ - text: "Successfully updated SSH project settings", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update SSH project settings", - type: "error" - }); - } + createNotification({ + text: "Successfully updated SSH project settings", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/ssh/SshCaByIDPage/SshCaByIDPage.tsx b/frontend/src/pages/ssh/SshCaByIDPage/SshCaByIDPage.tsx index bb6790a92..41a69a7bd 100644 --- a/frontend/src/pages/ssh/SshCaByIDPage/SshCaByIDPage.tsx +++ b/frontend/src/pages/ssh/SshCaByIDPage/SshCaByIDPage.tsx @@ -43,30 +43,22 @@ const Page = () => { ] as const); const onRemoveCaSubmit = async (caIdToDelete: string) => { - try { - if (!projectId) return; + if (!projectId) return; - await deleteSshCa({ caId: caIdToDelete }); + await deleteSshCa({ caId: caIdToDelete }); - createNotification({ - text: "Successfully deleted SSH CA", - type: "success" - }); + createNotification({ + text: "Successfully deleted SSH CA", + type: "success" + }); - handlePopUpClose("deleteSshCa"); - navigate({ - to: "/projects/ssh/$projectId/overview", - params: { - projectId - } - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete SSH CA", - type: "error" - }); - } + handlePopUpClose("deleteSshCa"); + navigate({ + to: "/projects/ssh/$projectId/overview", + params: { + projectId + } + }); }; return ( diff --git a/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateModal.tsx b/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateModal.tsx index 6e0baa502..32470308a 100644 --- a/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateModal.tsx +++ b/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateModal.tsx @@ -122,65 +122,57 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => { ttl, keyId }: FormData) => { - try { - if (!templateData) return; - if (!projectId) return; + if (!templateData) return; + if (!projectId) return; - switch (operation) { - case SshCertificateOperation.SIGN_SSH_KEY: { - const { serialNumber, signedKey } = await signSshKey({ - projectId, - certificateTemplateId: templateData.id, - publicKey: existingPublicKey, - certType, - principals: principals.split(",").map((user) => user.trim()), - ttl, - keyId - }); + switch (operation) { + case SshCertificateOperation.SIGN_SSH_KEY: { + const { serialNumber, signedKey } = await signSshKey({ + projectId, + certificateTemplateId: templateData.id, + publicKey: existingPublicKey, + certType, + principals: principals.split(",").map((user) => user.trim()), + ttl, + keyId + }); - setCertificateDetails({ - serialNumber, - signedKey - }); - break; - } - case SshCertificateOperation.ISSUE_SSH_CREDS: { - const { serialNumber, publicKey, privateKey, signedKey } = await issueSshCreds({ - projectId, - certificateTemplateId: templateData.id, - keyAlgorithm, - certType, - principals: principals.split(",").map((user) => user.trim()), - ttl, - keyId - }); - - setCertificateDetails({ - serialNumber, - privateKey, - publicKey, - signedKey - }); - break; - } - default: { - break; - } + setCertificateDetails({ + serialNumber, + signedKey + }); + break; } + case SshCertificateOperation.ISSUE_SSH_CREDS: { + const { serialNumber, publicKey, privateKey, signedKey } = await issueSshCreds({ + projectId, + certificateTemplateId: templateData.id, + keyAlgorithm, + certType, + principals: principals.split(",").map((user) => user.trim()), + ttl, + keyId + }); - reset(); - - createNotification({ - text: "Successfully created SSH certificate", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to create SSH certificate", - type: "error" - }); + setCertificateDetails({ + serialNumber, + privateKey, + publicKey, + signedKey + }); + break; + } + default: { + break; + } } + + reset(); + + createNotification({ + text: "Successfully created SSH certificate", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplateModal.tsx b/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplateModal.tsx index 17c7a552e..926a1d022 100644 --- a/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplateModal.tsx +++ b/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplateModal.tsx @@ -138,52 +138,44 @@ export const SshCertificateTemplateModal = ({ popUp, handlePopUpToggle, sshCaId allowedHosts, allowCustomKeyIds }: FormData) => { - try { - if (certTemplate) { - await updateSshCertTemplate({ - id: certTemplate.id, - name, - ttl, - maxTTL, - allowedUsers: allowedUsers ? allowedUsers.split(",").map((user) => user.trim()) : [], - allowedHosts: allowedHosts ? allowedHosts.split(",").map((host) => host.trim()) : [], - allowUserCertificates, - allowHostCertificates, - allowCustomKeyIds - }); + if (certTemplate) { + await updateSshCertTemplate({ + id: certTemplate.id, + name, + ttl, + maxTTL, + allowedUsers: allowedUsers ? allowedUsers.split(",").map((user) => user.trim()) : [], + allowedHosts: allowedHosts ? allowedHosts.split(",").map((host) => host.trim()) : [], + allowUserCertificates, + allowHostCertificates, + allowCustomKeyIds + }); - createNotification({ - text: "Successfully updated SSH certificate template", - type: "success" - }); - } else { - await createSshCertTemplate({ - sshCaId, - name, - ttl, - maxTTL, - allowedUsers: allowedUsers ? allowedUsers.split(",").map((user) => user.trim()) : [], - allowedHosts: allowedHosts ? allowedHosts.split(",").map((host) => host.trim()) : [], - allowUserCertificates, - allowHostCertificates, - allowCustomKeyIds - }); - - createNotification({ - text: "Successfully created SSH certificate template", - type: "success" - }); - } - - reset(); - handlePopUpToggle("sshCertificateTemplate", false); - } catch (err) { - console.error(err); createNotification({ - text: "Failed to save changes", - type: "error" + text: "Successfully updated SSH certificate template", + type: "success" + }); + } else { + await createSshCertTemplate({ + sshCaId, + name, + ttl, + maxTTL, + allowedUsers: allowedUsers ? allowedUsers.split(",").map((user) => user.trim()) : [], + allowedHosts: allowedHosts ? allowedHosts.split(",").map((host) => host.trim()) : [], + allowUserCertificates, + allowHostCertificates, + allowCustomKeyIds + }); + + createNotification({ + text: "Successfully created SSH certificate template", + type: "success" }); } + + reset(); + handlePopUpToggle("sshCertificateTemplate", false); }; return ( diff --git a/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplatesSection.tsx b/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplatesSection.tsx index 792c6ad3b..5b021715f 100644 --- a/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplatesSection.tsx +++ b/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplatesSection.tsx @@ -33,24 +33,16 @@ export const SshCertificateTemplatesSection = ({ caId }: Props) => { const { mutateAsync: updateSshCertTemplate } = useUpdateSshCertTemplate(); const onRemoveSshCertificateTemplateSubmit = async (id: string) => { - try { - await deleteSshCertTemplate({ - id - }); + await deleteSshCertTemplate({ + id + }); - await createNotification({ - text: "Successfully deleted SSH certificate template", - type: "success" - }); + createNotification({ + text: "Successfully deleted SSH certificate template", + type: "success" + }); - handlePopUpClose("deleteSshCertificateTemplate"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete SSH certificate template", - type: "error" - }); - } + handlePopUpClose("deleteSshCertificateTemplate"); }; const onUpdateSshCaStatus = async ({ @@ -60,26 +52,16 @@ export const SshCertificateTemplatesSection = ({ caId }: Props) => { templateId: string; status: SshCertTemplateStatus; }) => { - try { - await updateSshCertTemplate({ id: templateId, status }); + await updateSshCertTemplate({ id: templateId, status }); - await createNotification({ - text: `Successfully ${ - status === SshCertTemplateStatus.ACTIVE ? "enabled" : "disabled" - } SSH certificate template`, - type: "success" - }); + createNotification({ + text: `Successfully ${ + status === SshCertTemplateStatus.ACTIVE ? "enabled" : "disabled" + } SSH certificate template`, + type: "success" + }); - handlePopUpClose("sshCertificateTemplateStatus"); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${ - status === SshCertTemplateStatus.ACTIVE ? "enabled" : "disabled" - } SSH certificate template`, - type: "error" - }); - } + handlePopUpClose("sshCertificateTemplateStatus"); }; return ( diff --git a/frontend/src/pages/ssh/SshCasPage/components/SshCaModal.tsx b/frontend/src/pages/ssh/SshCasPage/components/SshCaModal.tsx index 4c8ce4e19..53e0298ff 100644 --- a/frontend/src/pages/ssh/SshCasPage/components/SshCaModal.tsx +++ b/frontend/src/pages/ssh/SshCasPage/components/SshCaModal.tsx @@ -106,47 +106,39 @@ export const SshCaModal = ({ popUp, handlePopUpToggle }: Props) => { publicKey, privateKey }: FormData) => { - try { - if (!projectId) return; + if (!projectId) return; - if (ca) { - await updateMutateAsync({ - caId: ca.id, - friendlyName - }); - } else { - const { id: newCaId } = await createMutateAsync({ - projectId, - friendlyName, - keySource, - keyAlgorithm, - publicKey, - privateKey - }); - - navigate({ - to: "/projects/ssh/$projectId/ca/$caId", - params: { - projectId, - caId: newCaId - } - }); - } - - reset(); - handlePopUpToggle("sshCa", false); - - createNotification({ - text: `Successfully ${ca ? "updated" : "created"} SSH CA`, - type: "success" + if (ca) { + await updateMutateAsync({ + caId: ca.id, + friendlyName }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${ca ? "update" : "create"} SSH CA`, - type: "error" + } else { + const { id: newCaId } = await createMutateAsync({ + projectId, + friendlyName, + keySource, + keyAlgorithm, + publicKey, + privateKey + }); + + navigate({ + to: "/projects/ssh/$projectId/ca/$caId", + params: { + projectId, + caId: newCaId + } }); } + + reset(); + handlePopUpToggle("sshCa", false); + + createNotification({ + text: `Successfully ${ca ? "updated" : "created"} SSH CA`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/ssh/SshCasPage/components/SshCaSection.tsx b/frontend/src/pages/ssh/SshCasPage/components/SshCaSection.tsx index b1d24e8f3..11942795c 100644 --- a/frontend/src/pages/ssh/SshCasPage/components/SshCaSection.tsx +++ b/frontend/src/pages/ssh/SshCasPage/components/SshCaSection.tsx @@ -18,46 +18,29 @@ export const SshCaSection = () => { const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "sshCa", "deleteSshCa", - "sshCaStatus", // enable / disable - "upgradePlan" + "sshCaStatus" // enable / disable ] as const); const onRemoveSshCaSubmit = async (caId: string) => { - try { - await deleteSshCa({ caId }); + await deleteSshCa({ caId }); - createNotification({ - text: "Successfully deleted SSH CA", - type: "success" - }); + createNotification({ + text: "Successfully deleted SSH CA", + type: "success" + }); - handlePopUpClose("deleteSshCa"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete SSH CA", - type: "error" - }); - } + handlePopUpClose("deleteSshCa"); }; const onUpdateSshCaStatus = async ({ caId, status }: { caId: string; status: SshCaStatus }) => { - try { - await updateSshCa({ caId, status }); + await updateSshCa({ caId, status }); - createNotification({ - text: `Successfully ${status === SshCaStatus.ACTIVE ? "enabled" : "disabled"} SSH CA`, - type: "success" - }); + createNotification({ + text: `Successfully ${status === SshCaStatus.ACTIVE ? "enabled" : "disabled"} SSH CA`, + type: "success" + }); - handlePopUpClose("sshCaStatus"); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${status === SshCaStatus.ACTIVE ? "enabled" : "disabled"} SSH CA`, - type: "error" - }); - } + handlePopUpClose("sshCaStatus"); }; return ( @@ -110,11 +93,6 @@ export const SshCaSection = () => { onUpdateSshCaStatus(popUp?.sshCaStatus?.data as { caId: string; status: SshCaStatus }) } /> - {/* handlePopUpToggle("upgradePlan", isOpen)} - text={(popUp.upgradePlan?.data as { description: string })?.description} - /> */} ); }; diff --git a/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/SshHostGroupDetailsByIDPage.tsx b/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/SshHostGroupDetailsByIDPage.tsx index d093003ac..ffb596ac2 100644 --- a/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/SshHostGroupDetailsByIDPage.tsx +++ b/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/SshHostGroupDetailsByIDPage.tsx @@ -44,30 +44,22 @@ const Page = () => { ] as const); const onRemoveSshGroupSubmit = async (groupIdToDelete: string) => { - try { - if (!projectId) return; + if (!projectId) return; - await deleteSshHostGroup({ sshHostGroupId: groupIdToDelete }); + await deleteSshHostGroup({ sshHostGroupId: groupIdToDelete }); - createNotification({ - text: "Successfully deleted SSH group", - type: "success" - }); + createNotification({ + text: "Successfully deleted SSH group", + type: "success" + }); - handlePopUpClose("deleteSshHostGroup"); - navigate({ - to: "/projects/ssh/$projectId/overview", - params: { - projectId - } - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete SSH group", - type: "error" - }); - } + handlePopUpClose("deleteSshHostGroup"); + navigate({ + to: "/projects/ssh/$projectId/overview", + params: { + projectId + } + }); }; return ( diff --git a/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/components/AddHostGroupMemberModal.tsx b/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/components/AddHostGroupMemberModal.tsx index 2374492a5..e45266fa0 100644 --- a/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/components/AddHostGroupMemberModal.tsx +++ b/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/components/AddHostGroupMemberModal.tsx @@ -42,30 +42,23 @@ export const AddHostGroupMemberModal = ({ popUp, handlePopUpToggle }: Props) => useAddHostToSshHostGroup(); const handleAddHost = async (sshHostId: string) => { - try { - if (!popUpData?.sshHostGroupId) { - createNotification({ - text: "Some data is missing, please refresh the page and try again", - type: "error" - }); - return; - } - - await addHostToSshHostGroup({ - sshHostGroupId: popUpData.sshHostGroupId, - sshHostId - }); - + if (!popUpData?.sshHostGroupId) { createNotification({ - text: "Successfully added host to the group", - type: "success" - }); - } catch { - createNotification({ - text: "Failed to add host to the group", + text: "Some data is missing, please refresh the page and try again", type: "error" }); + return; } + + await addHostToSshHostGroup({ + sshHostGroupId: popUpData.sshHostGroupId, + sshHostId + }); + + createNotification({ + text: "Successfully added host to the group", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/components/SshHostGroupHostsSection.tsx b/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/components/SshHostGroupHostsSection.tsx index df6f2b723..189ec6e56 100644 --- a/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/components/SshHostGroupHostsSection.tsx +++ b/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/components/SshHostGroupHostsSection.tsx @@ -29,8 +29,7 @@ export const SshHostGroupHostsSection = ({ sshHostGroupId }: Props) => { const handleAddSshHostModal = () => { if (!subscription?.sshHostGroups) { handlePopUpOpen("upgradePlan", { - description: - "You can manage hosts more efficiently with SSH host groups if you upgrade your Infisical plan to an Enterprise license.", + text: "Managing SSH host groups can be unlocked if you upgrade to Infisical Enterprise plan.", isEnterpriseFeature: true }); } else { @@ -41,25 +40,17 @@ export const SshHostGroupHostsSection = ({ sshHostGroupId }: Props) => { }; const onRemoveSshHostSubmit = async (sshHostId: string) => { - try { - await removeHostFromGroup({ - sshHostId, - sshHostGroupId - }); + await removeHostFromGroup({ + sshHostId, + sshHostGroupId + }); - await createNotification({ - text: "Successfully removed host from SSH group", - type: "success" - }); + createNotification({ + text: "Successfully removed host from SSH group", + type: "success" + }); - handlePopUpClose("removeHostFromSshHostGroup"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to remove host from SSH group", - type: "error" - }); - } + handlePopUpClose("removeHostFromSshHostGroup"); }; return ( @@ -107,10 +98,8 @@ export const SshHostGroupHostsSection = ({ sshHostGroupId }: Props) => { handlePopUpToggle("upgradePlan", isOpen)} - text={(popUp.upgradePlan?.data as { description: string })?.description} - isEnterpriseFeature={ - (popUp.upgradePlan?.data as { isEnterpriseFeature: boolean })?.isEnterpriseFeature - } + text={popUp.upgradePlan?.data?.text} + isEnterpriseFeature={popUp.upgradePlan?.data?.isEnterpriseFeature} /> ); diff --git a/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupModal.tsx b/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupModal.tsx index 6e65dd532..d7411f889 100644 --- a/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupModal.tsx +++ b/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupModal.tsx @@ -121,67 +121,59 @@ export const SshHostGroupModal = ({ popUp, handlePopUpToggle }: Props) => { }, [sshHostGroup]); const onFormSubmit = async ({ name, loginMappings }: FormData) => { - try { - if (!projectId) return; + if (!projectId) return; - // check if there is already a different host group with the same name - const existingNames = - sshHostGroups?.filter((h) => h.id !== sshHostGroup?.id).map((h) => h.name) || []; - - if (existingNames.includes(name.trim())) { - createNotification({ - text: "A host group with this name already exists.", - type: "error" - }); - return; - } - - const transformedLoginMappings = loginMappings.map(({ loginUser, allowedPrincipals }) => { - const usernames = allowedPrincipals - .filter((p) => p.type === "user" && p.value) - .map((p) => p.value); - - const groupNames = allowedPrincipals - .filter((p) => p.type === "group" && p.value) - .map((p) => p.value); - - return { - loginUser, - allowedPrincipals: { - usernames, - groups: groupNames - } - }; - }); - - if (sshHostGroup) { - await updateMutateAsync({ - sshHostGroupId: sshHostGroup.id, - name, - loginMappings: transformedLoginMappings - }); - } else { - await createMutateAsync({ - projectId, - name, - loginMappings: transformedLoginMappings - }); - } - - reset(); - handlePopUpToggle("sshHostGroup", false); + // check if there is already a different host group with the same name + const existingNames = + sshHostGroups?.filter((h) => h.id !== sshHostGroup?.id).map((h) => h.name) || []; + if (existingNames.includes(name.trim())) { createNotification({ - text: `Successfully ${sshHostGroup ? "updated" : "created"} SSH host group`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${sshHostGroup ? "update" : "create"} SSH host group`, + text: "A host group with this name already exists.", type: "error" }); + return; } + + const transformedLoginMappings = loginMappings.map(({ loginUser, allowedPrincipals }) => { + const usernames = allowedPrincipals + .filter((p) => p.type === "user" && p.value) + .map((p) => p.value); + + const groupNames = allowedPrincipals + .filter((p) => p.type === "group" && p.value) + .map((p) => p.value); + + return { + loginUser, + allowedPrincipals: { + usernames, + groups: groupNames + } + }; + }); + + if (sshHostGroup) { + await updateMutateAsync({ + sshHostGroupId: sshHostGroup.id, + name, + loginMappings: transformedLoginMappings + }); + } else { + await createMutateAsync({ + projectId, + name, + loginMappings: transformedLoginMappings + }); + } + + reset(); + handlePopUpToggle("sshHostGroup", false); + + createNotification({ + text: `Successfully ${sshHostGroup ? "updated" : "created"} SSH host group`, + type: "success" + }); }; const toggleMapping = (index: number) => { diff --git a/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupsSection.tsx b/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupsSection.tsx index 189a49686..5febe22b7 100644 --- a/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupsSection.tsx +++ b/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupsSection.tsx @@ -25,8 +25,7 @@ export const SshHostGroupsSection = () => { const handleAddSshHostGroupModal = () => { if (!subscription?.sshHostGroups) { handlePopUpOpen("upgradePlan", { - description: - "You can manage hosts more efficiently with SSH host groups if you upgrade your Infisical plan to an Enterprise license.", + text: "Managing SSH host groups can be unlocked if you upgrade to Infisical Enterprise plan.", isEnterpriseFeature: true }); } else { @@ -35,22 +34,14 @@ export const SshHostGroupsSection = () => { }; const onRemoveSshHostGroupSubmit = async (sshHostGroupId: string) => { - try { - const hostGroup = await deleteSshHostGroup({ sshHostGroupId }); + const hostGroup = await deleteSshHostGroup({ sshHostGroupId }); - createNotification({ - text: `Successfully deleted SSH host group: ${hostGroup.name}`, - type: "success" - }); + createNotification({ + text: `Successfully deleted SSH host group: ${hostGroup.name}`, + type: "success" + }); - handlePopUpClose("deleteSshHostGroup"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete SSH host group", - type: "error" - }); - } + handlePopUpClose("deleteSshHostGroup"); }; return ( @@ -106,10 +97,8 @@ export const SshHostGroupsSection = () => { handlePopUpToggle("upgradePlan", isOpen)} - text={(popUp.upgradePlan?.data as { description: string })?.description} - isEnterpriseFeature={ - (popUp.upgradePlan?.data as { isEnterpriseFeature: boolean })?.isEnterpriseFeature - } + text={popUp.upgradePlan?.data?.text} + isEnterpriseFeature={popUp.upgradePlan?.data?.isEnterpriseFeature} /> ); diff --git a/frontend/src/pages/ssh/SshHostsPage/components/SshHostModal.tsx b/frontend/src/pages/ssh/SshHostsPage/components/SshHostModal.tsx index e9b0d870a..afcde908f 100644 --- a/frontend/src/pages/ssh/SshHostsPage/components/SshHostModal.tsx +++ b/frontend/src/pages/ssh/SshHostsPage/components/SshHostModal.tsx @@ -140,93 +140,84 @@ export const SshHostModal = ({ popUp, handlePopUpToggle }: Props) => { }, [sshHost]); const onFormSubmit = async ({ hostname, alias, userCertTtl, loginMappings }: FormData) => { - try { - if (!projectId) return; + if (!projectId) return; - // Filter out login mappings that are from host groups - const hostLoginMappings = loginMappings.filter( - (mapping) => mapping.source === LoginMappingSource.HOST - ); + // Filter out login mappings that are from host groups + const hostLoginMappings = loginMappings.filter( + (mapping) => mapping.source === LoginMappingSource.HOST + ); - // check if there is already a different host with the same hostname - const existingHostnames = - sshHosts?.filter((h) => h.id !== sshHost?.id).map((h) => h.hostname) || []; + // check if there is already a different host with the same hostname + const existingHostnames = + sshHosts?.filter((h) => h.id !== sshHost?.id).map((h) => h.hostname) || []; - if (existingHostnames.includes(hostname.trim())) { + if (existingHostnames.includes(hostname.trim())) { + createNotification({ + text: "A host with this hostname already exists.", + type: "error" + }); + return; + } + + const trimmedAlias = alias.trim(); + + // check if there is already a different host with the same non-null alias + if (trimmedAlias) { + const existingAliases = + sshHosts?.filter((h) => h.id !== sshHost?.id && h.alias !== null).map((h) => h.alias) || []; + + if (existingAliases.includes(trimmedAlias)) { createNotification({ - text: "A host with this hostname already exists.", + text: "A host with this alias already exists.", type: "error" }); return; } + } - const trimmedAlias = alias.trim(); + const transformedLoginMappings = hostLoginMappings.map(({ loginUser, allowedPrincipals }) => { + const usernames = allowedPrincipals + .filter((p) => p.type === "user" && p.value) + .map((p) => p.value); - // check if there is already a different host with the same non-null alias - if (trimmedAlias) { - const existingAliases = - sshHosts?.filter((h) => h.id !== sshHost?.id && h.alias !== null).map((h) => h.alias) || - []; + const groupNames = allowedPrincipals + .filter((p) => p.type === "group" && p.value) + .map((p) => p.value); - if (existingAliases.includes(trimmedAlias)) { - createNotification({ - text: "A host with this alias already exists.", - type: "error" - }); - return; + return { + loginUser, + allowedPrincipals: { + usernames, + groups: groupNames } - } + }; + }); - const transformedLoginMappings = hostLoginMappings.map(({ loginUser, allowedPrincipals }) => { - const usernames = allowedPrincipals - .filter((p) => p.type === "user" && p.value) - .map((p) => p.value); - - const groupNames = allowedPrincipals - .filter((p) => p.type === "group" && p.value) - .map((p) => p.value); - - return { - loginUser, - allowedPrincipals: { - usernames, - groups: groupNames - } - }; + if (sshHost) { + await updateMutateAsync({ + sshHostId: sshHost.id, + hostname, + alias: trimmedAlias, + userCertTtl, + loginMappings: transformedLoginMappings }); - - if (sshHost) { - await updateMutateAsync({ - sshHostId: sshHost.id, - hostname, - alias: trimmedAlias, - userCertTtl, - loginMappings: transformedLoginMappings - }); - } else { - await createMutateAsync({ - projectId, - hostname, - alias: trimmedAlias, - userCertTtl, - loginMappings: transformedLoginMappings - }); - } - - reset(); - handlePopUpToggle("sshHost", false); - - createNotification({ - text: `Successfully ${sshHost ? "updated" : "added"} SSH host`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${sshHost ? "update" : "add"} SSH host`, - type: "error" + } else { + await createMutateAsync({ + projectId, + hostname, + alias: trimmedAlias, + userCertTtl, + loginMappings: transformedLoginMappings }); } + + reset(); + handlePopUpToggle("sshHost", false); + + createNotification({ + text: `Successfully ${sshHost ? "updated" : "added"} SSH host`, + type: "success" + }); }; const toggleMapping = (index: number) => { diff --git a/frontend/src/pages/ssh/SshHostsPage/components/SshHostsSection.tsx b/frontend/src/pages/ssh/SshHostsPage/components/SshHostsSection.tsx index 0f57cbeb2..53e8062ab 100644 --- a/frontend/src/pages/ssh/SshHostsPage/components/SshHostsSection.tsx +++ b/frontend/src/pages/ssh/SshHostsPage/components/SshHostsSection.tsx @@ -20,22 +20,14 @@ export const SshHostsSection = () => { ] as const); const onRemoveSshHostSubmit = async (sshHostId: string) => { - try { - const host = await deleteSshHost({ sshHostId }); + const host = await deleteSshHost({ sshHostId }); - createNotification({ - text: `Successfully deleted SSH host: ${host.hostname}`, - type: "success" - }); + createNotification({ + text: `Successfully deleted SSH host: ${host.hostname}`, + type: "success" + }); - handlePopUpClose("deleteSshHost"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete SSH host", - type: "error" - }); - } + handlePopUpClose("deleteSshHost"); }; return ( diff --git a/frontend/src/pages/user/PersonalSettingsPage/components/APIKeySection/APIKeyTable.tsx b/frontend/src/pages/user/PersonalSettingsPage/components/APIKeySection/APIKeyTable.tsx index 6fe6afd43..cf2960ada 100644 --- a/frontend/src/pages/user/PersonalSettingsPage/components/APIKeySection/APIKeyTable.tsx +++ b/frontend/src/pages/user/PersonalSettingsPage/components/APIKeySection/APIKeyTable.tsx @@ -22,19 +22,11 @@ export const APIKeyTable = () => { const { mutateAsync } = useDeleteAPIKey(); const handleDeleteAPIKeyDataClick = async (apiKeyDataId: string) => { - try { - await mutateAsync(apiKeyDataId); - createNotification({ - text: "Successfully deleted API key", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete API key", - type: "error" - }); - } + await mutateAsync(apiKeyDataId); + createNotification({ + text: "Successfully deleted API key", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/user/PersonalSettingsPage/components/APIKeySection/AddAPIKeyModal.tsx b/frontend/src/pages/user/PersonalSettingsPage/components/APIKeySection/AddAPIKeyModal.tsx index 213479b47..f28c39339 100644 --- a/frontend/src/pages/user/PersonalSettingsPage/components/APIKeySection/AddAPIKeyModal.tsx +++ b/frontend/src/pages/user/PersonalSettingsPage/components/APIKeySection/AddAPIKeyModal.tsx @@ -76,27 +76,19 @@ export const AddAPIKeyModal = ({ popUp, handlePopUpToggle }: Props) => { }; const onFormSubmit = async ({ name, expiresIn }: FormData) => { - try { - const { apiKey } = await mutateAsync({ - name, - expiresIn: expirationMapping[expiresIn] - }); + const { apiKey } = await mutateAsync({ + name, + expiresIn: expirationMapping[expiresIn] + }); - setNewAPIKey(apiKey); + setNewAPIKey(apiKey); - createNotification({ - text: "Successfully created API key", - type: "success" - }); + createNotification({ + text: "Successfully created API key", + type: "success" + }); - reset(); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to create API key", - type: "error" - }); - } + reset(); }; const hasAPIKey = Boolean(newAPIKey); diff --git a/frontend/src/pages/user/PersonalSettingsPage/components/ChangeEmailSection/ChangeEmailSection.tsx b/frontend/src/pages/user/PersonalSettingsPage/components/ChangeEmailSection/ChangeEmailSection.tsx index c9d457e44..bd37e5620 100644 --- a/frontend/src/pages/user/PersonalSettingsPage/components/ChangeEmailSection/ChangeEmailSection.tsx +++ b/frontend/src/pages/user/PersonalSettingsPage/components/ChangeEmailSection/ChangeEmailSection.tsx @@ -83,23 +83,14 @@ export const ChangeEmailSection = () => { return; } - try { - await requestEmailChangeOTP({ newEmail }); - setPendingEmail(newEmail); - setIsOTPModalOpen(true); + await requestEmailChangeOTP({ newEmail }); + setPendingEmail(newEmail); + setIsOTPModalOpen(true); - createNotification({ - text: "Verification code sent to your new email address. Check your inbox!", - type: "success" - }); - } catch (err: any) { - console.error(err); - const errorMessage = err?.response?.data?.message || "Failed to send verification code"; - createNotification({ - text: errorMessage, - type: "error" - }); - } + createNotification({ + text: "Verification code sent to your new email address. Check your inbox!", + type: "success" + }); }; const [typedOTP, setTypedOTP] = useState(""); @@ -135,8 +126,6 @@ export const ChangeEmailSection = () => { navigate({ to: "/login" }); }, 2000); } catch (err: any) { - console.error(err); - const errorMessage = err?.response?.data?.message || "Invalid verification code"; if (errorMessage.includes("Invalid verification code")) { // Reset to email step so user must request new OTP @@ -149,11 +138,6 @@ export const ChangeEmailSection = () => { text: "Invalid verification code. Please request a new one.", type: "error" }); - } else { - createNotification({ - text: errorMessage, - type: "error" - }); } } }; diff --git a/frontend/src/pages/user/PersonalSettingsPage/components/ChangePasswordSection/ChangePasswordSection.tsx b/frontend/src/pages/user/PersonalSettingsPage/components/ChangePasswordSection/ChangePasswordSection.tsx index 552eab741..1ae492162 100644 --- a/frontend/src/pages/user/PersonalSettingsPage/components/ChangePasswordSection/ChangePasswordSection.tsx +++ b/frontend/src/pages/user/PersonalSettingsPage/components/ChangePasswordSection/ChangePasswordSection.tsx @@ -97,21 +97,13 @@ export const ChangePasswordSection = () => { }; const onSetupPassword = async () => { - try { - await sendSetupPasswordEmail.mutateAsync(); + await sendSetupPasswordEmail.mutateAsync(); - createNotification({ - title: "Password setup verification email sent", - text: "Check your email to confirm password setup", - type: "info" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to send password setup email", - type: "error" - }); - } + createNotification({ + title: "Password setup verification email sent", + text: "Check your email to confirm password setup", + type: "info" + }); }; return ( diff --git a/frontend/src/pages/user/PersonalSettingsPage/components/DeleteAccountSection/DeleteAccountSection.tsx b/frontend/src/pages/user/PersonalSettingsPage/components/DeleteAccountSection/DeleteAccountSection.tsx index 5761a772a..301942346 100644 --- a/frontend/src/pages/user/PersonalSettingsPage/components/DeleteAccountSection/DeleteAccountSection.tsx +++ b/frontend/src/pages/user/PersonalSettingsPage/components/DeleteAccountSection/DeleteAccountSection.tsx @@ -15,23 +15,15 @@ export const DeleteAccountSection = () => { const { mutateAsync: deleteUserMutateAsync, isPending } = useDeleteMe(); const handleDeleteAccountSubmit = async () => { - try { - await deleteUserMutateAsync(); + await deleteUserMutateAsync(); - createNotification({ - text: "Successfully deleted account", - type: "success" - }); + createNotification({ + text: "Successfully deleted account", + type: "success" + }); - navigate({ to: "/login" }); - handlePopUpClose("deleteAccount"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete account", - type: "error" - }); - } + navigate({ to: "/login" }); + handlePopUpClose("deleteAccount"); }; return ( diff --git a/frontend/src/pages/user/PersonalSettingsPage/components/SecuritySection/MFASection.tsx b/frontend/src/pages/user/PersonalSettingsPage/components/SecuritySection/MFASection.tsx index ef29f3ff6..74f4ed99b 100644 --- a/frontend/src/pages/user/PersonalSettingsPage/components/SecuritySection/MFASection.tsx +++ b/frontend/src/pages/user/PersonalSettingsPage/components/SecuritySection/MFASection.tsx @@ -84,49 +84,27 @@ export const MFASection = () => { }, [totpRegistration, showMobileAuthSetup]); const handleTotpDeletion = async () => { - try { - await deleteTotpConfiguration(); + await deleteTotpConfiguration(); - await mutateAsync({ - selectedMfaMethod: MfaMethod.EMAIL - }); + await mutateAsync({ + selectedMfaMethod: MfaMethod.EMAIL + }); - createNotification({ - text: "Successfully deleted mobile authenticator and switched to email authentication", - type: "success" - }); + createNotification({ + text: "Successfully deleted mobile authenticator and switched to email authentication", + type: "success" + }); - handlePopUpClose("deleteTotpConfig"); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to delete mobile authenticator"; - - createNotification({ - text, - type: "error" - }); - } + handlePopUpClose("deleteTotpConfig"); }; const handleGenerateMoreRecoveryCodes = async () => { - try { - await createTotpRecoveryCodes(); + await createTotpRecoveryCodes(); - createNotification({ - text: "Successfully generated new recovery codes", - type: "success" - }); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to generate new recovery codes"; - - createNotification({ - text, - type: "error" - }); - } + createNotification({ + text: "Successfully generated new recovery codes", + type: "success" + }); }; const handleFormDataChange = async (field: string, value: any) => { @@ -200,10 +178,6 @@ export const MFASection = () => { await queryClient.invalidateQueries({ queryKey: userKeys.totpConfiguration }); } catch { - createNotification({ - text: "Failed to verify TOTP code. Please try again.", - type: "error" - }); setIsLoading(false); return; } @@ -249,12 +223,6 @@ export const MFASection = () => { setShowMobileAuthSetup(false); setTotpCode(""); setShouldShowRecoveryCodes.off(); - } catch (err) { - createNotification({ - text: "Something went wrong while updating two-factor authentication settings.", - type: "error" - }); - console.error(err); } finally { setIsLoading(false); } diff --git a/frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx b/frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx index 027f2ed46..f37be42a1 100644 --- a/frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx +++ b/frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx @@ -40,19 +40,11 @@ export const SessionsTable = () => { ] as const); const handleSignOut = async (sessionId: string) => { - try { - await revokeMySessionById(sessionId); - createNotification({ - text: "Session revoked successfully", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to revoke session", - type: "error" - }); - } + await revokeMySessionById(sessionId); + createNotification({ + text: "Session revoked successfully", + type: "success" + }); handlePopUpClose("deleteSession"); }; diff --git a/frontend/src/pages/user/PersonalSettingsPage/components/UserNameSection/UserNameSection.tsx b/frontend/src/pages/user/PersonalSettingsPage/components/UserNameSection/UserNameSection.tsx index df0dae9cb..1e06d298f 100644 --- a/frontend/src/pages/user/PersonalSettingsPage/components/UserNameSection/UserNameSection.tsx +++ b/frontend/src/pages/user/PersonalSettingsPage/components/UserNameSection/UserNameSection.tsx @@ -27,22 +27,14 @@ export const UserNameSection = (): JSX.Element => { }, [user]); const onFormSubmit = async ({ name }: FormData) => { - try { - if (!user?.id) return; - if (name === "") return; + if (!user?.id) return; + if (name === "") return; - await mutateAsync({ newName: name }); - createNotification({ - text: "Successfully renamed user", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to rename user", - type: "error" - }); - } + await mutateAsync({ newName: name }); + createNotification({ + text: "Successfully renamed user", + type: "success" + }); }; return ( diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 34c4153d6..4c6558db9 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -114,7 +114,6 @@ import { Route as kmsOverviewPageRouteImport } from './pages/kms/OverviewPage/ro import { Route as kmsKmipPageRouteImport } from './pages/kms/KmipPage/route' import { Route as certManagerSettingsPageRouteImport } from './pages/cert-manager/SettingsPage/route' import { Route as certManagerPoliciesPageRouteImport } from './pages/cert-manager/PoliciesPage/route' -import { Route as certManagerCertificatesPageRouteImport } from './pages/cert-manager/CertificatesPage/route' import { Route as certManagerCertificateAuthoritiesPageRouteImport } from './pages/cert-manager/CertificateAuthoritiesPage/route' import { Route as certManagerAlertingPageRouteImport } from './pages/cert-manager/AlertingPage/route' import { Route as organizationAppConnectionsOauthCallbackPageRouteImport } from './pages/organization/AppConnections/OauthCallbackPage/route' @@ -1203,13 +1202,6 @@ const certManagerPoliciesPageRouteRoute = getParentRoute: () => certManagerLayoutRoute, } as any) -const certManagerCertificatesPageRouteRoute = - certManagerCertificatesPageRouteImport.update({ - id: '/certificates', - path: '/certificates', - getParentRoute: () => certManagerLayoutRoute, - } as any) - const certManagerCertificateAuthoritiesPageRouteRoute = certManagerCertificateAuthoritiesPageRouteImport.update({ id: '/certificate-authorities', @@ -2786,13 +2778,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof certManagerCertificateAuthoritiesPageRouteImport parentRoute: typeof certManagerLayoutImport } - '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificates': { - id: '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificates' - path: '/certificates' - fullPath: '/projects/cert-management/$projectId/certificates' - preLoaderRoute: typeof certManagerCertificatesPageRouteImport - parentRoute: typeof certManagerLayoutImport - } '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/policies': { id: '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/policies' path: '/policies' @@ -4126,7 +4111,6 @@ const AuthenticateInjectOrgDetailsOrgLayoutProjectsCertManagementProjectIdCertMa interface certManagerLayoutRouteChildren { certManagerAlertingPageRouteRoute: typeof certManagerAlertingPageRouteRoute certManagerCertificateAuthoritiesPageRouteRoute: typeof certManagerCertificateAuthoritiesPageRouteRoute - certManagerCertificatesPageRouteRoute: typeof certManagerCertificatesPageRouteRoute certManagerPoliciesPageRouteRoute: typeof certManagerPoliciesPageRouteRoute certManagerSettingsPageRouteRoute: typeof certManagerSettingsPageRouteRoute projectAccessControlPageRouteCertManagerRoute: typeof projectAccessControlPageRouteCertManagerRoute @@ -4147,7 +4131,6 @@ const certManagerLayoutRouteChildren: certManagerLayoutRouteChildren = { certManagerAlertingPageRouteRoute: certManagerAlertingPageRouteRoute, certManagerCertificateAuthoritiesPageRouteRoute: certManagerCertificateAuthoritiesPageRouteRoute, - certManagerCertificatesPageRouteRoute: certManagerCertificatesPageRouteRoute, certManagerPoliciesPageRouteRoute: certManagerPoliciesPageRouteRoute, certManagerSettingsPageRouteRoute: certManagerSettingsPageRouteRoute, projectAccessControlPageRouteCertManagerRoute: @@ -5061,7 +5044,6 @@ export interface FileRoutesByFullPath { '/organization/app-connections/$appConnection/oauth/callback': typeof organizationAppConnectionsOauthCallbackPageRouteRoute '/projects/cert-management/$projectId/alerting': typeof certManagerAlertingPageRouteRoute '/projects/cert-management/$projectId/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute - '/projects/cert-management/$projectId/certificates': typeof certManagerCertificatesPageRouteRoute '/projects/cert-management/$projectId/policies': typeof certManagerPoliciesPageRouteRoute '/projects/cert-management/$projectId/settings': typeof certManagerSettingsPageRouteRoute '/projects/kms/$projectId/kmip': typeof kmsKmipPageRouteRoute @@ -5294,7 +5276,6 @@ export interface FileRoutesByTo { '/organization/app-connections/$appConnection/oauth/callback': typeof organizationAppConnectionsOauthCallbackPageRouteRoute '/projects/cert-management/$projectId/alerting': typeof certManagerAlertingPageRouteRoute '/projects/cert-management/$projectId/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute - '/projects/cert-management/$projectId/certificates': typeof certManagerCertificatesPageRouteRoute '/projects/cert-management/$projectId/policies': typeof certManagerPoliciesPageRouteRoute '/projects/cert-management/$projectId/settings': typeof certManagerSettingsPageRouteRoute '/projects/kms/$projectId/kmip': typeof kmsKmipPageRouteRoute @@ -5538,7 +5519,6 @@ export interface FileRoutesById { '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback': typeof organizationAppConnectionsOauthCallbackPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/alerting': typeof certManagerAlertingPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-authorities': typeof certManagerCertificateAuthoritiesPageRouteRoute - '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificates': typeof certManagerCertificatesPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/policies': typeof certManagerPoliciesPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/settings': typeof certManagerSettingsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/kmip': typeof kmsKmipPageRouteRoute @@ -5780,7 +5760,6 @@ export interface FileRouteTypes { | '/organization/app-connections/$appConnection/oauth/callback' | '/projects/cert-management/$projectId/alerting' | '/projects/cert-management/$projectId/certificate-authorities' - | '/projects/cert-management/$projectId/certificates' | '/projects/cert-management/$projectId/policies' | '/projects/cert-management/$projectId/settings' | '/projects/kms/$projectId/kmip' @@ -6012,7 +5991,6 @@ export interface FileRouteTypes { | '/organization/app-connections/$appConnection/oauth/callback' | '/projects/cert-management/$projectId/alerting' | '/projects/cert-management/$projectId/certificate-authorities' - | '/projects/cert-management/$projectId/certificates' | '/projects/cert-management/$projectId/policies' | '/projects/cert-management/$projectId/settings' | '/projects/kms/$projectId/kmip' @@ -6254,7 +6232,6 @@ export interface FileRouteTypes { | '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback' | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/alerting' | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-authorities' - | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificates' | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/policies' | '/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/settings' | '/_authenticate/_inject-org-details/_org-layout/projects/kms/$projectId/_kms-layout/kmip' @@ -6890,7 +6867,6 @@ export const routeTree = rootRoute "children": [ "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/alerting", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificate-authorities", - "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificates", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/policies", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/settings", "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/access-management", @@ -7005,10 +6981,6 @@ export const routeTree = rootRoute "filePath": "cert-manager/CertificateAuthoritiesPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout" }, - "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/certificates": { - "filePath": "cert-manager/CertificatesPage/route.tsx", - "parent": "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout" - }, "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/policies": { "filePath": "cert-manager/PoliciesPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout" diff --git a/frontend/src/routes.ts b/frontend/src/routes.ts index 16bdaf10f..cc37f3031 100644 --- a/frontend/src/routes.ts +++ b/frontend/src/routes.ts @@ -304,7 +304,6 @@ const certManagerRoutes = route("/projects/cert-management/$projectId", [ route("/$subscriberName", "cert-manager/PkiSubscriberDetailsByIDPage/route.tsx") ]), route("/certificate-templates", [index("cert-manager/PkiTemplateListPage/route.tsx")]), - route("/certificates", "cert-manager/CertificatesPage/route.tsx"), route("/certificate-authorities", "cert-manager/CertificateAuthoritiesPage/route.tsx"), route("/alerting", "cert-manager/AlertingPage/route.tsx"), route("/ca/$caName", "cert-manager/CertAuthDetailsByIDPage/route.tsx"),