diff --git a/frontend/components/utilities/secrets/checkOverrides.ts b/frontend/components/utilities/secrets/checkOverrides.ts
index fae465d88..83a779a85 100644
--- a/frontend/components/utilities/secrets/checkOverrides.ts
+++ b/frontend/components/utilities/secrets/checkOverrides.ts
@@ -1,11 +1,4 @@
-interface SecretDataProps {
- type: 'personal' | 'shared';
- pos: number;
- key: string;
- value: string;
- id: string;
- comment: string;
-}
+import { SecretDataProps } from "public/data/frequentInterfaces";
/**
* This function downloads the secrets as a .env file
@@ -16,16 +9,16 @@ interface SecretDataProps {
const checkOverrides = async ({ data }: { data: SecretDataProps[]; }) => {
let secrets : SecretDataProps[] = data!.map((secret) => Object.create(secret));
const overridenSecrets = data!.filter(
- (secret) => secret.type === 'personal'
+ (secret) => (secret.valueOverride == undefined || secret?.value != secret?.valueOverride) ? 'shared' : 'personal'
);
if (overridenSecrets.length) {
overridenSecrets.forEach((secret) => {
const index = secrets!.findIndex(
- (_secret) => _secret.key === secret.key && _secret.type === 'shared'
+ (_secret) => _secret.key === secret.key && (secret.valueOverride == undefined || secret?.value != secret?.valueOverride)
);
secrets![index].value = secret.value;
});
- secrets = secrets!.filter((secret) => secret.type === 'shared');
+ secrets = secrets!.filter((secret) => (secret.valueOverride == undefined || secret?.value != secret?.valueOverride));
}
return secrets;
}
diff --git a/frontend/components/utilities/secrets/downloadDotEnv.ts b/frontend/components/utilities/secrets/downloadDotEnv.ts
index dbb29497b..93ac774ba 100644
--- a/frontend/components/utilities/secrets/downloadDotEnv.ts
+++ b/frontend/components/utilities/secrets/downloadDotEnv.ts
@@ -1,16 +1,9 @@
+import { SecretDataProps } from "public/data/frequentInterfaces";
+
import { envMapping } from "../../../public/data/frequentConstants";
import checkOverrides from './checkOverrides';
-interface SecretDataProps {
- type: 'personal' | 'shared';
- pos: number;
- key: string;
- value: string;
- id: string;
- comment: string;
-}
-
/**
* This function downloads the secrets as a .env file
* @param {object} obj
diff --git a/frontend/components/utilities/secrets/downloadYaml.ts b/frontend/components/utilities/secrets/downloadYaml.ts
index 1c17b1410..26fca5f00 100644
--- a/frontend/components/utilities/secrets/downloadYaml.ts
+++ b/frontend/components/utilities/secrets/downloadYaml.ts
@@ -1,19 +1,12 @@
// import YAML from 'yaml';
// import { YAMLSeq } from 'yaml/types';
+import { SecretDataProps } from "public/data/frequentInterfaces";
+
// import { envMapping } from "../../../public/data/frequentConstants";
// import checkOverrides from './checkOverrides';
-interface SecretDataProps {
- type: 'personal' | 'shared';
- pos: number;
- key: string;
- value: string;
- id: string;
- comment: string;
-}
-
/**
* This function downloads the secrets as a .yml file
* @param {object} obj
diff --git a/frontend/components/utilities/secrets/encryptSecrets.ts b/frontend/components/utilities/secrets/encryptSecrets.ts
index 46c3150ef..a0f56990a 100644
--- a/frontend/components/utilities/secrets/encryptSecrets.ts
+++ b/frontend/components/utilities/secrets/encryptSecrets.ts
@@ -1,3 +1,5 @@
+import { SecretDataProps } from "public/data/frequentInterfaces";
+
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
const crypto = require("crypto");
@@ -9,15 +11,6 @@ const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
-interface SecretDataProps {
- type: 'personal' | 'shared';
- pos: number;
- key: string;
- value: string;
- id: string;
- comment: string;
-}
-
interface EncryptedSecretProps {
id: string;
createdAt: string;
@@ -106,7 +99,7 @@ const encryptSecrets = async ({ secretsToEncrypt, workspaceId, env }: { secretsT
secretCommentCiphertext,
secretCommentIV,
secretCommentTag,
- type: secret.type,
+ type: (secret.valueOverride == undefined || secret?.value != secret?.valueOverride) ? 'shared' : 'personal',
};
return result;
diff --git a/frontend/components/utilities/secrets/getSecretsForProject.ts b/frontend/components/utilities/secrets/getSecretsForProject.ts
index 81de27be0..d2c4ddf54 100644
--- a/frontend/components/utilities/secrets/getSecretsForProject.ts
+++ b/frontend/components/utilities/secrets/getSecretsForProject.ts
@@ -117,15 +117,19 @@ const getSecretsForProject = async ({
});
}
- const result = tempDecryptedSecrets.map((secret, index) => {
+ const secretKeys = [...new Set(tempDecryptedSecrets.map(secret => secret.key))];
+
+
+ const result = secretKeys.map((key, index) => {
return {
- id: secret['id'],
+ id: tempDecryptedSecrets.filter(secret => secret.key == key && secret.type == 'shared')[0]?.id,
+ idOverride: tempDecryptedSecrets.filter(secret => secret.key == key && secret.type == 'personal')[0]?.id,
pos: index,
- key: secret['key'],
- value: secret['value'],
- type: secret['type'],
- comment: secret['comment']
- };
+ key: key,
+ value: tempDecryptedSecrets.filter(secret => secret.key == key && secret.type == 'shared')[0]?.value,
+ valueOverride: tempDecryptedSecrets.filter(secret => secret.key == key && secret.type == 'personal')[0]?.value,
+ comment: tempDecryptedSecrets.filter(secret => secret.key == key && secret.type == 'shared')[0]?.comment,
+ }
});
setData(result);
diff --git a/frontend/ee/components/PITRecoverySidebar.tsx b/frontend/ee/components/PITRecoverySidebar.tsx
index 085c483ed..e8a1041cd 100644
--- a/frontend/ee/components/PITRecoverySidebar.tsx
+++ b/frontend/ee/components/PITRecoverySidebar.tsx
@@ -13,6 +13,15 @@ import { decryptAssymmetric, decryptSymmetric } from "~/components/utilities/cry
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
+export interface SecretDataProps {
+ pos: number;
+ key: string;
+ value: string;
+ type: string;
+ id: string;
+ environment: string;
+}
+
interface SideBarProps {
toggleSidebar: (value: boolean) => void;
setSnapshotData: (value: any) => void;
@@ -43,8 +52,6 @@ interface EncrypetedSecretVersionListProps {
* @param {function} obj.toggleSidebar - function that opens or closes the sidebar
* @param {function} obj.setSnapshotData - state manager for snapshot data
* @param {string} obj.chosenSnaphshot - the snapshot id which is currently selected
- *
- *
* @returns the sidebar with the options for point-in-time recovery (commits)
*/
const PITRecoverySidebar = ({
@@ -111,7 +118,21 @@ const PITRecoverySidebar = ({
}
})
- setSnapshotData({ id: secretSnapshotData._id, version: secretSnapshotData.version, createdAt: secretSnapshotData.createdAt, secretVersions: decryptedSecretVersions })
+
+ const secretKeys = [...new Set(decryptedSecretVersions.map((secret: SecretDataProps) => secret.key))];
+
+ const result = secretKeys.map((key, index) => {
+ return {
+ id: decryptedSecretVersions.filter((secret: SecretDataProps) => secret.key == key && secret.type == 'shared')[0].id,
+ pos: index,
+ key: key,
+ environment: decryptedSecretVersions.filter((secret: SecretDataProps) => secret.key == key && secret.type == 'shared')[0].environment,
+ 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({ id: secretSnapshotData._id, version: secretSnapshotData.version, createdAt: secretSnapshotData.createdAt, secretVersions: result, comment: '' })
}
return
+
{t("Point-in-time Recovery")}
toggleSidebar(false)}>
-
- {secretSnapshotsMetadata?.map((snapshot: SnaphotProps, id: number) =>
-
-
{timeSince(new Date(snapshot.createdAt))}
-
{" - " + snapshot.secretVersions.length + " Secrets"}
-
+
+ {secretSnapshotsMetadata?.map((snapshot: SnaphotProps, id: number) =>
exploreSnapshot({ snapshotId: snapshot._id })}
- className={`${chosenSnapshot == snapshot._id || (id == 0 && chosenSnapshot === "") ? "text-bunker-800 pointer-events-none" : "text-bunker-200 hover:text-primary duration-200 cursor-pointer"} text-sm`}>
- {id == 0 ? "Current Version" : chosenSnapshot == snapshot._id ? "Currently Viewing" : "Explore"}
+ key={snapshot._id}
+ onClick={() => exploreSnapshot({ snapshotId: snapshot._id })}
+ className={`${chosenSnapshot == snapshot._id || (id == 0 && chosenSnapshot === "") ? "bg-primary text-black pointer-events-none" : "bg-mineshaft-700 hover:bg-mineshaft-500 duration-200 cursor-pointer"} py-3 px-4 mb-2 rounded-md flex flex-row justify-between items-center`}
+ >
+
+
{timeSince(new Date(snapshot.createdAt))}
+
{" - " + snapshot.secretVersions.length + " Secrets"}
+
+
+ {id == 0 ? "Current Version" : chosenSnapshot == snapshot._id ? "Currently Viewing" : "Explore"}
+
+
)}
+
)}
-
-
)}
diff --git a/frontend/ee/components/SecretVersionList.tsx b/frontend/ee/components/SecretVersionList.tsx
index 3eb40005f..d07198dc0 100644
--- a/frontend/ee/components/SecretVersionList.tsx
+++ b/frontend/ee/components/SecretVersionList.tsx
@@ -52,7 +52,7 @@ const SecretVersionList = ({ secretId }: { secretId: string; }) => {
});
}
- const decryptedSecretVersions = encryptedSecretVersions.secretVersions.map((encryptedSecretVersion: EncrypetedSecretVersionListProps) => {
+ const decryptedSecretVersions = encryptedSecretVersions?.secretVersions.map((encryptedSecretVersion: EncrypetedSecretVersionListProps) => {
return {
createdAt: encryptedSecretVersion.createdAt,
value: decryptSymmetric({
@@ -87,28 +87,33 @@ const SecretVersionList = ({ secretId }: { secretId: string; }) => {
) : (
- {secretVersions?.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
- .map((version: DecryptedSecretVersionListProps, index: number) =>
-
-
-
-
- {(new Date(version.createdAt)).toLocaleDateString('en-US', {
- year: 'numeric',
- month: '2-digit',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit',
- second: '2-digit'
- })}
+ {secretVersions
+ ? secretVersions?.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
+ .map((version: DecryptedSecretVersionListProps, index: number) =>
+
+
+
+
+ {(new Date(version.createdAt)).toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit'
+ })}
+
+
-
-
- )}
+ )
+ : (
+
No version history yet.
+ )
+ }
)}
diff --git a/frontend/pages/dashboard/[id].tsx b/frontend/pages/dashboard/[id].tsx
index 4ce91002d..07b4133a0 100644
--- a/frontend/pages/dashboard/[id].tsx
+++ b/frontend/pages/dashboard/[id].tsx
@@ -45,11 +45,12 @@ import getWorkspaces from '../api/workspace/getWorkspaces';
interface SecretDataProps {
- type: 'personal' | 'shared';
pos: number;
key: string;
value: string;
+ valueOverride: string | undefined;
id: string;
+ idOverride: string | undefined;
comment: string;
}
@@ -68,10 +69,11 @@ interface SnapshotProps {
secretVersions: {
id: string;
pos: number;
- type: "personal" | "shared";
environment: string;
key: string;
value: string;
+ valueOverride: string;
+ comment: string;
}[];
}
@@ -99,7 +101,7 @@ function findDuplicates(arr: any[]) {
*/
export default function Dashboard() {
const [data, setData] = useState
();
- const [initialData, setInitialData] = useState([]);
+ const [initialData, setInitialData] = useState([]);
const [buttonReady, setButtonReady] = useState(false);
const router = useRouter();
const [workspaceId, setWorkspaceId] = useState('');
@@ -119,6 +121,7 @@ export default function Dashboard() {
const [sharedToHide, setSharedToHide] = useState([]);
const [snapshotData, setSnapshotData] = useState();
const [numSnapshots, setNumSnapshots] = useState();
+ const [saveLoading, setSaveLoading] = useState(false);
const { t } = useTranslation();
const { createNotification } = useNotificationContext();
@@ -213,16 +216,6 @@ export default function Dashboard() {
setInitialData(dataToSort);
reorderRows(dataToSort);
- setSharedToHide(
- dataToSort?.filter(row => (dataToSort
- ?.map((item) => item.key)
- .filter(
- (item, index) =>
- index !==
- dataToSort?.map((item) => item.key).indexOf(item)
- ).includes(row.key) && row.type == 'shared'))?.map((item) => item.id)
- )
-
setIsLoading(false);
} catch (error) {
console.log('Error', error);
@@ -238,39 +231,16 @@ export default function Dashboard() {
...data!,
{
id: guidGenerator(),
+ idOverride: guidGenerator(),
pos: data!.length,
key: '',
value: '',
- type: 'shared',
+ valueOverride: undefined,
comment: '',
}
]);
};
- /**
- * This function add an ovverrided version of a certain secret to the current user
- * @param {object} obj
- * @param {string} obj.id - if of this secret that is about to be overriden
- * @param {string} obj.keyName - key name of this secret
- * @param {string} obj.value - value of this secret
- * @param {string} obj.pos - position of this secret on the dashboard
- */
- const addOverride = ({ id, keyName, value, pos, comment }: overrideProps) => {
- setIsNew(false);
- const tempdata: SecretDataProps[] | 1 = [
- ...data!,
- {
- id: id,
- pos: pos,
- key: keyName,
- value: value,
- type: 'personal',
- comment: comment
- }
- ];
- sortValuesHandler(tempdata, sortMethod == "alhpabetical" ? "-alphabetical" : "alphabetical");
- };
-
const deleteRow = ({ ids, secretName }: { ids: string[]; secretName: string; }) => {
setButtonReady(true);
toggleSidebar("None");
@@ -289,15 +259,15 @@ export default function Dashboard() {
setButtonReady(true);
// find which shared secret corresponds to the overriden version
- const sharedVersionOfOverride = data!.filter(secret => secret.type == "shared" && secret.key == data!.filter(row => row.id == id)[0]?.key)[0]?.id;
+ // const sharedVersionOfOverride = data!.filter(secret => secret.type == "shared" && secret.key == data!.filter(row => row.id == id)[0]?.key)[0]?.id;
// change the sidebar to this shared secret; and unhide it
- toggleSidebar(sharedVersionOfOverride)
- setSharedToHide(sharedToHide!.filter(tempId => tempId != sharedVersionOfOverride))
+ // toggleSidebar(sharedVersionOfOverride)
+ // setSharedToHide(sharedToHide!.filter(tempId => tempId != sharedVersionOfOverride))
// resort secrets
- const tempData = data!.filter((row: SecretDataProps) => !(row.key == data!.filter(row => row.id == id)[0]?.key && row.type == 'personal'))
- sortValuesHandler(tempData, sortMethod == "alhpabetical" ? "-alphabetical" : "alphabetical")
+ // const tempData = data!.filter((row: SecretDataProps) => !(row.key == data!.filter(row => row.id == id)[0]?.key && row.type == 'personal'))
+ // sortValuesHandler(tempData, sortMethod == "alhpabetical" ? "-alphabetical" : "alphabetical")
};
const modifyValue = (value: string, pos: number) => {
@@ -308,6 +278,14 @@ export default function Dashboard() {
setButtonReady(true);
};
+ const modifyValueOverride = (value: string | undefined, pos: number) => {
+ setData((oldData) => {
+ oldData![pos].valueOverride = value;
+ return [...oldData!];
+ });
+ setButtonReady(true);
+ };
+
const modifyKey = (value: string, pos: number) => {
setData((oldData) => {
oldData![pos].key = value;
@@ -329,6 +307,10 @@ export default function Dashboard() {
modifyValue(value, pos);
}, []);
+ const listenChangeValueOverride = useCallback((value: string | undefined, pos: number) => {
+ modifyValueOverride(value, pos);
+ }, []);
+
const listenChangeKey = useCallback((value: string, pos: number) => {
modifyKey(value, pos);
}, []);
@@ -341,6 +323,7 @@ export default function Dashboard() {
* Save the changes of environment variables and push them to the database
*/
const savePush = async (dataToPush?: SecretDataProps[]) => {
+ setSaveLoading(true);
let newData: SecretDataProps[] | null | undefined;
// dataToPush is mostly used for rollbacks, otherwise we always take the current state data
if ((dataToPush ?? [])?.length > 0) {
@@ -349,16 +332,11 @@ export default function Dashboard() {
newData = data;
}
- const obj = Object.assign(
- {},
- ...newData!.map((row: SecretDataProps) => ({ [row.type.charAt(0) + row.key]: [row.value, row.comment ?? ''] }))
- );
-
// Checking if any of the secret keys start with a number - if so, don't do anything
- const nameErrors = !Object.keys(obj)
- .map((key) => !isNaN(Number(key[0].charAt(0))))
+ const nameErrors = !newData!
+ .map((secret) => !isNaN(Number(secret.key.charAt(0))))
.every((v) => v === false);
- const duplicatesExist = findDuplicates(data!.map((item: SecretDataProps) => item.key + item.type)).length > 0;
+ const duplicatesExist = findDuplicates(data!.map((item: SecretDataProps) => item.key)).length > 0;
if (nameErrors) {
return createNotification({
@@ -378,34 +356,64 @@ export default function Dashboard() {
setButtonReady(false);
const secretsToBeDeleted
- = initialData
+ = initialData!
.filter(initDataPoint => !newData!.map(newDataPoint => newDataPoint.id).includes(initDataPoint.id))
.map(secret => secret.id);
+ console.log('delete', secretsToBeDeleted.length)
const secretsToBeAdded
= newData!
- .filter(newDataPoint => !initialData.map(initDataPoint => initDataPoint.id).includes(newDataPoint.id));
+ .filter(newDataPoint => !initialData!.map(initDataPoint => initDataPoint.id).includes(newDataPoint.id));
+ console.log('add', secretsToBeAdded.length)
const secretsToBeUpdated
- = newData!.filter(newDataPoint => initialData
+ = newData!.filter(newDataPoint => initialData!
.filter(initDataPoint => newData!.map(newDataPoint => newDataPoint.id).includes(initDataPoint.id)
&& (newData!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].value != initDataPoint.value
|| newData!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].key != initDataPoint.key
|| newData!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].comment != initDataPoint.comment))
.map(secret => secret.id).includes(newDataPoint.id));
+ console.log('update', secretsToBeUpdated.length)
+
+ const newOverrides = newData!.filter(newDataPoint => newDataPoint.valueOverride != undefined)
+ const initOverrides = initialData!.filter(initDataPoint => initDataPoint.valueOverride != undefined)
+
+ const overridesToBeDeleted
+ = initOverrides
+ .filter(initDataPoint => !newOverrides!.map(newDataPoint => newDataPoint.id).includes(initDataPoint.id))
+ .map(secret => String(secret.idOverride));
+ console.log('override delete', overridesToBeDeleted.length)
+
+ const overridesToBeAdded
+ = newOverrides!
+ .filter(newDataPoint => !initOverrides.map(initDataPoint => initDataPoint.id).includes(newDataPoint.id))
+ .map(override => ({pos: override.pos, key: override.key, value: String(override.valueOverride), valueOverride: override.valueOverride, comment: '', id: String(override.idOverride), idOverride: String(override.idOverride)}));
+ console.log('override add', overridesToBeAdded.length)
+
+ const overridesToBeUpdated
+ = newOverrides!.filter(newDataPoint => initOverrides
+ .filter(initDataPoint => newOverrides!.map(newDataPoint => newDataPoint.id).includes(initDataPoint.id)
+ && (newOverrides!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].valueOverride != initDataPoint.valueOverride
+ || newOverrides!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].key != initDataPoint.key
+ || newOverrides!.filter(newDataPoint => newDataPoint.id == initDataPoint.id)[0].comment != initDataPoint.comment))
+ .map(secret => secret.id).includes(newDataPoint.id))
+ .map(override => ({pos: override.pos, key: override.key, value: String(override.valueOverride), valueOverride: override.valueOverride, comment: '', id: String(override.idOverride), idOverride: String(override.idOverride)}));
+ console.log('override update', overridesToBeUpdated.length)
- if (secretsToBeDeleted.length > 0) {
- await deleteSecrets({ secretIds: secretsToBeDeleted });
+ if (secretsToBeDeleted.concat(overridesToBeDeleted).length > 0) {
+ await deleteSecrets({ secretIds: secretsToBeDeleted.concat(overridesToBeDeleted) });
}
- if (secretsToBeAdded.length > 0) {
- const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeAdded, workspaceId, env: envMapping[env] })
+ if (secretsToBeAdded.concat(overridesToBeAdded).length > 0) {
+ const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeAdded.concat(overridesToBeAdded), workspaceId, env: envMapping[env] });
secrets && await addSecrets({ secrets, env: envMapping[env], workspaceId });
}
- if (secretsToBeUpdated.length > 0) {
- const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeUpdated, workspaceId, env: envMapping[env] })
+ if (secretsToBeUpdated.concat(overridesToBeUpdated).length > 0) {
+ const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeUpdated.concat(overridesToBeUpdated), workspaceId, env: envMapping[env] });
secrets && await updateSecrets({ secrets });
}
+ setInitialData(newData);
+
// If this user has never saved environment variables before, show them a prompt to read docs
if (!hasUserEverPushed) {
setCheckDocsPopUpVisible(true);
@@ -414,6 +422,7 @@ export default function Dashboard() {
// increasing the number of project commits
setNumSnapshots((numSnapshots ?? 0) + 1);
+ setSaveLoading(false);
};
const addData = (newData: SecretDataProps[]) => {
@@ -462,9 +471,8 @@ export default function Dashboard() {
data={data.filter((row: SecretDataProps) => row.key == data.filter(row => row.id == sidebarSecretId)[0]?.key)}
modifyKey={listenChangeKey}
modifyValue={listenChangeValue}
+ modifyValueOverride={listenChangeValueOverride}
modifyComment={listenChangeComment}
- addOverride={addOverride}
- deleteOverride={deleteOverride}
buttonReady={buttonReady}
savePush={savePush}
sharedToHide={sharedToHide}
@@ -533,6 +541,7 @@ export default function Dashboard() {
active={buttonReady}
iconDisabled={faCheck}
textDisabled={String(t("common:saved"))}
+ loading={saveLoading}
/>
)}
@@ -545,21 +554,11 @@ export default function Dashboard() {
.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: ''
+ id: sv.id, idOverride: sv.id, pos: position, valueOverride: sv.valueOverride, 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 })
@@ -663,16 +662,17 @@ export default function Dashboard() {
>
{!snapshotData && data?.filter(row => row.key?.toUpperCase().includes(searchKeys.toUpperCase()))
- .filter(row => !(sharedToHide.includes(row.id) && row.type == 'shared')).map((keyPair) => (
+ .filter(row => !sharedToHide.includes(row.id)).map((keyPair) => (
item.key + item.type)
- )?.includes(keyPair.key + keyPair.type)}
+ data?.map((item) => item.key)
+ )?.includes(keyPair.key)}
toggleSidebar={toggleSidebar}
sidebarSecretId={sidebarSecretId}
isSnapshot={false}
@@ -681,22 +681,26 @@ export default function Dashboard() {
{snapshotData && snapshotData.secretVersions?.sort((a, b) => a.key.localeCompare(b.key))
.filter(row => reverseEnvMapping[row.environment] == snapshotEnv)
.filter(row => row.key.toUpperCase().includes(searchKeys.toUpperCase()))
- .filter(row => !(snapshotData.secretVersions?.filter(row => (snapshotData.secretVersions
+ .filter(
+ row => !(snapshotData.secretVersions?.filter(row => (snapshotData.secretVersions
?.map((item) => item.key)
.filter(
(item, index) =>
index !==
snapshotData.secretVersions?.map((item) => item.key).indexOf(item)
- ).includes(row.key) && row.type == 'shared'))?.map((item) => item.id).includes(row.id) && row.type == 'shared')).map((keyPair) => (
+ ).includes(row.key)))?.map((item) => item.id).includes(row.id))
+ )
+ .map((keyPair) => (
item.key + item.type)
- )?.includes(keyPair.key + keyPair.type)}
+ data?.map((item) => item.key)
+ )?.includes(keyPair.key)}
toggleSidebar={toggleSidebar}
sidebarSecretId={sidebarSecretId}
isSnapshot={true}
diff --git a/frontend/public/data/frequentInterfaces.ts b/frontend/public/data/frequentInterfaces.ts
new file mode 100644
index 000000000..c2fa797fd
--- /dev/null
+++ b/frontend/public/data/frequentInterfaces.ts
@@ -0,0 +1,8 @@
+export interface SecretDataProps {
+ pos: number;
+ key: string;
+ value: string;
+ valueOverride: string | undefined;
+ id: string;
+ comment: string;
+}
\ No newline at end of file
From 47fd48b7b01b8013073a3666ce5220324c492c1b Mon Sep 17 00:00:00 2001
From: Vladyslav Matsiiako
Date: Thu, 12 Jan 2023 13:57:00 -0800
Subject: [PATCH 22/25] Fixed the TS error during signup
---
frontend/components/utilities/attemptLogin.ts | 40 ++++++++-----------
1 file changed, 17 insertions(+), 23 deletions(-)
diff --git a/frontend/components/utilities/attemptLogin.ts b/frontend/components/utilities/attemptLogin.ts
index c40dbb548..f1aa03ffb 100644
--- a/frontend/components/utilities/attemptLogin.ts
+++ b/frontend/components/utilities/attemptLogin.ts
@@ -1,3 +1,5 @@
+import { SecretDataProps } from 'public/data/frequentInterfaces';
+
import Aes256Gcm from '~/components/utilities/cryptography/aes-256-gcm';
import login1 from '~/pages/api/auth/Login1';
import login2 from '~/pages/api/auth/Login2';
@@ -13,14 +15,6 @@ import Telemetry from './telemetry/Telemetry';
import { saveTokenToLocalStorage } from './saveTokenToLocalStorage';
import SecurityClient from './SecurityClient';
-interface SecretDataProps {
- type: 'personal' | 'shared';
- pos: number;
- key: string;
- value: string;
- id: string;
- comment: string;
-}
const crypto = require("crypto");
const nacl = require('tweetnacl');
@@ -145,53 +139,53 @@ const attemptLogin = async (
);
const secretsToBeAdded: SecretDataProps[] = [{
- type: "shared",
pos: 0,
key: "DATABASE_URL",
value: "mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net",
+ valueOverride: undefined,
comment: "This is an example of secret referencing.",
id: ''
}, {
- type: "shared",
pos: 1,
key: "DB_USERNAME",
value: "OVERRIDE_THIS",
+ valueOverride: undefined,
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_PASSWORD",
+ value: "OVERRIDE_THIS",
+ valueOverride: undefined,
+ 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: ''
+ }, {
+ pos: 3,
key: "DB_USERNAME",
value: "user1234",
+ valueOverride: "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",
+ valueOverride: "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",
+ valueOverride: undefined,
+ comment: "",
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",
+ valueOverride: undefined,
+ comment: "",
id: ''
}]
const secrets = await encryptSecrets({ secretsToEncrypt: secretsToBeAdded, workspaceId: String(localStorage.getItem('projectData.id')), env: 'dev' })
From 71f60f1589c62ef3f6d7953a6e0e0299bd9636c6 Mon Sep 17 00:00:00 2001
From: Maidul Islam
Date: Thu, 12 Jan 2023 15:31:23 -0800
Subject: [PATCH 23/25] Update modify secrets api v2 so that fields are
optional
---
.../src/controllers/v2/secretsController.ts | 113 +++++++++---------
backend/src/ee/helpers/secret.ts | 92 +++++++-------
backend/src/ee/models/secretVersion.ts | 50 ++++----
backend/src/routes/v2/secrets.ts | 44 +++----
4 files changed, 141 insertions(+), 158 deletions(-)
diff --git a/backend/src/controllers/v2/secretsController.ts b/backend/src/controllers/v2/secretsController.ts
index ff9574711..f92fc4cee 100644
--- a/backend/src/controllers/v2/secretsController.ts
+++ b/backend/src/controllers/v2/secretsController.ts
@@ -2,8 +2,8 @@ import to from 'await-to-js';
import { Types } from 'mongoose';
import { Request, Response } from 'express';
import { ISecret, Secret } from '../../models';
-import {
- SECRET_PERSONAL,
+import {
+ SECRET_PERSONAL,
SECRET_SHARED,
ACTION_ADD_SECRETS,
ACTION_READ_SECRETS,
@@ -23,9 +23,9 @@ import { BadRequestError } from '../../utils/errors';
* @param res
*/
export const createSecrets = async (req: Request, res: Response) => {
- const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
+ const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
const { workspaceId, environment } = req.body;
-
+
let toAdd;
if (Array.isArray(req.body.secrets)) {
// case: create multiple secrets
@@ -34,7 +34,7 @@ export const createSecrets = async (req: Request, res: Response) => {
// case: create 1 secret
toAdd = [req.body.secrets];
}
-
+
const newSecrets = await Secret.insertMany(
toAdd.map(({
type,
@@ -66,7 +66,7 @@ export const createSecrets = async (req: Request, res: Response) => {
secretValueTag
}))
);
-
+
// (EE) add secret versions for new secrets
EESecretService.addSecretVersions({
secretVersions: newSecrets.map(({
@@ -160,7 +160,7 @@ export const createSecrets = async (req: Request, res: Response) => {
*/
export const getSecrets = async (req: Request, res: Response) => {
const { workspaceId, environment } = req.query;
-
+
let userId: Types.ObjectId | undefined = undefined // used for getting personal secrets for user
if (req.user) {
userId = req.user._id;
@@ -169,13 +169,13 @@ export const getSecrets = async (req: Request, res: Response) => {
if (req.serviceTokenData) {
userId = req.serviceTokenData.user._id
}
-
+
const [err, secrets] = await to(Secret.find(
{
workspace: workspaceId,
environment,
$or: [
- { user: userId },
+ { user: userId },
{ user: { $exists: false } }
],
type: { $in: [SECRET_SHARED, SECRET_PERSONAL] }
@@ -183,9 +183,9 @@ export const getSecrets = async (req: Request, res: Response) => {
).then())
if (err) throw ValidationError({ message: 'Failed to get secrets', stack: err.stack });
-
+
const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
-
+
const readAction = await EELogService.createActionSecret({
name: ACTION_READ_SECRETS,
userId: req.user._id.toString(),
@@ -214,7 +214,7 @@ export const getSecrets = async (req: Request, res: Response) => {
}
});
}
-
+
return res.status(200).send({
secrets
});
@@ -226,8 +226,8 @@ export const getSecrets = async (req: Request, res: Response) => {
* @param res
*/
export const updateSecrets = async (req: Request, res: Response) => {
- const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
-
+ const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
+
// TODO: move type
interface PatchSecret {
id: string;
@@ -242,7 +242,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
secretCommentTag: string;
}
- const ops = req.body.secrets.map((secret: PatchSecret) => {
+ const updateOperationsToPerform = req.body.secrets.map((secret: PatchSecret) => {
const {
secretKeyCiphertext,
secretKeyIV,
@@ -254,6 +254,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
secretCommentIV,
secretCommentTag
} = secret;
+
return ({
updateOne: {
filter: { _id: new Types.ObjectId(secret.id) },
@@ -268,8 +269,8 @@ export const updateSecrets = async (req: Request, res: Response) => {
secretValueIV,
secretValueTag,
...((
- secretCommentCiphertext &&
- secretCommentIV &&
+ secretCommentCiphertext &&
+ secretCommentIV &&
secretCommentTag
) ? {
secretCommentCiphertext,
@@ -280,15 +281,17 @@ export const updateSecrets = async (req: Request, res: Response) => {
}
});
});
- await Secret.bulkWrite(ops);
-
- const newSecretsObj: { [key: string]: PatchSecret } = {};
+
+ await Secret.bulkWrite(updateOperationsToPerform);
+
+ const secretModificationsBySecretId: { [key: string]: PatchSecret } = {};
req.body.secrets.forEach((secret: PatchSecret) => {
- newSecretsObj[secret.id] = secret;
+ secretModificationsBySecretId[secret.id] = secret;
});
- await EESecretService.addSecretVersions({
- secretVersions: req.secrets.map((secret: ISecret) => {
+ const ListOfSecretsBeforeModifications = req.secrets
+ const secretVersions = {
+ secretVersions: ListOfSecretsBeforeModifications.map((secret: ISecret) => {
const {
secretKeyCiphertext,
secretKeyIV,
@@ -298,37 +301,29 @@ export const updateSecrets = async (req: Request, res: Response) => {
secretValueTag,
secretCommentCiphertext,
secretCommentIV,
- secretCommentTag
- } = newSecretsObj[secret._id.toString()]
+ secretCommentTag,
+ } = secretModificationsBySecretId[secret._id.toString()]
+
return ({
secret: secret._id,
version: secret.version + 1,
workspace: secret.workspace,
type: secret.type,
environment: secret.environment,
- isDeleted: false,
- secretKeyCiphertext,
- secretKeyIV,
- secretKeyTag,
- secretValueCiphertext,
- secretValueIV,
- secretValueTag,
- ...((
- secretCommentCiphertext &&
- secretCommentIV &&
- secretCommentTag
- ) ? {
- secretCommentCiphertext,
- secretCommentIV,
- secretCommentTag
- } : {
- secretCommentCiphertext: '',
- secretCommentIV: '',
- secretCommentTag: ''
- })
+ secretKeyCiphertext: secretKeyCiphertext ? secretKeyCiphertext : secret.secretKeyCiphertext,
+ secretKeyIV: secretKeyIV ? secretKeyIV : secret.secretKeyIV,
+ secretKeyTag: secretKeyTag ? secretKeyTag : secret.secretKeyTag,
+ secretValueCiphertext: secretValueCiphertext ? secretValueCiphertext : secret.secretValueCiphertext,
+ secretValueIV: secretValueIV ? secretValueIV : secret.secretValueIV,
+ secretValueTag: secretValueTag ? secretValueTag : secret.secretValueTag,
+ secretCommentCiphertext: secretCommentCiphertext ? secretCommentCiphertext : secret.secretCommentCiphertext,
+ secretCommentIV: secretCommentIV ? secretCommentIV : secret.secretCommentIV,
+ secretCommentTag: secretCommentTag ? secretCommentTag : secret.secretCommentTag,
});
})
- });
+ }
+
+ await EESecretService.addSecretVersions(secretVersions);
// group secrets into workspaces so updated secrets can
@@ -355,7 +350,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
userId: req.user._id.toString(),
workspaceId: key,
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
- });
+ });
// (EE) create (audit) log
updateAction && await EELogService.createLog({
@@ -367,9 +362,9 @@ export const updateSecrets = async (req: Request, res: Response) => {
});
// (EE) take a secret snapshot
- await EESecretService.takeSecretSnapshot({
- workspaceId: key
- })
+ await EESecretService.takeSecretSnapshot({
+ workspaceId: key
+ })
if (postHogClient) {
postHogClient.capture({
@@ -385,7 +380,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
});
}
});
-
+
return res.status(200).send({
secrets: await Secret.find({
_id: {
@@ -401,15 +396,15 @@ export const updateSecrets = async (req: Request, res: Response) => {
* @param res
*/
export const deleteSecrets = async (req: Request, res: Response) => {
- const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
+ const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
const toDelete = req.secrets.map((s: any) => s._id);
-
+
await Secret.deleteMany({
_id: {
$in: toDelete
}
});
-
+
await EESecretService.markDeletedSecretVersions({
secretIds: toDelete
});
@@ -437,7 +432,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
userId: req.user._id.toString(),
workspaceId: key,
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
- });
+ });
// (EE) create (audit) log
deleteAction && await EELogService.createLog({
@@ -449,9 +444,9 @@ export const deleteSecrets = async (req: Request, res: Response) => {
});
// (EE) take a secret snapshot
- await EESecretService.takeSecretSnapshot({
- workspaceId: key
- })
+ await EESecretService.takeSecretSnapshot({
+ workspaceId: key
+ })
if (postHogClient) {
postHogClient.capture({
@@ -467,7 +462,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
});
}
});
-
+
return res.status(200).send({
secrets: req.secrets
});
diff --git a/backend/src/ee/helpers/secret.ts b/backend/src/ee/helpers/secret.ts
index 529c9a980..7edee915f 100644
--- a/backend/src/ee/helpers/secret.ts
+++ b/backend/src/ee/helpers/secret.ts
@@ -1,11 +1,11 @@
import { Types } from 'mongoose';
import * as Sentry from '@sentry/node';
import {
- Secret,
+ Secret,
ISecret
} from '../../models';
import {
- SecretSnapshot,
+ SecretSnapshot,
SecretVersion,
ISecretVersion
} from '../models';
@@ -18,24 +18,24 @@ import {
* @param {String} obj.workspaceId
* @returns {SecretSnapshot} secretSnapshot - new secret snapshot
*/
- const takeSecretSnapshotHelper = async ({
+const takeSecretSnapshotHelper = async ({
workspaceId
}: {
workspaceId: string;
}) => {
-
+
let secretSnapshot;
try {
const secretIds = (await Secret.find({
workspace: workspaceId
}, '_id')).map((s) => s._id);
-
+
const latestSecretVersions = (await SecretVersion.aggregate([
{
- $match: {
- secret: {
- $in: secretIds
- }
+ $match: {
+ secret: {
+ $in: secretIds
+ }
}
},
{
@@ -48,14 +48,14 @@ import {
{
$sort: { version: -1 }
}
- ])
+ ])
.exec())
.map((s) => s.versionId);
-
+
const latestSecretSnapshot = await SecretSnapshot.findOne({
workspace: workspaceId
}).sort({ version: -1 });
-
+
secretSnapshot = await new SecretSnapshot({
workspace: workspaceId,
version: latestSecretSnapshot ? latestSecretSnapshot.version + 1 : 1,
@@ -66,7 +66,7 @@ import {
Sentry.captureException(err);
throw new Error('Failed to take a secret snapshot');
}
-
+
return secretSnapshot;
}
@@ -87,9 +87,9 @@ const addSecretVersionsHelper = async ({
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
- throw new Error('Failed to add secret versions');
+ throw new Error(`Failed to add secret versions [err=${err}]`);
}
-
+
return newSecretVersions;
}
@@ -120,39 +120,39 @@ const markDeletedSecretVersionsHelper = async ({
const initSecretVersioningHelper = async () => {
try {
- await Secret.updateMany(
+ await Secret.updateMany(
{ version: { $exists: false } },
{ $set: { version: 1 } }
);
-
- const unversionedSecrets: ISecret[] = await Secret.aggregate([
- {
- $lookup: {
- from: 'secretversions',
- localField: '_id',
- foreignField: 'secret',
- as: 'versions',
- },
- },
- {
- $match: {
- versions: { $size: 0 },
- },
- },
- ]);
-
- if (unversionedSecrets.length > 0) {
- await addSecretVersionsHelper({
- secretVersions: unversionedSecrets.map((s, idx) => ({
- ...s,
- secret: s._id,
- version: s.version ? s.version : 1,
- isDeleted: false,
- workspace: s.workspace,
- environment: s.environment
- }))
- });
- }
+
+ const unversionedSecrets: ISecret[] = await Secret.aggregate([
+ {
+ $lookup: {
+ from: 'secretversions',
+ localField: '_id',
+ foreignField: 'secret',
+ as: 'versions',
+ },
+ },
+ {
+ $match: {
+ versions: { $size: 0 },
+ },
+ },
+ ]);
+
+ if (unversionedSecrets.length > 0) {
+ await addSecretVersionsHelper({
+ secretVersions: unversionedSecrets.map((s, idx) => ({
+ ...s,
+ secret: s._id,
+ version: s.version ? s.version : 1,
+ isDeleted: false,
+ workspace: s.workspace,
+ environment: s.environment
+ }))
+ });
+ }
} catch (err) {
Sentry.setUser(null);
@@ -162,7 +162,7 @@ const initSecretVersioningHelper = async () => {
}
export {
- takeSecretSnapshotHelper,
+ takeSecretSnapshotHelper,
addSecretVersionsHelper,
markDeletedSecretVersionsHelper,
initSecretVersioningHelper
diff --git a/backend/src/ee/models/secretVersion.ts b/backend/src/ee/models/secretVersion.ts
index 616d44fbd..391c0faec 100644
--- a/backend/src/ee/models/secretVersion.ts
+++ b/backend/src/ee/models/secretVersion.ts
@@ -10,14 +10,14 @@ import {
export interface ISecretVersion {
_id: Types.ObjectId;
- secret: Types.ObjectId;
- version: number;
+ secret: Types.ObjectId;
+ version: number;
workspace: Types.ObjectId; // new
type: string; // new
user: Types.ObjectId; // new
environment: string; // new
- isDeleted: boolean;
- secretKeyCiphertext: string;
+ isDeleted: boolean;
+ secretKeyCiphertext: string;
secretKeyIV: string;
secretKeyTag: string;
secretKeyHash: string;
@@ -28,17 +28,17 @@ export interface ISecretVersion {
}
const secretVersionSchema = new Schema(
- {
- secret: { // could be deleted
- type: Schema.Types.ObjectId,
- ref: 'Secret',
- required: true
- },
- version: {
- type: Number,
- default: 1,
- required: true
- },
+ {
+ secret: { // could be deleted
+ type: Schema.Types.ObjectId,
+ ref: 'Secret',
+ required: true
+ },
+ version: {
+ type: Number,
+ default: 1,
+ required: true
+ },
workspace: {
type: Schema.Types.ObjectId,
ref: 'Workspace',
@@ -59,12 +59,12 @@ const secretVersionSchema = new Schema(
enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD],
required: true
},
- isDeleted: { // consider removing field
- type: Boolean,
- default: false,
- required: true
- },
- secretKeyCiphertext: {
+ isDeleted: { // consider removing field
+ type: Boolean,
+ default: false,
+ required: true
+ },
+ secretKeyCiphertext: {
type: String,
required: true
},
@@ -94,10 +94,10 @@ const secretVersionSchema = new Schema(
secretValueHash: {
type: String
}
- },
- {
- timestamps: true
- }
+ },
+ {
+ timestamps: true
+ }
);
const SecretVersion = model('SecretVersion', secretVersionSchema);
diff --git a/backend/src/routes/v2/secrets.ts b/backend/src/routes/v2/secrets.ts
index 29eac042f..085d487cd 100644
--- a/backend/src/routes/v2/secrets.ts
+++ b/backend/src/routes/v2/secrets.ts
@@ -8,8 +8,8 @@ import {
} from '../../middleware';
import { query, check, body } from 'express-validator';
import { secretsController } from '../../controllers/v2';
-import {
- ADMIN,
+import {
+ ADMIN,
MEMBER,
SECRET_PERSONAL,
SECRET_SHARED
@@ -27,7 +27,7 @@ router.post(
if (value.length === 0) throw new Error('secrets cannot be an empty array')
for (const secret of value) {
if (
- !secret.type ||
+ !secret.type ||
!(secret.type === SECRET_PERSONAL || secret.type === SECRET_SHARED) ||
!secret.secretKeyCiphertext ||
!secret.secretKeyIV ||
@@ -42,7 +42,7 @@ router.post(
} else if (typeof value === 'object') {
// case: update 1 secret
if (
- !value.type ||
+ !value.type ||
!(value.type === SECRET_PERSONAL || value.type === SECRET_SHARED) ||
!value.secretKeyCiphertext ||
!value.secretKeyIV ||
@@ -52,13 +52,13 @@ router.post(
!value.secretValueTag
) {
throw new Error('secrets object is missing required secret properties');
- }
+ }
} else {
throw new Error('secrets must be an object or an array of objects')
}
-
+
return true;
- }),
+ }),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt']
@@ -95,36 +95,24 @@ router.patch(
if (value.length === 0) throw new Error('secrets cannot be an empty array')
for (const secret of value) {
if (
- !secret.id ||
- !secret.secretKeyCiphertext ||
- !secret.secretKeyIV ||
- !secret.secretKeyTag ||
- !secret.secretValueCiphertext ||
- !secret.secretValueIV ||
- !secret.secretValueTag
+ !secret.id
) {
- throw new Error('secrets array must contain objects that have required secret properties');
+ throw new Error('Each secret must contain a ID property');
}
}
} else if (typeof value === 'object') {
// case: update 1 secret
if (
- !value.id ||
- !value.secretKeyCiphertext ||
- !value.secretKeyIV ||
- !value.secretKeyTag ||
- !value.secretValueCiphertext ||
- !value.secretValueIV ||
- !value.secretValueTag
+ !value.id
) {
- throw new Error('secrets object is missing required secret properties');
- }
+ throw new Error('secret must contain a ID property');
+ }
} else {
throw new Error('secrets must be an object or an array of objects')
}
-
+
return true;
- }),
+ }),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt']
@@ -142,13 +130,13 @@ router.delete(
.custom((value) => {
// case: delete 1 secret
if (typeof value === 'string') return true;
-
+
if (Array.isArray(value)) {
// case: delete multiple secrets
if (value.length === 0) throw new Error('secrets cannot be an empty array');
return value.every((id: string) => typeof id === 'string')
}
-
+
throw new Error('secretIds must be a string or an array of strings');
})
.not()
From a707fe14982a620b314ab23e56afda1d180fb555 Mon Sep 17 00:00:00 2001
From: Maidul Islam
Date: Thu, 12 Jan 2023 15:56:49 -0800
Subject: [PATCH 24/25] disable integration
---
backend/src/variables/integration.ts | 66 ++++++++++++++--------------
1 file changed, 33 insertions(+), 33 deletions(-)
diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts
index 1625c3954..baea2b093 100644
--- a/backend/src/variables/integration.ts
+++ b/backend/src/variables/integration.ts
@@ -11,10 +11,10 @@ const INTEGRATION_VERCEL = 'vercel';
const INTEGRATION_NETLIFY = 'netlify';
const INTEGRATION_GITHUB = 'github';
const INTEGRATION_SET = new Set([
- INTEGRATION_HEROKU,
- INTEGRATION_VERCEL,
- INTEGRATION_NETLIFY,
- INTEGRATION_GITHUB
+ INTEGRATION_HEROKU,
+ INTEGRATION_VERCEL,
+ INTEGRATION_NETLIFY,
+ INTEGRATION_GITHUB
]);
// integration types
@@ -23,10 +23,10 @@ const INTEGRATION_OAUTH2 = 'oauth2';
// integration oauth endpoints
const INTEGRATION_HEROKU_TOKEN_URL = 'https://id.heroku.com/oauth/token';
const INTEGRATION_VERCEL_TOKEN_URL =
- 'https://api.vercel.com/v2/oauth/access_token';
+ 'https://api.vercel.com/v2/oauth/access_token';
const INTEGRATION_NETLIFY_TOKEN_URL = 'https://api.netlify.com/oauth/token';
const INTEGRATION_GITHUB_TOKEN_URL =
- 'https://github.com/login/oauth/access_token';
+ 'https://github.com/login/oauth/access_token';
// integration apps endpoints
const INTEGRATION_HEROKU_API_URL = 'https://api.heroku.com';
@@ -37,7 +37,7 @@ const INTEGRATION_OPTIONS = [
{
name: 'Heroku',
slug: 'heroku',
- image: 'Heroku',
+ image: 'Heroku',
isAvailable: true,
type: 'oauth2',
clientId: CLIENT_ID_HEROKU,
@@ -46,8 +46,8 @@ const INTEGRATION_OPTIONS = [
{
name: 'Vercel',
slug: 'vercel',
- image: 'Vercel',
- isAvailable: true,
+ image: 'Vercel',
+ isAvailable: false,
type: 'vercel',
clientId: '',
clientSlug: CLIENT_SLUG_VERCEL,
@@ -56,8 +56,8 @@ const INTEGRATION_OPTIONS = [
{
name: 'Netlify',
slug: 'netlify',
- image: 'Netlify',
- isAvailable: true,
+ image: 'Netlify',
+ isAvailable: false,
type: 'oauth2',
clientId: CLIENT_ID_NETLIFY,
docsLink: ''
@@ -65,17 +65,17 @@ const INTEGRATION_OPTIONS = [
{
name: 'GitHub',
slug: 'github',
- image: 'GitHub',
- isAvailable: true,
+ image: 'GitHub',
+ isAvailable: false,
type: 'oauth2',
clientId: CLIENT_ID_GITHUB,
docsLink: ''
-
+
},
{
name: 'Google Cloud Platform',
slug: 'gcp',
- image: 'Google Cloud Platform',
+ image: 'Google Cloud Platform',
isAvailable: false,
type: '',
clientId: '',
@@ -84,7 +84,7 @@ const INTEGRATION_OPTIONS = [
{
name: 'Amazon Web Services',
slug: 'aws',
- image: 'Amazon Web Services',
+ image: 'Amazon Web Services',
isAvailable: false,
type: '',
clientId: '',
@@ -93,7 +93,7 @@ const INTEGRATION_OPTIONS = [
{
name: 'Microsoft Azure',
slug: 'azure',
- image: 'Microsoft Azure',
+ image: 'Microsoft Azure',
isAvailable: false,
type: '',
clientId: '',
@@ -102,7 +102,7 @@ const INTEGRATION_OPTIONS = [
{
name: 'Travis CI',
slug: 'travisci',
- image: 'Travis CI',
+ image: 'Travis CI',
isAvailable: false,
type: '',
clientId: '',
@@ -111,7 +111,7 @@ const INTEGRATION_OPTIONS = [
{
name: 'Circle CI',
slug: 'circleci',
- image: 'Circle CI',
+ image: 'Circle CI',
isAvailable: false,
type: '',
clientId: '',
@@ -120,18 +120,18 @@ const INTEGRATION_OPTIONS = [
]
export {
- INTEGRATION_HEROKU,
- INTEGRATION_VERCEL,
- INTEGRATION_NETLIFY,
- INTEGRATION_GITHUB,
- INTEGRATION_SET,
- INTEGRATION_OAUTH2,
- INTEGRATION_HEROKU_TOKEN_URL,
- INTEGRATION_VERCEL_TOKEN_URL,
- INTEGRATION_NETLIFY_TOKEN_URL,
- INTEGRATION_GITHUB_TOKEN_URL,
- INTEGRATION_HEROKU_API_URL,
- INTEGRATION_VERCEL_API_URL,
- INTEGRATION_NETLIFY_API_URL,
- INTEGRATION_OPTIONS
+ INTEGRATION_HEROKU,
+ INTEGRATION_VERCEL,
+ INTEGRATION_NETLIFY,
+ INTEGRATION_GITHUB,
+ INTEGRATION_SET,
+ INTEGRATION_OAUTH2,
+ INTEGRATION_HEROKU_TOKEN_URL,
+ INTEGRATION_VERCEL_TOKEN_URL,
+ INTEGRATION_NETLIFY_TOKEN_URL,
+ INTEGRATION_GITHUB_TOKEN_URL,
+ INTEGRATION_HEROKU_API_URL,
+ INTEGRATION_VERCEL_API_URL,
+ INTEGRATION_NETLIFY_API_URL,
+ INTEGRATION_OPTIONS
};
From 1ac94ee940bb432deeab6360232c95533e5aa954 Mon Sep 17 00:00:00 2001
From: Maidul Islam
Date: Thu, 12 Jan 2023 16:37:59 -0800
Subject: [PATCH 25/25] selectively get user email from service toke/jwt
---
.../src/controllers/v2/secretController.ts | 191 +++++++++---------
.../src/controllers/v2/secretsController.ts | 5 +-
2 files changed, 101 insertions(+), 95 deletions(-)
diff --git a/backend/src/controllers/v2/secretController.ts b/backend/src/controllers/v2/secretController.ts
index c2cf45f9a..89567e616 100644
--- a/backend/src/controllers/v2/secretController.ts
+++ b/backend/src/controllers/v2/secretController.ts
@@ -7,7 +7,7 @@ const { ValidationError } = mongoose.Error;
import { BadRequestError, InternalServerError, UnauthorizedRequestError, ValidationError as RouteValidationError } from '../../utils/errors';
import { AnyBulkWriteOperation } from 'mongodb';
import { SECRET_PERSONAL, SECRET_SHARED } from "../../variables";
-// import { postHogClient } from '../../services';
+import { postHogClient } from '../../services';
/**
* Create secret for workspace with id [workspaceId] and environment [environment]
@@ -42,19 +42,19 @@ export const createSecret = async (req: Request, res: Response) => {
throw RouteValidationError({ message: error.message, stack: error.stack })
}
- // if (postHogClient) {
- // postHogClient.capture({
- // event: 'secrets added',
- // distinctId: req.user.email,
- // properties: {
- // numberOfSecrets: 1,
- // workspaceId,
- // environment,
- // channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
- // userAgent: req.headers?.['user-agent']
- // }
- // });
- // }
+ if (postHogClient) {
+ postHogClient.capture({
+ event: 'secrets added',
+ distinctId: req.user.email,
+ properties: {
+ numberOfSecrets: 1,
+ workspaceId,
+ environment,
+ channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
+ userAgent: req.headers?.['user-agent']
+ }
+ });
+ }
res.status(200).send({
secret
@@ -103,19 +103,19 @@ export const createSecrets = async (req: Request, res: Response) => {
throw InternalServerError({ message: "Unable to process your batch create request. Please try again", stack: bulkCreateError.stack })
}
- // if (postHogClient) {
- // postHogClient.capture({
- // event: 'secrets added',
- // distinctId: req.user.email,
- // properties: {
- // numberOfSecrets: (secretsToCreate ?? []).length,
- // workspaceId,
- // environment,
- // channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
- // userAgent: req.headers?.['user-agent']
- // }
- // });
- // }
+ if (postHogClient) {
+ postHogClient.capture({
+ event: 'secrets added',
+ distinctId: req.user.email,
+ properties: {
+ numberOfSecrets: (secretsToCreate ?? []).length,
+ workspaceId,
+ environment,
+ channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
+ userAgent: req.headers?.['user-agent']
+ }
+ });
+ }
res.status(200).send({
secrets
@@ -158,19 +158,19 @@ export const deleteSecrets = async (req: Request, res: Response) => {
throw InternalServerError()
}
- // if (postHogClient) {
- // postHogClient.capture({
- // event: 'secrets deleted',
- // distinctId: req.user.email,
- // properties: {
- // numberOfSecrets: numSecretsDeleted,
- // environment: environmentName,
- // workspaceId,
- // channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
- // userAgent: req.headers?.['user-agent']
- // }
- // });
- // }
+ if (postHogClient) {
+ postHogClient.capture({
+ event: 'secrets deleted',
+ distinctId: req.user.email,
+ properties: {
+ numberOfSecrets: numSecretsDeleted,
+ environment: environmentName,
+ workspaceId,
+ channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
+ userAgent: req.headers?.['user-agent']
+ }
+ });
+ }
res.status(200).send()
}
@@ -183,19 +183,19 @@ export const deleteSecrets = async (req: Request, res: Response) => {
export const deleteSecret = async (req: Request, res: Response) => {
await Secret.findByIdAndDelete(req._secret._id)
- // if (postHogClient) {
- // postHogClient.capture({
- // event: 'secrets deleted',
- // distinctId: req.user.email,
- // properties: {
- // numberOfSecrets: 1,
- // workspaceId: req._secret.workspace.toString(),
- // environment: req._secret.environment,
- // channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
- // userAgent: req.headers?.['user-agent']
- // }
- // });
- // }
+ if (postHogClient) {
+ postHogClient.capture({
+ event: 'secrets deleted',
+ distinctId: req.user.email,
+ properties: {
+ numberOfSecrets: 1,
+ workspaceId: req._secret.workspace.toString(),
+ environment: req._secret.environment,
+ channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
+ userAgent: req.headers?.['user-agent']
+ }
+ });
+ }
res.status(200).send({
secret: req._secret
@@ -252,19 +252,19 @@ export const updateSecrets = async (req: Request, res: Response) => {
throw InternalServerError()
}
- // if (postHogClient) {
- // postHogClient.capture({
- // event: 'secrets modified',
- // distinctId: req.user.email,
- // properties: {
- // numberOfSecrets: (secretsModificationsRequested ?? []).length,
- // environment: environmentName,
- // workspaceId,
- // channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
- // userAgent: req.headers?.['user-agent']
- // }
- // });
- // }
+ if (postHogClient) {
+ postHogClient.capture({
+ event: 'secrets modified',
+ distinctId: req.user.email,
+ properties: {
+ numberOfSecrets: (secretsModificationsRequested ?? []).length,
+ environment: environmentName,
+ workspaceId,
+ channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
+ userAgent: req.headers?.['user-agent']
+ }
+ });
+ }
return res.status(200).send()
}
@@ -304,19 +304,19 @@ export const updateSecret = async (req: Request, res: Response) => {
throw RouteValidationError({ message: "Unable to apply modifications, please try again", stack: error.stack })
}
- // if (postHogClient) {
- // postHogClient.capture({
- // event: 'secrets modified',
- // distinctId: req.user.email,
- // properties: {
- // numberOfSecrets: 1,
- // environment: environmentName,
- // workspaceId,
- // channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
- // userAgent: req.headers?.['user-agent']
- // }
- // });
- // }
+ if (postHogClient) {
+ postHogClient.capture({
+ event: 'secrets modified',
+ distinctId: req.user.email,
+ properties: {
+ numberOfSecrets: 1,
+ environment: environmentName,
+ workspaceId,
+ channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
+ userAgent: req.headers?.['user-agent']
+ }
+ });
+ }
return res.status(200).send(singleModificationUpdate)
}
@@ -332,13 +332,16 @@ export const getSecrets = async (req: Request, res: Response) => {
const { environment } = req.query;
const { workspaceId } = req.params;
- let userId: string | undefined = undefined // used for getting personal secrets for user
+ let userId: Types.ObjectId | undefined = undefined // used for getting personal secrets for user
+ let userEmail: Types.ObjectId | undefined = undefined // used for posthog
if (req.user) {
- userId = req.user._id.toString();
+ userId = req.user._id;
+ userEmail = req.user.email;
}
if (req.serviceTokenData) {
userId = req.serviceTokenData.user._id
+ userEmail = req.serviceTokenData.user.email;
}
const [err, secrets] = await to(Secret.find(
@@ -354,19 +357,19 @@ export const getSecrets = async (req: Request, res: Response) => {
throw RouteValidationError({ message: "Failed to get secrets, please try again", stack: err.stack })
}
- // if (postHogClient) {
- // postHogClient.capture({
- // event: 'secrets pulled',
- // distinctId: req.user.email,
- // properties: {
- // numberOfSecrets: (secrets ?? []).length,
- // environment,
- // workspaceId,
- // channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
- // userAgent: req.headers?.['user-agent']
- // }
- // });
- // }
+ if (postHogClient) {
+ postHogClient.capture({
+ event: 'secrets pulled',
+ distinctId: userEmail,
+ properties: {
+ numberOfSecrets: (secrets ?? []).length,
+ environment,
+ workspaceId,
+ channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
+ userAgent: req.headers?.['user-agent']
+ }
+ });
+ }
return res.json(secrets)
}
diff --git a/backend/src/controllers/v2/secretsController.ts b/backend/src/controllers/v2/secretsController.ts
index f92fc4cee..b5a27874f 100644
--- a/backend/src/controllers/v2/secretsController.ts
+++ b/backend/src/controllers/v2/secretsController.ts
@@ -162,12 +162,15 @@ export const getSecrets = async (req: Request, res: Response) => {
const { workspaceId, environment } = req.query;
let userId: Types.ObjectId | undefined = undefined // used for getting personal secrets for user
+ let userEmail: Types.ObjectId | undefined = undefined // used for posthog
if (req.user) {
userId = req.user._id;
+ userEmail = req.user.email;
}
if (req.serviceTokenData) {
userId = req.serviceTokenData.user._id
+ userEmail = req.serviceTokenData.user.email;
}
const [err, secrets] = await to(Secret.find(
@@ -204,7 +207,7 @@ export const getSecrets = async (req: Request, res: Response) => {
if (postHogClient) {
postHogClient.capture({
event: 'secrets added',
- distinctId: req.user.email,
+ distinctId: userEmail,
properties: {
numberOfSecrets: secrets.length,
environment,