From 3e0d4cb70a03ca2cb94eb69c173a1a4528844c42 Mon Sep 17 00:00:00 2001 From: Animesh Das Date: Fri, 17 Feb 2023 00:31:00 +0530 Subject: [PATCH 01/16] fixed infinite loading in dashboard --- frontend/src/pages/dashboard/[id].tsx | 3 +++ .../views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx | 3 +++ 2 files changed, 6 insertions(+) diff --git a/frontend/src/pages/dashboard/[id].tsx b/frontend/src/pages/dashboard/[id].tsx index d0dda0240..740a763af 100644 --- a/frontend/src/pages/dashboard/[id].tsx +++ b/frontend/src/pages/dashboard/[id].tsx @@ -188,6 +188,9 @@ export default function Dashboard() { useEffect(() => { (async () => { + if(router.isReady && workspaceId === "undefined"){ + router.push('/noprojects'); + } try { const tempNumSnapshots = await getProjectSercetSnapshotsCount({ workspaceId diff --git a/frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx b/frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx index aadabf088..17acaff33 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx @@ -123,6 +123,9 @@ export const ProjectSettingsPage = () => { await deleteWorkspace.mutateAsync({ workspaceID }); // redirect user to first workspace user is part of const ws = workspaces.find(({ _id }) => _id !== workspaceID); + if(!ws){ + router.push("/noprojects"); + } router.push(`/dashboard/${ws?._id}`); createNotification({ text: 'Successfully deleted workspace', From 54e2b661bc6a92a3e1fe80351d7eb5de561b7092 Mon Sep 17 00:00:00 2001 From: Animesh Das Date: Fri, 17 Feb 2023 14:20:58 +0530 Subject: [PATCH 02/16] ran prettier to fix indentation --- frontend/src/pages/dashboard/[id].tsx | 131 +++++++++++------- .../ProjectSettingsPage.tsx | 10 +- 2 files changed, 83 insertions(+), 58 deletions(-) diff --git a/frontend/src/pages/dashboard/[id].tsx b/frontend/src/pages/dashboard/[id].tsx index 83ee3be2f..70b92cf11 100644 --- a/frontend/src/pages/dashboard/[id].tsx +++ b/frontend/src/pages/dashboard/[id].tsx @@ -138,7 +138,7 @@ export default function Dashboard() { const [dropZoneData, setDropZoneData] = useState(); const [projectTags, setProjectTags] = useState([]); - const { hasUnsavedChanges, setHasUnsavedChanges } = useLeaveConfirm({initialValue: false}); + const { hasUnsavedChanges, setHasUnsavedChanges } = useLeaveConfirm({ initialValue: false }); const { t } = useTranslation(); const { createNotification } = useNotificationContext(); @@ -188,7 +188,7 @@ export default function Dashboard() { useEffect(() => { (async () => { - if(router.isReady && workspaceId === "undefined"){ + if (router.isReady && workspaceId === 'undefined') { router.push('/noprojects'); } try { @@ -288,7 +288,7 @@ export default function Dashboard() { valueOverride: undefined, comment: '', tags: [] - }, + } ]); }; @@ -476,7 +476,8 @@ export default function Dashboard() { initDataPoint.key || newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0] .comment !== initDataPoint.comment || - newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]?.tags !== initDataPoint?.tags) + newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]?.tags !== + initDataPoint?.tags) ) .map((secret) => secret.id) .includes(newDataPoint.id) @@ -504,7 +505,11 @@ export default function Dashboard() { }); if (secrets) await addSecrets({ secrets, env: selectedEnv.slug, workspaceId }); } - if (selectedEnv && !selectedEnv.isReadDenied && secretsToBeUpdated.concat(overridesToBeUpdated).length > 0) { + if ( + selectedEnv && + !selectedEnv.isReadDenied && + secretsToBeUpdated.concat(overridesToBeUpdated).length > 0 + ) { const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeUpdated.concat(overridesToBeUpdated), workspaceId, @@ -563,11 +568,13 @@ export default function Dashboard() { }; const handleOnEnvironmentChange = (envName: string) => { - if(hasUnsavedChanges) { + if (hasUnsavedChanges) { if (!window.confirm(leaveConfirmDefaultMessage)) return; } - const selectedWorkspaceEnv = workspaceEnvs.find(({ name }: { name: string }) => envName === name) || { + const selectedWorkspaceEnv = workspaceEnvs.find( + ({ name }: { name: string }) => envName === name + ) || { name: 'unknown', slug: 'unknown', isWriteDenied: false, @@ -656,16 +663,18 @@ export default function Dashboard() {
- {!selectedEnv?.isReadDenied &&
{(data?.length !== 0 || hasUnsavedChanges) && !snapshotData && (
@@ -799,50 +808,61 @@ export default function Dashboard() { className="mt-1 max-h-[calc(100vh-280px)] overflow-hidden overflow-y-scroll no-scrollbar no-scrollbar::-webkit-scrollbar border border-mineshaft-600 rounded-md" >
-
+
-
0
- Key - {!snapshotData && reorderRows(1)} - > - {sortMethod === 'alphabetical' ? : } - } +
+ 0 +
+ Key + {!snapshotData && ( + reorderRows(1)} + > + {sortMethod === 'alphabetical' ? ( + + ) : ( + + )} + + )}
-
-
Value
+
+
+ Value +
-
Comment
+
+ Comment +
-
Tags
+
+ Tags +
-
-
+
+
null} role="none" onClick={() => {}} className="invisible group-hover:visible" > - +
@@ -850,10 +870,16 @@ export default function Dashboard() {
{!snapshotData && data - ?.filter((row) => - row.key?.toUpperCase().includes(searchKeys.toUpperCase()) - || row.tags?.map(tag => tag.name).join(" ")?.toUpperCase().includes(searchKeys.toUpperCase()) - || row.comment?.toUpperCase().includes(searchKeys.toUpperCase())) + ?.filter( + (row) => + row.key?.toUpperCase().includes(searchKeys.toUpperCase()) || + row.tags + ?.map((tag) => tag.name) + .join(' ') + ?.toUpperCase() + .includes(searchKeys.toUpperCase()) || + row.comment?.toUpperCase().includes(searchKeys.toUpperCase()) + ) .filter((row) => !sharedToHide.includes(row.id)) .filter((row) => row.value !== undefined) .map((keyPair) => ( @@ -919,15 +945,15 @@ export default function Dashboard() { tags={projectTags} /> ))} -
-
-
@@ -974,8 +1000,7 @@ export default function Dashboard() { - row.id === sidebarSecretId && row.value !== undefined + (row: SecretDataProps) => row.id === sidebarSecretId && row.value !== undefined )} modifyKey={listenChangeKey} modifyValue={listenChangeValue} diff --git a/frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx b/frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx index 17acaff33..d57d76217 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx @@ -58,7 +58,7 @@ export const ProjectSettingsPage = () => { const renameWorkspace = useRenameWorkspace(); const toggleAutoCapitalization = useToggleAutoCapitalization(); - + const deleteWorkspace = useDeleteWorkspace(); // env crud operation const createWsEnv = useCreateWsEnvironment(); @@ -97,7 +97,7 @@ export const ProjectSettingsPage = () => { } }; - const onAutoCapitalizationToggle = async (state: boolean) => { + const onAutoCapitalizationToggle = async (state: boolean) => { try { await toggleAutoCapitalization.mutateAsync({ workspaceID, @@ -123,8 +123,8 @@ export const ProjectSettingsPage = () => { await deleteWorkspace.mutateAsync({ workspaceID }); // redirect user to first workspace user is part of const ws = workspaces.find(({ _id }) => _id !== workspaceID); - if(!ws){ - router.push("/noprojects"); + if (!ws) { + router.push('/noprojects'); } router.push(`/dashboard/${ws?._id}`); createNotification({ @@ -250,7 +250,7 @@ export const ProjectSettingsPage = () => { const res = await createWsTag.mutateAsync({ workspaceID, tagName: name, - tagSlug: name.replace(" ", "_") + tagSlug: name.replace(' ', '_') }); createNotification({ text: 'Successfully created a tag', From 8432f71d58cc7e5f39060af1c0c4895774e96c38 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Sun, 19 Feb 2023 00:44:38 -0500 Subject: [PATCH 03/16] add secrets auto reload --- .github/values.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/values.yaml b/.github/values.yaml index 62afb494f..819df5066 100644 --- a/.github/values.yaml +++ b/.github/values.yaml @@ -2,7 +2,8 @@ frontend: enabled: true name: frontend podAnnotations: {} - deploymentAnnotations: {} + deploymentAnnotations: + secrets.infisical.com/auto-reload: "true" replicaCount: 2 image: repository: infisical/frontend @@ -20,7 +21,8 @@ backend: enabled: true name: backend podAnnotations: {} - deploymentAnnotations: {} + deploymentAnnotations: + secrets.infisical.com/auto-reload: "true" replicaCount: 2 image: repository: infisical/backend From 20e7eae4fe36e69168b34c8b17328c288d39b8c3 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Sun, 19 Feb 2023 13:42:37 +0700 Subject: [PATCH 04/16] Add more Accept-Encoding to integrations syncs --- backend/src/integrations/sync.ts | 34 ++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index e4bdc116c..3309e5813 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -30,7 +30,6 @@ import { INTEGRATION_FLYIO_API_URL, INTEGRATION_CIRCLECI_API_URL, } from "../variables"; -import { access, appendFile } from "fs"; /** * Sync/push [secrets] to [app] in integration named [integration] @@ -181,7 +180,8 @@ const syncSecretsAzureKeyVault = async ({ while (url) { const res = await axios.get(url, { headers: { - Authorization: `Bearer ${accessToken}` + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' } }); @@ -202,7 +202,8 @@ const syncSecretsAzureKeyVault = async ({ const azureKeyVaultSecret = await axios.get(`${getAzureKeyVaultSecret.id}?api-version=7.3`, { headers: { - 'Authorization': `Bearer ${accessToken}` + 'Authorization': `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' } }); @@ -259,7 +260,8 @@ const syncSecretsAzureKeyVault = async ({ }, { headers: { - Authorization: `Bearer ${accessToken}` + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' } } ); @@ -270,7 +272,8 @@ const syncSecretsAzureKeyVault = async ({ deleteSecrets.forEach(async (secret) => { await axios.delete(`${integration.app}/secrets/${secret.key}?api-version=7.3`, { headers: { - 'Authorization': `Bearer ${accessToken}` + 'Authorization': `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' } }); }); @@ -488,6 +491,7 @@ const syncSecretsHeroku = async ({ headers: { Accept: "application/vnd.heroku+json; version=3", Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ) @@ -506,6 +510,7 @@ const syncSecretsHeroku = async ({ headers: { Accept: "application/vnd.heroku+json; version=3", Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ); @@ -552,7 +557,7 @@ const syncSecretsVercel = async ({ } : {}), }; - + const res = ( await Promise.all( ( @@ -561,7 +566,8 @@ const syncSecretsVercel = async ({ { params, headers: { - Authorization: `Bearer ${accessToken}` + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' } } )) @@ -573,7 +579,8 @@ const syncSecretsVercel = async ({ { params, headers: { - Authorization: `Bearer ${accessToken}` + Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' } } )).data) @@ -633,6 +640,7 @@ const syncSecretsVercel = async ({ params, headers: { Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ); @@ -649,6 +657,7 @@ const syncSecretsVercel = async ({ params, headers: { Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ); @@ -664,6 +673,7 @@ const syncSecretsVercel = async ({ params, headers: { Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ); @@ -723,6 +733,7 @@ const syncSecretsNetlify = async ({ params: getParams, headers: { Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ) @@ -837,6 +848,7 @@ const syncSecretsNetlify = async ({ params: syncParams, headers: { Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ); @@ -854,6 +866,7 @@ const syncSecretsNetlify = async ({ params: syncParams, headers: { Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ); @@ -868,6 +881,7 @@ const syncSecretsNetlify = async ({ params: syncParams, headers: { Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ); @@ -882,6 +896,7 @@ const syncSecretsNetlify = async ({ params: syncParams, headers: { Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ); @@ -1035,6 +1050,7 @@ const syncSecretsRender = async ({ { headers: { Authorization: `Bearer ${accessToken}`, + 'Accept-Encoding': 'application/json' }, } ); @@ -1088,6 +1104,7 @@ const syncSecretsFlyio = async ({ method: "post", headers: { Authorization: "Bearer " + accessToken, + 'Accept-Encoding': 'application/json' }, data: { query: SetSecrets, @@ -1167,6 +1184,7 @@ const syncSecretsFlyio = async ({ headers: { Authorization: "Bearer " + accessToken, "Content-Type": "application/json", + 'Accept-Encoding': 'application/json' }, data: { query: DeleteSecrets, From 5baccc73c97b63709688cd1be2a73f208c095585 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Sun, 19 Feb 2023 12:51:04 -0500 Subject: [PATCH 05/16] Add mongo root password --- backend/src/integrations/exchange.ts | 66 ++++++++++++++-------------- helm-charts/infisical/values.yaml | 14 +++--- 2 files changed, 42 insertions(+), 38 deletions(-) diff --git a/backend/src/integrations/exchange.ts b/backend/src/integrations/exchange.ts index 1e08a701f..301259198 100644 --- a/backend/src/integrations/exchange.ts +++ b/backend/src/integrations/exchange.ts @@ -138,7 +138,7 @@ const exchangeCodeAzure = async ({ try { res = (await axios.post( INTEGRATION_AZURE_TOKEN_URL, - new URLSearchParams({ + new URLSearchParams({ grant_type: 'authorization_code', code: code, scope: 'https://vault.azure.net/.default openid offline_access', @@ -147,16 +147,16 @@ const exchangeCodeAzure = async ({ redirect_uri: `${SITE_URL}/integrations/azure-key-vault/oauth2/callback` } as any) )).data; - + accessExpiresAt.setSeconds( - accessExpiresAt.getSeconds() + res.expires_in + accessExpiresAt.getSeconds() + res.expires_in ); } catch (err: any) { Sentry.setUser(null); Sentry.captureException(err); throw new Error('Failed OAuth2 code-token exchange with Azure'); } - + return ({ accessToken: res.access_token, refreshToken: res.refresh_token, @@ -175,36 +175,36 @@ const exchangeCodeAzure = async ({ * @returns {Date} obj2.accessExpiresAt - date of expiration for access token */ const exchangeCodeHeroku = async ({ - code + code }: { - code: string; + code: string; }) => { - let res: ExchangeCodeHerokuResponse; - const accessExpiresAt = new Date(); - try { - res = (await axios.post( - INTEGRATION_HEROKU_TOKEN_URL, - new URLSearchParams({ - grant_type: 'authorization_code', - code: code, - client_secret: CLIENT_SECRET_HEROKU - } as any) - )).data; - - accessExpiresAt.setSeconds( - accessExpiresAt.getSeconds() + res.expires_in - ); - } catch (err) { - Sentry.setUser(null); - Sentry.captureException(err); - throw new Error('Failed OAuth2 code-token exchange with Heroku'); - } - - return ({ - accessToken: res.access_token, - refreshToken: res.refresh_token, - accessExpiresAt - }); + let res: ExchangeCodeHerokuResponse; + const accessExpiresAt = new Date(); + try { + res = (await axios.post( + INTEGRATION_HEROKU_TOKEN_URL, + new URLSearchParams({ + grant_type: 'authorization_code', + code: code, + client_secret: CLIENT_SECRET_HEROKU + } as any) + )).data; + + accessExpiresAt.setSeconds( + accessExpiresAt.getSeconds() + res.expires_in + ); + } catch (err) { + Sentry.setUser(null); + Sentry.captureException(err); + throw new Error('Failed OAuth2 code-token exchange with Heroku'); + } + + return ({ + accessToken: res.access_token, + refreshToken: res.refresh_token, + accessExpiresAt + }); } /** @@ -234,7 +234,7 @@ const exchangeCodeVercel = async ({ code }: { code: string }) => { } catch (err) { Sentry.setUser(null); Sentry.captureException(err); - throw new Error('Failed OAuth2 code-token exchange with Vercel'); + throw new Error(`Failed OAuth2 code-token exchange with Vercel [err=${err}]`); } return { diff --git a/helm-charts/infisical/values.yaml b/helm-charts/infisical/values.yaml index f670028b3..d569e7c31 100644 --- a/helm-charts/infisical/values.yaml +++ b/helm-charts/infisical/values.yaml @@ -209,6 +209,8 @@ mongodb: ## databases: - "infisical" + rootPassword: root + rootUser: root ## MongoDB persistence configuration ## persistence: @@ -250,7 +252,7 @@ ingress: kubernetes.io/ingress.class: "nginx" # cert-manager.io/issuer: letsencrypt-nginx ## @param ingress.hostName Ingress hostname (your custom domain name) - ## Replace with your own domain + ## Replace with your own domain ## hostName: infisical.local ## @skip ingress.frontend @@ -264,9 +266,10 @@ ingress: path: /api pathType: Prefix ## @param ingress.tls Ingress TLS hosts (matching above hostName) - ## Replace with your own domain + ## Replace with your own domain ## - tls: [] + tls: + [] # - secretName: letsencrypt-nginx # hosts: # - infisical.local @@ -338,7 +341,8 @@ mailhog: ingressClassName: nginx ## @param mailhog.ingress.annotations Ingress annotations ## - annotations: {} + annotations: + {} # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" ## @param mailhog.ingress.labels Ingress labels @@ -352,4 +356,4 @@ mailhog: ## paths: - path: "/" - pathType: Prefix \ No newline at end of file + pathType: Prefix From b6bbfc08ad4603134ae8f25af046ead2a34894cd Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Sun, 19 Feb 2023 12:52:22 -0500 Subject: [PATCH 06/16] increase helm chart --- helm-charts/infisical/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helm-charts/infisical/Chart.yaml b/helm-charts/infisical/Chart.yaml index d0d293b71..80fe48fcc 100644 --- a/helm-charts/infisical/Chart.yaml +++ b/helm-charts/infisical/Chart.yaml @@ -7,7 +7,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.14 +version: 0.1.15 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to @@ -23,4 +23,4 @@ dependencies: - name: mailhog version: "~5.2.3" repository: "https://codecentric.github.io/helm-charts" - condition: mailhog.enabled \ No newline at end of file + condition: mailhog.enabled From 0062df58a287d5a9f5ccfe6338170e7329649d75 Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako Date: Sun, 19 Feb 2023 12:02:30 -0800 Subject: [PATCH 07/16] Fixed dashboard for the corner case of purely personal secrets --- frontend/public/data/frequentInterfaces.ts | 1 + .../dashboard/DashboardInputField.tsx | 4 +- frontend/src/components/dashboard/KeyPair.tsx | 10 +-- .../components/navigation/NavBarDashboard.tsx | 4 +- .../src/ee/components/PITRecoverySidebar.tsx | 31 ++++++- frontend/src/pages/dashboard/[id].tsx | 89 +++++++++---------- 6 files changed, 79 insertions(+), 60 deletions(-) diff --git a/frontend/public/data/frequentInterfaces.ts b/frontend/public/data/frequentInterfaces.ts index fa6c73a57..953096e1d 100644 --- a/frontend/public/data/frequentInterfaces.ts +++ b/frontend/public/data/frequentInterfaces.ts @@ -13,6 +13,7 @@ export interface SecretDataProps { value: string | undefined; valueOverride: string | undefined; id: string; + idOverride?: string; comment: string; tags: Tag[]; } \ No newline at end of file diff --git a/frontend/src/components/dashboard/DashboardInputField.tsx b/frontend/src/components/dashboard/DashboardInputField.tsx index 3e171b722..f9fb39d7e 100644 --- a/frontend/src/components/dashboard/DashboardInputField.tsx +++ b/frontend/src/components/dashboard/DashboardInputField.tsx @@ -99,8 +99,8 @@ const DashboardInputField = ({ />
)} - {!error &&
+
@@ -166,7 +166,7 @@ export default function Navbar() { leaveFrom="transform opacity-100 scale-100" leaveTo="transform opacity-0 scale-95" > - +
{t('nav:user.signed-in-as')} diff --git a/frontend/src/ee/components/PITRecoverySidebar.tsx b/frontend/src/ee/components/PITRecoverySidebar.tsx index 24b58eb0f..e72f05cc9 100644 --- a/frontend/src/ee/components/PITRecoverySidebar.tsx +++ b/frontend/src/ee/components/PITRecoverySidebar.tsx @@ -102,7 +102,9 @@ const PITRecoverySidebar = ({ toggleSidebar, setSnapshotData, chosenSnapshot }: }); } - const decryptedSecretVersions = secretSnapshotData.secretVersions.map( + const decryptedSecretVersions = secretSnapshotData.secretVersions.filter( + (sv: EncrypetedSecretVersionListProps) => (sv.type !== undefined && sv.environment !== undefined) + ).map( (encryptedSecretVersion: EncrypetedSecretVersionListProps, pos: number) => ({ id: encryptedSecretVersion._id, pos, @@ -125,10 +127,13 @@ const PITRecoverySidebar = ({ toggleSidebar, setSnapshotData, chosenSnapshot }: ); const secretKeys = [ - ...new Set(decryptedSecretVersions.map((secret: SecretDataProps) => secret.key)) + ...new Set(decryptedSecretVersions.filter((dsv: any) => dsv.type !== undefined || dsv.environemnt !== undefined) + .map((secret: SecretDataProps) => secret.key)) ]; - const result = secretKeys.map((key, index) => ({ + const result = secretKeys.map((key, index) => (decryptedSecretVersions.filter( + (secret: SecretDataProps) => secret.key === key && secret.type === 'shared' + )[0]?.id ? { id: decryptedSecretVersions.filter( (secret: SecretDataProps) => secret.key === key && secret.type === 'shared' )[0].id, @@ -146,6 +151,24 @@ const PITRecoverySidebar = ({ toggleSidebar, setSnapshotData, chosenSnapshot }: valueOverride: decryptedSecretVersions.filter( (secret: SecretDataProps) => secret.key === key && secret.type === 'personal' )[0]?.value + } : { + id: decryptedSecretVersions.filter( + (secret: SecretDataProps) => secret.key === key && secret.type === 'personal' + )[0].id, + pos: index, + key, + environment: decryptedSecretVersions.filter( + (secret: SecretDataProps) => secret.key === key && secret.type === 'personal' + )[0].environment, + tags: decryptedSecretVersions.filter( + (secret: SecretDataProps) => secret.key === key && secret.type === 'personal' + )[0].tags, + value: decryptedSecretVersions.filter( + (secret: SecretDataProps) => secret.key === key && secret.type === 'shared' + )[0]?.value, + valueOverride: decryptedSecretVersions.filter( + (secret: SecretDataProps) => secret.key === key && secret.type === 'personal' + )[0]?.value })); setSnapshotData({ @@ -161,7 +184,7 @@ const PITRecoverySidebar = ({ toggleSidebar, setSnapshotData, chosenSnapshot }:
{isLoading ? (
diff --git a/frontend/src/pages/dashboard/[id].tsx b/frontend/src/pages/dashboard/[id].tsx index 484f4c290..fd43616a3 100644 --- a/frontend/src/pages/dashboard/[id].tsx +++ b/frontend/src/pages/dashboard/[id].tsx @@ -332,27 +332,27 @@ export default function Dashboard() { }; const modifyValue = (value: string, id: string) => { - setData((oldData) => oldData?.map((e) => (e.id === id ? { ...e, value } : e))); + setData((oldData) => oldData?.map((e) => ((e.id ? e.id : e.idOverride) === id ? { ...e, value } : e))); setHasUnsavedChanges(true); }; const modifyValueOverride = (valueOverride: string | undefined, id: string) => { - setData((oldData) => oldData?.map((e) => (e.id === id ? { ...e, valueOverride } : e))); + setData((oldData) => oldData?.map((e) => ((e.id ? e.id : e.idOverride) === id ? { ...e, valueOverride } : e))); setHasUnsavedChanges(true); }; const modifyKey = (key: string, id: string) => { - setData((oldData) => oldData?.map((e) => (e.id === id ? { ...e, key } : e))); + setData((oldData) => oldData?.map((e) => ((e.id ? e.id : e.idOverride) === id ? { ...e, key } : e))); setHasUnsavedChanges(true); }; const modifyComment = (comment: string, id: string) => { - setData((oldData) => oldData?.map((e) => (e.id === id ? { ...e, comment } : e))); + setData((oldData) => oldData?.map((e) => ((e.id ? e.id : e.idOverride) === id ? { ...e, comment } : e))); setHasUnsavedChanges(true); }; const modifyTags = (tags: Tag[], id: string) => { - setData((oldData) => oldData?.map((e) => (e.id === id ? { ...e, tags } : e))); + setData((oldData) => oldData?.map((e) => ((e.id ? e.id : e.idOverride) === id ? { ...e, tags } : e))); setHasUnsavedChanges(true); }; @@ -444,7 +444,8 @@ export default function Dashboard() { initialData! .filter( (initDataPoint) => - newData!.map((dataPoint) => dataPoint.id).includes(initDataPoint.id) && + newData!.filter((dataPoint) => dataPoint.id) + .map((dataPoint) => dataPoint.id).includes(initDataPoint.id) && (newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].value !== initDataPoint.value || newData!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].key !== @@ -477,7 +478,7 @@ export default function Dashboard() { const overridesToBeAdded = newOverrides! .filter( (newDataPoint) => - !initOverrides.map((initDataPoint) => initDataPoint.id).includes(newDataPoint.id) + !initOverrides.map((initDataPoint) => initDataPoint.idOverride).includes(newDataPoint.idOverride) ) .map((override) => ({ pos: override.pos, @@ -496,18 +497,21 @@ export default function Dashboard() { initOverrides .filter( (initDataPoint) => - newOverrides!.map((dataPoint) => dataPoint.id).includes(initDataPoint.id) && - (newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0] + newOverrides!.map((dataPoint) => dataPoint.idOverride) + .includes(initDataPoint.idOverride) && + (newOverrides!.filter((dataPoint) => dataPoint.idOverride === initDataPoint.idOverride)[0] .valueOverride !== initDataPoint.valueOverride || - newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0].key !== - initDataPoint.key || - newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0] - .comment !== initDataPoint.comment || - JSON.stringify(newOverrides!.filter((dataPoint) => dataPoint.id === initDataPoint.id)[0]?.tags) !== - JSON.stringify(initDataPoint?.tags)) + newOverrides!.filter((dataPoint) => dataPoint.idOverride === initDataPoint.idOverride)[0].key !== + initDataPoint.key || + (newOverrides!.filter((dataPoint) => dataPoint.idOverride === initDataPoint.idOverride)[0] + .comment || '') !== (initDataPoint.comment || '') + || + (JSON.stringify(newOverrides!.filter((dataPoint) => dataPoint.idOverride === initDataPoint.idOverride)[0]?.tags) || '') !== + (JSON.stringify(initDataPoint?.tags) || '') + ) ) - .map((secret) => secret.id) - .includes(newDataPoint.id) + .map((secret) => secret.idOverride) + .includes(newDataPoint.idOverride) ) .map((override) => ({ pos: override.pos, @@ -665,7 +669,7 @@ export default function Dashboard() { idOverride: tempDecryptedSecrets.filter( (secret) => secret.key === key && secret.type === 'personal' )[0]?.id, - pos: (newData?.filter(dp => !dp.id.includes('-'))?.length ?? 0) + index, + pos: (newData?.filter(dp => !dp.id?.includes('-'))?.length ?? 0) + index, key, value: tempDecryptedSecrets.filter( (secret) => secret.key === key && secret.type === 'shared' @@ -681,8 +685,8 @@ export default function Dashboard() { )[0]?.tags })); - setInitialData(structuredClone(newData?.filter(dp => !dp.id.includes('-')).concat(formattedNewDecryptedKeys.filter(dk => dk.id)))); - setData(structuredClone(newData?.filter(dp => !dp.id.includes('-')).concat(formattedNewDecryptedKeys.filter(dk => dk.id)))) + setInitialData(structuredClone(newData?.filter(dp => !dp.id?.includes('-')).concat(formattedNewDecryptedKeys.filter(dk => dk.id)))); + setData(structuredClone(newData?.filter(dp => !dp.id?.includes('-')).concat(formattedNewDecryptedKeys.filter(dk => dk.id)))) } else { setInitialData(structuredClone(newData)); } @@ -882,17 +886,24 @@ export default function Dashboard() { comment: '', tags: sv.tags })); - setData(rolledBackSecrets); // Perform the rollback globally - performSecretRollback({ workspaceId, version: snapshotData.version }); - - setSnapshotData(undefined); - createNotification({ - text: `Rollback has been performed successfully.`, - type: 'success' - }); - setHasUnsavedChanges(false); + const result = await performSecretRollback({ workspaceId, version: snapshotData.version }); + if (result === undefined) { + createNotification({ + text: `Something went wrong during the rollback.`, + type: 'error' + }); + } else { + setData(rolledBackSecrets); + createNotification({ + text: `Successfully rolled back secrets.`, + type: 'success' + }); + setSnapshotData(undefined); + setHasUnsavedChanges(false); + togglePITSidebar(false); + } }} color="primary" size="md" @@ -975,7 +986,7 @@ export default function Dashboard() { alt="infisical loading indicator" />
- ) : data?.length !== 0 ? ( + ) : (data?.length !== 0 || snapshotData?.secretVersions) ? (
tag.name).join(" ")?.toUpperCase().includes(searchKeys.toUpperCase()) || row.comment?.toUpperCase().includes(searchKeys.toUpperCase())) .filter((row) => !sharedToHide.includes(row.id)) - .filter((row) => row.value !== undefined) + // .filter((row) => row.value !== undefined) .map((keyPair) => ( a.key.localeCompare(b.key)) .filter((row) => row.environment === selectedSnapshotEnv?.slug) .filter((row) => row.key.toUpperCase().includes(searchKeys.toUpperCase())) - .filter( - (row) => - !snapshotData.secretVersions - ?.filter((secretVersion) => - snapshotData.secretVersions - ?.map((item) => item.key) - .filter( - (item, index) => - index !== - snapshotData.secretVersions?.map((i) => i.key).indexOf(item) - ) - .includes(secretVersion.key) - ) - ?.map((item) => item.id) - .includes(row.id) - ) .map((keyPair) => ( Date: Sun, 19 Feb 2023 20:11:24 -0800 Subject: [PATCH 08/16] Added notifications to 2FA and fixed state --- frontend/src/components/login/MFAStep.tsx | 8 ++++++-- .../SecuritySection/SecuritySection.tsx | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/login/MFAStep.tsx b/frontend/src/components/login/MFAStep.tsx index 0bd3a8927..5628b1a87 100644 --- a/frontend/src/components/login/MFAStep.tsx +++ b/frontend/src/components/login/MFAStep.tsx @@ -60,6 +60,7 @@ export default function MFAStep({ }): JSX.Element { const router = useRouter(); const [isLoading, setIsLoading] = useState(false); + const [isLoadingResend, setIsLoadingResend] = useState(false); const [mfaCode, setMfaCode] = useState(''); const [triesLeft, setTriesLeft] = useState(undefined); @@ -103,9 +104,12 @@ export default function MFAStep({ const handleResendMfaCode = async () => { try { + setIsLoadingResend(true); await sendMfaToken.mutateAsync({ email }); + setIsLoadingResend(false); } catch (err) { console.error(err); + setIsLoadingResend(false); } } @@ -137,13 +141,13 @@ export default function MFAStep({ {t('mfa:step2-resend-alert')} diff --git a/frontend/src/views/Settings/PersonalSettingsPage/SecuritySection/SecuritySection.tsx b/frontend/src/views/Settings/PersonalSettingsPage/SecuritySection/SecuritySection.tsx index 5a1e965a1..0d7902c60 100644 --- a/frontend/src/views/Settings/PersonalSettingsPage/SecuritySection/SecuritySection.tsx +++ b/frontend/src/views/Settings/PersonalSettingsPage/SecuritySection/SecuritySection.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react'; +import { useNotificationContext } from '@app/components/context/Notifications/NotificationProvider'; import { Checkbox } from '@app/components/v2'; import { useGetUser } from '../../../../hooks/api'; @@ -9,6 +10,7 @@ import updateMyMfaEnabled from '../../../../pages/api/user/updateMyMfaEnabled'; export const SecuritySection = () => { const [isMfaEnabled, setIsMfaEnabled] = useState(false); const { data: user } = useGetUser(); + const { createNotification } = useNotificationContext(); useEffect(() => { if (user && typeof user.isMfaEnabled !== 'undefined') { @@ -21,18 +23,27 @@ export const SecuritySection = () => { const newUser: User = await updateMyMfaEnabled({ isMfaEnabled: state }); - + if (newUser) { setIsMfaEnabled(newUser.isMfaEnabled); } + + createNotification({ + text: `${newUser.isMfaEnabled ? 'Successfully turned on two-factor authentication.' : 'Successfully turned off two-factor authentication.'}`, + type: 'success' + }); } catch (err) { + createNotification({ + text: 'Something went wrong while toggling the two-factor authentication.', + type: 'error' + }); console.error(err); } } return (
-
+

Two-factor Authentication

From 59beabb445b89dca0c0202484ec8401296d22636 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Mon, 20 Feb 2023 13:27:16 +0700 Subject: [PATCH 09/16] Fix change password private key removal --- .../utilities/cryptography/changePassword.ts | 19 +++------ .../utilities/saveTokenToLocalStorage.ts | 40 ++++++++++-------- .../src/pages/api/auth/ChangePassword2.ts | 41 ++++++++----------- 3 files changed, 46 insertions(+), 54 deletions(-) diff --git a/frontend/src/components/utilities/cryptography/changePassword.ts b/frontend/src/components/utilities/cryptography/changePassword.ts index bdc741b3d..aeb138ec6 100644 --- a/frontend/src/components/utilities/cryptography/changePassword.ts +++ b/frontend/src/components/utilities/cryptography/changePassword.ts @@ -13,8 +13,6 @@ import { deriveArgonKey } from './crypto'; const clientOldPassword = new jsrp.client(); const clientNewPassword = new jsrp.client(); -// TODO: modify this function - /** * This function loggs in the user (whether it's right after signup, or a normal login) * @param {*} email @@ -105,9 +103,8 @@ const changePassword = async ( secret: Buffer.from(derivedKey.hash) }); - let res; try { - res = await changePassword2({ + await changePassword2({ clientProof, protectedKey, protectedKeyIV, @@ -118,23 +115,19 @@ const changePassword = async ( salt: result.salt, verifier: result.verifier }); - + saveTokenToLocalStorage({ protectedKey, protectedKeyIV, protectedKeyTag, encryptedPrivateKey, iv: encryptedPrivateKeyIV, - tag: encryptedPrivateKeyTag, + tag: encryptedPrivateKeyTag }); - if (res && res.status === 400) { - setCurrentPasswordError(true); - } else if (res && res.status === 200) { - setPasswordChanged(true); - setCurrentPassword(''); - setNewPassword(''); - } + setPasswordChanged(true); + setCurrentPassword(''); + setNewPassword(''); } catch (error) { setCurrentPasswordError(true); console.log(error); diff --git a/frontend/src/components/utilities/saveTokenToLocalStorage.ts b/frontend/src/components/utilities/saveTokenToLocalStorage.ts index 90cf5bd7d..c47241ed0 100644 --- a/frontend/src/components/utilities/saveTokenToLocalStorage.ts +++ b/frontend/src/components/utilities/saveTokenToLocalStorage.ts @@ -3,9 +3,9 @@ interface Props { protectedKeyIV?: string; protectedKeyTag?: string; publicKey?: string; - encryptedPrivateKey: string; - iv: string; - tag: string; + encryptedPrivateKey?: string; + iv?: string; + tag?: string; privateKey?: string; } @@ -20,38 +20,46 @@ export const saveTokenToLocalStorage = ({ privateKey, }: Props) => { try { - localStorage.removeItem("protectedKey"); - localStorage.removeItem("protectedKeyIV"); - localStorage.removeItem("protectedKeyTag"); - localStorage.removeItem("publicKey"); - localStorage.removeItem("encryptedPrivateKey"); - localStorage.removeItem("iv"); - localStorage.removeItem("tag"); - localStorage.removeItem("PRIVATE_KEY"); if (protectedKey) { + localStorage.removeItem("protectedKey"); localStorage.setItem("protectedKey", protectedKey); } if (protectedKeyIV) { + localStorage.removeItem("protectedKeyIV"); localStorage.setItem("protectedKeyIV", protectedKeyIV); } if (protectedKeyTag) { + localStorage.removeItem("protectedKeyTag"); localStorage.setItem("protectedKeyTag", protectedKeyTag); } if (publicKey) { + localStorage.removeItem("publicKey"); localStorage.setItem("publicKey", publicKey); } - if (privateKey) { - localStorage.setItem("PRIVATE_KEY", privateKey); + if (encryptedPrivateKey) { + localStorage.removeItem("encryptedPrivateKey"); + localStorage.setItem("encryptedPrivateKey", encryptedPrivateKey); } - localStorage.setItem("encryptedPrivateKey", encryptedPrivateKey); - localStorage.setItem("iv", iv); - localStorage.setItem("tag", tag); + if (iv) { + localStorage.removeItem("iv"); + localStorage.setItem("iv", iv); + } + + if (tag) { + localStorage.removeItem("tag"); + localStorage.setItem("tag", tag); + } + + if (privateKey) { + localStorage.removeItem("PRIVATE_KEY"); + localStorage.setItem("PRIVATE_KEY", privateKey); + } } catch (err) { if (err instanceof Error) { throw new Error( diff --git a/frontend/src/pages/api/auth/ChangePassword2.ts b/frontend/src/pages/api/auth/ChangePassword2.ts index b2ab4906e..1094af814 100644 --- a/frontend/src/pages/api/auth/ChangePassword2.ts +++ b/frontend/src/pages/api/auth/ChangePassword2.ts @@ -1,4 +1,4 @@ -import SecurityClient from '@app/components/utilities/SecurityClient'; +import { apiRequest } from "@app/config/request"; interface Props { clientProof: string; @@ -17,7 +17,7 @@ interface Props { * @param {*} clientPublicKey * @returns */ -const changePassword2 = ({ +const changePassword2 = async ({ clientProof, protectedKey, protectedKeyIV, @@ -27,29 +27,20 @@ const changePassword2 = ({ encryptedPrivateKeyTag, salt, verifier -}: Props) => - SecurityClient.fetchCall('/api/v1/password/change-password', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - clientProof, - protectedKey, - protectedKeyIV, - protectedKeyTag, - encryptedPrivateKey, - encryptedPrivateKeyIV, - encryptedPrivateKeyTag, - salt, - verifier - }) - }).then(async (res) => { - if (res && res.status === 200) { - return res; - } - console.log('Failed to change the password'); - return undefined; +}: Props) => { + const { data } = await apiRequest.post('/api/v1/password/change-password', { + clientProof, + protectedKey, + protectedKeyIV, + protectedKeyTag, + encryptedPrivateKey, + encryptedPrivateKeyIV, + encryptedPrivateKeyTag, + salt, + verifier }); + + return data; +} export default changePassword2; From e5f6ed3dc7778c1306ed12e4485a0e2440cf4584 Mon Sep 17 00:00:00 2001 From: akhilmhdh Date: Fri, 17 Feb 2023 23:25:56 +0530 Subject: [PATCH 10/16] feat(ui): components changes for new org settings page --- frontend/src/components/v2/Card/Card.tsx | 11 +++++-- .../DeleteActionModal/DeleteActionModal.tsx | 10 ++++--- frontend/src/components/v2/Input/Input.tsx | 8 ++--- frontend/src/components/v2/Modal/Modal.tsx | 2 +- frontend/src/components/v2/Select/Select.tsx | 29 +++++++++++++------ frontend/src/components/v2/Tag/Tag.tsx | 24 +++++++++++++++ frontend/src/components/v2/Tag/index.tsx | 1 + frontend/src/components/v2/index.tsx | 1 + 8 files changed, 65 insertions(+), 21 deletions(-) create mode 100644 frontend/src/components/v2/Tag/Tag.tsx create mode 100644 frontend/src/components/v2/Tag/index.tsx diff --git a/frontend/src/components/v2/Card/Card.tsx b/frontend/src/components/v2/Card/Card.tsx index 4bb3c3674..095018614 100644 --- a/frontend/src/components/v2/Card/Card.tsx +++ b/frontend/src/components/v2/Card/Card.tsx @@ -3,14 +3,19 @@ import { twMerge } from 'tailwind-merge'; export type CardTitleProps = { children: ReactNode; - subTitle?: string; + subTitle?: ReactNode; className?: string; }; export const CardTitle = ({ children, className, subTitle }: CardTitleProps) => ( -
+
{children} - {subTitle &&

{subTitle}

} + {subTitle &&

{subTitle}

}
); diff --git a/frontend/src/components/v2/DeleteActionModal/DeleteActionModal.tsx b/frontend/src/components/v2/DeleteActionModal/DeleteActionModal.tsx index 1d072cc89..31dcc611a 100644 --- a/frontend/src/components/v2/DeleteActionModal/DeleteActionModal.tsx +++ b/frontend/src/components/v2/DeleteActionModal/DeleteActionModal.tsx @@ -5,7 +5,7 @@ import { useToggle } from '@app/hooks'; import { Button } from '../Button'; import { FormControl } from '../FormControl'; import { Input } from '../Input'; -import { Modal, ModalContent } from '../Modal'; +import { Modal, ModalClose, ModalContent } from '../Modal'; type Props = { isOpen?: boolean; @@ -64,9 +64,11 @@ export const DeleteActionModal = ({ > Delete - + + + {' '}
} onClose={onClose} diff --git a/frontend/src/components/v2/Input/Input.tsx b/frontend/src/components/v2/Input/Input.tsx index 28524c72d..ed9ead646 100644 --- a/frontend/src/components/v2/Input/Input.tsx +++ b/frontend/src/components/v2/Input/Input.tsx @@ -84,19 +84,19 @@ export const Input = forwardRef( ): JSX.Element => { return (
- {leftIcon && {leftIcon}} + {leftIcon && {leftIcon}} - {rightIcon && {rightIcon}} + {rightIcon && {rightIcon}}
); } diff --git a/frontend/src/components/v2/Modal/Modal.tsx b/frontend/src/components/v2/Modal/Modal.tsx index c33881772..fc9a90642 100644 --- a/frontend/src/components/v2/Modal/Modal.tsx +++ b/frontend/src/components/v2/Modal/Modal.tsx @@ -9,7 +9,7 @@ import { IconButton } from '../IconButton'; export type ModalContentProps = DialogPrimitive.DialogContentProps & { title?: ReactNode; - subTitle?: string; + subTitle?: ReactNode; footerContent?: ReactNode; onClose?: () => void; }; diff --git a/frontend/src/components/v2/Select/Select.tsx b/frontend/src/components/v2/Select/Select.tsx index f8d559abe..ffeaf3c3f 100644 --- a/frontend/src/components/v2/Select/Select.tsx +++ b/frontend/src/components/v2/Select/Select.tsx @@ -14,18 +14,28 @@ type Props = { dropdownContainerClassName?: string; isLoading?: boolean; position?: 'item-aligned' | 'popper'; + isDisabled?: boolean; icon?: IconProp; }; -export type SelectProps = SelectPrimitive.SelectProps & Props; +export type SelectProps = Omit & Props; export const Select = forwardRef( ( - { children, placeholder, className, isLoading, dropdownContainerClassName, position, ...props }, + { + children, + placeholder, + className, + isLoading, + isDisabled, + dropdownContainerClassName, + position, + ...props + }, ref ): JSX.Element => { return ( - + ( className )} > - + {props.icon ? : placeholder} - {!props.disabled && ( + {!isDisabled && ( @@ -46,7 +56,7 @@ export const Select = forwardRef( ( ; + +const tagVariants = cva('inline-flex whitespace-nowrap text-sm rounded-md mx-1 text-bunker-200 ', { + variants: { + colorSchema: { + gray: 'bg-mineshaft-500', + red: 'bg-red/80 text-bunker-100' + }, + size: { + sm: 'px-2 py-1' + } + } +}); + +export const Tag = ({ children, className, colorSchema = 'gray', size = 'sm' }: Props) => ( +
{children}
+); diff --git a/frontend/src/components/v2/Tag/index.tsx b/frontend/src/components/v2/Tag/index.tsx new file mode 100644 index 000000000..ba2338b7b --- /dev/null +++ b/frontend/src/components/v2/Tag/index.tsx @@ -0,0 +1 @@ +export { Tag } from './Tag'; diff --git a/frontend/src/components/v2/index.tsx b/frontend/src/components/v2/index.tsx index 0f89a3a2b..d69b0bfb4 100644 --- a/frontend/src/components/v2/index.tsx +++ b/frontend/src/components/v2/index.tsx @@ -12,5 +12,6 @@ export * from './Select'; export * from './Spinner'; export * from './Switch'; export * from './Table'; +export * from './Tag'; export * from './TextArea'; export * from './UpgradePlanModal'; From c0d1495761b5fc85d183654006d4de11c7823e69 Mon Sep 17 00:00:00 2001 From: akhilmhdh Date: Fri, 17 Feb 2023 23:26:29 +0530 Subject: [PATCH 11/16] feat(ui): api hooks for new org settings page --- .../src/hooks/api/incidentContacts/index.tsx | 5 ++ .../hooks/api/incidentContacts/queries.tsx | 57 ++++++++++++++++++ .../src/hooks/api/incidentContacts/types.ts | 18 ++++++ frontend/src/hooks/api/index.tsx | 1 + frontend/src/hooks/api/organization/index.ts | 2 +- .../src/hooks/api/organization/queries.tsx | 17 +++++- frontend/src/hooks/api/organization/types.ts | 5 ++ frontend/src/hooks/api/types.ts | 5 +- frontend/src/hooks/api/users/index.tsx | 5 +- frontend/src/hooks/api/users/queries.tsx | 58 ++++++++++++++++++- frontend/src/hooks/api/users/types.ts | 18 +++++- frontend/src/hooks/api/workspace/index.tsx | 2 +- frontend/src/hooks/api/workspace/queries.tsx | 29 ++++++++-- 13 files changed, 207 insertions(+), 15 deletions(-) create mode 100644 frontend/src/hooks/api/incidentContacts/index.tsx create mode 100644 frontend/src/hooks/api/incidentContacts/queries.tsx create mode 100644 frontend/src/hooks/api/incidentContacts/types.ts diff --git a/frontend/src/hooks/api/incidentContacts/index.tsx b/frontend/src/hooks/api/incidentContacts/index.tsx new file mode 100644 index 000000000..6c53db28e --- /dev/null +++ b/frontend/src/hooks/api/incidentContacts/index.tsx @@ -0,0 +1,5 @@ +export { + useAddIncidentContact, + useDeleteIncidentContact, + useGetOrgIncidentContact +} from './queries'; diff --git a/frontend/src/hooks/api/incidentContacts/queries.tsx b/frontend/src/hooks/api/incidentContacts/queries.tsx new file mode 100644 index 000000000..4f028f16d --- /dev/null +++ b/frontend/src/hooks/api/incidentContacts/queries.tsx @@ -0,0 +1,57 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; + +import { apiRequest } from '@app/config/request'; + +import { AddIncidentContactDTO, DeleteIncidentContactDTO, IncidentContact } from './types'; + +const incidentContactKeys = { + getAllContact: (orgId: string) => ['org-incident-contacts', { orgId }] as const +}; + +const fetchOrgIncidentContacts = async (orgId: string) => { + const { data } = await apiRequest.get<{ incidentContactsOrg: IncidentContact[] }>( + `/api/v1/organization/${orgId}/incidentContactOrg` + ); + + return data.incidentContactsOrg; +}; + +export const useGetOrgIncidentContact = (orgId: string) => + useQuery({ + queryKey: incidentContactKeys.getAllContact(orgId), + queryFn: () => fetchOrgIncidentContacts(orgId), + enabled: Boolean(orgId) + }); + +// mutation +export const useAddIncidentContact = () => { + const queryClient = useQueryClient(); + + return useMutation<{}, {}, AddIncidentContactDTO>({ + mutationFn: async ({ orgId, email }) => { + const { data } = await apiRequest.post(`/api/v1/organization/${orgId}/incidentContactOrg`, { + email + }); + return data; + }, + onSuccess: (_, { orgId }) => { + queryClient.invalidateQueries(incidentContactKeys.getAllContact(orgId)); + } + }); +}; + +export const useDeleteIncidentContact = () => { + const queryClient = useQueryClient(); + + return useMutation<{}, {}, DeleteIncidentContactDTO>({ + mutationFn: async ({ orgId, email }) => { + const { data } = await apiRequest.delete(`/api/v1/organization/${orgId}/incidentContactOrg`, { + data: { email } + }); + return data; + }, + onSuccess: (_, { orgId }) => { + queryClient.invalidateQueries(incidentContactKeys.getAllContact(orgId)); + } + }); +}; diff --git a/frontend/src/hooks/api/incidentContacts/types.ts b/frontend/src/hooks/api/incidentContacts/types.ts new file mode 100644 index 000000000..a1ef8ab95 --- /dev/null +++ b/frontend/src/hooks/api/incidentContacts/types.ts @@ -0,0 +1,18 @@ +export type IncidentContact = { + _id: string; + email: string; + organization: string; + __v: number; + createdAt: Date; + updatedAt: Date; +}; + +export type DeleteIncidentContactDTO = { + orgId: string; + email: string; +}; + +export type AddIncidentContactDTO = { + orgId: string; + email: string; +}; diff --git a/frontend/src/hooks/api/index.tsx b/frontend/src/hooks/api/index.tsx index ad9acf0c3..c2035bbba 100644 --- a/frontend/src/hooks/api/index.tsx +++ b/frontend/src/hooks/api/index.tsx @@ -1,4 +1,5 @@ export * from './auth'; +export * from './incidentContacts'; export * from './keys'; export * from './organization'; export * from './serviceTokens'; diff --git a/frontend/src/hooks/api/organization/index.ts b/frontend/src/hooks/api/organization/index.ts index 4d9dd04f8..a11a7dd74 100644 --- a/frontend/src/hooks/api/organization/index.ts +++ b/frontend/src/hooks/api/organization/index.ts @@ -1 +1 @@ -export { useGetOrganization } from './queries'; +export { useGetOrganization, useRenameOrg } from './queries'; diff --git a/frontend/src/hooks/api/organization/queries.tsx b/frontend/src/hooks/api/organization/queries.tsx index b2f9c7ce6..0770fc413 100644 --- a/frontend/src/hooks/api/organization/queries.tsx +++ b/frontend/src/hooks/api/organization/queries.tsx @@ -1,8 +1,8 @@ -import { useQuery } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { apiRequest } from '@app/config/request'; -import { Organization } from './types'; +import { Organization, RenameOrgDTO } from './types'; const organizationKeys = { getUserOrganization: ['organization'] as const @@ -16,3 +16,16 @@ const fetchUserOrganization = async () => { export const useGetOrganization = () => useQuery({ queryKey: organizationKeys.getUserOrganization, queryFn: fetchUserOrganization }); + +// mutation +export const useRenameOrg = () => { + const queryClient = useQueryClient(); + + return useMutation<{}, {}, RenameOrgDTO>({ + mutationFn: ({ newOrgName, orgId }) => + apiRequest.patch(`/api/v1/organization/${orgId}/name`, { name: newOrgName }), + onSuccess: () => { + queryClient.invalidateQueries(organizationKeys.getUserOrganization); + } + }); +}; diff --git a/frontend/src/hooks/api/organization/types.ts b/frontend/src/hooks/api/organization/types.ts index 74720320b..92a5c5f1d 100644 --- a/frontend/src/hooks/api/organization/types.ts +++ b/frontend/src/hooks/api/organization/types.ts @@ -4,3 +4,8 @@ export type Organization = { createAt: string; updatedAt: string; }; + +export type RenameOrgDTO = { + orgId: string; + newOrgName: string; +}; diff --git a/frontend/src/hooks/api/types.ts b/frontend/src/hooks/api/types.ts index 699e9a81d..a8821e5a5 100644 --- a/frontend/src/hooks/api/types.ts +++ b/frontend/src/hooks/api/types.ts @@ -1,14 +1,17 @@ export type { GetAuthTokenAPI } from './auth/types'; +export type { IncidentContact } from './incidentContacts/types'; export type { UserWsKeyPair } from './keys/types'; export type { Organization } from './organization/types'; export type { CreateServiceTokenDTO, ServiceToken } from './serviceTokens/types'; export type { GetSubscriptionPlan, SubscriptionPlan } from './subscriptions/types'; -export type { User } from './users/types'; +export type { AddUserToWsDTO, AddUserToWsRes, OrgUser, User } from './users/types'; export type { CreateEnvironmentDTO, + CreateWorkspaceDTO, DeleteEnvironmentDTO, DeleteWorkspaceDTO, RenameWorkspaceDTO, + ToggleAutoCapitalizationDTO, UpdateEnvironmentDTO, Workspace, WorkspaceEnv, diff --git a/frontend/src/hooks/api/users/index.tsx b/frontend/src/hooks/api/users/index.tsx index 69c0fc98a..0980d4108 100644 --- a/frontend/src/hooks/api/users/index.tsx +++ b/frontend/src/hooks/api/users/index.tsx @@ -1,7 +1,10 @@ export { fetchOrgUsers, + useAddUserToOrg, useAddUserToWs, + useDeleteOrgMembership, useGetOrgUsers, useGetUser, - useLogoutUser + useLogoutUser, + useUpdateOrgUserRole } from './queries'; diff --git a/frontend/src/hooks/api/users/queries.tsx b/frontend/src/hooks/api/users/queries.tsx index 1ddcf3809..b73ac8495 100644 --- a/frontend/src/hooks/api/users/queries.tsx +++ b/frontend/src/hooks/api/users/queries.tsx @@ -1,4 +1,4 @@ -import { useMutation, useQuery } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { decryptAssymmetric, @@ -8,7 +8,15 @@ import { apiRequest } from '@app/config/request'; import { setAuthToken } from '@app/reactQuery'; import { useUploadWsKey } from '../keys/queries'; -import { AddUserToWsDTO, AddUserToWsRes, OrgUser, User } from './types'; +import { + AddUserToOrgDTO, + AddUserToWsDTO, + AddUserToWsRes, + DeletOrgMembershipDTO, + OrgUser, + UpdateOrgUserRoleDTO, + User +} from './types'; const userKeys = { getUser: ['user'] as const, @@ -32,7 +40,11 @@ export const fetchOrgUsers = async (orgId: string) => { }; export const useGetOrgUsers = (orgId: string) => - useQuery(userKeys.getOrgUsers(orgId), () => fetchOrgUsers(orgId)); + useQuery({ + queryKey: userKeys.getOrgUsers(orgId), + queryFn: () => fetchOrgUsers(orgId), + enabled: Boolean(orgId) + }); // mutation export const useAddUserToWs = () => { @@ -69,6 +81,46 @@ export const useAddUserToWs = () => { }); }; +export const useAddUserToOrg = () => { + const queryClient = useQueryClient(); + + return useMutation<{}, {}, AddUserToOrgDTO>({ + mutationFn: (dto) => apiRequest.post(`/api/v1/invite-org/signup`, dto), + onSuccess: (_, { organizationId }) => { + queryClient.invalidateQueries(userKeys.getOrgUsers(organizationId)); + } + }); +}; + +export const useDeleteOrgMembership = () => { + const queryClient = useQueryClient(); + + return useMutation<{}, {}, DeletOrgMembershipDTO>({ + mutationFn: ({ membershipId }) => apiRequest.delete(`/api/v1/membership-org/${membershipId}`), + onSuccess: (_, { orgId }) => { + queryClient.invalidateQueries(userKeys.getOrgUsers(orgId)); + } + }); +}; + +export const useUpdateOrgUserRole = () => { + const queryClient = useQueryClient(); + + return useMutation<{}, {}, UpdateOrgUserRoleDTO>({ + mutationFn: ({ organizationId, membershipId, role }) => + apiRequest.patch(`/api/v2/organizations/${organizationId}/memberships/${membershipId}`, { + role + }), + onSuccess: (_, { organizationId }) => { + queryClient.invalidateQueries(userKeys.getOrgUsers(organizationId)); + }, + // to remove old states + onError: (_, { organizationId }) => { + queryClient.invalidateQueries(userKeys.getOrgUsers(organizationId)); + } + }); +}; + export const useLogoutUser = () => useMutation({ mutationFn: () => apiRequest.post('/api/v1/auth/logout'), diff --git a/frontend/src/hooks/api/users/types.ts b/frontend/src/hooks/api/users/types.ts index 1b17cd023..b3fb5cf6b 100644 --- a/frontend/src/hooks/api/users/types.ts +++ b/frontend/src/hooks/api/users/types.ts @@ -32,7 +32,7 @@ export type OrgUser = { inviteEmail: string; organization: string; role: 'owner' | 'admin' | 'member'; - status: 'invited' | 'accepted'; + status: 'invited' | 'accepted' | 'verified' | 'completed'; deniedPermissions: any[]; }; @@ -45,3 +45,19 @@ export type AddUserToWsRes = { invitee: OrgUser['user']; latestKey: UserWsKeyPair; }; + +export type UpdateOrgUserRoleDTO = { + organizationId: string; + membershipId: string; + role: string; +}; + +export type DeletOrgMembershipDTO = { + membershipId: string; + orgId: string; +}; + +export type AddUserToOrgDTO = { + inviteeEmail: string; + organizationId: string; +}; diff --git a/frontend/src/hooks/api/workspace/index.tsx b/frontend/src/hooks/api/workspace/index.tsx index 52a01029e..cab8617bd 100644 --- a/frontend/src/hooks/api/workspace/index.tsx +++ b/frontend/src/hooks/api/workspace/index.tsx @@ -3,9 +3,9 @@ export { useCreateWsEnvironment, useDeleteWorkspace, useDeleteWsEnvironment, + useGetUserWorkspaceMemberships, useGetUserWorkspaces, useGetWorkspaceById, useRenameWorkspace, useToggleAutoCapitalization, useUpdateWsEnvironment} from './queries'; - \ No newline at end of file diff --git a/frontend/src/hooks/api/workspace/queries.tsx b/frontend/src/hooks/api/workspace/queries.tsx index 0c4ba970b..2bca2e9a5 100644 --- a/frontend/src/hooks/api/workspace/queries.tsx +++ b/frontend/src/hooks/api/workspace/queries.tsx @@ -13,16 +13,18 @@ import { Workspace } from './types'; - const workspaceKeys = { getWorkspaceById: (workspaceId: string) => [{ workspaceId }, 'workspace'] as const, + getWorkspaceMemberships: (orgId: string) => [{ orgId }, 'workspace-memberships'], getAllUserWorkspace: ['workspaces'] as const }; const fetchWorkspaceById = async (workspaceId: string) => { - const { data } = await apiRequest.get<{ workspace: Workspace }>(`/api/v1/workspace/${workspaceId}`); - return data.workspace; -} + const { data } = await apiRequest.get<{ workspace: Workspace }>( + `/api/v1/workspace/${workspaceId}` + ); + return data.workspace; +}; const fetchUserWorkspaces = async () => { const { data } = await apiRequest.get<{ workspaces: Workspace[] }>('/api/v1/workspace'); @@ -40,6 +42,21 @@ export const useGetWorkspaceById = (workspaceId: string) => { export const useGetUserWorkspaces = () => useQuery(workspaceKeys.getAllUserWorkspace, fetchUserWorkspaces); +const fetchUserWorkspaceMemberships = async (orgId: string) => { + const { data } = await apiRequest.get>( + `/api/v1/organization/${orgId}/workspace-memberships` + ); + return data; +}; + +// to get all userids in an org with the workspace they are part of +export const useGetUserWorkspaceMemberships = (orgId: string) => + useQuery({ + queryKey: workspaceKeys.getWorkspaceMemberships(orgId), + queryFn: () => fetchUserWorkspaceMemberships(orgId), + enabled: Boolean(orgId) + }); + // mutation export const useCreateWorkspace = () => { const queryClient = useQueryClient(); @@ -70,7 +87,9 @@ export const useToggleAutoCapitalization = () => { return useMutation<{}, {}, ToggleAutoCapitalizationDTO>({ mutationFn: ({ workspaceID, state }) => - apiRequest.patch(`/api/v2/workspace/${workspaceID}/auto-capitalization`, { autoCapitalization: state }), + apiRequest.patch(`/api/v2/workspace/${workspaceID}/auto-capitalization`, { + autoCapitalization: state + }), onSuccess: () => { queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace); } From 66f1c57a2a827b11bc8e054b1a6bd691f61dd4ba Mon Sep 17 00:00:00 2001 From: akhilmhdh Date: Fri, 17 Feb 2023 23:27:51 +0530 Subject: [PATCH 12/16] feat(ui): completed org settings page revamp --- .../basic/table/ProjectUsersTable.tsx | 234 ++++++++---- frontend/src/pages/settings/org/[id].tsx | 361 +----------------- .../OrgSettingsPage/OrgSettingsPage.tsx | 308 +++++++++++++++ .../OrgIncidentContactsTable.tsx | 171 +++++++++ .../OrgIncidentContactsTable/index.tsx | 1 + .../OrgMembersTable/OrgMembersTable.tsx | 274 +++++++++++++ .../components/OrgMembersTable/index.tsx | 1 + .../OrgNameChangeSection.tsx | 68 ++++ .../components/OrgNameChangeSection/index.tsx | 1 + .../OrgSettingsPage/components/index.tsx | 3 + .../views/Settings/OrgSettingsPage/index.tsx | 1 + 11 files changed, 990 insertions(+), 433 deletions(-) create mode 100644 frontend/src/views/Settings/OrgSettingsPage/OrgSettingsPage.tsx create mode 100644 frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsTable/OrgIncidentContactsTable.tsx create mode 100644 frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsTable/index.tsx create mode 100644 frontend/src/views/Settings/OrgSettingsPage/components/OrgMembersTable/OrgMembersTable.tsx create mode 100644 frontend/src/views/Settings/OrgSettingsPage/components/OrgMembersTable/index.tsx create mode 100644 frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx create mode 100644 frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/index.tsx create mode 100644 frontend/src/views/Settings/OrgSettingsPage/components/index.tsx create mode 100644 frontend/src/views/Settings/OrgSettingsPage/index.tsx diff --git a/frontend/src/components/basic/table/ProjectUsersTable.tsx b/frontend/src/components/basic/table/ProjectUsersTable.tsx index 27346195c..438517aa5 100644 --- a/frontend/src/components/basic/table/ProjectUsersTable.tsx +++ b/frontend/src/components/basic/table/ProjectUsersTable.tsx @@ -30,7 +30,7 @@ type Props = { type EnvironmentProps = { name: string; slug: string; -} +}; /** * This is the component that shows the users of a certin project @@ -91,26 +91,38 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => { }); }; - const handlePermissionUpdate = (index: number, val: string, membershipId: string, slug: string ) => { - let denials: { ability: string; environmentSlug: string; }[]; - if (val === "Read Only") { - denials = [{ - ability: "write", - environmentSlug: slug - }]; - } else if (val === "No Access") { - denials = [{ - ability: "write", - environmentSlug: slug - }, { - ability: "read", - environmentSlug: slug - }]; - } else if (val === "Add Only") { - denials = [{ - ability: "read", - environmentSlug: slug - }]; + const handlePermissionUpdate = ( + index: number, + val: string, + membershipId: string, + slug: string + ) => { + let denials: { ability: string; environmentSlug: string }[]; + if (val === 'Read Only') { + denials = [ + { + ability: 'write', + environmentSlug: slug + } + ]; + } else if (val === 'No Access') { + denials = [ + { + ability: 'write', + environmentSlug: slug + }, + { + ability: 'read', + environmentSlug: slug + } + ]; + } else if (val === 'Add Only') { + denials = [ + { + ability: 'read', + environmentSlug: slug + } + ]; } else { denials = []; } @@ -118,8 +130,12 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => { if (currentPlan !== plans.professional && host === 'https://app.infisical.com') { setIsUpgradeModalOpen(true); } else { - const allDenials = userData[index].deniedPermissions.filter((perm: { ability: string; environmentSlug: string; }) => perm.environmentSlug !== slug).concat(denials); - updateUserProjectPermission({ membershipId, denials: allDenials}); + const allDenials = userData[index].deniedPermissions + .filter( + (perm: { ability: string; environmentSlug: string }) => perm.environmentSlug !== slug + ) + .concat(denials); + updateUserProjectPermission({ membershipId, denials: allDenials }); changeData([ ...userData.slice(0, index), ...[ @@ -156,7 +172,7 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => { orgId }); if (subscriptions) { - setCurrentPlan(subscriptions.data[0].plan.product) + setCurrentPlan(subscriptions.data[0].plan.product); } })(); }, [userData, myUser]); @@ -186,25 +202,28 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => { const closeUpgradeModal = () => { setIsUpgradeModalOpen(false); - } + }; return ( -
-
+
+
- - +
+ - - - - {workspaceEnvs.map(env => ( - + + + {workspaceEnvs.map((env) => ( + ))} @@ -227,28 +246,28 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => { ) .map((row, index) => ( - - - - {workspaceEnvs.map((env) => )} - + ))} + diff --git a/frontend/src/pages/settings/org/[id].tsx b/frontend/src/pages/settings/org/[id].tsx index 562c45c9b..59c8c373c 100644 --- a/frontend/src/pages/settings/org/[id].tsx +++ b/frontend/src/pages/settings/org/[id].tsx @@ -1,374 +1,21 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { useEffect, useState } from 'react'; import Head from 'next/head'; -import { useRouter } from 'next/router'; import { useTranslation } from 'next-i18next'; -import { faCheck, faMagnifyingGlass, faPlus, faX } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { plans } from 'public/data/frequentConstants'; -import Button from '@app/components/basic/buttons/Button'; -import AddIncidentContactDialog from '@app/components/basic/dialog/AddIncidentContactDialog'; -import AddUserDialog from '@app/components/basic/dialog/AddUserDialog'; -import UpgradePlanModal from '@app/components/basic/dialog/UpgradePlan'; -import InputField from '@app/components/basic/InputField'; -import UserTable from '@app/components/basic/table/UserTable'; -import NavHeader from '@app/components/navigation/NavHeader'; -import guidGenerator from '@app/components/utilities/randomId'; import { getTranslatedServerSideProps } from '@app/components/utilities/withTranslateProps'; - -import addUserToOrg from '../../api/organization/addUserToOrg'; -import deleteIncidentContact from '../../api/organization/deleteIncidentContact'; -import getIncidentContacts from '../../api/organization/getIncidentContacts'; -import getOrganization from '../../api/organization/GetOrg'; -import getOrganizationSubscriptions from '../../api/organization/GetOrgSubscription'; -import getOrganizationUsers from '../../api/organization/GetOrgUsers'; -import renameOrg from '../../api/organization/renameOrg'; -import getUser from '../../api/user/getUser'; -import deleteWorkspace from '../../api/workspace/deleteWorkspace'; -import getWorkspaces from '../../api/workspace/getWorkspaces'; +import { OrgSettingsPage } from '@app/views/Settings/OrgSettingsPage'; export default function SettingsOrg() { - const [buttonReady, setButtonReady] = useState(false); - const router = useRouter(); - const host = window.location.origin; - const [orgName, setOrgName] = useState(''); - const [emailUser, setEmailUser] = useState(''); - const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = useState(''); - const [searchUsers, setSearchUsers] = useState(''); - const [isAddIncidentContactOpen, setIsAddIncidentContactOpen] = useState(false); - const [isAddUserOpen, setIsAddUserOpen] = useState(router.asPath.split('?')[1] === 'invite'); - const [incidentContacts, setIncidentContacts] = useState([]); - const [searchIncidentContact, setSearchIncidentContact] = useState(''); - const [userList, setUserList] = useState([]); - const [personalEmail, setPersonalEmail] = useState(''); - const [email, setEmail] = useState(''); - const [currentPlan, setCurrentPlan] = useState(''); - - const workspaceId = router.query.id as string; - const { t } = useTranslation(); - useEffect(() => { - (async () => { - const orgId = localStorage.getItem('orgData.id') as string; - const org = await getOrganization({ - orgId - }); - - setOrgName(org.name); - const incidentContactsData = await getIncidentContacts( - localStorage.getItem('orgData.id') as string - ); - - setIncidentContacts(incidentContactsData?.map((contact) => contact.email)); - - const user = await getUser(); - setPersonalEmail(user.email); - - const orgUsers = await getOrganizationUsers({ - orgId - }); - - setUserList( - orgUsers.map((orgUser) => ({ - key: guidGenerator(), - firstName: orgUser.user?.firstName, - lastName: orgUser.user?.lastName, - email: orgUser.user?.email == null ? orgUser.inviteEmail : orgUser.user?.email, - role: orgUser?.role, - status: orgUser?.status, - userId: orgUser.user?._id, - membershipId: orgUser._id, - publicKey: orgUser.user?.publicKey - })) - ); - - const subscriptions = await getOrganizationSubscriptions({ - orgId - }); - if (subscriptions) { - setCurrentPlan(subscriptions.data[0].plan.product); - } - })(); - }, []); - - const modifyOrgName = (newName: string) => { - setButtonReady(true); - setOrgName(newName); - }; - - const submitChanges = (newOrgName: string) => { - renameOrg(localStorage.getItem('orgData.id') as string, newOrgName); - setButtonReady(false); - }; - - const closeAddUserModal = () => { - setIsAddUserOpen(false); - }; - - const closeAddIncidentContactModal = () => { - setIsAddIncidentContactOpen(false); - }; - - const openAddUserModal = () => { - setIsAddUserOpen(true); - }; - - const openAddIncidentContactModal = () => { - setIsAddIncidentContactOpen(true); - }; - - const submitAddUserModal = async (newUserEmail: string) => { - await addUserToOrg(newUserEmail, localStorage.getItem('orgData.id') as string); - setEmail(''); - setIsAddUserOpen(false); - router.reload(); - }; - - const deleteIncidentContactFully = (incidentContact: string) => { - setIncidentContacts(incidentContacts.filter((contact) => contact !== incidentContact)); - deleteIncidentContact(localStorage.getItem('orgData.id') as string, incidentContact); - }; - - /** - * This function deleted a workspace. - * It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete - * It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete. - * It then deletes the workspace and forwards the user to another aviable workspace. - */ - const executeDeletingWorkspace = async () => { - const userWorkspaces = await getWorkspaces(); - - if (userWorkspaces.length > 1) { - if ( - userWorkspaces.filter((workspace) => workspace._id === workspaceId)[0].name === - workspaceToBeDeletedName - ) { - await deleteWorkspace(workspaceId); - const ws = await getWorkspaces(); - router.push(`/dashboard/${ws[0]._id}`); - } - } - }; - return ( -
+ <> {t('common:head-title', { title: t('settings-org:title') })} -
-
- - -
-
-

{t('settings-org:title')}

-

- {t('settings-org:description')} -

-
-
-
-
-
-
-
-

- {t('common:display-name')} -

- -
-
-
-
-
-
-
-
-
-

- {t('section-members:org-members')} -

-

- {t('section-members:org-members-description')} -

- - = 5 && currentPlan === plans.starter && host === 'https://app.infisical.com'} - onClose={closeAddUserModal} - text="You can add more members if you switch to Infisical's Team plan." - /> - {/* */} -
-
- - setSearchUsers(e.target.value)} - placeholder={t('section-members:search-members') as string} - /> -
-
-
-
- {userList && ( -
- -
- )} -
- -
-
-
-

- {t('section-incident:incident-contacts')} -

-

- {t('section-incident:incident-contacts-description')} -

-
-
-
-
-
- - setSearchIncidentContact(e.target.value)} - placeholder={t('common:search') as string} - /> -
- {incidentContacts?.filter((incidentEmail) => - incidentEmail.includes(searchIncidentContact) - ).length > 0 ? ( - incidentContacts - .filter((incidentEmail) => incidentEmail.includes(searchIncidentContact)) - .map((contact) => ( -
-

{contact}

-
-
-
- )) - ) : ( -
-

{t('section-incident:no-incident-contacts')}

-
- )} -
- - {/*
-

- Danger Zone -

-

- As soon as you delete an organization, you will - not be able to undo it. This will immediately - remove all organization members and cancel your - subscription. If you still want to do that, - please enter the name of the organization below. -

-
- -
- -

- Note: You can only delete a project in case you - have more than one. -

-
*/} -
-
-
-
+ + ); } diff --git a/frontend/src/views/Settings/OrgSettingsPage/OrgSettingsPage.tsx b/frontend/src/views/Settings/OrgSettingsPage/OrgSettingsPage.tsx new file mode 100644 index 000000000..26c06a9d2 --- /dev/null +++ b/frontend/src/views/Settings/OrgSettingsPage/OrgSettingsPage.tsx @@ -0,0 +1,308 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { useTranslation } from 'next-i18next'; +import { plans } from 'public/data/frequentConstants'; + +import { useNotificationContext } from '@app/components/context/Notifications/NotificationProvider'; +import NavHeader from '@app/components/navigation/NavHeader'; +import { + decryptAssymmetric, + encryptAssymmetric +} from '@app/components/utilities/cryptography/crypto'; +import { useOrganization, useSubscription, useUser, useWorkspace } from '@app/context'; +import { + useAddIncidentContact, + useAddUserToOrg, + useDeleteIncidentContact, + useDeleteOrgMembership, + useGetOrgIncidentContact, + useGetOrgUsers, + useGetUserWorkspaceMemberships, + useGetUserWsKey, + useRenameOrg, + useUpdateOrgUserRole, + useUploadWsKey +} from '@app/hooks/api'; + +import { OrgIncidentContactsTable, OrgMembersTable, OrgNameChangeSection } from './components'; + +export const OrgSettingsPage = () => { + const host = window.location.origin; + + const { t } = useTranslation(); + const { currentOrg } = useOrganization(); + const { currentWorkspace } = useWorkspace(); + const { user } = useUser(); + const { subscriptionPlan } = useSubscription(); + const { createNotification } = useNotificationContext(); + + const orgId = currentOrg?._id || ''; + const { data: orgUsers } = useGetOrgUsers(orgId); + const { data: workspaceMemberships } = useGetUserWorkspaceMemberships(orgId); + const { data: wsKey } = useGetUserWsKey(currentWorkspace?._id || ''); + const { data: incidentContact } = useGetOrgIncidentContact(orgId); + + const renameOrg = useRenameOrg(); + const removeUserOrgMembership = useDeleteOrgMembership(); + const addUserToOrg = useAddUserToOrg(); + const updateOrgUserRole = useUpdateOrgUserRole(); + const uploadWsKey = useUploadWsKey(); + const addIncidentContact = useAddIncidentContact(); + const removeIncidentContact = useDeleteIncidentContact(); + + const isMoreUsersNotAllowed = + (orgUsers || []).length >= 5 && + subscriptionPlan === plans.starter && + host === 'https://app.infisical.com'; + + const onRenameOrg = async (name: string) => { + if (!currentOrg?._id) return; + + try { + await renameOrg.mutateAsync({ orgId: currentOrg?._id, newOrgName: name }); + createNotification({ + text: 'Successfully renamed organization', + type: 'success' + }); + } catch (error) { + console.error(error); + createNotification({ + text: 'Failed to rename organization', + type: 'error' + }); + } + }; + + const onRemoveUserOrgMembership = async (membershipId: string) => { + if (!currentOrg?._id) return; + + try { + await removeUserOrgMembership.mutateAsync({ orgId: currentOrg?._id, membershipId }); + createNotification({ + text: 'Successfully removed used from org', + type: 'success' + }); + } catch (error) { + console.error(error); + createNotification({ + text: 'Failed to remove user from org', + type: 'error' + }); + } + }; + const onAddUserToOrg = async (email: string) => { + if (!currentOrg?._id) return; + + try { + await addUserToOrg.mutateAsync({ organizationId: currentOrg?._id, inviteeEmail: email }); + createNotification({ + text: 'Successfully invited user to org', + type: 'success' + }); + } catch (error) { + console.error(error); + createNotification({ + text: 'Failed to invite user to org', + type: 'error' + }); + } + }; + + const onUpdateOrgUserRole = async (membershipId: string, role: string) => { + if (!currentOrg?._id) return; + + try { + await updateOrgUserRole.mutateAsync({ organizationId: currentOrg?._id, membershipId, role }); + createNotification({ + text: 'Successfully updated user role', + type: 'success' + }); + } catch (error) { + console.error(error); + createNotification({ + text: 'Failed to update user role', + type: 'error' + }); + } + }; + + const onGrantUserAccess = async (userId: string, publicKey: string) => { + try { + const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY') as string; + if (!PRIVATE_KEY || !wsKey) return; + + // assymmetrically decrypt symmetric key with local private key + const key = decryptAssymmetric({ + ciphertext: wsKey.encryptedKey, + nonce: wsKey.nonce, + publicKey: wsKey.sender.publicKey, + privateKey: PRIVATE_KEY + }); + + const { ciphertext, nonce } = encryptAssymmetric({ + plaintext: key, + publicKey, + privateKey: PRIVATE_KEY + }); + + await uploadWsKey.mutateAsync({ + userId, + nonce, + encryptedKey: ciphertext, + workspaceId: currentWorkspace?._id || '' + }); + } catch (err) { + console.error(err); + createNotification({ + text: 'Failed to grant access to user', + type: 'error' + }); + } + }; + + const onAddIncidentContact = async (email: string) => { + if (!currentOrg?._id) return; + + try { + await addIncidentContact.mutateAsync({ orgId, email }); + createNotification({ + text: 'Successfully added incident contact', + type: 'success' + }); + } catch (error) { + console.error(error); + createNotification({ + text: 'Failed to add incident contact', + type: 'error' + }); + } + }; + + const onRemoveIncidentContact = async (email: string) => { + if (!currentOrg?._id) return; + + try { + await removeIncidentContact.mutateAsync({ orgId, email }); + createNotification({ + text: 'Successfully removed incident contact', + type: 'success' + }); + } catch (error) { + console.error(error); + createNotification({ + text: 'Failed to remove incident contact', + type: 'error' + }); + } + }; + + /** + * This function deleted a workspace. + * It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete + * It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete. + * It then deletes the workspace and forwards the user to another aviable workspace. + */ + // const executeDeletingWorkspace = async () => { + // const userWorkspaces = await getWorkspaces(); + // + // if (userWorkspaces.length > 1) { + // if ( + // userWorkspaces.filter((workspace) => workspace._id === workspaceId)[0].name === + // workspaceToBeDeletedName + // ) { + // await deleteWorkspace(workspaceId); + // const ws = await getWorkspaces(); + // router.push(`/dashboard/${ws[0]._id}`); + // } + // } + // }; + // + return ( +
+ +
+
+

{t('settings-org:title')}

+

+ {t('settings-org:description')} +

+
+
+
+ +
+

+ {t('section-members:org-members')} +

+

+ {t('section-members:org-members-description')} +

+ +
+
+
+
+

+ {t('section-incident:incident-contacts')} +

+

+ {t('section-incident:incident-contacts-description')} +

+
+
+
+ +
+
+ {/*
+

+ Danger Zone +

+

+ As soon as you delete an organization, you will + not be able to undo it. This will immediately + remove all organization members and cancel your + subscription. If you still want to do that, + please enter the name of the organization below. +

+
+ +
+ +

+ Note: You can only delete a project in case you + have more than one. +

+
*/} +
+
+ ); +}; diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsTable/OrgIncidentContactsTable.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsTable/OrgIncidentContactsTable.tsx new file mode 100644 index 000000000..296e5031d --- /dev/null +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsTable/OrgIncidentContactsTable.tsx @@ -0,0 +1,171 @@ +import { useState } from 'react'; +import { Controller, useForm } from 'react-hook-form'; +import { faMagnifyingGlass, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { yupResolver } from '@hookform/resolvers/yup'; +import * as yup from 'yup'; + +import { + Button, + DeleteActionModal, + FormControl, + IconButton, + Input, + Modal, + ModalContent, + Table, + TableContainer, + TBody, + Td, + Th, + THead, + Tr +} from '@app/components/v2'; +import { usePopUp } from '@app/hooks'; +import { IncidentContact } from '@app/hooks/api/types'; + +type Props = { + contacts?: IncidentContact[]; + onRemoveContact: (email: string) => Promise; + onAddContact: (email: string) => Promise; +}; + +const addContactFormSchema = yup.object({ + email: yup.string().email().required().label('Email').trim() +}); + +type TAddContactForm = yup.InferType; + +export const OrgIncidentContactsTable = ({ + contacts = [], + onAddContact, + onRemoveContact +}: Props) => { + const [searchContact, setSearchContact] = useState(''); + const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ + 'addContact', + 'removeContact' + ] as const); + + const { + control, + handleSubmit, + reset, + formState: { isSubmitting } + } = useForm({ resolver: yupResolver(addContactFormSchema) }); + + const onAddIncidentContact = ({ email }: TAddContactForm) => { + onAddContact(email); + handlePopUpClose('addContact'); + reset(); + }; + + const onRemoveIncidentContact = async () => { + const incidentContactEmail = (popUp?.removeContact?.data as { email: string })?.email; + await onRemoveContact(incidentContactEmail); + handlePopUpClose('removeContact'); + }; + + return ( +
+
+
+ setSearchContact(e.target.value)} + leftIcon={} + placeholder="Search incident contact by email..." + /> +
+
+ +
+
+
+ +
NAMEEMAILROLE - {env.slug.toUpperCase()}
+
NAMEEMAILROLE + + {env.slug.toUpperCase()} +
+
{/* PERMISSION */}
+ {row.firstName} {row.lastName} + {row.email} -
- handleRoleUpdate(index, e)} value={row.role} - disabled={myRole !== 'admin' || myUser === row.email} + isDisabled={myRole !== 'admin' || myUser === row.email} // onOpenChange={(open) => setIsOpen(open)} > Admin Member {row.status === 'completed' && myUser !== row.email && ( -
+
- - No Access - Read Only - Add Only - Read & Write - - + + {myUser !== row.email && // row.role !== "admin" && myRole !== 'member' ? ( -
+
) : ( -
+
)}
+ + + + + + + {contacts + ?.filter(({ email }) => email.toLocaleLowerCase().includes(searchContact)) + ?.map(({ email }) => ( + + + + + ))} + +
Email +
{email} + handlePopUpOpen('removeContact', { email })} + > + + +
+ +
+ { + handlePopUpToggle('addContact', isOpen); + reset(); + }} + > + + + ( + + + + )} + /> +
+ + +
+ +
+
+ handlePopUpToggle('removeContact', isOpen)} + onDeleteApproved={onRemoveIncidentContact} + /> +
+ ); +}; diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsTable/index.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsTable/index.tsx new file mode 100644 index 000000000..8fbc24611 --- /dev/null +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgIncidentContactsTable/index.tsx @@ -0,0 +1 @@ +export { OrgIncidentContactsTable } from './OrgIncidentContactsTable'; diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgMembersTable/OrgMembersTable.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgMembersTable/OrgMembersTable.tsx new file mode 100644 index 000000000..e23bf42ba --- /dev/null +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgMembersTable/OrgMembersTable.tsx @@ -0,0 +1,274 @@ +import { useMemo, useState } from 'react'; +import { Controller, useForm } from 'react-hook-form'; +import { faMagnifyingGlass, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { yupResolver } from '@hookform/resolvers/yup'; +import * as yup from 'yup'; + +import { + Button, + DeleteActionModal, + FormControl, + IconButton, + Input, + Modal, + ModalContent, + Select, + SelectItem, + Table, + TableContainer, + Tag, + TBody, + Td, + Th, + THead, + Tr, + UpgradePlanModal +} from '@app/components/v2'; +import { usePopUp } from '@app/hooks'; +import { OrgUser, Workspace } from '@app/hooks/api/types'; + +type Props = { + members?: OrgUser[]; + workspaceMemberships?: Record; + orgName: string; + isMoreUserNotAllowed: boolean; + onRemoveMember: (userId: string) => Promise; + onInviteMember: (email: string) => Promise; + onRoleChange: (membershipId: string, role: string) => Promise; + onGrantAccess: (userId: string, publicKey: string) => Promise; + // the current user id to block remove org button + userId: string; +}; + +const addMemberFormSchema = yup.object({ + email: yup.string().email().required().label('Email').trim() +}); + +type TAddMemberForm = yup.InferType; + +export const OrgMembersTable = ({ + members = [], + workspaceMemberships = {}, + orgName, + isMoreUserNotAllowed, + onRemoveMember, + onInviteMember, + onGrantAccess, + onRoleChange, + userId +}: Props) => { + const [searchMemberFilter, setSearchMemberFilter] = useState(''); + const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ + 'addMember', + 'removeMember', + 'upgradePlan' + ] as const); + + const { + control, + handleSubmit, + reset, + formState: { isSubmitting } + } = useForm({ resolver: yupResolver(addMemberFormSchema) }); + + const onAddMember = ({ email }: TAddMemberForm) => { + onInviteMember(email); + handlePopUpClose('addMember'); + reset(); + }; + + const onRemoveOrgMemberApproved = async () => { + const orgMembershipId = (popUp?.removeMember?.data as { id: string })?.id; + await onRemoveMember(orgMembershipId); + handlePopUpClose('removeMember'); + }; + + const isIamOwner = useMemo( + () => members.find(({ user }) => userId === user?._id)?.role === 'owner', + [userId, members] + ); + + const filterdUser = useMemo( + () => + members.filter( + ({ user, inviteEmail }) => + user?.firstName?.toLowerCase().includes(searchMemberFilter) || + user?.lastName?.toLowerCase().includes(searchMemberFilter) || + user?.email?.toLowerCase().includes(searchMemberFilter) || + inviteEmail?.includes(searchMemberFilter) + ), + [members, searchMemberFilter] + ); + + return ( +
+
+
+ setSearchMemberFilter(e.target.value)} + leftIcon={} + placeholder="Search members..." + /> +
+
+ +
+
+
+ + + + + + + + + + + + {filterdUser.map(({ user, inviteEmail, role, _id: orgMembershipId, status }) => { + const name = user ? `${user.firstName} ${user.lastName}` : '-'; + const email = user?.email || inviteEmail; + const userWs = workspaceMemberships?.[user?._id]; + + return ( + + + + + + + + ); + })} + +
NameEmailRoleProjects +
{name}{email} + {status === 'accepted' && ( + + )} + {(status === 'invited' || status === 'verified') && ( + + )} + {status === 'completed' && ( + + )} + + {userWs ? ( + userWs?.map(({ name: wsName, _id }) => ( + + {wsName} + + )) + ) : ( + This user isn't part of any projects yet + )} + + handlePopUpOpen('removeMember', { id: orgMembershipId })} + > + + +
+
+
+ { + handlePopUpToggle('addMember', isOpen); + reset(); + }} + > + + An invite is specific to an email address and expires after 1 day. +
+ For security reasons, you will need to separately add members to projects. + + } + > +
+ ( + + + + )} + /> +
+ + +
+ +
+
+ handlePopUpToggle('removeMember', isOpen)} + onDeleteApproved={onRemoveOrgMemberApproved} + /> + handlePopUpToggle('upgradePlan', isOpen)} + text="You can add custom environments if you switch to Infisical's Team plan." + /> +
+ ); +}; diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgMembersTable/index.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgMembersTable/index.tsx new file mode 100644 index 000000000..729d06592 --- /dev/null +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgMembersTable/index.tsx @@ -0,0 +1 @@ +export { OrgMembersTable } from './OrgMembersTable'; diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx new file mode 100644 index 000000000..9b18285c0 --- /dev/null +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx @@ -0,0 +1,68 @@ +import { useEffect } from 'react'; +import { Controller, useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import { faCheck } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { yupResolver } from '@hookform/resolvers/yup'; +import * as yup from 'yup'; + +import { Button, FormControl, Input } from '@app/components/v2'; + +type Props = { + orgName?: string; + onOrgNameChange: (name: string) => Promise; +}; + +const formSchema = yup.object({ + name: yup.string().required().label('Project Name') +}); + +type FormData = yup.InferType; + +export const OrgNameChangeSection = ({ onOrgNameChange, orgName }: Props): JSX.Element => { + const { + handleSubmit, + control, + reset, + formState: { isDirty, isSubmitting } + } = useForm({ resolver: yupResolver(formSchema) }); + const { t } = useTranslation(); + + useEffect(() => { + reset({ name: orgName }); + }, [orgName]); + + const onFormSubmit = async ({ name }: FormData) => { + await onOrgNameChange(name); + }; + + return ( +
+
+

{t('common:display-name')}

+
+ ( + + + + )} + control={control} + name="name" + /> +
+ +
+
+ ); +}; diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/index.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/index.tsx new file mode 100644 index 000000000..b668d3732 --- /dev/null +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgNameChangeSection/index.tsx @@ -0,0 +1 @@ +export { OrgNameChangeSection } from './OrgNameChangeSection'; diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/index.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/index.tsx new file mode 100644 index 000000000..15a09b0d2 --- /dev/null +++ b/frontend/src/views/Settings/OrgSettingsPage/components/index.tsx @@ -0,0 +1,3 @@ +export { OrgIncidentContactsTable } from './OrgIncidentContactsTable'; +export { OrgMembersTable } from './OrgMembersTable'; +export { OrgNameChangeSection } from './OrgNameChangeSection'; diff --git a/frontend/src/views/Settings/OrgSettingsPage/index.tsx b/frontend/src/views/Settings/OrgSettingsPage/index.tsx new file mode 100644 index 000000000..3ea2e3b0f --- /dev/null +++ b/frontend/src/views/Settings/OrgSettingsPage/index.tsx @@ -0,0 +1 @@ +export { OrgSettingsPage } from './OrgSettingsPage'; From 243c6ca22e5b5f06bd854090a75f2c7ef843e4d8 Mon Sep 17 00:00:00 2001 From: akhilmhdh Date: Mon, 20 Feb 2023 23:13:46 +0530 Subject: [PATCH 13/16] feat: changed delete org membership to v2 --- frontend/src/hooks/api/users/queries.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/hooks/api/users/queries.tsx b/frontend/src/hooks/api/users/queries.tsx index b73ac8495..c6b35960d 100644 --- a/frontend/src/hooks/api/users/queries.tsx +++ b/frontend/src/hooks/api/users/queries.tsx @@ -96,7 +96,8 @@ export const useDeleteOrgMembership = () => { const queryClient = useQueryClient(); return useMutation<{}, {}, DeletOrgMembershipDTO>({ - mutationFn: ({ membershipId }) => apiRequest.delete(`/api/v1/membership-org/${membershipId}`), + mutationFn: ({ membershipId, orgId }) => + apiRequest.delete(`/api/v2/organizations/${orgId}/memberships/${membershipId}`), onSuccess: (_, { orgId }) => { queryClient.invalidateQueries(userKeys.getOrgUsers(orgId)); } From 43ce2227257812d3d1a75434f72e78528e0cd012 Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako Date: Mon, 20 Feb 2023 18:12:29 -0800 Subject: [PATCH 14/16] Minor style updates --- README.md | 2 +- frontend/src/components/dashboard/DropZone.tsx | 2 +- frontend/src/components/v2/Input/Input.tsx | 4 ++-- frontend/src/components/v2/Tag/Tag.tsx | 4 ++-- frontend/src/layouts/AppLayout/AppLayout.tsx | 2 +- .../OrgIncidentContactsTable/OrgIncidentContactsTable.tsx | 5 +++++ .../components/OrgMembersTable/OrgMembersTable.tsx | 7 ++++--- 7 files changed, 16 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 9f2590080..875125433 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ - **[Activity Logs](https://infisical.com/docs/getting-started/dashboard/audit-logs)** to record every action taken in a project - **[Point-in-time Secrets Recovery](https://infisical.com/docs/getting-started/dashboard/pit-recovery)** for rolling back to any snapshot of your secrets - **Role-based Access Controls** per environment -- 🔜 **2FA** (next week) +- 🔜 **2FA** (more options coming soon) - 🔜 **1-Click Deploy** to AWS - 🔜 **Automatic Secret Rotation** - 🔜 **Smart Security Alerts** diff --git a/frontend/src/components/dashboard/DropZone.tsx b/frontend/src/components/dashboard/DropZone.tsx index c01ed39fa..2fddfcf9c 100644 --- a/frontend/src/components/dashboard/DropZone.tsx +++ b/frontend/src/components/dashboard/DropZone.tsx @@ -175,7 +175,7 @@ const DropZone = ({ id="fileSelect" type="file" className="opacity-0 absolute w-full h-full" - accept=".txt,.env,.yml" + accept="" onChange={handleFileSelect} /> {errorDragAndDrop ?
:
} diff --git a/frontend/src/components/v2/Input/Input.tsx b/frontend/src/components/v2/Input/Input.tsx index ed9ead646..e6524c839 100644 --- a/frontend/src/components/v2/Input/Input.tsx +++ b/frontend/src/components/v2/Input/Input.tsx @@ -12,7 +12,7 @@ type Props = { }; const inputVariants = cva( - 'input w-full py-2 text-gray-400 placeholder-gray-500 placeholder-opacity-50 outline-none focus:ring-2', + 'input w-full py-[0.375rem] text-gray-400 placeholder:text-sm placeholder-gray-500 placeholder-opacity-50 outline-none focus:ring-2', { variants: { size: { @@ -84,7 +84,7 @@ export const Input = forwardRef( ): JSX.Element => { return (
- {leftIcon && {leftIcon}} + {leftIcon && {leftIcon}} ; -const tagVariants = cva('inline-flex whitespace-nowrap text-sm rounded-md mx-1 text-bunker-200 ', { +const tagVariants = cva('inline-flex whitespace-nowrap text-sm rounded-sm mr-1.5 text-bunker-200 ', { variants: { colorSchema: { gray: 'bg-mineshaft-500', red: 'bg-red/80 text-bunker-100' }, size: { - sm: 'px-2 py-1' + sm: 'px-1 py-0.5' } } }); diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index 06fc6d5f7..b071c3272 100644 --- a/frontend/src/layouts/AppLayout/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout/AppLayout.tsx @@ -227,7 +227,7 @@ export const AppLayout = ({ children }: LayoutProps) => {