This commit is contained in:
x032205
2025-05-26 12:54:36 -04:00
55 changed files with 506 additions and 179 deletions

View File

@@ -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({

View File

@@ -2,11 +2,10 @@ import {
CreateOCIVaultSyncSchema, CreateOCIVaultSyncSchema,
OCIVaultSyncSchema, OCIVaultSyncSchema,
UpdateOCIVaultSyncSchema UpdateOCIVaultSyncSchema
} from "@app/services/secret-sync/oci-vault"; } from "@app/ee/services/secret-sync/oci-vault";
import { registerSyncSecretsEndpoints } from "@app/server/routes/v1/secret-sync-routers/secret-sync-endpoints";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
export const registerOCIVaultSyncRouter = async (server: FastifyZodProvider) => export const registerOCIVaultSyncRouter = async (server: FastifyZodProvider) =>
registerSyncSecretsEndpoints({ registerSyncSecretsEndpoints({
destination: SecretSync.OCIVault, destination: SecretSync.OCIVault,

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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
}; };
}; };

View File

@@ -19,7 +19,7 @@ export const licenseDALFactory = (db: TDbClient) => {
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`) .join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
.where(`${TableName.Users}.isGhost`, false) .where(`${TableName.Users}.isGhost`, false)
.count(); .count();
return Number(doc?.[0].count); return Number(doc?.[0]?.count ?? 0);
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "Count of Org Members" }); throw new DatabaseError({ error, name: "Count of Org Members" });
} }

View File

@@ -18,33 +18,33 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
environmentsUsed: 0, environmentsUsed: 0,
identityLimit: null, identityLimit: null,
identitiesUsed: 0, identitiesUsed: 0,
dynamicSecret: true, dynamicSecret: false,
secretVersioning: true, secretVersioning: true,
pitRecovery: true, pitRecovery: false,
ipAllowlisting: true, ipAllowlisting: false,
rbac: true, rbac: false,
githubOrgSync: true, githubOrgSync: false,
customRateLimits: false, customRateLimits: false,
customAlerts: true, customAlerts: false,
secretAccessInsights: true, secretAccessInsights: false,
auditLogs: true, auditLogs: false,
auditLogsRetentionDays: 3, auditLogsRetentionDays: 0,
auditLogStreams: true, auditLogStreams: false,
auditLogStreamLimit: 3, auditLogStreamLimit: 3,
samlSSO: true, samlSSO: false,
hsm: true, hsm: false,
oidcSSO: true, oidcSSO: false,
scim: true, scim: false,
ldap: true, ldap: false,
groups: true, groups: false,
status: null, status: null,
trial_end: null, trial_end: null,
has_used_trial: true, has_used_trial: true,
secretApproval: true, secretApproval: false,
secretRotation: true, secretRotation: false,
caCrl: true, caCrl: false,
instanceUserManagement: true, instanceUserManagement: false,
externalKms: true, externalKms: false,
rateLimits: { rateLimits: {
readLimit: 60, readLimit: 60,
writeLimit: 200, writeLimit: 200,
@@ -52,10 +52,12 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
}, },
pkiEst: false, pkiEst: false,
enforceMfa: false, enforceMfa: false,
projectTemplates: true, projectTemplates: false,
kmip: true, kmip: false,
gateway: true, gateway: false,
sshHostGroups: true sshHostGroups: false,
enterpriseSecretSyncs: false,
enterpriseAppConnections: false
}); });
export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => { export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {

View File

@@ -92,6 +92,10 @@ export const licenseServiceFactory = ({
const { const {
data: { currentPlan } data: { currentPlan }
} = await licenseServerOnPremApi.request.get<{ currentPlan: TFeatureSet }>("/api/license/v1/plan"); } = await licenseServerOnPremApi.request.get<{ currentPlan: TFeatureSet }>("/api/license/v1/plan");
const workspacesUsed = await projectDAL.countOfOrgProjects(null);
currentPlan.workspacesUsed = workspacesUsed;
onPremFeatures = currentPlan; onPremFeatures = currentPlan;
logger.info("Successfully synchronized license key features"); logger.info("Successfully synchronized license key features");
} catch (error) { } catch (error) {
@@ -185,6 +189,9 @@ export const licenseServiceFactory = ({
} = await licenseServerCloudApi.request.get<{ currentPlan: TFeatureSet }>( } = await licenseServerCloudApi.request.get<{ currentPlan: TFeatureSet }>(
`/api/license-server/v1/customers/${org.customerId}/cloud-plan` `/api/license-server/v1/customers/${org.customerId}/cloud-plan`
); );
const workspacesUsed = await projectDAL.countOfOrgProjects(orgId);
currentPlan.workspacesUsed = workspacesUsed;
await keyStore.setItemWithExpiry( await keyStore.setItemWithExpiry(
FEATURE_CACHE_KEY(org.id), FEATURE_CACHE_KEY(org.id),
LICENSE_SERVER_CLOUD_PLAN_TTL, LICENSE_SERVER_CLOUD_PLAN_TTL,

View File

@@ -27,7 +27,7 @@ export type TFeatureSet = {
slug: null; slug: null;
tier: -1; tier: -1;
workspaceLimit: null; workspaceLimit: null;
workspacesUsed: 0; workspacesUsed: number;
dynamicSecret: false; dynamicSecret: false;
memberLimit: null; memberLimit: null;
membersUsed: number; membersUsed: number;
@@ -72,6 +72,8 @@ export type TFeatureSet = {
kmip: false; kmip: false;
gateway: false; gateway: false;
sshHostGroups: false; sshHostGroups: false;
enterpriseSecretSyncs: false;
enterpriseAppConnections: false;
}; };
export type TOrgPlansTableDTO = { export type TOrgPlansTableDTO = {

View File

@@ -6,5 +6,6 @@ export const OCI_VAULT_SYNC_LIST_OPTION: TSecretSyncListItem = {
name: "OCI Vault", name: "OCI Vault",
destination: SecretSync.OCIVault, destination: SecretSync.OCIVault,
connection: AppConnection.OCI, connection: AppConnection.OCI,
canImportSecrets: true canImportSecrets: true,
enterprise: true
}; };

View File

@@ -1,7 +1,6 @@
import { secrets, vault } from "oci-sdk"; import { secrets, vault } from "oci-sdk";
import { delay } from "@app/lib/delay"; import { getOCIProvider } from "@app/ee/services/app-connections/oci";
import { getOCIProvider } from "@app/services/app-connection/oci";
import { import {
TCreateOCIVaultVariable, TCreateOCIVaultVariable,
TDeleteOCIVaultVariable, TDeleteOCIVaultVariable,
@@ -9,7 +8,8 @@ import {
TOCIVaultSyncWithCredentials, TOCIVaultSyncWithCredentials,
TUnmarkOCIVaultVariableFromDeletion, TUnmarkOCIVaultVariableFromDeletion,
TUpdateOCIVaultVariable TUpdateOCIVaultVariable
} from "@app/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 { 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";

View File

@@ -66,5 +66,6 @@ export const OCIVaultSyncListItemSchema = z.object({
name: z.literal("OCI Vault"), name: z.literal("OCI Vault"),
connection: z.literal(AppConnection.OCI), connection: z.literal(AppConnection.OCI),
destination: z.literal(SecretSync.OCIVault), destination: z.literal(SecretSync.OCIVault),
canImportSecrets: z.literal(true) canImportSecrets: z.literal(true),
enterprise: z.boolean()
}); });

View File

@@ -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";

View File

@@ -1014,7 +1014,8 @@ export const registerRoutes = async (
secretVersionV2BridgeDAL, secretVersionV2BridgeDAL,
secretVersionTagV2BridgeDAL, secretVersionTagV2BridgeDAL,
resourceMetadataDAL, resourceMetadataDAL,
appConnectionDAL appConnectionDAL,
licenseService
}); });
const secretQueueService = secretQueueFactory({ const secretQueueService = secretQueueFactory({
@@ -1631,7 +1632,8 @@ export const registerRoutes = async (
const appConnectionService = appConnectionServiceFactory({ const appConnectionService = appConnectionServiceFactory({
appConnectionDAL, appConnectionDAL,
permissionService, permissionService,
kmsService kmsService,
licenseService
}); });
const secretSyncService = secretSyncServiceFactory({ const secretSyncService = secretSyncServiceFactory({
@@ -1642,7 +1644,8 @@ export const registerRoutes = async (
folderDAL, folderDAL,
secretSyncQueue, secretSyncQueue,
projectBotService, projectBotService,
keyStore keyStore,
licenseService
}); });
const kmipService = kmipServiceFactory({ const kmipService = kmipServiceFactory({

View File

@@ -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";
@@ -39,7 +40,6 @@ import {
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 { MySqlConnectionListItemSchema, SanitizedMySqlConnectionSchema } from "@app/services/app-connection/mysql"; import { MySqlConnectionListItemSchema, SanitizedMySqlConnectionSchema } from "@app/services/app-connection/mysql";
import { OCIConnectionListItemSchema, SanitizedOCIConnectionSchema } from "@app/services/app-connection/oci";
import { import {
PostgresConnectionListItemSchema, PostgresConnectionListItemSchema,
SanitizedPostgresConnectionSchema SanitizedPostgresConnectionSchema

View File

@@ -1,3 +1,4 @@
import { registerOCIConnectionRouter } from "@app/ee/routes/v1/app-connection-routers/oci-connection-router";
import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { registerAuth0ConnectionRouter } from "./auth0-connection-router"; import { registerAuth0ConnectionRouter } from "./auth0-connection-router";
@@ -14,7 +15,6 @@ 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 { registerMySqlConnectionRouter } from "./mysql-connection-router"; import { registerMySqlConnectionRouter } from "./mysql-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";

View File

@@ -1,5 +1,6 @@
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { registerOCIVaultSyncRouter } from "../../../../ee/routes/v1/secret-sync-routers/oci-vault-sync-router";
import { registerAwsParameterStoreSyncRouter } from "./aws-parameter-store-sync-router"; import { registerAwsParameterStoreSyncRouter } from "./aws-parameter-store-sync-router";
import { registerAwsSecretsManagerSyncRouter } from "./aws-secrets-manager-sync-router"; import { registerAwsSecretsManagerSyncRouter } from "./aws-secrets-manager-sync-router";
import { registerAzureAppConfigurationSyncRouter } from "./azure-app-configuration-sync-router"; import { registerAzureAppConfigurationSyncRouter } from "./azure-app-configuration-sync-router";
@@ -10,7 +11,6 @@ import { registerGcpSyncRouter } from "./gcp-sync-router";
import { registerGitHubSyncRouter } from "./github-sync-router"; import { registerGitHubSyncRouter } from "./github-sync-router";
import { registerHCVaultSyncRouter } from "./hc-vault-sync-router"; import { registerHCVaultSyncRouter } from "./hc-vault-sync-router";
import { registerHumanitecSyncRouter } from "./humanitec-sync-router"; import { registerHumanitecSyncRouter } from "./humanitec-sync-router";
import { registerOCIVaultSyncRouter } from "./oci-vault-sync-router";
import { registerTeamCitySyncRouter } from "./teamcity-sync-router"; import { registerTeamCitySyncRouter } from "./teamcity-sync-router";
import { registerTerraformCloudSyncRouter } from "./terraform-cloud-sync-router"; import { registerTerraformCloudSyncRouter } from "./terraform-cloud-sync-router";
import { registerVercelSyncRouter } from "./vercel-sync-router"; import { registerVercelSyncRouter } from "./vercel-sync-router";

View File

@@ -1,6 +1,7 @@
import { z } from "zod"; import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { OCIVaultSyncListItemSchema, OCIVaultSyncSchema } from "@app/ee/services/secret-sync/oci-vault";
import { ApiDocsTags, SecretSyncs } from "@app/lib/api-docs"; import { ApiDocsTags, SecretSyncs } from "@app/lib/api-docs";
import { readLimit } from "@app/server/config/rateLimiter"; import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@@ -24,7 +25,6 @@ import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github"; import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
import { HCVaultSyncListItemSchema, HCVaultSyncSchema } from "@app/services/secret-sync/hc-vault"; import { HCVaultSyncListItemSchema, HCVaultSyncSchema } from "@app/services/secret-sync/hc-vault";
import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec"; import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec";
import { OCIVaultSyncListItemSchema, OCIVaultSyncSchema } from "@app/services/secret-sync/oci-vault";
import { TeamCitySyncListItemSchema, TeamCitySyncSchema } from "@app/services/secret-sync/teamcity"; import { TeamCitySyncListItemSchema, TeamCitySyncSchema } from "@app/services/secret-sync/teamcity";
import { TerraformCloudSyncListItemSchema, TerraformCloudSyncSchema } from "@app/services/secret-sync/terraform-cloud"; import { TerraformCloudSyncListItemSchema, TerraformCloudSyncSchema } from "@app/services/secret-sync/terraform-cloud";
import { VercelSyncListItemSchema, VercelSyncSchema } from "@app/services/secret-sync/vercel"; import { VercelSyncListItemSchema, VercelSyncSchema } from "@app/services/secret-sync/vercel";

View File

@@ -67,3 +67,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"
}

View File

@@ -1,14 +1,20 @@
import { TAppConnections } from "@app/db/schemas/app-connections"; import { TAppConnections } from "@app/db/schemas/app-connections";
import {
getOCIConnectionListItem,
OCIConnectionMethod,
validateOCIConnectionCredentials
} from "@app/ee/services/app-connections/oci";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { generateHash } from "@app/lib/crypto/encryption"; import { generateHash } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { APP_CONNECTION_NAME_MAP } from "@app/services/app-connection/app-connection-maps"; import { APP_CONNECTION_NAME_MAP, APP_CONNECTION_PLAN_MAP } from "@app/services/app-connection/app-connection-maps";
import { import {
transferSqlConnectionCredentialsToPlatform, transferSqlConnectionCredentialsToPlatform,
validateSqlConnectionCredentials validateSqlConnectionCredentials
} 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 { AppConnection } from "./app-connection-enums"; import { AppConnection, AppConnectionPlanType } from "./app-connection-enums";
import { TAppConnectionServiceFactoryDep } from "./app-connection-service"; import { TAppConnectionServiceFactoryDep } from "./app-connection-service";
import { import {
TAppConnection, TAppConnection,
@@ -55,7 +61,6 @@ import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnection
import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql"; import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql";
import { MySqlConnectionMethod } from "./mysql/mysql-connection-enums"; import { MySqlConnectionMethod } from "./mysql/mysql-connection-enums";
import { getMySqlConnectionListItem } from "./mysql/mysql-connection-fns"; import { getMySqlConnectionListItem } from "./mysql/mysql-connection-fns";
import { getOCIConnectionListItem, OCIConnectionMethod, validateOCIConnectionCredentials } from "./oci";
import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres"; import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres";
import { import {
getTeamCityConnectionListItem, getTeamCityConnectionListItem,
@@ -263,3 +268,18 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
[AppConnection.TeamCity]: platformManagedCredentialsNotSupported, [AppConnection.TeamCity]: platformManagedCredentialsNotSupported,
[AppConnection.OCI]: platformManagedCredentialsNotSupported [AppConnection.OCI]: platformManagedCredentialsNotSupported
}; };
export const enterpriseAppCheck = async (
licenseService: Pick<TLicenseServiceFactory, "getPlan">,
appConnection: AppConnection,
orgId: string,
errorMessage: string
) => {
if (APP_CONNECTION_PLAN_MAP[appConnection] === AppConnectionPlanType.Enterprise) {
const plan = await licenseService.getPlan(orgId);
if (!plan.enterpriseAppConnections)
throw new BadRequestError({
message: errorMessage
});
}
};

View File

@@ -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",
@@ -22,3 +22,26 @@ 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,
[AppConnection.MySql]: AppConnectionPlanType.Regular
};

View File

@@ -1,5 +1,8 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import { ValidateOCIConnectionCredentialsSchema } from "@app/ee/services/app-connections/oci";
import { ociConnectionService } from "@app/ee/services/app-connections/oci/oci-connection-service";
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";
@@ -9,6 +12,7 @@ import { DiscriminativePick, OrgServiceActor } from "@app/lib/types";
import { import {
decryptAppConnection, decryptAppConnection,
encryptAppConnectionCredentials, encryptAppConnectionCredentials,
enterpriseAppCheck,
getAppConnectionMethodName, getAppConnectionMethodName,
listAppConnectionOptions, listAppConnectionOptions,
TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM, TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM,
@@ -50,8 +54,6 @@ import { humanitecConnectionService } from "./humanitec/humanitec-connection-ser
import { ValidateLdapConnectionCredentialsSchema } from "./ldap"; import { ValidateLdapConnectionCredentialsSchema } from "./ldap";
import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql"; import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql";
import { ValidateMySqlConnectionCredentialsSchema } from "./mysql"; import { ValidateMySqlConnectionCredentialsSchema } from "./mysql";
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";
@@ -66,6 +68,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>;
@@ -96,7 +99,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(
@@ -193,6 +197,13 @@ export const appConnectionServiceFactory = ({
OrgPermissionSubjects.AppConnections OrgPermissionSubjects.AppConnections
); );
await enterpriseAppCheck(
licenseService,
app,
actor.orgId,
"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,
@@ -255,6 +266,13 @@ 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}` });
await enterpriseAppCheck(
licenseService,
appConnection.app as AppConnection,
actor.orgId,
"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,
@@ -401,6 +419,13 @@ 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}` });
await enterpriseAppCheck(
licenseService,
app,
actor.orgId,
"Failed to connect app 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,
@@ -470,6 +495,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)
}; };
}; };

View File

@@ -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,
@@ -77,12 +83,6 @@ import {
} from "./ldap"; } from "./ldap";
import { TMsSqlConnection, TMsSqlConnectionInput, TValidateMsSqlConnectionCredentialsSchema } from "./mssql"; import { TMsSqlConnection, TMsSqlConnectionInput, TValidateMsSqlConnectionCredentialsSchema } from "./mssql";
import { TMySqlConnection, TMySqlConnectionInput, TValidateMySqlConnectionCredentialsSchema } from "./mysql"; import { TMySqlConnection, TMySqlConnectionInput, TValidateMySqlConnectionCredentialsSchema } from "./mysql";
import {
TOCIConnection,
TOCIConnectionConfig,
TOCIConnectionInput,
TValidateOCIConnectionCredentialsSchema
} from "./oci";
import { import {
TPostgresConnection, TPostgresConnection,
TPostgresConnectionInput, TPostgresConnectionInput,

View File

@@ -425,6 +425,21 @@ export const projectDALFactory = (db: TDbClient) => {
return { docs, totalCount: Number(docs?.[0]?.count ?? 0) }; return { docs, totalCount: Number(docs?.[0]?.count ?? 0) };
}; };
const countOfOrgProjects = async (orgId: string | null, tx?: Knex) => {
try {
const doc = await (tx || db.replicaNode())(TableName.Project)
.andWhere((bd) => {
if (orgId) {
void bd.where({ orgId });
}
})
.count();
return Number(doc?.[0]?.count ?? 0);
} catch (error) {
throw new DatabaseError({ error, name: "Count of Org Projects" });
}
};
return { return {
...projectOrm, ...projectOrm,
findUserProjects, findUserProjects,
@@ -437,6 +452,7 @@ export const projectDALFactory = (db: TDbClient) => {
findProjectWithOrg, findProjectWithOrg,
checkProjectUpgradeStatus, checkProjectUpgradeStatus,
getProjectFromSplitId, getProjectFromSplitId,
searchProjects searchProjects,
countOfOrgProjects
}; };
}; };

View File

@@ -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"
}

View File

@@ -1,6 +1,9 @@
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import RE2 from "re2"; import RE2 from "re2";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OCI_VAULT_SYNC_LIST_OPTION, OCIVaultSyncFns } from "@app/ee/services/secret-sync/oci-vault";
import { BadRequestError } from "@app/lib/errors";
import { import {
AWS_PARAMETER_STORE_SYNC_LIST_OPTION, AWS_PARAMETER_STORE_SYNC_LIST_OPTION,
AwsParameterStoreSyncFns AwsParameterStoreSyncFns
@@ -11,7 +14,7 @@ import {
} from "@app/services/secret-sync/aws-secrets-manager"; } from "@app/services/secret-sync/aws-secrets-manager";
import { DATABRICKS_SYNC_LIST_OPTION, databricksSyncFactory } from "@app/services/secret-sync/databricks"; import { DATABRICKS_SYNC_LIST_OPTION, databricksSyncFactory } from "@app/services/secret-sync/databricks";
import { GITHUB_SYNC_LIST_OPTION, GithubSyncFns } from "@app/services/secret-sync/github"; import { GITHUB_SYNC_LIST_OPTION, GithubSyncFns } from "@app/services/secret-sync/github";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; import { SecretSync, SecretSyncPlanType } 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 { import {
TSecretMap, TSecretMap,
@@ -29,7 +32,7 @@ import { GcpSyncFns } from "./gcp/gcp-sync-fns";
import { HC_VAULT_SYNC_LIST_OPTION, HCVaultSyncFns } from "./hc-vault"; import { HC_VAULT_SYNC_LIST_OPTION, HCVaultSyncFns } from "./hc-vault";
import { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec"; import { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec";
import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns"; import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns";
import { OCI_VAULT_SYNC_LIST_OPTION, OCIVaultSyncFns } from "./oci-vault"; import { SECRET_SYNC_PLAN_MAP } from "./secret-sync-maps";
import { TEAMCITY_SYNC_LIST_OPTION, TeamCitySyncFns } from "./teamcity"; import { TEAMCITY_SYNC_LIST_OPTION, TeamCitySyncFns } from "./teamcity";
import { TERRAFORM_CLOUD_SYNC_LIST_OPTION, TerraformCloudSyncFns } from "./terraform-cloud"; import { TERRAFORM_CLOUD_SYNC_LIST_OPTION, TerraformCloudSyncFns } from "./terraform-cloud";
import { VERCEL_SYNC_LIST_OPTION, VercelSyncFns } from "./vercel"; import { VERCEL_SYNC_LIST_OPTION, VercelSyncFns } from "./vercel";
@@ -327,3 +330,18 @@ export const parseSyncErrorMessage = (err: unknown): string => {
? errorMessage ? errorMessage
: `${errorMessage.substring(0, MAX_MESSAGE_LENGTH - 3)}...`; : `${errorMessage.substring(0, MAX_MESSAGE_LENGTH - 3)}...`;
}; };
export const enterpriseSyncCheck = async (
licenseService: Pick<TLicenseServiceFactory, "getPlan">,
secretSync: SecretSync,
orgId: string,
errorMessage: string
) => {
if (SECRET_SYNC_PLAN_MAP[secretSync] === SecretSyncPlanType.Enterprise) {
const plan = await licenseService.getPlan(orgId);
if (!plan.enterpriseSecretSyncs)
throw new BadRequestError({
message: errorMessage
});
}
};

View File

@@ -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
};

View File

@@ -5,6 +5,7 @@ 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 { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
@@ -32,7 +33,7 @@ import {
SecretSyncInitialSyncBehavior SecretSyncInitialSyncBehavior
} 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 { enterpriseSyncCheck, 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 } from "@app/services/secret-sync/secret-sync-maps";
import { import {
SecretSyncAction, SecretSyncAction,
@@ -93,6 +94,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 +135,8 @@ export const secretSyncQueueFactory = ({
secretVersionTagDAL, secretVersionTagDAL,
secretVersionV2BridgeDAL, secretVersionV2BridgeDAL,
secretVersionTagV2BridgeDAL, secretVersionTagV2BridgeDAL,
resourceMetadataDAL resourceMetadataDAL,
licenseService
}: TSecretSyncQueueFactoryDep) => { }: TSecretSyncQueueFactoryDep) => {
const appCfg = getConfig(); const appCfg = getConfig();
@@ -323,7 +326,20 @@ 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;
await enterpriseSyncCheck(
licenseService,
destination,
orgId,
"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 +416,13 @@ 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}`);
await enterpriseSyncCheck(
licenseService,
secretSync.destination as SecretSync,
secretSync.connection.orgId,
"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 +682,13 @@ 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}`);
await enterpriseSyncCheck(
licenseService,
secretSync.destination as SecretSync,
secretSync.connection.orgId,
"Failed to remove secrets 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
}); });

