From 556a646dceaeaab01c062ded4f2358148a10335c Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako Date: Mon, 9 Jan 2023 19:01:22 -0800 Subject: [PATCH] Added sharing keys with a user while creating a new project --- .../basic/popups/BottomRightPopup.tsx | 2 +- .../components/navigation/NavBarDashboard.tsx | 4 +- frontend/components/utilities/attemptLogin.ts | 79 ++++++++++- .../utilities/secrets/encryptSecrets.ts | 128 +++++++++--------- frontend/ee/components/ActivityTable.tsx | 8 +- frontend/pages/dashboard/[id].tsx | 29 ++-- 6 files changed, 169 insertions(+), 81 deletions(-) diff --git a/frontend/components/basic/popups/BottomRightPopup.tsx b/frontend/components/basic/popups/BottomRightPopup.tsx index e6ea60f92c..71b4f4a660 100644 --- a/frontend/components/basic/popups/BottomRightPopup.tsx +++ b/frontend/components/basic/popups/BottomRightPopup.tsx @@ -36,7 +36,7 @@ export default function BottonRightPopup({ }: PopupProps): JSX.Element { return (
diff --git a/frontend/components/navigation/NavBarDashboard.tsx b/frontend/components/navigation/NavBarDashboard.tsx index b2752f3edf..c4e66a5c04 100644 --- a/frontend/components/navigation/NavBarDashboard.tsx +++ b/frontend/components/navigation/NavBarDashboard.tsx @@ -112,9 +112,9 @@ export default function Navbar() { href="https://infisical.com/docs/getting-started/introduction" target="_blank" rel="noopener noreferrer" - className="text-gray-200 hover:text-primary duration-200"> + className="text-gray-200 hover:bg-white/10 px-3 rounded-md duration-200 text-sm mr-4 py-2 flex items-center"> + Docs -
diff --git a/frontend/components/utilities/attemptLogin.ts b/frontend/components/utilities/attemptLogin.ts index d729987e75..c40dbb5481 100644 --- a/frontend/components/utilities/attemptLogin.ts +++ b/frontend/components/utilities/attemptLogin.ts @@ -4,7 +4,10 @@ import login2 from '~/pages/api/auth/Login2'; import addSecrets from '~/pages/api/files/AddSecrets'; import getOrganizations from '~/pages/api/organization/getOrgs'; import getOrganizationUserProjects from '~/pages/api/organization/GetOrgUserProjects'; +import getUser from '~/pages/api/user/getUser'; +import uploadKeys from '~/pages/api/workspace/uploadKeys'; +import { encryptAssymmetric } from './cryptography/crypto'; import encryptSecrets from './secrets/encryptSecrets'; import Telemetry from './telemetry/Telemetry'; import { saveTokenToLocalStorage } from './saveTokenToLocalStorage'; @@ -19,6 +22,7 @@ interface SecretDataProps { comment: string; } +const crypto = require("crypto"); const nacl = require('tweetnacl'); nacl.util = require('tweetnacl-util'); const jsrp = require('jsrp'); @@ -121,8 +125,81 @@ const attemptLogin = async ( telemetry.capture('User Logged In'); } + if (isSignUp) { + const randomBytes = crypto.randomBytes(16).toString("hex"); + const PRIVATE_KEY = String(localStorage.getItem("PRIVATE_KEY")); + + const myUser = await getUser(); + + const { ciphertext, nonce } = encryptAssymmetric({ + plaintext: randomBytes, + publicKey: myUser.publicKey, + privateKey: PRIVATE_KEY, + }) as { ciphertext: string; nonce: string }; + + await uploadKeys( + projectToLogin, + myUser._id, + ciphertext, + nonce + ); + + const secretsToBeAdded: SecretDataProps[] = [{ + type: "shared", + pos: 0, + key: "DATABASE_URL", + value: "mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net", + comment: "This is an example of secret referencing.", + id: '' + }, { + type: "shared", + pos: 1, + key: "DB_USERNAME", + value: "OVERRIDE_THIS", + comment: "This is an example of secret overriding. Your team can have a shared value of a secret, while you can override it to whatever value you need", + id: '' + }, { + type: "personal", + pos: 2, + key: "DB_USERNAME", + value: "user1234", + comment: "", + id: '' + }, { + type: "shared", + pos: 3, + key: "DB_PASSWORD", + value: "OVERRIDE_THIS", + comment: "This is an example of secret overriding. Your team can have a shared value of a secret, while you can override it to whatever value you need", + id: '' + }, { + type: "personal", + pos: 4, + key: "DB_PASSWORD", + value: "example_password", + comment: "", + id: '' + }, { + type: "shared", + pos: 5, + key: "TWILIO_AUTH_TOKEN", + value: "example_twillio_token", + comment: "This is an example of secret overriding. Your team can have a shared value of a secret, while you can override it to whatever value you need", + id: '' + }, { + type: "shared", + pos: 6, + key: "WEBSITE_URL", + value: "http://localhost:3000", + comment: "This is an example of secret overriding. Your team can have a shared value of a secret, while you can override it to whatever value you need", + id: '' + }] + const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeAdded, workspaceId: String(localStorage.getItem('projectData.id')), env: 'dev' }) + await addSecrets({ secrets: secrets ?? [], env: "dev", workspaceId: String(localStorage.getItem('projectData.id')) }); + } + if (isLogin) { - router.push('/dashboard/'); + router.push('/dashboard/' + localStorage.getItem('projectData.id')); } } catch (error) { console.log(error) diff --git a/frontend/components/utilities/secrets/encryptSecrets.ts b/frontend/components/utilities/secrets/encryptSecrets.ts index 87c47d29a8..46c3150ef5 100644 --- a/frontend/components/utilities/secrets/encryptSecrets.ts +++ b/frontend/components/utilities/secrets/encryptSecrets.ts @@ -42,75 +42,81 @@ interface EncryptedSecretProps { * @returns */ const encryptSecrets = async ({ secretsToEncrypt, workspaceId, env }: { secretsToEncrypt: SecretDataProps[]; workspaceId: string; env: string; }) => { - const sharedKey = await getLatestFileKey({ workspaceId }); + let secrets; + try { + const sharedKey = await getLatestFileKey({ workspaceId }); - const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); + const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); - let randomBytes: string; - if (Object.keys(sharedKey).length > 0) { - // case: a (shared) key exists for the workspace - randomBytes = decryptAssymmetric({ - ciphertext: sharedKey.latestKey.encryptedKey, - nonce: sharedKey.latestKey.nonce, - publicKey: sharedKey.latestKey.sender.publicKey, - privateKey: PRIVATE_KEY, - }); - } else { - // case: a (shared) key does not exist for the workspace - randomBytes = crypto.randomBytes(16).toString("hex"); - } - - const secrets = secretsToEncrypt.map((secret) => { - // encrypt key - const { - ciphertext: secretKeyCiphertext, - iv: secretKeyIV, - tag: secretKeyTag, - } = encryptSymmetric({ - plaintext: secret.key, - key: randomBytes, - }); + let randomBytes: string; + if (Object.keys(sharedKey).length > 0) { + // case: a (shared) key exists for the workspace + randomBytes = decryptAssymmetric({ + ciphertext: sharedKey.latestKey.encryptedKey, + nonce: sharedKey.latestKey.nonce, + publicKey: sharedKey.latestKey.sender.publicKey, + privateKey: PRIVATE_KEY, + }); + } else { + // case: a (shared) key does not exist for the workspace + randomBytes = crypto.randomBytes(16).toString("hex"); + } + + secrets = secretsToEncrypt.map((secret) => { + // encrypt key + const { + ciphertext: secretKeyCiphertext, + iv: secretKeyIV, + tag: secretKeyTag, + } = encryptSymmetric({ + plaintext: secret.key, + key: randomBytes, + }); - // encrypt value - const { - ciphertext: secretValueCiphertext, - iv: secretValueIV, - tag: secretValueTag, - } = encryptSymmetric({ - plaintext: secret.value, - key: randomBytes, - }); + // encrypt value + const { + ciphertext: secretValueCiphertext, + iv: secretValueIV, + tag: secretValueTag, + } = encryptSymmetric({ + plaintext: secret.value, + key: randomBytes, + }); - // encrypt comment - const { - ciphertext: secretCommentCiphertext, - iv: secretCommentIV, - tag: secretCommentTag, - } = encryptSymmetric({ - plaintext: secret.comment ?? '', - key: randomBytes, - }); + // encrypt comment + const { + ciphertext: secretCommentCiphertext, + iv: secretCommentIV, + tag: secretCommentTag, + } = encryptSymmetric({ + plaintext: secret.comment ?? '', + key: randomBytes, + }); - const result: EncryptedSecretProps = { - id: secret.id, - createdAt: '', - environment: env, - secretKeyCiphertext, - secretKeyIV, - secretKeyTag, - secretValueCiphertext, - secretValueIV, - secretValueTag, - secretCommentCiphertext, - secretCommentIV, - secretCommentTag, - type: secret.type, - }; + const result: EncryptedSecretProps = { + id: secret.id, + createdAt: '', + environment: env, + secretKeyCiphertext, + secretKeyIV, + secretKeyTag, + secretValueCiphertext, + secretValueIV, + secretValueTag, + secretCommentCiphertext, + secretCommentIV, + secretCommentTag, + type: secret.type, + }; - return result; - }); + return result; + }); + } catch (error) { + console.log("Error while encrypting secrets"); + } return secrets; + } export default encryptSecrets; diff --git a/frontend/ee/components/ActivityTable.tsx b/frontend/ee/components/ActivityTable.tsx index ba18afeae9..207dc094fb 100644 --- a/frontend/ee/components/ActivityTable.tsx +++ b/frontend/ee/components/ActivityTable.tsx @@ -72,10 +72,8 @@ const ActivityLogsRow = ({ row, toggleSidebar }: { row: logData, toggleSidebar: {String(t("common:timestamp"))} {row.createdAt} } - {payloadOpened && - row.payload?.map((action, index) => { - action.secretVersions.length > 0 && - + {payloadOpened && row.payload?.map((action, index) => { + return action.secretVersions.length > 0 && {t("activity:event." + action.name)} toggleSidebar(action._id)}> @@ -87,7 +85,7 @@ const ActivityLogsRow = ({ row, toggleSidebar }: { row: logData, toggleSidebar: {payloadOpened && - {String(t("common:ip-address"))} + {String(t("activity:ip-address"))} {row.ipAddress} } diff --git a/frontend/pages/dashboard/[id].tsx b/frontend/pages/dashboard/[id].tsx index 98725a0762..3063f89eb2 100644 --- a/frontend/pages/dashboard/[id].tsx +++ b/frontend/pages/dashboard/[id].tsx @@ -16,11 +16,9 @@ import { faPlus, } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Menu, Transition } from '@headlessui/react'; import getProjectSercetSnapshotsCount from 'ee/api/secrets/GetProjectSercetSnapshotsCount'; import performSecretRollback from 'ee/api/secrets/PerformSecretRollback'; import PITRecoverySidebar from 'ee/components/PITRecoverySidebar'; -import { Document, YAMLSeq } from 'yaml'; import Button from '~/components/basic/buttons/Button'; import ListBox from '~/components/basic/Listbox'; @@ -542,15 +540,24 @@ export default function Dashboard() { text={String(t("Rollback to this snapshot"))} onButtonPressed={async () => { // Update secrets in the state only for the current environment - setData( - snapshotData.secretVersions - .filter(row => reverseEnvMapping[row.environment] == env) - .map((sv, position) => { - return { - id: sv.id, pos: position, type: sv.type, key: sv.key, value: sv.value, comment: '' - } - }) - ); + const rolledBackSecrets = snapshotData.secretVersions + .filter(row => reverseEnvMapping[row.environment] == env) + .map((sv, position) => { + return { + id: sv.id, pos: position, type: sv.type, key: sv.key, value: sv.value, comment: '' + } + }); + setData(rolledBackSecrets); + + setSharedToHide( + rolledBackSecrets?.filter(row => (rolledBackSecrets + ?.map((item) => item.key) + .filter( + (item, index) => + index !== + rolledBackSecrets?.map((item) => item.key).indexOf(item) + ).includes(row.key) && row.type == 'shared'))?.map((item) => item.id) + ) // Perform the rollback globally performSecretRollback({ workspaceId, version: snapshotData.version })