From 9964d2ecaaeea7de2365bc2f01373a17c0a24d69 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 24 Dec 2024 22:21:33 +0530 Subject: [PATCH] feat: completed switch to virtual for org pages --- .../permissions/OrgPermissionCan.tsx | 28 +- frontend-v2/src/const/routes.ts | 22 +- .../AccessControlPage/AccessControlPage.tsx | 89 +++ .../components/OrgGroupsTab/OrgGroupsTab.tsx | 17 + .../OrgGroupsSection/OrgGroupModal.tsx | 189 +++++ .../OrgGroupsSection/OrgGroupsSection.tsx | 99 +++ .../OrgGroupsSection/OrgGroupsTable.tsx | 395 ++++++++++ .../components/OrgGroupsSection/index.tsx | 1 + .../OrgGroupsTab/components/index.tsx | 1 + .../components/OrgGroupsTab/index.tsx | 1 + .../OrgIdentityTab/OrgIdentityTab.tsx | 17 + .../IdentityAuthMethodModal.tsx | 55 ++ .../IdentityAuthMethodModalContent.tsx | 335 +++++++++ .../IdentitySection/IdentityAwsAuthForm.tsx | 377 ++++++++++ .../IdentitySection/IdentityAzureAuthForm.tsx | 373 ++++++++++ .../IdentitySection/IdentityGcpAuthForm.tsx | 406 +++++++++++ .../IdentitySection/IdentityJwtAuthForm.tsx | 688 ++++++++++++++++++ .../IdentityKubernetesAuthForm.tsx | 446 ++++++++++++ .../IdentitySection/IdentityModal.tsx | 300 ++++++++ .../IdentitySection/IdentityOidcAuthForm.tsx | 527 ++++++++++++++ .../IdentitySection/IdentitySection.tsx | 147 ++++ .../IdentitySection/IdentityTable.tsx | 323 ++++++++ .../IdentitySection/IdentityTokenAuthForm.tsx | 285 ++++++++ .../IdentityTokenAuthTokenModal.tsx | 53 ++ ...IdentityUniversalAuthClientSecretModal.tsx | 344 +++++++++ .../IdentityUniversalAuthForm.tsx | 414 +++++++++++ .../components/IdentitySection/index.tsx | 1 + .../OrgIdentityTab/components/index.tsx | 1 + .../components/OrgIdentityTab/index.tsx | 1 + .../OrgMembersTab/OrgMembersTab.tsx | 17 + .../OrgMembersSection/AddOrgMemberModal.tsx | 321 ++++++++ .../OrgMembersSection/OrgInviteLink.tsx | 55 ++ .../OrgMembersSection/OrgMembersSection.tsx | 182 +++++ .../OrgMembersSection/OrgMembersTable.tsx | 530 ++++++++++++++ .../components/OrgMembersSection/index.tsx | 1 + .../OrgMembersTab/components/index.tsx | 1 + .../components/OrgMembersTab/index.tsx | 1 + .../OrgRoleTabSection/OrgRoleTabSection.tsx | 17 + .../OrgRoleTabSection/OrgRoleTable.tsx | 278 +++++++ .../components/OrgRoleTabSection/index.tsx | 1 + .../AccessControlPage/components/index.tsx | 4 + .../organization/AccessControlPage/route.tsx | 19 + .../organization/AdminPage/AdminPage.tsx | 39 + .../OrgAdminProjects/OrgAdminProjects.tsx | 168 +++++ .../components/OrgAdminProjects/index.tsx | 1 + .../pages/organization/AdminPage/route.tsx | 9 + .../AuditLogsPage/AuditLogsPage.tsx | 24 + .../AuditLogsPage/components/LogsFilter.tsx | 325 +++++++++ .../AuditLogsPage/components/LogsSection.tsx | 115 +++ .../AuditLogsPage/components/LogsTable.tsx | 119 +++ .../AuditLogsPage/components/LogsTableRow.tsx | 101 +++ .../AuditLogsPage/components/index.tsx | 1 + .../AuditLogsPage/components/types.tsx | 37 + .../organization/AuditLogsPage/route.tsx | 9 + .../organization/BillingPage/BillingPage.tsx | 36 + .../BillingCloudTab/BillingCloudTab.tsx | 11 + .../BillingCloudTab/CurrentPlanSection.tsx | 72 ++ .../BillingCloudTab/ManagePlansModal.tsx | 62 ++ .../BillingCloudTab/ManagePlansTable.tsx | 138 ++++ .../BillingCloudTab/PreviewSection.tsx | 172 +++++ .../components/BillingCloudTab/index.tsx | 1 + .../BillingDetailsTab/BillingDetailsTab.tsx | 15 + .../BillingDetailsTab/CompanyNameSection.tsx | 91 +++ .../BillingDetailsTab/InvoiceEmailSection.tsx | 92 +++ .../BillingDetailsTab/PmtMethodsSection.tsx | 47 ++ .../BillingDetailsTab/PmtMethodsTable.tsx | 117 +++ .../BillingDetailsTab/TaxIDModal.tsx | 182 +++++ .../BillingDetailsTab/TaxIDSection.tsx | 42 ++ .../BillingDetailsTab/TaxIDTable.tsx | 139 ++++ .../components/BillingDetailsTab/index.tsx | 1 + .../BillingReceiptsTab/BillingReceiptsTab.tsx | 10 + .../BillingReceiptsTab/InvoicesTable.tsx | 80 ++ .../components/BillingReceiptsTab/index.tsx | 1 + .../BillingSelfHostedTab.tsx | 9 + .../BillingSelfHostedTab/LicensesSection.tsx | 63 ++ .../components/BillingSelfHostedTab/index.tsx | 1 + .../BillingTabGroup/BillingTabGroup.tsx | 42 ++ .../components/BillingTabGroup/index.tsx | 1 + .../BillingPage/components/index.tsx | 1 + .../pages/organization/BillingPage/route.tsx | 9 + .../CertManagerOverviewPage.tsx | 7 + .../CertManagerOverviewPage/route.tsx | 9 + .../GroupDetailsByIDPage.tsx | 207 ++++++ .../components/AddGroupMemberModal.tsx | 172 +++++ .../components/GroupCreateUpdateModal.tsx | 192 +++++ .../components/GroupDetailsSection.tsx | 88 +++ .../GroupMembersSection.tsx | 90 +++ .../GroupMembersSection/GroupMembersTable.tsx | 195 +++++ .../GroupMembershipRow.tsx | 56 ++ .../components/GroupMembersSection/index.tsx | 1 + .../GroupDetailsByIDPage/components/index.tsx | 1 + .../GroupDetailsByIDPage/route.tsx | 9 + .../IdentityDetailsByIDPage.tsx | 366 ++++++++++ .../IdentityAuthenticationSection.tsx | 157 ++++ .../IdentityClientSecrets.tsx | 142 ++++ .../IdentityTokens.tsx | 121 +++ .../IdentityAuthenticationSection/index.tsx | 1 + .../components/IdentityClientSecretModal.tsx | 182 +++++ .../components/IdentityDetailsSection.tsx | 118 +++ .../IdentityAddToProjectModal.tsx | 186 +++++ .../IdentityProjectRow.tsx | 111 +++ .../IdentityProjectsSection.tsx | 92 +++ .../IdentityProjectsTable.tsx | 151 ++++ .../IdentityProjectsSection/index.tsx | 1 + .../components/IdentityTokenListModal.tsx | 270 +++++++ .../components/IdentityTokenModal.tsx | 182 +++++ .../components/index.tsx | 6 + .../IdentityDetailsByIDPage/route.tsx | 9 + .../KmsOverviewPage/KmsOverviewPage.tsx | 5 + .../organization/KmsOverviewPage/route.tsx | 9 + .../organization/NoOrgPage/NoOrgPage.tsx | 31 + .../pages/organization/NoOrgPage/route.tsx | 8 + .../RoleByIDPage/RoleByIDPage.tsx | 186 +++++ .../components/OrgRoleModifySection.utils.ts | 84 +++ .../components/RoleDetailsSection.tsx | 95 +++ .../RoleByIDPage/components/RoleModal.tsx | 206 ++++++ .../OrgPermissionAdminConsoleRow.tsx | 136 ++++ .../OrgRoleWorkspaceRow.tsx | 130 ++++ .../RolePermissionRow.tsx | 229 ++++++ .../RolePermissionsSection.tsx | 183 +++++ .../RolePermissionsSection/index.tsx | 1 + .../RoleByIDPage/components/index.tsx | 3 + .../pages/organization/RoleByIDPage/route.tsx | 9 + .../SecretManagerOverviewPage.tsx | 529 ++++++++++++++ .../SecretManagerOverviewPage/route.tsx | 9 + .../SecretScanningPage/SecretScanningPage.tsx | 144 ++++ .../components/RiskStatusSelection.tsx | 46 ++ .../components/SecretScanningLogsTable.tsx | 91 +++ .../SecretScanningPage/components/index.tsx | 1 + .../organization/SecretScanningPage/route.tsx | 17 + .../SecretSharingPage/SecretSharingPage.tsx | 48 ++ .../components/AddShareSecretModal.tsx | 35 + .../components/ShareSecretSection.tsx | 75 ++ .../components/ShareSecretsRow.tsx | 75 ++ .../components/ShareSecretsTable.tsx | 79 ++ .../SecretSharingPage/components/index.tsx | 1 + .../organization/SecretSharingPage/route.tsx | 9 + .../SettingsPage/SettingsPage.tsx | 32 + .../AuditLogStreamTab/AuditLogStreamForm.tsx | 207 ++++++ .../AuditLogStreamTab/AuditLogStreamTab.tsx | 197 +++++ .../components/AuditLogStreamTab/index.tsx | 1 + .../components/ImportTab/ImportTab.tsx | 58 ++ .../components/EnvKeyPlatformModal.tsx | 118 +++ .../ImportTab/components/GenericDropzone.tsx | 117 +++ .../SelectImportFromPlatformModal.tsx | 101 +++ .../components/ImportTab/index.tsx | 1 + .../ExternalGroupOrgRoleMappings.tsx | 214 ++++++ .../OrgAuthTab/LDAPGroupMapModal.tsx | 293 ++++++++ .../components/OrgAuthTab/LDAPModal.tsx | 371 ++++++++++ .../components/OrgAuthTab/OIDCModal.tsx | 458 ++++++++++++ .../components/OrgAuthTab/OrgAuthTab.tsx | 195 +++++ .../OrgAuthTab/OrgGeneralAuthSection.tsx | 98 +++ .../OrgAuthTab/OrgGenericAuthSection.tsx | 117 +++ .../components/OrgAuthTab/OrgLDAPSection.tsx | 172 +++++ .../components/OrgAuthTab/OrgOIDCSection.tsx | 163 +++++ .../components/OrgAuthTab/OrgSCIMSection.tsx | 118 +++ .../components/OrgAuthTab/OrgSSOSection.tsx | 131 ++++ .../components/OrgAuthTab/SSOModal.tsx | 343 +++++++++ .../components/OrgAuthTab/ScimTokenModal.tsx | 318 ++++++++ .../components/OrgAuthTab/index.tsx | 1 + .../OrgDeleteSection/OrgDeleteSection.tsx | 73 ++ .../components/OrgDeleteSection/index.tsx | 1 + .../OrgEncryptionTab/AddExternalKmsForm.tsx | 116 +++ .../OrgEncryptionTab/AwsKmsForm.tsx | 273 +++++++ .../OrgEncryptionTab/GcpKmsForm.tsx | 360 +++++++++ .../OrgEncryptionTab/OrgEncryptionTab.tsx | 221 ++++++ .../UpdateExternalKmsForm.tsx | 37 + .../components/OrgEncryptionTab/index.tsx | 1 + .../OrgGeneralTab/OrgGeneralTab.tsx | 16 + .../components/OrgGeneralTab/index.tsx | 1 + .../AddOrgIncidentContactModal.tsx | 104 +++ .../OrgIncidentContactsSection.tsx | 53 ++ .../OrgIncidentContactsTable.tsx | 119 +++ .../OrgIncidentContactsSection/index.tsx | 2 + .../OrgNameChangeSection.tsx | 168 +++++ .../components/OrgNameChangeSection/index.tsx | 1 + .../components/OrgTabGroup/OrgTabGroup.tsx | 65 ++ .../components/OrgTabGroup/index.tsx | 1 + .../AddWorkflowIntegrationForm.tsx | 93 +++ .../IntegrationFormDetails.tsx | 26 + .../OrgWorkflowIntegrationTab.tsx | 238 ++++++ .../SlackIntegrationForm.tsx | 139 ++++ .../ProjectTemplatesTab.tsx | 9 + .../components/DeleteProjectTemplateModal.tsx | 49 ++ .../EditProjectTemplateSection.tsx | 55 ++ .../components/EditProjectTemplate.tsx | 119 +++ .../ProjectTemplateEditRoleForm.tsx | 211 ++++++ .../ProjectTemplateEnvironmentsForm.tsx | 281 +++++++ .../ProjectTemplateRolesSection.tsx | 223 ++++++ .../components/index.tsx | 1 + .../EditProjectTemplateSection/index.tsx | 1 + .../ProjectTemplateDetailsModal.tsx | 143 ++++ .../components/ProjectTemplatesSection.tsx | 117 +++ .../components/ProjectTemplatesTable.tsx | 191 +++++ .../ProjectTemplatesTab/components/index.tsx | 1 + .../components/ProjectTemplatesTab/index.tsx | 1 + .../SettingsPage/components/index.tsx | 1 + .../pages/organization/SettingsPage/route.tsx | 16 + .../UserDetailsByIDPage.tsx | 330 +++++++++ .../components/UserDetailsSection.tsx | 228 ++++++ .../components/UserOrgMembershipModal.tsx | 249 +++++++ .../UserAddToProjectModal.tsx | 155 ++++ .../UserAuditLogsSection.tsx | 56 ++ .../UserProjectsSection/UserGroupsRow.tsx | 46 ++ .../UserProjectsSection/UserGroupsSection.tsx | 70 ++ .../UserProjectsSection/UserGroupsTable.tsx | 138 ++++ .../UserProjectsSection/UserProjectRow.tsx | 105 +++ .../UserProjectsSection.tsx | 98 +++ .../UserProjectsSection/UserProjectsTable.tsx | 155 ++++ .../components/UserProjectsSection/index.tsx | 1 + .../UserDetailsByIDPage/components/index.tsx | 3 + .../UserDetailsByIDPage/route.tsx | 9 + frontend-v2/src/pages/organization/layout.tsx | 7 + frontend-v2/src/types/org.ts | 2 +- 214 files changed, 24341 insertions(+), 3 deletions(-) create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/AccessControlPage.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/OrgGroupsTab.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupModal.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsTable.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/components/OrgGroupsSection/index.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/components/index.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/index.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/OrgIdentityTab.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModalContent.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityTokenAuthForm.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityTokenAuthTokenModal.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthClientSecretModal.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/index.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/index.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/index.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgMembersTab/OrgMembersTab.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgMembersTab/components/OrgMembersSection/OrgInviteLink.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgMembersTab/components/OrgMembersSection/index.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgMembersTab/components/index.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgMembersTab/index.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgRoleTabSection/OrgRoleTabSection.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgRoleTabSection/OrgRoleTable.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/OrgRoleTabSection/index.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/components/index.tsx create mode 100644 frontend-v2/src/pages/organization/AccessControlPage/route.tsx create mode 100644 frontend-v2/src/pages/organization/AdminPage/AdminPage.tsx create mode 100644 frontend-v2/src/pages/organization/AdminPage/components/OrgAdminProjects/OrgAdminProjects.tsx create mode 100644 frontend-v2/src/pages/organization/AdminPage/components/OrgAdminProjects/index.tsx create mode 100644 frontend-v2/src/pages/organization/AdminPage/route.tsx create mode 100644 frontend-v2/src/pages/organization/AuditLogsPage/AuditLogsPage.tsx create mode 100644 frontend-v2/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx create mode 100644 frontend-v2/src/pages/organization/AuditLogsPage/components/LogsSection.tsx create mode 100644 frontend-v2/src/pages/organization/AuditLogsPage/components/LogsTable.tsx create mode 100644 frontend-v2/src/pages/organization/AuditLogsPage/components/LogsTableRow.tsx create mode 100644 frontend-v2/src/pages/organization/AuditLogsPage/components/index.tsx create mode 100644 frontend-v2/src/pages/organization/AuditLogsPage/components/types.tsx create mode 100644 frontend-v2/src/pages/organization/AuditLogsPage/route.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/BillingPage.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingCloudTab/BillingCloudTab.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingCloudTab/CurrentPlanSection.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingCloudTab/ManagePlansModal.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingCloudTab/ManagePlansTable.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingCloudTab/PreviewSection.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingCloudTab/index.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingDetailsTab/BillingDetailsTab.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingDetailsTab/CompanyNameSection.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingDetailsTab/InvoiceEmailSection.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingDetailsTab/PmtMethodsSection.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingDetailsTab/PmtMethodsTable.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingDetailsTab/TaxIDModal.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingDetailsTab/TaxIDSection.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingDetailsTab/TaxIDTable.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingDetailsTab/index.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingReceiptsTab/BillingReceiptsTab.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingReceiptsTab/InvoicesTable.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingReceiptsTab/index.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingSelfHostedTab/BillingSelfHostedTab.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingSelfHostedTab/LicensesSection.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingSelfHostedTab/index.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingTabGroup/BillingTabGroup.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/BillingTabGroup/index.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/components/index.tsx create mode 100644 frontend-v2/src/pages/organization/BillingPage/route.tsx create mode 100644 frontend-v2/src/pages/organization/CertManagerOverviewPage/CertManagerOverviewPage.tsx create mode 100644 frontend-v2/src/pages/organization/CertManagerOverviewPage/route.tsx create mode 100644 frontend-v2/src/pages/organization/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx create mode 100644 frontend-v2/src/pages/organization/GroupDetailsByIDPage/components/AddGroupMemberModal.tsx create mode 100644 frontend-v2/src/pages/organization/GroupDetailsByIDPage/components/GroupCreateUpdateModal.tsx create mode 100644 frontend-v2/src/pages/organization/GroupDetailsByIDPage/components/GroupDetailsSection.tsx create mode 100644 frontend-v2/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersSection.tsx create mode 100644 frontend-v2/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx create mode 100644 frontend-v2/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembershipRow.tsx create mode 100644 frontend-v2/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/index.tsx create mode 100644 frontend-v2/src/pages/organization/GroupDetailsByIDPage/components/index.tsx create mode 100644 frontend-v2/src/pages/organization/GroupDetailsByIDPage/route.tsx create mode 100644 frontend-v2/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx create mode 100644 frontend-v2/src/pages/organization/IdentityDetailsByIDPage/components/IdentityAuthenticationSection/IdentityAuthenticationSection.tsx create mode 100644 frontend-v2/src/pages/organization/IdentityDetailsByIDPage/components/IdentityAuthenticationSection/IdentityClientSecrets.tsx create mode 100644 frontend-v2/src/pages/organization/IdentityDetailsByIDPage/components/IdentityAuthenticationSection/IdentityTokens.tsx create mode 100644 frontend-v2/src/pages/organization/IdentityDetailsByIDPage/components/IdentityAuthenticationSection/index.tsx create mode 100644 frontend-v2/src/pages/organization/IdentityDetailsByIDPage/components/IdentityClientSecretModal.tsx create mode 100644 frontend-v2/src/pages/organization/IdentityDetailsByIDPage/components/IdentityDetailsSection.tsx create mode 100644 frontend-v2/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityAddToProjectModal.tsx create mode 100644 frontend-v2/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectRow.tsx create mode 100644 frontend-v2/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectsSection.tsx create mode 100644 frontend-v2/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectsTable.tsx create mode 100644 frontend-v2/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/index.tsx create mode 100644 frontend-v2/src/pages/organization/IdentityDetailsByIDPage/components/IdentityTokenListModal.tsx create mode 100644 frontend-v2/src/pages/organization/IdentityDetailsByIDPage/components/IdentityTokenModal.tsx create mode 100644 frontend-v2/src/pages/organization/IdentityDetailsByIDPage/components/index.tsx create mode 100644 frontend-v2/src/pages/organization/IdentityDetailsByIDPage/route.tsx create mode 100644 frontend-v2/src/pages/organization/KmsOverviewPage/KmsOverviewPage.tsx create mode 100644 frontend-v2/src/pages/organization/KmsOverviewPage/route.tsx create mode 100644 frontend-v2/src/pages/organization/NoOrgPage/NoOrgPage.tsx create mode 100644 frontend-v2/src/pages/organization/NoOrgPage/route.tsx create mode 100644 frontend-v2/src/pages/organization/RoleByIDPage/RoleByIDPage.tsx create mode 100644 frontend-v2/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts create mode 100644 frontend-v2/src/pages/organization/RoleByIDPage/components/RoleDetailsSection.tsx create mode 100644 frontend-v2/src/pages/organization/RoleByIDPage/components/RoleModal.tsx create mode 100644 frontend-v2/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgPermissionAdminConsoleRow.tsx create mode 100644 frontend-v2/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgRoleWorkspaceRow.tsx create mode 100644 frontend-v2/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionRow.tsx create mode 100644 frontend-v2/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx create mode 100644 frontend-v2/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/index.tsx create mode 100644 frontend-v2/src/pages/organization/RoleByIDPage/components/index.tsx create mode 100644 frontend-v2/src/pages/organization/RoleByIDPage/route.tsx create mode 100644 frontend-v2/src/pages/organization/SecretManagerOverviewPage/SecretManagerOverviewPage.tsx create mode 100644 frontend-v2/src/pages/organization/SecretManagerOverviewPage/route.tsx create mode 100644 frontend-v2/src/pages/organization/SecretScanningPage/SecretScanningPage.tsx create mode 100644 frontend-v2/src/pages/organization/SecretScanningPage/components/RiskStatusSelection.tsx create mode 100644 frontend-v2/src/pages/organization/SecretScanningPage/components/SecretScanningLogsTable.tsx create mode 100644 frontend-v2/src/pages/organization/SecretScanningPage/components/index.tsx create mode 100644 frontend-v2/src/pages/organization/SecretScanningPage/route.tsx create mode 100644 frontend-v2/src/pages/organization/SecretSharingPage/SecretSharingPage.tsx create mode 100644 frontend-v2/src/pages/organization/SecretSharingPage/components/AddShareSecretModal.tsx create mode 100644 frontend-v2/src/pages/organization/SecretSharingPage/components/ShareSecretSection.tsx create mode 100644 frontend-v2/src/pages/organization/SecretSharingPage/components/ShareSecretsRow.tsx create mode 100644 frontend-v2/src/pages/organization/SecretSharingPage/components/ShareSecretsTable.tsx create mode 100644 frontend-v2/src/pages/organization/SecretSharingPage/components/index.tsx create mode 100644 frontend-v2/src/pages/organization/SecretSharingPage/route.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/SettingsPage.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamForm.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamTab.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/AuditLogStreamTab/index.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/ImportTab/ImportTab.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/ImportTab/components/EnvKeyPlatformModal.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/ImportTab/components/GenericDropzone.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/ImportTab/components/SelectImportFromPlatformModal.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/ImportTab/index.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgAuthTab/ExternalGroupOrgRoleMappings.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgAuthTab/LDAPGroupMapModal.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgAuthTab/LDAPModal.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgAuthTab/OIDCModal.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgAuthTab.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGeneralAuthSection.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgGenericAuthSection.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgLDAPSection.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgOIDCSection.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgSCIMSection.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgAuthTab/OrgSSOSection.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgAuthTab/SSOModal.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgAuthTab/ScimTokenModal.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgAuthTab/index.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgDeleteSection/OrgDeleteSection.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgDeleteSection/index.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgEncryptionTab/AddExternalKmsForm.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgEncryptionTab/AwsKmsForm.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgEncryptionTab/GcpKmsForm.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgEncryptionTab/OrgEncryptionTab.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgEncryptionTab/UpdateExternalKmsForm.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgEncryptionTab/index.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgGeneralTab/OrgGeneralTab.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgGeneralTab/index.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgIncidentContactsSection/AddOrgIncidentContactModal.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgIncidentContactsSection/OrgIncidentContactsSection.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgIncidentContactsSection/OrgIncidentContactsTable.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgIncidentContactsSection/index.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgNameChangeSection/index.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgTabGroup/OrgTabGroup.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgTabGroup/index.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgWorkflowIntegrationTab/AddWorkflowIntegrationForm.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgWorkflowIntegrationTab/IntegrationFormDetails.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgWorkflowIntegrationTab/OrgWorkflowIntegrationTab.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/OrgWorkflowIntegrationTab/SlackIntegrationForm.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/ProjectTemplatesTab.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/DeleteProjectTemplateModal.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/EditProjectTemplateSection.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/EditProjectTemplate.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEditRoleForm.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEnvironmentsForm.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateRolesSection.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/index.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/index.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplateDetailsModal.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplatesSection.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplatesTable.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/index.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/index.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/components/index.tsx create mode 100644 frontend-v2/src/pages/organization/SettingsPage/route.tsx create mode 100644 frontend-v2/src/pages/organization/UserDetailsByIDPage/UserDetailsByIDPage.tsx create mode 100644 frontend-v2/src/pages/organization/UserDetailsByIDPage/components/UserDetailsSection.tsx create mode 100644 frontend-v2/src/pages/organization/UserDetailsByIDPage/components/UserOrgMembershipModal.tsx create mode 100644 frontend-v2/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserAddToProjectModal.tsx create mode 100644 frontend-v2/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserAuditLogsSection.tsx create mode 100644 frontend-v2/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserGroupsRow.tsx create mode 100644 frontend-v2/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserGroupsSection.tsx create mode 100644 frontend-v2/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserGroupsTable.tsx create mode 100644 frontend-v2/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectRow.tsx create mode 100644 frontend-v2/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsSection.tsx create mode 100644 frontend-v2/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsTable.tsx create mode 100644 frontend-v2/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/index.tsx create mode 100644 frontend-v2/src/pages/organization/UserDetailsByIDPage/components/index.tsx create mode 100644 frontend-v2/src/pages/organization/UserDetailsByIDPage/route.tsx create mode 100644 frontend-v2/src/pages/organization/layout.tsx diff --git a/frontend-v2/src/components/permissions/OrgPermissionCan.tsx b/frontend-v2/src/components/permissions/OrgPermissionCan.tsx index 006d43232..bbe5bf986 100644 --- a/frontend-v2/src/components/permissions/OrgPermissionCan.tsx +++ b/frontend-v2/src/components/permissions/OrgPermissionCan.tsx @@ -1,16 +1,37 @@ import { FunctionComponent, ReactNode } from "react"; import { BoundCanProps, Can } from "@casl/react"; +import { faLock } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { TOrgPermission, useOrgPermission } from "@app/context/OrgPermissionContext"; import { Tooltip } from "../v2"; +export const OrgPermissionGuardBanner = () => { + return ( +
+
+
+ +
+
+
Access Restricted
+
+ Your role has limited permissions, please
contact your admin to gain access +
+
+
+
+ ); +}; + type Props = { label?: ReactNode; // this prop is used when there exist already a tooltip as helper text for users // so when permission is allowed same tooltip will be reused to show helpertext renderTooltip?: boolean; allowedLabel?: string; + renderGuardBanner?: boolean; } & BoundCanProps; export const OrgPermissionCan: FunctionComponent = ({ @@ -19,6 +40,7 @@ export const OrgPermissionCan: FunctionComponent = ({ passThrough = true, renderTooltip, allowedLabel, + renderGuardBanner, ...props }) => { const { permission } = useOrgPermission(); @@ -36,10 +58,14 @@ export const OrgPermissionCan: FunctionComponent = ({ return {finalChild}; } - if (isAllowed && renderTooltip) { + if (isAllowed && renderTooltip && allowedLabel) { return {finalChild}; } + if (!isAllowed && renderGuardBanner) { + return ; + } + if (!isAllowed) return null; return finalChild; diff --git a/frontend-v2/src/const/routes.ts b/frontend-v2/src/const/routes.ts index 9c68a6138..f2e75a03c 100644 --- a/frontend-v2/src/const/routes.ts +++ b/frontend-v2/src/const/routes.ts @@ -11,5 +11,25 @@ export const ROUTE_PATHS = Object.freeze({ "/_restrict-login-signup/login/provider/success" ), SignUpSsoPage: setRoute("/signup/sso", "/_restrict-login-signup/signup/sso/"), - PasswordResetPage: setRoute("/password-reset", "/_restrict-login-signup/password-reset") + PasswordResetPage: setRoute("/password-reset", "/_restrict-login-signup/password-reset"), + OrgGroupDetailsByIDPage: setRoute( + "/organization/groups/$groupId", + "/_authenticate/_ctx-org-details/organization/_layout-org/groups/$groupId/" + ), + OrgIdentityDetailsByIDPage: setRoute( + "/organization/identities/$identityId", + "/_authenticate/_ctx-org-details/organization/_layout-org/identities/$identityId/" + ), + OrgUserDetailsByIDPage: setRoute( + "/organization/memberships/$membershipId", + "/_authenticate/_ctx-org-details/organization/_layout-org/memberships/$membershipId/" + ), + OrgAccessControlPage: setRoute( + "/organization/members", + "/_authenticate/_ctx-org-details/organization/_layout-org/members/" + ), + OrgRoleByIDPage: setRoute( + "/organization/roles/$roleId", + "/_authenticate/_ctx-org-details/organization/_layout-org/roles/$roleId/" + ) }); diff --git a/frontend-v2/src/pages/organization/AccessControlPage/AccessControlPage.tsx b/frontend-v2/src/pages/organization/AccessControlPage/AccessControlPage.tsx new file mode 100644 index 000000000..80133d32b --- /dev/null +++ b/frontend-v2/src/pages/organization/AccessControlPage/AccessControlPage.tsx @@ -0,0 +1,89 @@ +import { Helmet } from "react-helmet"; +import { useTranslation } from "react-i18next"; +import { useNavigate, useSearch } from "@tanstack/react-router"; + +import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { OrgPermissionActions, OrgPermissionSubjects, useOrgPermission } from "@app/context"; +import { OrgAccessControlTabSections } from "@app/types/org"; + +import { OrgGroupsTab, OrgIdentityTab, OrgMembersTab, OrgRoleTabSection } from "./components"; + +export const AccessControlPage = () => { + const { t } = useTranslation(); + const { permission } = useOrgPermission(); + const navigate = useNavigate({ + from: ROUTE_PATHS.OrgAccessControlPage.full + }); + const selectedTab = useSearch({ + from: ROUTE_PATHS.OrgAccessControlPage.id, + select: (el) => el.selectedTab, + structuralSharing: true + }); + + const updateSelectedTab = (tab: string) => { + navigate({ + search: { selectedTab: tab } + }); + }; + + const tabSections = [ + { + key: OrgAccessControlTabSections.Member, + label: "Users", + isHidden: permission.cannot(OrgPermissionActions.Read, OrgPermissionSubjects.Member), + component: OrgMembersTab + }, + { + key: OrgAccessControlTabSections.Groups, + label: "Groups", + isHidden: permission.cannot(OrgPermissionActions.Read, OrgPermissionSubjects.Groups), + component: OrgGroupsTab + }, + { + key: OrgAccessControlTabSections.Identities, + label: "Identities", + isHidden: permission.cannot(OrgPermissionActions.Read, OrgPermissionSubjects.Identity), + component: OrgIdentityTab + }, + { + key: OrgAccessControlTabSections.Roles, + label: "Roles", + isHidden: permission.cannot(OrgPermissionActions.Read, OrgPermissionSubjects.Role), + component: OrgRoleTabSection + } + ]; + + return ( +
+ + {t("common.head-title", { title: t("settings.org.title") })} + + +
+

Organization Access Control

+ + + {tabSections + .filter((el) => !el.isHidden) + .map((el) => ( + + {el.label} + + ))} + + {tabSections + .filter((el) => !el.isHidden) + .map(({ key, component: Component }) => ( + + + + ))} + +
+
+ ); +}; diff --git a/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/OrgGroupsTab.tsx b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/OrgGroupsTab.tsx new file mode 100644 index 000000000..2192f78fd --- /dev/null +++ b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/OrgGroupsTab.tsx @@ -0,0 +1,17 @@ +import { motion } from "framer-motion"; + +import { OrgGroupsSection } from "./components"; + +export const OrgGroupsTab = () => { + return ( + + + + ); +}; diff --git a/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupModal.tsx b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupModal.tsx new file mode 100644 index 000000000..908be4935 --- /dev/null +++ b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupModal.tsx @@ -0,0 +1,189 @@ +import { useEffect } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { + Button, + FilterableSelect, + FormControl, + Input, + Modal, + ModalContent +} from "@app/components/v2"; +import { useOrganization } from "@app/context"; +import { findOrgMembershipRole } from "@app/helpers/roles"; +import { useCreateGroup, useGetOrgRoles, useUpdateGroup } from "@app/hooks/api"; +import { UsePopUpState } from "@app/hooks/usePopUp"; + +const GroupFormSchema = z.object({ + name: z.string().min(1, "Name cannot be empty").max(50, "Name must be 50 characters or fewer"), + slug: z + .string() + .min(5, "Slug must be at least 5 characters long") + .max(36, "Slug must be 36 characters or fewer"), + role: z.object({ name: z.string(), slug: z.string() }) +}); + +export type TGroupFormData = z.infer; + +type Props = { + popUp: UsePopUpState<["group"]>; + handlePopUpClose: (popUpName: keyof UsePopUpState<["group"]>) => void; + handlePopUpToggle: (popUpName: keyof UsePopUpState<["group"]>, state?: boolean) => void; +}; + +export const OrgGroupModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => { + const { currentOrg } = useOrganization(); + const { data: roles } = useGetOrgRoles(currentOrg?.id || ""); + const { mutateAsync: createMutateAsync, isPending: createIsLoading } = useCreateGroup(); + const { mutateAsync: updateMutateAsync, isPending: updateIsLoading } = useUpdateGroup(); + + const { control, handleSubmit, reset } = useForm({ + resolver: zodResolver(GroupFormSchema) + }); + + useEffect(() => { + const group = popUp?.group?.data as { + groupId: string; + name: string; + slug: string; + role: string; + customRole: { + name: string; + slug: string; + }; + }; + + if (!roles?.length) return; + + if (group) { + reset({ + name: group.name, + slug: group.slug, + role: group?.customRole ?? findOrgMembershipRole(roles, group.role) + }); + } else { + reset({ + name: "", + slug: "", + role: findOrgMembershipRole(roles, currentOrg!.defaultMembershipRole) + }); + } + }, [popUp?.group?.data, roles]); + + const onGroupModalSubmit = async ({ name, slug, role }: TGroupFormData) => { + try { + if (!currentOrg?.id) return; + + const group = popUp?.group?.data as { + groupId: string; + name: string; + slug: string; + }; + + if (group) { + await updateMutateAsync({ + id: group.groupId, + name, + slug, + role: role.slug || undefined + }); + } else { + await createMutateAsync({ + name, + slug, + organizationId: currentOrg.id, + role: role.slug || undefined + }); + } + handlePopUpToggle("group", false); + reset(); + + createNotification({ + text: `Successfully ${popUp?.group?.data ? "updated" : "created"} group`, + type: "success" + }); + } catch { + createNotification({ + text: `Failed to ${popUp?.group?.data ? "updated" : "created"} group`, + type: "error" + }); + } + }; + + return ( + { + handlePopUpToggle("group", isOpen); + reset(); + }} + > + +
+ ( + + + + )} + /> + ( + + + + )} + /> + ( + + option.slug} + getOptionLabel={(option) => option.name} + /> + + )} + /> +
+ + +
+ +
+
+ ); +}; diff --git a/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx new file mode 100644 index 000000000..ec3ef159c --- /dev/null +++ b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx @@ -0,0 +1,99 @@ +import { faPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; +import { createNotification } from "@app/components/notifications"; +import { OrgPermissionCan } from "@app/components/permissions"; +import { Button, DeleteActionModal } from "@app/components/v2"; +import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context"; +import { useDeleteGroup } from "@app/hooks/api"; +import { usePopUp } from "@app/hooks/usePopUp"; + +import { OrgGroupModal } from "./OrgGroupModal"; +import { OrgGroupsTable } from "./OrgGroupsTable"; + +export const OrgGroupsSection = () => { + const { subscription } = useSubscription(); + const { mutateAsync: deleteMutateAsync } = useDeleteGroup(); + + const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ + "group", + "groupMembers", + "deleteGroup", + "upgradePlan" + ] as const); + + const handleAddGroupModal = () => { + if (!subscription?.groups) { + handlePopUpOpen("upgradePlan", { + description: + "You can manage users more efficiently with groups if you upgrade your Infisical plan." + }); + } else { + handlePopUpOpen("group"); + } + }; + + const onDeleteGroupSubmit = async ({ name, groupId }: { name: string; groupId: string }) => { + try { + await deleteMutateAsync({ + id: groupId + }); + createNotification({ + text: `Successfully deleted the group named ${name}`, + type: "success" + }); + } catch (err) { + console.error(err); + createNotification({ + text: `Failed to delete the group named ${name}`, + type: "error" + }); + } + + handlePopUpClose("deleteGroup"); + }; + + return ( +
+
+

Groups

+ + {(isAllowed) => ( + + )} + +
+ + + handlePopUpToggle("deleteGroup", isOpen)} + deleteKey="confirm" + onDeleteApproved={() => + onDeleteGroupSubmit(popUp?.deleteGroup?.data as { name: string; groupId: string }) + } + /> + handlePopUpToggle("upgradePlan", isOpen)} + text={(popUp.upgradePlan?.data as { description: string })?.description} + /> +
+ ); +}; diff --git a/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsTable.tsx b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsTable.tsx new file mode 100644 index 000000000..a644269d7 --- /dev/null +++ b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsTable.tsx @@ -0,0 +1,395 @@ +import { useMemo } from "react"; +import { + faArrowDown, + faArrowUp, + faEllipsis, + faMagnifyingGlass, + faSearch, + faUsers +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNavigate } from "@tanstack/react-router"; +import { twMerge } from "tailwind-merge"; + +import { createNotification } from "@app/components/notifications"; +import { OrgPermissionCan } from "@app/components/permissions"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + EmptyState, + IconButton, + Input, + Pagination, + Select, + SelectItem, + Table, + TableContainer, + TableSkeleton, + TBody, + Td, + Th, + THead, + Tr +} from "@app/components/v2"; +import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context"; +import { usePagination, useResetPageHelper } from "@app/hooks"; +import { useGetOrganizationGroups, useGetOrgRoles, useUpdateGroup } from "@app/hooks/api"; +import { OrderByDirection } from "@app/hooks/api/generic/types"; +import { UsePopUpState } from "@app/hooks/usePopUp"; + +type Props = { + handlePopUpOpen: ( + popUpName: keyof UsePopUpState<["group", "deleteGroup", "groupMembers"]>, + data?: { + groupId?: string; + name?: string; + slug?: string; + role?: string; + customRole?: { + name: string; + slug: string; + }; + } + ) => void; +}; + +enum GroupsOrderBy { + Name = "name", + Slug = "slug", + Role = "role" +} + +export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => { + const navigate = useNavigate(); + const { currentOrg } = useOrganization(); + const orgId = currentOrg?.id || ""; + const { isPending, data: groups = [] } = useGetOrganizationGroups(orgId); + const { mutateAsync: updateMutateAsync } = useUpdateGroup(); + + const { data: roles } = useGetOrgRoles(orgId); + + const handleChangeRole = async ({ id, role }: { id: string; role: string }) => { + try { + await updateMutateAsync({ + id, + role + }); + + createNotification({ + text: "Successfully updated group role", + type: "success" + }); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to update group role", + type: "error" + }); + } + }; + + const { + search, + setSearch, + setPage, + page, + perPage, + setPerPage, + offset, + orderDirection, + orderBy, + setOrderBy, + setOrderDirection, + toggleOrderDirection + } = usePagination(GroupsOrderBy.Name, { initPerPage: 20 }); + + const filteredGroups = useMemo(() => { + const filtered = search + ? groups?.filter( + ({ name, slug }) => + name.toLowerCase().includes(search.toLowerCase()) || + slug.toLowerCase().includes(search.toLowerCase()) + ) + : groups; + + const ordered = filtered?.sort((a, b) => { + switch (orderBy) { + case GroupsOrderBy.Role: { + const aValue = a.role === "custom" ? (a.customRole?.name as string) : a.role; + const bValue = b.role === "custom" ? (b.customRole?.name as string) : b.role; + + return aValue.toLowerCase().localeCompare(bValue.toLowerCase()); + } + default: + return a[orderBy].toLowerCase().localeCompare(b[orderBy].toLowerCase()); + } + }); + + return orderDirection === OrderByDirection.ASC ? ordered : ordered?.reverse(); + }, [search, groups, orderBy, orderDirection]); + + const handleSort = (column: GroupsOrderBy) => { + if (column === orderBy) { + toggleOrderDirection(); + return; + } + + setOrderBy(column); + setOrderDirection(OrderByDirection.ASC); + }; + + useResetPageHelper({ + totalCount: filteredGroups.length, + offset, + setPage + }); + + return ( +
+ setSearch(e.target.value)} + leftIcon={} + placeholder="Search groups..." + /> + + + + + + + + + + + {isPending && } + {!isPending && + filteredGroups + .slice(offset, perPage * page) + .map(({ id, name, slug, role, customRole }) => { + return ( + + navigate({ + to: "/organization/groups/$groupId", + params: { + groupId: id + } + }) + } + className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700" + key={`org-group-${id}`} + > + + + + + + ); + })} + +
+
+ Name + handleSort(GroupsOrderBy.Name)} + > + + +
+
+
+ Slug + handleSort(GroupsOrderBy.Slug)} + > + + +
+
+
+ Role + handleSort(GroupsOrderBy.Role)} + > + + +
+
+
{name}{slug} + + {(isAllowed) => { + return ( + + ); + }} + + + + +
+ +
+
+ + { + e.stopPropagation(); + createNotification({ + text: "Copied group ID to clipboard", + type: "info" + }); + navigator.clipboard.writeText(id); + }} + > + Copy Group ID + + + {(isAllowed) => ( + { + e.stopPropagation(); + handlePopUpOpen("group", { + groupId: id, + name, + slug, + role, + customRole + }); + }} + disabled={!isAllowed} + > + Edit Group + + )} + + + {(isAllowed) => ( + + navigate({ + to: "/organization/groups/$groupId", + params: { + groupId: id + } + }) + } + disabled={!isAllowed} + > + Manage Members + + )} + + + {(isAllowed) => ( + { + e.stopPropagation(); + handlePopUpOpen("deleteGroup", { + groupId: id, + name + }); + }} + disabled={!isAllowed} + > + Delete Group + + )} + + +
+
+ {Boolean(filteredGroups.length) && ( + + )} + {!isPending && !filteredGroups?.length && ( + + )} +
+
+ ); +}; diff --git a/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/components/OrgGroupsSection/index.tsx b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/components/OrgGroupsSection/index.tsx new file mode 100644 index 000000000..9928441a5 --- /dev/null +++ b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/components/OrgGroupsSection/index.tsx @@ -0,0 +1 @@ +export { OrgGroupsSection } from "./OrgGroupsSection"; diff --git a/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/components/index.tsx b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/components/index.tsx new file mode 100644 index 000000000..9928441a5 --- /dev/null +++ b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/components/index.tsx @@ -0,0 +1 @@ +export { OrgGroupsSection } from "./OrgGroupsSection"; diff --git a/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/index.tsx b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/index.tsx new file mode 100644 index 000000000..35da86edf --- /dev/null +++ b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgGroupsTab/index.tsx @@ -0,0 +1 @@ +export { OrgGroupsTab } from "./OrgGroupsTab"; diff --git a/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/OrgIdentityTab.tsx b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/OrgIdentityTab.tsx new file mode 100644 index 000000000..251b0b831 --- /dev/null +++ b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/OrgIdentityTab.tsx @@ -0,0 +1,17 @@ +import { motion } from "framer-motion"; + +import { IdentitySection } from "./components"; + +export const OrgIdentityTab = () => { + return ( + + + + ); +}; diff --git a/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal.tsx b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal.tsx new file mode 100644 index 000000000..70128fa1a --- /dev/null +++ b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal.tsx @@ -0,0 +1,55 @@ +import { useState } from "react"; + +import { Modal, ModalContent } from "@app/components/v2"; +import { IdentityAuthMethod, identityAuthToNameMap } from "@app/hooks/api/identities"; +import { UsePopUpState } from "@app/hooks/usePopUp"; + +import { IdentityAuthMethodModalContent } from "./IdentityAuthMethodModalContent"; + +type Props = { + popUp: UsePopUpState<["identityAuthMethod", "upgradePlan", "revokeAuthMethod"]>; + handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; + handlePopUpToggle: ( + popUpName: keyof UsePopUpState<["identityAuthMethod", "upgradePlan", "revokeAuthMethod"]>, + state?: boolean + ) => void; +}; + +export const IdentityAuthMethodModal = ({ popUp, handlePopUpOpen, handlePopUpToggle }: Props) => { + const [selectedAuthMethod, setSelectedAuthMethod] = useState(null); + + const initialAuthMethod = popUp?.identityAuthMethod?.data?.authMethod; + + const isSelectedAuthAlreadyConfigured = + popUp?.identityAuthMethod?.data?.allAuthMethods?.includes(selectedAuthMethod); + + return ( + { + handlePopUpToggle("identityAuthMethod", isOpen); + }} + > + + + + + ); +}; diff --git a/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModalContent.tsx b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModalContent.tsx new file mode 100644 index 000000000..859b21883 --- /dev/null +++ b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModalContent.tsx @@ -0,0 +1,335 @@ +import { useCallback } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; +import { createNotification } from "@app/components/notifications"; +import { + Badge, + DeleteActionModal, + FormControl, + Select, + SelectItem, + Tooltip +} from "@app/components/v2"; +import { useOrganization } from "@app/context"; +import { + useDeleteIdentityAwsAuth, + useDeleteIdentityAzureAuth, + useDeleteIdentityGcpAuth, + useDeleteIdentityKubernetesAuth, + useDeleteIdentityOidcAuth, + useDeleteIdentityTokenAuth, + useDeleteIdentityUniversalAuth +} from "@app/hooks/api"; +import { + IdentityAuthMethod, + identityAuthToNameMap, + useDeleteIdentityJwtAuth +} from "@app/hooks/api/identities"; +import { UsePopUpState } from "@app/hooks/usePopUp"; + +import { IdentityAwsAuthForm } from "./IdentityAwsAuthForm"; +import { IdentityAzureAuthForm } from "./IdentityAzureAuthForm"; +import { IdentityGcpAuthForm } from "./IdentityGcpAuthForm"; +import { IdentityJwtAuthForm } from "./IdentityJwtAuthForm"; +import { IdentityKubernetesAuthForm } from "./IdentityKubernetesAuthForm"; +import { IdentityOidcAuthForm } from "./IdentityOidcAuthForm"; +import { IdentityTokenAuthForm } from "./IdentityTokenAuthForm"; +import { IdentityUniversalAuthForm } from "./IdentityUniversalAuthForm"; + +type Props = { + popUp: UsePopUpState<["identityAuthMethod", "upgradePlan", "revokeAuthMethod"]>; + handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; + handlePopUpToggle: ( + popUpName: keyof UsePopUpState<["identityAuthMethod", "upgradePlan", "revokeAuthMethod"]>, + state?: boolean + ) => void; + + identity: { + name: string; + id: string; + authMethods: IdentityAuthMethod[]; + }; + initialAuthMethod: IdentityAuthMethod; + setSelectedAuthMethod: (authMethod: IdentityAuthMethod) => void; +}; + +type TRevokeOptions = { + identityId: string; + organizationId: string; +}; + +type TRevokeMethods = { + revokeMethod: (revokeOptions: TRevokeOptions) => Promise; + render: () => JSX.Element; +}; + +const identityAuthMethods = [ + { label: "Token Auth", value: IdentityAuthMethod.TOKEN_AUTH }, + { label: "Universal Auth", value: IdentityAuthMethod.UNIVERSAL_AUTH }, + { label: "Kubernetes Auth", value: IdentityAuthMethod.KUBERNETES_AUTH }, + { label: "GCP Auth", value: IdentityAuthMethod.GCP_AUTH }, + { label: "AWS Auth", value: IdentityAuthMethod.AWS_AUTH }, + { label: "Azure Auth", value: IdentityAuthMethod.AZURE_AUTH }, + { label: "OIDC Auth", value: IdentityAuthMethod.OIDC_AUTH }, + { + label: "JWT Auth", + value: IdentityAuthMethod.JWT_AUTH + } +]; + +const schema = z + .object({ + authMethod: z.nativeEnum(IdentityAuthMethod) + }) + .required(); + +export type FormData = z.infer; + +export const IdentityAuthMethodModalContent = ({ + popUp, + handlePopUpOpen, + handlePopUpToggle, + identity, + initialAuthMethod, + setSelectedAuthMethod +}: Props) => { + const { currentOrg } = useOrganization(); + const orgId = currentOrg?.id || ""; + + const { mutateAsync: revokeUniversalAuth } = useDeleteIdentityUniversalAuth(); + const { mutateAsync: revokeTokenAuth } = useDeleteIdentityTokenAuth(); + const { mutateAsync: revokeKubernetesAuth } = useDeleteIdentityKubernetesAuth(); + const { mutateAsync: revokeGcpAuth } = useDeleteIdentityGcpAuth(); + const { mutateAsync: revokeAwsAuth } = useDeleteIdentityAwsAuth(); + const { mutateAsync: revokeAzureAuth } = useDeleteIdentityAzureAuth(); + const { mutateAsync: revokeOidcAuth } = useDeleteIdentityOidcAuth(); + const { mutateAsync: revokeJwtAuth } = useDeleteIdentityJwtAuth(); + + const { control, watch } = useForm({ + resolver: zodResolver(schema), + defaultValues: async () => { + let authMethod = initialAuthMethod; + + if (!authMethod) { + const firstAuthMethodNotConfiguredAuthMethod = identityAuthMethods.find( + ({ value }) => !identity?.authMethods?.includes(value) + ); + + if (firstAuthMethodNotConfiguredAuthMethod) { + authMethod = firstAuthMethodNotConfiguredAuthMethod.value; + } + } + + setSelectedAuthMethod(authMethod); + return { + authMethod + }; + } + }); + + const watchedAuthMethod = watch("authMethod"); + + const identityAuthMethodData = { + identityId: identity.id, + name: identity.name, + authMethod: watch("authMethod"), + configuredAuthMethods: identity.authMethods + } as { + identityId: string; + name: string; + authMethod?: IdentityAuthMethod; + configuredAuthMethods?: IdentityAuthMethod[]; + }; + + const isSelectedAuthAlreadyConfigured = + identityAuthMethodData?.configuredAuthMethods?.includes(watchedAuthMethod); + + const methodMap: Record = { + [IdentityAuthMethod.UNIVERSAL_AUTH]: { + revokeMethod: revokeUniversalAuth, + render: () => ( + + ) + }, + + [IdentityAuthMethod.OIDC_AUTH]: { + revokeMethod: revokeOidcAuth, + render: () => ( + + ) + }, + + [IdentityAuthMethod.TOKEN_AUTH]: { + revokeMethod: revokeTokenAuth, + render: () => ( + + ) + }, + + [IdentityAuthMethod.AZURE_AUTH]: { + revokeMethod: revokeAzureAuth, + render: () => ( + + ) + }, + + [IdentityAuthMethod.GCP_AUTH]: { + revokeMethod: revokeGcpAuth, + render: () => ( + + ) + }, + + [IdentityAuthMethod.KUBERNETES_AUTH]: { + revokeMethod: revokeKubernetesAuth, + render: () => ( + + ) + }, + + [IdentityAuthMethod.AWS_AUTH]: { + revokeMethod: revokeAwsAuth, + render: () => ( + + ) + }, + + [IdentityAuthMethod.JWT_AUTH]: { + revokeMethod: revokeJwtAuth, + render: () => ( + + ) + } + }; + + const isAlreadyConfigured = useCallback((method: IdentityAuthMethod) => { + return identityAuthMethodData?.configuredAuthMethods?.includes(method); + }, []); + + const selectedMethodItem = methodMap[identityAuthMethodData.authMethod!]; + + return ( + <> + ( + + + + )} + /> + {selectedMethodItem?.render ? selectedMethodItem.render() :
} + handlePopUpToggle("upgradePlan", isOpen)} + text="You can use IP allowlisting if you switch to Infisical's Pro plan." + /> + handlePopUpToggle("revokeAuthMethod", isOpen)} + deleteKey="confirm" + buttonText="Remove" + onDeleteApproved={async () => { + if (!identityAuthMethodData.authMethod || !orgId || !selectedMethodItem) { + return; + } + + try { + await selectedMethodItem.revokeMethod({ + identityId: identityAuthMethodData.identityId, + organizationId: orgId + }); + + createNotification({ + text: "Successfully removed auth method", + type: "success" + }); + + handlePopUpToggle("revokeAuthMethod", false); + handlePopUpToggle("identityAuthMethod", false); + } catch { + createNotification({ + text: "Failed to remove auth method", + type: "error" + }); + } + }} + /> + + ); +}; diff --git a/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm.tsx b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm.tsx new file mode 100644 index 000000000..59528e3a7 --- /dev/null +++ b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm.tsx @@ -0,0 +1,377 @@ +import { useEffect } from "react"; +import { Controller, useFieldArray, useForm } from "react-hook-form"; +import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { Button, FormControl, IconButton, Input } from "@app/components/v2"; +import { useOrganization, useSubscription } from "@app/context"; +import { + useAddIdentityAwsAuth, + useGetIdentityAwsAuth, + useUpdateIdentityAwsAuth +} from "@app/hooks/api"; +import { IdentityAuthMethod } from "@app/hooks/api/identities"; +import { IdentityTrustedIp } from "@app/hooks/api/identities/types"; +import { UsePopUpState } from "@app/hooks/usePopUp"; + +const schema = z + .object({ + stsEndpoint: z.string(), + allowedPrincipalArns: z.string(), + allowedAccountIds: z.string(), + accessTokenTTL: z + .string() + .refine( + (value) => Number(value) <= 315360000, + "Access Token TTL cannot be greater than 315360000" + ), + accessTokenMaxTTL: z + .string() + .refine( + (value) => Number(value) <= 315360000, + "Access Token Max TTL cannot be greater than 315360000" + ), + accessTokenNumUsesLimit: z.string(), + accessTokenTrustedIps: z + .object({ + ipAddress: z.string().max(50) + }) + .array() + .min(1) + }) + .required(); + +export type FormData = z.infer; + +type Props = { + handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; + handlePopUpToggle: ( + popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>, + state?: boolean + ) => void; + identityAuthMethodData: { + identityId: string; + name: string; + configuredAuthMethods?: IdentityAuthMethod[]; + authMethod?: IdentityAuthMethod; + }; +}; + +export const IdentityAwsAuthForm = ({ + handlePopUpOpen, + handlePopUpToggle, + identityAuthMethodData +}: Props) => { + const { currentOrg } = useOrganization(); + const orgId = currentOrg?.id || ""; + const { subscription } = useSubscription(); + + const { mutateAsync: addMutateAsync } = useAddIdentityAwsAuth(); + const { mutateAsync: updateMutateAsync } = useUpdateIdentityAwsAuth(); + + const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes( + identityAuthMethodData.authMethod! || "" + ); + const { data } = useGetIdentityAwsAuth(identityAuthMethodData?.identityId ?? "", { + enabled: isUpdate + }); + + const { + control, + handleSubmit, + reset, + formState: { isSubmitting } + } = useForm({ + resolver: zodResolver(schema), + defaultValues: { + stsEndpoint: "https://sts.amazonaws.com/", + allowedPrincipalArns: "", + allowedAccountIds: "", + accessTokenTTL: "2592000", + accessTokenMaxTTL: "2592000", + accessTokenNumUsesLimit: "0", + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + } + }); + + const { + fields: accessTokenTrustedIpsFields, + append: appendAccessTokenTrustedIp, + remove: removeAccessTokenTrustedIp + } = useFieldArray({ control, name: "accessTokenTrustedIps" }); + + useEffect(() => { + if (data) { + reset({ + stsEndpoint: data.stsEndpoint, + allowedPrincipalArns: data.allowedPrincipalArns, + allowedAccountIds: data.allowedAccountIds, + accessTokenTTL: String(data.accessTokenTTL), + accessTokenMaxTTL: String(data.accessTokenMaxTTL), + accessTokenNumUsesLimit: String(data.accessTokenNumUsesLimit), + accessTokenTrustedIps: data.accessTokenTrustedIps.map( + ({ ipAddress, prefix }: IdentityTrustedIp) => { + return { + ipAddress: `${ipAddress}${prefix !== undefined ? `/${prefix}` : ""}` + }; + } + ) + }); + } else { + reset({ + stsEndpoint: "https://sts.amazonaws.com/", + allowedPrincipalArns: "", + allowedAccountIds: "", + accessTokenTTL: "2592000", + accessTokenMaxTTL: "2592000", + accessTokenNumUsesLimit: "0", + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + }); + } + }, [data]); + + const onFormSubmit = async ({ + allowedPrincipalArns, + allowedAccountIds, + stsEndpoint, + accessTokenTTL, + accessTokenMaxTTL, + accessTokenNumUsesLimit, + accessTokenTrustedIps + }: FormData) => { + try { + if (!identityAuthMethodData) return; + + if (data) { + await updateMutateAsync({ + organizationId: orgId, + stsEndpoint, + allowedPrincipalArns, + allowedAccountIds, + identityId: identityAuthMethodData.identityId, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps + }); + } else { + await addMutateAsync({ + organizationId: orgId, + identityId: identityAuthMethodData.identityId, + stsEndpoint: stsEndpoint || "", + allowedPrincipalArns: allowedPrincipalArns || "", + allowedAccountIds: allowedAccountIds || "", + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps + }); + } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); + } catch { + createNotification({ + text: `Failed to ${isUpdate ? "update" : "configure"} identity`, + type: "error" + }); + } + }; + + return ( +
+ ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + {accessTokenTrustedIpsFields.map(({ id }, index) => ( +
+ { + return ( + + { + if (subscription?.ipAllowlisting) { + field.onChange(e); + return; + } + + handlePopUpOpen("upgradePlan"); + }} + placeholder="123.456.789.0" + /> + + ); + }} + /> + { + if (subscription?.ipAllowlisting) { + removeAccessTokenTrustedIp(index); + return; + } + + handlePopUpOpen("upgradePlan"); + }} + size="lg" + colorSchema="danger" + variant="plain" + ariaLabel="update" + className="p-3" + > + + +
+ ))} +
+ +
+
+
+ + + +
+ {isUpdate && ( + + )} +
+ + ); +}; diff --git a/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm.tsx b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm.tsx new file mode 100644 index 000000000..7af879888 --- /dev/null +++ b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm.tsx @@ -0,0 +1,373 @@ +import { useEffect } from "react"; +import { Controller, useFieldArray, useForm } from "react-hook-form"; +import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { Button, FormControl, IconButton, Input } from "@app/components/v2"; +import { useOrganization, useSubscription } from "@app/context"; +import { + useAddIdentityAzureAuth, + useGetIdentityAzureAuth, + useUpdateIdentityAzureAuth +} from "@app/hooks/api"; +import { IdentityAuthMethod } from "@app/hooks/api/identities"; +import { IdentityTrustedIp } from "@app/hooks/api/identities/types"; +import { UsePopUpState } from "@app/hooks/usePopUp"; + +const schema = z + .object({ + tenantId: z.string().min(1), + resource: z.string(), + allowedServicePrincipalIds: z.string(), + accessTokenTTL: z.string().refine((val) => Number(val) <= 315360000, { + message: "Access Token TTL cannot be greater than 315360000" + }), + accessTokenMaxTTL: z.string().refine((val) => Number(val) <= 315360000, { + message: "Access Token Max TTL cannot be greater than 315360000" + }), + accessTokenNumUsesLimit: z.string(), + accessTokenTrustedIps: z + .array( + z.object({ + ipAddress: z.string().max(50) + }) + ) + .min(1) + }) + .required(); + +export type FormData = z.infer; + +type Props = { + handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; + handlePopUpToggle: ( + popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>, + state?: boolean + ) => void; + identityAuthMethodData: { + identityId: string; + name: string; + configuredAuthMethods?: IdentityAuthMethod[]; + authMethod?: IdentityAuthMethod; + }; +}; + +export const IdentityAzureAuthForm = ({ + handlePopUpOpen, + handlePopUpToggle, + identityAuthMethodData +}: Props) => { + const { currentOrg } = useOrganization(); + const orgId = currentOrg?.id || ""; + const { subscription } = useSubscription(); + + const { mutateAsync: addMutateAsync } = useAddIdentityAzureAuth(); + const { mutateAsync: updateMutateAsync } = useUpdateIdentityAzureAuth(); + + const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes( + identityAuthMethodData.authMethod! || "" + ); + const { data } = useGetIdentityAzureAuth(identityAuthMethodData?.identityId ?? "", { + enabled: isUpdate + }); + + const { + control, + handleSubmit, + reset, + + formState: { isSubmitting } + } = useForm({ + resolver: zodResolver(schema), + defaultValues: { + tenantId: "", + resource: "https://management.azure.com/", + allowedServicePrincipalIds: "", + accessTokenTTL: "2592000", + accessTokenMaxTTL: "2592000", + accessTokenNumUsesLimit: "0", + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + } + }); + + const { + fields: accessTokenTrustedIpsFields, + append: appendAccessTokenTrustedIp, + remove: removeAccessTokenTrustedIp + } = useFieldArray({ control, name: "accessTokenTrustedIps" }); + + useEffect(() => { + if (data) { + reset({ + tenantId: data.tenantId, + resource: data.resource, + allowedServicePrincipalIds: data.allowedServicePrincipalIds, + accessTokenTTL: String(data.accessTokenTTL), + accessTokenMaxTTL: String(data.accessTokenMaxTTL), + accessTokenNumUsesLimit: String(data.accessTokenNumUsesLimit), + accessTokenTrustedIps: data.accessTokenTrustedIps.map( + ({ ipAddress, prefix }: IdentityTrustedIp) => { + return { + ipAddress: `${ipAddress}${prefix !== undefined ? `/${prefix}` : ""}` + }; + } + ) + }); + } else { + reset({ + tenantId: "", + resource: "https://management.azure.com/", + allowedServicePrincipalIds: "", + accessTokenTTL: "2592000", + accessTokenMaxTTL: "2592000", + accessTokenNumUsesLimit: "0", + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + }); + } + }, [data]); + + const onFormSubmit = async ({ + tenantId, + resource, + allowedServicePrincipalIds, + accessTokenTTL, + accessTokenMaxTTL, + accessTokenNumUsesLimit, + accessTokenTrustedIps + }: FormData) => { + try { + if (!identityAuthMethodData) return; + + if (data) { + await updateMutateAsync({ + organizationId: orgId, + identityId: identityAuthMethodData.identityId, + tenantId, + resource, + allowedServicePrincipalIds, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps + }); + } else { + await addMutateAsync({ + organizationId: orgId, + identityId: identityAuthMethodData.identityId, + tenantId: tenantId || "", + resource: resource || "", + allowedServicePrincipalIds: allowedServicePrincipalIds || "", + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps + }); + } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); + } catch { + createNotification({ + text: `Failed to ${isUpdate ? "update" : "configure"} identity`, + type: "error" + }); + } + }; + + return ( +
+ ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + {accessTokenTrustedIpsFields.map(({ id }, index) => ( +
+ { + return ( + + { + if (subscription?.ipAllowlisting) { + field.onChange(e); + return; + } + + handlePopUpOpen("upgradePlan"); + }} + placeholder="123.456.789.0" + /> + + ); + }} + /> + { + if (subscription?.ipAllowlisting) { + removeAccessTokenTrustedIp(index); + return; + } + + handlePopUpOpen("upgradePlan"); + }} + size="lg" + colorSchema="danger" + variant="plain" + ariaLabel="update" + className="p-3" + > + + +
+ ))} +
+ +
+
+
+ + + +
+ {isUpdate && ( + + )} +
+ + ); +}; diff --git a/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm.tsx b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm.tsx new file mode 100644 index 000000000..0cccb8213 --- /dev/null +++ b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm.tsx @@ -0,0 +1,406 @@ +import { useEffect } from "react"; +import { Controller, useFieldArray, useForm } from "react-hook-form"; +import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { Button, FormControl, IconButton, Input, Select, SelectItem } from "@app/components/v2"; +import { useOrganization, useSubscription } from "@app/context"; +import { + useAddIdentityGcpAuth, + useGetIdentityGcpAuth, + useUpdateIdentityGcpAuth +} from "@app/hooks/api"; +import { IdentityAuthMethod } from "@app/hooks/api/identities"; +import { IdentityTrustedIp } from "@app/hooks/api/identities/types"; +import { UsePopUpState } from "@app/hooks/usePopUp"; + +const schema = z + .object({ + type: z.enum(["iam", "gce"]), + allowedServiceAccounts: z.string(), + allowedProjects: z.string(), + allowedZones: z.string(), + accessTokenTTL: z.string().refine((val) => Number(val) <= 315360000, { + message: "Access Token TTL cannot be greater than 315360000" + }), + accessTokenMaxTTL: z.string().refine((val) => Number(val) <= 315360000, { + message: "Access Token Max TTL cannot be greater than 315360000" + }), + accessTokenNumUsesLimit: z.string(), + accessTokenTrustedIps: z + .array( + z.object({ + ipAddress: z.string().max(50) + }) + ) + .min(1) + }) + .required(); + +export type FormData = z.infer; + +type Props = { + handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; + handlePopUpToggle: ( + popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>, + state?: boolean + ) => void; + identityAuthMethodData: { + identityId: string; + name: string; + configuredAuthMethods?: IdentityAuthMethod[]; + authMethod?: IdentityAuthMethod; + }; +}; + +export const IdentityGcpAuthForm = ({ + handlePopUpOpen, + handlePopUpToggle, + identityAuthMethodData +}: Props) => { + const { currentOrg } = useOrganization(); + const orgId = currentOrg?.id || ""; + const { subscription } = useSubscription(); + + const { mutateAsync: addMutateAsync } = useAddIdentityGcpAuth(); + const { mutateAsync: updateMutateAsync } = useUpdateIdentityGcpAuth(); + + const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes( + identityAuthMethodData.authMethod! || "" + ); + const { data } = useGetIdentityGcpAuth(identityAuthMethodData?.identityId ?? "", { + enabled: isUpdate + }); + + const { + control, + handleSubmit, + reset, + formState: { isSubmitting }, + watch + } = useForm({ + resolver: zodResolver(schema), + defaultValues: { + type: "gce", + allowedServiceAccounts: "", + allowedProjects: "", + allowedZones: "", + accessTokenTTL: "2592000", + accessTokenMaxTTL: "2592000", + accessTokenNumUsesLimit: "0", + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + } + }); + + const watchedType = watch("type"); + + const { + fields: accessTokenTrustedIpsFields, + append: appendAccessTokenTrustedIp, + remove: removeAccessTokenTrustedIp + } = useFieldArray({ control, name: "accessTokenTrustedIps" }); + + useEffect(() => { + if (data) { + reset({ + type: data.type || "gce", + allowedServiceAccounts: data.allowedServiceAccounts, + allowedProjects: data.allowedProjects, + allowedZones: data.allowedZones, + accessTokenTTL: String(data.accessTokenTTL), + accessTokenMaxTTL: String(data.accessTokenMaxTTL), + accessTokenNumUsesLimit: String(data.accessTokenNumUsesLimit), + accessTokenTrustedIps: data.accessTokenTrustedIps.map( + ({ ipAddress, prefix }: IdentityTrustedIp) => { + return { + ipAddress: `${ipAddress}${prefix !== undefined ? `/${prefix}` : ""}` + }; + } + ) + }); + } else { + reset({ + type: "gce", + allowedServiceAccounts: "", + allowedProjects: "", + allowedZones: "", + accessTokenTTL: "2592000", + accessTokenMaxTTL: "2592000", + accessTokenNumUsesLimit: "0", + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + }); + } + }, [data]); + + const onFormSubmit = async ({ + type, + allowedServiceAccounts, + allowedProjects, + allowedZones, + accessTokenTTL, + accessTokenMaxTTL, + accessTokenNumUsesLimit, + accessTokenTrustedIps + }: FormData) => { + try { + if (!identityAuthMethodData) return; + + if (data) { + await updateMutateAsync({ + identityId: identityAuthMethodData.identityId, + organizationId: orgId, + type, + allowedServiceAccounts, + allowedProjects, + allowedZones, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps + }); + } else { + await addMutateAsync({ + identityId: identityAuthMethodData.identityId, + organizationId: orgId, + type, + allowedServiceAccounts: allowedServiceAccounts || "", + allowedProjects: allowedProjects || "", + allowedZones: allowedZones || "", + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps + }); + } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); + } catch { + createNotification({ + text: `Failed to ${isUpdate ? "update" : "configure"} identity`, + type: "error" + }); + } + }; + + return ( +
+ ( + + + + )} + /> + ( + + + + )} + /> + {watchedType === "gce" && ( + ( + + + + )} + /> + )} + {watchedType === "gce" && ( + ( + + + + )} + /> + )} + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + {accessTokenTrustedIpsFields.map(({ id }, index) => ( +
+ { + return ( + + { + if (subscription?.ipAllowlisting) { + field.onChange(e); + return; + } + + handlePopUpOpen("upgradePlan"); + }} + placeholder="123.456.789.0" + /> + + ); + }} + /> + { + if (subscription?.ipAllowlisting) { + removeAccessTokenTrustedIp(index); + return; + } + + handlePopUpOpen("upgradePlan"); + }} + size="lg" + colorSchema="danger" + variant="plain" + ariaLabel="update" + className="p-3" + > + + +
+ ))} +
+ +
+
+
+ + + +
+ {isUpdate && ( + + )} +
+ + ); +}; diff --git a/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm.tsx b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm.tsx new file mode 100644 index 000000000..e975b272d --- /dev/null +++ b/frontend-v2/src/pages/organization/AccessControlPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm.tsx @@ -0,0 +1,688 @@ +import { useEffect } from "react"; +import { Controller, useFieldArray, useForm } from "react-hook-form"; +import { faQuestionCircle } from "@fortawesome/free-regular-svg-icons"; +import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { + Button, + FormControl, + IconButton, + Input, + Select, + SelectItem, + TextArea, + Tooltip +} from "@app/components/v2"; +import { useOrganization, useSubscription } from "@app/context"; +import { useAddIdentityJwtAuth, useUpdateIdentityJwtAuth } from "@app/hooks/api"; +import { IdentityAuthMethod } from "@app/hooks/api/identities"; +import { IdentityJwtConfigurationType } from "@app/hooks/api/identities/enums"; +import { useGetIdentityJwtAuth } from "@app/hooks/api/identities/queries"; +import { IdentityTrustedIp } from "@app/hooks/api/identities/types"; +import { UsePopUpState } from "@app/hooks/usePopUp"; + +const commonSchema = z.object({ + accessTokenTrustedIps: z + .array( + z.object({ + ipAddress: z.string().max(50) + }) + ) + .min(1), + accessTokenTTL: z.string().refine((val) => Number(val) <= 315360000, { + message: "Access Token TTL cannot be greater than 315360000" + }), + accessTokenMaxTTL: z.string().refine((val) => Number(val) <= 315360000, { + message: "Access Token Max TTL cannot be greater than 315360000" + }), + accessTokenNumUsesLimit: z.string(), + boundIssuer: z.string().trim().default(""), + boundAudiences: z.string().optional().default(""), + boundClaims: z.array( + z.object({ + key: z.string(), + value: z.string() + }) + ), + boundSubject: z.string().optional().default("") +}); + +const schema = z.discriminatedUnion("configurationType", [ + z + .object({ + configurationType: z.literal(IdentityJwtConfigurationType.JWKS), + jwksUrl: z.string().trim().url(), + jwksCaCert: z.string().trim().default(""), + publicKeys: z + .object({ + value: z.string() + }) + .array() + .optional() + }) + .merge(commonSchema), + z + .object({ + configurationType: z.literal(IdentityJwtConfigurationType.STATIC), + jwksUrl: z.string().trim().optional(), + jwksCaCert: z.string().trim().optional().default(""), + publicKeys: z + .object({ + value: z.string().min(1) + }) + .array() + .min(1) + }) + .merge(commonSchema) +]); + +export type FormData = z.infer; + +type Props = { + handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; + handlePopUpToggle: ( + popUpName: keyof UsePopUpState<["identityAuthMethod", "revokeAuthMethod"]>, + state?: boolean + ) => void; + identityAuthMethodData: { + identityId: string; + name: string; + configuredAuthMethods?: IdentityAuthMethod[]; + authMethod?: IdentityAuthMethod; + }; +}; + +export const IdentityJwtAuthForm = ({ + handlePopUpOpen, + handlePopUpToggle, + identityAuthMethodData +}: Props) => { + const { currentOrg } = useOrganization(); + const orgId = currentOrg?.id || ""; + const { subscription } = useSubscription(); + + const { mutateAsync: addMutateAsync } = useAddIdentityJwtAuth(); + const { mutateAsync: updateMutateAsync } = useUpdateIdentityJwtAuth(); + + const isUpdate = identityAuthMethodData?.configuredAuthMethods?.includes( + identityAuthMethodData.authMethod! || "" + ); + const { data } = useGetIdentityJwtAuth(identityAuthMethodData?.identityId ?? "", { + enabled: isUpdate + }); + + const { + watch, + control, + handleSubmit, + reset, + setValue, + formState: { isSubmitting } + } = useForm({ + resolver: zodResolver(schema), + defaultValues: { + accessTokenTTL: "2592000", + accessTokenMaxTTL: "2592000", + accessTokenNumUsesLimit: "0", + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }], + configurationType: IdentityJwtConfigurationType.JWKS + } + }); + + const selectedConfigurationType = watch("configurationType") as IdentityJwtConfigurationType; + + const { + fields: publicKeyFields, + append: appendPublicKeyFields, + remove: removePublicKeyFields + } = useFieldArray({ + control, + name: "publicKeys" + }); + + const { + fields: boundClaimsFields, + append: appendBoundClaimField, + remove: removeBoundClaimField + } = useFieldArray({ + control, + name: "boundClaims" + }); + + const { + fields: accessTokenTrustedIpsFields, + append: appendAccessTokenTrustedIp, + remove: removeAccessTokenTrustedIp + } = useFieldArray({ control, name: "accessTokenTrustedIps" }); + + useEffect(() => { + if (data) { + reset({ + configurationType: data.configurationType, + jwksUrl: data.jwksUrl, + jwksCaCert: data.jwksCaCert, + publicKeys: data.publicKeys.map((pk) => ({ + value: pk + })), + boundIssuer: data.boundIssuer, + boundAudiences: data.boundAudiences, + boundClaims: Object.entries(data.boundClaims).map(([key, value]) => ({ + key, + value + })), + boundSubject: data.boundSubject, + accessTokenTTL: String(data.accessTokenTTL), + accessTokenMaxTTL: String(data.accessTokenMaxTTL), + accessTokenNumUsesLimit: String(data.accessTokenNumUsesLimit), + accessTokenTrustedIps: data.accessTokenTrustedIps.map( + ({ ipAddress, prefix }: IdentityTrustedIp) => { + return { + ipAddress: `${ipAddress}${prefix !== undefined ? `/${prefix}` : ""}` + }; + } + ) + }); + } else { + reset({ + configurationType: IdentityJwtConfigurationType.JWKS, + jwksUrl: "", + jwksCaCert: "", + boundIssuer: "", + boundAudiences: "", + boundClaims: [], + boundSubject: "", + accessTokenTTL: "2592000", + accessTokenMaxTTL: "2592000", + accessTokenNumUsesLimit: "0", + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + }); + } + }, [data]); + + const onFormSubmit = async ({ + accessTokenTrustedIps, + accessTokenTTL, + accessTokenMaxTTL, + accessTokenNumUsesLimit, + configurationType, + jwksUrl, + jwksCaCert, + publicKeys, + boundIssuer, + boundAudiences, + boundClaims, + boundSubject + }: FormData) => { + try { + if (!identityAuthMethodData) { + return; + } + + if (data) { + await updateMutateAsync({ + identityId: identityAuthMethodData.identityId, + organizationId: orgId, + configurationType, + jwksUrl, + jwksCaCert, + publicKeys: publicKeys?.map((field) => field.value).filter(Boolean), + boundIssuer, + boundAudiences, + boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])), + boundSubject, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps + }); + } else { + await addMutateAsync({ + identityId: identityAuthMethodData.identityId, + configurationType, + jwksUrl, + jwksCaCert, + publicKeys: publicKeys?.map((field) => field.value).filter(Boolean), + boundIssuer, + boundAudiences, + boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])), + boundSubject, + organizationId: orgId, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps + }); + } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); + } catch { + createNotification({ + text: `Failed to ${isUpdate ? "update" : "configure"} identity`, + type: "error" + }); + } + }; + + return ( +
+ ( + + + + )} + /> + {selectedConfigurationType === IdentityJwtConfigurationType.JWKS && ( + <> + ( + + + + )} + /> + ( + +