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` });
@@ -14,7 +14,7 @@ description: "Learn how to configure Auth0 OIDC for Infisical SSO."
<Step title="Setup application in Auth0"> <Step title="Setup application in Auth0">
1.1. From the Application's Page, navigate to the settings tab of the Auth0 application you want to integrate with Infisical. 1.1. From the Application's Page, navigate to the settings tab of the Auth0 application you want to integrate with Infisical.
![OIDC auth0 list of applications](../../../images/sso/auth0-oidc/application-settings.png) ![OIDC auth0 list of applications](../../../images/sso/auth0-oidc/application-settings.png)
1.2. In the Application URIs section, set the **Application Login URI** and **Allowed Web Origins** fields to `https://app.infisical.com` and the **Allowed Callback URL** field to `https://app.infisical.com/api/v1/sso/oidc/callback`. 1.2. In the Application URIs section, set the **Application Login URI** and **Allowed Web Origins** fields to `https://app.infisical.com` and the **Allowed Callback URL** field to `https://app.infisical.com/api/v1/sso/oidc/callback`.
![OIDC auth0 create application uris](../../../images/sso/auth0-oidc/application-uris.png) ![OIDC auth0 create application uris](../../../images/sso/auth0-oidc/application-uris.png)
![OIDC auth0 create application origin](../../../images/sso/auth0-oidc/application-origin.png) ![OIDC auth0 create application origin](../../../images/sso/auth0-oidc/application-origin.png)
@@ -70,7 +70,7 @@ description: "Learn how to configure Auth0 OIDC for Infisical SSO."
prior to enforcing OIDC SSO to prevent any unintended issues. prior to enforcing OIDC SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
</Steps> </Steps>
+14 -14
View File
@@ -23,30 +23,30 @@ description: "Learn how to configure Auth0 SAML for Infisical SSO."
</Step> </Step>
<Step title="Create a SAML application in Auth0"> <Step title="Create a SAML application in Auth0">
2.1. In your Auth0 account, head to Applications and create an application. 2.1. In your Auth0 account, head to Applications and create an application.
![Auth0 SAML app creation](../../../images/sso/auth0-saml/create-application.png) ![Auth0 SAML app creation](../../../images/sso/auth0-saml/create-application.png)
Select **Regular Web Application** and press **Create**. Select **Regular Web Application** and press **Create**.
![Auth0 SAML app creation](../../../images/sso/auth0-saml/create-application-2.png) ![Auth0 SAML app creation](../../../images/sso/auth0-saml/create-application-2.png)
2.2. In the Application head to Settings > Application URIs and add the **Application Callback URL** from step 1 into the **Allowed Callback URLs** field. 2.2. In the Application head to Settings > Application URIs and add the **Application Callback URL** from step 1 into the **Allowed Callback URLs** field.
![Auth0 SAML allowed callback URLs](../../../images/sso/auth0-saml/auth0-config.png) ![Auth0 SAML allowed callback URLs](../../../images/sso/auth0-saml/auth0-config.png)
2.3. In the Application head to Addons > SAML2 Web App and copy the **Issuer**, **Identity Provider Login URL**, and **Identity Provider Certificate** from the **Usage** tab. 2.3. In the Application head to Addons > SAML2 Web App and copy the **Issuer**, **Identity Provider Login URL**, and **Identity Provider Certificate** from the **Usage** tab.
![Auth0 SAML config](../../../images/sso/auth0-saml/auth0-config-2.png) ![Auth0 SAML config](../../../images/sso/auth0-saml/auth0-config-2.png)
2.4. Back in Infisical, set **Issuer**, **Identity Provider Login URL**, and **Certificate** to the corresponding items from step 2.3. 2.4. Back in Infisical, set **Issuer**, **Identity Provider Login URL**, and **Certificate** to the corresponding items from step 2.3.
![Auth0 SAML Infisical config](../../../images/sso/auth0-saml/infisical-config.png) ![Auth0 SAML Infisical config](../../../images/sso/auth0-saml/infisical-config.png)
2.5. Back in Auth0, in the **Settings** tab, set the **Application Callback URL** to the **Application Callback URL** from step 1 2.5. Back in Auth0, in the **Settings** tab, set the **Application Callback URL** to the **Application Callback URL** from step 1
and update the **Settings** field with the JSON under the picture below (replacing `<audience-from-infisical>` with the **Audience** from step 1). and update the **Settings** field with the JSON under the picture below (replacing `<audience-from-infisical>` with the **Audience** from step 1).
![Auth0 SAML config](../../../images/sso/auth0-saml/auth0-config-3.png) ![Auth0 SAML config](../../../images/sso/auth0-saml/auth0-config-3.png)
```json ```json
{ {
"audience": "<audience-from-infisical>", "audience": "<audience-from-infisical>",
@@ -76,7 +76,7 @@ description: "Learn how to configure Auth0 SAML for Infisical SSO."
Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO. Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
@@ -96,4 +96,4 @@ description: "Learn how to configure Auth0 SAML for Infisical SSO."
32`. 32`.
<div class="height:1px;"/> <div class="height:1px;"/>
- `SITE_URL`: The absolute URL of your self-hosted instance of Infisical including the protocol (e.g. https://app.infisical.com) - `SITE_URL`: The absolute URL of your self-hosted instance of Infisical including the protocol (e.g. https://app.infisical.com)
</Note> </Note>
+5 -5
View File
@@ -5,7 +5,7 @@ description: "Learn how to configure Microsoft Entra ID for Infisical SSO."
<Info> <Info>
Azure SAML SSO is a paid feature. Azure SAML SSO is a paid feature.
If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical, If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical,
then you should contact [email protected] to purchase an enterprise license to use it. then you should contact [email protected] to purchase an enterprise license to use it.
</Info> </Info>
@@ -26,7 +26,7 @@ description: "Learn how to configure Microsoft Entra ID for Infisical SSO."
![Azure SAML enterprise applications](../../../images/sso/azure/enterprise-applications.png) ![Azure SAML enterprise applications](../../../images/sso/azure/enterprise-applications.png)
![Azure SAML new application](../../../images/sso/azure/new-application.png) ![Azure SAML new application](../../../images/sso/azure/new-application.png)
On the next screen, press the **+ Create your own application** button. On the next screen, press the **+ Create your own application** button.
Give the application a unique name like Infisical; choose the "Integrate any other application you don't find in the gallery (Non-gallery)" Give the application a unique name like Infisical; choose the "Integrate any other application you don't find in the gallery (Non-gallery)"
option and hit the **Create** button. option and hit the **Create** button.
@@ -89,9 +89,9 @@ description: "Learn how to configure Microsoft Entra ID for Infisical SSO."
</Step> </Step>
<Step title="Assign users in Azure to the application"> <Step title="Assign users in Azure to the application">
Back in Azure, navigate to the **Users and groups** tab and select **+ Add user/group** to assign access to the login with SSO application on a user or group-level. Back in Azure, navigate to the **Users and groups** tab and select **+ Add user/group** to assign access to the login with SSO application on a user or group-level.
![Azure SAML assignment](../../../images/sso/azure/assignment.png) ![Azure SAML assignment](../../../images/sso/azure/assignment.png)
</Step> </Step>
<Step title="Enable SAML SSO in Infisical"> <Step title="Enable SAML SSO in Infisical">
Enabling SAML SSO allows members in your organization to log into Infisical via Azure. Enabling SAML SSO allows members in your organization to log into Infisical via Azure.
@@ -109,7 +109,7 @@ description: "Learn how to configure Microsoft Entra ID for Infisical SSO."
prior to enforcing SAML SSO to prevent any unintended issues. prior to enforcing SAML SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
</Steps> </Steps>
@@ -70,7 +70,7 @@ Prerequisites:
We recommend ensuring that your account is provisioned using the identity provider prior to enforcing OIDC SSO to prevent any unintended issues. We recommend ensuring that your account is provisioned using the identity provider prior to enforcing OIDC SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
@@ -24,21 +24,21 @@ description: "Learn how to configure Google SAML for Infisical SSO."
<Step title="Create a SAML application in Google"> <Step title="Create a SAML application in Google">
2.1. In your [Google Admin console](https://support.google.com/a/answer/182076), head to Menu > Apps > Web and mobile apps and 2.1. In your [Google Admin console](https://support.google.com/a/answer/182076), head to Menu > Apps > Web and mobile apps and
create a **custom SAML app**. create a **custom SAML app**.
![Google SAML app creation](../../../images/sso/google-saml/create-custom-saml-app.png) ![Google SAML app creation](../../../images/sso/google-saml/create-custom-saml-app.png)
2.2. In the **App details** tab, give the application a unique name like Infisical. 2.2. In the **App details** tab, give the application a unique name like Infisical.
![Google SAML app naming](../../../images/sso/google-saml/name-custom-saml-app.png) ![Google SAML app naming](../../../images/sso/google-saml/name-custom-saml-app.png)
2.3. In the **Google Identity Provider details** tab, copy the **SSO URL**, **Entity ID** and **Certificate**. 2.3. In the **Google Identity Provider details** tab, copy the **SSO URL**, **Entity ID** and **Certificate**.
![Google SAML custom app details](../../../images/sso/google-saml/custom-saml-app-config.png) ![Google SAML custom app details](../../../images/sso/google-saml/custom-saml-app-config.png)
2.4. Back in Infisical, set **SSO URL** and **Certificate** to the corresponding items from step 2.3. 2.4. Back in Infisical, set **SSO URL** and **Certificate** to the corresponding items from step 2.3.
![Google SAML Infisical config](../../../images/sso/google-saml/infisical-config.png) ![Google SAML Infisical config](../../../images/sso/google-saml/infisical-config.png)
2.5. Back in the Google Admin console, in the **Service provider details** tab, set the **ACS URL** and **Entity ID** to the corresponding items from step 1. 2.5. Back in the Google Admin console, in the **Service provider details** tab, set the **ACS URL** and **Entity ID** to the corresponding items from step 1.
Also, check the **Signed response** checkbox. Also, check the **Signed response** checkbox.
@@ -84,7 +84,7 @@ description: "Learn how to configure Google SAML for Infisical SSO."
prior to enforcing SAML SSO to prevent any unintended issues. prior to enforcing SAML SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
@@ -5,7 +5,7 @@ description: "Learn how to configure JumpCloud SAML for Infisical SSO."
<Info> <Info>
JumpCloud SAML SSO is a paid feature. JumpCloud SAML SSO is a paid feature.
If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical, If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical,
then you should contact [email protected] to purchase an enterprise license to use it. then you should contact [email protected] to purchase an enterprise license to use it.
</Info> </Info>
@@ -83,13 +83,12 @@ description: "Learn how to configure JumpCloud SAML for Infisical SSO."
To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one JumpCloud user with Infisical; To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one JumpCloud user with Infisical;
Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO. Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
<Warning> <Warning>
We recommend ensuring that your account is provisioned the application in JumpCloud We recommend ensuring that your account is provisioned in the application in JumpCloud prior to enforcing SAML SSO to prevent any unintended issues.
prior to enforcing SAML SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
</Steps> </Steps>
@@ -97,7 +97,7 @@ description: "Learn how to configure Keycloak OIDC for Infisical SSO."
prior to enforcing OIDC SSO to prevent any unintended issues. prior to enforcing OIDC SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
</Steps> </Steps>
@@ -5,7 +5,7 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
<Info> <Info>
Keycloak SAML SSO is a paid feature. Keycloak SAML SSO is a paid feature.
If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical, If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical,
then you should contact [email protected] to purchase an enterprise license to use it. then you should contact [email protected] to purchase an enterprise license to use it.
</Info> </Info>
@@ -13,36 +13,36 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
<Steps> <Steps>
<Step title="Prepare the SAML SSO configuration in Infisical"> <Step title="Prepare the SAML SSO configuration in Infisical">
In Infisical, head to the **Single Sign-On (SSO)** page and select the **General** tab. Click **Connect** for **SAML** under the Connect to an Identity Provider section. Select **Keycloak**, then click **Connect** again. In Infisical, head to the **Single Sign-On (SSO)** page and select the **General** tab. Click **Connect** for **SAML** under the Connect to an Identity Provider section. Select **Keycloak**, then click **Connect** again.
![SSO connect section](../../../images/sso/connect-saml.png) ![SSO connect section](../../../images/sso/connect-saml.png)
Next, copy the **Valid redirect URI** and **SP Entity ID** to use when configuring the Keycloak SAML application. Next, copy the **Valid redirect URI** and **SP Entity ID** to use when configuring the Keycloak SAML application.
![Keycloak SAML initial configuration](../../../images/sso/keycloak/init-config.png) ![Keycloak SAML initial configuration](../../../images/sso/keycloak/init-config.png)
</Step> </Step>
<Step title="Create a SAML client application in Keycloak"> <Step title="Create a SAML client application in Keycloak">
2.1. In your realm, navigate to the **Clients** tab and click **Create client** to create a new client application. 2.1. In your realm, navigate to the **Clients** tab and click **Create client** to create a new client application.
![SAML keycloak list of clients](../../../images/sso/keycloak/clients-list.png) ![SAML keycloak list of clients](../../../images/sso/keycloak/clients-list.png)
<Info> <Info>
You don’t typically need to make a realm dedicated to Infisical. We recommend adding Infisical as a client to your primary realm. You don’t typically need to make a realm dedicated to Infisical. We recommend adding Infisical as a client to your primary realm.
</Info> </Info>
In the General Settings step, set **Client type** to **SAML**, the **Client ID** field to `https://app.infisical.com`, and the **Name** field to a friendly name like **Infisical**. In the General Settings step, set **Client type** to **SAML**, the **Client ID** field to `https://app.infisical.com`, and the **Name** field to a friendly name like **Infisical**.
![SAML keycloak create client general settings](../../../images/sso/keycloak/create-client-general-settings.png) ![SAML keycloak create client general settings](../../../images/sso/keycloak/create-client-general-settings.png)
<Info> <Info>
If you’re self-hosting Infisical, then you will want to replace https://app.infisical.com with your own domain. If you’re self-hosting Infisical, then you will want to replace https://app.infisical.com with your own domain.
</Info> </Info>
Next, in the Login Settings step, set both the **Home URL** field and **Valid redirect URIs** field to the **Valid redirect URI** from step 1 and press **Save**. Next, in the Login Settings step, set both the **Home URL** field and **Valid redirect URIs** field to the **Valid redirect URI** from step 1 and press **Save**.
![SAML keycloak create client login settings](../../../images/sso/keycloak/create-client-login-settings.png) ![SAML keycloak create client login settings](../../../images/sso/keycloak/create-client-login-settings.png)
2.2. Once you've created the client, under its **Settings** tab, make sure to set the following values: 2.2. Once you've created the client, under its **Settings** tab, make sure to set the following values:
- Under **SAML Capabilities**: - Under **SAML Capabilities**:
- Name ID format: email (or username). - Name ID format: email (or username).
- Force name ID format: On. - Force name ID format: On.
@@ -54,59 +54,59 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
- Signature algorithm: RSA_SHA256. - Signature algorithm: RSA_SHA256.
![SAML keycloak client SAML capabilities](../../../images/sso/keycloak/client-saml-capabilities.png) ![SAML keycloak client SAML capabilities](../../../images/sso/keycloak/client-saml-capabilities.png)
![SAML keycloak client signature encryption](../../../images/sso/keycloak/client-signature-encryption.png) ![SAML keycloak client signature encryption](../../../images/sso/keycloak/client-signature-encryption.png)
2.3. Next, navigate to the **Client scopes** tab select the client's dedicated scope. 2.3. Next, navigate to the **Client scopes** tab select the client's dedicated scope.
![SAML keycloak client scopes list](../../../images/sso/keycloak/client-scopes-list.png) ![SAML keycloak client scopes list](../../../images/sso/keycloak/client-scopes-list.png)
Next click **Add predefined mapper**. Next click **Add predefined mapper**.
![SAML keycloak client mappers empty](../../../images/sso/keycloak/client-mappers-empty.png) ![SAML keycloak client mappers empty](../../../images/sso/keycloak/client-mappers-empty.png)
Select the **X500 email**, **X500 givenName**, and **X500 surname** attributes and click **Add**. Select the **X500 email**, **X500 givenName**, and **X500 surname** attributes and click **Add**.
![SAML keycloak client mappers predefined](../../../images/sso/keycloak/client-mappers-predefined.png) ![SAML keycloak client mappers predefined](../../../images/sso/keycloak/client-mappers-predefined.png)
Now click on the **X500 email** mapper and set the **SAML Attribute Name** field to **email**. Now click on the **X500 email** mapper and set the **SAML Attribute Name** field to **email**.
![SAML keycloak client mappers email](../../../images/sso/keycloak/client-mappers-email.png) ![SAML keycloak client mappers email](../../../images/sso/keycloak/client-mappers-email.png)
Repeat the same for **X500 givenName** and **X500 surname** mappers, setting the **SAML Attribute Name** field to **firstName** and **lastName** respectively. Repeat the same for **X500 givenName** and **X500 surname** mappers, setting the **SAML Attribute Name** field to **firstName** and **lastName** respectively.
Next, back in the client scope's **Mappers**, click **Add mapper** and select **by configuration**. Next, back in the client scope's **Mappers**, click **Add mapper** and select **by configuration**.
![SAML keycloak client mappers by configuration](../../../images/sso/keycloak/client-mappers-by-configuration.png) ![SAML keycloak client mappers by configuration](../../../images/sso/keycloak/client-mappers-by-configuration.png)
Select **User Property**. Select **User Property**.
![SAML keycloak client mappers user property](../../../images/sso/keycloak/client-mappers-user-property.png) ![SAML keycloak client mappers user property](../../../images/sso/keycloak/client-mappers-user-property.png)
Set the the **Name** field to **Username**, the **Property** field to **username**, and the **SAML Attribtue Name** to **username**. Set the the **Name** field to **Username**, the **Property** field to **username**, and the **SAML Attribute Name** to **username**.
![SAML keycloak client mappers username](../../../images/sso/keycloak/client-mappers-username.png) ![SAML keycloak client mappers username](../../../images/sso/keycloak/client-mappers-username.png)
Repeat the same for the `id` attribute, setting the **Name** field to **ID**, the **Property** field to **id**, and the **SAML Attribute Name** to **id**. Repeat the same for the `id` attribute, setting the **Name** field to **ID**, the **Property** field to **id**, and the **SAML Attribute Name** to **id**.
![SAML keycloak client mappers id](../../../images/sso/keycloak/client-mappers-id.png) ![SAML keycloak client mappers id](../../../images/sso/keycloak/client-mappers-id.png)
Once you've completed the above steps, the list of mappers should look like this: Once you've completed the above steps, the list of mappers should look like this:
![SAML keycloak client mappers completed](../../../images/sso/keycloak/client-mappers-completed.png) ![SAML keycloak client mappers completed](../../../images/sso/keycloak/client-mappers-completed.png)
</Step> </Step>
<Step title="Retrieve Identity Provider (IdP) Information from Keycloak"> <Step title="Retrieve Identity Provider (IdP) Information from Keycloak">
Back in Keycloak, navigate to Configure > Realm settings > General tab > Endpoints > SAML 2.0 Identity Provider Metadata and copy the IDP URL. This should appear in various places and take the form: `https://keycloak-mysite.com/realms/myrealm/protocol/saml`. Back in Keycloak, navigate to Configure > Realm settings > General tab > Endpoints > SAML 2.0 Identity Provider Metadata and copy the IDP URL. This should appear in various places and take the form: `https://keycloak-mysite.com/realms/myrealm/protocol/saml`.
![SAML keycloak realm SAML metadata](../../../images/sso/keycloak/realm-saml-metadata.png) ![SAML keycloak realm SAML metadata](../../../images/sso/keycloak/realm-saml-metadata.png)
Also, in the **Keys** tab, locate the RS256 key and copy the certificate to use when finishing configuring Keycloak SAML in Infisical. Also, in the **Keys** tab, locate the RS256 key and copy the certificate to use when finishing configuring Keycloak SAML in Infisical.
![SAML keycloak realm settings keys](../../../images/sso/keycloak/realm-settings-keys.png) ![SAML keycloak realm settings keys](../../../images/sso/keycloak/realm-settings-keys.png)
</Step> </Step>
<Step title="Finish configuring SAML in Infisical"> <Step title="Finish configuring SAML in Infisical">
Back in Infisical, set **IDP URL** and **Certificate** to the items from step 3. Also, set the **Client ID** to the `https://app.infisical.com`. Back in Infisical, set **IDP URL** and **Certificate** to the items from step 3. Also, set the **Client ID** to the `https://app.infisical.com`.
Once you've done that, press **Update** to complete the required configuration. Once you've done that, press **Update** to complete the required configuration.
![SAML Okta paste values into Infisical](../../../images/sso/keycloak/idp-values.png) ![SAML Okta paste values into Infisical](../../../images/sso/keycloak/idp-values.png)
@@ -119,7 +119,7 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
<Step title="Enforce SAML SSO in Infisical"> <Step title="Enforce SAML SSO in Infisical">
Enforcing SAML SSO ensures that members in your organization can only access Infisical Enforcing SAML SSO ensures that members in your organization can only access Infisical
by logging into the organization via Keycloak. by logging into the organization via Keycloak.
To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one Keycloak user with Infisical; To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one Keycloak user with Infisical;
Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO. Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
@@ -128,7 +128,7 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
prior to enforcing SAML SSO to prevent any unintended issues. prior to enforcing SAML SSO to prevent any unintended issues.
</Warning> </Warning>
<Info> <Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin. In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info> </Info>
</Step> </Step>
</Steps> </Steps>
@@ -147,4 +147,4 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
32`. 32`.
<div class="height:1px;"/> <div class="height:1px;"/>
- `SITE_URL`: The absolute URL of your self-hosted instance of Infisical including the protocol (e.g. https://app.infisical.com) - `SITE_URL`: The absolute URL of your self-hosted instance of Infisical including the protocol (e.g. https://app.infisical.com)
</Note> </Note>
+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)
+19 -11
View File
@@ -32,7 +32,7 @@ Used to configure platform-specific security and operational settings
<ParamField query="HOST" type="string" default="localhost" optional> <ParamField query="HOST" type="string" default="localhost" optional>
Specifies the network interface Infisical will bind to when accepting incoming connections. Specifies the network interface Infisical will bind to when accepting incoming connections.
By default, Infisical binds to `localhost`, which restricts access to connections from the same machine. By default, Infisical binds to `localhost`, which restricts access to connections from the same machine.
To make the application accessible externally (e.g., for self-hosted deployments), set this to `0.0.0.0`, which tells the server to listen on all network interfaces. To make the application accessible externally (e.g., for self-hosted deployments), set this to `0.0.0.0`, which tells the server to listen on all network interfaces.
@@ -95,9 +95,8 @@ The platform utilizes Postgres to persist all of its data and Redis for caching
</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 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">
@@ -222,7 +230,7 @@ SMTP_FROM_NAME=Infisical
This will be used to verify the email you are sending from. This will be used to verify the email you are sending from.
![Create SES identity](../../images/self-hosting/configuration/email/ses-create-identity.png) ![Create SES identity](../../images/self-hosting/configuration/email/ses-create-identity.png)
<Info> <Info>
If you AWS SES is under sandbox mode, you will only be able to send emails to verified identies. If you AWS SES is under sandbox mode, you will only be able to send emails to verified identies.
</Info> </Info>
</Step> </Step>
<Step title="Create an account and configure AWS SES"> <Step title="Create an account and configure AWS SES">
@@ -388,9 +396,9 @@ SMTP_FROM_NAME=Infisical
</Info> </Info>
</Accordion> </Accordion>
<Accordion title="SMTP2Go"> <Accordion title="SMTP2Go">
1. Create an account and configure [SMTP2Go](https://www.smtp2go.com/) to send emails. 1. Create an account and configure [SMTP2Go](https://www.smtp2go.com/) to send emails.
2. Turn on SMTP authentication 2. Turn on SMTP authentication
``` ```
SMTP_HOST=mail.smtp2go.com SMTP_HOST=mail.smtp2go.com
SMTP_PORT=You can use one of the following ports: 2525, 80, 25, 8025, or 587 SMTP_PORT=You can use one of the following ports: 2525, 80, 25, 8025, or 587
@@ -401,7 +409,7 @@ SMTP_FROM_NAME=Infisical
``` ```
{" "} {" "}
<Note> <Note>
Optional (for TLS/SSL): Optional (for TLS/SSL):
TLS: Available on the same ports (2525, 80, 25, 8025, or 587) TLS: Available on the same ports (2525, 80, 25, 8025, or 587)
@@ -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 && (