diff --git a/.env.example b/.env.example index cb5c39b35..12eac2c37 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,9 @@ # "development", "ropsten", "rinkeby", "mainnet", "polygon", "moonbeamalpha" GATSBY_NETWORK="rinkeby" +## Define a GATSBY_RBAC_URL to implement permission based restrictions +#GATSBY_RBAC_URL="http://localhost:3000" + #GATSBY_INFURA_PROJECT_ID="xxx" #GATSBY_MARKET_FEE_ADDRESS="0xxx" #GATSBY_ANALYTICS_ID="xxx" diff --git a/app.config.js b/app.config.js index 722d0239d..ba3a87393 100644 --- a/app.config.js +++ b/app.config.js @@ -4,6 +4,7 @@ module.exports = { // networks in their wallet. // Ocean Protocol contracts are deployed for: 'mainnet', 'rinkeby', 'development' network: process.env.GATSBY_NETWORK || 'mainnet', + rbacUrl: process.env.GATSBY_RBAC_URL, infuraProjectId: process.env.GATSBY_INFURA_PROJECT_ID || 'xxx', diff --git a/package-lock.json b/package-lock.json index 95f89b505..5b1b1b9f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65003,6 +65003,11 @@ "clsx": "^1.1.1" } }, + "reactjs-popup": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/reactjs-popup/-/reactjs-popup-2.0.4.tgz", + "integrity": "sha512-G5jTXL2JkClKAYAdqedf+K9QvbNsFWvdbrXW1/vWiyanuCU/d7DtQzQux+uKOz2HeNVRsFQHvs7abs0Z7VLAhg==" + }, "read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", diff --git a/src/components/organisms/AssetActions/index.tsx b/src/components/organisms/AssetActions/index.tsx index 290dd6d4e..03c916354 100644 --- a/src/components/organisms/AssetActions/index.tsx +++ b/src/components/organisms/AssetActions/index.tsx @@ -1,4 +1,5 @@ import React, { ReactElement, useState, useEffect } from 'react' +import Permission from '../Permission' import styles from './index.module.css' import Compute from './Compute' import Consume from './Consume' @@ -111,5 +112,9 @@ export default function AssetActions(): ReactElement { } ) - return + return ( + + + + ) } diff --git a/src/components/organisms/Permission.tsx b/src/components/organisms/Permission.tsx new file mode 100644 index 000000000..022e1cd51 --- /dev/null +++ b/src/components/organisms/Permission.tsx @@ -0,0 +1,62 @@ +import React, { ReactElement, useEffect, useState } from 'react' +import { useWeb3 } from '../../providers/Web3' +import rbacRequest from '../../utils/rbac' +import Alert from '../atoms/Alert' +import Loader from '../atoms/Loader' +import appConfig from '../../../app.config' + +export default function Permission({ + eventType, + children +}: { + eventType: string + children: ReactElement +}): ReactElement { + const url = appConfig.rbacUrl + const [data, updateData] = useState() + const [errorMessage, updateError] = useState() + const [messageState, updateMessageState] = + useState<'error' | 'warning' | 'info' | 'success'>() + const { accountId } = useWeb3() + useEffect(() => { + if (url === undefined) return + const getData = async () => { + if (accountId === undefined) { + updateError('Please make sure your wallet is connected to proceed.') + updateMessageState('info') + } else { + const data = await rbacRequest(eventType, accountId) + updateData(data) + if (data === 'ERROR') { + updateError( + 'There was an error verifying your permissions. Please refresh the page or conntact your network administrator' + ) + updateMessageState('error') + } else if (data === false) { + updateError( + `Sorry, you don't have permission to ${eventType}. Please make sure you have connected your registered address.` + ) + updateMessageState('warning') + } else if (data !== true) { + updateError( + 'An unkown error occured. Please conntact your network administrator' + ) + updateMessageState('error') + } + } + } + getData() + }, [eventType, accountId, url]) + + if (url === undefined || data === true) { + return <>{children} + } + + return ( + <> + +
+ + + ) +} diff --git a/src/components/pages/Home.tsx b/src/components/pages/Home.tsx index f3a61d35f..8e62e4b76 100644 --- a/src/components/pages/Home.tsx +++ b/src/components/pages/Home.tsx @@ -12,8 +12,10 @@ import Button from '../atoms/Button' import Bookmarks from '../molecules/Bookmarks' import axios from 'axios' import { queryMetadata } from '../../utils/aquarius' +import Permission from '../organisms/Permission' import { getHighestLiquidityDIDs } from '../../utils/subgraph' import { DDO, Logger } from '@oceanprotocol/lib' + import { useWeb3 } from '../../providers/Web3' const queryLatest = { @@ -121,33 +123,35 @@ export default function HomePage(): ReactElement { }, [config.subgraphUri, loading, web3Loading]) return ( - <> - - - + + <> + + + -
-

Bookmarks

- -
+
+

Bookmarks

+ +
+ + {queryAndDids && ( + + )} - {queryAndDids && ( + All data sets and algorithms → + + } /> - )} - - - All data sets and algorithms → - - } - /> - + +
) } diff --git a/src/components/pages/Publish/index.tsx b/src/components/pages/Publish/index.tsx index 31dce4427..aaaaca3f4 100644 --- a/src/components/pages/Publish/index.tsx +++ b/src/components/pages/Publish/index.tsx @@ -1,4 +1,5 @@ import React, { ReactElement, useState, useEffect } from 'react' +import Permission from '../../organisms/Permission' import { Formik, FormikState } from 'formik' import { usePublish } from '../../../hooks/usePublish' import styles from './index.module.css' @@ -215,86 +216,92 @@ export default function PublishPage({ } return isInPurgatory && purgatoryData ? null : ( - { - // move user's focus to top of screen - window.scrollTo({ top: 0, left: 0, behavior: 'smooth' }) - // kick off publishing - publishType === 'dataset' - ? await handleSubmit(values, resetForm) - : await handleAlgorithmSubmit(values, resetForm) - }} - enableReinitialize - > - {({ values }) => { - const tabs = [ - { - title: 'Data Set', - content: - }, - { - title: 'Algorithm', - content: - } - ] + + { + // move user's focus to top of screen + window.scrollTo({ top: 0, left: 0, behavior: 'smooth' }) + // kick off publishing + publishType === 'dataset' + ? await handleSubmit(values, resetForm) + : await handleAlgorithmSubmit(values, resetForm) + }} + enableReinitialize + > + {({ values }) => { + const tabs = [ + { + title: 'Data Set', + content: + }, + { + title: 'Algorithm', + content: + } + ] - return ( - <> - - - {hasFeedback ? ( - + - ) : ( - <> - - { - setPublishType(title.toLowerCase().replace(' ', '') as any) - title === 'Algorithm' - ? setdatasetInitialValues(values) - : setAlgoInitialValues(values) + {hasFeedback ? ( + - - )} + ) : ( + <> + - {debug === true && } - - ) - }} - + { + setPublishType( + title.toLowerCase().replace(' ', '') as any + ) + title === 'Algorithm' + ? setdatasetInitialValues(values) + : setAlgoInitialValues(values) + }} + /> + + )} + + {debug === true && } + + ) + }} + + ) } diff --git a/src/components/templates/Page.tsx b/src/components/templates/Page.tsx index 12a5a3c00..1672d4dbf 100644 --- a/src/components/templates/Page.tsx +++ b/src/components/templates/Page.tsx @@ -23,7 +23,6 @@ export default function Page({ return ( <> - {title && !noPageHeader && ( -
- {(text || owner) && ( - - )} -
- - + <> +
+ {(text || owner) && ( + + )} +
+ + +
+
+
+
-
-
- -
- + + ) } diff --git a/src/pages/asset/index.tsx b/src/pages/asset/index.tsx index 61d335698..025602a3c 100644 --- a/src/pages/asset/index.tsx +++ b/src/pages/asset/index.tsx @@ -1,4 +1,5 @@ import React, { ReactElement, useEffect, useState } from 'react' +import Permission from '../../components/organisms/Permission' import { PageProps } from 'gatsby' import PageTemplateAssetDetails from '../../components/templates/PageAssetDetails' import AssetProvider from '../../providers/Asset' @@ -11,8 +12,10 @@ export default function PageGatsbyAssetDetails(props: PageProps): ReactElement { }, [props.location.pathname]) return ( - - - + + + + + ) } diff --git a/src/utils/rbac.ts b/src/utils/rbac.ts new file mode 100644 index 000000000..80b08e4f5 --- /dev/null +++ b/src/utils/rbac.ts @@ -0,0 +1,34 @@ +import fetch from 'cross-fetch' +import appConfig from '../../app.config' + +export default async function rbacRequest( + eventType: string, + address: string +): Promise { + const url = appConfig.rbacUrl + if (url === undefined) { + return true + } else { + const data = { + component: 'market', + eventType, + authService: 'address', + credentials: { + address + } + } + try { + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }) + return await response.json() + } catch (error) { + console.error('Error parsing json: ' + error.message) + return 'ERROR' + } + } +}