From 2cbad206b593c1b2a8981fad6c9af98a50a4c563 Mon Sep 17 00:00:00 2001 From: x032205 Date: Sat, 17 May 2025 03:02:33 -0400 Subject: [PATCH 01/31] feat(audit-logs): Audit org updates, project create / update / delete --- .../ee/services/audit-log/audit-log-types.ts | 44 ++++++++--- .../src/ee/services/license/license-fns.ts | 4 +- .../server/routes/v1/organization-router.ts | 11 ++- .../src/server/routes/v1/project-router.ts | 77 +++++++++++++++++++ .../src/server/routes/v2/project-router.ts | 42 ++++++---- .../src/hooks/api/auditLogs/constants.tsx | 9 ++- frontend/src/hooks/api/auditLogs/enums.tsx | 9 ++- 7 files changed, 164 insertions(+), 32 deletions(-) diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index 365ada987..2b2fe1f4c 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -315,7 +315,6 @@ export enum EventType { CREATE_PROJECT_TEMPLATE = "create-project-template", UPDATE_PROJECT_TEMPLATE = "update-project-template", DELETE_PROJECT_TEMPLATE = "delete-project-template", - APPLY_PROJECT_TEMPLATE = "apply-project-template", GET_APP_CONNECTIONS = "get-app-connections", GET_AVAILABLE_APP_CONNECTIONS_DETAILS = "get-available-app-connections-details", GET_APP_CONNECTION = "get-app-connection", @@ -375,7 +374,13 @@ export enum EventType { MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST = "microsoft-teams-workflow-integration-list", PROJECT_ASSUME_PRIVILEGE_SESSION_START = "project-assume-privileges-session-start", - PROJECT_ASSUME_PRIVILEGE_SESSION_END = "project-assume-privileges-session-end" + PROJECT_ASSUME_PRIVILEGE_SESSION_END = "project-assume-privileges-session-end", + + UPDATE_ORG = "update-org", + + CREATE_PROJECT = "create-project", + UPDATE_PROJECT = "update-project", + DELETE_PROJECT = "delete-project" } export const filterableSecretEvents: EventType[] = [ @@ -2451,14 +2456,6 @@ interface DeleteProjectTemplateEvent { }; } -interface ApplyProjectTemplateEvent { - type: EventType.APPLY_PROJECT_TEMPLATE; - metadata: { - template: string; - projectId: string; - }; -} - interface GetAppConnectionsEvent { type: EventType.GET_APP_CONNECTIONS; metadata: { @@ -2913,6 +2910,26 @@ interface MicrosoftTeamsWorkflowIntegrationUpdateEvent { }; } +interface OrgUpdateEvent { + type: EventType.UPDATE_ORG; + metadata: Record; // The update parameters +} + +interface ProjectCreateEvent { + type: EventType.CREATE_PROJECT; + metadata: Record; // The creation parameters +} + +interface ProjectUpdateEvent { + type: EventType.UPDATE_PROJECT; + metadata: Record; // The update parameters +} + +interface ProjectDeleteEvent { + type: EventType.DELETE_PROJECT; + metadata: Record; +} + export type Event = | GetSecretsEvent | GetSecretEvent @@ -3117,7 +3134,6 @@ export type Event = | CreateProjectTemplateEvent | UpdateProjectTemplateEvent | DeleteProjectTemplateEvent - | ApplyProjectTemplateEvent | GetAppConnectionsEvent | GetAvailableAppConnectionsDetailsEvent | GetAppConnectionEvent @@ -3179,4 +3195,8 @@ export type Event = | MicrosoftTeamsWorkflowIntegrationGetTeamsEvent | MicrosoftTeamsWorkflowIntegrationGetEvent | MicrosoftTeamsWorkflowIntegrationListEvent - | MicrosoftTeamsWorkflowIntegrationUpdateEvent; + | MicrosoftTeamsWorkflowIntegrationUpdateEvent + | OrgUpdateEvent + | ProjectCreateEvent + | ProjectUpdateEvent + | ProjectDeleteEvent; diff --git a/backend/src/ee/services/license/license-fns.ts b/backend/src/ee/services/license/license-fns.ts index b7ae6f7ee..8326d08a1 100644 --- a/backend/src/ee/services/license/license-fns.ts +++ b/backend/src/ee/services/license/license-fns.ts @@ -26,8 +26,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ customRateLimits: false, customAlerts: false, secretAccessInsights: false, - auditLogs: false, - auditLogsRetentionDays: 0, + auditLogs: true, + auditLogsRetentionDays: 2, auditLogStreams: false, auditLogStreamLimit: 3, samlSSO: false, diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts index e14dacebb..1ec34d5a1 100644 --- a/backend/src/server/routes/v1/organization-router.ts +++ b/backend/src/server/routes/v1/organization-router.ts @@ -301,8 +301,17 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => { data: req.body }); + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + event: { + type: EventType.UPDATE_ORG, + metadata: req.body + } + }); + return { - message: "Successfully changed organization name", + message: "Successfully updated organization", organization }; } diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index 2e983cb83..8a2e74e74 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -263,6 +263,17 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorOrgId: req.permission.orgId }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + projectId: req.params.workspaceId, + event: { + type: EventType.DELETE_PROJECT, + metadata: {} + } + }); + return { workspace }; } }); @@ -297,6 +308,19 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { projectId: req.params.workspaceId, name: req.body.name }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + projectId: req.params.workspaceId, + event: { + type: EventType.UPDATE_PROJECT, + metadata: { + name: req.body.name + } + } + }); + return { message: "Successfully changed workspace name", workspace @@ -375,6 +399,17 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actor: req.permission.type, actorOrgId: req.permission.orgId }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + projectId: req.params.workspaceId, + event: { + type: EventType.UPDATE_PROJECT, + metadata: req.body + } + }); + return { workspace }; @@ -411,6 +446,17 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { projectId: req.params.workspaceId, autoCapitalization: req.body.autoCapitalization }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + projectId: req.params.workspaceId, + event: { + type: EventType.UPDATE_PROJECT, + metadata: req.body + } + }); + return { message: "Successfully changed workspace settings", workspace @@ -448,6 +494,17 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { projectId: req.params.workspaceId, hasDeleteProtection: req.body.hasDeleteProtection }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + projectId: req.params.workspaceId, + event: { + type: EventType.UPDATE_PROJECT, + metadata: req.body + } + }); + return { message: "Successfully changed workspace settings", workspace @@ -486,6 +543,16 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { workspaceSlug: req.params.workspaceSlug }); + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + projectId: workspace.id, + event: { + type: EventType.UPDATE_PROJECT, + metadata: req.body + } + }); + return { message: "Successfully changed workspace version limit", workspace @@ -524,6 +591,16 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { auditLogsRetentionDays: req.body.auditLogsRetentionDays }); + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + projectId: workspace.id, + event: { + type: EventType.UPDATE_PROJECT, + metadata: req.body + } + }); + return { message: "Successfully updated project's audit logs retention period", workspace diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/project-router.ts index 3d92bfb1a..a8778facf 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/project-router.ts @@ -206,19 +206,15 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { } }); - if (req.body.template) { - await server.services.auditLog.createAuditLog({ - ...req.auditLogInfo, - orgId: req.permission.orgId, - event: { - type: EventType.APPLY_PROJECT_TEMPLATE, - metadata: { - template: req.body.template, - projectId: project.id - } - } - }); - } + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + projectId: project.id, + event: { + type: EventType.CREATE_PROJECT, + metadata: req.body + } + }); return { project }; } @@ -262,6 +258,16 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actor: req.permission.type }); + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + projectId: project.id, + event: { + type: EventType.DELETE_PROJECT, + metadata: {} + } + }); + return project; } }); @@ -341,6 +347,16 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { actorOrgId: req.permission.orgId }); + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + orgId: req.permission.orgId, + projectId: project.id, + event: { + type: EventType.UPDATE_PROJECT, + metadata: req.body + } + }); + return project; } }); diff --git a/frontend/src/hooks/api/auditLogs/constants.tsx b/frontend/src/hooks/api/auditLogs/constants.tsx index f726566cd..555d66b88 100644 --- a/frontend/src/hooks/api/auditLogs/constants.tsx +++ b/frontend/src/hooks/api/auditLogs/constants.tsx @@ -123,7 +123,6 @@ export const eventToNameMap: { [K in EventType]: string } = { [EventType.CREATE_PROJECT_TEMPLATE]: "Create project template", [EventType.UPDATE_PROJECT_TEMPLATE]: "Update project template", [EventType.DELETE_PROJECT_TEMPLATE]: "Delete project template", - [EventType.APPLY_PROJECT_TEMPLATE]: "Apply project template", [EventType.GET_APP_CONNECTIONS]: "List App Connections", [EventType.GET_AVAILABLE_APP_CONNECTIONS_DETAILS]: "List App Connections Details", [EventType.GET_APP_CONNECTION]: "Get App Connection", @@ -189,7 +188,13 @@ export const eventToNameMap: { [K in EventType]: string } = { [EventType.ADD_IDENTITY_LDAP_AUTH]: "Attached LDAP Auth to identity", [EventType.UPDATE_IDENTITY_LDAP_AUTH]: "Updated LDAP Auth for identity", [EventType.GET_IDENTITY_LDAP_AUTH]: "Retrieved LDAP Auth for identity", - [EventType.REVOKE_IDENTITY_LDAP_AUTH]: "Revoked LDAP Auth for identity" + [EventType.REVOKE_IDENTITY_LDAP_AUTH]: "Revoked LDAP Auth for identity", + + [EventType.UPDATE_ORG]: "Update Organization", + + [EventType.CREATE_PROJECT]: "Create Project", + [EventType.UPDATE_PROJECT]: "Update Project", + [EventType.DELETE_PROJECT]: "Delete Project" }; export const userAgentTypeToNameMap: { [K in UserAgentType]: string } = { diff --git a/frontend/src/hooks/api/auditLogs/enums.tsx b/frontend/src/hooks/api/auditLogs/enums.tsx index b74969d6d..19dfd9522 100644 --- a/frontend/src/hooks/api/auditLogs/enums.tsx +++ b/frontend/src/hooks/api/auditLogs/enums.tsx @@ -131,7 +131,6 @@ export enum EventType { CREATE_PROJECT_TEMPLATE = "create-project-template", UPDATE_PROJECT_TEMPLATE = "update-project-template", DELETE_PROJECT_TEMPLATE = "delete-project-template", - APPLY_PROJECT_TEMPLATE = "apply-project-template", GET_APP_CONNECTIONS = "get-app-connections", GET_AVAILABLE_APP_CONNECTIONS_DETAILS = "get-available-app-connections-details", GET_APP_CONNECTION = "get-app-connection", @@ -183,5 +182,11 @@ export enum EventType { MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CHECK_INSTALLATION_STATUS = "microsoft-teams-workflow-integration-check-installation-status", MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET_TEAMS = "microsoft-teams-workflow-integration-get-teams", MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET = "microsoft-teams-workflow-integration-get", - MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST = "microsoft-teams-workflow-integration-list" + MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST = "microsoft-teams-workflow-integration-list", + + UPDATE_ORG = "update-org", + + CREATE_PROJECT = "create-project", + UPDATE_PROJECT = "update-project", + DELETE_PROJECT = "delete-project" } From 9bc5c55cd0fa25c6a424666a4965144884c4944c Mon Sep 17 00:00:00 2001 From: x032205 Date: Sat, 17 May 2025 03:03:44 -0400 Subject: [PATCH 02/31] revert license --- backend/src/ee/services/license/license-fns.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/ee/services/license/license-fns.ts b/backend/src/ee/services/license/license-fns.ts index 8326d08a1..b7ae6f7ee 100644 --- a/backend/src/ee/services/license/license-fns.ts +++ b/backend/src/ee/services/license/license-fns.ts @@ -26,8 +26,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ customRateLimits: false, customAlerts: false, secretAccessInsights: false, - auditLogs: true, - auditLogsRetentionDays: 2, + auditLogs: false, + auditLogsRetentionDays: 0, auditLogStreams: false, auditLogStreamLimit: 3, samlSSO: false, From a77cc77be8ad1db98a4e55577b1b8b86c331dc62 Mon Sep 17 00:00:00 2001 From: x032205 Date: Sat, 17 May 2025 03:15:22 -0400 Subject: [PATCH 03/31] explicitly pass values --- .../ee/services/audit-log/audit-log-types.ts | 2 +- .../src/server/routes/v1/project-router.ts | 25 +++++++++++++++---- .../src/server/routes/v2/project-router.ts | 23 +++++++++++++++-- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index 2b2fe1f4c..7793cd97f 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -2917,7 +2917,7 @@ interface OrgUpdateEvent { interface ProjectCreateEvent { type: EventType.CREATE_PROJECT; - metadata: Record; // The creation parameters + metadata: Record; // The creation parameters } interface ProjectUpdateEvent { diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index 8a2e74e74..11fb63ef4 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -406,7 +406,14 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { projectId: req.params.workspaceId, event: { type: EventType.UPDATE_PROJECT, - metadata: req.body + metadata: { + ...(req.body.name !== undefined && { name: req.body.name }), + ...(req.body.description !== undefined && { description: req.body.description }), + ...(req.body.autoCapitalization !== undefined && { autoCapitalization: req.body.autoCapitalization }), + ...(req.body.hasDeleteProtection !== undefined && { hasDeleteProtection: req.body.hasDeleteProtection }), + ...(req.body.slug !== undefined && { slug: req.body.slug }), + ...(req.body.secretSharing !== undefined && { secretSharing: req.body.secretSharing }) + } } }); @@ -453,7 +460,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { projectId: req.params.workspaceId, event: { type: EventType.UPDATE_PROJECT, - metadata: req.body + metadata: { + autoCapitalization: req.body.autoCapitalization + } } }); @@ -501,7 +510,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { projectId: req.params.workspaceId, event: { type: EventType.UPDATE_PROJECT, - metadata: req.body + metadata: { + hasDeleteProtection: req.body.hasDeleteProtection + } } }); @@ -549,7 +560,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { projectId: workspace.id, event: { type: EventType.UPDATE_PROJECT, - metadata: req.body + metadata: { + pitVersionLimit: req.body.pitVersionLimit + } } }); @@ -597,7 +610,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { projectId: workspace.id, event: { type: EventType.UPDATE_PROJECT, - metadata: req.body + metadata: { + auditLogsRetentionDays: req.body.auditLogsRetentionDays + } } }); diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/project-router.ts index a8778facf..d06a7df47 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/project-router.ts @@ -212,7 +212,21 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { projectId: project.id, event: { type: EventType.CREATE_PROJECT, - metadata: req.body + metadata: { + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + ...(req.body.projectName !== undefined && { workspaceName: req.body.projectName }), + ...(req.body.projectDescription !== undefined && { workspaceDescription: req.body.projectDescription }), + ...(req.body.slug !== undefined && { slug: req.body.slug }), + ...(req.body.kmsKeyId !== undefined && { kmsKeyId: req.body.kmsKeyId }), + ...(req.body.template !== undefined && { template: req.body.template }), + ...(req.body.type !== undefined && { type: req.body.type }), + ...(req.body.shouldCreateDefaultEnvs !== undefined && { + createDefaultEnvs: req.body.shouldCreateDefaultEnvs + }) + } } }); @@ -353,7 +367,12 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { projectId: project.id, event: { type: EventType.UPDATE_PROJECT, - metadata: req.body + metadata: { + ...(req.body.name !== undefined && { name: req.body.name }), + ...(req.body.description !== undefined && { description: req.body.description }), + ...(req.body.autoCapitalization !== undefined && { autoCapitalization: req.body.autoCapitalization }), + ...(req.body.hasDeleteProtection !== undefined && { hasDeleteProtection: req.body.hasDeleteProtection }) + } } }); From 966294bd0ea859fe597bcb2b0558e3fdd78f52c1 Mon Sep 17 00:00:00 2001 From: x032205 Date: Mon, 19 May 2025 23:33:58 -0400 Subject: [PATCH 04/31] move OCI Vault Secret Sync to EE --- .../oci-vault-sync-router.ts | 4 +- .../src/ee/services/license/license-fns.ts | 3 +- .../src/ee/services/license/license-types.ts | 1 + .../services/secret-sync/oci-vault/index.ts | 0 .../oci-vault/oci-vault-sync-constants.ts | 3 +- .../oci-vault/oci-vault-sync-fns.ts | 6 +- .../oci-vault/oci-vault-sync-schemas.ts | 3 +- .../oci-vault/oci-vault-sync-types.ts | 0 .../routes/v1/secret-sync-routers/index.ts | 2 +- .../secret-sync-routers/secret-sync-router.ts | 2 +- .../certificate/certificate-service.ts | 2 +- .../services/secret-sync/secret-sync-fns.ts | 2 +- .../services/secret-sync/secret-sync-types.ts | 7 ++- .../secret-syncs/SecretSyncSelect.tsx | 62 ++++++++++++++----- .../src/hooks/api/secretSyncs/types/index.ts | 1 + frontend/src/hooks/api/subscriptions/types.ts | 1 + 16 files changed, 69 insertions(+), 30 deletions(-) rename backend/src/{server => ee}/routes/v1/secret-sync-routers/oci-vault-sync-router.ts (73%) rename backend/src/{ => ee}/services/secret-sync/oci-vault/index.ts (100%) rename backend/src/{ => ee}/services/secret-sync/oci-vault/oci-vault-sync-constants.ts (89%) rename backend/src/{ => ee}/services/secret-sync/oci-vault/oci-vault-sync-fns.ts (99%) rename backend/src/{ => ee}/services/secret-sync/oci-vault/oci-vault-sync-schemas.ts (97%) rename backend/src/{ => ee}/services/secret-sync/oci-vault/oci-vault-sync-types.ts (100%) diff --git a/backend/src/server/routes/v1/secret-sync-routers/oci-vault-sync-router.ts b/backend/src/ee/routes/v1/secret-sync-routers/oci-vault-sync-router.ts similarity index 73% rename from backend/src/server/routes/v1/secret-sync-routers/oci-vault-sync-router.ts rename to backend/src/ee/routes/v1/secret-sync-routers/oci-vault-sync-router.ts index b46f27a50..4d316369c 100644 --- a/backend/src/server/routes/v1/secret-sync-routers/oci-vault-sync-router.ts +++ b/backend/src/ee/routes/v1/secret-sync-routers/oci-vault-sync-router.ts @@ -2,10 +2,10 @@ import { CreateOCIVaultSyncSchema, OCIVaultSyncSchema, UpdateOCIVaultSyncSchema -} from "@app/services/secret-sync/oci-vault"; +} from "@app/ee/services/secret-sync/oci-vault"; import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; -import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints"; +import { registerSyncSecretsEndpoints } from "../../../../server/routes/v1/secret-sync-routers/secret-sync-endpoints"; export const registerOCIVaultSyncRouter = async (server: FastifyZodProvider) => registerSyncSecretsEndpoints({ diff --git a/backend/src/ee/services/license/license-fns.ts b/backend/src/ee/services/license/license-fns.ts index b7ae6f7ee..9725b9b52 100644 --- a/backend/src/ee/services/license/license-fns.ts +++ b/backend/src/ee/services/license/license-fns.ts @@ -54,7 +54,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ projectTemplates: false, kmip: false, gateway: false, - sshHostGroups: false + sshHostGroups: false, + enterpriseSecretSyncs: false }); export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => { diff --git a/backend/src/ee/services/license/license-types.ts b/backend/src/ee/services/license/license-types.ts index 358849fb2..51db5cb18 100644 --- a/backend/src/ee/services/license/license-types.ts +++ b/backend/src/ee/services/license/license-types.ts @@ -72,6 +72,7 @@ export type TFeatureSet = { kmip: false; gateway: false; sshHostGroups: false; + enterpriseSecretSyncs: false; }; export type TOrgPlansTableDTO = { diff --git a/backend/src/services/secret-sync/oci-vault/index.ts b/backend/src/ee/services/secret-sync/oci-vault/index.ts similarity index 100% rename from backend/src/services/secret-sync/oci-vault/index.ts rename to backend/src/ee/services/secret-sync/oci-vault/index.ts diff --git a/backend/src/services/secret-sync/oci-vault/oci-vault-sync-constants.ts b/backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-constants.ts similarity index 89% rename from backend/src/services/secret-sync/oci-vault/oci-vault-sync-constants.ts rename to backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-constants.ts index 9e2aad056..b864e354b 100644 --- a/backend/src/services/secret-sync/oci-vault/oci-vault-sync-constants.ts +++ b/backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-constants.ts @@ -6,5 +6,6 @@ export const OCI_VAULT_SYNC_LIST_OPTION: TSecretSyncListItem = { name: "OCI Vault", destination: SecretSync.OCIVault, connection: AppConnection.OCI, - canImportSecrets: true + canImportSecrets: true, + enterprise: true }; diff --git a/backend/src/services/secret-sync/oci-vault/oci-vault-sync-fns.ts b/backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-fns.ts similarity index 99% rename from backend/src/services/secret-sync/oci-vault/oci-vault-sync-fns.ts rename to backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-fns.ts index e270f2e02..152d02c51 100644 --- a/backend/src/services/secret-sync/oci-vault/oci-vault-sync-fns.ts +++ b/backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-fns.ts @@ -1,7 +1,5 @@ import { secrets, vault } from "oci-sdk"; -import { delay } from "@app/lib/delay"; -import { getOCIProvider } from "@app/services/app-connection/oci"; import { TCreateOCIVaultVariable, TDeleteOCIVaultVariable, @@ -9,7 +7,9 @@ import { TOCIVaultSyncWithCredentials, TUnmarkOCIVaultVariableFromDeletion, 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 { getOCIProvider } from "@app/services/app-connection/oci"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; diff --git a/backend/src/services/secret-sync/oci-vault/oci-vault-sync-schemas.ts b/backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-schemas.ts similarity index 97% rename from backend/src/services/secret-sync/oci-vault/oci-vault-sync-schemas.ts rename to backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-schemas.ts index 84a58bc8a..a0bd29382 100644 --- a/backend/src/services/secret-sync/oci-vault/oci-vault-sync-schemas.ts +++ b/backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-schemas.ts @@ -66,5 +66,6 @@ export const OCIVaultSyncListItemSchema = z.object({ name: z.literal("OCI Vault"), connection: z.literal(AppConnection.OCI), destination: z.literal(SecretSync.OCIVault), - canImportSecrets: z.literal(true) + canImportSecrets: z.literal(true), + enterprise: z.boolean() }); diff --git a/backend/src/services/secret-sync/oci-vault/oci-vault-sync-types.ts b/backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-types.ts similarity index 100% rename from backend/src/services/secret-sync/oci-vault/oci-vault-sync-types.ts rename to backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-types.ts diff --git a/backend/src/server/routes/v1/secret-sync-routers/index.ts b/backend/src/server/routes/v1/secret-sync-routers/index.ts index b5bd62ad6..c22a40432 100644 --- a/backend/src/server/routes/v1/secret-sync-routers/index.ts +++ b/backend/src/server/routes/v1/secret-sync-routers/index.ts @@ -1,5 +1,6 @@ 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 { registerAwsSecretsManagerSyncRouter } from "./aws-secrets-manager-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 { registerHCVaultSyncRouter } from "./hc-vault-sync-router"; import { registerHumanitecSyncRouter } from "./humanitec-sync-router"; -import { registerOCIVaultSyncRouter } from "./oci-vault-sync-router"; import { registerTeamCitySyncRouter } from "./teamcity-sync-router"; import { registerTerraformCloudSyncRouter } from "./terraform-cloud-sync-router"; import { registerVercelSyncRouter } from "./vercel-sync-router"; diff --git a/backend/src/server/routes/v1/secret-sync-routers/secret-sync-router.ts b/backend/src/server/routes/v1/secret-sync-routers/secret-sync-router.ts index a7a561738..9fb1c825a 100644 --- a/backend/src/server/routes/v1/secret-sync-routers/secret-sync-router.ts +++ b/backend/src/server/routes/v1/secret-sync-routers/secret-sync-router.ts @@ -1,6 +1,7 @@ import { z } from "zod"; 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 { readLimit } from "@app/server/config/rateLimiter"; 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 { HCVaultSyncListItemSchema, HCVaultSyncSchema } from "@app/services/secret-sync/hc-vault"; 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 { TerraformCloudSyncListItemSchema, TerraformCloudSyncSchema } from "@app/services/secret-sync/terraform-cloud"; import { VercelSyncListItemSchema, VercelSyncSchema } from "@app/services/secret-sync/vercel"; diff --git a/backend/src/services/certificate/certificate-service.ts b/backend/src/services/certificate/certificate-service.ts index 292b5f109..be3f8677e 100644 --- a/backend/src/services/certificate/certificate-service.ts +++ b/backend/src/services/certificate/certificate-service.ts @@ -8,6 +8,7 @@ import { ProjectPermissionCertificateActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; +import { NotFoundError } from "@app/lib/errors"; import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal"; import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; import { TCertificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal"; @@ -29,7 +30,6 @@ import { TGetCertPrivateKeyDTO, TRevokeCertDTO } from "./certificate-types"; -import { NotFoundError } from "@app/lib/errors"; type TCertificateServiceFactoryDep = { certificateDAL: Pick; diff --git a/backend/src/services/secret-sync/secret-sync-fns.ts b/backend/src/services/secret-sync/secret-sync-fns.ts index 1bb4da9db..bf00e5d31 100644 --- a/backend/src/services/secret-sync/secret-sync-fns.ts +++ b/backend/src/services/secret-sync/secret-sync-fns.ts @@ -1,6 +1,7 @@ import { AxiosError } from "axios"; import RE2 from "re2"; +import { OCI_VAULT_SYNC_LIST_OPTION, OCIVaultSyncFns } from "@app/ee/services/secret-sync/oci-vault"; import { AWS_PARAMETER_STORE_SYNC_LIST_OPTION, AwsParameterStoreSyncFns @@ -29,7 +30,6 @@ import { GcpSyncFns } from "./gcp/gcp-sync-fns"; import { HC_VAULT_SYNC_LIST_OPTION, HCVaultSyncFns } from "./hc-vault"; import { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec"; import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns"; -import { OCI_VAULT_SYNC_LIST_OPTION, OCIVaultSyncFns } from "./oci-vault"; import { TEAMCITY_SYNC_LIST_OPTION, TeamCitySyncFns } from "./teamcity"; import { TERRAFORM_CLOUD_SYNC_LIST_OPTION, TerraformCloudSyncFns } from "./terraform-cloud"; import { VERCEL_SYNC_LIST_OPTION, VercelSyncFns } from "./vercel"; diff --git a/backend/src/services/secret-sync/secret-sync-types.ts b/backend/src/services/secret-sync/secret-sync-types.ts index 64d027e18..adc4888ab 100644 --- a/backend/src/services/secret-sync/secret-sync-types.ts +++ b/backend/src/services/secret-sync/secret-sync-types.ts @@ -1,6 +1,12 @@ import { Job } from "bullmq"; 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 { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema"; import { @@ -67,7 +73,6 @@ import { THumanitecSyncListItem, THumanitecSyncWithCredentials } from "./humanitec"; -import { TOCIVaultSync, TOCIVaultSyncInput, TOCIVaultSyncListItem, TOCIVaultSyncWithCredentials } from "./oci-vault"; import { TTeamCitySync, TTeamCitySyncInput, diff --git a/frontend/src/components/secret-syncs/SecretSyncSelect.tsx b/frontend/src/components/secret-syncs/SecretSyncSelect.tsx index cbcba4513..428c1bf2d 100644 --- a/frontend/src/components/secret-syncs/SecretSyncSelect.tsx +++ b/frontend/src/components/secret-syncs/SecretSyncSelect.tsx @@ -4,14 +4,21 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Spinner, Tooltip } from "@app/components/v2"; import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs"; import { SecretSync, useSecretSyncOptions } from "@app/hooks/api/secretSyncs"; +import { twMerge } from "tailwind-merge"; +import { UpgradePlanModal } from "../license/UpgradePlanModal"; +import { usePopUp } from "@app/hooks"; +import { useSubscription } from "@app/context"; type Props = { onSelect: (destination: SecretSync) => void; }; export const SecretSyncSelect = ({ onSelect }: Props) => { + const { subscription } = useSubscription(); const { isPending, data: secretSyncOptions } = useSecretSyncOptions(); + const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const); + if (isPending) { return (
@@ -23,28 +30,49 @@ export const SecretSyncSelect = ({ onSelect }: Props) => { return (
- {secretSyncOptions?.map(({ destination }) => { + {secretSyncOptions?.map(({ destination, enterprise }) => { const { image, name } = SECRET_SYNC_MAP[destination]; return ( - + + ); })} + handlePopUpToggle("upgradePlan", isOpen)} + text="You can use every Secret Sync if you switch to Infisical's Enterprise plan." + /> Date: Tue, 20 May 2025 13:25:15 -0400 Subject: [PATCH 05/31] app connection + finishing touches --- .../oci-connection-router.ts | 10 +-- .../services/app-connections}/oci/index.ts | 0 .../oci/oci-connection-enums.ts | 0 .../oci/oci-connection-fns.ts | 0 .../oci/oci-connection-schemas.ts | 0 .../oci/oci-connection-service.ts | 25 ++++++- .../oci/oci-connection-types.ts | 2 +- .../services/license/__mocks__/license-fns.ts | 4 +- .../src/ee/services/license/license-fns.ts | 3 +- .../src/ee/services/license/license-types.ts | 1 + .../oci-vault/oci-vault-sync-fns.ts | 2 +- .../oci-vault/oci-vault-sync-types.ts | 2 +- backend/src/server/routes/index.ts | 9 ++- .../app-connection-router.ts | 2 +- .../routes/v1/app-connection-routers/index.ts | 2 +- .../app-connection/app-connection-enums.ts | 5 ++ .../app-connection/app-connection-fns.ts | 6 +- .../app-connection/app-connection-maps.ts | 24 ++++++- .../app-connection/app-connection-service.ts | 45 +++++++++++-- .../app-connection/app-connection-types.ts | 12 ++-- .../services/secret-sync/secret-sync-enums.ts | 5 ++ .../services/secret-sync/secret-sync-maps.ts | 20 +++++- .../services/secret-sync/secret-sync-queue.ts | 47 +++++++++++-- .../secret-sync/secret-sync-service.ts | 59 ++++++++++++++++- docs/integrations/app-connections/oci.mdx | 7 ++ docs/integrations/secret-syncs/oci-vault.mdx | 7 ++ .../secret-syncs/SecretSyncSelect.tsx | 13 ++-- frontend/src/helpers/appConnections.ts | 4 +- frontend/src/hooks/api/subscriptions/types.ts | 1 + .../components/AppConnectionList.tsx | 66 ++++++++++++++----- .../OrgSecretShareLimitSection.tsx | 2 +- 31 files changed, 319 insertions(+), 66 deletions(-) rename backend/src/{server => ee}/routes/v1/app-connection-routers/oci-connection-router.ts (94%) rename backend/src/{services/app-connection => ee/services/app-connections}/oci/index.ts (100%) rename backend/src/{services/app-connection => ee/services/app-connections}/oci/oci-connection-enums.ts (100%) rename backend/src/{services/app-connection => ee/services/app-connections}/oci/oci-connection-fns.ts (100%) rename backend/src/{services/app-connection => ee/services/app-connections}/oci/oci-connection-schemas.ts (100%) rename backend/src/{services/app-connection => ee/services/app-connections}/oci/oci-connection-service.ts (68%) rename backend/src/{services/app-connection => ee/services/app-connections}/oci/oci-connection-types.ts (87%) diff --git a/backend/src/server/routes/v1/app-connection-routers/oci-connection-router.ts b/backend/src/ee/routes/v1/app-connection-routers/oci-connection-router.ts similarity index 94% rename from backend/src/server/routes/v1/app-connection-routers/oci-connection-router.ts rename to backend/src/ee/routes/v1/app-connection-routers/oci-connection-router.ts index d78eee3d9..e87e5b69e 100644 --- a/backend/src/server/routes/v1/app-connection-routers/oci-connection-router.ts +++ b/backend/src/ee/routes/v1/app-connection-routers/oci-connection-router.ts @@ -1,16 +1,16 @@ 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 { CreateOCIConnectionSchema, SanitizedOCIConnectionSchema, 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 { registerAppConnectionEndpoints } from "./app-connection-endpoints"; +import { registerAppConnectionEndpoints } from "../../../../server/routes/v1/app-connection-routers/app-connection-endpoints"; export const registerOCIConnectionRouter = async (server: FastifyZodProvider) => { registerAppConnectionEndpoints({ diff --git a/backend/src/services/app-connection/oci/index.ts b/backend/src/ee/services/app-connections/oci/index.ts similarity index 100% rename from backend/src/services/app-connection/oci/index.ts rename to backend/src/ee/services/app-connections/oci/index.ts diff --git a/backend/src/services/app-connection/oci/oci-connection-enums.ts b/backend/src/ee/services/app-connections/oci/oci-connection-enums.ts similarity index 100% rename from backend/src/services/app-connection/oci/oci-connection-enums.ts rename to backend/src/ee/services/app-connections/oci/oci-connection-enums.ts diff --git a/backend/src/services/app-connection/oci/oci-connection-fns.ts b/backend/src/ee/services/app-connections/oci/oci-connection-fns.ts similarity index 100% rename from backend/src/services/app-connection/oci/oci-connection-fns.ts rename to backend/src/ee/services/app-connections/oci/oci-connection-fns.ts diff --git a/backend/src/services/app-connection/oci/oci-connection-schemas.ts b/backend/src/ee/services/app-connections/oci/oci-connection-schemas.ts similarity index 100% rename from backend/src/services/app-connection/oci/oci-connection-schemas.ts rename to backend/src/ee/services/app-connections/oci/oci-connection-schemas.ts diff --git a/backend/src/services/app-connection/oci/oci-connection-service.ts b/backend/src/ee/services/app-connections/oci/oci-connection-service.ts similarity index 68% rename from backend/src/services/app-connection/oci/oci-connection-service.ts rename to backend/src/ee/services/app-connections/oci/oci-connection-service.ts index 2d72135e5..c2e60399c 100644 --- a/backend/src/services/app-connection/oci/oci-connection-service.ts +++ b/backend/src/ee/services/app-connections/oci/oci-connection-service.ts @@ -1,7 +1,9 @@ +import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; 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 { TOCIConnection } from "./oci-connection-types"; @@ -22,8 +24,23 @@ type TListOCIVaultKeysDTO = { vaultOcid: string; }; -export const ociConnectionService = (getAppConnection: TGetAppConnectionFunc) => { +// Enterprise check +export const checkPlan = async (licenseService: Pick, orgId: string) => { + const plan = await licenseService.getPlan(orgId); + if (!plan.enterpriseAppConnections) + throw new BadRequestError({ + message: + "Failed to use app connection due to plan restriction. Upgrade plan to access enterprise app connections." + }); +}; + +export const ociConnectionService = ( + getAppConnection: TGetAppConnectionFunc, + licenseService: Pick +) => { const listCompartments = async (connectionId: string, actor: OrgServiceActor) => { + await checkPlan(licenseService, actor.orgId); + const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor); try { @@ -36,6 +53,8 @@ export const ociConnectionService = (getAppConnection: TGetAppConnectionFunc) => }; const listVaults = async ({ connectionId, compartmentOcid }: TListOCIVaultsDTO, actor: OrgServiceActor) => { + await checkPlan(licenseService, actor.orgId); + const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor); try { @@ -51,6 +70,8 @@ export const ociConnectionService = (getAppConnection: TGetAppConnectionFunc) => { connectionId, compartmentOcid, vaultOcid }: TListOCIVaultKeysDTO, actor: OrgServiceActor ) => { + await checkPlan(licenseService, actor.orgId); + const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor); try { diff --git a/backend/src/services/app-connection/oci/oci-connection-types.ts b/backend/src/ee/services/app-connections/oci/oci-connection-types.ts similarity index 87% rename from backend/src/services/app-connection/oci/oci-connection-types.ts rename to backend/src/ee/services/app-connections/oci/oci-connection-types.ts index 74ddfe0c8..e07554f29 100644 --- a/backend/src/services/app-connection/oci/oci-connection-types.ts +++ b/backend/src/ee/services/app-connections/oci/oci-connection-types.ts @@ -2,7 +2,7 @@ import z from "zod"; import { DiscriminativePick } from "@app/lib/types"; -import { AppConnection } from "../app-connection-enums"; +import { AppConnection } from "../../../../services/app-connection/app-connection-enums"; import { CreateOCIConnectionSchema, OCIConnectionSchema, diff --git a/backend/src/ee/services/license/__mocks__/license-fns.ts b/backend/src/ee/services/license/__mocks__/license-fns.ts index 6a8f807ad..5259d4616 100644 --- a/backend/src/ee/services/license/__mocks__/license-fns.ts +++ b/backend/src/ee/services/license/__mocks__/license-fns.ts @@ -29,7 +29,9 @@ export const getDefaultOnPremFeatures = () => { secretApproval: true, secretRotation: true, caCrl: false, - sshHostGroups: false + sshHostGroups: false, + enterpriseSecretSyncs: false, + enterpriseAppConnections: false }; }; diff --git a/backend/src/ee/services/license/license-fns.ts b/backend/src/ee/services/license/license-fns.ts index 9725b9b52..0b943be30 100644 --- a/backend/src/ee/services/license/license-fns.ts +++ b/backend/src/ee/services/license/license-fns.ts @@ -55,7 +55,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ kmip: false, gateway: false, sshHostGroups: false, - enterpriseSecretSyncs: false + enterpriseSecretSyncs: false, + enterpriseAppConnections: false }); export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => { diff --git a/backend/src/ee/services/license/license-types.ts b/backend/src/ee/services/license/license-types.ts index 51db5cb18..9511771ff 100644 --- a/backend/src/ee/services/license/license-types.ts +++ b/backend/src/ee/services/license/license-types.ts @@ -73,6 +73,7 @@ export type TFeatureSet = { gateway: false; sshHostGroups: false; enterpriseSecretSyncs: false; + enterpriseAppConnections: false; }; export type TOrgPlansTableDTO = { diff --git a/backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-fns.ts b/backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-fns.ts index 152d02c51..5b05b2301 100644 --- a/backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-fns.ts +++ b/backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-fns.ts @@ -1,5 +1,6 @@ import { secrets, vault } from "oci-sdk"; +import { getOCIProvider } from "@app/ee/services/app-connections/oci"; import { TCreateOCIVaultVariable, TDeleteOCIVaultVariable, @@ -9,7 +10,6 @@ import { TUpdateOCIVaultVariable } from "@app/ee/services/secret-sync/oci-vault/oci-vault-sync-types"; import { delay } from "@app/lib/delay"; -import { getOCIProvider } from "@app/services/app-connection/oci"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; diff --git a/backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-types.ts b/backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-types.ts index c040cd0c0..8804b1322 100644 --- a/backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-types.ts +++ b/backend/src/ee/services/secret-sync/oci-vault/oci-vault-sync-types.ts @@ -1,7 +1,7 @@ import { SimpleAuthenticationDetailsProvider } from "oci-sdk"; 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"; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 5196971f9..fd0f0142d 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -1015,7 +1015,8 @@ export const registerRoutes = async ( secretVersionV2BridgeDAL, secretVersionTagV2BridgeDAL, resourceMetadataDAL, - appConnectionDAL + appConnectionDAL, + licenseService }); const secretQueueService = secretQueueFactory({ @@ -1632,7 +1633,8 @@ export const registerRoutes = async ( const appConnectionService = appConnectionServiceFactory({ appConnectionDAL, permissionService, - kmsService + kmsService, + licenseService }); const secretSyncService = secretSyncServiceFactory({ @@ -1643,7 +1645,8 @@ export const registerRoutes = async ( folderDAL, secretSyncQueue, projectBotService, - keyStore + keyStore, + licenseService }); const kmipService = kmipServiceFactory({ diff --git a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts index b9ce3deb8..a05a2f263 100644 --- a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts +++ b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts @@ -1,5 +1,6 @@ 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 { ApiDocsTags } from "@app/lib/api-docs"; import { readLimit } from "@app/server/config/rateLimiter"; @@ -38,7 +39,6 @@ import { } from "@app/services/app-connection/humanitec"; import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap"; import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql"; -import { OCIConnectionListItemSchema, SanitizedOCIConnectionSchema } from "@app/services/app-connection/oci"; import { PostgresConnectionListItemSchema, SanitizedPostgresConnectionSchema diff --git a/backend/src/server/routes/v1/app-connection-routers/index.ts b/backend/src/server/routes/v1/app-connection-routers/index.ts index 6f6fa1991..357c617e1 100644 --- a/backend/src/server/routes/v1/app-connection-routers/index.ts +++ b/backend/src/server/routes/v1/app-connection-routers/index.ts @@ -1,5 +1,6 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { registerOCIConnectionRouter } from "../../../../ee/routes/v1/app-connection-routers/oci-connection-router"; import { registerAuth0ConnectionRouter } from "./auth0-connection-router"; import { registerAwsConnectionRouter } from "./aws-connection-router"; import { registerAzureAppConfigurationConnectionRouter } from "./azure-app-configuration-connection-router"; @@ -13,7 +14,6 @@ import { registerHCVaultConnectionRouter } from "./hc-vault-connection-router"; import { registerHumanitecConnectionRouter } from "./humanitec-connection-router"; import { registerLdapConnectionRouter } from "./ldap-connection-router"; import { registerMsSqlConnectionRouter } from "./mssql-connection-router"; -import { registerOCIConnectionRouter } from "./oci-connection-router"; import { registerPostgresConnectionRouter } from "./postgres-connection-router"; import { registerTeamCityConnectionRouter } from "./teamcity-connection-router"; import { registerTerraformCloudConnectionRouter } from "./terraform-cloud-router"; diff --git a/backend/src/services/app-connection/app-connection-enums.ts b/backend/src/services/app-connection/app-connection-enums.ts index 6e09f1293..afae27039 100644 --- a/backend/src/services/app-connection/app-connection-enums.ts +++ b/backend/src/services/app-connection/app-connection-enums.ts @@ -66,3 +66,8 @@ export enum AWSRegion { // South America SA_EAST_1 = "sa-east-1" // Sao Paulo } + +export enum AppConnectionPlanType { + Enterprise = "enterprise", + Regular = "regular" +} diff --git a/backend/src/services/app-connection/app-connection-fns.ts b/backend/src/services/app-connection/app-connection-fns.ts index f6fd894a6..e6d1b2c55 100644 --- a/backend/src/services/app-connection/app-connection-fns.ts +++ b/backend/src/services/app-connection/app-connection-fns.ts @@ -8,6 +8,11 @@ import { } from "@app/services/app-connection/shared/sql"; import { KmsDataKey } from "@app/services/kms/kms-types"; +import { + getOCIConnectionListItem, + OCIConnectionMethod, + validateOCIConnectionCredentials +} from "../../ee/services/app-connections/oci"; import { AppConnection } from "./app-connection-enums"; import { TAppConnectionServiceFactoryDep } from "./app-connection-service"; import { @@ -53,7 +58,6 @@ import { } from "./humanitec"; import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnectionCredentials } from "./ldap"; import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql"; -import { getOCIConnectionListItem, OCIConnectionMethod, validateOCIConnectionCredentials } from "./oci"; import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres"; import { getTeamCityConnectionListItem, diff --git a/backend/src/services/app-connection/app-connection-maps.ts b/backend/src/services/app-connection/app-connection-maps.ts index c32336453..b4b968031 100644 --- a/backend/src/services/app-connection/app-connection-maps.ts +++ b/backend/src/services/app-connection/app-connection-maps.ts @@ -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.AWS]: "AWS", @@ -21,3 +21,25 @@ export const APP_CONNECTION_NAME_MAP: Record = { [AppConnection.TeamCity]: "TeamCity", [AppConnection.OCI]: "OCI" }; + +export const APP_CONNECTION_PLAN_MAP: Record = { + [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 +}; diff --git a/backend/src/services/app-connection/app-connection-service.ts b/backend/src/services/app-connection/app-connection-service.ts index 85b63138a..662edab07 100644 --- a/backend/src/services/app-connection/app-connection-service.ts +++ b/backend/src/services/app-connection/app-connection-service.ts @@ -1,5 +1,6 @@ import { ForbiddenError, subject } from "@casl/ability"; +import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { OrgPermissionAppConnectionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { generateHash } from "@app/lib/crypto/encryption"; @@ -17,9 +18,11 @@ import { import { auth0ConnectionService } from "@app/services/app-connection/auth0/auth0-connection-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { ValidateOCIConnectionCredentialsSchema } from "../../ee/services/app-connections/oci"; +import { ociConnectionService } from "../../ee/services/app-connections/oci/oci-connection-service"; import { TAppConnectionDALFactory } from "./app-connection-dal"; -import { AppConnection } from "./app-connection-enums"; -import { APP_CONNECTION_NAME_MAP } from "./app-connection-maps"; +import { AppConnection, AppConnectionPlanType } from "./app-connection-enums"; +import { APP_CONNECTION_NAME_MAP, APP_CONNECTION_PLAN_MAP } from "./app-connection-maps"; import { TAppConnection, TAppConnectionConfig, @@ -49,8 +52,6 @@ import { ValidateHumanitecConnectionCredentialsSchema } from "./humanitec"; import { humanitecConnectionService } from "./humanitec/humanitec-connection-service"; import { ValidateLdapConnectionCredentialsSchema } from "./ldap"; import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql"; -import { ValidateOCIConnectionCredentialsSchema } from "./oci"; -import { ociConnectionService } from "./oci/oci-connection-service"; import { ValidatePostgresConnectionCredentialsSchema } from "./postgres"; import { ValidateTeamCityConnectionCredentialsSchema } from "./teamcity"; import { teamcityConnectionService } from "./teamcity/teamcity-connection-service"; @@ -65,6 +66,7 @@ export type TAppConnectionServiceFactoryDep = { appConnectionDAL: TAppConnectionDALFactory; permissionService: Pick; kmsService: Pick; + licenseService: Pick; }; export type TAppConnectionServiceFactory = ReturnType; @@ -94,7 +96,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record { const listAppConnectionsByOrg = async (actor: OrgServiceActor, app?: AppConnection) => { const { permission } = await permissionService.getOrgPermission( @@ -191,6 +194,16 @@ export const appConnectionServiceFactory = ({ OrgPermissionSubjects.AppConnections ); + // Enterprise check + if (APP_CONNECTION_PLAN_MAP[app] === AppConnectionPlanType.Enterprise) { + const plan = await licenseService.getPlan(actor.orgId); + if (!plan.enterpriseAppConnections) + throw new BadRequestError({ + message: + "Failed to create app connection due to plan restriction. Upgrade plan to access enterprise app connections." + }); + } + const validatedCredentials = await validateAppConnectionCredentials({ app, credentials, @@ -253,6 +266,16 @@ export const appConnectionServiceFactory = ({ if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` }); + // Enterprise check + if (APP_CONNECTION_PLAN_MAP[appConnection.app as AppConnection] === AppConnectionPlanType.Enterprise) { + const plan = await licenseService.getPlan(actor.orgId); + if (!plan.enterpriseAppConnections) + throw new BadRequestError({ + message: + "Failed to update app connection due to plan restriction. Upgrade plan to access enterprise app connections." + }); + } + const { permission } = await permissionService.getOrgPermission( actor.type, actor.id, @@ -399,6 +422,16 @@ export const appConnectionServiceFactory = ({ if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` }); + // Enterprise check + if (APP_CONNECTION_PLAN_MAP[app] === AppConnectionPlanType.Enterprise) { + const plan = await licenseService.getPlan(actor.orgId); + if (!plan.enterpriseAppConnections) + throw new BadRequestError({ + message: + "Failed to connect app connection due to plan restriction. Upgrade plan to access enterprise app connections." + }); + } + const { permission: orgPermission } = await permissionService.getOrgPermission( actor.type, actor.id, @@ -468,6 +501,6 @@ export const appConnectionServiceFactory = ({ hcvault: hcVaultConnectionService(connectAppConnectionById), windmill: windmillConnectionService(connectAppConnectionById), teamcity: teamcityConnectionService(connectAppConnectionById), - oci: ociConnectionService(connectAppConnectionById) + oci: ociConnectionService(connectAppConnectionById, licenseService) }; }; diff --git a/backend/src/services/app-connection/app-connection-types.ts b/backend/src/services/app-connection/app-connection-types.ts index 42ccfc84e..496f5032e 100644 --- a/backend/src/services/app-connection/app-connection-types.ts +++ b/backend/src/services/app-connection/app-connection-types.ts @@ -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 { 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 { TAuth0Connection, @@ -76,12 +82,6 @@ import { TValidateLdapConnectionCredentialsSchema } from "./ldap"; import { TMsSqlConnection, TMsSqlConnectionInput, TValidateMsSqlConnectionCredentialsSchema } from "./mssql"; -import { - TOCIConnection, - TOCIConnectionConfig, - TOCIConnectionInput, - TValidateOCIConnectionCredentialsSchema -} from "./oci"; import { TPostgresConnection, TPostgresConnectionInput, diff --git a/backend/src/services/secret-sync/secret-sync-enums.ts b/backend/src/services/secret-sync/secret-sync-enums.ts index a0982c5b6..e829e2131 100644 --- a/backend/src/services/secret-sync/secret-sync-enums.ts +++ b/backend/src/services/secret-sync/secret-sync-enums.ts @@ -26,3 +26,8 @@ export enum SecretSyncImportBehavior { PrioritizeSource = "prioritize-source", PrioritizeDestination = "prioritize-destination" } + +export enum SecretSyncPlanType { + Enterprise = "enterprise", + Regular = "regular" +} diff --git a/backend/src/services/secret-sync/secret-sync-maps.ts b/backend/src/services/secret-sync/secret-sync-maps.ts index 21cb912b4..5b2906f1c 100644 --- a/backend/src/services/secret-sync/secret-sync-maps.ts +++ b/backend/src/services/secret-sync/secret-sync-maps.ts @@ -1,5 +1,5 @@ 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.AWSParameterStore]: "AWS Parameter Store", @@ -36,3 +36,21 @@ export const SECRET_SYNC_CONNECTION_MAP: Record = { [SecretSync.TeamCity]: AppConnection.TeamCity, [SecretSync.OCIVault]: AppConnection.OCI }; + +export const SECRET_SYNC_PLAN_MAP: Record = { + [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 +}; diff --git a/backend/src/services/secret-sync/secret-sync-queue.ts b/backend/src/services/secret-sync/secret-sync-queue.ts index 62b4ba3cc..e8b4d569e 100644 --- a/backend/src/services/secret-sync/secret-sync-queue.ts +++ b/backend/src/services/secret-sync/secret-sync-queue.ts @@ -5,8 +5,10 @@ import { Job } from "bullmq"; import { ProjectMembershipRole, SecretType } from "@app/db/schemas"; import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service"; 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 { getConfig } from "@app/lib/config/env"; +import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; import { decryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns"; @@ -29,11 +31,12 @@ import { TSecretSyncDALFactory } from "@app/services/secret-sync/secret-sync-dal import { SecretSync, SecretSyncImportBehavior, - SecretSyncInitialSyncBehavior + SecretSyncInitialSyncBehavior, + SecretSyncPlanType } from "@app/services/secret-sync/secret-sync-enums"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { parseSyncErrorMessage, SecretSyncFns } from "@app/services/secret-sync/secret-sync-fns"; -import { SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps"; +import { SECRET_SYNC_NAME_MAP, SECRET_SYNC_PLAN_MAP } from "@app/services/secret-sync/secret-sync-maps"; import { SecretSyncAction, SecretSyncStatus, @@ -93,6 +96,7 @@ type TSecretSyncQueueFactoryDep = { secretVersionV2BridgeDAL: Pick; secretVersionTagV2BridgeDAL: Pick; resourceMetadataDAL: Pick; + licenseService: Pick; }; type SecretSyncActionJob = Job< @@ -133,7 +137,8 @@ export const secretSyncQueueFactory = ({ secretVersionTagDAL, secretVersionV2BridgeDAL, secretVersionTagV2BridgeDAL, - resourceMetadataDAL + resourceMetadataDAL, + licenseService }: TSecretSyncQueueFactoryDep) => { const appCfg = getConfig(); @@ -323,7 +328,22 @@ export const secretSyncQueueFactory = ({ secretSync: TSecretSyncWithCredentials, importBehavior: SecretSyncImportBehavior ): Promise => { - const { projectId, environment, folder } = secretSync; + const { + projectId, + environment, + folder, + destination, + connection: { orgId } + } = secretSync; + + // Enterprise Check + if (SECRET_SYNC_PLAN_MAP[destination] === SecretSyncPlanType.Enterprise) { + const plan = await licenseService.getPlan(orgId); + if (!plan.enterpriseSecretSyncs) + throw new BadRequestError({ + message: "Failed to import secrets due to plan restriction. Upgrade plan to access enterprise secret syncs." + }); + } if (!environment || !folder) throw new Error( @@ -400,6 +420,15 @@ export const secretSyncQueueFactory = ({ if (!secretSync) throw new Error(`Cannot find secret sync with ID ${syncId}`); + // Enterprise Check + if (SECRET_SYNC_PLAN_MAP[secretSync.destination as SecretSync] === SecretSyncPlanType.Enterprise) { + const plan = await licenseService.getPlan(secretSync.connection.orgId); + if (!plan.enterpriseSecretSyncs) + throw new BadRequestError({ + message: "Failed to sync secrets due to plan restriction. Upgrade plan to access enterprise secret syncs." + }); + } + await secretSyncDAL.updateById(syncId, { syncStatus: SecretSyncStatus.Running }); @@ -659,6 +688,16 @@ export const secretSyncQueueFactory = ({ if (!secretSync) throw new Error(`Cannot find secret sync with ID ${syncId}`); + // Enterprise Check + if (SECRET_SYNC_PLAN_MAP[secretSync.destination as SecretSync] === SecretSyncPlanType.Enterprise) { + const plan = await licenseService.getPlan(secretSync.connection.orgId); + if (!plan.enterpriseSecretSyncs) + throw new BadRequestError({ + message: + "Failed to access secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs." + }); + } + await secretSyncDAL.updateById(syncId, { removeStatus: SecretSyncStatus.Running }); diff --git a/backend/src/services/secret-sync/secret-sync-service.ts b/backend/src/services/secret-sync/secret-sync-service.ts index db350f785..2fc1f103d 100644 --- a/backend/src/services/secret-sync/secret-sync-service.ts +++ b/backend/src/services/secret-sync/secret-sync-service.ts @@ -1,6 +1,7 @@ import { ForbiddenError } from "@casl/ability"; 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 { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { @@ -15,7 +16,7 @@ import { OrgServiceActor } from "@app/lib/types"; import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service"; import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; -import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; +import { SecretSync, SecretSyncPlanType } from "@app/services/secret-sync/secret-sync-enums"; import { listSecretSyncOptions } from "@app/services/secret-sync/secret-sync-fns"; import { SecretSyncStatus, @@ -34,7 +35,7 @@ import { import { TSecretImportDALFactory } from "../secret-import/secret-import-dal"; import { TSecretSyncDALFactory } from "./secret-sync-dal"; -import { SECRET_SYNC_CONNECTION_MAP, SECRET_SYNC_NAME_MAP } from "./secret-sync-maps"; +import { SECRET_SYNC_CONNECTION_MAP, SECRET_SYNC_NAME_MAP, SECRET_SYNC_PLAN_MAP } from "./secret-sync-maps"; import { TSecretSyncQueueFactory } from "./secret-sync-queue"; type TSecretSyncServiceFactoryDep = { @@ -49,6 +50,7 @@ type TSecretSyncServiceFactoryDep = { TSecretSyncQueueFactory, "queueSecretSyncSyncSecretsById" | "queueSecretSyncImportSecretsById" | "queueSecretSyncRemoveSecretsById" >; + licenseService: Pick; }; export type TSecretSyncServiceFactory = ReturnType; @@ -61,7 +63,8 @@ export const secretSyncServiceFactory = ({ appConnectionService, projectBotService, secretSyncQueue, - keyStore + keyStore, + licenseService }: TSecretSyncServiceFactoryDep) => { const listSecretSyncsByProjectId = async ( { projectId, destination }: TListSecretSyncsByProjectId, @@ -191,6 +194,16 @@ export const secretSyncServiceFactory = ({ { projectId, secretPath, environment, ...params }: TCreateSecretSyncDTO, actor: OrgServiceActor ) => { + // Enterprise check + if (SECRET_SYNC_PLAN_MAP[params.destination] === SecretSyncPlanType.Enterprise) { + const plan = await licenseService.getPlan(actor.orgId); + if (!plan.enterpriseSecretSyncs) + throw new BadRequestError({ + message: + "Failed to create secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs." + }); + } + const { permission: projectPermission } = await permissionService.getProjectPermission({ actor: actor.type, actorId: actor.id, @@ -260,6 +273,16 @@ export const secretSyncServiceFactory = ({ message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID ${syncId}` }); + // Enterprise check + if (SECRET_SYNC_PLAN_MAP[secretSync.destination as SecretSync] === SecretSyncPlanType.Enterprise) { + const plan = await licenseService.getPlan(actor.orgId); + if (!plan.enterpriseSecretSyncs) + throw new BadRequestError({ + message: + "Failed to update secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs." + }); + } + const { permission } = await permissionService.getProjectPermission({ actor: actor.type, actorId: actor.id, @@ -408,6 +431,16 @@ export const secretSyncServiceFactory = ({ message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"` }); + // Enterprise check + if (SECRET_SYNC_PLAN_MAP[secretSync.destination as SecretSync] === SecretSyncPlanType.Enterprise) { + const plan = await licenseService.getPlan(actor.orgId); + if (!plan.enterpriseSecretSyncs) + throw new BadRequestError({ + message: + "Failed to trigger secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs." + }); + } + const { permission } = await permissionService.getProjectPermission({ actor: actor.type, actorId: actor.id, @@ -463,6 +496,16 @@ export const secretSyncServiceFactory = ({ message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"` }); + // Enterprise check + if (SECRET_SYNC_PLAN_MAP[secretSync.destination as SecretSync] === SecretSyncPlanType.Enterprise) { + const plan = await licenseService.getPlan(actor.orgId); + if (!plan.enterpriseSecretSyncs) + throw new BadRequestError({ + message: + "Failed to trigger secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs." + }); + } + const { permission } = await permissionService.getProjectPermission({ actor: actor.type, actorId: actor.id, @@ -512,6 +555,16 @@ export const secretSyncServiceFactory = ({ message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"` }); + // Enterprise check + if (SECRET_SYNC_PLAN_MAP[secretSync.destination as SecretSync] === SecretSyncPlanType.Enterprise) { + const plan = await licenseService.getPlan(actor.orgId); + if (!plan.enterpriseSecretSyncs) + throw new BadRequestError({ + message: + "Failed to trigger secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs." + }); + } + const { permission } = await permissionService.getProjectPermission({ actor: actor.type, actorId: actor.id, diff --git a/docs/integrations/app-connections/oci.mdx b/docs/integrations/app-connections/oci.mdx index ff51ce1d9..58fb3c1d3 100644 --- a/docs/integrations/app-connections/oci.mdx +++ b/docs/integrations/app-connections/oci.mdx @@ -3,6 +3,13 @@ title: "OCI Connection" description: "Learn how to configure an Oracle Cloud Infrastructure Connection for Infisical." --- + + 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. + + 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 diff --git a/docs/integrations/secret-syncs/oci-vault.mdx b/docs/integrations/secret-syncs/oci-vault.mdx index 67a3426aa..00b7120e7 100644 --- a/docs/integrations/secret-syncs/oci-vault.mdx +++ b/docs/integrations/secret-syncs/oci-vault.mdx @@ -3,6 +3,13 @@ title: "OCI Vault Sync" description: "Learn how to configure an Oracle Cloud Infrastructure Vault Sync for Infisical." --- + + 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. + + **Prerequisites:** - 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) diff --git a/frontend/src/components/secret-syncs/SecretSyncSelect.tsx b/frontend/src/components/secret-syncs/SecretSyncSelect.tsx index 428c1bf2d..1b2fe9c5b 100644 --- a/frontend/src/components/secret-syncs/SecretSyncSelect.tsx +++ b/frontend/src/components/secret-syncs/SecretSyncSelect.tsx @@ -1,13 +1,14 @@ import { faWrench } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { twMerge } from "tailwind-merge"; import { Spinner, Tooltip } from "@app/components/v2"; -import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs"; -import { SecretSync, useSecretSyncOptions } from "@app/hooks/api/secretSyncs"; -import { twMerge } from "tailwind-merge"; -import { UpgradePlanModal } from "../license/UpgradePlanModal"; -import { usePopUp } from "@app/hooks"; import { useSubscription } from "@app/context"; +import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs"; +import { usePopUp } from "@app/hooks"; +import { SecretSync, useSecretSyncOptions } from "@app/hooks/api/secretSyncs"; + +import { UpgradePlanModal } from "../license/UpgradePlanModal"; type Props = { onSelect: (destination: SecretSync) => void; @@ -52,7 +53,7 @@ export const SecretSyncSelect = ({ onSelect }: Props) => { )} > {enterprise && !subscription.enterpriseSecretSyncs && ( -
+
)} = { [AppConnection.AWS]: { name: "AWS", image: "Amazon Web Services.png" }, [AppConnection.GitHub]: { name: "GitHub", image: "GitHub.png" }, @@ -63,7 +63,7 @@ export const APP_CONNECTION_MAP: Record< [AppConnection.HCVault]: { name: "Hashicorp Vault", image: "Vault.png", size: 65 }, [AppConnection.LDAP]: { name: "LDAP", image: "LDAP.png", size: 65 }, [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"]) => { diff --git a/frontend/src/hooks/api/subscriptions/types.ts b/frontend/src/hooks/api/subscriptions/types.ts index a8ca35a92..a861c215a 100644 --- a/frontend/src/hooks/api/subscriptions/types.ts +++ b/frontend/src/hooks/api/subscriptions/types.ts @@ -51,4 +51,5 @@ export type SubscriptionPlan = { projectTemplates: boolean; kmip: boolean; enterpriseSecretSyncs: boolean; + enterpriseAppConnections: boolean; }; diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx index 5e9f69335..1019d2225 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx @@ -1,8 +1,12 @@ import { faWrench } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { twMerge } from "tailwind-merge"; +import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { Spinner, Tooltip } from "@app/components/v2"; +import { useSubscription } from "@app/context"; import { APP_CONNECTION_MAP } from "@app/helpers/appConnections"; +import { usePopUp } from "@app/hooks"; import { useAppConnectionOptions } from "@app/hooks/api/appConnections"; import { AppConnection } from "@app/hooks/api/appConnections/enums"; @@ -11,8 +15,11 @@ type Props = { }; export const AppConnectionsSelect = ({ onSelect }: Props) => { + const { subscription } = useSubscription(); const { isPending, data: appConnectionOptions } = useAppConnectionOptions(); + const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const); + if (isPending) { return (
@@ -25,29 +32,52 @@ export const AppConnectionsSelect = ({ onSelect }: Props) => { return (
{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 ( - + + ); })} + handlePopUpToggle("upgradePlan", isOpen)} + text="You can use every App Connection if you switch to Infisical's Enterprise plan." + /> Date: Tue, 20 May 2025 14:52:52 -0400 Subject: [PATCH 06/31] greptile review fixes --- .../oci-vault-sync-router.ts | 3 +- .../routes/v1/app-connection-routers/index.ts | 2 +- .../app-connection/app-connection-fns.ts | 20 ++++- .../app-connection/app-connection-service.ts | 50 +++++------- .../services/secret-sync/secret-sync-fns.ts | 20 ++++- .../services/secret-sync/secret-sync-queue.ts | 51 +++++------- .../secret-sync/secret-sync-service.ts | 81 ++++++++----------- .../secret-syncs/SecretSyncSelect.tsx | 2 +- .../components/AppConnectionList.tsx | 2 +- 9 files changed, 116 insertions(+), 115 deletions(-) diff --git a/backend/src/ee/routes/v1/secret-sync-routers/oci-vault-sync-router.ts b/backend/src/ee/routes/v1/secret-sync-routers/oci-vault-sync-router.ts index 4d316369c..2efe3e3f5 100644 --- a/backend/src/ee/routes/v1/secret-sync-routers/oci-vault-sync-router.ts +++ b/backend/src/ee/routes/v1/secret-sync-routers/oci-vault-sync-router.ts @@ -3,10 +3,9 @@ import { OCIVaultSyncSchema, UpdateOCIVaultSyncSchema } 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 { registerSyncSecretsEndpoints } from "../../../../server/routes/v1/secret-sync-routers/secret-sync-endpoints"; - export const registerOCIVaultSyncRouter = async (server: FastifyZodProvider) => registerSyncSecretsEndpoints({ destination: SecretSync.OCIVault, diff --git a/backend/src/server/routes/v1/app-connection-routers/index.ts b/backend/src/server/routes/v1/app-connection-routers/index.ts index 357c617e1..04c6a802f 100644 --- a/backend/src/server/routes/v1/app-connection-routers/index.ts +++ b/backend/src/server/routes/v1/app-connection-routers/index.ts @@ -1,6 +1,6 @@ +import { registerOCIConnectionRouter } from "@app/ee/routes/v1/app-connection-routers/oci-connection-router"; import { AppConnection } from "@app/services/app-connection/app-connection-enums"; -import { registerOCIConnectionRouter } from "../../../../ee/routes/v1/app-connection-routers/oci-connection-router"; import { registerAuth0ConnectionRouter } from "./auth0-connection-router"; import { registerAwsConnectionRouter } from "./aws-connection-router"; import { registerAzureAppConfigurationConnectionRouter } from "./azure-app-configuration-connection-router"; diff --git a/backend/src/services/app-connection/app-connection-fns.ts b/backend/src/services/app-connection/app-connection-fns.ts index e6d1b2c55..12b58ee36 100644 --- a/backend/src/services/app-connection/app-connection-fns.ts +++ b/backend/src/services/app-connection/app-connection-fns.ts @@ -1,7 +1,8 @@ import { TAppConnections } from "@app/db/schemas/app-connections"; +import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { generateHash } from "@app/lib/crypto/encryption"; 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 { transferSqlConnectionCredentialsToPlatform, validateSqlConnectionCredentials @@ -13,7 +14,7 @@ import { OCIConnectionMethod, validateOCIConnectionCredentials } from "../../ee/services/app-connections/oci"; -import { AppConnection } from "./app-connection-enums"; +import { AppConnection, AppConnectionPlanType } from "./app-connection-enums"; import { TAppConnectionServiceFactoryDep } from "./app-connection-service"; import { TAppConnection, @@ -261,3 +262,18 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record< [AppConnection.TeamCity]: platformManagedCredentialsNotSupported, [AppConnection.OCI]: platformManagedCredentialsNotSupported }; + +export const enterpriseAppCheck = async ( + licenseService: Pick, + 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 + }); + } +}; diff --git a/backend/src/services/app-connection/app-connection-service.ts b/backend/src/services/app-connection/app-connection-service.ts index 662edab07..6637fd93d 100644 --- a/backend/src/services/app-connection/app-connection-service.ts +++ b/backend/src/services/app-connection/app-connection-service.ts @@ -10,6 +10,7 @@ import { DiscriminativePick, OrgServiceActor } from "@app/lib/types"; import { decryptAppConnection, encryptAppConnectionCredentials, + enterpriseAppCheck, getAppConnectionMethodName, listAppConnectionOptions, TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM, @@ -21,8 +22,8 @@ import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { ValidateOCIConnectionCredentialsSchema } from "../../ee/services/app-connections/oci"; import { ociConnectionService } from "../../ee/services/app-connections/oci/oci-connection-service"; import { TAppConnectionDALFactory } from "./app-connection-dal"; -import { AppConnection, AppConnectionPlanType } from "./app-connection-enums"; -import { APP_CONNECTION_NAME_MAP, APP_CONNECTION_PLAN_MAP } from "./app-connection-maps"; +import { AppConnection } from "./app-connection-enums"; +import { APP_CONNECTION_NAME_MAP } from "./app-connection-maps"; import { TAppConnection, TAppConnectionConfig, @@ -194,15 +195,12 @@ export const appConnectionServiceFactory = ({ OrgPermissionSubjects.AppConnections ); - // Enterprise check - if (APP_CONNECTION_PLAN_MAP[app] === AppConnectionPlanType.Enterprise) { - const plan = await licenseService.getPlan(actor.orgId); - if (!plan.enterpriseAppConnections) - throw new BadRequestError({ - message: - "Failed to create app connection due to plan restriction. Upgrade plan to access enterprise app connections." - }); - } + 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({ app, @@ -266,15 +264,12 @@ export const appConnectionServiceFactory = ({ if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` }); - // Enterprise check - if (APP_CONNECTION_PLAN_MAP[appConnection.app as AppConnection] === AppConnectionPlanType.Enterprise) { - const plan = await licenseService.getPlan(actor.orgId); - if (!plan.enterpriseAppConnections) - throw new BadRequestError({ - message: - "Failed to update app connection due to plan restriction. Upgrade plan to access enterprise app connections." - }); - } + 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( actor.type, @@ -422,15 +417,12 @@ export const appConnectionServiceFactory = ({ if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` }); - // Enterprise check - if (APP_CONNECTION_PLAN_MAP[app] === AppConnectionPlanType.Enterprise) { - const plan = await licenseService.getPlan(actor.orgId); - if (!plan.enterpriseAppConnections) - throw new BadRequestError({ - message: - "Failed to connect app connection due to plan restriction. Upgrade plan to access enterprise app connections." - }); - } + 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( actor.type, diff --git a/backend/src/services/secret-sync/secret-sync-fns.ts b/backend/src/services/secret-sync/secret-sync-fns.ts index bf00e5d31..8f58b6fcc 100644 --- a/backend/src/services/secret-sync/secret-sync-fns.ts +++ b/backend/src/services/secret-sync/secret-sync-fns.ts @@ -1,7 +1,9 @@ import { AxiosError } from "axios"; 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 { AWS_PARAMETER_STORE_SYNC_LIST_OPTION, AwsParameterStoreSyncFns @@ -12,7 +14,7 @@ import { } from "@app/services/secret-sync/aws-secrets-manager"; 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 { 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 { TSecretMap, @@ -30,6 +32,7 @@ import { GcpSyncFns } from "./gcp/gcp-sync-fns"; import { HC_VAULT_SYNC_LIST_OPTION, HCVaultSyncFns } from "./hc-vault"; import { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec"; import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns"; +import { SECRET_SYNC_PLAN_MAP } from "./secret-sync-maps"; import { TEAMCITY_SYNC_LIST_OPTION, TeamCitySyncFns } from "./teamcity"; import { TERRAFORM_CLOUD_SYNC_LIST_OPTION, TerraformCloudSyncFns } from "./terraform-cloud"; import { VERCEL_SYNC_LIST_OPTION, VercelSyncFns } from "./vercel"; @@ -327,3 +330,18 @@ export const parseSyncErrorMessage = (err: unknown): string => { ? errorMessage : `${errorMessage.substring(0, MAX_MESSAGE_LENGTH - 3)}...`; }; + +export const enterpriseSyncCheck = async ( + licenseService: Pick, + 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 + }); + } +}; diff --git a/backend/src/services/secret-sync/secret-sync-queue.ts b/backend/src/services/secret-sync/secret-sync-queue.ts index e8b4d569e..6f627c24e 100644 --- a/backend/src/services/secret-sync/secret-sync-queue.ts +++ b/backend/src/services/secret-sync/secret-sync-queue.ts @@ -8,7 +8,6 @@ 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 { getConfig } from "@app/lib/config/env"; -import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; import { decryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns"; @@ -31,12 +30,11 @@ import { TSecretSyncDALFactory } from "@app/services/secret-sync/secret-sync-dal import { SecretSync, SecretSyncImportBehavior, - SecretSyncInitialSyncBehavior, - SecretSyncPlanType + SecretSyncInitialSyncBehavior } from "@app/services/secret-sync/secret-sync-enums"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; -import { parseSyncErrorMessage, SecretSyncFns } from "@app/services/secret-sync/secret-sync-fns"; -import { SECRET_SYNC_NAME_MAP, SECRET_SYNC_PLAN_MAP } from "@app/services/secret-sync/secret-sync-maps"; +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 { SecretSyncAction, SecretSyncStatus, @@ -336,14 +334,12 @@ export const secretSyncQueueFactory = ({ connection: { orgId } } = secretSync; - // Enterprise Check - if (SECRET_SYNC_PLAN_MAP[destination] === SecretSyncPlanType.Enterprise) { - const plan = await licenseService.getPlan(orgId); - if (!plan.enterpriseSecretSyncs) - throw new BadRequestError({ - message: "Failed to import secrets due to plan restriction. Upgrade plan to access enterprise secret syncs." - }); - } + await enterpriseSyncCheck( + licenseService, + destination, + orgId, + "Failed to import secrets due to plan restriction. Upgrade plan to access enterprise secret syncs." + ); if (!environment || !folder) throw new Error( @@ -420,14 +416,12 @@ export const secretSyncQueueFactory = ({ if (!secretSync) throw new Error(`Cannot find secret sync with ID ${syncId}`); - // Enterprise Check - if (SECRET_SYNC_PLAN_MAP[secretSync.destination as SecretSync] === SecretSyncPlanType.Enterprise) { - const plan = await licenseService.getPlan(secretSync.connection.orgId); - if (!plan.enterpriseSecretSyncs) - throw new BadRequestError({ - message: "Failed to sync secrets due to plan restriction. Upgrade plan to access enterprise secret syncs." - }); - } + await 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, { syncStatus: SecretSyncStatus.Running @@ -688,15 +682,12 @@ export const secretSyncQueueFactory = ({ if (!secretSync) throw new Error(`Cannot find secret sync with ID ${syncId}`); - // Enterprise Check - if (SECRET_SYNC_PLAN_MAP[secretSync.destination as SecretSync] === SecretSyncPlanType.Enterprise) { - const plan = await licenseService.getPlan(secretSync.connection.orgId); - if (!plan.enterpriseSecretSyncs) - throw new BadRequestError({ - message: - "Failed to access secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs." - }); - } + await 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, { removeStatus: SecretSyncStatus.Running diff --git a/backend/src/services/secret-sync/secret-sync-service.ts b/backend/src/services/secret-sync/secret-sync-service.ts index 2fc1f103d..e7751d3f9 100644 --- a/backend/src/services/secret-sync/secret-sync-service.ts +++ b/backend/src/services/secret-sync/secret-sync-service.ts @@ -16,8 +16,8 @@ import { OrgServiceActor } from "@app/lib/types"; import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service"; import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; -import { SecretSync, SecretSyncPlanType } from "@app/services/secret-sync/secret-sync-enums"; -import { listSecretSyncOptions } from "@app/services/secret-sync/secret-sync-fns"; +import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; +import { enterpriseSyncCheck, listSecretSyncOptions } from "@app/services/secret-sync/secret-sync-fns"; import { SecretSyncStatus, TCreateSecretSyncDTO, @@ -35,7 +35,7 @@ import { import { TSecretImportDALFactory } from "../secret-import/secret-import-dal"; import { TSecretSyncDALFactory } from "./secret-sync-dal"; -import { SECRET_SYNC_CONNECTION_MAP, SECRET_SYNC_NAME_MAP, SECRET_SYNC_PLAN_MAP } from "./secret-sync-maps"; +import { SECRET_SYNC_CONNECTION_MAP, SECRET_SYNC_NAME_MAP } from "./secret-sync-maps"; import { TSecretSyncQueueFactory } from "./secret-sync-queue"; type TSecretSyncServiceFactoryDep = { @@ -194,15 +194,12 @@ export const secretSyncServiceFactory = ({ { projectId, secretPath, environment, ...params }: TCreateSecretSyncDTO, actor: OrgServiceActor ) => { - // Enterprise check - if (SECRET_SYNC_PLAN_MAP[params.destination] === SecretSyncPlanType.Enterprise) { - const plan = await licenseService.getPlan(actor.orgId); - if (!plan.enterpriseSecretSyncs) - throw new BadRequestError({ - message: - "Failed to create secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs." - }); - } + 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({ actor: actor.type, @@ -273,15 +270,12 @@ export const secretSyncServiceFactory = ({ message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID ${syncId}` }); - // Enterprise check - if (SECRET_SYNC_PLAN_MAP[secretSync.destination as SecretSync] === SecretSyncPlanType.Enterprise) { - const plan = await licenseService.getPlan(actor.orgId); - if (!plan.enterpriseSecretSyncs) - throw new BadRequestError({ - message: - "Failed to update secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs." - }); - } + 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({ actor: actor.type, @@ -431,15 +425,12 @@ export const secretSyncServiceFactory = ({ message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"` }); - // Enterprise check - if (SECRET_SYNC_PLAN_MAP[secretSync.destination as SecretSync] === SecretSyncPlanType.Enterprise) { - const plan = await licenseService.getPlan(actor.orgId); - if (!plan.enterpriseSecretSyncs) - throw new BadRequestError({ - message: - "Failed to trigger secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs." - }); - } + 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({ actor: actor.type, @@ -496,15 +487,12 @@ export const secretSyncServiceFactory = ({ message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"` }); - // Enterprise check - if (SECRET_SYNC_PLAN_MAP[secretSync.destination as SecretSync] === SecretSyncPlanType.Enterprise) { - const plan = await licenseService.getPlan(actor.orgId); - if (!plan.enterpriseSecretSyncs) - throw new BadRequestError({ - message: - "Failed to trigger secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs." - }); - } + 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({ actor: actor.type, @@ -555,15 +543,12 @@ export const secretSyncServiceFactory = ({ message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"` }); - // Enterprise check - if (SECRET_SYNC_PLAN_MAP[secretSync.destination as SecretSync] === SecretSyncPlanType.Enterprise) { - const plan = await licenseService.getPlan(actor.orgId); - if (!plan.enterpriseSecretSyncs) - throw new BadRequestError({ - message: - "Failed to trigger secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs." - }); - } + 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({ actor: actor.type, diff --git a/frontend/src/components/secret-syncs/SecretSyncSelect.tsx b/frontend/src/components/secret-syncs/SecretSyncSelect.tsx index 1b2fe9c5b..693c17888 100644 --- a/frontend/src/components/secret-syncs/SecretSyncSelect.tsx +++ b/frontend/src/components/secret-syncs/SecretSyncSelect.tsx @@ -35,13 +35,13 @@ export const SecretSyncSelect = ({ onSelect }: Props) => { const { image, name } = SECRET_SYNC_MAP[destination]; return (
)} - {!isPending && - risksData?.totalCount !== undefined && - risksData.totalCount >= PER_PAGE_INIT && ( - setPage(newPage)} - onChangePerPage={(newPerPage) => setPerPage(newPerPage)} - /> - )} + {!isPending && risksData?.totalCount !== undefined && ( + setPage(newPage)} + onChangePerPage={handlePerPageChange} + /> + )}
diff --git a/frontend/src/pages/organization/SecretSharingSettingsPage/components/OrgSecretShareLimitSection/OrgSecretShareLimitSection.tsx b/frontend/src/pages/organization/SecretSharingSettingsPage/components/OrgSecretShareLimitSection/OrgSecretShareLimitSection.tsx index e0d88a082..d65b76b16 100644 --- a/frontend/src/pages/organization/SecretSharingSettingsPage/components/OrgSecretShareLimitSection/OrgSecretShareLimitSection.tsx +++ b/frontend/src/pages/organization/SecretSharingSettingsPage/components/OrgSecretShareLimitSection/OrgSecretShareLimitSection.tsx @@ -1,7 +1,7 @@ +import { useEffect } from "react"; import { Controller, useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; -import { useEffect } from "react"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserGroupsTable.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserGroupsTable.tsx index b22b1377f..12d4e89c9 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserGroupsTable.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserGroupsTable.tsx @@ -20,6 +20,7 @@ import { THead, Tr } from "@app/components/v2"; +import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { OrderByDirection } from "@app/hooks/api/generic/types"; import { OrgUser } from "@app/hooks/api/types"; @@ -52,7 +53,14 @@ export const UserGroupsTable = ({ handlePopUpOpen, orgMembership }: Props) => { offset, orderDirection, toggleOrderDirection - } = usePagination(UserGroupsOrderBy.Name, { initPerPage: 10 }); + } = usePagination(UserGroupsOrderBy.Name, { + initPerPage: getUserTablePreference("userGroupsTable", "perPage", 20) + }); + + const handlePerPageChange = (newPerPage: number) => { + setPerPage(newPerPage); + setUserTablePreference("userGroupsTable", "perPage", newPerPage); + }; const filteredGroupMemberships = useMemo( () => @@ -119,7 +127,7 @@ export const UserGroupsTable = ({ handlePopUpOpen, orgMembership }: Props) => { page={page} perPage={perPage} onChangePage={setPage} - onChangePerPage={setPerPage} + onChangePerPage={handlePerPageChange} /> )} {!isPending && !filteredGroupMemberships?.length && ( diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsTable.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsTable.tsx index ff9ed9bbc..c15c48cd3 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsTable.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsTable.tsx @@ -22,6 +22,7 @@ import { Tr } from "@app/components/v2"; import { useOrganization } from "@app/context"; +import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { useGetOrgMembershipProjectMemberships } from "@app/hooks/api"; import { OrderByDirection } from "@app/hooks/api/generic/types"; @@ -54,7 +55,14 @@ export const UserProjectsTable = ({ membershipId, handlePopUpOpen }: Props) => { offset, orderDirection, toggleOrderDirection - } = usePagination(UserProjectsOrderBy.Name, { initPerPage: 10 }); + } = usePagination(UserProjectsOrderBy.Name, { + initPerPage: getUserTablePreference("userProjectsTable", "perPage", 20) + }); + + const handlePerPageChange = (newPerPage: number) => { + setPerPage(newPerPage); + setUserTablePreference("userProjectsTable", "perPage", newPerPage); + }; const { data: projectMemberships = [], isPending } = useGetOrgMembershipProjectMemberships( orgId, @@ -136,7 +144,7 @@ export const UserProjectsTable = ({ membershipId, handlePopUpOpen }: Props) => { page={page} perPage={perPage} onChangePage={setPage} - onChangePerPage={setPerPage} + onChangePerPage={handlePerPageChange} /> )} {!isPending && !filteredProjectMemberships?.length && ( diff --git a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsTable.tsx b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsTable.tsx index 24f459a93..731bb18d4 100644 --- a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsTable.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsTable.tsx @@ -27,6 +27,7 @@ import { Tr } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { useListWorkspaceGroups } from "@app/hooks/api"; import { OrderByDirection } from "@app/hooks/api/generic/types"; @@ -62,7 +63,14 @@ export const GroupTable = ({ handlePopUpOpen }: Props) => { orderDirection, orderBy, toggleOrderDirection - } = usePagination(GroupsOrderBy.Name, { initPerPage: 20 }); + } = usePagination(GroupsOrderBy.Name, { + initPerPage: getUserTablePreference("projectGroupsTable", "perPage", 20) + }); + + const handlePerPageChange = (newPerPage: number) => { + setPerPage(newPerPage); + setUserTablePreference("projectGroupsTable", "perPage", newPerPage); + }; const { data: groupMemberships = [], isPending } = useListWorkspaceGroups( currentWorkspace?.id || "" @@ -183,7 +191,7 @@ export const GroupTable = ({ handlePopUpOpen }: Props) => { page={page} perPage={perPage} onChangePage={setPage} - onChangePerPage={setPerPage} + onChangePerPage={handlePerPageChange} /> )} {!isPending && !filteredGroupMemberships?.length && ( diff --git a/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx b/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx index c9e563065..730d5336d 100644 --- a/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx @@ -42,6 +42,7 @@ import { } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { formatProjectRoleName } from "@app/helpers/roles"; +import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; import { withProjectPermission } from "@app/hoc"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { useDeleteIdentityFromWorkspace, useGetWorkspaceIdentityMemberships } from "@app/hooks/api"; @@ -72,7 +73,14 @@ export const IdentityTab = withProjectPermission( perPage, page, setPerPage - } = usePagination(ProjectIdentityOrderBy.Name); + } = usePagination(ProjectIdentityOrderBy.Name, { + initPerPage: getUserTablePreference("projectIdentityTable", "perPage", 20) + }); + + const handlePerPageChange = (newPerPage: number) => { + setPerPage(newPerPage); + setUserTablePreference("projectIdentityTable", "perPage", newPerPage); + }; const workspaceId = currentWorkspace?.id ?? ""; @@ -403,7 +411,7 @@ export const IdentityTab = withProjectPermission( page={page} perPage={perPage} onChangePage={(newPage) => setPage(newPage)} - onChangePerPage={(newPerPage) => setPerPage(newPerPage)} + onChangePerPage={handlePerPageChange} /> )} {!isPending && data && data?.identityMemberships.length === 0 && ( diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersTable.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersTable.tsx index c03f7f793..c409e503b 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersTable.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersTable.tsx @@ -51,6 +51,7 @@ import { useWorkspace } from "@app/context"; import { formatProjectRoleName } from "@app/helpers/roles"; +import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { useGetProjectRoles, useGetWorkspaceUsers } from "@app/hooks/api"; import { OrderByDirection } from "@app/hooks/api/generic/types"; @@ -101,12 +102,12 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => { setOrderDirection, toggleOrderDirection } = usePagination(MembersOrderBy.Name, { - initPerPage: parseInt(localStorage.getItem("PROJECT_MEMBERS_TABLE_PER_PAGE") || "20", 10) + initPerPage: getUserTablePreference("projectMembersTable", "perPage", 20) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - localStorage.setItem("PROJECT_MEMBERS_TABLE_PER_PAGE", newPerPage.toString()); + setUserTablePreference("projectMembersTable", "perPage", newPerPage); }; const { data: members = [], isPending: isMembersLoading } = useGetWorkspaceUsers( diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/IntegrationsTable.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/IntegrationsTable.tsx index a8d2cccd5..db250a2e7 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/IntegrationsTable.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/IntegrationsTable.tsx @@ -32,6 +32,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { OrderByDirection } from "@app/hooks/api/generic/types"; import { useSyncIntegration } from "@app/hooks/api/integrations/queries"; @@ -110,7 +111,14 @@ export const IntegrationsTable = ({ orderBy, setOrderDirection, setOrderBy - } = usePagination(IntegrationsOrderBy.App, { initPerPage: 20 }); + } = usePagination(IntegrationsOrderBy.App, { + initPerPage: getUserTablePreference("integrationsTable", "perPage", 20) + }); + + const handlePerPageChange = (newPerPage: number) => { + setPerPage(newPerPage); + setUserTablePreference("integrationsTable", "perPage", newPerPage); + }; useEffect(() => { if (integrations?.some((integration) => integration.isSynced === false)) @@ -437,7 +445,7 @@ export const IntegrationsTable = ({ page={page} perPage={perPage} onChangePage={setPage} - onChangePerPage={setPerPage} + onChangePerPage={handlePerPageChange} /> )} {!isLoading && !filteredIntegrations?.length && ( diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx index 802e88bc1..e9c50b05f 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx @@ -38,6 +38,7 @@ import { } from "@app/components/v2"; import { useWorkspace } from "@app/context"; import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs"; +import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { OrderByDirection } from "@app/hooks/api/generic/types"; import { @@ -119,7 +120,14 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => { orderBy, setOrderDirection, setOrderBy - } = usePagination(SecretSyncsOrderBy.Name, { initPerPage: 20 }); + } = usePagination(SecretSyncsOrderBy.Name, { + initPerPage: getUserTablePreference("secretSyncTable", "perPage", 20) + }); + + const handlePerPageChange = (newPerPage: number) => { + setPerPage(newPerPage); + setUserTablePreference("secretSyncTable", "perPage", newPerPage); + }; const filteredSecretSyncs = useMemo( () => @@ -465,7 +473,7 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => { page={page} perPage={perPage} onChangePage={setPage} - onChangePerPage={setPerPage} + onChangePerPage={handlePerPageChange} /> )} {!filteredSecretSyncs?.length && ( diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx index 5f0d90f6c..ad91c30eb 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx @@ -64,6 +64,7 @@ import { useWorkspace } from "@app/context"; import { ProjectPermissionSecretRotationActions } from "@app/context/ProjectPermissionContext/types"; +import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { useCreateFolder, @@ -180,7 +181,14 @@ export const OverviewPage = () => { page, setPerPage, orderBy - } = usePagination(DashboardSecretsOrderBy.Name); + } = usePagination(DashboardSecretsOrderBy.Name, { + initPerPage: getUserTablePreference("secretOverviewTable", "perPage", 100) + }); + + const handlePerPageChange = (newPerPage: number) => { + setPerPage(newPerPage); + setUserTablePreference("secretOverviewTable", "perPage", newPerPage); + }; const resetSelectedEntries = useCallback(() => { setSelectedEntries({ @@ -1416,7 +1424,7 @@ export const OverviewPage = () => { page={page} perPage={perPage} onChangePage={(newPage) => setPage(newPage)} - onChangePerPage={(newPerPage) => setPerPage(newPerPage)} + onChangePerPage={handlePerPageChange} /> )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx index d28f28392..0873b5064 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx @@ -29,6 +29,7 @@ import { ProjectPermissionSecretActions, ProjectPermissionSecretRotationActions } from "@app/context/ProjectPermissionContext/types"; +import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { useGetImportedSecretsSingleEnv, @@ -98,7 +99,14 @@ const Page = () => { page, setPerPage, orderBy - } = usePagination(DashboardSecretsOrderBy.Name); + } = usePagination(DashboardSecretsOrderBy.Name, { + initPerPage: getUserTablePreference("secretDashboardTable", "perPage", 100) + }); + + const handlePerPageChange = (newPerPage: number) => { + setPerPage(newPerPage); + setUserTablePreference("secretDashboardTable", "perPage", newPerPage); + }; const [snapshotId, setSnapshotId] = useState(null); const isRollbackMode = Boolean(snapshotId); @@ -558,7 +566,7 @@ const Page = () => { page={page} perPage={perPage} onChangePage={(newPage) => setPage(newPage)} - onChangePerPage={(newPerPage) => setPerPage(newPerPage)} + onChangePerPage={handlePerPageChange} /> )} { offset, orderDirection, toggleOrderDirection - } = usePagination(TagsOrderBy.Slug, { initPerPage: 10 }); + } = usePagination(TagsOrderBy.Slug, { + initPerPage: getUserTablePreference("secretTagsTable", "perPage", 20) + }); + + const handlePerPageChange = (newPerPage: number) => { + setPerPage(newPerPage); + setUserTablePreference("secretTagsTable", "perPage", newPerPage); + }; const filteredTags = useMemo( () => @@ -151,7 +159,7 @@ export const SecretTagsTable = ({ handlePopUpOpen }: Props) => { page={page} perPage={perPage} onChangePage={setPage} - onChangePerPage={setPerPage} + onChangePerPage={handlePerPageChange} /> )} {!isPending && !filteredTags?.length && ( From 6a2358339142130f5233f84d997745f5877317eb Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Thu, 22 May 2025 11:41:35 -0300 Subject: [PATCH 10/31] Only select all secret value on edit but no view permissions, and keep the select until user starts writting --- .../src/ee/services/license/license-fns.ts | 54 +++++++++---------- .../InfisicalSecretInput.tsx | 3 ++ .../components/v2/SecretInput/SecretInput.tsx | 13 ++++- .../SecretOverviewTableRow/SecretEditRow.tsx | 1 + .../components/SecretListView/SecretItem.tsx | 7 +-- 5 files changed, 47 insertions(+), 31 deletions(-) diff --git a/backend/src/ee/services/license/license-fns.ts b/backend/src/ee/services/license/license-fns.ts index 8ef91c6f8..9c978060d 100644 --- a/backend/src/ee/services/license/license-fns.ts +++ b/backend/src/ee/services/license/license-fns.ts @@ -18,44 +18,44 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ environmentsUsed: 0, identityLimit: null, identitiesUsed: 0, - dynamicSecret: false, + dynamicSecret: true, secretVersioning: true, - pitRecovery: false, - ipAllowlisting: false, - rbac: false, - githubOrgSync: false, - customRateLimits: false, - customAlerts: false, - secretAccessInsights: false, - auditLogs: false, + pitRecovery: true, + ipAllowlisting: true, + rbac: true, + githubOrgSync: true, + customRateLimits: true, + customAlerts: true, + secretAccessInsights: true, + auditLogs: true, auditLogsRetentionDays: 0, - auditLogStreams: false, + auditLogStreams: true, auditLogStreamLimit: 3, - samlSSO: false, - hsm: false, - oidcSSO: false, - scim: false, - ldap: false, - groups: false, + samlSSO: true, + hsm: true, + oidcSSO: true, + scim: true, + ldap: true, + groups: true, status: null, trial_end: null, has_used_trial: true, - secretApproval: false, - secretRotation: false, - caCrl: false, - instanceUserManagement: false, - externalKms: false, + secretApproval: true, + secretRotation: true, + caCrl: true, + instanceUserManagement: true, + externalKms: true, rateLimits: { readLimit: 60, writeLimit: 200, secretsLimit: 40 }, - pkiEst: false, - enforceMfa: false, - projectTemplates: false, - kmip: false, - gateway: false, - sshHostGroups: false + pkiEst: true, + enforceMfa: true, + projectTemplates: true, + kmip: true, + gateway: true, + sshHostGroups: true }); export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => { diff --git a/frontend/src/components/v2/InfisicalSecretInput/InfisicalSecretInput.tsx b/frontend/src/components/v2/InfisicalSecretInput/InfisicalSecretInput.tsx index dd6d3575e..a42ee02a5 100644 --- a/frontend/src/components/v2/InfisicalSecretInput/InfisicalSecretInput.tsx +++ b/frontend/src/components/v2/InfisicalSecretInput/InfisicalSecretInput.tsx @@ -51,6 +51,7 @@ type Props = Omit, "onChange" | "val isVisible?: boolean; isReadOnly?: boolean; isDisabled?: boolean; + canEditButNotView?: boolean; secretPath?: string; environment?: string; containerClassName?: string; @@ -70,6 +71,7 @@ export const InfisicalSecretInput = forwardRef( containerClassName, secretPath: propSecretPath, environment: propEnvironment, + canEditButNotView, ...props }, ref @@ -273,6 +275,7 @@ export const InfisicalSecretInput = forwardRef( { @@ -51,6 +52,7 @@ type Props = TextareaHTMLAttributes & { isReadOnly?: boolean; isDisabled?: boolean; containerClassName?: string; + canEditButNotView?: boolean; }; const commonClassName = "font-mono text-sm caret-white border-none outline-none w-full break-all"; @@ -66,6 +68,7 @@ export const SecretInput = forwardRef( isDisabled, isReadOnly, onFocus, + canEditButNotView, ...props }, ref @@ -93,7 +96,15 @@ export const SecretInput = forwardRef( onFocus={(evt) => { onFocus?.(evt); setIsSecretFocused.on(); - evt.currentTarget.select(); + if (canEditButNotView && value === HIDDEN_SECRET_VALUE) { + evt.currentTarget.select(); + } + }} + onMouseDown={(e) => { + if (canEditButNotView && value === HIDDEN_SECRET_VALUE) { + e.preventDefault(); + e.currentTarget.select(); + } }} disabled={isDisabled} spellCheck={false} diff --git a/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretEditRow.tsx b/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretEditRow.tsx index a925d3b4f..72eb085c0 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretEditRow.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretEditRow.tsx @@ -232,6 +232,7 @@ export const SecretEditRow = ({ environment={environment} isImport={isImportedSecret} defaultValue={secretValueHidden ? "" : undefined} + canEditButNotView={secretValueHidden && !isOverride} /> )} /> diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx index 32586e990..3142ec45a 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx @@ -58,7 +58,7 @@ import { } from "./SecretListView.utils"; import { CollapsibleSecretImports } from "./CollapsibleSecretImports"; -const hiddenValue = "******"; +export const HIDDEN_SECRET_VALUE = "******"; type Props = { secret: SecretV3RawSanitized; @@ -122,7 +122,7 @@ export const SecretItem = memo( const getDefaultValue = () => { if (secret.secretValueHidden) { - return canEditSecretValue ? hiddenValue : ""; + return canEditSecretValue ? HIDDEN_SECRET_VALUE : ""; } return secret.valueOverride || secret.value || ""; }; @@ -366,10 +366,11 @@ export const SecretItem = memo( isReadOnly={isReadOnly || isRotatedSecret} key="secret-value" isVisible={isVisible && !secretValueHidden} + canEditButNotView={secretValueHidden && !isOverriden} environment={environment} secretPath={secretPath} {...field} - defaultValue={secretValueHidden ? hiddenValue : undefined} + defaultValue={secretValueHidden ? HIDDEN_SECRET_VALUE : undefined} containerClassName="py-1.5 rounded-md transition-all" /> )} From ef4df9691dbd815e433eb45ed6681a3413aae7c9 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Thu, 22 May 2025 11:46:43 -0300 Subject: [PATCH 11/31] Fix license-fns test changes --- .../src/ee/services/license/license-fns.ts | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/backend/src/ee/services/license/license-fns.ts b/backend/src/ee/services/license/license-fns.ts index 9c978060d..8ef91c6f8 100644 --- a/backend/src/ee/services/license/license-fns.ts +++ b/backend/src/ee/services/license/license-fns.ts @@ -18,44 +18,44 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ environmentsUsed: 0, identityLimit: null, identitiesUsed: 0, - dynamicSecret: true, + dynamicSecret: false, secretVersioning: true, - pitRecovery: true, - ipAllowlisting: true, - rbac: true, - githubOrgSync: true, - customRateLimits: true, - customAlerts: true, - secretAccessInsights: true, - auditLogs: true, + pitRecovery: false, + ipAllowlisting: false, + rbac: false, + githubOrgSync: false, + customRateLimits: false, + customAlerts: false, + secretAccessInsights: false, + auditLogs: false, auditLogsRetentionDays: 0, - auditLogStreams: true, + auditLogStreams: false, auditLogStreamLimit: 3, - samlSSO: true, - hsm: true, - oidcSSO: true, - scim: true, - ldap: true, - groups: true, + samlSSO: false, + hsm: false, + oidcSSO: false, + scim: false, + ldap: false, + groups: false, status: null, trial_end: null, has_used_trial: true, - secretApproval: true, - secretRotation: true, - caCrl: true, - instanceUserManagement: true, - externalKms: true, + secretApproval: false, + secretRotation: false, + caCrl: false, + instanceUserManagement: false, + externalKms: false, rateLimits: { readLimit: 60, writeLimit: 200, secretsLimit: 40 }, - pkiEst: true, - enforceMfa: true, - projectTemplates: true, - kmip: true, - gateway: true, - sshHostGroups: true + pkiEst: false, + enforceMfa: false, + projectTemplates: false, + kmip: false, + gateway: false, + sshHostGroups: false }); export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => { From f0a45fb7d8b9f23f7cf04bac898fad0417f79145 Mon Sep 17 00:00:00 2001 From: x032205 Date: Thu, 22 May 2025 11:32:49 -0400 Subject: [PATCH 12/31] Review fixes --- frontend/src/helpers/userTablePreferences.ts | 8 ++++++-- .../kms/KmipPage/components/KmipClientTable.tsx | 10 +++++++--- .../kms/OverviewPage/components/CmekTable.tsx | 10 +++++++--- .../components/OrgGroupsSection/OrgGroupsTable.tsx | 10 +++++++--- .../components/IdentitySection/IdentityTable.tsx | 10 +++++++--- .../OrgMembersSection/OrgMembersTable.tsx | 10 +++++++--- .../components/AppConnectionsTable.tsx | 10 +++++++--- .../GroupMembersSection/GroupMembersTable.tsx | 10 +++++++--- .../IdentityProjectsTable.tsx | 10 +++++++--- .../components/AllProjectView.tsx | 10 +++++++--- .../components/MyProjectView.tsx | 10 +++++++--- .../SecretScanningPage/SecretScanningPage.tsx | 14 +++++++++----- .../UserProjectsSection/UserGroupsTable.tsx | 10 +++++++--- .../UserProjectsSection/UserProjectsTable.tsx | 10 +++++++--- .../components/GroupsSection/GroupsTable.tsx | 10 +++++++--- .../components/IdentityTab/IdentityTab.tsx | 10 +++++++--- .../MembersTab/components/MembersTable.tsx | 10 +++++++--- .../NativeIntegrationsTab/IntegrationsTable.tsx | 10 +++++++--- .../SecretSyncTable/SecretSyncsTable.tsx | 10 +++++++--- .../secret-manager/OverviewPage/OverviewPage.tsx | 10 +++++++--- .../SecretDashboardPage/SecretDashboardPage.tsx | 10 +++++++--- .../SecretTagsSection/SecretTagsTable.tsx | 10 +++++++--- 22 files changed, 155 insertions(+), 67 deletions(-) diff --git a/frontend/src/helpers/userTablePreferences.ts b/frontend/src/helpers/userTablePreferences.ts index 95b219f67..a23438746 100644 --- a/frontend/src/helpers/userTablePreferences.ts +++ b/frontend/src/helpers/userTablePreferences.ts @@ -1,5 +1,9 @@ const TABLE_PREFERENCES_KEY = "userTablePreferences"; +export enum PreferenceKey { + PerPage = "perPage" +} + interface TableSpecificPreferences { [preferenceKey: string]: any; } @@ -33,7 +37,7 @@ const saveAllTablePreferences = (preferences: UserTablePreferences): void => { // Retrieves a specific preference for a given table export const getUserTablePreference = ( tableName: string, - preferenceKey: string, + preferenceKey: PreferenceKey, defaultValue: T ): T => { const preferences = getAllTablePreferences(); @@ -55,7 +59,7 @@ export const getUserTablePreference = ( // Sets a specific preference for a given table and saves it to localStorage export const setUserTablePreference = ( tableName: string, - preferenceKey: string, + preferenceKey: PreferenceKey, value: any ): void => { const preferences = getAllTablePreferences(); diff --git a/frontend/src/pages/kms/KmipPage/components/KmipClientTable.tsx b/frontend/src/pages/kms/KmipPage/components/KmipClientTable.tsx index aae8e81b3..bcc49b11a 100644 --- a/frontend/src/pages/kms/KmipPage/components/KmipClientTable.tsx +++ b/frontend/src/pages/kms/KmipPage/components/KmipClientTable.tsx @@ -43,7 +43,11 @@ import { useSubscription, useWorkspace } from "@app/context"; -import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { OrderByDirection } from "@app/hooks/api/generic/types"; import { useGetKmipClientsByProjectId } from "@app/hooks/api/kmip"; @@ -73,12 +77,12 @@ export const KmipClientTable = () => { page, setPerPage } = usePagination(KmipClientOrderBy.Name, { - initPerPage: getUserTablePreference("kmipClientTable", "perPage", 20) + initPerPage: getUserTablePreference("kmipClientTable", PreferenceKey.PerPage, 20) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("kmipClientTable", "perPage", newPerPage); + setUserTablePreference("kmipClientTable", PreferenceKey.PerPage, newPerPage); }; const { data, isPending, isFetching } = useGetKmipClientsByProjectId({ diff --git a/frontend/src/pages/kms/OverviewPage/components/CmekTable.tsx b/frontend/src/pages/kms/OverviewPage/components/CmekTable.tsx index 248cad09d..e1a2fe789 100644 --- a/frontend/src/pages/kms/OverviewPage/components/CmekTable.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/CmekTable.tsx @@ -53,7 +53,11 @@ import { useWorkspace } from "@app/context"; import { kmsKeyUsageOptions } from "@app/helpers/kms"; -import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; import { usePagination, usePopUp, useResetPageHelper, useTimedReset } from "@app/hooks"; import { useGetCmeksByProjectId, useUpdateCmek } from "@app/hooks/api/cmeks"; import { CmekOrderBy, KmsKeyUsage, TCmek } from "@app/hooks/api/cmeks/types"; @@ -102,12 +106,12 @@ export const CmekTable = () => { page, setPerPage } = usePagination(CmekOrderBy.Name, { - initPerPage: getUserTablePreference("cmekClientTable", "perPage", 20) + initPerPage: getUserTablePreference("cmekClientTable", PreferenceKey.PerPage, 20) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("cmekClientTable", "perPage", newPerPage); + setUserTablePreference("cmekClientTable", PreferenceKey.PerPage, newPerPage); }; const { data, isPending, isFetching } = useGetCmeksByProjectId({ diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsTable.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsTable.tsx index 7369bbc11..14de28fd6 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsTable.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsTable.tsx @@ -34,7 +34,11 @@ import { Tr } from "@app/components/v2"; import { OrgPermissionGroupActions, OrgPermissionSubjects, useOrganization } from "@app/context"; -import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { useGetOrganizationGroups, useGetOrgRoles, useUpdateGroup } from "@app/hooks/api"; import { OrderByDirection } from "@app/hooks/api/generic/types"; @@ -105,12 +109,12 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => { setOrderDirection, toggleOrderDirection } = usePagination(GroupsOrderBy.Name, { - initPerPage: getUserTablePreference("orgGroupsTable", "perPage", 20) + initPerPage: getUserTablePreference("orgGroupsTable", PreferenceKey.PerPage, 20) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("orgGroupsTable", "perPage", newPerPage); + setUserTablePreference("orgGroupsTable", PreferenceKey.PerPage, newPerPage); }; const filteredGroups = useMemo(() => { diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx index 03cd3ac06..d499a7b36 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx @@ -42,7 +42,11 @@ import { Tr } from "@app/components/v2"; import { OrgPermissionIdentityActions, OrgPermissionSubjects, useOrganization } from "@app/context"; -import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { useGetOrgRoles, useSearchIdentities, useUpdateIdentity } from "@app/hooks/api"; import { OrderByDirection } from "@app/hooks/api/generic/types"; @@ -78,12 +82,12 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => { page, setPerPage } = usePagination(OrgIdentityOrderBy.Name, { - initPerPage: getUserTablePreference("identityTable", "perPage", 20) + initPerPage: getUserTablePreference("identityTable", PreferenceKey.PerPage, 20) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("identityTable", "perPage", newPerPage); + setUserTablePreference("identityTable", PreferenceKey.PerPage, newPerPage); }; const [filteredRoles, setFilteredRoles] = useState([]); diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx index e9c9d1f16..e0350741e 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx @@ -42,7 +42,11 @@ import { useSubscription, useUser } from "@app/context"; -import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { useFetchServerStatus, @@ -172,12 +176,12 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro setOrderDirection, toggleOrderDirection } = usePagination(OrgMembersOrderBy.Name, { - initPerPage: getUserTablePreference("orgMembersTable", "perPage", 20) + initPerPage: getUserTablePreference("orgMembersTable", PreferenceKey.PerPage, 20) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("orgMembersTable", "perPage", newPerPage); + setUserTablePreference("orgMembersTable", PreferenceKey.PerPage, newPerPage); }; const filteredUsers = useMemo( diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionsTable.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionsTable.tsx index ffaac835e..bc448663a 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionsTable.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionsTable.tsx @@ -30,7 +30,11 @@ import { Tr } from "@app/components/v2"; import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; -import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { TAppConnection, useListAppConnections } from "@app/hooks/api/appConnections"; import { AppConnection } from "@app/hooks/api/appConnections/enums"; @@ -78,12 +82,12 @@ export const AppConnectionsTable = () => { setOrderDirection, setOrderBy } = usePagination(AppConnectionsOrderBy.App, { - initPerPage: getUserTablePreference("appConnectionsTable", "perPage", 20) + initPerPage: getUserTablePreference("appConnectionsTable", PreferenceKey.PerPage, 20) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("appConnectionsTable", "perPage", newPerPage); + setUserTablePreference("appConnectionsTable", PreferenceKey.PerPage, newPerPage); }; const filteredAppConnections = useMemo( diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx index 360dc95c9..6472c34c1 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx @@ -25,7 +25,11 @@ import { Tr } from "@app/components/v2"; import { OrgPermissionGroupActions, OrgPermissionSubjects, useOrganization } from "@app/context"; -import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { useListGroupUsers, useOidcManageGroupMembershipsEnabled } from "@app/hooks/api"; import { OrderByDirection } from "@app/hooks/api/generic/types"; @@ -59,12 +63,12 @@ export const GroupMembersTable = ({ groupId, groupSlug, handlePopUpOpen }: Props orderDirection, toggleOrderDirection } = usePagination(GroupMembersOrderBy.Name, { - initPerPage: getUserTablePreference("groupMembersTable", "perPage", 20) + initPerPage: getUserTablePreference("groupMembersTable", PreferenceKey.PerPage, 20) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("groupMembersTable", "perPage", newPerPage); + setUserTablePreference("groupMembersTable", PreferenceKey.PerPage, newPerPage); }; const { currentOrg } = useOrganization(); diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectsTable.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectsTable.tsx index c32c3226a..ef70d1f96 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectsTable.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectsTable.tsx @@ -21,7 +21,11 @@ import { THead, Tr } from "@app/components/v2"; -import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { useGetIdentityProjectMemberships } from "@app/hooks/api"; import { OrderByDirection } from "@app/hooks/api/generic/types"; @@ -55,12 +59,12 @@ export const IdentityProjectsTable = ({ identityId, handlePopUpOpen }: Props) => orderDirection, toggleOrderDirection } = usePagination(IdentityProjectsOrderBy.Name, { - initPerPage: getUserTablePreference("identityProjectsTable", "perPage", 20) + initPerPage: getUserTablePreference("identityProjectsTable", PreferenceKey.PerPage, 20) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("identityProjectsTable", "perPage", newPerPage); + setUserTablePreference("identityProjectsTable", PreferenceKey.PerPage, newPerPage); }; const filteredProjectMemberships = useMemo( diff --git a/frontend/src/pages/organization/SecretManagerOverviewPage/components/AllProjectView.tsx b/frontend/src/pages/organization/SecretManagerOverviewPage/components/AllProjectView.tsx index cf27a9961..6be1a4554 100644 --- a/frontend/src/pages/organization/SecretManagerOverviewPage/components/AllProjectView.tsx +++ b/frontend/src/pages/organization/SecretManagerOverviewPage/components/AllProjectView.tsx @@ -28,7 +28,11 @@ import { } from "@app/components/v2"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; import { getProjectHomePage } from "@app/helpers/project"; -import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { useRequestProjectAccess, useSearchProjects } from "@app/hooks/api"; import { ProjectType, Workspace } from "@app/hooks/api/workspace/types"; @@ -106,12 +110,12 @@ export const AllProjectView = ({ toggleOrderDirection, orderDirection } = usePagination("name", { - initPerPage: getUserTablePreference("allProjectsTable", "perPage", 50) + initPerPage: getUserTablePreference("allProjectsTable", PreferenceKey.PerPage, 50) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("allProjectsTable", "perPage", newPerPage); + setUserTablePreference("allProjectsTable", PreferenceKey.PerPage, newPerPage); }; const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp([ diff --git a/frontend/src/pages/organization/SecretManagerOverviewPage/components/MyProjectView.tsx b/frontend/src/pages/organization/SecretManagerOverviewPage/components/MyProjectView.tsx index 3e84e1809..a04b4e196 100644 --- a/frontend/src/pages/organization/SecretManagerOverviewPage/components/MyProjectView.tsx +++ b/frontend/src/pages/organization/SecretManagerOverviewPage/components/MyProjectView.tsx @@ -19,7 +19,11 @@ import { OrgPermissionCan } from "@app/components/permissions"; import { Button, IconButton, Input, Pagination, Skeleton, Tooltip } from "@app/components/v2"; import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { getProjectHomePage } from "@app/helpers/project"; -import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { useGetUserWorkspaces } from "@app/hooks/api"; import { OrderByDirection } from "@app/hooks/api/generic/types"; @@ -65,12 +69,12 @@ export const MyProjectView = ({ toggleOrderDirection, orderDirection } = usePagination(ProjectOrderBy.Name, { - initPerPage: getUserTablePreference("myProjectsTable", "perPage", 20) + initPerPage: getUserTablePreference("myProjectsTable", PreferenceKey.PerPage, 24) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("myProjectsTable", "perPage", newPerPage); + setUserTablePreference("myProjectsTable", PreferenceKey.PerPage, newPerPage); }; const { data: projectFavorites, isPending: isProjectFavoritesLoading } = diff --git a/frontend/src/pages/organization/SecretScanningPage/SecretScanningPage.tsx b/frontend/src/pages/organization/SecretScanningPage/SecretScanningPage.tsx index 41dec2981..f0ad03677 100644 --- a/frontend/src/pages/organization/SecretScanningPage/SecretScanningPage.tsx +++ b/frontend/src/pages/organization/SecretScanningPage/SecretScanningPage.tsx @@ -13,7 +13,11 @@ import { useOrganization, useServerConfig } from "@app/context"; -import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; import { withPermission } from "@app/hoc"; import { usePagination, usePopUp } from "@app/hooks"; import { @@ -48,12 +52,12 @@ export const SecretScanningPage = withPermission( const { offset, limit, orderBy, setPage, perPage, page, setPerPage } = usePagination( SecretScanningOrderBy.CreatedAt, - { initPerPage: getUserTablePreference("secretScanningTable", "perPage", 20) } + { initPerPage: getUserTablePreference("secretScanningTable", PreferenceKey.PerPage, 20) } ); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("secretScanningTable", "perPage", newPerPage); + setUserTablePreference("secretScanningTable", PreferenceKey.PerPage, newPerPage); }; const repositoryNames = watch("repositoryNames"); @@ -184,7 +188,7 @@ export const SecretScanningPage = withPermission( )} -
+
{integrationEnabled && (
)} - {!isPending && risksData?.totalCount !== undefined && ( + {!isPending && risksData?.totalCount !== undefined && risksData.totalCount >= 10 && ( { orderDirection, toggleOrderDirection } = usePagination(UserGroupsOrderBy.Name, { - initPerPage: getUserTablePreference("userGroupsTable", "perPage", 20) + initPerPage: getUserTablePreference("userGroupsTable", PreferenceKey.PerPage, 10) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("userGroupsTable", "perPage", newPerPage); + setUserTablePreference("userGroupsTable", PreferenceKey.PerPage, newPerPage); }; const filteredGroupMemberships = useMemo( diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsTable.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsTable.tsx index c15c48cd3..9e6042c03 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsTable.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsTable.tsx @@ -22,7 +22,11 @@ import { Tr } from "@app/components/v2"; import { useOrganization } from "@app/context"; -import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { useGetOrgMembershipProjectMemberships } from "@app/hooks/api"; import { OrderByDirection } from "@app/hooks/api/generic/types"; @@ -56,12 +60,12 @@ export const UserProjectsTable = ({ membershipId, handlePopUpOpen }: Props) => { orderDirection, toggleOrderDirection } = usePagination(UserProjectsOrderBy.Name, { - initPerPage: getUserTablePreference("userProjectsTable", "perPage", 20) + initPerPage: getUserTablePreference("userProjectsTable", PreferenceKey.PerPage, 10) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("userProjectsTable", "perPage", newPerPage); + setUserTablePreference("userProjectsTable", PreferenceKey.PerPage, newPerPage); }; const { data: projectMemberships = [], isPending } = useGetOrgMembershipProjectMemberships( diff --git a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsTable.tsx b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsTable.tsx index 731bb18d4..8f06576e2 100644 --- a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsTable.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsTable.tsx @@ -27,7 +27,11 @@ import { Tr } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; -import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { useListWorkspaceGroups } from "@app/hooks/api"; import { OrderByDirection } from "@app/hooks/api/generic/types"; @@ -64,12 +68,12 @@ export const GroupTable = ({ handlePopUpOpen }: Props) => { orderBy, toggleOrderDirection } = usePagination(GroupsOrderBy.Name, { - initPerPage: getUserTablePreference("projectGroupsTable", "perPage", 20) + initPerPage: getUserTablePreference("projectGroupsTable", PreferenceKey.PerPage, 20) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("projectGroupsTable", "perPage", newPerPage); + setUserTablePreference("projectGroupsTable", PreferenceKey.PerPage, newPerPage); }; const { data: groupMemberships = [], isPending } = useListWorkspaceGroups( diff --git a/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx b/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx index 730d5336d..d20beb738 100644 --- a/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx @@ -42,7 +42,11 @@ import { } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { formatProjectRoleName } from "@app/helpers/roles"; -import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; import { withProjectPermission } from "@app/hoc"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { useDeleteIdentityFromWorkspace, useGetWorkspaceIdentityMemberships } from "@app/hooks/api"; @@ -74,12 +78,12 @@ export const IdentityTab = withProjectPermission( page, setPerPage } = usePagination(ProjectIdentityOrderBy.Name, { - initPerPage: getUserTablePreference("projectIdentityTable", "perPage", 20) + initPerPage: getUserTablePreference("projectIdentityTable", PreferenceKey.PerPage, 20) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("projectIdentityTable", "perPage", newPerPage); + setUserTablePreference("projectIdentityTable", PreferenceKey.PerPage, newPerPage); }; const workspaceId = currentWorkspace?.id ?? ""; diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersTable.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersTable.tsx index c409e503b..c1251bef2 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersTable.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersTable.tsx @@ -51,7 +51,11 @@ import { useWorkspace } from "@app/context"; import { formatProjectRoleName } from "@app/helpers/roles"; -import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { useGetProjectRoles, useGetWorkspaceUsers } from "@app/hooks/api"; import { OrderByDirection } from "@app/hooks/api/generic/types"; @@ -102,12 +106,12 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => { setOrderDirection, toggleOrderDirection } = usePagination(MembersOrderBy.Name, { - initPerPage: getUserTablePreference("projectMembersTable", "perPage", 20) + initPerPage: getUserTablePreference("projectMembersTable", PreferenceKey.PerPage, 20) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("projectMembersTable", "perPage", newPerPage); + setUserTablePreference("projectMembersTable", PreferenceKey.PerPage, newPerPage); }; const { data: members = [], isPending: isMembersLoading } = useGetWorkspaceUsers( diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/IntegrationsTable.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/IntegrationsTable.tsx index db250a2e7..775e42c50 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/IntegrationsTable.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/IntegrationsTable.tsx @@ -32,7 +32,11 @@ import { Tooltip, Tr } from "@app/components/v2"; -import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { OrderByDirection } from "@app/hooks/api/generic/types"; import { useSyncIntegration } from "@app/hooks/api/integrations/queries"; @@ -112,12 +116,12 @@ export const IntegrationsTable = ({ setOrderDirection, setOrderBy } = usePagination(IntegrationsOrderBy.App, { - initPerPage: getUserTablePreference("integrationsTable", "perPage", 20) + initPerPage: getUserTablePreference("integrationsTable", PreferenceKey.PerPage, 20) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("integrationsTable", "perPage", newPerPage); + setUserTablePreference("integrationsTable", PreferenceKey.PerPage, newPerPage); }; useEffect(() => { diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx index e9c50b05f..fcd42a10f 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx @@ -38,7 +38,11 @@ import { } from "@app/components/v2"; import { useWorkspace } from "@app/context"; import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs"; -import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { OrderByDirection } from "@app/hooks/api/generic/types"; import { @@ -121,12 +125,12 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => { setOrderDirection, setOrderBy } = usePagination(SecretSyncsOrderBy.Name, { - initPerPage: getUserTablePreference("secretSyncTable", "perPage", 20) + initPerPage: getUserTablePreference("secretSyncTable", PreferenceKey.PerPage, 20) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("secretSyncTable", "perPage", newPerPage); + setUserTablePreference("secretSyncTable", PreferenceKey.PerPage, newPerPage); }; const filteredSecretSyncs = useMemo( diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx index ad91c30eb..c94aef0d5 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx @@ -64,7 +64,11 @@ import { useWorkspace } from "@app/context"; import { ProjectPermissionSecretRotationActions } from "@app/context/ProjectPermissionContext/types"; -import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { useCreateFolder, @@ -182,12 +186,12 @@ export const OverviewPage = () => { setPerPage, orderBy } = usePagination(DashboardSecretsOrderBy.Name, { - initPerPage: getUserTablePreference("secretOverviewTable", "perPage", 100) + initPerPage: getUserTablePreference("secretOverviewTable", PreferenceKey.PerPage, 100) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("secretOverviewTable", "perPage", newPerPage); + setUserTablePreference("secretOverviewTable", PreferenceKey.PerPage, newPerPage); }; const resetSelectedEntries = useCallback(() => { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx index 0873b5064..49bf1a72c 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx @@ -29,7 +29,11 @@ import { ProjectPermissionSecretActions, ProjectPermissionSecretRotationActions } from "@app/context/ProjectPermissionContext/types"; -import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { useGetImportedSecretsSingleEnv, @@ -100,12 +104,12 @@ const Page = () => { setPerPage, orderBy } = usePagination(DashboardSecretsOrderBy.Name, { - initPerPage: getUserTablePreference("secretDashboardTable", "perPage", 100) + initPerPage: getUserTablePreference("secretDashboardTable", PreferenceKey.PerPage, 100) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("secretDashboardTable", "perPage", newPerPage); + setUserTablePreference("secretDashboardTable", PreferenceKey.PerPage, newPerPage); }; const [snapshotId, setSnapshotId] = useState(null); diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/SecretTagsTable.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/SecretTagsTable.tsx index 180e78645..d0633eb12 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/SecretTagsTable.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/SecretTagsTable.tsx @@ -25,7 +25,11 @@ import { Tr } from "@app/components/v2"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; -import { getUserTablePreference, setUserTablePreference } from "@app/helpers/userTablePreferences"; +import { + getUserTablePreference, + PreferenceKey, + setUserTablePreference +} from "@app/helpers/userTablePreferences"; import { usePagination, useResetPageHelper } from "@app/hooks"; import { OrderByDirection } from "@app/hooks/api/generic/types"; import { useGetWsTags } from "@app/hooks/api/tags"; @@ -63,12 +67,12 @@ export const SecretTagsTable = ({ handlePopUpOpen }: Props) => { orderDirection, toggleOrderDirection } = usePagination(TagsOrderBy.Slug, { - initPerPage: getUserTablePreference("secretTagsTable", "perPage", 20) + initPerPage: getUserTablePreference("secretTagsTable", PreferenceKey.PerPage, 20) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); - setUserTablePreference("secretTagsTable", "perPage", newPerPage); + setUserTablePreference("secretTagsTable", PreferenceKey.PerPage, newPerPage); }; const filteredTags = useMemo( From b46a0dfc21b5252b3c0d1a4e623088eb8d35dc2f Mon Sep 17 00:00:00 2001 From: = Date: Fri, 23 May 2025 02:03:14 +0530 Subject: [PATCH 13/31] feat: org id logger --- backend/src/@types/fastify.d.ts | 1 + backend/src/lib/logger/logger.ts | 17 +++++++++++++---- .../src/server/plugins/auth/inject-identity.ts | 2 ++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index b098368c4..1882964bb 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -110,6 +110,7 @@ import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integ declare module "@fastify/request-context" { interface RequestContextData { reqId: string; + orgId?: string; identityAuthInfo?: { identityId: string; oidc?: { diff --git a/backend/src/lib/logger/logger.ts b/backend/src/lib/logger/logger.ts index afde8ef97..219b4a9a7 100644 --- a/backend/src/lib/logger/logger.ts +++ b/backend/src/lib/logger/logger.ts @@ -95,11 +95,20 @@ const extractReqId = () => { try { return requestContext.get("reqId") || UNKNOWN_REQUEST_ID; } catch (err) { + // eslint-disable-next-line no-console console.log("failed to get request context", err); return UNKNOWN_REQUEST_ID; } }; +const extractOrgId = () => { + try { + return requestContext.get("orgId"); + } catch { + return ""; + } +}; + export const initLogger = () => { const cfg = loggerConfig.parse(process.env); const targets: pino.TransportMultiOptions["targets"][number][] = [ @@ -135,22 +144,22 @@ export const initLogger = () => { const wrapLogger = (originalLogger: Logger): CustomLogger => { // eslint-disable-next-line no-param-reassign, @typescript-eslint/no-explicit-any originalLogger.info = (obj: unknown, msg?: string, ...args: any[]) => { - return originalLogger.child({ reqId: extractReqId() }).info(obj, msg, ...args); + return originalLogger.child({ reqId: extractReqId(), orgId: extractOrgId() }).info(obj, msg, ...args); }; // eslint-disable-next-line no-param-reassign, @typescript-eslint/no-explicit-any originalLogger.error = (obj: unknown, msg?: string, ...args: any[]) => { - return originalLogger.child({ reqId: extractReqId() }).error(obj, msg, ...args); + return originalLogger.child({ reqId: extractReqId(), orgId: extractOrgId() }).error(obj, msg, ...args); }; // eslint-disable-next-line no-param-reassign, @typescript-eslint/no-explicit-any originalLogger.warn = (obj: unknown, msg?: string, ...args: any[]) => { - return originalLogger.child({ reqId: extractReqId() }).warn(obj, msg, ...args); + return originalLogger.child({ reqId: extractReqId(), orgId: extractOrgId() }).warn(obj, msg, ...args); }; // eslint-disable-next-line no-param-reassign, @typescript-eslint/no-explicit-any originalLogger.debug = (obj: unknown, msg?: string, ...args: any[]) => { - return originalLogger.child({ reqId: extractReqId() }).debug(obj, msg, ...args); + return originalLogger.child({ reqId: extractReqId(), orgId: extractOrgId() }).debug(obj, msg, ...args); }; return originalLogger; diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index 57a1313c6..1013eb12f 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -123,6 +123,7 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { switch (authMode) { case AuthMode.JWT: { const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity(token); + requestContext.set("orgId", orgId); req.auth = { authMode: AuthMode.JWT, user, @@ -138,6 +139,7 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { case AuthMode.IDENTITY_ACCESS_TOKEN: { const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken(token, req.realIp); const serverCfg = await getServerCfg(); + requestContext.set("orgId", identity.orgId); req.auth = { authMode: AuthMode.IDENTITY_ACCESS_TOKEN, actor, From 85edbbcdc39ae12ef48256f56c1cb9f345cb80b5 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Thu, 22 May 2025 16:29:40 -0700 Subject: [PATCH 14/31] add org id to missing auth modes --- backend/src/server/plugins/auth/inject-identity.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts index 1013eb12f..afea5c9f9 100644 --- a/backend/src/server/plugins/auth/inject-identity.ts +++ b/backend/src/server/plugins/auth/inject-identity.ts @@ -159,6 +159,7 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { } case AuthMode.SERVICE_TOKEN: { const serviceToken = await server.services.serviceToken.fnValidateServiceToken(token); + requestContext.set("orgId", serviceToken.orgId); req.auth = { orgId: serviceToken.orgId, authMode: AuthMode.SERVICE_TOKEN as const, @@ -183,6 +184,7 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => { } case AuthMode.SCIM_TOKEN: { const { orgId, scimTokenId } = await server.services.scim.fnValidateScimToken(token); + requestContext.set("orgId", orgId); req.auth = { authMode: AuthMode.SCIM_TOKEN, actor, scimTokenId, orgId, authMethod: null }; break; } From b950e07ad67ada6f86de6ba67c6709ebf6ee7cd2 Mon Sep 17 00:00:00 2001 From: x032205 Date: Fri, 23 May 2025 02:06:05 -0400 Subject: [PATCH 15/31] fixed firefox bug --- frontend/src/components/secret-syncs/SecretSyncSelect.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/secret-syncs/SecretSyncSelect.tsx b/frontend/src/components/secret-syncs/SecretSyncSelect.tsx index 693c17888..24975cca2 100644 --- a/frontend/src/components/secret-syncs/SecretSyncSelect.tsx +++ b/frontend/src/components/secret-syncs/SecretSyncSelect.tsx @@ -49,12 +49,11 @@ export const SecretSyncSelect = ({ onSelect }: Props) => { } className={twMerge( "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", - enterprise && !subscription.enterpriseSecretSyncs ? "border-0 bg-opacity-0" : "" + enterprise && !subscription.enterpriseSecretSyncs + ? "border-0 bg-opacity-0 opacity-40" + : "" )} > - {enterprise && !subscription.enterpriseSecretSyncs && ( -
- )} Date: Fri, 23 May 2025 03:19:45 -0400 Subject: [PATCH 16/31] feat(smtp-service): Custom CA Certs --- backend/src/lib/config/env.ts | 18 +++++++++++++--- docs/self-hosting/configuration/envars.mdx | 25 ++++++++++++++++------ 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index e38dbcfb5..22fb82943 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -69,6 +69,9 @@ const envSchema = z SMTP_PASSWORD: zpStr(z.string().optional()), SMTP_FROM_ADDRESS: zpStr(z.string().optional()), SMTP_FROM_NAME: zpStr(z.string().optional().default("Infisical")), + SMTP_CUSTOM_CA_CERT: zpStr( + z.string().optional().describe("PEM-encoded custom CA certificate(s) for the SMTP server") + ), COOKIE_SECRET_SIGN_KEY: z .string() .min(32) @@ -298,6 +301,17 @@ export const initEnvConfig = (logger?: CustomLogger) => { }; export const formatSmtpConfig = () => { + const tlsOptions: { + rejectUnauthorized: boolean; + ca?: string | string[]; + } = { + rejectUnauthorized: envCfg.SMTP_TLS_REJECT_UNAUTHORIZED + }; + + if (envCfg.SMTP_CUSTOM_CA_CERT) { + tlsOptions.ca = envCfg.SMTP_CUSTOM_CA_CERT; + } + return { host: envCfg.SMTP_HOST, port: envCfg.SMTP_PORT, @@ -309,8 +323,6 @@ export const formatSmtpConfig = () => { from: `"${envCfg.SMTP_FROM_NAME}" <${envCfg.SMTP_FROM_ADDRESS}>`, ignoreTLS: envCfg.SMTP_IGNORE_TLS, requireTLS: envCfg.SMTP_REQUIRE_TLS, - tls: { - rejectUnauthorized: envCfg.SMTP_TLS_REJECT_UNAUTHORIZED - } + tls: tlsOptions }; }; diff --git a/docs/self-hosting/configuration/envars.mdx b/docs/self-hosting/configuration/envars.mdx index b63c58d3a..199111e84 100644 --- a/docs/self-hosting/configuration/envars.mdx +++ b/docs/self-hosting/configuration/envars.mdx @@ -32,7 +32,7 @@ Used to configure platform-specific security and operational settings Specifies the network interface Infisical will bind to when accepting incoming connections. - By default, Infisical binds to `localhost`, which restricts access to connections from the same machine. + By default, Infisical binds to `localhost`, which restricts access to connections from the same machine. To make the application accessible externally (e.g., for self-hosted deployments), set this to `0.0.0.0`, which tells the server to listen on all network interfaces. @@ -95,7 +95,7 @@ The platform utilizes Postgres to persist all of its data and Redis for caching - Configure the SSL certificate for securing a Postgres connection by first encoding it in base64. + Configure the SSL certificate for securing a Postgres connection by first encoding it in base64. Use the command below to encode your certificate: `echo "" | base64` @@ -222,7 +222,7 @@ SMTP_FROM_NAME=Infisical This will be used to verify the email you are sending from. ![Create SES identity](../../images/self-hosting/configuration/email/ses-create-identity.png) - If you AWS SES is under sandbox mode, you will only be able to send emails to verified identies. + If you AWS SES is under sandbox mode, you will only be able to send emails to verified identies. @@ -388,9 +388,9 @@ SMTP_FROM_NAME=Infisical - + 1. Create an account and configure [SMTP2Go](https://www.smtp2go.com/) to send emails. -2. Turn on SMTP authentication +2. Turn on SMTP authentication ``` SMTP_HOST=mail.smtp2go.com SMTP_PORT=You can use one of the following ports: 2525, 80, 25, 8025, or 587 @@ -401,7 +401,7 @@ SMTP_FROM_NAME=Infisical ``` {" "} - + Optional (for TLS/SSL): TLS: Available on the same ports (2525, 80, 25, 8025, or 587) @@ -410,6 +410,19 @@ SSL: Available on ports 465, 8465, and 443 +### Custom CA Certificate for Email Service TLS + +If your SMTP server uses a certificate signed by a custom Certificate Authority, you need to tell Infisical to trust this custom CA. To do this, set the following environment variables: + +``` +SMTP_PORT=465 # Or your SMTPS/STARTTLS port +SMTP_CUSTOM_CA_CERT='[CERTIFICATE PEM]' + +# Always keep these as true for custom CA +SMTP_REQUIRE_TLS=true +SMTP_TLS_REJECT_UNAUTHORIZED=true +``` + ## Authentication By default, users can only login via email/password based login method. From 12beb0668218db3338bbc9130e8032233478f713 Mon Sep 17 00:00:00 2001 From: x032205 Date: Fri, 23 May 2025 12:33:31 -0400 Subject: [PATCH 17/31] Swap to using base64 --- backend/src/lib/config/env.ts | 2 +- docs/self-hosting/configuration/envars.mdx | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 22fb82943..5b1785a66 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -309,7 +309,7 @@ export const formatSmtpConfig = () => { }; if (envCfg.SMTP_CUSTOM_CA_CERT) { - tlsOptions.ca = envCfg.SMTP_CUSTOM_CA_CERT; + tlsOptions.ca = Buffer.from(envCfg.SMTP_CUSTOM_CA_CERT, "base64").toString("utf-8"); } return { diff --git a/docs/self-hosting/configuration/envars.mdx b/docs/self-hosting/configuration/envars.mdx index 199111e84..9412ff936 100644 --- a/docs/self-hosting/configuration/envars.mdx +++ b/docs/self-hosting/configuration/envars.mdx @@ -416,13 +416,17 @@ If your SMTP server uses a certificate signed by a custom Certificate Authority, ``` SMTP_PORT=465 # Or your SMTPS/STARTTLS port -SMTP_CUSTOM_CA_CERT='[CERTIFICATE PEM]' +SMTP_CUSTOM_CA_CERT='[BASE64 ENCODED CERTIFICATE PEM]' # Always keep these as true for custom CA SMTP_REQUIRE_TLS=true SMTP_TLS_REJECT_UNAUTHORIZED=true ``` + + The `SMTP_CUSTOM_CA_CERT` environment variable **must be encoded in base64 format** + + ## Authentication By default, users can only login via email/password based login method. From db44d958d3505714783d6610a905fe582101b7c0 Mon Sep 17 00:00:00 2001 From: x032205 Date: Fri, 23 May 2025 12:41:58 -0400 Subject: [PATCH 18/31] Base64 example for docs --- docs/self-hosting/configuration/envars.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/self-hosting/configuration/envars.mdx b/docs/self-hosting/configuration/envars.mdx index 9412ff936..e35dc2503 100644 --- a/docs/self-hosting/configuration/envars.mdx +++ b/docs/self-hosting/configuration/envars.mdx @@ -424,7 +424,7 @@ SMTP_TLS_REJECT_UNAUTHORIZED=true ``` - The `SMTP_CUSTOM_CA_CERT` environment variable **must be encoded in base64 format** + The `SMTP_CUSTOM_CA_CERT` environment variable **must be encoded in base64 format**. Use the command below to encode your certificate: `echo "" | base64` ## Authentication From db4db04ba63073c34d74f7abee980df2847decb0 Mon Sep 17 00:00:00 2001 From: x032205 Date: Fri, 23 May 2025 13:02:04 -0400 Subject: [PATCH 19/31] Doc updates --- docs/self-hosting/configuration/envars.mdx | 35 ++++++++-------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/docs/self-hosting/configuration/envars.mdx b/docs/self-hosting/configuration/envars.mdx index e35dc2503..8da732d6d 100644 --- a/docs/self-hosting/configuration/envars.mdx +++ b/docs/self-hosting/configuration/envars.mdx @@ -96,8 +96,7 @@ The platform utilizes Postgres to persist all of its data and Redis for caching Configure the SSL certificate for securing a Postgres connection by first encoding it in base64. - Use the command below to encode your certificate: - `echo "" | base64` + Use the following command to encode your certificate: `echo "" | base64` @@ -111,10 +110,9 @@ DB_READ_REPLICAS=[{"DB_CONNECTION_URI":""}] Configure the SSL certificate for securing a Postgres replica connection by first encoding it in base64. - Use the command below to encode your certificate: - `echo "" | base64` + Use the following command to encode your certificate: `echo "" | base64` - If not provided it will use master SSL certificate. + If not provided it will use master SSL certificate. @@ -169,6 +167,16 @@ Without email configuration, Infisical's core functions like sign-up/login and s If this is `true`, Infisical will validate the server's SSL/TLS certificate and reject the connection if the certificate is invalid or not trusted. If set to `false`, the client will accept the server's certificate regardless of its validity, which can be useful in development or testing environments but is not recommended for production use. + + + If your SMTP server uses a certificate signed by a custom Certificate Authority, you should set this variable so that Infisical can trust the custom CA. + + This variable **must be a base64 encoded PEM certificate**. Use the following command to encode your certificate: `echo "" | base64` + + Infisical highly encourages the following variables be used alongside this one for maximum security: + - `SMTP_REQUIRE_TLS=true` + - `SMTP_TLS_REJECT_UNAUTHORIZED=true` + @@ -410,23 +418,6 @@ SSL: Available on ports 465, 8465, and 443 -### Custom CA Certificate for Email Service TLS - -If your SMTP server uses a certificate signed by a custom Certificate Authority, you need to tell Infisical to trust this custom CA. To do this, set the following environment variables: - -``` -SMTP_PORT=465 # Or your SMTPS/STARTTLS port -SMTP_CUSTOM_CA_CERT='[BASE64 ENCODED CERTIFICATE PEM]' - -# Always keep these as true for custom CA -SMTP_REQUIRE_TLS=true -SMTP_TLS_REJECT_UNAUTHORIZED=true -``` - - - The `SMTP_CUSTOM_CA_CERT` environment variable **must be encoded in base64 format**. Use the command below to encode your certificate: `echo "" | base64` - - ## Authentication By default, users can only login via email/password based login method. From b75bb93d83465dacbabc1d53f4253f8562941cc2 Mon Sep 17 00:00:00 2001 From: x032205 Date: Fri, 23 May 2025 13:08:15 -0400 Subject: [PATCH 20/31] Describe fix --- backend/src/lib/config/env.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 5b1785a66..ae5af701e 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -70,7 +70,7 @@ const envSchema = z SMTP_FROM_ADDRESS: zpStr(z.string().optional()), SMTP_FROM_NAME: zpStr(z.string().optional().default("Infisical")), SMTP_CUSTOM_CA_CERT: zpStr( - z.string().optional().describe("PEM-encoded custom CA certificate(s) for the SMTP server") + z.string().optional().describe("Base64 encoded custom CA certificate PEM(s) for the SMTP server") ), COOKIE_SECRET_SIGN_KEY: z .string() From 73c6c076e837a1476158dc6136bb5b90a062f308 Mon Sep 17 00:00:00 2001 From: x032205 Date: Fri, 23 May 2025 13:18:56 -0400 Subject: [PATCH 21/31] Review fixes --- backend/src/ee/services/audit-log/audit-log-types.ts | 6 +++--- backend/src/server/routes/v1/project-router.ts | 5 +---- backend/src/server/routes/v2/project-router.ts | 10 +++++----- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index 172bf3b4e..e4874619b 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -2936,7 +2936,7 @@ interface OrgUpdateEvent { interface ProjectCreateEvent { type: EventType.CREATE_PROJECT; metadata: { - projectName: string; + name: string; slug?: string; type: ProjectType; }; @@ -2959,8 +2959,8 @@ interface ProjectUpdateEvent { interface ProjectDeleteEvent { type: EventType.DELETE_PROJECT; metadata: { - projectId: string; - projectName: string; + id: string; + name: string; }; } diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index 8a53a648d..651faede4 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -270,10 +270,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { projectId: req.params.workspaceId, event: { type: EventType.DELETE_PROJECT, - metadata: { - projectId: workspace.id, - projectName: workspace.name - } + metadata: workspace } }); diff --git a/backend/src/server/routes/v2/project-router.ts b/backend/src/server/routes/v2/project-router.ts index b4d48e4f3..00cd69329 100644 --- a/backend/src/server/routes/v2/project-router.ts +++ b/backend/src/server/routes/v2/project-router.ts @@ -212,7 +212,10 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { projectId: project.id, event: { type: EventType.CREATE_PROJECT, - metadata: req.body + metadata: { + ...req.body, + name: req.body.projectName + } } }); @@ -264,10 +267,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { projectId: project.id, event: { type: EventType.DELETE_PROJECT, - metadata: { - projectId: project.id, - projectName: project.name - } + metadata: project } }); From 84322f4f68270eb65e9eb0b7a88e886107d43004 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Fri, 23 May 2025 14:10:04 -0700 Subject: [PATCH 22/31] temp: add log to help debug verify loop --- backend/src/services/user/user-service.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/backend/src/services/user/user-service.ts b/backend/src/services/user/user-service.ts index 29f6300d6..36509962e 100644 --- a/backend/src/services/user/user-service.ts +++ b/backend/src/services/user/user-service.ts @@ -5,6 +5,7 @@ import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/pe import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; 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 { TokenType } from "@app/services/auth-token/auth-token-types"; import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; @@ -80,6 +81,17 @@ export const userServiceFactory = ({ const verifyEmailVerificationCode = async (username: string, code: string) => { // akhilmhdh: case sensitive email resolution 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}` + ); + const user = usersByusername?.length > 1 ? usersByusername.find((el) => el.username === username) : usersByusername?.[0]; if (!user) throw new NotFoundError({ name: `User with username '${username}' not found` }); From df573649857ef6249c3f3b4719277adeeac66cf9 Mon Sep 17 00:00:00 2001 From: x032205 Date: Fri, 23 May 2025 17:59:29 -0400 Subject: [PATCH 23/31] ui fix --- .../AppConnectionsPage/components/AppConnectionList.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx index a26349fd0..5c06f71c8 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx @@ -52,12 +52,11 @@ export const AppConnectionsSelect = ({ onSelect }: Props) => { } className={twMerge( "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", - enterprise && !subscription.enterpriseAppConnections ? "border-0 bg-opacity-0" : "" + enterprise && !subscription.enterpriseAppConnections + ? "border-0 bg-opacity-0 opacity-40" + : "" )} > - {enterprise && !subscription.enterpriseAppConnections && ( -
- )} Date: Fri, 23 May 2025 15:01:09 -0700 Subject: [PATCH 24/31] improvement: make log more cloudwatch friendly --- backend/src/services/user/user-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/services/user/user-service.ts b/backend/src/services/user/user-service.ts index 36509962e..aae32d91f 100644 --- a/backend/src/services/user/user-service.ts +++ b/backend/src/services/user/user-service.ts @@ -89,7 +89,7 @@ export const userServiceFactory = ({ username: user.username, isEmailVerified: user.isEmailVerified })), - `Verify email users: ${username}` + `Verify email users: [username=${username}]` ); const user = From 6369d1386253fe2fd377f85f1676fe03a2929ac0 Mon Sep 17 00:00:00 2001 From: x032205 Date: Fri, 23 May 2025 18:47:33 -0400 Subject: [PATCH 25/31] Update docs and some UI to make Admin SSO bypass more clear --- .../documentation/platform/sso/auth0-oidc.mdx | 4 +- .../documentation/platform/sso/auth0-saml.mdx | 28 +++---- docs/documentation/platform/sso/azure.mdx | 10 +-- .../platform/sso/general-oidc/overview.mdx | 2 +- .../platform/sso/google-saml.mdx | 18 ++--- docs/documentation/platform/sso/jumpcloud.mdx | 6 +- .../platform/sso/keycloak-oidc/overview.mdx | 2 +- .../platform/sso/keycloak-saml.mdx | 78 +++++++++---------- docs/documentation/platform/sso/okta.mdx | 6 +- docs/documentation/platform/sso/overview.mdx | 32 +++++--- .../OrgSsoTab/OrgGeneralAuthSection.tsx | 11 ++- .../components/OrgSsoTab/OrgOIDCSection.tsx | 11 ++- 12 files changed, 119 insertions(+), 89 deletions(-) diff --git a/docs/documentation/platform/sso/auth0-oidc.mdx b/docs/documentation/platform/sso/auth0-oidc.mdx index 0665a7b30..4b54c053d 100644 --- a/docs/documentation/platform/sso/auth0-oidc.mdx +++ b/docs/documentation/platform/sso/auth0-oidc.mdx @@ -14,7 +14,7 @@ description: "Learn how to configure Auth0 OIDC for Infisical SSO." 1.1. From the Application's Page, navigate to the settings tab of the Auth0 application you want to integrate with Infisical. ![OIDC auth0 list of applications](../../../images/sso/auth0-oidc/application-settings.png) - + 1.2. In the Application URIs section, set the **Application Login URI** and **Allowed Web Origins** fields to `https://app.infisical.com` and the **Allowed Callback URL** field to `https://app.infisical.com/api/v1/sso/oidc/callback`. ![OIDC auth0 create application uris](../../../images/sso/auth0-oidc/application-uris.png) ![OIDC auth0 create application origin](../../../images/sso/auth0-oidc/application-origin.png) @@ -70,7 +70,7 @@ description: "Learn how to configure Auth0 OIDC for Infisical SSO." prior to enforcing OIDC SSO to prevent any unintended issues. - 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. diff --git a/docs/documentation/platform/sso/auth0-saml.mdx b/docs/documentation/platform/sso/auth0-saml.mdx index 562360ecb..22ef00c89 100644 --- a/docs/documentation/platform/sso/auth0-saml.mdx +++ b/docs/documentation/platform/sso/auth0-saml.mdx @@ -23,30 +23,30 @@ description: "Learn how to configure Auth0 SAML for Infisical SSO." 2.1. In your Auth0 account, head to Applications and create an application. - + ![Auth0 SAML app creation](../../../images/sso/auth0-saml/create-application.png) - + Select **Regular Web Application** and press **Create**. - + ![Auth0 SAML app creation](../../../images/sso/auth0-saml/create-application-2.png) - + 2.2. In the Application head to Settings > Application URIs and add the **Application Callback URL** from step 1 into the **Allowed Callback URLs** field. - + ![Auth0 SAML allowed callback URLs](../../../images/sso/auth0-saml/auth0-config.png) - + 2.3. In the Application head to Addons > SAML2 Web App and copy the **Issuer**, **Identity Provider Login URL**, and **Identity Provider Certificate** from the **Usage** tab. - + ![Auth0 SAML config](../../../images/sso/auth0-saml/auth0-config-2.png) - + 2.4. Back in Infisical, set **Issuer**, **Identity Provider Login URL**, and **Certificate** to the corresponding items from step 2.3. - + ![Auth0 SAML Infisical config](../../../images/sso/auth0-saml/infisical-config.png) - + 2.5. Back in Auth0, in the **Settings** tab, set the **Application Callback URL** to the **Application Callback URL** from step 1 and update the **Settings** field with the JSON under the picture below (replacing `` with the **Audience** from step 1). - + ![Auth0 SAML config](../../../images/sso/auth0-saml/auth0-config-3.png) - + ```json { "audience": "", @@ -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. - 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. @@ -96,4 +96,4 @@ description: "Learn how to configure Auth0 SAML for Infisical SSO." 32`.
- `SITE_URL`: The absolute URL of your self-hosted instance of Infisical including the protocol (e.g. https://app.infisical.com) - \ No newline at end of file + diff --git a/docs/documentation/platform/sso/azure.mdx b/docs/documentation/platform/sso/azure.mdx index 137dc6564..0957dc4d1 100644 --- a/docs/documentation/platform/sso/azure.mdx +++ b/docs/documentation/platform/sso/azure.mdx @@ -5,7 +5,7 @@ description: "Learn how to configure Microsoft Entra ID for Infisical SSO." 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, then you should contact sales@infisical.com to purchase an enterprise license to use it. @@ -26,7 +26,7 @@ description: "Learn how to configure Microsoft Entra ID for Infisical SSO." ![Azure SAML enterprise applications](../../../images/sso/azure/enterprise-applications.png) ![Azure SAML new application](../../../images/sso/azure/new-application.png) - + 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)" option and hit the **Create** button. @@ -89,9 +89,9 @@ description: "Learn how to configure Microsoft Entra ID for Infisical SSO." Back in Azure, navigate to the **Users and groups** tab and select **+ Add user/group** to assign access to the login with SSO application on a user or group-level. - + ![Azure SAML assignment](../../../images/sso/azure/assignment.png) - + 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. - 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. diff --git a/docs/documentation/platform/sso/general-oidc/overview.mdx b/docs/documentation/platform/sso/general-oidc/overview.mdx index 76ac982f8..07ddaaedd 100644 --- a/docs/documentation/platform/sso/general-oidc/overview.mdx +++ b/docs/documentation/platform/sso/general-oidc/overview.mdx @@ -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. - 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. diff --git a/docs/documentation/platform/sso/google-saml.mdx b/docs/documentation/platform/sso/google-saml.mdx index 99223c815..84888b2f9 100644 --- a/docs/documentation/platform/sso/google-saml.mdx +++ b/docs/documentation/platform/sso/google-saml.mdx @@ -24,21 +24,21 @@ description: "Learn how to configure Google SAML for Infisical SSO." 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**. - + ![Google SAML app creation](../../../images/sso/google-saml/create-custom-saml-app.png) - + 2.2. In the **App details** tab, give the application a unique name like Infisical. - + ![Google SAML app naming](../../../images/sso/google-saml/name-custom-saml-app.png) - + 2.3. In the **Google Identity Provider details** tab, copy the **SSO URL**, **Entity ID** and **Certificate**. - + ![Google SAML custom app details](../../../images/sso/google-saml/custom-saml-app-config.png) - + 2.4. Back in Infisical, set **SSO URL** and **Certificate** to the corresponding items from step 2.3. - + ![Google SAML Infisical config](../../../images/sso/google-saml/infisical-config.png) - + 2.5. Back in the Google Admin console, in the **Service provider details** tab, set the **ACS URL** and **Entity ID** to the corresponding items from step 1. 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. - 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. diff --git a/docs/documentation/platform/sso/jumpcloud.mdx b/docs/documentation/platform/sso/jumpcloud.mdx index 0898c0715..1956064d6 100644 --- a/docs/documentation/platform/sso/jumpcloud.mdx +++ b/docs/documentation/platform/sso/jumpcloud.mdx @@ -5,7 +5,7 @@ description: "Learn how to configure JumpCloud SAML for Infisical SSO." 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, then you should contact sales@infisical.com to purchase an enterprise license to use it. @@ -83,13 +83,13 @@ 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; Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO. - + We recommend ensuring that your account is provisioned the application in JumpCloud prior to enforcing SAML SSO to prevent any unintended issues. - 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. diff --git a/docs/documentation/platform/sso/keycloak-oidc/overview.mdx b/docs/documentation/platform/sso/keycloak-oidc/overview.mdx index 06d8dfa43..2c75fc6fe 100644 --- a/docs/documentation/platform/sso/keycloak-oidc/overview.mdx +++ b/docs/documentation/platform/sso/keycloak-oidc/overview.mdx @@ -97,7 +97,7 @@ description: "Learn how to configure Keycloak OIDC for Infisical SSO." prior to enforcing OIDC SSO to prevent any unintended issues. - 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. diff --git a/docs/documentation/platform/sso/keycloak-saml.mdx b/docs/documentation/platform/sso/keycloak-saml.mdx index ba6aa0c3a..9336f1080 100644 --- a/docs/documentation/platform/sso/keycloak-saml.mdx +++ b/docs/documentation/platform/sso/keycloak-saml.mdx @@ -5,7 +5,7 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO." 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, then you should contact sales@infisical.com to purchase an enterprise license to use it. @@ -13,36 +13,36 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO." In Infisical, head to the **Single Sign-On (SSO)** page and select the **General** tab. Click **Connect** for **SAML** under the Connect to an Identity Provider section. Select **Keycloak**, then click **Connect** again. - + ![SSO connect section](../../../images/sso/connect-saml.png) - + Next, copy the **Valid redirect URI** and **SP Entity ID** to use when configuring the Keycloak SAML application. - + ![Keycloak SAML initial configuration](../../../images/sso/keycloak/init-config.png) 2.1. In your realm, navigate to the **Clients** tab and click **Create client** to create a new client application. - + ![SAML keycloak list of clients](../../../images/sso/keycloak/clients-list.png) - + You don’t typically need to make a realm dedicated to Infisical. We recommend adding Infisical as a client to your primary realm. - + In the General Settings step, set **Client type** to **SAML**, the **Client ID** field to `https://app.infisical.com`, and the **Name** field to a friendly name like **Infisical**. - + ![SAML keycloak create client general settings](../../../images/sso/keycloak/create-client-general-settings.png) - + If you’re self-hosting Infisical, then you will want to replace https://app.infisical.com with your own domain. - + Next, in the Login Settings step, set both the **Home URL** field and **Valid redirect URIs** field to the **Valid redirect URI** from step 1 and press **Save**. - + ![SAML keycloak create client login settings](../../../images/sso/keycloak/create-client-login-settings.png) - + 2.2. Once you've created the client, under its **Settings** tab, make sure to set the following values: - + - Under **SAML Capabilities**: - Name ID format: email (or username). - Force name ID format: On. @@ -54,59 +54,59 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO." - Signature algorithm: RSA_SHA256. ![SAML keycloak client SAML capabilities](../../../images/sso/keycloak/client-saml-capabilities.png) - + ![SAML keycloak client signature encryption](../../../images/sso/keycloak/client-signature-encryption.png) - + 2.3. Next, navigate to the **Client scopes** tab select the client's dedicated scope. - + ![SAML keycloak client scopes list](../../../images/sso/keycloak/client-scopes-list.png) - + Next click **Add predefined mapper**. - + ![SAML keycloak client mappers empty](../../../images/sso/keycloak/client-mappers-empty.png) - + Select the **X500 email**, **X500 givenName**, and **X500 surname** attributes and click **Add**. - + ![SAML keycloak client mappers predefined](../../../images/sso/keycloak/client-mappers-predefined.png) - - Now click on the **X500 email** mapper and set the **SAML Attribute Name** field to **email**. + + Now click on the **X500 email** mapper and set the **SAML Attribute Name** field to **email**. ![SAML keycloak client mappers email](../../../images/sso/keycloak/client-mappers-email.png) - + 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**. - + ![SAML keycloak client mappers by configuration](../../../images/sso/keycloak/client-mappers-by-configuration.png) - + Select **User Property**. - + ![SAML keycloak client mappers user property](../../../images/sso/keycloak/client-mappers-user-property.png) Set the the **Name** field to **Username**, the **Property** field to **username**, and the **SAML Attribtue Name** to **username**. - + ![SAML keycloak client mappers username](../../../images/sso/keycloak/client-mappers-username.png) - + Repeat the same for the `id` attribute, setting the **Name** field to **ID**, the **Property** field to **id**, and the **SAML Attribute Name** to **id**. - + ![SAML keycloak client mappers id](../../../images/sso/keycloak/client-mappers-id.png) - + Once you've completed the above steps, the list of mappers should look like this: - + ![SAML keycloak client mappers completed](../../../images/sso/keycloak/client-mappers-completed.png) Back in Keycloak, navigate to Configure > Realm settings > General tab > Endpoints > SAML 2.0 Identity Provider Metadata and copy the IDP URL. This should appear in various places and take the form: `https://keycloak-mysite.com/realms/myrealm/protocol/saml`. - + ![SAML keycloak realm SAML metadata](../../../images/sso/keycloak/realm-saml-metadata.png) - + Also, in the **Keys** tab, locate the RS256 key and copy the certificate to use when finishing configuring Keycloak SAML in Infisical. - + ![SAML keycloak realm settings keys](../../../images/sso/keycloak/realm-settings-keys.png) 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. ![SAML Okta paste values into Infisical](../../../images/sso/keycloak/idp-values.png) @@ -119,7 +119,7 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO." Enforcing SAML SSO ensures that members in your organization can only access Infisical 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; 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. - 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. @@ -147,4 +147,4 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO." 32`.
- `SITE_URL`: The absolute URL of your self-hosted instance of Infisical including the protocol (e.g. https://app.infisical.com) - \ No newline at end of file + diff --git a/docs/documentation/platform/sso/okta.mdx b/docs/documentation/platform/sso/okta.mdx index 2af689e4c..f7dc90c08 100644 --- a/docs/documentation/platform/sso/okta.mdx +++ b/docs/documentation/platform/sso/okta.mdx @@ -96,10 +96,10 @@ description: "Learn how to configure Okta SAML 2.0 for Infisical SSO." We recommend ensuring that your account is provisioned the application in Okta prior to enforcing SAML SSO to prevent any unintended issues. - - 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. + diff --git a/docs/documentation/platform/sso/overview.mdx b/docs/documentation/platform/sso/overview.mdx index e5d5e5c16..53cb5dd4e 100644 --- a/docs/documentation/platform/sso/overview.mdx +++ b/docs/documentation/platform/sso/overview.mdx @@ -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. +## Admin Login Portal + +Organization Admins can utilize the Admin Login Portal to bypass SSO enforcement in case of an emergency. + +This portal is accessible at `/login/admin` (e.g., https://app.infisical.com/login/admin). + + + This bypass functionality is exclusively available to **Organization Admins**. **Server Admins** are not permitted to use this feature. + + ## FAQ - - By default, Infisical Cloud is configured to not trust emails from external - identity providers to prevent any malicious account takeover attempts via - email spoofing. Accordingly, Infisical creates a new user for anyone provisioned - through an external identity provider and requires an additional email - verification step upon their first login. + + By default, Infisical Cloud is configured to not trust emails from external + identity providers to prevent any malicious account takeover attempts via + email spoofing. Accordingly, Infisical creates a new user for anyone provisioned + through an external identity provider and requires an additional email + 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, - you can configure this behavior in the Server Admin Console. - - + 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 are likely being redirected because you're not using your username and password, or you're not an **Organization Admin**. This portal requires **Organization Admin** status and direct credential login (username and password). **Server Admin** status alone is insufficient. + diff --git a/frontend/src/pages/organization/SsoPage/components/OrgSsoTab/OrgGeneralAuthSection.tsx b/frontend/src/pages/organization/SsoPage/components/OrgSsoTab/OrgGeneralAuthSection.tsx index 21c440957..ac8685192 100644 --- a/frontend/src/pages/organization/SsoPage/components/OrgSsoTab/OrgGeneralAuthSection.tsx +++ b/frontend/src/pages/organization/SsoPage/components/OrgSsoTab/OrgGeneralAuthSection.tsx @@ -129,7 +129,16 @@ export const OrgGeneralAuthSection = () => { level.

- In case of a lockout, admins can use the admin login portal at{" "} + In case of a lockout, admins can use the{" "} + + Admin Login Portal + {" "} + at{" "} { level.

- In case of a lockout, admins can use the admin login portal at{" "} + In case of a lockout, admins can use the{" "} + + Admin Login Portal + {" "} + at{" "} Date: Fri, 23 May 2025 20:18:05 -0400 Subject: [PATCH 26/31] UI tweaks --- .../secret-syncs/SecretSyncSelect.tsx | 49 +++++++----------- .../components/AppConnectionList.tsx | 51 +++++++------------ 2 files changed, 36 insertions(+), 64 deletions(-) diff --git a/frontend/src/components/secret-syncs/SecretSyncSelect.tsx b/frontend/src/components/secret-syncs/SecretSyncSelect.tsx index 24975cca2..62d99544f 100644 --- a/frontend/src/components/secret-syncs/SecretSyncSelect.tsx +++ b/frontend/src/components/secret-syncs/SecretSyncSelect.tsx @@ -1,6 +1,5 @@ import { faWrench } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { twMerge } from "tailwind-merge"; import { Spinner, Tooltip } from "@app/components/v2"; import { useSubscription } from "@app/context"; @@ -34,38 +33,26 @@ export const SecretSyncSelect = ({ onSelect }: Props) => { {secretSyncOptions?.map(({ destination, enterprise }) => { const { image, name } = SECRET_SYNC_MAP[destination]; return ( - + enterprise && !subscription.enterpriseSecretSyncs + ? 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" > - - + {`${name} +

+ ); })} { const { image, name, size = 50, enterprise = false } = APP_CONNECTION_MAP[option.app]; return ( - enterprise && !subscription.enterpriseAppConnections - ? "Enterprise Plan Only" - : undefined + ? 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" > - - + {`${name} +
+ {name} +
+ ); })} Date: Fri, 23 May 2025 20:29:34 -0400 Subject: [PATCH 27/31] Small out-of-scope greptile fixes --- docs/documentation/platform/sso/jumpcloud.mdx | 3 +-- docs/documentation/platform/sso/keycloak-saml.mdx | 2 +- docs/documentation/platform/sso/okta.mdx | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/documentation/platform/sso/jumpcloud.mdx b/docs/documentation/platform/sso/jumpcloud.mdx index 1956064d6..3cad22247 100644 --- a/docs/documentation/platform/sso/jumpcloud.mdx +++ b/docs/documentation/platform/sso/jumpcloud.mdx @@ -85,8 +85,7 @@ description: "Learn how to configure JumpCloud SAML for Infisical SSO." Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO. - We recommend ensuring that your account is provisioned the application in JumpCloud - prior to enforcing SAML SSO to prevent any unintended issues. + We recommend ensuring that your account is provisioned in the application in JumpCloud prior to enforcing SAML SSO to prevent any unintended issues. 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. diff --git a/docs/documentation/platform/sso/keycloak-saml.mdx b/docs/documentation/platform/sso/keycloak-saml.mdx index 9336f1080..daca360b4 100644 --- a/docs/documentation/platform/sso/keycloak-saml.mdx +++ b/docs/documentation/platform/sso/keycloak-saml.mdx @@ -83,7 +83,7 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO." ![SAML keycloak client mappers user property](../../../images/sso/keycloak/client-mappers-user-property.png) - Set the the **Name** field to **Username**, the **Property** field to **username**, and the **SAML Attribtue Name** to **username**. + Set the the **Name** field to **Username**, the **Property** field to **username**, and the **SAML Attribute Name** to **username**. ![SAML keycloak client mappers username](../../../images/sso/keycloak/client-mappers-username.png) diff --git a/docs/documentation/platform/sso/okta.mdx b/docs/documentation/platform/sso/okta.mdx index f7dc90c08..ecdf6ca39 100644 --- a/docs/documentation/platform/sso/okta.mdx +++ b/docs/documentation/platform/sso/okta.mdx @@ -93,8 +93,7 @@ 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. - We recommend ensuring that your account is provisioned the application in Okta - prior to enforcing SAML SSO to prevent any unintended issues. + We recommend ensuring that your account is provisioned for the application in Okta prior to enforcing SAML SSO to prevent any unintended issues. From bb276a0dbafc5cf35326e195b9fa740a9170cf99 Mon Sep 17 00:00:00 2001 From: x032205 Date: Sat, 24 May 2025 01:25:49 -0400 Subject: [PATCH 28/31] review fixes --- docs/documentation/platform/sso/overview.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/documentation/platform/sso/overview.mdx b/docs/documentation/platform/sso/overview.mdx index 53cb5dd4e..ee284063c 100644 --- a/docs/documentation/platform/sso/overview.mdx +++ b/docs/documentation/platform/sso/overview.mdx @@ -39,14 +39,14 @@ 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. -## Admin Login Portal +## SSO Break Glass -Organization Admins can utilize the Admin Login Portal to bypass SSO enforcement in case of an emergency. +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). - This bypass functionality is exclusively available to **Organization Admins**. **Server Admins** are not permitted to use this feature. + 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. ## FAQ @@ -63,6 +63,6 @@ This portal is accessible at `/login/admin` (e.g., https://app.infisical.com/log you can configure this behavior in the Server Admin Console. - You are likely being redirected because you're not using your username and password, or you're not an **Organization Admin**. This portal requires **Organization Admin** status and direct credential login (username and password). **Server Admin** status alone is insufficient. + You are likely being redirected because you're not using your email and password, 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. From eb4e72792297b16928e0b5810feb20c028386349 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Sat, 24 May 2025 01:29:38 -0400 Subject: [PATCH 29/31] Update overview.mdx --- docs/documentation/platform/sso/overview.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/documentation/platform/sso/overview.mdx b/docs/documentation/platform/sso/overview.mdx index ee284063c..66243f7d8 100644 --- a/docs/documentation/platform/sso/overview.mdx +++ b/docs/documentation/platform/sso/overview.mdx @@ -63,6 +63,6 @@ This portal is accessible at `/login/admin` (e.g., https://app.infisical.com/log you can configure this behavior in the Server Admin Console. - You are likely being redirected because you're not using your email and password, 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. + 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. From 982b506eb85003e5a4e852824bd6821515aaf762 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 25 May 2025 19:48:59 +0530 Subject: [PATCH 30/31] feat: small patch on license --- .../src/ee/services/license/license-service.ts | 7 +++++++ .../src/ee/services/license/license-types.ts | 2 +- backend/src/services/project/project-dal.ts | 18 +++++++++++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/backend/src/ee/services/license/license-service.ts b/backend/src/ee/services/license/license-service.ts index f5b1f96ec..e9a2ada8b 100644 --- a/backend/src/ee/services/license/license-service.ts +++ b/backend/src/ee/services/license/license-service.ts @@ -92,6 +92,10 @@ export const licenseServiceFactory = ({ const { data: { currentPlan } } = await licenseServerOnPremApi.request.get<{ currentPlan: TFeatureSet }>("/api/license/v1/plan"); + + const workspacesUsed = await projectDAL.countOfOrgProjects(null); + currentPlan.workspacesUsed = workspacesUsed; + onPremFeatures = currentPlan; logger.info("Successfully synchronized license key features"); } catch (error) { @@ -185,6 +189,9 @@ export const licenseServiceFactory = ({ } = await licenseServerCloudApi.request.get<{ currentPlan: TFeatureSet }>( `/api/license-server/v1/customers/${org.customerId}/cloud-plan` ); + const workspacesUsed = await projectDAL.countOfOrgProjects(orgId); + currentPlan.workspacesUsed = workspacesUsed; + await keyStore.setItemWithExpiry( FEATURE_CACHE_KEY(org.id), LICENSE_SERVER_CLOUD_PLAN_TTL, diff --git a/backend/src/ee/services/license/license-types.ts b/backend/src/ee/services/license/license-types.ts index 9511771ff..f509c7127 100644 --- a/backend/src/ee/services/license/license-types.ts +++ b/backend/src/ee/services/license/license-types.ts @@ -27,7 +27,7 @@ export type TFeatureSet = { slug: null; tier: -1; workspaceLimit: null; - workspacesUsed: 0; + workspacesUsed: number; dynamicSecret: false; memberLimit: null; membersUsed: number; diff --git a/backend/src/services/project/project-dal.ts b/backend/src/services/project/project-dal.ts index 43f1d57e4..b47adc4c7 100644 --- a/backend/src/services/project/project-dal.ts +++ b/backend/src/services/project/project-dal.ts @@ -425,6 +425,21 @@ export const projectDALFactory = (db: TDbClient) => { 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); + } catch (error) { + throw new DatabaseError({ error, name: "Count of Org Projects" }); + } + }; + return { ...projectOrm, findUserProjects, @@ -437,6 +452,7 @@ export const projectDALFactory = (db: TDbClient) => { findProjectWithOrg, checkProjectUpgradeStatus, getProjectFromSplitId, - searchProjects + searchProjects, + countOfOrgProjects }; }; From 782bf2cdc92f3aa790c3e7626d38c63d06dc1875 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 25 May 2025 22:35:16 +0530 Subject: [PATCH 31/31] feat: resolved count fallback --- backend/src/ee/services/license/license-dal.ts | 2 +- backend/src/services/project/project-dal.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/ee/services/license/license-dal.ts b/backend/src/ee/services/license/license-dal.ts index cab428e86..88a2dadf6 100644 --- a/backend/src/ee/services/license/license-dal.ts +++ b/backend/src/ee/services/license/license-dal.ts @@ -19,7 +19,7 @@ export const licenseDALFactory = (db: TDbClient) => { .join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`) .where(`${TableName.Users}.isGhost`, false) .count(); - return Number(doc?.[0].count); + return Number(doc?.[0]?.count ?? 0); } catch (error) { throw new DatabaseError({ error, name: "Count of Org Members" }); } diff --git a/backend/src/services/project/project-dal.ts b/backend/src/services/project/project-dal.ts index b47adc4c7..bdcee1e61 100644 --- a/backend/src/services/project/project-dal.ts +++ b/backend/src/services/project/project-dal.ts @@ -434,7 +434,7 @@ export const projectDALFactory = (db: TDbClient) => { } }) .count(); - return Number(doc?.[0].count); + return Number(doc?.[0]?.count ?? 0); } catch (error) { throw new DatabaseError({ error, name: "Count of Org Projects" }); }