Skip to content

Commit

Permalink
Merge pull request #425 from deltaDAO/feat/form-whitelist
Browse files Browse the repository at this point in the history
feat: add whitelist & blacklist inputs
  • Loading branch information
moritzkirstein authored Oct 3, 2023
2 parents f8a4795 + e6667b4 commit 83b65d8
Show file tree
Hide file tree
Showing 15 changed files with 213 additions and 8 deletions.
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);
}
86 changes: 86 additions & 0 deletions src/components/@shared/FormInput/InputElement/Credential/index.tsx
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

0 comments on commit 83b65d8

Please sign in to comment.