fix: url resolver and minor fixes

This commit is contained in:
Sérgio Salgado 2021-04-20 16:45:30 +01:00 committed by Josh Harvey
parent 2062413c75
commit 75a2ecd3c2
15 changed files with 274 additions and 290 deletions

View file

@ -220,17 +220,14 @@ const resetPassword = (token, userID, newPassword, context) => {
.then(() => true) .then(() => true)
} }
const reset2FA = (token, userID, code, secret, context) => { const reset2FA = (token, userID, code, context) => {
const isCodeValid = otplib.authenticator.verify({ token: code, secret })
if (!isCodeValid) throw new authErrors.InvalidTwoFactorError()
return users.getUserById(userID) return users.getUserById(userID)
.then(user => { .then(user => {
const isCodeValid = otplib.authenticator.verify({ token: code, secret: user.temp_twofa_code })
if (!isCodeValid) throw new authErrors.InvalidTwoFactorError()
destroySessionIfSameUser(context, user) destroySessionIfSameUser(context, user)
if (user.temp_twofa_code !== secret) { return users.reset2FASecret(token, user.id, user.temp_twofa_code)
throw new authErrors.InvalidTwoFactorError()
}
return users.reset2FASecret(token, user.id, secret)
}) })
.then(() => true) .then(() => true)
} }

View file

@ -28,7 +28,7 @@ const resolver = {
createRegisterToken: (...[, { username, role }]) => authentication.createRegisterToken(username, role), createRegisterToken: (...[, { username, role }]) => authentication.createRegisterToken(username, role),
register: (...[, { token, username, password, role }]) => authentication.register(token, username, password, role), register: (...[, { token, username, password, role }]) => authentication.register(token, username, password, role),
resetPassword: (...[, { token, userID, newPassword }, context]) => authentication.resetPassword(token, userID, newPassword, 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) reset2FA: (...[, { token, userID, code }, context]) => authentication.reset2FA(token, userID, code, context)
} }
} }

View file

