diff --git a/src/components/AgentAuthority/ColumnLeft/ColumnLeftWrap/ColumnLeftWrap.js b/src/components/AgentAuthority/ColumnLeft/ColumnLeftWrap/ColumnLeftWrap.js index 2b3977d..e128de4 100644 --- a/src/components/AgentAuthority/ColumnLeft/ColumnLeftWrap/ColumnLeftWrap.js +++ b/src/components/AgentAuthority/ColumnLeft/ColumnLeftWrap/ColumnLeftWrap.js @@ -5,6 +5,37 @@ */ export default function ColumnLeftWrap() { + // const setStoreDataFn = (resData) => { + // setRecords(() => { + // const verifiedProofs = resData.filter( + // (record) => record.state === 'done' + // ) + // const presentations = resData.filter( + // (record) => record.state === 'presentation-received' + // ) + // verifiedProofs.forEach((verified) => { + // const presentation = verified.by_format.pres.indy.requested_proof + // // console.log(presentation) + // // startFetchHandlerCredOffer( + // // origin, + // // verified.connection_id, + // // licenseCredDefId, + // // presentation.revealed_attrs['0_id_uuid'].raw, + // // presentation.revealed_attrs['0_type_uuid'].raw, + // // 'expiry', + // // setStatusIssueCred, + // // setDataIssueCred + // // ) + // }) + // return resData + // }) + // } + //const intervalIdFetch = startGetRecordsHandler(origin, setStoreDataFn) + // if (statusRecordEvents !== 'started') clearInterval(intervalIdFetch) + // return function clear() { + // return clearInterval(intervalIdFetch) + //} + return ( <>
diff --git a/src/components/AgentAuthority/ContentWrap/ContentWrap.js b/src/components/AgentAuthority/ContentWrap/ContentWrap.js index 7b37cab..429734b 100644 --- a/src/components/AgentAuthority/ContentWrap/ContentWrap.js +++ b/src/components/AgentAuthority/ContentWrap/ContentWrap.js @@ -6,9 +6,16 @@ import ColumnLeftWrap from '../ColumnLeft/ColumnLeftWrap' import ColumnRightWrap from '../ColumnRight/ColumnRightWrap' +import ProofRequest from '../RequestProof' +import Verify from '../Verify' +import IssueLicense from '../IssueLicense' + export default function ContentWrap({ origin }) { return ( <> + + + diff --git a/src/components/AgentAuthority/IssueLicense/IssueLicense.js b/src/components/AgentAuthority/IssueLicense/IssueLicense.js new file mode 100644 index 0000000..95cc148 --- /dev/null +++ b/src/components/AgentAuthority/IssueLicense/IssueLicense.js @@ -0,0 +1,76 @@ +/** + * Listens for verified presentation proofs and issues a license + */ +import { useEffect, useState } from 'react' +import LicenseCredDef from '../LicenseCredDef' +import useGetLoopedPresentProofRecords from '../../../interface/hooks/use-get-looped-present-proof-records' +import usePostIssueCredentialSendOffer from '../../../interface/hooks/use-post-issue-credential-send-offer' + +export default function IssueLicense({ origin }) { + const [licenseCredDefId, setLicenseCredDefId] = useState('') + const [statusRecords, errorRecords, startGetRecordsHandler] = + useGetLoopedPresentProofRecords() + const [errorIssueCred, startFetchHandlerCredOffer] = + usePostIssueCredentialSendOffer() + + useEffect(() => { + const sendCredOffers = (verifiedProofs) => { + verifiedProofs.forEach((verified) => { + const presentation = verified.by_format.pres.indy.requested_proof + console.log(presentation.predicates['0_expiration_dateint_GE_uui']) + startFetchHandlerCredOffer( + origin, + verified.connection_id, + licenseCredDefId, + presentation.revealed_attrs['0_id_uuid'].raw, + presentation.revealed_attrs['0_type_uuid'].raw, + 'expiry', + () => {}, + () => {} + ) + }) + } + const intervalIdFetch = startGetRecordsHandler( + origin, + 'done', + sendCredOffers + ) + if (statusRecords !== 'started') clearInterval(intervalIdFetch) + return function clear() { + return clearInterval(intervalIdFetch) + } + }, [ + origin, + statusRecords, + startFetchHandlerCredOffer, + startGetRecordsHandler, + licenseCredDefId, + ]) + + return ( + <> + +
+
+ + {errorRecords} + {errorIssueCred} + +
+
+ + ) +} diff --git a/src/components/AgentAuthority/IssueLicense/index.js b/src/components/AgentAuthority/IssueLicense/index.js new file mode 100644 index 0000000..f8b8eff --- /dev/null +++ b/src/components/AgentAuthority/IssueLicense/index.js @@ -0,0 +1 @@ +export { default } from './IssueLicense' diff --git a/src/components/AgentAuthority/LicenseCredDef/LicenseCredDef.js b/src/components/AgentAuthority/LicenseCredDef/LicenseCredDef.js new file mode 100644 index 0000000..5e058be --- /dev/null +++ b/src/components/AgentAuthority/LicenseCredDef/LicenseCredDef.js @@ -0,0 +1,72 @@ +/** + * Get or create the credential definitions for the + * license issued by the authority + */ +import { useEffect } from 'react' + +import { AUTHORITY_LABEL, LICENSE_SCHEMA_NAME } from '../../../utils/env.js' +import useGetCredDefinitionsCreated from '../../../interface/hooks/use-get-cred-definitions-created' +import usePostSchemas from '../../../interface/hooks/use-post-schemas' +import usePostCredentialDefinitions from '../../../interface/hooks/use-post-credential-definitions' + +export default function LicenseCredDef({ origin, setLicenseCredDefId }) { + const [errorDefinitionsCreated, startFetchHandlerDefinitionsCreated] = + useGetCredDefinitionsCreated() + + const [errorPostSchema, startPostSchema] = usePostSchemas() + const [errorPostCredDefs, startPostCredDef] = usePostCredentialDefinitions() + + useEffect(() => { + startFetchHandlerDefinitionsCreated(origin, (credDefIds) => { + const licenseCredDefId = credDefIds.find((credDefId) => + credDefId.includes(LICENSE_SCHEMA_NAME) + ) + if (licenseCredDefId) { + setLicenseCredDefId(licenseCredDefId) + } else { + startPostSchema(origin, LICENSE_SCHEMA_NAME, (schemaId) => { + startPostCredDef( + origin, + schemaId, + AUTHORITY_LABEL, + setLicenseCredDefId + ) + }) + } + }) + }, [ + origin, + startFetchHandlerDefinitionsCreated, + startPostSchema, + startPostCredDef, + setLicenseCredDefId, + ]) + + return ( + <> +
+
+ + {errorDefinitionsCreated} + {errorPostSchema} + {errorPostCredDefs} + +
+
+ + ) +} diff --git a/src/components/AgentAuthority/LicenseCredDef/index.js b/src/components/AgentAuthority/LicenseCredDef/index.js new file mode 100644 index 0000000..ab1b4cf --- /dev/null +++ b/src/components/AgentAuthority/LicenseCredDef/index.js @@ -0,0 +1 @@ +export { default } from './LicenseCredDef' diff --git a/src/components/AgentAuthority/RequestProof/RequestProof.js b/src/components/AgentAuthority/RequestProof/RequestProof.js new file mode 100644 index 0000000..34150e1 --- /dev/null +++ b/src/components/AgentAuthority/RequestProof/RequestProof.js @@ -0,0 +1,80 @@ +/** + * Listens for presentation proof proposals and sends requests in response, then deletes the original proposal + */ +import { useEffect } from 'react' +import useGetLoopedPresentProofRecords from '../../../interface/hooks/use-get-looped-present-proof-records' +import useDeleteRecord from '../../../interface/hooks/use-delete-record' +import usePostPresentProofSendRequest from '../../../interface/hooks/use-post-present-proof-send-request' + +export default function RequestProof({ origin }) { + const [statusRecords, errorRecords, startGetRecordsHandler] = + useGetLoopedPresentProofRecords() + const [errorRequest, startFetchHandlerRequest] = + usePostPresentProofSendRequest() + const [errorDelete, startDeleteHandler] = useDeleteRecord() + + useEffect(() => { + const sendRequests = (proposals) => { + console.log(proposals) + proposals.forEach((proposal) => { + startFetchHandlerRequest({ + origin, + comment: proposal.pres_proposal.comment, + connectionId: proposal.connection_id, + proposal: proposal.by_format.pres_proposal.indy, + validity: new Date().toISOString().split('T')[0], + setStatus: () => {}, + setStoreData: () => { + startDeleteHandler( + origin, + proposal.pres_ex_id, + () => {}, + () => {} + ) + }, + }) + }) + } + const intervalIdFetch = startGetRecordsHandler( + origin, + 'proposal-received', + sendRequests + ) + if (statusRecords !== 'started') clearInterval(intervalIdFetch) + return function clear() { + return clearInterval(intervalIdFetch) + } + }, [ + origin, + statusRecords, + startFetchHandlerRequest, + startGetRecordsHandler, + startDeleteHandler, + ]) + + return ( + <> +
+
+ + {errorRecords} + {errorRequest} + {errorDelete} + +
+
+ + ) +} diff --git a/src/components/AgentAuthority/RequestProof/index.js b/src/components/AgentAuthority/RequestProof/index.js new file mode 100644 index 0000000..dc5add5 --- /dev/null +++ b/src/components/AgentAuthority/RequestProof/index.js @@ -0,0 +1 @@ +export { default } from './RequestProof' diff --git a/src/components/AgentAuthority/Verify/Verify.js b/src/components/AgentAuthority/Verify/Verify.js new file mode 100644 index 0000000..59f0d46 --- /dev/null +++ b/src/components/AgentAuthority/Verify/Verify.js @@ -0,0 +1,58 @@ +/** + * Listens for presentation proofs and verifies them + */ +import { useEffect } from 'react' +import useGetLoopedPresentProofRecords from '../../../interface/hooks/use-get-looped-present-proof-records' +import usePostPresentProofRecordsVerifyPresentation from '../../../interface/hooks/use-post-present-proof-records-verify-presentation' + +export default function Verify({ origin }) { + const [statusRecords, errorRecords, startGetRecordsHandler] = + useGetLoopedPresentProofRecords() + const [errorVerify, startFetchHandlerVerify] = + usePostPresentProofRecordsVerifyPresentation() + + useEffect(() => { + const sendVerifications = (presentations) => { + presentations.forEach((presentation) => { + startFetchHandlerVerify( + origin, + presentation.pres_ex_id, + () => {}, + () => {} + ) + }) + } + const intervalIdFetch = startGetRecordsHandler( + origin, + 'presentation-received', + sendVerifications + ) + if (statusRecords !== 'started') clearInterval(intervalIdFetch) + return function clear() { + return clearInterval(intervalIdFetch) + } + }, [origin, statusRecords, startFetchHandlerVerify, startGetRecordsHandler]) + + return ( + <> +
+
+ + {errorRecords} + {errorVerify} + +
+
+ + ) +} diff --git a/src/components/AgentAuthority/Verify/index.js b/src/components/AgentAuthority/Verify/index.js new file mode 100644 index 0000000..5fd8cab --- /dev/null +++ b/src/components/AgentAuthority/Verify/index.js @@ -0,0 +1 @@ +export { default } from './Verify' diff --git a/src/components/Common/Misc/ErrorBox/ErrorBox.js b/src/components/Common/Misc/ErrorBox/ErrorBox.js new file mode 100644 index 0000000..a624e1f --- /dev/null +++ b/src/components/Common/Misc/ErrorBox/ErrorBox.js @@ -0,0 +1,35 @@ +export default function ErrorBox({ children, visibility, content }) { + const clickReloadHandler = () => { + window.location.reload() + } + return ( +
+
+
+
+
{children || content}
+
+
+ +
+
+
+
+ ) +} diff --git a/src/components/Common/Misc/ErrorBox/index.js b/src/components/Common/Misc/ErrorBox/index.js new file mode 100644 index 0000000..9633e95 --- /dev/null +++ b/src/components/Common/Misc/ErrorBox/index.js @@ -0,0 +1 @@ +export { default } from './ErrorBox' diff --git a/src/interface/hooks/use-delete-record.js b/src/interface/hooks/use-delete-record.js new file mode 100644 index 0000000..fe35448 --- /dev/null +++ b/src/interface/hooks/use-delete-record.js @@ -0,0 +1,26 @@ +/** + * It returns a function that can be used to delete a connection. + */ +import { useCallback, useState } from 'react' +import del from '../api/helpers/del' + +export default function useDeleteRecord() { + const path = '/present-proof-2.0/records/' + const transformData = (retrievedData) => retrievedData + const [error, setError] = useState(null) + const onStartFetch = useCallback( + (origin, presExId, setStatus, setStoreData) => { + del( + origin, + path + presExId, + {}, + setStatus, + setError, + setStoreData, + transformData + ) + }, + [] + ) + return [error, onStartFetch] +} diff --git a/src/interface/hooks/use-get-cred-definitions-created.js b/src/interface/hooks/use-get-cred-definitions-created.js new file mode 100644 index 0000000..5f39325 --- /dev/null +++ b/src/interface/hooks/use-get-cred-definitions-created.js @@ -0,0 +1,19 @@ +/** + * This function returns a function that, will fetch the credential + * definitions created by the user + */ +import { useCallback, useState } from 'react' +import get from '../api/helpers/get' + +export default function useGetCredDefinitionsCreated() { + const path = '/credential-definitions/created' + const transformData = (retrievedData) => + retrievedData.credential_definition_ids + + const [error, setError] = useState(null) + + const onStartFetch = useCallback((fetchOrigin, setStoreData) => { + get(fetchOrigin, path, {}, () => {}, setError, setStoreData, transformData) + }, []) + return [error, onStartFetch] +} diff --git a/src/interface/hooks/use-get-looped-present-proof-records.js b/src/interface/hooks/use-get-looped-present-proof-records.js new file mode 100644 index 0000000..2fecb08 --- /dev/null +++ b/src/interface/hooks/use-get-looped-present-proof-records.js @@ -0,0 +1,28 @@ +/** + * This function returns continuously the present proof records + */ +import { useCallback, useState } from 'react' +import getLooped from '../api/helpers/get-looped' +export default function useGetLoopedPresentProofRecords() { + const path = '/present-proof-2.0/records' + const transformData = (retData) => retData.results + const statusOptions = ['started', 'error', 'stopped'] + const [status, setStatus] = useState(statusOptions[0]) + const [error, setError] = useState(null) + const onStartFetch = useCallback((origin, state = null, setStoreData) => { + const params = state ? `state=${state}` : {} + const intervalId = setInterval(() => { + getLooped( + origin, + path, + params, + setStatus, + setError, + setStoreData, + transformData + ) + }, 3000) + return intervalId + }, []) + return [status, error, onStartFetch] +} diff --git a/src/interface/hooks/use-post-credential-definitions.js b/src/interface/hooks/use-post-credential-definitions.js new file mode 100644 index 0000000..d285a4b --- /dev/null +++ b/src/interface/hooks/use-post-credential-definitions.js @@ -0,0 +1,51 @@ +/** + * This function is used to create a credential definition + */ +import { useCallback, useState } from 'react' +import post from '../api/helpers/post' + +export default function usePostCredentialDefinitions() { + const path = '/credential-definitions' + const transformData = (retrievedData) => + retrievedData.credential_definition_id + const [error, setError] = useState(null) + const onStartFetch = useCallback( + (fetchOrigin, schemaId, persona, setStoreData) => { + const params = {} + const createBody = (schemaId, persona) => { + const supportRevocation = false + const sanitizeTitleWUnderscore = (str) => { + const space = new RegExp(' ', 'g') + str = str.replace(space, '_') + return str + } + + const schemaDefName = schemaId.split(':')[2] + const schemaDefTagName = sanitizeTitleWUnderscore(schemaDefName) + const schemaDefTagPrefix = `${persona}.agent` + const credDefTag = `${schemaDefTagPrefix}.${schemaDefTagName}` + const did = schemaId.split(':')[0] + const definitionBody = { + schema_id: schemaId, + support_revocation: supportRevocation, + tag: credDefTag, + did: did, + } + return definitionBody + } + const body = createBody(schemaId, persona) + post( + fetchOrigin, + path, + params, + body, + () => {}, + setError, + setStoreData, + transformData + ) + }, + [] + ) + return [error, onStartFetch] +} diff --git a/src/interface/hooks/use-post-issue-credential-send-offer.js b/src/interface/hooks/use-post-issue-credential-send-offer.js new file mode 100644 index 0000000..f9b99c2 --- /dev/null +++ b/src/interface/hooks/use-post-issue-credential-send-offer.js @@ -0,0 +1,86 @@ +/** + * This function is used to send with POST a credential offer to the server + */ +import { useCallback, useState } from 'react' +import post from '../api/helpers/post' + +export default function usePostIssueCredentialSendOffer() { + const path = '/issue-credential-2.0/send-offer' + const transformData = (retrievedData) => retrievedData.cred_ex_id + const [error, setError] = useState(null) + const onStartFetch = useCallback( + ( + origin, + connectionId, + credDefId, + id, + type, + expiration, + setStoreStatus, + setStoreData + ) => { + const createBody = (connectionId, credDefId, id, type, expiration) => { + const CRED_PREVIEW_TYPE = + 'https://didcomm.org/issue-credential/2.0/credential-preview' + const exchangeTracing = false + const autoRemove = false + const getTimestamp = () => { + const timestamp = new Date() / 1000 + return timestamp.toFixed() + } + const getDateString = (d) => { + if (!d) { + let now = new Date() + now = +now.setHours(0, 0, 0, 0) + 86400000 + now = new Date(now).toISOString() + now = now.slice(0, 10) + d = now + } + d = d.split('-').join('') + return d + } + const credAttrs = { + id: id, + type: type, + expiration_dateint: getDateString(expiration), + timestamp: getTimestamp(), + } + const convertToNameValueArr = (obj) => { + const nameValueArr = [] + for (const [name, value] of Object.entries(obj)) { + nameValueArr.push({ name, value }) + } + return nameValueArr + } + const credAttrsArr = convertToNameValueArr(credAttrs) + const credPreview = { + '@type': CRED_PREVIEW_TYPE, + attributes: credAttrsArr, + } + const offerRequest = { + connection_id: connectionId, + comment: `Offer on cred def id ${credDefId}`, + auto_remove: autoRemove, + credential_preview: credPreview, + filter: { indy: { cred_def_id: credDefId } }, + trace: exchangeTracing, + } + return offerRequest + } + const params = {} + const body = createBody(connectionId, credDefId, id, type, expiration) + post( + origin, + path, + params, + body, + setStoreStatus, + setError, + setStoreData, + transformData + ) + }, + [] + ) + return [error, onStartFetch] +} diff --git a/src/interface/hooks/use-post-present-proof-records-verify-presentation.js b/src/interface/hooks/use-post-present-proof-records-verify-presentation.js new file mode 100644 index 0000000..0abb0ab --- /dev/null +++ b/src/interface/hooks/use-post-present-proof-records-verify-presentation.js @@ -0,0 +1,43 @@ +/** + * This function is used to post a verify presentation record. + */ +import { useCallback, useState } from 'react' +import post from '../api/helpers/post' + +export default function usePostPresentProofRecordsVerifyPresentation() { + const [inflight, setInflight] = useState(new Set()) + + const path = '/present-proof-2.0/records/?/verify-presentation' + + const [error, setError] = useState(null) + const onStartFetch = useCallback( + (origin, presentationExchangeId, setStatus, setStoreData) => { + if (!inflight.has(presentationExchangeId)) { + const pathFinal = path.replace('?', presentationExchangeId) + const params = {} + const body = {} + + const transformData = (retrievedData) => { + setInflight( + (prev) => + new Set([...prev].filter((id) => id != presentationExchangeId)) + ) + return retrievedData + } + + post( + origin, + pathFinal, + params, + body, + setStatus, + setError, + setStoreData, + transformData + ) + } + }, + [inflight] + ) + return [error, onStartFetch] +} diff --git a/src/interface/hooks/use-post-present-proof-send-request.js b/src/interface/hooks/use-post-present-proof-send-request.js new file mode 100644 index 0000000..28cd066 --- /dev/null +++ b/src/interface/hooks/use-post-present-proof-send-request.js @@ -0,0 +1,82 @@ +/** + * This function is used to send with POST a proposal for a present proof + */ +import { useCallback, useState } from 'react' +import post from '../api/helpers/post' + +export default function usePostPresentProofSendRequest() { + const [inflight, setInflight] = useState(new Set()) + + const path = '/present-proof-2.0/send-request' + + const [error, setError] = useState(null) + + const createBody = (comment, connectionId, proposal, validity) => { + const reqPrs4zkProofs = [ + { + name: 'expiration_dateint', + p_type: '>=', + p_value: validity.split('-').join('') * 1, + restrictions: [{ schema_name: 'drone schema' }], + }, + ] + + const proofProposalWebRequest = { + comment, + connection_id: connectionId, + presentation_request: { + indy: { + name: proposal.name, + version: proposal.version, + requested_attributes: proposal.requested_attributes, + requested_predicates: Object.fromEntries( + reqPrs4zkProofs.map((e) => [`0_${e.name}_GE_uuid`, e]) + ), + }, + }, + + trace: false, + } + return proofProposalWebRequest + } + + const onStartFetch = useCallback( + ({ + origin, + comment, + connectionId, + proposal, + validity, + setStatus, + setStoreData, + }) => { + if (!inflight.has(proposal.pres_ex_id)) { + setInflight((prev) => new Set(prev.add(proposal.pres_ex_id))) + + const params = {} + const body = createBody(comment, connectionId, proposal, validity) + + const transformData = (retrievedData) => { + setInflight( + (prev) => + new Set([...prev].filter((id) => id != proposal.pres_ex_id)) + ) + return retrievedData.pres_ex_id + } + + post( + origin, + path, + params, + body, + setStatus, + setError, + setStoreData, + transformData + ) + } + }, + [inflight] + ) + return [error, onStartFetch] +} diff --git a/src/interface/hooks/use-post-schemas.js b/src/interface/hooks/use-post-schemas.js new file mode 100644 index 0000000..28059be --- /dev/null +++ b/src/interface/hooks/use-post-schemas.js @@ -0,0 +1,39 @@ +/** + * It creates a function that will post a schema to the server. + */ +import { useCallback, useState } from 'react' +import post from '../api/helpers/post' +/* {"schema_name":"drone schema","schema_version":"1.91","attributes":["id","name","surname","type","title", +"subtitle","expiration_dateint","timestamp"]} */ +export default function usePostSchemas() { + const path = '/schemas' + const transformData = (retrievedData) => retrievedData.schema_id + const [error, setError] = useState(null) + const onStartFetch = useCallback((fetchOrigin, schemaName, setStoreData) => { + const createBody = (schemaName) => { + const version = () => { + const major = parseInt(Math.random() * 10 + 0) + const minor = parseInt(Math.random() * 100 + 0) + return `${major}.${minor}` + } + return { + schema_name: schemaName, + schema_version: version(), + attributes: ['id', 'type', 'expiration_dateint'], + } + } + + const body = createBody(schemaName) + post( + fetchOrigin, + path, + {}, + body, + () => {}, + setError, + setStoreData, + transformData + ) + }, []) + return [error, onStartFetch] +} diff --git a/src/utils/env.js b/src/utils/env.js index 695a41d..aa1a087 100644 --- a/src/utils/env.js +++ b/src/utils/env.js @@ -1,2 +1,6 @@ export const API_HOST = process.env.REACT_APP_API_HOST || 'localhost' export const API_PORT = process.env.REACT_APP_API_PORT || 8051 +export const AUTHORITY_LABEL = + process.env.REACT_APP_AUTHORITY_LABEL || 'authority' +export const LICENSE_SCHEMA_NAME = + process.env.REACT_APP_LICENSE_SCHEMA_NAME || 'AuthorityLicense'