From 019872ff3169049a2982ae54c1bb44f86450ccb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Salgado?= Date: Wed, 12 May 2021 19:40:11 +0100 Subject: [PATCH] feat: add authenticated routing to pazuz project --- new-lamassu-admin/src/pazuz/App.js | 95 ++++++++------- .../src/pazuz/apollo/Provider.js | 28 ++++- new-lamassu-admin/src/pazuz/routing/routes.js | 115 +++++++++++++++--- 3 files changed, 172 insertions(+), 66 deletions(-) diff --git a/new-lamassu-admin/src/pazuz/App.js b/new-lamassu-admin/src/pazuz/App.js index 3baed658..32b9c44d 100644 --- a/new-lamassu-admin/src/pazuz/App.js +++ b/new-lamassu-admin/src/pazuz/App.js @@ -1,15 +1,17 @@ +import { useQuery } from '@apollo/react-hooks' import CssBaseline from '@material-ui/core/CssBaseline' import Grid from '@material-ui/core/Grid' +import Slide from '@material-ui/core/Slide' import { StylesProvider, jssPreset, MuiThemeProvider, makeStyles } from '@material-ui/core/styles' -import { axios } from '@use-hooks/axios' +import gql from 'graphql-tag' import { create } from 'jss' import extendJss from 'jss-plugin-extend' -import React, { useContext, useEffect, useState } from 'react' +import React, { useContext, useState } from 'react' import { useLocation, useHistory, @@ -69,11 +71,32 @@ const useStyles = makeStyles({ } }) +const GET_USER_DATA = gql` + query userData { + userData { + id + username + role + enabled + last_accessed + last_accessed_from + last_accessed_address + } + } +` + const Main = () => { const classes = useStyles() const location = useLocation() const history = useHistory() - const { wizardTested, userData } = useContext(AppContext) + const { wizardTested, userData, setUserData } = useContext(AppContext) + + const { loading } = useQuery(GET_USER_DATA, { + onCompleted: userResponse => { + if (!userData && userResponse?.userData) + setUserData(userResponse.userData) + } + }) const route = location.pathname @@ -97,7 +120,17 @@ const Main = () => { )}
{sidebar && !is404 && wizardTested && ( - + + + + } + /> )} @@ -109,9 +142,7 @@ const Main = () => { onClick={onClick} /> )} -
- -
+
{!loading && }
@@ -121,46 +152,26 @@ const Main = () => { const App = () => { const [wizardTested, setWizardTested] = useState(false) const [userData, setUserData] = useState(null) - const [loading, setLoading] = useState(true) - const url = - process.env.NODE_ENV === 'development' ? 'https://localhost:8070' : '' - - useEffect(() => { - getUserData() - }, []) - - const getUserData = () => { - axios({ - method: 'GET', - url: `${url}/user-data`, - withCredentials: true - }) - .then(res => { - setLoading(false) - if (res.status === 200) setUserData(res.data.user) - }) - .catch(err => { - setLoading(false) - if (err.status === 403) setUserData(null) - }) + const setRole = role => { + if (userData && userData.role !== role) { + setUserData({ ...userData, role }) + } } return ( - {!loading && ( - - - - - -
- - - - - )} + value={{ wizardTested, setWizardTested, userData, setUserData, setRole }}> + + + + + +
+ + + + ) } diff --git a/new-lamassu-admin/src/pazuz/apollo/Provider.js b/new-lamassu-admin/src/pazuz/apollo/Provider.js index 1dfaa322..5512815d 100644 --- a/new-lamassu-admin/src/pazuz/apollo/Provider.js +++ b/new-lamassu-admin/src/pazuz/apollo/Provider.js @@ -4,20 +4,23 @@ import { ApolloClient } from 'apollo-client' import { ApolloLink } from 'apollo-link' import { onError } from 'apollo-link-error' import { HttpLink } from 'apollo-link-http' -import React from 'react' +import React, { useContext } from 'react' import { useHistory, useLocation } from 'react-router-dom' +import AppContext from 'src/AppContext' + const URI = process.env.NODE_ENV === 'development' ? 'https://localhost:8070' : '' -const getClient = (history, location) => +const getClient = (history, location, setUserData, setRole) => new ApolloClient({ link: ApolloLink.from([ onError(({ graphQLErrors, networkError }) => { if (graphQLErrors) graphQLErrors.forEach(({ message, locations, path, extensions }) => { if (extensions?.code === 'UNAUTHENTICATED') { - if (location.pathname !== '/404') history.push('/404') + setUserData(null) + if (location.pathname !== '/login') history.push('/login') } console.log( `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}` @@ -25,6 +28,21 @@ const getClient = (history, location) => }) if (networkError) console.log(`[Network error]: ${networkError}`) }), + new ApolloLink((operation, forward) => { + return forward(operation).map(response => { + const context = operation.getContext() + const { + response: { headers } + } = context + + if (headers) { + const role = headers.get('role') + setRole(role) + } + + return response + }) + }), new HttpLink({ credentials: 'include', uri: `${URI}/graphql` @@ -49,7 +67,9 @@ const getClient = (history, location) => const Provider = ({ children }) => { const history = useHistory() const location = useLocation() - const client = getClient(history, location) + const { setUserData, setRole } = useContext(AppContext) + const client = getClient(history, location, setUserData, setRole) + return {children} } diff --git a/new-lamassu-admin/src/pazuz/routing/routes.js b/new-lamassu-admin/src/pazuz/routing/routes.js index 323b938e..1656dcb6 100644 --- a/new-lamassu-admin/src/pazuz/routing/routes.js +++ b/new-lamassu-admin/src/pazuz/routing/routes.js @@ -5,7 +5,6 @@ import * as R from 'ramda' import React, { useContext } from 'react' import { matchPath, - Route, Redirect, Switch, useHistory, @@ -13,11 +12,14 @@ import { } from 'react-router-dom' import AppContext from 'src/AppContext' -import AuthRegister from 'src/pages/AuthRegister' +import Login from 'src/pages/Authentication/Login' +import Register from 'src/pages/Authentication/Register' +import Reset2FA from 'src/pages/Authentication/Reset2FA' +import ResetPassword from 'src/pages/Authentication/ResetPassword' import Blacklist from 'src/pages/Blacklist' import Cashout from 'src/pages/Cashout' import Commissions from 'src/pages/Commissions' -import ConfigMigration from 'src/pages/ConfigMigration' +// import ConfigMigration from 'src/pages/ConfigMigration' import { Customers, CustomerProfile } from 'src/pages/Customers' import Dashboard from 'src/pages/Dashboard' import Funding from 'src/pages/Funding' @@ -34,9 +36,14 @@ import ReceiptPrinting from 'src/pages/OperatorInfo/ReceiptPrinting' import TermsConditions from 'src/pages/OperatorInfo/TermsConditions' import ServerLogs from 'src/pages/ServerLogs' import Services from 'src/pages/Services/Services' +import SessionManagement from 'src/pages/SessionManagement/SessionManagement' import Transactions from 'src/pages/Transactions/Transactions' import Triggers from 'src/pages/Triggers' +import UserManagement from 'src/pages/UserManagement/UserManagement' import Wizard from 'src/pages/Wizard' +import PrivateRoute from 'src/routing/PrivateRoute' +import PublicRoute from 'src/routing/PublicRoute' +import { ROLES } from 'src/routing/utils' import { namespaces } from 'src/utils/config' const useStyles = makeStyles({ @@ -53,12 +60,14 @@ const tree = [ key: 'transactions', label: 'Transactions', route: '/transactions', + allowedRoles: [ROLES.USER, ROLES.SUPERUSER], component: Transactions }, { key: 'maintenance', label: 'Maintenance', route: '/maintenance', + allowedRoles: [ROLES.USER, ROLES.SUPERUSER], get component() { return () => }, @@ -67,30 +76,35 @@ const tree = [ key: 'cash_cassettes', label: 'Cash Cassettes', route: '/maintenance/cash-cassettes', + allowedRoles: [ROLES.USER, ROLES.SUPERUSER], component: CashCassettes }, { key: 'funding', label: 'Funding', route: '/maintenance/funding', + allowedRoles: [ROLES.USER, ROLES.SUPERUSER], component: Funding }, { key: 'logs', label: 'Machine Logs', route: '/maintenance/logs', + allowedRoles: [ROLES.USER, ROLES.SUPERUSER], component: MachineLogs }, { key: 'machine-status', label: 'Machine Status', route: '/maintenance/machine-status', + allowedRoles: [ROLES.USER, ROLES.SUPERUSER], component: MachineStatus }, { key: 'server-logs', label: 'Server', route: '/maintenance/server-logs', + allowedRoles: [ROLES.USER, ROLES.SUPERUSER], component: ServerLogs } ] @@ -99,6 +113,7 @@ const tree = [ key: 'settings', label: 'Settings', route: '/settings', + allowedRoles: [ROLES.USER, ROLES.SUPERUSER], get component() { return () => }, @@ -107,30 +122,35 @@ const tree = [ key: namespaces.COMMISSIONS, label: 'Commissions', route: '/settings/commissions', + allowedRoles: [ROLES.USER, ROLES.SUPERUSER], component: Commissions }, { key: namespaces.LOCALE, label: 'Locales', route: '/settings/locale', + allowedRoles: [ROLES.USER, ROLES.SUPERUSER], component: Locales }, { key: namespaces.CASH_OUT, label: 'Cash-out', route: '/settings/cash-out', + allowedRoles: [ROLES.USER, ROLES.SUPERUSER], component: Cashout }, { key: namespaces.NOTIFICATIONS, label: 'Notifications', route: '/settings/notifications', + allowedRoles: [ROLES.USER, ROLES.SUPERUSER], component: Notifications }, { key: 'services', label: '3rd party services', route: '/settings/3rd-party-services', + allowedRoles: [ROLES.USER, ROLES.SUPERUSER], component: Services }, { @@ -138,6 +158,7 @@ const tree = [ label: 'Operator Info', route: '/settings/operator-info', title: 'Operator Information', + allowedRoles: [ROLES.USER, ROLES.SUPERUSER], get component() { return () => ( }, @@ -189,32 +215,62 @@ const tree = [ key: 'triggers', label: 'Triggers', route: '/compliance/triggers', + allowedRoles: [ROLES.USER, ROLES.SUPERUSER], component: Triggers }, { key: 'customers', label: 'Customers', route: '/compliance/customers', + allowedRoles: [ROLES.USER, ROLES.SUPERUSER], component: Customers }, { key: 'blacklist', label: 'Blacklist', route: '/compliance/blacklist', + allowedRoles: [ROLES.USER, ROLES.SUPERUSER], component: Blacklist }, { key: 'promo-codes', label: 'Promo Codes', route: '/compliance/loyalty/codes', + allowedRoles: [ROLES.USER, ROLES.SUPERUSER], component: PromoCodes }, { key: 'customer', route: '/compliance/customer/:id', + allowedRoles: [ROLES.USER, ROLES.SUPERUSER], component: CustomerProfile } ] + }, + { + key: 'system', + label: 'System', + route: '/system', + allowedRoles: [ROLES.SUPERUSER], + get component() { + return () => + }, + children: [ + { + key: 'user-management', + label: 'User Management', + route: '/system/user-management', + allowedRoles: [ROLES.SUPERUSER], + component: UserManagement + }, + { + key: 'session-management', + label: 'Session Management', + route: '/system/session-management', + allowedRoles: [ROLES.SUPERUSER], + component: SessionManagement + } + ] } ] @@ -252,13 +308,29 @@ const Routes = () => { const history = useHistory() const location = useLocation() + const { wizardTested, userData } = useContext(AppContext) - const { wizardTested } = useContext(AppContext) - - const dontTriggerPages = ['/404', '/register', '/wizard'] + const dontTriggerPages = [ + '/404', + '/register', + '/wizard', + '/login', + '/register', + '/resetpassword', + '/reset2fa' + ] if (!wizardTested && !R.contains(location.pathname)(dontTriggerPages)) { history.push('/wizard') + return null + } + + const getFilteredRoutes = () => { + if (!userData) return [] + return flattened.filter(value => { + const keys = value.allowedRoles + return R.includes(userData.role, keys) + }) } const Transition = location.state ? Slide : Fade @@ -276,10 +348,10 @@ const Routes = () => { return ( - + - - + + { } /> - - - - - - {flattened.map(({ route, component: Page, key }) => ( - + + + + + {/* */} + + + + {getFilteredRoutes().map(({ route, component: Page, key }) => ( + { } /> - + ))} - - + + - + ) }