Merge pull request #73 from ente-io/signup-flow-update

Signup flow update
This commit is contained in:
Abhinav-grd 2021-05-23 20:30:58 +05:30 committed by GitHub
commit 67aa33cac3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 190 additions and 123 deletions

View file

@ -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 {

View file

@ -382,7 +382,7 @@ export default function App({ Component, pageProps, err }) {
</EnteSpinner>
</Container>
) : (
<Component err={err} />
<Component err={err} setLoading={setLoading} />
)}
</>
);

View file

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

View file

@ -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={() => {

View file

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

View file

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

View file

@ -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,

View file

@ -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,

View file

@ -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',