Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add whitelist & blacklist inputs #425

Merged
merged 9 commits into from
Oct 3, 2023
14 changes: 14 additions & 0 deletions content/pages/editMetadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,20 @@
"options": ["This asset uses algorithm custom parameters"],
"required": false
},
{
"name": "allow",
"label": "Allow ETH Address",
"placeholder": "e.g. 0xe328aB96B7CbB55A6E1c1054678137bA09780acA",
"help": "Enter an ETH address and click the ADD button to append to the list. Only ETH addresses in the allow list can consume this asset. If the list is empty anyone can download or compute this asset.",
"type": "credentials"
},
{
"name": "deny",
"label": "Deny ETH Address",
"placeholder": "e.g. 0xe328aB96B7CbB55A6E1c1054678137bA09780acA",
"help": "Enter an ETH address and click the ADD button to append to the list. If an ETH address is in the deny list, download or compute of this asset will be denied for that ETH address.",
"type": "credentials"
},
{
"name": "paymentCollector",
"label": "Payment Collector Address",
Expand Down
14 changes: 14 additions & 0 deletions content/publish/form.json
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,20 @@
"type": "checkbox",
"options": ["This asset uses user defined parameters"],
"required": false
},
{
"name": "allow",
"label": "Allow ETH Address",
"placeholder": "e.g. 0xe328aB96B7CbB55A6E1c1054678137bA09780acA",
"help": "Enter an ETH address and click the ADD button to append to the list. Only ETH addresses in the allow list can consume this asset. If the list is empty anyone can download or compute this asset.",
"type": "credentials"
},
{
"name": "deny",
"label": "Deny ETH Address",
"placeholder": "e.g. 0xe328aB96B7CbB55A6E1c1054678137bA09780acA",
"help": "Enter an ETH address and click the ADD button to append to the list. If an ETH address is in the deny list, download or compute of this asset will be denied for that ETH address.",
"type": "credentials"
}
]
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.addressListContainer {
display: flex;
flex-direction: column;
gap: calc(var(--spacer) / 4);
margin-top: calc(var(--spacer) / 2);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useField } from 'formik'
import React, { useState, ChangeEvent, FormEvent, useEffect } from 'react'
import Button from '../../../atoms/Button'
import styles from './index.module.css'
import { isAddress } from 'ethers/lib/utils.js'
import { toast } from 'react-toastify'
import InputGroup from '../../InputGroup'
import InputElement from '..'
import { InputProps } from '../..'

export default function Credentials(props: InputProps) {
const [field, meta, helpers] = useField(props.name)
const [addressList, setAddressList] = useState<string[]>(field.value || [])
const [value, setValue] = useState('')

useEffect(() => {
helpers.setValue(addressList)
}, [addressList])

function handleDeleteAddress(value: string) {
const newInput = addressList.filter((input) => input !== value)
setAddressList(newInput)
helpers.setValue(newInput)
}

function handleAddValue(e: FormEvent<HTMLButtonElement>) {
e.preventDefault()
if (!isAddress(value)) {
toast.error('Wallet address is invalid')
return
}
if (addressList.includes(value.toLowerCase())) {
toast.error('Wallet address already added into hte list')
return
}
setAddressList((addressList) => [...addressList, value.toLowerCase()])
setValue('')
}

return (
<div>
<InputGroup>
<InputElement
name="address"
placeholder={props.placeholder}
value={value}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setValue(e.target.value)
}
/>
<Button
style="primary"
size="small"
onClick={(e: FormEvent<HTMLButtonElement>) => {
e.preventDefault()
handleAddValue(e)
}}
>
Add
</Button>
</InputGroup>
<div>
{addressList.length > 0 &&
addressList.map((value, i) => {
return (
<div className={styles.addressListContainer} key={value}>
<InputGroup>
<InputElement name={`address[${i}]`} value={value} disabled />
<Button
style="primary"
size="small"
onClick={(e: React.SyntheticEvent) => {
e.preventDefault()
handleDeleteAddress(value)
}}
>
Remove
</Button>
</InputGroup>
</div>
)
})}
</div>
</div>
)
}
3 changes: 3 additions & 0 deletions src/components/@shared/FormInput/InputElement/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { extensions, oceanTheme } from '@utils/codemirror'
import { ConsumerParameters } from './ConsumerParameters'
import ServiceCredential from './ServiceCredential'
import ComputeEnvSelection from './ComputeEnvSelection'
import Credentials from './Credential'

