Skip to content

Commit

Permalink
feat: add remote verification templates (#148)
Browse files Browse the repository at this point in the history
* feat: add remote verification templates

Signed-off-by: Sai Ranjit Tummalapalli <[email protected]>

* chore: bump versions

Signed-off-by: Sai Ranjit Tummalapalli <[email protected]>

* fix: verifier capability for previous version

Signed-off-by: Sai Ranjit Tummalapalli <[email protected]>

---------

Signed-off-by: Sai Ranjit Tummalapalli <[email protected]>
Signed-off-by: Poonam Ghewande <[email protected]>
  • Loading branch information
sairanjit authored and poonam-ghewande committed Dec 5, 2023
1 parent 9538b1b commit d6d4a70
Show file tree
Hide file tree
Showing 15 changed files with 282 additions and 158 deletions.
3 changes: 3 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ OCA_URL=https://raw.githubusercontent.com/credebl/credebl-aries-oca-bundles/rele

#BASE_URL
PUBLIC_ORG=https://example.com

#PROOF_TEMPLATE_URL
PROOF_TEMPLATE_URL=
4 changes: 2 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ android {
applicationId "id.credebl.adeya"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 6
versionName "1.0.3"
versionCode 8
versionName "1.0.5"

missingDimensionStrategy 'react-native-camera', 'general'
}
Expand Down
3 changes: 2 additions & 1 deletion app/contexts/configuration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface ConfigurationContext {
credentialEmptyList: React.FC<EmptyListProps>
developer: React.FC
OCABundleResolver: OCABundleResolverType
proofTemplateBaseUrl?: string
scan: React.FC<ScanProps>
useBiometry: React.FC
record: React.FC<RecordProps>
Expand All @@ -39,7 +40,7 @@ export interface ConfigurationContext {
useCustomNotifications: () => { total: number; notifications: any }
connectionTimerDelay?: number
autoRedirectConnectionToHome?: boolean
proofRequestTemplates?: Array<ProofRequestTemplate>
proofRequestTemplates?: () => Array<ProofRequestTemplate>
enableTours?: boolean
enableWalletNaming?: boolean
enableBackupWallet?: boolean
Expand Down
2 changes: 1 addition & 1 deletion app/contexts/store.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const defaultState: State = {
developerModeEnabled: false,
biometryPreferencesUpdated: false,
useBiometry: false,
useVerifierCapability: false,
useVerifierCapability: true,
useConnectionInviterCapability: false,
useDevVerifierTemplates: false,
walletName: generateRandomWalletName(),
Expand Down
5 changes: 3 additions & 2 deletions app/defaultConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { BrandingOverlayType, RemoteOCABundleResolver } from '@hyperledger/aries
import { Config } from 'react-native-config'

import defaultIndyLedgers from '../configs/ledgers/indy'
import { defaultProofRequestTemplates } from '../verifier'
import { useProofRequestTemplates } from '../verifier'

import EmptyList from './components/misc/EmptyList'
import Record from './components/record/Record'
Expand Down Expand Up @@ -30,6 +30,7 @@ export const defaultConfiguration: ConfigurationContext = {
brandingOverlayType: BrandingOverlayType.Branding10,
preLoad: true,
}),
proofTemplateBaseUrl: Config.PROOF_TEMPLATE_URL ?? '',
scan: Scan,
useBiometry: UseBiometry,
record: Record,
Expand All @@ -45,7 +46,7 @@ export const defaultConfiguration: ConfigurationContext = {
pageTitle: '',
},
useCustomNotifications: useNotifications,
proofRequestTemplates: defaultProofRequestTemplates,
proofRequestTemplates: useProofRequestTemplates,
enableTours: false,
enableWalletNaming: true,
enableBackupWallet: true,
Expand Down
32 changes: 27 additions & 5 deletions app/hooks/proof-request-templates.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,36 @@
import { useMemo } from 'react'
import { useEffect, useState } from 'react'

import { ProofRequestTemplate } from '../../verifier'
import { useConfiguration } from '../contexts/configuration'
import { applyTemplateMarkers, useRemoteProofBundleResolver } from '../utils/proofBundle'

export const useTemplates = (): Array<ProofRequestTemplate> => {
const { proofRequestTemplates } = useConfiguration()
return proofRequestTemplates || []
const [proofRequestTemplates, setProofRequestTemplates] = useState<ProofRequestTemplate[]>([])
const { proofTemplateBaseUrl } = useConfiguration()
const resolver = useRemoteProofBundleResolver(proofTemplateBaseUrl)
const acceptDevCredentials = false
useEffect(() => {
resolver.resolve(acceptDevCredentials).then(templates => {
if (templates) {
setProofRequestTemplates(applyTemplateMarkers(templates))
}
})
}, [])
return proofRequestTemplates
}

export const useTemplate = (templateId: string): ProofRequestTemplate | undefined => {
const { proofRequestTemplates } = useConfiguration()
return useMemo(() => proofRequestTemplates?.find(template => template.id === templateId), [templateId])
const [proofRequestTemplate, setProofRequestTemplate] = useState<ProofRequestTemplate | undefined>(undefined)
const { proofTemplateBaseUrl } = useConfiguration()
const resolver = useRemoteProofBundleResolver(proofTemplateBaseUrl)
const acceptDevCredentials = false

useEffect(() => {
resolver.resolveById(templateId, acceptDevCredentials).then(template => {
if (template) {
setProofRequestTemplate(applyTemplateMarkers(template))
}
})
}, [])
return proofRequestTemplate
}
2 changes: 1 addition & 1 deletion app/screens/CredentialOfferAccept.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ const CredentialOfferAccept: React.FC<CredentialOfferAcceptProps> = ({ visible,
</ScrollView>

<View style={[styles.controlsContainer]}>
{credentialDeliveryStatus === DeliveryStatus.Pending && (
{credentialDeliveryStatus === DeliveryStatus.Pending && credential.state === CredentialState.RequestSent && (
<View>
<Button
title={t('Loading.BackToHome')}
Expand Down
2 changes: 1 addition & 1 deletion app/screens/ListCredentials.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const ListCredentials: React.FC = () => {
updateCredentials().then(updatedCredentials => {
setCredentialList(updatedCredentials)
})
}, [])
}, [credentialList])

return (
<View style={styles.container}>
Expand Down
85 changes: 58 additions & 27 deletions app/screens/ProofRequestDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { MetaOverlay, OverlayType } from '@hyperledger/aries-oca'
import { MetaOverlay } from '@hyperledger/aries-oca'
import { Attribute, Field, Predicate } from '@hyperledger/aries-oca/build/legacy'
import { OverlayType } from '@hyperledger/aries-oca/build/types/TypeEnums'
import { StackScreenProps } from '@react-navigation/stack'
import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
Expand All @@ -18,9 +20,8 @@ import { useStore } from '../contexts/store'
import { useTheme } from '../contexts/theme'
import { useTemplate } from '../hooks/proof-request-templates'
import { Screens, ProofRequestsStackParams } from '../types/navigators'
import { Attribute, Field, Predicate } from '../types/record'
import { useAppAgent } from '../utils/agent'
import { formatIfDate } from '../utils/helpers'
import { formatIfDate, pTypeToText } from '../utils/helpers'
import { buildFieldsFromAnonCredsProofRequestTemplate } from '../utils/oca'
import { parseSchemaFromId } from '../utils/schema'
import { testIdWithKey } from '../utils/testable'
Expand Down Expand Up @@ -53,14 +54,6 @@ const PredicateItem: React.FC<{
onChangeValue: (name: string, value: string) => void
}> = ({ item, style, onChangeValue }) => {
const { ColorPallet } = useTheme()
const [pValue, setPValue] = useState(item.pValue)

useEffect(() => {
// can't format the date if parameterizable, must remain a number
if (!item.parameterizable) {
setPValue(formatIfDate(item.format, pValue))
}
}, [])

const defaultStyle = StyleSheet.create({
input: {
Expand All @@ -71,25 +64,25 @@ const PredicateItem: React.FC<{
})

return (
<View style={{ flexDirection: 'row' }}>
<View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>
<Text style={style}>{item.label || item.name}</Text>
<Text style={style}>{item.pType}</Text>
{item.parameterizable && (
<TextInput
keyboardType="numeric"
style={[style, defaultStyle.input]}
onChangeText={value => onChangeValue(item.name || '', value)}>
{pValue}
{item.pValue}
</TextInput>
)}
{!item.parameterizable && <Text style={style}>{pValue}</Text>}
{!item.parameterizable && <Text style={style}>{item.pValue}</Text>}
</View>
)
}

const ProofRequestAttributesCard: React.FC<ProofRequestAttributesCardParams> = ({ data, onChangeValue }) => {
const { ListItems, ColorPallet } = useTheme()
const { i18n } = useTranslation()
const { t, i18n } = useTranslation()
const { OCABundleResolver } = useConfiguration()

const style = StyleSheet.create({
Expand All @@ -111,16 +104,18 @@ const ProofRequestAttributesCard: React.FC<ProofRequestAttributesCardParams> = (
...ListItems.requestTemplateTitle,
fontWeight: 'bold',
fontSize: 18,
paddingVertical: 8,
marginRight: 8,
},
attributesList: {
paddingLeft: 14,
},
fieldContainer: { flexDirection: 'row', paddingVertical: 8 },
})

const [meta, setMeta] = useState<MetaOverlay | undefined>(undefined)
const [attributes, setAttributes] = useState<Field[] | undefined>(undefined)
const [attributeTypes, setAttributeTypes] = useState<Record<string, string> | undefined>(undefined)
const [attributeFormats, setAttributeFormats] = useState<Record<string, string | undefined> | undefined>(undefined)

useEffect(() => {
OCABundleResolver.resolve({ identifiers: { schemaId: data.schema }, language: i18n.language }).then(bundle => {
Expand All @@ -140,9 +135,6 @@ const ProofRequestAttributesCard: React.FC<ProofRequestAttributesCardParams> = (
})
setMeta(metaOverlay)
})
}, [data.schema])

useEffect(() => {
const attributes = buildFieldsFromAnonCredsProofRequestTemplate(data)
OCABundleResolver.presentationFields({
identifiers: { schemaId: data.schema },
Expand All @@ -153,6 +145,32 @@ const ProofRequestAttributesCard: React.FC<ProofRequestAttributesCardParams> = (
})
}, [data.schema])

useEffect(() => {
const credDefId = (data.requestedAttributes ?? data.requestedPredicates)
?.flatMap(reqItem => reqItem.restrictions?.map(restrictionItem => restrictionItem.cred_def_id))
.find(item => item !== undefined)
const params = {
identifiers: {
credentialDefinitionId: credDefId,
schemaId: data.schema,
},
language: i18n.language,
attributes,
}
OCABundleResolver.resolveAllBundles(params).then(bundle => {
setAttributeTypes(bundle?.bundle?.captureBase.attributes)
setAttributeFormats(
(bundle.bundle as any)?.bundle.attributes
.map((attr: any) => {
return { name: attr.name, format: attr.format }
})
.reduce((prev: { [key: string]: string }, curr: { name: string; format?: string }) => {
return { ...prev, [curr.name]: curr.format }
}, {}),
)
})
}, [])

return (
<View style={[style.credentialCard]}>
<Text style={style.schemaTitle}>{meta?.name}</Text>
Expand All @@ -161,11 +179,20 @@ const ProofRequestAttributesCard: React.FC<ProofRequestAttributesCardParams> = (
data={attributes}
keyExtractor={(record, index) => record.name || index.toString()}
renderItem={({ item }) => {
item.format = attributeFormats?.[item.name ?? '']
let parsedPredicate: Predicate | undefined = undefined
if (item instanceof Predicate) {
parsedPredicate = pTypeToText(item, t, attributeTypes) as Predicate
if (!parsedPredicate.parameterizable) {
parsedPredicate.pValue = formatIfDate(parsedPredicate.format, parsedPredicate.pValue)
}
}

return (
<View style={{ flexDirection: 'row' }}>
<View style={style.fieldContainer}>
<Text style={style.attributeTitle}>{`\u2022`}</Text>
{item instanceof Attribute && <AttributeItem style={style.attributeTitle} item={item as Attribute} />}
{item instanceof Predicate && (
{!item?.pType && <AttributeItem style={style.attributeTitle} item={item as Attribute} />}
{item?.pType && (
<PredicateItem
item={item as Predicate}
style={style.attributeTitle}
Expand Down Expand Up @@ -230,11 +257,11 @@ const ProofRequestDetails: React.FC<ProofRequestDetailsProps> = ({ route, naviga
>(undefined)

const template = useTemplate(templateId)
if (!template) {
throw new Error('Unable to find proof request template')
}

useEffect(() => {
if (!template) {
return
}
const attributes = template.payload.type === ProofRequestType.AnonCreds ? template.payload.data : []

OCABundleResolver.resolve({ identifiers: { templateId }, language: i18n.language }).then(bundle => {
Expand All @@ -255,7 +282,7 @@ const ProofRequestDetails: React.FC<ProofRequestDetailsProps> = ({ route, naviga
setMeta(metaOverlay)
setAttributes(attributes)
})
}, [templateId])
}, [templateId, template])

const onlyNumberRegex = /^\d+$/

Expand All @@ -278,6 +305,10 @@ const ProofRequestDetails: React.FC<ProofRequestDetailsProps> = ({ route, naviga
)

const useProofRequest = useCallback(async () => {
if (!template) {
return
}

if (invalidPredicate) {
setInvalidPredicate({ visible: true, predicate: invalidPredicate.predicate })
return
Expand All @@ -296,7 +327,7 @@ const ProofRequestDetails: React.FC<ProofRequestDetailsProps> = ({ route, naviga
// Else redirect to the screen with connectionless request
navigation.navigate(Screens.ProofRequesting, { templateId, predicateValues: customPredicateValues })
}
}, [templateId, connectionId, customPredicateValues, invalidPredicate])
}, [agent, template, templateId, connectionId, customPredicateValues, invalidPredicate])

const showTemplateUsageHistory = useCallback(async () => {
navigation.navigate(Screens.ProofRequestUsageHistory, { templateId })
Expand Down
15 changes: 9 additions & 6 deletions app/screens/ProofRequesting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,6 @@ const ProofRequesting: React.FC<ProofRequestingProps> = ({ route, navigation })
},
})

if (!template) {
throw new Error('Unable to find proof request template')
}

const createProofRequest = useCallback(async () => {
try {
setMessage(undefined)
Expand Down Expand Up @@ -165,10 +161,15 @@ const ProofRequesting: React.FC<ProofRequestingProps> = ({ route, navigation })
}, [isFocused])

useEffect(() => {
if (!template) {
return
}

const sendAsyncProof = async () => {
if (record && record.state === DidExchangeState.Completed) {
//send haptic feedback to verifier that connection is completed
Vibration.vibrate()
setGenerating(true)
// send proof logic
const result = await sendProofRequest(agent, template, record.id, predicateValues)
if (result?.proofRecord) {
Expand All @@ -181,13 +182,15 @@ const ProofRequesting: React.FC<ProofRequestingProps> = ({ route, navigation })
}
}
sendAsyncProof()
}, [record])
}, [record, template])

useEffect(() => {
if (proofRecord && (isPresentationReceived(proofRecord) || isPresentationFailed(proofRecord))) {
if (goalCode?.endsWith('verify.once')) {
deleteConnectionRecordById(agent, record?.id ?? '')
}

setGenerating(true)
navigation.navigate(Screens.ProofDetails, { recordId: proofRecord.id })
}
}, [proofRecord])
Expand All @@ -197,7 +200,7 @@ const ProofRequesting: React.FC<ProofRequestingProps> = ({ route, navigation })
<ScrollView>
<View style={styles.qrContainer}>
{generating && <LoadingIndicator />}
{message && <QRRenderer value={message} size={qrSize} />}
{!generating && message && <QRRenderer value={message} size={qrSize} />}
</View>
<View style={styles.headerContainer}>
<Text style={styles.primaryHeaderText}>{t('Verifier.ScanQR')}</Text>
Expand Down
Loading

0 comments on commit d6d4a70

Please sign in to comment.