Skip to content

Commit

Permalink
Merge pull request #188 from mocherfaoui/import-export-secrets
Browse files Browse the repository at this point in the history
add ability to import/export secrets with comments
  • Loading branch information
vmatsiiako authored Jan 7, 2023
2 parents 53273df + 51368e6 commit eed6c75
Show file tree
Hide file tree
Showing 7 changed files with 2,482 additions and 2,305 deletions.
85 changes: 57 additions & 28 deletions frontend/components/dashboard/DropZone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import Image from "next/image";
import { useTranslation } from "next-i18next";
import { faUpload } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { parseDocument, Scalar, YAMLMap } from 'yaml';

import Button from "../basic/buttons/Button";
import Error from "../basic/Error";
import parse from "../utilities/file";
import { parseDotEnv } from '../utilities/parseDotEnv';
import guidGenerator from "../utilities/randomId";

interface DropZoneProps {
Expand Down Expand Up @@ -51,6 +52,53 @@ const DropZone = ({

const [loading, setLoading] = useState(false);

const getSecrets = (file: ArrayBuffer, fileType: string) => {
let secrets;
switch (fileType) {
case 'env': {
const keyPairs = parseDotEnv(file);
secrets = Object.keys(keyPairs).map((key, index) => {
return {
id: guidGenerator(),
pos: numCurrentRows + index,
key: key,
value: keyPairs[key as keyof typeof keyPairs].value,
comment: keyPairs[key as keyof typeof keyPairs].comments.join('\n'),
type: 'shared',
};
});
break;
}
case 'yml': {
const parsedFile = parseDocument(file.toString());
const keyPairs = parsedFile.contents!.toJSON();

secrets = Object.keys(keyPairs).map((key, index) => {
const fileContent = parsedFile.contents as YAMLMap<Scalar, Scalar>;
const comment =
fileContent!.items
.find((item) => item.key.value === key)
?.key?.commentBefore?.split('\n')
.map((comment) => comment.trim())
.join('\n') ?? '';
return {
id: guidGenerator(),
pos: numCurrentRows + index,
key: key,
value: keyPairs[key as keyof typeof keyPairs]?.toString() ?? '',
comment,
type: 'shared',
};
});
break;
}
default:
secrets = '';
break;
}
return secrets;
};

// This function function immediately parses the file after it is dropped
const handleDrop = async (e: DragEvent) => {
setLoading(true);
Expand All @@ -61,20 +109,12 @@ const DropZone = ({

const file = e.dataTransfer.files[0];
const reader = new FileReader();
const fileType = file.name.split('.')[1];

reader.onload = (event) => {
if (event.target === null || event.target.result === null) return;
// parse function's argument looks like to be ArrayBuffer
const keyPairs = parse(event.target.result as Buffer);
const newData = Object.keys(keyPairs).map((key, index) => {
return {
id: guidGenerator(),
pos: numCurrentRows + index,
key: key,
value: keyPairs[key as keyof typeof keyPairs],
type: "shared",
};
});
const newData = getSecrets(event.target.result as ArrayBuffer, fileType);
setData(newData);
setButtonReady(true);
};
Expand All @@ -95,25 +135,14 @@ const DropZone = ({
setTimeout(() => setLoading(false), 5000);
if (e.currentTarget.files === null) return;
const file = e.currentTarget.files[0];
const fileType = file.name.split('.')[1];
const reader = new FileReader();
reader.onload = (event) => {
if (event.target === null || event.target.result === null) return;
const { result } = event.target;
if (typeof result === "string") {
const newData = result
.split("\n")
.map((line: string, index: number) => {
return {
id: guidGenerator(),
pos: numCurrentRows + index,
key: line.split("=")[0],
value: line.split("=").slice(1, line.split("=").length).join("="),
type: "shared",
};
});
setData(newData);
setButtonReady(true);
}
const newData = getSecrets(result as ArrayBuffer, fileType);
setData(newData);
setButtonReady(true);
};
reader.readAsText(file);
};
Expand All @@ -139,7 +168,7 @@ const DropZone = ({
id="fileSelect"
type="file"
className="opacity-0 absolute w-full h-full"
accept=".txt,.env"
accept=".txt,.env,.yml"
onChange={handleFileSelect}
/>
{errorDragAndDrop ? (
Expand Down Expand Up @@ -176,7 +205,7 @@ const DropZone = ({
id="fileSelect"
type="file"
className="opacity-0 absolute w-full h-full"
accept=".txt,.env"
accept=".txt,.env,.yml"
onChange={handleFileSelect}
/>
<div className="flex flex-row w-full items-center justify-center mb-6 mt-5">
Expand Down
47 changes: 0 additions & 47 deletions frontend/components/utilities/file.ts

This file was deleted.

66 changes: 66 additions & 0 deletions frontend/components/utilities/parseDotEnv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const LINE =
/(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;

/**
* Return text that is the buffer parsed
* @param {ArrayBuffer} src - source buffer
* @returns {String} text - text of buffer
*/
export function parseDotEnv(src: ArrayBuffer) {
const object: {
[key: string]: { value: string; comments: string[] };
} = {};

// Convert buffer to string
let lines = src.toString();

// Convert line breaks to same format
lines = lines.replace(/\r\n?/gm, '\n');

let comments: string[] = [];

lines
.split('\n')
.map((line) => {
// collect comments of each env variable
if (line.startsWith('#')) {
comments.push(line.replace('#', '').trim());
} else if (line) {
let match;
let item: [string, string, string[]] | [] = [];

while ((match = LINE.exec(line)) !== null) {
const key = match[1];

// Default undefined or null to empty string
let value = match[2] || '';

// Remove whitespace
value = value.trim();

// Check if double quoted
const maybeQuote = value[0];

// Remove surrounding quotes
value = value.replace(/^(['"`])([\s\S]*)\1$/gm, '$2');

// Expand newlines if double quoted
if (maybeQuote === '"') {
value = value.replace(/\\n/g, '\n');
value = value.replace(/\\r/g, '\r');
}
item = [key, value, comments];
}
comments = [];
return item;
}
return [];
})
.filter((line) => line.length > 1)
.forEach((line) => {
const [key, value, comments] = line;
object[key as string] = { value, comments };
});

return object;
}
4 changes: 1 addition & 3 deletions frontend/components/utilities/telemetry/Telemetry.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class Capturer {

}

class Telemetry {
export default class Telemetry {
constructor() {
if (!Telemetry.instance) {
Telemetry.instance = new Capturer();
Expand All @@ -40,5 +40,3 @@ class Telemetry {
return Telemetry.instance;
}
}

module.exports = Telemetry;
Loading

0 comments on commit eed6c75

Please sign in to comment.