Checkpoint weaving frontend and backend MFA

This commit is contained in:
Tuan Dang
2023-02-13 12:29:36 +07:00
parent 17406e413d
commit e53fd110f6
30 changed files with 1177 additions and 482 deletions
+148
View File
@@ -0,0 +1,148 @@
import React, { useState } from 'react';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import { faWarning } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import Button from '@app/components/basic/buttons/Button';
import Error from '@app/components/basic/Error';
import InputField from '@app/components/basic/InputField';
import attemptLogin from '@app/components/utilities/attemptLogin';
import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps';
/**
* 1st step of login - user enters their username and password
* @param {Object} obj
* @param {String} obj.email - email of user
* @param {Function} obj.setEmail - function to set the email of user
* @param {String} obj.password - password of user
* @param {String} obj.setPassword - function to set the password of user
* @param {Function} obj.setStep - function to set the login flow step
* @returns
*/
export default function LoginStep ({
email,
setEmail,
password,
setPassword,
setStep
}: {
email: string;
setEmail: (email: string) => void;
password: string;
setPassword: (password: string) => void;
setStep: (step: number) => void;
}) {
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const [loginError, setLoginError] = useState(false);
const { t } = useTranslation();
const handleLogin = async () => {
try {
if (!email || !password) {
return;
}
setIsLoading(true);
const isLoginSuccessful = await attemptLogin(email, password);
if (isLoginSuccessful && isLoginSuccessful.success) {
// case: login was successful
if (isLoginSuccessful.mfaEnabled) {
// case: login requires MFA step
setStep(2);
setIsLoading(false);
return;
}
// case: login does not require MFA step
router.push(`/dashboard/${localStorage.getItem('projectData.id')}`);
}
} catch (err) {
console.error(err);
setLoginError(true);
}
setIsLoading(false);
}
return (
<form onSubmit={(e) => e.preventDefault()}>
<div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-4 pt-8 px-6 rounded-xl drop-shadow-xl">
<p className="text-3xl w-max mx-auto flex justify-center font-semibold text-bunker-100 mb-6">
{t('login:login')}
</p>
<div className="flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28">
<InputField
label={t('common:email')}
onChangeHandler={setEmail}
type="email"
value={email}
placeholder=""
isRequired
autoComplete="username"
/>
</div>
<div className="relative flex items-center justify-center w-full md:p-2 rounded-lg md:mt-2 mt-6 max-h-24 md:max-h-28">
<InputField
label={t('common:password')}
onChangeHandler={setPassword}
type="password"
value={password}
placeholder=""
isRequired
autoComplete="current-password"
id="current-password"
/>
<div className="absolute top-2 right-3 text-primary-700 hover:text-primary duration-200 cursor-pointer text-sm">
<Link href="/verify-email">
<button
type="button"
className="text-primary-700 hover:text-primary duration-200 font-normal text-sm underline-offset-4 ml-1.5"
>
{t('login:forgot-password')}
</button>
</Link>
</div>
</div>
{!isLoading && loginError && <Error text={t('login:error-login') ?? ''} />}
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
<div className="text-l mt-6 m-8 px-8 py-3 text-lg">
<Button
type="submit"
text={t('login:login') ?? ''}
onButtonPressed={async () => handleLogin()}
loading={isLoading}
size="lg"
/>
</div>
</div>
</div>
{false && (
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-300 rounded-md max-w-md mx-auto mt-4">
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-6 text-6xl" />
{t('common:maintenance-alert')}
</div>
)}
<div className="flex flex-row items-center justify-center md:pb-4 mt-4">
<p className="text-sm flex justify-center text-gray-400 w-max">
{t('login:need-account')}
</p>
<Link href="/signup">
<button
type="button"
className="text-primary-700 hover:text-primary duration-200 font-normal text-sm underline-offset-4 ml-1.5"
>
{t('login:create-account')}
</button>
</Link>
</div>
</form>
);
}
export const getStaticProps = getTranslatedStaticProps(['auth', 'login']);
@@ -1,9 +1,11 @@
/* eslint-disable react/jsx-props-no-spreading */
import React, { useState } from 'react';
import ReactCodeInput from 'react-code-input';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import sendVerificationEmail from '@app/pages/api/auth/SendVerificationEmail';
import attemptLoginMfa from '@app/components/utilities/attemptLoginMfa';
import resendMfaToken from '@app/pages/api/auth/resendMfaToken';
import Button from '../basic/buttons/Button';
import Error from '../basic/Error';
@@ -27,63 +29,65 @@ const props = {
borderColor: '#2d2f33'
}
} as const;
const propsPhone = {
inputStyle: {
fontFamily: 'monospace',
margin: '4px',
MozAppearance: 'textfield',
width: '40px',
borderRadius: '5px',
fontSize: '24px',
height: '40px',
paddingLeft: '7',
backgroundColor: '#0d1117',
color: 'white',
border: '1px solid #2d2f33',
textAlign: 'center',
outlineColor: '#8ca542',
borderColor: '#2d2f33'
}
} as const;
interface CodeInputStepProps {
email: string;
incrementStep: () => void;
setCode: (value: string) => void;
codeError: boolean;
}
/**
* This is the second step of sign up where users need to verify their email
* @param {object} obj
* @param {string} obj.email - user's email to which we just sent a verification email
* @param {function} obj.incrementStep - goes to the next step of signup
* @param {function} obj.setCode - state updating function that set the current value of the emai verification code
* @param {boolean} obj.codeError - whether the code was inputted wrong or now
* 2nd step of login - users enter their MFA code
* @param {Object} obj
* @param {String} obj.email - email of user
* @param {String} obj.password - password of user
* @param {Function} obj.setStep - function to set the login flow step
* @returns
*/
export default function TwoFAStep({
export default function MFAStep({
email,
incrementStep,
setCode,
codeError
}: CodeInputStepProps): JSX.Element {
password
}: {
email: string;
password: string;
}): JSX.Element {
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const [isResendingVerificationEmail, setIsResendingVerificationEmail] = useState(false);
const [mfaCode, setMfaCode] = useState('');
const [codeError, setCodeError] = useState(false);
const { t } = useTranslation();
const resendVerificationEmail = async () => {
setIsResendingVerificationEmail(true);
setIsLoading(true);
sendVerificationEmail(email);
setTimeout(() => {
setIsLoading(false);
setIsResendingVerificationEmail(false);
}, 2000);
};
const handleLoginMfa = async () => {
try {
if (mfaCode.length !== 6) {
return;
}
setIsLoading(true);
const isLoginSuccessful = await attemptLoginMfa({
email,
password,
mfaToken: mfaCode
});
if (isLoginSuccessful) {
setIsLoading(false);
router.push(`/dashboard/${localStorage.getItem('projectData.id')}`);
}
} catch (err) {
console.error(err);
setCodeError(true);
}
}
const handleResendMfaCode = async () => {
try {
await resendMfaToken({
email
});
} catch (err) {
console.error(err);
}
}
return (
<div className="bg-bunker w-max mx-auto h-7/12 pt-10 pb-4 px-8 rounded-xl drop-shadow-xl mb-64 md:mb-16">
<form className="bg-bunker w-max mx-auto h-7/12 pt-10 pb-4 px-8 rounded-xl drop-shadow-xl mb-64 md:mb-16">
<p className="text-l flex justify-center text-bunker-300">{t('signup:step2-message')}</p>
<p className="text-l flex justify-center font-semibold my-2 text-bunker-300">{email} </p>
<div className="hidden md:block">
@@ -92,38 +96,31 @@ export default function TwoFAStep({
inputMode="tel"
type="text"
fields={6}
onChange={setCode}
onChange={setMfaCode}
{...props}
className="mt-6 mb-2"
/>
</div>
<div className="block md:hidden">
<ReactCodeInput
name=""
inputMode="tel"
type="text"
fields={6}
onChange={setCode}
{...propsPhone}
className="mt-2 mb-6"
/>
</div>
{codeError && <Error text={t('signup:step2-code-error')} />}
<div className="flex max-w-max min-w-28 flex-col items-center justify-center md:p-2 max-h-24 mx-auto text-lg px-4 mt-4 mb-2">
<Button text={t('signup:verify') ?? ''} onButtonPressed={incrementStep} size="lg" />
<Button
text={t('signup:verify') ?? ''}
onButtonPressed={() => handleLoginMfa()}
size="lg"
/>
</div>
<div className="flex flex-col items-center justify-center w-full max-h-24 max-w-md mx-auto pt-2">
<div className="flex flex-row items-baseline gap-1 text-sm">
<span className="text-bunker-400">{t('signup:step2-resend-alert')}</span>
<u
className={`font-normal ${
isResendingVerificationEmail
isLoading
? 'text-bunker-400'
: 'text-primary-700 hover:text-primary duration-200'
}`}
>
<button disabled={isLoading} onClick={resendVerificationEmail} type="button">
{isResendingVerificationEmail
<button disabled={isLoading} onClick={() => handleResendMfaCode()} type="button">
{isLoading
? t('signup:step2-resend-progress')
: t('signup:step2-resend-submit')}
</button>
@@ -131,6 +128,6 @@ export default function TwoFAStep({
</div>
<p className="text-sm text-bunker-400 pb-2">{t('signup:step2-spam-alert')}</p>
</div>
</div>
</form>
);
}
+23 -22
View File
@@ -1,7 +1,6 @@
import crypto from 'crypto';
import React, { useState } from 'react';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import { faCheck, faX } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -10,20 +9,21 @@ import nacl from 'tweetnacl';
import { encodeBase64 } from 'tweetnacl-util';
import completeAccountInformationSignup from '@app/pages/api/auth/CompleteAccountInformationSignup';
import getOrganizations from '@app/pages/api/organization/getOrgs';
import ProjectService from '@app/services/ProjectService';
import Button from '../basic/buttons/Button';
import InputField from '../basic/InputField';
import attemptLogin from '../utilities/attemptLogin';
import passwordCheck from '../utilities/checks/PasswordCheck';
import Aes256Gcm from '../utilities/cryptography/aes-256-gcm';
import { deriveArgonKey } from '../utilities/cryptography/crypto';
import { saveTokenToLocalStorage } from '../utilities/saveTokenToLocalStorage';
import SecurityClient from '../utilities/SecurityClient';
// eslint-disable-next-line new-cap
const client = new jsrp.client();
interface UserInfoStepProps {
verificationToken: string;
incrementStep: () => void;
email: string;
password: string;
@@ -48,7 +48,6 @@ interface UserInfoStepProps {
* @param {string} obj.setLastName - function managing the state of user's last name
*/
export default function UserInfoStep({
verificationToken,
incrementStep,
email,
password,
@@ -66,7 +65,6 @@ export default function UserInfoStep({
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
const router = useRouter();
// Verifies if the information that the users entered (name, workspace)
// is there, and if the password matches the criteria.
@@ -110,6 +108,8 @@ export default function UserInfoStep({
async () => {
client.createVerifier(async (err: any, result: { salt: string; verifier: string }) => {
try {
// TODO: moduralize into KeyService
const derivedKey = await deriveArgonKey({
password,
salt: result.salt,
@@ -158,28 +158,29 @@ export default function UserInfoStep({
encryptedPrivateKeyTag,
salt: result.salt,
verifier: result.verifier,
token: verificationToken,
organizationName: `${firstName}'s organization`
});
// if everything works, go the main dashboard page.
if (response.status === 200) {
// response = await response.json();
SecurityClient.setToken(response.token);
saveTokenToLocalStorage({
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag,
privateKey
});
saveTokenToLocalStorage({
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag,
privateKey
});
await attemptLogin(email, password, () => {}, router, true, false);
incrementStep();
}
incrementStep();
const userOrgs = await getOrganizations();
await ProjectService.initProject({
organizationId: userOrgs[0]?._id,
projectName: 'Example Project'
});
} catch (error) {
setIsLoading(false);
+190 -210
View File
@@ -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;