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" { declare module "@fastify/request-context" {
interface RequestContextData { interface RequestContextData {
reqId: string; reqId: string;
orgId?: string;
identityAuthInfo?: { identityAuthInfo?: {
identityId: string; identityId: string;
oidc?: { oidc?: {
@@ -1,16 +1,16 @@
import z from "zod"; import z from "zod";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { import {
CreateOCIConnectionSchema, CreateOCIConnectionSchema,
SanitizedOCIConnectionSchema, SanitizedOCIConnectionSchema,
UpdateOCIConnectionSchema UpdateOCIConnectionSchema
} from "@app/services/app-connection/oci"; } from "@app/ee/services/app-connections/oci";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { registerAppConnectionEndpoints } from "./app-connection-endpoints"; import { registerAppConnectionEndpoints } from "../../../../server/routes/v1/app-connection-routers/app-connection-endpoints";
export const registerOCIConnectionRouter = async (server: FastifyZodProvider) => { export const registerOCIConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({ registerAppConnectionEndpoints({
@@ -2,11 +2,10 @@ import {
CreateOCIVaultSyncSchema, CreateOCIVaultSyncSchema,
OCIVaultSyncSchema, OCIVaultSyncSchema,
UpdateOCIVaultSyncSchema UpdateOCIVaultSyncSchema
} from "@app/services/secret-sync/oci-vault"; } from "@app/ee/services/secret-sync/oci-vault";
import { registerSyncSecretsEndpoints } from "@app/server/routes/v1/secret-sync-routers/secret-sync-endpoints";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
export const registerOCIVaultSyncRouter = async (server: FastifyZodProvider) => export const registerOCIVaultSyncRouter = async (server: FastifyZodProvider) =>
registerSyncSecretsEndpoints({ registerSyncSecretsEndpoints({
destination: SecretSync.OCIVault, destination: SecretSync.OCIVault,
@@ -1,7 +1,9 @@
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { OrgServiceActor } from "@app/lib/types"; import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums"; import { AppConnection } from "../../../../services/app-connection/app-connection-enums";
import { TLicenseServiceFactory } from "../../license/license-service";
import { listOCICompartments, listOCIVaultKeys, listOCIVaults } from "./oci-connection-fns"; import { listOCICompartments, listOCIVaultKeys, listOCIVaults } from "./oci-connection-fns";
import { TOCIConnection } from "./oci-connection-types"; import { TOCIConnection } from "./oci-connection-types";
@@ -22,8 +24,23 @@ type TListOCIVaultKeysDTO = {
vaultOcid: string; vaultOcid: string;
}; };
export const ociConnectionService = (getAppConnection: TGetAppConnectionFunc) => { // Enterprise check
export const checkPlan = async (licenseService: Pick<TLicenseServiceFactory, "getPlan">, orgId: string) => {
const plan = await licenseService.getPlan(orgId);
if (!plan.enterpriseAppConnections)
throw new BadRequestError({
message:
"Failed to use app connection due to plan restriction. Upgrade plan to access enterprise app connections."
});
};
export const ociConnectionService = (
getAppConnection: TGetAppConnectionFunc,
licenseService: Pick<TLicenseServiceFactory, "getPlan">
) => {
const listCompartments = async (connectionId: string, actor: OrgServiceActor) => { const listCompartments = async (connectionId: string, actor: OrgServiceActor) => {
await checkPlan(licenseService, actor.orgId);
const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor); const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor);
try { try {
@@ -36,6 +53,8 @@ export const ociConnectionService = (getAppConnection: TGetAppConnectionFunc) =>
}; };
const listVaults = async ({ connectionId, compartmentOcid }: TListOCIVaultsDTO, actor: OrgServiceActor) => { const listVaults = async ({ connectionId, compartmentOcid }: TListOCIVaultsDTO, actor: OrgServiceActor) => {
await checkPlan(licenseService, actor.orgId);
const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor); const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor);
try { try {
@@ -51,6 +70,8 @@ export const ociConnectionService = (getAppConnection: TGetAppConnectionFunc) =>
{ connectionId, compartmentOcid, vaultOcid }: TListOCIVaultKeysDTO, { connectionId, compartmentOcid, vaultOcid }: TListOCIVaultKeysDTO,
actor: OrgServiceActor actor: OrgServiceActor
) => { ) => {
await checkPlan(licenseService, actor.orgId);
const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor); const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor);
try { try {
@@ -2,7 +2,7 @@ import z from "zod";
import { DiscriminativePick } from "@app/lib/types"; import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums"; import { AppConnection } from "../../../../services/app-connection/app-connection-enums";
import { import {
CreateOCIConnectionSchema, CreateOCIConnectionSchema,
OCIConnectionSchema, OCIConnectionSchema,
@@ -1,3 +1,4 @@
import { ProjectType } from "@app/db/schemas";
import { import {
TCreateProjectTemplateDTO, TCreateProjectTemplateDTO,
TUpdateProjectTemplateDTO TUpdateProjectTemplateDTO
@@ -315,7 +316,6 @@ export enum EventType {
CREATE_PROJECT_TEMPLATE = "create-project-template", CREATE_PROJECT_TEMPLATE = "create-project-template",
UPDATE_PROJECT_TEMPLATE = "update-project-template", UPDATE_PROJECT_TEMPLATE = "update-project-template",
DELETE_PROJECT_TEMPLATE = "delete-project-template", DELETE_PROJECT_TEMPLATE = "delete-project-template",
APPLY_PROJECT_TEMPLATE = "apply-project-template",
GET_APP_CONNECTIONS = "get-app-connections", GET_APP_CONNECTIONS = "get-app-connections",
GET_AVAILABLE_APP_CONNECTIONS_DETAILS = "get-available-app-connections-details", GET_AVAILABLE_APP_CONNECTIONS_DETAILS = "get-available-app-connections-details",
GET_APP_CONNECTION = "get-app-connection", GET_APP_CONNECTION = "get-app-connection",
@@ -375,7 +375,13 @@ export enum EventType {
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST = "microsoft-teams-workflow-integration-list", MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST = "microsoft-teams-workflow-integration-list",
PROJECT_ASSUME_PRIVILEGE_SESSION_START = "project-assume-privileges-session-start", 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[] = [ export const filterableSecretEvents: EventType[] = [
@@ -2451,14 +2457,6 @@ interface DeleteProjectTemplateEvent {
}; };
} }
interface ApplyProjectTemplateEvent {
type: EventType.APPLY_PROJECT_TEMPLATE;
metadata: {
template: string;
projectId: string;
};
}
interface GetAppConnectionsEvent { interface GetAppConnectionsEvent {
type: EventType.GET_APP_CONNECTIONS; type: EventType.GET_APP_CONNECTIONS;
metadata: { 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 = export type Event =
| GetSecretsEvent | GetSecretsEvent
| GetSecretEvent | GetSecretEvent
@@ -3117,7 +3168,6 @@ export type Event =
| CreateProjectTemplateEvent | CreateProjectTemplateEvent
| UpdateProjectTemplateEvent | UpdateProjectTemplateEvent
| DeleteProjectTemplateEvent | DeleteProjectTemplateEvent
| ApplyProjectTemplateEvent
| GetAppConnectionsEvent | GetAppConnectionsEvent
| GetAvailableAppConnectionsDetailsEvent | GetAvailableAppConnectionsDetailsEvent
| GetAppConnectionEvent | GetAppConnectionEvent
@@ -3179,4 +3229,8 @@ export type Event =
| MicrosoftTeamsWorkflowIntegrationGetTeamsEvent | MicrosoftTeamsWorkflowIntegrationGetTeamsEvent
| MicrosoftTeamsWorkflowIntegrationGetEvent | MicrosoftTeamsWorkflowIntegrationGetEvent
| MicrosoftTeamsWorkflowIntegrationListEvent | MicrosoftTeamsWorkflowIntegrationListEvent
| MicrosoftTeamsWorkflowIntegrationUpdateEvent; | MicrosoftTeamsWorkflowIntegrationUpdateEvent
| OrgUpdateEvent
| ProjectCreateEvent
| ProjectUpdateEvent
| ProjectDeleteEvent;
@@ -29,7 +29,9 @@ export const getDefaultOnPremFeatures = () => {
secretApproval: true, secretApproval: true,
secretRotation: true, secretRotation: true,
caCrl: false, caCrl: false,
sshHostGroups: false sshHostGroups: false,
enterpriseSecretSyncs: false,
enterpriseAppConnections: false
}; };
}; };
@@ -19,7 +19,7 @@ export const licenseDALFactory = (db: TDbClient) => {
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`) .join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
.where(`${TableName.Users}.isGhost`, false) .where(`${TableName.Users}.isGhost`, false)
.count(); .count();
return Number(doc?.[0].count); return Number(doc?.[0]?.count ?? 0);
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "Count of Org Members" }); throw new DatabaseError({ error, name: "Count of Org Members" });
} }
@@ -55,7 +55,9 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
projectTemplates: false, projectTemplates: false,
kmip: false, kmip: false,
gateway: false, gateway: false,
sshHostGroups: false sshHostGroups: false,
enterpriseSecretSyncs: false,
enterpriseAppConnections: false
}); });
export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => { export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
@@ -92,6 +92,10 @@ export const licenseServiceFactory = ({
const { const {
data: { currentPlan } data: { currentPlan }
} = await licenseServerOnPremApi.request.get<{ currentPlan: TFeatureSet }>("/api/license/v1/plan"); } = await licenseServerOnPremApi.request.get<{ currentPlan: TFeatureSet }>("/api/license/v1/plan");
const workspacesUsed = await projectDAL.countOfOrgProjects(null);
currentPlan.workspacesUsed = workspacesUsed;
onPremFeatures = currentPlan; onPremFeatures = currentPlan;
logger.info("Successfully synchronized license key features"); logger.info("Successfully synchronized license key features");
} catch (error) { } catch (error) {
@@ -185,6 +189,9 @@ export const licenseServiceFactory = ({
} = await licenseServerCloudApi.request.get<{ currentPlan: TFeatureSet }>( } = await licenseServerCloudApi.request.get<{ currentPlan: TFeatureSet }>(
`/api/license-server/v1/customers/${org.customerId}/cloud-plan` `/api/license-server/v1/customers/${org.customerId}/cloud-plan`
); );
const workspacesUsed = await projectDAL.countOfOrgProjects(orgId);
currentPlan.workspacesUsed = workspacesUsed;
await keyStore.setItemWithExpiry( await keyStore.setItemWithExpiry(
FEATURE_CACHE_KEY(org.id), FEATURE_CACHE_KEY(org.id),
LICENSE_SERVER_CLOUD_PLAN_TTL, LICENSE_SERVER_CLOUD_PLAN_TTL,
@@ -27,7 +27,7 @@ export type TFeatureSet = {
slug: null; slug: null;
tier: -1; tier: -1;
workspaceLimit: null; workspaceLimit: null;
workspacesUsed: 0; workspacesUsed: number;
dynamicSecret: false; dynamicSecret: false;
memberLimit: null; memberLimit: null;
membersUsed: number; membersUsed: number;
@@ -72,6 +72,8 @@ export type TFeatureSet = {
kmip: false; kmip: false;
gateway: false; gateway: false;
sshHostGroups: false; sshHostGroups: false;
enterpriseSecretSyncs: false;
enterpriseAppConnections: false;
}; };
export type TOrgPlansTableDTO = { export type TOrgPlansTableDTO = {
@@ -6,5 +6,6 @@ export const OCI_VAULT_SYNC_LIST_OPTION: TSecretSyncListItem = {
name: "OCI Vault", name: "OCI Vault",
destination: SecretSync.OCIVault, destination: SecretSync.OCIVault,
connection: AppConnection.OCI, connection: AppConnection.OCI,
canImportSecrets: true canImportSecrets: true,
enterprise: true
}; };
@@ -1,7 +1,6 @@
import { secrets, vault } from "oci-sdk"; import { secrets, vault } from "oci-sdk";
import { delay } from "@app/lib/delay"; import { getOCIProvider } from "@app/ee/services/app-connections/oci";
import { getOCIProvider } from "@app/services/app-connection/oci";
import { import {
TCreateOCIVaultVariable, TCreateOCIVaultVariable,
TDeleteOCIVaultVariable, TDeleteOCIVaultVariable,
@@ -9,7 +8,8 @@ import {
TOCIVaultSyncWithCredentials, TOCIVaultSyncWithCredentials,
TUnmarkOCIVaultVariableFromDeletion, TUnmarkOCIVaultVariableFromDeletion,
TUpdateOCIVaultVariable TUpdateOCIVaultVariable
} from "@app/services/secret-sync/oci-vault/oci-vault-sync-types"; } from "@app/ee/services/secret-sync/oci-vault/oci-vault-sync-types";
import { delay } from "@app/lib/delay";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns"; import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
@@ -66,5 +66,6 @@ export const OCIVaultSyncListItemSchema = z.object({
name: z.literal("OCI Vault"), name: z.literal("OCI Vault"),
connection: z.literal(AppConnection.OCI), connection: z.literal(AppConnection.OCI),
destination: z.literal(SecretSync.OCIVault), destination: z.literal(SecretSync.OCIVault),
canImportSecrets: z.literal(true) canImportSecrets: z.literal(true),
enterprise: z.boolean()
}); });
@@ -1,7 +1,7 @@
import { SimpleAuthenticationDetailsProvider } from "oci-sdk"; import { SimpleAuthenticationDetailsProvider } from "oci-sdk";
import { z } from "zod"; import { z } from "zod";
import { TOCIConnection } from "@app/services/app-connection/oci"; import { TOCIConnection } from "@app/ee/services/app-connections/oci";
import { CreateOCIVaultSyncSchema, OCIVaultSyncListItemSchema, OCIVaultSyncSchema } from "./oci-vault-sync-schemas"; import { CreateOCIVaultSyncSchema, OCIVaultSyncListItemSchema, OCIVaultSyncSchema } from "./oci-vault-sync-schemas";
+15 -3
View File
@@ -69,6 +69,9 @@ const envSchema = z
SMTP_PASSWORD: zpStr(z.string().optional()), SMTP_PASSWORD: zpStr(z.string().optional()),
SMTP_FROM_ADDRESS: zpStr(z.string().optional()), SMTP_FROM_ADDRESS: zpStr(z.string().optional()),
SMTP_FROM_NAME: zpStr(z.string().optional().default("Infisical")), 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 COOKIE_SECRET_SIGN_KEY: z
.string() .string()
.min(32) .min(32)
@@ -298,6 +301,17 @@ export const initEnvConfig = (logger?: CustomLogger) => {
}; };
export const formatSmtpConfig = () => { 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 { return {
host: envCfg.SMTP_HOST, host: envCfg.SMTP_HOST,
port: envCfg.SMTP_PORT, port: envCfg.SMTP_PORT,
@@ -309,8 +323,6 @@ export const formatSmtpConfig = () => {
from: `"${envCfg.SMTP_FROM_NAME}" <${envCfg.SMTP_FROM_ADDRESS}>`, from: `"${envCfg.SMTP_FROM_NAME}" <${envCfg.SMTP_FROM_ADDRESS}>`,
ignoreTLS: envCfg.SMTP_IGNORE_TLS, ignoreTLS: envCfg.SMTP_IGNORE_TLS,
requireTLS: envCfg.SMTP_REQUIRE_TLS, requireTLS: envCfg.SMTP_REQUIRE_TLS,
tls: { tls: tlsOptions
rejectUnauthorized: envCfg.SMTP_TLS_REJECT_UNAUTHORIZED
}
}; };
}; };
+13 -4
View File
@@ -95,11 +95,20 @@ const extractReqId = () => {
try { try {
return requestContext.get("reqId") || UNKNOWN_REQUEST_ID; return requestContext.get("reqId") || UNKNOWN_REQUEST_ID;
} catch (err) { } catch (err) {
// eslint-disable-next-line no-console
console.log("failed to get request context", err); console.log("failed to get request context", err);
return UNKNOWN_REQUEST_ID; return UNKNOWN_REQUEST_ID;
} }
}; };
const extractOrgId = () => {
try {
return requestContext.get("orgId");
} catch {
return "";
}
};
export const initLogger = () => { export const initLogger = () => {
const cfg = loggerConfig.parse(process.env); const cfg = loggerConfig.parse(process.env);
const targets: pino.TransportMultiOptions["targets"][number][] = [ const targets: pino.TransportMultiOptions["targets"][number][] = [
@@ -135,22 +144,22 @@ export const initLogger = () => {
const wrapLogger = (originalLogger: Logger): CustomLogger => { const wrapLogger = (originalLogger: Logger): CustomLogger => {
// eslint-disable-next-line no-param-reassign, @typescript-eslint/no-explicit-any // eslint-disable-next-line no-param-reassign, @typescript-eslint/no-explicit-any
originalLogger.info = (obj: unknown, msg?: string, ...args: 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 // eslint-disable-next-line no-param-reassign, @typescript-eslint/no-explicit-any
originalLogger.error = (obj: unknown, msg?: string, ...args: 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 // eslint-disable-next-line no-param-reassign, @typescript-eslint/no-explicit-any
originalLogger.warn = (obj: unknown, msg?: string, ...args: 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 // eslint-disable-next-line no-param-reassign, @typescript-eslint/no-explicit-any
originalLogger.debug = (obj: unknown, msg?: string, ...args: 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; return originalLogger;
@@ -123,6 +123,7 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
switch (authMode) { switch (authMode) {
case AuthMode.JWT: { case AuthMode.JWT: {
const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity(token); const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity(token);
requestContext.set("orgId", orgId);
req.auth = { req.auth = {
authMode: AuthMode.JWT, authMode: AuthMode.JWT,
user, user,
@@ -138,6 +139,7 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
case AuthMode.IDENTITY_ACCESS_TOKEN: { case AuthMode.IDENTITY_ACCESS_TOKEN: {
const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken(token, req.realIp); const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken(token, req.realIp);
const serverCfg = await getServerCfg(); const serverCfg = await getServerCfg();
requestContext.set("orgId", identity.orgId);
req.auth = { req.auth = {
authMode: AuthMode.IDENTITY_ACCESS_TOKEN, authMode: AuthMode.IDENTITY_ACCESS_TOKEN,
actor, actor,
@@ -157,6 +159,7 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
} }
case AuthMode.SERVICE_TOKEN: { case AuthMode.SERVICE_TOKEN: {
const serviceToken = await server.services.serviceToken.fnValidateServiceToken(token); const serviceToken = await server.services.serviceToken.fnValidateServiceToken(token);
requestContext.set("orgId", serviceToken.orgId);
req.auth = { req.auth = {
orgId: serviceToken.orgId, orgId: serviceToken.orgId,
authMode: AuthMode.SERVICE_TOKEN as const, authMode: AuthMode.SERVICE_TOKEN as const,
@@ -181,6 +184,7 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
} }
case AuthMode.SCIM_TOKEN: { case AuthMode.SCIM_TOKEN: {
const { orgId, scimTokenId } = await server.services.scim.fnValidateScimToken(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 }; req.auth = { authMode: AuthMode.SCIM_TOKEN, actor, scimTokenId, orgId, authMethod: null };
break; break;
} }
+6 -3
View File
@@ -1014,7 +1014,8 @@ export const registerRoutes = async (
secretVersionV2BridgeDAL, secretVersionV2BridgeDAL,
secretVersionTagV2BridgeDAL, secretVersionTagV2BridgeDAL,
resourceMetadataDAL, resourceMetadataDAL,
appConnectionDAL appConnectionDAL,
licenseService
}); });
const secretQueueService = secretQueueFactory({ const secretQueueService = secretQueueFactory({
@@ -1631,7 +1632,8 @@ export const registerRoutes = async (
const appConnectionService = appConnectionServiceFactory({ const appConnectionService = appConnectionServiceFactory({
appConnectionDAL, appConnectionDAL,
permissionService, permissionService,
kmsService kmsService,
licenseService
}); });
const secretSyncService = secretSyncServiceFactory({ const secretSyncService = secretSyncServiceFactory({
@@ -1642,7 +1644,8 @@ export const registerRoutes = async (
folderDAL, folderDAL,
secretSyncQueue, secretSyncQueue,
projectBotService, projectBotService,
keyStore keyStore,
licenseService
}); });
const kmipService = kmipServiceFactory({ const kmipService = kmipServiceFactory({
@@ -1,5 +1,6 @@
import { z } from "zod"; import { z } from "zod";
import { OCIConnectionListItemSchema, SanitizedOCIConnectionSchema } from "@app/ee/services/app-connections/oci";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags } from "@app/lib/api-docs"; import { ApiDocsTags } from "@app/lib/api-docs";
import { readLimit } from "@app/server/config/rateLimiter"; import { readLimit } from "@app/server/config/rateLimiter";
@@ -42,7 +43,6 @@ import {
} from "@app/services/app-connection/humanitec"; } from "@app/services/app-connection/humanitec";
import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap"; import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap";
import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql"; import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql";
import { OCIConnectionListItemSchema, SanitizedOCIConnectionSchema } from "@app/services/app-connection/oci";
import { import {
PostgresConnectionListItemSchema, PostgresConnectionListItemSchema,
SanitizedPostgresConnectionSchema SanitizedPostgresConnectionSchema
@@ -1,3 +1,4 @@
import { registerOCIConnectionRouter } from "@app/ee/routes/v1/app-connection-routers/oci-connection-router";
import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { registerOnePassConnectionRouter } from "./1password-connection-router"; import { registerOnePassConnectionRouter } from "./1password-connection-router";
@@ -14,7 +15,6 @@ import { registerHCVaultConnectionRouter } from "./hc-vault-connection-router";
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router"; import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
import { registerLdapConnectionRouter } from "./ldap-connection-router"; import { registerLdapConnectionRouter } from "./ldap-connection-router";
import { registerMsSqlConnectionRouter } from "./mssql-connection-router"; import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
import { registerOCIConnectionRouter } from "./oci-connection-router";
import { registerPostgresConnectionRouter } from "./postgres-connection-router"; import { registerPostgresConnectionRouter } from "./postgres-connection-router";
import { registerTeamCityConnectionRouter } from "./teamcity-connection-router"; import { registerTeamCityConnectionRouter } from "./teamcity-connection-router";
import { registerTerraformCloudConnectionRouter } from "./terraform-cloud-router"; import { registerTerraformCloudConnectionRouter } from "./terraform-cloud-router";
@@ -312,8 +312,17 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
data: req.body data: req.body
}); });
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.UPDATE_ORG,
metadata: req.body
}
});
return { return {
message: "Successfully changed organization name", message: "Successfully updated organization",
organization organization
}; };
} }
@@ -263,6 +263,17 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId 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 }; return { workspace };
} }
}); });
@@ -297,6 +308,17 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
projectId: req.params.workspaceId, projectId: req.params.workspaceId,
name: req.body.name 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 { return {
message: "Successfully changed workspace name", message: "Successfully changed workspace name",
workspace workspace
@@ -375,6 +397,17 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId 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 { return {
workspace workspace
}; };
@@ -411,6 +444,17 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
projectId: req.params.workspaceId, projectId: req.params.workspaceId,
autoCapitalization: req.body.autoCapitalization 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 { return {
message: "Successfully changed workspace settings", message: "Successfully changed workspace settings",
workspace workspace
@@ -448,6 +492,17 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
projectId: req.params.workspaceId, projectId: req.params.workspaceId,
hasDeleteProtection: req.body.hasDeleteProtection 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 { return {
message: "Successfully changed workspace settings", message: "Successfully changed workspace settings",
workspace workspace
@@ -486,6 +541,16 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
workspaceSlug: req.params.workspaceSlug 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 { return {
message: "Successfully changed workspace version limit", message: "Successfully changed workspace version limit",
workspace workspace
@@ -524,6 +589,16 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
auditLogsRetentionDays: req.body.auditLogsRetentionDays 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 { return {
message: "Successfully updated project's audit logs retention period", message: "Successfully updated project's audit logs retention period",
workspace 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 { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { registerOnePassSyncRouter } from "./1password-sync-router"; import { registerOnePassSyncRouter } from "./1password-sync-router";
@@ -11,7 +12,6 @@ import { registerGcpSyncRouter } from "./gcp-sync-router";
import { registerGitHubSyncRouter } from "./github-sync-router"; import { registerGitHubSyncRouter } from "./github-sync-router";
import { registerHCVaultSyncRouter } from "./hc-vault-sync-router"; import { registerHCVaultSyncRouter } from "./hc-vault-sync-router";
import { registerHumanitecSyncRouter } from "./humanitec-sync-router"; import { registerHumanitecSyncRouter } from "./humanitec-sync-router";
import { registerOCIVaultSyncRouter } from "./oci-vault-sync-router";
import { registerTeamCitySyncRouter } from "./teamcity-sync-router"; import { registerTeamCitySyncRouter } from "./teamcity-sync-router";
import { registerTerraformCloudSyncRouter } from "./terraform-cloud-sync-router"; import { registerTerraformCloudSyncRouter } from "./terraform-cloud-sync-router";
import { registerVercelSyncRouter } from "./vercel-sync-router"; import { registerVercelSyncRouter } from "./vercel-sync-router";
@@ -1,6 +1,7 @@
import { z } from "zod"; import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { OCIVaultSyncListItemSchema, OCIVaultSyncSchema } from "@app/ee/services/secret-sync/oci-vault";
import { ApiDocsTags, SecretSyncs } from "@app/lib/api-docs"; import { ApiDocsTags, SecretSyncs } from "@app/lib/api-docs";
import { readLimit } from "@app/server/config/rateLimiter"; import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@@ -25,7 +26,6 @@ import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github"; import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
import { HCVaultSyncListItemSchema, HCVaultSyncSchema } from "@app/services/secret-sync/hc-vault"; import { HCVaultSyncListItemSchema, HCVaultSyncSchema } from "@app/services/secret-sync/hc-vault";
import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec"; import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec";
import { OCIVaultSyncListItemSchema, OCIVaultSyncSchema } from "@app/services/secret-sync/oci-vault";
import { TeamCitySyncListItemSchema, TeamCitySyncSchema } from "@app/services/secret-sync/teamcity"; import { TeamCitySyncListItemSchema, TeamCitySyncSchema } from "@app/services/secret-sync/teamcity";
import { TerraformCloudSyncListItemSchema, TerraformCloudSyncSchema } from "@app/services/secret-sync/terraform-cloud"; import { TerraformCloudSyncListItemSchema, TerraformCloudSyncSchema } from "@app/services/secret-sync/terraform-cloud";
import { VercelSyncListItemSchema, VercelSyncSchema } from "@app/services/secret-sync/vercel"; import { VercelSyncListItemSchema, VercelSyncSchema } from "@app/services/secret-sync/vercel";
+31 -12
View File
@@ -206,19 +206,18 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
} }
}); });
if (req.body.template) { await server.services.auditLog.createAuditLog({
await server.services.auditLog.createAuditLog({ ...req.auditLogInfo,
...req.auditLogInfo, orgId: req.permission.orgId,
orgId: req.permission.orgId, projectId: project.id,
event: { event: {
type: EventType.APPLY_PROJECT_TEMPLATE, type: EventType.CREATE_PROJECT,
metadata: { metadata: {
template: req.body.template, ...req.body,
projectId: project.id name: req.body.projectName
}
} }
}); }
} });
return { project }; return { project };
} }
@@ -262,6 +261,16 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
actor: req.permission.type 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; return project;
} }
}); });
@@ -341,6 +350,16 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
actorOrgId: req.permission.orgId 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; return project;
} }
}); });
@@ -67,3 +67,8 @@ export enum AWSRegion {
// South America // South America
SA_EAST_1 = "sa-east-1" // Sao Paulo SA_EAST_1 = "sa-east-1" // Sao Paulo
} }
export enum AppConnectionPlanType {
Enterprise = "enterprise",
Regular = "regular"
}
@@ -1,7 +1,13 @@
import { TAppConnections } from "@app/db/schemas/app-connections"; import { TAppConnections } from "@app/db/schemas/app-connections";
import {
getOCIConnectionListItem,
OCIConnectionMethod,
validateOCIConnectionCredentials
} from "@app/ee/services/app-connections/oci";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { generateHash } from "@app/lib/crypto/encryption"; import { generateHash } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { APP_CONNECTION_NAME_MAP } from "@app/services/app-connection/app-connection-maps"; import { APP_CONNECTION_NAME_MAP, APP_CONNECTION_PLAN_MAP } from "@app/services/app-connection/app-connection-maps";
import { import {
transferSqlConnectionCredentialsToPlatform, transferSqlConnectionCredentialsToPlatform,
validateSqlConnectionCredentials validateSqlConnectionCredentials
@@ -13,7 +19,7 @@ import {
OnePassConnectionMethod, OnePassConnectionMethod,
validateOnePassConnectionCredentials validateOnePassConnectionCredentials
} from "./1password"; } from "./1password";
import { AppConnection } from "./app-connection-enums"; // import { AppConnection, AppConnectionPlanType } from "./app-connection-enums";
import { TAppConnectionServiceFactoryDep } from "./app-connection-service"; import { TAppConnectionServiceFactoryDep } from "./app-connection-service";
import { import {
TAppConnection, TAppConnection,
@@ -58,7 +64,6 @@ import {
} from "./humanitec"; } from "./humanitec";
import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnectionCredentials } from "./ldap"; import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnectionCredentials } from "./ldap";
import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql"; import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql";
import { getOCIConnectionListItem, OCIConnectionMethod, validateOCIConnectionCredentials } from "./oci";
import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres"; import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres";
import { import {
getTeamCityConnectionListItem, getTeamCityConnectionListItem,
@@ -266,3 +271,18 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
[AppConnection.OCI]: platformManagedCredentialsNotSupported, [AppConnection.OCI]: platformManagedCredentialsNotSupported,
[AppConnection.OnePass]: 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> = { export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.AWS]: "AWS", [AppConnection.AWS]: "AWS",
@@ -22,3 +22,25 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.OCI]: "OCI", [AppConnection.OCI]: "OCI",
[AppConnection.OnePass]: "1Password" [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 { ForbiddenError, subject } from "@casl/ability";
import { ValidateOCIConnectionCredentialsSchema } from "@app/ee/services/app-connections/oci";
import { ociConnectionService } from "@app/ee/services/app-connections/oci/oci-connection-service";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionAppConnectionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionAppConnectionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { generateHash } from "@app/lib/crypto/encryption"; import { generateHash } from "@app/lib/crypto/encryption";
@@ -9,6 +12,7 @@ import { DiscriminativePick, OrgServiceActor } from "@app/lib/types";
import { import {
decryptAppConnection, decryptAppConnection,
encryptAppConnectionCredentials, encryptAppConnectionCredentials,
enterpriseAppCheck,
getAppConnectionMethodName, getAppConnectionMethodName,
listAppConnectionOptions, listAppConnectionOptions,
TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM, TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM,
@@ -51,8 +55,6 @@ import { ValidateHumanitecConnectionCredentialsSchema } from "./humanitec";
import { humanitecConnectionService } from "./humanitec/humanitec-connection-service"; import { humanitecConnectionService } from "./humanitec/humanitec-connection-service";
import { ValidateLdapConnectionCredentialsSchema } from "./ldap"; import { ValidateLdapConnectionCredentialsSchema } from "./ldap";
import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql"; import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql";
import { ValidateOCIConnectionCredentialsSchema } from "./oci";
import { ociConnectionService } from "./oci/oci-connection-service";
import { ValidatePostgresConnectionCredentialsSchema } from "./postgres"; import { ValidatePostgresConnectionCredentialsSchema } from "./postgres";
import { ValidateTeamCityConnectionCredentialsSchema } from "./teamcity"; import { ValidateTeamCityConnectionCredentialsSchema } from "./teamcity";
import { teamcityConnectionService } from "./teamcity/teamcity-connection-service"; import { teamcityConnectionService } from "./teamcity/teamcity-connection-service";
@@ -67,6 +69,7 @@ export type TAppConnectionServiceFactoryDep = {
appConnectionDAL: TAppConnectionDALFactory; appConnectionDAL: TAppConnectionDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">; kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
}; };
export type TAppConnectionServiceFactory = ReturnType<typeof appConnectionServiceFactory>; export type TAppConnectionServiceFactory = ReturnType<typeof appConnectionServiceFactory>;
@@ -97,7 +100,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
export const appConnectionServiceFactory = ({ export const appConnectionServiceFactory = ({
appConnectionDAL, appConnectionDAL,
permissionService, permissionService,
kmsService kmsService,
licenseService
}: TAppConnectionServiceFactoryDep) => { }: TAppConnectionServiceFactoryDep) => {
const listAppConnectionsByOrg = async (actor: OrgServiceActor, app?: AppConnection) => { const listAppConnectionsByOrg = async (actor: OrgServiceActor, app?: AppConnection) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission(
@@ -194,6 +198,13 @@ export const appConnectionServiceFactory = ({
OrgPermissionSubjects.AppConnections OrgPermissionSubjects.AppConnections
); );
await enterpriseAppCheck(
licenseService,
app,
actor.orgId,
"Failed to create app connection due to plan restriction. Upgrade plan to access enterprise app connections."
);
const validatedCredentials = await validateAppConnectionCredentials({ const validatedCredentials = await validateAppConnectionCredentials({
app, app,
credentials, credentials,
@@ -256,6 +267,13 @@ export const appConnectionServiceFactory = ({
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` }); if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
await enterpriseAppCheck(
licenseService,
appConnection.app as AppConnection,
actor.orgId,
"Failed to update app connection due to plan restriction. Upgrade plan to access enterprise app connections."
);
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission(
actor.type, actor.type,
actor.id, actor.id,
@@ -402,6 +420,13 @@ export const appConnectionServiceFactory = ({
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` }); if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
await enterpriseAppCheck(
licenseService,
app,
actor.orgId,
"Failed to connect app due to plan restriction. Upgrade plan to access enterprise app connections."
);
const { permission: orgPermission } = await permissionService.getOrgPermission( const { permission: orgPermission } = await permissionService.getOrgPermission(
actor.type, actor.type,
actor.id, actor.id,
@@ -471,7 +496,7 @@ export const appConnectionServiceFactory = ({
hcvault: hcVaultConnectionService(connectAppConnectionById), hcvault: hcVaultConnectionService(connectAppConnectionById),
windmill: windmillConnectionService(connectAppConnectionById), windmill: windmillConnectionService(connectAppConnectionById),
teamcity: teamcityConnectionService(connectAppConnectionById), teamcity: teamcityConnectionService(connectAppConnectionById),
oci: ociConnectionService(connectAppConnectionById), oci: ociConnectionService(connectAppConnectionById, licenseService),
onepass: onePassConnectionService(connectAppConnectionById) 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 { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
import { TSqlConnectionConfig } from "@app/services/app-connection/shared/sql/sql-connection-types"; import { TSqlConnectionConfig } from "@app/services/app-connection/shared/sql/sql-connection-types";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
@@ -82,12 +88,6 @@ import {
TValidateLdapConnectionCredentialsSchema TValidateLdapConnectionCredentialsSchema
} from "./ldap"; } from "./ldap";
import { TMsSqlConnection, TMsSqlConnectionInput, TValidateMsSqlConnectionCredentialsSchema } from "./mssql"; import { TMsSqlConnection, TMsSqlConnectionInput, TValidateMsSqlConnectionCredentialsSchema } from "./mssql";
import {
TOCIConnection,
TOCIConnectionConfig,
TOCIConnectionInput,
TValidateOCIConnectionCredentialsSchema
} from "./oci";
import { import {
TPostgresConnection, TPostgresConnection,
TPostgresConnectionInput, TPostgresConnectionInput,
+17 -1
View File
@@ -425,6 +425,21 @@ export const projectDALFactory = (db: TDbClient) => {
return { docs, totalCount: Number(docs?.[0]?.count ?? 0) }; return { docs, totalCount: Number(docs?.[0]?.count ?? 0) };
}; };
const countOfOrgProjects = async (orgId: string | null, tx?: Knex) => {
try {
const doc = await (tx || db.replicaNode())(TableName.Project)
.andWhere((bd) => {
if (orgId) {
void bd.where({ orgId });
}
})
.count();
return Number(doc?.[0]?.count ?? 0);
} catch (error) {
throw new DatabaseError({ error, name: "Count of Org Projects" });
}
};
return { return {
...projectOrm, ...projectOrm,
findUserProjects, findUserProjects,
@@ -437,6 +452,7 @@ export const projectDALFactory = (db: TDbClient) => {
findProjectWithOrg, findProjectWithOrg,
checkProjectUpgradeStatus, checkProjectUpgradeStatus,
getProjectFromSplitId, getProjectFromSplitId,
searchProjects searchProjects,
countOfOrgProjects
}; };
}; };
@@ -27,3 +27,8 @@ export enum SecretSyncImportBehavior {
PrioritizeSource = "prioritize-source", PrioritizeSource = "prioritize-source",
PrioritizeDestination = "prioritize-destination" PrioritizeDestination = "prioritize-destination"
} }
export enum SecretSyncPlanType {
Enterprise = "enterprise",
Regular = "regular"
}
@@ -1,6 +1,9 @@
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import RE2 from "re2"; import RE2 from "re2";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OCI_VAULT_SYNC_LIST_OPTION, OCIVaultSyncFns } from "@app/ee/services/secret-sync/oci-vault";
import { BadRequestError } from "@app/lib/errors";
import { import {
AWS_PARAMETER_STORE_SYNC_LIST_OPTION, AWS_PARAMETER_STORE_SYNC_LIST_OPTION,
AwsParameterStoreSyncFns AwsParameterStoreSyncFns
@@ -11,7 +14,7 @@ import {
} from "@app/services/secret-sync/aws-secrets-manager"; } from "@app/services/secret-sync/aws-secrets-manager";
import { DATABRICKS_SYNC_LIST_OPTION, databricksSyncFactory } from "@app/services/secret-sync/databricks"; import { DATABRICKS_SYNC_LIST_OPTION, databricksSyncFactory } from "@app/services/secret-sync/databricks";
import { GITHUB_SYNC_LIST_OPTION, GithubSyncFns } from "@app/services/secret-sync/github"; import { GITHUB_SYNC_LIST_OPTION, GithubSyncFns } from "@app/services/secret-sync/github";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; import { SecretSync, SecretSyncPlanType } from "@app/services/secret-sync/secret-sync-enums";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { import {
TSecretMap, TSecretMap,
@@ -30,7 +33,7 @@ import { GcpSyncFns } from "./gcp/gcp-sync-fns";
import { HC_VAULT_SYNC_LIST_OPTION, HCVaultSyncFns } from "./hc-vault"; import { HC_VAULT_SYNC_LIST_OPTION, HCVaultSyncFns } from "./hc-vault";
import { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec"; import { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec";
import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns"; import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns";
import { OCI_VAULT_SYNC_LIST_OPTION, OCIVaultSyncFns } from "./oci-vault"; import { SECRET_SYNC_PLAN_MAP } from "./secret-sync-maps";
import { TEAMCITY_SYNC_LIST_OPTION, TeamCitySyncFns } from "./teamcity"; import { TEAMCITY_SYNC_LIST_OPTION, TeamCitySyncFns } from "./teamcity";
import { TERRAFORM_CLOUD_SYNC_LIST_OPTION, TerraformCloudSyncFns } from "./terraform-cloud"; import { TERRAFORM_CLOUD_SYNC_LIST_OPTION, TerraformCloudSyncFns } from "./terraform-cloud";
import { VERCEL_SYNC_LIST_OPTION, VercelSyncFns } from "./vercel"; import { VERCEL_SYNC_LIST_OPTION, VercelSyncFns } from "./vercel";
@@ -336,3 +339,18 @@ export const parseSyncErrorMessage = (err: unknown): string => {
? errorMessage ? errorMessage
: `${errorMessage.substring(0, MAX_MESSAGE_LENGTH - 3)}...`; : `${errorMessage.substring(0, MAX_MESSAGE_LENGTH - 3)}...`;
}; };
export const enterpriseSyncCheck = async (
licenseService: Pick<TLicenseServiceFactory, "getPlan">,
secretSync: SecretSync,
orgId: string,
errorMessage: string
) => {
if (SECRET_SYNC_PLAN_MAP[secretSync] === SecretSyncPlanType.Enterprise) {
const plan = await licenseService.getPlan(orgId);
if (!plan.enterpriseSecretSyncs)
throw new BadRequestError({
message: errorMessage
});
}
};
@@ -1,5 +1,5 @@
import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; import { SecretSync, SecretSyncPlanType } from "@app/services/secret-sync/secret-sync-enums";
export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = { export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
[SecretSync.AWSParameterStore]: "AWS Parameter Store", [SecretSync.AWSParameterStore]: "AWS Parameter Store",
@@ -38,3 +38,21 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
[SecretSync.OCIVault]: AppConnection.OCI, [SecretSync.OCIVault]: AppConnection.OCI,
[SecretSync.OnePass]: AppConnection.OnePass [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 { ProjectMembershipRole, SecretType } from "@app/db/schemas";
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service"; import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore"; import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
@@ -32,7 +33,7 @@ import {
SecretSyncInitialSyncBehavior SecretSyncInitialSyncBehavior
} from "@app/services/secret-sync/secret-sync-enums"; } from "@app/services/secret-sync/secret-sync-enums";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { parseSyncErrorMessage, SecretSyncFns } from "@app/services/secret-sync/secret-sync-fns"; import { enterpriseSyncCheck, parseSyncErrorMessage, SecretSyncFns } from "@app/services/secret-sync/secret-sync-fns";
import { SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps"; import { SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps";
import { import {
SecretSyncAction, SecretSyncAction,
@@ -93,6 +94,7 @@ type TSecretSyncQueueFactoryDep = {
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">; secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">; secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">; resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
}; };
type SecretSyncActionJob = Job< type SecretSyncActionJob = Job<
@@ -133,7 +135,8 @@ export const secretSyncQueueFactory = ({
secretVersionTagDAL, secretVersionTagDAL,
secretVersionV2BridgeDAL, secretVersionV2BridgeDAL,
secretVersionTagV2BridgeDAL, secretVersionTagV2BridgeDAL,
resourceMetadataDAL resourceMetadataDAL,
licenseService
}: TSecretSyncQueueFactoryDep) => { }: TSecretSyncQueueFactoryDep) => {
const appCfg = getConfig(); const appCfg = getConfig();
@@ -323,7 +326,20 @@ export const secretSyncQueueFactory = ({
secretSync: TSecretSyncWithCredentials, secretSync: TSecretSyncWithCredentials,
importBehavior: SecretSyncImportBehavior importBehavior: SecretSyncImportBehavior
): Promise<TSecretMap> => { ): Promise<TSecretMap> => {
const { projectId, environment, folder } = secretSync; const {
projectId,
environment,
folder,
destination,
connection: { orgId }
} = secretSync;
await enterpriseSyncCheck(
licenseService,
destination,
orgId,
"Failed to import secrets due to plan restriction. Upgrade plan to access enterprise secret syncs."
);
if (!environment || !folder) if (!environment || !folder)
throw new Error( throw new Error(
@@ -400,6 +416,13 @@ export const secretSyncQueueFactory = ({
if (!secretSync) throw new Error(`Cannot find secret sync with ID ${syncId}`); if (!secretSync) throw new Error(`Cannot find secret sync with ID ${syncId}`);
await enterpriseSyncCheck(
licenseService,
secretSync.destination as SecretSync,
secretSync.connection.orgId,
"Failed to sync secrets due to plan restriction. Upgrade plan to access enterprise secret syncs."
);
await secretSyncDAL.updateById(syncId, { await secretSyncDAL.updateById(syncId, {
syncStatus: SecretSyncStatus.Running syncStatus: SecretSyncStatus.Running
}); });
@@ -659,6 +682,13 @@ export const secretSyncQueueFactory = ({
if (!secretSync) throw new Error(`Cannot find secret sync with ID ${syncId}`); if (!secretSync) throw new Error(`Cannot find secret sync with ID ${syncId}`);
await enterpriseSyncCheck(
licenseService,
secretSync.destination as SecretSync,
secretSync.connection.orgId,
"Failed to remove secrets due to plan restriction. Upgrade plan to access enterprise secret syncs."
);
await secretSyncDAL.updateById(syncId, { await secretSyncDAL.updateById(syncId, {
removeStatus: SecretSyncStatus.Running removeStatus: SecretSyncStatus.Running
}); });
@@ -1,6 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas"; import { ActionProjectType } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { throwIfMissingSecretReadValueOrDescribePermission } from "@app/ee/services/permission/permission-fns"; import { throwIfMissingSecretReadValueOrDescribePermission } from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { import {
@@ -16,7 +17,7 @@ import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-c
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { listSecretSyncOptions } from "@app/services/secret-sync/secret-sync-fns"; import { enterpriseSyncCheck, listSecretSyncOptions } from "@app/services/secret-sync/secret-sync-fns";
import { import {
SecretSyncStatus, SecretSyncStatus,
TCreateSecretSyncDTO, TCreateSecretSyncDTO,
@@ -49,6 +50,7 @@ type TSecretSyncServiceFactoryDep = {
TSecretSyncQueueFactory, TSecretSyncQueueFactory,
"queueSecretSyncSyncSecretsById" | "queueSecretSyncImportSecretsById" | "queueSecretSyncRemoveSecretsById" "queueSecretSyncSyncSecretsById" | "queueSecretSyncImportSecretsById" | "queueSecretSyncRemoveSecretsById"
>; >;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
}; };
export type TSecretSyncServiceFactory = ReturnType<typeof secretSyncServiceFactory>; export type TSecretSyncServiceFactory = ReturnType<typeof secretSyncServiceFactory>;
@@ -61,7 +63,8 @@ export const secretSyncServiceFactory = ({
appConnectionService, appConnectionService,
projectBotService, projectBotService,
secretSyncQueue, secretSyncQueue,
keyStore keyStore,
licenseService
}: TSecretSyncServiceFactoryDep) => { }: TSecretSyncServiceFactoryDep) => {
const listSecretSyncsByProjectId = async ( const listSecretSyncsByProjectId = async (
{ projectId, destination }: TListSecretSyncsByProjectId, { projectId, destination }: TListSecretSyncsByProjectId,
@@ -191,6 +194,13 @@ export const secretSyncServiceFactory = ({
{ projectId, secretPath, environment, ...params }: TCreateSecretSyncDTO, { projectId, secretPath, environment, ...params }: TCreateSecretSyncDTO,
actor: OrgServiceActor actor: OrgServiceActor
) => { ) => {
await enterpriseSyncCheck(
licenseService,
params.destination,
actor.orgId,
"Failed to create secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs."
);
const { permission: projectPermission } = await permissionService.getProjectPermission({ const { permission: projectPermission } = await permissionService.getProjectPermission({
actor: actor.type, actor: actor.type,
actorId: actor.id, actorId: actor.id,
@@ -260,6 +270,13 @@ export const secretSyncServiceFactory = ({
message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID ${syncId}` message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID ${syncId}`
}); });
await enterpriseSyncCheck(
licenseService,
secretSync.destination as SecretSync,
actor.orgId,
"Failed to update secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs."
);
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor: actor.type, actor: actor.type,
actorId: actor.id, actorId: actor.id,
@@ -408,6 +425,13 @@ export const secretSyncServiceFactory = ({
message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"` message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"`
}); });
await enterpriseSyncCheck(
licenseService,
secretSync.destination as SecretSync,
actor.orgId,
"Failed to trigger secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs."
);
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor: actor.type, actor: actor.type,
actorId: actor.id, actorId: actor.id,
@@ -463,6 +487,13 @@ export const secretSyncServiceFactory = ({
message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"` message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"`
}); });
await enterpriseSyncCheck(
licenseService,
secretSync.destination as SecretSync,
actor.orgId,
"Failed to trigger secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs."
);
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor: actor.type, actor: actor.type,
actorId: actor.id, actorId: actor.id,
@@ -512,6 +543,13 @@ export const secretSyncServiceFactory = ({
message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"` message: `Could not find ${SECRET_SYNC_NAME_MAP[destination]} Sync with ID "${syncId}"`
}); });
await enterpriseSyncCheck(
licenseService,
secretSync.destination as SecretSync,
actor.orgId,
"Failed to trigger secret sync due to plan restriction. Upgrade plan to access enterprise secret syncs."
);
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
actor: actor.type, actor: actor.type,
actorId: actor.id, actorId: actor.id,
@@ -1,6 +1,12 @@
import { Job } from "bullmq"; import { Job } from "bullmq";
import { AuditLogInfo } from "@app/ee/services/audit-log/audit-log-types"; import { AuditLogInfo } from "@app/ee/services/audit-log/audit-log-types";
import {
TOCIVaultSync,
TOCIVaultSyncInput,
TOCIVaultSyncListItem,
TOCIVaultSyncWithCredentials
} from "@app/ee/services/secret-sync/oci-vault";
import { QueueJobs } from "@app/queue"; import { QueueJobs } from "@app/queue";
import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema"; import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema";
import { import {
@@ -73,7 +79,6 @@ import {
THumanitecSyncListItem, THumanitecSyncListItem,
THumanitecSyncWithCredentials THumanitecSyncWithCredentials
} from "./humanitec"; } from "./humanitec";
import { TOCIVaultSync, TOCIVaultSyncInput, TOCIVaultSyncListItem, TOCIVaultSyncWithCredentials } from "./oci-vault";
import { import {
TTeamCitySync, TTeamCitySync,
TTeamCitySyncInput, 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 { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { TokenType } from "@app/services/auth-token/auth-token-types"; import { TokenType } from "@app/services/auth-token/auth-token-types";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
@@ -80,6 +81,17 @@ export const userServiceFactory = ({
const verifyEmailVerificationCode = async (username: string, code: string) => { const verifyEmailVerificationCode = async (username: string, code: string) => {
// akhilmhdh: case sensitive email resolution // akhilmhdh: case sensitive email resolution
const usersByusername = await userDAL.findUserByUsername(username); const usersByusername = await userDAL.findUserByUsername(username);
logger.info(
usersByusername.map((user) => ({
id: user.id,
email: user.email,
username: user.username,
isEmailVerified: user.isEmailVerified
})),
`Verify email users: [username=${username}]`
);
const user = const user =
usersByusername?.length > 1 ? usersByusername.find((el) => el.username === username) : usersByusername?.[0]; usersByusername?.length > 1 ? usersByusername.find((el) => el.username === username) : usersByusername?.[0];
if (!user) throw new NotFoundError({ name: `User with username '${username}' not found` }); if (!user) throw new NotFoundError({ name: `User with username '${username}' not found` });
@@ -70,7 +70,7 @@ description: "Learn how to configure Auth0 OIDC for Infisical SSO."
prior to enforcing OIDC SSO to prevent any unintended issues. prior to enforcing OIDC SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
</Steps> </Steps>
@@ -76,7 +76,7 @@ description: "Learn how to configure Auth0 SAML for Infisical SSO."
Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO. Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
+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. prior to enforcing SAML SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
</Steps> </Steps>
@@ -70,7 +70,7 @@ Prerequisites:
We recommend ensuring that your account is provisioned using the identity provider prior to enforcing OIDC SSO to prevent any unintended issues. We recommend ensuring that your account is provisioned using the identity provider prior to enforcing OIDC SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
@@ -84,7 +84,7 @@ description: "Learn how to configure Google SAML for Infisical SSO."
prior to enforcing SAML SSO to prevent any unintended issues. prior to enforcing SAML SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
@@ -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. Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
<Warning> <Warning>
We recommend ensuring that your account is provisioned the application in JumpCloud We recommend ensuring that your account is provisioned in the application in JumpCloud prior to enforcing SAML SSO to prevent any unintended issues.
prior to enforcing SAML SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
</Steps> </Steps>
@@ -97,7 +97,7 @@ description: "Learn how to configure Keycloak OIDC for Infisical SSO."
prior to enforcing OIDC SSO to prevent any unintended issues. prior to enforcing OIDC SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
</Steps> </Steps>
@@ -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) ![SAML keycloak client mappers user property](../../../images/sso/keycloak/client-mappers-user-property.png)
Set the the **Name** field to **Username**, the **Property** field to **username**, and the **SAML Attribtue Name** to **username**. Set the the **Name** field to **Username**, the **Property** field to **username**, and the **SAML Attribute Name** to **username**.
![SAML keycloak client mappers username](../../../images/sso/keycloak/client-mappers-username.png) ![SAML keycloak client mappers username](../../../images/sso/keycloak/client-mappers-username.png)
@@ -128,7 +128,7 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
prior to enforcing SAML SSO to prevent any unintended issues. prior to enforcing SAML SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
</Steps> </Steps>
+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. Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
<Warning> <Warning>
We recommend ensuring that your account is provisioned the application in Okta We recommend ensuring that your account is provisioned for the application in Okta prior to enforcing SAML SSO to prevent any unintended issues.
prior to enforcing SAML SSO to prevent any unintended issues.
</Warning> </Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
<Info>
In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step> </Step>
</Steps> </Steps>
+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. For enhanced security, Infisical enforces PKCE (Proof Key for Code Exchange) with the OAuth 2.0-based SSO providers and OIDC. This provides additional protection against authorization code interception attacks and strengthens your authentication flow security.
</Info> </Info>
## SSO Break Glass
In the event your SSO provider experiences downtime, and you need to access Infisical, Organization Admins can utilize the Admin Login Portal to bypass SSO enforcement.
This portal is accessible at `/login/admin` (e.g., https://app.infisical.com/login/admin).
<Note>
To bypass SSO for an organization, you must be an **Organization Admin** for that specific organization. This **Organization Admin** role is independent of **Server Admin** status. Being a **Server Admin** alone does not grant permission to use this bypass feature.
</Note>
## FAQ ## FAQ
<AccordionGroup> <AccordionGroup>
<Accordion title="Why does Infisical require additional email verification for users connected via SAML?"> <Accordion title="Why does Infisical require additional email verification for users connected via SAML?">
By default, Infisical Cloud is configured to not trust emails from external By default, Infisical Cloud is configured to not trust emails from external
identity providers to prevent any malicious account takeover attempts via identity providers to prevent any malicious account takeover attempts via
email spoofing. Accordingly, Infisical creates a new user for anyone provisioned email spoofing. Accordingly, Infisical creates a new user for anyone provisioned
through an external identity provider and requires an additional email through an external identity provider and requires an additional email
verification step upon their first login. verification step upon their first login.
If you're running a self-hosted instance of Infisical and would like it to trust emails from external identity providers, If you're running a self-hosted instance of Infisical and would like it to trust emails from external identity providers,
you can configure this behavior in the Server Admin Console. you can configure this behavior in the Server Admin Console.
</Accordion>
</Accordion> <Accordion title="Why do I get redirected to SSO when trying to use the Admin Login Portal?">
You are likely being redirected because you do not have email authentication mode enabled, or you're not an **Organization Admin**. This portal requires **Organization Admin** status and direct credential login (email and password). **Server Admin** status alone is insufficient.
</Accordion>
</AccordionGroup> </AccordionGroup>
@@ -3,6 +3,13 @@ title: "OCI Connection"
description: "Learn how to configure an Oracle Cloud Infrastructure Connection for Infisical." description: "Learn how to configure an Oracle Cloud Infrastructure Connection for Infisical."
--- ---
<Info>
OCI App Connection is a paid feature.
If you're using Infisical Cloud, then it is available under the **Enterprise Tier**. If you're self-hosting Infisical,
then you should contact [email protected] to purchase an enterprise license to use it.
</Info>
Infisical supports the use of [API Signing Key Authentication](https://docs.oracle.com/en-us/iaas/Content/API/Concepts/apisigningkey.htm) to connect with OCI. Infisical supports the use of [API Signing Key Authentication](https://docs.oracle.com/en-us/iaas/Content/API/Concepts/apisigningkey.htm) to connect with OCI.
## Create OCI User ## Create OCI User
@@ -3,6 +3,13 @@ title: "OCI Vault Sync"
description: "Learn how to configure an Oracle Cloud Infrastructure Vault Sync for Infisical." description: "Learn how to configure an Oracle Cloud Infrastructure Vault Sync for Infisical."
--- ---
<Info>
OCI Vault Sync is a paid feature.
If you're using Infisical Cloud, then it is available under the **Enterprise Tier**. If you're self-hosting Infisical,
then you should contact [email protected] to purchase an enterprise license to use it.
</Info>
**Prerequisites:** **Prerequisites:**
- Create an [OCI Connection](/integrations/app-connections/oci) with the required **Secret Sync** permissions - Create an [OCI Connection](/integrations/app-connections/oci) with the required **Secret Sync** permissions
- [Create](https://docs.oracle.com/en-us/iaas/Content/Identity/compartments/To_create_a_compartment.htm) or use an existing OCI Compartment (which the OCI Connection is authorized to access) - [Create](https://docs.oracle.com/en-us/iaas/Content/Identity/compartments/To_create_a_compartment.htm) or use an existing OCI Compartment (which the OCI Connection is authorized to access)
+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> <ParamField query="DB_ROOT_CERT" type="string" default="" optional>
Configure the SSL certificate for securing a Postgres connection by first encoding it in base64. Configure the SSL certificate for securing a Postgres connection by first encoding it in base64.
Use the command below to encode your certificate: Use the following command to encode your certificate: `echo "<certificate>" | base64`
`echo "<certificate>" | base64`
</ParamField> </ParamField>
<ParamField query="DB_READ_REPLICAS" type="string" default="" optional> <ParamField query="DB_READ_REPLICAS" type="string" default="" optional>
@@ -111,10 +110,9 @@ DB_READ_REPLICAS=[{"DB_CONNECTION_URI":""}]
</ParamField> </ParamField>
<ParamField query="DB_ROOT_CERT" type="string" default="" optional> <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. Configure the SSL certificate for securing a Postgres replica connection by first encoding it in base64.
Use the command below to encode your certificate: Use the following command to encode your certificate: `echo "<certificate>" | base64`
`echo "<certificate>" | base64`
If not provided it will use master SSL certificate. If not provided it will use master SSL certificate.
</ParamField> </ParamField>
</Expandable> </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> <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. 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>
<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>
<Accordion title="Twilio SendGrid"> <Accordion title="Twilio SendGrid">
@@ -2,16 +2,23 @@ import { faWrench } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Spinner, Tooltip } from "@app/components/v2"; import { Spinner, Tooltip } from "@app/components/v2";
import { useSubscription } from "@app/context";
import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs"; import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
import { usePopUp } from "@app/hooks";
import { SecretSync, useSecretSyncOptions } from "@app/hooks/api/secretSyncs"; import { SecretSync, useSecretSyncOptions } from "@app/hooks/api/secretSyncs";
import { UpgradePlanModal } from "../license/UpgradePlanModal";
type Props = { type Props = {
onSelect: (destination: SecretSync) => void; onSelect: (destination: SecretSync) => void;
}; };
export const SecretSyncSelect = ({ onSelect }: Props) => { export const SecretSyncSelect = ({ onSelect }: Props) => {
const { subscription } = useSubscription();
const { isPending, data: secretSyncOptions } = useSecretSyncOptions(); const { isPending, data: secretSyncOptions } = useSecretSyncOptions();
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
if (isPending) { if (isPending) {
return ( return (
<div className="flex h-full flex-col items-center justify-center py-2.5"> <div className="flex h-full flex-col items-center justify-center py-2.5">
@@ -23,14 +30,17 @@ export const SecretSyncSelect = ({ onSelect }: Props) => {
return ( return (
<div className="grid grid-cols-4 gap-2"> <div className="grid grid-cols-4 gap-2">
{secretSyncOptions?.map(({ destination }) => { {secretSyncOptions?.map(({ destination, enterprise }) => {
const { image, name } = SECRET_SYNC_MAP[destination]; const { image, name } = SECRET_SYNC_MAP[destination];
return ( return (
<button <button
type="button" type="button"
key={destination} onClick={() =>
onClick={() => onSelect(destination)} enterprise && !subscription.enterpriseSecretSyncs
className="group relative flex h-28 cursor-pointer flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-700 p-4 duration-200 hover:bg-mineshaft-600" ? handlePopUpOpen("upgradePlan")
: onSelect(destination)
}
className="group relative flex h-28 cursor-pointer flex-col items-center justify-center overflow-hidden rounded-md border border-mineshaft-600 bg-mineshaft-700 p-4 duration-200 hover:bg-mineshaft-600"
> >
<img <img
src={`/images/integrations/${image}`} src={`/images/integrations/${image}`}
@@ -45,6 +55,11 @@ export const SecretSyncSelect = ({ onSelect }: Props) => {
</button> </button>
); );
})} })}
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You can use every Secret Sync if you switch to Infisical's Enterprise plan."
/>
<Tooltip <Tooltip
side="bottom" side="bottom"
className="max-w-sm py-4" className="max-w-sm py-4"
+2 -2
View File
@@ -35,7 +35,7 @@ import { OCIConnectionMethod } from "@app/hooks/api/appConnections/types/oci-con
export const APP_CONNECTION_MAP: Record< export const APP_CONNECTION_MAP: Record<
AppConnection, AppConnection,
{ name: string; image: string; size?: number } { name: string; image: string; size?: number; enterprise?: boolean }
> = { > = {
[AppConnection.AWS]: { name: "AWS", image: "Amazon Web Services.png" }, [AppConnection.AWS]: { name: "AWS", image: "Amazon Web Services.png" },
[AppConnection.GitHub]: { name: "GitHub", image: "GitHub.png" }, [AppConnection.GitHub]: { name: "GitHub", image: "GitHub.png" },
@@ -64,7 +64,7 @@ export const APP_CONNECTION_MAP: Record<
[AppConnection.HCVault]: { name: "Hashicorp Vault", image: "Vault.png", size: 65 }, [AppConnection.HCVault]: { name: "Hashicorp Vault", image: "Vault.png", size: 65 },
[AppConnection.LDAP]: { name: "LDAP", image: "LDAP.png", size: 65 }, [AppConnection.LDAP]: { name: "LDAP", image: "LDAP.png", size: 65 },
[AppConnection.TeamCity]: { name: "TeamCity", image: "TeamCity.png" }, [AppConnection.TeamCity]: { name: "TeamCity", image: "TeamCity.png" },
[AppConnection.OCI]: { name: "OCI", image: "Oracle.png" }, [AppConnection.OCI]: { name: "OCI", image: "Oracle.png", enterprise: true },
[AppConnection.OnePass]: { name: "1Password", image: "1Password.png" } [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.CREATE_PROJECT_TEMPLATE]: "Create project template",
[EventType.UPDATE_PROJECT_TEMPLATE]: "Update project template", [EventType.UPDATE_PROJECT_TEMPLATE]: "Update project template",
[EventType.DELETE_PROJECT_TEMPLATE]: "Delete 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_APP_CONNECTIONS]: "List App Connections",
[EventType.GET_AVAILABLE_APP_CONNECTIONS_DETAILS]: "List App Connections Details", [EventType.GET_AVAILABLE_APP_CONNECTIONS_DETAILS]: "List App Connections Details",
[EventType.GET_APP_CONNECTION]: "Get App Connection", [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.ADD_IDENTITY_LDAP_AUTH]: "Attached LDAP Auth to identity",
[EventType.UPDATE_IDENTITY_LDAP_AUTH]: "Updated LDAP Auth for identity", [EventType.UPDATE_IDENTITY_LDAP_AUTH]: "Updated LDAP Auth for identity",
[EventType.GET_IDENTITY_LDAP_AUTH]: "Retrieved 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 } = { export const userAgentTypeToNameMap: { [K in UserAgentType]: string } = {
+7 -2
View File
@@ -131,7 +131,6 @@ export enum EventType {
CREATE_PROJECT_TEMPLATE = "create-project-template", CREATE_PROJECT_TEMPLATE = "create-project-template",
UPDATE_PROJECT_TEMPLATE = "update-project-template", UPDATE_PROJECT_TEMPLATE = "update-project-template",
DELETE_PROJECT_TEMPLATE = "delete-project-template", DELETE_PROJECT_TEMPLATE = "delete-project-template",
APPLY_PROJECT_TEMPLATE = "apply-project-template",
GET_APP_CONNECTIONS = "get-app-connections", GET_APP_CONNECTIONS = "get-app-connections",
GET_AVAILABLE_APP_CONNECTIONS_DETAILS = "get-available-app-connections-details", GET_AVAILABLE_APP_CONNECTIONS_DETAILS = "get-available-app-connections-details",
GET_APP_CONNECTION = "get-app-connection", 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_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_TEAMS = "microsoft-teams-workflow-integration-get-teams",
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET = "microsoft-teams-workflow-integration-get", 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; name: string;
destination: SecretSync; destination: SecretSync;
canImportSecrets: boolean; canImportSecrets: boolean;
enterprise?: boolean;
}; };
export type TSecretSync = export type TSecretSync =
@@ -50,4 +50,6 @@ export type SubscriptionPlan = {
enforceMfa: boolean; enforceMfa: boolean;
projectTemplates: boolean; projectTemplates: boolean;
kmip: boolean; kmip: boolean;
enterpriseSecretSyncs: boolean;
enterpriseAppConnections: boolean;
}; };
@@ -43,6 +43,11 @@ import {
useSubscription, useSubscription,
useWorkspace useWorkspace
} from "@app/context"; } from "@app/context";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
import { OrderByDirection } from "@app/hooks/api/generic/types"; import { OrderByDirection } from "@app/hooks/api/generic/types";
import { useGetKmipClientsByProjectId } from "@app/hooks/api/kmip"; import { useGetKmipClientsByProjectId } from "@app/hooks/api/kmip";
@@ -71,7 +76,14 @@ export const KmipClientTable = () => {
perPage, perPage,
page, page,
setPerPage 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({ const { data, isPending, isFetching } = useGetKmipClientsByProjectId({
projectId, projectId,
@@ -290,7 +302,7 @@ export const KmipClientTable = () => {
page={page} page={page}
perPage={perPage} perPage={perPage}
onChangePage={(newPage) => setPage(newPage)} onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)} onChangePerPage={handlePerPageChange}
/> />
)} )}
{!isPending && kmipClients.length === 0 && ( {!isPending && kmipClients.length === 0 && (
@@ -53,6 +53,11 @@ import {
useWorkspace useWorkspace
} from "@app/context"; } from "@app/context";
import { kmsKeyUsageOptions } from "@app/helpers/kms"; import { kmsKeyUsageOptions } from "@app/helpers/kms";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { usePagination, usePopUp, useResetPageHelper, useTimedReset } from "@app/hooks"; import { usePagination, usePopUp, useResetPageHelper, useTimedReset } from "@app/hooks";
import { useGetCmeksByProjectId, useUpdateCmek } from "@app/hooks/api/cmeks"; import { useGetCmeksByProjectId, useUpdateCmek } from "@app/hooks/api/cmeks";
import { CmekOrderBy, KmsKeyUsage, TCmek } from "@app/hooks/api/cmeks/types"; import { CmekOrderBy, KmsKeyUsage, TCmek } from "@app/hooks/api/cmeks/types";
@@ -100,7 +105,14 @@ export const CmekTable = () => {
perPage, perPage,
page, page,
setPerPage 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({ const { data, isPending, isFetching } = useGetCmeksByProjectId({
projectId, projectId,
@@ -508,7 +520,7 @@ export const CmekTable = () => {
page={page} page={page}
perPage={perPage} perPage={perPage}
onChangePage={(newPage) => setPage(newPage)} onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)} onChangePerPage={handlePerPageChange}
/> />
)} )}
{!isPending && keys.length === 0 && ( {!isPending && keys.length === 0 && (
@@ -34,6 +34,11 @@ import {
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { OrgPermissionGroupActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { OrgPermissionGroupActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { usePagination, useResetPageHelper } from "@app/hooks"; import { usePagination, useResetPageHelper } from "@app/hooks";
import { useGetOrganizationGroups, useGetOrgRoles, useUpdateGroup } from "@app/hooks/api"; import { useGetOrganizationGroups, useGetOrgRoles, useUpdateGroup } from "@app/hooks/api";
import { OrderByDirection } from "@app/hooks/api/generic/types"; import { OrderByDirection } from "@app/hooks/api/generic/types";
@@ -103,7 +108,14 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => {
setOrderBy, setOrderBy,
setOrderDirection, setOrderDirection,
toggleOrderDirection 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 filteredGroups = useMemo(() => {
const filtered = search const filtered = search
@@ -376,7 +388,7 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => {
page={page} page={page}
perPage={perPage} perPage={perPage}
onChangePage={setPage} onChangePage={setPage}
onChangePerPage={setPerPage} onChangePerPage={handlePerPageChange}
/> />
)} )}
{!isPending && !filteredGroups?.length && ( {!isPending && !filteredGroups?.length && (
@@ -42,6 +42,11 @@ import {
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { OrgPermissionIdentityActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { OrgPermissionIdentityActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { usePagination, useResetPageHelper } from "@app/hooks"; import { usePagination, useResetPageHelper } from "@app/hooks";
import { useGetOrgRoles, useSearchIdentities, useUpdateIdentity } from "@app/hooks/api"; import { useGetOrgRoles, useSearchIdentities, useUpdateIdentity } from "@app/hooks/api";
import { OrderByDirection } from "@app/hooks/api/generic/types"; import { OrderByDirection } from "@app/hooks/api/generic/types";
@@ -76,7 +81,15 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
perPage, perPage,
page, page,
setPerPage 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 [filteredRoles, setFilteredRoles] = useState<string[]>([]);
const organizationId = currentOrg?.id || ""; const organizationId = currentOrg?.id || "";
@@ -379,7 +392,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
page={page} page={page}
perPage={perPage} perPage={perPage}
onChangePage={(newPage) => setPage(newPage)} onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)} onChangePerPage={handlePerPageChange}
/> />
)} )}
{!isPending && data && data?.identities.length === 0 && ( {!isPending && data && data?.identities.length === 0 && (
@@ -42,6 +42,11 @@ import {
useSubscription, useSubscription,
useUser useUser
} from "@app/context"; } from "@app/context";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { usePagination, useResetPageHelper } from "@app/hooks"; import { usePagination, useResetPageHelper } from "@app/hooks";
import { import {
useFetchServerStatus, useFetchServerStatus,
@@ -170,7 +175,14 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro
setOrderBy, setOrderBy,
setOrderDirection, setOrderDirection,
toggleOrderDirection 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( const filteredUsers = useMemo(
() => () =>
@@ -513,7 +525,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLinks }: Pro
page={page} page={page}
perPage={perPage} perPage={perPage}
onChangePage={setPage} onChangePage={setPage}
onChangePerPage={setPerPage} onChangePerPage={handlePerPageChange}
/> />
)} )}
{!isMembersLoading && !filteredUsers?.length && ( {!isMembersLoading && !filteredUsers?.length && (
@@ -1,8 +1,11 @@
import { faWrench } from "@fortawesome/free-solid-svg-icons"; import { faWrench } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { Spinner, Tooltip } from "@app/components/v2"; import { Spinner, Tooltip } from "@app/components/v2";
import { useSubscription } from "@app/context";
import { APP_CONNECTION_MAP } from "@app/helpers/appConnections"; import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
import { usePopUp } from "@app/hooks";
import { useAppConnectionOptions } from "@app/hooks/api/appConnections"; import { useAppConnectionOptions } from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums"; import { AppConnection } from "@app/hooks/api/appConnections/enums";
@@ -11,8 +14,11 @@ type Props = {
}; };
export const AppConnectionsSelect = ({ onSelect }: Props) => { export const AppConnectionsSelect = ({ onSelect }: Props) => {
const { subscription } = useSubscription();
const { isPending, data: appConnectionOptions } = useAppConnectionOptions(); const { isPending, data: appConnectionOptions } = useAppConnectionOptions();
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
if (isPending) { if (isPending) {
return ( return (
<div className="flex h-full flex-col items-center justify-center py-2.5"> <div className="flex h-full flex-col items-center justify-center py-2.5">
@@ -25,13 +31,16 @@ export const AppConnectionsSelect = ({ onSelect }: Props) => {
return ( return (
<div className="grid grid-cols-4 gap-2"> <div className="grid grid-cols-4 gap-2">
{appConnectionOptions?.map((option) => { {appConnectionOptions?.map((option) => {
const { image, name, size = 50 } = APP_CONNECTION_MAP[option.app]; const { image, name, size = 50, enterprise = false } = APP_CONNECTION_MAP[option.app];
return ( return (
<button <button
type="button" type="button"
key={option.app} onClick={() =>
onClick={() => onSelect(option.app)} enterprise && !subscription.enterpriseAppConnections
? handlePopUpOpen("upgradePlan")
: onSelect(option.app)
}
className="group relative flex h-28 cursor-pointer flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-700 p-4 duration-200 hover:bg-mineshaft-600" className="group relative flex h-28 cursor-pointer flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-700 p-4 duration-200 hover:bg-mineshaft-600"
> >
<img <img
@@ -48,6 +57,11 @@ export const AppConnectionsSelect = ({ onSelect }: Props) => {
</button> </button>
); );
})} })}
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You can use every App Connection if you switch to Infisical's Enterprise plan."
/>
<Tooltip <Tooltip
side="bottom" side="bottom"
className="max-w-sm py-4" className="max-w-sm py-4"
@@ -30,6 +30,11 @@ import {
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; 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 { usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
import { TAppConnection, useListAppConnections } from "@app/hooks/api/appConnections"; import { TAppConnection, useListAppConnections } from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums"; import { AppConnection } from "@app/hooks/api/appConnections/enums";
@@ -76,7 +81,14 @@ export const AppConnectionsTable = () => {
orderBy, orderBy,
setOrderDirection, setOrderDirection,
setOrderBy 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( const filteredAppConnections = useMemo(
() => () =>
@@ -282,7 +294,7 @@ export const AppConnectionsTable = () => {
page={page} page={page}
perPage={perPage} perPage={perPage}
onChangePage={setPage} onChangePage={setPage}
onChangePerPage={setPerPage} onChangePerPage={handlePerPageChange}
/> />
)} )}
{!isPending && !filteredAppConnections?.length && ( {!isPending && !filteredAppConnections?.length && (
@@ -25,6 +25,11 @@ import {
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { OrgPermissionGroupActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { OrgPermissionGroupActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { usePagination, useResetPageHelper } from "@app/hooks"; import { usePagination, useResetPageHelper } from "@app/hooks";
import { useListGroupUsers, useOidcManageGroupMembershipsEnabled } from "@app/hooks/api"; import { useListGroupUsers, useOidcManageGroupMembershipsEnabled } from "@app/hooks/api";
import { OrderByDirection } from "@app/hooks/api/generic/types"; import { OrderByDirection } from "@app/hooks/api/generic/types";
@@ -57,7 +62,14 @@ export const GroupMembersTable = ({ groupId, groupSlug, handlePopUpOpen }: Props
offset, offset,
orderDirection, orderDirection,
toggleOrderDirection 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(); const { currentOrg } = useOrganization();
@@ -163,7 +175,7 @@ export const GroupMembersTable = ({ groupId, groupSlug, handlePopUpOpen }: Props
page={page} page={page}
perPage={perPage} perPage={perPage}
onChangePage={setPage} onChangePage={setPage}
onChangePerPage={setPerPage} onChangePerPage={handlePerPageChange}
/> />
)} )}
{!isPending && !filteredGroupMemberships?.length && ( {!isPending && !filteredGroupMemberships?.length && (
@@ -21,6 +21,11 @@ import {
THead, THead,
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { usePagination, useResetPageHelper } from "@app/hooks"; import { usePagination, useResetPageHelper } from "@app/hooks";
import { useGetIdentityProjectMemberships } from "@app/hooks/api"; import { useGetIdentityProjectMemberships } from "@app/hooks/api";
import { OrderByDirection } from "@app/hooks/api/generic/types"; import { OrderByDirection } from "@app/hooks/api/generic/types";
@@ -53,7 +58,14 @@ export const IdentityProjectsTable = ({ identityId, handlePopUpOpen }: Props) =>
offset, offset,
orderDirection, orderDirection,
toggleOrderDirection 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( const filteredProjectMemberships = useMemo(
() => () =>
@@ -132,7 +144,7 @@ export const IdentityProjectsTable = ({ identityId, handlePopUpOpen }: Props) =>
page={page} page={page}
perPage={perPage} perPage={perPage}
onChangePage={setPage} onChangePage={setPage}
onChangePerPage={setPerPage} onChangePerPage={handlePerPageChange}
/> />
)} )}
{!isPending && !filteredProjectMemberships?.length && ( {!isPending && !filteredProjectMemberships?.length && (
@@ -28,6 +28,11 @@ import {
} from "@app/components/v2"; } from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/context";
import { getProjectHomePage } from "@app/helpers/project"; import { getProjectHomePage } from "@app/helpers/project";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
import { useRequestProjectAccess, useSearchProjects } from "@app/hooks/api"; import { useRequestProjectAccess, useSearchProjects } from "@app/hooks/api";
import { ProjectType, Workspace } from "@app/hooks/api/workspace/types"; import { ProjectType, Workspace } from "@app/hooks/api/workspace/types";
@@ -104,7 +109,15 @@ export const AllProjectView = ({
limit, limit,
toggleOrderDirection, toggleOrderDirection,
orderDirection 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([ const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp([
"requestAccessConfirmation" "requestAccessConfirmation"
] as const); ] as const);
@@ -274,7 +287,7 @@ export const AllProjectView = ({
count={searchedProjects?.totalCount || 0} count={searchedProjects?.totalCount || 0}
page={page} page={page}
onChangePage={setPage} onChangePage={setPage}
onChangePerPage={setPerPage} onChangePerPage={handlePerPageChange}
/> />
)} )}
{!isProjectLoading && !searchedProjects?.totalCount && ( {!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 { Button, IconButton, Input, Pagination, Skeleton, Tooltip } from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { getProjectHomePage } from "@app/helpers/project"; import { getProjectHomePage } from "@app/helpers/project";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { usePagination, useResetPageHelper } from "@app/hooks"; import { usePagination, useResetPageHelper } from "@app/hooks";
import { useGetUserWorkspaces } from "@app/hooks/api"; import { useGetUserWorkspaces } from "@app/hooks/api";
import { OrderByDirection } from "@app/hooks/api/generic/types"; import { OrderByDirection } from "@app/hooks/api/generic/types";
@@ -63,7 +68,15 @@ export const MyProjectView = ({
limit, limit,
toggleOrderDirection, toggleOrderDirection,
orderDirection 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 } = const { data: projectFavorites, isPending: isProjectFavoritesLoading } =
useGetUserProjectFavorites(currentOrg?.id); useGetUserProjectFavorites(currentOrg?.id);
@@ -415,7 +428,7 @@ export const MyProjectView = ({
count={filteredWorkspaces.length} count={filteredWorkspaces.length}
page={page} page={page}
onChangePage={setPage} onChangePage={setPage}
onChangePerPage={setPerPage} onChangePerPage={handlePerPageChange}
/> />
)} )}
{isWorkspaceEmpty && ( {isWorkspaceEmpty && (
@@ -13,6 +13,11 @@ import {
useOrganization, useOrganization,
useServerConfig useServerConfig
} from "@app/context"; } from "@app/context";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { withPermission } from "@app/hoc"; import { withPermission } from "@app/hoc";
import { usePagination, usePopUp } from "@app/hooks"; import { usePagination, usePopUp } from "@app/hooks";
import { import {
@@ -28,8 +33,6 @@ import { SecretScanningFilter } from "./components/SecretScanningFilters";
import { SecretScanningFilterFormData, secretScanningFilterFormSchema } from "./components/types"; import { SecretScanningFilterFormData, secretScanningFilterFormSchema } from "./components/types";
import { SecretScanningLogsTable } from "./components"; import { SecretScanningLogsTable } from "./components";
const PER_PAGE_INIT = 25;
export const SecretScanningPage = withPermission( export const SecretScanningPage = withPermission(
() => { () => {
const queryParams = useSearch({ const queryParams = useSearch({
@@ -49,9 +52,14 @@ export const SecretScanningPage = withPermission(
const { offset, limit, orderBy, setPage, perPage, page, setPerPage } = usePagination( const { offset, limit, orderBy, setPage, perPage, page, setPerPage } = usePagination(
SecretScanningOrderBy.CreatedAt, 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 repositoryNames = watch("repositoryNames");
const resolvedStatus = watch("resolved"); const resolvedStatus = watch("resolved");
@@ -180,7 +188,7 @@ export const SecretScanningPage = withPermission(
</div> </div>
)} )}
</div> </div>
<div className="mt-8 space-y-3"> <div className="mt-8 space-y-2">
{integrationEnabled && ( {integrationEnabled && (
<div className="flex w-full items-center justify-end"> <div className="flex w-full items-center justify-end">
<SecretScanningFilter <SecretScanningFilter
@@ -191,18 +199,16 @@ export const SecretScanningPage = withPermission(
</div> </div>
)} )}
<SecretScanningLogsTable gitRisks={risksData?.risks} isPending={isPending} /> <SecretScanningLogsTable gitRisks={risksData?.risks} isPending={isPending} />
{!isPending && {!isPending && risksData?.totalCount !== undefined && risksData.totalCount >= 10 && (
risksData?.totalCount !== undefined && <Pagination
risksData.totalCount >= PER_PAGE_INIT && ( className="rounded-md"
<Pagination count={risksData.totalCount}
className="rounded-md" page={page}
count={risksData.totalCount} perPage={perPage}
page={page} onChangePage={(newPage) => setPage(newPage)}
perPage={perPage} onChangePerPage={handlePerPageChange}
onChangePage={(newPage) => setPage(newPage)} />
onChangePerPage={(newPerPage) => setPerPage(newPerPage)} )}
/>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -129,7 +129,16 @@ export const OrgGeneralAuthSection = () => {
level. level.
</span> </span>
<p className="mt-4"> <p className="mt-4">
In case of a lockout, admins can use the admin login portal at{" "} In case of a lockout, admins can use the{" "}
<a
target="_blank"
className="underline underline-offset-2 hover:text-mineshaft-300"
href="https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal"
rel="noreferrer"
>
Admin Login Portal
</a>{" "}
at{" "}
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
@@ -212,7 +212,16 @@ export const OrgOIDCSection = (): JSX.Element => {
level. level.
</span> </span>
<p className="mt-4"> <p className="mt-4">
In case of a lockout, admins can use the admin login portal at{" "} In case of a lockout, admins can use the{" "}
<a
target="_blank"
className="underline underline-offset-2 hover:text-mineshaft-300"
href="https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal"
rel="noreferrer"
>
Admin Login Portal
</a>{" "}
at{" "}
<a <a
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
@@ -20,6 +20,11 @@ import {
THead, THead,
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { usePagination, useResetPageHelper } from "@app/hooks"; import { usePagination, useResetPageHelper } from "@app/hooks";
import { OrderByDirection } from "@app/hooks/api/generic/types"; import { OrderByDirection } from "@app/hooks/api/generic/types";
import { OrgUser } from "@app/hooks/api/types"; import { OrgUser } from "@app/hooks/api/types";
@@ -52,7 +57,14 @@ export const UserGroupsTable = ({ handlePopUpOpen, orgMembership }: Props) => {
offset, offset,
orderDirection, orderDirection,
toggleOrderDirection 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( const filteredGroupMemberships = useMemo(
() => () =>
@@ -119,7 +131,7 @@ export const UserGroupsTable = ({ handlePopUpOpen, orgMembership }: Props) => {
page={page} page={page}
perPage={perPage} perPage={perPage}
onChangePage={setPage} onChangePage={setPage}
onChangePerPage={setPerPage} onChangePerPage={handlePerPageChange}
/> />
)} )}
{!isPending && !filteredGroupMemberships?.length && ( {!isPending && !filteredGroupMemberships?.length && (
@@ -22,6 +22,11 @@ import {
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { useOrganization } from "@app/context"; import { useOrganization } from "@app/context";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { usePagination, useResetPageHelper } from "@app/hooks"; import { usePagination, useResetPageHelper } from "@app/hooks";
import { useGetOrgMembershipProjectMemberships } from "@app/hooks/api"; import { useGetOrgMembershipProjectMemberships } from "@app/hooks/api";
import { OrderByDirection } from "@app/hooks/api/generic/types"; import { OrderByDirection } from "@app/hooks/api/generic/types";
@@ -54,7 +59,14 @@ export const UserProjectsTable = ({ membershipId, handlePopUpOpen }: Props) => {
offset, offset,
orderDirection, orderDirection,
toggleOrderDirection 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( const { data: projectMemberships = [], isPending } = useGetOrgMembershipProjectMemberships(
orgId, orgId,
@@ -136,7 +148,7 @@ export const UserProjectsTable = ({ membershipId, handlePopUpOpen }: Props) => {
page={page} page={page}
perPage={perPage} perPage={perPage}
onChangePage={setPage} onChangePage={setPage}
onChangePerPage={setPerPage} onChangePerPage={handlePerPageChange}
/> />
)} )}
{!isPending && !filteredProjectMemberships?.length && ( {!isPending && !filteredProjectMemberships?.length && (
@@ -27,6 +27,11 @@ import {
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { usePagination, useResetPageHelper } from "@app/hooks"; import { usePagination, useResetPageHelper } from "@app/hooks";
import { useListWorkspaceGroups } from "@app/hooks/api"; import { useListWorkspaceGroups } from "@app/hooks/api";
import { OrderByDirection } from "@app/hooks/api/generic/types"; import { OrderByDirection } from "@app/hooks/api/generic/types";
@@ -62,7 +67,14 @@ export const GroupTable = ({ handlePopUpOpen }: Props) => {
orderDirection, orderDirection,
orderBy, orderBy,
toggleOrderDirection 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( const { data: groupMemberships = [], isPending } = useListWorkspaceGroups(
currentWorkspace?.id || "" currentWorkspace?.id || ""
@@ -183,7 +195,7 @@ export const GroupTable = ({ handlePopUpOpen }: Props) => {
page={page} page={page}
perPage={perPage} perPage={perPage}
onChangePage={setPage} onChangePage={setPage}
onChangePerPage={setPerPage} onChangePerPage={handlePerPageChange}
/> />
)} )}
{!isPending && !filteredGroupMemberships?.length && ( {!isPending && !filteredGroupMemberships?.length && (
@@ -42,6 +42,11 @@ import {
} from "@app/components/v2"; } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { formatProjectRoleName } from "@app/helpers/roles"; import { formatProjectRoleName } from "@app/helpers/roles";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { withProjectPermission } from "@app/hoc"; import { withProjectPermission } from "@app/hoc";
import { usePagination, useResetPageHelper } from "@app/hooks"; import { usePagination, useResetPageHelper } from "@app/hooks";
import { useDeleteIdentityFromWorkspace, useGetWorkspaceIdentityMemberships } from "@app/hooks/api"; import { useDeleteIdentityFromWorkspace, useGetWorkspaceIdentityMemberships } from "@app/hooks/api";
@@ -72,7 +77,14 @@ export const IdentityTab = withProjectPermission(
perPage, perPage,
page, page,
setPerPage 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 ?? ""; const workspaceId = currentWorkspace?.id ?? "";
@@ -403,7 +415,7 @@ export const IdentityTab = withProjectPermission(
page={page} page={page}
perPage={perPage} perPage={perPage}
onChangePage={(newPage) => setPage(newPage)} onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)} onChangePerPage={handlePerPageChange}
/> />
)} )}
{!isPending && data && data?.identityMemberships.length === 0 && ( {!isPending && data && data?.identityMemberships.length === 0 && (
@@ -51,6 +51,11 @@ import {
useWorkspace useWorkspace
} from "@app/context"; } from "@app/context";
import { formatProjectRoleName } from "@app/helpers/roles"; import { formatProjectRoleName } from "@app/helpers/roles";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { usePagination, useResetPageHelper } from "@app/hooks"; import { usePagination, useResetPageHelper } from "@app/hooks";
import { useGetProjectRoles, useGetWorkspaceUsers } from "@app/hooks/api"; import { useGetProjectRoles, useGetWorkspaceUsers } from "@app/hooks/api";
import { OrderByDirection } from "@app/hooks/api/generic/types"; import { OrderByDirection } from "@app/hooks/api/generic/types";
@@ -101,12 +106,12 @@ export const MembersTable = ({ handlePopUpOpen }: Props) => {
setOrderDirection, setOrderDirection,
toggleOrderDirection toggleOrderDirection
} = usePagination<MembersOrderBy>(MembersOrderBy.Name, { } = 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) => { const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage); setPerPage(newPerPage);
localStorage.setItem("PROJECT_MEMBERS_TABLE_PER_PAGE", newPerPage.toString()); setUserTablePreference("projectMembersTable", PreferenceKey.PerPage, newPerPage);
}; };
const { data: members = [], isPending: isMembersLoading } = useGetWorkspaceUsers( const { data: members = [], isPending: isMembersLoading } = useGetWorkspaceUsers(
@@ -32,6 +32,11 @@ import {
Tooltip, Tooltip,
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { usePagination, useResetPageHelper } from "@app/hooks"; import { usePagination, useResetPageHelper } from "@app/hooks";
import { OrderByDirection } from "@app/hooks/api/generic/types"; import { OrderByDirection } from "@app/hooks/api/generic/types";
import { useSyncIntegration } from "@app/hooks/api/integrations/queries"; import { useSyncIntegration } from "@app/hooks/api/integrations/queries";
@@ -110,7 +115,14 @@ export const IntegrationsTable = ({
orderBy, orderBy,
setOrderDirection, setOrderDirection,
setOrderBy 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(() => { useEffect(() => {
if (integrations?.some((integration) => integration.isSynced === false)) if (integrations?.some((integration) => integration.isSynced === false))
@@ -437,7 +449,7 @@ export const IntegrationsTable = ({
page={page} page={page}
perPage={perPage} perPage={perPage}
onChangePage={setPage} onChangePage={setPage}
onChangePerPage={setPerPage} onChangePerPage={handlePerPageChange}
/> />
)} )}
{!isLoading && !filteredIntegrations?.length && ( {!isLoading && !filteredIntegrations?.length && (
@@ -38,6 +38,11 @@ import {
} from "@app/components/v2"; } from "@app/components/v2";
import { useWorkspace } from "@app/context"; import { useWorkspace } from "@app/context";
import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs"; import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
import { OrderByDirection } from "@app/hooks/api/generic/types"; import { OrderByDirection } from "@app/hooks/api/generic/types";
import { import {
@@ -119,7 +124,14 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => {
orderBy, orderBy,
setOrderDirection, setOrderDirection,
setOrderBy 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( const filteredSecretSyncs = useMemo(
() => () =>
@@ -465,7 +477,7 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => {
page={page} page={page}
perPage={perPage} perPage={perPage}
onChangePage={setPage} onChangePage={setPage}
onChangePerPage={setPerPage} onChangePerPage={handlePerPageChange}
/> />
)} )}
{!filteredSecretSyncs?.length && ( {!filteredSecretSyncs?.length && (
@@ -64,6 +64,11 @@ import {
useWorkspace useWorkspace
} from "@app/context"; } from "@app/context";
import { ProjectPermissionSecretRotationActions } from "@app/context/ProjectPermissionContext/types"; import { ProjectPermissionSecretRotationActions } from "@app/context/ProjectPermissionContext/types";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
import { import {
useCreateFolder, useCreateFolder,
@@ -180,7 +185,14 @@ export const OverviewPage = () => {
page, page,
setPerPage, setPerPage,
orderBy 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(() => { const resetSelectedEntries = useCallback(() => {
setSelectedEntries({ setSelectedEntries({
@@ -1416,7 +1428,7 @@ export const OverviewPage = () => {
page={page} page={page}
perPage={perPage} perPage={perPage}
onChangePage={(newPage) => setPage(newPage)} onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)} onChangePerPage={handlePerPageChange}
/> />
)} )}
</div> </div>
@@ -29,6 +29,11 @@ import {
ProjectPermissionSecretActions, ProjectPermissionSecretActions,
ProjectPermissionSecretRotationActions ProjectPermissionSecretRotationActions
} from "@app/context/ProjectPermissionContext/types"; } from "@app/context/ProjectPermissionContext/types";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
import { import {
useGetImportedSecretsSingleEnv, useGetImportedSecretsSingleEnv,
@@ -98,7 +103,14 @@ const Page = () => {
page, page,
setPerPage, setPerPage,
orderBy 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 [snapshotId, setSnapshotId] = useState<string | null>(null);
const isRollbackMode = Boolean(snapshotId); const isRollbackMode = Boolean(snapshotId);
@@ -558,7 +570,7 @@ const Page = () => {
page={page} page={page}
perPage={perPage} perPage={perPage}
onChangePage={(newPage) => setPage(newPage)} onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={(newPerPage) => setPerPage(newPerPage)} onChangePerPage={handlePerPageChange}
/> />
)} )}
<Modal <Modal
@@ -25,9 +25,14 @@ import {
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { usePagination, useResetPageHelper } from "@app/hooks"; import { usePagination, useResetPageHelper } from "@app/hooks";
import { useGetWsTags } from "@app/hooks/api";
import { OrderByDirection } from "@app/hooks/api/generic/types"; import { OrderByDirection } from "@app/hooks/api/generic/types";
import { useGetWsTags } from "@app/hooks/api/tags";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = { type Props = {
@@ -61,7 +66,14 @@ export const SecretTagsTable = ({ handlePopUpOpen }: Props) => {
offset, offset,
orderDirection, orderDirection,
toggleOrderDirection 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( const filteredTags = useMemo(
() => () =>
@@ -151,7 +163,7 @@ export const SecretTagsTable = ({ handlePopUpOpen }: Props) => {
page={page} page={page}
perPage={perPage} perPage={perPage}
onChangePage={setPage} onChangePage={setPage}
onChangePerPage={setPerPage} onChangePerPage={handlePerPageChange}
/> />
)} )}
{!isPending && !filteredTags?.length && ( {!isPending && !filteredTags?.length && (