Using local and session storage to maintain session.
+ Fixed SSR of styled components. + Added warninig message in console.
This commit is contained in:
parent
2e10ea441e
commit
5478a2e8a1
12 changed files with 129 additions and 60 deletions
15
.babelrc
Normal file
15
.babelrc
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"presets": [
|
||||
"next/babel"
|
||||
],
|
||||
"plugins": [
|
||||
[
|
||||
"styled-components",
|
||||
{
|
||||
"ssr": true,
|
||||
"displayName": true,
|
||||
"preprocess": false
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
|
@ -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<IAppContext>(null);
|
||||
const FlexContainer = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
const [key, setKey] = useState<string>();
|
||||
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 (
|
||||
<AppContext.Provider value={{ key, setKey }}>
|
||||
<>
|
||||
<GlobalStyles />
|
||||
<Navbar>
|
||||
<Image src="/icon.png" />
|
||||
{constants.COMPANY_NAME}
|
||||
<FlexContainer>
|
||||
<Image src="/icon.png" />
|
||||
{constants.COMPANY_NAME}
|
||||
</FlexContainer>
|
||||
{user && <Button variant='link' onClick={logout}>
|
||||
<span className="material-icons">power_settings_new</span>
|
||||
</Button>}
|
||||
</Navbar>
|
||||
<Component />
|
||||
</AppContext.Provider>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -29,6 +29,7 @@ export default class MyDocument extends Document {
|
|||
sheet.seal()
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Html>
|
||||
|
|
|
@ -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<keyAttributes>();
|
||||
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}
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Button block type='submit' disabled={loading}>{constants.SET_PASSPHRASE}</Button>
|
||||
<Button block type='submit' disabled={loading}>{constants.VERIFY_PASSPHRASE}</Button>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
|
|
|
@ -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('/');
|
||||
}
|
||||
|
|
|
@ -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<string>();
|
||||
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);
|
||||
|
|
|
@ -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}`);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
17
src/utils/storage/localStorage.ts
Normal file
17
src/utils/storage/localStorage.ts
Normal file
|
@ -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();
|
||||
}
|
15
src/utils/storage/sessionStorage.ts
Normal file
15
src/utils/storage/sessionStorage.ts
Normal file
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue