commit
4f5f0fe378
BIN
public/vault.png
BIN
public/vault.png
Binary file not shown.
Before Width: | Height: | Size: 48 KiB |
|
@ -13,10 +13,10 @@ const CONFIRM_ACTION_VALUES = [
|
||||||
{ text: 'LOGOUT', type: 'danger' },
|
{ text: 'LOGOUT', type: 'danger' },
|
||||||
{ text: 'DELETE', type: 'danger' },
|
{ text: 'DELETE', type: 'danger' },
|
||||||
{ text: 'SESSION_EXPIRED', type: 'primary' },
|
{ text: 'SESSION_EXPIRED', type: 'primary' },
|
||||||
{ text: 'DOWNLOAD_APP', type: 'primary' },
|
{ text: 'DOWNLOAD_APP', type: 'success' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface Props {
|
interface Props {
|
||||||
callback: any;
|
callback: any;
|
||||||
action: CONFIRM_ACTION;
|
action: CONFIRM_ACTION;
|
||||||
show: boolean;
|
show: boolean;
|
||||||
|
@ -37,19 +37,19 @@ function ConfirmDialog({ callback, action, ...props }: Props) {
|
||||||
<Modal.Title id="contained-modal-title-vcenter">
|
<Modal.Title id="contained-modal-title-vcenter">
|
||||||
{
|
{
|
||||||
constants[
|
constants[
|
||||||
`${CONFIRM_ACTION_VALUES[action]?.text}_MESSAGE`
|
`${CONFIRM_ACTION_VALUES[action]?.text}_MESSAGE`
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
</Modal.Title>
|
</Modal.Title>
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
<Modal.Footer style={{ borderTop: 'none' }}>
|
<Modal.Footer style={{ borderTop: 'none' }}>
|
||||||
{action != CONFIRM_ACTION.SESSION_EXPIRED && (
|
{action != CONFIRM_ACTION.SESSION_EXPIRED && (
|
||||||
<Button variant="secondary" onClick={props.onHide}>
|
<Button variant="outline-secondary" onClick={props.onHide}>
|
||||||
{constants.CANCEL}
|
{constants.CANCEL}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
variant={`${CONFIRM_ACTION_VALUES[action]?.type}`}
|
variant={`outline-${CONFIRM_ACTION_VALUES[action]?.type}`}
|
||||||
onClick={callback}
|
onClick={callback}
|
||||||
>
|
>
|
||||||
{constants[CONFIRM_ACTION_VALUES[action]?.text]}
|
{constants[CONFIRM_ACTION_VALUES[action]?.text]}
|
||||||
|
|
61
src/components/MessageDailog.tsx
Normal file
61
src/components/MessageDailog.tsx
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Button, Modal } from 'react-bootstrap';
|
||||||
|
import constants from 'utils/strings/constants';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
show: boolean;
|
||||||
|
children?: any;
|
||||||
|
onHide: () => void;
|
||||||
|
attributes?: {
|
||||||
|
title?: string;
|
||||||
|
ok?: boolean;
|
||||||
|
staticBackdrop?: boolean;
|
||||||
|
cancel?: { text: string; action?: any };
|
||||||
|
proceed?: { text: string; action: any };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function MessageDialog({ attributes, children, ...props }: Props) {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
{...props}
|
||||||
|
size="lg"
|
||||||
|
centered
|
||||||
|
backdrop={attributes?.staticBackdrop ? 'static' : 'true'}
|
||||||
|
>
|
||||||
|
<Modal.Body>
|
||||||
|
{attributes?.title && (
|
||||||
|
<Modal.Title>
|
||||||
|
<strong>{attributes.title}</strong>
|
||||||
|
<hr />
|
||||||
|
</Modal.Title>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</Modal.Body>
|
||||||
|
{attributes && (
|
||||||
|
<Modal.Footer style={{ borderTop: 'none' }}>
|
||||||
|
{attributes.ok && (
|
||||||
|
<Button variant="secondary" onClick={props.onHide}>
|
||||||
|
{constants.OK}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{attributes.cancel && (
|
||||||
|
<Button
|
||||||
|
variant="outline-danger"
|
||||||
|
onClick={attributes.cancel.action ?? props.onHide}
|
||||||
|
>
|
||||||
|
{attributes.cancel.text}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{attributes.proceed && (
|
||||||
|
<Button
|
||||||
|
variant="outline-success"
|
||||||
|
onClick={attributes.proceed.action}
|
||||||
|
>
|
||||||
|
{attributes.proceed.text}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Modal.Footer>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
117
src/components/PassphraseForm.tsx
Normal file
117
src/components/PassphraseForm.tsx
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
import React, { useEffect, 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 Button from 'react-bootstrap/Button';
|
||||||
|
import constants from 'utils/strings/constants';
|
||||||
|
import { Formik, FormikHelpers } from 'formik';
|
||||||
|
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import { KeyAttributes } from 'types';
|
||||||
|
import CryptoWorker, { setSessionKeys } from 'utils/crypto';
|
||||||
|
import { Spinner } from 'react-bootstrap';
|
||||||
|
import { propTypes } from 'react-bootstrap/esm/Image';
|
||||||
|
|
||||||
|
interface formValues {
|
||||||
|
passphrase: string;
|
||||||
|
}
|
||||||
|
interface Props {
|
||||||
|
callback: (passphrase: string, setFieldError) => void;
|
||||||
|
fieldType: string;
|
||||||
|
title: string;
|
||||||
|
placeholder: string;
|
||||||
|
buttonText: string;
|
||||||
|
alternateOption: { text: string; click: () => void };
|
||||||
|
back: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PassPhraseForm(props: Props) {
|
||||||
|
const [loading, SetLoading] = useState(false);
|
||||||
|
const submitForm = async (
|
||||||
|
values: formValues,
|
||||||
|
{ setFieldError }: FormikHelpers<formValues>
|
||||||
|
) => {
|
||||||
|
SetLoading(true);
|
||||||
|
await props.callback(values.passphrase, setFieldError);
|
||||||
|
SetLoading(false);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Card
|
||||||
|
style={{ minWidth: '320px', padding: '40px 30px' }}
|
||||||
|
className="text-center"
|
||||||
|
>
|
||||||
|
<Card.Body>
|
||||||
|
<Card.Title style={{ marginBottom: '24px' }}>
|
||||||
|
{props.title}
|
||||||
|
</Card.Title>
|
||||||
|
<Formik<formValues>
|
||||||
|
initialValues={{ passphrase: '' }}
|
||||||
|
onSubmit={submitForm}
|
||||||
|
validationSchema={Yup.object().shape({
|
||||||
|
passphrase: Yup.string().required(
|
||||||
|
constants.REQUIRED
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
values,
|
||||||
|
touched,
|
||||||
|
errors,
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
}) => (
|
||||||
|
<Form noValidate onSubmit={handleSubmit}>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Control
|
||||||
|
type={props.fieldType}
|
||||||
|
placeholder={props.placeholder}
|
||||||
|
value={values.passphrase}
|
||||||
|
onChange={handleChange('passphrase')}
|
||||||
|
onBlur={handleBlur('passphrase')}
|
||||||
|
isInvalid={Boolean(
|
||||||
|
touched.passphrase &&
|
||||||
|
errors.passphrase
|
||||||
|
)}
|
||||||
|
disabled={loading}
|
||||||
|
autoFocus={true}
|
||||||
|
/>
|
||||||
|
<Form.Control.Feedback type="invalid">
|
||||||
|
{errors.passphrase}
|
||||||
|
</Form.Control.Feedback>
|
||||||
|
</Form.Group>
|
||||||
|
<Button block type="submit" disabled={loading}>
|
||||||
|
{loading ? (
|
||||||
|
<Spinner animation="border" />
|
||||||
|
) : (
|
||||||
|
props.buttonText
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
<br />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
onClick={props.alternateOption.click}
|
||||||
|
>
|
||||||
|
{props.alternateOption.text}
|
||||||
|
</Button>
|
||||||
|
<Button variant="link" onClick={props.back}>
|
||||||
|
{constants.GO_BACK}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
135
src/components/PasswordForm.tsx
Normal file
135
src/components/PasswordForm.tsx
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
import React, { useState, useEffect, useContext } 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';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
callback: (passphrase: any, setFieldError: any) => Promise<void>;
|
||||||
|
buttonText: string;
|
||||||
|
back: () => void;
|
||||||
|
}
|
||||||
|
interface formValues {
|
||||||
|
passphrase: string;
|
||||||
|
confirm: string;
|
||||||
|
}
|
||||||
|
function SetPassword(props: Props) {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const onSubmit = async (
|
||||||
|
values: formValues,
|
||||||
|
{ setFieldError }: FormikHelpers<formValues>
|
||||||
|
) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const { passphrase, confirm } = values;
|
||||||
|
if (passphrase === confirm) {
|
||||||
|
await props.callback(passphrase, setFieldError);
|
||||||
|
} else {
|
||||||
|
setFieldError('confirm', constants.PASSPHRASE_MATCH_ERROR);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setFieldError(
|
||||||
|
'passphrase',
|
||||||
|
`${constants.UNKNOWN_ERROR} ${e.message}`
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Card style={{ maxWidth: '540px', padding: '20px' }}>
|
||||||
|
<Card.Body>
|
||||||
|
<div
|
||||||
|
className="text-center"
|
||||||
|
style={{ marginBottom: '40px' }}
|
||||||
|
>
|
||||||
|
<p>{constants.ENTER_ENC_PASSPHRASE}</p>
|
||||||
|
{constants.PASSPHRASE_DISCLAIMER()}
|
||||||
|
</div>
|
||||||
|
<Formik<formValues>
|
||||||
|
initialValues={{ passphrase: '', confirm: '' }}
|
||||||
|
validationSchema={Yup.object().shape({
|
||||||
|
passphrase: Yup.string().required(
|
||||||
|
constants.REQUIRED
|
||||||
|
),
|
||||||
|
confirm: Yup.string().required(constants.REQUIRED),
|
||||||
|
})}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
values,
|
||||||
|
touched,
|
||||||
|
errors,
|
||||||
|
handleChange,
|
||||||
|
handleBlur,
|
||||||
|
handleSubmit,
|
||||||
|
}) => (
|
||||||
|
<Form noValidate onSubmit={handleSubmit}>
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Control
|
||||||
|
type="password"
|
||||||
|
placeholder={constants.PASSPHRASE_HINT}
|
||||||
|
value={values.passphrase}
|
||||||
|
onChange={handleChange('passphrase')}
|
||||||
|
onBlur={handleBlur('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.PASSPHRASE_CONFIRM
|
||||||
|
}
|
||||||
|
value={values.confirm}
|
||||||
|
onChange={handleChange('confirm')}
|
||||||
|
onBlur={handleBlur('confirm')}
|
||||||
|
isInvalid={Boolean(
|
||||||
|
touched.confirm && errors.confirm
|
||||||
|
)}
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
|
<Form.Control.Feedback type="invalid">
|
||||||
|
{errors.confirm}
|
||||||
|
</Form.Control.Feedback>
|
||||||
|
</Form.Group>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
block
|
||||||
|
disabled={loading}
|
||||||
|
style={{ marginTop: '28px' }}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<Spinner animation="border" />
|
||||||
|
) : (
|
||||||
|
props.buttonText
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
<div className="text-center" style={{ marginTop: '20px' }}>
|
||||||
|
<Button variant="link" onClick={props.back}>
|
||||||
|
{constants.GO_BACK}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default SetPassword;
|
86
src/components/RecoveryKeyModal.tsx
Normal file
86
src/components/RecoveryKeyModal.tsx
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Spinner } from 'react-bootstrap';
|
||||||
|
import { downloadAsFile } from 'utils/common';
|
||||||
|
import { getRecoveryKey } from 'utils/crypto';
|
||||||
|
import { setJustSignedUp } from 'utils/storage';
|
||||||
|
import constants from 'utils/strings/constants';
|
||||||
|
import { MessageDialog } from './MessageDailog';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
show: boolean;
|
||||||
|
onHide: () => void;
|
||||||
|
somethingWentWrong: any;
|
||||||
|
}
|
||||||
|
function RecoveryKeyModal({ somethingWentWrong, ...props }: Props) {
|
||||||
|
const [recoveryKey, setRecoveryKey] = useState(null);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!props.show) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const main = async () => {
|
||||||
|
const recoveryKey = await getRecoveryKey();
|
||||||
|
if (!recoveryKey) {
|
||||||
|
somethingWentWrong();
|
||||||
|
props.onHide();
|
||||||
|
}
|
||||||
|
setRecoveryKey(recoveryKey);
|
||||||
|
};
|
||||||
|
main();
|
||||||
|
}, [props.show]);
|
||||||
|
|
||||||
|
function onSaveClick() {
|
||||||
|
downloadAsFile(constants.RECOVERY_KEY_FILENAME, recoveryKey);
|
||||||
|
onSaveLaterClick();
|
||||||
|
}
|
||||||
|
function onSaveLaterClick() {
|
||||||
|
props.onHide();
|
||||||
|
setJustSignedUp(false);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<MessageDialog
|
||||||
|
{...props}
|
||||||
|
attributes={{
|
||||||
|
title: constants.DOWNLOAD_RECOVERY_KEY,
|
||||||
|
cancel: {
|
||||||
|
text: constants.SAVE_LATER,
|
||||||
|
action: onSaveLaterClick,
|
||||||
|
},
|
||||||
|
staticBackdrop: true,
|
||||||
|
proceed: {
|
||||||
|
text: constants.SAVE,
|
||||||
|
action: onSaveClick,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p>{constants.RECOVERY_KEY_DESCRIPTION}</p>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
background: '#1a1919',
|
||||||
|
height: '150px',
|
||||||
|
padding: '40px',
|
||||||
|
color: 'white',
|
||||||
|
margin: '20px 0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{recoveryKey ? (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
wordWrap: 'break-word',
|
||||||
|
overflowWrap: 'break-word',
|
||||||
|
minWidth: '30%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{recoveryKey}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Spinner animation="border" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p>{constants.KEY_NOT_STORED_DISCLAIMER}</p>
|
||||||
|
</MessageDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default RecoveryKeyModal;
|
|
@ -14,15 +14,20 @@ import exportService from 'services/exportService';
|
||||||
import { file } from 'services/fileService';
|
import { file } from 'services/fileService';
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
import { collection } from 'services/collectionService';
|
import { collection } from 'services/collectionService';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import RecoveryKeyModal from './RecoveryKeyModal';
|
||||||
|
import { justSignedUp } from 'utils/storage';
|
||||||
interface Props {
|
interface Props {
|
||||||
files: file[];
|
files: file[];
|
||||||
collections: collection[];
|
collections: collection[];
|
||||||
setConfirmAction: any;
|
setConfirmAction: any;
|
||||||
|
somethingWentWrong: any;
|
||||||
}
|
}
|
||||||
export default function Sidebar(props: Props) {
|
export default function Sidebar(props: Props) {
|
||||||
const [usage, SetUsage] = useState<string>(null);
|
const [usage, SetUsage] = useState<string>(null);
|
||||||
const subscription: Subscription = getData(LS_KEYS.SUBSCRIPTION);
|
const subscription: Subscription = getData(LS_KEYS.SUBSCRIPTION);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [modalView, setModalView] = useState(justSignedUp());
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
|
@ -48,6 +53,7 @@ export default function Sidebar(props: Props) {
|
||||||
props.setConfirmAction(CONFIRM_ACTION.DOWNLOAD_APP);
|
props.setConfirmAction(CONFIRM_ACTION.DOWNLOAD_APP);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
|
@ -114,14 +120,47 @@ export default function Sidebar(props: Props) {
|
||||||
support
|
support
|
||||||
</a>
|
</a>
|
||||||
</h5>
|
</h5>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: '1px',
|
||||||
|
marginTop: '40px',
|
||||||
|
background: '#242424',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
<>
|
||||||
|
<RecoveryKeyModal
|
||||||
|
show={modalView}
|
||||||
|
onHide={() => setModalView(false)}
|
||||||
|
somethingWentWrong={props.somethingWentWrong}
|
||||||
|
/>
|
||||||
|
<h5
|
||||||
|
style={{ cursor: 'pointer', marginTop: '30px' }}
|
||||||
|
onClick={() => setModalView(true)}
|
||||||
|
>
|
||||||
|
{constants.DOWNLOAD_RECOVERY_KEY}
|
||||||
|
</h5>
|
||||||
|
</>
|
||||||
|
<h5
|
||||||
|
style={{ cursor: 'pointer', marginTop: '30px' }}
|
||||||
|
onClick={() => router.push('changePassword')}
|
||||||
|
>
|
||||||
|
{constants.CHANGE_PASSWORD}
|
||||||
|
</h5>
|
||||||
<h5
|
<h5
|
||||||
style={{ cursor: 'pointer', marginTop: '30px' }}
|
style={{ cursor: 'pointer', marginTop: '30px' }}
|
||||||
onClick={exportFiles}
|
onClick={exportFiles}
|
||||||
>
|
>
|
||||||
{constants.EXPORT}
|
{constants.EXPORT}
|
||||||
</h5>
|
</h5>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: '1px',
|
||||||
|
marginTop: '40px',
|
||||||
|
background: '#242424',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
<h5
|
<h5
|
||||||
style={{
|
style={{
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
|
|
@ -124,10 +124,20 @@ const GlobalStyles = createGlobalStyle`
|
||||||
.btn-outline-success {
|
.btn-outline-success {
|
||||||
color: #2dc262;
|
color: #2dc262;
|
||||||
border-color: #2dc262;
|
border-color: #2dc262;
|
||||||
|
border-width: 2px;
|
||||||
}
|
}
|
||||||
.btn-outline-success:hover {
|
.btn-outline-success:hover {
|
||||||
background: #2dc262;
|
background: #2dc262;
|
||||||
}
|
}
|
||||||
|
.btn-outline-danger {
|
||||||
|
border-width: 2px;
|
||||||
|
}
|
||||||
|
.btn-outline-secondary {
|
||||||
|
border-width: 2px;
|
||||||
|
}
|
||||||
|
.btn-outline-primary {
|
||||||
|
border-width: 2px;
|
||||||
|
}
|
||||||
.card {
|
.card {
|
||||||
background-color: #242424;
|
background-color: #242424;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
@ -140,7 +150,8 @@ const GlobalStyles = createGlobalStyle`
|
||||||
margin-top: 50px;
|
margin-top: 50px;
|
||||||
}
|
}
|
||||||
.alert-success {
|
.alert-success {
|
||||||
background-color: #c4ffd6;
|
background-color: #a9f7ff;
|
||||||
|
color: #000000;
|
||||||
}
|
}
|
||||||
.alert-primary {
|
.alert-primary {
|
||||||
background-color: #c4ffd6;
|
background-color: #c4ffd6;
|
||||||
|
|
79
src/pages/changePassword/index.tsx
Normal file
79
src/pages/changePassword/index.tsx
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import React, { useState, useEffect, useContext } from 'react';
|
||||||
|
import constants from 'utils/strings/constants';
|
||||||
|
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { getKey, SESSION_KEYS, setKey } from 'utils/storage/sessionStorage';
|
||||||
|
import { B64EncryptionResult } from 'services/uploadService';
|
||||||
|
import CryptoWorker, {
|
||||||
|
setSessionKeys,
|
||||||
|
generateAndSaveIntermediateKeyAttributes,
|
||||||
|
} from 'utils/crypto';
|
||||||
|
import { getActualKey } from 'utils/common/key';
|
||||||
|
import { logoutUser, setKeys, UpdatedKey } from 'services/userService';
|
||||||
|
import PasswordForm from 'components/PasswordForm';
|
||||||
|
|
||||||
|
export interface KEK {
|
||||||
|
key: string;
|
||||||
|
opsLimit: number;
|
||||||
|
memLimit: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Generate() {
|
||||||
|
const [token, setToken] = useState<string>();
|
||||||
|
const router = useRouter();
|
||||||
|
const key = getKey(SESSION_KEYS.ENCRYPTION_KEY);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const user = getData(LS_KEYS.USER);
|
||||||
|
if (!user?.token) {
|
||||||
|
router.push('/');
|
||||||
|
} else {
|
||||||
|
setToken(user.token);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onSubmit = async (passphrase, setFieldError) => {
|
||||||
|
const cryptoWorker = await new CryptoWorker();
|
||||||
|
const key: string = await getActualKey();
|
||||||
|
const keyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES);
|
||||||
|
const kekSalt: string = await cryptoWorker.generateSaltToDeriveKey();
|
||||||
|
let kek: KEK;
|
||||||
|
try {
|
||||||
|
kek = await cryptoWorker.deriveSensitiveKey(passphrase, kekSalt);
|
||||||
|
} catch (e) {
|
||||||
|
setFieldError('confirm', constants.PASSWORD_GENERATION_FAILED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const encryptedKeyAttributes: B64EncryptionResult = await cryptoWorker.encryptToB64(
|
||||||
|
key,
|
||||||
|
kek.key
|
||||||
|
);
|
||||||
|
const updatedKey: UpdatedKey = {
|
||||||
|
kekSalt,
|
||||||
|
encryptedKey: encryptedKeyAttributes.encryptedData,
|
||||||
|
keyDecryptionNonce: encryptedKeyAttributes.nonce,
|
||||||
|
opsLimit: kek.opsLimit,
|
||||||
|
memLimit: kek.memLimit,
|
||||||
|
};
|
||||||
|
|
||||||
|
await setKeys(token, updatedKey);
|
||||||
|
|
||||||
|
const updatedKeyAttributes = Object.assign(keyAttributes, updatedKey);
|
||||||
|
await generateAndSaveIntermediateKeyAttributes(
|
||||||
|
passphrase,
|
||||||
|
updatedKeyAttributes,
|
||||||
|
key
|
||||||
|
);
|
||||||
|
|
||||||
|
setSessionKeys(key);
|
||||||
|
router.push('/gallery');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PasswordForm
|
||||||
|
callback={onSubmit}
|
||||||
|
buttonText={constants.CHANGE_PASSWORD}
|
||||||
|
back={() => router.push('/gallery')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,35 +1,22 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import Container from 'components/Container';
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import Card from 'react-bootstrap/Card';
|
|
||||||
import Form from 'react-bootstrap/Form';
|
|
||||||
import Button from 'react-bootstrap/Button';
|
|
||||||
import constants from 'utils/strings/constants';
|
import constants from 'utils/strings/constants';
|
||||||
import { Formik, FormikHelpers } from 'formik';
|
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||||
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import * as Yup from 'yup';
|
|
||||||
import { KeyAttributes } from 'types';
|
import { KeyAttributes } from 'types';
|
||||||
import { setKey, SESSION_KEYS, getKey } from 'utils/storage/sessionStorage';
|
import { SESSION_KEYS, getKey } from 'utils/storage/sessionStorage';
|
||||||
import CryptoWorker, { generateIntermediateKeyAttributes } from 'utils/crypto';
|
import CryptoWorker, {
|
||||||
|
generateAndSaveIntermediateKeyAttributes,
|
||||||
|
setSessionKeys,
|
||||||
|
} from 'utils/crypto';
|
||||||
import { logoutUser } from 'services/userService';
|
import { logoutUser } from 'services/userService';
|
||||||
import { isFirstLogin } from 'utils/storage';
|
import { isFirstLogin } from 'utils/storage';
|
||||||
import { Spinner } from 'react-bootstrap';
|
import PassPhraseForm from 'components/PassphraseForm';
|
||||||
|
|
||||||
const Image = styled.img`
|
|
||||||
width: 200px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
max-width: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface formValues {
|
|
||||||
passphrase: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Credentials() {
|
export default function Credentials() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [keyAttributes, setKeyAttributes] = useState<KeyAttributes>();
|
const [keyAttributes, setKeyAttributes] = useState<KeyAttributes>();
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
router.prefetch('/gallery');
|
router.prefetch('/gallery');
|
||||||
|
@ -47,14 +34,9 @@ export default function Credentials() {
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const verifyPassphrase = async (
|
const verifyPassphrase = async (passphrase, setFieldError) => {
|
||||||
values: formValues,
|
|
||||||
{ setFieldError }: FormikHelpers<formValues>
|
|
||||||
) => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
try {
|
||||||
const cryptoWorker = await new CryptoWorker();
|
const cryptoWorker = await new CryptoWorker();
|
||||||
const { passphrase } = values;
|
|
||||||
const kek: string = await cryptoWorker.deriveKey(
|
const kek: string = await cryptoWorker.deriveKey(
|
||||||
passphrase,
|
passphrase,
|
||||||
keyAttributes.kekSalt,
|
keyAttributes.kekSalt,
|
||||||
|
@ -69,21 +51,13 @@ export default function Credentials() {
|
||||||
kek
|
kek
|
||||||
);
|
);
|
||||||
if (isFirstLogin()) {
|
if (isFirstLogin()) {
|
||||||
const intermediateKeyAttributes = await generateIntermediateKeyAttributes(
|
generateAndSaveIntermediateKeyAttributes(
|
||||||
passphrase,
|
passphrase,
|
||||||
keyAttributes,
|
keyAttributes,
|
||||||
key
|
key
|
||||||
);
|
);
|
||||||
setData(LS_KEYS.KEY_ATTRIBUTES, intermediateKeyAttributes);
|
|
||||||
}
|
}
|
||||||
const sessionKeyAttributes = await cryptoWorker.encryptToB64(
|
setSessionKeys(key);
|
||||||
key
|
|
||||||
);
|
|
||||||
const sessionKey = sessionKeyAttributes.key;
|
|
||||||
const sessionNonce = sessionKeyAttributes.nonce;
|
|
||||||
const encryptionKey = sessionKeyAttributes.encryptedData;
|
|
||||||
setKey(SESSION_KEYS.ENCRYPTION_KEY, { encryptionKey });
|
|
||||||
setData(LS_KEYS.SESSION, { sessionKey, sessionNonce });
|
|
||||||
router.push('/gallery');
|
router.push('/gallery');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -95,76 +69,20 @@ export default function Credentials() {
|
||||||
`${constants.UNKNOWN_ERROR} ${e.message}`
|
`${constants.UNKNOWN_ERROR} ${e.message}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<PassPhraseForm
|
||||||
{/* <Image alt="vault" src="/vault.png" /> */}
|
callback={verifyPassphrase}
|
||||||
<Card
|
title={constants.ENTER_PASSPHRASE}
|
||||||
style={{ minWidth: '320px', padding: '40px 30px' }}
|
placeholder={constants.RETURN_PASSPHRASE_HINT}
|
||||||
className="text-center"
|
buttonText={constants.VERIFY_PASSPHRASE}
|
||||||
>
|
fieldType="password"
|
||||||
<Card.Body>
|
alternateOption={{
|
||||||
<Card.Title style={{ marginBottom: '24px' }}>
|
text: constants.FORGOT_PASSWORD,
|
||||||
{constants.ENTER_PASSPHRASE}
|
click: () => router.push('/recover'),
|
||||||
</Card.Title>
|
}}
|
||||||
<Formik<formValues>
|
back={logoutUser}
|
||||||
initialValues={{ passphrase: '' }}
|
/>
|
||||||
onSubmit={verifyPassphrase}
|
|
||||||
validationSchema={Yup.object().shape({
|
|
||||||
passphrase: Yup.string().required(
|
|
||||||
constants.REQUIRED
|
|
||||||
),
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{({
|
|
||||||
values,
|
|
||||||
touched,
|
|
||||||
errors,
|
|
||||||
handleChange,
|
|
||||||
handleBlur,
|
|
||||||
handleSubmit,
|
|
||||||
}) => (
|
|
||||||
<Form noValidate onSubmit={handleSubmit}>
|
|
||||||
<Form.Group>
|
|
||||||
<Form.Control
|
|
||||||
type="password"
|
|
||||||
placeholder={
|
|
||||||
constants.RETURN_PASSPHRASE_HINT
|
|
||||||
}
|
|
||||||
value={values.passphrase}
|
|
||||||
onChange={handleChange('passphrase')}
|
|
||||||
onBlur={handleBlur('passphrase')}
|
|
||||||
isInvalid={Boolean(
|
|
||||||
touched.passphrase &&
|
|
||||||
errors.passphrase
|
|
||||||
)}
|
|
||||||
disabled={loading}
|
|
||||||
autoFocus={true}
|
|
||||||
/>
|
|
||||||
<Form.Control.Feedback type="invalid">
|
|
||||||
{errors.passphrase}
|
|
||||||
</Form.Control.Feedback>
|
|
||||||
</Form.Group>
|
|
||||||
<Button block type="submit" disabled={loading}>
|
|
||||||
{loading ? (
|
|
||||||
<Spinner animation="border" />
|
|
||||||
) : (
|
|
||||||
constants.VERIFY_PASSPHRASE
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
<br />
|
|
||||||
<div>
|
|
||||||
<a href="#" onClick={logoutUser}>
|
|
||||||
{constants.LOGOUT}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
)}
|
|
||||||
</Formik>
|
|
||||||
</Card.Body>
|
|
||||||
</Card>
|
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import UploadButton from './components/UploadButton';
|
||||||
import { checkConnectivity } from 'utils/common';
|
import { checkConnectivity } from 'utils/common';
|
||||||
import { isFirstLogin, setIsFirstLogin } from 'utils/storage';
|
import { isFirstLogin, setIsFirstLogin } from 'utils/storage';
|
||||||
import { logoutUser } from 'services/userService';
|
import { logoutUser } from 'services/userService';
|
||||||
|
import { MessageDialog } from 'components/MessageDailog';
|
||||||
const DATE_CONTAINER_HEIGHT = 45;
|
const DATE_CONTAINER_HEIGHT = 45;
|
||||||
const IMAGE_CONTAINER_HEIGHT = 200;
|
const IMAGE_CONTAINER_HEIGHT = 200;
|
||||||
const NO_OF_PAGES = 2;
|
const NO_OF_PAGES = 2;
|
||||||
|
@ -102,16 +103,6 @@ const ListContainer = styled.div<{ columns: number }>`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Image = styled.img`
|
|
||||||
width: 200px;
|
|
||||||
max-width: 100%;
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const DateContainer = styled.div`
|
const DateContainer = styled.div`
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
`;
|
`;
|
||||||
|
@ -163,6 +154,7 @@ export default function Gallery(props: Props) {
|
||||||
const [isFirstLoad, setIsFirstLoad] = useState(false);
|
const [isFirstLoad, setIsFirstLoad] = useState(false);
|
||||||
const [selected, setSelected] = useState<selectedState>({ count: 0 });
|
const [selected, setSelected] = useState<selectedState>({ count: 0 });
|
||||||
const [confirmAction, setConfirmAction] = useState<CONFIRM_ACTION>(null);
|
const [confirmAction, setConfirmAction] = useState<CONFIRM_ACTION>(null);
|
||||||
|
const [requestFailed, setRequestFailed] = useState(false);
|
||||||
const loadingBar = useRef(null);
|
const loadingBar = useRef(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const key = getKey(SESSION_KEYS.ENCRYPTION_KEY);
|
const key = getKey(SESSION_KEYS.ENCRYPTION_KEY);
|
||||||
|
@ -435,6 +427,14 @@ export default function Gallery(props: Props) {
|
||||||
callback={confirmCallbacks.get(confirmAction)}
|
callback={confirmCallbacks.get(confirmAction)}
|
||||||
action={confirmAction}
|
action={confirmAction}
|
||||||
/>
|
/>
|
||||||
|
<MessageDialog
|
||||||
|
show={requestFailed}
|
||||||
|
onHide={() => setRequestFailed(false)}
|
||||||
|
attributes={{
|
||||||
|
title: constants.UNKNOWN_ERROR,
|
||||||
|
ok: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Collections
|
<Collections
|
||||||
collections={collections}
|
collections={collections}
|
||||||
selected={Number(router.query.collection)}
|
selected={Number(router.query.collection)}
|
||||||
|
@ -452,15 +452,30 @@ export default function Gallery(props: Props) {
|
||||||
files={data}
|
files={data}
|
||||||
collections={collections}
|
collections={collections}
|
||||||
setConfirmAction={setConfirmAction}
|
setConfirmAction={setConfirmAction}
|
||||||
|
somethingWentWrong={() => setRequestFailed(true)}
|
||||||
/>
|
/>
|
||||||
<UploadButton openFileUploader={props.openFileUploader} />
|
<UploadButton openFileUploader={props.openFileUploader} />
|
||||||
{!isFirstLoad && data.length == 0 ? (
|
{!isFirstLoad && data.length == 0 ? (
|
||||||
<Jumbotron>
|
<div
|
||||||
<Image alt="vault" src="/vault.png" />
|
style={{
|
||||||
<Button variant="primary" onClick={props.openFileUploader}>
|
height: '60%',
|
||||||
|
display: 'grid',
|
||||||
|
placeItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="outline-success"
|
||||||
|
onClick={props.openFileUploader}
|
||||||
|
style={{
|
||||||
|
paddingLeft: '32px',
|
||||||
|
paddingRight: '32px',
|
||||||
|
paddingTop: '12px',
|
||||||
|
paddingBottom: '12px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{constants.UPLOAD_FIRST_PHOTO}
|
{constants.UPLOAD_FIRST_PHOTO}
|
||||||
</Button>
|
</Button>
|
||||||
</Jumbotron>
|
</div>
|
||||||
) : filteredData.length ? (
|
) : filteredData.length ? (
|
||||||
<Container>
|
<Container>
|
||||||
<AutoSizer>
|
<AutoSizer>
|
||||||
|
|
|
@ -1,31 +1,17 @@
|
||||||
import React, { useState, useEffect, useContext } from 'react';
|
import React, { useState, useEffect } 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 constants from 'utils/strings/constants';
|
||||||
import { Formik, FormikHelpers } from 'formik';
|
|
||||||
import * as Yup from 'yup';
|
|
||||||
import Button from 'react-bootstrap/Button';
|
|
||||||
import { logoutUser, putAttributes } from 'services/userService';
|
import { logoutUser, putAttributes } from 'services/userService';
|
||||||
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
|
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { getKey, SESSION_KEYS, setKey } from 'utils/storage/sessionStorage';
|
import { getKey, SESSION_KEYS } from 'utils/storage/sessionStorage';
|
||||||
import { B64EncryptionResult } from 'services/uploadService';
|
import { B64EncryptionResult } from 'services/uploadService';
|
||||||
import CryptoWorker from 'utils/crypto';
|
import CryptoWorker, {
|
||||||
import { generateIntermediateKeyAttributes } from 'utils/crypto';
|
setSessionKeys,
|
||||||
import { Spinner } from 'react-bootstrap';
|
generateAndSaveIntermediateKeyAttributes,
|
||||||
|
} from 'utils/crypto';
|
||||||
const Image = styled.img`
|
import PasswordForm from 'components/PasswordForm';
|
||||||
width: 200px;
|
import { KeyAttributes } from 'types';
|
||||||
margin-bottom: 20px;
|
import { setJustSignedUp } from 'utils/storage';
|
||||||
max-width: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface formValues {
|
|
||||||
passphrase: string;
|
|
||||||
confirm: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface KEK {
|
export interface KEK {
|
||||||
key: string;
|
key: string;
|
||||||
|
@ -34,12 +20,11 @@ export interface KEK {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Generate() {
|
export default function Generate() {
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [token, setToken] = useState<string>();
|
const [token, setToken] = useState<string>();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const key = getKey(SESSION_KEYS.ENCRYPTION_KEY);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const key = getKey(SESSION_KEYS.ENCRYPTION_KEY);
|
||||||
router.prefetch('/gallery');
|
router.prefetch('/gallery');
|
||||||
const user = getData(LS_KEYS.USER);
|
const user = getData(LS_KEYS.USER);
|
||||||
if (!user?.token) {
|
if (!user?.token) {
|
||||||
|
@ -51,177 +36,72 @@ export default function Generate() {
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onSubmit = async (
|
const onSubmit = async (passphrase, setFieldError) => {
|
||||||
values: formValues,
|
const cryptoWorker = await new CryptoWorker();
|
||||||
{ setFieldError }: FormikHelpers<formValues>
|
const masterKey: string = await cryptoWorker.generateEncryptionKey();
|
||||||
) => {
|
const recoveryKey: string = await cryptoWorker.generateEncryptionKey();
|
||||||
setLoading(true);
|
const kekSalt: string = await cryptoWorker.generateSaltToDeriveKey();
|
||||||
|
let kek: KEK;
|
||||||
try {
|
try {
|
||||||
const { passphrase, confirm } = values;
|
kek = await cryptoWorker.deriveSensitiveKey(passphrase, kekSalt);
|
||||||
if (passphrase === confirm) {
|
|
||||||
const cryptoWorker = await new CryptoWorker();
|
|
||||||
const key: string = await cryptoWorker.generateMasterKey();
|
|
||||||
const kekSalt: string = await cryptoWorker.generateSaltToDeriveKey();
|
|
||||||
let kek: KEK;
|
|
||||||
try {
|
|
||||||
kek = await cryptoWorker.deriveSensitiveKey(
|
|
||||||
passphrase,
|
|
||||||
kekSalt
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
setFieldError(
|
|
||||||
'confirm',
|
|
||||||
constants.PASSWORD_GENERATION_FAILED
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const encryptedKeyAttributes: B64EncryptionResult = await cryptoWorker.encryptToB64(
|
|
||||||
key,
|
|
||||||
kek.key
|
|
||||||
);
|
|
||||||
const keyPair = await cryptoWorker.generateKeyPair();
|
|
||||||
const encryptedKeyPairAttributes: B64EncryptionResult = await cryptoWorker.encryptToB64(
|
|
||||||
keyPair.privateKey,
|
|
||||||
key
|
|
||||||
);
|
|
||||||
|
|
||||||
const keyAttributes = {
|
|
||||||
kekSalt,
|
|
||||||
encryptedKey: encryptedKeyAttributes.encryptedData,
|
|
||||||
keyDecryptionNonce: encryptedKeyAttributes.nonce,
|
|
||||||
publicKey: keyPair.publicKey,
|
|
||||||
encryptedSecretKey:
|
|
||||||
encryptedKeyPairAttributes.encryptedData,
|
|
||||||
secretKeyDecryptionNonce: encryptedKeyPairAttributes.nonce,
|
|
||||||
opsLimit: kek.opsLimit,
|
|
||||||
memLimit: kek.memLimit,
|
|
||||||
};
|
|
||||||
await putAttributes(
|
|
||||||
token,
|
|
||||||
getData(LS_KEYS.USER).name,
|
|
||||||
keyAttributes
|
|
||||||
);
|
|
||||||
|
|
||||||
setData(
|
|
||||||
LS_KEYS.KEY_ATTRIBUTES,
|
|
||||||
await generateIntermediateKeyAttributes(
|
|
||||||
passphrase,
|
|
||||||
keyAttributes,
|
|
||||||
key
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const sessionKeyAttributes = await cryptoWorker.encryptToB64(
|
|
||||||
key
|
|
||||||
);
|
|
||||||
const sessionKey = sessionKeyAttributes.key;
|
|
||||||
const sessionNonce = sessionKeyAttributes.nonce;
|
|
||||||
const encryptionKey = sessionKeyAttributes.encryptedData;
|
|
||||||
setKey(SESSION_KEYS.ENCRYPTION_KEY, { encryptionKey });
|
|
||||||
setData(LS_KEYS.SESSION, { sessionKey, sessionNonce });
|
|
||||||
router.push('/gallery');
|
|
||||||
} else {
|
|
||||||
setFieldError('confirm', constants.PASSPHRASE_MATCH_ERROR);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setFieldError(
|
setFieldError('confirm', constants.PASSWORD_GENERATION_FAILED);
|
||||||
'passphrase',
|
return;
|
||||||
`${constants.UNKNOWN_ERROR} ${e.message}`
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
}
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
setSessionKeys(masterKey);
|
||||||
|
setJustSignedUp(true);
|
||||||
|
router.push('/gallery');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<>
|
||||||
{/* <Image alt="vault" src="/vault.png" style={{ paddingBottom: '40px' }} /> */}
|
<PasswordForm
|
||||||
<Card style={{ maxWidth: '540px', padding: '20px' }}>
|
callback={onSubmit}
|
||||||
<Card.Body>
|
buttonText={constants.SET_PASSPHRASE}
|
||||||
<div
|
back={logoutUser}
|
||||||
className="text-center"
|
/>
|
||||||
style={{ marginBottom: '40px' }}
|
</>
|
||||||
>
|
|
||||||
<p>{constants.ENTER_ENC_PASSPHRASE}</p>
|
|
||||||
{constants.PASSPHRASE_DISCLAIMER()}
|
|
||||||
</div>
|
|
||||||
<Formik<formValues>
|
|
||||||
initialValues={{ passphrase: '', confirm: '' }}
|
|
||||||
validationSchema={Yup.object().shape({
|
|
||||||
passphrase: Yup.string().required(
|
|
||||||
constants.REQUIRED
|
|
||||||
),
|
|
||||||
confirm: Yup.string().required(constants.REQUIRED),
|
|
||||||
})}
|
|
||||||
onSubmit={onSubmit}
|
|
||||||
>
|
|
||||||
{({
|
|
||||||
values,
|
|
||||||
touched,
|
|
||||||
errors,
|
|
||||||
handleChange,
|
|
||||||
handleBlur,
|
|
||||||
handleSubmit,
|
|
||||||
}) => (
|
|
||||||
<Form noValidate onSubmit={handleSubmit}>
|
|
||||||
<Form.Group>
|
|
||||||
<Form.Control
|
|
||||||
type="password"
|
|
||||||
placeholder={constants.PASSPHRASE_HINT}
|
|
||||||
value={values.passphrase}
|
|
||||||
onChange={handleChange('passphrase')}
|
|
||||||
onBlur={handleBlur('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.PASSPHRASE_CONFIRM
|
|
||||||
}
|
|
||||||
value={values.confirm}
|
|
||||||
onChange={handleChange('confirm')}
|
|
||||||
onBlur={handleBlur('confirm')}
|
|
||||||
isInvalid={Boolean(
|
|
||||||
touched.confirm && errors.confirm
|
|
||||||
)}
|
|
||||||
disabled={loading}
|
|
||||||
/>
|
|
||||||
<Form.Control.Feedback type="invalid">
|
|
||||||
{errors.confirm}
|
|
||||||
</Form.Control.Feedback>
|
|
||||||
</Form.Group>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
block
|
|
||||||
disabled={loading}
|
|
||||||
style={{ marginTop: '28px' }}
|
|
||||||
>
|
|
||||||
{loading ? (
|
|
||||||
<Spinner animation="border" />
|
|
||||||
) : (
|
|
||||||
constants.SET_PASSPHRASE
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
)}
|
|
||||||
</Formik>
|
|
||||||
<div className="text-center" style={{ marginTop: '20px' }}>
|
|
||||||
<a href="#" onClick={logoutUser}>
|
|
||||||
{constants.LOGOUT}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</Card.Body>
|
|
||||||
</Card>
|
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
70
src/pages/recover/index.tsx
Normal file
70
src/pages/recover/index.tsx
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import constants from 'utils/strings/constants';
|
||||||
|
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { KeyAttributes } from 'types';
|
||||||
|
import CryptoWorker, { setSessionKeys } from 'utils/crypto';
|
||||||
|
import PassPhraseForm from 'components/PassphraseForm';
|
||||||
|
import { MessageDialog } from 'components/MessageDailog';
|
||||||
|
|
||||||
|
export default function Recover() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [keyAttributes, setKeyAttributes] = useState<KeyAttributes>();
|
||||||
|
const [messageDialogView, SetMessageDialogView] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
router.prefetch('/gallery');
|
||||||
|
const user = getData(LS_KEYS.USER);
|
||||||
|
const keyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES);
|
||||||
|
if (!user?.token) {
|
||||||
|
router.push('/');
|
||||||
|
} else if (!keyAttributes) {
|
||||||
|
router.push('/generate');
|
||||||
|
} else {
|
||||||
|
setKeyAttributes(keyAttributes);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const recover = async (recoveryKey: string, setFieldError) => {
|
||||||
|
try {
|
||||||
|
const cryptoWorker = await new CryptoWorker();
|
||||||
|
let masterKey: string = await cryptoWorker.decryptB64(
|
||||||
|
keyAttributes.masterKeyEncryptedWithRecoveryKey,
|
||||||
|
keyAttributes.masterKeyDecryptionNonce,
|
||||||
|
await cryptoWorker.fromHex(recoveryKey)
|
||||||
|
);
|
||||||
|
setSessionKeys(masterKey);
|
||||||
|
router.push('/changePassword');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
setFieldError('passphrase', constants.INCORRECT_RECOVERY_KEY);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PassPhraseForm
|
||||||
|
callback={recover}
|
||||||
|
fieldType="text"
|
||||||
|
title={constants.RECOVER_ACCOUNT}
|
||||||
|
placeholder={constants.RETURN_RECOVERY_KEY_HINT}
|
||||||
|
buttonText={constants.RECOVER}
|
||||||
|
alternateOption={{
|
||||||
|
text: constants.NO_RECOVERY_KEY,
|
||||||
|
click: () => SetMessageDialogView(true),
|
||||||
|
}}
|
||||||
|
back={router.back}
|
||||||
|
/>
|
||||||
|
<MessageDialog
|
||||||
|
show={messageDialogView}
|
||||||
|
onHide={() => SetMessageDialogView(false)}
|
||||||
|
attributes={{
|
||||||
|
title: constants.SORRY,
|
||||||
|
ok: true,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{constants.NO_RECOVERY_KEY_MESSAGE}
|
||||||
|
</MessageDialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -215,7 +215,7 @@ export const AddCollection = async (
|
||||||
const worker = await new CryptoWorker();
|
const worker = await new CryptoWorker();
|
||||||
const encryptionKey = await getActualKey();
|
const encryptionKey = await getActualKey();
|
||||||
const token = getToken();
|
const token = getToken();
|
||||||
const collectionKey: string = await worker.generateMasterKey();
|
const collectionKey: string = await worker.generateEncryptionKey();
|
||||||
const {
|
const {
|
||||||
encryptedData: encryptedKey,
|
encryptedData: encryptedKey,
|
||||||
nonce: keyDecryptionNonce,
|
nonce: keyDecryptionNonce,
|
||||||
|
|
|
@ -7,6 +7,20 @@ import { clearData } from 'utils/storage/localStorage';
|
||||||
import localForage from 'utils/storage/localForage';
|
import localForage from 'utils/storage/localForage';
|
||||||
import { getToken } from 'utils/common/key';
|
import { getToken } from 'utils/common/key';
|
||||||
|
|
||||||
|
export interface UpdatedKey {
|
||||||
|
kekSalt: string;
|
||||||
|
encryptedKey: string;
|
||||||
|
keyDecryptionNonce: string;
|
||||||
|
memLimit: number;
|
||||||
|
opsLimit: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RecoveryKey {
|
||||||
|
masterKeyEncryptedWithRecoveryKey: string;
|
||||||
|
masterKeyDecryptionNonce: string;
|
||||||
|
recoveryKeyEncryptedWithMasterKey: string;
|
||||||
|
recoveryKeyDecryptionNonce: string;
|
||||||
|
}
|
||||||
const ENDPOINT = getEndpoint();
|
const ENDPOINT = getEndpoint();
|
||||||
|
|
||||||
export interface user {
|
export interface user {
|
||||||
|
@ -31,10 +45,26 @@ export const putAttributes = (
|
||||||
name: string,
|
name: string,
|
||||||
keyAttributes: KeyAttributes
|
keyAttributes: KeyAttributes
|
||||||
) => {
|
) => {
|
||||||
console.log('name ' + name);
|
|
||||||
return HTTPService.put(
|
return HTTPService.put(
|
||||||
`${ENDPOINT}/users/attributes`,
|
`${ENDPOINT}/users/attributes`,
|
||||||
{ name: name, keyAttributes: keyAttributes },
|
{ name: name ? name : '', keyAttributes: keyAttributes },
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
'X-Auth-Token': token,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setKeys = (token: string, updatedKey: UpdatedKey) => {
|
||||||
|
return HTTPService.put(`${ENDPOINT}/users/keys`, updatedKey, null, {
|
||||||
|
'X-Auth-Token': token,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SetRecoveryKey = (token: string, recoveryKey: RecoveryKey) => {
|
||||||
|
return HTTPService.put(
|
||||||
|
`${ENDPOINT}/users/recovery-key`,
|
||||||
|
recoveryKey,
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
'X-Auth-Token': token,
|
'X-Auth-Token': token,
|
||||||
|
|
|
@ -7,6 +7,10 @@ export interface KeyAttributes {
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
encryptedSecretKey: string;
|
encryptedSecretKey: string;
|
||||||
secretKeyDecryptionNonce: string;
|
secretKeyDecryptionNonce: string;
|
||||||
|
masterKeyEncryptedWithRecoveryKey: string;
|
||||||
|
masterKeyDecryptionNonce: string;
|
||||||
|
recoveryKeyEncryptedWithMasterKey: string;
|
||||||
|
recoveryKeyDecryptionNonce: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ENCRYPTION_CHUNK_SIZE = 4 * 1024 * 1024;
|
export const ENCRYPTION_CHUNK_SIZE = 4 * 1024 * 1024;
|
||||||
|
|
|
@ -16,3 +16,19 @@ export function getFileExtension(fileName): string {
|
||||||
export function runningInBrowser() {
|
export function runningInBrowser() {
|
||||||
return typeof window !== 'undefined';
|
return typeof window !== 'undefined';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function downloadAsFile(filename: string, content: string) {
|
||||||
|
const file = new Blob([content], {
|
||||||
|
type: 'text/plain',
|
||||||
|
});
|
||||||
|
var a = document.createElement('a');
|
||||||
|
a.href = URL.createObjectURL(file);
|
||||||
|
a.download = filename;
|
||||||
|
|
||||||
|
a.style.display = 'none';
|
||||||
|
document.body.appendChild(a);
|
||||||
|
|
||||||
|
a.click();
|
||||||
|
|
||||||
|
a.remove();
|
||||||
|
}
|
||||||
|
|
|
@ -3,16 +3,20 @@ import { B64EncryptionResult } from 'services/uploadService';
|
||||||
import { KeyAttributes } from 'types';
|
import { KeyAttributes } from 'types';
|
||||||
import * as Comlink from 'comlink';
|
import * as Comlink from 'comlink';
|
||||||
import { runningInBrowser } from 'utils/common';
|
import { runningInBrowser } from 'utils/common';
|
||||||
|
import { SESSION_KEYS, setKey } from 'utils/storage/sessionStorage';
|
||||||
|
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
|
||||||
|
import { getActualKey, getToken } from 'utils/common/key';
|
||||||
|
import { SetRecoveryKey } from 'services/userService';
|
||||||
|
|
||||||
const CryptoWorker: any =
|
const CryptoWorker: any =
|
||||||
runningInBrowser() &&
|
runningInBrowser() &&
|
||||||
Comlink.wrap(new Worker('worker/crypto.worker.js', { type: 'module' }));
|
Comlink.wrap(new Worker('worker/crypto.worker.js', { type: 'module' }));
|
||||||
|
|
||||||
export async function generateIntermediateKeyAttributes(
|
export async function generateAndSaveIntermediateKeyAttributes(
|
||||||
passphrase,
|
passphrase,
|
||||||
keyAttributes,
|
existingKeyAttributes,
|
||||||
key
|
key
|
||||||
): Promise<KeyAttributes> {
|
) {
|
||||||
const cryptoWorker = await new CryptoWorker();
|
const cryptoWorker = await new CryptoWorker();
|
||||||
const intermediateKekSalt: string = await cryptoWorker.generateSaltToDeriveKey();
|
const intermediateKekSalt: string = await cryptoWorker.generateSaltToDeriveKey();
|
||||||
const intermediateKek: KEK = await cryptoWorker.deriveIntermediateKey(
|
const intermediateKek: KEK = await cryptoWorker.deriveIntermediateKey(
|
||||||
|
@ -23,16 +27,84 @@ export async function generateIntermediateKeyAttributes(
|
||||||
key,
|
key,
|
||||||
intermediateKek.key
|
intermediateKek.key
|
||||||
);
|
);
|
||||||
return {
|
|
||||||
|
const updatedKeyAttributes = Object.assign(existingKeyAttributes, {
|
||||||
kekSalt: intermediateKekSalt,
|
kekSalt: intermediateKekSalt,
|
||||||
encryptedKey: encryptedKeyAttributes.encryptedData,
|
encryptedKey: encryptedKeyAttributes.encryptedData,
|
||||||
keyDecryptionNonce: encryptedKeyAttributes.nonce,
|
keyDecryptionNonce: encryptedKeyAttributes.nonce,
|
||||||
publicKey: keyAttributes.publicKey,
|
|
||||||
encryptedSecretKey: keyAttributes.encryptedSecretKey,
|
|
||||||
secretKeyDecryptionNonce: keyAttributes.secretKeyDecryptionNonce,
|
|
||||||
opsLimit: intermediateKek.opsLimit,
|
opsLimit: intermediateKek.opsLimit,
|
||||||
memLimit: intermediateKek.memLimit,
|
memLimit: intermediateKek.memLimit,
|
||||||
};
|
});
|
||||||
|
setData(LS_KEYS.KEY_ATTRIBUTES, updatedKeyAttributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const setSessionKeys = async (key: string) => {
|
||||||
|
const cryptoWorker = await new CryptoWorker();
|
||||||
|
const sessionKeyAttributes = await cryptoWorker.encryptToB64(key);
|
||||||
|
const sessionKey = sessionKeyAttributes.key;
|
||||||
|
const sessionNonce = sessionKeyAttributes.nonce;
|
||||||
|
const encryptionKey = sessionKeyAttributes.encryptedData;
|
||||||
|
setKey(SESSION_KEYS.ENCRYPTION_KEY, { encryptionKey });
|
||||||
|
setData(LS_KEYS.SESSION, { sessionKey, sessionNonce });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getRecoveryKey = async () => {
|
||||||
|
let recoveryKey = null;
|
||||||
|
try {
|
||||||
|
const cryptoWorker = await new CryptoWorker();
|
||||||
|
|
||||||
|
const keyAttributes: KeyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES);
|
||||||
|
const {
|
||||||
|
recoveryKeyEncryptedWithMasterKey,
|
||||||
|
recoveryKeyDecryptionNonce,
|
||||||
|
} = keyAttributes;
|
||||||
|
const masterKey = await getActualKey();
|
||||||
|
if (recoveryKeyEncryptedWithMasterKey) {
|
||||||
|
recoveryKey = await cryptoWorker.decryptB64(
|
||||||
|
recoveryKeyEncryptedWithMasterKey,
|
||||||
|
recoveryKeyDecryptionNonce,
|
||||||
|
masterKey
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
recoveryKey = await createNewRecoveryKey();
|
||||||
|
}
|
||||||
|
recoveryKey = await cryptoWorker.toHex(recoveryKey);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('getRecoveryKey failed', e);
|
||||||
|
} finally {
|
||||||
|
return recoveryKey;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function createNewRecoveryKey() {
|
||||||
|
const masterKey = await getActualKey();
|
||||||
|
const existingAttributes = getData(LS_KEYS.KEY_ATTRIBUTES);
|
||||||
|
|
||||||
|
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 recoveryKeyAttributes = {
|
||||||
|
masterKeyEncryptedWithRecoveryKey: encryptedMasterKey.encryptedData,
|
||||||
|
masterKeyDecryptionNonce: encryptedMasterKey.nonce,
|
||||||
|
recoveryKeyEncryptedWithMasterKey: encryptedRecoveryKey.encryptedData,
|
||||||
|
recoveryKeyDecryptionNonce: encryptedRecoveryKey.nonce,
|
||||||
|
};
|
||||||
|
await SetRecoveryKey(getToken(), recoveryKeyAttributes);
|
||||||
|
|
||||||
|
const updatedKeyAttributes = Object.assign(
|
||||||
|
existingAttributes,
|
||||||
|
recoveryKeyAttributes
|
||||||
|
);
|
||||||
|
setData(LS_KEYS.KEY_ATTRIBUTES, updatedKeyAttributes);
|
||||||
|
|
||||||
|
return recoveryKey;
|
||||||
|
}
|
||||||
export default CryptoWorker;
|
export default CryptoWorker;
|
||||||
|
|
|
@ -313,7 +313,7 @@ export async function deriveIntermediateKey(passphrase: string, salt: string) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMasterKey() {
|
export async function generateEncryptionKey() {
|
||||||
await sodium.ready;
|
await sodium.ready;
|
||||||
return await toB64(sodium.crypto_kdf_keygen());
|
return await toB64(sodium.crypto_kdf_keygen());
|
||||||
}
|
}
|
||||||
|
@ -361,3 +361,12 @@ export async function fromString(input: string) {
|
||||||
await sodium.ready;
|
await sodium.ready;
|
||||||
return sodium.from_string(input);
|
return sodium.from_string(input);
|
||||||
}
|
}
|
||||||
|
export async function toHex(input: string) {
|
||||||
|
await sodium.ready;
|
||||||
|
return sodium.to_hex(await fromB64(input));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fromHex(input: string) {
|
||||||
|
await sodium.ready;
|
||||||
|
return await toB64(sodium.from_hex(input));
|
||||||
|
}
|
||||||
|
|
|
@ -6,3 +6,10 @@ export const isFirstLogin = () =>
|
||||||
export function setIsFirstLogin(status) {
|
export function setIsFirstLogin(status) {
|
||||||
setData(LS_KEYS.IS_FIRST_LOGIN, { status });
|
setData(LS_KEYS.IS_FIRST_LOGIN, { status });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const justSignedUp = () =>
|
||||||
|
getData(LS_KEYS.JUST_SIGNED_UP)?.status ?? false;
|
||||||
|
|
||||||
|
export function setJustSignedUp(status) {
|
||||||
|
setData(LS_KEYS.JUST_SIGNED_UP, { status });
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ export enum LS_KEYS {
|
||||||
KEY_ATTRIBUTES = 'keyAttributes',
|
KEY_ATTRIBUTES = 'keyAttributes',
|
||||||
SUBSCRIPTION = 'subscription',
|
SUBSCRIPTION = 'subscription',
|
||||||
IS_FIRST_LOGIN = 'isFirstLogin',
|
IS_FIRST_LOGIN = 'isFirstLogin',
|
||||||
|
JUST_SIGNED_UP = 'justSignedUp',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setData = (key: LS_KEYS, value: object) => {
|
export const setData = (key: LS_KEYS, value: object) => {
|
||||||
|
|
|
@ -112,7 +112,7 @@ const englishConstants = {
|
||||||
'sorry, this operation is currently not supported on the web, please install the desktop app',
|
'sorry, this operation is currently not supported on the web, please install the desktop app',
|
||||||
DOWNLOAD_APP: 'download',
|
DOWNLOAD_APP: 'download',
|
||||||
APP_DOWNLOAD_URL: 'https://github.com/ente-io/bhari-frame/releases/',
|
APP_DOWNLOAD_URL: 'https://github.com/ente-io/bhari-frame/releases/',
|
||||||
EXPORT: 'export ',
|
EXPORT: 'export data',
|
||||||
SUBSCRIPTION_PLAN: 'subscription plan',
|
SUBSCRIPTION_PLAN: 'subscription plan',
|
||||||
USAGE_DETAILS: 'usage',
|
USAGE_DETAILS: 'usage',
|
||||||
FREE_SUBSCRIPTION_INFO: (expiryTime) => (
|
FREE_SUBSCRIPTION_INFO: (expiryTime) => (
|
||||||
|
@ -160,6 +160,24 @@ const englishConstants = {
|
||||||
SYNC_FAILED:
|
SYNC_FAILED:
|
||||||
'failed to sync with remote server, please refresh page to try again',
|
'failed to sync with remote server, please refresh page to try again',
|
||||||
PASSWORD_GENERATION_FAILED: `your browser was unable to generate a strong enough password that meets ente's encryption standards, please try using the mobile app or another browser`,
|
PASSWORD_GENERATION_FAILED: `your browser was unable to generate a strong enough password that meets ente's encryption standards, please try using the mobile app or another browser`,
|
||||||
|
CHANGE_PASSWORD: 'change password',
|
||||||
|
GO_BACK: 'go back',
|
||||||
|
DOWNLOAD_RECOVERY_KEY: 'recovery key',
|
||||||
|
SAVE_LATER: 'save later',
|
||||||
|
SAVE: 'save',
|
||||||
|
RECOVERY_KEY_DESCRIPTION: 'if you forget your password, the only way you can recover your data is with this key',
|
||||||
|
KEY_NOT_STORED_DISCLAIMER: 'we don\'t store this key, so please save this in a safe place',
|
||||||
|
RECOVERY_KEY_FILENAME: 'ente-recovery-key.txt',
|
||||||
|
FORGOT_PASSWORD: 'forgot password?',
|
||||||
|
RECOVER_ACCOUNT: 'recover account',
|
||||||
|
RETURN_RECOVERY_KEY_HINT: 'recovery key',
|
||||||
|
RECOVER: 'recover',
|
||||||
|
NO_RECOVERY_KEY: 'no recovery key?',
|
||||||
|
INCORRECT_RECOVERY_KEY: 'incorrect recovery key',
|
||||||
|
SORRY: 'sorry',
|
||||||
|
NO_RECOVERY_KEY_MESSAGE:
|
||||||
|
'due to the nature of our end-to-end encryption protocol, your data cannot be decrypted without your password or recovery key',
|
||||||
|
OK: 'ok',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default englishConstants;
|
export default englishConstants;
|
||||||
|
|
|
@ -104,8 +104,8 @@ export class Crypto {
|
||||||
return libsodium.encryptUTF8(data, key);
|
return libsodium.encryptUTF8(data, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateMasterKey() {
|
async generateEncryptionKey() {
|
||||||
return libsodium.generateMasterKey();
|
return libsodium.generateEncryptionKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateSaltToDeriveKey() {
|
async generateSaltToDeriveKey() {
|
||||||
|
@ -131,6 +131,12 @@ export class Crypto {
|
||||||
async fromB64(string) {
|
async fromB64(string) {
|
||||||
return libsodium.fromB64(string);
|
return libsodium.fromB64(string);
|
||||||
}
|
}
|
||||||
|
async toHex(string) {
|
||||||
|
return libsodium.toHex(string);
|
||||||
|
}
|
||||||
|
async fromHex(string) {
|
||||||
|
return libsodium.fromHex(string);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Comlink.expose(Crypto);
|
Comlink.expose(Crypto);
|
||||||
|
|
Loading…
Reference in a new issue