integrated exchangeAB and verifySRP APIs
This commit is contained in:
parent
3607139efb
commit
37c7b44648
|
@ -1,20 +1,36 @@
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { getSRPAttributes, sendOtt, verifySRP } from 'services/userService';
|
import {
|
||||||
|
clearFiles,
|
||||||
|
getSRPAttributes,
|
||||||
|
loginViaSRP,
|
||||||
|
sendOtt,
|
||||||
|
} from 'services/userService';
|
||||||
import { setData, LS_KEYS } from 'utils/storage/localStorage';
|
import { setData, LS_KEYS } from 'utils/storage/localStorage';
|
||||||
import { PAGES } from 'constants/pages';
|
import { PAGES } from 'constants/pages';
|
||||||
import FormPaperTitle from './Form/FormPaper/Title';
|
import FormPaperTitle from './Form/FormPaper/Title';
|
||||||
import FormPaperFooter from './Form/FormPaper/Footer';
|
import FormPaperFooter from './Form/FormPaper/Footer';
|
||||||
import LinkButton from './pages/gallery/LinkButton';
|
import LinkButton from './pages/gallery/LinkButton';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { setUserSRPSetupPending } from 'utils/storage';
|
import { setIsFirstLogin, setUserSRPSetupPending } from 'utils/storage';
|
||||||
import { addLocalLog } from 'utils/logging';
|
import { addLocalLog } from 'utils/logging';
|
||||||
import { Formik, FormikHelpers } from 'formik';
|
import { Formik, FormikHelpers } from 'formik';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { TextField } from '@mui/material';
|
import { TextField } from '@mui/material';
|
||||||
import { VerticallyCentered } from './Container';
|
import { VerticallyCentered } from './Container';
|
||||||
import ShowHidePassword from './Form/ShowHidePassword';
|
import ShowHidePassword from './Form/ShowHidePassword';
|
||||||
import { useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
import SubmitButton from './SubmitButton';
|
import SubmitButton from './SubmitButton';
|
||||||
|
import { SESSION_KEYS, clearKeys } from 'utils/storage/sessionStorage';
|
||||||
|
import { getAppName, APPS } from 'constants/apps';
|
||||||
|
import {
|
||||||
|
generateAndSaveIntermediateKeyAttributes,
|
||||||
|
saveKeyInSessionStore,
|
||||||
|
decryptAndStoreToken,
|
||||||
|
} from 'utils/crypto';
|
||||||
|
import ComlinkCryptoWorker from 'utils/comlink/ComlinkCryptoWorker';
|
||||||
|
import { CustomError } from 'utils/error';
|
||||||
|
import { logError } from 'utils/sentry';
|
||||||
|
import { AppContext } from 'pages/_app';
|
||||||
|
|
||||||
interface LoginProps {
|
interface LoginProps {
|
||||||
signUp: () => void;
|
signUp: () => void;
|
||||||
|
@ -26,6 +42,7 @@ interface FormValues {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Login(props: LoginProps) {
|
export default function Login(props: LoginProps) {
|
||||||
|
const appContext = useContext(AppContext);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
@ -57,11 +74,102 @@ export default function Login(props: LoginProps) {
|
||||||
setData(LS_KEYS.USER, { email });
|
setData(LS_KEYS.USER, { email });
|
||||||
router.push(PAGES.VERIFY);
|
router.push(PAGES.VERIFY);
|
||||||
} else {
|
} else {
|
||||||
verifySRP(srpAttributes.srpSalt, email, passphrase);
|
const {
|
||||||
// TODO , make the srp login flow
|
keyAttributes,
|
||||||
|
encryptedToken,
|
||||||
|
token,
|
||||||
|
id,
|
||||||
|
twoFactorSessionID,
|
||||||
|
} = await loginViaSRP(srpAttributes.srpSalt, email, passphrase);
|
||||||
|
if (twoFactorSessionID) {
|
||||||
|
setData(LS_KEYS.USER, {
|
||||||
|
email,
|
||||||
|
twoFactorSessionID,
|
||||||
|
isTwoFactorEnabled: true,
|
||||||
|
});
|
||||||
|
setIsFirstLogin(true);
|
||||||
|
router.push(PAGES.TWO_FACTOR_VERIFY);
|
||||||
|
} else {
|
||||||
|
setData(LS_KEYS.USER, {
|
||||||
|
email,
|
||||||
|
token,
|
||||||
|
encryptedToken,
|
||||||
|
id,
|
||||||
|
isTwoFactorEnabled: false,
|
||||||
|
});
|
||||||
|
if (keyAttributes) {
|
||||||
|
try {
|
||||||
|
const cryptoWorker =
|
||||||
|
await ComlinkCryptoWorker.getInstance();
|
||||||
|
let kek: string = null;
|
||||||
|
try {
|
||||||
|
kek = await cryptoWorker.deriveKey(
|
||||||
|
passphrase,
|
||||||
|
keyAttributes.kekSalt,
|
||||||
|
keyAttributes.opsLimit,
|
||||||
|
keyAttributes.memLimit
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
logError(e, 'failed to derive key');
|
||||||
|
throw Error(CustomError.WEAK_DEVICE);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const key = await cryptoWorker.decryptB64(
|
||||||
|
keyAttributes.encryptedKey,
|
||||||
|
keyAttributes.keyDecryptionNonce,
|
||||||
|
kek
|
||||||
|
);
|
||||||
|
clearFiles();
|
||||||
|
clearKeys();
|
||||||
|
await generateAndSaveIntermediateKeyAttributes(
|
||||||
|
passphrase,
|
||||||
|
keyAttributes,
|
||||||
|
key
|
||||||
|
);
|
||||||
|
|
||||||
|
await saveKeyInSessionStore(
|
||||||
|
SESSION_KEYS.ENCRYPTION_KEY,
|
||||||
|
key
|
||||||
|
);
|
||||||
|
await decryptAndStoreToken(key);
|
||||||
|
const redirectURL = appContext.redirectURL;
|
||||||
|
appContext.setRedirectURL(null);
|
||||||
|
const appName = getAppName();
|
||||||
|
if (appName === APPS.AUTH) {
|
||||||
|
router.push(PAGES.AUTH);
|
||||||
|
} else {
|
||||||
|
router.push(redirectURL ?? PAGES.GALLERY);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logError(e, 'user entered a wrong password');
|
||||||
|
throw Error(CustomError.INCORRECT_PASSWORD);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
switch (e.message) {
|
||||||
|
case CustomError.WEAK_DEVICE:
|
||||||
|
setFieldError(
|
||||||
|
'passphrase',
|
||||||
|
t('WEAK_DEVICE')
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case CustomError.INCORRECT_PASSWORD:
|
||||||
|
setFieldError(
|
||||||
|
'passphrase',
|
||||||
|
t('INCORRECT_PASSPHRASE')
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setFieldError(
|
||||||
|
'passphrase',
|
||||||
|
`${t('UNKNOWN_ERROR')} ${e.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setFieldError('password', `${t('UNKNOWN_ERROR} ${e.message}')}`);
|
setFieldError('passphrase', `${t('UNKNOWN_ERROR} ${e.message}')}`);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,7 @@ import localForage from 'utils/storage/localForage';
|
||||||
import { getToken } from 'utils/common/key';
|
import { getToken } from 'utils/common/key';
|
||||||
import HTTPService from './HTTPService';
|
import HTTPService from './HTTPService';
|
||||||
import {
|
import {
|
||||||
generateSRPA,
|
generateSRPClient,
|
||||||
generateSRPAttributes,
|
generateSRPAttributes,
|
||||||
getRecoveryKey,
|
getRecoveryKey,
|
||||||
} from 'utils/crypto';
|
} from 'utils/crypto';
|
||||||
|
@ -25,7 +25,8 @@ import {
|
||||||
GetRemoteStoreValueResponse,
|
GetRemoteStoreValueResponse,
|
||||||
GetSRPAttributesResponse,
|
GetSRPAttributesResponse,
|
||||||
SetupSRPRequest,
|
SetupSRPRequest,
|
||||||
PostVerifySRPResponse,
|
ExchangeSRPABResponse,
|
||||||
|
EmailVerificationResponse,
|
||||||
} from 'types/user';
|
} from 'types/user';
|
||||||
import { ServerErrorCodes } from 'utils/error';
|
import { ServerErrorCodes } from 'utils/error';
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
|
@ -37,6 +38,7 @@ import { AxiosResponse } from 'axios';
|
||||||
import { APPS, getAppName } from 'constants/apps';
|
import { APPS, getAppName } from 'constants/apps';
|
||||||
import { addLocalLog } from 'utils/logging';
|
import { addLocalLog } from 'utils/logging';
|
||||||
import { setUserSRPSetupPending } from 'utils/storage';
|
import { setUserSRPSetupPending } from 'utils/storage';
|
||||||
|
import { convertBase64ToBuffer, convertBufferToBase64 } from 'utils/user';
|
||||||
|
|
||||||
const ENDPOINT = getEndpoint();
|
const ENDPOINT = getEndpoint();
|
||||||
|
|
||||||
|
@ -505,36 +507,64 @@ export const setupSRP = async (setupSRPRequest: SetupSRPRequest) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const verifySRP = async (
|
export const loginViaSRP = async (
|
||||||
srpSalt: string,
|
srpSalt: string,
|
||||||
email: string,
|
email: string,
|
||||||
password: string
|
password: string
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
addLocalLog(() => `starting srp verify for ${email}`);
|
addLocalLog(() => `starting srp verify for ${email}`);
|
||||||
const srpA = await generateSRPA(srpSalt, email, password);
|
const srpClient = await generateSRPClient(srpSalt, email, password);
|
||||||
addLocalLog(() => `srpA: ${srpA}`);
|
addLocalLog(() => `srpClient: ${srpClient}`);
|
||||||
const { srpB } = await postSRPA(email, srpA);
|
|
||||||
|
const srpA = srpClient.computeA();
|
||||||
|
const { srpB } = await exchangeAB(email, convertBufferToBase64(srpA));
|
||||||
addLocalLog(() => `srp verify successful, srpB: ${srpB}`);
|
addLocalLog(() => `srp verify successful, srpB: ${srpB}`);
|
||||||
|
srpClient.setB(convertBase64ToBuffer(srpB));
|
||||||
|
|
||||||
|
const k = srpClient.computeK();
|
||||||
|
addLocalLog(() => `srp k: ${convertBufferToBase64(k)}`);
|
||||||
|
const m1 = srpClient.computeM1();
|
||||||
|
addLocalLog(() => `srp m1: ${convertBufferToBase64(m1)}`);
|
||||||
|
const x = await verifySRP(email, convertBufferToBase64(m1));
|
||||||
|
addLocalLog(() => `srp verify successful, x: ${x}`);
|
||||||
|
return x;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError(e, 'srp verify failed');
|
logError(e, 'srp verify failed');
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const postSRPA = async (email: string, srpA: string) => {
|
export const exchangeAB = async (email: string, srpA: string) => {
|
||||||
try {
|
try {
|
||||||
const resp = await HTTPService.post(
|
const resp = await HTTPService.post(
|
||||||
`${ENDPOINT}/users/srp/verify`,
|
`${ENDPOINT}/users/srp/exchange-ab`,
|
||||||
{
|
{
|
||||||
email,
|
email,
|
||||||
srpA,
|
srpA,
|
||||||
},
|
},
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
return resp.data as PostVerifySRPResponse;
|
return resp.data as ExchangeSRPABResponse;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError(e, 'failed to post SRP A');
|
logError(e, 'exchangeAB failed');
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const verifySRP = async (email: string, srpM1: string) => {
|
||||||
|
try {
|
||||||
|
const resp = await HTTPService.post(
|
||||||
|
`${ENDPOINT}/users/srp/verify`,
|
||||||
|
{
|
||||||
|
email,
|
||||||
|
srpM1,
|
||||||
|
},
|
||||||
|
null
|
||||||
|
);
|
||||||
|
return resp.data as EmailVerificationResponse;
|
||||||
|
} catch (e) {
|
||||||
|
logError(e, 'failed to verify SRP');
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -118,6 +118,6 @@ export interface SetupSRPRequest {
|
||||||
srpVerifier: string;
|
srpVerifier: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PostVerifySRPResponse {
|
export interface ExchangeSRPABResponse {
|
||||||
srpB: string;
|
srpB: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -282,12 +282,12 @@ export const generateSRPAttributes = async (
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generateSRPA = async (
|
export const generateSRPClient = async (
|
||||||
srpSalt: string,
|
srpSalt: string,
|
||||||
email: string,
|
email: string,
|
||||||
password: string
|
password: string
|
||||||
) => {
|
) => {
|
||||||
return new Promise<string>((resolve, reject) => {
|
return new Promise<SrpClient>((resolve, reject) => {
|
||||||
SRP.genKey(function (err, secret1) {
|
SRP.genKey(function (err, secret1) {
|
||||||
try {
|
try {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -301,17 +301,11 @@ export const generateSRPA = async (
|
||||||
Buffer.from(password),
|
Buffer.from(password),
|
||||||
Buffer.from(secret1)
|
Buffer.from(secret1)
|
||||||
);
|
);
|
||||||
const srpA = srpClient.computeA();
|
|
||||||
//convert buffer To B64 srpA
|
|
||||||
|
|
||||||
resolve(convertBufferToB64(srpA));
|
resolve(srpClient);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const convertBufferToB64 = (buffer: Buffer) => {
|
|
||||||
return buffer.toString('base64');
|
|
||||||
};
|
|
||||||
|
|
|
@ -41,3 +41,11 @@ export const isInternalUser = () => {
|
||||||
userEmail.endsWith('@ente.io') || userEmail === 'kr.anand619@gmail.com'
|
userEmail.endsWith('@ente.io') || userEmail === 'kr.anand619@gmail.com'
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const convertBufferToBase64 = (buffer: Buffer) => {
|
||||||
|
return buffer.toString('base64');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const convertBase64ToBuffer = (base64: string) => {
|
||||||
|
return Buffer.from(base64, 'base64');
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue