integrated exchangeAB and verifySRP APIs

This commit is contained in:
Abhinav 2023-06-07 20:17:18 +05:30
parent 3607139efb
commit 37c7b44648
5 changed files with 166 additions and 26 deletions

View file

@ -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);
}; };

View file

@ -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;
} }
}; };

View file

@ -118,6 +118,6 @@ export interface SetupSRPRequest {
srpVerifier: string; srpVerifier: string;
} }
export interface PostVerifySRPResponse { export interface ExchangeSRPABResponse {
srpB: string; srpB: string;
} }

View file

@ -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');
};

View file

@ -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');
};