Merge pull request #73 from ente-io/signup-flow-update
Signup flow update
This commit is contained in:
commit
67aa33cac3
|
@ -1,13 +1,11 @@
|
|||
import React, { useState, useEffect, useContext } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import Container from 'components/Container';
|
||||
import styled from 'styled-components';
|
||||
import Card from 'react-bootstrap/Card';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { Formik, FormikHelpers } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import { Spinner } from 'react-bootstrap';
|
||||
import SubmitButton from './SubmitButton';
|
||||
|
||||
interface Props {
|
||||
|
|
|
@ -382,7 +382,7 @@ export default function App({ Component, pageProps, err }) {
|
|||
</EnteSpinner>
|
||||
</Container>
|
||||
) : (
|
||||
<Component err={err} />
|
||||
<Component err={err} setLoading={setLoading} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
|
|||
import styled from 'styled-components';
|
||||
|
||||
import constants from 'utils/strings/constants';
|
||||
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
|
||||
import { useRouter } from 'next/router';
|
||||
import { KeyAttributes } from 'types';
|
||||
import { SESSION_KEYS, getKey } from 'utils/storage/sessionStorage';
|
||||
|
@ -53,7 +53,7 @@ export default function Credentials() {
|
|||
kek
|
||||
);
|
||||
if (isFirstLogin()) {
|
||||
generateAndSaveIntermediateKeyAttributes(
|
||||
await generateAndSaveIntermediateKeyAttributes(
|
||||
passphrase,
|
||||
keyAttributes,
|
||||
key
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { logoutUser, putAttributes } from 'services/userService';
|
||||
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
|
||||
import { useRouter } from 'next/router';
|
||||
import { getKey, SESSION_KEYS } from 'utils/storage/sessionStorage';
|
||||
import { B64EncryptionResult } from 'services/uploadService';
|
||||
import CryptoWorker, {
|
||||
import {
|
||||
setSessionKeys,
|
||||
generateAndSaveIntermediateKeyAttributes,
|
||||
generateKeyAttributes,
|
||||
} from 'utils/crypto';
|
||||
import SetPasswordForm from 'components/SetPasswordForm';
|
||||
import { KeyAttributes } from 'types';
|
||||
import { setJustSignedUp } from 'utils/storage';
|
||||
import RecoveryKeyModal from 'components/RecoveryKeyModal';
|
||||
import MessageDialog, { MessageAttributes } from 'components/MessageDialog';
|
||||
import { KeyAttributes } from 'types';
|
||||
|
||||
export interface KEK {
|
||||
key: string;
|
||||
|
@ -21,83 +20,71 @@ export interface KEK {
|
|||
memLimit: number;
|
||||
}
|
||||
|
||||
export default function Generate() {
|
||||
export default function Generate(props) {
|
||||
const [token, setToken] = useState<string>();
|
||||
const router = useRouter();
|
||||
const [dialogMessage, setDialogMessage] = useState<MessageAttributes>(null);
|
||||
const [recoverModalView, setRecoveryModalView] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
props.setLoading(true);
|
||||
const key = getKey(SESSION_KEYS.ENCRYPTION_KEY);
|
||||
const keyAttributes: KeyAttributes = getData(
|
||||
LS_KEYS.ORIGINAL_KEY_ATTRIBUTES
|
||||
);
|
||||
router.prefetch('/gallery');
|
||||
const user = getData(LS_KEYS.USER);
|
||||
if (!user?.token) {
|
||||
router.push('/');
|
||||
return;
|
||||
}
|
||||
setToken(user.token);
|
||||
if (keyAttributes?.encryptedKey) {
|
||||
const main = async () => {
|
||||
try {
|
||||
await putAttributes(user.token, keyAttributes);
|
||||
} catch (e) {
|
||||
//ignore
|
||||
}
|
||||
setData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES, null);
|
||||
setRecoveryModalView(true);
|
||||
};
|
||||
main();
|
||||
} else if (key) {
|
||||
router.push('/gallery');
|
||||
} else {
|
||||
setToken(user.token);
|
||||
}
|
||||
props.setLoading(false);
|
||||
}, []);
|
||||
|
||||
const onSubmit = async (passphrase, setFieldError) => {
|
||||
const cryptoWorker = await new CryptoWorker();
|
||||
const masterKey: string = await cryptoWorker.generateEncryptionKey();
|
||||
const recoveryKey: string = await cryptoWorker.generateEncryptionKey();
|
||||
const kekSalt: string = await cryptoWorker.generateSaltToDeriveKey();
|
||||
let kek: KEK;
|
||||
try {
|
||||
kek = await cryptoWorker.deriveSensitiveKey(passphrase, kekSalt);
|
||||
const { keyAttributes, masterKey } = await generateKeyAttributes(
|
||||
passphrase
|
||||
);
|
||||
|
||||
await putAttributes(token, keyAttributes);
|
||||
await generateAndSaveIntermediateKeyAttributes(
|
||||
passphrase,
|
||||
keyAttributes,
|
||||
masterKey
|
||||
);
|
||||
await setSessionKeys(masterKey);
|
||||
setJustSignedUp(true);
|
||||
setRecoveryModalView(true);
|
||||
} catch (e) {
|
||||
setFieldError('confirm', constants.PASSWORD_GENERATION_FAILED);
|
||||
return;
|
||||
console.error(e);
|
||||
setFieldError('passphrase', constants.PASSWORD_GENERATION_FAILED);
|
||||
}
|
||||
const masterKeyEncryptedWithKek: B64EncryptionResult =
|
||||
await cryptoWorker.encryptToB64(masterKey, kek.key);
|
||||
const masterKeyEncryptedWithRecoveryKey: B64EncryptionResult =
|
||||
await cryptoWorker.encryptToB64(masterKey, recoveryKey);
|
||||
const recoveryKeyEncryptedWithMasterKey: B64EncryptionResult =
|
||||
await cryptoWorker.encryptToB64(recoveryKey, masterKey);
|
||||
|
||||
const keyPair = await cryptoWorker.generateKeyPair();
|
||||
const encryptedKeyPairAttributes: B64EncryptionResult =
|
||||
await cryptoWorker.encryptToB64(keyPair.privateKey, masterKey);
|
||||
|
||||
const keyAttributes: KeyAttributes = {
|
||||
kekSalt,
|
||||
encryptedKey: masterKeyEncryptedWithKek.encryptedData,
|
||||
keyDecryptionNonce: masterKeyEncryptedWithKek.nonce,
|
||||
publicKey: keyPair.publicKey,
|
||||
encryptedSecretKey: encryptedKeyPairAttributes.encryptedData,
|
||||
secretKeyDecryptionNonce: encryptedKeyPairAttributes.nonce,
|
||||
opsLimit: kek.opsLimit,
|
||||
memLimit: kek.memLimit,
|
||||
masterKeyEncryptedWithRecoveryKey:
|
||||
masterKeyEncryptedWithRecoveryKey.encryptedData,
|
||||
masterKeyDecryptionNonce: masterKeyEncryptedWithRecoveryKey.nonce,
|
||||
recoveryKeyEncryptedWithMasterKey:
|
||||
recoveryKeyEncryptedWithMasterKey.encryptedData,
|
||||
recoveryKeyDecryptionNonce: recoveryKeyEncryptedWithMasterKey.nonce,
|
||||
};
|
||||
await putAttributes(token, getData(LS_KEYS.USER).name, keyAttributes);
|
||||
await generateAndSaveIntermediateKeyAttributes(
|
||||
passphrase,
|
||||
keyAttributes,
|
||||
masterKey
|
||||
);
|
||||
|
||||
await setSessionKeys(masterKey);
|
||||
setJustSignedUp(true);
|
||||
setRecoveryModalView(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SetPasswordForm
|
||||
callback={onSubmit}
|
||||
buttonText={constants.SET_PASSPHRASE}
|
||||
back={logoutUser}
|
||||
/>
|
||||
{!recoverModalView && (
|
||||
<SetPasswordForm
|
||||
callback={onSubmit}
|
||||
buttonText={constants.SET_PASSPHRASE}
|
||||
back={logoutUser}
|
||||
/>
|
||||
)}
|
||||
<RecoveryKeyModal
|
||||
show={recoverModalView}
|
||||
onHide={() => {
|
||||
|
|
|
@ -97,13 +97,9 @@ export default function Home() {
|
|||
)}
|
||||
</Formik>
|
||||
<br />
|
||||
<Card.Link
|
||||
href="#"
|
||||
onClick={register}
|
||||
style={{ fontSize: '14px' }}
|
||||
>
|
||||
<Button variant="link" onClick={register}>
|
||||
{constants.NO_ACCOUNT}
|
||||
</Card.Link>
|
||||
</Button>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Container>
|
||||
|
|
|
@ -3,22 +3,28 @@ import { useRouter } from 'next/router';
|
|||
import Card from 'react-bootstrap/Card';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
import FormControl from 'react-bootstrap/FormControl';
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { Formik, FormikHelpers } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import { getOtt } from 'services/userService';
|
||||
import Container from 'components/Container';
|
||||
import { setData, LS_KEYS, getData } from 'utils/storage/localStorage';
|
||||
import { DisclaimerContainer } from 'components/Container';
|
||||
import SubmitButton from 'components/SubmitButton';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import {
|
||||
generateAndSaveIntermediateKeyAttributes,
|
||||
generateKeyAttributes,
|
||||
setSessionKeys,
|
||||
} from 'utils/crypto';
|
||||
import { setJustSignedUp } from 'utils/storage';
|
||||
|
||||
interface FormValues {
|
||||
name: string;
|
||||
email: string;
|
||||
passphrase: string;
|
||||
confirm: string;
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
export default function SignUp() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
|
@ -31,34 +37,61 @@ export default function Home() {
|
|||
}, []);
|
||||
|
||||
const registerUser = async (
|
||||
{ name, email }: FormValues,
|
||||
{ email, passphrase, confirm }: FormValues,
|
||||
{ setFieldError }: FormikHelpers<FormValues>
|
||||
) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
setLoading(true);
|
||||
setData(LS_KEYS.USER, { name, email });
|
||||
setData(LS_KEYS.USER, { email });
|
||||
await getOtt(email);
|
||||
router.push('/verify');
|
||||
} catch (e) {
|
||||
setFieldError('email', `${constants.UNKNOWN_ERROR} ${e.message}`);
|
||||
}
|
||||
try {
|
||||
if (passphrase === confirm) {
|
||||
const { keyAttributes, masterKey } =
|
||||
await generateKeyAttributes(passphrase);
|
||||
setData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES, keyAttributes);
|
||||
await generateAndSaveIntermediateKeyAttributes(
|
||||
passphrase,
|
||||
keyAttributes,
|
||||
masterKey
|
||||
);
|
||||
|
||||
await setSessionKeys(masterKey);
|
||||
setJustSignedUp(true);
|
||||
router.push('/verify');
|
||||
} else {
|
||||
setFieldError('confirm', constants.PASSPHRASE_MATCH_ERROR);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setFieldError('passphrase', constants.PASSWORD_GENERATION_FAILED);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Card style={{ minWidth: '300px' }} className="text-center">
|
||||
<Card.Body>
|
||||
<Card style={{ minWidth: '400px' }} className="text-center">
|
||||
<Card.Body style={{ padding: '40px 30px' }}>
|
||||
<Card.Title style={{ marginBottom: '20px' }}>
|
||||
{constants.SIGN_UP}
|
||||
</Card.Title>
|
||||
<Formik<FormValues>
|
||||
initialValues={{ name: '', email: '' }}
|
||||
initialValues={{
|
||||
email: '',
|
||||
passphrase: '',
|
||||
confirm: '',
|
||||
}}
|
||||
validationSchema={Yup.object().shape({
|
||||
name: Yup.string().required(constants.REQUIRED),
|
||||
email: Yup.string()
|
||||
.email(constants.EMAIL_ERROR)
|
||||
.required(constants.REQUIRED),
|
||||
passphrase: Yup.string().required(
|
||||
constants.REQUIRED
|
||||
),
|
||||
confirm: Yup.string().required(constants.REQUIRED),
|
||||
})}
|
||||
validateOnChange={false}
|
||||
validateOnBlur={false}
|
||||
|
@ -72,21 +105,6 @@ export default function Home() {
|
|||
handleSubmit,
|
||||
}): JSX.Element => (
|
||||
<Form noValidate onSubmit={handleSubmit}>
|
||||
<Form.Group controlId="registrationForm.name">
|
||||
<Form.Control
|
||||
type="text"
|
||||
placeholder={constants.ENTER_NAME}
|
||||
value={values.name}
|
||||
onChange={handleChange('name')}
|
||||
isInvalid={Boolean(
|
||||
touched.name && errors.name
|
||||
)}
|
||||
disabled={loading}
|
||||
/>
|
||||
<FormControl.Feedback type="invalid">
|
||||
{errors.name}
|
||||
</FormControl.Feedback>
|
||||
</Form.Group>
|
||||
<Form.Group controlId="registrationForm.email">
|
||||
<Form.Control
|
||||
type="email"
|
||||
|
@ -102,9 +120,40 @@ export default function Home() {
|
|||
{errors.email}
|
||||
</FormControl.Feedback>
|
||||
</Form.Group>
|
||||
<DisclaimerContainer>
|
||||
{constants.DATA_DISCLAIMER}
|
||||
</DisclaimerContainer>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
type="password"
|
||||
placeholder={constants.PASSPHRASE_HINT}
|
||||
value={values.passphrase}
|
||||
onChange={handleChange('passphrase')}
|
||||
isInvalid={Boolean(
|
||||
touched.passphrase &&
|
||||
errors.passphrase
|
||||
)}
|
||||
autoFocus={true}
|
||||
disabled={loading}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{errors.passphrase}
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
type="password"
|
||||
placeholder={
|
||||
constants.RE_ENTER_PASSPHRASE
|
||||
}
|
||||
value={values.confirm}
|
||||
onChange={handleChange('confirm')}
|
||||
isInvalid={Boolean(
|
||||
touched.confirm && errors.confirm
|
||||
)}
|
||||
disabled={loading}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{errors.confirm}
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
|
||||
<SubmitButton
|
||||
buttonText={constants.SUBMIT}
|
||||
|
@ -113,6 +162,10 @@ export default function Home() {
|
|||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
<br />
|
||||
<Button variant="link" onClick={router.back}>
|
||||
{constants.GO_BACK}
|
||||
</Button>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Container>
|
||||
|
|
|
@ -52,14 +52,10 @@ export const verifyOtt = (email: string, ott: string) => {
|
|||
return HTTPService.get(`${ENDPOINT}/users/credentials`, { email, ott });
|
||||
};
|
||||
|
||||
export const putAttributes = (
|
||||
token: string,
|
||||
name: string,
|
||||
keyAttributes: KeyAttributes
|
||||
) => {
|
||||
export const putAttributes = (token: string, keyAttributes: KeyAttributes) => {
|
||||
return HTTPService.put(
|
||||
`${ENDPOINT}/users/attributes`,
|
||||
{ name: name ? name : '', keyAttributes: keyAttributes },
|
||||
{ keyAttributes: keyAttributes },
|
||||
null,
|
||||
{
|
||||
'X-Auth-Token': token,
|
||||
|
|
|
@ -16,30 +16,70 @@ export const getDedicatedCryptoWorker = (): any =>
|
|||
runningInBrowser() &&
|
||||
Comlink.wrap(new Worker('worker/crypto.worker.js', { type: 'module' }));
|
||||
|
||||
export async function generateKeyAttributes(
|
||||
passphrase: string
|
||||
): Promise<{ keyAttributes: KeyAttributes; masterKey: string }> {
|
||||
const cryptoWorker = await new CryptoWorker();
|
||||
const masterKey: string = await cryptoWorker.generateEncryptionKey();
|
||||
const recoveryKey: string = await cryptoWorker.generateEncryptionKey();
|
||||
const kekSalt: string = await cryptoWorker.generateSaltToDeriveKey();
|
||||
const kek = await cryptoWorker.deriveSensitiveKey(passphrase, kekSalt);
|
||||
|
||||
const masterKeyEncryptedWithKek: B64EncryptionResult =
|
||||
await cryptoWorker.encryptToB64(masterKey, kek.key);
|
||||
const masterKeyEncryptedWithRecoveryKey: B64EncryptionResult =
|
||||
await cryptoWorker.encryptToB64(masterKey, recoveryKey);
|
||||
const recoveryKeyEncryptedWithMasterKey: B64EncryptionResult =
|
||||
await cryptoWorker.encryptToB64(recoveryKey, masterKey);
|
||||
|
||||
const keyPair = await cryptoWorker.generateKeyPair();
|
||||
const encryptedKeyPairAttributes: B64EncryptionResult =
|
||||
await cryptoWorker.encryptToB64(keyPair.privateKey, masterKey);
|
||||
|
||||
const keyAttributes: KeyAttributes = {
|
||||
kekSalt,
|
||||
encryptedKey: masterKeyEncryptedWithKek.encryptedData,
|
||||
keyDecryptionNonce: masterKeyEncryptedWithKek.nonce,
|
||||
publicKey: keyPair.publicKey,
|
||||
encryptedSecretKey: encryptedKeyPairAttributes.encryptedData,
|
||||
secretKeyDecryptionNonce: encryptedKeyPairAttributes.nonce,
|
||||
opsLimit: kek.opsLimit,
|
||||
memLimit: kek.memLimit,
|
||||
masterKeyEncryptedWithRecoveryKey:
|
||||
masterKeyEncryptedWithRecoveryKey.encryptedData,
|
||||
masterKeyDecryptionNonce: masterKeyEncryptedWithRecoveryKey.nonce,
|
||||
recoveryKeyEncryptedWithMasterKey:
|
||||
recoveryKeyEncryptedWithMasterKey.encryptedData,
|
||||
recoveryKeyDecryptionNonce: recoveryKeyEncryptedWithMasterKey.nonce,
|
||||
};
|
||||
|
||||
return { keyAttributes, masterKey };
|
||||
}
|
||||
|
||||
export async function generateAndSaveIntermediateKeyAttributes(
|
||||
passphrase,
|
||||
existingKeyAttributes,
|
||||
key
|
||||
) {
|
||||
): Promise<KeyAttributes> {
|
||||
const cryptoWorker = await new CryptoWorker();
|
||||
const intermediateKekSalt: string = await cryptoWorker.generateSaltToDeriveKey();
|
||||
const intermediateKekSalt: string =
|
||||
await cryptoWorker.generateSaltToDeriveKey();
|
||||
const intermediateKek: KEK = await cryptoWorker.deriveIntermediateKey(
|
||||
passphrase,
|
||||
intermediateKekSalt
|
||||
);
|
||||
const encryptedKeyAttributes: B64EncryptionResult = await cryptoWorker.encryptToB64(
|
||||
key,
|
||||
intermediateKek.key
|
||||
);
|
||||
const encryptedKeyAttributes: B64EncryptionResult =
|
||||
await cryptoWorker.encryptToB64(key, intermediateKek.key);
|
||||
|
||||
const updatedKeyAttributes = Object.assign(existingKeyAttributes, {
|
||||
const intermediateKeyAttributes = Object.assign(existingKeyAttributes, {
|
||||
kekSalt: intermediateKekSalt,
|
||||
encryptedKey: encryptedKeyAttributes.encryptedData,
|
||||
keyDecryptionNonce: encryptedKeyAttributes.nonce,
|
||||
opsLimit: intermediateKek.opsLimit,
|
||||
memLimit: intermediateKek.memLimit,
|
||||
});
|
||||
setData(LS_KEYS.KEY_ATTRIBUTES, updatedKeyAttributes);
|
||||
setData(LS_KEYS.KEY_ATTRIBUTES, intermediateKeyAttributes);
|
||||
return intermediateKeyAttributes;
|
||||
}
|
||||
|
||||
export const setSessionKeys = async (key: string) => {
|
||||
|
@ -83,14 +123,10 @@ async function createNewRecoveryKey() {
|
|||
const cryptoWorker = await new CryptoWorker();
|
||||
|
||||
const recoveryKey = await cryptoWorker.generateEncryptionKey();
|
||||
const encryptedMasterKey: B64EncryptionResult = await cryptoWorker.encryptToB64(
|
||||
masterKey,
|
||||
recoveryKey
|
||||
);
|
||||
const encryptedRecoveryKey: B64EncryptionResult = await cryptoWorker.encryptToB64(
|
||||
recoveryKey,
|
||||
masterKey
|
||||
);
|
||||
const encryptedMasterKey: B64EncryptionResult =
|
||||
await cryptoWorker.encryptToB64(masterKey, recoveryKey);
|
||||
const encryptedRecoveryKey: B64EncryptionResult =
|
||||
await cryptoWorker.encryptToB64(recoveryKey, masterKey);
|
||||
const recoveryKeyAttributes = {
|
||||
masterKeyEncryptedWithRecoveryKey: encryptedMasterKey.encryptedData,
|
||||
masterKeyDecryptionNonce: encryptedMasterKey.nonce,
|
||||
|
|
|
@ -2,6 +2,7 @@ export enum LS_KEYS {
|
|||
USER = 'user',
|
||||
SESSION = 'session',
|
||||
KEY_ATTRIBUTES = 'keyAttributes',
|
||||
ORIGINAL_KEY_ATTRIBUTES = 'originalKeyAttributes',
|
||||
SUBSCRIPTION = 'subscription',
|
||||
PLANS = 'plans',
|
||||
IS_FIRST_LOGIN = 'isFirstLogin',
|
||||
|
|
Loading…
Reference in a new issue