integrated exchangeAB and verifySRP APIs
This commit is contained in:
parent
3607139efb
commit
37c7b44648
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -118,6 +118,6 @@ export interface SetupSRPRequest {
|
|||
srpVerifier: string;
|
||||
}
|
||||
|
||||
export interface PostVerifySRPResponse {
|
||||
export interface ExchangeSRPABResponse {
|
||||
srpB: string;
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
};
|
||||
|
|
|
@ -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');
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue