mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge
This commit is contained in:
@@ -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({
|
||||||
@@ -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,
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { OrgServiceActor } from "@app/lib/types";
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
|
||||||
import { AppConnection } from "../app-connection-enums";
|
import { AppConnection } from "../../../../services/app-connection/app-connection-enums";
|
||||||
|
import { TLicenseServiceFactory } from "../../license/license-service";
|
||||||
import { listOCICompartments, listOCIVaultKeys, listOCIVaults } from "./oci-connection-fns";
|
import { listOCICompartments, listOCIVaultKeys, listOCIVaults } from "./oci-connection-fns";
|
||||||
import { TOCIConnection } from "./oci-connection-types";
|
import { TOCIConnection } from "./oci-connection-types";
|
||||||
|
|
||||||
@@ -22,8 +24,23 @@ type TListOCIVaultKeysDTO = {
|
|||||||
vaultOcid: string;
|
vaultOcid: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ociConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
|
// Enterprise check
|
||||||
|
export const checkPlan = async (licenseService: Pick<TLicenseServiceFactory, "getPlan">, orgId: string) => {
|
||||||
|
const plan = await licenseService.getPlan(orgId);
|
||||||
|
if (!plan.enterpriseAppConnections)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to use app connection due to plan restriction. Upgrade plan to access enterprise app connections."
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ociConnectionService = (
|
||||||
|
getAppConnection: TGetAppConnectionFunc,
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">
|
||||||
|
) => {
|
||||||
const listCompartments = async (connectionId: string, actor: OrgServiceActor) => {
|
const listCompartments = async (connectionId: string, actor: OrgServiceActor) => {
|
||||||
|
await checkPlan(licenseService, actor.orgId);
|
||||||
|
|
||||||
const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor);
|
const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -36,6 +53,8 @@ export const ociConnectionService = (getAppConnection: TGetAppConnectionFunc) =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
const listVaults = async ({ connectionId, compartmentOcid }: TListOCIVaultsDTO, actor: OrgServiceActor) => {
|
const listVaults = async ({ connectionId, compartmentOcid }: TListOCIVaultsDTO, actor: OrgServiceActor) => {
|
||||||
|
await checkPlan(licenseService, actor.orgId);
|
||||||
|
|
||||||
const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor);
|
const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -51,6 +70,8 @@ export const ociConnectionService = (getAppConnection: TGetAppConnectionFunc) =>
|
|||||||
{ connectionId, compartmentOcid, vaultOcid }: TListOCIVaultKeysDTO,
|
{ connectionId, compartmentOcid, vaultOcid }: TListOCIVaultKeysDTO,
|
||||||
actor: OrgServiceActor
|
actor: OrgServiceActor
|
||||||
) => {
|
) => {
|
||||||
|
await checkPlan(licenseService, actor.orgId);
|
||||||
|
|
||||||
const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor);
|
const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -2,7 +2,7 @@ import z from "zod";
|
|||||||
|
|
||||||
import { DiscriminativePick } from "@app/lib/types";
|
import { DiscriminativePick } from "@app/lib/types";
|
||||||
|
|
||||||
import { AppConnection } from "../app-connection-enums";
|
import { AppConnection } from "../../../../services/app-connection/app-connection-enums";
|
||||||
import {
|
import {
|
||||||
CreateOCIConnectionSchema,
|
CreateOCIConnectionSchema,
|
||||||
OCIConnectionSchema,
|
OCIConnectionSchema,
|
||||||
@@ -29,7 +29,9 @@ export const getDefaultOnPremFeatures = () => {
|
|||||||
secretApproval: true,
|
secretApproval: true,
|
||||||
secretRotation: true,
|
secretRotation: true,
|
||||||
caCrl: false,
|
caCrl: false,
|
||||||
sshHostGroups: false
|
sshHostGroups: false,
|
||||||
|
enterpriseSecretSyncs: false,
|
||||||
|
enterpriseAppConnections: false
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
@@ -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";
|
||||||
@@ -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()
|
||||||
});
|
});
|
||||||
@@ -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";
|
||||||
|
|
||||||
@@ -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({
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -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
|
||||||
|
};
|
||||||
|
|||||||
@@ -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)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,3 +26,8 @@ export enum SecretSyncImportBehavior {
|
|||||||
PrioritizeSource = "prioritize-source",
|
PrioritizeSource = "prioritize-source",
|
||||||
PrioritizeDestination = "prioritize-destination"
|
PrioritizeDestination = "prioritize-destination"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum SecretSyncPlanType {
|
||||||
|
Enterprise = "enterprise",
|
||||||
|
Regular = "regular"
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
import { SecretSync, SecretSyncPlanType } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
|
||||||
export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
|
export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
|
||||||
[SecretSync.AWSParameterStore]: "AWS Parameter Store",
|
[SecretSync.AWSParameterStore]: "AWS Parameter Store",
|
||||||
@@ -36,3 +36,21 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
|||||||
[SecretSync.TeamCity]: AppConnection.TeamCity,
|
[SecretSync.TeamCity]: AppConnection.TeamCity,
|
||||||
[SecretSync.OCIVault]: AppConnection.OCI
|
[SecretSync.OCIVault]: AppConnection.OCI
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
|
||||||
|
[SecretSync.AWSParameterStore]: SecretSyncPlanType.Regular,
|
||||||
|
[SecretSync.AWSSecretsManager]: SecretSyncPlanType.Regular,
|
||||||
|
[SecretSync.GitHub]: SecretSyncPlanType.Regular,
|
||||||
|
[SecretSync.GCPSecretManager]: SecretSyncPlanType.Regular,
|
||||||
|
[SecretSync.AzureKeyVault]: SecretSyncPlanType.Regular,
|
||||||
|
[SecretSync.AzureAppConfiguration]: SecretSyncPlanType.Regular,
|
||||||
|
[SecretSync.Databricks]: SecretSyncPlanType.Regular,
|
||||||
|
[SecretSync.Humanitec]: SecretSyncPlanType.Regular,
|
||||||
|
[SecretSync.TerraformCloud]: SecretSyncPlanType.Regular,
|
||||||
|
[SecretSync.Camunda]: SecretSyncPlanType.Regular,
|
||||||
|
[SecretSync.Vercel]: SecretSyncPlanType.Regular,
|
||||||
|
[SecretSync.Windmill]: SecretSyncPlanType.Regular,
|
||||||
|
[SecretSync.HCVault]: SecretSyncPlanType.Regular,
|
||||||
|
[SecretSync.TeamCity]: SecretSyncPlanType.Regular,
|
||||||
|
[SecretSync.OCIVault]: SecretSyncPlanType.Enterprise
|
||||||
|
};
|
||||||
|
|||||||
@@ -5,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
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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` });
|
||||||
|
|||||||
@@ -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.
|
||||||

|

|
||||||
|
|
||||||
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`.
|
||||||

|

|
||||||

|

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

|

|
||||||
|
|
||||||
Select **Regular Web Application** and press **Create**.
|
Select **Regular Web Application** and press **Create**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
```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>
|
||||||
|
|||||||
@@ -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."
|
|||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||

|

|
||||||
</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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||

|

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

|

|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||

|

|
||||||
</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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<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**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<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**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Next click **Add predefined mapper**.
|
Next click **Add predefined mapper**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Select **User Property**.
|
Select **User Property**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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:
|
||||||
|
|
||||||

|

|
||||||
</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`.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||

|

|
||||||
</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.
|
||||||
|
|
||||||

|

|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"]) => {
|
||||||
|
|||||||
@@ -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 =
|
||||||
|
|||||||
@@ -50,4 +50,6 @@ export type SubscriptionPlan = {
|
|||||||
enforceMfa: boolean;
|
enforceMfa: boolean;
|
||||||
projectTemplates: boolean;
|
projectTemplates: boolean;
|
||||||
kmip: boolean;
|
kmip: boolean;
|
||||||
|
enterpriseSecretSyncs: boolean;
|
||||||
|
enterpriseAppConnections: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user