diff --git a/.babelrc b/.babelrc new file mode 100644 index 000000000..cfae37f6e --- /dev/null +++ b/.babelrc @@ -0,0 +1,15 @@ +{ + "presets": [ + "next/babel" + ], + "plugins": [ + [ + "styled-components", + { + "ssr": true, + "displayName": true, + "preprocess": false + } + ] + ] +} \ No newline at end of file diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index ad8a32c0a..162604faf 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,8 +1,12 @@ -import React, { useState, createContext } from 'react'; +import React, { useEffect, useState } 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'; +import Button from 'react-bootstrap/Button'; +import { clearKeys } from 'utils/storage/sessionStorage'; +import { clearData, getData, LS_KEYS } from 'utils/storage/localStorage'; +import { useRouter } from 'next/router'; const GlobalStyles = createGlobalStyle` html, body { @@ -37,23 +41,45 @@ const Image = styled.img` margin-right: 5px; `; -export interface IAppContext { - key: string; - setKey: (key: string) => void -} - -export const AppContext = createContext(null); +const FlexContainer = styled.div` + flex: 1; +`; export default function App({ Component, pageProps }) { - const [key, setKey] = useState(); + const router = useRouter(); + const [user, setUser] = useState(); + + useEffect(() => { + const user = getData(LS_KEYS.USER); + setUser(user); + console.log(`%c${constants.CONSOLE_WARNING_STOP}`, 'color: red; font-size: 52px;'); + console.log(`%c${constants.CONSOLE_WARNING_DESC}`, 'font-size: 20px;'); + + router.events.on('routeChangeComplete', () => { + const user = getData(LS_KEYS.USER); + setUser(user); + }); + }, []); + + const logout = () => { + clearKeys(); + clearData(); + router.push("/"); + } + return ( - + <> - - {constants.COMPANY_NAME} + + + {constants.COMPANY_NAME} + + {user && } - + ); } \ No newline at end of file diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx index d6117d9e3..4e9a4317e 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.tsx @@ -29,6 +29,7 @@ export default class MyDocument extends Document { sheet.seal() } } + render() { return ( diff --git a/src/pages/credentials/index.tsx b/src/pages/credentials/index.tsx index 901e7dab5..c826172a1 100644 --- a/src/pages/credentials/index.tsx +++ b/src/pages/credentials/index.tsx @@ -6,14 +6,14 @@ 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 { getData, LS_KEYS, setData } from 'utils/storage/localStorage'; 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 { strToUint8, base64ToUint8, secureRandomString } from 'utils/crypto/common'; +import { decrypt, encrypt } from 'utils/crypto/aes'; import { keyAttributes } from 'types'; +import { setKey, SESSION_KEYS, getKey } from 'utils/storage/sessionStorage'; const Image = styled.img` width: 200px; @@ -29,16 +29,16 @@ export default function Credentials() { const router = useRouter(); const [keyAttributes, setKeyAttributes] = useState(); const [loading, setLoading] = useState(false); - const context = useContext(AppContext); useEffect(() => { - const user = getData(SESSION_KEYS.USER); - const keyAttributes = getData(SESSION_KEYS.KEY_ATTRIBUTES); + const user = getData(LS_KEYS.USER); + const keyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES); + const key = getKey(SESSION_KEYS.ENCRYPTION_KEY); if (!user?.token) { router.push('/'); } else if (!keyAttributes) { router.push('/generate'); - } else if (context.key) { + } else if (key) { router.push('/gallery') } else { setKeyAttributes(keyAttributes); @@ -54,7 +54,11 @@ export default function Credentials() { if (kekHash === keyAttributes.kekHash) { const key = await decrypt(keyAttributes.encryptedKey, kek, keyAttributes.encryptedKeyIV); - context.setKey(key); + const sessionKey = secureRandomString(32); + const sessionIV = secureRandomString(16); + const encryptionKey = await encrypt(key, sessionKey, sessionIV); + setKey(SESSION_KEYS.ENCRYPTION_KEY, { encryptionKey }); + setData(LS_KEYS.SESSION, { sessionKey, sessionIV }); router.push('/gallery'); } else { setFieldError('passphrase', constants.INCORRECT_PASSPHRASE); @@ -93,7 +97,7 @@ export default function Credentials() { {errors.passphrase} - + )} diff --git a/src/pages/gallery/index.tsx b/src/pages/gallery/index.tsx index 510d2776d..f2ab5963c 100644 --- a/src/pages/gallery/index.tsx +++ b/src/pages/gallery/index.tsx @@ -1,24 +1,23 @@ 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'; +import { clearData } from 'utils/storage/localStorage'; +import { clearKeys, getKey, SESSION_KEYS } from 'utils/storage/sessionStorage'; export default function Gallery() { - const context = useContext(AppContext); const router = useRouter(); useEffect(() => { - if (!context.key) { + const key = getKey(SESSION_KEYS.ENCRYPTION_KEY); + if (!key) { router.push("/"); } }, []); const logout = () => { - context.setKey(null); + clearKeys(); clearData(); router.push('/'); } diff --git a/src/pages/generate/index.tsx b/src/pages/generate/index.tsx index 000dd069f..60b19fb33 100644 --- a/src/pages/generate/index.tsx +++ b/src/pages/generate/index.tsx @@ -11,9 +11,9 @@ import { secureRandomString, strToUint8, base64ToUint8, binToBase64 } from 'util 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 { getData, LS_KEYS, setData } from 'utils/storage/localStorage'; import { useRouter } from 'next/router'; -import { AppContext } from 'pages/_app'; +import { getKey, SESSION_KEYS, setKey } from 'utils/storage/sessionStorage'; const Image = styled.img` width: 200px; @@ -30,13 +30,13 @@ export default function Generate() { const [loading, setLoading] = useState(false); const [token, setToken] = useState(); const router = useRouter(); - const context = useContext(AppContext); + const key = getKey(SESSION_KEYS.ENCRYPTION_KEY); useEffect(() => { - const user = getData(SESSION_KEYS.USER); + const user = getData(LS_KEYS.USER); if (!user?.token) { router.push("/"); - } else if (context.key) { + } else if (key) { router.push('/gallery'); } else { setToken(user.token); @@ -55,11 +55,17 @@ export default function Generate() { const kekHash = await hash(base64ToUint8(kek), base64ToUint8(kekHashSalt)); const encryptedKeyIV = secureRandomString(16); const encryptedKey = await encrypt(key, kek, encryptedKeyIV); - await putKeyAttributes(token, { + const keyAttributes = { kekSalt, kekHashSalt, kekHash, encryptedKeyIV, encryptedKey, - }); - context.setKey(key); + }; + await putKeyAttributes(token, keyAttributes); + setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes); + const sessionKey = secureRandomString(32); + const sessionIV = secureRandomString(16); + const encryptionKey = await encrypt(key, sessionKey, sessionIV); + setKey(SESSION_KEYS.ENCRYPTION_KEY, { encryptionKey }); + setData(LS_KEYS.SESSION, { sessionKey, sessionIV }); router.push('/gallery'); } else { setFieldError('confirm', constants.PASSPHRASE_MATCH_ERROR); diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 691f23395..0b1f5a0ee 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -9,7 +9,7 @@ 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'; +import { setData, LS_KEYS, getData } from 'utils/storage/localStorage'; interface formValues { email: string; @@ -20,7 +20,7 @@ export default function Home() { const router = useRouter(); useEffect(() => { - const user = getData(SESSION_KEYS.USER); + const user = getData(LS_KEYS.USER); if (user?.email) { router.push('/verify'); } @@ -30,7 +30,7 @@ export default function Home() { try { setLoading(true); await getOtt(email); - setData(SESSION_KEYS.USER, { email }); + setData(LS_KEYS.USER, { email }); router.push('/verify'); } catch (e) { setFieldError('email', `${constants.UNKNOWN_ERROR} ${e.message}`); diff --git a/src/pages/verify/index.tsx b/src/pages/verify/index.tsx index 457dc7a5f..a96177ff6 100644 --- a/src/pages/verify/index.tsx +++ b/src/pages/verify/index.tsx @@ -5,7 +5,7 @@ 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 { LS_KEYS, getData, setData } from 'utils/storage/localStorage'; import { useRouter } from 'next/router'; import { Formik, FormikHelpers } from 'formik'; import * as Yup from 'yup'; @@ -28,7 +28,7 @@ export default function Verify() { const router = useRouter(); useEffect(() => { - const user = getData(SESSION_KEYS.USER); + const user = getData(LS_KEYS.USER); if (!user?.email) { router.push("/"); } else if (user.token) { @@ -42,11 +42,11 @@ export default function Verify() { try { setLoading(true); const resp = await verifyOtt(email, ott); - setData(SESSION_KEYS.USER, { + setData(LS_KEYS.USER, { email, token: resp.data.token, }); - setData(SESSION_KEYS.KEY_ATTRIBUTES, resp.data.keyAttributes); + setData(LS_KEYS.KEY_ATTRIBUTES, resp.data.keyAttributes); if (resp.data.keyAttributes?.encryptedKey) { router.push("/credentials"); } else { diff --git a/src/utils/sessionStorage.ts b/src/utils/sessionStorage.ts deleted file mode 100644 index 9a542aa16..000000000 --- a/src/utils/sessionStorage.ts +++ /dev/null @@ -1,17 +0,0 @@ -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(); -} diff --git a/src/utils/storage/localStorage.ts b/src/utils/storage/localStorage.ts new file mode 100644 index 000000000..7eb54c9a9 --- /dev/null +++ b/src/utils/storage/localStorage.ts @@ -0,0 +1,17 @@ +export enum LS_KEYS { + USER='user', + SESSION='session', + KEY_ATTRIBUTES='keyAttributes', +} + +export const setData = (key: LS_KEYS, value: object) => { + localStorage.setItem(key, JSON.stringify(value)); +} + +export const getData = (key: LS_KEYS) => { + return JSON.parse(localStorage.getItem(key)); +} + +export const clearData = () => { + localStorage.clear(); +} diff --git a/src/utils/storage/sessionStorage.ts b/src/utils/storage/sessionStorage.ts new file mode 100644 index 000000000..75cdd712c --- /dev/null +++ b/src/utils/storage/sessionStorage.ts @@ -0,0 +1,15 @@ +export enum SESSION_KEYS { + ENCRYPTION_KEY='encryptionKey', +} + +export const setKey = (key: SESSION_KEYS, value: object) => { + sessionStorage.setItem(key, JSON.stringify(value)); +} + +export const getKey = (key: SESSION_KEYS) => { + return JSON.parse(sessionStorage.getItem(key)); +} + +export const clearKeys = () => { + sessionStorage.clear(); +} diff --git a/src/utils/strings/englishConstants.tsx b/src/utils/strings/englishConstants.tsx index d18dbfe84..922136f18 100644 --- a/src/utils/strings/englishConstants.tsx +++ b/src/utils/strings/englishConstants.tsx @@ -25,6 +25,7 @@ const englishConstants = { ENTER_PASSPHRASE: 'Please enter your passphrase.', RETURN_PASSPHRASE_HINT: 'That thing you promised to never forget.', SET_PASSPHRASE: 'Set Passphrase', + VERIFY_PASSPHRASE: 'Verify Passphrase', INCORRECT_PASSPHRASE: 'Incorrect Passphrase', ENTER_ENC_PASSPHRASE: 'Please enter a passphrase that we can use to encrypt your data.', PASSPHRASE_DISCLAIMER: () => ( @@ -36,6 +37,8 @@ const englishConstants = { PASSPHRASE_HINT: 'Something you will never forget', PASSPHRASE_CONFIRM: 'Please repeat it once more', PASSPHRASE_MATCH_ERROR: `Passphrase didn't match`, + CONSOLE_WARNING_STOP: 'STOP!', + CONSOLE_WARNING_DESC: `This is a browser feature intended for developers. If someone told you to copy-paste something here to enable a feature or "hack" someone's account, it is a scam and will give them access to your account.` }; export default englishConstants;