Login and key generation flow complete!

This commit is contained in:
Pushkar Anand 2020-09-13 03:23:41 +05:30
parent 8e3da8e315
commit 2e10ea441e
26 changed files with 980 additions and 69 deletions

View file

@ -8,17 +8,23 @@
"start": "next start"
},
"dependencies": {
"axios": "^0.20.0",
"bootstrap": "^4.5.2",
"formik": "^2.1.5",
"http-proxy-middleware": "^1.0.5",
"next": "9.5.3",
"react": "16.13.1",
"react-bootstrap": "^1.3.0",
"react-dom": "16.13.1",
"scrypt-js": "^3.0.1",
"styled-components": "^5.2.0"
"styled-components": "^5.2.0",
"yup": "^0.29.3"
},
"devDependencies": {
"@types/node": "^14.6.4",
"@types/react": "^16.9.49",
"@types/styled-components": "^5.1.3",
"@types/yup": "^0.29.7",
"babel-plugin-styled-components": "^1.11.1",
"typescript": "^4.0.2"
}

1
public/email_sent.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

1
public/vault.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.7 KiB

View file

@ -0,0 +1,13 @@
import styled from 'styled-components';
const Container = styled.div`
flex: 1;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
overflow: auto;
padding: 10px;
`;
export default Container;

View file

@ -11,6 +11,8 @@ const Navbar = styled.div`
align-items: center;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.7);
margin-bottom: 10px;
position: sticky;
top: 0;
`;
export default Navbar;

View file