@ -71,7 +71,7 @@ const typeDef = `
createRegisterToken(username: String!, role: String!): RegistrationToken @auth(requires: [SUPERUSER]) createRegisterToken(username: String!, role: String!): RegistrationToken @auth(requires: [SUPERUSER])
register(token: String!, username: String!, password: String!, role: String!): Boolean register(token: String!, username: String!, password: String!, role: String!): Boolean
resetPassword(token: String!, userID: ID!, newPassword: String!): Boolean resetPassword(token: String!, userID: ID!, newPassword: String!): Boolean
reset2FA(token: String!, userID: ID!, secret: String!, code: String!): Boolean reset2FA(token: String!, userID: ID!, code: String!): Boolean
} }
` `

View file

@ -1,4 +1,4 @@
import { useLazyQuery } from '@apollo/react-hooks' import { useQuery } from '@apollo/react-hooks'
import CssBaseline from '@material-ui/core/CssBaseline' import CssBaseline from '@material-ui/core/CssBaseline'
import Grid from '@material-ui/core/Grid' import Grid from '@material-ui/core/Grid'
import Slide from '@material-ui/core/Slide' import Slide from '@material-ui/core/Slide'
@ -11,7 +11,7 @@ import {
import gql from 'graphql-tag' import gql from 'graphql-tag'
import { create } from 'jss' import { create } from 'jss'
import extendJss from 'jss-plugin-extend' import extendJss from 'jss-plugin-extend'
import React, { useContext, useEffect, useState } from 'react' import React, { useContext, useState } from 'react'
import { import {
useLocation, useLocation,
useHistory, useHistory,
@ -91,17 +91,13 @@ const Main = () => {
const history = useHistory() const history = useHistory()
const { wizardTested, userData, setUserData } = useContext(AppContext) const { wizardTested, userData, setUserData } = useContext(AppContext)
const [getUserData, { loading }] = useLazyQuery(GET_USER_DATA, { const { loading } = useQuery(GET_USER_DATA, {
onCompleted: userResponse => { onCompleted: userResponse => {
if (!userData && userResponse?.userData) if (!userData && userResponse?.userData)
setUserData(userResponse.userData) setUserData(userResponse.userData)
} }
}) })
useEffect(() => {
getUserData()
}, [getUserData])
const route = location.pathname const route = location.pathname
const sidebar = hasSidebar(route) const sidebar = hasSidebar(route)

View file

@ -47,6 +47,19 @@ const Input2FAState = ({ state, dispatch }) => {
const [invalidToken, setInvalidToken] = useState(false) const [invalidToken, setInvalidToken] = useState(false)
const [getUserData, { error: queryError }] = useLazyQuery(GET_USER_DATA, {
onCompleted: ({ userData }) => {
setUserData(userData)
history.push('/')
}
})
const [input2FA, { error: mutationError }] = useMutation(INPUT_2FA, {
onCompleted: ({ input2FA: success }) => {
success ? getUserData() : setInvalidToken(true)
}
})
const handle2FAChange = value => { const handle2FAChange = value => {
dispatch({ dispatch({
type: STATES.INPUT_2FA, type: STATES.INPUT_2FA,
@ -73,19 +86,6 @@ const Input2FAState = ({ state, dispatch }) => {
}) })
} }
const [input2FA, { error: mutationError }] = useMutation(INPUT_2FA, {
onCompleted: ({ input2FA: success }) => {
success ? getUserData() : setInvalidToken(true)
}
})
const [getUserData, { error: queryError }] = useLazyQuery(GET_USER_DATA, {
onCompleted: ({ userData }) => {
setUserData(userData)
history.push('/')
}
})
const getErrorMsg = () => { const getErrorMsg = () => {
if (queryError) return 'Internal server error' if (queryError) return 'Internal server error'
if (state.twoFAField.length !== 6 && invalidToken) if (state.twoFAField.length !== 6 && invalidToken)

View file

@ -69,12 +69,12 @@ const Register = () => {
const initialState = { const initialState = {
username: null, username: null,
role: null, role: null,
wasSuccessful: false result: ''
} }
const reducer = (state, action) => { const reducer = (state, action) => {
const { type, payload } = action const { type, payload } = action
return { ...state, [type]: payload } return { ...state, ...payload, result: type }
} }
const [state, dispatch] = useReducer(reducer, initialState) const [state, dispatch] = useReducer(reducer, initialState)
@ -83,15 +83,23 @@ const Register = () => {
variables: { token: token }, variables: { token: token },
onCompleted: ({ validateRegisterLink: info }) => { onCompleted: ({ validateRegisterLink: info }) => {
if (!info) { if (!info) {
dispatch({ type: 'wasSuccessful', payload: false }) dispatch({
type: 'failure'
})
} else { } else {
dispatch({ type: 'wasSuccessful', payload: true }) dispatch({
dispatch({ type: 'username', payload: info.username }) type: 'success',
dispatch({ type: 'role', payload: info.role }) payload: {
username: info.username,
role: info.role
}
})
} }
}, },
onError: () => { onError: () => {
dispatch({ type: 'wasSuccessful', payload: false }) dispatch({
type: 'failure'
})
} }
}) })
@ -127,7 +135,7 @@ const Register = () => {
<Logo className={classes.icon} /> <Logo className={classes.icon} />
<H2 className={classes.title}>Lamassu Admin</H2> <H2 className={classes.title}>Lamassu Admin</H2>
</div> </div>
{!loading && state.wasSuccessful && ( {!loading && state.result === 'success' && (
<Formik <Formik
validationSchema={validationSchema} validationSchema={validationSchema}
initialValues={initialValues} initialValues={initialValues}
@ -176,7 +184,7 @@ const Register = () => {
)} )}
</Formik> </Formik>
)} )}
{!loading && !state.wasSuccessful && ( {!loading && state.result === 'failure' && (
<> <>
<Label3>Link has expired</Label3> <Label3>Link has expired</Label3>
</> </>

View file

@ -3,7 +3,7 @@ import { makeStyles, Grid } from '@material-ui/core'
import Paper from '@material-ui/core/Paper' import Paper from '@material-ui/core/Paper'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import QRCode from 'qrcode.react' import QRCode from 'qrcode.react'
import React, { useState } from 'react' import React, { useReducer, useState } from 'react'
import { useLocation, useHistory } from 'react-router-dom' import { useLocation, useHistory } from 'react-router-dom'
import { ActionButton, Button } from 'src/components/buttons' import { ActionButton, Button } from 'src/components/buttons'
@ -28,13 +28,8 @@ const VALIDATE_RESET_2FA_LINK = gql`
` `
const RESET_2FA = gql` const RESET_2FA = gql`
mutation reset2FA( mutation reset2FA($token: String!, $userID: ID!, $code: String!) {
$token: String! reset2FA(token: $token, userID: $userID, code: $code)
$userID: ID!
$secret: String!
$code: String!
) {
reset2FA(token: $token, userID: $userID, secret: $secret, code: $code)
} }
` `
@ -42,37 +37,52 @@ const Reset2FA = () => {
const classes = useStyles() const classes = useStyles()
const history = useHistory() const history = useHistory()
const token = QueryParams().get('t') const token = QueryParams().get('t')
const [userID, setUserID] = useState(null)
const [isLoading, setLoading] = useState(true)
const [wasSuccessful, setSuccess] = useState(false)
const [secret, setSecret] = useState(null)
const [otpauth, setOtpauth] = useState(null)
const [isShowing, setShowing] = useState(false) const [isShowing, setShowing] = useState(false)
const [invalidToken, setInvalidToken] = useState(false) const [invalidToken, setInvalidToken] = useState(false)
const [twoFAConfirmation, setTwoFAConfirmation] = useState('') const [twoFAConfirmation, setTwoFAConfirmation] = useState('')
const initialState = {
userID: null,
secret: null,
otpauth: null,
result: null
}
const reducer = (state, action) => {
const { type, payload } = action
return { ...state, ...payload, result: type }
}
const [state, dispatch] = useReducer(reducer, initialState)
const handle2FAChange = value => { const handle2FAChange = value => {
setTwoFAConfirmation(value) setTwoFAConfirmation(value)
setInvalidToken(false) setInvalidToken(false)
} }
const { error: queryError } = useQuery(VALIDATE_RESET_2FA_LINK, { const { error: queryError, loading } = useQuery(VALIDATE_RESET_2FA_LINK, {
variables: { token: token }, variables: { token: token },
onCompleted: ({ validateReset2FALink: info }) => { onCompleted: ({ validateReset2FALink: info }) => {
setLoading(false)
if (!info) { if (!info) {
setSuccess(false) dispatch({
type: 'failure'
})
} else { } else {
setUserID(info.user_id) dispatch({
setSecret(info.secret) type: 'success',
setOtpauth(info.otpauth) payload: {
setSuccess(true) userID: info.user_id,
secret: info.secret,
otpauth: info.otpauth
}
})
} }
}, },
onError: () => { onError: () => {
setLoading(false) dispatch({
setSuccess(false) type: 'failure'
})
} }
}) })
@ -107,7 +117,7 @@ const Reset2FA = () => {
<Logo className={classes.icon} /> <Logo className={classes.icon} />
<H2 className={classes.title}>Lamassu Admin</H2> <H2 className={classes.title}>Lamassu Admin</H2>
</div> </div>
{!isLoading && wasSuccessful && ( {!loading && state.result === 'success' && (
<> <>
<div className={classes.infoWrapper}> <div className={classes.infoWrapper}>
<Label2 className={classes.info2}> <Label2 className={classes.info2}>
@ -117,7 +127,11 @@ const Reset2FA = () => {
</Label2> </Label2>
</div> </div>
<div className={classes.qrCodeWrapper}> <div className={classes.qrCodeWrapper}>
<QRCode size={240} fgColor={primaryColor} value={otpauth} /> <QRCode
size={240}
fgColor={primaryColor}
value={state.otpauth}
/>
</div> </div>
<div className={classes.secretWrapper}> <div className={classes.secretWrapper}>
<Label2 className={classes.secretLabel}> <Label2 className={classes.secretLabel}>
@ -127,7 +141,7 @@ const Reset2FA = () => {
className={ className={
isShowing ? classes.secret : classes.hiddenSecret isShowing ? classes.secret : classes.hiddenSecret
}> }>
{secret} {state.secret}
</Label2> </Label2>
<ActionButton <ActionButton
color="primary" color="primary"
@ -160,8 +174,7 @@ const Reset2FA = () => {
reset2FA({ reset2FA({
variables: { variables: {
token: token, token: token,
userID: userID, userID: state.userID,
secret: secret,
code: twoFAConfirmation code: twoFAConfirmation
} }
}) })
@ -172,7 +185,7 @@ const Reset2FA = () => {
</div> </div>
</> </>
)} )}
{!isLoading && !wasSuccessful && ( {!loading && state.result === 'failure' && (
<> <>
<Label3>Link has expired</Label3> <Label3>Link has expired</Label3>
</> </>

View file

@ -2,7 +2,7 @@ import { useQuery } from '@apollo/react-hooks'
import { makeStyles, Box, Chip } from '@material-ui/core' import { makeStyles, Box, Chip } from '@material-ui/core'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState, useContext } from 'react' import React, { useReducer, useState, useContext } from 'react'
import AppContext from 'src/AppContext' import AppContext from 'src/AppContext'
import { Link } from 'src/components/buttons' import { Link } from 'src/components/buttons'
@ -39,23 +39,27 @@ const Users = () => {
const { data: userResponse } = useQuery(GET_USERS) const { data: userResponse } = useQuery(GET_USERS)
const [showCreateUserModal, setShowCreateUserModal] = useState(false) const initialState = {
const toggleCreateUserModal = () => showCreateUserModal: false,
setShowCreateUserModal(!showCreateUserModal) showResetPasswordModal: false,
showReset2FAModal: false,
showRoleModal: false,
showEnableUserModal: false
}
const [showResetPasswordModal, setShowResetPasswordModal] = useState(false) const reducer = (_, action) => {
const toggleResetPasswordModal = () => const { type, payload } = action
setShowResetPasswordModal(!showResetPasswordModal) switch (type) {
case 'close':
return initialState
case 'open':
return { ...initialState, [payload]: true }
default:
return initialState
}
}
const [showReset2FAModal, setShowReset2FAModal] = useState(false) const [state, dispatch] = useReducer(reducer, initialState)
const toggleReset2FAModal = () => setShowReset2FAModal(!showReset2FAModal)
const [showRoleModal, setShowRoleModal] = useState(false)
const toggleRoleModal = () => setShowRoleModal(!showRoleModal)
const [showEnableUserModal, setShowEnableUserModal] = useState(false)
const toggleEnableUserModal = () =>
setShowEnableUserModal(!showEnableUserModal)
const [userInfo, setUserInfo] = useState(null) const [userInfo, setUserInfo] = useState(null)
@ -103,7 +107,10 @@ const Users = () => {
checked={u.role === 'superuser'} checked={u.role === 'superuser'}
onClick={() => { onClick={() => {
setUserInfo(u) setUserInfo(u)
toggleRoleModal() dispatch({
type: 'open',
payload: 'showRoleModal'
})
}} }}
value={u.role === 'superuser'} value={u.role === 'superuser'}
/> />
@ -130,7 +137,10 @@ const Users = () => {
className={classes.actionChip} className={classes.actionChip}
onClick={() => { onClick={() => {
setUserInfo(u) setUserInfo(u)
toggleResetPasswordModal() dispatch({
type: 'open',
payload: 'showResetPasswordModal'
})
}} }}
/> />
<Chip <Chip
@ -139,7 +149,10 @@ const Users = () => {
className={classes.actionChip} className={classes.actionChip}
onClick={() => { onClick={() => {
setUserInfo(u) setUserInfo(u)
toggleReset2FAModal() dispatch({
type: 'open',
payload: 'showReset2FAModal'
})
}} }}
/> />
</> </>
@ -157,7 +170,10 @@ const Users = () => {
checked={u.enabled} checked={u.enabled}
onClick={() => { onClick={() => {
setUserInfo(u) setUserInfo(u)
toggleEnableUserModal() dispatch({
type: 'open',
payload: 'showEnableUserModal'
})
}} }}
value={u.enabled} value={u.enabled}
/> />
@ -174,36 +190,40 @@ const Users = () => {
className={classes.tableWidth} className={classes.tableWidth}
display="flex" display="flex"
justifyContent="flex-end"> justifyContent="flex-end">
<Link color="primary" onClick={toggleCreateUserModal}> <Link
color="primary"
onClick={() => {
dispatch({
type: 'open',
payload: 'showCreateUserModal'
})
}}>
Add new user Add new user
</Link> </Link>
</Box> </Box>
<DataTable elements={elements} data={R.path(['users'])(userResponse)} /> <DataTable elements={elements} data={R.path(['users'])(userResponse)} />
<CreateUserModal <CreateUserModal state={state} dispatch={dispatch} />
showModal={showCreateUserModal}
toggleModal={toggleCreateUserModal}
/>
<ResetPasswordModal <ResetPasswordModal
showModal={showResetPasswordModal} state={state}
toggleModal={toggleResetPasswordModal} dispatch={dispatch}
user={userInfo} user={userInfo}
requiresConfirmation={userInfo?.role === 'superuser'} requiresConfirmation={userInfo?.role === 'superuser'}
/> />
<Reset2FAModal <Reset2FAModal
showModal={showReset2FAModal} state={state}
toggleModal={toggleReset2FAModal} dispatch={dispatch}
user={userInfo} user={userInfo}
requiresConfirmation={userInfo?.role === 'superuser'} requiresConfirmation={userInfo?.role === 'superuser'}
/> />
<ChangeRoleModal <ChangeRoleModal
showModal={showRoleModal} state={state}
toggleModal={toggleRoleModal} dispatch={dispatch}
user={userInfo} user={userInfo}
requiresConfirmation={userInfo?.role === 'superuser'} requiresConfirmation={userInfo?.role === 'superuser'}
/> />
<EnableUserModal <EnableUserModal
showModal={showEnableUserModal} state={state}
toggleModal={toggleEnableUserModal} dispatch={dispatch}
user={userInfo} user={userInfo}
requiresConfirmation={userInfo?.role === 'superuser'} requiresConfirmation={userInfo?.role === 'superuser'}
/> />

View file

@ -29,12 +29,7 @@ const CHANGE_USER_ROLE = gql`
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const ChangeRoleModal = ({ const ChangeRoleModal = ({ state, dispatch, user, requiresConfirmation }) => {
showModal,
toggleModal,
user,
requiresConfirmation
}) => {
const classes = useStyles() const classes = useStyles()
const [changeUserRole] = useMutation(CHANGE_USER_ROLE, { const [changeUserRole] = useMutation(CHANGE_USER_ROLE, {
@ -56,18 +51,21 @@ const ChangeRoleModal = ({
const handleClose = () => { const handleClose = () => {
setConfirmation(null) setConfirmation(null)
toggleModal() dispatch({
type: 'close',
payload: 'showRoleModal'
})
} }
return ( return (
(showModal && requiresConfirmation && !confirmation && ( (state.showRoleModal && requiresConfirmation && !confirmation && (
<Input2FAModal <Input2FAModal
showModal={showModal} showModal={state.showRoleModal}
handleClose={handleClose} handleClose={handleClose}
setConfirmation={setConfirmation} setConfirmation={setConfirmation}
/> />
)) || )) ||
(showModal && ( (state.showRoleModal && (
<Modal <Modal
closeOnBackdropClick={true} closeOnBackdropClick={true}
width={450} width={450}

View file

@ -12,7 +12,7 @@ import { Button } from 'src/components/buttons'
import { TextInput, RadioGroup } from 'src/components/inputs/formik' import { TextInput, RadioGroup } from 'src/components/inputs/formik'
import { H1, H3, Info2, P, Mono } from 'src/components/typography' import { H1, H3, Info2, P, Mono } from 'src/components/typography'
import CopyToClipboard from 'src/pages/Transactions/CopyToClipboard' import CopyToClipboard from 'src/pages/Transactions/CopyToClipboard'
import { URI } from 'src/utils/apollo' import { urlResolver } from 'src/utils/urlResolver'
import styles from '../UserManagement.styles' import styles from '../UserManagement.styles'
@ -39,7 +39,7 @@ const initialValues = {
role: '' role: ''
} }
const CreateUserModal = ({ showModal, toggleModal }) => { const CreateUserModal = ({ state, dispatch }) => {
const classes = useStyles() const classes = useStyles()
const [usernameField, setUsernameField] = useState('') const [usernameField, setUsernameField] = useState('')
@ -58,12 +58,15 @@ const CreateUserModal = ({ showModal, toggleModal }) => {
const handleClose = () => { const handleClose = () => {
setCreateUserURL(null) setCreateUserURL(null)
toggleModal() dispatch({
type: 'close',
payload: 'showCreateUserModal'
})
} }
const [createUser, { error }] = useMutation(CREATE_USER, { const [createUser, { error }] = useMutation(CREATE_USER, {
onCompleted: ({ createRegisterToken: token }) => { onCompleted: ({ createRegisterToken: token }) => {
setCreateUserURL(`${URI}/register?t=${token.token}`) setCreateUserURL(urlResolver(`/register?t=${token.token}`))
} }
}) })
@ -81,7 +84,7 @@ const CreateUserModal = ({ showModal, toggleModal }) => {
return ( return (
<> <>
{showModal && !createUserURL && ( {state.showCreateUserModal && !createUserURL && (
<Modal <Modal
closeOnBackdropClick={true} closeOnBackdropClick={true}
width={600} width={600}
@ -137,7 +140,7 @@ const CreateUserModal = ({ showModal, toggleModal }) => {
</Formik> </Formik>
</Modal> </Modal>
)} )}
{showModal && createUserURL && ( {state.showCreateUserModal && createUserURL && (
<Modal <Modal
closeOnBackdropClick={true} closeOnBackdropClick={true}
width={500} width={500}

View file

@ -1,72 +0,0 @@
import { makeStyles } from '@material-ui/core/styles'
import React from 'react'
import Modal from 'src/components/Modal'
import { Button } from 'src/components/buttons'
import { Info2, P } from 'src/components/typography'
import styles from '../UserManagement.styles'
const useStyles = makeStyles(styles)
const DeleteUserModal = ({
showModal,
toggleModal,
user,
confirm,
inputConfirmToggle,
setAction
}) => {
const classes = useStyles()
const handleClose = () => {
toggleModal()
}
return (
showModal && (
<Modal
closeOnBackdropClick={true}
width={600}
height={275}
handleClose={handleClose}
open={true}>
<Info2 className={classes.modalTitle}>Delete {user.username}?</Info2>
<P className={classes.info}>
You are about to delete {user.username}. This will remove existent
sessions and revoke this user's permissions to access the system.
</P>
<P className={classes.info}>
This is a <b>PERMANENT</b> operation. Do you wish to proceed?
</P>
<div className={classes.footer}>
<Button
className={classes.submit}
onClick={() => {
if (user.role === 'superuser') {
setAction(() =>
confirm.bind(null, {
variables: {
id: user.id
}
})
)
inputConfirmToggle()
} else {
confirm({
variables: {
id: user.id
}
})
}
handleClose()
}}>
Confirm
</Button>
</div>
</Modal>
)
)
}
export default DeleteUserModal

View file

@ -29,12 +29,7 @@ const DISABLE_USER = gql`
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const EnableUserModal = ({ const EnableUserModal = ({ state, dispatch, user, requiresConfirmation }) => {
showModal,
toggleModal,
user,
requiresConfirmation
}) => {
const classes = useStyles() const classes = useStyles()
const [enableUser] = useMutation(ENABLE_USER, { const [enableUser] = useMutation(ENABLE_USER, {
@ -47,37 +42,46 @@ const EnableUserModal = ({
const [confirmation, setConfirmation] = useState(null) const [confirmation, setConfirmation] = useState(null)
const disable = () => {
disableUser({
variables: {
confirmationCode: confirmation,
id: user.id
}
})
}
const enable = () => {
enableUser({
variables: {
confirmationCode: confirmation,
id: user.id
}
})
}
const submit = () => { const submit = () => {
user?.enabled user?.enabled ? disable() : enable()
? disableUser({
variables: {
confirmationCode: confirmation,
id: user.id
}
})
: enableUser({
variables: {
confirmationCode: confirmation,
id: user.id
}
})
handleClose() handleClose()
} }
const handleClose = () => { const handleClose = () => {
setConfirmation(null) setConfirmation(null)
toggleModal() dispatch({
type: 'close',
payload: 'showEnableUserModal'
})
} }
return ( return (
(showModal && requiresConfirmation && !confirmation && ( (state.showEnableUserModal && requiresConfirmation && !confirmation && (
<Input2FAModal <Input2FAModal
showModal={showModal} showModal={state.showEnableUserModal}
handleClose={handleClose} handleClose={handleClose}
setConfirmation={setConfirmation} setConfirmation={setConfirmation}
/> />
)) || )) ||
(showModal && ( (state.showEnableUserModal && (
<Modal <Modal
closeOnBackdropClick={true} closeOnBackdropClick={true}
width={450} width={450}

View file

@ -6,7 +6,7 @@ import React, { useEffect, useState } from 'react'
import Modal from 'src/components/Modal' import Modal from 'src/components/Modal'
import { Info2, P, Mono } from 'src/components/typography' import { Info2, P, Mono } from 'src/components/typography'
import CopyToClipboard from 'src/pages/Transactions/CopyToClipboard' import CopyToClipboard from 'src/pages/Transactions/CopyToClipboard'
import { URI } from 'src/utils/apollo' import { urlResolver } from 'src/utils/urlResolver'
import styles from '../UserManagement.styles' import styles from '../UserManagement.styles'
@ -24,12 +24,7 @@ const CREATE_RESET_2FA_TOKEN = gql`
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const Reset2FAModal = ({ const Reset2FAModal = ({ state, dispatch, user, requiresConfirmation }) => {
showModal,
toggleModal,
user,
requiresConfirmation
}) => {
const classes = useStyles() const classes = useStyles()
const [reset2FAUrl, setReset2FAUrl] = useState('') const [reset2FAUrl, setReset2FAUrl] = useState('')
@ -37,7 +32,7 @@ const Reset2FAModal = ({
CREATE_RESET_2FA_TOKEN, CREATE_RESET_2FA_TOKEN,
{ {
onCompleted: ({ createReset2FAToken: token }) => { onCompleted: ({ createReset2FAToken: token }) => {
setReset2FAUrl(`${URI}/reset2fa?t=${token.token}`) setReset2FAUrl(urlResolver(`/reset2fa?t=${token.token}`))
} }
} }
) )
@ -45,7 +40,7 @@ const Reset2FAModal = ({
const [confirmation, setConfirmation] = useState(null) const [confirmation, setConfirmation] = useState(null)
useEffect(() => { useEffect(() => {
showModal && state.showReset2FAModal &&
(confirmation || !requiresConfirmation) && (confirmation || !requiresConfirmation) &&
createReset2FAToken({ createReset2FAToken({
variables: { variables: {
@ -53,49 +48,60 @@ const Reset2FAModal = ({
userID: user?.id userID: user?.id
} }
}) })
}, [confirmation, createReset2FAToken, requiresConfirmation, showModal, user]) }, [
confirmation,
createReset2FAToken,
requiresConfirmation,
state.showReset2FAModal,
user?.id
])
const handleClose = () => { const handleClose = () => {
setConfirmation(null) setConfirmation(null)
toggleModal() dispatch({
type: 'close',
payload: 'showReset2FAModal'
})
} }
return ( return (
(showModal && requiresConfirmation && !confirmation && ( (state.showReset2FAModal && requiresConfirmation && !confirmation && (
<Input2FAModal <Input2FAModal
showModal={showModal} showModal={state.showReset2FAModal}
handleClose={handleClose} handleClose={handleClose}
setConfirmation={setConfirmation} setConfirmation={setConfirmation}
/> />
)) || )) ||
(showModal && (confirmation || !requiresConfirmation) && !loading && ( (state.showReset2FAModal &&
<Modal (confirmation || !requiresConfirmation) &&
closeOnBackdropClick={true} !loading && (
width={500} <Modal
height={200} closeOnBackdropClick={true}
handleClose={handleClose} width={500}
open={true}> height={200}
<Info2 className={classes.modalTitle}> handleClose={handleClose}
Reset 2FA for {user.username} open={true}>
</Info2> <Info2 className={classes.modalTitle}>
<P className={classes.info}> Reset 2FA for {user.username}
Safely share this link with {user.username} for a two-factor </Info2>
authentication reset. <P className={classes.info}>
</P> Safely share this link with {user.username} for a two-factor
<div className={classes.addressWrapper}> authentication reset.
<Mono className={classes.address}> </P>
<strong> <div className={classes.addressWrapper}>
<CopyToClipboard <Mono className={classes.address}>
className={classes.link} <strong>
buttonClassname={classes.copyToClipboard} <CopyToClipboard
wrapperClassname={classes.linkWrapper}> className={classes.link}
{reset2FAUrl} buttonClassname={classes.copyToClipboard}
</CopyToClipboard> wrapperClassname={classes.linkWrapper}>
</strong> {reset2FAUrl}
</Mono> </CopyToClipboard>
</div> </strong>
</Modal> </Mono>
)) </div>
</Modal>
))
) )
} }

View file

@ -6,7 +6,7 @@ import React, { useEffect, useState } from 'react'
import Modal from 'src/components/Modal' import Modal from 'src/components/Modal'
import { Info2, P, Mono } from 'src/components/typography' import { Info2, P, Mono } from 'src/components/typography'
import CopyToClipboard from 'src/pages/Transactions/CopyToClipboard' import CopyToClipboard from 'src/pages/Transactions/CopyToClipboard'
import { URI } from 'src/utils/apollo' import { urlResolver } from 'src/utils/urlResolver'
import styles from '../UserManagement.styles' import styles from '../UserManagement.styles'
@ -28,8 +28,8 @@ const CREATE_RESET_PASSWORD_TOKEN = gql`
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const ResetPasswordModal = ({ const ResetPasswordModal = ({
showModal, state,
toggleModal, dispatch,
user, user,
requiresConfirmation requiresConfirmation
}) => { }) => {
@ -40,7 +40,7 @@ const ResetPasswordModal = ({
CREATE_RESET_PASSWORD_TOKEN, CREATE_RESET_PASSWORD_TOKEN,
{ {
onCompleted: ({ createResetPasswordToken: token }) => { onCompleted: ({ createResetPasswordToken: token }) => {
setResetPasswordUrl(`${URI}/resetpassword?t=${token.token}`) setResetPasswordUrl(urlResolver(`/resetpassword?t=${token.token}`))
} }
} }
) )
@ -48,7 +48,7 @@ const ResetPasswordModal = ({
const [confirmation, setConfirmation] = useState(null) const [confirmation, setConfirmation] = useState(null)
useEffect(() => { useEffect(() => {
showModal && state.showResetPasswordModal &&
(confirmation || !requiresConfirmation) && (confirmation || !requiresConfirmation) &&
createResetPasswordToken({ createResetPasswordToken({
variables: { variables: {
@ -59,51 +59,56 @@ const ResetPasswordModal = ({
}, [ }, [
confirmation, confirmation,
createResetPasswordToken, createResetPasswordToken,
showModal, requiresConfirmation,
user, state.showResetPasswordModal,
requiresConfirmation user?.id
]) ])
const handleClose = () => { const handleClose = () => {
setConfirmation(null) setConfirmation(null)
toggleModal() dispatch({
type: 'close',
payload: 'showResetPasswordModal'
})
} }
return ( return (
(showModal && requiresConfirmation && !confirmation && ( (state.showResetPasswordModal && requiresConfirmation && !confirmation && (
<Input2FAModal <Input2FAModal
showModal={showModal} showModal={state.showResetPasswordModal}
handleClose={handleClose} handleClose={handleClose}
setConfirmation={setConfirmation} setConfirmation={setConfirmation}
/> />
)) || )) ||
(showModal && (confirmation || !requiresConfirmation) && !loading && ( (state.showResetPasswordModal &&
<Modal (confirmation || !requiresConfirmation) &&
closeOnBackdropClick={true} !loading && (
width={500} <Modal
height={180} closeOnBackdropClick={true}
handleClose={handleClose} width={500}
open={true}> height={180}
<Info2 className={classes.modalTitle}> handleClose={handleClose}
Reset password for {user.username} open={true}>
</Info2> <Info2 className={classes.modalTitle}>
<P className={classes.info}> Reset password for {user.username}
Safely share this link with {user.username} for a password reset. </Info2>
</P> <P className={classes.info}>
<div className={classes.addressWrapper}> Safely share this link with {user.username} for a password reset.
<Mono className={classes.address}> </P>
<strong> <div className={classes.addressWrapper}>
<CopyToClipboard <Mono className={classes.address}>
className={classes.link} <strong>
buttonClassname={classes.copyToClipboard} <CopyToClipboard
wrapperClassname={classes.linkWrapper}> className={classes.link}
{resetPasswordUrl} buttonClassname={classes.copyToClipboard}
</CopyToClipboard> wrapperClassname={classes.linkWrapper}>
</strong> {resetPasswordUrl}
</Mono> </CopyToClipboard>
</div> </strong>
</Modal> </Mono>
)) </div>
</Modal>
))
) )
} }

View file

@ -0,0 +1,6 @@
const url =
process.env.NODE_ENV === 'development' ? 'https://localhost:3001' : ''
const urlResolver = content => `${url}${content}`
export { urlResolver }