mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 17:27:40 +00:00
Merge
This commit is contained in:
Vendored
+1
@@ -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?: {
|
||||||
|
|||||||
+5
-5
@@ -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
-3
@@ -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,
|
||||||
+23
-2
@@ -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 {
|
||||||
+1
-1
@@ -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 = {
|
||||||
|
|||||||
+2
-1
@@ -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
|
||||||
};
|
};
|
||||||
+3
-3
@@ -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";
|
||||||
+2
-1
@@ -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
-1
@@ -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";
|
||||||
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/pe
|
|||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||||
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
||||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||||
@@ -80,6 +81,17 @@ export const userServiceFactory = ({
|
|||||||
const verifyEmailVerificationCode = async (username: string, code: string) => {
|
const verifyEmailVerificationCode = async (username: string, code: string) => {
|
||||||
// akhilmhdh: case sensitive email resolution
|
// akhilmhdh: case sensitive email resolution
|
||||||
const usersByusername = await userDAL.findUserByUsername(username);
|
const usersByusername = await userDAL.findUserByUsername(username);
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
usersByusername.map((user) => ({
|
||||||
|
id: user.id,
|
||||||
|
email: user.email,
|
||||||
|
username: user.username,
|
||||||
|
isEmailVerified: user.isEmailVerified
|
||||||
|
})),
|
||||||
|
`Verify email users: [username=${username}]`
|
||||||
|
);
|
||||||
|
|
||||||
const user =
|
const user =
|
||||||
usersByusername?.length > 1 ? usersByusername.find((el) => el.username === username) : usersByusername?.[0];
|
usersByusername?.length > 1 ? usersByusername.find((el) => el.username === username) : usersByusername?.[0];
|
||||||
if (!user) throw new NotFoundError({ name: `User with username '${username}' not found` });
|
if (!user) throw new NotFoundError({ name: `User with username '${username}' not found` });
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ description: "Learn how to configure Auth0 OIDC for Infisical SSO."
|
|||||||
<Step title="Setup application in Auth0">
|
<Step title="Setup application in Auth0">
|
||||||
1.1. From the Application's Page, navigate to the settings tab of the Auth0 application you want to integrate with Infisical.
|
1.1. From the Application's Page, navigate to the settings tab of the Auth0 application you want to integrate with Infisical.
|
||||||

|

|
||||||
|
|
||||||
1.2. In the Application URIs section, set the **Application Login URI** and **Allowed Web Origins** fields to `https://app.infisical.com` and the **Allowed Callback URL** field to `https://app.infisical.com/api/v1/sso/oidc/callback`.
|
1.2. In the Application URIs section, set the **Application Login URI** and **Allowed Web Origins** fields to `https://app.infisical.com` and the **Allowed Callback URL** field to `https://app.infisical.com/api/v1/sso/oidc/callback`.
|
||||||

|

|
||||||

|

|
||||||
@@ -70,7 +70,7 @@ description: "Learn how to configure Auth0 OIDC for Infisical SSO."
|
|||||||
prior to enforcing OIDC SSO to prevent any unintended issues.
|
prior to enforcing OIDC SSO to prevent any unintended issues.
|
||||||
</Warning>
|
</Warning>
|
||||||
<Info>
|
<Info>
|
||||||
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
|
In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
|
||||||
</Info>
|
</Info>
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|||||||
@@ -23,30 +23,30 @@ description: "Learn how to configure Auth0 SAML for Infisical SSO."
|
|||||||
</Step>
|
</Step>
|
||||||
<Step title="Create a SAML application in Auth0">
|
<Step title="Create a SAML application in Auth0">
|
||||||
2.1. In your Auth0 account, head to Applications and create an application.
|
2.1. In your Auth0 account, head to Applications and create an application.
|
||||||
|
|
||||||

|

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

|

|
||||||
|
|
||||||
2.2. In the Application head to Settings > Application URIs and add the **Application Callback URL** from step 1 into the **Allowed Callback URLs** field.
|
2.2. In the Application head to Settings > Application URIs and add the **Application Callback URL** from step 1 into the **Allowed Callback URLs** field.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
2.3. In the Application head to Addons > SAML2 Web App and copy the **Issuer**, **Identity Provider Login URL**, and **Identity Provider Certificate** from the **Usage** tab.
|
2.3. In the Application head to Addons > SAML2 Web App and copy the **Issuer**, **Identity Provider Login URL**, and **Identity Provider Certificate** from the **Usage** tab.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
2.4. Back in Infisical, set **Issuer**, **Identity Provider Login URL**, and **Certificate** to the corresponding items from step 2.3.
|
2.4. Back in Infisical, set **Issuer**, **Identity Provider Login URL**, and **Certificate** to the corresponding items from step 2.3.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
2.5. Back in Auth0, in the **Settings** tab, set the **Application Callback URL** to the **Application Callback URL** from step 1
|
2.5. Back in Auth0, in the **Settings** tab, set the **Application Callback URL** to the **Application Callback URL** from step 1
|
||||||
and update the **Settings** field with the JSON under the picture below (replacing `<audience-from-infisical>` with the **Audience** from step 1).
|
and update the **Settings** field with the JSON under the picture below (replacing `<audience-from-infisical>` with the **Audience** from step 1).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"audience": "<audience-from-infisical>",
|
"audience": "<audience-from-infisical>",
|
||||||
@@ -76,7 +76,7 @@ description: "Learn how to configure Auth0 SAML for Infisical SSO."
|
|||||||
Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
|
Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
|
||||||
|
|
||||||
<Info>
|
<Info>
|
||||||
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
|
In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
|
||||||
</Info>
|
</Info>
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
@@ -96,4 +96,4 @@ description: "Learn how to configure Auth0 SAML for Infisical SSO."
|
|||||||
32`.
|
32`.
|
||||||
<div class="height:1px;"/>
|
<div class="height:1px;"/>
|
||||||
- `SITE_URL`: The absolute URL of your self-hosted instance of Infisical including the protocol (e.g. https://app.infisical.com)
|
- `SITE_URL`: The absolute URL of your self-hosted instance of Infisical including the protocol (e.g. https://app.infisical.com)
|
||||||
</Note>
|
</Note>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description: "Learn how to configure Microsoft Entra ID for Infisical SSO."
|
|||||||
|
|
||||||
<Info>
|
<Info>
|
||||||
Azure SAML SSO is a paid feature.
|
Azure SAML SSO is a paid feature.
|
||||||
|
|
||||||
If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical,
|
If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical,
|
||||||
then you should contact [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."
|
|||||||

|

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

|

|
||||||
|
|
||||||
On the next screen, press the **+ Create your own application** button.
|
On the next screen, press the **+ Create your own application** button.
|
||||||
Give the application a unique name like Infisical; choose the "Integrate any other application you don't find in the gallery (Non-gallery)"
|
Give the application a unique name like Infisical; choose the "Integrate any other application you don't find in the gallery (Non-gallery)"
|
||||||
option and hit the **Create** button.
|
option and hit the **Create** button.
|
||||||
@@ -89,9 +89,9 @@ description: "Learn how to configure Microsoft Entra ID for Infisical SSO."
|
|||||||
</Step>
|
</Step>
|
||||||
<Step title="Assign users in Azure to the application">
|
<Step title="Assign users in Azure to the application">
|
||||||
Back in Azure, navigate to the **Users and groups** tab and select **+ Add user/group** to assign access to the login with SSO application on a user or group-level.
|
Back in Azure, navigate to the **Users and groups** tab and select **+ Add user/group** to assign access to the login with SSO application on a user or group-level.
|
||||||
|
|
||||||

|

|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Enable SAML SSO in Infisical">
|
<Step title="Enable SAML SSO in Infisical">
|
||||||
Enabling SAML SSO allows members in your organization to log into Infisical via Azure.
|
Enabling SAML SSO allows members in your organization to log into Infisical via Azure.
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ description: "Learn how to configure Microsoft Entra ID for Infisical SSO."
|
|||||||
prior to enforcing SAML SSO to prevent any unintended issues.
|
prior to enforcing SAML SSO to prevent any unintended issues.
|
||||||
</Warning>
|
</Warning>
|
||||||
<Info>
|
<Info>
|
||||||
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
|
In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
|
||||||
</Info>
|
</Info>
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ Prerequisites:
|
|||||||
We recommend ensuring that your account is provisioned using the identity provider prior to enforcing OIDC SSO to prevent any unintended issues.
|
We recommend ensuring that your account is provisioned using the identity provider prior to enforcing OIDC SSO to prevent any unintended issues.
|
||||||
</Warning>
|
</Warning>
|
||||||
<Info>
|
<Info>
|
||||||
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
|
In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
|
||||||
</Info>
|
</Info>
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
|
|||||||
@@ -24,21 +24,21 @@ description: "Learn how to configure Google SAML for Infisical SSO."
|
|||||||
<Step title="Create a SAML application in Google">
|
<Step title="Create a SAML application in Google">
|
||||||
2.1. In your [Google Admin console](https://support.google.com/a/answer/182076), head to Menu > Apps > Web and mobile apps and
|
2.1. In your [Google Admin console](https://support.google.com/a/answer/182076), head to Menu > Apps > Web and mobile apps and
|
||||||
create a **custom SAML app**.
|
create a **custom SAML app**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
2.2. In the **App details** tab, give the application a unique name like Infisical.
|
2.2. In the **App details** tab, give the application a unique name like Infisical.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
2.3. In the **Google Identity Provider details** tab, copy the **SSO URL**, **Entity ID** and **Certificate**.
|
2.3. In the **Google Identity Provider details** tab, copy the **SSO URL**, **Entity ID** and **Certificate**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
2.4. Back in Infisical, set **SSO URL** and **Certificate** to the corresponding items from step 2.3.
|
2.4. Back in Infisical, set **SSO URL** and **Certificate** to the corresponding items from step 2.3.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
2.5. Back in the Google Admin console, in the **Service provider details** tab, set the **ACS URL** and **Entity ID** to the corresponding items from step 1.
|
2.5. Back in the Google Admin console, in the **Service provider details** tab, set the **ACS URL** and **Entity ID** to the corresponding items from step 1.
|
||||||
|
|
||||||
Also, check the **Signed response** checkbox.
|
Also, check the **Signed response** checkbox.
|
||||||
@@ -84,7 +84,7 @@ description: "Learn how to configure Google SAML for Infisical SSO."
|
|||||||
prior to enforcing SAML SSO to prevent any unintended issues.
|
prior to enforcing SAML SSO to prevent any unintended issues.
|
||||||
</Warning>
|
</Warning>
|
||||||
<Info>
|
<Info>
|
||||||
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
|
In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
|
||||||
</Info>
|
</Info>
|
||||||
</Step>
|
</Step>
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ description: "Learn how to configure JumpCloud SAML for Infisical SSO."
|
|||||||
|
|
||||||
<Info>
|
<Info>
|
||||||
JumpCloud SAML SSO is a paid feature.
|
JumpCloud SAML SSO is a paid feature.
|
||||||
|
|
||||||
If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical,
|
If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical,
|
||||||
then you should contact [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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Next, copy the **Valid redirect URI** and **SP Entity ID** to use when configuring the Keycloak SAML application.
|
Next, copy the **Valid redirect URI** and **SP Entity ID** to use when configuring the Keycloak SAML application.
|
||||||
|
|
||||||

|

|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Create a SAML client application in Keycloak">
|
<Step title="Create a SAML client application in Keycloak">
|
||||||
2.1. In your realm, navigate to the **Clients** tab and click **Create client** to create a new client application.
|
2.1. In your realm, navigate to the **Clients** tab and click **Create client** to create a new client application.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<Info>
|
<Info>
|
||||||
You don’t typically need to make a realm dedicated to Infisical. We recommend adding Infisical as a client to your primary realm.
|
You don’t typically need to make a realm dedicated to Infisical. We recommend adding Infisical as a client to your primary realm.
|
||||||
</Info>
|
</Info>
|
||||||
|
|
||||||
In the General Settings step, set **Client type** to **SAML**, the **Client ID** field to `https://app.infisical.com`, and the **Name** field to a friendly name like **Infisical**.
|
In the General Settings step, set **Client type** to **SAML**, the **Client ID** field to `https://app.infisical.com`, and the **Name** field to a friendly name like **Infisical**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<Info>
|
<Info>
|
||||||
If you’re self-hosting Infisical, then you will want to replace https://app.infisical.com with your own domain.
|
If you’re self-hosting Infisical, then you will want to replace https://app.infisical.com with your own domain.
|
||||||
</Info>
|
</Info>
|
||||||
|
|
||||||
Next, in the Login Settings step, set both the **Home URL** field and **Valid redirect URIs** field to the **Valid redirect URI** from step 1 and press **Save**.
|
Next, in the Login Settings step, set both the **Home URL** field and **Valid redirect URIs** field to the **Valid redirect URI** from step 1 and press **Save**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
2.2. Once you've created the client, under its **Settings** tab, make sure to set the following values:
|
2.2. Once you've created the client, under its **Settings** tab, make sure to set the following values:
|
||||||
|
|
||||||
- Under **SAML Capabilities**:
|
- Under **SAML Capabilities**:
|
||||||
- Name ID format: email (or username).
|
- Name ID format: email (or username).
|
||||||
- Force name ID format: On.
|
- Force name ID format: On.
|
||||||
@@ -54,59 +54,59 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
|
|||||||
- Signature algorithm: RSA_SHA256.
|
- Signature algorithm: RSA_SHA256.
|
||||||
|
|
||||||

|

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

|

|
||||||
|
|
||||||
2.3. Next, navigate to the **Client scopes** tab select the client's dedicated scope.
|
2.3. Next, navigate to the **Client scopes** tab select the client's dedicated scope.
|
||||||
|
|
||||||

|

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

|

|
||||||
|
|
||||||
Select the **X500 email**, **X500 givenName**, and **X500 surname** attributes and click **Add**.
|
Select the **X500 email**, **X500 givenName**, and **X500 surname** attributes and click **Add**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Now click on the **X500 email** mapper and set the **SAML Attribute Name** field to **email**.
|
Now click on the **X500 email** mapper and set the **SAML Attribute Name** field to **email**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Repeat the same for **X500 givenName** and **X500 surname** mappers, setting the **SAML Attribute Name** field to **firstName** and **lastName** respectively.
|
Repeat the same for **X500 givenName** and **X500 surname** mappers, setting the **SAML Attribute Name** field to **firstName** and **lastName** respectively.
|
||||||
|
|
||||||
Next, back in the client scope's **Mappers**, click **Add mapper** and select **by configuration**.
|
Next, back in the client scope's **Mappers**, click **Add mapper** and select **by configuration**.
|
||||||
|
|
||||||

|

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

|

|
||||||
|
|
||||||
Set the the **Name** field to **Username**, the **Property** field to **username**, and the **SAML Attribtue Name** to **username**.
|
Set the the **Name** field to **Username**, the **Property** field to **username**, and the **SAML Attribute Name** to **username**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Repeat the same for the `id` attribute, setting the **Name** field to **ID**, the **Property** field to **id**, and the **SAML Attribute Name** to **id**.
|
Repeat the same for the `id` attribute, setting the **Name** field to **ID**, the **Property** field to **id**, and the **SAML Attribute Name** to **id**.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Once you've completed the above steps, the list of mappers should look like this:
|
Once you've completed the above steps, the list of mappers should look like this:
|
||||||
|
|
||||||

|

|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Retrieve Identity Provider (IdP) Information from Keycloak">
|
<Step title="Retrieve Identity Provider (IdP) Information from Keycloak">
|
||||||
Back in Keycloak, navigate to Configure > Realm settings > General tab > Endpoints > SAML 2.0 Identity Provider Metadata and copy the IDP URL. This should appear in various places and take the form: `https://keycloak-mysite.com/realms/myrealm/protocol/saml`.
|
Back in Keycloak, navigate to Configure > Realm settings > General tab > Endpoints > SAML 2.0 Identity Provider Metadata and copy the IDP URL. This should appear in various places and take the form: `https://keycloak-mysite.com/realms/myrealm/protocol/saml`.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Also, in the **Keys** tab, locate the RS256 key and copy the certificate to use when finishing configuring Keycloak SAML in Infisical.
|
Also, in the **Keys** tab, locate the RS256 key and copy the certificate to use when finishing configuring Keycloak SAML in Infisical.
|
||||||
|
|
||||||

|

|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Finish configuring SAML in Infisical">
|
<Step title="Finish configuring SAML in Infisical">
|
||||||
Back in Infisical, set **IDP URL** and **Certificate** to the items from step 3. Also, set the **Client ID** to the `https://app.infisical.com`.
|
Back in Infisical, set **IDP URL** and **Certificate** to the items from step 3. Also, set the **Client ID** to the `https://app.infisical.com`.
|
||||||
|
|
||||||
Once you've done that, press **Update** to complete the required configuration.
|
Once you've done that, press **Update** to complete the required configuration.
|
||||||
|
|
||||||

|

|
||||||
@@ -119,7 +119,7 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
|
|||||||
<Step title="Enforce SAML SSO in Infisical">
|
<Step title="Enforce SAML SSO in Infisical">
|
||||||
Enforcing SAML SSO ensures that members in your organization can only access Infisical
|
Enforcing SAML SSO ensures that members in your organization can only access Infisical
|
||||||
by logging into the organization via Keycloak.
|
by logging into the organization via Keycloak.
|
||||||
|
|
||||||
To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one Keycloak user with Infisical;
|
To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one Keycloak user with Infisical;
|
||||||
Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
|
Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
|
|||||||
prior to enforcing SAML SSO to prevent any unintended issues.
|
prior to enforcing SAML SSO to prevent any unintended issues.
|
||||||
</Warning>
|
</Warning>
|
||||||
<Info>
|
<Info>
|
||||||
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
|
In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
|
||||||
</Info>
|
</Info>
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
@@ -147,4 +147,4 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
|
|||||||
32`.
|
32`.
|
||||||
<div class="height:1px;"/>
|
<div class="height:1px;"/>
|
||||||
- `SITE_URL`: The absolute URL of your self-hosted instance of Infisical including the protocol (e.g. https://app.infisical.com)
|
- `SITE_URL`: The absolute URL of your self-hosted instance of Infisical including the protocol (e.g. https://app.infisical.com)
|
||||||
</Note>
|
</Note>
|
||||||
|
|||||||
@@ -93,13 +93,12 @@ description: "Learn how to configure Okta SAML 2.0 for Infisical SSO."
|
|||||||
Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
|
Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
|
||||||
|
|
||||||
<Warning>
|
<Warning>
|
||||||
We recommend ensuring that your account is provisioned the application in Okta
|
We recommend ensuring that your account is provisioned for the application in Okta prior to enforcing SAML SSO to prevent any unintended issues.
|
||||||
prior to enforcing SAML SSO to prevent any unintended issues.
|
|
||||||
</Warning>
|
</Warning>
|
||||||
<Info>
|
|
||||||
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
|
|
||||||
</Info>
|
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
|
||||||
|
</Info>
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
|
|||||||
@@ -39,18 +39,30 @@ If your required identity provider is not shown in the list above, please reach
|
|||||||
For enhanced security, Infisical enforces PKCE (Proof Key for Code Exchange) with the OAuth 2.0-based SSO providers and OIDC. This provides additional protection against authorization code interception attacks and strengthens your authentication flow security.
|
For enhanced security, Infisical enforces PKCE (Proof Key for Code Exchange) with the OAuth 2.0-based SSO providers and OIDC. This provides additional protection against authorization code interception attacks and strengthens your authentication flow security.
|
||||||
</Info>
|
</Info>
|
||||||
|
|
||||||
|
## SSO Break Glass
|
||||||
|
|
||||||
|
In the event your SSO provider experiences downtime, and you need to access Infisical, Organization Admins can utilize the Admin Login Portal to bypass SSO enforcement.
|
||||||
|
|
||||||
|
This portal is accessible at `/login/admin` (e.g., https://app.infisical.com/login/admin).
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
To bypass SSO for an organization, you must be an **Organization Admin** for that specific organization. This **Organization Admin** role is independent of **Server Admin** status. Being a **Server Admin** alone does not grant permission to use this bypass feature.
|
||||||
|
</Note>
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
<AccordionGroup>
|
<AccordionGroup>
|
||||||
<Accordion title="Why does Infisical require additional email verification for users connected via SAML?">
|
<Accordion title="Why does Infisical require additional email verification for users connected via SAML?">
|
||||||
By default, Infisical Cloud is configured to not trust emails from external
|
By default, Infisical Cloud is configured to not trust emails from external
|
||||||
identity providers to prevent any malicious account takeover attempts via
|
identity providers to prevent any malicious account takeover attempts via
|
||||||
email spoofing. Accordingly, Infisical creates a new user for anyone provisioned
|
email spoofing. Accordingly, Infisical creates a new user for anyone provisioned
|
||||||
through an external identity provider and requires an additional email
|
through an external identity provider and requires an additional email
|
||||||
verification step upon their first login.
|
verification step upon their first login.
|
||||||
|
|
||||||
If you're running a self-hosted instance of Infisical and would like it to trust emails from external identity providers,
|
If you're running a self-hosted instance of Infisical and would like it to trust emails from external identity providers,
|
||||||
you can configure this behavior in the Server Admin Console.
|
you can configure this behavior in the Server Admin Console.
|
||||||
|
</Accordion>
|
||||||
</Accordion>
|
<Accordion title="Why do I get redirected to SSO when trying to use the Admin Login Portal?">
|
||||||
|
You are likely being redirected because you do not have email authentication mode enabled, or you're not an **Organization Admin**. This portal requires **Organization Admin** status and direct credential login (email and password). **Server Admin** status alone is insufficient.
|
||||||
|
</Accordion>
|
||||||
</AccordionGroup>
|
</AccordionGroup>
|
||||||
|
|||||||
@@ -3,6 +3,13 @@ title: "OCI Connection"
|
|||||||
description: "Learn how to configure an Oracle Cloud Infrastructure Connection for Infisical."
|
description: "Learn how to configure an Oracle Cloud Infrastructure Connection for Infisical."
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<Info>
|
||||||
|
OCI App Connection is a paid feature.
|
||||||
|
|
||||||
|
If you're using Infisical Cloud, then it is available under the **Enterprise Tier**. If you're self-hosting Infisical,
|
||||||
|
then you should contact [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)
|
||||||
|
|||||||
@@ -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.
|
||||||

|

|
||||||
<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"
|
||||||
|
|||||||
@@ -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 } = {
|
||||||
|
|||||||
@@ -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 && (
|
||||||
|
|||||||
+14
-2
@@ -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 && (
|
||||||
|
|||||||
+15
-2
@@ -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 && (
|
||||||
|
|||||||
+14
-2
@@ -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 && (
|
||||||
|
|||||||
+17
-3
@@ -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"
|
||||||
|
|||||||
+14
-2
@@ -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 && (
|
||||||
|
|||||||
+14
-2
@@ -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 && (
|
||||||
|
|||||||
+14
-2
@@ -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 && (
|
||||||
|
|||||||
+15
-2
@@ -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 && (
|
||||||
|
|||||||
+15
-2
@@ -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>
|
||||||
|
|||||||
+10
-1
@@ -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"
|
||||||
|
|||||||
+14
-2
@@ -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 && (
|
||||||
|
|||||||
+14
-2
@@ -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 && (
|
||||||
|
|||||||
+14
-2
@@ -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 && (
|
||||||
|
|||||||
+14
-2
@@ -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 && (
|
||||||
|
|||||||
+7
-2
@@ -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(
|
||||||
|
|||||||
+14
-2
@@ -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 && (
|
||||||
|
|||||||
+14
-2
@@ -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
|
||||||
|
|||||||
+15
-3
@@ -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 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user