View File

@@ -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 {
@@ -16,7 +17,7 @@ import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-c
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 } from "@app/services/secret-sync/secret-sync-enums";
import { listSecretSyncOptions } from "@app/services/secret-sync/secret-sync-fns"; import { enterpriseSyncCheck, listSecretSyncOptions } from "@app/services/secret-sync/secret-sync-fns";
import { import {
SecretSyncStatus, SecretSyncStatus,
TCreateSecretSyncDTO, TCreateSecretSyncDTO,
@@ -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,13 @@ export const secretSyncServiceFactory = ({
{ projectId, secretPath, environment, ...params }: TCreateSecretSyncDTO, { projectId, secretPath, environment, ...params }: TCreateSecretSyncDTO,
actor: OrgServiceActor actor: OrgServiceActor
) => { ) => {
await enterpriseSyncCheck(
licenseService,
params.destination,
actor.orgId,
"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 +270,13 @@ 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}`
}); });
await enterpriseSyncCheck(
licenseService,
secretSync.destination as SecretSync,
actor.orgId,
"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 +425,13 @@ 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}"`
}); });
await enterpriseSyncCheck(
licenseService,
secretSync.destination as SecretSync,
actor.orgId,
"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 +487,13 @@ 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}"`
}); });
await enterpriseSyncCheck(
licenseService,
secretSync.destination as SecretSync,
actor.orgId,
"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 +543,13 @@ 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}"`
}); });
await enterpriseSyncCheck(
licenseService,
secretSync.destination as SecretSync,
actor.orgId,
"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,

View File

@@ -1,6 +1,12 @@
import { Job } from "bullmq"; import { Job } from "bullmq";
import { AuditLogInfo } from "@app/ee/services/audit-log/audit-log-types"; import { AuditLogInfo } from "@app/ee/services/audit-log/audit-log-types";
import {
TOCIVaultSync,
TOCIVaultSyncInput,
TOCIVaultSyncListItem,
TOCIVaultSyncWithCredentials
} from "@app/ee/services/secret-sync/oci-vault";
import { QueueJobs } from "@app/queue"; import { QueueJobs } from "@app/queue";
import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema"; import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema";
import { import {
@@ -67,7 +73,6 @@ import {
THumanitecSyncListItem, THumanitecSyncListItem,
THumanitecSyncWithCredentials THumanitecSyncWithCredentials
} from "./humanitec"; } from "./humanitec";
import { TOCIVaultSync, TOCIVaultSyncInput, TOCIVaultSyncListItem, TOCIVaultSyncWithCredentials } from "./oci-vault";
import { import {
TTeamCitySync, TTeamCitySync,
TTeamCitySyncInput, TTeamCitySyncInput,

View File

@@ -5,6 +5,7 @@ import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/pe
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { TokenType } from "@app/services/auth-token/auth-token-types"; import { TokenType } from "@app/services/auth-token/auth-token-types";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
@@ -80,6 +81,17 @@ export const userServiceFactory = ({
const verifyEmailVerificationCode = async (username: string, code: string) => { const verifyEmailVerificationCode = async (username: string, code: string) => {
// akhilmhdh: case sensitive email resolution // akhilmhdh: case sensitive email resolution
const usersByusername = await userDAL.findUserByUsername(username); const usersByusername = await userDAL.findUserByUsername(username);
logger.info(
usersByusername.map((user) => ({
id: user.id,
email: user.email,
username: user.username,
isEmailVerified: user.isEmailVerified
})),
`Verify email users: [username=${username}]`
);
const user = const user =
usersByusername?.length > 1 ? usersByusername.find((el) => el.username === username) : usersByusername?.[0]; usersByusername?.length > 1 ? usersByusername.find((el) => el.username === username) : usersByusername?.[0];
if (!user) throw new NotFoundError({ name: `User with username '${username}' not found` }); if (!user) throw new NotFoundError({ name: `User with username '${username}' not found` });

View File

@@ -14,7 +14,7 @@ description: "Learn how to configure Auth0 OIDC for Infisical SSO."
<Step title="Setup application in Auth0"> <Step title="Setup application in Auth0">
1.1. From the Application's Page, navigate to the settings tab of the Auth0 application you want to integrate with Infisical. 1.1. From the Application's Page, navigate to the settings tab of the Auth0 application you want to integrate with Infisical.
![OIDC auth0 list of applications](../../../images/sso/auth0-oidc/application-settings.png) ![OIDC auth0 list of applications](../../../images/sso/auth0-oidc/application-settings.png)
1.2. In the Application URIs section, set the **Application Login URI** and **Allowed Web Origins** fields to `https://app.infisical.com` and the **Allowed Callback URL** field to `https://app.infisical.com/api/v1/sso/oidc/callback`. 1.2. In the Application URIs section, set the **Application Login URI** and **Allowed Web Origins** fields to `https://app.infisical.com` and the **Allowed Callback URL** field to `https://app.infisical.com/api/v1/sso/oidc/callback`.
![OIDC auth0 create application uris](../../../images/sso/auth0-oidc/application-uris.png) ![OIDC auth0 create application uris](../../../images/sso/auth0-oidc/application-uris.png)
![OIDC auth0 create application origin](../../../images/sso/auth0-oidc/application-origin.png) ![OIDC auth0 create application origin](../../../images/sso/auth0-oidc/application-origin.png)
@@ -70,7 +70,7 @@ description: "Learn how to configure Auth0 OIDC for Infisical SSO."
prior to enforcing OIDC SSO to prevent any unintended issues. prior to enforcing OIDC SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
</Steps> </Steps>

View File

@@ -23,30 +23,30 @@ description: "Learn how to configure Auth0 SAML for Infisical SSO."
</Step> </Step>
<Step title="Create a SAML application in Auth0"> <Step title="Create a SAML application in Auth0">
2.1. In your Auth0 account, head to Applications and create an application. 2.1. In your Auth0 account, head to Applications and create an application.
![Auth0 SAML app creation](../../../images/sso/auth0-saml/create-application.png) ![Auth0 SAML app creation](../../../images/sso/auth0-saml/create-application.png)
Select **Regular Web Application** and press **Create**. Select **Regular Web Application** and press **Create**.
![Auth0 SAML app creation](../../../images/sso/auth0-saml/create-application-2.png) ![Auth0 SAML app creation](../../../images/sso/auth0-saml/create-application-2.png)
2.2. In the Application head to Settings > Application URIs and add the **Application Callback URL** from step 1 into the **Allowed Callback URLs** field. 2.2. In the Application head to Settings > Application URIs and add the **Application Callback URL** from step 1 into the **Allowed Callback URLs** field.
![Auth0 SAML allowed callback URLs](../../../images/sso/auth0-saml/auth0-config.png) ![Auth0 SAML allowed callback URLs](../../../images/sso/auth0-saml/auth0-config.png)
2.3. In the Application head to Addons > SAML2 Web App and copy the **Issuer**, **Identity Provider Login URL**, and **Identity Provider Certificate** from the **Usage** tab. 2.3. In the Application head to Addons > SAML2 Web App and copy the **Issuer**, **Identity Provider Login URL**, and **Identity Provider Certificate** from the **Usage** tab.
![Auth0 SAML config](../../../images/sso/auth0-saml/auth0-config-2.png) ![Auth0 SAML config](../../../images/sso/auth0-saml/auth0-config-2.png)
2.4. Back in Infisical, set **Issuer**, **Identity Provider Login URL**, and **Certificate** to the corresponding items from step 2.3. 2.4. Back in Infisical, set **Issuer**, **Identity Provider Login URL**, and **Certificate** to the corresponding items from step 2.3.
![Auth0 SAML Infisical config](../../../images/sso/auth0-saml/infisical-config.png) ![Auth0 SAML Infisical config](../../../images/sso/auth0-saml/infisical-config.png)
2.5. Back in Auth0, in the **Settings** tab, set the **Application Callback URL** to the **Application Callback URL** from step 1 2.5. Back in Auth0, in the **Settings** tab, set the **Application Callback URL** to the **Application Callback URL** from step 1
and update the **Settings** field with the JSON under the picture below (replacing `<audience-from-infisical>` with the **Audience** from step 1). and update the **Settings** field with the JSON under the picture below (replacing `<audience-from-infisical>` with the **Audience** from step 1).
![Auth0 SAML config](../../../images/sso/auth0-saml/auth0-config-3.png) ![Auth0 SAML config](../../../images/sso/auth0-saml/auth0-config-3.png)
```json ```json
{ {
"audience": "<audience-from-infisical>", "audience": "<audience-from-infisical>",
@@ -76,7 +76,7 @@ description: "Learn how to configure Auth0 SAML for Infisical SSO."
Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO. Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
@@ -96,4 +96,4 @@ description: "Learn how to configure Auth0 SAML for Infisical SSO."
32`. 32`.
<div class="height:1px;"/> <div class="height:1px;"/>
- `SITE_URL`: The absolute URL of your self-hosted instance of Infisical including the protocol (e.g. https://app.infisical.com) - `SITE_URL`: The absolute URL of your self-hosted instance of Infisical including the protocol (e.g. https://app.infisical.com)
</Note> </Note>

View File

@@ -5,7 +5,7 @@ description: "Learn how to configure Microsoft Entra ID for Infisical SSO."
<Info> <Info>
Azure SAML SSO is a paid feature. Azure SAML SSO is a paid feature.
If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical, If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical,
then you should contact sales@infisical.com to purchase an enterprise license to use it. then you should contact sales@infisical.com to purchase an enterprise license to use it.
</Info> </Info>
@@ -26,7 +26,7 @@ description: "Learn how to configure Microsoft Entra ID for Infisical SSO."
![Azure SAML enterprise applications](../../../images/sso/azure/enterprise-applications.png) ![Azure SAML enterprise applications](../../../images/sso/azure/enterprise-applications.png)
![Azure SAML new application](../../../images/sso/azure/new-application.png) ![Azure SAML new application](../../../images/sso/azure/new-application.png)
On the next screen, press the **+ Create your own application** button. On the next screen, press the **+ Create your own application** button.
Give the application a unique name like Infisical; choose the "Integrate any other application you don't find in the gallery (Non-gallery)" Give the application a unique name like Infisical; choose the "Integrate any other application you don't find in the gallery (Non-gallery)"
option and hit the **Create** button. option and hit the **Create** button.
@@ -89,9 +89,9 @@ description: "Learn how to configure Microsoft Entra ID for Infisical SSO."
</Step> </Step>
<Step title="Assign users in Azure to the application"> <Step title="Assign users in Azure to the application">
Back in Azure, navigate to the **Users and groups** tab and select **+ Add user/group** to assign access to the login with SSO application on a user or group-level. Back in Azure, navigate to the **Users and groups** tab and select **+ Add user/group** to assign access to the login with SSO application on a user or group-level.
![Azure SAML assignment](../../../images/sso/azure/assignment.png) ![Azure SAML assignment](../../../images/sso/azure/assignment.png)
</Step> </Step>
<Step title="Enable SAML SSO in Infisical"> <Step title="Enable SAML SSO in Infisical">
Enabling SAML SSO allows members in your organization to log into Infisical via Azure. Enabling SAML SSO allows members in your organization to log into Infisical via Azure.
@@ -109,7 +109,7 @@ description: "Learn how to configure Microsoft Entra ID for Infisical SSO."
prior to enforcing SAML SSO to prevent any unintended issues. prior to enforcing SAML SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
</Steps> </Steps>

View File

@@ -70,7 +70,7 @@ Prerequisites:
We recommend ensuring that your account is provisioned using the identity provider prior to enforcing OIDC SSO to prevent any unintended issues. We recommend ensuring that your account is provisioned using the identity provider prior to enforcing OIDC SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>

View File

@@ -24,21 +24,21 @@ description: "Learn how to configure Google SAML for Infisical SSO."
<Step title="Create a SAML application in Google"> <Step title="Create a SAML application in Google">
2.1. In your [Google Admin console](https://support.google.com/a/answer/182076), head to Menu > Apps > Web and mobile apps and 2.1. In your [Google Admin console](https://support.google.com/a/answer/182076), head to Menu > Apps > Web and mobile apps and
create a **custom SAML app**. create a **custom SAML app**.
![Google SAML app creation](../../../images/sso/google-saml/create-custom-saml-app.png) ![Google SAML app creation](../../../images/sso/google-saml/create-custom-saml-app.png)
2.2. In the **App details** tab, give the application a unique name like Infisical. 2.2. In the **App details** tab, give the application a unique name like Infisical.
![Google SAML app naming](../../../images/sso/google-saml/name-custom-saml-app.png) ![Google SAML app naming](../../../images/sso/google-saml/name-custom-saml-app.png)
2.3. In the **Google Identity Provider details** tab, copy the **SSO URL**, **Entity ID** and **Certificate**. 2.3. In the **Google Identity Provider details** tab, copy the **SSO URL**, **Entity ID** and **Certificate**.
![Google SAML custom app details](../../../images/sso/google-saml/custom-saml-app-config.png) ![Google SAML custom app details](../../../images/sso/google-saml/custom-saml-app-config.png)
2.4. Back in Infisical, set **SSO URL** and **Certificate** to the corresponding items from step 2.3. 2.4. Back in Infisical, set **SSO URL** and **Certificate** to the corresponding items from step 2.3.
![Google SAML Infisical config](../../../images/sso/google-saml/infisical-config.png) ![Google SAML Infisical config](../../../images/sso/google-saml/infisical-config.png)
2.5. Back in the Google Admin console, in the **Service provider details** tab, set the **ACS URL** and **Entity ID** to the corresponding items from step 1. 2.5. Back in the Google Admin console, in the **Service provider details** tab, set the **ACS URL** and **Entity ID** to the corresponding items from step 1.
Also, check the **Signed response** checkbox. Also, check the **Signed response** checkbox.
@@ -84,7 +84,7 @@ description: "Learn how to configure Google SAML for Infisical SSO."
prior to enforcing SAML SSO to prevent any unintended issues. prior to enforcing SAML SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>

View File

@@ -5,7 +5,7 @@ description: "Learn how to configure JumpCloud SAML for Infisical SSO."
<Info> <Info>
JumpCloud SAML SSO is a paid feature. JumpCloud SAML SSO is a paid feature.
If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical, If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical,
then you should contact sales@infisical.com to purchase an enterprise license to use it. then you should contact sales@infisical.com to purchase an enterprise license to use it.
</Info> </Info>
@@ -83,13 +83,12 @@ description: "Learn how to configure JumpCloud SAML for Infisical SSO."
To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one JumpCloud user with Infisical; To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one JumpCloud user with Infisical;
Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO. Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
<Warning> <Warning>
We recommend ensuring that your account is provisioned the application in JumpCloud We recommend ensuring that your account is provisioned in the application in JumpCloud prior to enforcing SAML SSO to prevent any unintended issues.
prior to enforcing SAML SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
</Steps> </Steps>

View File

@@ -97,7 +97,7 @@ description: "Learn how to configure Keycloak OIDC for Infisical SSO."
prior to enforcing OIDC SSO to prevent any unintended issues. prior to enforcing OIDC SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
</Steps> </Steps>

View File

@@ -5,7 +5,7 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
<Info> <Info>
Keycloak SAML SSO is a paid feature. Keycloak SAML SSO is a paid feature.
If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical, If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical,
then you should contact sales@infisical.com to purchase an enterprise license to use it. then you should contact sales@infisical.com to purchase an enterprise license to use it.
</Info> </Info>
@@ -13,36 +13,36 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
<Steps> <Steps>
<Step title="Prepare the SAML SSO configuration in Infisical"> <Step title="Prepare the SAML SSO configuration in Infisical">
In Infisical, head to the **Single Sign-On (SSO)** page and select the **General** tab. Click **Connect** for **SAML** under the Connect to an Identity Provider section. Select **Keycloak**, then click **Connect** again. In Infisical, head to the **Single Sign-On (SSO)** page and select the **General** tab. Click **Connect** for **SAML** under the Connect to an Identity Provider section. Select **Keycloak**, then click **Connect** again.
![SSO connect section](../../../images/sso/connect-saml.png) ![SSO connect section](../../../images/sso/connect-saml.png)
Next, copy the **Valid redirect URI** and **SP Entity ID** to use when configuring the Keycloak SAML application. Next, copy the **Valid redirect URI** and **SP Entity ID** to use when configuring the Keycloak SAML application.
![Keycloak SAML initial configuration](../../../images/sso/keycloak/init-config.png) ![Keycloak SAML initial configuration](../../../images/sso/keycloak/init-config.png)
</Step> </Step>
<Step title="Create a SAML client application in Keycloak"> <Step title="Create a SAML client application in Keycloak">
2.1. In your realm, navigate to the **Clients** tab and click **Create client** to create a new client application. 2.1. In your realm, navigate to the **Clients** tab and click **Create client** to create a new client application.
![SAML keycloak list of clients](../../../images/sso/keycloak/clients-list.png) ![SAML keycloak list of clients](../../../images/sso/keycloak/clients-list.png)
<Info> <Info>
You don’t typically need to make a realm dedicated to Infisical. We recommend adding Infisical as a client to your primary realm. You don’t typically need to make a realm dedicated to Infisical. We recommend adding Infisical as a client to your primary realm.
</Info> </Info>
In the General Settings step, set **Client type** to **SAML**, the **Client ID** field to `https://app.infisical.com`, and the **Name** field to a friendly name like **Infisical**. In the General Settings step, set **Client type** to **SAML**, the **Client ID** field to `https://app.infisical.com`, and the **Name** field to a friendly name like **Infisical**.
![SAML keycloak create client general settings](../../../images/sso/keycloak/create-client-general-settings.png) ![SAML keycloak create client general settings](../../../images/sso/keycloak/create-client-general-settings.png)
<Info> <Info>
If you’re self-hosting Infisical, then you will want to replace https://app.infisical.com with your own domain. If you’re self-hosting Infisical, then you will want to replace https://app.infisical.com with your own domain.
</Info> </Info>
Next, in the Login Settings step, set both the **Home URL** field and **Valid redirect URIs** field to the **Valid redirect URI** from step 1 and press **Save**. Next, in the Login Settings step, set both the **Home URL** field and **Valid redirect URIs** field to the **Valid redirect URI** from step 1 and press **Save**.
![SAML keycloak create client login settings](../../../images/sso/keycloak/create-client-login-settings.png) ![SAML keycloak create client login settings](../../../images/sso/keycloak/create-client-login-settings.png)
2.2. Once you've created the client, under its **Settings** tab, make sure to set the following values: 2.2. Once you've created the client, under its **Settings** tab, make sure to set the following values:
- Under **SAML Capabilities**: - Under **SAML Capabilities**:
- Name ID format: email (or username). - Name ID format: email (or username).
- Force name ID format: On. - Force name ID format: On.
@@ -54,59 +54,59 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
- Signature algorithm: RSA_SHA256. - Signature algorithm: RSA_SHA256.
![SAML keycloak client SAML capabilities](../../../images/sso/keycloak/client-saml-capabilities.png) ![SAML keycloak client SAML capabilities](../../../images/sso/keycloak/client-saml-capabilities.png)
![SAML keycloak client signature encryption](../../../images/sso/keycloak/client-signature-encryption.png) ![SAML keycloak client signature encryption](../../../images/sso/keycloak/client-signature-encryption.png)
2.3. Next, navigate to the **Client scopes** tab select the client's dedicated scope. 2.3. Next, navigate to the **Client scopes** tab select the client's dedicated scope.
![SAML keycloak client scopes list](../../../images/sso/keycloak/client-scopes-list.png) ![SAML keycloak client scopes list](../../../images/sso/keycloak/client-scopes-list.png)
Next click **Add predefined mapper**. Next click **Add predefined mapper**.
![SAML keycloak client mappers empty](../../../images/sso/keycloak/client-mappers-empty.png) ![SAML keycloak client mappers empty](../../../images/sso/keycloak/client-mappers-empty.png)
Select the **X500 email**, **X500 givenName**, and **X500 surname** attributes and click **Add**. Select the **X500 email**, **X500 givenName**, and **X500 surname** attributes and click **Add**.
![SAML keycloak client mappers predefined](../../../images/sso/keycloak/client-mappers-predefined.png) ![SAML keycloak client mappers predefined](../../../images/sso/keycloak/client-mappers-predefined.png)
Now click on the **X500 email** mapper and set the **SAML Attribute Name** field to **email**. Now click on the **X500 email** mapper and set the **SAML Attribute Name** field to **email**.
![SAML keycloak client mappers email](../../../images/sso/keycloak/client-mappers-email.png) ![SAML keycloak client mappers email](../../../images/sso/keycloak/client-mappers-email.png)
Repeat the same for **X500 givenName** and **X500 surname** mappers, setting the **SAML Attribute Name** field to **firstName** and **lastName** respectively. Repeat the same for **X500 givenName** and **X500 surname** mappers, setting the **SAML Attribute Name** field to **firstName** and **lastName** respectively.
Next, back in the client scope's **Mappers**, click **Add mapper** and select **by configuration**. Next, back in the client scope's **Mappers**, click **Add mapper** and select **by configuration**.
![SAML keycloak client mappers by configuration](../../../images/sso/keycloak/client-mappers-by-configuration.png) ![SAML keycloak client mappers by configuration](../../../images/sso/keycloak/client-mappers-by-configuration.png)
Select **User Property**. Select **User Property**.
![SAML keycloak client mappers user property](../../../images/sso/keycloak/client-mappers-user-property.png) ![SAML keycloak client mappers user property](../../../images/sso/keycloak/client-mappers-user-property.png)
Set the the **Name** field to **Username**, the **Property** field to **username**, and the **SAML Attribtue Name** to **username**. Set the the **Name** field to **Username**, the **Property** field to **username**, and the **SAML Attribute Name** to **username**.
![SAML keycloak client mappers username](../../../images/sso/keycloak/client-mappers-username.png) ![SAML keycloak client mappers username](../../../images/sso/keycloak/client-mappers-username.png)
Repeat the same for the `id` attribute, setting the **Name** field to **ID**, the **Property** field to **id**, and the **SAML Attribute Name** to **id**. Repeat the same for the `id` attribute, setting the **Name** field to **ID**, the **Property** field to **id**, and the **SAML Attribute Name** to **id**.
![SAML keycloak client mappers id](../../../images/sso/keycloak/client-mappers-id.png) ![SAML keycloak client mappers id](../../../images/sso/keycloak/client-mappers-id.png)
Once you've completed the above steps, the list of mappers should look like this: Once you've completed the above steps, the list of mappers should look like this:
![SAML keycloak client mappers completed](../../../images/sso/keycloak/client-mappers-completed.png) ![SAML keycloak client mappers completed](../../../images/sso/keycloak/client-mappers-completed.png)
</Step> </Step>
<Step title="Retrieve Identity Provider (IdP) Information from Keycloak"> <Step title="Retrieve Identity Provider (IdP) Information from Keycloak">
Back in Keycloak, navigate to Configure > Realm settings > General tab > Endpoints > SAML 2.0 Identity Provider Metadata and copy the IDP URL. This should appear in various places and take the form: `https://keycloak-mysite.com/realms/myrealm/protocol/saml`. Back in Keycloak, navigate to Configure > Realm settings > General tab > Endpoints > SAML 2.0 Identity Provider Metadata and copy the IDP URL. This should appear in various places and take the form: `https://keycloak-mysite.com/realms/myrealm/protocol/saml`.
![SAML keycloak realm SAML metadata](../../../images/sso/keycloak/realm-saml-metadata.png) ![SAML keycloak realm SAML metadata](../../../images/sso/keycloak/realm-saml-metadata.png)
Also, in the **Keys** tab, locate the RS256 key and copy the certificate to use when finishing configuring Keycloak SAML in Infisical. Also, in the **Keys** tab, locate the RS256 key and copy the certificate to use when finishing configuring Keycloak SAML in Infisical.
![SAML keycloak realm settings keys](../../../images/sso/keycloak/realm-settings-keys.png) ![SAML keycloak realm settings keys](../../../images/sso/keycloak/realm-settings-keys.png)
</Step> </Step>
<Step title="Finish configuring SAML in Infisical"> <Step title="Finish configuring SAML in Infisical">
Back in Infisical, set **IDP URL** and **Certificate** to the items from step 3. Also, set the **Client ID** to the `https://app.infisical.com`. Back in Infisical, set **IDP URL** and **Certificate** to the items from step 3. Also, set the **Client ID** to the `https://app.infisical.com`.
Once you've done that, press **Update** to complete the required configuration. Once you've done that, press **Update** to complete the required configuration.
![SAML Okta paste values into Infisical](../../../images/sso/keycloak/idp-values.png) ![SAML Okta paste values into Infisical](../../../images/sso/keycloak/idp-values.png)
@@ -119,7 +119,7 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
<Step title="Enforce SAML SSO in Infisical"> <Step title="Enforce SAML SSO in Infisical">
Enforcing SAML SSO ensures that members in your organization can only access Infisical Enforcing SAML SSO ensures that members in your organization can only access Infisical
by logging into the organization via Keycloak. by logging into the organization via Keycloak.
To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one Keycloak user with Infisical; To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one Keycloak user with Infisical;
Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO. Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
@@ -128,7 +128,7 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
prior to enforcing SAML SSO to prevent any unintended issues. prior to enforcing SAML SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
</Steps> </Steps>
@@ -147,4 +147,4 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
32`. 32`.
<div class="height:1px;"/> <div class="height:1px;"/>
- `SITE_URL`: The absolute URL of your self-hosted instance of Infisical including the protocol (e.g. https://app.infisical.com) - `SITE_URL`: The absolute URL of your self-hosted instance of Infisical including the protocol (e.g. https://app.infisical.com)
</Note> </Note>

View File

@@ -93,13 +93,12 @@ description: "Learn how to configure Okta SAML 2.0 for Infisical SSO."
Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO. Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
<Warning> <Warning>
We recommend ensuring that your account is provisioned the application in Okta We recommend ensuring that your account is provisioned for the application in Okta prior to enforcing SAML SSO to prevent any unintended issues.
prior to enforcing SAML SSO to prevent any unintended issues.
</Warning> </Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
<Info>
In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step> </Step>
</Steps> </Steps>

View File

@@ -39,18 +39,30 @@ If your required identity provider is not shown in the list above, please reach
For enhanced security, Infisical enforces PKCE (Proof Key for Code Exchange) with the OAuth 2.0-based SSO providers and OIDC. This provides additional protection against authorization code interception attacks and strengthens your authentication flow security. For enhanced security, Infisical enforces PKCE (Proof Key for Code Exchange) with the OAuth 2.0-based SSO providers and OIDC. This provides additional protection against authorization code interception attacks and strengthens your authentication flow security.
</Info> </Info>
## SSO Break Glass
In the event your SSO provider experiences downtime, and you need to access Infisical, Organization Admins can utilize the Admin Login Portal to bypass SSO enforcement.
This portal is accessible at `/login/admin` (e.g., https://app.infisical.com/login/admin).
<Note>
To bypass SSO for an organization, you must be an **Organization Admin** for that specific organization. This **Organization Admin** role is independent of **Server Admin** status. Being a **Server Admin** alone does not grant permission to use this bypass feature.
</Note>
## FAQ ## FAQ
<AccordionGroup> <AccordionGroup>
<Accordion title="Why does Infisical require additional email verification for users connected via SAML?"> <Accordion title="Why does Infisical require additional email verification for users connected via SAML?">
By default, Infisical Cloud is configured to not trust emails from external By default, Infisical Cloud is configured to not trust emails from external
identity providers to prevent any malicious account takeover attempts via identity providers to prevent any malicious account takeover attempts via
email spoofing. Accordingly, Infisical creates a new user for anyone provisioned email spoofing. Accordingly, Infisical creates a new user for anyone provisioned
through an external identity provider and requires an additional email through an external identity provider and requires an additional email
verification step upon their first login. verification step upon their first login.
If you're running a self-hosted instance of Infisical and would like it to trust emails from external identity providers, If you're running a self-hosted instance of Infisical and would like it to trust emails from external identity providers,
you can configure this behavior in the Server Admin Console. you can configure this behavior in the Server Admin Console.
</Accordion>
</Accordion> <Accordion title="Why do I get redirected to SSO when trying to use the Admin Login Portal?">
You are likely being redirected because you do not have email authentication mode enabled, or you're not an **Organization Admin**. This portal requires **Organization Admin** status and direct credential login (email and password). **Server Admin** status alone is insufficient.
</Accordion>
</AccordionGroup> </AccordionGroup>

View File

@@ -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 team@infisical.com 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

View File

@@ -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 team@infisical.com 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)

View File

@@ -2,16 +2,23 @@ import { faWrench } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Spinner, Tooltip } from "@app/components/v2"; import { Spinner, Tooltip } from "@app/components/v2";
import { useSubscription } from "@app/context";
import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs"; import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
import { usePopUp } from "@app/hooks";
import { SecretSync, useSecretSyncOptions } from "@app/hooks/api/secretSyncs"; 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;
}; };
export const SecretSyncSelect = ({ onSelect }: Props) => { export const SecretSyncSelect = ({ onSelect }: Props) => {
const { subscription } = useSubscription();
const { isPending, data: secretSyncOptions } = useSecretSyncOptions(); const { isPending, data: secretSyncOptions } = useSecretSyncOptions();
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">
@@ -23,14 +30,17 @@ export const SecretSyncSelect = ({ onSelect }: Props) => {
return ( return (
<div className="grid grid-cols-4 gap-2"> <div className="grid grid-cols-4 gap-2">
{secretSyncOptions?.map(({ destination }) => { {secretSyncOptions?.map(({ destination, enterprise }) => {
const { image, name } = SECRET_SYNC_MAP[destination]; const { image, name } = SECRET_SYNC_MAP[destination];
return ( return (
<button <button
type="button" type="button"
key={destination} onClick={() =>
onClick={() => onSelect(destination)} enterprise && !subscription.enterpriseSecretSyncs
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" ? handlePopUpOpen("upgradePlan")
: onSelect(destination)
}
className="group relative flex h-28 cursor-pointer flex-col items-center justify-center overflow-hidden rounded-md border border-mineshaft-600 bg-mineshaft-700 p-4 duration-200 hover:bg-mineshaft-600"
> >
<img <img
src={`/images/integrations/${image}`} src={`/images/integrations/${image}`}
@@ -45,6 +55,11 @@ export const SecretSyncSelect = ({ onSelect }: Props) => {
</button> </button>
); );
})} })}
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You can use every Secret Sync 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"

View File

@@ -35,7 +35,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" },
@@ -65,7 +65,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"]) => {

View File

@@ -21,6 +21,7 @@ export type TSecretSyncOption = {
name: string; name: string;
destination: SecretSync; destination: SecretSync;
canImportSecrets: boolean; canImportSecrets: boolean;
enterprise?: boolean;
}; };
export type TSecretSync = export type TSecretSync =

View File

@@ -50,4 +50,6 @@ export type SubscriptionPlan = {
enforceMfa: boolean; enforceMfa: boolean;
projectTemplates: boolean; projectTemplates: boolean;
kmip: boolean; kmip: boolean;
enterpriseSecretSyncs: boolean;
enterpriseAppConnections: boolean;
}; };

View File

@@ -1,8 +1,11 @@
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 { 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 +14,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,13 +31,16 @@ 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 <button
type="button" type="button"
key={option.app} onClick={() =>
onClick={() => onSelect(option.app)} enterprise && !subscription.enterpriseAppConnections
? handlePopUpOpen("upgradePlan")
: onSelect(option.app)
}
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" 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"
> >
<img <img
@@ -48,6 +57,11 @@ export const AppConnectionsSelect = ({ onSelect }: Props) => {
</button> </button>
); );
})} })}
<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"

View File

@@ -129,7 +129,16 @@ export const OrgGeneralAuthSection = () => {
level. level.
</span> </span>
<p className="mt-4"> <p className="mt-4">
In case of a lockout, admins can use the admin login portal at{" "} In case of a lockout, admins can use the{" "}
<a
target="_blank"
className="underline underline-offset-2 hover:text-mineshaft-300"
href="https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal"
rel="noreferrer"
>
Admin Login Portal
</a>{" "}
at{" "}
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"

View File

@@ -212,7 +212,16 @@ export const OrgOIDCSection = (): JSX.Element => {
level. level.
</span> </span>
<p className="mt-4"> <p className="mt-4">
In case of a lockout, admins can use the admin login portal at{" "} In case of a lockout, admins can use the{" "}
<a
target="_blank"
className="underline underline-offset-2 hover:text-mineshaft-300"
href="https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal"
rel="noreferrer"
>
Admin Login Portal
</a>{" "}
at{" "}
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"