feature: add suport for project scoped app connections

This commit is contained in:
Scott Wilson
2025-09-12 17:26:01 -07:00
parent 666ae3bc54
commit 93e5cf4b2b
135 changed files with 2823 additions and 742 deletions
@@ -0,0 +1,44 @@
import { Helmet } from "react-helmet";
import { PageHeader } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import {
ProjectPermissionAppConnectionActions,
ProjectPermissionSub
} from "@app/context/ProjectPermissionContext/types";
import { withProjectPermission } from "@app/hoc";
import { AppConnectionsTable } from "@app/pages/organization/AppConnections/AppConnectionsPage/components";
export const AppConnectionsPage = withProjectPermission(
() => {
const { currentWorkspace } = useWorkspace();
return (
<div className="bg-bunker-800">
<Helmet>
<title>Infisical | App Connections</title>
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
</Helmet>
<div className="flex w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl">
<PageHeader
className="w-full"
title="App Connections"
description="Manage project App Connections"
/>
<AppConnectionsTable
projectId={currentWorkspace.id}
projectType={currentWorkspace.type}
/>
</div>
</div>
</div>
);
},
{
action: ProjectPermissionAppConnectionActions.Read,
subject: ProjectPermissionSub.AppConnections
}
);
@@ -0,0 +1,19 @@
import { createFileRoute } from "@tanstack/react-router";
import { AppConnectionsPage } from "./AppConnectionsPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/cert-management/$projectId/_cert-manager-layout/app-connections"
)({
component: AppConnectionsPage,
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "App Connections"
}
]
};
}
});
@@ -0,0 +1,19 @@
import { createFileRoute } from "@tanstack/react-router";
import { AppConnectionsPage } from "./AppConnectionsPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/app-connections"
)({
component: AppConnectionsPage,
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "App Connections"
}
]
};
}
});
@@ -0,0 +1,19 @@
import { createFileRoute } from "@tanstack/react-router";
import { AppConnectionsPage } from "./AppConnectionsPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/projects/secret-scanning/$projectId/_secret-scanning-layout/app-connections"
)({
component: AppConnectionsPage,
beforeLoad: ({ context }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "App Connections"
}
]
};
}
});
@@ -0,0 +1,19 @@
import { ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types";
import { ConditionsFields } from "./ConditionsFields";
type Props = {
position?: number;
isDisabled?: boolean;
};
export const AppConnectionPermissionConditions = ({ position = 0, isDisabled }: Props) => {
return (
<ConditionsFields
isDisabled={isDisabled}
subject={ProjectPermissionSub.AppConnections}
position={position}
selectOptions={[{ value: "connectionId", label: "Connection ID" }]}
/>
);
};
@@ -23,6 +23,7 @@ export const renderOperatorSelectItems = (type: string) => {
case "secretTags":
return <SelectItem value={PermissionConditionOperators.$IN}>Contains</SelectItem>;
case "identityId":
case "connectionId":
return (
<>
<SelectItem value={PermissionConditionOperators.$EQ}>Equal</SelectItem>
@@ -12,6 +12,7 @@ import {
} from "@app/context";
import {
PermissionConditionOperators,
ProjectPermissionAppConnectionActions,
ProjectPermissionAuditLogsActions,
ProjectPermissionCommitsActions,
ProjectPermissionDynamicSecretActions,
@@ -134,6 +135,14 @@ const SecretScanningConfigPolicyActionSchema = z.object({
[ProjectPermissionSecretScanningConfigActions.Update]: z.boolean().optional()
});
const AppConnectionPolicyActionSchema = z.object({
[ProjectPermissionAppConnectionActions.Create]: z.boolean().optional(),
[ProjectPermissionAppConnectionActions.Read]: z.boolean().optional(),
[ProjectPermissionAppConnectionActions.Edit]: z.boolean().optional(),
[ProjectPermissionAppConnectionActions.Delete]: z.boolean().optional(),
[ProjectPermissionAppConnectionActions.Connect]: z.boolean().optional()
});
const KmipPolicyActionSchema = z.object({
[ProjectPermissionKmipActions.ReadClients]: z.boolean().optional(),
[ProjectPermissionKmipActions.CreateClients]: z.boolean().optional(),
@@ -311,6 +320,12 @@ export const projectRoleFormSchema = z.object({
})
.array()
.default([]),
[ProjectPermissionSub.AppConnections]: AppConnectionPolicyActionSchema.extend({
inverted: z.boolean().optional(),
conditions: ConditionSchema
})
.array()
.default([]),
[ProjectPermissionSub.Commits]: CommitPolicyActionSchema.array().default([]),
[ProjectPermissionSub.Member]: MemberPolicyActionSchema.array().default([]),
@@ -393,7 +408,8 @@ type TConditionalFields =
| ProjectPermissionSub.SecretRotation
| ProjectPermissionSub.Identity
| ProjectPermissionSub.SecretSyncs
| ProjectPermissionSub.SecretEvents;
| ProjectPermissionSub.SecretEvents
| ProjectPermissionSub.AppConnections;
export const isConditionalSubjects = (
subject: ProjectPermissionSub
@@ -408,7 +424,8 @@ export const isConditionalSubjects = (
subject === ProjectPermissionSub.PkiSubscribers ||
subject === ProjectPermissionSub.CertificateTemplates ||
subject === ProjectPermissionSub.SecretSyncs ||
subject === ProjectPermissionSub.SecretEvents;
subject === ProjectPermissionSub.SecretEvents ||
subject === ProjectPermissionSub.AppConnections;
const convertCaslConditionToFormOperator = (caslConditions: TPermissionCondition) => {
const formConditions: z.infer<typeof ConditionSchema> = [];
@@ -515,7 +532,8 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
ProjectPermissionSub.SshCertificates,
ProjectPermissionSub.SshHostGroups,
ProjectPermissionSub.SecretSyncs,
ProjectPermissionSub.SecretEvents
ProjectPermissionSub.SecretEvents,
ProjectPermissionSub.AppConnections
].includes(subject)
) {
// from above statement we are sure it won't be undefined
@@ -654,6 +672,27 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
return;
}
if (subject === ProjectPermissionSub.AppConnections) {
const canCreate = action.includes(ProjectPermissionAppConnectionActions.Create);
const canRead = action.includes(ProjectPermissionAppConnectionActions.Read);
const canEdit = action.includes(ProjectPermissionAppConnectionActions.Edit);
const canDelete = action.includes(ProjectPermissionAppConnectionActions.Delete);
const canConnect = action.includes(ProjectPermissionAppConnectionActions.Connect);
// from above statement we are sure it won't be undefined
formVal[subject]!.push({
[ProjectPermissionAppConnectionActions.Read]: canRead,
[ProjectPermissionAppConnectionActions.Create]: canCreate,
[ProjectPermissionAppConnectionActions.Edit]: canEdit,
[ProjectPermissionAppConnectionActions.Delete]: canDelete,
[ProjectPermissionAppConnectionActions.Connect]: canConnect,
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
inverted
});
return;
}
// for other subjects
const canRead = action.includes(ProjectPermissionActions.Read);
const canEdit = action.includes(ProjectPermissionActions.Edit);
@@ -1597,6 +1636,31 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
value: ProjectPermissionSecretEventActions.SubscribeImportMutations
}
]
},
[ProjectPermissionSub.AppConnections]: {
title: "App Connections",
actions: [
{
label: "Read",
value: ProjectPermissionAppConnectionActions.Read
},
{
label: "Create",
value: ProjectPermissionAppConnectionActions.Create
},
{
label: "Update",
value: ProjectPermissionAppConnectionActions.Edit
},
{
label: "Delete",
value: ProjectPermissionAppConnectionActions.Delete
},
{
label: "Connect",
value: ProjectPermissionAppConnectionActions.Connect
}
]
}
};
@@ -1669,7 +1733,8 @@ export const ProjectTypePermissionSubjects: Record<
...KmsPermissionSubjects(),
...CertificateManagerPermissionSubjects(),
...SshPermissionSubjects(),
...SecretScanningSubject()
...SecretScanningSubject(),
[ProjectPermissionSub.AppConnections]: true
},
[ProjectType.KMS]: {
...SharedPermissionSubjects,
@@ -1677,7 +1742,8 @@ export const ProjectTypePermissionSubjects: Record<
...SecretsManagerPermissionSubjects(),
...CertificateManagerPermissionSubjects(),
...SshPermissionSubjects(),
...SecretScanningSubject()
...SecretScanningSubject(),
[ProjectPermissionSub.AppConnections]: false
},
[ProjectType.CertificateManager]: {
...SharedPermissionSubjects,
@@ -1685,7 +1751,8 @@ export const ProjectTypePermissionSubjects: Record<
...KmsPermissionSubjects(),
...SecretsManagerPermissionSubjects(),
...SshPermissionSubjects(),
...SecretScanningSubject()
...SecretScanningSubject(),
[ProjectPermissionSub.AppConnections]: true
},
[ProjectType.SSH]: {
...SharedPermissionSubjects,
@@ -1693,7 +1760,8 @@ export const ProjectTypePermissionSubjects: Record<
...CertificateManagerPermissionSubjects(),
...KmsPermissionSubjects(),
...SecretsManagerPermissionSubjects(),
...SecretScanningSubject()
...SecretScanningSubject(),
[ProjectPermissionSub.AppConnections]: false
},
[ProjectType.SecretScanning]: {
...SharedPermissionSubjects,
@@ -1701,7 +1769,8 @@ export const ProjectTypePermissionSubjects: Record<
...SshPermissionSubjects(),
...CertificateManagerPermissionSubjects(),
...KmsPermissionSubjects(),
...SecretsManagerPermissionSubjects()
...SecretsManagerPermissionSubjects(),
[ProjectPermissionSub.AppConnections]: true
}
};
@@ -16,6 +16,7 @@ import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { AddPoliciesButton } from "./AddPoliciesButton";
import { AppConnectionPermissionConditions } from "./AppConnectionPermissionConditions";
import { DynamicSecretPermissionConditions } from "./DynamicSecretPermissionConditions";
import { GeneralPermissionConditions } from "./GeneralPermissionConditions";
import { GeneralPermissionPolicies } from "./GeneralPermissionPolicies";
@@ -77,6 +78,10 @@ export const renderConditionalComponents = (
return <SecretEventPermissionConditions isDisabled={isDisabled} />;
}
if (subject === ProjectPermissionSub.AppConnections) {
return <AppConnectionPermissionConditions isDisabled={isDisabled} />;
}
return <GeneralPermissionConditions isDisabled={isDisabled} type={subject} />;
}