const cx = classNames.bind(styles)

Expand Down Expand Up @@ -212,6 +213,8 @@ const InputElement = forwardRef(
)
case 'tags':
return <TagsAutoComplete {...field} {...props} />
case 'credentials':
return <Credentials {...field} {...props} />
default:
return prefix || postfix ? (
<div
Expand Down
15 changes: 13 additions & 2 deletions src/components/Asset/Edit/EditMetadata.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ import { getEncryptedFiles } from '@utils/provider'
import { assetStateToNumber } from '@utils/assetState'
import { setMinterToPublisher, setMinterToDispenser } from '@utils/dispenser'
import { useAccount, useProvider, useNetwork, useSigner } from 'wagmi'
import { transformConsumerParameters } from '@components/Publish/_utils'
import {
transformConsumerParameters,
generateCredentials
} from '@components/Publish/_utils'

export default function Edit({
asset
Expand Down Expand Up @@ -161,12 +164,19 @@ export default function Edit({
)
}

const updatedCredentials = generateCredentials(
asset?.credentials,
values?.allow,
values?.deny
)

// TODO: remove version update at a later time
const updatedAsset: Asset = {
...(asset as Asset),
version: '4.1.0',
metadata: updatedMetadata,
services: [updatedService]
services: [updatedService],
credentials: updatedCredentials
}

if (
Expand Down Expand Up @@ -244,6 +254,7 @@ export default function Edit({
initialValues={getInitialValues(
asset?.metadata,
asset?.services[0],
asset?.credentials,
asset?.accessDetails?.price || '0',
paymentCollector,
assetState
Expand Down
7 changes: 6 additions & 1 deletion src/components/Asset/Edit/FormEditMetadata.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,12 @@ export default function FormEditMetadata({
)}
</>
)}

<Field
{...getFieldContent('allow', data)}
component={Input}
name="allow"
/>
<Field {...getFieldContent('deny', data)} component={Input} name="deny" />
<Field
{...getFieldContent('paymentCollector', data)}
component={Input}
Expand Down
14 changes: 13 additions & 1 deletion src/components/Asset/Edit/_constants.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { Metadata, Service, ServiceComputeOptions } from '@oceanprotocol/lib'
import {
Credentials,
Metadata,
Service,
ServiceComputeOptions
} from '@oceanprotocol/lib'
import { parseConsumerParameters, secondsToString } from '@utils/ddo'
import { ComputeEditForm, MetadataEditForm } from './_types'

export function getInitialValues(
metadata: Metadata,
service: Service,
credentials: Credentials,
price: string,
paymentCollector: string,
assetState: string
Expand All @@ -23,6 +29,12 @@ export function getInitialValues(
metadata?.algorithm?.consumerParameters
),
paymentCollector,
allow:
credentials?.allow?.find((credential) => credential.type === 'address')
?.values || [],
deny:
credentials?.deny?.find((credential) => credential.type === 'address')
?.values || [],
assetState,
service: {
usesConsumerParameters: service?.consumerParameters?.length > 0,
Expand Down
2 changes: 2 additions & 0 deletions src/components/Asset/Edit/_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export interface MetadataEditForm {
tags?: string[]
usesConsumerParameters?: boolean
consumerParameters?: FormConsumerParameter[]
allow?: string[]
deny?: string[]
assetState?: string
service?: {
usesConsumerParameters?: boolean
Expand Down
2 changes: 2 additions & 0 deletions src/components/Asset/Edit/_validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export const validationSchema = Yup.object().shape({
.nullable()
.transform((value) => value || null)
}),
allow: Yup.array().of(Yup.string()).nullable(),
deny: Yup.array().of(Yup.string()).nullable(),
paymentCollector: Yup.string().test(
'ValidAddress',
'Must be a valid Ethereum Address.',
Expand Down
10 changes: 10 additions & 0 deletions src/components/Publish/Services/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ export default function ServicesFields(): ReactElement {
component={Input}
name="services[0].usesConsumerParameters"
/>
<Field
{...getFieldContent('allow', content.services.fields)}
component={Input}
name="services[0].allow"
/>
<Field
{...getFieldContent('deny', content.services.fields)}
component={Input}
name="services[0].deny"
/>
{values.services[0].usesConsumerParameters && (
<Field
{...getFieldContent(
Expand Down
4 changes: 3 additions & 1 deletion src/components/Publish/_constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ export const initialValues: FormPublishData = {
},
computeOptions,
usesConsumerParameters: false,
consumerParameters: []
consumerParameters: [],
allow: [],
deny: []
}
],
pricing: {
Expand Down
2 changes: 2 additions & 0 deletions src/components/Publish/_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export interface FormPublishService {
computeOptions?: ServiceComputeOptions
usesConsumerParameters?: boolean
consumerParameters?: FormConsumerParameter[]
allow?: string[]
deny?: string[]
}

export interface FormPublishData {
Expand Down
38 changes: 36 additions & 2 deletions src/components/Publish/_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
getEventFromTx,
ConsumerParameter,
Metadata,
Service
Service,
Credentials
} from '@oceanprotocol/lib'
import { mapTimeoutStringToSeconds, normalizeFile } from '@utils/ddo'
import { generateNftCreateData } from '@utils/nft'
Expand Down Expand Up @@ -96,6 +97,35 @@ export function transformConsumerParameters(
return transformedValues as ConsumerParameter[]
}

export function generateCredentials(
oldCredentials: Credentials,
updatedAllow: string[],
updatedDeny: string[]
): Credentials {
const updatedCredentials = {
allow: oldCredentials?.allow || [],
deny: oldCredentials?.deny || []
}

const credentialTypes = [
{ type: 'allow', values: updatedAllow },
{ type: 'deny', values: updatedDeny }
]

credentialTypes.forEach((credentialType) => {
updatedCredentials[credentialType.type] = [
...updatedCredentials[credentialType.type].filter(
(credential) => credential?.type !== 'address'
),
...(credentialType.values.length > 0
? [{ type: 'address', values: credentialType.values }]
: [])
]
})

return updatedCredentials
}

export async function transformPublishFormToDdo(
values: FormPublishData,
// Those 2 are only passed during actual publishing process
Expand All @@ -121,7 +151,8 @@ export async function transformPublishFormToDdo(
consumerParameters,
gaiaXInformation
} = metadata
const { access, files, links, providerUrl, timeout } = services[0]
const { access, files, links, providerUrl, timeout, allow, deny } =
services[0]

const did = nftAddress ? generateDid(nftAddress, chainId) : '0x...'
const currentTime = dateToStringNoMS(new Date())
Expand Down Expand Up @@ -227,6 +258,8 @@ export async function transformPublishFormToDdo(
: undefined
}

const newCredentials = generateCredentials(undefined, allow, deny)

const newDdo: DDO = {
'@context': ['https://w3id.org/did/v1'],
id: did,
Expand All @@ -235,6 +268,7 @@ export async function transformPublishFormToDdo(
chainId,
metadata: newMetadata,
services: [newService],
credentials: newCredentials,
// Only added for DDO preview, reflecting Asset response,
// again, we can assume if `datatokenAddress` is not passed,
// we are on preview.
Expand Down
4 changes: 3 additions & 1 deletion src/components/Publish/_validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ const validationService = {
otherwise: Yup.array()
.nullable()
.transform((value) => value || null)
})
}),
allow: Yup.array().of(Yup.string()).nullable(),
deny: Yup.array().of(Yup.string()).nullable()
}

const validationPricing = {
Expand Down
Loading