diff options
44 files changed, 423 insertions, 607 deletions
diff --git a/opendc-web/opendc-web-ui/package.json b/opendc-web/opendc-web-ui/package.json index f6917398..9c41c2e2 100644 --- a/opendc-web/opendc-web-ui/package.json +++ b/opendc-web/opendc-web-ui/package.json @@ -17,6 +17,7 @@ "license": "MIT", "private": true, "dependencies": { + "@auth0/auth0-react": "^1.5.0", "@sentry/react": "^5.30.0", "@sentry/tracing": "^5.30.0", "approximate-number": "~2.0.0", @@ -32,14 +33,12 @@ "react": "~17.0.2", "react-dom": "~17.0.2", "react-fontawesome": "~1.7.1", - "react-google-login": "~5.1.14", "react-hotkeys": "^2.0.0", "react-konva": "~17.0.2-0", "react-redux": "~7.2.0", "reactstrap": "^8.9.0", "recharts": "~2.0.9", "redux": "~4.0.5", - "redux-localstorage": "^0.4.1", "redux-logger": "~3.0.6", "redux-saga": "~1.1.3", "redux-thunk": "~2.3.0", diff --git a/opendc-web/opendc-web-ui/src/api/index.js b/opendc-web/opendc-web-ui/src/api/index.js index 65358745..680d49ce 100644 --- a/opendc-web/opendc-web-ui/src/api/index.js +++ b/opendc-web/opendc-web-ui/src/api/index.js @@ -20,30 +20,32 @@ * SOFTWARE. */ -import { getAuthToken } from '../auth' - const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL /** * Send the specified request to the OpenDC API. + * + * @param auth The authentication context. * @param path Relative path for the API. * @param method The method to use for the request. * @param body The body of the request. */ -export async function request(path, method = 'GET', body) { - const res = await fetch(`${apiUrl}/v2/${path}`, { +export async function request(auth, path, method = 'GET', body) { + const { getAccessTokenSilently } = auth + const token = await getAccessTokenSilently() + const response = await fetch(`${apiUrl}/${path}`, { method: method, headers: { - 'auth-token': getAuthToken(), + Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', }, body: body && JSON.stringify(body), }) - const { status, content } = await res.json() + const json = await response.json() - if (status.code !== 200) { - throw status + if (!response.ok) { + throw response.message } - return content + return json.data } diff --git a/opendc-web/opendc-web-ui/src/api/portfolios.js b/opendc-web/opendc-web-ui/src/api/portfolios.js index 6202e702..28898e6a 100644 --- a/opendc-web/opendc-web-ui/src/api/portfolios.js +++ b/opendc-web/opendc-web-ui/src/api/portfolios.js @@ -22,18 +22,18 @@ import { request } from './index' -export function addPortfolio(projectId, portfolio) { - return request(`projects/${projectId}/portfolios`, 'POST', { portfolio }) +export function addPortfolio(auth, projectId, portfolio) { + return request(auth, `projects/${projectId}/portfolios`, 'POST', { portfolio }) } -export function getPortfolio(portfolioId) { - return request(`portfolios/${portfolioId}`) +export function getPortfolio(auth, portfolioId) { + return request(auth, `portfolios/${portfolioId}`) } -export function updatePortfolio(portfolioId, portfolio) { - return request(`portfolios/${portfolioId}`, 'PUT', { portfolio }) +export function updatePortfolio(auth, portfolioId, portfolio) { + return request(auth, `portfolios/${portfolioId}`, 'PUT', { portfolio }) } -export function deletePortfolio(portfolioId) { - return request(`portfolios/${portfolioId}`, 'DELETE') +export function deletePortfolio(auth, portfolioId) { + return request(auth, `portfolios/${portfolioId}`, 'DELETE') } diff --git a/opendc-web/opendc-web-ui/src/api/prefabs.js b/opendc-web/opendc-web-ui/src/api/prefabs.js index a8bd3f3b..eb9aa23c 100644 --- a/opendc-web/opendc-web-ui/src/api/prefabs.js +++ b/opendc-web/opendc-web-ui/src/api/prefabs.js @@ -22,18 +22,18 @@ import { request } from './index' -export function getPrefab(prefabId) { - return request(`prefabs/${prefabId}`) +export function getPrefab(auth, prefabId) { + return request(auth, `prefabs/${prefabId}`) } -export function addPrefab(prefab) { - return request('prefabs', 'POST', { prefab }) +export function addPrefab(auth, prefab) { + return request(auth, 'prefabs/', 'POST', { prefab }) } -export function updatePrefab(prefab) { - return request(`prefabs/${prefab._id}`, 'PUT', { prefab }) +export function updatePrefab(auth, prefab) { + return request(auth, `prefabs/${prefab._id}`, 'PUT', { prefab }) } -export function deletePrefab(prefabId) { - return request(`prefabs/${prefabId}`, 'DELETE') +export function deletePrefab(auth, prefabId) { + return request(auth, `prefabs/${prefabId}`, 'DELETE') } diff --git a/opendc-web/opendc-web-ui/src/api/projects.js b/opendc-web/opendc-web-ui/src/api/projects.js index 9ff7deda..93052080 100644 --- a/opendc-web/opendc-web-ui/src/api/projects.js +++ b/opendc-web/opendc-web-ui/src/api/projects.js @@ -22,18 +22,22 @@ import { request } from './index' -export function getProject(projectId) { - return request(`projects/${projectId}`) +export function getProjects(auth) { + return request(auth, `projects/`) } -export function addProject(project) { - return request('projects', 'POST', { project }) +export function getProject(auth, projectId) { + return request(auth, `projects/${projectId}`) } -export function updateProject(project) { - return request(`projects/${project._id}`, 'PUT', { project }) +export function addProject(auth, project) { + return request(auth, 'projects/', 'POST', { project }) } -export function deleteProject(projectId) { - return request(`projects/${projectId}`, 'DELETE') +export function updateProject(auth, project) { + return request(auth, `projects/${project._id}`, 'PUT', { project }) +} + +export function deleteProject(auth, projectId) { + return request(auth, `projects/${projectId}`, 'DELETE') } diff --git a/opendc-web/opendc-web-ui/src/api/scenarios.js b/opendc-web/opendc-web-ui/src/api/scenarios.js index 9f8c717b..095aa788 100644 --- a/opendc-web/opendc-web-ui/src/api/scenarios.js +++ b/opendc-web/opendc-web-ui/src/api/scenarios.js @@ -22,18 +22,18 @@ import { request } from './index' -export function addScenario(portfolioId, scenario) { - return request(`portfolios/${portfolioId}/scenarios`, 'POST', { scenario }) +export function addScenario(auth, portfolioId, scenario) { + return request(auth, `portfolios/${portfolioId}/scenarios`, 'POST', { scenario }) } -export function getScenario(scenarioId) { - return request(`scenarios/${scenarioId}`) +export function getScenario(auth, scenarioId) { + return request(auth, `scenarios/${scenarioId}`) } -export function updateScenario(scenarioId, scenario) { - return request(`scenarios/${scenarioId}`, 'PUT', { scenario }) +export function updateScenario(auth, scenarioId, scenario) { + return request(auth, `scenarios/${scenarioId}`, 'PUT', { scenario }) } -export function deleteScenario(scenarioId) { - return request(`scenarios/${scenarioId}`, 'DELETE') +export function deleteScenario(auth, scenarioId) { + return request(auth, `scenarios/${scenarioId}`, 'DELETE') } diff --git a/opendc-web/opendc-web-ui/src/api/schedulers.js b/opendc-web/opendc-web-ui/src/api/schedulers.js index 7791e51e..1b69f1a1 100644 --- a/opendc-web/opendc-web-ui/src/api/schedulers.js +++ b/opendc-web/opendc-web-ui/src/api/schedulers.js @@ -22,6 +22,6 @@ import { request } from './index' -export function getAllSchedulers() { - return request('schedulers') +export function getAllSchedulers(auth) { + return request(auth, 'schedulers/') } diff --git a/opendc-web/opendc-web-ui/src/api/token-signin.js b/opendc-web/opendc-web-ui/src/api/token-signin.js deleted file mode 100644 index a3761fa1..00000000 --- a/opendc-web/opendc-web-ui/src/api/token-signin.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -export function performTokenSignIn(token) { - const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL - - return fetch(`${apiUrl}/tokensignin`, { - method: 'POST', - body: new URLSearchParams({ - idtoken: token, - }), - }).then((res) => res.json()) -} diff --git a/opendc-web/opendc-web-ui/src/api/topologies.js b/opendc-web/opendc-web-ui/src/api/topologies.js index e6df73c7..c8744e6c 100644 --- a/opendc-web/opendc-web-ui/src/api/topologies.js +++ b/opendc-web/opendc-web-ui/src/api/topologies.js @@ -22,18 +22,18 @@ import { request } from './index' -export function addTopology(topology) { - return request(`projects/${topology.projectId}/topologies`, 'POST', { topology }) +export function addTopology(auth, topology) { + return request(auth, `projects/${topology.projectId}/topologies`, 'POST', { topology }) } -export function getTopology(topologyId) { - return request(`topologies/${topologyId}`) +export function getTopology(auth, topologyId) { + return request(auth, `topologies/${topologyId}`) } -export function updateTopology(topology) { - return request(`topologies/${topology._id}`, 'PUT', { topology }) +export function updateTopology(auth, topology) { + return request(auth, `topologies/${topology._id}`, 'PUT', { topology }) } -export function deleteTopology(topologyId) { - return request(`topologies/${topologyId}`, 'DELETE') +export function deleteTopology(auth, topologyId) { + return request(auth, `topologies/${topologyId}`, 'DELETE') } diff --git a/opendc-web/opendc-web-ui/src/api/traces.js b/opendc-web/opendc-web-ui/src/api/traces.js index 1c5cfa1d..df03a2dd 100644 --- a/opendc-web/opendc-web-ui/src/api/traces.js +++ b/opendc-web/opendc-web-ui/src/api/traces.js @@ -22,6 +22,6 @@ import { request } from './index' -export function getAllTraces() { - return request('traces') +export function getAllTraces(auth) { + return request(auth, 'traces/') } diff --git a/opendc-web/opendc-web-ui/src/auth.js b/opendc-web/opendc-web-ui/src/auth.js index faed9829..706151bf 100644 --- a/opendc-web/opendc-web-ui/src/auth.js +++ b/opendc-web/opendc-web-ui/src/auth.js @@ -1,83 +1,65 @@ -import { LOG_IN_SUCCEEDED, LOG_OUT } from './redux/actions/auth' -import { DELETE_CURRENT_USER_SUCCEEDED } from './redux/actions/users' -import { useEffect, useState } from 'react' -import { useRouter } from 'next/router' -import { useSelector } from 'react-redux' - -const getAuthObject = () => { - const authItem = global.localStorage && localStorage.getItem('auth') - if (!authItem || authItem === '{}') { - return undefined - } - return JSON.parse(authItem) -} - -export const userIsLoggedIn = () => { - const authObj = getAuthObject() - - if (!authObj || !authObj.googleId) { - return false - } - - const currentTime = new Date().getTime() - return parseInt(authObj.expiresAt, 10) - currentTime > 0 -} - -export const getAuthToken = () => { - const authObj = getAuthObject() - if (!authObj) { - return undefined - } - - return authObj.authToken -} - -export const saveAuthLocalStorage = (payload) => { - localStorage.setItem('auth', JSON.stringify(payload)) -} - -export const clearAuthLocalStorage = () => { - localStorage.setItem('auth', '') -} - -export const authRedirectMiddleware = (store) => (next) => (action) => { - switch (action.type) { - case LOG_IN_SUCCEEDED: - saveAuthLocalStorage(action.payload) - window.location.href = '/projects' - break - case LOG_OUT: - case DELETE_CURRENT_USER_SUCCEEDED: - clearAuthLocalStorage() - window.location.href = '/' - break - default: - next(action) - return - } - - next(action) -} +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ -export function useIsLoggedIn() { - const [isLoggedIn, setLoggedIn] = useState(false) - - useEffect(() => { - setLoggedIn(userIsLoggedIn()) - }, []) +import { Auth0Provider, useAuth0 } from '@auth0/auth0-react' +import { useEffect } from 'react' +import { useRouter } from 'next/router' - return isLoggedIn +/** + * Obtain the authentication context. + */ +export function useAuth() { + return useAuth0() } +/** + * Force the user to be authenticated or redirect to the homepage. + */ export function useRequireAuth() { + const auth = useAuth() const router = useRouter() + const { isLoading, isAuthenticated } = auth + useEffect(() => { - if (!userIsLoggedIn()) { + if (!isLoading && !isAuthenticated) { router.replace('/') } - }) + }, [isLoading, isAuthenticated]) + + return auth } -export function useUser() { - return useSelector((state) => state.auth) +/** + * AuthProvider which provides an authentication context. + */ +export function AuthProvider({ children }) { + return ( + <Auth0Provider + domain={process.env.NEXT_PUBLIC_AUTH0_DOMAIN} + clientId={process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID} + redirectUri={global.window && global.window.location.origin} + audience={process.env.NEXT_PUBLIC_AUTH0_AUDIENCE} + > + {children} + </Auth0Provider> + ) } diff --git a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js index f16a3feb..5c9ea1b8 100644 --- a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js +++ b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js @@ -15,7 +15,7 @@ import Login from '../../containers/auth/Login' import Logout from '../../containers/auth/Logout' import ProfileName from '../../containers/auth/ProfileName' import { login, navbar, opendcBrand } from './Navbar.module.scss' -import { useIsLoggedIn } from '../../auth' +import { useAuth } from '../../auth' export const NAVBAR_HEIGHT = 60 @@ -44,10 +44,10 @@ export const NavItem = ({ route, children }) => { export const LoggedInSection = () => { const router = useRouter() - const isLoggedIn = useIsLoggedIn() + const { isAuthenticated } = useAuth() return ( <Nav navbar className="auth-links"> - {isLoggedIn ? ( + {isAuthenticated ? ( [ router.asPath === '/' ? ( <NavItem route="/projects" key="projects"> @@ -58,12 +58,10 @@ export const LoggedInSection = () => { </Link> </NavItem> ) : ( - <NavItem route="/profile" key="profile"> - <Link href="/profile"> - <NavLink title="My Profile"> - <ProfileName /> - </NavLink> - </Link> + <NavItem key="profile"> + <NavLink title="My Profile"> + <ProfileName /> + </NavLink> </NavItem> ), <NavItem route="logout" key="logout"> diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectList.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectList.js index 90d42326..cb17b835 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/ProjectList.js +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectList.js @@ -1,12 +1,12 @@ import PropTypes from 'prop-types' import React from 'react' -import { Authorization } from '../../shapes' +import { Project } from '../../shapes' import ProjectRow from './ProjectRow' -const ProjectList = ({ authorizations }) => { +const ProjectList = ({ projects }) => { return ( <div className="vertically-expanding-container"> - {authorizations.length === 0 ? ( + {projects.length === 0 ? ( <div className="alert alert-info"> <span className="info-icon fa fa-question-circle mr-2" /> <strong>No projects here yet...</strong> Add some with the 'New Project' button! @@ -22,8 +22,8 @@ const ProjectList = ({ authorizations }) => { </tr> </thead> <tbody> - {authorizations.map((authorization) => ( - <ProjectRow projectAuth={authorization} key={authorization.project._id} /> + {projects.map((project) => ( + <ProjectRow project={project} key={project._id} /> ))} </tbody> </table> @@ -33,7 +33,7 @@ const ProjectList = ({ authorizations }) => { } ProjectList.propTypes = { - authorizations: PropTypes.arrayOf(Authorization).isRequired, + projects: PropTypes.arrayOf(Project).isRequired, } export default ProjectList diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectRow.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectRow.js index a0aac098..9a95b777 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/ProjectRow.js +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectRow.js @@ -1,24 +1,29 @@ import classNames from 'classnames' import React from 'react' import ProjectActions from '../../containers/projects/ProjectActions' -import { Authorization } from '../../shapes' +import { Project } from '../../shapes' import { AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP } from '../../util/authorizations' import { parseAndFormatDateTime } from '../../util/date-time' +import { useAuth } from '../../auth' -const ProjectRow = ({ projectAuth }) => ( - <tr> - <td className="pt-3">{projectAuth.project.name}</td> - <td className="pt-3">{parseAndFormatDateTime(projectAuth.project.datetimeLastEdited)}</td> - <td className="pt-3"> - <span className={classNames('fa', 'fa-' + AUTH_ICON_MAP[projectAuth.authorizationLevel], 'mr-2')} /> - {AUTH_DESCRIPTION_MAP[projectAuth.authorizationLevel]} - </td> - <ProjectActions projectId={projectAuth.project._id} /> - </tr> -) +const ProjectRow = ({ project }) => { + const { user } = useAuth() + const { level } = project.authorizations.find((auth) => auth.userId === user.sub) + return ( + <tr> + <td className="pt-3">{project.name}</td> + <td className="pt-3">{parseAndFormatDateTime(project.datetimeLastEdited)}</td> + <td className="pt-3"> + <span className={classNames('fa', 'fa-' + AUTH_ICON_MAP[level], 'mr-2')} /> + {AUTH_DESCRIPTION_MAP[level]} + </td> + <ProjectActions projectId={project._id} /> + </tr> + ) +} ProjectRow.propTypes = { - projectAuth: Authorization.isRequired, + project: Project.isRequired, } export default ProjectRow diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js index 7aaa1886..e1be51dc 100644 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js +++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js @@ -60,7 +60,7 @@ const ScenarioListContainer = ({ portfolioId }) => { /> <NewScenarioModalComponent show={isVisible} - currentPortfolioId={currentProjectId} + currentPortfolioId={portfolioId} currentPortfolioScenarioIds={scenarios.map((s) => s._id)} traces={traces} schedulers={schedulers} diff --git a/opendc-web/opendc-web-ui/src/containers/auth/Login.js b/opendc-web/opendc-web-ui/src/containers/auth/Login.js index b0da0d0e..8459ef5f 100644 --- a/opendc-web/opendc-web-ui/src/containers/auth/Login.js +++ b/opendc-web/opendc-web-ui/src/containers/auth/Login.js @@ -1,43 +1,18 @@ import React from 'react' -import GoogleLogin from 'react-google-login' -import { useDispatch } from 'react-redux' -import { logIn } from '../../redux/actions/auth' import { Button } from 'reactstrap' +import { useAuth } from '../../auth' function Login({ visible, className }) { - const dispatch = useDispatch() - - const onLogin = (payload) => dispatch(logIn(payload)) - const onAuthResponse = (response) => { - onLogin({ - email: response.getBasicProfile().getEmail(), - givenName: response.getBasicProfile().getGivenName(), - familyName: response.getBasicProfile().getFamilyName(), - googleId: response.googleId, - authToken: response.getAuthResponse().id_token, - expiresAt: response.getAuthResponse().expires_at, - }) - } - const onAuthFailure = (error) => { - // TODO Show error alert - console.error(error) - } + const { loginWithRedirect } = useAuth() if (!visible) { return <span /> } return ( - <GoogleLogin - clientId={process.env.NEXT_PUBLIC_OAUTH_CLIENT_ID} - onSuccess={onAuthResponse} - onFailure={onAuthFailure} - render={(renderProps) => ( - <Button color="primary" onClick={renderProps.onClick} className={className}> - <span aria-hidden className="fa fa-google" /> Login with Google - </Button> - )} - /> + <Button color="primary" onClick={() => loginWithRedirect()} className={className}> + <span aria-hidden className="fa fa-sign-in" /> Sign In + </Button> ) } diff --git a/opendc-web/opendc-web-ui/src/containers/auth/Logout.js b/opendc-web/opendc-web-ui/src/containers/auth/Logout.js index 94d4d061..37705c5d 100644 --- a/opendc-web/opendc-web-ui/src/containers/auth/Logout.js +++ b/opendc-web/opendc-web-ui/src/containers/auth/Logout.js @@ -1,11 +1,10 @@ import React from 'react' -import { useDispatch } from 'react-redux' -import { logOut } from '../../redux/actions/auth' import LogoutButton from '../../components/navigation/LogoutButton' +import { useAuth } from '../../auth' const Logout = (props) => { - const dispatch = useDispatch() - return <LogoutButton {...props} onLogout={() => dispatch(logOut())} /> + const { logout } = useAuth() + return <LogoutButton {...props} onLogout={() => logout({ returnTo: window.location.origin })} /> } export default Logout diff --git a/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js b/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js index 3992c00f..70f5b884 100644 --- a/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js +++ b/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js @@ -1,13 +1,9 @@ import React from 'react' -import { useUser } from '../../auth' +import { useAuth } from '../../auth' function ProfileName() { - const user = useUser() - return ( - <span> - {user.givenName} {user.familyName} - </span> - ) + const { isLoading, user } = useAuth() + return isLoading ? <span>Loading...</span> : <span>{user.name}</span> } export default ProfileName diff --git a/opendc-web/opendc-web-ui/src/containers/projects/ProjectListContainer.js b/opendc-web/opendc-web-ui/src/containers/projects/ProjectListContainer.js new file mode 100644 index 00000000..6632a8b5 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/containers/projects/ProjectListContainer.js @@ -0,0 +1,34 @@ +import React from 'react' +import PropTypes from 'prop-types' +import ProjectList from '../../components/projects/ProjectList' +import { useAuth } from '../../auth' +import { useProjects } from '../../data/project' + +const getVisibleProjects = (projects, filter, userId) => { + switch (filter) { + case 'SHOW_ALL': + return projects + case 'SHOW_OWN': + return projects.filter((project) => + project.authorizations.some((a) => a.userId === userId && a.level === 'OWN') + ) + case 'SHOW_SHARED': + return projects.filter((project) => + project.authorizations.some((a) => a.userId === userId && a.level !== 'OWN') + ) + default: + return projects + } +} + +const ProjectListContainer = ({ filter }) => { + const { user } = useAuth() + const projects = useProjects() + return <ProjectList projects={getVisibleProjects(projects, filter, user?.sub)} /> +} + +ProjectListContainer.propTypes = { + filter: PropTypes.string.isRequired, +} + +export default ProjectListContainer diff --git a/opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js b/opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js deleted file mode 100644 index 8e1d063b..00000000 --- a/opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { useSelector } from 'react-redux' -import ProjectList from '../../components/projects/ProjectList' - -const getVisibleProjectAuths = (projectAuths, filter) => { - switch (filter) { - case 'SHOW_ALL': - return projectAuths - case 'SHOW_OWN': - return projectAuths.filter((projectAuth) => projectAuth.authorizationLevel === 'OWN') - case 'SHOW_SHARED': - return projectAuths.filter((projectAuth) => projectAuth.authorizationLevel !== 'OWN') - default: - return projectAuths - } -} - -const VisibleProjectAuthList = ({ filter }) => { - const authorizations = useSelector((state) => { - const denormalizedAuthorizations = state.projectList.authorizationsOfCurrentUser.map((authorizationIds) => { - const authorization = state.objects.authorization[authorizationIds] - authorization.user = state.objects.user[authorization.userId] - authorization.project = state.objects.project[authorization.projectId] - return authorization - }) - - return getVisibleProjectAuths(denormalizedAuthorizations, filter) - }) - return <ProjectList authorizations={authorizations} /> -} - -VisibleProjectAuthList.propTypes = { - filter: PropTypes.string.isRequired, -} - -export default VisibleProjectAuthList diff --git a/opendc-web/opendc-web-ui/src/data/project.js b/opendc-web/opendc-web-ui/src/data/project.js index 0db49fdd..de2bc0d3 100644 --- a/opendc-web/opendc-web-ui/src/data/project.js +++ b/opendc-web/opendc-web-ui/src/data/project.js @@ -23,6 +23,13 @@ import { useSelector } from 'react-redux' /** + * Return the available projects. + */ +export function useProjects() { + return useSelector((state) => state.projects) +} + +/** * Return the current active project. */ export function useActiveProject() { diff --git a/opendc-web/opendc-web-ui/src/pages/_app.js b/opendc-web/opendc-web-ui/src/pages/_app.js index 761ae0cd..f9727a7a 100644 --- a/opendc-web/opendc-web-ui/src/pages/_app.js +++ b/opendc-web/opendc-web-ui/src/pages/_app.js @@ -24,19 +24,29 @@ import Head from 'next/head' import { Provider } from 'react-redux' import { useStore } from '../redux' import '../index.scss' +import { AuthProvider, useAuth } from '../auth' -export default function App({ Component, pageProps }) { - const store = useStore(pageProps.initialReduxState) +// This setup is necessary to forward the Auth0 context to the Redux context +const Inner = ({ Component, pageProps }) => { + const auth = useAuth() + const store = useStore(pageProps.initialReduxState, { auth }) + return ( + <Provider store={store}> + <Component {...pageProps} /> + </Provider> + ) +} +export default function App(props) { return ( <> <Head> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> <meta name="theme-color" content="#00A6D6" /> </Head> - <Provider store={store}> - <Component {...pageProps} /> - </Provider> + <AuthProvider> + <Inner {...props} /> + </AuthProvider> </> ) } diff --git a/opendc-web/opendc-web-ui/src/api/users.js b/opendc-web/opendc-web-ui/src/pages/logout.js index 3da030ad..e96e0605 100644 --- a/opendc-web/opendc-web-ui/src/api/users.js +++ b/opendc-web/opendc-web-ui/src/pages/logout.js @@ -20,20 +20,20 @@ * SOFTWARE. */ -import { request } from './index' +import React from 'react' +import Head from 'next/head' +import AppNavbarContainer from '../containers/navigation/AppNavbarContainer' -export function getUserByEmail(email) { - return request(`users` + new URLSearchParams({ email })) +function Logout() { + return ( + <> + <Head> + <title>Logged Out - OpenDC</title> + </Head> + <AppNavbarContainer fullWidth={false} /> + <span>Logged out successfully</span> + </> + ) } -export function addUser(user) { - return request('users', 'POST', { user }) -} - -export function getUser(userId) { - return request(`users/${userId}`) -} - -export function deleteUser(userId) { - return request(`users/${userId}`, 'DELETE') -} +export default Logout diff --git a/opendc-web/opendc-web-ui/src/pages/profile.js b/opendc-web/opendc-web-ui/src/pages/profile.js deleted file mode 100644 index 97afc4d9..00000000 --- a/opendc-web/opendc-web-ui/src/pages/profile.js +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React, { useState } from 'react' -import Head from 'next/head' -import { useDispatch } from 'react-redux' -import AppNavbarContainer from '../containers/navigation/AppNavbarContainer' -import ConfirmationModal from '../components/modals/ConfirmationModal' -import { deleteCurrentUser } from '../redux/actions/users' -import { useRequireAuth } from '../auth' - -const Profile = () => { - useRequireAuth() - - const dispatch = useDispatch() - - const [isDeleteModalOpen, setDeleteModalOpen] = useState(false) - const onDelete = (isConfirmed) => { - if (isConfirmed) { - dispatch(deleteCurrentUser()) - } - setDeleteModalOpen(false) - } - - return ( - <> - <Head> - <title>My Profile - OpenDC</title> - </Head> - <div className="full-height"> - <AppNavbarContainer fullWidth={false} /> - <div className="container text-page-container full-height"> - <button - className="btn btn-danger mb-2 ml-auto mr-auto" - style={{ maxWidth: 300 }} - onClick={() => setDeleteModalOpen(true)} - > - Delete my account on OpenDC - </button> - <p className="text-muted text-center"> - This does not delete your Google account, but simply disconnects it from the OpenDC platform and - deletes any project info that is associated with you (projects you own and any authorizations - you may have on other projects). - </p> - </div> - <ConfirmationModal - title="Delete my account" - message="Are you sure you want to delete your OpenDC account?" - show={isDeleteModalOpen} - callback={onDelete} - /> - </div> - </> - ) -} - -export default Profile diff --git a/opendc-web/opendc-web-ui/src/pages/projects/index.js b/opendc-web/opendc-web-ui/src/pages/projects/index.js index 05e9514f..8603c228 100644 --- a/opendc-web/opendc-web-ui/src/pages/projects/index.js +++ b/opendc-web/opendc-web-ui/src/pages/projects/index.js @@ -1,13 +1,13 @@ import React, { useEffect, useState } from 'react' import Head from 'next/head' import { useDispatch } from 'react-redux' -import { fetchAuthorizationsOfCurrentUser } from '../../redux/actions/users' import ProjectFilterPanel from '../../components/projects/FilterPanel' import NewProjectContainer from '../../containers/projects/NewProjectContainer' -import VisibleProjectList from '../../containers/projects/VisibleProjectAuthList' +import ProjectListContainer from '../../containers/projects/ProjectListContainer' import AppNavbarContainer from '../../containers/navigation/AppNavbarContainer' import { useRequireAuth } from '../../auth' import { Container } from 'reactstrap' +import { fetchProjects } from '../../redux/actions/projects' function Projects() { useRequireAuth() @@ -15,7 +15,7 @@ function Projects() { const dispatch = useDispatch() const [filter, setFilter] = useState('SHOW_ALL') - useEffect(() => dispatch(fetchAuthorizationsOfCurrentUser()), []) + useEffect(() => dispatch(fetchProjects()), []) return ( <> @@ -26,7 +26,7 @@ function Projects() { <AppNavbarContainer fullWidth={false} /> <Container className="text-page-container"> <ProjectFilterPanel onSelect={setFilter} activeFilter={filter} /> - <VisibleProjectList filter={filter} /> + <ProjectListContainer filter={filter} /> <NewProjectContainer /> </Container> </div> diff --git a/opendc-web/opendc-web-ui/src/redux/actions/auth.js b/opendc-web/opendc-web-ui/src/redux/actions/auth.js deleted file mode 100644 index 38c1a782..00000000 --- a/opendc-web/opendc-web-ui/src/redux/actions/auth.js +++ /dev/null @@ -1,23 +0,0 @@ -export const LOG_IN = 'LOG_IN' -export const LOG_IN_SUCCEEDED = 'LOG_IN_SUCCEEDED' -export const LOG_OUT = 'LOG_OUT' - -export function logIn(payload) { - return { - type: LOG_IN, - payload, - } -} - -export function logInSucceeded(payload) { - return { - type: LOG_IN_SUCCEEDED, - payload, - } -} - -export function logOut() { - return { - type: LOG_OUT, - } -} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/projects.js b/opendc-web/opendc-web-ui/src/redux/actions/projects.js index 15158164..a6324c43 100644 --- a/opendc-web/opendc-web-ui/src/redux/actions/projects.js +++ b/opendc-web/opendc-web-ui/src/redux/actions/projects.js @@ -1,24 +1,35 @@ +export const FETCH_PROJECTS = 'FETCH_PROJECTS' +export const FETCH_PROJECTS_SUCCEEDED = 'FETCH_PROJECTS_SUCCEEDED' export const ADD_PROJECT = 'ADD_PROJECT' export const ADD_PROJECT_SUCCEEDED = 'ADD_PROJECT_SUCCEEDED' export const DELETE_PROJECT = 'DELETE_PROJECT' export const DELETE_PROJECT_SUCCEEDED = 'DELETE_PROJECT_SUCCEEDED' export const OPEN_PROJECT_SUCCEEDED = 'OPEN_PROJECT_SUCCEEDED' +export function fetchProjects() { + return { + type: FETCH_PROJECTS, + } +} + +export function fetchProjectsSucceeded(projects) { + return { + type: FETCH_PROJECTS_SUCCEEDED, + projects, + } +} + export function addProject(name) { - return (dispatch, getState) => { - const { auth } = getState() - dispatch({ - type: ADD_PROJECT, - name, - userId: auth.userId, - }) + return { + type: ADD_PROJECT, + name, } } -export function addProjectSucceeded(authorization) { +export function addProjectSucceeded(project) { return { type: ADD_PROJECT_SUCCEEDED, - authorization, + project, } } diff --git a/opendc-web/opendc-web-ui/src/redux/actions/users.js b/opendc-web/opendc-web-ui/src/redux/actions/users.js deleted file mode 100644 index 4868ac34..00000000 --- a/opendc-web/opendc-web-ui/src/redux/actions/users.js +++ /dev/null @@ -1,37 +0,0 @@ -export const FETCH_AUTHORIZATIONS_OF_CURRENT_USER = 'FETCH_AUTHORIZATIONS_OF_CURRENT_USER' -export const FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED = 'FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED' -export const DELETE_CURRENT_USER = 'DELETE_CURRENT_USER' -export const DELETE_CURRENT_USER_SUCCEEDED = 'DELETE_CURRENT_USER_SUCCEEDED' - -export function fetchAuthorizationsOfCurrentUser() { - return (dispatch, getState) => { - const { auth } = getState() - dispatch({ - type: FETCH_AUTHORIZATIONS_OF_CURRENT_USER, - userId: auth.userId, - }) - } -} - -export function fetchAuthorizationsOfCurrentUserSucceeded(authorizationsOfCurrentUser) { - return { - type: FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED, - authorizationsOfCurrentUser, - } -} - -export function deleteCurrentUser() { - return (dispatch, getState) => { - const { auth } = getState() - dispatch({ - type: DELETE_CURRENT_USER, - userId: auth.userId, - }) - } -} - -export function deleteCurrentUserSucceeded() { - return { - type: DELETE_CURRENT_USER_SUCCEEDED, - } -} diff --git a/opendc-web/opendc-web-ui/src/redux/index.js b/opendc-web/opendc-web-ui/src/redux/index.js index c706752b..ee6ca3f5 100644 --- a/opendc-web/opendc-web-ui/src/redux/index.js +++ b/opendc-web/opendc-web-ui/src/redux/index.js @@ -1,40 +1,32 @@ import { useMemo } from 'react' -import { applyMiddleware, compose, createStore } from 'redux' +import { applyMiddleware, createStore } from 'redux' import { createLogger } from 'redux-logger' -import persistState from 'redux-localstorage' import createSagaMiddleware from 'redux-saga' import thunk from 'redux-thunk' -import { authRedirectMiddleware } from '../auth' import rootReducer from './reducers' import rootSaga from './sagas' import { viewportAdjustmentMiddleware } from './middleware/viewport-adjustment' let store -function initStore(initialState) { - const sagaMiddleware = createSagaMiddleware() +function initStore(initialState, ctx) { + const sagaMiddleware = createSagaMiddleware({ context: ctx }) - const middlewares = [thunk, sagaMiddleware, authRedirectMiddleware, viewportAdjustmentMiddleware] + const middlewares = [thunk, sagaMiddleware, viewportAdjustmentMiddleware] if (process.env.NODE_ENV !== 'production') { middlewares.push(createLogger()) } - let enhancer = applyMiddleware(...middlewares) - - if (global.localStorage) { - enhancer = compose(persistState('auth'), enhancer) - } - - const configuredStore = createStore(rootReducer, enhancer) + const configuredStore = createStore(rootReducer, initialState, applyMiddleware(...middlewares)) sagaMiddleware.run(rootSaga) store = configuredStore return configuredStore } -export const initializeStore = (preloadedState) => { - let _store = store ?? initStore(preloadedState) +export const initializeStore = (preloadedState, ctx) => { + let _store = store ?? initStore(preloadedState, ctx) // After navigating to a page with an initial Redux state, merge that state // with the current state in the store, and create a new store @@ -55,6 +47,6 @@ export const initializeStore = (preloadedState) => { return _store } -export function useStore(initialState) { - return useMemo(() => initializeStore(initialState), [initialState]) +export function useStore(initialState, ctx) { + return useMemo(() => initializeStore(initialState, ctx), [initialState, ctx]) } diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/auth.js b/opendc-web/opendc-web-ui/src/redux/reducers/auth.js deleted file mode 100644 index 399a4b10..00000000 --- a/opendc-web/opendc-web-ui/src/redux/reducers/auth.js +++ /dev/null @@ -1,12 +0,0 @@ -import { LOG_IN_SUCCEEDED, LOG_OUT } from '../actions/auth' - -export function auth(state = {}, action) { - switch (action.type) { - case LOG_IN_SUCCEEDED: - return action.payload - case LOG_OUT: - return {} - default: - return state - } -} diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/index.js b/opendc-web/opendc-web-ui/src/redux/reducers/index.js index 9dff379b..b143d417 100644 --- a/opendc-web/opendc-web-ui/src/redux/reducers/index.js +++ b/opendc-web/opendc-web-ui/src/redux/reducers/index.js @@ -1,15 +1,14 @@ import { combineReducers } from 'redux' -import { auth } from './auth' import { construction } from './construction-mode' import { currentPortfolioId, currentProjectId, currentScenarioId, currentTopologyId } from './current-ids' import { interactionLevel } from './interaction-level' import { map } from './map' import { objects } from './objects' -import { projectList } from './project-list' +import { projects } from './projects' const rootReducer = combineReducers({ objects, - projectList, + projects, construction, map, currentProjectId, @@ -17,7 +16,6 @@ const rootReducer = combineReducers({ currentPortfolioId, currentScenarioId, interactionLevel, - auth, }) export default rootReducer diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/project-list.js b/opendc-web/opendc-web-ui/src/redux/reducers/project-list.js deleted file mode 100644 index ad803db0..00000000 --- a/opendc-web/opendc-web-ui/src/redux/reducers/project-list.js +++ /dev/null @@ -1,18 +0,0 @@ -import { combineReducers } from 'redux' -import { ADD_PROJECT_SUCCEEDED, DELETE_PROJECT_SUCCEEDED } from '../actions/projects' -import { FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED } from '../actions/users' - -export function authorizationsOfCurrentUser(state = [], action) { - switch (action.type) { - case FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED: - return action.authorizationsOfCurrentUser - case ADD_PROJECT_SUCCEEDED: - return [...state, action.authorization] - case DELETE_PROJECT_SUCCEEDED: - return state.filter((authorization) => authorization[1] !== action.id) - default: - return state - } -} - -export const projectList = combineReducers({ authorizationsOfCurrentUser }) diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/projects.js b/opendc-web/opendc-web-ui/src/redux/reducers/projects.js new file mode 100644 index 00000000..a920e47f --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/projects.js @@ -0,0 +1,14 @@ +import { ADD_PROJECT_SUCCEEDED, DELETE_PROJECT_SUCCEEDED, FETCH_PROJECTS_SUCCEEDED } from '../actions/projects' + +export function projects(state = [], action) { + switch (action.type) { + case FETCH_PROJECTS_SUCCEEDED: + return action.projects + case ADD_PROJECT_SUCCEEDED: + return [...state, action.project] + case DELETE_PROJECT_SUCCEEDED: + return state.filter((project) => project._id !== action.id) + default: + return state + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/index.js b/opendc-web/opendc-web-ui/src/redux/sagas/index.js index 6332b2fb..a8f44843 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/index.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/index.js @@ -1,7 +1,6 @@ import { takeEvery } from 'redux-saga/effects' -import { LOG_IN } from '../actions/auth' import { ADD_PORTFOLIO, DELETE_PORTFOLIO, OPEN_PORTFOLIO_SUCCEEDED, UPDATE_PORTFOLIO } from '../actions/portfolios' -import { ADD_PROJECT, DELETE_PROJECT, OPEN_PROJECT_SUCCEEDED } from '../actions/projects' +import { ADD_PROJECT, DELETE_PROJECT, FETCH_PROJECTS, OPEN_PROJECT_SUCCEEDED } from '../actions/projects' import { ADD_TILE, CANCEL_NEW_ROOM_CONSTRUCTION, @@ -11,10 +10,8 @@ import { import { ADD_UNIT, DELETE_MACHINE, DELETE_UNIT } from '../actions/topology/machine' import { ADD_MACHINE, DELETE_RACK, EDIT_RACK_NAME } from '../actions/topology/rack' import { ADD_RACK_TO_TILE, DELETE_ROOM, EDIT_ROOM_NAME } from '../actions/topology/room' -import { DELETE_CURRENT_USER, FETCH_AUTHORIZATIONS_OF_CURRENT_USER } from '../actions/users' import { onAddPortfolio, onDeletePortfolio, onOpenPortfolioSucceeded, onUpdatePortfolio } from './portfolios' -import { onDeleteCurrentUser } from './profile' -import { onOpenProjectSucceeded, onProjectAdd, onProjectDelete } from './projects' +import { onFetchProjects, onOpenProjectSucceeded, onProjectAdd, onProjectDelete } from './projects' import { onAddMachine, onAddRackToTile, @@ -32,7 +29,6 @@ import { onEditRoomName, onStartNewRoomConstruction, } from './topology' -import { onFetchAuthorizationsOfCurrentUser, onFetchLoggedInUser } from './users' import { ADD_TOPOLOGY, DELETE_TOPOLOGY } from '../actions/topologies' import { ADD_SCENARIO, DELETE_SCENARIO, OPEN_SCENARIO_SUCCEEDED, UPDATE_SCENARIO } from '../actions/scenarios' import { onAddScenario, onDeleteScenario, onOpenScenarioSucceeded, onUpdateScenario } from './scenarios' @@ -40,14 +36,10 @@ import { onAddPrefab } from './prefabs' import { ADD_PREFAB } from '../actions/prefabs' export default function* rootSaga() { - yield takeEvery(LOG_IN, onFetchLoggedInUser) - - yield takeEvery(FETCH_AUTHORIZATIONS_OF_CURRENT_USER, onFetchAuthorizationsOfCurrentUser) + yield takeEvery(FETCH_PROJECTS, onFetchProjects) yield takeEvery(ADD_PROJECT, onProjectAdd) yield takeEvery(DELETE_PROJECT, onProjectDelete) - yield takeEvery(DELETE_CURRENT_USER, onDeleteCurrentUser) - yield takeEvery(OPEN_PROJECT_SUCCEEDED, onOpenProjectSucceeded) yield takeEvery(OPEN_PORTFOLIO_SUCCEEDED, onOpenPortfolioSucceeded) yield takeEvery(OPEN_SCENARIO_SUCCEEDED, onOpenScenarioSucceeded) diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/objects.js b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js index 82dbb935..e5fd092d 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/objects.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js @@ -1,9 +1,8 @@ -import { call, put, select } from 'redux-saga/effects' +import { call, put, select, getContext } from 'redux-saga/effects' import { addToStore } from '../actions/objects' import { getAllSchedulers } from '../../api/schedulers' import { getProject } from '../../api/projects' import { getAllTraces } from '../../api/traces' -import { getUser } from '../../api/users' import { getTopology, updateTopology } from '../../api/topologies' import { uuid } from 'uuidv4' @@ -42,9 +41,10 @@ function* fetchAndStoreObjects(objectType, apiCall) { return objects } -export const fetchAndStoreProject = (id) => fetchAndStoreObject('project', id, call(getProject, id)) - -export const fetchAndStoreUser = (id) => fetchAndStoreObject('user', id, call(getUser, id)) +export const fetchAndStoreProject = function* (id) { + const auth = yield getContext('auth') + return yield fetchAndStoreObject('project', id, call(getProject, auth, id)) +} export const fetchAndStoreTopology = function* (id) { const topologyStore = yield select(OBJECT_SELECTORS['topology']) @@ -52,10 +52,11 @@ export const fetchAndStoreTopology = function* (id) { const tileStore = yield select(OBJECT_SELECTORS['tile']) const rackStore = yield select(OBJECT_SELECTORS['rack']) const machineStore = yield select(OBJECT_SELECTORS['machine']) + const auth = yield getContext('auth') let topology = topologyStore[id] if (!topology) { - const fullTopology = yield call(getTopology, id) + const fullTopology = yield call(getTopology, auth, id) for (let roomIdx in fullTopology.rooms) { const fullRoom = fullTopology.rooms[roomIdx] @@ -142,8 +143,8 @@ const generateIdIfNotPresent = (obj) => { export const updateTopologyOnServer = function* (id) { const topology = yield getTopologyAsObject(id, true) - - yield call(updateTopology, topology) + const auth = yield getContext('auth') + yield call(updateTopology, auth, topology) } export const getTopologyAsObject = function* (id, keepIds) { @@ -217,10 +218,14 @@ export const getRackById = function* (id, keepIds) { } } -export const fetchAndStoreAllTraces = () => fetchAndStoreObjects('trace', call(getAllTraces)) +export const fetchAndStoreAllTraces = function* () { + const auth = yield getContext('auth') + return yield fetchAndStoreObjects('trace', call(getAllTraces, auth)) +} export const fetchAndStoreAllSchedulers = function* () { - const objects = yield call(getAllSchedulers) + const auth = yield getContext('auth') + const objects = yield call(getAllSchedulers, auth) for (let object of objects) { object._id = object.name yield put(addToStore('scheduler', object)) diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js b/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js index 8ddf888d..340cb490 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js @@ -1,4 +1,4 @@ -import { call, put, select, delay } from 'redux-saga/effects' +import { call, put, select, delay, getContext } from 'redux-saga/effects' import { addPropToStoreObject, addToStore } from '../actions/objects' import { addPortfolio, deletePortfolio, getPortfolio, updatePortfolio } from '../../api/portfolios' import { getProject } from '../../api/projects' @@ -8,7 +8,8 @@ import { getScenario } from '../../api/scenarios' export function* onOpenPortfolioSucceeded(action) { try { - const project = yield call(getProject, action.projectId) + const auth = yield getContext('auth') + const project = yield call(getProject, auth, action.projectId) yield put(addToStore('project', project)) yield fetchAndStoreAllTopologiesOfProject(project._id) yield fetchPortfoliosOfProject() @@ -66,11 +67,12 @@ export function* fetchPortfoliosOfProject() { export function* fetchPortfolioWithScenarios(portfolioId) { try { - const portfolio = yield call(getPortfolio, portfolioId) + const auth = yield getContext('auth') + const portfolio = yield call(getPortfolio, auth, portfolioId) yield put(addToStore('portfolio', portfolio)) for (let i in portfolio.scenarioIds) { - const scenario = yield call(getScenario, portfolio.scenarioIds[i]) + const scenario = yield call(getScenario, auth, portfolio.scenarioIds[i]) yield put(addToStore('scenario', scenario)) } return portfolio @@ -82,9 +84,10 @@ export function* fetchPortfolioWithScenarios(portfolioId) { export function* onAddPortfolio(action) { try { const currentProjectId = yield select((state) => state.currentProjectId) - + const auth = yield getContext('auth') const portfolio = yield call( addPortfolio, + auth, currentProjectId, Object.assign({}, action.portfolio, { projectId: currentProjectId, @@ -106,7 +109,8 @@ export function* onAddPortfolio(action) { export function* onUpdatePortfolio(action) { try { - const portfolio = yield call(updatePortfolio, action.portfolio._id, action.portfolio) + const auth = yield getContext('auth') + const portfolio = yield call(updatePortfolio, auth, action.portfolio._id, action.portfolio) yield put(addToStore('portfolio', portfolio)) } catch (error) { console.error(error) @@ -115,7 +119,8 @@ export function* onUpdatePortfolio(action) { export function* onDeletePortfolio(action) { try { - yield call(deletePortfolio, action.id) + const auth = yield getContext('auth') + yield call(deletePortfolio, auth, action.id) const currentProjectId = yield select((state) => state.currentProjectId) const portfolioIds = yield select((state) => state.objects.project[currentProjectId].portfolioIds) diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js b/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js index 3158a219..ec679391 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js @@ -1,4 +1,4 @@ -import { call, put, select } from 'redux-saga/effects' +import { call, put, select, getContext } from 'redux-saga/effects' import { addToStore } from '../actions/objects' import { addPrefab } from '../../api/prefabs' import { getRackById } from './objects' @@ -7,7 +7,8 @@ export function* onAddPrefab(action) { try { const currentRackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId) const currentRackJson = yield getRackById(currentRackId, false) - const prefab = yield call(addPrefab, { name: action.name, rack: currentRackJson }) + const auth = yield getContext('auth') + const prefab = yield call(addPrefab, auth, { name: action.name, rack: currentRackJson }) yield put(addToStore('prefab', prefab)) } catch (error) { console.error(error) diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/profile.js b/opendc-web/opendc-web-ui/src/redux/sagas/profile.js deleted file mode 100644 index e187b765..00000000 --- a/opendc-web/opendc-web-ui/src/redux/sagas/profile.js +++ /dev/null @@ -1,12 +0,0 @@ -import { call, put } from 'redux-saga/effects' -import { deleteCurrentUserSucceeded } from '../actions/users' -import { deleteUser } from '../../api/users' - -export function* onDeleteCurrentUser(action) { - try { - yield call(deleteUser, action.userId) - yield put(deleteCurrentUserSucceeded()) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/projects.js b/opendc-web/opendc-web-ui/src/redux/sagas/projects.js index ecd9a7c9..506df6ed 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/projects.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/projects.js @@ -1,14 +1,15 @@ -import { call, put } from 'redux-saga/effects' +import { call, put, getContext } from 'redux-saga/effects' import { addToStore } from '../actions/objects' -import { addProjectSucceeded, deleteProjectSucceeded } from '../actions/projects' -import { addProject, deleteProject, getProject } from '../../api/projects' +import { addProjectSucceeded, deleteProjectSucceeded, fetchProjectsSucceeded } from '../actions/projects' +import { addProject, deleteProject, getProject, getProjects } from '../../api/projects' import { fetchAndStoreAllTopologiesOfProject } from './topology' import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' import { fetchPortfoliosOfProject } from './portfolios' export function* onOpenProjectSucceeded(action) { try { - const project = yield call(getProject, action.id) + const auth = yield getContext('auth') + const project = yield call(getProject, auth, action.id) yield put(addToStore('project', project)) yield fetchAndStoreAllTopologiesOfProject(action.id, true) @@ -22,17 +23,10 @@ export function* onOpenProjectSucceeded(action) { export function* onProjectAdd(action) { try { - const project = yield call(addProject, { name: action.name }) + const auth = yield getContext('auth') + const project = yield call(addProject, auth, { name: action.name }) yield put(addToStore('project', project)) - - const authorization = { - projectId: project._id, - userId: action.userId, - authorizationLevel: 'OWN', - project, - } - yield put(addToStore('authorization', authorization)) - yield put(addProjectSucceeded([authorization.userId, authorization.projectId])) + yield put(addProjectSucceeded(project)) } catch (error) { console.error(error) } @@ -40,9 +34,20 @@ export function* onProjectAdd(action) { export function* onProjectDelete(action) { try { - yield call(deleteProject, action.id) + const auth = yield getContext('auth') + yield call(deleteProject, auth, action.id) yield put(deleteProjectSucceeded(action.id)) } catch (error) { console.error(error) } } + +export function* onFetchProjects(action) { + try { + const auth = yield getContext('auth') + const projects = yield call(getProjects, auth) + yield put(fetchProjectsSucceeded(projects)) + } catch (error) { + console.error(error) + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js b/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js index a5463fa6..bdb7c45d 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js @@ -1,4 +1,4 @@ -import { call, put, select } from 'redux-saga/effects' +import { call, put, select, getContext } from 'redux-saga/effects' import { addPropToStoreObject, addToStore } from '../actions/objects' import { getProject } from '../../api/projects' import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' @@ -8,7 +8,8 @@ import { fetchPortfolioWithScenarios, watchForPortfolioResults } from './portfol export function* onOpenScenarioSucceeded(action) { try { - const project = yield call(getProject, action.projectId) + const auth = yield getContext('auth') + const project = yield call(getProject, auth, action.projectId) yield put(addToStore('project', project)) yield fetchAndStoreAllTopologiesOfProject(project._id) yield fetchAndStoreAllSchedulers() @@ -23,7 +24,8 @@ export function* onOpenScenarioSucceeded(action) { export function* onAddScenario(action) { try { - const scenario = yield call(addScenario, action.scenario.portfolioId, action.scenario) + const auth = yield getContext('auth') + const scenario = yield call(addScenario, auth, action.scenario.portfolioId, action.scenario) yield put(addToStore('scenario', scenario)) const scenarioIds = yield select((state) => state.objects.portfolio[action.scenario.portfolioId].scenarioIds) @@ -40,7 +42,8 @@ export function* onAddScenario(action) { export function* onUpdateScenario(action) { try { - const scenario = yield call(updateScenario, action.scenario._id, action.scenario) + const auth = yield getContext('auth') + const scenario = yield call(updateScenario, auth, action.scenario._id, action.scenario) yield put(addToStore('scenario', scenario)) } catch (error) { console.error(error) @@ -49,7 +52,8 @@ export function* onUpdateScenario(action) { export function* onDeleteScenario(action) { try { - yield call(deleteScenario, action.id) + const auth = yield getContext('auth') + yield call(deleteScenario, auth, action.id) const currentPortfolioId = yield select((state) => state.currentPortfolioId) const scenarioIds = yield select((state) => state.objects.portfolio[currentPortfolioId].scenarioIds) diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js index 65f97cc9..e5fd3d39 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js @@ -1,4 +1,4 @@ -import { call, put, select } from 'redux-saga/effects' +import { call, put, select, getContext } from 'redux-saga/effects' import { goDownOneInteractionLevel } from '../actions/interaction-level' import { addIdToStoreObjectListProp, @@ -50,8 +50,10 @@ export function* onAddTopology(action) { topologyToBeCreated = { name: action.name, rooms: [] } } + const auth = yield getContext('auth') const topology = yield call( addTopology, + auth, Object.assign({}, topologyToBeCreated, { projectId: currentProjectId, }) @@ -79,7 +81,8 @@ export function* onDeleteTopology(action) { yield put(setCurrentTopology(topologyIds.filter((t) => t !== action.id)[0])) } - yield call(deleteTopology, action.id) + const auth = yield getContext('auth') + yield call(deleteTopology, auth, action.id) yield put( addPropToStoreObject('project', currentProjectId, { diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/users.js b/opendc-web/opendc-web-ui/src/redux/sagas/users.js deleted file mode 100644 index 88c424b5..00000000 --- a/opendc-web/opendc-web-ui/src/redux/sagas/users.js +++ /dev/null @@ -1,44 +0,0 @@ -import { call, put } from 'redux-saga/effects' -import { logInSucceeded } from '../actions/auth' -import { addToStore } from '../actions/objects' -import { fetchAuthorizationsOfCurrentUserSucceeded } from '../actions/users' -import { performTokenSignIn } from '../../api/token-signin' -import { addUser } from '../../api/users' -import { saveAuthLocalStorage } from '../../auth' -import { fetchAndStoreProject, fetchAndStoreUser } from './objects' - -export function* onFetchLoggedInUser(action) { - try { - const tokenResponse = yield call(performTokenSignIn, action.payload.authToken) - - let userId = tokenResponse.userId - - if (tokenResponse.isNewUser) { - saveAuthLocalStorage({ authToken: action.payload.authToken }) - const newUser = yield call(addUser, action.payload) - userId = newUser._id - } - - yield put(logInSucceeded(Object.assign({ userId }, action.payload))) - } catch (error) { - console.error(error) - } -} - -export function* onFetchAuthorizationsOfCurrentUser(action) { - try { - const user = yield call(fetchAndStoreUser, action.userId) - - for (const authorization of user.authorizations) { - authorization.userId = action.userId - yield put(addToStore('authorization', authorization)) - yield fetchAndStoreProject(authorization.projectId) - } - - const authorizationIds = user.authorizations.map((authorization) => [action.userId, authorization.projectId]) - - yield put(fetchAuthorizationsOfCurrentUserSucceeded(authorizationIds)) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/shapes.js b/opendc-web/opendc-web-ui/src/shapes.js index 9aeb99d8..6c29eab0 100644 --- a/opendc-web/opendc-web-ui/src/shapes.js +++ b/opendc-web/opendc-web-ui/src/shapes.js @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + import PropTypes from 'prop-types' export const User = PropTypes.shape({ diff --git a/opendc-web/opendc-web-ui/yarn.lock b/opendc-web/opendc-web-ui/yarn.lock index 9aed2596..f8bfc180 100644 --- a/opendc-web/opendc-web-ui/yarn.lock +++ b/opendc-web/opendc-web-ui/yarn.lock @@ -2,6 +2,26 @@ # yarn lockfile v1 +"@auth0/auth0-react@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@auth0/auth0-react/-/auth0-react-1.5.0.tgz#f2dcfef1cab59f8555ade4e2b5ec13befce67395" + integrity sha512-LFJUd3V6aKCKxblJvrz3JIHzyBCz2X3ax5RdSjxZwGr5XxPwdhogeBWcyijnuB8moKD2ncl56OpOslbVGLIJ/w== + dependencies: + "@auth0/auth0-spa-js" "^1.15.0" + +"@auth0/auth0-spa-js@^1.15.0": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@auth0/auth0-spa-js/-/auth0-spa-js-1.15.0.tgz#9fa563b7b2e49dc4c6a465a0c240078322e80159" + integrity sha512-d/crchAbhncl9irIMuw1zNSZgX+id0U7mzASQr2htMJ73JCYaAvBSdGXL0WcYS4yBm1Xsx1JYm3b5tEZ5p/ncg== + dependencies: + abortcontroller-polyfill "^1.7.1" + browser-tabs-lock "^1.2.13" + core-js "^3.11.0" + es-cookie "^1.3.2" + fast-text-encoding "^1.0.3" + promise-polyfill "^8.2.0" + unfetch "^4.2.0" + "@babel/code-frame@7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -325,6 +345,11 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0" integrity sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw== +abortcontroller-polyfill@^1.7.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz#1b5b487bd6436b5b764fd52a612509702c3144b5" + integrity sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q== + aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -479,6 +504,13 @@ brorand@^1.0.1, brorand@^1.1.0: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= +browser-tabs-lock@^1.2.13: + version "1.2.14" + resolved "https://registry.yarnpkg.com/browser-tabs-lock/-/browser-tabs-lock-1.2.14.tgz#f4ba30810d20199a1858c102da1f91e339250728" + integrity sha512-ssSpCRcvFe4vc098LDnrJOQDfZiG35KhQGB9hthTbwJk5mmUkePwhcMlW61NH3YuIE2Y9uGLqf9yxEBKbaDlaw== + dependencies: + lodash ">=4.17.21" + browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" @@ -755,6 +787,11 @@ convert-source-map@1.7.0: dependencies: safe-buffer "~5.1.1" +core-js@^3.11.0: + version "3.12.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.12.1.tgz#6b5af4ff55616c08a44d386f1f510917ff204112" + integrity sha512-Ne9DKPHTObRuB09Dru5AjwKjY4cJHVGu+y5f7coGn1E9Grkc3p2iBwE9AI/nJzsE29mQF7oq+mhYYRqOMFN1Bw== + core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -1104,6 +1141,11 @@ es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2: string.prototype.trimstart "^1.0.4" unbox-primitive "^1.0.0" +es-cookie@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/es-cookie/-/es-cookie-1.3.2.tgz#80e831597f72a25721701bdcb21d990319acd831" + integrity sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q== + es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -1181,6 +1223,11 @@ fast-equals@^2.0.0: resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-2.0.2.tgz#f4029b437d9e8184b807233bdbcd371e3bc73cf7" integrity sha512-ehHsR38w6sqy5E0QrWQxclb+zl3ulNsgCVWt1cMoZ6QBFgtkr4lKZWpQP1kfEFn6bWnm78pmiDGak+zUvQ2/DQ== +fast-text-encoding@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" + integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== + figures@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" @@ -1702,7 +1749,7 @@ lodash.throttle@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= -lodash@^4.17.13, lodash@^4.17.19: +lodash@>=4.17.21, lodash@^4.17.13, lodash@^4.17.19: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -2168,7 +2215,12 @@ process@0.11.10, process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= -prop-types@15.7.2, prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@~15.7.2: +promise-polyfill@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.2.0.tgz#367394726da7561457aba2133c9ceefbd6267da0" + integrity sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g== + +prop-types@15.7.2, prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@~15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -2275,14 +2327,6 @@ react-fontawesome@~1.7.1: dependencies: prop-types "^15.5.6" -react-google-login@~5.1.14: - version "5.1.25" - resolved "https://registry.yarnpkg.com/react-google-login/-/react-google-login-5.1.25.tgz#6bf5b4c8ce4e9e64a4c80eb14b80c9c6bd07bf9b" - integrity sha512-N7SZkjTEX9NsC3hywXs68SPJWAHo6M19Bs1OIFPirG0yomGF7KnbKjSqoiIfxz1V7fKAt8bkfBAzogwnGWYTeQ== - dependencies: - "@types/react" "*" - prop-types "^15.6.0" - react-hotkeys@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/react-hotkeys/-/react-hotkeys-2.0.0.tgz#a7719c7340cbba888b0e9184f806a9ec0ac2c53f" @@ -2463,11 +2507,6 @@ reduce-css-calc@^2.1.8: css-unit-converter "^1.1.1" postcss-value-parser "^3.3.0" -redux-localstorage@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/redux-localstorage/-/redux-localstorage-0.4.1.tgz#faf6d719c581397294d811473ffcedee065c933c" - integrity sha1-+vbXGcWBOXKU2BFHP/zt7gZckzw= - redux-logger@~3.0.6: version "3.0.6" resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf" @@ -2977,6 +3016,11 @@ unbox-primitive@^1.0.0: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" +unfetch@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" + integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== + unpipe@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" |
