feat: add authenticated routing to pazuz project
This commit is contained in:
parent
047b5752b7
commit
019872ff31
3 changed files with 172 additions and 66 deletions
|
|
@ -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 = () => {
|
|||
)}
|
||||
<main className={classes.wrapper}>
|
||||
{sidebar && !is404 && wizardTested && (
|
||||
<TitleSection title={parent.title}></TitleSection>
|
||||
<Slide
|
||||
direction="left"
|
||||
in={true}
|
||||
mountOnEnter
|
||||
unmountOnExit
|
||||
children={
|
||||
<div>
|
||||
<TitleSection title={parent.title}></TitleSection>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Grid container className={classes.grid}>
|
||||
|
|
@ -109,9 +142,7 @@ const Main = () => {
|
|||
onClick={onClick}
|
||||
/>
|
||||
)}
|
||||
<div className={contentClassName}>
|
||||
<Routes />
|
||||
</div>
|
||||
<div className={contentClassName}>{!loading && <Routes />}</div>
|
||||
</Grid>
|
||||
</main>
|
||||
</div>
|
||||
|
|
@ -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 (
|
||||
<AppContext.Provider
|
||||
value={{ wizardTested, setWizardTested, userData, setUserData }}>
|
||||
{!loading && (
|
||||
<Router>
|
||||
<ApolloProvider>
|
||||
<StylesProvider jss={jss}>
|
||||
<MuiThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<Main />
|
||||
</MuiThemeProvider>
|
||||
</StylesProvider>
|
||||
</ApolloProvider>
|
||||
</Router>
|
||||
)}
|
||||
value={{ wizardTested, setWizardTested, userData, setUserData, setRole }}>
|
||||
<Router>
|
||||
<ApolloProvider>
|
||||
<StylesProvider jss={jss}>
|
||||
<MuiThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<Main />
|
||||
</MuiThemeProvider>
|
||||
</StylesProvider>
|
||||
</ApolloProvider>
|
||||
</Router>
|
||||
</AppContext.Provider>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <ApolloProvider client={client}>{children}</ApolloProvider>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 () => <Redirect to={this.children[0].route} />
|
||||
},
|
||||
|
|
@ -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 () => <Redirect to={this.children[0].route} />
|
||||
},
|
||||
|
|
@ -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 () => (
|
||||
<Redirect
|
||||
|
|
@ -153,24 +174,28 @@ const tree = [
|
|||
key: 'contact-info',
|
||||
label: 'Contact information',
|
||||
route: '/settings/operator-info/contact-info',
|
||||
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
||||
component: ContactInfo
|
||||
},
|
||||
{
|
||||
key: 'receipt-printing',
|
||||
label: 'Receipt',
|
||||
route: '/settings/operator-info/receipt-printing',
|
||||
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
||||
component: ReceiptPrinting
|
||||
},
|
||||
{
|
||||
key: 'coin-atm-radar',
|
||||
label: 'Coin ATM Radar',
|
||||
route: '/settings/operator-info/coin-atm-radar',
|
||||
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
||||
component: CoinAtmRadar
|
||||
},
|
||||
{
|
||||
key: 'terms-conditions',
|
||||
label: 'Terms & Conditions',
|
||||
route: '/settings/operator-info/terms-conditions',
|
||||
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
||||
component: TermsConditions
|
||||
}
|
||||
]
|
||||
|
|
@ -181,6 +206,7 @@ const tree = [
|
|||
key: 'compliance',
|
||||
label: 'Compliance',
|
||||
route: '/compliance',
|
||||
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
|
||||
get component() {
|
||||
return () => <Redirect to={this.children[0].route} />
|
||||
},
|
||||
|
|
@ -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 () => <Redirect to={this.children[0].route} />
|
||||
},
|
||||
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 (
|
||||
<Switch>
|
||||
<Route exact path="/">
|
||||
<PrivateRoute exact path="/">
|
||||
<Redirect to={{ pathname: '/dashboard' }} />
|
||||
</Route>
|
||||
<Route path={'/dashboard'}>
|
||||
</PrivateRoute>
|
||||
<PrivateRoute path={'/dashboard'}>
|
||||
<Transition
|
||||
className={classes.wrapper}
|
||||
{...transitionProps}
|
||||
|
|
@ -292,13 +364,16 @@ const Routes = () => {
|
|||
</div>
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
<Route path="/machines" component={Machines} />
|
||||
<Route path="/wizard" component={Wizard} />
|
||||
<Route path="/register" component={AuthRegister} />
|
||||
<Route path="/configmigration" component={ConfigMigration} />
|
||||
{flattened.map(({ route, component: Page, key }) => (
|
||||
<Route path={route} key={key}>
|
||||
</PrivateRoute>
|
||||
<PrivateRoute path="/machines" component={Machines} />
|
||||
<PrivateRoute path="/wizard" component={Wizard} />
|
||||
<PublicRoute path="/register" component={Register} />
|
||||
{/* <Route path="/configmigration" component={ConfigMigration} /> */}
|
||||
<PublicRoute path="/login" restricted component={Login} />
|
||||
<PublicRoute path="/resetpassword" component={ResetPassword} />
|
||||
<PublicRoute path="/reset2fa" component={Reset2FA} />
|
||||
{getFilteredRoutes().map(({ route, component: Page, key }) => (
|
||||
<PrivateRoute path={route} key={key}>
|
||||
<Transition
|
||||
className={classes.wrapper}
|
||||
{...transitionProps}
|
||||
|
|
@ -311,12 +386,12 @@ const Routes = () => {
|
|||
</div>
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
</PrivateRoute>
|
||||
))}
|
||||
<Route path="/404" />
|
||||
<Route path="*">
|
||||
<PublicRoute path="/404" />
|
||||
<PublicRoute path="*">
|
||||
<Redirect to={{ pathname: '/404' }} />
|
||||
</Route>
|
||||
</PublicRoute>
|
||||
</Switch>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue