This commit is contained in:
x032205
2025-05-26 10:39:51 -04:00
88 changed files with 1101 additions and 259 deletions
+1
View File
@@ -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?: {
@@ -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({
@@ -2,11 +2,10 @@ import {
CreateOCIVaultSyncSchema,
OCIVaultSyncSchema,
UpdateOCIVaultSyncSchema
} from "@app/services/secret-sync/oci-vault";
} from "@app/ee/services/secret-sync/oci-vault";
import { registerSyncSecretsEndpoints } from "@app/server/routes/v1/secret-sync-routers/secret-sync-endpoints";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
export const registerOCIVaultSyncRouter = async (server: FastifyZodProvider) =>
registerSyncSecretsEndpoints({
destination: SecretSync.OCIVault,
@@ -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<TLicenseServiceFactory, "getPlan">, orgId: string) => {
const plan = await licenseService.getPlan(orgId);
if (!plan.enterpriseAppConnections)
throw new BadRequestError({
message:
"Failed to use app connection due to plan restriction. Upgrade plan to access enterprise app connections."
});
};
export const ociConnectionService = (
getAppConnection: TGetAppConnectionFunc,
licenseService: Pick<TLicenseServiceFactory, "getPlan">
) => {
const listCompartments = async (connectionId: string, actor: OrgServiceActor) => {
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 {
@@ -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,
@@ -1,3 +1,4 @@
import { ProjectType } from "@app/db/schemas";
import {
TCreateProjectTemplateDTO,
TUpdateProjectTemplateDTO
@@ -315,7 +316,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 +375,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 +2457,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 +2911,59 @@ interface MicrosoftTeamsWorkflowIntegrationUpdateEvent {
};
}
interface OrgUpdateEvent {
type: EventType.UPDATE_ORG;
metadata: {
name?: string;
slug?: string;
authEnforced?: boolean;
scimEnabled?: boolean;
defaultMembershipRoleSlug?: string;
enforceMfa?: boolean;
selectedMfaMethod?: string;
allowSecretSharingOutsideOrganization?: boolean;
bypassOrgAuthEnabled?: boolean;
userTokenExpiration?: string;
secretsProductEnabled?: boolean;
pkiProductEnabled?: boolean;
kmsProductEnabled?: boolean;
sshProductEnabled?: boolean;
scannerProductEnabled?: boolean;
shareSecretsProductEnabled?: boolean;
};
}
interface ProjectCreateEvent {
type: EventType.CREATE_PROJECT;
metadata: {
name: string;
slug?: string;
type: ProjectType;
};
}
interface ProjectUpdateEvent {
type: EventType.UPDATE_PROJECT;
metadata: {
name?: string;
description?: string;
autoCapitalization?: boolean;
hasDeleteProtection?: boolean;
slug?: string;
secretSharing?: boolean;
pitVersionLimit?: number;
auditLogsRetentionDays?: number;
};
}
interface ProjectDeleteEvent {
type: EventType.DELETE_PROJECT;
metadata: {
id: string;
name: string;
};
}
export type Event =
| GetSecretsEvent
| GetSecretEvent
@@ -3117,7 +3168,6 @@ export type Event =
| CreateProjectTemplateEvent
| UpdateProjectTemplateEvent
| DeleteProjectTemplateEvent
| ApplyProjectTemplateEvent
| GetAppConnectionsEvent
| GetAvailableAppConnectionsDetailsEvent
| GetAppConnectionEvent
@@ -3179,4 +3229,8 @@ export type Event =
| MicrosoftTeamsWorkflowIntegrationGetTeamsEvent
| MicrosoftTeamsWorkflowIntegrationGetEvent
| MicrosoftTeamsWorkflowIntegrationListEvent
| MicrosoftTeamsWorkflowIntegrationUpdateEvent;
| MicrosoftTeamsWorkflowIntegrationUpdateEvent
| OrgUpdateEvent
| ProjectCreateEvent
| ProjectUpdateEvent
| ProjectDeleteEvent;
@@ -29,7 +29,9 @@ export const getDefaultOnPremFeatures = () => {
secretApproval: true,
secretRotation: true,
caCrl: false,
sshHostGroups: false
sshHostGroups: false,
enterpriseSecretSyncs: false,
enterpriseAppConnections: false
};
};
@@ -19,7 +19,7 @@ export const licenseDALFactory = (db: TDbClient) => {
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
.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" });
}
@@ -55,7 +55,9 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
projectTemplates: false,
kmip: false,
gateway: false,
sshHostGroups: false
sshHostGroups: false,
enterpriseSecretSyncs: false,
enterpriseAppConnections: false
});
export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
@@ -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,
@@ -27,7 +27,7 @@ export type TFeatureSet = {
slug: null;
tier: -1;
workspaceLimit: null;
workspacesUsed: 0;
workspacesUsed: number;
dynamicSecret: false;
memberLimit: null;
membersUsed: number;
@@ -72,6 +72,8 @@ export type TFeatureSet = {
kmip: false;
gateway: false;
sshHostGroups: false;
enterpriseSecretSyncs: false;
enterpriseAppConnections: false;
};
export type TOrgPlansTableDTO = {
@@ -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
};
@@ -1,7 +1,6 @@
import { secrets, vault } from "oci-sdk";
import { delay } from "@app/lib/delay";
import { getOCIProvider } from "@app/services/app-connection/oci";
import { getOCIProvider } from "@app/ee/services/app-connections/oci";
import {
TCreateOCIVaultVariable,
TDeleteOCIVaultVariable,
@@ -9,7 +8,8 @@ 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 { 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";
@@ -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()
});
@@ -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";
+15 -3
View File
@@ -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("Base64 encoded custom CA certificate PEM(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 = Buffer.from(envCfg.SMTP_CUSTOM_CA_CERT, "base64").toString("utf-8");
}
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
};
};
+13 -4
View File
@@ -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;
@@ -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,
@@ -157,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,
@@ -181,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;
}
+6 -3
View File
@@ -1014,7 +1014,8 @@ export const registerRoutes = async (
secretVersionV2BridgeDAL,
secretVersionTagV2BridgeDAL,
resourceMetadataDAL,
appConnectionDAL
appConnectionDAL,
licenseService
});
const secretQueueService = secretQueueFactory({
@@ -1631,7 +1632,8 @@ export const registerRoutes = async (
const appConnectionService = appConnectionServiceFactory({
appConnectionDAL,
permissionService,
kmsService
kmsService,
licenseService
});
const secretSyncService = secretSyncServiceFactory({
@@ -1642,7 +1644,8 @@ export const registerRoutes = async (
folderDAL,
secretSyncQueue,
projectBotService,
keyStore
keyStore,
licenseService
});
const kmipService = kmipServiceFactory({
@@ -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";
@@ -42,7 +43,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
@@ -1,3 +1,4 @@
import { registerOCIConnectionRouter } from "@app/ee/routes/v1/app-connection-routers/oci-connection-router";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { registerOnePassConnectionRouter } from "./1password-connection-router";
@@ -14,7 +15,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";
@@ -312,8 +312,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
};
}
@@ -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: workspace
}
});
return { workspace };
}
});
@@ -297,6 +308,17 @@ 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: req.body
}
});
return {
message: "Successfully changed workspace name",
workspace
@@ -375,6 +397,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 +444,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 +492,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 +541,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 +589,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
@@ -1,3 +1,4 @@
import { registerOCIVaultSyncRouter } from "@app/ee/routes/v1/secret-sync-routers/oci-vault-sync-router";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { registerOnePassSyncRouter } from "./1password-sync-router";
@@ -11,7 +12,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";
@@ -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";
@@ -25,7 +26,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";
+31 -12
View File
@@ -206,19 +206,18 @@ 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,
name: req.body.projectName
}
});
}
}
});
return { project };
}
@@ -262,6 +261,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: project
}
});
return project;
}
});
@@ -341,6 +350,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;
}
});
@@ -67,3 +67,8 @@ export enum AWSRegion {
// South America
SA_EAST_1 = "sa-east-1" // Sao Paulo
}
export enum AppConnectionPlanType {
Enterprise = "enterprise",
Regular = "regular"
}
@@ -1,7 +1,13 @@
import { TAppConnections } from "@app/db/schemas/app-connections";
import {
getOCIConnectionListItem,
OCIConnectionMethod,
validateOCIConnectionCredentials
} from "@app/ee/services/app-connections/oci";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { generateHash } from "@app/lib/crypto/encryption";
import { 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 +19,7 @@ import {
OnePassConnectionMethod,
validateOnePassConnectionCredentials
} from "./1password";
import { AppConnection } from "./app-connection-enums";
// import { AppConnection, AppConnectionPlanType } from "./app-connection-enums";
import { TAppConnectionServiceFactoryDep } from "./app-connection-service";
import {
TAppConnection,
@@ -58,7 +64,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,
@@ -266,3 +271,18 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
[AppConnection.OCI]: platformManagedCredentialsNotSupported,
[AppConnection.OnePass]: platformManagedCredentialsNotSupported
};
export const enterpriseAppCheck = async (
licenseService: Pick<TLicenseServiceFactory, "getPlan">,
appConnection: AppConnection,
orgId: string,
errorMessage: string
) => {
if (APP_CONNECTION_PLAN_MAP[appConnection] === AppConnectionPlanType.Enterprise) {
const plan = await licenseService.getPlan(orgId);
if (!plan.enterpriseAppConnections)
throw new BadRequestError({
message: errorMessage
});
}
};
@@ -1,4 +1,4 @@
import { AppConnection } from "./app-connection-enums";
import { AppConnection, AppConnectionPlanType } from "./app-connection-enums";
export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.AWS]: "AWS",
@@ -22,3 +22,25 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.OCI]: "OCI",
[AppConnection.OnePass]: "1Password"
};
export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanType> = {
[AppConnection.AWS]: AppConnectionPlanType.Regular,
[AppConnection.GitHub]: AppConnectionPlanType.Regular,
[AppConnection.GCP]: AppConnectionPlanType.Regular,
[AppConnection.AzureKeyVault]: AppConnectionPlanType.Regular,
[AppConnection.AzureAppConfiguration]: AppConnectionPlanType.Regular,
[AppConnection.AzureClientSecrets]: AppConnectionPlanType.Regular,
[AppConnection.Databricks]: AppConnectionPlanType.Regular,
[AppConnection.Humanitec]: AppConnectionPlanType.Regular,
[AppConnection.TerraformCloud]: AppConnectionPlanType.Regular,
[AppConnection.Vercel]: AppConnectionPlanType.Regular,
[AppConnection.Postgres]: AppConnectionPlanType.Regular,
[AppConnection.MsSql]: AppConnectionPlanType.Regular,
[AppConnection.Camunda]: AppConnectionPlanType.Regular,
[AppConnection.Windmill]: AppConnectionPlanType.Regular,
[AppConnection.Auth0]: AppConnectionPlanType.Regular,
[AppConnection.HCVault]: AppConnectionPlanType.Regular,
[AppConnection.LDAP]: AppConnectionPlanType.Regular,
[AppConnection.TeamCity]: AppConnectionPlanType.Regular,
[AppConnection.OCI]: AppConnectionPlanType.Enterprise
};
@@ -1,5 +1,8 @@
import { ForbiddenError, subject } from "@casl/ability";
import { ValidateOCIConnectionCredentialsSchema } from "@app/ee/services/app-connections/oci";
import { ociConnectionService } from "@app/ee/services/app-connections/oci/oci-connection-service";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionAppConnectionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { generateHash } from "@app/lib/crypto/encryption";
@@ -9,6 +12,7 @@ import { DiscriminativePick, OrgServiceActor } from "@app/lib/types";
import {
decryptAppConnection,
encryptAppConnectionCredentials,
enterpriseAppCheck,
getAppConnectionMethodName,
listAppConnectionOptions,
TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM,
@@ -51,8 +55,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";
@@ -67,6 +69,7 @@ export type TAppConnectionServiceFactoryDep = {
appConnectionDAL: TAppConnectionDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
};
export type TAppConnectionServiceFactory = ReturnType<typeof appConnectionServiceFactory>;
@@ -97,7 +100,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
export const appConnectionServiceFactory = ({
appConnectionDAL,
permissionService,
kmsService
kmsService,
licenseService
}: TAppConnectionServiceFactoryDep) => {
const listAppConnectionsByOrg = async (actor: OrgServiceActor, app?: AppConnection) => {
const { permission } = await permissionService.getOrgPermission(
@@ -194,6 +198,13 @@ export const appConnectionServiceFactory = ({
OrgPermissionSubjects.AppConnections
);
await enterpriseAppCheck(
licenseService,
app,
actor.orgId,
"Failed to create app connection due to plan restriction. Upgrade plan to access enterprise app connections."
);
const validatedCredentials = await validateAppConnectionCredentials({
app,
credentials,
@@ -256,6 +267,13 @@ export const appConnectionServiceFactory = ({
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
await enterpriseAppCheck(
licenseService,
appConnection.app as AppConnection,
actor.orgId,
"Failed to update app connection due to plan restriction. Upgrade plan to access enterprise app connections."
);
const { permission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
@@ -402,6 +420,13 @@ export const appConnectionServiceFactory = ({
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
await enterpriseAppCheck(
licenseService,
app,
actor.orgId,
"Failed to connect app due to plan restriction. Upgrade plan to access enterprise app connections."
);
const { permission: orgPermission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
@@ -471,7 +496,7 @@ export const appConnectionServiceFactory = ({
hcvault: hcVaultConnectionService(connectAppConnectionById),
windmill: windmillConnectionService(connectAppConnectionById),
teamcity: teamcityConnectionService(connectAppConnectionById),
oci: ociConnectionService(connectAppConnectionById),
oci: ociConnectionService(connectAppConnectionById, licenseService),
onepass: onePassConnectionService(connectAppConnectionById)
};
};
@@ -1,3 +1,9 @@
import {
TOCIConnection,
TOCIConnectionConfig,
TOCIConnectionInput,
TValidateOCIConnectionCredentialsSchema
} from "@app/ee/services/app-connections/oci";
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
import { TSqlConnectionConfig } from "@app/services/app-connection/shared/sql/sql-connection-types";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
@@ -82,12 +88,6 @@ import {
TValidateLdapConnectionCredentialsSchema
} from "./ldap";
import { TMsSqlConnection, TMsSqlConnectionInput, TValidateMsSqlConnectionCredentialsSchema } from "./mssql";
import {
TOCIConnection,
TOCIConnectionConfig,
TOCIConnectionInput,
TValidateOCIConnectionCredentialsSchema
} from "./oci";
import {
TPostgresConnection,
TPostgresConnectionInput,
+17 -1
View File
@@ -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 ?? 0);
} 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
};
};
@@ -27,3 +27,8 @@ export enum SecretSyncImportBehavior {
PrioritizeSource = "prioritize-source",
PrioritizeDestination = "prioritize-destination"
}
export enum SecretSyncPlanType {
Enterprise = "enterprise",
Regular = "regular"
}
@@ -1,6 +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
@@ -11,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,7 +33,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 { OCI_VAULT_SYNC_LIST_OPTION, OCIVaultSyncFns } from "./oci-vault";
import { SECRET_SYNC_PLAN_MAP } from "./secret-sync-maps";
import { TEAMCITY_SYNC_LIST_OPTION, TeamCitySyncFns } from "./teamcity";
import { TERRAFORM_CLOUD_SYNC_LIST_OPTION, TerraformCloudSyncFns } from "./terraform-cloud";
import { VERCEL_SYNC_LIST_OPTION, VercelSyncFns } from "./vercel";
@@ -336,3 +339,18 @@ export const parseSyncErrorMessage = (err: unknown): string => {
? errorMessage
: `${errorMessage.substring(0, MAX_MESSAGE_LENGTH - 3)}...`;
};
export const enterpriseSyncCheck = async (
licenseService: Pick<TLicenseServiceFactory, "getPlan">,
secretSync: SecretSync,
orgId: string,
errorMessage: string
) => {
if (SECRET_SYNC_PLAN_MAP[secretSync] === SecretSyncPlanType.Enterprise) {
const plan = await licenseService.getPlan(orgId);
if (!plan.enterpriseSecretSyncs)
throw new BadRequestError({
message: errorMessage
});
}
};
@@ -1,5 +1,5 @@
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { SecretSync, SecretSyncPlanType } from "@app/services/secret-sync/secret-sync-enums";
export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
[SecretSync.AWSParameterStore]: "AWS Parameter Store",
@@ -38,3 +38,21 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
[SecretSync.OCIVault]: AppConnection.OCI,
[SecretSync.OnePass]: AppConnection.OnePass
};
export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
[SecretSync.AWSParameterStore]: SecretSyncPlanType.Regular,
[SecretSync.AWSSecretsManager]: SecretSyncPlanType.Regular,
[SecretSync.GitHub]: SecretSyncPlanType.Regular,
[SecretSync.GCPSecretManager]: SecretSyncPlanType.Regular,
[SecretSync.AzureKeyVault]: SecretSyncPlanType.Regular,
[SecretSync.AzureAppConfiguration]: SecretSyncPlanType.Regular,
[SecretSync.Databricks]: SecretSyncPlanType.Regular,
[SecretSync.Humanitec]: SecretSyncPlanType.Regular,
[SecretSync.TerraformCloud]: SecretSyncPlanType.Regular,
[SecretSync.Camunda]: SecretSyncPlanType.Regular,
[SecretSync.Vercel]: SecretSyncPlanType.Regular,
[SecretSync.Windmill]: SecretSyncPlanType.Regular,
[SecretSync.HCVault]: SecretSyncPlanType.Regular,
[SecretSync.TeamCity]: SecretSyncPlanType.Regular,
[SecretSync.OCIVault]: SecretSyncPlanType.Enterprise
};
@@ -5,6 +5,7 @@ import { Job } from "bullmq";
import { ProjectMembershipRole, SecretType } from "@app/db/schemas";
import { 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 { logger } from "@app/lib/logger";
@@ -32,7 +33,7 @@ import {
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 { 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,
@@ -93,6 +94,7 @@ type TSecretSyncQueueFactoryDep = {
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
};
type SecretSyncActionJob = Job<
@@ -133,7 +135,8 @@ export const secretSyncQueueFactory = ({
secretVersionTagDAL,
secretVersionV2BridgeDAL,
secretVersionTagV2BridgeDAL,
resourceMetadataDAL
resourceMetadataDAL,
licenseService
}: TSecretSyncQueueFactoryDep) => {
const appCfg = getConfig();
@@ -323,7 +326,20 @@ export const secretSyncQueueFactory = ({
secretSync: TSecretSyncWithCredentials,
importBehavior: SecretSyncImportBehavior
): Promise<TSecretMap> => {
const { projectId, environment, folder } = secretSync;
const {
projectId,
environment,
folder,
destination,
connection: { orgId }
} = secretSync;
await enterpriseSyncCheck(
licenseService,
destination,
orgId,
"Failed to import secrets due to plan restriction. Upgrade plan to access enterprise secret syncs."
);
if (!environment || !folder)
throw new Error(
@@ -400,6 +416,13 @@ export const secretSyncQueueFactory = ({
if (!secretSync) throw new Error(`Cannot find secret sync with ID ${syncId}`);
await enterpriseSyncCheck(
licenseService,
secretSync.destination as SecretSync,
secretSync.connection.orgId,
"Failed to sync secrets due to plan restriction. Upgrade plan to access enterprise secret syncs."
);
await secretSyncDAL.updateById(syncId, {
syncStatus: SecretSyncStatus.Running
});
@@ -659,6 +682,13 @@ export const secretSyncQueueFactory = ({
if (!secretSync) throw new Error(`Cannot find secret sync with ID ${syncId}`);
await enterpriseSyncCheck(
licenseService,
secretSync.destination as SecretSync,
secretSync.connection.orgId,
"Failed to remove secrets due to plan restriction. Upgrade plan to access enterprise secret syncs."
);
await secretSyncDAL.updateById(syncId, {
removeStatus: SecretSyncStatus.Running
});
@@ -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 {
@@ -16,7 +17,7 @@ import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-c
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 { listSecretSyncOptions } from "@app/services/secret-sync/secret-sync-fns";
import { enterpriseSyncCheck, listSecretSyncOptions } from "@app/services/secret-sync/secret-sync-fns";
import {
SecretSyncStatus,
TCreateSecretSyncDTO,
@@ -49,6 +50,7 @@ type TSecretSyncServiceFactoryDep = {
TSecretSyncQueueFactory,
"queueSecretSyncSyncSecretsById" | "queueSecretSyncImportSecretsById" | "queueSecretSyncRemoveSecretsById"
>;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
};
export type TSecretSyncServiceFactory = ReturnType<typeof secretSyncServiceFactory>;
@@ -61,7 +63,8 @@ export const secretSyncServiceFactory = ({
appConnectionService,
projectBotService,
secretSyncQueue,
keyStore
keyStore,
licenseService
}: TSecretSyncServiceFactoryDep) => {
const listSecretSyncsByProjectId = async (
{ projectId, destination }: TListSecretSyncsByProjectId,
@@ -191,6 +194,13 @@ export const secretSyncServiceFactory = ({
{ projectId, secretPath, environment, ...params }: TCreateSecretSyncDTO,
actor: OrgServiceActor
) => {
await enterpriseSyncCheck(
licenseService,
params.destination,
actor.orgId,
"Failed to create secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs."
);
const { permission: projectPermission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
@@ -260,6 +270,13 @@ export const secretSyncServiceFactory = ({
message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID ${syncId}`
});
await enterpriseSyncCheck(
licenseService,
secretSync.destination as SecretSync,
actor.orgId,
"Failed to update secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs."
);
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
@@ -408,6 +425,13 @@ export const secretSyncServiceFactory = ({
message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"`
});
await enterpriseSyncCheck(
licenseService,
secretSync.destination as SecretSync,
actor.orgId,
"Failed to trigger secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs."
);
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
@@ -463,6 +487,13 @@ export const secretSyncServiceFactory = ({
message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"`
});
await enterpriseSyncCheck(
licenseService,
secretSync.destination as SecretSync,
actor.orgId,
"Failed to trigger secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs."
);
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
@@ -512,6 +543,13 @@ export const secretSyncServiceFactory = ({
message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"`
});
await enterpriseSyncCheck(
licenseService,
secretSync.destination as SecretSync,
actor.orgId,
"Failed to trigger secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs."
);
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
@@ -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 {
@@ -73,7 +79,6 @@ import {
THumanitecSyncListItem,
THumanitecSyncWithCredentials
} from "./humanitec";
import { TOCIVaultSync, TOCIVaultSyncInput, TOCIVaultSyncListItem, TOCIVaultSyncWithCredentials } from "./oci-vault";
import {
TTeamCitySync,
TTeamCitySyncInput,
+12
View File
@@ -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=${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` });
@@ -70,7 +70,7 @@ description: "Learn how to configure Auth0 OIDC for Infisical SSO."
prior to enforcing OIDC SSO to prevent any unintended issues.
</Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step>
</Steps>
@@ -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.
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step>
+1 -1
View File
@@ -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.
</Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step>
</Steps>
@@ -70,7 +70,7 @@ Prerequisites:
We recommend ensuring that your account is provisioned using the identity provider prior to enforcing OIDC SSO to prevent any unintended issues.
</Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step>
@@ -84,7 +84,7 @@ description: "Learn how to configure Google SAML for Infisical SSO."
prior to enforcing SAML SSO to prevent any unintended issues.
</Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step>
@@ -85,11 +85,10 @@ 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.
<Warning>
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.
</Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step>
</Steps>
@@ -97,7 +97,7 @@ description: "Learn how to configure Keycloak OIDC for Infisical SSO."
prior to enforcing OIDC SSO to prevent any unintended issues.
</Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step>
</Steps>
@@ -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)
@@ -128,7 +128,7 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
prior to enforcing SAML SSO to prevent any unintended issues.
</Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step>
</Steps>
+4 -5
View File
@@ -93,13 +93,12 @@ description: "Learn how to configure Okta SAML 2.0 for Infisical SSO."
Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
<Warning>
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.
</Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
<Info>
In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step>
</Steps>
+22 -10
View File
@@ -39,18 +39,30 @@ If your required identity provider is not shown in the list above, please reach
For enhanced security, Infisical enforces PKCE (Proof Key for Code Exchange) with the OAuth 2.0-based SSO providers and OIDC. This provides additional protection against authorization code interception attacks and strengthens your authentication flow security.
</Info>
## SSO Break Glass
In the event your SSO provider experiences downtime, and you need to access Infisical, Organization Admins can utilize the Admin Login Portal to bypass SSO enforcement.
This portal is accessible at `/login/admin` (e.g., https://app.infisical.com/login/admin).
<Note>
To bypass SSO for an organization, you must be an **Organization Admin** for that specific organization. This **Organization Admin** role is independent of **Server Admin** status. Being a **Server Admin** alone does not grant permission to use this bypass feature.
</Note>
## FAQ
<AccordionGroup>
<Accordion title="Why does Infisical require additional email verification for users connected via SAML?">
By default, Infisical Cloud is configured to not trust emails from external
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.
<Accordion title="Why does Infisical require additional email verification for users connected via SAML?">
By default, Infisical Cloud is configured to not trust emails from external
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.
</Accordion>
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.
</Accordion>
<Accordion title="Why do I get redirected to SSO when trying to use the Admin Login Portal?">
You are likely being redirected because you do not have email authentication mode enabled, or you're not an **Organization Admin**. This portal requires **Organization Admin** status and direct credential login (email and password). **Server Admin** status alone is insufficient.
</Accordion>
</AccordionGroup>
@@ -3,6 +3,13 @@ title: "OCI Connection"
description: "Learn how to configure an Oracle Cloud Infrastructure Connection for Infisical."
---
<Info>
OCI App Connection is a paid feature.
If you're using Infisical Cloud, then it is available under the **Enterprise Tier**. If you're self-hosting Infisical,
then you should contact [email protected] to purchase an enterprise license to use it.
</Info>
Infisical supports the use of [API Signing Key Authentication](https://docs.oracle.com/en-us/iaas/Content/API/Concepts/apisigningkey.htm) to connect with OCI.
## Create OCI User
@@ -3,6 +3,13 @@ title: "OCI Vault Sync"
description: "Learn how to configure an Oracle Cloud Infrastructure Vault Sync for Infisical."
---
<Info>
OCI Vault Sync is a paid feature.
If you're using Infisical Cloud, then it is available under the **Enterprise Tier**. If you're self-hosting Infisical,
then you should contact [email protected] to purchase an enterprise license to use it.
</Info>
**Prerequisites:**
- 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)
+13 -5
View File
@@ -96,8 +96,7 @@ The platform utilizes Postgres to persist all of its data and Redis for caching
<ParamField query="DB_ROOT_CERT" type="string" default="" optional>
Configure the SSL certificate for securing a Postgres connection by first encoding it in base64.
Use the command below to encode your certificate:
`echo "<certificate>" | base64`
Use the following command to encode your certificate: `echo "<certificate>" | base64`
</ParamField>
<ParamField query="DB_READ_REPLICAS" type="string" default="" optional>
@@ -111,10 +110,9 @@ DB_READ_REPLICAS=[{"DB_CONNECTION_URI":""}]
</ParamField>
<ParamField query="DB_ROOT_CERT" type="string" default="" optional>
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 "<certificate>" | base64`
Use the following command to encode your certificate: `echo "<certificate>" | base64`
If not provided it will use master SSL certificate.
If not provided it will use master SSL certificate.
</ParamField>
</Expandable>
@@ -169,6 +167,16 @@ Without email configuration, Infisical's core functions like sign-up/login and s
<ParamField query="SMTP_TLS_REJECT_UNAUTHORIZED" type="bool" default="true" optional>
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.
</ParamField>
<ParamField query="SMTP_CUSTOM_CA_CERT" type="string" default="none" optional>
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 "<certificate>" | base64`
Infisical highly encourages the following variables be used alongside this one for maximum security:
- `SMTP_REQUIRE_TLS=true`
- `SMTP_TLS_REJECT_UNAUTHORIZED=true`
</ParamField>
</Accordion>
<Accordion title="Twilio SendGrid">
@@ -2,16 +2,23 @@ import { faWrench } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Spinner, Tooltip } from "@app/components/v2";
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;
};
export const SecretSyncSelect = ({ onSelect }: Props) => {
const { subscription } = useSubscription();
const { isPending, data: secretSyncOptions } = useSecretSyncOptions();
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
if (isPending) {
return (
<div className="flex h-full flex-col items-center justify-center py-2.5">
@@ -23,14 +30,17 @@ export const SecretSyncSelect = ({ onSelect }: Props) => {
return (
<div className="grid grid-cols-4 gap-2">
{secretSyncOptions?.map(({ destination }) => {
{secretSyncOptions?.map(({ destination, enterprise }) => {
const { image, name } = SECRET_SYNC_MAP[destination];
return (
<button
type="button"
key={destination}
onClick={() => onSelect(destination)}
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"
onClick={() =>
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"
>
<img
src={`/images/integrations/${image}`}
@@ -45,6 +55,11 @@ export const SecretSyncSelect = ({ onSelect }: Props) => {
</button>
);
})}
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You can use every Secret Sync if you switch to Infisical's Enterprise plan."
/>
<Tooltip
side="bottom"
className="max-w-sm py-4"
+2 -2
View File
@@ -35,7 +35,7 @@ import { OCIConnectionMethod } from "@app/hooks/api/appConnections/types/oci-con
export const APP_CONNECTION_MAP: Record<
AppConnection,
{ name: string; image: string; size?: number }
{ name: string; image: string; size?: number; enterprise?: boolean }
> = {
[AppConnection.AWS]: { name: "AWS", image: "Amazon Web Services.png" },
[AppConnection.GitHub]: { name: "GitHub", image: "GitHub.png" },
@@ -64,7 +64,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 },
[AppConnection.OnePass]: { name: "1Password", image: "1Password.png" }
};
@@ -0,0 +1,73 @@
const TABLE_PREFERENCES_KEY = "userTablePreferences";
export enum PreferenceKey {
PerPage = "perPage"
}
interface TableSpecificPreferences {
[preferenceKey: string]: any;
}
interface UserTablePreferences {
[tableName: string]: TableSpecificPreferences;
}
// Retrieves all table preferences from localStorage
const getAllTablePreferences = (): UserTablePreferences => {
try {
const preferencesString = localStorage.getItem(TABLE_PREFERENCES_KEY);
if (preferencesString) {
return JSON.parse(preferencesString) as UserTablePreferences;
}
} catch (error) {
console.error("Error reading user table preferences from localStorage:", error);
}
return {};
};
// Saves all table preferences to localStorage
const saveAllTablePreferences = (preferences: UserTablePreferences): void => {
try {
localStorage.setItem(TABLE_PREFERENCES_KEY, JSON.stringify(preferences));
} catch (error) {
console.error("Error saving user table preferences to localStorage:", error);
}
};
// Retrieves a specific preference for a given table
export const getUserTablePreference = <T>(
tableName: string,
preferenceKey: PreferenceKey,
defaultValue: T
): T => {
const preferences = getAllTablePreferences();
if (
preferences &&
typeof preferences === "object" &&
tableName in preferences &&
preferenceKey in preferences[tableName]
) {
const value = preferences[tableName][preferenceKey];
if (value !== undefined && value !== null) {
return value as T;
}
}
return defaultValue;
};
// Sets a specific preference for a given table and saves it to localStorage
export const setUserTablePreference = (
tableName: string,
preferenceKey: PreferenceKey,
value: any
): void => {
const preferences = getAllTablePreferences();
if (!preferences[tableName]) {
preferences[tableName] = {};
}
preferences[tableName][preferenceKey] = value;
saveAllTablePreferences(preferences);
};
@@ -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 } = {
+7 -2
View File
@@ -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"
}
@@ -22,6 +22,7 @@ export type TSecretSyncOption = {
name: string;
destination: SecretSync;
canImportSecrets: boolean;
enterprise?: boolean;
};
export type TSecretSync =
@@ -50,4 +50,6 @@ export type SubscriptionPlan = {
enforceMfa: boolean;
projectTemplates: boolean;
kmip: boolean;
enterpriseSecretSyncs: boolean;
enterpriseAppConnections: boolean;
};
@@ -43,6 +43,11 @@ import {
useSubscription,
useWorkspace
} from "@app/context";
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";
@@ -71,7 +76,14 @@ export const KmipClientTable = () => {
perPage,
page,
setPerPage
} = usePagination(KmipClientOrderBy.Name);
} = usePagination(KmipClientOrderBy.Name, {
initPerPage: getUserTablePreference("kmipClientTable", PreferenceKey.PerPage, 20)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("kmipClientTable", PreferenceKey.PerPage, newPerPage);
};
const { data, isPending, isFetching } = useGetKmipClientsByProjectId({
projectId,
@@ -290,7 +302,7 @@ export const KmipClientTable = () => {
page={page}
perPage={perPage}
onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
onChangePerPage={handlePerPageChange}
/>
)}
{!isPending && kmipClients.length === 0 && (
@@ -53,6 +53,11 @@ import {
useWorkspace
} from "@app/context";
import { kmsKeyUsageOptions } from "@app/helpers/kms";
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";
@@ -100,7 +105,14 @@ export const CmekTable = () => {
perPage,
page,
setPerPage
} = usePagination(CmekOrderBy.Name);
} = usePagination(CmekOrderBy.Name, {
initPerPage: getUserTablePreference("cmekClientTable", PreferenceKey.PerPage, 20)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("cmekClientTable", PreferenceKey.PerPage, newPerPage);
};
const { data, isPending, isFetching } = useGetCmeksByProjectId({
projectId,
@@ -508,7 +520,7 @@ export const CmekTable = () => {
page={page}
perPage={perPage}
onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
onChangePerPage={handlePerPageChange}
/>
)}
{!isPending && keys.length === 0 && (
@@ -34,6 +34,11 @@ import {
Tr
} from "@app/components/v2";
import { OrgPermissionGroupActions, OrgPermissionSubjects, useOrganization } from "@app/context";
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";
@@ -103,7 +108,14 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => {
setOrderBy,
setOrderDirection,
toggleOrderDirection
} = usePagination<GroupsOrderBy>(GroupsOrderBy.Name, { initPerPage: 20 });
} = usePagination<GroupsOrderBy>(GroupsOrderBy.Name, {
initPerPage: getUserTablePreference("orgGroupsTable", PreferenceKey.PerPage, 20)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("orgGroupsTable", PreferenceKey.PerPage, newPerPage);
};
const filteredGroups = useMemo(() => {
const filtered = search
@@ -376,7 +388,7 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => {
page={page}
perPage={perPage}
onChangePage={setPage}
onChangePerPage={setPerPage}
onChangePerPage={handlePerPageChange}
/>
)}
{!isPending && !filteredGroups?.length && (
@@ -42,6 +42,11 @@ import {
Tr
} from "@app/components/v2";
import { OrgPermissionIdentityActions, OrgPermissionSubjects, useOrganization } from "@app/context";
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";
@@ -76,7 +81,15 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
perPage,
page,
setPerPage
} = usePagination<OrgIdentityOrderBy>(OrgIdentityOrderBy.Name);
} = usePagination<OrgIdentityOrderBy>(OrgIdentityOrderBy.Name, {
initPerPage: getUserTablePreference("identityTable", PreferenceKey.PerPage, 20)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("identityTable", PreferenceKey.PerPage, newPerPage);
};
const [filteredRoles, setFilteredRoles] = useState<string[]>([]);
const organizationId = currentOrg?.id || "";
@@ -379,7 +392,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
page={page}
perPage={perPage}
onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
onChangePerPage={handlePerPageChange}
/>
)}
{!isPending && data && data?.identities.length === 0 && (
@@ -42,6 +42,11 @@ import {
useSubscription,
useUser
} from "@app/context";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { usePagination, useResetPageHelper } from "@app/hooks";
import {
useFetchServerStatus,
@@ -170,7 +175,14 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro
setOrderBy,
setOrderDirection,
toggleOrderDirection
} = usePagination<OrgMembersOrderBy>(OrgMembersOrderBy.Name, { initPerPage: 20 });
} = usePagination<OrgMembersOrderBy>(OrgMembersOrderBy.Name, {
initPerPage: getUserTablePreference("orgMembersTable", PreferenceKey.PerPage, 20)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("orgMembersTable", PreferenceKey.PerPage, newPerPage);
};
const filteredUsers = useMemo(
() =>
@@ -513,7 +525,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro
page={page}
perPage={perPage}
onChangePage={setPage}
onChangePerPage={setPerPage}
onChangePerPage={handlePerPageChange}
/>
)}
{!isMembersLoading && !filteredUsers?.length && (
@@ -1,8 +1,11 @@
import { faWrench } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
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 +14,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 (
<div className="flex h-full flex-col items-center justify-center py-2.5">
@@ -25,13 +31,16 @@ export const AppConnectionsSelect = ({ onSelect }: Props) => {
return (
<div className="grid grid-cols-4 gap-2">
{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 (
<button
type="button"
key={option.app}
onClick={() => onSelect(option.app)}
onClick={() =>
enterprise && !subscription.enterpriseAppConnections
? handlePopUpOpen("upgradePlan")
: onSelect(option.app)
}
className="group relative flex h-28 cursor-pointer flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-700 p-4 duration-200 hover:bg-mineshaft-600"
>
<img
@@ -48,6 +57,11 @@ export const AppConnectionsSelect = ({ onSelect }: Props) => {
</button>
);
})}
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You can use every App Connection if you switch to Infisical's Enterprise plan."
/>
<Tooltip
side="bottom"
className="max-w-sm py-4"
@@ -30,6 +30,11 @@ import {
Tr
} from "@app/components/v2";
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
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";
@@ -76,7 +81,14 @@ export const AppConnectionsTable = () => {
orderBy,
setOrderDirection,
setOrderBy
} = usePagination<AppConnectionsOrderBy>(AppConnectionsOrderBy.App, { initPerPage: 20 });
} = usePagination<AppConnectionsOrderBy>(AppConnectionsOrderBy.App, {
initPerPage: getUserTablePreference("appConnectionsTable", PreferenceKey.PerPage, 20)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("appConnectionsTable", PreferenceKey.PerPage, newPerPage);
};
const filteredAppConnections = useMemo(
() =>
@@ -282,7 +294,7 @@ export const AppConnectionsTable = () => {
page={page}
perPage={perPage}
onChangePage={setPage}
onChangePerPage={setPerPage}
onChangePerPage={handlePerPageChange}
/>
)}
{!isPending && !filteredAppConnections?.length && (
@@ -25,6 +25,11 @@ import {
Tr
} from "@app/components/v2";
import { OrgPermissionGroupActions, OrgPermissionSubjects, useOrganization } from "@app/context";
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";
@@ -57,7 +62,14 @@ export const GroupMembersTable = ({ groupId, groupSlug, handlePopUpOpen }: Props
offset,
orderDirection,
toggleOrderDirection
} = usePagination(GroupMembersOrderBy.Name, { initPerPage: 10 });
} = usePagination(GroupMembersOrderBy.Name, {
initPerPage: getUserTablePreference("groupMembersTable", PreferenceKey.PerPage, 20)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("groupMembersTable", PreferenceKey.PerPage, newPerPage);
};
const { currentOrg } = useOrganization();
@@ -163,7 +175,7 @@ export const GroupMembersTable = ({ groupId, groupSlug, handlePopUpOpen }: Props
page={page}
perPage={perPage}
onChangePage={setPage}
onChangePerPage={setPerPage}
onChangePerPage={handlePerPageChange}
/>
)}
{!isPending && !filteredGroupMemberships?.length && (
@@ -21,6 +21,11 @@ import {
THead,
Tr
} from "@app/components/v2";
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";
@@ -53,7 +58,14 @@ export const IdentityProjectsTable = ({ identityId, handlePopUpOpen }: Props) =>
offset,
orderDirection,
toggleOrderDirection
} = usePagination(IdentityProjectsOrderBy.Name, { initPerPage: 10 });
} = usePagination(IdentityProjectsOrderBy.Name, {
initPerPage: getUserTablePreference("identityProjectsTable", PreferenceKey.PerPage, 20)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("identityProjectsTable", PreferenceKey.PerPage, newPerPage);
};
const filteredProjectMemberships = useMemo(
() =>
@@ -132,7 +144,7 @@ export const IdentityProjectsTable = ({ identityId, handlePopUpOpen }: Props) =>
page={page}
perPage={perPage}
onChangePage={setPage}
onChangePerPage={setPerPage}
onChangePerPage={handlePerPageChange}
/>
)}
{!isPending && !filteredProjectMemberships?.length && (
@@ -28,6 +28,11 @@ import {
} from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { getProjectHomePage } from "@app/helpers/project";
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";
@@ -104,7 +109,15 @@ export const AllProjectView = ({
limit,
toggleOrderDirection,
orderDirection
} = usePagination("name", { initPerPage: 50 });
} = usePagination("name", {
initPerPage: getUserTablePreference("allProjectsTable", PreferenceKey.PerPage, 50)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("allProjectsTable", PreferenceKey.PerPage, newPerPage);
};
const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp([
"requestAccessConfirmation"
] as const);
@@ -274,7 +287,7 @@ export const AllProjectView = ({
count={searchedProjects?.totalCount || 0}
page={page}
onChangePage={setPage}
onChangePerPage={setPerPage}
onChangePerPage={handlePerPageChange}
/>
)}
{!isProjectLoading && !searchedProjects?.totalCount && (
@@ -19,6 +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,
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";
@@ -63,7 +68,15 @@ export const MyProjectView = ({
limit,
toggleOrderDirection,
orderDirection
} = usePagination(ProjectOrderBy.Name, { initPerPage: 24 });
} = usePagination(ProjectOrderBy.Name, {
initPerPage: getUserTablePreference("myProjectsTable", PreferenceKey.PerPage, 24)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("myProjectsTable", PreferenceKey.PerPage, newPerPage);
};
const { data: projectFavorites, isPending: isProjectFavoritesLoading } =
useGetUserProjectFavorites(currentOrg?.id);
@@ -415,7 +428,7 @@ export const MyProjectView = ({
count={filteredWorkspaces.length}
page={page}
onChangePage={setPage}
onChangePerPage={setPerPage}
onChangePerPage={handlePerPageChange}
/>
)}
{isWorkspaceEmpty && (
@@ -13,6 +13,11 @@ import {
useOrganization,
useServerConfig
} from "@app/context";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { withPermission } from "@app/hoc";
import { usePagination, usePopUp } from "@app/hooks";
import {
@@ -28,8 +33,6 @@ import { SecretScanningFilter } from "./components/SecretScanningFilters";
import { SecretScanningFilterFormData, secretScanningFilterFormSchema } from "./components/types";
import { SecretScanningLogsTable } from "./components";
const PER_PAGE_INIT = 25;
export const SecretScanningPage = withPermission(
() => {
const queryParams = useSearch({
@@ -49,9 +52,14 @@ export const SecretScanningPage = withPermission(
const { offset, limit, orderBy, setPage, perPage, page, setPerPage } = usePagination(
SecretScanningOrderBy.CreatedAt,
{ initPerPage: PER_PAGE_INIT }
{ initPerPage: getUserTablePreference("secretScanningTable", PreferenceKey.PerPage, 20) }
);
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("secretScanningTable", PreferenceKey.PerPage, newPerPage);
};
const repositoryNames = watch("repositoryNames");
const resolvedStatus = watch("resolved");
@@ -180,7 +188,7 @@ export const SecretScanningPage = withPermission(
</div>
)}
</div>
<div className="mt-8 space-y-3">
<div className="mt-8 space-y-2">
{integrationEnabled && (
<div className="flex w-full items-center justify-end">
<SecretScanningFilter
@@ -191,18 +199,16 @@ export const SecretScanningPage = withPermission(
</div>
)}
<SecretScanningLogsTable gitRisks={risksData?.risks} isPending={isPending} />
{!isPending &&
risksData?.totalCount !== undefined &&
risksData.totalCount >= PER_PAGE_INIT && (
<Pagination
className="rounded-md"
count={risksData.totalCount}
page={page}
perPage={perPage}
onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
/>
)}
{!isPending && risksData?.totalCount !== undefined && risksData.totalCount >= 10 && (
<Pagination
className="rounded-md"
count={risksData.totalCount}
page={page}
perPage={perPage}
onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={handlePerPageChange}
/>
)}
</div>
</div>
</div>
@@ -129,7 +129,16 @@ export const OrgGeneralAuthSection = () => {
level.
</span>
<p className="mt-4">
In case of a lockout, admins can use the admin login portal at{" "}
In case of a lockout, admins can use the{" "}
<a
target="_blank"
className="underline underline-offset-2 hover:text-mineshaft-300"
href="https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal"
rel="noreferrer"
>
Admin Login Portal
</a>{" "}
at{" "}
<a
target="_blank"
rel="noopener noreferrer"
@@ -212,7 +212,16 @@ export const OrgOIDCSection = (): JSX.Element => {
level.
</span>
<p className="mt-4">
In case of a lockout, admins can use the admin login portal at{" "}
In case of a lockout, admins can use the{" "}
<a
target="_blank"
className="underline underline-offset-2 hover:text-mineshaft-300"
href="https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal"
rel="noreferrer"
>
Admin Login Portal
</a>{" "}
at{" "}
<a
target="_blank"
rel="noopener noreferrer"
@@ -20,6 +20,11 @@ import {
THead,
Tr
} from "@app/components/v2";
import {
getUserTablePreference,
PreferenceKey,
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 +57,14 @@ export const UserGroupsTable = ({ handlePopUpOpen, orgMembership }: Props) => {
offset,
orderDirection,
toggleOrderDirection
} = usePagination(UserGroupsOrderBy.Name, { initPerPage: 10 });
} = usePagination(UserGroupsOrderBy.Name, {
initPerPage: getUserTablePreference("userGroupsTable", PreferenceKey.PerPage, 10)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("userGroupsTable", PreferenceKey.PerPage, newPerPage);
};
const filteredGroupMemberships = useMemo(
() =>
@@ -119,7 +131,7 @@ export const UserGroupsTable = ({ handlePopUpOpen, orgMembership }: Props) => {
page={page}
perPage={perPage}
onChangePage={setPage}
onChangePerPage={setPerPage}
onChangePerPage={handlePerPageChange}
/>
)}
{!isPending && !filteredGroupMemberships?.length && (
@@ -22,6 +22,11 @@ import {
Tr
} from "@app/components/v2";
import { useOrganization } from "@app/context";
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";
@@ -54,7 +59,14 @@ export const UserProjectsTable = ({ membershipId, handlePopUpOpen }: Props) => {
offset,
orderDirection,
toggleOrderDirection
} = usePagination(UserProjectsOrderBy.Name, { initPerPage: 10 });
} = usePagination(UserProjectsOrderBy.Name, {
initPerPage: getUserTablePreference("userProjectsTable", PreferenceKey.PerPage, 10)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("userProjectsTable", PreferenceKey.PerPage, newPerPage);
};
const { data: projectMemberships = [], isPending } = useGetOrgMembershipProjectMemberships(
orgId,
@@ -136,7 +148,7 @@ export const UserProjectsTable = ({ membershipId, handlePopUpOpen }: Props) => {
page={page}
perPage={perPage}
onChangePage={setPage}
onChangePerPage={setPerPage}
onChangePerPage={handlePerPageChange}
/>
)}
{!isPending && !filteredProjectMemberships?.length && (
@@ -27,6 +27,11 @@ import {
Tr
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
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";
@@ -62,7 +67,14 @@ export const GroupTable = ({ handlePopUpOpen }: Props) => {
orderDirection,
orderBy,
toggleOrderDirection
} = usePagination(GroupsOrderBy.Name, { initPerPage: 20 });
} = usePagination(GroupsOrderBy.Name, {
initPerPage: getUserTablePreference("projectGroupsTable", PreferenceKey.PerPage, 20)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("projectGroupsTable", PreferenceKey.PerPage, newPerPage);
};
const { data: groupMemberships = [], isPending } = useListWorkspaceGroups(
currentWorkspace?.id || ""
@@ -183,7 +195,7 @@ export const GroupTable = ({ handlePopUpOpen }: Props) => {
page={page}
perPage={perPage}
onChangePage={setPage}
onChangePerPage={setPerPage}
onChangePerPage={handlePerPageChange}
/>
)}
{!isPending && !filteredGroupMemberships?.length && (
@@ -42,6 +42,11 @@ import {
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { formatProjectRoleName } from "@app/helpers/roles";
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";
@@ -72,7 +77,14 @@ export const IdentityTab = withProjectPermission(
perPage,
page,
setPerPage
} = usePagination(ProjectIdentityOrderBy.Name);
} = usePagination(ProjectIdentityOrderBy.Name, {
initPerPage: getUserTablePreference("projectIdentityTable", PreferenceKey.PerPage, 20)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("projectIdentityTable", PreferenceKey.PerPage, newPerPage);
};
const workspaceId = currentWorkspace?.id ?? "";
@@ -403,7 +415,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 && (
@@ -51,6 +51,11 @@ import {
useWorkspace
} from "@app/context";
import { formatProjectRoleName } from "@app/helpers/roles";
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";
@@ -101,12 +106,12 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => {
setOrderDirection,
toggleOrderDirection
} = usePagination<MembersOrderBy>(MembersOrderBy.Name, {
initPerPage: parseInt(localStorage.getItem("PROJECT_MEMBERS_TABLE_PER_PAGE") || "20", 10)
initPerPage: getUserTablePreference("projectMembersTable", PreferenceKey.PerPage, 20)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
localStorage.setItem("PROJECT_MEMBERS_TABLE_PER_PAGE", newPerPage.toString());
setUserTablePreference("projectMembersTable", PreferenceKey.PerPage, newPerPage);
};
const { data: members = [], isPending: isMembersLoading } = useGetWorkspaceUsers(
@@ -32,6 +32,11 @@ import {
Tooltip,
Tr
} from "@app/components/v2";
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";
@@ -110,7 +115,14 @@ export const IntegrationsTable = ({
orderBy,
setOrderDirection,
setOrderBy
} = usePagination<IntegrationsOrderBy>(IntegrationsOrderBy.App, { initPerPage: 20 });
} = usePagination<IntegrationsOrderBy>(IntegrationsOrderBy.App, {
initPerPage: getUserTablePreference("integrationsTable", PreferenceKey.PerPage, 20)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("integrationsTable", PreferenceKey.PerPage, newPerPage);
};
useEffect(() => {
if (integrations?.some((integration) => integration.isSynced === false))
@@ -437,7 +449,7 @@ export const IntegrationsTable = ({
page={page}
perPage={perPage}
onChangePage={setPage}
onChangePerPage={setPerPage}
onChangePerPage={handlePerPageChange}
/>
)}
{!isLoading && !filteredIntegrations?.length && (
@@ -38,6 +38,11 @@ import {
} from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
import { OrderByDirection } from "@app/hooks/api/generic/types";
import {
@@ -119,7 +124,14 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => {
orderBy,
setOrderDirection,
setOrderBy
} = usePagination<SecretSyncsOrderBy>(SecretSyncsOrderBy.Name, { initPerPage: 20 });
} = usePagination<SecretSyncsOrderBy>(SecretSyncsOrderBy.Name, {
initPerPage: getUserTablePreference("secretSyncTable", PreferenceKey.PerPage, 20)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("secretSyncTable", PreferenceKey.PerPage, newPerPage);
};
const filteredSecretSyncs = useMemo(
() =>
@@ -465,7 +477,7 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => {
page={page}
perPage={perPage}
onChangePage={setPage}
onChangePerPage={setPerPage}
onChangePerPage={handlePerPageChange}
/>
)}
{!filteredSecretSyncs?.length && (
@@ -64,6 +64,11 @@ import {
useWorkspace
} from "@app/context";
import { ProjectPermissionSecretRotationActions } from "@app/context/ProjectPermissionContext/types";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
import {
useCreateFolder,
@@ -180,7 +185,14 @@ export const OverviewPage = () => {
page,
setPerPage,
orderBy
} = usePagination<DashboardSecretsOrderBy>(DashboardSecretsOrderBy.Name);
} = usePagination<DashboardSecretsOrderBy>(DashboardSecretsOrderBy.Name, {
initPerPage: getUserTablePreference("secretOverviewTable", PreferenceKey.PerPage, 100)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("secretOverviewTable", PreferenceKey.PerPage, newPerPage);
};
const resetSelectedEntries = useCallback(() => {
setSelectedEntries({
@@ -1416,7 +1428,7 @@ export const OverviewPage = () => {
page={page}
perPage={perPage}
onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
onChangePerPage={handlePerPageChange}
/>
)}
</div>
@@ -29,6 +29,11 @@ import {
ProjectPermissionSecretActions,
ProjectPermissionSecretRotationActions
} from "@app/context/ProjectPermissionContext/types";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
import {
useGetImportedSecretsSingleEnv,
@@ -98,7 +103,14 @@ const Page = () => {
page,
setPerPage,
orderBy
} = usePagination<DashboardSecretsOrderBy>(DashboardSecretsOrderBy.Name);
} = usePagination<DashboardSecretsOrderBy>(DashboardSecretsOrderBy.Name, {
initPerPage: getUserTablePreference("secretDashboardTable", PreferenceKey.PerPage, 100)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("secretDashboardTable", PreferenceKey.PerPage, newPerPage);
};
const [snapshotId, setSnapshotId] = useState<string | null>(null);
const isRollbackMode = Boolean(snapshotId);
@@ -558,7 +570,7 @@ const Page = () => {
page={page}
perPage={perPage}
onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
onChangePerPage={handlePerPageChange}
/>
)}
<Modal
@@ -25,9 +25,14 @@ import {
Tr
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { usePagination, useResetPageHelper } from "@app/hooks";
import { useGetWsTags } from "@app/hooks/api";
import { OrderByDirection } from "@app/hooks/api/generic/types";
import { useGetWsTags } from "@app/hooks/api/tags";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
@@ -61,7 +66,14 @@ export const SecretTagsTable = ({ handlePopUpOpen }: Props) => {
offset,
orderDirection,
toggleOrderDirection
} = usePagination(TagsOrderBy.Slug, { initPerPage: 10 });
} = usePagination(TagsOrderBy.Slug, {
initPerPage: getUserTablePreference("secretTagsTable", PreferenceKey.PerPage, 20)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("secretTagsTable", PreferenceKey.PerPage, newPerPage);
};
const filteredTags = useMemo(
() =>
@@ -151,7 +163,7 @@ export const SecretTagsTable = ({ handlePopUpOpen }: Props) => {
page={page}
perPage={perPage}
onChangePage={setPage}
onChangePerPage={setPerPage}
onChangePerPage={handlePerPageChange}
/>
)}
{!isPending && !filteredTags?.length && (