Add frontend, backend and CLI

This commit is contained in:
Maidul Islam
2022-11-17 17:54:35 -05:00
parent 8d00c5cdb7
commit bea0ff6e05
276 changed files with 50230 additions and 72 deletions
@@ -0,0 +1,29 @@
import token from "../../pages/api/auth/Token"
import { PATH } from '../../const';
export default class SecurityClient {
static authOrigins = [PATH]
static #token = '';
contructor() {
}
static setToken(token) {
this.#token = token;
}
static async fetchCall(resource, options) {
let req = new Request(resource, options);
const destOrigin = new URL(req.url).origin;
if (this.#token == "") {
this.setToken(await token())
}
if (this.#token && this.authOrigins.includes(destOrigin)) {
req.headers.set('Authorization', "Bearer " + this.#token);
return fetch(req);
}
}
}
@@ -0,0 +1,182 @@
import login1 from "../../pages/api/auth/Login1";
import login2 from "../../pages/api/auth/Login2";
import Aes256Gcm from "../../components/aes-256-gcm";
import pushKeys from "./pushKeys";
import { initPostHog } from "../analytics/posthog";
import getOrganizations from "../../pages/api/organization/getOrgs";
import getOrganizationUserProjects from "../../pages/api/organization/GetOrgUserProjects";
import SecurityClient from "./SecurityClient";
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
const jsrp = require("jsrp");
const client = new jsrp.client();
/**
* This function loggs in the user (whether it's right after signup, or a normal login)
* @param {*} email
* @param {*} password
* @param {*} setErrorLogin
* @param {*} router
* @param {*} isSignUp
* @returns
*/
const attemptLogin = async (
email,
password,
setErrorLogin,
router,
isSignUp,
isLogin
) => {
try {
let userWorkspace, userOrg;
client.init(
{
username: email,
password: password,
},
async () => {
const clientPublicKey = client.getPublicKey();
let serverPublicKey, salt;
try {
const res = await login1(email, clientPublicKey);
res = await res.json();
serverPublicKey = res.serverPublicKey;
salt = res.salt;
} catch (err) {
setErrorLogin(true);
console.log("Wrong password", err);
}
let response;
try {
client.setSalt(salt);
client.setServerPublicKey(serverPublicKey);
const clientProof = client.getProof(); // called M1
response = await login2(email, clientProof);
} catch (err) {
setErrorLogin(true);
console.log("Password verification failed");
}
// if everything works, go the main dashboard page.
try {
if (response.status == "200") {
response = await response.json();
SecurityClient.setToken(response["token"]);
const publicKey = response["publicKey"];
const encryptedPrivateKey =
response["encryptedPrivateKey"];
const iv = response["iv"];
const tag = response["tag"];
const PRIVATE_KEY = Aes256Gcm.decrypt(
encryptedPrivateKey,
iv,
tag,
password.slice(0, 32).padStart(32 + (password.slice(0, 32).length - new Blob([password]).size), "0")
);
try {
localStorage.setItem("publicKey", publicKey);
localStorage.setItem(
"encryptedPrivateKey",
encryptedPrivateKey
);
localStorage.setItem("iv", iv);
localStorage.setItem("tag", tag);
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY);
} catch (err) {
setErrorLogin(true);
console.error(
"Unable to send the tokens in local storage:" +
err.message
);
}
} else {
setErrorLogin(true);
}
const userOrgs = await getOrganizations();
const userOrgsData = userOrgs.map(org => org._id);
let orgToLogin;
if (userOrgsData.includes(localStorage.getItem("orgData.id"))) {
orgToLogin = localStorage.getItem("orgData.id");
} else {
orgToLogin = userOrgsData[0];
localStorage.setItem("orgData.id", orgToLogin);
}
let orgUserProjects = await getOrganizationUserProjects({
orgId: orgToLogin,
});
orgUserProjects = orgUserProjects?.map(project => project._id);
let projectToLogin;
if (orgUserProjects.includes(localStorage.getItem("projectData.id"))) {
projectToLogin = localStorage.getItem("projectData.id");
} else {
try {
projectToLogin = orgUserProjects[0];
localStorage.setItem("projectData.id", projectToLogin);
} catch (error) {
console.log("ERROR: User likely has no projects. ", error)
}
}
// If user is logging in for the first time, add the example keys
if (isSignUp) {
await pushKeys(
{
DATABASE_URL: [
"mongodb+srv://this_is:[email protected]",
"personal",
],
TWILIO_AUTH_TOKEN: [
"hgSIwDAKvz8PJfkj6xkzYqzGmAP3HLuG",
"shared",
],
WEBSITE_URL: [
"http://localhost:3000",
"shared",
],
STRIPE_SECRET_KEY: [
"sk_test_7348oyho4hfq398HIUOH78",
"shared",
],
},
projectToLogin,
"Development"
);
}
try {
if (email) {
if (process.env.NEXT_PUBLIC_ENV == "production") {
const posthog = initPostHog();
posthog.identify(email);
posthog.capture("User Logged In");
}
}
} catch (error) {
console.log("posthog", error);
}
if (isLogin) {
router.push("/dashboard/");
}
} catch (error) {
setErrorLogin(true);
console.log("Login response not available");
}
}
);
} catch (error) {
console.log("Something went wrong during authentication");
}
return true;
};
export default attemptLogin;
@@ -0,0 +1,109 @@
import Aes256Gcm from "../aes-256-gcm";
import SRP1 from "../../pages/api/auth/SRP1";
import changePassword2 from "../../pages/api/auth/ChangePassword2";
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
const jsrp = require("jsrp");
const clientOldPassword = new jsrp.client();
const clientNewPassword = new jsrp.client();
/**
* This function loggs in the user (whether it's right after signup, or a normal login)
* @param {*} email
* @param {*} password
* @param {*} setErrorLogin
* @param {*} router
* @param {*} isSignUp
* @returns
*/
const changePassword = async (
email,
currentPassword,
newPassword,
setCurrentPasswordError,
setPasswordChanged,
setCurrentPassword,
setNewPassword
) => {
try {
setPasswordChanged(false);
setCurrentPasswordError(false);
clientOldPassword.init(
{
username: email,
password: currentPassword,
},
async () => {
const clientPublicKey = clientOldPassword.getPublicKey();
let serverPublicKey, salt;
try {
const res = await SRP1({
clientPublicKey: clientPublicKey
});
serverPublicKey = res.serverPublicKey;
salt = res.salt;
} catch (err) {
setCurrentPasswordError(true);
console.log("Wrong current password", err, 1);
}
clientOldPassword.setSalt(salt);
clientOldPassword.setServerPublicKey(serverPublicKey);
const clientProof = clientOldPassword.getProof(); // called M1
clientNewPassword.init({
username: email,
password: newPassword
}, async () => {
clientNewPassword.createVerifier(async (err, result) => {
let { ciphertext, iv, tag } = Aes256Gcm.encrypt(
localStorage.getItem("PRIVATE_KEY"),
newPassword.slice(0, 32).padStart(32 + (newPassword.slice(0, 32).length - new Blob([newPassword]).size), "0")
);
if (ciphertext) {
localStorage.setItem(
"encryptedPrivateKey",
ciphertext
);
localStorage.setItem("iv", iv);
localStorage.setItem("tag", tag);
let res;
try {
res = await changePassword2({
encryptedPrivateKey: ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
clientProof
});
if (res.status == 400) {
setCurrentPasswordError(true);
} else if (res.status == 200) {
setPasswordChanged(true);
setCurrentPassword("");
setNewPassword("");
}
} catch (err) {
setCurrentPasswordError(true)
console.log(err);
}
}
});
});
}
);
} catch (error) {
console.log("Something went wrong during changing the password", slat, serverPublicKey);
}
return true;
};
export default changePassword;
@@ -0,0 +1,65 @@
/**
* This function checks a user password with respect to some criteria.
* @param {*} password
* @param {*} setPasswordError
* @param {*} setPasswordErrorMessage
* @param {*} currentErrorCheck
* @returns
*/
const passwordCheck = (
password,
setPasswordErrorLength,
setPasswordErrorNumber,
setPasswordErrorLowerCase,
currentErrorCheck
) => {
let errorCheck = currentErrorCheck;
if (!password || password.length < 14) {
setPasswordErrorLength(true);
errorCheck = true;
} else {
setPasswordErrorLength(false);
}
if (!/\d/.test(password)) {
setPasswordErrorNumber(true);
errorCheck = true;
} else {
setPasswordErrorNumber(false);
}
if (!/[a-z]/.test(password)) {
setPasswordErrorLowerCase(true);
errorCheck = true;
// } else if (/(.)(?:(?!\1).){1,2}/.test(password)) {
// console.log(111)
// setPasswordError(true);
// setPasswordErrorMessage("Password should not contain repeating characters.");
// errorCheck = true;
// } else if (RegExp(`[${email}]`).test(password)) {
// console.log(222)
// setPasswordError(true);
// setPasswordErrorMessage("Password should not contain your email.");
// errorCheck = true;
} else {
setPasswordErrorLowerCase(false);
}
// if (!/[A-Z]/.test(password)) {
// setPasswordErrorUpperCase(true);
// errorCheck = true;
// } else {
// setPasswordErrorUpperCase(false);
// }
// if (!/(?=.*[!@#$%^&*])/.test(password)) {
// setPasswordErrorSpecialChar(true);
// // "Please add at least 1 special character (*, !, #, %)."
// errorCheck = true;
// } else {
// setPasswordErrorSpecialChar(false);
// }
return errorCheck;
};
export default passwordCheck;
+105
View File
@@ -0,0 +1,105 @@
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
const aes = require("../aes-256-gcm");
/**
* Return assymmetrically encrypted [plaintext] using [publicKey] where
* [publicKey] likely belongs to the recipient.
* @param {Object} obj
* @param {String} obj.plaintext - plaintext to encrypt
* @param {String} obj.publicKey - public key of the recipient
* @param {String} obj.privateKey - private key of the sender (current user)
* @returns {Object} obj
* @returns {String} ciphertext - base64-encoded ciphertext
* @returns {String} nonce - base64-encoded nonce
*/
const encryptAssymmetric = ({ plaintext, publicKey, privateKey }) => {
const nonce = nacl.randomBytes(24);
const ciphertext = nacl.box(
nacl.util.decodeUTF8(plaintext),
nonce,
nacl.util.decodeBase64(publicKey),
nacl.util.decodeBase64(privateKey)
);
return {
ciphertext: nacl.util.encodeBase64(ciphertext),
nonce: nacl.util.encodeBase64(nonce),
};
};
/**
* Return assymmetrically decrypted [ciphertext] using [privateKey] where
* [privateKey] likely belongs to the recipient.
* @param {Object} obj
* @param {String} obj.ciphertext - ciphertext to decrypt
* @param {String} obj.nonce - nonce
* @param {String} obj.publicKey - base64-encoded public key of the sender
* @param {String} obj.privateKey - base64-encoded private key of the receiver (current user)
* @param {String} plaintext - UTF8 plaintext
*/
const decryptAssymmetric = ({ ciphertext, nonce, publicKey, privateKey }) => {
const plaintext = nacl.box.open(
nacl.util.decodeBase64(ciphertext),
nacl.util.decodeBase64(nonce),
nacl.util.decodeBase64(publicKey),
nacl.util.decodeBase64(privateKey)
);
return nacl.util.encodeUTF8(plaintext);
};
/**
* Return symmetrically encrypted [plaintext] using [key].
* @param {Object} obj
* @param {String} obj.plaintext - plaintext to encrypt
* @param {String} obj.key - 16-byte hex key
*/
const encryptSymmetric = ({ plaintext, key }) => {
let ciphertext, iv, tag;
try {
const obj = aes.encrypt(plaintext, key);
ciphertext = obj.ciphertext;
iv = obj.iv;
tag = obj.tag;
} catch (err) {
console.log("Failed to perform encryption");
console.log(err);
process.exit(1);
}
return {
ciphertext,
iv,
tag,
};
};
/**
* Return symmetrically decypted [ciphertext] using [iv], [tag],
* and [key].
* @param {Object} obj
* @param {String} obj.ciphertext - ciphertext to decrypt
* @param {String} obj.iv - iv
* @param {String} obj.tag - tag
* @param {String} obj.key - 32-byte hex key
*
*/
const decryptSymmetric = ({ ciphertext, iv, tag, key }) => {
let plaintext;
try {
plaintext = aes.decrypt(ciphertext, iv, tag, key);
} catch (err) {
console.log("Failed to perform decryption");
process.exit(1);
}
return plaintext;
};
module.exports = {
encryptAssymmetric,
decryptAssymmetric,
encryptSymmetric,
decryptSymmetric,
};
+47
View File
@@ -0,0 +1,47 @@
const LINE =
/(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
/**
* Return text that is the buffer parsed
* @param {Buffer} src - source buffer
* @returns {String} text - text of buffer
*/
function parse(src) {
const obj = {};
// Convert buffer to string
let lines = src.toString();
// Convert line breaks to same format
lines = lines.replace(/\r\n?/gm, "\n");
let match;
while ((match = LINE.exec(lines)) != 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");
}
// Add to object
obj[key] = value;
}
return obj;
}
export default parse;
File diff suppressed because one or more lines are too long
@@ -0,0 +1,106 @@
import getSecrets from "../../pages/api/files/GetSecrets";
import guidGenerator from "./randomId";
const {
decryptAssymmetric,
decryptSymmetric,
} = require("../../components/utilities/crypto");
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
const envMapping = {
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test",
};
const getSecretsForProject = async ({
env,
setFileState,
setIsKeyAvailable,
setData,
workspaceId
}) => {
try {
let file;
try {
file = await getSecrets(
workspaceId,
envMapping[env]
);
setFileState(file);
} catch (error) {
console.log("ERROR: Not able to access the latest file");
}
// This is called isKeyAvilable but what it really means is if a person is able to create new key pairs
setIsKeyAvailable(
!file.key ? (file.secrets.length == 0 ? true : false) : true
);
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
let tempFileState = [];
if (file.key) {
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: file.key.encryptedKey,
nonce: file.key.nonce,
publicKey: file.key.sender.publicKey,
privateKey: PRIVATE_KEY,
});
file.secrets.map((secretPair) => {
// decrypt .env file with symmetric key
const plainTextKey = decryptSymmetric({
ciphertext: secretPair.secretKey.ciphertext,
iv: secretPair.secretKey.iv,
tag: secretPair.secretKey.tag,
key,
});
const plainTextValue = decryptSymmetric({
ciphertext: secretPair.secretValue.ciphertext,
iv: secretPair.secretValue.iv,
tag: secretPair.secretValue.tag,
key,
});
tempFileState.push({
key: plainTextKey,
value: plainTextValue,
type: secretPair.type,
});
});
}
setFileState(tempFileState);
setData(
tempFileState.map((line, index) => [
guidGenerator(),
index,
line["key"],
line["value"],
line["type"],
])
// .sort((a, b) =>
// sortMethod == "alphabetical"
// ? a[2].localeCompare(b[2])
// : b[2].localeCompare(a[2])
// )
);
return tempFileState.map((line, index) => [
guidGenerator(),
index,
line["key"],
line["value"],
line["type"],
]);
} catch (error) {
console.log("Something went wrong during accessing or decripting secrets.");
}
return true;
};
export default getSecretsForProject;
@@ -0,0 +1,96 @@
import Aes256Gcm from "../aes-256-gcm";
import SRP1 from "../../pages/api/auth/SRP1";
import issueBackupPrivateKey from "../../pages/api/auth/IssueBackupPrivateKey";
import generateBackupPDF from "./generateBackupPDF";
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
const jsrp = require("jsrp");
const clientPassword = new jsrp.client();
const clientKey = new jsrp.client();
const crypto = require("crypto");
/**
* This function loggs in the user (whether it's right after signup, or a normal login)
* @param {*} email
* @param {*} password
* @param {*} setErrorLogin
* @param {*} router
* @param {*} isSignUp
* @returns
*/
const issueBackupKey = async ({
email,
password,
personalName,
setBackupKeyError,
setBackupKeyIssued
}) => {
try {
setBackupKeyError(false);
setBackupKeyIssued(false);
clientPassword.init(
{
username: email,
password: password,
},
async () => {
const clientPublicKey = clientPassword.getPublicKey();
let serverPublicKey, salt;
try {
const res = await SRP1({
clientPublicKey: clientPublicKey
});
serverPublicKey = res.serverPublicKey;
salt = res.salt;
} catch (err) {
setBackupKeyError(true);
console.log("Wrong current password", err, 1);
}
clientPassword.setSalt(salt);
clientPassword.setServerPublicKey(serverPublicKey);
const clientProof = clientPassword.getProof(); // called M1
const generatedKey = crypto.randomBytes(16).toString("hex");
clientKey.init({
username: email,
password: generatedKey
}, async () => {
clientKey.createVerifier(async (err, result) => {
let { ciphertext, iv, tag } = Aes256Gcm.encrypt(
localStorage.getItem("PRIVATE_KEY"),
generatedKey
);
const res = await issueBackupPrivateKey({
encryptedPrivateKey: ciphertext,
iv,
tag,
salt: result.salt,
verifier: result.verifier,
clientProof
});
if (res.status == 400) {
setBackupKeyError(true);
} else if (res.status == 200) {
generateBackupPDF(personalName, email, generatedKey);
setBackupKeyIssued(true);
}
});
})
}
);
} catch (error) {
setBackupKeyError(true);
console.log("Failed to issue a backup key");
}
return true;
};
export default issueBackupKey;
+109
View File
@@ -0,0 +1,109 @@
import getLatestFileKey from "../../pages/api/workspace/getLatestFileKey";
import getWorkspaceKeys from "../../pages/api/workspace/getWorkspaceKeys";
import uploadSecrets from "../../pages/api/files/UploadSecrets";
const crypto = require("crypto");
const {
decryptAssymmetric,
decryptSymmetric,
encryptSymmetric,
encryptAssymmetric,
} = require("../../components/utilities/crypto");
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
const envMapping = {
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test",
};
const pushKeys = async (obj, workspaceId, env) => {
let sharedKey = await getLatestFileKey(workspaceId);
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
let randomBytes;
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 = Object.keys(obj).map((key) => {
// encrypt key
const {
ciphertext: ciphertextKey,
iv: ivKey,
tag: tagKey,
} = encryptSymmetric({
plaintext: key,
key: randomBytes,
});
// encrypt value
const {
ciphertext: ciphertextValue,
iv: ivValue,
tag: tagValue,
} = encryptSymmetric({
plaintext: obj[key][0],
key: randomBytes,
});
const visibility = obj[key][1] != null ? obj[key][1] : "personal";
return {
ciphertextKey,
ivKey,
tagKey,
hashKey: crypto.createHash("sha256").update(key).digest("hex"),
ciphertextValue,
ivValue,
tagValue,
hashValue: crypto
.createHash("sha256")
.update(obj[key][0])
.digest("hex"),
type: visibility,
};
});
// obtain public keys of all receivers (i.e. members in workspace)
const publicKeys = await getWorkspaceKeys({
workspaceId,
});
// assymmetrically encrypt key with each receiver public keys
const keys = publicKeys.map((k) => {
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: randomBytes,
publicKey: k.publicKey,
privateKey: PRIVATE_KEY,
});
return {
encryptedKey: ciphertext,
nonce,
userId: k.userId,
};
});
// send payload
await uploadSecrets({
workspaceId,
secrets,
keys,
environment: envMapping[env],
});
};
export default pushKeys;
@@ -0,0 +1,77 @@
import publicKeyInfical from "../../pages/api/auth/publicKeyInfisical"
import changeHerokuConfigVars from "../../pages/api/integrations/ChangeHerokuConfigVars";
const crypto = require("crypto");
const {
encryptSymmetric,
encryptAssymmetric,
} = require("./crypto");
const nacl = require("tweetnacl");
nacl.util = require("tweetnacl-util");
const pushKeysIntegration = async ({ obj, integrationId }) => {
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
let randomBytes = crypto.randomBytes(16).toString("hex");
const secrets = Object.keys(obj).map((key) => {
// encrypt key
const {
ciphertext: ciphertextKey,
iv: ivKey,
tag: tagKey,
} = encryptSymmetric({
plaintext: key,
key: randomBytes,
});
// encrypt value
const {
ciphertext: ciphertextValue,
iv: ivValue,
tag: tagValue,
} = encryptSymmetric({
plaintext: obj[key],
key: randomBytes,
});
const visibility = "shared";
return {
ciphertextKey,
ivKey,
tagKey,
hashKey: crypto.createHash("sha256").update(key).digest("hex"),
ciphertextValue,
ivValue,
tagValue,
hashValue: crypto
.createHash("sha256")
.update(obj[key])
.digest("hex"),
type: visibility,
};
});
// obtain public keys of all receivers (i.e. members in workspace)
let publicKeyInfisical = await publicKeyInfical();
publicKeyInfisical = (await publicKeyInfisical.json()).publicKey;
// assymmetrically encrypt key with each receiver public keys
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: randomBytes,
publicKey: publicKeyInfisical,
privateKey: PRIVATE_KEY,
});
const key = {
encryptedKey: ciphertext,
nonce,
};
changeHerokuConfigVars({integrationId, key, secrets})
};
export default pushKeysIntegration;
+25
View File
@@ -0,0 +1,25 @@
/**
* This function returns a random identifier.
* @returns
*/
const guidGenerator = () => {
var S4 = function () {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};
return (
S4() +
S4() +
"-" +
S4() +
"-" +
S4() +
"-" +
S4() +
"-" +
S4() +
S4() +
S4()
);
};
export default guidGenerator;