From e9d61b133418cbe9bec38ef9ac2ad0253c61d413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Malak?= Date: Mon, 8 Nov 2021 17:46:16 +0100 Subject: [PATCH] Auth error handling. Auth redirecting --- client/src/App.tsx | 29 +++++++++++++++++++--------- client/src/containers/Auth.tsx | 8 ++++++-- client/src/utils/ProtectedRoute.tsx | 9 ++++++++- client/src/utils/authErrorHandler.ts | 20 +++++++++++++++++++ client/src/utils/errorHandler.ts | 2 ++ client/src/utils/index.ts | 2 ++ docker-compose.yml | 2 ++ list.md | 13 +++++++++++++ src/middleware/errorHandler.ts | 14 ++++++++++++++ src/utils/index.ts | 1 + src/utils/resourceExists.ts | 17 ++++++++++++++++ 11 files changed, 105 insertions(+), 12 deletions(-) create mode 100644 client/src/utils/authErrorHandler.ts create mode 100644 list.md create mode 100644 src/utils/resourceExists.ts diff --git a/client/src/App.tsx b/client/src/App.tsx index 6a066b2..23acef2 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -2,19 +2,30 @@ import { Fragment, useContext, useEffect } from 'react'; import { Switch, Route } from 'react-router-dom'; import { Navbar } from './components/Navigation/Navbar'; import { Editor, Home, Snippet, Snippets, Auth, Profile } from './containers'; -import { ProtectedRoute } from './utils'; -import { AuthContext } from './store'; +import { decodeToken, ProtectedRoute } from './utils'; +import { AuthContext, SnippetsContext } from './store'; export const App = () => { - const { autoLogin } = useContext(AuthContext); + const { autoLogin, logout } = useContext(AuthContext); + const { getSnippets, countTags } = useContext(SnippetsContext); useEffect(() => { - // autoLogin(); - // const checker = setInterval(() => { - // autoLogin(); - // console.log('cake'); - // }, 1000); - // return () => window.clearInterval(checker); + autoLogin(); + + getSnippets(); + countTags(); + + const checkTokenValidity = setInterval(() => { + if (localStorage.token) { + const { exp: expiresAt } = decodeToken(localStorage.token); + + if (Date.now() > expiresAt * 1000) { + logout(); + } + } + }, 1000); + + return () => window.clearInterval(checkTokenValidity); }, []); return ( diff --git a/client/src/containers/Auth.tsx b/client/src/containers/Auth.tsx index 2a7a78a..d437994 100644 --- a/client/src/containers/Auth.tsx +++ b/client/src/containers/Auth.tsx @@ -1,5 +1,5 @@ import { useContext, useEffect } from 'react'; -import { useHistory } from 'react-router'; +import { useHistory, useLocation } from 'react-router'; import { AuthContext } from '../store'; import { AuthForm } from '../components/Auth'; import { Card, Layout } from '../components/UI'; @@ -8,9 +8,13 @@ export const Auth = (): JSX.Element => { const { isAuthenticated } = useContext(AuthContext); const history = useHistory(); + // Get previous location + const location = useLocation<{ from: string }>(); + const { from } = location.state || '/'; + useEffect(() => { if (isAuthenticated) { - history.push('/'); + history.push(from); } }, [isAuthenticated]); diff --git a/client/src/utils/ProtectedRoute.tsx b/client/src/utils/ProtectedRoute.tsx index 54a5d13..caaafd6 100644 --- a/client/src/utils/ProtectedRoute.tsx +++ b/client/src/utils/ProtectedRoute.tsx @@ -10,6 +10,13 @@ export const ProtectedRoute = ({ ...rest }: RouteProps) => { if (isAuthenticated) { return ; } else { - return ; + return ( + + ); } }; diff --git a/client/src/utils/authErrorHandler.ts b/client/src/utils/authErrorHandler.ts new file mode 100644 index 0000000..68fbfa6 --- /dev/null +++ b/client/src/utils/authErrorHandler.ts @@ -0,0 +1,20 @@ +import { errorHandler } from '.'; +import { UserWithRole } from '../typescript/interfaces'; + +interface Params { + err: any; + setIsAuthenticated: (v: React.SetStateAction) => void; + setUser: (v: React.SetStateAction) => void; +} + +export const authErrorHandler = (params: Params) => { + const { err, setUser, setIsAuthenticated } = params; + + errorHandler(err); + + localStorage.removeItem('token'); + + setUser(null); + + setIsAuthenticated(false); +}; diff --git a/client/src/utils/errorHandler.ts b/client/src/utils/errorHandler.ts index 98a1159..f102fad 100644 --- a/client/src/utils/errorHandler.ts +++ b/client/src/utils/errorHandler.ts @@ -11,5 +11,7 @@ export const errorHandler = (err: any) => { msg = 'Something went wrong'; } + // todo: emit notification + // redirect on error console.log(msg); }; diff --git a/client/src/utils/index.ts b/client/src/utils/index.ts index 9327a09..c64769d 100644 --- a/client/src/utils/index.ts +++ b/client/src/utils/index.ts @@ -3,4 +3,6 @@ export * from './badgeColor'; export * from './findLanguage'; export * from './searchParser'; export * from './ProtectedRoute'; +export * from './authErrorHandler'; export * from './errorHandler'; +export * from './decodeToken'; diff --git a/docker-compose.yml b/docker-compose.yml index fd3c750..74d9e30 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,4 +7,6 @@ services: - /path/to/host/data:/app/data ports: - 5000:5000 + environment: + - NODE_ENV: production restart: unless-stopped diff --git a/list.md b/list.md new file mode 100644 index 0000000..aeb01a9 --- /dev/null +++ b/list.md @@ -0,0 +1,13 @@ +**Implemented** + +- [x] Login / logout / register +- [x] Auto login (token valid 14 days) +- [x] Roles (admin, user) + +**Planned features** + +- [ ] Guest access (read and search access to snippets marked as public) +- [ ] Generate token for raw snippet access +- [ ] Admin panel (right now admin and users share the same view) + +By default, admin account is created with login `admin@local.com` and password `snippet-admin`. If there are any snippets in the database, they will be assigned to the admin as the owner. diff --git a/src/middleware/errorHandler.ts b/src/middleware/errorHandler.ts index bdc7591..4cfedfc 100644 --- a/src/middleware/errorHandler.ts +++ b/src/middleware/errorHandler.ts @@ -11,6 +11,20 @@ export const errorHandler = ( ) => { logger.log(err.message, 'ERROR'); + if (process.env.NODE_ENV == 'development') { + console.log(err); + } + + const error = { + ...err + }; + + if (err.message == 'Validation error') { + // @ts-ignore + error.message = err.errors[0].message; + error.statusCode = 400; + } + res.status(err.statusCode || 500).json({ error: err.message || 'Internal Server Error' }); diff --git a/src/utils/index.ts b/src/utils/index.ts index 5a404b8..0d2cd26 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -7,3 +7,4 @@ export * from './createAdmin'; export * from './hashPassword'; export * from './signToken'; export * from './createAdmin'; +export * from './resourceExists'; diff --git a/src/utils/resourceExists.ts b/src/utils/resourceExists.ts new file mode 100644 index 0000000..4bfaf6e --- /dev/null +++ b/src/utils/resourceExists.ts @@ -0,0 +1,17 @@ +import { NextFunction } from 'express'; +import { ErrorResponse } from '.'; + +export const resourceExists = ( + resource: any, + name: string, + id: number, + next: NextFunction +) => { + if (!resource) { + return next( + new ErrorResponse(404, `${name} with the id of ${id} was not found`) + ); + } + + return; +};