@ -1,6 +1,8 @@
import React from 'react';
import React, { useState, createContext } from 'react';
import styled, {createGlobalStyle } from 'styled-components';
import Navbar from 'components/Navbar';
import constants from 'utils/strings/constants';
import 'bootstrap/dist/css/bootstrap.min.css';
const GlobalStyles = createGlobalStyle`
html, body {
@ -35,15 +37,23 @@ const Image = styled.img`
margin-right: 5px;
`;
export interface IAppContext {
key: string;
setKey: (key: string) => void
}
export const AppContext = createContext<IAppContext>(null);
export default function App({ Component, pageProps }) {
const [key, setKey] = useState<string>();
return (
<>
<AppContext.Provider value={{ key, setKey }}>
<GlobalStyles />
<Navbar>
<Image src="/icon.png" />
ente
{constants.COMPANY_NAME}
</Navbar>
<Component />
</>
</AppContext.Provider>
);
}

View file

@ -1,7 +1,7 @@
import Document, {
Html, Head, Main, NextScript,
} from 'next/document'
import { ServerStyleSheet } from 'styled-components'
} from 'next/document';
import { ServerStyleSheet } from 'styled-components';
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
@ -35,13 +35,6 @@ export default class MyDocument extends Document {
<Head>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
<link rel="icon" href="/icon.png" type="image/png"/>
<script src="https://unpkg.com/react-bootstrap@next/dist/react-bootstrap.min.js" crossorigin></script>
<link
rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
crossorigin="anonymous"
/>
</Head>
<body>
<Main />

13
src/pages/api/[...all].ts Normal file
View file

@ -0,0 +1,13 @@
import { createProxyMiddleware } from 'http-proxy-middleware';
export const config = {
api: {
bodyParser: false,
},
};
export default createProxyMiddleware({
target: "http://api.staging.ente.io",
changeOrigin: true,
pathRewrite: { '^/api': '/' },
});

View file

@ -1,6 +0,0 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
export default (req, res) => {
res.statusCode = 200
res.json({ name: 'John Doe' })
}

View file

@ -0,0 +1,103 @@
import React, { useEffect, useState, 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 Button from 'react-bootstrap/Button';
import constants from 'utils/strings/constants';
import { Formik, FormikHelpers } from 'formik';
import { getData, SESSION_KEYS } from 'utils/sessionStorage';
import { useRouter } from 'next/router';
import * as Yup from 'yup';
import { hash } from 'utils/crypto/scrypt';
import { strToUint8, base64ToUint8 } from 'utils/crypto/common';
import { AppContext } from 'pages/_app';
import { decrypt } from 'utils/crypto/aes';
import { keyAttributes } from 'types';
const Image = styled.img`
width: 200px;
margin-bottom: 20px;
max-width: 100%;
`;
interface formValues {
passphrase: string;
}
export default function Credentials() {
const router = useRouter();
const [keyAttributes, setKeyAttributes] = useState<keyAttributes>();
const [loading, setLoading] = useState(false);
const context = useContext(AppContext);
useEffect(() => {
const user = getData(SESSION_KEYS.USER);
const keyAttributes = getData(SESSION_KEYS.KEY_ATTRIBUTES);
if (!user?.token) {
router.push('/');
} else if (!keyAttributes) {
router.push('/generate');
} else if (context.key) {
router.push('/gallery')
} else {
setKeyAttributes(keyAttributes);
}
}, []);
const verifyPassphrase = async (values: formValues, { setFieldError }: FormikHelpers<formValues>) => {
setLoading(true);
try {
const { passphrase } = values;
const kek = await hash(strToUint8(passphrase), base64ToUint8(keyAttributes.kekSalt));
const kekHash = await hash(base64ToUint8(kek), base64ToUint8(keyAttributes.kekHashSalt));
if (kekHash === keyAttributes.kekHash) {
const key = await decrypt(keyAttributes.encryptedKey, kek, keyAttributes.encryptedKeyIV);
context.setKey(key);
router.push('/gallery');
} else {
setFieldError('passphrase', constants.INCORRECT_PASSPHRASE);
}
} catch (e) {
setFieldError('passphrase', `${constants.UNKNOWN_ERROR} ${e.message}`);
}
setLoading(false);
}
return (<Container>
<Image src='/vault.svg' />
<Card style={{ minWidth: '300px'}}>
<Card.Body>
<p className="text-center">{constants.ENTER_PASSPHRASE}</p>
<Formik<formValues>
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}
/>
<Form.Control.Feedback type="invalid">
{errors.passphrase}
</Form.Control.Feedback>
</Form.Group>
<Button block type='submit' disabled={loading}>{constants.SET_PASSPHRASE}</Button>
</Form>
)}
</Formik>
</Card.Body>
</Card>
</Container>)
}

View file

@ -0,0 +1,35 @@
import React, { useContext, useEffect } from 'react';
import Link from 'next/link';
import { AppContext } from 'pages/_app';
import { useRouter } from 'next/router';
import Container from 'components/Container';
import Card from 'react-bootstrap/Card';
import Button from 'react-bootstrap/Button';
import { clearData } from 'utils/sessionStorage';
export default function Gallery() {
const context = useContext(AppContext);
const router = useRouter();
useEffect(() => {
if (!context.key) {
router.push("/");
}
}, []);
const logout = () => {
context.setKey(null);
clearData();
router.push('/');
}
return (<Container>
<Card className="text-center">
<Card.Body>
Imagine a very nice and secure gallery of your memories here.<br/>
<br/>
<Button block onClick={logout}>Logout</Button>
</Card.Body>
</Card>
</Container>);
}

View file

@ -0,0 +1,126 @@
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 { secureRandomString, strToUint8, base64ToUint8, binToBase64 } from 'utils/crypto/common';
import { hash } from 'utils/crypto/scrypt';
import { encrypt } from 'utils/crypto/aes';
import { putKeyAttributes } from 'services/userService';
import { getData, SESSION_KEYS } from 'utils/sessionStorage';
import { useRouter } from 'next/router';
import { AppContext } from 'pages/_app';
const Image = styled.img`
width: 200px;
margin-bottom: 20px;
max-width: 100%;
`;
interface formValues {
passphrase: string;
confirm: string;
}
export default function Generate() {
const [loading, setLoading] = useState(false);
const [token, setToken] = useState<string>();
const router = useRouter();
const context = useContext(AppContext);
useEffect(() => {
const user = getData(SESSION_KEYS.USER);
if (!user?.token) {
router.push("/");
} else if (context.key) {
router.push('/gallery');
} else {
setToken(user.token);
}
}, []);
const onSubmit = async (values: formValues, { setFieldError }: FormikHelpers<formValues>) => {
setLoading(true);
try {
const { passphrase, confirm } = values;
if (passphrase === confirm) {
const key = secureRandomString(32);
const kekSalt = secureRandomString(32);
const kek = await hash(strToUint8(passphrase), base64ToUint8(kekSalt));
const kekHashSalt = secureRandomString(32);
const kekHash = await hash(base64ToUint8(kek), base64ToUint8(kekHashSalt));
const encryptedKeyIV = secureRandomString(16);
const encryptedKey = await encrypt(key, kek, encryptedKeyIV);
await putKeyAttributes(token, {
kekSalt, kekHashSalt, kekHash,
encryptedKeyIV, encryptedKey,
});
context.setKey(key);
router.push('/gallery');
} else {
setFieldError('confirm', constants.PASSPHRASE_MATCH_ERROR);
}
} catch (e) {
setFieldError('passphrase', `${constants.UNKNOWN_ERROR} ${e.message}`);
}
setLoading(false);
}
return (<Container>
<Image src='/vault.svg' />
<Card>
<Card.Body>
<div className="text-center">
<p>{constants.ENTER_ENC_PASSPHRASE}</p>
<p>{constants.PASSPHRASE_DISCLAIMER()}</p>
</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="text"
placeholder={constants.PASSPHRASE_HINT}
value={values.passphrase}
onChange={handleChange('passphrase')}
onBlur={handleBlur('passphrase')}
isInvalid={Boolean(touched.passphrase && errors.passphrase)}
disabled={loading}
/>
<Form.Control.Feedback type='invalid'>
{errors.passphrase}
</Form.Control.Feedback>
</Form.Group>
<Form.Group>
<Form.Control
type="text"
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}>{constants.SET_PASSPHRASE}</Button>
</Form>
)}
</Formik>
</Card.Body>
</Card>
</Container>)
}

View file

@ -1,38 +1,88 @@
import React from 'react';
import styled, { css } from 'styled-components';
import React, { useState, useEffect } from 'react';
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, SESSION_KEYS, getData } from 'utils/sessionStorage';
const Container = styled.div`
flex: 1;
display: flex;
align-items: center;
justify-content: center;
`;
interface formValues {
email: string;
}
export default function Home() {
return (
<Container>
<Card style={{ minWidth: '300px' }}>
<Card.Body>
<Card.Title>
Login
</Card.Title>
<Form>
<Form.Group controlId="formBasicEmail">
<Form.Label>Email address</Form.Label>
<Form.Control type="email" placeholder="Enter email" />
<Form.Text className="text-muted">
We'll never share your email with anyone else.
</Form.Text>
</Form.Group>
<Button variant="primary" type="submit" block>
Submit
</Button>
</Form>
</Card.Body>
</Card>
</Container>
)
const [loading, setLoading] = useState(false);
const router = useRouter();
useEffect(() => {
const user = getData(SESSION_KEYS.USER);
if (user?.email) {
router.push('/verify');
}
}, []);
const loginUser = async ({ email }: formValues, { setFieldError }: FormikHelpers<formValues>) => {
try {
setLoading(true);
await getOtt(email);
setData(SESSION_KEYS.USER, { email });
router.push('/verify');
} catch (e) {
setFieldError('email', `${constants.UNKNOWN_ERROR} ${e.message}`);
}
setLoading(false);
}
return (
<Container>
<Card style={{ minWidth: '300px' }}>
<Card.Body>
<Card.Title>{constants.LOGIN}</Card.Title>
<Formik<formValues>
initialValues={{email: ''}}
validationSchema={Yup.object().shape({
email: Yup.string()
.email(constants.EMAIL_ERROR)
.required(constants.REQUIRED)
})}
onSubmit={loginUser}
>
{({values, errors, touched, handleChange, handleBlur, handleSubmit}) => (
<Form noValidate onSubmit={handleSubmit}>
<Form.Group controlId="formBasicEmail">
<Form.Label>{constants.EMAIL}</Form.Label>
<Form.Control
type="email"
placeholder={constants.ENTER_EMAIL}
value={values.email}
onChange={handleChange('email')}
onBlur={handleBlur('email')}
isInvalid={Boolean(touched.email && errors.email)}
disabled={loading}
/>
<FormControl.Feedback type="invalid">
{errors.email}
</FormControl.Feedback>
<Form.Text className="text-muted">
{constants.EMAIL_DISCLAIMER}
</Form.Text>
</Form.Group>
<Button
variant="primary" type="submit" block
disabled={loading}
>
{constants.SUBMIT}
</Button>
</Form>
)}
</Formik>
</Card.Body>
</Card>
</Container>
)
}

119
src/pages/verify/index.tsx Normal file
View file

@ -0,0 +1,119 @@
import React, { useState, useEffect } from 'react';
import Container from 'components/Container';
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 styled from 'styled-components';
import { SESSION_KEYS, getData, setData } from 'utils/sessionStorage';
import { useRouter } from 'next/router';
import { Formik, FormikHelpers } from 'formik';
import * as Yup from 'yup';
import { verifyOtt, getOtt } from 'services/userService';
const Image = styled.img`
width: 350px;
margin-bottom: 20px;
max-width: 100%;
`;
interface formValues {
ott: string;
}
export default function Verify() {
const [email, setEmail] = useState('');
const [loading, setLoading] = useState(false);
const [resend, setResend] = useState(0);
const router = useRouter();
useEffect(() => {
const user = getData(SESSION_KEYS.USER);
if (!user?.email) {
router.push("/");
} else if (user.token) {
router.push("/credentials")
} else {
setEmail(user.email);
}
}, []);
const onSubmit = async ({ ott }: formValues, { setFieldError }: FormikHelpers<formValues>) => {
try {
setLoading(true);
const resp = await verifyOtt(email, ott);
setData(SESSION_KEYS.USER, {
email,
token: resp.data.token,
});
setData(SESSION_KEYS.KEY_ATTRIBUTES, resp.data.keyAttributes);
if (resp.data.keyAttributes?.encryptedKey) {
router.push("/credentials");
} else {
router.push("/generate");
}
} catch (e) {
if (e?.response?.status === 401) {
setFieldError('ott', constants.INVALID_CODE);
} else {
setFieldError('ott', `${constants.UNKNOWN_ERROR} ${e.message}`);
}
}
setLoading(false);
}
const resendEmail = async () => {
setResend(1);
const resp = await getOtt(email);
setResend(2);
setTimeout(() => setResend(0), 3000);
}
if (!email) {
return null;
}
return (<Container>
<Image src='/email_sent.svg' />
<Card style={{ minWidth: '300px' }} className="text-center">
<Card.Body>
<Card.Title>{constants.VERIFY_EMAIL}</Card.Title>
{constants.EMAIL_SENT({ email })}
{constants.CHECK_INBOX}<br />
<br/>
<Formik<formValues>
initialValues={{ ott: '' }}
validationSchema={Yup.object().shape({
ott: Yup.string().required(constants.REQUIRED),
})}
onSubmit={onSubmit}
>
{({ values, touched, errors, handleChange, handleBlur, handleSubmit }) => (
<Form noValidate onSubmit={handleSubmit}>
<Form.Group>
<Form.Control
className="text-center"
type='text'
value={values.ott}
onChange={handleChange('ott')}
onBlur={handleBlur('ott')}
isInvalid={Boolean(touched.ott && errors.ott)}
placeholder={constants.ENTER_OTT}
disabled={loading}
/>
<Form.Control.Feedback type='invalid'>
{errors.ott}
</Form.Control.Feedback>
</Form.Group>
<Button type="submit" block disabled={loading}>{constants.VERIFY}</Button>
<br/>
{resend === 0 && <a href="#" onClick={resendEmail}>{constants.RESEND_MAIL}</a>}
{resend === 1 && <span>{constants.SENDING}</span>}
{resend === 2 && <span>{constants.SENT}</span>}
</Form>
)}
</Formik>
</Card.Body>
</Card>
</Container>)
}

139
src/services/HTTPService.ts Normal file
View file

@ -0,0 +1,139 @@
import axios, { AxiosRequestConfig } from 'axios';
interface IHTTPHeaders {
[headerKey: string]: string;
}
interface IQueryPrams {
[paramName: string]: string;
}
/**
* Service to manage all HTTP calls.
*/
class HTTPService {
/**
* header object to be appened to all api calls.
*/
private headers: IHTTPHeaders = {
'content-type': 'application/json',
};
/**
* Sets the headers to the given object.
*/
public setHeaders(headers: IHTTPHeaders) {
this.headers = headers;
}
/**
* Adds a header to list of headers.
*/
public appendHeader(key: string, value: string) {
this.headers = {
...this.headers,
[key]: value,
};
}
/**
* Removes the given header.
*/
public removeHeader(key: string) {
this.headers[key] = undefined;
}
/**
* Returns axios interceptors.
*/
// eslint-disable-next-line class-methods-use-this
public getInterceptors() {
return axios.interceptors;
}
/**
* Generic HTTP request.
* This is done so that developer can use any functionality
* provided by axios. Here, only the set heards are spread
* over what was sent in config.
*/
public request(config: AxiosRequestConfig, customConfig?: any) {
// eslint-disable-next-line no-param-reassign
config.headers = {
...this.headers,
...config.headers,
};
return axios({ ...config, ...customConfig });
}
/**
* Get request.
*/
public get(url: string, params?: IQueryPrams, headers?: IHTTPHeaders, customConfig?: any) {
return this.request(
{
headers,
method: 'GET',
params,
url,
},
customConfig,
);
}
/**
* Post request
*/
public post(url: string, data?: any, params?: IQueryPrams,
headers?: IHTTPHeaders, customConfig?: any) {
return this.request(
{
data,
headers,
method: 'POST',
params,
url,
},
customConfig,
);
}
/**
* Put request
*/
public put(url: string, data: any, params?: IQueryPrams,
headers?: IHTTPHeaders, customConfig?: any) {
return this.request(
{
data,
headers,
method: 'PUT',
params,
url,
},
customConfig,
);
}
/**
* Delete request
*/
public delete(url: string, data: any, params?: IQueryPrams,
headers?: IHTTPHeaders, customConfig?: any) {
return this.request(
{
data,
headers,
method: 'DELETE',
params,
url,
},
customConfig,
);
}
}
// Creates a Singleton Service.
// This will help me maintain common headers / functionality
// at a central place.
export default new HTTPService();

View file

@ -0,0 +1,16 @@
import HTTPService from './HTTPService';
import { keyAttributes } from 'types';
export const getOtt = (email: string) => {
return HTTPService.get('/api/users/ott', { email })
}
export const verifyOtt = (email: string, ott: string) => {
return HTTPService.get('/api/users/credentials', { email, ott });
}
export const putKeyAttributes = (token: string, keyAttributes: keyAttributes) => {
return HTTPService.put('/api/users/key-attributes', keyAttributes, null, {
'X-Auth-Token': token,
});
}

7
src/types.ts Normal file
View file

@ -0,0 +1,7 @@
export interface keyAttributes {
kekSalt: string;
kekHash: string;
kekHashSalt: string;
encryptedKey: string;
encryptedKeyIV: string;
};

51
src/utils/crypto/aes.ts Normal file
View file

@ -0,0 +1,51 @@
import { base64ToUint8, binToBase64 } from './common';
/**
* Takes base64 encoded binary data, key and iv and returns
* base64 encoded encrypted binary message.
* @param data
* @param key
* @param iv
*/
export async function encrypt(data: string, key: string, iv: string) {
const cryptoKey = await crypto.subtle.importKey(
'raw', base64ToUint8(key), { name: 'AES-CBC' },
false, ['encrypt', 'decrypt']
);
const result = await window.crypto.subtle.encrypt(
{
name: "AES-CBC",
iv: base64ToUint8(iv),
},
cryptoKey,
base64ToUint8(data),
);
return binToBase64(result);
}
/**
* Takes base64 encoded binary data, key and iv and returns
* base64 encoded decrypted binary message.
* @param data
* @param key
* @param iv
*/
export async function decrypt(data: string, key: string, iv: string) {
const cryptoKey = await crypto.subtle.importKey(
'raw', base64ToUint8(key), { name: 'AES-CBC' },
false, ['encrypt', 'decrypt']
);
const result = await window.crypto.subtle.decrypt(
{
name: "AES-CBC",
iv: base64ToUint8(iv),
},
cryptoKey,
base64ToUint8(data),
);
return binToBase64(result);
}

View file

@ -0,0 +1,25 @@
/**
* Converts base64 encoded string to Uint8 Array.
* @param str
*/
export const base64ToUint8 = (str: string) => Uint8Array.from(atob(str), c => c.charCodeAt(0));
/**
* Converts string to Uint8 Array.
* @param str
*/
export const strToUint8 = (str: string) => Uint8Array.from(str, c => c.charCodeAt(0));
/**
* Converts binary data to base64 encoded string.
* @param bin
*/
export const binToBase64 = (bin: Uint8Array | ArrayBuffer) => btoa(
String.fromCharCode(...new Uint8Array(bin)));
/**
* Generates base64 encoded string of random bytes of given length.
* @param length
*/
export const secureRandomString = (length: number) => binToBase64(
crypto.getRandomValues(new Uint8Array(length)));

View file

@ -0,0 +1,7 @@
import * as scrypt from 'scrypt-js';
import { binToBase64 } from './common';
export const hash = async (passphrase: Uint8Array, salt: Uint8Array) => {
const result = await scrypt.scrypt(passphrase, salt, 16384, 16, 1, 32);
return binToBase64(result);
}

View file

@ -0,0 +1,17 @@
export enum SESSION_KEYS {
USER='user',
SESSION='session',
KEY_ATTRIBUTES='keyAttributes',
}
export const setData = (key: SESSION_KEYS, value: object) => {
sessionStorage.setItem(key, JSON.stringify(value));
}
export const getData = (key: SESSION_KEYS) => {
return JSON.parse(sessionStorage.getItem(key));
}
export const clearData = () => {
sessionStorage.clear();
}

View file

@ -0,0 +1,4 @@
import { getConstantValue } from "./vernacularStrings";
const constants = getConstantValue();
export default constants;

View file

@ -0,0 +1,41 @@
import { template } from "./vernacularStrings";
/**
* Global English constants.
*/
const englishConstants = {
COMPANY_NAME: 'ente',
LOGIN: 'Login',
EMAIL: 'Email Address',
ENTER_EMAIL: 'Enter email',
EMAIL_DISCLAIMER: `We'll never share your email with anyone else.`,
SUBMIT: 'Submit',
EMAIL_ERROR: 'Enter a valid email address',
REQUIRED: 'Required',
VERIFY_EMAIL: 'Verify Email',
EMAIL_SENT: ({ email }) => (<p>We have sent a mail to <b>{email}</b>.</p>),
CHECK_INBOX: 'Please check your inbox (and spam) to complete verification.',
ENTER_OTT: 'Enter verification code here',
RESEND_MAIL: 'Did not get email?',
VERIFY: 'Verify',
UNKNOWN_ERROR: 'Oops! Something went wrong. Please try again.',
INVALID_CODE: 'Invalid verification code',
SENDING: 'Sending...',
SENT: 'Sent! Check again.',
ENTER_PASSPHRASE: 'Please enter your passphrase.',
RETURN_PASSPHRASE_HINT: 'That thing you promised to never forget.',
SET_PASSPHRASE: 'Set Passphrase',
INCORRECT_PASSPHRASE: 'Incorrect Passphrase',
ENTER_ENC_PASSPHRASE: 'Please enter a passphrase that we can use to encrypt your data.',
PASSPHRASE_DISCLAIMER: () => (
<p>
We don't store your passphrase, so if you forget,
<strong> we will not be able to help you</strong> recover your data.
</p>
),
PASSPHRASE_HINT: 'Something you will never forget',
PASSPHRASE_CONFIRM: 'Please repeat it once more',
PASSPHRASE_MATCH_ERROR: `Passphrase didn't match`,
};
export default englishConstants;

View file

@ -1,3 +1,5 @@
import englishConstants from './englishConstants';
/** Enums of supported locale */
export enum locale {
en='en',
@ -48,13 +50,6 @@ export const getLocale = (lang: string) => {
}
};
/**
* Global English constants.
*/
const englishConstants = {
ENTE: 'Ente',
};
/**
* Global constants
*/
@ -66,7 +61,7 @@ const globalConstants: VernacularConstants<typeof englishConstants> = {
* Function to extend global constants with local constants
* @param localConstants
*/
export function getConstantValue<T>(localConstants: VernacularConstants<T>) {
export function getConstantValue<T>(localConstants?: VernacularConstants<T>) {
const searchParam = typeof window !== 'undefined' ? window.location.search : '';
const query = new URLSearchParams(searchParam);
const currLocale = getLocale(query.get('lang'));
@ -74,14 +69,14 @@ export function getConstantValue<T>(localConstants: VernacularConstants<T>) {
if (currLocale !== 'en') {
return {
...globalConstants.en,
...localConstants.en,
...localConstants?.en,
...globalConstants[currLocale],
...localConstants[currLocale],
...localConstants?.[currLocale],
};
}
return {
...globalConstants[currLocale],
...localConstants[currLocale],
...localConstants?.[currLocale],
};
}

View file

@ -17,7 +17,8 @@
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": "./src"
"baseUrl": "./src",
"downlevelIteration": true
},
"include": [
"next-env.d.ts",

154
yarn.lock
View file

@ -965,7 +965,7 @@
"@babel/helper-plugin-utils" "^7.10.4"
"@babel/plugin-transform-typescript" "^7.10.4"
"@babel/runtime@7.11.2", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
"@babel/runtime@7.11.2", "@babel/runtime@^7.10.5", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
version "7.11.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736"
integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==
@ -1093,6 +1093,13 @@
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
"@types/http-proxy@^1.17.4":
version "1.17.4"
resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.4.tgz#e7c92e3dbe3e13aa799440ff42e6d3a17a9d045b"
integrity sha512-IrSHl2u6AWXduUaDLqYpt45tLVCtYv7o4Z0s1KghBCDgIIS9oW5K1H8mZG/A2CfeLdEa7rTd1ACOiHBc1EMT2Q==
dependencies:
"@types/node" "*"
"@types/invariant@^2.2.33":
version "2.2.34"
resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.34.tgz#05e4f79f465c2007884374d4795452f995720bbe"
@ -1103,6 +1110,11 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==
"@types/node@*":
version "14.10.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.10.1.tgz#cc323bad8e8a533d4822f45ce4e5326f36e42177"
integrity sha512-aYNbO+FZ/3KGeQCEkNhHFRIzBOUgc7QvcVNKXbfnhDkSfwUv91JsQQa10rDgKSTSLkXZ1UIyPe4FJJNVgw1xWQ==
"@types/node@^14.6.4":
version "14.6.4"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.6.4.tgz#a145cc0bb14ef9c4777361b7bbafa5cf8e3acb5a"
@ -1150,6 +1162,11 @@
resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52"
integrity sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=
"@types/yup@^0.29.7":
version "0.29.7"
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.7.tgz#80c5e427a3b152e583ac2859767ccf59db0d3a16"
integrity sha512-x3Zeh8/qLZ6fG4S1EztI1S1mLj6N1pSUV1PAj/9finZba48d3Maxtyz4WYNUY0NE76u1KSukfNLkjcRlb+O00g==
"@webassemblyjs/ast@1.9.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964"
@ -1487,6 +1504,13 @@ atob@^2.1.2:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
axios@^0.20.0:
version "0.20.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.20.0.tgz#057ba30f04884694993a8cd07fa394cff11c50bd"
integrity sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==
dependencies:
follow-redirects "^1.10.0"
babel-plugin-dynamic-import-node@^2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3"
@ -1582,6 +1606,11 @@ bn.js@^5.1.1:
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.3.tgz#beca005408f642ebebea80b042b4d18d2ac0ee6b"
integrity sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==
bootstrap@^4.5.2:
version "4.5.2"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.5.2.tgz#a85c4eda59155f0d71186b6e6ad9b875813779ab"
integrity sha512-vlGn0bcySYl/iV+BGA544JkkZP5LB3jsmkeKLFQakCOwCM3AOk7VkldBz4jrzSe+Z0Ezn99NVXa1o45cQY4R6A==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@ -1606,7 +1635,7 @@ braces@^2.3.1, braces@^2.3.2:
split-string "^3.0.2"
to-regex "^3.0.1"
braces@~3.0.2:
braces@^3.0.1, braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
@ -2215,6 +2244,11 @@ decode-uri-component@^0.2.0:
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
deepmerge@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==
define-properties@^1.1.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
@ -2458,6 +2492,11 @@ event-target-shim@^5.0.0:
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
eventemitter3@^4.0.0:
version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
events@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379"
@ -2598,11 +2637,35 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
fn-name@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-3.0.0.tgz#0596707f635929634d791f452309ab41558e3c5c"
integrity sha512-eNMNr5exLoavuAMhIUVsOKF79SWd/zG104ef6sxBTSw+cZc6BXdQXDvYcGvp0VbxVVSp1XDUNoz7mg1xMtSznA==
follow-redirects@^1.0.0, follow-redirects@^1.10.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
formik@^2.1.5:
version "2.1.5"
resolved "https://registry.yarnpkg.com/formik/-/formik-2.1.5.tgz#de5bbbe35543fa6d049fe96b8ee329d6cd6892b8"
integrity sha512-bWpo3PiqVDYslvrRjTq0Isrm0mFXHiO33D8MS6t6dWcqSFGeYF52nlpCM2xwOJ6tRVRznDkL+zz/iHPL4LDuvQ==
dependencies:
deepmerge "^2.1.1"
hoist-non-react-statics "^3.3.0"
lodash "^4.17.14"
lodash-es "^4.17.14"
react-fast-compare "^2.0.1"
scheduler "^0.18.0"
tiny-warning "^1.0.2"
tslib "^1.10.0"
fragment-cache@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
@ -2799,6 +2862,26 @@ htmlparser2@4.1.0:
domutils "^2.0.0"
entities "^2.0.0"
http-proxy-middleware@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-1.0.5.tgz#4c6e25d95a411e3d750bc79ccf66290675176dc2"
integrity sha512-CKzML7u4RdGob8wuKI//H8Ein6wNTEQR7yjVEzPbhBLGdOfkfvgTnp2HLnniKBDP9QW4eG10/724iTWLBeER3g==
dependencies:
"@types/http-proxy" "^1.17.4"
http-proxy "^1.18.1"
is-glob "^4.0.1"
lodash "^4.17.19"
micromatch "^4.0.2"
http-proxy@^1.18.1:
version "1.18.1"
resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
dependencies:
eventemitter3 "^4.0.0"
follow-redirects "^1.0.0"
requires-port "^1.0.0"
https-browserify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
@ -3151,7 +3234,7 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
lodash-es@^4.17.15:
lodash-es@^4.17.11, lodash-es@^4.17.14, lodash-es@^4.17.15:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
@ -3161,7 +3244,7 @@ lodash.sortby@^4.7.0:
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19:
lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19:
version "4.17.20"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
@ -3263,6 +3346,14 @@ micromatch@^3.1.10, micromatch@^3.1.4:
snapdragon "^0.8.1"
to-regex "^3.0.2"
micromatch@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"
integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==
dependencies:
braces "^3.0.1"
picomatch "^2.0.5"
miller-rabin@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
@ -3722,7 +3813,7 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
picomatch@^2.0.4, picomatch@^2.2.1:
picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1:
version "2.2.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
@ -3867,6 +3958,11 @@ prop-types@15.7.2, prop-types@^15.6.2, prop-types@^15.7.2:
object-assign "^4.1.1"
react-is "^16.8.1"
property-expr@^2.0.2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.4.tgz#37b925478e58965031bb612ec5b3260f8241e910"
integrity sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg==
prr@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
@ -3983,6 +4079,11 @@ react-dom@16.13.1:
prop-types "^15.6.2"
scheduler "^0.19.1"
react-fast-compare@^2.0.1:
version "2.0.4"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
react-is@16.13.1, react-is@^16.3.2, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@ -4145,6 +4246,11 @@ repeat-string@^1.6.1:
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
resolve-url-loader@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.1.tgz#28931895fa1eab9be0647d3b2958c100ae3c0bf0"
@ -4246,6 +4352,14 @@ sass-loader@8.0.2:
schema-utils "^2.6.1"
semver "^6.3.0"
scheduler@^0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.18.0.tgz#5901ad6659bc1d8f3fdaf36eb7a67b0d6746b1c4"
integrity sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
scheduler@^0.19.1:
version "0.19.1"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196"
@ -4600,6 +4714,11 @@ supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
synchronous-promise@^2.0.13:
version "2.0.13"
resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.13.tgz#9d8c165ddee69c5a6542862b405bc50095926702"
integrity sha512-R9N6uDkVsghHePKh1TEqbnLddO2IY25OcsksyFp/qBe7XYd0PVbKEWxhcdMhpLzE1I6skj5l4aEZ3CRxcbArlA==
tapable@^1.0.0, tapable@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
@ -4644,6 +4763,11 @@ timers-browserify@^2.0.4:
dependencies:
setimmediate "^1.0.4"
tiny-warning@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
to-arraybuffer@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
@ -4686,6 +4810,11 @@ to-regex@^3.0.1, to-regex@^3.0.2:
regex-not "^1.0.2"
safe-regex "^1.1.0"
toposort@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=
tr46@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
@ -4703,7 +4832,7 @@ ts-pnp@^1.1.6:
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==
tslib@^1.9.0:
tslib@^1.10.0, tslib@^1.9.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
@ -4989,3 +5118,16 @@ yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yup@^0.29.3:
version "0.29.3"
resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.3.tgz#69a30fd3f1c19f5d9e31b1cf1c2b851ce8045fea"
integrity sha512-RNUGiZ/sQ37CkhzKFoedkeMfJM0vNQyaz+wRZJzxdKE7VfDeVKH8bb4rr7XhRLbHJz5hSjoDNwMEIaKhuMZ8gQ==
dependencies:
"@babel/runtime" "^7.10.5"
fn-name "~3.0.0"
lodash "^4.17.15"
lodash-es "^4.17.11"
property-expr "^2.0.2"
synchronous-promise "^2.0.13"
toposort "^2.0.2"