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 { getSRPAttributes, sendOtt, verifySRP } from 'services/userService';
import {
clearFiles,
getSRPAttributes,
loginViaSRP,
sendOtt,
} from 'services/userService';
import { setData, LS_KEYS } from 'utils/storage/localStorage';
import { PAGES } from 'constants/pages';
import FormPaperTitle from './Form/FormPaper/Title';
import FormPaperFooter from './Form/FormPaper/Footer';
import LinkButton from './pages/gallery/LinkButton';
import { t } from 'i18next';
import { setUserSRPSetupPending } from 'utils/storage';
import { setIsFirstLogin, setUserSRPSetupPending } from 'utils/storage';
import { addLocalLog } from 'utils/logging';
import { Formik, FormikHelpers } from 'formik';
import * as Yup from 'yup';
import { TextField } from '@mui/material';
import { VerticallyCentered } from './Container';
import ShowHidePassword from './Form/ShowHidePassword';
import { useState } from 'react';
import { useContext, useState } from 'react';
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 {
signUp: () => void;
@ -26,6 +42,7 @@ interface FormValues {
}
export default function Login(props: LoginProps) {
const appContext = useContext(AppContext);
const router = useRouter();
const [loading, setLoading] = useState(false);
@ -57,11 +74,102 @@ export default function Login(props: LoginProps) {
setData(LS_KEYS.USER, { email });
router.push(PAGES.VERIFY);
} else {
verifySRP(srpAttributes.srpSalt, email, passphrase);
// TODO , make the srp login flow
const {
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) {
setFieldError('password', `${t('UNKNOWN_ERROR} ${e.message}')}`);
setFieldError('passphrase', `${t('UNKNOWN_ERROR} ${e.message}')}`);
}
setLoading(false);
};

View file

@ -7,7 +7,7 @@ import localForage from 'utils/storage/localForage';
import { getToken } from 'utils/common/key';
import HTTPService from './HTTPService';
import {
generateSRPA,
generateSRPClient,
generateSRPAttributes,
getRecoveryKey,
} from 'utils/crypto';
@ -25,7 +25,8 @@ import {
GetRemoteStoreValueResponse,
GetSRPAttributesResponse,
SetupSRPRequest,
PostVerifySRPResponse,
ExchangeSRPABResponse,
EmailVerificationResponse,
} from 'types/user';
import { ServerErrorCodes } from 'utils/error';
import isElectron from 'is-electron';
@ -37,6 +38,7 @@ import { AxiosResponse } from 'axios';
import { APPS, getAppName } from 'constants/apps';
import { addLocalLog } from 'utils/logging';
import { setUserSRPSetupPending } from 'utils/storage';
import { convertBase64ToBuffer, convertBufferToBase64 } from 'utils/user';
const ENDPOINT = getEndpoint();
@ -505,36 +507,64 @@ export const setupSRP = async (setupSRPRequest: SetupSRPRequest) => {
}
};
export const verifySRP = async (
export const loginViaSRP = async (
srpSalt: string,
email: string,
password: string
) => {
try {
addLocalLog(() => `starting srp verify for ${email}`);
const srpA = await generateSRPA(srpSalt, email, password);
addLocalLog(() => `srpA: ${srpA}`);
const { srpB } = await postSRPA(email, srpA);
const srpClient = await generateSRPClient(srpSalt, email, password);
addLocalLog(() => `srpClient: ${srpClient}`);
const srpA = srpClient.computeA();
const { srpB } = await exchangeAB(email, convertBufferToBase64(srpA));
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) {
logError(e, 'srp verify failed');
throw e;
}
};
export const postSRPA = async (email: string, srpA: string) => {
export const exchangeAB = async (email: string, srpA: string) => {
try {
const resp = await HTTPService.post(
`${ENDPOINT}/users/srp/verify`,
`${ENDPOINT}/users/srp/exchange-ab`,
{
email,
srpA,
},
null
);
return resp.data as PostVerifySRPResponse;
return resp.data as ExchangeSRPABResponse;
} 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;
}
};

View file

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

View file

@ -282,12 +282,12 @@ export const generateSRPAttributes = async (
};
};
export const generateSRPA = async (
export const generateSRPClient = async (
srpSalt: string,
email: string,
password: string
) => {
return new Promise<string>((resolve, reject) => {
return new Promise<SrpClient>((resolve, reject) => {
SRP.genKey(function (err, secret1) {
try {
if (err) {
@ -301,17 +301,11 @@ export const generateSRPA = async (
Buffer.from(password),
Buffer.from(secret1)
);
const srpA = srpClient.computeA();
//convert buffer To B64 srpA
resolve(convertBufferToB64(srpA));
resolve(srpClient);
} catch (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'
);
};
export const convertBufferToBase64 = (buffer: Buffer) => {
return buffer.toString('base64');
};
export const convertBase64ToBuffer = (base64: string) => {
return Buffer.from(base64, 'base64');
};