diff --git a/src/App.js b/src/App.js index 1e0aee5a..110d3567 100644 --- a/src/App.js +++ b/src/App.js @@ -25,8 +25,9 @@ function App() { // TODO: This is assuming a host at 8000 and 8080 - should probably be set more dynamically const hostnames = { local: { + userdb: 'http://127.0.0.1:8080/users/', + bcoapi: 'http://127.0.0.1:8000/api/', bcoapi_accounts_new: 'http://127.0.0.1:8000/api/accounts/new/', - // TODO: this v is not listed in the URLS for the bco API bcoapi_description_permissions: 'http://127.0.0.1:8000/api/description/permissions/', bcoapi_objects_create: 'http://127.0.0.1:8000/api/objects/create/', bcoapi_objects_list: 'http://127.0.0.1:8000/api/objects/token/', @@ -46,6 +47,8 @@ function App() { ] }, test: { + userdb: 'https://test.portal.biochemistry.gwu.edu/users/', + bcoapi: 'https://test.portal.biochemistry.gwu.edu/api/', bcoapi_accounts_new: 'https://test.portal.biochemistry.gwu.edu/api/accounts/new/', bcoapi_description_permissions: 'https://test.portal.biochemistry.gwu.edu/api/description/permissions/', bcoapi_objects_create: 'https://test.portal.biochemistry.gwu.edu/api/objects/create/', @@ -66,6 +69,8 @@ function App() { ] }, production: { + userdb: 'https://biocomputeobject.org/users/', + bcoapi: 'https://biocomputeobject.org/api/', bcoapi_accounts_new: 'https://biocomputeobject.org/api/accounts/new/', bcoapi_description_permissions: 'https://biocomputeobject.org/api/description/permissions/', bcoapi_objects_create: 'https://biocomputeobject.org/api/objects/create/', diff --git a/src/components/API/RetrieveDraftObject.js b/src/components/API/RetrieveDraftObject.js index f8d38a67..35344745 100644 --- a/src/components/API/RetrieveDraftObject.js +++ b/src/components/API/RetrieveDraftObject.js @@ -3,7 +3,7 @@ /* Retrievs a draft object using the current user's token and an object's draft id */ -export default function RetrieveDraftObject(objectId) { +export default function RetrieveDraftObject(objectId, setObjectContents) { let objectContents = ''; let userToken = ''; @@ -11,7 +11,7 @@ export default function RetrieveDraftObject(objectId) { userToken = item.token; }); - fetch(objectId, { + fetch(`DRAFT/${objectId}`, { method: 'GET', headers: { Authorization: `Token ${userToken}`, @@ -25,7 +25,7 @@ export default function RetrieveDraftObject(objectId) { })) .then((response) => { if (response.status === 200) { - objectContents = response.data; + setObjectContents(response.data); console.log('Server return contents33: ', objectContents); localStorage.setItem('bco', JSON.stringify(objectContents)); } diff --git a/src/components/API/UserdbChangePassword.js b/src/components/API/UserdbChangePassword.js new file mode 100644 index 00000000..bd3ad292 --- /dev/null +++ b/src/components/API/UserdbChangePassword.js @@ -0,0 +1,37 @@ +// /src/components/API/UserdbChangePassword.js + +/* Returns a JSON Web Token that can be used for authenticated requests. */ + +export default function UserdbChangePassword(values) { + fetch(`${values.userdb}change_password/`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + old_password: values.old_password, + new_password: values.new_password, + }), + headers: { + Authorization: `JWT ${localStorage.getItem('token')}`, + 'Content-Type': 'application/json' + }, + }) + .then((response) => { + if (!response.ok) { + throw new Error(response.status); + } else { + return response.json() + .then((data) => { + alert('Your password has been updated.'); + console.log('data', ); + }); + } + }) + .catch((error) => { + // TODO: This needs to be fleshed out to get all errors and deal with them + alert(`The provided OLD PASSWORD was not correct. ${error}`); + console.log('error', error); + // return error; + }); +} diff --git a/src/components/API/UserdbConfirmPasswordReset.js b/src/components/API/UserdbConfirmPasswordReset.js new file mode 100644 index 00000000..9b72164d --- /dev/null +++ b/src/components/API/UserdbConfirmPasswordReset.js @@ -0,0 +1,34 @@ +// /src/components/API/UserdbConfirmPasswordReset.js + +/* Returns a JSON Web Token that can be used for authenticated requests. */ + +export default function UserdbConfirmPasswordReset(values) { + fetch(`${values.userdb}password_reset/confirm/`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + password: values.password, + token: values.token + }) + }) + .then((response) => { + if (!response.ok) { + throw new Error(response.status); + } else { + return response.json() + .then((data) => { + alert('Your password has been reset.'); + console.log('data', data); + window.location.href = '/login'; + }); + } + }) + .catch((error) => { + // TODO: This needs to be fleshed out to get all errors and deal with them + alert(`Unable to locate an account with those provided credentials. ${error}`); + console.log('error', error); + // return error; + }); +} diff --git a/src/components/API/UserdbPasswordReset.js b/src/components/API/UserdbPasswordReset.js new file mode 100644 index 00000000..fe99ae2b --- /dev/null +++ b/src/components/API/UserdbPasswordReset.js @@ -0,0 +1,33 @@ +// /src/components/API/UserdbPasswordReset.js + +/* Returns a JSON Web Token that can be used for authenticated requests. */ + +export default function UserdbPasswordReset(values, setAlternateView, alternateView) { + fetch(`${values.userdb}password_reset/`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + email: values.email + }) + }) + .then((response) => { + if (!response.ok) { + throw new Error(response.status); + } else { + return response.json() + .then((data) => { + alert('A password reset email has been sent to the provided email address.'); + console.log('data', alternateView); + setAlternateView(true); + }); + } + }) + .catch((error) => { + // TODO: This needs to be fleshed out to get all errors and deal with them + alert(`Unable to locate an account with those provided credentials. ${error}`); + console.log('error', error); + // return error; + }); +} diff --git a/src/components/API/untitled.txt b/src/components/API/untitled.txt deleted file mode 100644 index 82b9c188..00000000 --- a/src/components/API/untitled.txt +++ /dev/null @@ -1,33 +0,0 @@ -// /src/components/API/RetrieveDraftObjectPermissions.js - -/* Retrievs a draft object using the current user's token and an object's -draft id */ - -export default function RetrieveDraftObject(objectId) { - let objectContents = ''; - let userToken = ''; - - JSON.parse(localStorage.getItem('user')).apiinfo.forEach((item) => { - userToken = item.token; - }); - - fetch(objectId, { - method: 'GET', - headers: { - Authorization: `Token ${userToken}`, - 'Content-type': 'application/json; charset=UTF-8' - } - }) - .then((res) => res.json() - .then((data) => ({ - data, - status: res.status - })) - .then((response) => { - if (response.status === 200) { - objectContents = response.data; - console.log('Server return contents33: ', objectContents); - localStorage.setItem('bco', JSON.stringify(objectContents)); - } - })); -} diff --git a/src/components/ResetPassword.js b/src/components/ResetPassword.js new file mode 100644 index 00000000..45b5ab0e --- /dev/null +++ b/src/components/ResetPassword.js @@ -0,0 +1,215 @@ +// src/views/auth/ResetPassword.js + +import React, { useContext, useState } from 'react'; +import { Link as RouterLink } from 'react-router-dom'; +import * as Yup from 'yup'; +import { Formik } from 'formik'; +import { + Box, + Button, + Container, + Link, + TextField, + Typography, + makeStyles +} from '@material-ui/core'; +import Page from 'src/components/Page'; + +import UserdbPasswordReset from 'src/components/API/UserdbPasswordReset'; +import UserdbConfirmPasswordReset from 'src/components/API/UserdbConfirmPasswordReset'; +import { FetchContext } from 'src/App'; + +const useStyles = makeStyles((theme) => ({ + alertSpec: { + width: '100%', + '& > * + *': { + marginTop: theme.spacing(2), + } + }, + root: { + backgroundColor: theme.palette.background.dark, + height: '100%', + paddingBottom: theme.spacing(3), + paddingTop: theme.spacing(3) + } +})); + +const ResetPassword = () => { + const [alternateView, setAlternateView] = useState(false); + const classes = useStyles(); + const fc = useContext(FetchContext); + console.log('fc', fc.sending.userdb); + return ( + + {(alternateView === true) + ? ( + + + { + // Determine whether or not our login was legitimate. + console.log('values', values); + UserdbConfirmPasswordReset(values); + }} + > + {({ + errors, handleBlur, handleChange, handleSubmit, touched, values + }) => ( +
+ + + Create New Password + + + Enter the token you recieved in your email, and your new password. + + + + + + + + + + Don't have an account? + {' '} + + Sign up + + + + )} +
+
+
+ ) + : ( + + + { + // Determine whether or not our login was legitimate. + console.log('values', values); + UserdbPasswordReset(values, setAlternateView, alternateView); + }} + > + {({ + errors, handleBlur, handleChange, handleSubmit, touched, values + }) => ( +
+ + + Reset Password + + + Enter your email address. If there is an account associated with that address + we will provide you a link to reset your password + + + + + + + + Don't have an account? + {' '} + + Sign up + + + + )} +
+
+
+ )} +
+ ); +}; + +export default ResetPassword; diff --git a/src/layouts/shared/NavItem.js b/src/layouts/shared/NavItem.js index 0691e965..ccbd6ef3 100644 --- a/src/layouts/shared/NavItem.js +++ b/src/layouts/shared/NavItem.js @@ -59,7 +59,7 @@ const NavItem = ({ {...rest} > + + + + )} + + ); +}; + +Password.propTypes = { + className: PropTypes.string +}; + +export default Password; diff --git a/src/views/account/AccountView/index.js b/src/views/account/AccountView/index.js index 6b27f517..ddaf814d 100644 --- a/src/views/account/AccountView/index.js +++ b/src/views/account/AccountView/index.js @@ -2,14 +2,21 @@ import React, { useEffect } from 'react'; import { + Accordion, + AccordionSummary, + AccordionDetails, Container, Grid, - makeStyles + makeStyles, + Typography, } from '@material-ui/core'; -import AddServer from './AddServer' + +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import Page from 'src/components/Page'; +import AddServer from './AddServer'; import Profile from './Profile'; import ServerInfo from './ServerInfo'; +import Password from './Password'; const useStyles = makeStyles((theme) => ({ root: { @@ -25,7 +32,6 @@ const useStyles = makeStyles((theme) => ({ export const ParentContext = React.createContext(); const AccountView = () => { - const classes = useStyles(); // State for the add server and group dialogs. @@ -37,38 +43,57 @@ const AccountView = () => { // On page load, load the user's information. useEffect(() => { - // "Fake" that a server has been added. setServerAdded(true); - - }, []) + }, []); return ( - - - - - - + + + + + + + - - - - - - + + } + aria-controls="panel1a-content" + id="panel1a-header" + > + + Change Password (click to expand) + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - + + + ); }; diff --git a/src/views/auth/LoginView.js b/src/views/auth/LoginView.js index 337511d5..ee15c195 100644 --- a/src/views/auth/LoginView.js +++ b/src/views/auth/LoginView.js @@ -78,6 +78,11 @@ const LoginView = () => { Sign in using your Portal credentials. + + + Forgot Password? Reset it here. + + diff --git a/src/views/auth/ResetPassword.js b/src/views/auth/ResetPassword.js new file mode 100644 index 00000000..45b5ab0e --- /dev/null +++ b/src/views/auth/ResetPassword.js @@ -0,0 +1,215 @@ +// src/views/auth/ResetPassword.js + +import React, { useContext, useState } from 'react'; +import { Link as RouterLink } from 'react-router-dom'; +import * as Yup from 'yup'; +import { Formik } from 'formik'; +import { + Box, + Button, + Container, + Link, + TextField, + Typography, + makeStyles +} from '@material-ui/core'; +import Page from 'src/components/Page'; + +import UserdbPasswordReset from 'src/components/API/UserdbPasswordReset'; +import UserdbConfirmPasswordReset from 'src/components/API/UserdbConfirmPasswordReset'; +import { FetchContext } from 'src/App'; + +const useStyles = makeStyles((theme) => ({ + alertSpec: { + width: '100%', + '& > * + *': { + marginTop: theme.spacing(2), + } + }, + root: { + backgroundColor: theme.palette.background.dark, + height: '100%', + paddingBottom: theme.spacing(3), + paddingTop: theme.spacing(3) + } +})); + +const ResetPassword = () => { + const [alternateView, setAlternateView] = useState(false); + const classes = useStyles(); + const fc = useContext(FetchContext); + console.log('fc', fc.sending.userdb); + return ( + + {(alternateView === true) + ? ( + + + { + // Determine whether or not our login was legitimate. + console.log('values', values); + UserdbConfirmPasswordReset(values); + }} + > + {({ + errors, handleBlur, handleChange, handleSubmit, touched, values + }) => ( +
+ + + Create New Password + + + Enter the token you recieved in your email, and your new password. + + + + + + + + + + Don't have an account? + {' '} + + Sign up + + + + )} +
+
+
+ ) + : ( + + + { + // Determine whether or not our login was legitimate. + console.log('values', values); + UserdbPasswordReset(values, setAlternateView, alternateView); + }} + > + {({ + errors, handleBlur, handleChange, handleSubmit, touched, values + }) => ( +
+ + + Reset Password + + + Enter your email address. If there is an account associated with that address + we will provide you a link to reset your password + + + + + + + + Don't have an account? + {' '} + + Sign up + + + + )} +
+
+
+ )} +
+ ); +}; + +export default ResetPassword; diff --git a/src/views/builder/BuilderView/ColorCoded/DescriptionDomain.js b/src/views/builder/BuilderView/ColorCoded/DescriptionDomain.js index ee611b92..e607c4bc 100644 --- a/src/views/builder/BuilderView/ColorCoded/DescriptionDomain.js +++ b/src/views/builder/BuilderView/ColorCoded/DescriptionDomain.js @@ -612,7 +612,7 @@ export default function DescriptionDomain({ ) : ( - + @@ -721,7 +721,7 @@ export default function DescriptionDomain({ } ))} - + @@ -782,7 +782,7 @@ export default function DescriptionDomain({ { items.ddPipelineSteps.map((item, index) => ( <> - + {compCheck} @@ -802,7 +802,7 @@ export default function DescriptionDomain({ - + { - item.prerequisite !== undefined ? ( item.prerequisite.map((subitem, subindex) => ( - <> - + + setPrerequisiteInput(e, index, 'prerequisite', subindex, 'name')} /> - + setPrerequisiteInput(e, index, 'prerequisite', subindex, 'filename')} /> - + setPrerequisiteInput(e, index, 'prerequisite', subindex, 'uri')} /> - + setPrerequisiteInput(e, index, 'prerequisite', subindex, 'access_time')} /> - + setPrerequisiteInput(e, index, 'prerequisite', subindex, 'sha1_checksum')} /> { subindex !== item.prerequisite.length - 1 ? ( - + ) : ( - + ) } - + )) - ) - - : () + : () } - + @@ -884,42 +881,44 @@ export default function DescriptionDomain({ { - item.input_list.map((subitem, subindex) => ( - <> - - - setListInput(e, index, 'input_list', subindex, 'filename')} /> - - - setListInput(e, index, 'input_list', subindex, 'uri')} /> - - - setListInput(e, index, 'input_list', subindex, 'access_time')} /> - - - setListInput(e, index, 'input_list', subindex, 'sha1_checksum')} /> - - { - subindex !== item.input_list.length - 1 - ? ( - - - - ) - : ( - - - - ) - } - - )) + item.input_list !== undefined + ? ( + item.input_list.map((subitem, subindex) => ( + + + setListInput(e, index, 'input_list', subindex, 'filename')} /> + + + setListInput(e, index, 'input_list', subindex, 'uri')} /> + + + setListInput(e, index, 'input_list', subindex, 'access_time')} /> + + + setListInput(e, index, 'input_list', subindex, 'sha1_checksum')} /> + + { + subindex !== item.input_list.length - 1 + ? ( + + + + ) + : ( + + + + ) + } + + ))) + : () } - + @@ -942,41 +941,44 @@ export default function DescriptionDomain({ { - item.output_list.map((subitem, subindex) => ( - <> - - setListInput(e, index, 'output_list', subindex, 'filename')} /> - - - setListInput(e, index, 'output_list', subindex, 'uri')} /> - - - setListInput(e, index, 'output_list', subindex, 'access_time')} /> - - - setListInput(e, index, 'output_list', subindex, 'sha1_checksum')} /> - - { - subindex !== item.output_list.length - 1 - ? ( - - - - ) - : ( - - - - ) - } - - )) + item.output_list !== undefined + ? ( + item.output_list.map((subitem, subindex) => ( + + + setListInput(e, index, 'output_list', subindex, 'filename')} /> + + + setListInput(e, index, 'output_list', subindex, 'uri')} /> + + + setListInput(e, index, 'output_list', subindex, 'access_time')} /> + + + setListInput(e, index, 'output_list', subindex, 'sha1_checksum')} /> + + { + subindex !== item.output_list.length - 1 + ? ( + + + + ) + : ( + + + + ) + } + + ))) + : () } - + @@ -989,7 +991,7 @@ export default function DescriptionDomain({ )) - } + } - - ); -} diff --git a/src/views/objects/ObjectView/Tools/LogicField.js b/src/views/objects/ObjectView/Tools/LogicField.js deleted file mode 100644 index 163eea99..00000000 --- a/src/views/objects/ObjectView/Tools/LogicField.js +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import InputLabel from '@material-ui/core/InputLabel'; -import MenuItem from '@material-ui/core/MenuItem'; -import FormHelperText from '@material-ui/core/FormHelperText'; -import FormControl from '@material-ui/core/FormControl'; -import Select from '@material-ui/core/Select'; - -const useStyles = makeStyles((theme) => ({ - formControl: { - margin: theme.spacing(1), - marginLeft: '0', - marginRight: '0', - minWidth: 90, - }, - selectEmpty: { - marginTop: theme.spacing(2), - }, -})); - -export default function LogicField() { - const classes = useStyles(); - const [age, setAge] = React.useState(''); - - const handleChange = (event) => { - setAge(event.target.value); - }; - - return ( -
- - Operator - - -
- ); -} diff --git a/src/views/objects/ObjectView/Tools/RegexBox.js b/src/views/objects/ObjectView/Tools/RegexBox.js deleted file mode 100644 index 2258b89d..00000000 --- a/src/views/objects/ObjectView/Tools/RegexBox.js +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import FormGroup from '@material-ui/core/FormGroup'; -import FormControlLabel from '@material-ui/core/FormControlLabel'; -import Checkbox from '@material-ui/core/Checkbox'; - -export default function RegexBox() { - const [state, setState] = React.useState({ - checkedB: true - }); - - const handleChange = (event) => { - setState({ ...state, [event.target.name]: event.target.checked }); - }; - - return ( - - - } - label="REGEX" - /> - - ); -} diff --git a/src/views/objects/ObjectView/Tools/SearchField.js b/src/views/objects/ObjectView/Tools/SearchField.js deleted file mode 100644 index 7d441b7e..00000000 --- a/src/views/objects/ObjectView/Tools/SearchField.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import TextField from '@material-ui/core/TextField'; - -const useStyles = makeStyles((theme) => ({ - root: { - '& > *': { - margin: theme.spacing(1), - marginLeft: '0', - marginRight: '0' - }, - }, -})); - -export default function SearchField() { - const classes = useStyles(); - - return ( -
- - - ); -} \ No newline at end of file diff --git a/src/views/objects/ObjectView/index.js b/src/views/objects/ObjectView/index.js index 4339c041..ddb742e6 100644 --- a/src/views/objects/ObjectView/index.js +++ b/src/views/objects/ObjectView/index.js @@ -1,22 +1,27 @@ // src/views/objects/ObjectView/index.js -import React from 'react'; - -// Tools +import React, { useContext } from 'react'; +import { FetchContext } from 'src/App'; import Tools from './Tools'; - -// Views import Views from './Views'; - // This is the parent for the object views. // The state model is based on https://reactjs.org/docs/lifting-state-up.html export default function ObjectView() { + const fc = useContext(FetchContext); // Split to get a real URI, then ask for the object. return ( -
- - -
+ (fc.isLoggedIn === false) + ? ( +
+ +
+ ) + : ( +
+ + +
+ ) ); }