fix: multiple small fixes across auth
This commit is contained in:
parent
9fa97725ec
commit
bbc37c0202
22 changed files with 296 additions and 291 deletions
|
|
@ -51,7 +51,7 @@ const apolloServer = new ApolloServer({
|
|||
console.log(error)
|
||||
return error
|
||||
},
|
||||
context: async ({ req }) => {
|
||||
context: async ({ req, res }) => {
|
||||
if (!req.session.user) return { req }
|
||||
|
||||
const user = await users.verifyAndUpdateUser(
|
||||
|
|
@ -67,6 +67,9 @@ const apolloServer = new ApolloServer({
|
|||
req.session.user.id = user.id
|
||||
req.session.user.role = user.role
|
||||
|
||||
res.set('role', user.role)
|
||||
res.set('Access-Control-Expose-Headers', 'role')
|
||||
|
||||
return { req }
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -21,8 +21,9 @@ function authenticateUser(username, password) {
|
|||
if (!isMatch) throw new authErrors.InvalidCredentialsError()
|
||||
return loginHelper.validateUser(username, hashedPassword)
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e)
|
||||
.then(user => {
|
||||
if (!user) throw new authErrors.InvalidCredentialsError()
|
||||
return user
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +71,7 @@ const validateRegisterLink = token => {
|
|||
|
||||
const validateResetPasswordLink = token => {
|
||||
if (!token) throw new authErrors.InvalidUrlError()
|
||||
return users.validatePasswordResetToken(token)
|
||||
return users.validateAuthToken(token, 'reset_password')
|
||||
.then(r => {
|
||||
if (!r.success) throw new authErrors.InvalidUrlError()
|
||||
return { id: r.userID }
|
||||
|
|
@ -80,7 +81,7 @@ const validateResetPasswordLink = token => {
|
|||
|
||||
const validateReset2FALink = token => {
|
||||
if (!token) throw new authErrors.InvalidUrlError()
|
||||
return users.validate2FAResetToken(token)
|
||||
return users.validateAuthToken(token, 'reset_twofa')
|
||||
.then(r => {
|
||||
if (!r.success) throw new authErrors.InvalidUrlError()
|
||||
return users.getUserById(r.userID)
|
||||
|
|
@ -111,10 +112,12 @@ const login = (username, password) => {
|
|||
}
|
||||
|
||||
const input2FA = (username, password, rememberMe, code, context) => {
|
||||
return authenticateUser(username, password).then(user => {
|
||||
if (!user) throw new authErrors.InvalidCredentialsError()
|
||||
|
||||
return users.getUserById(user.id).then(user => {
|
||||
return authenticateUser(username, password)
|
||||
.then(user => {
|
||||
if (!user) throw new authErrors.InvalidCredentialsError()
|
||||
return users.getUserById(user.id)
|
||||
})
|
||||
.then(user => {
|
||||
const secret = user.twofa_code
|
||||
const isCodeValid = otplib.authenticator.verify({ token: code, secret: secret })
|
||||
if (!isCodeValid) throw new authErrors.InvalidTwoFactorError()
|
||||
|
|
@ -125,25 +128,32 @@ const input2FA = (username, password, rememberMe, code, context) => {
|
|||
|
||||
return true
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const setup2FA = (username, password, secret, codeConfirmation) => {
|
||||
return authenticateUser(username, password).then(user => {
|
||||
if (!user || !secret) throw new authErrors.InvalidCredentialsError()
|
||||
const setup2FA = (username, password, rememberMe, secret, codeConfirmation, context) => {
|
||||
return authenticateUser(username, password)
|
||||
.then(user => {
|
||||
if (!user || !secret) throw new authErrors.InvalidCredentialsError()
|
||||
|
||||
const isCodeValid = otplib.authenticator.verify({ token: codeConfirmation, secret: secret })
|
||||
if (!isCodeValid) throw new authErrors.InvalidTwoFactorError()
|
||||
const isCodeValid = otplib.authenticator.verify({ token: codeConfirmation, secret: secret })
|
||||
if (!isCodeValid) throw new authErrors.InvalidTwoFactorError()
|
||||
|
||||
return users.save2FASecret(user.id, secret).then(() => true)
|
||||
})
|
||||
return users.getUserById(user.id)
|
||||
})
|
||||
.then(user => {
|
||||
const finalUser = { id: user.id, username: user.username, role: user.role }
|
||||
context.req.session.user = finalUser
|
||||
if (rememberMe) context.req.session.cookie.maxAge = REMEMBER_ME_AGE
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
const createResetPasswordToken = userID => {
|
||||
return users.getUserById(userID)
|
||||
.then(user => {
|
||||
if (!user) throw new authErrors.InvalidCredentialsError()
|
||||
return users.createResetPasswordToken(user.id)
|
||||
return users.createAuthToken(user.id, 'reset_password')
|
||||
})
|
||||
.catch(err => console.error(err))
|
||||
}
|
||||
|
|
@ -152,7 +162,7 @@ const createReset2FAToken = userID => {
|
|||
return users.getUserById(userID)
|
||||
.then(user => {
|
||||
if (!user) throw new authErrors.InvalidCredentialsError()
|
||||
return users.createReset2FAToken(user.id)
|
||||
return users.createAuthToken(user.id, 'reset_twofa')
|
||||
})
|
||||
.catch(err => console.error(err))
|
||||
}
|
||||
|
|
@ -177,20 +187,26 @@ const register = (token, username, password, role) => {
|
|||
}
|
||||
|
||||
const resetPassword = (token, userID, newPassword, context) => {
|
||||
return users.getUserById(userID).then(user => {
|
||||
if (!user) throw new authErrors.InvalidCredentialsError()
|
||||
if (context.req.session.user && user.id === context.req.session.user.id) context.req.session.destroy()
|
||||
return users.updatePassword(token, user.id, newPassword)
|
||||
}).then(() => true).catch(err => console.error(err))
|
||||
return users.getUserById(userID)
|
||||
.then(user => {
|
||||
if (!user) throw new authErrors.InvalidCredentialsError()
|
||||
if (context.req.session.user && user.id === context.req.session.user.id) context.req.session.destroy()
|
||||
return users.updatePassword(token, user.id, newPassword)
|
||||
})
|
||||
.then(() => true)
|
||||
.catch(err => console.error(err))
|
||||
}
|
||||
|
||||
const reset2FA = (token, userID, code, secret, context) => {
|
||||
return users.getUserById(userID).then(user => {
|
||||
const isCodeValid = otplib.authenticator.verify({ token: code, secret })
|
||||
if (!isCodeValid) throw new authErrors.InvalidTwoFactorError()
|
||||
if (context.req.session.user && user.id === context.req.session.user.id) context.req.session.destroy()
|
||||
return users.reset2FASecret(token, user.id, secret).then(() => true)
|
||||
}).catch(err => console.error(err))
|
||||
const isCodeValid = otplib.authenticator.verify({ token: code, secret })
|
||||
if (!isCodeValid) throw new authErrors.InvalidTwoFactorError()
|
||||
|
||||
return users.getUserById(userID)
|
||||
.then(user => {
|
||||
if (context.req.session.user && user.id === context.req.session.user.id) context.req.session.destroy()
|
||||
return users.reset2FASecret(token, user.id, secret).then(() => true)
|
||||
})
|
||||
.catch(err => console.error(err))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ const resolver = {
|
|||
users: () => users.getUsers(),
|
||||
sessions: () => sessionManager.getSessions(),
|
||||
userSessions: (...[, { username }]) => sessionManager.getSessionsByUsername(username),
|
||||
userData: (root, args, context, info) => authentication.getUserData(context),
|
||||
userData: (...[, {}, context]) => authentication.getUserData(context),
|
||||
get2FASecret: (...[, { username, password }]) => authentication.get2FASecret(username, password),
|
||||
confirm2FA: (root, args, context, info) => authentication.confirm2FA(args.code, context),
|
||||
confirm2FA: (...[, { code }, context]) => authentication.confirm2FA(code, context),
|
||||
validateRegisterLink: (...[, { token }]) => authentication.validateRegisterLink(token),
|
||||
validateResetPasswordLink: (...[, { token }]) => authentication.validateResetPasswordLink(token),
|
||||
validateReset2FALink: (...[, { token }]) => authentication.validateReset2FALink(token)
|
||||
|
|
@ -17,18 +17,18 @@ const resolver = {
|
|||
Mutation: {
|
||||
enableUser: (...[, { id }]) => users.enableUser(id),
|
||||
disableUser: (...[, { id }]) => users.disableUser(id),
|
||||
deleteSession: (root, args, context, info) => authentication.deleteSession(args.sid, context),
|
||||
deleteSession: (...[, { sid }, context]) => authentication.deleteSession(sid, context),
|
||||
deleteUserSessions: (...[, { username }]) => sessionManager.deleteSessionsByUsername(username),
|
||||
changeUserRole: (...[, { id, newRole }]) => users.changeUserRole(id, newRole),
|
||||
login: (...[, { username, password }]) => authentication.login(username, password),
|
||||
input2FA: (root, args, context, info) => authentication.input2FA(args.username, args.password, args.rememberMe, args.code, context),
|
||||
setup2FA: (...[, { username, password, secret, codeConfirmation }]) => authentication.setup2FA(username, password, secret, codeConfirmation),
|
||||
input2FA: (...[, { username, password, rememberMe, code }, context]) => authentication.input2FA(username, password, rememberMe, code, context),
|
||||
setup2FA: (...[, { username, password, rememberMe, secret, codeConfirmation }, context]) => authentication.setup2FA(username, password, rememberMe, secret, codeConfirmation, context),
|
||||
createResetPasswordToken: (...[, { userID }]) => authentication.createResetPasswordToken(userID),
|
||||
createReset2FAToken: (...[, { userID }]) => authentication.createReset2FAToken(userID),
|
||||
createRegisterToken: (...[, { username, role }]) => authentication.createRegisterToken(username, role),
|
||||
register: (...[, { token, username, password, role }]) => authentication.register(token, username, password, role),
|
||||
resetPassword: (root, args, context, info) => authentication.resetPassword(args.token, args.userID, args.newPassword, context),
|
||||
reset2FA: (root, args, context, info) => authentication.reset2FA(args.token, args.userID, args.code, args.secret, context)
|
||||
resetPassword: (...[, { token, userID, newPassword }, context]) => authentication.resetPassword(token, userID, newPassword, context),
|
||||
reset2FA: (...[, { token, userID, code, secret }, context]) => authentication.reset2FA(token, userID, code, secret, context)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ const typeDef = `
|
|||
toggleUserEnable(id: ID!): User @auth(requires: [SUPERUSER])
|
||||
login(username: String!, password: String!): String
|
||||
input2FA(username: String!, password: String!, code: String!, rememberMe: Boolean!): Boolean
|
||||
setup2FA(username: String!, password: String!, secret: String!, codeConfirmation: String!): Boolean
|
||||
setup2FA(username: String!, password: String!, rememberMe: Boolean!, secret: String!, codeConfirmation: String!): Boolean
|
||||
createResetPasswordToken(userID: ID!): ResetToken @auth(requires: [SUPERUSER])
|
||||
createReset2FAToken(userID: ID!): ResetToken @auth(requires: [SUPERUSER])
|
||||
createRegisterToken(username: String!, role: String!): RegistrationToken @auth(requires: [SUPERUSER])
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ function validateUser (username, password) {
|
|||
const sql = 'SELECT id, username FROM users WHERE username=$1 AND password=$2'
|
||||
const sqlUpdateLastAccessed = 'UPDATE users SET last_accessed = now() WHERE username=$1'
|
||||
|
||||
return db.oneOrNone(sql, [username, password])
|
||||
return db.one(sql, [username, password])
|
||||
.then(user => {
|
||||
return db.none(sqlUpdateLastAccessed, [user.username])
|
||||
.then(() => user)
|
||||
|
|
|
|||
44
lib/users.js
44
lib/users.js
|
|
@ -72,16 +72,16 @@ function save2FASecret (id, secret) {
|
|||
})
|
||||
}
|
||||
|
||||
function validate2FAResetToken (token) {
|
||||
function validateAuthToken (token, type) {
|
||||
const sql = `SELECT user_id, now() < expire AS success FROM auth_tokens
|
||||
WHERE token=$1 AND type='reset_twofa'`
|
||||
WHERE token=$1 AND type=$2`
|
||||
|
||||
return db.one(sql, [token])
|
||||
return db.one(sql, [token, type])
|
||||
.then(res => ({ userID: res.user_id, success: res.success }))
|
||||
}
|
||||
|
||||
function reset2FASecret (token, id, secret) {
|
||||
return validate2FAResetToken(token).then(res => {
|
||||
return validateAuthToken(token, 'reset_twofa').then(res => {
|
||||
if (!res.success) throw new Error('Failed to verify 2FA reset token')
|
||||
return db.tx(t => {
|
||||
const q1 = t.none('UPDATE users SET twofa_code=$1 WHERE id=$2', [secret, id])
|
||||
|
|
@ -92,23 +92,15 @@ function reset2FASecret (token, id, secret) {
|
|||
})
|
||||
}
|
||||
|
||||
function createReset2FAToken (userID) {
|
||||
function createAuthToken (userID, type) {
|
||||
const token = crypto.randomBytes(32).toString('hex')
|
||||
const sql = `INSERT INTO auth_tokens (token, type, user_id) VALUES ($1, 'reset_twofa', $2) ON CONFLICT (user_id, type) DO UPDATE SET token=$1, expire=now() + interval '30 minutes' RETURNING *`
|
||||
const sql = `INSERT INTO auth_tokens (token, type, user_id) VALUES ($1, $2, $3) ON CONFLICT (user_id, type) DO UPDATE SET token=$1, expire=now() + interval '30 minutes' RETURNING *`
|
||||
|
||||
return db.one(sql, [token, userID])
|
||||
}
|
||||
|
||||
function validatePasswordResetToken (token) {
|
||||
const sql = `SELECT user_id, now() < expire AS success FROM auth_tokens
|
||||
WHERE token=$1 AND type='reset_password'`
|
||||
|
||||
return db.one(sql, [token])
|
||||
.then(res => ({ userID: res.user_id, success: res.success }))
|
||||
return db.one(sql, [token, type, userID])
|
||||
}
|
||||
|
||||
function updatePassword (token, id, password) {
|
||||
return validatePasswordResetToken(token).then(res => {
|
||||
return validateAuthToken(token, 'reset_password').then(res => {
|
||||
if (!res.success) throw new Error('Failed to verify password reset token')
|
||||
return bcrypt.hash(password, 12).then(function (hash) {
|
||||
return db.tx(t => {
|
||||
|
|
@ -121,13 +113,6 @@ function updatePassword (token, id, password) {
|
|||
})
|
||||
}
|
||||
|
||||
function createResetPasswordToken (userID) {
|
||||
const token = crypto.randomBytes(32).toString('hex')
|
||||
const sql = `INSERT INTO auth_tokens (token, type, user_id) VALUES ($1, 'reset_password', $2) ON CONFLICT (user_id, type) DO UPDATE SET token=$1, expire=now() + interval '30 minutes' RETURNING *`
|
||||
|
||||
return db.one(sql, [token, userID])
|
||||
}
|
||||
|
||||
function createUserRegistrationToken (username, role) {
|
||||
const token = crypto.randomBytes(32).toString('hex')
|
||||
const sql = `INSERT INTO user_register_tokens (token, username, role) VALUES ($1, $2, $3) ON CONFLICT (username)
|
||||
|
|
@ -162,11 +147,8 @@ function changeUserRole (id, newRole) {
|
|||
}
|
||||
|
||||
function enableUser (id) {
|
||||
return db.tx(t => {
|
||||
const q1 = t.none(`UPDATE users SET enabled=true WHERE id=$1`, [id])
|
||||
const q2 = t.none(`DELETE FROM user_sessions WHERE sess -> 'user' ->> 'id'=$1`, [id])
|
||||
return t.batch([q1, q2])
|
||||
})
|
||||
const sql = `UPDATE users SET enabled=true WHERE id=$1`
|
||||
return db.none(sql, [id])
|
||||
}
|
||||
|
||||
function disableUser (id) {
|
||||
|
|
@ -187,10 +169,8 @@ module.exports = {
|
|||
updatePassword,
|
||||
save2FASecret,
|
||||
reset2FASecret,
|
||||
validate2FAResetToken,
|
||||
createReset2FAToken,
|
||||
validatePasswordResetToken,
|
||||
createResetPasswordToken,
|
||||
validateAuthToken,
|
||||
createAuthToken,
|
||||
createUserRegistrationToken,
|
||||
validateUserRegistrationToken,
|
||||
register,
|
||||
|
|
|
|||
|
|
@ -3,9 +3,12 @@ import classnames from 'classnames'
|
|||
import React from 'react'
|
||||
import OtpInput from 'react-otp-input'
|
||||
|
||||
import typographyStyles from 'src/components/typography/styles'
|
||||
|
||||
import styles from './CodeInput.styles'
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
const useTypographyStyles = makeStyles(typographyStyles)
|
||||
|
||||
const CodeInput = ({
|
||||
name,
|
||||
|
|
@ -18,6 +21,7 @@ const CodeInput = ({
|
|||
...props
|
||||
}) => {
|
||||
const classes = useStyles()
|
||||
const typographyClasses = useTypographyStyles()
|
||||
|
||||
return (
|
||||
<OtpInput
|
||||
|
|
@ -27,7 +31,11 @@ const CodeInput = ({
|
|||
numInputs={numInputs}
|
||||
separator={<span> </span>}
|
||||
containerStyle={classnames(containerStyle, classes.container)}
|
||||
inputStyle={classnames(inputStyle, classes.input)}
|
||||
inputStyle={classnames(
|
||||
inputStyle,
|
||||
classes.input,
|
||||
typographyClasses.confirmationCode
|
||||
)}
|
||||
focusStyle={classes.focus}
|
||||
errorStyle={classes.error}
|
||||
hasErrored={error}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,9 @@
|
|||
import {
|
||||
fontPrimary,
|
||||
primaryColor,
|
||||
zircon,
|
||||
errorColor
|
||||
} from 'src/styling/variables'
|
||||
import { primaryColor, zircon, errorColor } from 'src/styling/variables'
|
||||
|
||||
const styles = {
|
||||
input: {
|
||||
width: '3.5rem !important',
|
||||
height: '5rem',
|
||||
fontFamily: fontPrimary,
|
||||
fontSize: 35,
|
||||
color: primaryColor,
|
||||
border: '2px solid',
|
||||
borderColor: zircon,
|
||||
borderRadius: '4px'
|
||||
|
|
|
|||
|
|
@ -36,15 +36,7 @@ const Subheader = ({ item, classes, user }) => {
|
|||
<nav>
|
||||
<ul className={classes.subheaderUl}>
|
||||
{item.children.map((it, idx) => {
|
||||
if (
|
||||
!R.includes(
|
||||
user.role,
|
||||
it.allowedRoles.map(v => {
|
||||
return v.key
|
||||
})
|
||||
)
|
||||
)
|
||||
return <></>
|
||||
if (!R.includes(user.role, it.allowedRoles)) return <></>
|
||||
return (
|
||||
<li key={idx} className={classes.subheaderLi}>
|
||||
<NavLink
|
||||
|
|
@ -131,15 +123,7 @@ const Header = memo(({ tree, user }) => {
|
|||
<nav className={classes.nav}>
|
||||
<ul className={classes.ul}>
|
||||
{tree.map((it, idx) => {
|
||||
if (
|
||||
!R.includes(
|
||||
user.role,
|
||||
it.allowedRoles.map(v => {
|
||||
return v.key
|
||||
})
|
||||
)
|
||||
)
|
||||
return <></>
|
||||
if (!R.includes(user.role, it.allowedRoles)) return <></>
|
||||
return (
|
||||
<NavLink
|
||||
key={idx}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ import {
|
|||
fontSize5,
|
||||
fontPrimary,
|
||||
fontSecondary,
|
||||
fontMonospaced
|
||||
fontMonospaced,
|
||||
fontSize2FA
|
||||
} from 'src/styling/variables'
|
||||
|
||||
const base = {
|
||||
|
|
@ -125,6 +126,12 @@ export default {
|
|||
fontWeight: 500,
|
||||
color: fontColor
|
||||
},
|
||||
confirmationCode: {
|
||||
extend: base,
|
||||
fontSize: fontSize2FA,
|
||||
fontFamily: fontPrimary,
|
||||
fontWeight: 900
|
||||
},
|
||||
inline: {
|
||||
display: 'inline'
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useQuery } from '@apollo/react-hooks'
|
||||
import { useLazyQuery } 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,
|
||||
|
|
@ -10,8 +11,7 @@ import {
|
|||
import gql from 'graphql-tag'
|
||||
import { create } from 'jss'
|
||||
import extendJss from 'jss-plugin-extend'
|
||||
import * as R from 'ramda'
|
||||
import React, { useContext, useState, useEffect } from 'react'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import {
|
||||
useLocation,
|
||||
useHistory,
|
||||
|
|
@ -71,32 +71,36 @@ 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, setUserData } = useContext(AppContext)
|
||||
|
||||
const GET_USER_DATA = gql`
|
||||
query userData {
|
||||
userData {
|
||||
id
|
||||
username
|
||||
role
|
||||
enabled
|
||||
last_accessed
|
||||
last_accessed_from
|
||||
last_accessed_address
|
||||
}
|
||||
const [getUserData, { loading }] = useLazyQuery(GET_USER_DATA, {
|
||||
onCompleted: userResponse => {
|
||||
if (!userData && userResponse?.userData)
|
||||
setUserData(userResponse.userData)
|
||||
}
|
||||
`
|
||||
|
||||
const { data: userResponse, loading } = useQuery(GET_USER_DATA)
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!R.equals(userData, userResponse?.userData) && !loading)
|
||||
setUserData(userResponse?.userData)
|
||||
}, [loading, setUserData, userData, userResponse])
|
||||
getUserData()
|
||||
}, [getUserData])
|
||||
|
||||
const route = location.pathname
|
||||
|
||||
|
|
@ -120,7 +124,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}>
|
||||
|
|
@ -143,9 +157,15 @@ const App = () => {
|
|||
const [wizardTested, setWizardTested] = useState(false)
|
||||
const [userData, setUserData] = useState(null)
|
||||
|
||||
const setRole = role => {
|
||||
if (userData && userData.role !== role) {
|
||||
setUserData({ ...userData, role })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<AppContext.Provider
|
||||
value={{ wizardTested, setWizardTested, userData, setUserData }}>
|
||||
value={{ wizardTested, setWizardTested, userData, setUserData, setRole }}>
|
||||
<Router>
|
||||
<ApolloProvider>
|
||||
<StylesProvider jss={jss}>
|
||||
|
|
|
|||
|
|
@ -39,13 +39,7 @@ const GET_USER_DATA = gql`
|
|||
}
|
||||
`
|
||||
|
||||
const Input2FAState = ({
|
||||
twoFAField,
|
||||
onTwoFAChange,
|
||||
clientField,
|
||||
passwordField,
|
||||
rememberMeField
|
||||
}) => {
|
||||
const Input2FAState = ({ state, dispatch }) => {
|
||||
const classes = useStyles()
|
||||
const history = useHistory()
|
||||
const { setUserData } = useContext(AppContext)
|
||||
|
|
@ -53,10 +47,26 @@ const Input2FAState = ({
|
|||
const [invalidToken, setInvalidToken] = useState(false)
|
||||
|
||||
const handle2FAChange = value => {
|
||||
onTwoFAChange(value)
|
||||
dispatch({ type: 'twoFAField', payload: value })
|
||||
setInvalidToken(false)
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (state.twoFAField.length !== 6) {
|
||||
setInvalidToken(true)
|
||||
return
|
||||
}
|
||||
|
||||
input2FA({
|
||||
variables: {
|
||||
username: state.clientField,
|
||||
password: state.passwordField,
|
||||
code: state.twoFAField,
|
||||
rememberMe: state.rememberMeField
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const [input2FA, { error: mutationError }] = useMutation(INPUT_2FA, {
|
||||
onCompleted: ({ input2FA: success }) => {
|
||||
success ? getUserData() : setInvalidToken(true)
|
||||
|
|
@ -72,13 +82,15 @@ const Input2FAState = ({
|
|||
|
||||
const getErrorMsg = () => {
|
||||
if (queryError) return 'Internal server error'
|
||||
if (twoFAField.length !== 6 && invalidToken)
|
||||
if (state.twoFAField.length !== 6 && invalidToken)
|
||||
return 'The code should have 6 characters!'
|
||||
if (mutationError || invalidToken)
|
||||
return 'Code is invalid. Please try again.'
|
||||
return null
|
||||
}
|
||||
|
||||
const errorMessage = getErrorMsg()
|
||||
|
||||
return (
|
||||
<>
|
||||
<H2 className={classes.info}>
|
||||
|
|
@ -86,32 +98,15 @@ const Input2FAState = ({
|
|||
</H2>
|
||||
<CodeInput
|
||||
name="2fa"
|
||||
value={twoFAField}
|
||||
value={state.twoFAField}
|
||||
onChange={handle2FAChange}
|
||||
numInputs={6}
|
||||
error={invalidToken}
|
||||
shouldAutoFocus
|
||||
/>
|
||||
<div className={classes.twofaFooter}>
|
||||
{getErrorMsg() && (
|
||||
<P className={classes.errorMessage}>{getErrorMsg()}</P>
|
||||
)}
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (twoFAField.length !== 6) {
|
||||
setInvalidToken(true)
|
||||
return
|
||||
}
|
||||
input2FA({
|
||||
variables: {
|
||||
username: clientField,
|
||||
password: passwordField,
|
||||
code: twoFAField,
|
||||
rememberMe: rememberMeField
|
||||
}
|
||||
})
|
||||
}}
|
||||
buttonClassName={classes.loginButton}>
|
||||
{errorMessage && <P className={classes.errorMessage}>{errorMessage}</P>}
|
||||
<Button onClick={handleSubmit} buttonClassName={classes.loginButton}>
|
||||
Login
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import Paper from '@material-ui/core/Paper'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import React, { useState } from 'react'
|
||||
import React, { useReducer } from 'react'
|
||||
|
||||
import { H2 } from 'src/components/typography'
|
||||
import { ReactComponent as Logo } from 'src/styling/icons/menu/logo.svg'
|
||||
|
|
@ -9,75 +9,36 @@ import Input2FAState from './Input2FAState'
|
|||
import styles from './Login.styles'
|
||||
import LoginState from './LoginState'
|
||||
import Setup2FAState from './Setup2FAState'
|
||||
import { STATES } from './states'
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const STATES = {
|
||||
LOGIN: 'Login',
|
||||
SETUP_2FA: 'Setup 2FA',
|
||||
INPUT_2FA: 'Input 2FA'
|
||||
}
|
||||
|
||||
const LoginCard = () => {
|
||||
const classes = useStyles()
|
||||
|
||||
const [twoFAField, setTwoFAField] = useState('')
|
||||
const [clientField, setClientField] = useState('')
|
||||
const [passwordField, setPasswordField] = useState('')
|
||||
const [rememberMeField, setRememberMeField] = useState(false)
|
||||
const [loginState, setLoginState] = useState(STATES.LOGIN)
|
||||
|
||||
const onClientChange = newValue => {
|
||||
setClientField(newValue)
|
||||
const initialState = {
|
||||
twoFAField: '',
|
||||
clientField: '',
|
||||
passwordField: '',
|
||||
rememberMeField: false,
|
||||
loginState: STATES.LOGIN
|
||||
}
|
||||
|
||||
const onPasswordChange = newValue => {
|
||||
setPasswordField(newValue)
|
||||
const reducer = (state, action) => {
|
||||
const { type, payload } = action
|
||||
return { ...state, ...payload, loginState: type }
|
||||
}
|
||||
|
||||
const onRememberMeChange = newValue => {
|
||||
setRememberMeField(newValue)
|
||||
}
|
||||
|
||||
const onTwoFAChange = newValue => {
|
||||
setTwoFAField(newValue)
|
||||
}
|
||||
|
||||
const handleLoginState = newState => {
|
||||
setLoginState(newState)
|
||||
}
|
||||
const [state, dispatch] = useReducer(reducer, initialState)
|
||||
|
||||
const renderState = () => {
|
||||
switch (loginState) {
|
||||
switch (state.loginState) {
|
||||
case STATES.LOGIN:
|
||||
return (
|
||||
<LoginState
|
||||
onClientChange={onClientChange}
|
||||
onPasswordChange={onPasswordChange}
|
||||
onRememberMeChange={onRememberMeChange}
|
||||
STATES={STATES}
|
||||
handleLoginState={handleLoginState}
|
||||
/>
|
||||
)
|
||||
return <LoginState state={state} dispatch={dispatch} />
|
||||
case STATES.INPUT_2FA:
|
||||
return (
|
||||
<Input2FAState
|
||||
twoFAField={twoFAField}
|
||||
onTwoFAChange={onTwoFAChange}
|
||||
clientField={clientField}
|
||||
passwordField={passwordField}
|
||||
rememberMeField={rememberMeField}
|
||||
/>
|
||||
)
|
||||
return <Input2FAState state={state} dispatch={dispatch} />
|
||||
case STATES.SETUP_2FA:
|
||||
return (
|
||||
<Setup2FAState
|
||||
clientField={clientField}
|
||||
passwordField={passwordField}
|
||||
STATES={STATES}
|
||||
handleLoginState={handleLoginState}
|
||||
/>
|
||||
)
|
||||
return <Setup2FAState state={state} dispatch={dispatch} />
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { Checkbox, SecretInput, TextInput } from 'src/components/inputs/formik'
|
|||
import { Label2, P } from 'src/components/typography'
|
||||
|
||||
import styles from './Login.styles'
|
||||
import { STATES } from './states'
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
|
|
@ -33,21 +34,10 @@ const initialValues = {
|
|||
rememberMe: false
|
||||
}
|
||||
|
||||
const LoginState = ({
|
||||
onClientChange,
|
||||
onPasswordChange,
|
||||
onRememberMeChange,
|
||||
STATES,
|
||||
handleLoginState
|
||||
}) => {
|
||||
const LoginState = ({ state, dispatch }) => {
|
||||
const classes = useStyles()
|
||||
|
||||
const [login, { error: mutationError }] = useMutation(LOGIN, {
|
||||
onCompleted: ({ login }) => {
|
||||
if (login === 'INPUT2FA') handleLoginState(STATES.INPUT_2FA)
|
||||
if (login === 'SETUP2FA') handleLoginState(STATES.SETUP_2FA)
|
||||
}
|
||||
})
|
||||
const [login, { error: mutationError }] = useMutation(LOGIN)
|
||||
|
||||
const getErrorMsg = (formikErrors, formikTouched) => {
|
||||
if (!formikErrors || !formikTouched) return null
|
||||
|
|
@ -58,21 +48,36 @@ const LoginState = ({
|
|||
return null
|
||||
}
|
||||
|
||||
const submitLogin = async (username, password, rememberMe) => {
|
||||
const { data: loginResponse } = await login({
|
||||
variables: {
|
||||
username,
|
||||
password
|
||||
}
|
||||
})
|
||||
|
||||
if (!loginResponse.login) return
|
||||
|
||||
const stateVar =
|
||||
loginResponse.login === 'INPUT2FA' ? STATES.INPUT_2FA : STATES.SETUP_2FA
|
||||
|
||||
return dispatch({
|
||||
type: stateVar,
|
||||
payload: {
|
||||
clientField: username,
|
||||
passwordField: password,
|
||||
rememberMeField: rememberMe
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Formik
|
||||
validationSchema={validationSchema}
|
||||
initialValues={initialValues}
|
||||
onSubmit={values => {
|
||||
onClientChange(values.client)
|
||||
onPasswordChange(values.password)
|
||||
onRememberMeChange(values.rememberMe)
|
||||
login({
|
||||
variables: {
|
||||
username: values.client,
|
||||
password: values.password
|
||||
}
|
||||
})
|
||||
}}>
|
||||
onSubmit={values =>
|
||||
submitLogin(values.client, values.password, values.rememberMe)
|
||||
}>
|
||||
{({ errors, touched }) => (
|
||||
<Form id="login-form">
|
||||
<Field
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { makeStyles, Grid } from '@material-ui/core'
|
|||
import Paper from '@material-ui/core/Paper'
|
||||
import { Field, Form, Formik } from 'formik'
|
||||
import gql from 'graphql-tag'
|
||||
import React, { useState } from 'react'
|
||||
import React, { useReducer } from 'react'
|
||||
import { useLocation, useHistory } from 'react-router-dom'
|
||||
import * as Yup from 'yup'
|
||||
|
||||
|
|
@ -65,26 +65,33 @@ const Register = () => {
|
|||
const classes = useStyles()
|
||||
const history = useHistory()
|
||||
const token = QueryParams().get('t')
|
||||
const [username, setUsername] = useState(null)
|
||||
const [role, setRole] = useState(null)
|
||||
const [isLoading, setLoading] = useState(true)
|
||||
const [wasSuccessful, setSuccess] = useState(false)
|
||||
|
||||
const { error: queryError } = useQuery(VALIDATE_REGISTER_LINK, {
|
||||
const initialState = {
|
||||
username: null,
|
||||
role: null,
|
||||
wasSuccessful: false
|
||||
}
|
||||
|
||||
const reducer = (state, action) => {
|
||||
const { type, payload } = action
|
||||
return { ...state, [type]: payload }
|
||||
}
|
||||
|
||||
const [state, dispatch] = useReducer(reducer, initialState)
|
||||
|
||||
const { error: queryError, loading } = useQuery(VALIDATE_REGISTER_LINK, {
|
||||
variables: { token: token },
|
||||
onCompleted: ({ validateRegisterLink: info }) => {
|
||||
setLoading(false)
|
||||
if (!info) {
|
||||
setSuccess(false)
|
||||
dispatch({ type: 'wasSuccessful', payload: false })
|
||||
} else {
|
||||
setSuccess(true)
|
||||
setUsername(info.username)
|
||||
setRole(info.role)
|
||||
dispatch({ type: 'wasSuccessful', payload: true })
|
||||
dispatch({ type: 'username', payload: info.username })
|
||||
dispatch({ type: 'role', payload: info.role })
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
setLoading(false)
|
||||
setSuccess(false)
|
||||
dispatch({ type: 'wasSuccessful', payload: false })
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -120,7 +127,7 @@ const Register = () => {
|
|||
<Logo className={classes.icon} />
|
||||
<H2 className={classes.title}>Lamassu Admin</H2>
|
||||
</div>
|
||||
{!isLoading && wasSuccessful && (
|
||||
{!loading && state.wasSuccessful && (
|
||||
<Formik
|
||||
validationSchema={validationSchema}
|
||||
initialValues={initialValues}
|
||||
|
|
@ -128,9 +135,9 @@ const Register = () => {
|
|||
register({
|
||||
variables: {
|
||||
token: token,
|
||||
username: username,
|
||||
username: state.username,
|
||||
password: values.password,
|
||||
role: role
|
||||
role: state.role
|
||||
}
|
||||
})
|
||||
}}>
|
||||
|
|
@ -169,7 +176,7 @@ const Register = () => {
|
|||
)}
|
||||
</Formik>
|
||||
)}
|
||||
{!isLoading && !wasSuccessful && (
|
||||
{!loading && !state.wasSuccessful && (
|
||||
<>
|
||||
<Label2 className={classes.inputLabel}>
|
||||
Link has expired
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { useMutation, useQuery } from '@apollo/react-hooks'
|
||||
import { useMutation, useQuery, useLazyQuery } from '@apollo/react-hooks'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import gql from 'graphql-tag'
|
||||
import QRCode from 'qrcode.react'
|
||||
import React, { useState } from 'react'
|
||||
import React, { useContext, useState } from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
|
||||
import AppContext from 'src/AppContext'
|
||||
import { ActionButton, Button } from 'src/components/buttons'
|
||||
import { CodeInput } from 'src/components/inputs/base'
|
||||
import { Label2, P } from 'src/components/typography'
|
||||
|
|
@ -15,12 +17,14 @@ const SETUP_2FA = gql`
|
|||
mutation setup2FA(
|
||||
$username: String!
|
||||
$password: String!
|
||||
$rememberMe: Boolean!
|
||||
$secret: String!
|
||||
$codeConfirmation: String!
|
||||
) {
|
||||
setup2FA(
|
||||
username: $username
|
||||
password: $password
|
||||
rememberMe: $rememberMe
|
||||
secret: $secret
|
||||
codeConfirmation: $codeConfirmation
|
||||
)
|
||||
|
|
@ -36,15 +40,22 @@ const GET_2FA_SECRET = gql`
|
|||
}
|
||||
`
|
||||
|
||||
const GET_USER_DATA = gql`
|
||||
{
|
||||
userData {
|
||||
id
|
||||
username
|
||||
role
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const Setup2FAState = ({
|
||||
clientField,
|
||||
passwordField,
|
||||
STATES,
|
||||
handleLoginState
|
||||
}) => {
|
||||
const Setup2FAState = ({ state, dispatch }) => {
|
||||
const classes = useStyles()
|
||||
const history = useHistory()
|
||||
const { setUserData } = useContext(AppContext)
|
||||
|
||||
const [secret, setSecret] = useState(null)
|
||||
const [otpauth, setOtpauth] = useState(null)
|
||||
|
|
@ -59,21 +70,28 @@ const Setup2FAState = ({
|
|||
}
|
||||
|
||||
const { error: queryError } = useQuery(GET_2FA_SECRET, {
|
||||
variables: { username: clientField, password: passwordField },
|
||||
variables: { username: state.clientField, password: state.passwordField },
|
||||
onCompleted: ({ get2FASecret }) => {
|
||||
setSecret(get2FASecret.secret)
|
||||
setOtpauth(get2FASecret.otpauth)
|
||||
}
|
||||
})
|
||||
|
||||
const [getUserData] = useLazyQuery(GET_USER_DATA, {
|
||||
onCompleted: ({ userData }) => {
|
||||
setUserData(userData)
|
||||
history.push('/')
|
||||
}
|
||||
})
|
||||
|
||||
const [setup2FA, { error: mutationError }] = useMutation(SETUP_2FA, {
|
||||
onCompleted: ({ setup2FA: success }) => {
|
||||
success ? handleLoginState(STATES.LOGIN) : setInvalidToken(true)
|
||||
success ? getUserData() : setInvalidToken(true)
|
||||
}
|
||||
})
|
||||
|
||||
const getErrorMsg = () => {
|
||||
if (mutationError || queryError) return 'Internal server error'
|
||||
if (mutationError || queryError) return 'Internal server error.'
|
||||
if (twoFAConfirmation.length !== 6 && invalidToken)
|
||||
return 'The code should have 6 characters!'
|
||||
if (invalidToken) return 'Code is invalid. Please try again.'
|
||||
|
|
@ -135,8 +153,9 @@ const Setup2FAState = ({
|
|||
}
|
||||
setup2FA({
|
||||
variables: {
|
||||
username: clientField,
|
||||
password: passwordField,
|
||||
username: state.clientField,
|
||||
password: state.passwordField,
|
||||
rememberMe: state.rememberMeField,
|
||||
secret: secret,
|
||||
codeConfirmation: twoFAConfirmation
|
||||
}
|
||||
|
|
|
|||
7
new-lamassu-admin/src/pages/Authentication/states.js
Normal file
7
new-lamassu-admin/src/pages/Authentication/states.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
const STATES = {
|
||||
LOGIN: 'LOGIN',
|
||||
SETUP_2FA: 'SETUP2FA',
|
||||
INPUT_2FA: 'INPUT2FA'
|
||||
}
|
||||
|
||||
export { STATES }
|
||||
|
|
@ -53,10 +53,9 @@ const SessionManagement = () => {
|
|||
textAlign: 'center',
|
||||
size: 'sm',
|
||||
view: s => {
|
||||
if (R.isNil(s.sess.ua)) return 'No Record'
|
||||
const ua = parser(s.sess.ua)
|
||||
return s.sess.ua
|
||||
? `${ua.browser.name} ${ua.browser.version} on ${ua.os.name} ${ua.os.version}`
|
||||
: `No Record`
|
||||
return `${ua.browser.name} ${ua.browser.version} on ${ua.os.name} ${ua.os.version}`
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ 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'
|
||||
|
|
@ -37,7 +36,6 @@ 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 TokenManagement from 'src/pages/TokenManagement/TokenManagement'
|
||||
import SessionManagement from 'src/pages/SessionManagement/SessionManagement'
|
||||
import Transactions from 'src/pages/Transactions/Transactions'
|
||||
import Triggers from 'src/pages/Triggers'
|
||||
|
|
@ -283,22 +281,6 @@ const tree = [
|
|||
}
|
||||
]
|
||||
}
|
||||
// {
|
||||
// key: 'system',
|
||||
// label: 'System',
|
||||
// route: '/system',
|
||||
// get component() {
|
||||
// return () => <Redirect to={this.children[0].route} />
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// key: 'token-management',
|
||||
// label: 'Token Management',
|
||||
// route: '/system/token-management',
|
||||
// component: TokenManagement
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
]
|
||||
|
||||
const map = R.map(R.when(R.has('children'), R.prop('children')))
|
||||
|
|
@ -356,9 +338,7 @@ const Routes = () => {
|
|||
if (!userData) return []
|
||||
|
||||
return flattened.filter(value => {
|
||||
const keys = value.allowedRoles.map(v => {
|
||||
return v.key
|
||||
})
|
||||
const keys = value.allowedRoles
|
||||
return R.includes(userData.role, keys)
|
||||
})
|
||||
}
|
||||
|
|
@ -379,7 +359,7 @@ const Routes = () => {
|
|||
return (
|
||||
<Switch>
|
||||
<PrivateRoute exact path="/">
|
||||
<Redirect to={{ pathname: '/transactions' }} />
|
||||
<Redirect to={{ pathname: '/dashboard' }} />
|
||||
</PrivateRoute>
|
||||
<PrivateRoute path={'/dashboard'}>
|
||||
<Transition
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
export const isLoggedIn = userData => !!userData
|
||||
import * as R from 'ramda'
|
||||
|
||||
export const isLoggedIn = userData =>
|
||||
!R.isNil(userData?.id) &&
|
||||
!R.isNil(userData?.username) &&
|
||||
!R.isNil(userData?.role)
|
||||
|
||||
export const ROLES = {
|
||||
USER: { key: 'user' },
|
||||
SUPERUSER: { key: 'superuser' }
|
||||
USER: 'user',
|
||||
SUPERUSER: 'superuser'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ let fontSize2 = 20
|
|||
let fontSize3 = 16
|
||||
let fontSize4 = 14
|
||||
let fontSize5 = 13
|
||||
const fontSize2FA = 35
|
||||
|
||||
if (version === 8) {
|
||||
fontSize1 = 32
|
||||
|
|
@ -158,6 +159,7 @@ export {
|
|||
fontPrimary,
|
||||
fontSecondary,
|
||||
fontMonospaced,
|
||||
fontSize2FA,
|
||||
// named font sizes
|
||||
smallestFontSize,
|
||||
inputFontSize,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import AppContext from 'src/AppContext'
|
|||
const URI =
|
||||
process.env.NODE_ENV === 'development' ? 'https://localhost:8070' : ''
|
||||
|
||||
const getClient = (history, location, setUserData) =>
|
||||
const getClient = (history, location, setUserData, setRole) =>
|
||||
new ApolloClient({
|
||||
link: ApolloLink.from([
|
||||
onError(({ graphQLErrors, networkError }) => {
|
||||
|
|
@ -28,6 +28,21 @@ const getClient = (history, location, setUserData) =>
|
|||
})
|
||||
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`
|
||||
|
|
@ -52,8 +67,8 @@ const getClient = (history, location, setUserData) =>
|
|||
const Provider = ({ children }) => {
|
||||
const history = useHistory()
|
||||
const location = useLocation()
|
||||
const { setUserData } = useContext(AppContext)
|
||||
const client = getClient(history, location, setUserData)
|
||||
const { setUserData, setRole } = useContext(AppContext)
|
||||
const client = getClient(history, location, setUserData, setRole)
|
||||
|
||||
return <ApolloProvider client={client}>{children}</ApolloProvider>
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue