mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-16 10:18:16 +00:00
Checkpoint weaving frontend and backend MFA
This commit is contained in:
@@ -1,20 +1,12 @@
|
||||
/* eslint-disable prefer-destructuring */
|
||||
import crypto from 'crypto';
|
||||
|
||||
import jsrp from 'jsrp';
|
||||
import { SecretDataProps } from 'public/data/frequentInterfaces';
|
||||
|
||||
import Aes256Gcm from '@app/components/utilities/cryptography/aes-256-gcm';
|
||||
import login1 from '@app/pages/api/auth/Login1';
|
||||
import login2 from '@app/pages/api/auth/Login2';
|
||||
import addSecrets from '@app/pages/api/files/AddSecrets';
|
||||
import getOrganizations from '@app/pages/api/organization/getOrgs';
|
||||
import getOrganizationUserProjects from '@app/pages/api/organization/GetOrgUserProjects';
|
||||
import getUser from '@app/pages/api/user/getUser';
|
||||
import uploadKeys from '@app/pages/api/workspace/uploadKeys';
|
||||
import KeyService from '@app/services/KeyService';
|
||||
|
||||
import { deriveArgonKey, encryptAssymmetric } from './cryptography/crypto';
|
||||
import encryptSecrets from './secrets/encryptSecrets';
|
||||
import Telemetry from './telemetry/Telemetry';
|
||||
import { saveTokenToLocalStorage } from './saveTokenToLocalStorage';
|
||||
import SecurityClient from './SecurityClient';
|
||||
@@ -22,44 +14,39 @@ import SecurityClient from './SecurityClient';
|
||||
// eslint-disable-next-line new-cap
|
||||
const client = new jsrp.client();
|
||||
|
||||
interface IsLoginSuccessful {
|
||||
mfaEnabled: boolean;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function logs in the user (whether it's right after signup, or a normal login)
|
||||
* @param {string} email - email of the user logging in
|
||||
* @param {string} password - password of the user logging in
|
||||
* @param {function} setErrorLogin - function that visually dispay an error is something is wrong
|
||||
* @param {*} router
|
||||
* @param {boolean} isSignUp - whether this log in is a part of signup
|
||||
* @param {boolean} isLogin - ?
|
||||
* @returns
|
||||
* Return whether or not login is successful for user with email [email]
|
||||
* and password [password]
|
||||
* @param {string} email - email of user to log in
|
||||
* @param {string} password - password of user to log in
|
||||
*/
|
||||
const attemptLogin = async (
|
||||
email: string,
|
||||
password: string,
|
||||
setErrorLogin: (value: boolean) => void,
|
||||
router: any,
|
||||
isSignUp: boolean,
|
||||
isLogin: boolean
|
||||
) => {
|
||||
try {
|
||||
const telemetry = new Telemetry().getInstance();
|
||||
|
||||
password: string
|
||||
): Promise<IsLoginSuccessful> => {
|
||||
const telemetry = new Telemetry().getInstance();
|
||||
return new Promise((resolve, reject) => {
|
||||
client.init(
|
||||
{
|
||||
username: email,
|
||||
password
|
||||
},
|
||||
async () => {
|
||||
const clientPublicKey = client.getPublicKey();
|
||||
|
||||
try {
|
||||
const clientPublicKey = client.getPublicKey();
|
||||
const { serverPublicKey, salt } = await login1(email, clientPublicKey);
|
||||
|
||||
client.setSalt(salt);
|
||||
client.setServerPublicKey(serverPublicKey);
|
||||
const clientProof = client.getProof(); // called M1
|
||||
|
||||
// if everything works, go the main dashboard page.
|
||||
const { // mfaEnabled
|
||||
const {
|
||||
mfaEnabled,
|
||||
encryptionVersion,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
@@ -73,52 +60,40 @@ const attemptLogin = async (
|
||||
email,
|
||||
clientProof
|
||||
);
|
||||
|
||||
if (mfaEnabled) {
|
||||
// case: MFA is enabled
|
||||
|
||||
SecurityClient.setToken(token);
|
||||
// set temporary (MFA) JWT token
|
||||
SecurityClient.setToken(token);
|
||||
|
||||
let privateKey;
|
||||
if (encryptionVersion === 1) {
|
||||
privateKey = Aes256Gcm.decrypt({
|
||||
ciphertext: encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
secret: password
|
||||
.slice(0, 32)
|
||||
.padStart(32 + (password.slice(0, 32).length - new Blob([password]).size), '0')
|
||||
resolve({
|
||||
mfaEnabled,
|
||||
success: true
|
||||
});
|
||||
|
||||
saveTokenToLocalStorage({
|
||||
publicKey,
|
||||
} else if (
|
||||
!mfaEnabled &&
|
||||
encryptionVersion &&
|
||||
encryptedPrivateKey &&
|
||||
iv &&
|
||||
tag &&
|
||||
token
|
||||
) {
|
||||
// case: MFA is not enabled
|
||||
|
||||
// set JWT token
|
||||
SecurityClient.setToken(token);
|
||||
|
||||
const privateKey = await KeyService.decryptPrivateKey({
|
||||
encryptionVersion,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
privateKey
|
||||
});
|
||||
} else if (encryptionVersion === 2 && protectedKey && protectedKeyIV && protectedKeyTag) {
|
||||
const derivedKey = await deriveArgonKey({
|
||||
password,
|
||||
salt,
|
||||
mem: 65536,
|
||||
time: 3,
|
||||
parallelism: 1,
|
||||
hashLen: 32
|
||||
});
|
||||
|
||||
if (!derivedKey) throw new Error('Failed to derive key');
|
||||
|
||||
const key = Aes256Gcm.decrypt({
|
||||
ciphertext: protectedKey,
|
||||
iv: protectedKeyIV,
|
||||
tag: protectedKeyTag,
|
||||
secret: Buffer.from(derivedKey.hash)
|
||||
});
|
||||
|
||||
// decrypt back the private key
|
||||
privateKey = Aes256Gcm.decrypt({
|
||||
ciphertext: encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
secret: Buffer.from(key, 'hex')
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag
|
||||
});
|
||||
|
||||
saveTokenToLocalStorage({
|
||||
@@ -131,155 +106,160 @@ const attemptLogin = async (
|
||||
tag,
|
||||
privateKey
|
||||
});
|
||||
}
|
||||
|
||||
if (!privateKey) throw new Error('Failed to decrypt private key');
|
||||
|
||||
// TODO: in the future - move this logic elsewhere
|
||||
// because this function is about logging the user in
|
||||
// and not initializing the login details
|
||||
const userOrgs = await getOrganizations();
|
||||
const orgId = userOrgs[0]._id;
|
||||
localStorage.setItem('orgData.id', orgId);
|
||||
|
||||
const userOrgs = await getOrganizations();
|
||||
const userOrgsData = userOrgs.map((org: { _id: string }) => 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: { _id: string }) => 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 (email) {
|
||||
telemetry.identify(email);
|
||||
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[] = [
|
||||
{
|
||||
pos: 0,
|
||||
key: 'DATABASE_URL',
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
value: 'mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net',
|
||||
valueOverride: undefined,
|
||||
comment: 'Secret referencing example',
|
||||
id: '',
|
||||
tags: []
|
||||
},
|
||||
{
|
||||
pos: 1,
|
||||
key: 'DB_USERNAME',
|
||||
value: 'OVERRIDE_THIS',
|
||||
valueOverride: undefined,
|
||||
comment:
|
||||
'Override secrets with personal value',
|
||||
id: '',
|
||||
tags: []
|
||||
},
|
||||
{
|
||||
pos: 2,
|
||||
key: 'DB_PASSWORD',
|
||||
value: 'OVERRIDE_THIS',
|
||||
valueOverride: undefined,
|
||||
comment:
|
||||
'Another secret override',
|
||||
id: '',
|
||||
tags: []
|
||||
},
|
||||
{
|
||||
pos: 3,
|
||||
key: 'DB_USERNAME',
|
||||
value: 'user1234',
|
||||
valueOverride: 'user1234',
|
||||
comment: '',
|
||||
id: '',
|
||||
tags: []
|
||||
},
|
||||
{
|
||||
pos: 4,
|
||||
key: 'DB_PASSWORD',
|
||||
value: 'example_password',
|
||||
valueOverride: 'example_password',
|
||||
comment: '',
|
||||
id: '',
|
||||
tags: []
|
||||
},
|
||||
{
|
||||
pos: 5,
|
||||
key: 'TWILIO_AUTH_TOKEN',
|
||||
value: 'example_twillio_token',
|
||||
valueOverride: undefined,
|
||||
comment: '',
|
||||
id: '',
|
||||
tags: []
|
||||
},
|
||||
{
|
||||
pos: 6,
|
||||
key: 'WEBSITE_URL',
|
||||
value: 'http://localhost:3000',
|
||||
valueOverride: undefined,
|
||||
comment: '',
|
||||
id: '',
|
||||
tags: []
|
||||
}
|
||||
];
|
||||
const secrets = await encryptSecrets({
|
||||
secretsToEncrypt: secretsToBeAdded,
|
||||
workspaceId: String(localStorage.getItem('projectData.id')),
|
||||
env: 'dev'
|
||||
const orgUserProjects = await getOrganizationUserProjects({
|
||||
orgId
|
||||
});
|
||||
await addSecrets({
|
||||
secrets: secrets ?? [],
|
||||
env: 'dev',
|
||||
workspaceId: String(localStorage.getItem('projectData.id'))
|
||||
localStorage.setItem('projectData.id', orgUserProjects[0]._id);
|
||||
|
||||
// // TODO: this part definitely needs to be refactored
|
||||
// const userOrgs = await getOrganizations();
|
||||
// const userOrgsData = userOrgs.map((org: { _id: string }) => 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: { _id: string }) => 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 (email) {
|
||||
telemetry.identify(email);
|
||||
telemetry.capture('User Logged In');
|
||||
}
|
||||
|
||||
resolve({
|
||||
mfaEnabled: false,
|
||||
success: true
|
||||
});
|
||||
}
|
||||
|
||||
if (isLogin) {
|
||||
if (localStorage.getItem('projectData.id') !== "undefined") {
|
||||
router.push(`/dashboard/${localStorage.getItem('projectData.id')}`);
|
||||
} else {
|
||||
router.push("/noprojects");
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
setErrorLogin(true);
|
||||
console.log('Login response not available');
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log('Something went wrong during authentication');
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
export default attemptLogin;
|
||||
|
||||
// should be function: init first project
|
||||
|
||||
// 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[] = [
|
||||
// {
|
||||
// pos: 0,
|
||||
// key: 'DATABASE_URL',
|
||||
// // eslint-disable-next-line no-template-curly-in-string
|
||||
// value: 'mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net',
|
||||
// valueOverride: undefined,
|
||||
// comment: 'Secret referencing example',
|
||||
// id: '',
|
||||
// tags: []
|
||||
// },
|
||||
// {
|
||||
// pos: 1,
|
||||
// key: 'DB_USERNAME',
|
||||
// value: 'OVERRIDE_THIS',
|
||||
// valueOverride: undefined,
|
||||
// comment:
|
||||
// 'Override secrets with personal value',
|
||||
// id: '',
|
||||
// tags: []
|
||||
// },
|
||||
// {
|
||||
// pos: 2,
|
||||
// key: 'DB_PASSWORD',
|
||||
// value: 'OVERRIDE_THIS',
|
||||
// valueOverride: undefined,
|
||||
// comment:
|
||||
// 'Another secret override',
|
||||
// id: '',
|
||||
// tags: []
|
||||
// },
|
||||
// {
|
||||
// pos: 3,
|
||||
// key: 'DB_USERNAME',
|
||||
// value: 'user1234',
|
||||
// valueOverride: 'user1234',
|
||||
// comment: '',
|
||||
// id: '',
|
||||
// tags: []
|
||||
// },
|
||||
// {
|
||||
// pos: 4,
|
||||
// key: 'DB_PASSWORD',
|
||||
// value: 'example_password',
|
||||
// valueOverride: 'example_password',
|
||||
// comment: '',
|
||||
// id: '',
|
||||
// tags: []
|
||||
// },
|
||||
// {
|
||||
// pos: 5,
|
||||
// key: 'TWILIO_AUTH_TOKEN',
|
||||
// value: 'example_twillio_token',
|
||||
// valueOverride: undefined,
|
||||
// comment: '',
|
||||
// id: '',
|
||||
// tags: []
|
||||
// },
|
||||
// {
|
||||
// pos: 6,
|
||||
// key: 'WEBSITE_URL',
|
||||
// value: 'http://localhost:3000',
|
||||
// valueOverride: undefined,
|
||||
// comment: '',
|
||||
// id: '',
|
||||
// tags: []
|
||||
// }
|
||||
// ];
|
||||
// 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'))
|
||||
// });
|
||||
// }
|
||||
@@ -0,0 +1,88 @@
|
||||
/* eslint-disable prefer-destructuring */
|
||||
import jsrp from 'jsrp';
|
||||
|
||||
import login1 from '@app/pages/api/auth/Login1';
|
||||
import verifyMfaToken from '@app/pages/api/auth/verifyMfaToken';
|
||||
import KeyService from '@app/services/KeyService';
|
||||
|
||||
import { saveTokenToLocalStorage } from './saveTokenToLocalStorage';
|
||||
import SecurityClient from './SecurityClient';
|
||||
|
||||
// eslint-disable-next-line new-cap
|
||||
const client = new jsrp.client();
|
||||
|
||||
/**
|
||||
* Return whether or not MFA-login is successful for user with email [email]
|
||||
* and MFA token [mfaToken]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.email - email of user
|
||||
* @param {String} obj.mfaToken - MFA code/token
|
||||
*/
|
||||
const attemptLoginMfa = async ({
|
||||
email,
|
||||
password,
|
||||
mfaToken
|
||||
}: {
|
||||
email: string;
|
||||
password: string;
|
||||
mfaToken: string;
|
||||
}): Promise<Boolean> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
client.init({
|
||||
username: email,
|
||||
password
|
||||
}, async () => {
|
||||
try {
|
||||
const clientPublicKey = client.getPublicKey();
|
||||
const { salt } = await login1(email, clientPublicKey);
|
||||
|
||||
const {
|
||||
encryptionVersion,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
token,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag
|
||||
} = await verifyMfaToken({
|
||||
email,
|
||||
mfaToken
|
||||
});
|
||||
|
||||
// set JWT token
|
||||
SecurityClient.setToken(token);
|
||||
|
||||
const privateKey = await KeyService.decryptPrivateKey({
|
||||
encryptionVersion,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
password,
|
||||
salt,
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag
|
||||
});
|
||||
|
||||
saveTokenToLocalStorage({
|
||||
protectedKey,
|
||||
protectedKeyIV,
|
||||
protectedKeyTag,
|
||||
publicKey,
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
privateKey
|
||||
});
|
||||
|
||||
resolve(true);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default attemptLoginMfa;
|
||||
Reference in New Issue
Block a user