app connection + finishing touches

This commit is contained in:
x032205
2025-05-20 13:25:15 -04:00
parent 966294bd0e
commit 1efdb31037
31 changed files with 319 additions and 66 deletions
@@ -1,16 +1,16 @@
import z from "zod"; import z from "zod";
import { readLimit } 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 { import {
CreateOCIConnectionSchema, CreateOCIConnectionSchema,
SanitizedOCIConnectionSchema, SanitizedOCIConnectionSchema,
UpdateOCIConnectionSchema UpdateOCIConnectionSchema
} from "@app/services/app-connection/oci"; } from "@app/ee/services/app-connections/oci";
import { readLimit } 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 { AuthMode } from "@app/services/auth/auth-type";
import { registerAppConnectionEndpoints } from "./app-connection-endpoints"; import { registerAppConnectionEndpoints } from "../../../../server/routes/v1/app-connection-routers/app-connection-endpoints";
export const registerOCIConnectionRouter = async (server: FastifyZodProvider) => { export const registerOCIConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({ registerAppConnectionEndpoints({
@@ -1,7 +1,9 @@
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { OrgServiceActor } from "@app/lib/types"; import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums"; import { AppConnection } from "../../../../services/app-connection/app-connection-enums";
import { TLicenseServiceFactory } from "../../license/license-service";
import { listOCICompartments, listOCIVaultKeys, listOCIVaults } from "./oci-connection-fns"; import { listOCICompartments, listOCIVaultKeys, listOCIVaults } from "./oci-connection-fns";
import { TOCIConnection } from "./oci-connection-types"; import { TOCIConnection } from "./oci-connection-types";
@@ -22,8 +24,23 @@ type TListOCIVaultKeysDTO = {
vaultOcid: string; vaultOcid: string;
}; };
export const ociConnectionService = (getAppConnection: TGetAppConnectionFunc) => { // Enterprise check
export const checkPlan = async (licenseService: Pick<TLicenseServiceFactory, "getPlan">, 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 ociConnectionService = (
getAppConnection: TGetAppConnectionFunc,
licenseService: Pick<TLicenseServiceFactory, "getPlan">
) => {
const listCompartments = async (connectionId: string, actor: OrgServiceActor) => { const listCompartments = async (connectionId: string, actor: OrgServiceActor) => {
await checkPlan(licenseService, actor.orgId);
const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor); const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor);
try { try {
@@ -36,6 +53,8 @@ export const ociConnectionService = (getAppConnection: TGetAppConnectionFunc) =>
}; };
const listVaults = async ({ connectionId, compartmentOcid }: TListOCIVaultsDTO, actor: OrgServiceActor) => { const listVaults = async ({ connectionId, compartmentOcid }: TListOCIVaultsDTO, actor: OrgServiceActor) => {
await checkPlan(licenseService, actor.orgId);
const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor); const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor);
try { try {
@@ -51,6 +70,8 @@ export const ociConnectionService = (getAppConnection: TGetAppConnectionFunc) =>
{ connectionId, compartmentOcid, vaultOcid }: TListOCIVaultKeysDTO, { connectionId, compartmentOcid, vaultOcid }: TListOCIVaultKeysDTO,
actor: OrgServiceActor actor: OrgServiceActor
) => { ) => {
await checkPlan(licenseService, actor.orgId);
const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor); const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor);
try { try {
@@ -2,7 +2,7 @@ import z from "zod";
import { DiscriminativePick } from "@app/lib/types"; import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums"; import { AppConnection } from "../../../../services/app-connection/app-connection-enums";
import { import {
CreateOCIConnectionSchema, CreateOCIConnectionSchema,
OCIConnectionSchema, OCIConnectionSchema,
@@ -29,7 +29,9 @@ export const getDefaultOnPremFeatures = () => {
secretApproval: true, secretApproval: true,
secretRotation: true, secretRotation: true,
caCrl: false, caCrl: false,
sshHostGroups: false sshHostGroups: false,
enterpriseSecretSyncs: false,
enterpriseAppConnections: false
}; };
}; };
@@ -55,7 +55,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
kmip: false, kmip: false,
gateway: false, gateway: false,
sshHostGroups: false, sshHostGroups: false,
enterpriseSecretSyncs: false enterpriseSecretSyncs: false,
enterpriseAppConnections: false
}); });
export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => { export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
@@ -73,6 +73,7 @@ export type TFeatureSet = {
gateway: false; gateway: false;
sshHostGroups: false; sshHostGroups: false;
enterpriseSecretSyncs: false; enterpriseSecretSyncs: false;
enterpriseAppConnections: false;
}; };
export type TOrgPlansTableDTO = { export type TOrgPlansTableDTO = {
@@ -1,5 +1,6 @@
import { secrets, vault } from "oci-sdk"; import { secrets, vault } from "oci-sdk";
import { getOCIProvider } from "@app/ee/services/app-connections/oci";
import { import {
TCreateOCIVaultVariable, TCreateOCIVaultVariable,
TDeleteOCIVaultVariable, TDeleteOCIVaultVariable,
@@ -9,7 +10,6 @@ import {
TUpdateOCIVaultVariable TUpdateOCIVaultVariable
} from "@app/ee/services/secret-sync/oci-vault/oci-vault-sync-types"; } from "@app/ee/services/secret-sync/oci-vault/oci-vault-sync-types";
import { delay } from "@app/lib/delay"; import { delay } from "@app/lib/delay";
import { getOCIProvider } from "@app/services/app-connection/oci";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns"; import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
@@ -1,7 +1,7 @@
import { SimpleAuthenticationDetailsProvider } from "oci-sdk"; import { SimpleAuthenticationDetailsProvider } from "oci-sdk";
import { z } from "zod"; import { z } from "zod";
import { TOCIConnection } from "@app/services/app-connection/oci"; import { TOCIConnection } from "@app/ee/services/app-connections/oci";
import { CreateOCIVaultSyncSchema, OCIVaultSyncListItemSchema, OCIVaultSyncSchema } from "./oci-vault-sync-schemas"; import { CreateOCIVaultSyncSchema, OCIVaultSyncListItemSchema, OCIVaultSyncSchema } from "./oci-vault-sync-schemas";
+6 -3
View File
@@ -1015,7 +1015,8 @@ export const registerRoutes = async (
secretVersionV2BridgeDAL, secretVersionV2BridgeDAL,
secretVersionTagV2BridgeDAL, secretVersionTagV2BridgeDAL,
resourceMetadataDAL, resourceMetadataDAL,
appConnectionDAL appConnectionDAL,
licenseService
}); });
const secretQueueService = secretQueueFactory({ const secretQueueService = secretQueueFactory({
@@ -1632,7 +1633,8 @@ export const registerRoutes = async (
const appConnectionService = appConnectionServiceFactory({ const appConnectionService = appConnectionServiceFactory({
appConnectionDAL, appConnectionDAL,
permissionService, permissionService,
kmsService kmsService,
licenseService
}); });
const secretSyncService = secretSyncServiceFactory({ const secretSyncService = secretSyncServiceFactory({
@@ -1643,7 +1645,8 @@ export const registerRoutes = async (
folderDAL, folderDAL,
secretSyncQueue, secretSyncQueue,
projectBotService, projectBotService,
keyStore keyStore,
licenseService
}); });
const kmipService = kmipServiceFactory({ const kmipService = kmipServiceFactory({
@@ -1,5 +1,6 @@
import { z } from "zod"; import { z } from "zod";
import { OCIConnectionListItemSchema, SanitizedOCIConnectionSchema } from "@app/ee/services/app-connections/oci";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags } from "@app/lib/api-docs"; import { ApiDocsTags } from "@app/lib/api-docs";
import { readLimit } from "@app/server/config/rateLimiter"; import { readLimit } from "@app/server/config/rateLimiter";
@@ -38,7 +39,6 @@ import {
} from "@app/services/app-connection/humanitec"; } from "@app/services/app-connection/humanitec";
import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap"; import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap";
import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql"; import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql";
import { OCIConnectionListItemSchema, SanitizedOCIConnectionSchema } from "@app/services/app-connection/oci";
import { import {
PostgresConnectionListItemSchema, PostgresConnectionListItemSchema,
SanitizedPostgresConnectionSchema SanitizedPostgresConnectionSchema
@@ -1,5 +1,6 @@
import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { registerOCIConnectionRouter } from "../../../../ee/routes/v1/app-connection-routers/oci-connection-router";
import { registerAuth0ConnectionRouter } from "./auth0-connection-router"; import { registerAuth0ConnectionRouter } from "./auth0-connection-router";
import { registerAwsConnectionRouter } from "./aws-connection-router"; import { registerAwsConnectionRouter } from "./aws-connection-router";
import { registerAzureAppConfigurationConnectionRouter } from "./azure-app-configuration-connection-router"; import { registerAzureAppConfigurationConnectionRouter } from "./azure-app-configuration-connection-router";
@@ -13,7 +14,6 @@ import { registerHCVaultConnectionRouter } from "./hc-vault-connection-router";
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router"; import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
import { registerLdapConnectionRouter } from "./ldap-connection-router"; import { registerLdapConnectionRouter } from "./ldap-connection-router";
import { registerMsSqlConnectionRouter } from "./mssql-connection-router"; import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
import { registerOCIConnectionRouter } from "./oci-connection-router";
import { registerPostgresConnectionRouter } from "./postgres-connection-router"; import { registerPostgresConnectionRouter } from "./postgres-connection-router";
import { registerTeamCityConnectionRouter } from "./teamcity-connection-router"; import { registerTeamCityConnectionRouter } from "./teamcity-connection-router";
import { registerTerraformCloudConnectionRouter } from "./terraform-cloud-router"; import { registerTerraformCloudConnectionRouter } from "./terraform-cloud-router";
@@ -66,3 +66,8 @@ export enum AWSRegion {
// South America // South America
SA_EAST_1 = "sa-east-1" // Sao Paulo SA_EAST_1 = "sa-east-1" // Sao Paulo
} }
export enum AppConnectionPlanType {
Enterprise = "enterprise",
Regular = "regular"
}
@@ -8,6 +8,11 @@ import {
} from "@app/services/app-connection/shared/sql"; } from "@app/services/app-connection/shared/sql";
import { KmsDataKey } from "@app/services/kms/kms-types"; import { KmsDataKey } from "@app/services/kms/kms-types";
import {
getOCIConnectionListItem,
OCIConnectionMethod,
validateOCIConnectionCredentials
} from "../../ee/services/app-connections/oci";
import { AppConnection } from "./app-connection-enums"; import { AppConnection } from "./app-connection-enums";
import { TAppConnectionServiceFactoryDep } from "./app-connection-service"; import { TAppConnectionServiceFactoryDep } from "./app-connection-service";
import { import {
@@ -53,7 +58,6 @@ import {
} from "./humanitec"; } from "./humanitec";
import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnectionCredentials } from "./ldap"; import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnectionCredentials } from "./ldap";
import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql"; import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql";
import { getOCIConnectionListItem, OCIConnectionMethod, validateOCIConnectionCredentials } from "./oci";
import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres"; import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres";
import { import {
getTeamCityConnectionListItem, getTeamCityConnectionListItem,
@@ -1,4 +1,4 @@
import { AppConnection } from "./app-connection-enums"; import { AppConnection, AppConnectionPlanType } from "./app-connection-enums";
export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = { export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.AWS]: "AWS", [AppConnection.AWS]: "AWS",
@@ -21,3 +21,25 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.TeamCity]: "TeamCity", [AppConnection.TeamCity]: "TeamCity",
[AppConnection.OCI]: "OCI" [AppConnection.OCI]: "OCI"
}; };
export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanType> = {
[AppConnection.AWS]: AppConnectionPlanType.Regular,
[AppConnection.GitHub]: AppConnectionPlanType.Regular,
[AppConnection.GCP]: AppConnectionPlanType.Regular,
[AppConnection.AzureKeyVault]: AppConnectionPlanType.Regular,
[AppConnection.AzureAppConfiguration]: AppConnectionPlanType.Regular,
[AppConnection.AzureClientSecrets]: AppConnectionPlanType.Regular,
[AppConnection.Databricks]: AppConnectionPlanType.Regular,
[AppConnection.Humanitec]: AppConnectionPlanType.Regular,
[AppConnection.TerraformCloud]: AppConnectionPlanType.Regular,
[AppConnection.Vercel]: AppConnectionPlanType.Regular,
[AppConnection.Postgres]: AppConnectionPlanType.Regular,
[AppConnection.MsSql]: AppConnectionPlanType.Regular,
[AppConnection.Camunda]: AppConnectionPlanType.Regular,
[AppConnection.Windmill]: AppConnectionPlanType.Regular,
[AppConnection.Auth0]: AppConnectionPlanType.Regular,
[AppConnection.HCVault]: AppConnectionPlanType.Regular,
[AppConnection.LDAP]: AppConnectionPlanType.Regular,
[AppConnection.TeamCity]: AppConnectionPlanType.Regular,
[AppConnection.OCI]: AppConnectionPlanType.Enterprise
};
@@ -1,5 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionAppConnectionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionAppConnectionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { generateHash } from "@app/lib/crypto/encryption"; import { generateHash } from "@app/lib/crypto/encryption";
@@ -17,9 +18,11 @@ import {
import { auth0ConnectionService } from "@app/services/app-connection/auth0/auth0-connection-service"; import { auth0ConnectionService } from "@app/services/app-connection/auth0/auth0-connection-service";
import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { ValidateOCIConnectionCredentialsSchema } from "../../ee/services/app-connections/oci";
import { ociConnectionService } from "../../ee/services/app-connections/oci/oci-connection-service";
import { TAppConnectionDALFactory } from "./app-connection-dal"; import { TAppConnectionDALFactory } from "./app-connection-dal";
import { AppConnection } from "./app-connection-enums"; import { AppConnection, AppConnectionPlanType } from "./app-connection-enums";
import { APP_CONNECTION_NAME_MAP } from "./app-connection-maps"; import { APP_CONNECTION_NAME_MAP, APP_CONNECTION_PLAN_MAP } from "./app-connection-maps";
import { import {
TAppConnection, TAppConnection,
TAppConnectionConfig, TAppConnectionConfig,
@@ -49,8 +52,6 @@ import { ValidateHumanitecConnectionCredentialsSchema } from "./humanitec";
import { humanitecConnectionService } from "./humanitec/humanitec-connection-service"; import { humanitecConnectionService } from "./humanitec/humanitec-connection-service";
import { ValidateLdapConnectionCredentialsSchema } from "./ldap"; import { ValidateLdapConnectionCredentialsSchema } from "./ldap";
import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql"; import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql";
import { ValidateOCIConnectionCredentialsSchema } from "./oci";
import { ociConnectionService } from "./oci/oci-connection-service";
import { ValidatePostgresConnectionCredentialsSchema } from "./postgres"; import { ValidatePostgresConnectionCredentialsSchema } from "./postgres";
import { ValidateTeamCityConnectionCredentialsSchema } from "./teamcity"; import { ValidateTeamCityConnectionCredentialsSchema } from "./teamcity";
import { teamcityConnectionService } from "./teamcity/teamcity-connection-service"; import { teamcityConnectionService } from "./teamcity/teamcity-connection-service";
@@ -65,6 +66,7 @@ export type TAppConnectionServiceFactoryDep = {
appConnectionDAL: TAppConnectionDALFactory; appConnectionDAL: TAppConnectionDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">; kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
}; };
export type TAppConnectionServiceFactory = ReturnType<typeof appConnectionServiceFactory>; export type TAppConnectionServiceFactory = ReturnType<typeof appConnectionServiceFactory>;
@@ -94,7 +96,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
export const appConnectionServiceFactory = ({ export const appConnectionServiceFactory = ({
appConnectionDAL, appConnectionDAL,
permissionService, permissionService,
kmsService kmsService,
licenseService
}: TAppConnectionServiceFactoryDep) => { }: TAppConnectionServiceFactoryDep) => {
const listAppConnectionsByOrg = async (actor: OrgServiceActor, app?: AppConnection) => { const listAppConnectionsByOrg = async (actor: OrgServiceActor, app?: AppConnection) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission(
@@ -191,6 +194,16 @@ export const appConnectionServiceFactory = ({
OrgPermissionSubjects.AppConnections OrgPermissionSubjects.AppConnections
); );
// Enterprise check
if (APP_CONNECTION_PLAN_MAP[app] === AppConnectionPlanType.Enterprise) {
const plan = await licenseService.getPlan(actor.orgId);
if (!plan.enterpriseAppConnections)
throw new BadRequestError({
message:
"Failed to create app connection due to plan restriction. Upgrade plan to access enterprise app connections."
});
}
const validatedCredentials = await validateAppConnectionCredentials({ const validatedCredentials = await validateAppConnectionCredentials({
app, app,
credentials, credentials,
@@ -253,6 +266,16 @@ export const appConnectionServiceFactory = ({
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` }); if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
// Enterprise check
if (APP_CONNECTION_PLAN_MAP[appConnection.app as AppConnection] === AppConnectionPlanType.Enterprise) {
const plan = await licenseService.getPlan(actor.orgId);
if (!plan.enterpriseAppConnections)
throw new BadRequestError({
message:
"Failed to update app connection due to plan restriction. Upgrade plan to access enterprise app connections."
});
}
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission(
actor.type, actor.type,
actor.id, actor.id,
@@ -399,6 +422,16 @@ export const appConnectionServiceFactory = ({
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` }); if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
// Enterprise check
if (APP_CONNECTION_PLAN_MAP[app] === AppConnectionPlanType.Enterprise) {
const plan = await licenseService.getPlan(actor.orgId);
if (!plan.enterpriseAppConnections)
throw new BadRequestError({
message:
"Failed to connect app connection due to plan restriction. Upgrade plan to access enterprise app connections."
});
}
const { permission: orgPermission } = await permissionService.getOrgPermission( const { permission: orgPermission } = await permissionService.getOrgPermission(
actor.type, actor.type,
actor.id, actor.id,
@@ -468,6 +501,6 @@ export const appConnectionServiceFactory = ({
hcvault: hcVaultConnectionService(connectAppConnectionById), hcvault: hcVaultConnectionService(connectAppConnectionById),
windmill: windmillConnectionService(connectAppConnectionById), windmill: windmillConnectionService(connectAppConnectionById),
teamcity: teamcityConnectionService(connectAppConnectionById), teamcity: teamcityConnectionService(connectAppConnectionById),
oci: ociConnectionService(connectAppConnectionById) oci: ociConnectionService(connectAppConnectionById, licenseService)
}; };
}; };
@@ -2,6 +2,12 @@ import { TAppConnectionDALFactory } from "@app/services/app-connection/app-conne
import { TSqlConnectionConfig } from "@app/services/app-connection/shared/sql/sql-connection-types"; import { TSqlConnectionConfig } from "@app/services/app-connection/shared/sql/sql-connection-types";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import {
TOCIConnection,
TOCIConnectionConfig,
TOCIConnectionInput,
TValidateOCIConnectionCredentialsSchema
} from "../../ee/services/app-connections/oci";
import { AWSRegion } from "./app-connection-enums"; import { AWSRegion } from "./app-connection-enums";
import { import {
TAuth0Connection, TAuth0Connection,
@@ -76,12 +82,6 @@ import {
TValidateLdapConnectionCredentialsSchema TValidateLdapConnectionCredentialsSchema
} from "./ldap"; } from "./ldap";
import { TMsSqlConnection, TMsSqlConnectionInput, TValidateMsSqlConnectionCredentialsSchema } from "./mssql"; import { TMsSqlConnection, TMsSqlConnectionInput, TValidateMsSqlConnectionCredentialsSchema } from "./mssql";
import {
TOCIConnection,
TOCIConnectionConfig,
TOCIConnectionInput,
TValidateOCIConnectionCredentialsSchema
} from "./oci";
import { import {
TPostgresConnection, TPostgresConnection,
TPostgresConnectionInput, TPostgresConnectionInput,
@@ -26,3 +26,8 @@ export enum SecretSyncImportBehavior {
PrioritizeSource = "prioritize-source", PrioritizeSource = "prioritize-source",
PrioritizeDestination = "prioritize-destination" PrioritizeDestination = "prioritize-destination"
} }
export enum SecretSyncPlanType {
Enterprise = "enterprise",
Regular = "regular"
}
@@ -1,5 +1,5 @@
import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; import { SecretSync, SecretSyncPlanType } from "@app/services/secret-sync/secret-sync-enums";
export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = { export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
[SecretSync.AWSParameterStore]: "AWS Parameter Store", [SecretSync.AWSParameterStore]: "AWS Parameter Store",
@@ -36,3 +36,21 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
[SecretSync.TeamCity]: AppConnection.TeamCity, [SecretSync.TeamCity]: AppConnection.TeamCity,
[SecretSync.OCIVault]: AppConnection.OCI [SecretSync.OCIVault]: AppConnection.OCI
}; };
export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
[SecretSync.AWSParameterStore]: SecretSyncPlanType.Regular,
[SecretSync.AWSSecretsManager]: SecretSyncPlanType.Regular,
[SecretSync.GitHub]: SecretSyncPlanType.Regular,
[SecretSync.GCPSecretManager]: SecretSyncPlanType.Regular,
[SecretSync.AzureKeyVault]: SecretSyncPlanType.Regular,
[SecretSync.AzureAppConfiguration]: SecretSyncPlanType.Regular,
[SecretSync.Databricks]: SecretSyncPlanType.Regular,
[SecretSync.Humanitec]: SecretSyncPlanType.Regular,
[SecretSync.TerraformCloud]: SecretSyncPlanType.Regular,
[SecretSync.Camunda]: SecretSyncPlanType.Regular,
[SecretSync.Vercel]: SecretSyncPlanType.Regular,
[SecretSync.Windmill]: SecretSyncPlanType.Regular,
[SecretSync.HCVault]: SecretSyncPlanType.Regular,
[SecretSync.TeamCity]: SecretSyncPlanType.Regular,
[SecretSync.OCIVault]: SecretSyncPlanType.Enterprise
};
@@ -5,8 +5,10 @@ import { Job } from "bullmq";
import { ProjectMembershipRole, SecretType } from "@app/db/schemas"; import { ProjectMembershipRole, SecretType } from "@app/db/schemas";
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service"; import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } 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"; import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
import { decryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns"; import { decryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns";
@@ -29,11 +31,12 @@ import { TSecretSyncDALFactory } from "@app/services/secret-sync/secret-sync-dal
import { import {
SecretSync, SecretSync,
SecretSyncImportBehavior, SecretSyncImportBehavior,
SecretSyncInitialSyncBehavior SecretSyncInitialSyncBehavior,
SecretSyncPlanType
} from "@app/services/secret-sync/secret-sync-enums"; } from "@app/services/secret-sync/secret-sync-enums";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { parseSyncErrorMessage, SecretSyncFns } from "@app/services/secret-sync/secret-sync-fns"; import { parseSyncErrorMessage, SecretSyncFns } from "@app/services/secret-sync/secret-sync-fns";
import { SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps"; import { SECRET_SYNC_NAME_MAP, SECRET_SYNC_PLAN_MAP } from "@app/services/secret-sync/secret-sync-maps";
import { import {
SecretSyncAction, SecretSyncAction,
SecretSyncStatus, SecretSyncStatus,
@@ -93,6 +96,7 @@ type TSecretSyncQueueFactoryDep = {
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">; secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">; secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">; resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
}; };
type SecretSyncActionJob = Job< type SecretSyncActionJob = Job<
@@ -133,7 +137,8 @@ export const secretSyncQueueFactory = ({
secretVersionTagDAL, secretVersionTagDAL,
secretVersionV2BridgeDAL, secretVersionV2BridgeDAL,
secretVersionTagV2BridgeDAL, secretVersionTagV2BridgeDAL,
resourceMetadataDAL resourceMetadataDAL,
licenseService
}: TSecretSyncQueueFactoryDep) => { }: TSecretSyncQueueFactoryDep) => {
const appCfg = getConfig(); const appCfg = getConfig();
@@ -323,7 +328,22 @@ export const secretSyncQueueFactory = ({
secretSync: TSecretSyncWithCredentials, secretSync: TSecretSyncWithCredentials,
importBehavior: SecretSyncImportBehavior importBehavior: SecretSyncImportBehavior
): Promise<TSecretMap> => { ): Promise<TSecretMap> => {
const { projectId, environment, folder } = secretSync; const {
projectId,
environment,
folder,
destination,
connection: { orgId }
} = secretSync;
// Enterprise Check
if (SECRET_SYNC_PLAN_MAP[destination] === SecretSyncPlanType.Enterprise) {
const plan = await licenseService.getPlan(orgId);
if (!plan.enterpriseSecretSyncs)
throw new BadRequestError({
message: "Failed to import secrets due to plan restriction. Upgrade plan to access enterprise secret syncs."
});
}
if (!environment || !folder) if (!environment || !folder)
throw new Error( throw new Error(
@@ -400,6 +420,15 @@ export const secretSyncQueueFactory = ({
if (!secretSync) throw new Error(`Cannot find secret sync with ID ${syncId}`); if (!secretSync) throw new Error(`Cannot find secret sync with ID ${syncId}`);
// Enterprise Check
if (SECRET_SYNC_PLAN_MAP[secretSync.destination as SecretSync] === SecretSyncPlanType.Enterprise) {
const plan = await licenseService.getPlan(secretSync.connection.orgId);
if (!plan.enterpriseSecretSyncs)
throw new BadRequestError({
message: "Failed to sync secrets due to plan restriction. Upgrade plan to access enterprise secret syncs."
});
}
await secretSyncDAL.updateById(syncId, { await secretSyncDAL.updateById(syncId, {
syncStatus: SecretSyncStatus.Running syncStatus: SecretSyncStatus.Running
}); });
@@ -659,6 +688,16 @@ export const secretSyncQueueFactory = ({
if (!secretSync) throw new Error(`Cannot find secret sync with ID ${syncId}`); if (!secretSync) throw new Error(`Cannot find secret sync with ID ${syncId}`);
// Enterprise Check
if (SECRET_SYNC_PLAN_MAP[secretSync.destination as SecretSync] === SecretSyncPlanType.Enterprise) {
const plan = await licenseService.getPlan(secretSync.connection.orgId);
if (!plan.enterpriseSecretSyncs)
throw new BadRequestError({
message:
"Failed to access secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs."
});
}
await secretSyncDAL.updateById(syncId, { await secretSyncDAL.updateById(syncId, {
removeStatus: SecretSyncStatus.Running removeStatus: SecretSyncStatus.Running
}); });
@@ -1,6 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas"; import { ActionProjectType } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { throwIfMissingSecretReadValueOrDescribePermission } from "@app/ee/services/permission/permission-fns"; import { throwIfMissingSecretReadValueOrDescribePermission } from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { import {
@@ -15,7 +16,7 @@ import { OrgServiceActor } from "@app/lib/types";
import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service"; import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service";
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; import { SecretSync, SecretSyncPlanType } from "@app/services/secret-sync/secret-sync-enums";
import { listSecretSyncOptions } from "@app/services/secret-sync/secret-sync-fns"; import { listSecretSyncOptions } from "@app/services/secret-sync/secret-sync-fns";
import { import {
SecretSyncStatus, SecretSyncStatus,
@@ -34,7 +35,7 @@ import {
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal"; import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
import { TSecretSyncDALFactory } from "./secret-sync-dal"; import { TSecretSyncDALFactory } from "./secret-sync-dal";
import { SECRET_SYNC_CONNECTION_MAP, SECRET_SYNC_NAME_MAP } from "./secret-sync-maps"; import { SECRET_SYNC_CONNECTION_MAP, SECRET_SYNC_NAME_MAP, SECRET_SYNC_PLAN_MAP } from "./secret-sync-maps";
import { TSecretSyncQueueFactory } from "./secret-sync-queue"; import { TSecretSyncQueueFactory } from "./secret-sync-queue";
type TSecretSyncServiceFactoryDep = { type TSecretSyncServiceFactoryDep = {
@@ -49,6 +50,7 @@ type TSecretSyncServiceFactoryDep = {
TSecretSyncQueueFactory, TSecretSyncQueueFactory,
"queueSecretSyncSyncSecretsById" | "queueSecretSyncImportSecretsById" | "queueSecretSyncRemoveSecretsById" "queueSecretSyncSyncSecretsById" | "queueSecretSyncImportSecretsById" | "queueSecretSyncRemoveSecretsById"
>; >;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
}; };
export type TSecretSyncServiceFactory = ReturnType<typeof secretSyncServiceFactory>; export type TSecretSyncServiceFactory = ReturnType<typeof secretSyncServiceFactory>;
@@ -61,7 +63,8 @@ export const secretSyncServiceFactory = ({
appConnectionService, appConnectionService,
projectBotService, projectBotService,
secretSyncQueue, secretSyncQueue,
keyStore keyStore,
licenseService
}: TSecretSyncServiceFactoryDep) => { }: TSecretSyncServiceFactoryDep) => {
const listSecretSyncsByProjectId = async ( const listSecretSyncsByProjectId = async (
{ projectId, destination }: TListSecretSyncsByProjectId, { projectId, destination }: TListSecretSyncsByProjectId,
@@ -191,6 +194,16 @@ export const secretSyncServiceFactory = ({
{ projectId, secretPath, environment, ...params }: TCreateSecretSyncDTO, { projectId, secretPath, environment, ...params }: TCreateSecretSyncDTO,
actor: OrgServiceActor actor: OrgServiceActor
) => { ) => {
// Enterprise check
if (SECRET_SYNC_PLAN_MAP[params.destination] === SecretSyncPlanType.Enterprise) {
const plan = await licenseService.getPlan(actor.orgId);
if (!plan.enterpriseSecretSyncs)
throw new BadRequestError({
message:
"Failed to create secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs."
});
}
const { permission: projectPermission } = await permissionService.getProjectPermission({ const { permission: projectPermission } = await permissionService.getProjectPermission({
actor: actor.type, actor: actor.type,
actorId: actor.id, actorId: actor.id,
@@ -260,6 +273,16 @@ export const secretSyncServiceFactory = ({
message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID ${syncId}` message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID ${syncId}`
}); });
// Enterprise check
if (SECRET_SYNC_PLAN_MAP[secretSync.destination as SecretSync] === SecretSyncPlanType.Enterprise) {
const plan = await licenseService.getPlan(actor.orgId);
if (!plan.enterpriseSecretSyncs)
throw new BadRequestError({
message:
"Failed to update secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs."
});
}
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor: actor.type, actor: actor.type,
actorId: actor.id, actorId: actor.id,
@@ -408,6 +431,16 @@ export const secretSyncServiceFactory = ({
message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"` message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"`
}); });
// Enterprise check
if (SECRET_SYNC_PLAN_MAP[secretSync.destination as SecretSync] === SecretSyncPlanType.Enterprise) {
const plan = await licenseService.getPlan(actor.orgId);
if (!plan.enterpriseSecretSyncs)
throw new BadRequestError({
message:
"Failed to trigger secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs."
});
}
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor: actor.type, actor: actor.type,
actorId: actor.id, actorId: actor.id,
@@ -463,6 +496,16 @@ export const secretSyncServiceFactory = ({
message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"` message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"`
}); });
// Enterprise check
if (SECRET_SYNC_PLAN_MAP[secretSync.destination as SecretSync] === SecretSyncPlanType.Enterprise) {
const plan = await licenseService.getPlan(actor.orgId);
if (!plan.enterpriseSecretSyncs)
throw new BadRequestError({
message:
"Failed to trigger secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs."
});
}
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor: actor.type, actor: actor.type,
actorId: actor.id, actorId: actor.id,
@@ -512,6 +555,16 @@ export const secretSyncServiceFactory = ({
message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"` message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"`
}); });
// Enterprise check
if (SECRET_SYNC_PLAN_MAP[secretSync.destination as SecretSync] === SecretSyncPlanType.Enterprise) {
const plan = await licenseService.getPlan(actor.orgId);
if (!plan.enterpriseSecretSyncs)
throw new BadRequestError({
message:
"Failed to trigger secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs."
});
}
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor: actor.type, actor: actor.type,
actorId: actor.id, actorId: actor.id,
@@ -3,6 +3,13 @@ title: "OCI Connection"
description: "Learn how to configure an Oracle Cloud Infrastructure Connection for Infisical." description: "Learn how to configure an Oracle Cloud Infrastructure Connection for Infisical."
--- ---
<Info>
OCI 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 [email protected] to purchase an enterprise license to use it.
</Info>
Infisical supports the use of [API Signing Key Authentication](https://docs.oracle.com/en-us/iaas/Content/API/Concepts/apisigningkey.htm) to connect with OCI. Infisical supports the use of [API Signing Key Authentication](https://docs.oracle.com/en-us/iaas/Content/API/Concepts/apisigningkey.htm) to connect with OCI.
## Create OCI User ## Create OCI User
@@ -3,6 +3,13 @@ title: "OCI Vault Sync"
description: "Learn how to configure an Oracle Cloud Infrastructure Vault Sync for Infisical." description: "Learn how to configure an Oracle Cloud Infrastructure Vault Sync for Infisical."
--- ---
<Info>
OCI Vault 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 [email protected] to purchase an enterprise license to use it.
</Info>
**Prerequisites:** **Prerequisites:**
- Create an [OCI Connection](/integrations/app-connections/oci) with the required **Secret Sync** permissions - Create an [OCI Connection](/integrations/app-connections/oci) with the required **Secret Sync** permissions
- [Create](https://docs.oracle.com/en-us/iaas/Content/Identity/compartments/To_create_a_compartment.htm) or use an existing OCI Compartment (which the OCI Connection is authorized to access) - [Create](https://docs.oracle.com/en-us/iaas/Content/Identity/compartments/To_create_a_compartment.htm) or use an existing OCI Compartment (which the OCI Connection is authorized to access)
@@ -1,13 +1,14 @@
import { faWrench } from "@fortawesome/free-solid-svg-icons"; import { faWrench } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
import { Spinner, Tooltip } from "@app/components/v2"; import { Spinner, Tooltip } from "@app/components/v2";
import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
import { SecretSync, useSecretSyncOptions } from "@app/hooks/api/secretSyncs";
import { twMerge } from "tailwind-merge";
import { UpgradePlanModal } from "../license/UpgradePlanModal";
import { usePopUp } from "@app/hooks";
import { useSubscription } from "@app/context"; import { useSubscription } from "@app/context";
import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
import { usePopUp } from "@app/hooks";
import { SecretSync, useSecretSyncOptions } from "@app/hooks/api/secretSyncs";
import { UpgradePlanModal } from "../license/UpgradePlanModal";
type Props = { type Props = {
onSelect: (destination: SecretSync) => void; onSelect: (destination: SecretSync) => void;
@@ -52,7 +53,7 @@ export const SecretSyncSelect = ({ onSelect }: Props) => {
)} )}
> >
{enterprise && !subscription.enterpriseSecretSyncs && ( {enterprise && !subscription.enterpriseSecretSyncs && (
<div className="absolute h-full w-full backdrop-blur-[1px]"></div> <div className="absolute h-full w-full backdrop-blur-[1px]" />
)} )}
<img <img
src={`/images/integrations/${image}`} src={`/images/integrations/${image}`}
+2 -2
View File
@@ -34,7 +34,7 @@ import { OCIConnectionMethod } from "@app/hooks/api/appConnections/types/oci-con
export const APP_CONNECTION_MAP: Record< export const APP_CONNECTION_MAP: Record<
AppConnection, AppConnection,
{ name: string; image: string; size?: number } { name: string; image: string; size?: number; enterprise?: boolean }
> = { > = {
[AppConnection.AWS]: { name: "AWS", image: "Amazon Web Services.png" }, [AppConnection.AWS]: { name: "AWS", image: "Amazon Web Services.png" },
[AppConnection.GitHub]: { name: "GitHub", image: "GitHub.png" }, [AppConnection.GitHub]: { name: "GitHub", image: "GitHub.png" },
@@ -63,7 +63,7 @@ export const APP_CONNECTION_MAP: Record<
[AppConnection.HCVault]: { name: "Hashicorp Vault", image: "Vault.png", size: 65 }, [AppConnection.HCVault]: { name: "Hashicorp Vault", image: "Vault.png", size: 65 },
[AppConnection.LDAP]: { name: "LDAP", image: "LDAP.png", size: 65 }, [AppConnection.LDAP]: { name: "LDAP", image: "LDAP.png", size: 65 },
[AppConnection.TeamCity]: { name: "TeamCity", image: "TeamCity.png" }, [AppConnection.TeamCity]: { name: "TeamCity", image: "TeamCity.png" },
[AppConnection.OCI]: { name: "OCI", image: "Oracle.png" } [AppConnection.OCI]: { name: "OCI", image: "Oracle.png", enterprise: true }
}; };
export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => { export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => {
@@ -51,4 +51,5 @@ export type SubscriptionPlan = {
projectTemplates: boolean; projectTemplates: boolean;
kmip: boolean; kmip: boolean;
enterpriseSecretSyncs: boolean; enterpriseSecretSyncs: boolean;
enterpriseAppConnections: boolean;
}; };
@@ -1,8 +1,12 @@
import { faWrench } from "@fortawesome/free-solid-svg-icons"; import { faWrench } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { Spinner, Tooltip } from "@app/components/v2"; import { Spinner, Tooltip } from "@app/components/v2";
import { useSubscription } from "@app/context";
import { APP_CONNECTION_MAP } from "@app/helpers/appConnections"; import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
import { usePopUp } from "@app/hooks";
import { useAppConnectionOptions } from "@app/hooks/api/appConnections"; import { useAppConnectionOptions } from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums"; import { AppConnection } from "@app/hooks/api/appConnections/enums";
@@ -11,8 +15,11 @@ type Props = {
}; };
export const AppConnectionsSelect = ({ onSelect }: Props) => { export const AppConnectionsSelect = ({ onSelect }: Props) => {
const { subscription } = useSubscription();
const { isPending, data: appConnectionOptions } = useAppConnectionOptions(); const { isPending, data: appConnectionOptions } = useAppConnectionOptions();
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
if (isPending) { if (isPending) {
return ( return (
<div className="flex h-full flex-col items-center justify-center py-2.5"> <div className="flex h-full flex-col items-center justify-center py-2.5">
@@ -25,29 +32,52 @@ export const AppConnectionsSelect = ({ onSelect }: Props) => {
return ( return (
<div className="grid grid-cols-4 gap-2"> <div className="grid grid-cols-4 gap-2">
{appConnectionOptions?.map((option) => { {appConnectionOptions?.map((option) => {
const { image, name, size = 50 } = APP_CONNECTION_MAP[option.app]; const { image, name, size = 50, enterprise = false } = APP_CONNECTION_MAP[option.app];
return ( return (
<button <Tooltip
type="button" content={
key={option.app} enterprise && !subscription.enterpriseAppConnections
onClick={() => onSelect(option.app)} ? "Enterprise Plan Only"
className="group relative flex h-28 cursor-pointer flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-700 p-4 duration-200 hover:bg-mineshaft-600" : undefined
}
> >
<img <button
src={`/images/integrations/${image}`} type="button"
style={{ key={option.app}
width: `${size}px` onClick={() =>
}} enterprise && !subscription.enterpriseAppConnections
className="mt-auto" ? handlePopUpOpen("upgradePlan")
alt={`${name} logo`} : onSelect(option.app)
/> }
<div className="mt-auto max-w-xs text-center text-xs font-medium text-gray-300 duration-200 group-hover:text-gray-200"> className={twMerge(
{name} "group relative flex h-28 cursor-pointer flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-700 p-4 duration-200 hover:bg-mineshaft-600",
</div> enterprise && !subscription.enterpriseAppConnections ? "border-0 bg-opacity-0" : ""
</button> )}
>
{enterprise && !subscription.enterpriseAppConnections && (
<div className="absolute h-full w-full backdrop-blur-[1px]" />
)}
<img
src={`/images/integrations/${image}`}
style={{
width: `${size}px`
}}
className="mt-auto"
alt={`${name} logo`}
/>
<div className="mt-auto max-w-xs text-center text-xs font-medium text-gray-300 duration-200 group-hover:text-gray-200">
{name}
</div>
</button>
</Tooltip>
); );
})} })}
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You can use every App Connection if you switch to Infisical's Enterprise plan."
/>
<Tooltip <Tooltip
side="bottom" side="bottom"
className="max-w-sm py-4" className="max-w-sm py-4"
@@ -1,7 +1,7 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { useEffect } from "react";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions"; import { OrgPermissionCan } from "@app/components/permissions";